@gajae-code/coding-agent 0.12.0 → 0.12.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/CHANGELOG.md +82 -1
- package/dist/types/async/job-manager.d.ts +17 -3
- package/dist/types/config/settings-schema.d.ts +54 -0
- 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-runtime.d.ts +18 -0
- 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 +31 -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/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/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 +4 -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 +11 -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 +38 -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 +8 -1
- 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 +3 -1
- package/dist/types/task/provider-retry-status.d.ts +8 -0
- package/dist/types/task/receipt.d.ts +7 -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 +4 -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/generate-sdk-operation-inventory.ts +3 -0
- package/scripts/resident-memory-bench.ts +1341 -0
- package/scripts/run-acp-conformance.ts +285 -0
- package/src/async/job-manager.ts +78 -20
- 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 +51 -0
- package/src/coordinator-mcp/question-gate-codec.ts +1 -1
- package/src/coordinator-mcp/server.ts +5 -12
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +1 -0
- package/src/defaults/gjc/skills/ralplan/SKILL.md +19 -16
- package/src/defaults/gjc/skills/team/SKILL.md +2 -0
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +37 -24
- 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/managed-owner-admission.ts +1 -1
- package/src/gjc-runtime/ralplan-runtime.ts +411 -17
- 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-runtime.ts +401 -611
- package/src/gjc-runtime/workflow-manifest.generated.json +8 -99
- package/src/gjc-runtime/workflow-manifest.ts +4 -68
- package/src/internal-urls/docs-index.generated.ts +15 -15
- package/src/modes/DESIGN.md +51 -0
- package/src/modes/acp/acp-agent.ts +298 -186
- package/src/modes/components/gjc-bundle-settings.ts +508 -0
- 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 -14
- package/src/modes/components/transcript-viewer-overlay.ts +99 -24
- package/src/modes/controllers/command-controller.ts +4 -0
- package/src/modes/controllers/extension-ui-controller.ts +7 -0
- package/src/modes/controllers/selector-controller.ts +45 -0
- package/src/modes/runtime-init.ts +3 -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/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 +6 -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 +11 -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 +22 -0
- package/src/sdk/session.ts +88 -9
- package/src/session/agent-session.ts +822 -172
- 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 +44 -6
- package/src/session/internal/managed-session-storage.ts +48 -6
- package/src/session/session-manager.ts +1614 -435
- package/src/session/session-storage.ts +9 -1
- 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 +23 -14
- package/src/task/provider-retry-status.ts +16 -0
- package/src/task/receipt.ts +23 -7
- 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 +4 -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,285 @@
|
|
|
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
|
+
return { root, head: await commandOutput(["git", "rev-parse", "HEAD"], root) };
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
async function runRunner(options: { command: string[]; cwd: string; sessionCwd?: string }): Promise<void> {
|
|
232
|
+
// The fixture seeds the corpus scratch workspace; the runner only creates it.
|
|
233
|
+
await commandOutput(
|
|
234
|
+
options.command,
|
|
235
|
+
options.cwd,
|
|
236
|
+
options.sessionCwd ? { GJC_ACP_CONFORMANCE_CWD: options.sessionCwd } : undefined,
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
async function readReport(reportPath: string): Promise<unknown> {
|
|
240
|
+
return await Bun.file(reportPath).json();
|
|
241
|
+
}
|
|
242
|
+
async function writeReport(reportPath: string, report: ConformanceReport): Promise<void> {
|
|
243
|
+
await fs.mkdir(path.dirname(reportPath), { recursive: true });
|
|
244
|
+
await Bun.write(reportPath, `${JSON.stringify(report, null, 2)}\n`);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function parseCli(argv: string[]): RunAcpConformanceOptions {
|
|
248
|
+
const values: Partial<RunAcpConformanceOptions> = { format: "json", profile: "acp-core-v1" };
|
|
249
|
+
for (let index = 0; index < argv.length; index++) {
|
|
250
|
+
const flag = argv[index];
|
|
251
|
+
if (flag === "--format") {
|
|
252
|
+
if (argv[++index] !== "json") throw new Error("--format must be json.");
|
|
253
|
+
continue;
|
|
254
|
+
}
|
|
255
|
+
if (flag === "--agent-command") {
|
|
256
|
+
values.agentCommand = argv[++index];
|
|
257
|
+
continue;
|
|
258
|
+
}
|
|
259
|
+
if (flag === "--report") {
|
|
260
|
+
values.reportPath = argv[++index];
|
|
261
|
+
continue;
|
|
262
|
+
}
|
|
263
|
+
if (flag === "--cwd") {
|
|
264
|
+
values.cwd = argv[++index];
|
|
265
|
+
continue;
|
|
266
|
+
}
|
|
267
|
+
if (flag === "--profile") {
|
|
268
|
+
values.profile = argv[++index];
|
|
269
|
+
continue;
|
|
270
|
+
}
|
|
271
|
+
throw new Error(`Unknown argument: ${flag}.`);
|
|
272
|
+
}
|
|
273
|
+
if (!values.agentCommand || !values.reportPath)
|
|
274
|
+
throw new Error(
|
|
275
|
+
"Usage: conformance:run --agent-command <command> --format json --report <path> [--cwd <path>] [--profile acp-core-v1]",
|
|
276
|
+
);
|
|
277
|
+
return values as RunAcpConformanceOptions;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if (import.meta.main) {
|
|
281
|
+
runAcpConformance(parseCli(process.argv.slice(2))).catch(error => {
|
|
282
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
|
|
283
|
+
process.exitCode = 1;
|
|
284
|
+
});
|
|
285
|
+
}
|
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,7 +524,7 @@ 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;
|
|
503
528
|
this.#runLifecycle(id, "terminal", job);
|
|
504
529
|
this.#scheduleEviction(id);
|
|
505
530
|
this.#markRecordTerminal(id, "cancelled");
|
|
@@ -509,7 +534,7 @@ export class AsyncJobManager {
|
|
|
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;
|
|
@@ -517,6 +542,18 @@ export class AsyncJobManager {
|
|
|
517
542
|
this.#drainResumeQueue();
|
|
518
543
|
return;
|
|
519
544
|
}
|
|
545
|
+
if (outcome.kind === "failed") {
|
|
546
|
+
job.status = "failed";
|
|
547
|
+
job.setupFailureSummary = outcome.setupFailureSummary;
|
|
548
|
+
this.#freezeEndTime(job);
|
|
549
|
+
job.errorText = outcome.text;
|
|
550
|
+
this.#enqueueDelivery(id, outcome.text);
|
|
551
|
+
this.#runLifecycle(id, "terminal", job);
|
|
552
|
+
this.#scheduleEviction(id);
|
|
553
|
+
this.#markRecordTerminal(id, "failed");
|
|
554
|
+
this.#drainResumeQueue();
|
|
555
|
+
return;
|
|
556
|
+
}
|
|
520
557
|
|
|
521
558
|
job.status = "completed";
|
|
522
559
|
this.#freezeEndTime(job);
|
|
@@ -684,7 +721,9 @@ export class AsyncJobManager {
|
|
|
684
721
|
historicalJobIds: [],
|
|
685
722
|
status: "completed",
|
|
686
723
|
sessionFile,
|
|
687
|
-
|
|
724
|
+
// The synthesized record copies this descriptor's owner, so the
|
|
725
|
+
// descriptor is owner-compatible with the record by construction.
|
|
726
|
+
resumable: hasRetainedResumeContext({ resumable: true, sessionFile, descriptor }),
|
|
688
727
|
};
|
|
689
728
|
this.#subagentRecords.set(record.subagentId, record);
|
|
690
729
|
return record;
|
|
@@ -765,13 +804,24 @@ export class AsyncJobManager {
|
|
|
765
804
|
}
|
|
766
805
|
|
|
767
806
|
/**
|
|
768
|
-
* Resolve
|
|
769
|
-
*
|
|
770
|
-
*
|
|
771
|
-
* without one.
|
|
807
|
+
* Resolve a descriptor only when `ownerId` matches the record by strict
|
|
808
|
+
* equality. Two undefined owners match; a distinguishably foreign descriptor
|
|
809
|
+
* is treated as absent for eligibility, runner selection, and execution.
|
|
772
810
|
*/
|
|
773
|
-
#
|
|
774
|
-
|
|
811
|
+
#descriptorForRecord(rec: SubagentRecord): ResumeDescriptor | undefined {
|
|
812
|
+
const descriptor = this.#resumeDescriptors.get(rec.subagentId);
|
|
813
|
+
return descriptor !== undefined && descriptor.ownerId === rec.ownerId ? descriptor : undefined;
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
/**
|
|
817
|
+
* Resolve the resume runner for a record and its already owner-compatible
|
|
818
|
+
* descriptor. Per-descriptor authority is unavailable when the descriptor is foreign.
|
|
819
|
+
*/
|
|
820
|
+
#resolveResumeRunner(rec: SubagentRecord, descriptor: ResumeDescriptor | undefined): ResumeRunner | undefined {
|
|
821
|
+
return (
|
|
822
|
+
(descriptor !== undefined ? this.#descriptorResumeRunners.get(rec.subagentId) : undefined) ??
|
|
823
|
+
this.#resumeRunner
|
|
824
|
+
);
|
|
775
825
|
}
|
|
776
826
|
|
|
777
827
|
getResumeDescriptor(subagentId: string, filter?: AsyncJobFilter): ResumeDescriptor | undefined {
|
|
@@ -1045,7 +1095,11 @@ export class AsyncJobManager {
|
|
|
1045
1095
|
return { ok: true, status: rec.status };
|
|
1046
1096
|
}
|
|
1047
1097
|
|
|
1048
|
-
/**
|
|
1098
|
+
/**
|
|
1099
|
+
* Resume a non-running subagent from retained context: an owner-compatible
|
|
1100
|
+
* descriptor or a legacy session file. Workflow routing keeps `not_found`,
|
|
1101
|
+
* `context_unavailable`, `no_runner`, and `resume_failed` distinct.
|
|
1102
|
+
*/
|
|
1049
1103
|
resumeSubagent(
|
|
1050
1104
|
subagentId: string,
|
|
1051
1105
|
filter?: AsyncJobFilter,
|
|
@@ -1066,8 +1120,11 @@ export class AsyncJobManager {
|
|
|
1066
1120
|
}
|
|
1067
1121
|
return { ok: false, status: "queued", reason: "already_queued" };
|
|
1068
1122
|
}
|
|
1069
|
-
|
|
1070
|
-
if (!
|
|
1123
|
+
const descriptor = this.#descriptorForRecord(rec);
|
|
1124
|
+
if (!hasRetainedResumeContext({ resumable: rec.resumable, sessionFile: rec.sessionFile, descriptor })) {
|
|
1125
|
+
return { ok: false, reason: "context_unavailable" };
|
|
1126
|
+
}
|
|
1127
|
+
if (!this.#resolveResumeRunner(rec, descriptor)) return { ok: false, reason: "no_runner" };
|
|
1071
1128
|
if (this.getRunningJobs().length >= this.#maxRunningJobs) {
|
|
1072
1129
|
const seq = ++this.#resumeSeq;
|
|
1073
1130
|
rec.status = "queued";
|
|
@@ -1081,12 +1138,13 @@ export class AsyncJobManager {
|
|
|
1081
1138
|
});
|
|
1082
1139
|
return { ok: true, queued: true, status: "queued" };
|
|
1083
1140
|
}
|
|
1084
|
-
return this.#startResume(rec, message);
|
|
1141
|
+
return this.#startResume(rec, message, descriptor);
|
|
1085
1142
|
}
|
|
1086
1143
|
|
|
1087
1144
|
#startResume(
|
|
1088
1145
|
rec: SubagentRecord,
|
|
1089
|
-
message
|
|
1146
|
+
message: string | undefined,
|
|
1147
|
+
descriptor: ResumeDescriptor | undefined,
|
|
1090
1148
|
): { ok: boolean; status?: SubagentLifecycle; jobId?: string; reason?: string } {
|
|
1091
1149
|
if (this.#isOwnerSubagentShutdownFenced(rec.ownerId)) {
|
|
1092
1150
|
return { ok: false, status: rec.status, reason: "owner_shutdown_in_progress" };
|
|
@@ -1095,8 +1153,8 @@ export class AsyncJobManager {
|
|
|
1095
1153
|
// Clear any retained progress from the previous run so a resumed subagent
|
|
1096
1154
|
// never renders the prior run's tool/output as live before it emits again.
|
|
1097
1155
|
this.#subagentProgress.delete(rec.subagentId);
|
|
1098
|
-
const runner = this.#resolveResumeRunner(rec
|
|
1099
|
-
const newJobId = runner?.(rec.subagentId, message,
|
|
1156
|
+
const runner = this.#resolveResumeRunner(rec, descriptor);
|
|
1157
|
+
const newJobId = runner?.(rec.subagentId, message, descriptor);
|
|
1100
1158
|
if (!newJobId) return { ok: false, reason: "resume_failed" };
|
|
1101
1159
|
if (prevJobId && prevJobId !== newJobId) rec.historicalJobIds.push(prevJobId);
|
|
1102
1160
|
rec.currentJobId = newJobId;
|
|
@@ -1122,7 +1180,7 @@ export class AsyncJobManager {
|
|
|
1122
1180
|
continue;
|
|
1123
1181
|
}
|
|
1124
1182
|
try {
|
|
1125
|
-
const result = this.#startResume(rec, entry.message);
|
|
1183
|
+
const result = this.#startResume(rec, entry.message, this.#descriptorForRecord(rec));
|
|
1126
1184
|
if (result.reason === "owner_shutdown_in_progress") {
|
|
1127
1185
|
index += 1;
|
|
1128
1186
|
continue;
|
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)
|