@qduc/term2 0.11.0 → 0.11.1
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/dist/agent.d.ts +11 -0
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +54 -18
- package/dist/agent.js.map +1 -1
- package/dist/components/layout/BackgroundTasksPanel.d.ts.map +1 -1
- package/dist/components/layout/BackgroundTasksPanel.js +35 -16
- package/dist/components/layout/BackgroundTasksPanel.js.map +1 -1
- package/dist/components/message/CommandMessage.js +4 -2
- package/dist/components/message/CommandMessage.js.map +1 -1
- package/dist/components/message/command-message-helpers.d.ts +4 -4
- package/dist/hooks/conversation-ui-reducer.d.ts +5 -0
- package/dist/hooks/conversation-ui-reducer.d.ts.map +1 -1
- package/dist/hooks/conversation-ui-reducer.js +8 -0
- package/dist/hooks/conversation-ui-reducer.js.map +1 -1
- package/dist/hooks/use-conversation.d.ts.map +1 -1
- package/dist/hooks/use-conversation.js +1 -0
- package/dist/hooks/use-conversation.js.map +1 -1
- package/dist/lib/agent-configuration.d.ts.map +1 -1
- package/dist/lib/agent-configuration.js +3 -0
- package/dist/lib/agent-configuration.js.map +1 -1
- package/dist/lib/agent-factory.d.ts +11 -0
- package/dist/lib/agent-factory.d.ts.map +1 -1
- package/dist/lib/agent-factory.js +3 -0
- package/dist/lib/agent-factory.js.map +1 -1
- package/dist/lib/chained-input-filter.d.ts +20 -0
- package/dist/lib/chained-input-filter.d.ts.map +1 -1
- package/dist/lib/chained-input-filter.js +75 -0
- package/dist/lib/chained-input-filter.js.map +1 -1
- package/dist/lib/subagent-bridge.d.ts +20 -1
- package/dist/lib/subagent-bridge.d.ts.map +1 -1
- package/dist/lib/subagent-bridge.js +26 -0
- package/dist/lib/subagent-bridge.js.map +1 -1
- package/dist/prompts/anthropic.md +6 -55
- package/dist/prompts/async-subagent-delegation.d.ts +2 -1
- package/dist/prompts/async-subagent-delegation.d.ts.map +1 -1
- package/dist/prompts/async-subagent-delegation.js +20 -6
- package/dist/prompts/async-subagent-delegation.js.map +1 -1
- package/dist/prompts/async-subagent-delegation.ts +26 -6
- package/dist/prompts/gpt-5.6.md +49 -0
- package/dist/prompts/kimi.md +25 -63
- package/dist/prompts/orchestrator-prompt.test.ts +77 -0
- package/dist/prompts/orchestrator.md +15 -1
- package/dist/prompts/prompt-constructor.d.ts +1 -0
- package/dist/prompts/prompt-constructor.d.ts.map +1 -1
- package/dist/prompts/prompt-constructor.js +6 -2
- package/dist/prompts/prompt-constructor.js.map +1 -1
- package/dist/prompts/prompt-constructor.test.ts +22 -1
- package/dist/prompts/prompt-constructor.ts +9 -1
- package/dist/prompts/prompt-profiles.d.ts.map +1 -1
- package/dist/prompts/prompt-profiles.js +5 -0
- package/dist/prompts/prompt-profiles.js.map +1 -1
- package/dist/prompts/prompt-profiles.test.ts +20 -0
- package/dist/prompts/prompt-profiles.ts +5 -0
- package/dist/prompts/search-via-shell.d.ts.map +1 -1
- package/dist/prompts/search-via-shell.js +8 -25
- package/dist/prompts/search-via-shell.js.map +1 -1
- package/dist/prompts/search-via-shell.test.ts +18 -0
- package/dist/prompts/search-via-shell.ts +8 -25
- package/dist/prompts/subagent-delegation.test.ts +15 -0
- package/dist/prompts/subagents/researcher.md +6 -0
- package/dist/prompts/subagents/worker-prompt.test.ts +35 -0
- package/dist/prompts/subagents/worker.md +8 -0
- package/dist/services/conversation/conversation-adapter.d.ts +11 -3
- package/dist/services/conversation/conversation-adapter.d.ts.map +1 -1
- package/dist/services/conversation/conversation-adapter.js +123 -31
- package/dist/services/conversation/conversation-adapter.js.map +1 -1
- package/dist/services/conversation/conversation-events.d.ts +25 -1
- package/dist/services/conversation/conversation-events.d.ts.map +1 -1
- package/dist/services/conversation/conversation-orchestrator.d.ts.map +1 -1
- package/dist/services/conversation/conversation-orchestrator.js +141 -26
- package/dist/services/conversation/conversation-orchestrator.js.map +1 -1
- package/dist/services/conversation/conversation-orchestrator.types.d.ts +2 -0
- package/dist/services/conversation/conversation-orchestrator.types.d.ts.map +1 -1
- package/dist/services/conversation/conversation-service.d.ts +3 -0
- package/dist/services/conversation/conversation-service.d.ts.map +1 -1
- package/dist/services/conversation/conversation-service.js +4 -0
- package/dist/services/conversation/conversation-service.js.map +1 -1
- package/dist/services/logging/conversation-log-events.d.ts +9 -1
- package/dist/services/logging/conversation-log-events.d.ts.map +1 -1
- package/dist/services/logging/conversation-log-events.js.map +1 -1
- package/dist/services/logging/conversation-log-writer.d.ts +1 -0
- package/dist/services/logging/conversation-log-writer.d.ts.map +1 -1
- package/dist/services/logging/conversation-log-writer.js +1 -1
- package/dist/services/logging/conversation-log-writer.js.map +1 -1
- package/dist/services/logging/conversation-logger.d.ts.map +1 -1
- package/dist/services/logging/conversation-logger.js +10 -0
- package/dist/services/logging/conversation-logger.js.map +1 -1
- package/dist/services/queue/queue-controller.d.ts +1 -0
- package/dist/services/queue/queue-controller.d.ts.map +1 -1
- package/dist/services/queue/queue-controller.js +7 -4
- package/dist/services/queue/queue-controller.js.map +1 -1
- package/dist/services/retry/retry-classifier.d.ts.map +1 -1
- package/dist/services/retry/retry-classifier.js +4 -2
- package/dist/services/retry/retry-classifier.js.map +1 -1
- package/dist/services/subagents/execution-runner.d.ts +3 -3
- package/dist/services/subagents/execution-runner.d.ts.map +1 -1
- package/dist/services/subagents/execution-runner.js +96 -7
- package/dist/services/subagents/execution-runner.js.map +1 -1
- package/dist/services/subagents/mentor-runner.d.ts.map +1 -1
- package/dist/services/subagents/mentor-runner.js +24 -5
- package/dist/services/subagents/mentor-runner.js.map +1 -1
- package/dist/services/subagents/runtime.d.ts.map +1 -1
- package/dist/services/subagents/runtime.js +17 -7
- package/dist/services/subagents/runtime.js.map +1 -1
- package/dist/services/subagents/subagent-async-registry.d.ts +30 -2
- package/dist/services/subagents/subagent-async-registry.d.ts.map +1 -1
- package/dist/services/subagents/subagent-async-registry.js +314 -41
- package/dist/services/subagents/subagent-async-registry.js.map +1 -1
- package/dist/services/subagents/subagent-manager.d.ts +15 -1
- package/dist/services/subagents/subagent-manager.d.ts.map +1 -1
- package/dist/services/subagents/subagent-manager.js +16 -0
- package/dist/services/subagents/subagent-manager.js.map +1 -1
- package/dist/services/subagents/subagent-notification-store.d.ts +40 -22
- package/dist/services/subagents/subagent-notification-store.d.ts.map +1 -1
- package/dist/services/subagents/subagent-notification-store.js +81 -39
- package/dist/services/subagents/subagent-notification-store.js.map +1 -1
- package/dist/services/subagents/subagent-run-control.d.ts +41 -0
- package/dist/services/subagents/subagent-run-control.d.ts.map +1 -0
- package/dist/services/subagents/subagent-run-control.js +166 -0
- package/dist/services/subagents/subagent-run-control.js.map +1 -0
- package/dist/services/subagents/tool-policy.d.ts +24 -4
- package/dist/services/subagents/tool-policy.d.ts.map +1 -1
- package/dist/services/subagents/tool-policy.js +134 -14
- package/dist/services/subagents/tool-policy.js.map +1 -1
- package/dist/services/subagents/types.d.ts +93 -0
- package/dist/services/subagents/types.d.ts.map +1 -1
- package/dist/tools/agent/ask-orchestrator.d.ts +11 -0
- package/dist/tools/agent/ask-orchestrator.d.ts.map +1 -0
- package/dist/tools/agent/ask-orchestrator.js +39 -0
- package/dist/tools/agent/ask-orchestrator.js.map +1 -0
- package/dist/tools/agent/run-subagent-async.d.ts +43 -1
- package/dist/tools/agent/run-subagent-async.d.ts.map +1 -1
- package/dist/tools/agent/run-subagent-async.js +230 -7
- package/dist/tools/agent/run-subagent-async.js.map +1 -1
- package/dist/tools/file/code-context.d.ts +1 -0
- package/dist/tools/file/code-context.d.ts.map +1 -1
- package/dist/tools/file/code-context.js +4 -4
- package/dist/tools/file/code-context.js.map +1 -1
- package/dist/tools/file/grep.d.ts +1 -0
- package/dist/tools/file/grep.d.ts.map +1 -1
- package/dist/tools/file/grep.js +16 -10
- package/dist/tools/file/grep.js.map +1 -1
- package/dist/utils/conversation/conversation-event-handler.d.ts.map +1 -1
- package/dist/utils/conversation/conversation-event-handler.js +6 -3
- package/dist/utils/conversation/conversation-event-handler.js.map +1 -1
- package/package.json +1 -1
|
@@ -31,6 +31,11 @@ export const PROMPT_PROFILES: PromptProfile[] = [
|
|
|
31
31
|
basePromptFile: 'codex.md',
|
|
32
32
|
matches: ({ normalizedModel }) => normalizedModel.includes('gpt-5') && normalizedModel.includes('codex'),
|
|
33
33
|
},
|
|
34
|
+
{
|
|
35
|
+
id: 'gpt-5.6',
|
|
36
|
+
basePromptFile: 'gpt-5.6.md',
|
|
37
|
+
matches: ({ normalizedModel }) => normalizedModel.includes('gpt-5.6'),
|
|
38
|
+
},
|
|
34
39
|
{
|
|
35
40
|
id: 'gpt-5.5',
|
|
36
41
|
basePromptFile: 'gpt-5.5.md',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"search-via-shell.d.ts","sourceRoot":"","sources":["../../source/prompts/search-via-shell.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAE,MAAM,kCAAkC,CAAC;AAcpE,MAAM,WAAW,qBAAqB;IACpC,WAAW,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC;IACvC,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;CACrC;AAED,wBAAgB,yBAAyB,CAAC,OAAO,GAAE,qBAA0B,GAAG,MAAM,
|
|
1
|
+
{"version":3,"file":"search-via-shell.d.ts","sourceRoot":"","sources":["../../source/prompts/search-via-shell.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAE,MAAM,kCAAkC,CAAC;AAcpE,MAAM,WAAW,qBAAqB;IACpC,WAAW,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC;IACvC,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;CACrC;AAED,wBAAgB,yBAAyB,CAAC,OAAO,GAAE,qBAA0B,GAAG,MAAM,CAgCrF"}
|
|
@@ -22,32 +22,15 @@ export function getSearchViaShellAddendum(options = {}) {
|
|
|
22
22
|
|
|
23
23
|
Use the \`shell\` tool with the standard CLI binaries.`;
|
|
24
24
|
const textSearch = hasRg
|
|
25
|
-
? `**For text search**, use \`rg\` (ripgrep).
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
- \`rg -uu "pattern" .\` — include hidden + gitignored.
|
|
30
|
-
- \`rg -g '*.ts' "pattern" .\` — restrict by glob.
|
|
31
|
-
- \`rg -t ts "pattern" .\` — restrict by language preset.
|
|
32
|
-
- \`rg -n "pattern" .\` — show line numbers (useful for follow-up edits).
|
|
33
|
-
- \`rg -l "pattern" .\` — list files only.
|
|
34
|
-
- \`rg -C 3 "pattern" .\` — 3 lines of context.
|
|
35
|
-
- **Always pass an explicit path** (e.g. \`.\` or \`src/\`) to \`rg\`. When no path is given and stdin is a pipe, ripgrep reads stdin instead of searching the filesystem, returning no results.`
|
|
36
|
-
: `**For text search**, use \`grep\`. Examples:
|
|
37
|
-
- \`grep -rn "pattern" src/\` — recursive search with line numbers.
|
|
38
|
-
- \`grep -ri "pattern" src/\` — case-insensitive recursive search.
|
|
39
|
-
- \`grep -rl "pattern" src/\` — list matching files only.
|
|
40
|
-
- \`grep -C 3 "pattern" src/\` — show 3 lines of context.`;
|
|
25
|
+
? `**For text search**, use \`rg\` (ripgrep). It respects \`.gitignore\` by default; \`--no-ignore\` or \`-uu\` when you need to search past it.
|
|
26
|
+
|
|
27
|
+
**Always pass an explicit path** (e.g. \`.\` or \`src/\`). With no path given and stdin attached to a pipe, ripgrep reads stdin instead of searching the filesystem and silently returns no results.`
|
|
28
|
+
: `**For text search**, use \`grep\`, with \`-rn\` for a recursive search reporting line numbers.`;
|
|
41
29
|
const fileSearch = hasFd
|
|
42
|
-
? `**For file search**, use \`fd\`.
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
- \`fd 'pattern' path/\` — scoped to a directory.`
|
|
47
|
-
: `**For file search**, use \`find\`. Examples:
|
|
48
|
-
- \`find src/ -type f -name '*.ts'\` — search a subtree by basename glob.
|
|
49
|
-
- **Always search from a specific path, not \`/\`.** Scanning the whole filesystem can exhaust resources on large trees.
|
|
50
|
-
- When using \`find -regex\` with alternation, put the longest alternative first: \`'.*\\.(tsx|ts)'\` works; \`'.*\\.(ts|tsx)'\` silently skips \`.tsx\`.`;
|
|
30
|
+
? `**For file search**, use \`fd\`. It also respects \`.gitignore\`; \`-H -I\` includes hidden and ignored files.`
|
|
31
|
+
: `**For file search**, use \`find\`, scoped to a specific path rather than \`/\` — scanning the whole filesystem can exhaust resources on large trees.
|
|
32
|
+
|
|
33
|
+
With \`find -regex\` alternation, put the longest alternative first: \`'.*\\.(tsx|ts)'\` matches, while \`'.*\\.(ts|tsx)'\` silently skips \`.tsx\`.`;
|
|
51
34
|
const hygiene = `**General shell hygiene:**
|
|
52
35
|
- Quote paths that contain spaces.
|
|
53
36
|
- Prefer absolute paths or paths relative to a known root; avoid \`cd\`.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"search-via-shell.js","sourceRoot":"","sources":["../../source/prompts/search-via-shell.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAG1C,SAAS,kBAAkB,CAAC,GAAW;IACrC,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,SAAS,CAAC,GAAG,EAAE,CAAC,WAAW,CAAC,EAAE;YAC3C,KAAK,EAAE,QAAQ;YACf,WAAW,EAAE,IAAI;SAClB,CAAC,CAAC;QACH,OAAO,MAAM,CAAC,MAAM,KAAK,CAAC,CAAC;IAC7B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAOD,MAAM,UAAU,yBAAyB,CAAC,OAAO,GAA0B,EAAE;IAC3E,MAAM,WAAW,GAAG,OAAO,CAAC,WAAW,IAAI,kBAAkB,CAAC;IAC9D,MAAM,QAAQ,GAAG,OAAO,CAAC,gBAAgB,EAAE,QAAQ,EAAE,IAAI,KAAK,CAAC;IAE/D,sEAAsE;IACtE,6EAA6E;IAC7E,MAAM,KAAK,GAAG,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;IACnD,MAAM,KAAK,GAAG,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;IAEnD,MAAM,MAAM,GAAG;;uDAEsC,CAAC;IAEtD,MAAM,UAAU,GAAG,KAAK;QACtB,CAAC,CAAC
|
|
1
|
+
{"version":3,"file":"search-via-shell.js","sourceRoot":"","sources":["../../source/prompts/search-via-shell.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAG1C,SAAS,kBAAkB,CAAC,GAAW;IACrC,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,SAAS,CAAC,GAAG,EAAE,CAAC,WAAW,CAAC,EAAE;YAC3C,KAAK,EAAE,QAAQ;YACf,WAAW,EAAE,IAAI;SAClB,CAAC,CAAC;QACH,OAAO,MAAM,CAAC,MAAM,KAAK,CAAC,CAAC;IAC7B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAOD,MAAM,UAAU,yBAAyB,CAAC,OAAO,GAA0B,EAAE;IAC3E,MAAM,WAAW,GAAG,OAAO,CAAC,WAAW,IAAI,kBAAkB,CAAC;IAC9D,MAAM,QAAQ,GAAG,OAAO,CAAC,gBAAgB,EAAE,QAAQ,EAAE,IAAI,KAAK,CAAC;IAE/D,sEAAsE;IACtE,6EAA6E;IAC7E,MAAM,KAAK,GAAG,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;IACnD,MAAM,KAAK,GAAG,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;IAEnD,MAAM,MAAM,GAAG;;uDAEsC,CAAC;IAEtD,MAAM,UAAU,GAAG,KAAK;QACtB,CAAC,CAAC;;qMAE+L;QACjM,CAAC,CAAC,gGAAgG,CAAC;IAErG,MAAM,UAAU,GAAG,KAAK;QACtB,CAAC,CAAC,gHAAgH;QAClH,CAAC,CAAC;;qJAE+I,CAAC;IAEpJ,MAAM,OAAO,GAAG;;;;2GAIyF,CAAC;IAE1G,OAAO,GAAG,MAAM,OAAO,UAAU,OAAO,UAAU,OAAO,OAAO,EAAE,CAAC;AACrE,CAAC"}
|
|
@@ -49,3 +49,21 @@ it('getSearchViaShellAddendum falls back to grep and find when both missing', ()
|
|
|
49
49
|
expect(includesWord(result, 'find')).toBe(true);
|
|
50
50
|
expect(result.toLowerCase().includes('hygiene')).toBe(true);
|
|
51
51
|
});
|
|
52
|
+
|
|
53
|
+
it('getSearchViaShellAddendum warns that pathless rg reads stdin', () => {
|
|
54
|
+
const result = getSearchViaShellAddendum({
|
|
55
|
+
checkBinary: () => true,
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
expect(result.toLowerCase()).toContain('explicit path');
|
|
59
|
+
expect(result.toLowerCase()).toContain('stdin');
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('getSearchViaShellAddendum warns about find -regex alternation ordering', () => {
|
|
63
|
+
const result = getSearchViaShellAddendum({
|
|
64
|
+
checkBinary: () => false,
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
expect(result).toContain('-regex');
|
|
68
|
+
expect(result.toLowerCase()).toContain('longest alternative first');
|
|
69
|
+
});
|
|
@@ -32,33 +32,16 @@ export function getSearchViaShellAddendum(options: SearchViaShellOptions = {}):
|
|
|
32
32
|
Use the \`shell\` tool with the standard CLI binaries.`;
|
|
33
33
|
|
|
34
34
|
const textSearch = hasRg
|
|
35
|
-
? `**For text search**, use \`rg\` (ripgrep).
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
- \`rg -uu "pattern" .\` — include hidden + gitignored.
|
|
40
|
-
- \`rg -g '*.ts' "pattern" .\` — restrict by glob.
|
|
41
|
-
- \`rg -t ts "pattern" .\` — restrict by language preset.
|
|
42
|
-
- \`rg -n "pattern" .\` — show line numbers (useful for follow-up edits).
|
|
43
|
-
- \`rg -l "pattern" .\` — list files only.
|
|
44
|
-
- \`rg -C 3 "pattern" .\` — 3 lines of context.
|
|
45
|
-
- **Always pass an explicit path** (e.g. \`.\` or \`src/\`) to \`rg\`. When no path is given and stdin is a pipe, ripgrep reads stdin instead of searching the filesystem, returning no results.`
|
|
46
|
-
: `**For text search**, use \`grep\`. Examples:
|
|
47
|
-
- \`grep -rn "pattern" src/\` — recursive search with line numbers.
|
|
48
|
-
- \`grep -ri "pattern" src/\` — case-insensitive recursive search.
|
|
49
|
-
- \`grep -rl "pattern" src/\` — list matching files only.
|
|
50
|
-
- \`grep -C 3 "pattern" src/\` — show 3 lines of context.`;
|
|
35
|
+
? `**For text search**, use \`rg\` (ripgrep). It respects \`.gitignore\` by default; \`--no-ignore\` or \`-uu\` when you need to search past it.
|
|
36
|
+
|
|
37
|
+
**Always pass an explicit path** (e.g. \`.\` or \`src/\`). With no path given and stdin attached to a pipe, ripgrep reads stdin instead of searching the filesystem and silently returns no results.`
|
|
38
|
+
: `**For text search**, use \`grep\`, with \`-rn\` for a recursive search reporting line numbers.`;
|
|
51
39
|
|
|
52
40
|
const fileSearch = hasFd
|
|
53
|
-
? `**For file search**, use \`fd\`.
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
- \`fd 'pattern' path/\` — scoped to a directory.`
|
|
58
|
-
: `**For file search**, use \`find\`. Examples:
|
|
59
|
-
- \`find src/ -type f -name '*.ts'\` — search a subtree by basename glob.
|
|
60
|
-
- **Always search from a specific path, not \`/\`.** Scanning the whole filesystem can exhaust resources on large trees.
|
|
61
|
-
- When using \`find -regex\` with alternation, put the longest alternative first: \`'.*\\.(tsx|ts)'\` works; \`'.*\\.(ts|tsx)'\` silently skips \`.tsx\`.`;
|
|
41
|
+
? `**For file search**, use \`fd\`. It also respects \`.gitignore\`; \`-H -I\` includes hidden and ignored files.`
|
|
42
|
+
: `**For file search**, use \`find\`, scoped to a specific path rather than \`/\` — scanning the whole filesystem can exhaust resources on large trees.
|
|
43
|
+
|
|
44
|
+
With \`find -regex\` alternation, put the longest alternative first: \`'.*\\.(tsx|ts)'\` matches, while \`'.*\\.(ts|tsx)'\` silently skips \`.tsx\`.`;
|
|
62
45
|
|
|
63
46
|
const hygiene = `**General shell hygiene:**
|
|
64
47
|
- Quote paths that contain spaces.
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { it, expect } from 'vitest';
|
|
2
2
|
import { getSubagentDelegationAddendum } from './subagent-delegation.js';
|
|
3
|
+
import { getAsyncSubagentDelegationAddendum } from './async-subagent-delegation.js';
|
|
3
4
|
|
|
4
5
|
it('getSubagentDelegationAddendum returns non-empty delegation guidance', () => {
|
|
5
6
|
const result = getSubagentDelegationAddendum();
|
|
@@ -70,3 +71,17 @@ it('does not advertise the librarian when persistent memory is disabled', () =>
|
|
|
70
71
|
const result = getSubagentDelegationAddendum({ memoryEnabled: false });
|
|
71
72
|
expect(result.includes('librarian')).toBe(false);
|
|
72
73
|
});
|
|
74
|
+
|
|
75
|
+
it('async control guidance distinguishes steering, cancellation, answers, and fresh-turn limits honestly', () => {
|
|
76
|
+
const result = getAsyncSubagentDelegationAddendum({ orchestratorMode: true, controlsEnabled: true });
|
|
77
|
+
|
|
78
|
+
expect(result).toContain('`send_message`');
|
|
79
|
+
expect(result).toContain('`cancel_run`');
|
|
80
|
+
expect(result).toContain('name or runId');
|
|
81
|
+
expect(result).toContain('`reply_to`');
|
|
82
|
+
expect(result).toContain('fresh session turn');
|
|
83
|
+
expect(result).toContain('maximum of three continuation segments');
|
|
84
|
+
expect(result).toContain('not SDK live injection');
|
|
85
|
+
expect(result).toContain('single point of contact');
|
|
86
|
+
expect(result).toContain('Do NOT call `get_subagent_result` immediately');
|
|
87
|
+
});
|
|
@@ -23,6 +23,12 @@ You have access to web search and web fetch tools, and read-only access to the w
|
|
|
23
23
|
- Use only tools listed in the Available Tool Guidance section. If a tool is not listed there, it is not available.
|
|
24
24
|
- Use available read, search, and code-context tools to inspect workspace files when relevant.
|
|
25
25
|
|
|
26
|
+
## Asking the orchestrator
|
|
27
|
+
|
|
28
|
+
- Use `ask_orchestrator` only for a genuine blocker that cannot be resolved from the task, workspace, or available research tools. State the specific decision needed.
|
|
29
|
+
- The tool is available only when listed in your tools. It asks the owning orchestrator, never the user; do not contact the user directly.
|
|
30
|
+
- Continue after the answer and finish the research task rather than ending or relaunching it.
|
|
31
|
+
|
|
26
32
|
## Approach
|
|
27
33
|
|
|
28
34
|
1. Identify what information is needed.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { it, expect } from 'vitest';
|
|
4
|
+
|
|
5
|
+
const workerPrompt = fs.readFileSync(path.join(import.meta.dirname, 'worker.md'), 'utf-8');
|
|
6
|
+
const researcherPrompt = fs.readFileSync(path.join(import.meta.dirname, 'researcher.md'), 'utf-8');
|
|
7
|
+
|
|
8
|
+
it('worker prompt still requires running a validation command', () => {
|
|
9
|
+
const lower = workerPrompt.toLowerCase();
|
|
10
|
+
expect(lower).toContain('validation command');
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it('worker prompt notes that validation and diff stat are auto-captured', () => {
|
|
14
|
+
const lower = workerPrompt.toLowerCase();
|
|
15
|
+
expect(lower).toContain('automatically captures');
|
|
16
|
+
expect(lower).toContain('diff stat');
|
|
17
|
+
expect(lower).toContain('do not need to paste the full output');
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('worker prompt mentions that shell-driven edits may not appear in diff stat', () => {
|
|
21
|
+
const lower = workerPrompt.toLowerCase();
|
|
22
|
+
expect(lower).toContain('shell-driven edits');
|
|
23
|
+
expect(lower).toContain('may not appear');
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('execution role prompts reserve ask_orchestrator for genuine blockers and never direct user contact', () => {
|
|
27
|
+
for (const prompt of [workerPrompt, researcherPrompt]) {
|
|
28
|
+
const lower = prompt.toLowerCase();
|
|
29
|
+
expect(lower).toContain('ask_orchestrator');
|
|
30
|
+
expect(lower).toContain('genuine blocker');
|
|
31
|
+
expect(lower).toContain('decision needed');
|
|
32
|
+
expect(lower).toContain('continue after');
|
|
33
|
+
expect(lower).toContain('do not contact the user');
|
|
34
|
+
}
|
|
35
|
+
});
|
|
@@ -24,6 +24,12 @@ You have read and write access to the workspace (within the assigned write bound
|
|
|
24
24
|
- Read relevant files before editing them.
|
|
25
25
|
- Keep edits limited to the assigned scope. Do not broaden the task.
|
|
26
26
|
|
|
27
|
+
## Asking the orchestrator
|
|
28
|
+
|
|
29
|
+
- Use `ask_orchestrator` only for a genuine blocker that cannot be resolved from the task, workspace, or available tools. State the specific decision needed, not an open-ended status update.
|
|
30
|
+
- The tool is available only when listed in your tools. It asks the owning orchestrator, never the user; do not contact the user directly.
|
|
31
|
+
- After the answer arrives, continue after it and complete the assigned work. Do not terminate or relaunch the task merely because you asked.
|
|
32
|
+
|
|
27
33
|
## Scope discipline
|
|
28
34
|
|
|
29
35
|
- If the parent names editable, read-only, or forbidden files in the task, obey those boundaries strictly.
|
|
@@ -45,5 +51,7 @@ After completing the task, return a concise report that includes:
|
|
|
45
51
|
- Any issues encountered or assumptions made
|
|
46
52
|
- Any scope conflicts: work you identified as needed but that fell outside your assigned scope
|
|
47
53
|
|
|
54
|
+
The harness automatically captures the last validation command you ran (test/lint/typecheck/tsc/build) with its exit status and output, and a per-file line-change diff stat from your write-tool edits. You still need to run a validation command and may state which command you ran, but you do not need to paste the full output — the structured evidence is captured for you. Shell-driven edits outside the write tools may not appear in the diff stat; mention them in your summary if they matter.
|
|
55
|
+
|
|
48
56
|
Do not include implementation details that are already visible in the diff.
|
|
49
57
|
Do not claim work you did not do.
|
|
@@ -16,6 +16,8 @@ export type SendMessageOptions = {
|
|
|
16
16
|
bypassInputSurgeGuard?: boolean;
|
|
17
17
|
replayFromHistory?: boolean;
|
|
18
18
|
preferredMessageId?: string;
|
|
19
|
+
/** The turn is model input only and must not be projected as a user message in the UI. */
|
|
20
|
+
suppressUserMessageDisplay?: boolean;
|
|
19
21
|
};
|
|
20
22
|
export type HandleApprovalDecisionOptions = {
|
|
21
23
|
onTextChunk?: (fullText: string, chunk: string) => void;
|
|
@@ -52,6 +54,7 @@ export type ConversationEventSink = (event: ConversationEvent) => void;
|
|
|
52
54
|
export type QueuedTurnStartObserver = (execution: {
|
|
53
55
|
readonly requestId: string;
|
|
54
56
|
readonly input: string | UserTurn;
|
|
57
|
+
readonly suppressUserMessageDisplay?: boolean;
|
|
55
58
|
}) => void;
|
|
56
59
|
export declare class ConversationAdapter {
|
|
57
60
|
#private;
|
|
@@ -68,13 +71,18 @@ export declare class ConversationAdapter {
|
|
|
68
71
|
approval: SessionApprovalQuery;
|
|
69
72
|
turnFlow: TurnFlow;
|
|
70
73
|
queueForeground?: boolean;
|
|
74
|
+
queueCapacity?: number;
|
|
75
|
+
/** Test seam: bound how long cancel waits for a hung active turn. */
|
|
76
|
+
activeCancelTimeoutMs?: number;
|
|
71
77
|
queuePersistence?: QueuePersistence<QueuedMessageSnapshot>;
|
|
72
78
|
});
|
|
73
79
|
setEventSink(sink: ConversationEventSink | null): void;
|
|
74
80
|
setQueueStateObserver(observer: QueueStateObserver | null): void;
|
|
75
81
|
setQueuedTurnStartObserver(observer: QueuedTurnStartObserver | null): void;
|
|
76
82
|
isQueueActive(): boolean;
|
|
77
|
-
|
|
83
|
+
/** Whether the foreground queue owns a new submission's UI lifecycle. */
|
|
84
|
+
isQueueOwningSubmissions(): boolean;
|
|
85
|
+
sendMessage(input: string | UserTurn, { onTextChunk, onReasoningChunk, onCommandMessage, onEvent, hallucinationRetryCount, bypassInputSurgeGuard, replayFromHistory, preferredMessageId, suppressUserMessageDisplay, }?: SendMessageOptions): Promise<ConversationTerminal>;
|
|
78
86
|
resumeQueue(): Promise<void>;
|
|
79
87
|
discardQueue(): Promise<void>;
|
|
80
88
|
/**
|
|
@@ -83,10 +91,10 @@ export declare class ConversationAdapter {
|
|
|
83
91
|
* the queue is empty, the adapter has no queue (pass-through mode), or the
|
|
84
92
|
* underlying controller rejects the removal.
|
|
85
93
|
*
|
|
86
|
-
*
|
|
87
|
-
* the queue/pending state observers fire consistently.
|
|
94
|
+
* The item is correlated by its controller id, never its queue position.
|
|
88
95
|
*/
|
|
89
96
|
removeLastQueuedItem(): Promise<{
|
|
97
|
+
id: string;
|
|
90
98
|
text: string;
|
|
91
99
|
} | null>;
|
|
92
100
|
abort(): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"conversation-adapter.d.ts","sourceRoot":"","sources":["../../../source/services/conversation/conversation-adapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,sBAAsB,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAC1G,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAClE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,iCAAiC,CAAC;AAG5E,OAAO,EAAqB,KAAK,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AAC5E,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,oBAAoB,EAAE,MAAM,mCAAmC,CAAC;AAC3G,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AACpE,OAAO,KAAK,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,MAAM,iCAAiC,CAAC;AAChG,OAAO,EAKL,KAAK,gBAAgB,EAEtB,MAAM,8BAA8B,CAAC;AAEtC,MAAM,MAAM,kBAAkB,GAAG;IAC/B,WAAW,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACxD,gBAAgB,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7D,gBAAgB,CAAC,EAAE,CAAC,OAAO,EAAE,cAAc,KAAK,IAAI,CAAC;IACrD,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,CAAC;IAC7C,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,kBAAkB,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"conversation-adapter.d.ts","sourceRoot":"","sources":["../../../source/services/conversation/conversation-adapter.ts"],"names":[],"mappings":"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"}
|
|
@@ -2,6 +2,20 @@ import { collectTerminalResult } from '../session/terminal-result-collector.js';
|
|
|
2
2
|
import { getCallIdFromObject } from '../interruption-info.js';
|
|
3
3
|
import { normalizeUserTurn } from '../../types/user-turn.js';
|
|
4
4
|
import { QueueController, } from '../queue/queue-controller.js';
|
|
5
|
+
const QUEUED_NON_TEXT_PLACEHOLDER = '[queued non-text user turn]';
|
|
6
|
+
const LEGACY_QUEUED_MESSAGE_PLACEHOLDER = '\u0000queued-message';
|
|
7
|
+
/** Upper bound for waiting on an active turn during cancel so the queue cannot stick in `cancelling`. */
|
|
8
|
+
const ACTIVE_CANCEL_TIMEOUT_MS = 10_000;
|
|
9
|
+
function queueCancellationError(message) {
|
|
10
|
+
const error = new Error(message);
|
|
11
|
+
error.name = 'AbortError';
|
|
12
|
+
return error;
|
|
13
|
+
}
|
|
14
|
+
function delay(ms) {
|
|
15
|
+
return new Promise((resolve) => {
|
|
16
|
+
setTimeout(resolve, ms);
|
|
17
|
+
});
|
|
18
|
+
}
|
|
5
19
|
export class ConversationAdapter {
|
|
6
20
|
#sessionId;
|
|
7
21
|
#startedAt;
|
|
@@ -15,17 +29,18 @@ export class ConversationAdapter {
|
|
|
15
29
|
#logs;
|
|
16
30
|
#approval;
|
|
17
31
|
#turnFlow;
|
|
18
|
-
#pendingMessages = [];
|
|
19
32
|
#messagesById = new Map();
|
|
20
33
|
#queue;
|
|
21
34
|
#nextQueuedMessageId = 1;
|
|
22
35
|
#nextActionId = 1;
|
|
23
36
|
#activeTurn = Promise.resolve();
|
|
37
|
+
#activeRequestId = null;
|
|
24
38
|
#cancellation = Promise.resolve();
|
|
25
39
|
#approvalExecutionId = null;
|
|
26
40
|
#approvalActionId = null;
|
|
27
41
|
#queueStateObserver = null;
|
|
28
42
|
#queuedTurnStartObserver = null;
|
|
43
|
+
#activeCancelTimeoutMs;
|
|
29
44
|
constructor(deps) {
|
|
30
45
|
this.#sessionId = deps.sessionId;
|
|
31
46
|
this.#startedAt = deps.startedAt;
|
|
@@ -38,22 +53,26 @@ export class ConversationAdapter {
|
|
|
38
53
|
this.#logs = deps.logs;
|
|
39
54
|
this.#approval = deps.approval;
|
|
40
55
|
this.#turnFlow = deps.turnFlow;
|
|
56
|
+
this.#activeCancelTimeoutMs = deps.activeCancelTimeoutMs ?? ACTIVE_CANCEL_TIMEOUT_MS;
|
|
41
57
|
if (deps.queueForeground) {
|
|
42
58
|
const driver = {
|
|
43
59
|
start: (execution) => this.#startQueuedTurn(execution),
|
|
44
60
|
cancel: async () => {
|
|
45
|
-
|
|
61
|
+
// Prefer natural abort settlement, but never block queue cancel forever
|
|
62
|
+
// if the underlying turn ignores abort.
|
|
63
|
+
await Promise.race([
|
|
64
|
+
this.#activeTurn.then(() => undefined, () => undefined),
|
|
65
|
+
delay(this.#activeCancelTimeoutMs),
|
|
66
|
+
]);
|
|
46
67
|
},
|
|
47
68
|
};
|
|
48
69
|
this.#queue = new QueueController({
|
|
49
70
|
driver,
|
|
50
71
|
snapshotFactory: (item) => {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
}
|
|
54
|
-
const pending = this.#pendingMessages.shift();
|
|
55
|
-
return pending ? { requestId: pending.requestId } : { requestId: `recovered:${item.id}`, recovered: true };
|
|
72
|
+
const message = this.#messagesById.get(item.id);
|
|
73
|
+
return message ? { requestId: item.id } : { requestId: item.id, recovered: true };
|
|
56
74
|
},
|
|
75
|
+
capacity: deps.queueCapacity,
|
|
57
76
|
persistence: deps.queuePersistence,
|
|
58
77
|
});
|
|
59
78
|
}
|
|
@@ -84,6 +103,13 @@ export class ConversationAdapter {
|
|
|
84
103
|
state.kind === 'cancelling' ||
|
|
85
104
|
state.kind === 'completing');
|
|
86
105
|
}
|
|
106
|
+
/** Whether the foreground queue owns a new submission's UI lifecycle. */
|
|
107
|
+
isQueueOwningSubmissions() {
|
|
108
|
+
if (!this.#queue)
|
|
109
|
+
return false;
|
|
110
|
+
const state = this.#queue.state();
|
|
111
|
+
return state.kind !== 'idle' || state.queue.length > 0;
|
|
112
|
+
}
|
|
87
113
|
#notifyQueueState() {
|
|
88
114
|
if (!this.#queue || !this.#queueStateObserver)
|
|
89
115
|
return;
|
|
@@ -120,7 +146,7 @@ export class ConversationAdapter {
|
|
|
120
146
|
firstUserMessagePreview,
|
|
121
147
|
}, fn);
|
|
122
148
|
}
|
|
123
|
-
async sendMessage(input, { onTextChunk, onReasoningChunk, onCommandMessage, onEvent, hallucinationRetryCount = 0, bypassInputSurgeGuard, replayFromHistory, preferredMessageId, } = {}) {
|
|
149
|
+
async sendMessage(input, { onTextChunk, onReasoningChunk, onCommandMessage, onEvent, hallucinationRetryCount = 0, bypassInputSurgeGuard, replayFromHistory, preferredMessageId, suppressUserMessageDisplay, } = {}) {
|
|
124
150
|
const queue = this.#queue;
|
|
125
151
|
if (!queue) {
|
|
126
152
|
return this.#executeMessage(input, {
|
|
@@ -135,8 +161,12 @@ export class ConversationAdapter {
|
|
|
135
161
|
}
|
|
136
162
|
return new Promise((resolve, reject) => {
|
|
137
163
|
const requestId = preferredMessageId ?? String(this.#nextQueuedMessageId++);
|
|
164
|
+
if (this.#messagesById.has(requestId)) {
|
|
165
|
+
reject(new Error(`A queued message already uses request id ${requestId}`));
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
138
168
|
const message = {
|
|
139
|
-
input,
|
|
169
|
+
input: typeof input === 'string' ? input : structuredClone(input),
|
|
140
170
|
options: {
|
|
141
171
|
onTextChunk,
|
|
142
172
|
onReasoningChunk,
|
|
@@ -145,15 +175,27 @@ export class ConversationAdapter {
|
|
|
145
175
|
hallucinationRetryCount,
|
|
146
176
|
bypassInputSurgeGuard,
|
|
147
177
|
replayFromHistory,
|
|
178
|
+
suppressUserMessageDisplay,
|
|
148
179
|
},
|
|
149
180
|
resolve,
|
|
150
181
|
reject,
|
|
151
182
|
};
|
|
152
183
|
this.#messagesById.set(requestId, message);
|
|
153
|
-
|
|
184
|
+
const displayText = normalizeUserTurn(input).text;
|
|
185
|
+
const controllerText = displayText.trim() ? displayText : QUEUED_NON_TEXT_PLACEHOLDER;
|
|
154
186
|
void queue
|
|
155
|
-
.command({ kind: 'submit',
|
|
156
|
-
.then(() =>
|
|
187
|
+
.command({ kind: 'submit', id: requestId, text: controllerText })
|
|
188
|
+
.then((result) => {
|
|
189
|
+
if (result.kind !== 'accepted') {
|
|
190
|
+
const reason = result.kind === 'rejected' ? result.reason : result.kind;
|
|
191
|
+
this.#settleFailure(requestId, new Error(`Foreground queue rejected message: ${reason}`));
|
|
192
|
+
}
|
|
193
|
+
this.#notifyQueueState();
|
|
194
|
+
})
|
|
195
|
+
.catch((error) => {
|
|
196
|
+
this.#settleFailure(requestId, error);
|
|
197
|
+
this.#notifyQueueState();
|
|
198
|
+
});
|
|
157
199
|
});
|
|
158
200
|
}
|
|
159
201
|
async resumeQueue() {
|
|
@@ -166,7 +208,13 @@ export class ConversationAdapter {
|
|
|
166
208
|
async discardQueue() {
|
|
167
209
|
if (!this.#queue)
|
|
168
210
|
return;
|
|
169
|
-
|
|
211
|
+
const queuedIds = this.#queue.state().queue.map((item) => item.id);
|
|
212
|
+
const result = await this.#queue.command({ kind: 'discard_queue' });
|
|
213
|
+
if (result.kind === 'accepted') {
|
|
214
|
+
for (const requestId of queuedIds) {
|
|
215
|
+
this.#settleFailure(requestId, queueCancellationError('Queued message was discarded'));
|
|
216
|
+
}
|
|
217
|
+
}
|
|
170
218
|
this.#notifyQueueState();
|
|
171
219
|
}
|
|
172
220
|
/**
|
|
@@ -175,8 +223,7 @@ export class ConversationAdapter {
|
|
|
175
223
|
* the queue is empty, the adapter has no queue (pass-through mode), or the
|
|
176
224
|
* underlying controller rejects the removal.
|
|
177
225
|
*
|
|
178
|
-
*
|
|
179
|
-
* the queue/pending state observers fire consistently.
|
|
226
|
+
* The item is correlated by its controller id, never its queue position.
|
|
180
227
|
*/
|
|
181
228
|
async removeLastQueuedItem() {
|
|
182
229
|
const queue = this.#queue;
|
|
@@ -187,27 +234,39 @@ export class ConversationAdapter {
|
|
|
187
234
|
return null;
|
|
188
235
|
// The queue is FIFO. The "last" queued item is the one at the tail.
|
|
189
236
|
const lastItem = state.queue[state.queue.length - 1];
|
|
237
|
+
// Prefer the in-memory turn text: controller text may be a non-text placeholder.
|
|
238
|
+
const pending = this.#messagesById.get(lastItem.id);
|
|
239
|
+
const restoredText = pending
|
|
240
|
+
? normalizeUserTurn(pending.input).text
|
|
241
|
+
: lastItem.text === QUEUED_NON_TEXT_PLACEHOLDER || lastItem.text === LEGACY_QUEUED_MESSAGE_PLACEHOLDER
|
|
242
|
+
? ''
|
|
243
|
+
: lastItem.text;
|
|
190
244
|
const result = await queue.command({ kind: 'remove_queued', itemId: lastItem.id });
|
|
191
245
|
if (result.kind !== 'accepted')
|
|
192
246
|
return null;
|
|
193
|
-
|
|
194
|
-
// pending entry for this item is the last one we added. Removing it keeps
|
|
195
|
-
// the adapter's internal bookkeeping consistent with the queue controller.
|
|
196
|
-
this.#pendingMessages.pop();
|
|
197
|
-
// Note: we don't need to touch #messagesById here because the queue head
|
|
198
|
-
// has not yet started executing this item (it was still queued), so there
|
|
199
|
-
// is no pending execution for it to resolve. The pending message entry
|
|
200
|
-
// was only there to support the snapshot factory if the head was popped.
|
|
247
|
+
this.#settleFailure(lastItem.id, queueCancellationError('Queued message was removed'));
|
|
201
248
|
this.#notifyQueueState();
|
|
202
|
-
return {
|
|
249
|
+
return { id: lastItem.id, text: restoredText };
|
|
203
250
|
}
|
|
204
251
|
abort() {
|
|
205
252
|
if (!this.#queue) {
|
|
206
253
|
this.#turnFlow.abort?.();
|
|
207
254
|
return;
|
|
208
255
|
}
|
|
256
|
+
// Abort the live model/tool turn, then ask the controller to leave running
|
|
257
|
+
// and pause with retained work. If the active turn fails to settle on its
|
|
258
|
+
// own (a hung generator, missing abort hook, etc.), force-settle the active
|
|
259
|
+
// request so orchestrator awaits cannot stick forever. Retained queued
|
|
260
|
+
// requests stay pending — pause is not a terminal fate.
|
|
209
261
|
this.#turnFlow.abort?.();
|
|
262
|
+
const activeRequestId = this.#activeRequestId;
|
|
210
263
|
this.#cancellation = this.#queue.command({ kind: 'cancel' }).then(() => {
|
|
264
|
+
if (activeRequestId && this.#messagesById.has(activeRequestId)) {
|
|
265
|
+
this.#settleFailure(activeRequestId, queueCancellationError('Active turn was cancelled'));
|
|
266
|
+
}
|
|
267
|
+
if (this.#activeRequestId === activeRequestId) {
|
|
268
|
+
this.#activeRequestId = null;
|
|
269
|
+
}
|
|
211
270
|
this.#notifyQueueState();
|
|
212
271
|
});
|
|
213
272
|
}
|
|
@@ -220,6 +279,7 @@ export class ConversationAdapter {
|
|
|
220
279
|
this.#queuedTurnStartObserver({
|
|
221
280
|
requestId: execution.snapshot.requestId,
|
|
222
281
|
input: message.input,
|
|
282
|
+
suppressUserMessageDisplay: message.options.suppressUserMessageDisplay,
|
|
223
283
|
});
|
|
224
284
|
}
|
|
225
285
|
catch (error) {
|
|
@@ -228,14 +288,31 @@ export class ConversationAdapter {
|
|
|
228
288
|
});
|
|
229
289
|
}
|
|
230
290
|
}
|
|
231
|
-
this.#
|
|
291
|
+
this.#activeRequestId = execution.snapshot.requestId;
|
|
292
|
+
this.#activeTurn = this.#runQueuedTurn(execution).finally(() => {
|
|
293
|
+
if (this.#activeRequestId === execution.snapshot.requestId) {
|
|
294
|
+
this.#activeRequestId = null;
|
|
295
|
+
}
|
|
296
|
+
});
|
|
232
297
|
}
|
|
298
|
+
/**
|
|
299
|
+
* Run one foreground queue item.
|
|
300
|
+
*
|
|
301
|
+
* Failure policy: a failed active turn emits `failed` to the controller, which
|
|
302
|
+
* pauses the queue (`reason: 'failure'`) whenever retained work remains. The
|
|
303
|
+
* next item does not auto-start; the user must resume or discard. Cancellation
|
|
304
|
+
* of the active turn is the same shape with `reason: 'manual'`.
|
|
305
|
+
*/
|
|
233
306
|
async #runQueuedTurn(execution) {
|
|
234
307
|
const message = this.#messagesById.get(execution.snapshot.requestId);
|
|
235
|
-
if (message)
|
|
236
|
-
this.#messagesById.delete(execution.snapshot.requestId);
|
|
237
308
|
try {
|
|
238
|
-
const
|
|
309
|
+
const recoveredInput = execution.item.text === QUEUED_NON_TEXT_PLACEHOLDER || execution.item.text === LEGACY_QUEUED_MESSAGE_PLACEHOLDER
|
|
310
|
+
? null
|
|
311
|
+
: execution.item.text;
|
|
312
|
+
if (!message && !recoveredInput) {
|
|
313
|
+
throw new Error('Recovered queued message has no executable text input');
|
|
314
|
+
}
|
|
315
|
+
const result = await this.#executeMessage(message?.input ?? recoveredInput, message?.options ?? {});
|
|
239
316
|
if (result.type === 'approval_required') {
|
|
240
317
|
this.#approvalExecutionId = execution.executionId;
|
|
241
318
|
this.#approvalActionId = `adapter-action-${this.#nextActionId++}`;
|
|
@@ -246,19 +323,34 @@ export class ConversationAdapter {
|
|
|
246
323
|
request: {}, // existing runtime doesn't expose typed tool request details
|
|
247
324
|
});
|
|
248
325
|
this.#notifyQueueState();
|
|
249
|
-
|
|
326
|
+
this.#settleSuccess(execution.snapshot.requestId, result);
|
|
250
327
|
return;
|
|
251
328
|
}
|
|
252
329
|
await this.#queue.event({ kind: 'completed', executionId: execution.executionId, terminal: result });
|
|
253
330
|
this.#notifyQueueState();
|
|
254
|
-
|
|
331
|
+
this.#settleSuccess(execution.snapshot.requestId, result);
|
|
255
332
|
}
|
|
256
333
|
catch (error) {
|
|
334
|
+
// Controller pauses with retained queue on failure when work remains.
|
|
257
335
|
await this.#queue.event({ kind: 'failed', executionId: execution.executionId, failure: error });
|
|
258
336
|
this.#notifyQueueState();
|
|
259
|
-
|
|
337
|
+
this.#settleFailure(execution.snapshot.requestId, error);
|
|
260
338
|
}
|
|
261
339
|
}
|
|
340
|
+
#settleSuccess(requestId, terminal) {
|
|
341
|
+
const message = this.#messagesById.get(requestId);
|
|
342
|
+
if (!message)
|
|
343
|
+
return;
|
|
344
|
+
this.#messagesById.delete(requestId);
|
|
345
|
+
message.resolve(terminal);
|
|
346
|
+
}
|
|
347
|
+
#settleFailure(requestId, error) {
|
|
348
|
+
const message = this.#messagesById.get(requestId);
|
|
349
|
+
if (!message)
|
|
350
|
+
return;
|
|
351
|
+
this.#messagesById.delete(requestId);
|
|
352
|
+
message.reject(error);
|
|
353
|
+
}
|
|
262
354
|
async #executeMessage(input, { onTextChunk, onReasoningChunk, onCommandMessage, onEvent, hallucinationRetryCount = 0, bypassInputSurgeGuard, replayFromHistory, } = {}) {
|
|
263
355
|
const turn = normalizeUserTurn(input);
|
|
264
356
|
return this.#withTrafficContext(turn.text, async () => {
|