@nghyane/arcane 0.1.13 → 0.1.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +28 -0
- package/package.json +21 -70
- package/scripts/format-prompts.ts +1 -3
- package/src/cli/args.ts +2 -7
- package/src/cli/config-cli.ts +1 -1
- package/src/cli/plugin-cli.ts +1 -1
- package/src/cli/setup-cli.ts +1 -1
- package/src/cli/update-cli.ts +1 -1
- package/src/cli/web-search-cli.ts +1 -1
- package/src/cli.ts +0 -1
- package/src/commands/config.ts +1 -1
- package/src/commands/grep.ts +1 -1
- package/src/commands/jupyter.ts +1 -1
- package/src/commands/plugin.ts +1 -1
- package/src/commands/setup.ts +1 -1
- package/src/commands/shell.ts +1 -1
- package/src/commands/ssh.ts +1 -1
- package/src/commands/stats.ts +1 -1
- package/src/commands/update.ts +1 -1
- package/src/config/model-registry.ts +3 -4
- package/src/config/model-resolver.ts +36 -9
- package/src/config/prompt-templates.ts +1 -9
- package/src/config/settings-schema.ts +32 -88
- package/src/config/settings.ts +3 -4
- package/src/debug/index.ts +1 -1
- package/src/debug/log-formatting.ts +1 -1
- package/src/debug/log-viewer.ts +2 -2
- package/src/discovery/helpers.ts +13 -3
- package/src/exa/index.ts +1 -35
- package/src/exa/render.ts +30 -190
- package/src/export/html/index.ts +1 -1
- package/src/extensibility/custom-tools/loader.ts +1 -1
- package/src/extensibility/custom-tools/types.ts +5 -1
- package/src/extensibility/custom-tools/wrapper.ts +1 -1
- package/src/extensibility/extensions/runner.ts +1 -1
- package/src/extensibility/extensions/types.ts +1 -1
- package/src/extensibility/extensions/wrapper.ts +7 -15
- package/src/extensibility/hooks/runner.ts +1 -1
- package/src/extensibility/hooks/types.ts +1 -1
- package/src/extensibility/plugins/doctor.ts +1 -1
- package/src/index.ts +13 -13
- package/src/lsp/index.ts +77 -24
- package/src/lsp/render.ts +34 -583
- package/src/lsp/types.ts +3 -3
- package/src/lsp/utils.ts +1 -1
- package/src/main.ts +1 -1
- package/src/mcp/tool-bridge.ts +1 -24
- package/src/modes/components/assistant-message.ts +7 -7
- package/src/modes/components/bash-execution.ts +50 -112
- package/src/modes/components/bordered-loader.ts +1 -1
- package/src/modes/components/branch-summary-message.ts +16 -10
- package/src/modes/components/compaction-summary-message.ts +20 -12
- package/src/modes/components/context-group.ts +106 -0
- package/src/modes/components/custom-message.ts +4 -5
- package/src/modes/components/diff.ts +2 -2
- package/src/modes/components/dynamic-border.ts +1 -1
- package/src/modes/components/extensions/extension-dashboard.ts +1 -1
- package/src/modes/components/extensions/extension-list.ts +1 -1
- package/src/modes/components/extensions/inspector-panel.ts +1 -1
- package/src/modes/components/footer.ts +2 -2
- package/src/modes/components/history-search.ts +1 -1
- package/src/modes/components/hook-editor.ts +1 -1
- package/src/modes/components/hook-input.ts +1 -1
- package/src/modes/components/hook-message.ts +4 -5
- package/src/modes/components/hook-selector.ts +1 -1
- package/src/modes/components/index.ts +0 -2
- package/src/modes/components/keybinding-hints.ts +1 -1
- package/src/modes/components/login-dialog.ts +1 -1
- package/src/modes/components/mcp-add-wizard.ts +1 -1
- package/src/modes/components/model-selector.ts +1 -1
- package/src/modes/components/oauth-selector.ts +1 -1
- package/src/modes/components/plugin-settings.ts +1 -1
- package/src/modes/components/python-execution.ts +51 -91
- package/src/modes/components/queue-mode-selector.ts +1 -1
- package/src/modes/components/session-selector.ts +1 -1
- package/src/modes/components/settings-defs.ts +5 -10
- package/src/modes/components/settings-selector.ts +1 -1
- package/src/modes/components/show-images-selector.ts +1 -1
- package/src/modes/components/skill-message.ts +4 -4
- package/src/modes/components/status-line/segments.ts +2 -2
- package/src/modes/components/status-line/separators.ts +1 -1
- package/src/modes/components/status-line-segment-editor.ts +1 -1
- package/src/modes/components/status-line.ts +1 -1
- package/src/modes/components/theme-selector.ts +1 -1
- package/src/modes/components/thinking-selector.ts +1 -1
- package/src/modes/components/todo-display.ts +2 -4
- package/src/modes/components/todo-reminder.ts +4 -4
- package/src/modes/components/tool-execution.ts +118 -440
- package/src/modes/components/tool-image-display.ts +107 -0
- package/src/modes/components/tree-selector.ts +2 -2
- package/src/modes/components/ttsr-notification.ts +4 -17
- package/src/modes/components/user-message-selector.ts +1 -1
- package/src/modes/components/user-message.ts +9 -10
- package/src/modes/components/welcome.ts +1 -1
- package/src/modes/controllers/command-controller.ts +1 -1
- package/src/modes/controllers/event-controller.ts +58 -187
- package/src/modes/controllers/extension-ui-controller.ts +1 -1
- package/src/modes/controllers/input-controller.ts +3 -1
- package/src/modes/controllers/mcp-command-controller.ts +1 -1
- package/src/modes/controllers/selector-controller.ts +3 -26
- package/src/modes/controllers/ssh-command-controller.ts +1 -1
- package/src/modes/interactive-mode.ts +3 -7
- package/src/modes/print-mode.ts +5 -5
- package/src/modes/rpc/rpc-mode.ts +1 -1
- package/src/modes/types.ts +1 -2
- package/src/modes/utils/ui-helpers.ts +34 -32
- package/src/patch/edit-tool.ts +742 -0
- package/src/patch/index.ts +32 -898
- package/src/patch/schemas.ts +208 -0
- package/src/patch/shared.ts +83 -151
- package/src/prompts/agents/explore.md +22 -37
- package/src/prompts/agents/init.md +1 -1
- package/src/prompts/agents/librarian.md +29 -20
- package/src/prompts/agents/oracle.md +9 -2
- package/src/prompts/agents/reviewer.md +14 -48
- package/src/prompts/agents/task.md +16 -8
- package/src/prompts/compaction/branch-summary.md +4 -1
- package/src/prompts/compaction/compaction-summary.md +4 -1
- package/src/prompts/system/subagent-system-prompt.md +1 -1
- package/src/prompts/system/system-prompt.md +162 -178
- package/src/prompts/system/verification-reminder.md +6 -0
- package/src/sdk.ts +0 -9
- package/src/session/agent-session.ts +244 -1459
- package/src/session/model-controller.ts +406 -0
- package/src/session/retry-utils.ts +71 -0
- package/src/session/session-manager.ts +22 -186
- package/src/session/session-types.ts +312 -0
- package/src/session/stats.ts +387 -0
- package/src/session/streaming-edit.ts +258 -0
- package/src/session/ttsr.ts +213 -0
- package/src/slash-commands/builtin-registry.ts +0 -8
- package/src/stt/recorder.ts +2 -2
- package/src/system-prompt.ts +1 -14
- package/src/task/agents.ts +7 -33
- package/src/task/executor.ts +50 -438
- package/src/task/index.ts +104 -71
- package/src/task/progress-tracker.ts +390 -0
- package/src/task/render.ts +371 -187
- package/src/task/subprocess-tool-registry.ts +1 -1
- package/src/task/types.ts +14 -47
- package/src/tools/ask.ts +31 -42
- package/src/tools/bash-interactive.ts +2 -2
- package/src/tools/bash-interceptor.ts +2 -2
- package/src/tools/bash-normalize.ts +1 -1
- package/src/tools/bash-skill-urls.ts +2 -2
- package/src/tools/bash.ts +87 -136
- package/src/tools/browser.ts +54 -84
- package/src/tools/create-tools.ts +186 -0
- package/src/tools/default-renderer.ts +104 -0
- package/src/tools/explore.ts +11 -10
- package/src/tools/fetch.ts +24 -114
- package/src/tools/find.ts +48 -132
- package/src/tools/gemini-image.ts +5 -15
- package/src/tools/github.ts +450 -0
- package/src/tools/grep.ts +43 -179
- package/src/tools/index.ts +35 -198
- package/src/tools/json-tree.ts +3 -3
- package/src/tools/librarian.ts +18 -18
- package/src/tools/list-limit.ts +2 -2
- package/src/tools/notebook.ts +35 -87
- package/src/tools/oracle.ts +25 -25
- package/src/tools/output-meta.ts +89 -4
- package/src/tools/output-utils.ts +2 -2
- package/src/tools/python.ts +86 -637
- package/src/tools/read.ts +36 -119
- package/src/tools/reviewer-tool.ts +19 -21
- package/src/tools/search-code.ts +128 -0
- package/src/tools/ssh.ts +67 -126
- package/src/tools/subagent-tool.ts +197 -123
- package/src/tools/todo-write.ts +15 -31
- package/src/tools/tool-errors.ts +0 -30
- package/src/tools/undo-edit.ts +30 -67
- package/src/tools/write.ts +78 -127
- package/src/tui/code-cell.ts +4 -4
- package/src/tui/file-list.ts +2 -2
- package/src/tui/output-block.ts +1 -1
- package/src/tui/status-line.ts +1 -1
- package/src/tui/tree-list.ts +2 -2
- package/src/tui/types.ts +1 -1
- package/src/tui/utils.ts +1 -1
- package/src/{tools → ui}/render-utils.ts +87 -126
- package/src/utils/external-editor.ts +4 -4
- package/src/utils/file-mentions.ts +1 -1
- package/src/utils/index.ts +30 -0
- package/src/utils/tools-manager.ts +9 -19
- package/src/web/github-client.ts +290 -0
- package/src/web/scrapers/github.ts +11 -62
- package/src/web/search/auth.ts +1 -3
- package/src/web/search/index.ts +82 -46
- package/src/web/search/provider.ts +11 -16
- package/src/web/search/providers/grep.ts +160 -0
- package/src/web/search/render.ts +48 -235
- package/src/web/search/types.ts +1 -1
- package/src/commands/commit.ts +0 -36
- package/src/commit/agentic/agent.ts +0 -311
- package/src/commit/agentic/fallback.ts +0 -96
- package/src/commit/agentic/index.ts +0 -359
- package/src/commit/agentic/prompts/analyze-file.md +0 -22
- package/src/commit/agentic/prompts/session-user.md +0 -25
- package/src/commit/agentic/prompts/split-confirm.md +0 -1
- package/src/commit/agentic/prompts/system.md +0 -38
- package/src/commit/agentic/state.ts +0 -69
- package/src/commit/agentic/tools/analyze-file.ts +0 -118
- package/src/commit/agentic/tools/git-file-diff.ts +0 -194
- package/src/commit/agentic/tools/git-hunk.ts +0 -50
- package/src/commit/agentic/tools/git-overview.ts +0 -84
- package/src/commit/agentic/tools/index.ts +0 -56
- package/src/commit/agentic/tools/propose-changelog.ts +0 -128
- package/src/commit/agentic/tools/propose-commit.ts +0 -154
- package/src/commit/agentic/tools/recent-commits.ts +0 -81
- package/src/commit/agentic/tools/split-commit.ts +0 -280
- package/src/commit/agentic/topo-sort.ts +0 -44
- package/src/commit/agentic/trivial.ts +0 -51
- package/src/commit/agentic/validation.ts +0 -200
- package/src/commit/analysis/conventional.ts +0 -165
- package/src/commit/analysis/index.ts +0 -4
- package/src/commit/analysis/scope.ts +0 -242
- package/src/commit/analysis/summary.ts +0 -112
- package/src/commit/analysis/validation.ts +0 -66
- package/src/commit/changelog/detect.ts +0 -37
- package/src/commit/changelog/generate.ts +0 -110
- package/src/commit/changelog/index.ts +0 -234
- package/src/commit/changelog/parse.ts +0 -44
- package/src/commit/cli.ts +0 -93
- package/src/commit/git/diff.ts +0 -148
- package/src/commit/git/errors.ts +0 -9
- package/src/commit/git/index.ts +0 -211
- package/src/commit/git/operations.ts +0 -54
- package/src/commit/index.ts +0 -5
- package/src/commit/map-reduce/index.ts +0 -64
- package/src/commit/map-reduce/map-phase.ts +0 -178
- package/src/commit/map-reduce/reduce-phase.ts +0 -145
- package/src/commit/map-reduce/utils.ts +0 -9
- package/src/commit/message.ts +0 -11
- package/src/commit/model-selection.ts +0 -69
- package/src/commit/pipeline.ts +0 -243
- package/src/commit/prompts/analysis-system.md +0 -148
- package/src/commit/prompts/analysis-user.md +0 -38
- package/src/commit/prompts/changelog-system.md +0 -50
- package/src/commit/prompts/changelog-user.md +0 -18
- package/src/commit/prompts/file-observer-system.md +0 -24
- package/src/commit/prompts/file-observer-user.md +0 -8
- package/src/commit/prompts/reduce-system.md +0 -50
- package/src/commit/prompts/reduce-user.md +0 -17
- package/src/commit/prompts/summary-retry.md +0 -3
- package/src/commit/prompts/summary-system.md +0 -38
- package/src/commit/prompts/summary-user.md +0 -13
- package/src/commit/prompts/types-description.md +0 -2
- package/src/commit/types.ts +0 -109
- package/src/commit/utils/exclusions.ts +0 -42
- package/src/mcp/render.ts +0 -123
- package/src/modes/components/agent-dashboard.ts +0 -1130
- package/src/modes/components/codemode-group.ts +0 -369
- package/src/modes/components/read-tool-group.ts +0 -119
- package/src/modes/components/visual-truncate.ts +0 -63
- package/src/prompts/system/subagent-user-prompt.md +0 -8
- package/src/prompts/tools/ask.md +0 -44
- package/src/prompts/tools/bash.md +0 -24
- package/src/prompts/tools/browser.md +0 -33
- package/src/prompts/tools/calculator.md +0 -12
- package/src/prompts/tools/explore.md +0 -29
- package/src/prompts/tools/fetch.md +0 -16
- package/src/prompts/tools/find.md +0 -18
- package/src/prompts/tools/gemini-image.md +0 -23
- package/src/prompts/tools/grep.md +0 -28
- package/src/prompts/tools/hashline.md +0 -232
- package/src/prompts/tools/librarian.md +0 -24
- package/src/prompts/tools/lsp.md +0 -28
- package/src/prompts/tools/oracle.md +0 -26
- package/src/prompts/tools/patch.md +0 -74
- package/src/prompts/tools/python.md +0 -66
- package/src/prompts/tools/read.md +0 -36
- package/src/prompts/tools/replace.md +0 -38
- package/src/prompts/tools/reviewer.md +0 -41
- package/src/prompts/tools/ssh.md +0 -51
- package/src/prompts/tools/task-summary.md +0 -28
- package/src/prompts/tools/task.md +0 -146
- package/src/prompts/tools/todo-write.md +0 -65
- package/src/prompts/tools/undo-edit.md +0 -7
- package/src/prompts/tools/web-search.md +0 -19
- package/src/prompts/tools/write.md +0 -18
- package/src/task/batch.ts +0 -102
- package/src/task/discovery.ts +0 -126
- package/src/task/parallel.ts +0 -84
- package/src/task/template.ts +0 -32
- package/src/tools/calculator.ts +0 -537
- package/src/tools/jtd-to-typescript.ts +0 -198
- package/src/tools/renderers.ts +0 -60
- package/src/tools/tool-result.ts +0 -86
- /package/src/{modes/theme → theme}/dark.json +0 -0
- /package/src/{modes/theme → theme}/defaults/dark-catppuccin.json +0 -0
- /package/src/{modes/theme → theme}/defaults/dark-dracula.json +0 -0
- /package/src/{modes/theme → theme}/defaults/dark-gruvbox.json +0 -0
- /package/src/{modes/theme → theme}/defaults/dark-solarized.json +0 -0
- /package/src/{modes/theme → theme}/defaults/dark-tokyo-night.json +0 -0
- /package/src/{modes/theme → theme}/defaults/index.ts +0 -0
- /package/src/{modes/theme → theme}/defaults/light-catppuccin.json +0 -0
- /package/src/{modes/theme → theme}/defaults/light-github.json +0 -0
- /package/src/{modes/theme → theme}/defaults/light-solarized.json +0 -0
- /package/src/{modes/theme → theme}/light.json +0 -0
- /package/src/{modes/theme → theme}/mermaid-cache.ts +0 -0
- /package/src/{modes/theme → theme}/theme-schema.json +0 -0
- /package/src/{modes/theme → theme}/theme.ts +0 -0
package/src/task/index.ts
CHANGED
|
@@ -10,22 +10,51 @@ import * as path from "node:path";
|
|
|
10
10
|
import type { AgentTool, AgentToolResult, AgentToolUpdateCallback } from "@nghyane/arcane-agent";
|
|
11
11
|
import { Snowflake } from "@nghyane/arcane-utils";
|
|
12
12
|
import type { ToolSession } from "..";
|
|
13
|
-
import type { Theme } from "../
|
|
13
|
+
import type { Theme } from "../theme/theme";
|
|
14
|
+
import { EventBus } from "../utils/event-bus";
|
|
14
15
|
import { getBundledAgent } from "./agents";
|
|
15
16
|
import { runAgent } from "./executor";
|
|
16
17
|
import { AgentOutputManager } from "./output-manager";
|
|
18
|
+
import { extractAgentOutput, ProgressTracker } from "./progress-tracker";
|
|
17
19
|
import { renderCall, renderResult } from "./render";
|
|
18
|
-
import {
|
|
19
|
-
|
|
20
|
+
import {
|
|
21
|
+
type AgentProgress,
|
|
22
|
+
TASK_SUBAGENT_EVENT_CHANNEL,
|
|
23
|
+
type TaskParams,
|
|
24
|
+
type TaskSchema,
|
|
25
|
+
type TaskToolDetails,
|
|
26
|
+
taskSchema,
|
|
27
|
+
} from "./types";
|
|
20
28
|
|
|
21
29
|
// Re-export types and utilities
|
|
22
30
|
export { loadBundledAgents as BUNDLED_AGENTS } from "./agents";
|
|
23
|
-
export { type BatchOptions, type BatchResult, type BatchTask, runTaskBatch } from "./batch";
|
|
24
|
-
export { discoverAgents, getAgent } from "./discovery";
|
|
25
31
|
export { AgentOutputManager } from "./output-manager";
|
|
26
|
-
export type {
|
|
32
|
+
export type {
|
|
33
|
+
AgentDefinition,
|
|
34
|
+
AgentProgress,
|
|
35
|
+
SingleResult,
|
|
36
|
+
TaskParams,
|
|
37
|
+
TaskToolDetails,
|
|
38
|
+
} from "./types";
|
|
27
39
|
export { taskSchema } from "./types";
|
|
28
40
|
|
|
41
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
42
|
+
// Helpers
|
|
43
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
44
|
+
|
|
45
|
+
/** Derive a CamelCase ID from a short description for artifact naming. */
|
|
46
|
+
function deriveId(description: string): string {
|
|
47
|
+
return (
|
|
48
|
+
description
|
|
49
|
+
.replace(/[^a-zA-Z0-9\s]/g, "")
|
|
50
|
+
.split(/\s+/)
|
|
51
|
+
.filter(Boolean)
|
|
52
|
+
.map(w => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase())
|
|
53
|
+
.join("")
|
|
54
|
+
.slice(0, 32) || "Task"
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
29
58
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
30
59
|
// Tool Class
|
|
31
60
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
@@ -43,8 +72,12 @@ export class TaskTool implements AgentTool<TaskSchema, TaskToolDetails, Theme> {
|
|
|
43
72
|
readonly renderCall = renderCall;
|
|
44
73
|
readonly renderResult = renderResult;
|
|
45
74
|
|
|
46
|
-
readonly description =
|
|
47
|
-
"
|
|
75
|
+
readonly description = [
|
|
76
|
+
"Perform a task (a sub-task of the user's overall task) using a sub-agent that has access to: grep, find, read, bash, edit, write, explore, web_search, fetch, python, undo_edit, todo_write.",
|
|
77
|
+
"When to use: Complex multi-step tasks; operations producing lots of output tokens not needed after; changes across many layers after planning; when user asks to launch an 'agent'.",
|
|
78
|
+
"When NOT to use: Single logical task; reading a single file; performing text search; editing a single file; not sure what changes to make.",
|
|
79
|
+
"How to use: Run multiple sub-agents concurrently if tasks are independent; include all necessary context and a detailed plan; tell sub-agent how to verify work; show user concise summary of result.",
|
|
80
|
+
].join(" ");
|
|
48
81
|
|
|
49
82
|
private constructor(private readonly session: ToolSession) {
|
|
50
83
|
this.parameters = taskSchema;
|
|
@@ -72,7 +105,10 @@ export class TaskTool implements AgentTool<TaskSchema, TaskToolDetails, Theme> {
|
|
|
72
105
|
};
|
|
73
106
|
}
|
|
74
107
|
|
|
75
|
-
const
|
|
108
|
+
const isLowComplexity = params.complexity === "low";
|
|
109
|
+
const modelOverride = isLowComplexity
|
|
110
|
+
? "arcane/fast"
|
|
111
|
+
: (this.session.getActiveModelString?.() ?? this.session.getModelString?.());
|
|
76
112
|
const sessionFile = this.session.getSessionFile();
|
|
77
113
|
const artifactsDir = sessionFile ? sessionFile.slice(0, -6) : null;
|
|
78
114
|
const tempArtifactsDir = artifactsDir ? null : path.join(os.tmpdir(), `arcane-task-${Snowflake.next()}`);
|
|
@@ -82,7 +118,7 @@ export class TaskTool implements AgentTool<TaskSchema, TaskToolDetails, Theme> {
|
|
|
82
118
|
const emitProgress = () => {
|
|
83
119
|
const progress = Array.from(progressMap.values());
|
|
84
120
|
onUpdate?.({
|
|
85
|
-
content: [{ type: "text", text: `Running task
|
|
121
|
+
content: [{ type: "text", text: `Running task...` }],
|
|
86
122
|
details: {
|
|
87
123
|
results: [],
|
|
88
124
|
totalDurationMs: Date.now() - startTime,
|
|
@@ -102,99 +138,96 @@ export class TaskTool implements AgentTool<TaskSchema, TaskToolDetails, Theme> {
|
|
|
102
138
|
|
|
103
139
|
const outputManager =
|
|
104
140
|
this.session.agentOutputManager ?? new AgentOutputManager(this.session.getArtifactsDir ?? (() => null));
|
|
105
|
-
const
|
|
141
|
+
const derivedId = deriveId(params.description);
|
|
142
|
+
const [uniqueId] = await outputManager.allocateBatch([derivedId]);
|
|
106
143
|
|
|
107
|
-
//
|
|
108
|
-
const
|
|
109
|
-
id: uniqueId,
|
|
110
|
-
description: params.description,
|
|
111
|
-
assignment: params.assignment,
|
|
112
|
-
skills: params.skills,
|
|
113
|
-
};
|
|
114
|
-
const rendered = renderTemplate(params.context ?? "", taskItem);
|
|
115
|
-
|
|
116
|
-
// Resolve skills
|
|
117
|
-
const contextFiles = this.session.contextFiles;
|
|
118
|
-
const availableSkills = this.session.skills;
|
|
119
|
-
const promptTemplates = this.session.promptTemplates;
|
|
120
|
-
let resolvedSkills = availableSkills;
|
|
121
|
-
let preloadedSkills: typeof availableSkills | undefined;
|
|
122
|
-
|
|
123
|
-
if (params.skills !== undefined && availableSkills) {
|
|
124
|
-
const skillLookup = new Map(availableSkills.map(s => [s.name, s]));
|
|
125
|
-
const resolved: typeof availableSkills = [];
|
|
126
|
-
const missing: string[] = [];
|
|
127
|
-
for (const name of params.skills) {
|
|
128
|
-
const trimmed = name.trim();
|
|
129
|
-
if (!trimmed) continue;
|
|
130
|
-
const skill = skillLookup.get(trimmed);
|
|
131
|
-
if (skill) resolved.push(skill);
|
|
132
|
-
else missing.push(trimmed);
|
|
133
|
-
}
|
|
134
|
-
if (missing.length > 0) {
|
|
135
|
-
const available = availableSkills.map(s => s.name).join(", ") || "none";
|
|
136
|
-
return {
|
|
137
|
-
content: [{ type: "text", text: `Unknown skills: ${missing.join(", ")}. Available: ${available}` }],
|
|
138
|
-
details: { results: [], totalDurationMs: Date.now() - startTime },
|
|
139
|
-
};
|
|
140
|
-
}
|
|
141
|
-
resolvedSkills = resolved;
|
|
142
|
-
preloadedSkills = resolved;
|
|
143
|
-
}
|
|
144
|
+
// Set up EventBus — all observation flows through here
|
|
145
|
+
const eventBus = new EventBus();
|
|
144
146
|
|
|
145
|
-
|
|
147
|
+
const tracker = new ProgressTracker({
|
|
146
148
|
index: 0,
|
|
147
149
|
id: uniqueId,
|
|
148
150
|
agent: agentName,
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
151
|
+
task: params.prompt,
|
|
152
|
+
description: params.description,
|
|
153
|
+
startTime,
|
|
154
|
+
onProgress: progress => {
|
|
155
|
+
progressMap.set(0, { ...structuredClone(progress) });
|
|
156
|
+
emitProgress();
|
|
157
|
+
},
|
|
158
|
+
onTerminateRequest: () => eventBus.emit("executor:terminate", {}),
|
|
159
|
+
});
|
|
160
|
+
tracker.subscribe(eventBus);
|
|
161
|
+
|
|
162
|
+
// Capture output from agent_end
|
|
163
|
+
let agentOutput = "";
|
|
164
|
+
const outputListener = eventBus.on(TASK_SUBAGENT_EVENT_CHANNEL, (data: unknown) => {
|
|
165
|
+
const payload = data as { event?: { type: string; messages?: unknown[] } };
|
|
166
|
+
if (payload.event?.type === "agent_end") {
|
|
167
|
+
agentOutput = extractAgentOutput(payload.event as Parameters<typeof extractAgentOutput>[0]);
|
|
168
|
+
}
|
|
157
169
|
});
|
|
158
|
-
emitProgress();
|
|
159
170
|
|
|
160
171
|
const result = await runAgent({
|
|
161
172
|
cwd: this.session.cwd,
|
|
162
173
|
agent: effectiveAgent,
|
|
163
|
-
task:
|
|
164
|
-
description:
|
|
174
|
+
task: params.prompt,
|
|
175
|
+
description: params.description,
|
|
165
176
|
index: 0,
|
|
166
177
|
id: uniqueId,
|
|
167
178
|
isSubagent: true,
|
|
168
179
|
modelOverride,
|
|
180
|
+
thinkingLevel: isLowComplexity ? "minimal" : undefined,
|
|
169
181
|
sessionFile,
|
|
170
182
|
persistArtifacts: !!artifactsDir,
|
|
171
183
|
artifactsDir: effectiveArtifactsDir,
|
|
172
184
|
contextFile: contextFilePath,
|
|
173
185
|
enableLsp: false,
|
|
174
186
|
signal,
|
|
175
|
-
eventBus
|
|
176
|
-
onProgress: progress => {
|
|
177
|
-
progressMap.set(0, { ...structuredClone(progress) });
|
|
178
|
-
emitProgress();
|
|
179
|
-
},
|
|
187
|
+
eventBus,
|
|
180
188
|
authStorage: this.session.subagentContext?.authStorage,
|
|
181
189
|
modelRegistry: this.session.subagentContext?.modelRegistry,
|
|
182
190
|
settings: this.session.settings,
|
|
183
191
|
mcpManager: this.session.subagentContext?.mcpManager,
|
|
184
|
-
contextFiles,
|
|
185
|
-
skills:
|
|
186
|
-
|
|
187
|
-
promptTemplates,
|
|
192
|
+
contextFiles: this.session.contextFiles,
|
|
193
|
+
skills: this.session.skills,
|
|
194
|
+
promptTemplates: this.session.promptTemplates,
|
|
188
195
|
});
|
|
189
196
|
|
|
197
|
+
// Finalize tracker
|
|
198
|
+
const wasAborted = result.aborted ?? false;
|
|
199
|
+
tracker.finalize(wasAborted ? "aborted" : result.exitCode === 0 ? "completed" : "failed");
|
|
200
|
+
tracker.dispose();
|
|
201
|
+
outputListener();
|
|
202
|
+
|
|
203
|
+
// Enrich result with tracker data
|
|
204
|
+
result.tokens = tracker.progress.tokens;
|
|
205
|
+
result.lastIntent = tracker.progress.lastIntent;
|
|
206
|
+
result.usage = tracker.usage;
|
|
207
|
+
result.toolHistory = tracker.progress.toolHistory.map(t => ({
|
|
208
|
+
tool: t.tool,
|
|
209
|
+
args: t.args,
|
|
210
|
+
status: t.status === "running" ? ("error" as const) : t.status,
|
|
211
|
+
}));
|
|
212
|
+
|
|
213
|
+
// Write output artifact for agent:// URL integration
|
|
214
|
+
if (artifactsDir && agentOutput) {
|
|
215
|
+
const outputFile = path.join(effectiveArtifactsDir, `${uniqueId}.md`);
|
|
216
|
+
try {
|
|
217
|
+
await Bun.write(outputFile, agentOutput);
|
|
218
|
+
} catch {
|
|
219
|
+
// Non-fatal
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
190
223
|
if (tempArtifactsDir) {
|
|
191
224
|
await fs.rm(tempArtifactsDir, { recursive: true, force: true });
|
|
192
225
|
}
|
|
193
226
|
|
|
194
227
|
const totalDuration = Date.now() - startTime;
|
|
195
|
-
const output =
|
|
228
|
+
const output = agentOutput.trim() || result.stderr.trim() || "(no output)";
|
|
196
229
|
|
|
197
|
-
// Return structured result as JSON for
|
|
230
|
+
// Return structured result as JSON for code tool composability
|
|
198
231
|
const structured = {
|
|
199
232
|
exitCode: result.exitCode,
|
|
200
233
|
output,
|
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Progress observer for subagent execution.
|
|
3
|
+
*
|
|
4
|
+
* Subscribes to raw AgentEvents via EventBus and maintains:
|
|
5
|
+
* - TUI progress state (tool history, recent output, status)
|
|
6
|
+
* - Usage accumulation (tokens, cost)
|
|
7
|
+
*
|
|
8
|
+
* Pure observer — does not control execution lifecycle.
|
|
9
|
+
*/
|
|
10
|
+
import type { AgentEvent } from "@nghyane/arcane-agent";
|
|
11
|
+
import type { Usage } from "@nghyane/arcane-ai";
|
|
12
|
+
import type { EventBus } from "../utils/event-bus";
|
|
13
|
+
import { subprocessToolRegistry } from "./subprocess-tool-registry";
|
|
14
|
+
import { type AgentProgress, TASK_SUBAGENT_EVENT_CHANNEL } from "./types";
|
|
15
|
+
|
|
16
|
+
const RECENT_OUTPUT_TAIL_BYTES = 8 * 1024;
|
|
17
|
+
|
|
18
|
+
function extractToolArgsPreview(args: Record<string, unknown>): string {
|
|
19
|
+
const previewKeys = ["command", "file_path", "path", "pattern", "query", "url", "task", "prompt"];
|
|
20
|
+
for (const key of previewKeys) {
|
|
21
|
+
if (args[key] && typeof args[key] === "string") {
|
|
22
|
+
const value = args[key] as string;
|
|
23
|
+
return value.length > 60 ? `${value.slice(0, 59)}…` : value;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return "";
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function getNumberField(record: Record<string, unknown>, key: string): number | undefined {
|
|
30
|
+
if (!Object.hasOwn(record, key)) return undefined;
|
|
31
|
+
const value = record[key];
|
|
32
|
+
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function firstNumberField(record: Record<string, unknown>, keys: string[]): number | undefined {
|
|
36
|
+
for (const key of keys) {
|
|
37
|
+
const value = getNumberField(record, key);
|
|
38
|
+
if (value !== undefined) return value;
|
|
39
|
+
}
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function getUsageTokens(usage: unknown): number {
|
|
44
|
+
if (!usage || typeof usage !== "object") return 0;
|
|
45
|
+
const record = usage as Record<string, unknown>;
|
|
46
|
+
const totalTokens = firstNumberField(record, ["totalTokens", "total_tokens"]);
|
|
47
|
+
if (totalTokens !== undefined && totalTokens > 0) return totalTokens;
|
|
48
|
+
const input = firstNumberField(record, ["input", "input_tokens", "inputTokens"]) ?? 0;
|
|
49
|
+
const output = firstNumberField(record, ["output", "output_tokens", "outputTokens"]) ?? 0;
|
|
50
|
+
const cacheRead = firstNumberField(record, ["cacheRead", "cache_read", "cacheReadTokens"]) ?? 0;
|
|
51
|
+
const cacheWrite = firstNumberField(record, ["cacheWrite", "cache_write", "cacheWriteTokens"]) ?? 0;
|
|
52
|
+
return input + output + cacheRead + cacheWrite;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function getMessageField<K extends string>(message: unknown, key: K): unknown {
|
|
56
|
+
if (message && typeof message === "object" && key in message) {
|
|
57
|
+
return (message as Record<string, unknown>)[key];
|
|
58
|
+
}
|
|
59
|
+
return undefined;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface ProgressTrackerOptions {
|
|
63
|
+
index: number;
|
|
64
|
+
id: string;
|
|
65
|
+
agent: string;
|
|
66
|
+
task: string;
|
|
67
|
+
description?: string;
|
|
68
|
+
startTime: number;
|
|
69
|
+
/** Minimum ms between progress emissions */
|
|
70
|
+
coalesceMs?: number;
|
|
71
|
+
onProgress?: (progress: AgentProgress) => void;
|
|
72
|
+
/** Called when a tool handler signals termination (e.g. shouldTerminate). */
|
|
73
|
+
onTerminateRequest?: () => void;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export class ProgressTracker {
|
|
77
|
+
#progress: AgentProgress;
|
|
78
|
+
#options: ProgressTrackerOptions;
|
|
79
|
+
#recentOutputTail = "";
|
|
80
|
+
#lastProgressEmitMs = 0;
|
|
81
|
+
#progressTimeoutId?: NodeJS.Timeout;
|
|
82
|
+
#coalesceMs: number;
|
|
83
|
+
#unsubscribe?: () => void;
|
|
84
|
+
|
|
85
|
+
// Usage accumulation
|
|
86
|
+
#usage: Usage = {
|
|
87
|
+
input: 0,
|
|
88
|
+
output: 0,
|
|
89
|
+
cacheRead: 0,
|
|
90
|
+
cacheWrite: 0,
|
|
91
|
+
totalTokens: 0,
|
|
92
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
|
|
93
|
+
};
|
|
94
|
+
#hasUsage = false;
|
|
95
|
+
|
|
96
|
+
constructor(options: ProgressTrackerOptions) {
|
|
97
|
+
this.#options = options;
|
|
98
|
+
this.#coalesceMs = options.coalesceMs ?? 150;
|
|
99
|
+
this.#progress = {
|
|
100
|
+
index: options.index,
|
|
101
|
+
id: options.id,
|
|
102
|
+
agent: options.agent,
|
|
103
|
+
status: "running",
|
|
104
|
+
task: options.task,
|
|
105
|
+
description: options.description,
|
|
106
|
+
lastIntent: undefined,
|
|
107
|
+
recentTools: [],
|
|
108
|
+
recentOutput: [],
|
|
109
|
+
toolCount: 0,
|
|
110
|
+
tokens: 0,
|
|
111
|
+
durationMs: 0,
|
|
112
|
+
toolHistory: [],
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** Subscribe to raw agent events on an EventBus. */
|
|
117
|
+
subscribe(eventBus: EventBus): void {
|
|
118
|
+
this.#unsubscribe = eventBus.on(TASK_SUBAGENT_EVENT_CHANNEL, (data: unknown) => {
|
|
119
|
+
const payload = data as { event?: AgentEvent };
|
|
120
|
+
if (payload.event) {
|
|
121
|
+
this.#processEvent(payload.event);
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
get progress(): AgentProgress {
|
|
127
|
+
return this.#progress;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** Accumulated usage, or undefined if no usage events received. */
|
|
131
|
+
get usage(): Usage | undefined {
|
|
132
|
+
return this.#hasUsage ? this.#usage : undefined;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** Set terminal status and flush. */
|
|
136
|
+
finalize(status: "completed" | "failed" | "aborted"): void {
|
|
137
|
+
this.#progress.status = status;
|
|
138
|
+
this.#progress.currentTool = undefined;
|
|
139
|
+
this.#progress.currentToolArgs = undefined;
|
|
140
|
+
this.#progress.currentToolStartMs = undefined;
|
|
141
|
+
this.#flushProgress();
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** Clean up timers and subscription. */
|
|
145
|
+
dispose(): void {
|
|
146
|
+
if (this.#progressTimeoutId) {
|
|
147
|
+
clearTimeout(this.#progressTimeoutId);
|
|
148
|
+
this.#progressTimeoutId = undefined;
|
|
149
|
+
}
|
|
150
|
+
this.#unsubscribe?.();
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
#processEvent(event: AgentEvent): void {
|
|
154
|
+
const now = Date.now();
|
|
155
|
+
let flush = false;
|
|
156
|
+
|
|
157
|
+
switch (event.type) {
|
|
158
|
+
case "message_start":
|
|
159
|
+
if (event.message?.role === "assistant") {
|
|
160
|
+
this.#resetRecentOutput();
|
|
161
|
+
}
|
|
162
|
+
break;
|
|
163
|
+
case "tool_execution_start":
|
|
164
|
+
this.#handleToolStart(event, now);
|
|
165
|
+
flush = true;
|
|
166
|
+
break;
|
|
167
|
+
case "tool_execution_end":
|
|
168
|
+
this.#handleToolEnd(event, now);
|
|
169
|
+
flush = true;
|
|
170
|
+
break;
|
|
171
|
+
case "message_update":
|
|
172
|
+
this.#handleMessageUpdate(event);
|
|
173
|
+
break;
|
|
174
|
+
case "message_end":
|
|
175
|
+
this.#handleMessageEnd(event);
|
|
176
|
+
break;
|
|
177
|
+
case "agent_end":
|
|
178
|
+
flush = true;
|
|
179
|
+
break;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
this.#scheduleProgress(flush);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// -- Tool events --
|
|
186
|
+
|
|
187
|
+
#handleToolStart(event: Extract<AgentEvent, { type: "tool_execution_start" }>, now: number): void {
|
|
188
|
+
const toolArgs = extractToolArgsPreview(event.args ?? {});
|
|
189
|
+
|
|
190
|
+
this.#progress.toolCount++;
|
|
191
|
+
this.#progress.currentTool = event.toolName;
|
|
192
|
+
this.#progress.currentToolArgs = toolArgs;
|
|
193
|
+
this.#progress.currentToolStartMs = now;
|
|
194
|
+
const intent = event.intent?.trim();
|
|
195
|
+
if (intent) {
|
|
196
|
+
this.#progress.lastIntent = intent;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (this.#progress.toolHistory.length < 50) {
|
|
200
|
+
this.#progress.toolHistory.push({
|
|
201
|
+
tool: event.toolName,
|
|
202
|
+
args: toolArgs,
|
|
203
|
+
status: "running",
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
#handleToolEnd(event: Extract<AgentEvent, { type: "tool_execution_end" }>, now: number): void {
|
|
209
|
+
const isError = !!(event as { isError?: boolean }).isError;
|
|
210
|
+
|
|
211
|
+
for (let i = this.#progress.toolHistory.length - 1; i >= 0; i--) {
|
|
212
|
+
if (this.#progress.toolHistory[i].status === "running") {
|
|
213
|
+
this.#progress.toolHistory[i].status = isError ? "error" : "success";
|
|
214
|
+
break;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (this.#progress.currentTool) {
|
|
219
|
+
this.#progress.recentTools.unshift({
|
|
220
|
+
tool: this.#progress.currentTool,
|
|
221
|
+
args: this.#progress.currentToolArgs || "",
|
|
222
|
+
endMs: now,
|
|
223
|
+
});
|
|
224
|
+
if (this.#progress.recentTools.length > 5) {
|
|
225
|
+
this.#progress.recentTools.pop();
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
this.#progress.currentTool = undefined;
|
|
229
|
+
this.#progress.currentToolArgs = undefined;
|
|
230
|
+
this.#progress.currentToolStartMs = undefined;
|
|
231
|
+
|
|
232
|
+
const handler = subprocessToolRegistry.getHandler(event.toolName);
|
|
233
|
+
if (handler) {
|
|
234
|
+
const eventArgs = (event as { args?: Record<string, unknown> }).args ?? {};
|
|
235
|
+
if (
|
|
236
|
+
handler.shouldTerminate?.({
|
|
237
|
+
toolName: event.toolName,
|
|
238
|
+
toolCallId: event.toolCallId,
|
|
239
|
+
args: eventArgs,
|
|
240
|
+
result: event.result,
|
|
241
|
+
isError: event.isError,
|
|
242
|
+
})
|
|
243
|
+
) {
|
|
244
|
+
this.#options.onTerminateRequest?.();
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
// -- Message events --
|
|
249
|
+
|
|
250
|
+
#handleMessageUpdate(event: Extract<AgentEvent, { type: "message_update" }>): void {
|
|
251
|
+
if (event.message?.role !== "assistant") return;
|
|
252
|
+
const assistantEvent = (
|
|
253
|
+
event as AgentEvent & {
|
|
254
|
+
assistantMessageEvent?: { type?: string; delta?: string };
|
|
255
|
+
}
|
|
256
|
+
).assistantMessageEvent;
|
|
257
|
+
if (assistantEvent?.type === "text_delta" && typeof assistantEvent.delta === "string") {
|
|
258
|
+
this.#appendRecentOutputTail(assistantEvent.delta);
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
if (assistantEvent && assistantEvent.type !== "text_delta") {
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
const content =
|
|
265
|
+
getMessageField(event.message, "content") ?? (event as AgentEvent & { content?: unknown }).content;
|
|
266
|
+
if (content && Array.isArray(content)) {
|
|
267
|
+
this.#replaceRecentOutputFromContent(content);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
#handleMessageEnd(event: Extract<AgentEvent, { type: "message_end" }>): void {
|
|
272
|
+
const role = event.message?.role;
|
|
273
|
+
const messageUsage = getMessageField(event.message, "usage") ?? (event as AgentEvent & { usage?: unknown }).usage;
|
|
274
|
+
if (!messageUsage || typeof messageUsage !== "object") return;
|
|
275
|
+
|
|
276
|
+
this.#progress.tokens += getUsageTokens(messageUsage);
|
|
277
|
+
|
|
278
|
+
if (role === "assistant") {
|
|
279
|
+
this.#hasUsage = true;
|
|
280
|
+
const u = messageUsage as Record<string, unknown>;
|
|
281
|
+
this.#usage.input += getNumberField(u, "input") ?? 0;
|
|
282
|
+
this.#usage.output += getNumberField(u, "output") ?? 0;
|
|
283
|
+
this.#usage.cacheRead += getNumberField(u, "cacheRead") ?? 0;
|
|
284
|
+
this.#usage.cacheWrite += getNumberField(u, "cacheWrite") ?? 0;
|
|
285
|
+
this.#usage.totalTokens += getNumberField(u, "totalTokens") ?? 0;
|
|
286
|
+
const costRecord = (u as { cost?: Record<string, unknown> }).cost;
|
|
287
|
+
if (costRecord) {
|
|
288
|
+
this.#usage.cost.input += getNumberField(costRecord, "input") ?? 0;
|
|
289
|
+
this.#usage.cost.output += getNumberField(costRecord, "output") ?? 0;
|
|
290
|
+
this.#usage.cost.cacheRead += getNumberField(costRecord, "cacheRead") ?? 0;
|
|
291
|
+
this.#usage.cost.cacheWrite += getNumberField(costRecord, "cacheWrite") ?? 0;
|
|
292
|
+
this.#usage.cost.total += getNumberField(costRecord, "total") ?? 0;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// -- Recent output tracking --
|
|
298
|
+
|
|
299
|
+
#resetRecentOutput(): void {
|
|
300
|
+
this.#recentOutputTail = "";
|
|
301
|
+
this.#progress.recentOutput = [];
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
#appendRecentOutputTail(text: string): void {
|
|
305
|
+
if (!text) return;
|
|
306
|
+
this.#recentOutputTail += text;
|
|
307
|
+
if (this.#recentOutputTail.length > RECENT_OUTPUT_TAIL_BYTES) {
|
|
308
|
+
this.#recentOutputTail = this.#recentOutputTail.slice(-RECENT_OUTPUT_TAIL_BYTES);
|
|
309
|
+
}
|
|
310
|
+
this.#updateRecentOutputLines();
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
#replaceRecentOutputFromContent(content: unknown[]): void {
|
|
314
|
+
this.#recentOutputTail = "";
|
|
315
|
+
for (const block of content) {
|
|
316
|
+
if (!block || typeof block !== "object") continue;
|
|
317
|
+
const record = block as { type?: unknown; text?: unknown };
|
|
318
|
+
if (record.type !== "text" || typeof record.text !== "string") continue;
|
|
319
|
+
if (!record.text) continue;
|
|
320
|
+
this.#recentOutputTail += record.text;
|
|
321
|
+
if (this.#recentOutputTail.length > RECENT_OUTPUT_TAIL_BYTES) {
|
|
322
|
+
this.#recentOutputTail = this.#recentOutputTail.slice(-RECENT_OUTPUT_TAIL_BYTES);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
this.#updateRecentOutputLines();
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
#updateRecentOutputLines(): void {
|
|
329
|
+
const lines = this.#recentOutputTail.split("\n").filter(line => line.trim());
|
|
330
|
+
this.#progress.recentOutput = lines.slice(-8).reverse();
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// -- Progress coalescing --
|
|
334
|
+
|
|
335
|
+
#emitProgressNow(): void {
|
|
336
|
+
this.#progress.durationMs = Date.now() - this.#options.startTime;
|
|
337
|
+
this.#options.onProgress?.({ ...this.#progress });
|
|
338
|
+
this.#lastProgressEmitMs = Date.now();
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
#flushProgress(): void {
|
|
342
|
+
if (this.#progressTimeoutId) {
|
|
343
|
+
clearTimeout(this.#progressTimeoutId);
|
|
344
|
+
this.#progressTimeoutId = undefined;
|
|
345
|
+
}
|
|
346
|
+
this.#emitProgressNow();
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
#scheduleProgress(flush = false): void {
|
|
350
|
+
if (flush) {
|
|
351
|
+
this.#flushProgress();
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
const now = Date.now();
|
|
355
|
+
const elapsed = now - this.#lastProgressEmitMs;
|
|
356
|
+
if (this.#lastProgressEmitMs === 0 || elapsed >= this.#coalesceMs) {
|
|
357
|
+
this.#flushProgress();
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
if (this.#progressTimeoutId) return;
|
|
361
|
+
this.#progressTimeoutId = setTimeout(() => {
|
|
362
|
+
this.#progressTimeoutId = undefined;
|
|
363
|
+
this.#emitProgressNow();
|
|
364
|
+
}, this.#coalesceMs - elapsed);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Extract the last assistant message text from an agent_end event.
|
|
370
|
+
* Callers use this to get subagent output from the EventBus.
|
|
371
|
+
*/
|
|
372
|
+
export function extractAgentOutput(event: Extract<AgentEvent, { type: "agent_end" }>): string {
|
|
373
|
+
const messages = event.messages;
|
|
374
|
+
if (!messages || !Array.isArray(messages)) return "";
|
|
375
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
376
|
+
const msg = messages[i];
|
|
377
|
+
if ((msg as { role?: string })?.role !== "assistant") continue;
|
|
378
|
+
const content = (msg as { content?: unknown[] })?.content;
|
|
379
|
+
if (!content || !Array.isArray(content)) continue;
|
|
380
|
+
const chunks: string[] = [];
|
|
381
|
+
for (const block of content) {
|
|
382
|
+
if ((block as { type?: string })?.type === "text" && (block as { text?: string })?.text) {
|
|
383
|
+
chunks.push((block as { text: string }).text);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
if (chunks.length > 0) return chunks.join("");
|
|
387
|
+
break;
|
|
388
|
+
}
|
|
389
|
+
return "";
|
|
390
|
+
}
|