Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.internal.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ This changelog documents internal development changes, refactors, tooling update
### Changed
- Refactored formatting strategy from TodoWrite to Task tools (TaskCreate, TaskUpdate, TaskList, TaskGet). Added `formatTaskParameter()` method to IMessageFormatter interface and updated AgentSessionManager to handle Task tools as thought activities. ([CYPACK-788](https://linear.app/ceedar/issue/CYPACK-788), [#837](https://github.com/ceedaragents/cyrus/pull/837))
- Redesigned TaskCreate formatting for parallel execution (concise `⏳ **subject**` checklist items), improved TaskUpdate/TaskGet to show subject names with status emojis, added ToolSearch formatting (`🔍 Loading`/`🔍 Searching tools`) rendered as non-ephemeral thought in AgentSessionManager, and added TaskOutput formatting (`📤 Waiting for`/`📤 Checking`). Updated both ClaudeMessageFormatter and GeminiMessageFormatter with matching logic. ([CYPACK-795](https://linear.app/ceedar/issue/CYPACK-795), [#846](https://github.com/ceedaragents/cyrus/pull/846))
- Deferred TaskUpdate/TaskGet activity posting from tool_use time to tool_result time to enrich with task subject. Added `taskSubjectsByToolUseId` and `taskSubjectsById` caches to AgentSessionManager for subject resolution from TaskCreate results and TaskGet result parsing. ([CYPACK-797](https://linear.app/ceedar/issue/CYPACK-797), [#847](https://github.com/ceedaragents/cyrus/pull/847))

### Added
- Subroutine result text is now stored in procedure history when advancing between subroutines. On error results (e.g. `error_max_turns` from single-turn subroutines), `AgentSessionManager` recovers by using the last completed subroutine's result via `ProcedureAnalyzer.getLastSubroutineResult()`, allowing the procedure to continue to completion instead of failing. Added `disallowAllTools` parameter to `buildAgentRunnerConfig` and `tools` config pass-through to `ClaudeRunner` for properly disabling built-in tools. ([CYPACK-792](https://linear.app/ceedar/issue/CYPACK-792), [#843](https://github.com/ceedaragents/cyrus/pull/843))
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ All notable changes to this project will be documented in this file.
### Changed
- **Updated Claude SDK dependencies** - Updated `@anthropic-ai/claude-agent-sdk` to v0.2.34 and `@anthropic-ai/sdk` to v0.73.0. See [claude-agent-sdk changelog](https://github.com/anthropics/claude-agent-sdk-typescript/blob/main/CHANGELOG.md#v0234) for details. ([CYPACK-788](https://linear.app/ceedar/issue/CYPACK-788), [#837](https://github.com/ceedaragents/cyrus/pull/837))
- **Improved task and tool activity display** - Task creation now shows as concise checklist items instead of verbose multi-line entries, task status updates display the task name with status emoji, and tool search/background task output activities are now cleanly formatted. ([CYPACK-795](https://linear.app/ceedar/issue/CYPACK-795), [#846](https://github.com/ceedaragents/cyrus/pull/846))
- **Task status updates now show task descriptions** - Task update and task detail activities now display the task subject alongside the task number (e.g., "Task #3 — Fix login bug") instead of just the number. ([CYPACK-797](https://linear.app/ceedar/issue/CYPACK-797), [#847](https://github.com/ceedaragents/cyrus/pull/847))

### Fixed
- **Procedures no longer fail when a subroutine exits with an error** - When a single-turn subroutine hits the max turns limit, Cyrus now recovers by using the last successful subroutine's result, allowing the workflow to continue to completion instead of stopping mid-procedure. ([CYPACK-792](https://linear.app/ceedar/issue/CYPACK-792), [#843](https://github.com/ceedaragents/cyrus/pull/843))
Expand Down
114 changes: 103 additions & 11 deletions packages/edge-worker/src/AgentSessionManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,8 @@ export class AgentSessionManager extends EventEmitter {
private activeTasksBySession: Map<string, string> = new Map(); // Maps session ID to active Task tool use ID
private toolCallsByToolUseId: Map<string, { name: string; input: any }> =
new Map(); // Track tool calls by their tool_use_id
private taskSubjectsByToolUseId: Map<string, string> = new Map(); // Cache TaskCreate subjects by toolUseId until result arrives with task ID
private taskSubjectsById: Map<string, string> = new Map(); // Cache task subjects by task ID (e.g., "1" → "Fix login bug")
private activeStatusActivitiesBySession: Map<string, string> = new Map(); // Maps session ID to active compacting status activity ID
private procedureAnalyzer?: ProcedureAnalyzer;
private sharedApplicationServer?: SharedApplicationServer;
Expand Down Expand Up @@ -1014,8 +1016,91 @@ export class AgentSessionManager extends EventEmitter {
this.toolCallsByToolUseId.delete(entry.metadata.toolUseId);
}

// Handle TaskCreate results: cache the task ID → subject mapping
const baseToolName = toolName.replace("↪ ", "");
if (baseToolName === "TaskCreate" && entry.metadata?.toolUseId) {
const cachedSubject = this.taskSubjectsByToolUseId.get(
entry.metadata.toolUseId,
);
if (cachedSubject) {
// Parse task ID from result like "Task #1 created successfully: ..."
const taskIdMatch = toolResult.content?.match(/Task #(\d+)/);
if (taskIdMatch?.[1]) {
this.taskSubjectsById.set(taskIdMatch[1], cachedSubject);
}
this.taskSubjectsByToolUseId.delete(
entry.metadata.toolUseId!,
);
}
}

// Handle TaskUpdate/TaskGet results: post enriched thought with subject
if (baseToolName === "TaskUpdate" || baseToolName === "TaskGet") {
const formatter = session.agentRunner?.getFormatter();
if (!formatter) {
console.warn(
`[AgentSessionManager] No formatter available for session ${linearAgentActivitySessionId}`,
);
return;
}

// Try to enrich toolInput with subject from cache or result
const enrichedInput = { ...toolInput };
if (!enrichedInput.subject) {
const taskId = enrichedInput.taskId || "";
// First try: look up subject from our cache
const cachedSubject = this.taskSubjectsById.get(taskId);
if (cachedSubject) {
enrichedInput.subject = cachedSubject;
} else if (baseToolName === "TaskGet" && toolResult.content) {
// Second try: parse subject from TaskGet result content
// Format: "ID: 123\nSubject: Fix bug\nStatus: ..."
const subjectMatch =
toolResult.content.match(/^Subject:\s*(.+)$/m);
if (subjectMatch?.[1]) {
enrichedInput.subject = subjectMatch[1].trim();
// Also cache it for future TaskUpdate calls
if (taskId) {
this.taskSubjectsById.set(
taskId,
enrichedInput.subject,
);
}
}
} else if (
baseToolName === "TaskUpdate" &&
toolResult.content
) {
// Try to parse subject from TaskUpdate result content
// Format: "Updated task #3 subject" or may contain task details
const subjectMatch =
toolResult.content.match(/^Subject:\s*(.+)$/m);
if (subjectMatch?.[1]) {
enrichedInput.subject = subjectMatch[1].trim();
if (taskId) {
this.taskSubjectsById.set(
taskId,
enrichedInput.subject,
);
}
}
}
}

const formattedTask = formatter.formatTaskParameter(
baseToolName,
enrichedInput,
);
content = {
type: "thought",
body: formattedTask,
};
ephemeral = false;
break;
}

// Skip creating activity for TodoWrite/write_todos results since they already created a non-ephemeral thought
// Skip Task tool results (TaskCreate, TaskUpdate, etc.) since they already created a non-ephemeral thought
// Skip TaskCreate/TaskList results since they already created a non-ephemeral thought
// Skip ToolSearch results since they already created a non-ephemeral thought
// Skip AskUserQuestion results since it's custom handled via Linear's select signal elicitation
if (
Expand All @@ -1024,12 +1109,8 @@ export class AgentSessionManager extends EventEmitter {
toolName === "write_todos" ||
toolName === "TaskCreate" ||
toolName === "↪ TaskCreate" ||
toolName === "TaskUpdate" ||
toolName === "↪ TaskUpdate" ||
toolName === "TaskList" ||
toolName === "↪ TaskList" ||
toolName === "TaskGet" ||
toolName === "↪ TaskGet" ||
toolName === "ToolSearch" ||
toolName === "↪ ToolSearch" ||
toolName === "AskUserQuestion" ||
Expand Down Expand Up @@ -1129,12 +1210,7 @@ export class AgentSessionManager extends EventEmitter {
};
// TodoWrite/write_todos is not ephemeral
ephemeral = false;
} else if (
toolName === "TaskCreate" ||
toolName === "TaskUpdate" ||
toolName === "TaskList" ||
toolName === "TaskGet"
) {
} else if (toolName === "TaskCreate" || toolName === "TaskList") {
// Get formatter from runner
const formatter = session.agentRunner?.getFormatter();
if (!formatter) {
Expand All @@ -1156,6 +1232,22 @@ export class AgentSessionManager extends EventEmitter {
};
// Task tools are not ephemeral
ephemeral = false;

// Cache TaskCreate subject by toolUseId so we can map it to task ID when result arrives
if (
toolName === "TaskCreate" &&
toolInput?.subject &&
entry.metadata.toolUseId
) {
this.taskSubjectsByToolUseId.set(
entry.metadata.toolUseId,
toolInput.subject,
);
}
} else if (toolName === "TaskUpdate" || toolName === "TaskGet") {
// Skip posting at tool_use time — defer to tool_result time
// so we can enrich with subject from result or cache
return;
} else if (toolName === "ToolSearch") {
// Get formatter from runner
const formatter = session.agentRunner?.getFormatter();
Expand Down
Loading