@gajae-code/coding-agent 0.12.0 → 0.12.2
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 +111 -0
- package/dist/types/async/job-manager.d.ts +17 -3
- package/dist/types/config/settings-schema.d.ts +56 -2
- package/dist/types/exa/mcp-client.d.ts +10 -1
- package/dist/types/exec/bash-executor.d.ts +45 -12
- package/dist/types/extensibility/extensions/types.d.ts +16 -2
- package/dist/types/extensibility/gjc-plugins/index.d.ts +12 -2
- package/dist/types/extensibility/gjc-plugins/installer.d.ts +63 -7
- package/dist/types/extensibility/gjc-plugins/lifecycle-reconciliation.d.ts +55 -0
- package/dist/types/extensibility/gjc-plugins/lifecycle.d.ts +64 -0
- package/dist/types/extensibility/gjc-plugins/runtime-quarantine.d.ts +67 -0
- package/dist/types/extensibility/gjc-plugins/session-validation.d.ts +3 -1
- package/dist/types/extensibility/gjc-plugins/types.d.ts +124 -0
- package/dist/types/gjc-runtime/launch-tmux.d.ts +1 -0
- package/dist/types/gjc-runtime/ralplan-review-conflicts.d.ts +114 -0
- package/dist/types/gjc-runtime/ralplan-runtime.d.ts +23 -3
- package/dist/types/gjc-runtime/team-launch.d.ts +1 -1
- package/dist/types/gjc-runtime/team-runtime.d.ts +25 -0
- package/dist/types/gjc-runtime/team-worker-heartbeat.d.ts +43 -0
- package/dist/types/gjc-runtime/tmux-owner-isolation.d.ts +10 -0
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +37 -80
- package/dist/types/modes/acp/acp-agent.d.ts +2 -19
- package/dist/types/modes/components/gjc-bundle-settings.d.ts +34 -0
- package/dist/types/modes/components/irc-sidebar.d.ts +16 -0
- package/dist/types/modes/components/notifications-settings-editor.d.ts +1 -0
- package/dist/types/modes/components/session-observer-overlay.d.ts +18 -0
- package/dist/types/modes/components/settings-selector.d.ts +9 -0
- package/dist/types/modes/components/status-line/gh.d.ts +5 -0
- package/dist/types/modes/components/transcript-viewer-overlay.d.ts +18 -1
- package/dist/types/modes/controllers/command-controller.d.ts +1 -1
- package/dist/types/modes/controllers/event-controller.d.ts +1 -1
- package/dist/types/modes/controllers/extension-ui-controller.d.ts +2 -1
- package/dist/types/modes/controllers/selector-controller.d.ts +1 -1
- package/dist/types/modes/interactive-mode.d.ts +9 -0
- package/dist/types/modes/theme/theme.d.ts +3 -1
- package/dist/types/modes/types.d.ts +32 -0
- package/dist/types/reminders/star-reminder.d.ts +3 -13
- package/dist/types/runtime-mcp/manager.d.ts +1 -0
- package/dist/types/sdk/acp/adapter.d.ts +3 -1
- package/dist/types/sdk/broker/transport.d.ts +2 -0
- package/dist/types/sdk/bus/attachment-registry.d.ts +1 -0
- package/dist/types/sdk/bus/chat-daemon-control.d.ts +5 -1
- package/dist/types/sdk/bus/config.d.ts +6 -2
- package/dist/types/sdk/bus/index.d.ts +29 -0
- package/dist/types/sdk/bus/kind-aware-reconciliation.d.ts +8 -1
- package/dist/types/sdk/bus/lifecycle-control-runtime.d.ts +4 -0
- package/dist/types/sdk/bus/reconciliation-store.d.ts +7 -3
- package/dist/types/sdk/bus/rich-draft.d.ts +1 -0
- package/dist/types/sdk/bus/rich-render.d.ts +2 -0
- package/dist/types/sdk/bus/telegram-adoption-intent.d.ts +165 -0
- package/dist/types/sdk/bus/telegram-cli.d.ts +12 -0
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +12 -4
- package/dist/types/sdk/bus/telegram-daemon.d.ts +25 -1
- package/dist/types/sdk/bus/telegram-reference.d.ts +11 -1
- package/dist/types/sdk/bus/threaded-render.d.ts +3 -2
- package/dist/types/sdk/bus/topic-registry.d.ts +11 -1
- package/dist/types/sdk/host/query/revision-store.d.ts +2 -0
- package/dist/types/sdk/host/reverse-leases.d.ts +1 -1
- package/dist/types/sdk/prompt-status.d.ts +23 -3
- package/dist/types/sdk/session.d.ts +6 -0
- package/dist/types/session/agent-session.d.ts +40 -2
- package/dist/types/session/artifacts.d.ts +8 -0
- package/dist/types/session/blob-store.d.ts +57 -4
- package/dist/types/session/internal/managed-session-storage.d.ts +30 -4
- package/dist/types/session/session-manager.d.ts +23 -6
- package/dist/types/session/session-storage.d.ts +4 -0
- package/dist/types/session/streaming-output.d.ts +36 -7
- package/dist/types/setup/credential-import.d.ts +12 -0
- package/dist/types/skill-state/active-state.d.ts +12 -0
- package/dist/types/skill-state/workflow-hud.d.ts +6 -0
- package/dist/types/task/executor.d.ts +3 -2
- package/dist/types/task/index.d.ts +7 -1
- package/dist/types/task/output-manager.d.ts +1 -0
- package/dist/types/task/provider-retry-status.d.ts +8 -0
- package/dist/types/task/receipt.d.ts +9 -1
- package/dist/types/task/types.d.ts +23 -0
- package/dist/types/tools/ask-answer-registry.d.ts +2 -2
- package/dist/types/tools/bash-interactive.d.ts +4 -1
- package/dist/types/tools/bash.d.ts +3 -1
- package/dist/types/tools/image-gen.d.ts +4 -0
- package/dist/types/tools/index.d.ts +6 -0
- package/dist/types/tools/output-meta.d.ts +23 -2
- package/dist/types/tools/path-utils.d.ts +1 -0
- package/dist/types/tools/read.d.ts +0 -6
- package/dist/types/tools/review.d.ts +5 -7
- package/dist/types/tools/subagent.d.ts +2 -0
- package/dist/types/utils/gh.d.ts +13 -0
- package/dist/types/utils/image-resize.d.ts +1 -0
- package/dist/types/web/search/providers/kimi.d.ts +2 -0
- package/dist/types/web/search/providers/searxng.d.ts +7 -0
- package/dist/types/web/search/providers/xai.d.ts +2 -0
- package/package.json +10 -8
- package/scripts/acp-conformance-agent.ts +218 -0
- package/scripts/capture-gjc-bundle-settings.ts +306 -0
- package/scripts/dogfood-ralplan-review-conflicts.ts +272 -0
- package/scripts/generate-sdk-operation-inventory.ts +5 -0
- package/scripts/resident-memory-bench.ts +1341 -0
- package/scripts/run-acp-conformance.ts +290 -0
- package/src/async/job-manager.ts +99 -39
- package/src/cli/fast-help.ts +1 -1
- package/src/cli/plugin-cli.ts +176 -18
- package/src/commands/team.ts +19 -1
- package/src/config/file-lock.ts +11 -2
- package/src/config/settings-schema.ts +53 -2
- package/src/coordinator-mcp/question-gate-codec.ts +1 -1
- package/src/coordinator-mcp/server.ts +5 -12
- package/src/debug/index.ts +42 -17
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +1 -0
- package/src/defaults/gjc/skills/ralplan/SKILL.md +25 -20
- package/src/defaults/gjc/skills/team/SKILL.md +2 -0
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +39 -26
- package/src/defaults/gjc/skills/ultragoal/validation-batch-contracts.md +12 -0
- package/src/defaults/gjc-defaults.ts +4 -4
- package/src/exa/mcp-client.ts +12 -3
- package/src/exec/bash-executor.ts +144 -27
- package/src/extensibility/extensions/runner.ts +15 -0
- package/src/extensibility/extensions/types.ts +18 -1
- package/src/extensibility/gjc-plugins/constrained-hooks.ts +8 -2
- package/src/extensibility/gjc-plugins/index.ts +19 -2
- package/src/extensibility/gjc-plugins/installer.ts +189 -50
- package/src/extensibility/gjc-plugins/lifecycle-reconciliation.ts +199 -0
- package/src/extensibility/gjc-plugins/lifecycle.ts +626 -0
- package/src/extensibility/gjc-plugins/observability.ts +3 -3
- package/src/extensibility/gjc-plugins/prompt-appendix.ts +5 -1
- package/src/extensibility/gjc-plugins/runtime-adapters.ts +12 -2
- package/src/extensibility/gjc-plugins/runtime-quarantine.ts +121 -0
- package/src/extensibility/gjc-plugins/schema.ts +84 -12
- package/src/extensibility/gjc-plugins/session-validation.ts +9 -3
- package/src/extensibility/gjc-plugins/types.ts +141 -0
- package/src/extensibility/gjc-plugins/validation.ts +9 -0
- package/src/gjc-runtime/launch-tmux.ts +25 -6
- package/src/gjc-runtime/launch-worktree.ts +103 -1
- package/src/gjc-runtime/ledger-event-renderer.ts +1 -0
- package/src/gjc-runtime/managed-owner-admission.ts +1 -1
- package/src/gjc-runtime/ralplan-review-conflicts.ts +472 -0
- package/src/gjc-runtime/ralplan-runtime.ts +491 -27
- package/src/gjc-runtime/team-launch.ts +8 -0
- package/src/gjc-runtime/team-runtime.ts +140 -32
- package/src/gjc-runtime/team-worker-heartbeat.ts +149 -0
- package/src/gjc-runtime/tmux-owner-isolation.ts +10 -0
- package/src/gjc-runtime/tmux-sessions.ts +58 -13
- package/src/gjc-runtime/ultragoal-evidence.ts +6 -74
- package/src/gjc-runtime/ultragoal-runtime.ts +635 -612
- package/src/gjc-runtime/workflow-manifest.generated.json +37 -99
- package/src/gjc-runtime/workflow-manifest.ts +21 -70
- package/src/internal-urls/docs-index.generated.ts +16 -16
- package/src/modes/DESIGN.md +104 -0
- package/src/modes/acp/acp-agent.ts +298 -186
- package/src/modes/components/gjc-bundle-settings.ts +508 -0
- package/src/modes/components/irc-sidebar.ts +30 -1
- package/src/modes/components/notifications-settings-editor.ts +12 -1
- package/src/modes/components/oauth-selector.ts +68 -30
- package/src/modes/components/session-observer-overlay.ts +305 -86
- package/src/modes/components/settings-selector.ts +56 -5
- package/src/modes/components/status-line/gh.ts +16 -0
- package/src/modes/components/tool-status-header.ts +3 -15
- package/src/modes/components/transcript-viewer-overlay.ts +99 -24
- package/src/modes/controllers/command-controller.ts +42 -34
- package/src/modes/controllers/event-controller.ts +27 -16
- package/src/modes/controllers/extension-ui-controller.ts +193 -34
- package/src/modes/controllers/selector-controller.ts +160 -22
- package/src/modes/interactive-mode.ts +189 -62
- package/src/modes/jobs-observer.ts +1 -1
- package/src/modes/runtime-init.ts +3 -0
- package/src/modes/theme/theme.ts +11 -9
- package/src/modes/types.ts +88 -0
- package/src/prompts/agent-fragments/ralplan-persistence.md +4 -3
- package/src/prompts/system/eager-todo.md +8 -4
- package/src/prompts/system/system-prompt.md +11 -4
- package/src/prompts/tools/bash.md +5 -5
- package/src/prompts/tools/search-tool-bm25.md +5 -0
- package/src/prompts/tools/task.md +1 -1
- package/src/reminders/star-reminder.ts +4 -42
- package/src/runtime-mcp/manager.ts +19 -4
- package/src/sdk/acp/adapter.ts +22 -6
- package/src/sdk/broker/lifecycle.ts +18 -13
- package/src/sdk/broker/transport.ts +35 -3
- package/src/sdk/bus/attachment-registry.ts +5 -1
- package/src/sdk/bus/chat-daemon-cli.ts +4 -39
- package/src/sdk/bus/chat-daemon-control.ts +7 -3
- package/src/sdk/bus/config.ts +18 -2
- package/src/sdk/bus/index.ts +661 -126
- package/src/sdk/bus/kind-aware-reconciliation.ts +148 -73
- package/src/sdk/bus/lifecycle-commands.ts +4 -1
- package/src/sdk/bus/lifecycle-control-runtime.ts +403 -88
- package/src/sdk/bus/reconciliation-store.ts +95 -16
- package/src/sdk/bus/rich-draft.ts +1 -1
- package/src/sdk/bus/rich-render.ts +2 -2
- package/src/sdk/bus/telegram-adoption-intent.ts +574 -0
- package/src/sdk/bus/telegram-cli.ts +33 -8
- package/src/sdk/bus/telegram-daemon-cli.ts +1 -0
- package/src/sdk/bus/telegram-daemon-contract.ts +12 -4
- package/src/sdk/bus/telegram-daemon.ts +916 -23
- package/src/sdk/bus/telegram-reference.ts +40 -3
- package/src/sdk/bus/threaded-render.ts +48 -3
- package/src/sdk/bus/topic-registry.ts +20 -0
- package/src/sdk/host/query/revision-store.ts +36 -2
- package/src/sdk/host/reverse-leases.ts +44 -7
- package/src/sdk/prompt-status.ts +16 -3
- package/src/sdk/protocol/operation-inventory.generated.json +33 -0
- package/src/sdk/session.ts +89 -9
- package/src/session/agent-session.ts +844 -173
- package/src/session/artifacts.ts +22 -0
- package/src/session/auth-broker-config.ts +10 -3
- package/src/session/blob-store.ts +915 -24
- package/src/session/internal/managed-session-scope.ts +200 -111
- package/src/session/internal/managed-session-storage.ts +492 -62
- package/src/session/session-manager.ts +1614 -435
- package/src/session/session-storage.ts +38 -45
- package/src/session/streaming-output.ts +238 -68
- package/src/setup/credential-import.ts +75 -13
- package/src/skill-state/active-state.ts +32 -0
- package/src/skill-state/workflow-hud.ts +17 -0
- package/src/skill-state/workflow-mutation-guard.ts +496 -2
- package/src/task/executor.ts +122 -39
- package/src/task/index.ts +280 -45
- package/src/task/output-manager.ts +24 -14
- package/src/task/provider-retry-status.ts +16 -0
- package/src/task/receipt.ts +34 -8
- package/src/task/render.ts +6 -2
- package/src/task/types.ts +108 -0
- package/src/tools/ask-answer-registry.ts +12 -6
- package/src/tools/ask.ts +86 -55
- package/src/tools/ast-edit.ts +1 -0
- package/src/tools/ast-grep.ts +1 -0
- package/src/tools/bash-interactive.ts +8 -3
- package/src/tools/bash.ts +503 -60
- package/src/tools/find.ts +1 -0
- package/src/tools/image-gen.ts +29 -5
- package/src/tools/index.ts +6 -0
- package/src/tools/output-meta.ts +108 -14
- package/src/tools/path-utils.ts +3 -2
- package/src/tools/read.ts +18 -0
- package/src/tools/review.ts +5 -7
- package/src/tools/search.ts +1 -0
- package/src/tools/subagent-render.ts +6 -2
- package/src/tools/subagent.ts +6 -0
- package/src/tools/telegram-send.ts +6 -3
- package/src/utils/gh.ts +39 -0
- package/src/utils/image-resize.ts +3 -0
- package/src/web/search/providers/anthropic.ts +4 -4
- package/src/web/search/providers/kimi.ts +15 -5
- package/src/web/search/providers/searxng.ts +47 -4
- package/src/web/search/providers/xai.ts +7 -2
- package/dist/types/gjc-runtime/ultragoal-pipeline.d.ts +0 -23
- package/src/defaults/gjc/skills/ultragoal/pipeline-validation-contracts.md +0 -30
- package/src/gjc-runtime/ultragoal-pipeline.ts +0 -306
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
|
|
3
|
+
import * as fs from "node:fs/promises";
|
|
4
|
+
import * as os from "node:os";
|
|
5
|
+
import * as path from "node:path";
|
|
6
|
+
|
|
7
|
+
export const ACPX_VERSION = "0.13.0";
|
|
8
|
+
export const ACPX_GIT_HEAD = "47dc1c56b20da3c248a4a1b5c5106f52e65e6594";
|
|
9
|
+
/** `conformance/profiles/acp-core-v1.json#required_cases` at the pinned commit. */
|
|
10
|
+
export const ACP_CORE_V1_CASE_IDS = [
|
|
11
|
+
"acp.v1.initialize.handshake",
|
|
12
|
+
"acp.v1.session.new.basic",
|
|
13
|
+
"acp.v1.session.prompt.single_turn",
|
|
14
|
+
"acp.v1.session.update.termination",
|
|
15
|
+
"acp.v1.session.cancel.in_flight",
|
|
16
|
+
"acp.v1.session.cancel.idle",
|
|
17
|
+
"acp.v1.session.prompt.multi_turn",
|
|
18
|
+
"acp.v1.errors.invalid_params",
|
|
19
|
+
"acp.v1.errors.invalid_prompt_session_type",
|
|
20
|
+
"acp.v1.errors.permission_denied",
|
|
21
|
+
"acp.v1.errors.permission_denied.write",
|
|
22
|
+
"acp.v1.errors.unknown_session",
|
|
23
|
+
"acp.v1.session.prompt.echo_empty",
|
|
24
|
+
"acp.v1.session.prompt.unrecognized",
|
|
25
|
+
"acp.v1.errors.invalid_params.cwd_null",
|
|
26
|
+
"acp.v1.session.prompt.structured_blocks",
|
|
27
|
+
"acp.v1.permissions.read.approved",
|
|
28
|
+
"acp.v1.permissions.write.approved",
|
|
29
|
+
"acp.v1.session.prompt.background_completion",
|
|
30
|
+
"acp.v1.session.cancel.followup_prompt",
|
|
31
|
+
"acp.v1.session.prompt.post_success_drain",
|
|
32
|
+
] as const;
|
|
33
|
+
|
|
34
|
+
export type AcpxMetadata = { version: string; gitHead?: string };
|
|
35
|
+
export type AcpxCheckout = { root: string; head: string };
|
|
36
|
+
export type CaseResult = { id: string; passed: boolean; [key: string]: unknown };
|
|
37
|
+
export type UpstreamReport = {
|
|
38
|
+
profileId: string;
|
|
39
|
+
results: CaseResult[];
|
|
40
|
+
totals?: { cases: number; passed: number; failed: number };
|
|
41
|
+
[key: string]: unknown;
|
|
42
|
+
};
|
|
43
|
+
export type ConformanceReport = {
|
|
44
|
+
command: string[];
|
|
45
|
+
cwd: string;
|
|
46
|
+
gjc: { commit: string; dirty: boolean };
|
|
47
|
+
acpx: { version: string; gitHead: string };
|
|
48
|
+
profile: string;
|
|
49
|
+
agentCommand: string;
|
|
50
|
+
matrix: CaseResult[];
|
|
51
|
+
totals: { cases: number; passed: number; failed: number };
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export interface RunAcpConformanceOptions {
|
|
55
|
+
agentCommand: string;
|
|
56
|
+
reportPath: string;
|
|
57
|
+
format?: "json";
|
|
58
|
+
cwd?: string;
|
|
59
|
+
profile?: string;
|
|
60
|
+
fetchMetadata?: () => Promise<AcpxMetadata>;
|
|
61
|
+
checkout?: () => Promise<AcpxCheckout>;
|
|
62
|
+
runRunner?: (options: { command: string[]; cwd: string; sessionCwd?: string }) => Promise<void>;
|
|
63
|
+
readReport?: (reportPath: string) => Promise<unknown>;
|
|
64
|
+
writeReport?: (reportPath: string, report: ConformanceReport) => Promise<void>;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function assertProvenance(metadata: AcpxMetadata, checkout: AcpxCheckout): void {
|
|
68
|
+
if (metadata.version !== ACPX_VERSION)
|
|
69
|
+
throw new Error(`Expected acpx@${ACPX_VERSION}; received ${metadata.version}.`);
|
|
70
|
+
if (metadata.gitHead !== ACPX_GIT_HEAD)
|
|
71
|
+
throw new Error(`Expected acpx gitHead ${ACPX_GIT_HEAD}; received ${metadata.gitHead ?? "missing"}.`);
|
|
72
|
+
if (checkout.head !== ACPX_GIT_HEAD)
|
|
73
|
+
throw new Error(`Expected source checkout HEAD ${ACPX_GIT_HEAD}; received ${checkout.head}.`);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function validateResults(report: unknown, profile: string): CaseResult[] {
|
|
77
|
+
if (!report || typeof report !== "object") throw new Error("Conformance runner report is missing or malformed.");
|
|
78
|
+
const value = report as Partial<UpstreamReport>;
|
|
79
|
+
if (value.profileId !== profile || !Array.isArray(value.results))
|
|
80
|
+
throw new Error("Conformance runner report is missing profileId or results.");
|
|
81
|
+
const expected = new Set<string>(ACP_CORE_V1_CASE_IDS);
|
|
82
|
+
const seen = new Set<string>();
|
|
83
|
+
for (const result of value.results) {
|
|
84
|
+
if (!result || typeof result.id !== "string" || typeof result.passed !== "boolean")
|
|
85
|
+
throw new Error("Conformance runner report contains a malformed case result.");
|
|
86
|
+
if (seen.has(result.id)) throw new Error(`Conformance runner report has duplicate case ID: ${result.id}.`);
|
|
87
|
+
seen.add(result.id);
|
|
88
|
+
if (!expected.has(result.id)) throw new Error(`Conformance runner report has unexpected case ID: ${result.id}.`);
|
|
89
|
+
if (!result.passed) throw new Error(`Conformance case failed: ${result.id}.`);
|
|
90
|
+
}
|
|
91
|
+
for (const id of ACP_CORE_V1_CASE_IDS)
|
|
92
|
+
if (!seen.has(id)) throw new Error(`Conformance runner report is missing required case ID: ${id}.`);
|
|
93
|
+
return value.results;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Runs the pinned upstream conformance suite against a real ACP agent command.
|
|
98
|
+
*
|
|
99
|
+
* `--cwd` must be a real path, not one reached through a symlink (macOS `/tmp` is a
|
|
100
|
+
* link to `/private/tmp`): the upstream client enforces its session cwd root against
|
|
101
|
+
* the resolved path, so a symlinked workspace fails the client-authority cases.
|
|
102
|
+
*/
|
|
103
|
+
export async function runAcpConformance(options: RunAcpConformanceOptions): Promise<ConformanceReport> {
|
|
104
|
+
const profile = options.profile ?? "acp-core-v1";
|
|
105
|
+
const requestedCwd = options.cwd ?? process.cwd();
|
|
106
|
+
const resolvedCwd = await fs.realpath(requestedCwd).catch(() => requestedCwd);
|
|
107
|
+
if (path.resolve(requestedCwd) !== resolvedCwd)
|
|
108
|
+
throw new Error(
|
|
109
|
+
`--cwd must be a real path; ${requestedCwd} resolves to ${resolvedCwd} and the client rejects paths outside its session cwd root.`,
|
|
110
|
+
);
|
|
111
|
+
if (profile !== "acp-core-v1") throw new Error(`Unsupported conformance profile: ${profile}.`);
|
|
112
|
+
// The upstream runner spawns the agent from its own checkout, so any repo-relative
|
|
113
|
+
// path in the command must be resolved against this repository first.
|
|
114
|
+
const agentCommand = options.agentCommand
|
|
115
|
+
.split(" ")
|
|
116
|
+
.map(token => (token.endsWith(".ts") && !path.isAbsolute(token) ? path.resolve(token) : token))
|
|
117
|
+
.join(" ");
|
|
118
|
+
const metadata = await (options.fetchMetadata ?? fetchNpmMetadata)();
|
|
119
|
+
const checkout = await (options.checkout ?? checkoutAcpxSource)();
|
|
120
|
+
assertProvenance(metadata, checkout);
|
|
121
|
+
const upstreamReportPath = path.join(os.tmpdir(), `gjc-acpx-${crypto.randomUUID()}.json`);
|
|
122
|
+
const command = [
|
|
123
|
+
"bun",
|
|
124
|
+
path.join(checkout.root, "conformance", "runner", "run.ts"),
|
|
125
|
+
"--profile",
|
|
126
|
+
path.join(checkout.root, "conformance", "profiles", `${profile}.json`),
|
|
127
|
+
"--cases-dir",
|
|
128
|
+
path.join(checkout.root, "conformance", "cases"),
|
|
129
|
+
"--agent-command",
|
|
130
|
+
agentCommand,
|
|
131
|
+
"--format",
|
|
132
|
+
"json",
|
|
133
|
+
"--report",
|
|
134
|
+
upstreamReportPath,
|
|
135
|
+
"--cwd",
|
|
136
|
+
options.cwd ?? process.cwd(),
|
|
137
|
+
];
|
|
138
|
+
// A failing run still writes its report. Preserve that matrix as CI evidence before
|
|
139
|
+
// surfacing the failure, so the uploaded artifact explains what actually broke.
|
|
140
|
+
let runnerFailure: unknown;
|
|
141
|
+
try {
|
|
142
|
+
await (options.runRunner ?? runRunner)({ command, cwd: checkout.root, sessionCwd: options.cwd ?? process.cwd() });
|
|
143
|
+
} catch (error) {
|
|
144
|
+
runnerFailure = error;
|
|
145
|
+
}
|
|
146
|
+
const upstream = await (options.readReport ?? readReport)(upstreamReportPath).catch(() => undefined);
|
|
147
|
+
if (runnerFailure !== undefined) {
|
|
148
|
+
if (upstream && typeof upstream === "object") {
|
|
149
|
+
const failed = upstream as Partial<UpstreamReport>;
|
|
150
|
+
await (options.writeReport ?? writeReport)(options.reportPath, {
|
|
151
|
+
command,
|
|
152
|
+
cwd: options.cwd ?? process.cwd(),
|
|
153
|
+
gjc: gjcIdentity(),
|
|
154
|
+
acpx: { version: metadata.version, gitHead: metadata.gitHead as string },
|
|
155
|
+
profile,
|
|
156
|
+
agentCommand,
|
|
157
|
+
matrix: Array.isArray(failed.results) ? failed.results : [],
|
|
158
|
+
totals: failed.totals ?? { cases: 0, passed: 0, failed: 0 },
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
throw runnerFailure;
|
|
162
|
+
}
|
|
163
|
+
const matrix = validateResults(upstream, profile);
|
|
164
|
+
const report: ConformanceReport = {
|
|
165
|
+
command,
|
|
166
|
+
cwd: options.cwd ?? process.cwd(),
|
|
167
|
+
gjc: gjcIdentity(),
|
|
168
|
+
acpx: { version: metadata.version, gitHead: metadata.gitHead! },
|
|
169
|
+
profile,
|
|
170
|
+
agentCommand,
|
|
171
|
+
matrix,
|
|
172
|
+
totals: { cases: matrix.length, passed: matrix.length, failed: 0 },
|
|
173
|
+
};
|
|
174
|
+
await (options.writeReport ?? writeReport)(options.reportPath, report);
|
|
175
|
+
return report;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function gjcIdentity(): { commit: string; dirty: boolean } {
|
|
179
|
+
try {
|
|
180
|
+
const commit = Bun.spawnSync(["git", "rev-parse", "HEAD"]).stdout.toString().trim();
|
|
181
|
+
const dirty = Bun.spawnSync(["git", "status", "--porcelain"]).stdout.toString().trim().length > 0;
|
|
182
|
+
return { commit, dirty };
|
|
183
|
+
} catch {
|
|
184
|
+
return { commit: "unknown", dirty: true };
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
async function fetchNpmMetadata(): Promise<AcpxMetadata> {
|
|
189
|
+
const response = await fetch(`https://registry.npmjs.org/acpx/${ACPX_VERSION}`);
|
|
190
|
+
if (!response.ok)
|
|
191
|
+
throw new Error(`Unable to resolve acpx@${ACPX_VERSION}: ${response.status} ${response.statusText}.`);
|
|
192
|
+
return (await response.json()) as AcpxMetadata;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
async function commandOutput(command: string[], cwd?: string, env?: Record<string, string>): Promise<string> {
|
|
196
|
+
const child = Bun.spawn(command, {
|
|
197
|
+
cwd,
|
|
198
|
+
stdout: "pipe",
|
|
199
|
+
stderr: "pipe",
|
|
200
|
+
...(env ? { env: { ...process.env, ...env } } : {}),
|
|
201
|
+
});
|
|
202
|
+
const [code, stdout, stderr] = await Promise.all([
|
|
203
|
+
child.exited,
|
|
204
|
+
new Response(child.stdout).text(),
|
|
205
|
+
new Response(child.stderr).text(),
|
|
206
|
+
]);
|
|
207
|
+
if (code !== 0)
|
|
208
|
+
throw new Error(`${command.join(" ")} failed (exit ${code}).\nstdout:\n${stdout}\nstderr:\n${stderr}`);
|
|
209
|
+
return stdout.trim();
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
async function checkoutAcpxSource(): Promise<AcpxCheckout> {
|
|
213
|
+
const root = path.join(os.homedir(), ".cache", "gjc", "acpx", ACPX_GIT_HEAD);
|
|
214
|
+
try {
|
|
215
|
+
await fs.access(path.join(root, ".git"));
|
|
216
|
+
} catch {
|
|
217
|
+
await fs.mkdir(path.dirname(root), { recursive: true });
|
|
218
|
+
await commandOutput(["git", "clone", "https://github.com/openclaw/acpx.git", root]);
|
|
219
|
+
}
|
|
220
|
+
await commandOutput(["git", "fetch", "--depth", "1", "origin", ACPX_GIT_HEAD], root);
|
|
221
|
+
await commandOutput(["git", "checkout", "--detach", ACPX_GIT_HEAD], root);
|
|
222
|
+
// A reused cache must match the pin exactly; local edits would invalidate the
|
|
223
|
+
// provenance the report claims.
|
|
224
|
+
await commandOutput(["git", "reset", "--hard", ACPX_GIT_HEAD], root);
|
|
225
|
+
await commandOutput(["git", "clean", "-fdx", "--exclude=node_modules"], root);
|
|
226
|
+
const status = await commandOutput(["git", "status", "--porcelain"], root);
|
|
227
|
+
if (status.length > 0) throw new Error(`acpx source checkout is not clean at ${ACPX_GIT_HEAD}.`);
|
|
228
|
+
// The upstream runner resolves its own imports (`@agentclientprotocol/sdk`, `zod`)
|
|
229
|
+
// from this checkout, not from the gjc workspace, so the pin has to be installed
|
|
230
|
+
// before it can run. `git clean` deliberately preserves node_modules so a warm
|
|
231
|
+
// cache skips the reinstall.
|
|
232
|
+
await commandOutput(["bun", "install", "--no-save", "--ignore-scripts"], root);
|
|
233
|
+
return { root, head: await commandOutput(["git", "rev-parse", "HEAD"], root) };
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
async function runRunner(options: { command: string[]; cwd: string; sessionCwd?: string }): Promise<void> {
|
|
237
|
+
// The fixture seeds the corpus scratch workspace; the runner only creates it.
|
|
238
|
+
await commandOutput(
|
|
239
|
+
options.command,
|
|
240
|
+
options.cwd,
|
|
241
|
+
options.sessionCwd ? { GJC_ACP_CONFORMANCE_CWD: options.sessionCwd } : undefined,
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
async function readReport(reportPath: string): Promise<unknown> {
|
|
245
|
+
return await Bun.file(reportPath).json();
|
|
246
|
+
}
|
|
247
|
+
async function writeReport(reportPath: string, report: ConformanceReport): Promise<void> {
|
|
248
|
+
await fs.mkdir(path.dirname(reportPath), { recursive: true });
|
|
249
|
+
await Bun.write(reportPath, `${JSON.stringify(report, null, 2)}\n`);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function parseCli(argv: string[]): RunAcpConformanceOptions {
|
|
253
|
+
const values: Partial<RunAcpConformanceOptions> = { format: "json", profile: "acp-core-v1" };
|
|
254
|
+
for (let index = 0; index < argv.length; index++) {
|
|
255
|
+
const flag = argv[index];
|
|
256
|
+
if (flag === "--format") {
|
|
257
|
+
if (argv[++index] !== "json") throw new Error("--format must be json.");
|
|
258
|
+
continue;
|
|
259
|
+
}
|
|
260
|
+
if (flag === "--agent-command") {
|
|
261
|
+
values.agentCommand = argv[++index];
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
if (flag === "--report") {
|
|
265
|
+
values.reportPath = argv[++index];
|
|
266
|
+
continue;
|
|
267
|
+
}
|
|
268
|
+
if (flag === "--cwd") {
|
|
269
|
+
values.cwd = argv[++index];
|
|
270
|
+
continue;
|
|
271
|
+
}
|
|
272
|
+
if (flag === "--profile") {
|
|
273
|
+
values.profile = argv[++index];
|
|
274
|
+
continue;
|
|
275
|
+
}
|
|
276
|
+
throw new Error(`Unknown argument: ${flag}.`);
|
|
277
|
+
}
|
|
278
|
+
if (!values.agentCommand || !values.reportPath)
|
|
279
|
+
throw new Error(
|
|
280
|
+
"Usage: conformance:run --agent-command <command> --format json --report <path> [--cwd <path>] [--profile acp-core-v1]",
|
|
281
|
+
);
|
|
282
|
+
return values as RunAcpConformanceOptions;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
if (import.meta.main) {
|
|
286
|
+
runAcpConformance(parseCli(process.argv.slice(2))).catch(error => {
|
|
287
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
|
|
288
|
+
process.exitCode = 1;
|
|
289
|
+
});
|
|
290
|
+
}
|
package/src/async/job-manager.ts
CHANGED
|
@@ -32,6 +32,8 @@ export interface AsyncJob {
|
|
|
32
32
|
promise: Promise<void>;
|
|
33
33
|
resultText?: string;
|
|
34
34
|
errorText?: string;
|
|
35
|
+
/** Safe, bounded cause when session setup failed before the LLM began work. */
|
|
36
|
+
setupFailureSummary?: string;
|
|
35
37
|
metadata?: AsyncJobMetadata;
|
|
36
38
|
/**
|
|
37
39
|
* Registry id of the agent that registered the job (e.g. "0-Main",
|
|
@@ -69,9 +71,12 @@ export interface AsyncJobMetadata {
|
|
|
69
71
|
* non-terminal and non-delivering: the run suspended at a safe boundary and the
|
|
70
72
|
* subagent can be resumed from its persisted sessionFile. `completed` always
|
|
71
73
|
* wins a race with a late pause because the run returns it once it has actually
|
|
72
|
-
* finished.
|
|
74
|
+
* finished. A `failed` outcome retains a safe setup diagnostic for receipt rendering.
|
|
73
75
|
*/
|
|
74
|
-
export type SubagentRunOutcome =
|
|
76
|
+
export type SubagentRunOutcome =
|
|
77
|
+
| { kind: "completed"; text: string }
|
|
78
|
+
| { kind: "failed"; text: string; setupFailureSummary?: string }
|
|
79
|
+
| { kind: "paused"; note?: string };
|
|
75
80
|
|
|
76
81
|
/** Canonical lifecycle of a subagent across pause/resume cycles. */
|
|
77
82
|
export type SubagentLifecycle = "running" | "paused" | "queued" | "completed" | "failed" | "cancelled";
|
|
@@ -146,7 +151,11 @@ export interface SubagentRecord {
|
|
|
146
151
|
historicalJobIds: string[];
|
|
147
152
|
status: SubagentLifecycle;
|
|
148
153
|
sessionFile: string | null;
|
|
149
|
-
/**
|
|
154
|
+
/**
|
|
155
|
+
* Explicit veto, not a complete availability result. False always denies;
|
|
156
|
+
* true still requires an owner-compatible descriptor or non-blank session
|
|
157
|
+
* file, followed by a separately available runner (`no_runner` otherwise).
|
|
158
|
+
*/
|
|
150
159
|
resumable: boolean;
|
|
151
160
|
queued?: { ownerId?: string; seq: number; message?: string; createdAt: number };
|
|
152
161
|
/** Resolved model the subagent was asked to use, e.g. "openai-codex/gpt-5.5". */
|
|
@@ -176,6 +185,22 @@ function sessionFileFromResumeDescriptorData(data: unknown): string | null {
|
|
|
176
185
|
return typeof sessionFile === "string" && sessionFile.trim().length > 0 ? sessionFile : null;
|
|
177
186
|
}
|
|
178
187
|
|
|
188
|
+
/**
|
|
189
|
+
* Derive retained context from an already owner-compatible descriptor or a
|
|
190
|
+
* legacy session file. A descriptor is sufficient even when `sessionFile` is
|
|
191
|
+
* null because the descriptor is the payload the resume runner consumes.
|
|
192
|
+
*/
|
|
193
|
+
function hasRetainedResumeContext(input: {
|
|
194
|
+
resumable: boolean;
|
|
195
|
+
sessionFile: string | null;
|
|
196
|
+
descriptor: ResumeDescriptor | undefined;
|
|
197
|
+
}): boolean {
|
|
198
|
+
if (!input.resumable) return false;
|
|
199
|
+
return (
|
|
200
|
+
input.descriptor !== undefined || (typeof input.sessionFile === "string" && input.sessionFile.trim().length > 0)
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
|
|
179
204
|
/** A pending resume awaiting a free concurrency slot. */
|
|
180
205
|
interface ResumeQueueEntry {
|
|
181
206
|
subagentId: string;
|
|
@@ -499,21 +524,34 @@ export class AsyncJobManager {
|
|
|
499
524
|
typeof result === "string" ? { kind: "completed", text: result } : result;
|
|
500
525
|
|
|
501
526
|
if (job.status === "cancelled") {
|
|
502
|
-
job.resultText = outcome.kind === "
|
|
527
|
+
job.resultText = outcome.kind === "paused" ? outcome.note : outcome.text;
|
|
528
|
+
this.#markRecordTerminal(id, "cancelled");
|
|
503
529
|
this.#runLifecycle(id, "terminal", job);
|
|
504
530
|
this.#scheduleEviction(id);
|
|
505
|
-
this.#markRecordTerminal(id, "cancelled");
|
|
506
531
|
this.#drainResumeQueue();
|
|
507
532
|
return;
|
|
508
533
|
}
|
|
509
534
|
if (outcome.kind === "paused") {
|
|
510
535
|
// Sole canonical writer of the running -> paused transition. No
|
|
511
536
|
// delivery and no eviction scheduling: a paused subagent stays
|
|
512
|
-
// listed and resumable from its
|
|
537
|
+
// listed and resumable from its retained resume context.
|
|
513
538
|
job.status = "paused";
|
|
514
539
|
this.#freezeEndTime(job);
|
|
515
540
|
if (outcome.note) job.resultText = outcome.note;
|
|
516
541
|
this.#markRecordPaused(id);
|
|
542
|
+
this.#notifyChange();
|
|
543
|
+
this.#drainResumeQueue();
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
546
|
+
if (outcome.kind === "failed") {
|
|
547
|
+
job.status = "failed";
|
|
548
|
+
job.setupFailureSummary = outcome.setupFailureSummary;
|
|
549
|
+
this.#freezeEndTime(job);
|
|
550
|
+
job.errorText = outcome.text;
|
|
551
|
+
this.#markRecordTerminal(id, "failed");
|
|
552
|
+
this.#enqueueDelivery(id, outcome.text);
|
|
553
|
+
this.#runLifecycle(id, "terminal", job);
|
|
554
|
+
this.#scheduleEviction(id);
|
|
517
555
|
this.#drainResumeQueue();
|
|
518
556
|
return;
|
|
519
557
|
}
|
|
@@ -521,28 +559,28 @@ export class AsyncJobManager {
|
|
|
521
559
|
job.status = "completed";
|
|
522
560
|
this.#freezeEndTime(job);
|
|
523
561
|
job.resultText = outcome.text;
|
|
562
|
+
this.#markRecordTerminal(id, "completed");
|
|
524
563
|
this.#enqueueDelivery(id, outcome.text);
|
|
525
564
|
this.#runLifecycle(id, "terminal", job);
|
|
526
565
|
this.#scheduleEviction(id);
|
|
527
|
-
this.#markRecordTerminal(id, "completed");
|
|
528
566
|
this.#drainResumeQueue();
|
|
529
567
|
} catch (error) {
|
|
530
568
|
if (job.status === "cancelled") {
|
|
531
569
|
job.errorText = error instanceof Error ? error.message : String(error);
|
|
570
|
+
this.#markRecordTerminal(id, "cancelled");
|
|
532
571
|
this.#runLifecycle(id, "terminal", job);
|
|
533
572
|
this.#scheduleEviction(id);
|
|
534
|
-
this.#markRecordTerminal(id, "cancelled");
|
|
535
573
|
this.#drainResumeQueue();
|
|
536
574
|
return;
|
|
537
575
|
}
|
|
538
|
-
this.#runLifecycle(id, "terminal", job);
|
|
539
576
|
const errorText = error instanceof Error ? error.message : String(error);
|
|
540
577
|
job.status = "failed";
|
|
541
578
|
this.#freezeEndTime(job);
|
|
542
579
|
job.errorText = errorText;
|
|
580
|
+
this.#markRecordTerminal(id, "failed");
|
|
543
581
|
this.#enqueueDelivery(id, errorText);
|
|
582
|
+
this.#runLifecycle(id, "terminal", job);
|
|
544
583
|
this.#scheduleEviction(id);
|
|
545
|
-
this.#markRecordTerminal(id, "failed");
|
|
546
584
|
this.#drainResumeQueue();
|
|
547
585
|
}
|
|
548
586
|
})();
|
|
@@ -562,20 +600,22 @@ export class AsyncJobManager {
|
|
|
562
600
|
if (!job) return false;
|
|
563
601
|
if (filter?.ownerId && job.ownerId !== filter.ownerId) return false;
|
|
564
602
|
if (job.status === "paused") {
|
|
565
|
-
this.#runLifecycle(id, "cancel");
|
|
566
603
|
// Paused jobs have no running promise to abort; transition directly.
|
|
567
604
|
// The session file is kept, so the record stays resumable by id.
|
|
568
605
|
job.status = "cancelled";
|
|
569
606
|
this.#markRecordTerminal(id, "cancelled");
|
|
607
|
+
this.#runLifecycle(id, "cancel");
|
|
570
608
|
this.#scheduleEviction(id);
|
|
571
609
|
this.#drainResumeQueue();
|
|
572
610
|
return true;
|
|
573
611
|
}
|
|
574
612
|
if (job.status !== "running") return false;
|
|
575
|
-
this.#runLifecycle(id, "cancel");
|
|
576
613
|
job.status = "cancelled";
|
|
577
614
|
this.#freezeEndTime(job);
|
|
615
|
+
this.#markRecordTerminal(id, "cancelled");
|
|
616
|
+
this.#runLifecycle(id, "cancel");
|
|
578
617
|
job.abortController.abort();
|
|
618
|
+
this.#notifyChange();
|
|
579
619
|
return true;
|
|
580
620
|
}
|
|
581
621
|
|
|
@@ -659,6 +699,7 @@ export class AsyncJobManager {
|
|
|
659
699
|
/** Register or replace the canonical record for a subagent. */
|
|
660
700
|
registerSubagentRecord(record: SubagentRecord): void {
|
|
661
701
|
this.#subagentRecords.set(record.subagentId, record);
|
|
702
|
+
this.#notifyChange();
|
|
662
703
|
}
|
|
663
704
|
|
|
664
705
|
/** Patch model metadata onto an existing subagent record (best-effort; no-op if unknown). */
|
|
@@ -684,7 +725,9 @@ export class AsyncJobManager {
|
|
|
684
725
|
historicalJobIds: [],
|
|
685
726
|
status: "completed",
|
|
686
727
|
sessionFile,
|
|
687
|
-
|
|
728
|
+
// The synthesized record copies this descriptor's owner, so the
|
|
729
|
+
// descriptor is owner-compatible with the record by construction.
|
|
730
|
+
resumable: hasRetainedResumeContext({ resumable: true, sessionFile, descriptor }),
|
|
688
731
|
};
|
|
689
732
|
this.#subagentRecords.set(record.subagentId, record);
|
|
690
733
|
return record;
|
|
@@ -765,13 +808,24 @@ export class AsyncJobManager {
|
|
|
765
808
|
}
|
|
766
809
|
|
|
767
810
|
/**
|
|
768
|
-
* Resolve
|
|
769
|
-
*
|
|
770
|
-
*
|
|
771
|
-
|
|
811
|
+
* Resolve a descriptor only when `ownerId` matches the record by strict
|
|
812
|
+
* equality. Two undefined owners match; a distinguishably foreign descriptor
|
|
813
|
+
* is treated as absent for eligibility, runner selection, and execution.
|
|
814
|
+
*/
|
|
815
|
+
#descriptorForRecord(rec: SubagentRecord): ResumeDescriptor | undefined {
|
|
816
|
+
const descriptor = this.#resumeDescriptors.get(rec.subagentId);
|
|
817
|
+
return descriptor !== undefined && descriptor.ownerId === rec.ownerId ? descriptor : undefined;
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
/**
|
|
821
|
+
* Resolve the resume runner for a record and its already owner-compatible
|
|
822
|
+
* descriptor. Per-descriptor authority is unavailable when the descriptor is foreign.
|
|
772
823
|
*/
|
|
773
|
-
#resolveResumeRunner(
|
|
774
|
-
return
|
|
824
|
+
#resolveResumeRunner(rec: SubagentRecord, descriptor: ResumeDescriptor | undefined): ResumeRunner | undefined {
|
|
825
|
+
return (
|
|
826
|
+
(descriptor !== undefined ? this.#descriptorResumeRunners.get(rec.subagentId) : undefined) ??
|
|
827
|
+
this.#resumeRunner
|
|
828
|
+
);
|
|
775
829
|
}
|
|
776
830
|
|
|
777
831
|
getResumeDescriptor(subagentId: string, filter?: AsyncJobFilter): ResumeDescriptor | undefined {
|
|
@@ -1045,7 +1099,11 @@ export class AsyncJobManager {
|
|
|
1045
1099
|
return { ok: true, status: rec.status };
|
|
1046
1100
|
}
|
|
1047
1101
|
|
|
1048
|
-
/**
|
|
1102
|
+
/**
|
|
1103
|
+
* Resume a non-running subagent from retained context: an owner-compatible
|
|
1104
|
+
* descriptor or a legacy session file. Workflow routing keeps `not_found`,
|
|
1105
|
+
* `context_unavailable`, `no_runner`, and `resume_failed` distinct.
|
|
1106
|
+
*/
|
|
1049
1107
|
resumeSubagent(
|
|
1050
1108
|
subagentId: string,
|
|
1051
1109
|
filter?: AsyncJobFilter,
|
|
@@ -1066,8 +1124,11 @@ export class AsyncJobManager {
|
|
|
1066
1124
|
}
|
|
1067
1125
|
return { ok: false, status: "queued", reason: "already_queued" };
|
|
1068
1126
|
}
|
|
1069
|
-
|
|
1070
|
-
if (!
|
|
1127
|
+
const descriptor = this.#descriptorForRecord(rec);
|
|
1128
|
+
if (!hasRetainedResumeContext({ resumable: rec.resumable, sessionFile: rec.sessionFile, descriptor })) {
|
|
1129
|
+
return { ok: false, reason: "context_unavailable" };
|
|
1130
|
+
}
|
|
1131
|
+
if (!this.#resolveResumeRunner(rec, descriptor)) return { ok: false, reason: "no_runner" };
|
|
1071
1132
|
if (this.getRunningJobs().length >= this.#maxRunningJobs) {
|
|
1072
1133
|
const seq = ++this.#resumeSeq;
|
|
1073
1134
|
rec.status = "queued";
|
|
@@ -1079,14 +1140,16 @@ export class AsyncJobManager {
|
|
|
1079
1140
|
message,
|
|
1080
1141
|
createdAt: rec.queued.createdAt,
|
|
1081
1142
|
});
|
|
1143
|
+
this.#notifyChange();
|
|
1082
1144
|
return { ok: true, queued: true, status: "queued" };
|
|
1083
1145
|
}
|
|
1084
|
-
return this.#startResume(rec, message);
|
|
1146
|
+
return this.#startResume(rec, message, descriptor);
|
|
1085
1147
|
}
|
|
1086
1148
|
|
|
1087
1149
|
#startResume(
|
|
1088
1150
|
rec: SubagentRecord,
|
|
1089
|
-
message
|
|
1151
|
+
message: string | undefined,
|
|
1152
|
+
descriptor: ResumeDescriptor | undefined,
|
|
1090
1153
|
): { ok: boolean; status?: SubagentLifecycle; jobId?: string; reason?: string } {
|
|
1091
1154
|
if (this.#isOwnerSubagentShutdownFenced(rec.ownerId)) {
|
|
1092
1155
|
return { ok: false, status: rec.status, reason: "owner_shutdown_in_progress" };
|
|
@@ -1095,13 +1158,14 @@ export class AsyncJobManager {
|
|
|
1095
1158
|
// Clear any retained progress from the previous run so a resumed subagent
|
|
1096
1159
|
// never renders the prior run's tool/output as live before it emits again.
|
|
1097
1160
|
this.#subagentProgress.delete(rec.subagentId);
|
|
1098
|
-
const runner = this.#resolveResumeRunner(rec
|
|
1099
|
-
const newJobId = runner?.(rec.subagentId, message,
|
|
1161
|
+
const runner = this.#resolveResumeRunner(rec, descriptor);
|
|
1162
|
+
const newJobId = runner?.(rec.subagentId, message, descriptor);
|
|
1100
1163
|
if (!newJobId) return { ok: false, reason: "resume_failed" };
|
|
1101
1164
|
if (prevJobId && prevJobId !== newJobId) rec.historicalJobIds.push(prevJobId);
|
|
1102
1165
|
rec.currentJobId = newJobId;
|
|
1103
1166
|
rec.status = this.#jobs.get(newJobId)?.status ?? "running";
|
|
1104
1167
|
rec.queued = undefined;
|
|
1168
|
+
this.#notifyChange();
|
|
1105
1169
|
return { ok: true, status: rec.status, jobId: newJobId };
|
|
1106
1170
|
}
|
|
1107
1171
|
|
|
@@ -1122,7 +1186,7 @@ export class AsyncJobManager {
|
|
|
1122
1186
|
continue;
|
|
1123
1187
|
}
|
|
1124
1188
|
try {
|
|
1125
|
-
const result = this.#startResume(rec, entry.message);
|
|
1189
|
+
const result = this.#startResume(rec, entry.message, this.#descriptorForRecord(rec));
|
|
1126
1190
|
if (result.reason === "owner_shutdown_in_progress") {
|
|
1127
1191
|
index += 1;
|
|
1128
1192
|
continue;
|
|
@@ -1144,16 +1208,15 @@ export class AsyncJobManager {
|
|
|
1144
1208
|
if (!rec) return false;
|
|
1145
1209
|
if (rec.status === "running" && rec.currentJobId) return this.cancel(rec.currentJobId, filter);
|
|
1146
1210
|
if (rec.status === "paused") {
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
this.#scheduleEviction(rec.currentJobId);
|
|
1152
|
-
}
|
|
1153
|
-
}
|
|
1211
|
+
const currentJobId = rec.currentJobId;
|
|
1212
|
+
const job = currentJobId ? this.#jobs.get(currentJobId) : undefined;
|
|
1213
|
+
const shouldScheduleEviction = job?.status === "paused";
|
|
1214
|
+
if (shouldScheduleEviction) job.status = "cancelled";
|
|
1154
1215
|
rec.status = "cancelled";
|
|
1155
1216
|
this.#liveHandles.delete(rec.subagentId);
|
|
1156
1217
|
this.#subagentProgress.delete(rec.subagentId);
|
|
1218
|
+
if (shouldScheduleEviction && currentJobId) this.#scheduleEviction(currentJobId);
|
|
1219
|
+
else this.#notifyChange();
|
|
1157
1220
|
this.#drainResumeQueue();
|
|
1158
1221
|
return true;
|
|
1159
1222
|
}
|
|
@@ -1163,6 +1226,7 @@ export class AsyncJobManager {
|
|
|
1163
1226
|
rec.status = "cancelled";
|
|
1164
1227
|
rec.queued = undefined;
|
|
1165
1228
|
this.#subagentProgress.delete(rec.subagentId);
|
|
1229
|
+
this.#notifyChange();
|
|
1166
1230
|
return true;
|
|
1167
1231
|
}
|
|
1168
1232
|
return false;
|
|
@@ -1413,11 +1477,7 @@ export class AsyncJobManager {
|
|
|
1413
1477
|
*/
|
|
1414
1478
|
cancelAll(filter?: AsyncJobFilter): void {
|
|
1415
1479
|
for (const job of this.getRunningJobs(filter)) {
|
|
1416
|
-
this
|
|
1417
|
-
job.status = "cancelled";
|
|
1418
|
-
this.#freezeEndTime(job);
|
|
1419
|
-
job.abortController.abort();
|
|
1420
|
-
this.#scheduleEviction(job.id);
|
|
1480
|
+
if (this.cancel(job.id, filter)) this.#scheduleEviction(job.id);
|
|
1421
1481
|
}
|
|
1422
1482
|
}
|
|
1423
1483
|
|
package/src/cli/fast-help.ts
CHANGED
|
@@ -93,7 +93,7 @@ Available Tools (default-enabled unless noted):
|
|
|
93
93
|
bash - Execute bash commands
|
|
94
94
|
edit - Edit files with find/replace
|
|
95
95
|
write - Write files (creates/overwrites)
|
|
96
|
-
|
|
96
|
+
search - Search file contents
|
|
97
97
|
find - Find files by glob pattern
|
|
98
98
|
lsp - Language server protocol (code intelligence)
|
|
99
99
|
python - Execute Python code (requires: ${APP_NAME} setup python)
|