@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,218 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Credential-free ACP fixture launcher. The loopback endpoint is deliberately
|
|
5
|
+
* deterministic; the child remains the production `gjc --mode acp` surface.
|
|
6
|
+
*/
|
|
7
|
+
import * as fs from "node:fs/promises";
|
|
8
|
+
import * as os from "node:os";
|
|
9
|
+
import * as path from "node:path";
|
|
10
|
+
|
|
11
|
+
const root = path.resolve(import.meta.dir, "..", "..", "..");
|
|
12
|
+
const agentDir = await fs.mkdtemp(path.join(os.tmpdir(), "gjc-acp-conformance-"));
|
|
13
|
+
let child: ReturnType<typeof Bun.spawn> | undefined;
|
|
14
|
+
let server: ReturnType<typeof Bun.serve> | undefined;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Deterministic replies for the pinned `acp-core-v1` corpus. Each branch mirrors one
|
|
18
|
+
* upstream case expectation; nothing here reaches the network.
|
|
19
|
+
*/
|
|
20
|
+
function response(prompt: string): string {
|
|
21
|
+
if (prompt.includes("inspect-prompt")) return `received blocks: {"type":"resource"}`;
|
|
22
|
+
if (prompt.includes("this-command-does-not-exist")) return "unrecognized prompt";
|
|
23
|
+
if (/^\s*read\b/m.test(prompt)) return "read README.md: acpx";
|
|
24
|
+
if (/^\s*write\b/m.test(prompt)) {
|
|
25
|
+
const target = /write\s+(\S+)/.exec(prompt)?.[1] ?? "file";
|
|
26
|
+
return `wrote ${target}`;
|
|
27
|
+
}
|
|
28
|
+
const echo = /^\s*echo\s*(.*)$/m.exec(prompt);
|
|
29
|
+
if (echo) return echo[1] || "echo";
|
|
30
|
+
return "ok";
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function sse(value: unknown): string {
|
|
34
|
+
return `data: ${JSON.stringify(value)}\n\n`;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function completion(delta: Record<string, unknown>, finishReason: string | null = null): string {
|
|
38
|
+
return sse({ id: "gjc-conformance", choices: [{ delta, finish_reason: finishReason }] });
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function toolCall(name: "read" | "write", arguments_: Record<string, string>): string {
|
|
42
|
+
return completion(
|
|
43
|
+
{
|
|
44
|
+
tool_calls: [
|
|
45
|
+
{
|
|
46
|
+
index: 0,
|
|
47
|
+
id: `conformance-${name}`,
|
|
48
|
+
type: "function",
|
|
49
|
+
function: { name, arguments: JSON.stringify(arguments_) },
|
|
50
|
+
},
|
|
51
|
+
],
|
|
52
|
+
},
|
|
53
|
+
"tool_calls",
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function textCompletion(text: string): string {
|
|
58
|
+
return `${completion({ content: text })}${completion({}, "stop")}data: [DONE]\n\n`;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async function cleanup(code = 0): Promise<never> {
|
|
62
|
+
server?.stop(true);
|
|
63
|
+
if (child && child.exitCode === null) child.kill();
|
|
64
|
+
await fs.rm(agentDir, { recursive: true, force: true });
|
|
65
|
+
process.exit(code);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
server = Bun.serve({
|
|
69
|
+
hostname: "127.0.0.1",
|
|
70
|
+
port: 0,
|
|
71
|
+
async fetch(request) {
|
|
72
|
+
const body = (await request.json().catch(() => ({}))) as {
|
|
73
|
+
messages?: Array<{ role?: string; content?: unknown }>;
|
|
74
|
+
};
|
|
75
|
+
// OpenAI-style content is either a plain string or an array of typed blocks.
|
|
76
|
+
const textOf = (content: unknown): string =>
|
|
77
|
+
typeof content === "string"
|
|
78
|
+
? content
|
|
79
|
+
: Array.isArray(content)
|
|
80
|
+
? content
|
|
81
|
+
.map(block =>
|
|
82
|
+
typeof block === "object" &&
|
|
83
|
+
block !== null &&
|
|
84
|
+
typeof (block as { text?: unknown }).text === "string"
|
|
85
|
+
? (block as { text: string }).text
|
|
86
|
+
: "",
|
|
87
|
+
)
|
|
88
|
+
.join("")
|
|
89
|
+
: "";
|
|
90
|
+
const messages = body.messages ?? [];
|
|
91
|
+
const prompt =
|
|
92
|
+
[...messages]
|
|
93
|
+
.reverse()
|
|
94
|
+
.filter(message => message.role === "user")
|
|
95
|
+
.map(message => textOf(message.content))
|
|
96
|
+
.find(text => text.length > 0) ?? "";
|
|
97
|
+
const hasToolResult = messages.some(message => message.role === "tool");
|
|
98
|
+
const isRead = /^\s*read\b/m.test(prompt);
|
|
99
|
+
const isWrite = /^\s*write\b/m.test(prompt);
|
|
100
|
+
const isLateTool = prompt.includes("late-tool 40 follow-up");
|
|
101
|
+
|
|
102
|
+
if (prompt.includes("sleep")) {
|
|
103
|
+
const requested = Number(/\bsleep\s+(\d+)/.exec(prompt)?.[1] ?? 0);
|
|
104
|
+
const delay = Math.min(Math.max(requested, 0), 30_000);
|
|
105
|
+
const stream = new ReadableStream<Uint8Array>({
|
|
106
|
+
start(controller) {
|
|
107
|
+
let closed = false;
|
|
108
|
+
const close = () => {
|
|
109
|
+
if (closed) return;
|
|
110
|
+
closed = true;
|
|
111
|
+
controller.close();
|
|
112
|
+
};
|
|
113
|
+
request.signal.addEventListener("abort", close, { once: true });
|
|
114
|
+
void Bun.sleep(delay).then(() => {
|
|
115
|
+
if (closed) return;
|
|
116
|
+
controller.enqueue(new TextEncoder().encode(textCompletion("slept")));
|
|
117
|
+
close();
|
|
118
|
+
});
|
|
119
|
+
},
|
|
120
|
+
});
|
|
121
|
+
return new Response(stream, { headers: { "content-type": "text/event-stream" } });
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if ((isRead || isWrite || isLateTool) && !hasToolResult) {
|
|
125
|
+
if (isLateTool) {
|
|
126
|
+
const prefix = `${completion({ content: "preparing" })}${completion({ content: " writing now" })}${completion({ content: " before tool" })}`;
|
|
127
|
+
return new Response(
|
|
128
|
+
`${prefix}${toolCall("write", { path: ".acpx-conformance-late.txt", content: "late" })}data: [DONE]\n\n`,
|
|
129
|
+
{
|
|
130
|
+
headers: { "content-type": "text/event-stream" },
|
|
131
|
+
},
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
// Neutral pre-tool text: every corpus assertion below must come from the real
|
|
135
|
+
// tool result, never from a marker announced before the tool ran.
|
|
136
|
+
const provisional = isRead ? "checking the requested file" : "applying the requested write";
|
|
137
|
+
return new Response(
|
|
138
|
+
`${completion({ content: provisional })}${toolCall(
|
|
139
|
+
isRead ? "read" : "write",
|
|
140
|
+
isRead
|
|
141
|
+
? { path: "README.md" }
|
|
142
|
+
: {
|
|
143
|
+
path: /write\s+(\S+)/.exec(prompt)?.[1] ?? "file",
|
|
144
|
+
content: /write\s+\S+\s*(.*)/.exec(prompt)?.[1] ?? "",
|
|
145
|
+
},
|
|
146
|
+
)}data: [DONE]\n\n`,
|
|
147
|
+
{ headers: { "content-type": "text/event-stream" } },
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Derive the reply from what the tool actually returned so a broken permission
|
|
152
|
+
// bridge, filesystem bridge, or tool result cannot still satisfy the corpus.
|
|
153
|
+
const toolText = messages
|
|
154
|
+
.filter(message => message.role === "tool")
|
|
155
|
+
.map(message => textOf(message.content))
|
|
156
|
+
.join("\n");
|
|
157
|
+
const toolDenied = /rejected by user|permission denied|not permitted/i.test(toolText);
|
|
158
|
+
const reply = toolDenied
|
|
159
|
+
? `permission denied: ${toolText.slice(0, 200)}`
|
|
160
|
+
: isRead
|
|
161
|
+
? `read README.md: ${toolText.trim().slice(0, 200)}`
|
|
162
|
+
: isWrite
|
|
163
|
+
? `wrote ${/write\s+(\S+)/.exec(prompt)?.[1] ?? "file"}: ${toolText.trim().slice(0, 120)}`
|
|
164
|
+
: isLateTool
|
|
165
|
+
? `writing now: ${toolText.trim().slice(0, 120)}`
|
|
166
|
+
: response(prompt);
|
|
167
|
+
return new Response(textCompletion(reply), { headers: { "content-type": "text/event-stream" } });
|
|
168
|
+
},
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
await Bun.write(
|
|
172
|
+
path.join(agentDir, "models.json"),
|
|
173
|
+
JSON.stringify({
|
|
174
|
+
providers: {
|
|
175
|
+
conformance: {
|
|
176
|
+
api: "openai-completions",
|
|
177
|
+
baseUrl: `http://127.0.0.1:${server.port}/v1`,
|
|
178
|
+
auth: "none",
|
|
179
|
+
models: [{ id: "fixture", name: "fixture", input: ["text"], contextWindow: 1_000_000, maxTokens: 8192 }],
|
|
180
|
+
},
|
|
181
|
+
},
|
|
182
|
+
}),
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
// The pinned corpus reads `README.md` from the session cwd the runner passes on
|
|
186
|
+
// `session/new`. The runner creates that directory but seeds no content, so seed it
|
|
187
|
+
// here (never inside the upstream corpus) when the harness names it.
|
|
188
|
+
const scratchCwd = process.env.GJC_ACP_CONFORMANCE_CWD?.trim();
|
|
189
|
+
if (scratchCwd) {
|
|
190
|
+
const scratchReadme = path.join(scratchCwd, "README.md");
|
|
191
|
+
if (!(await Bun.file(scratchReadme).exists())) await Bun.write(scratchReadme, "acpx conformance workspace\n");
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
child = Bun.spawn(
|
|
195
|
+
[
|
|
196
|
+
"bun",
|
|
197
|
+
path.join(root, "packages", "coding-agent", "src", "cli.ts"),
|
|
198
|
+
"--mode",
|
|
199
|
+
"acp",
|
|
200
|
+
"--model",
|
|
201
|
+
"conformance/fixture",
|
|
202
|
+
],
|
|
203
|
+
{
|
|
204
|
+
cwd: root,
|
|
205
|
+
env: { ...process.env, GJC_CODING_AGENT_DIR: agentDir, PI_CODING_AGENT_DIR: agentDir },
|
|
206
|
+
stdin: "inherit",
|
|
207
|
+
stdout: "inherit",
|
|
208
|
+
stderr: "inherit",
|
|
209
|
+
},
|
|
210
|
+
);
|
|
211
|
+
process.once("SIGTERM", () => {
|
|
212
|
+
void cleanup(0);
|
|
213
|
+
});
|
|
214
|
+
process.once("SIGINT", () => {
|
|
215
|
+
void cleanup(0);
|
|
216
|
+
});
|
|
217
|
+
await child.exited;
|
|
218
|
+
await cleanup(child.exitCode ?? 1);
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
import * as fs from "node:fs/promises";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import type { GjcLifecycleContext } from "../src/extensibility/gjc-plugins/lifecycle";
|
|
4
|
+
import type { GjcRuntimeSnapshotProvider } from "../src/extensibility/gjc-plugins/runtime-quarantine";
|
|
5
|
+
import type {
|
|
6
|
+
GjcBundleIdentity,
|
|
7
|
+
GjcBundleSummary,
|
|
8
|
+
GjcLifecycleResult,
|
|
9
|
+
GjcToggleResult,
|
|
10
|
+
GjcUpdateApplyResult,
|
|
11
|
+
GjcUpdatePreview,
|
|
12
|
+
} from "../src/extensibility/gjc-plugins/types";
|
|
13
|
+
import { type GjcBundleLifecyclePort, GjcBundleSettingsComponent } from "../src/modes/components/gjc-bundle-settings";
|
|
14
|
+
import { setTheme } from "../src/modes/theme/theme";
|
|
15
|
+
import {
|
|
16
|
+
GJC_BUNDLE_SETTINGS_ENTRIES,
|
|
17
|
+
GJC_BUNDLE_SETTINGS_STATES,
|
|
18
|
+
GJC_BUNDLE_SETTINGS_VIEWPORTS,
|
|
19
|
+
type GjcBundleSettingsFixture,
|
|
20
|
+
} from "../test/fixtures/gjc-bundles-settings-cases";
|
|
21
|
+
|
|
22
|
+
export const GJC_BUNDLE_SETTINGS_CAPTURE_FILES = [
|
|
23
|
+
"terminal.txt",
|
|
24
|
+
"terminal-ansi.txt",
|
|
25
|
+
"terminal.html",
|
|
26
|
+
"metadata.json",
|
|
27
|
+
] as const;
|
|
28
|
+
|
|
29
|
+
type CaptureFileName = (typeof GJC_BUNDLE_SETTINGS_CAPTURE_FILES)[number];
|
|
30
|
+
|
|
31
|
+
type FixtureEntry = (typeof GJC_BUNDLE_SETTINGS_ENTRIES)[number];
|
|
32
|
+
|
|
33
|
+
type CapturePlanItem = {
|
|
34
|
+
entryId: string;
|
|
35
|
+
fileName: CaptureFileName;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
type CaptureMetadata = {
|
|
39
|
+
entryId: string;
|
|
40
|
+
stateId: string;
|
|
41
|
+
viewport: { id: string; cols: number; rows: number };
|
|
42
|
+
variant: { renderMode: string };
|
|
43
|
+
sha256: Record<Exclude<CaptureFileName, "metadata.json">, string>;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Locator-shaped content that must never reach a capture. Applied to the
|
|
48
|
+
* rendered terminal text, which is the only surface that can carry a leaked
|
|
49
|
+
* locator; generated HTML/CSS legitimately contains `#rrggbb` colors and
|
|
50
|
+
* escaped entities, so it is checked against the same rules via its source
|
|
51
|
+
* text rather than its markup.
|
|
52
|
+
*/
|
|
53
|
+
const FORBIDDEN_CAPTURE_CONTENT = /:\/\/user:|@[^\s/]+|\?[^\s]*=|token|\/Users\/|\/home\//i;
|
|
54
|
+
|
|
55
|
+
function sha256(content: string): string {
|
|
56
|
+
return new Bun.CryptoHasher("sha256").update(content).digest("hex");
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function json(value: unknown): string {
|
|
60
|
+
return `${JSON.stringify(value, null, 2)}\n`;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function escapeHtml(text: string): string {
|
|
64
|
+
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function ansiToHtml(ansi: string): string {
|
|
68
|
+
const sgr = /\x1b\[([0-9;]*)m/g;
|
|
69
|
+
let html = "";
|
|
70
|
+
let offset = 0;
|
|
71
|
+
let style = "";
|
|
72
|
+
for (const match of ansi.matchAll(sgr)) {
|
|
73
|
+
html += `<span style="${style}">${escapeHtml(ansi.slice(offset, match.index))}</span>`;
|
|
74
|
+
offset = (match.index ?? 0) + match[0].length;
|
|
75
|
+
const codes = (match[1] || "0").split(";").map(Number);
|
|
76
|
+
for (let index = 0; index < codes.length; index += 1) {
|
|
77
|
+
const code = codes[index];
|
|
78
|
+
if (code === 0) style = "";
|
|
79
|
+
else if (code === 1) style = `${style}font-weight:700;`;
|
|
80
|
+
else if (code === 2) style = `${style}opacity:.65;`;
|
|
81
|
+
else if (code === 22) style = style.replace("font-weight:700;", "").replace("opacity:.65;", "");
|
|
82
|
+
else if (code === 38 && codes[index + 1] === 2) {
|
|
83
|
+
const [red, green, blue] = codes.slice(index + 2, index + 5);
|
|
84
|
+
if ([red, green, blue].every(Number.isInteger)) style = `${style}color:rgb(${red},${green},${blue});`;
|
|
85
|
+
index += 4;
|
|
86
|
+
} else if (code === 39) style = style.replace(/color:[^;]+;/g, "");
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
html += `<span style="${style}">${escapeHtml(ansi.slice(offset))}</span>`;
|
|
90
|
+
return `<!doctype html>
|
|
91
|
+
<html lang="en">
|
|
92
|
+
<head><meta charset="utf-8"><meta name="color-scheme" content="dark"><title>GJC Bundles settings</title><style>body{margin:0;background:#111;color:#eee}pre{margin:0;padding:1em;white-space:pre-wrap;font-family:ui-monospace,monospace;line-height:1.2}</style></head>
|
|
93
|
+
<body><pre>${html}</pre></body>
|
|
94
|
+
</html>
|
|
95
|
+
`;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function asciiText(text: string): string {
|
|
99
|
+
return text
|
|
100
|
+
.replaceAll("─", "-")
|
|
101
|
+
.replaceAll("…", "...")
|
|
102
|
+
.replaceAll("·", ".")
|
|
103
|
+
.replaceAll("→", "->")
|
|
104
|
+
.replaceAll("−", "-");
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function cloneSummary(summary: GjcBundleSummary): GjcBundleSummary {
|
|
108
|
+
return {
|
|
109
|
+
...summary,
|
|
110
|
+
identity: { ...summary.identity },
|
|
111
|
+
source: { ...summary.source },
|
|
112
|
+
surfaces: summary.surfaces.map(surface => ({ ...surface })),
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
class FixtureLifecyclePort implements GjcBundleLifecyclePort {
|
|
117
|
+
constructor(private readonly fixture: GjcBundleSettingsFixture) {}
|
|
118
|
+
|
|
119
|
+
async listGjcBundles(_ctx: GjcLifecycleContext): Promise<GjcBundleSummary[]> {
|
|
120
|
+
return this.fixture.bundles.map(cloneSummary);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
async getGjcBundle(
|
|
124
|
+
_ctx: GjcLifecycleContext,
|
|
125
|
+
identity: GjcBundleIdentity,
|
|
126
|
+
): Promise<GjcLifecycleResult<GjcBundleSummary>> {
|
|
127
|
+
const summary = this.fixture.bundles.find(
|
|
128
|
+
bundle =>
|
|
129
|
+
bundle.identity.kind === identity.kind &&
|
|
130
|
+
bundle.identity.scope === identity.scope &&
|
|
131
|
+
bundle.identity.name === identity.name,
|
|
132
|
+
);
|
|
133
|
+
return summary
|
|
134
|
+
? { ok: true, value: cloneSummary(summary) }
|
|
135
|
+
: { ok: false, error: { code: "not_installed", message: "Bundle is not installed." } };
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
async previewGjcBundleUpdate(
|
|
139
|
+
_ctx: GjcLifecycleContext,
|
|
140
|
+
_identity: GjcBundleIdentity,
|
|
141
|
+
): Promise<GjcLifecycleResult<GjcUpdatePreview>> {
|
|
142
|
+
return this.fixture.updatePreview
|
|
143
|
+
? { ok: true, value: this.fixture.updatePreview }
|
|
144
|
+
: { ok: false, error: { code: "source_unsupported", message: "Update is unavailable." } };
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
async applyGjcBundleUpdate(
|
|
148
|
+
_ctx: GjcLifecycleContext,
|
|
149
|
+
_token: GjcUpdatePreview["token"],
|
|
150
|
+
): Promise<GjcLifecycleResult<GjcUpdateApplyResult>> {
|
|
151
|
+
return { ok: false, error: { code: "stale_candidate", message: "Capture fixtures do not apply updates." } };
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
async setGjcBundleEnabled(
|
|
155
|
+
_ctx: GjcLifecycleContext,
|
|
156
|
+
_identity: GjcBundleIdentity,
|
|
157
|
+
_enabled: boolean,
|
|
158
|
+
): Promise<GjcLifecycleResult<GjcToggleResult>> {
|
|
159
|
+
return { ok: false, error: { code: "invalid_target", message: "Capture fixtures do not mutate bundles." } };
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
async setGjcBundleSurfaceEnabled(
|
|
163
|
+
_ctx: GjcLifecycleContext,
|
|
164
|
+
_identity: GjcBundleIdentity,
|
|
165
|
+
_surfaceId: string,
|
|
166
|
+
_enabled: boolean,
|
|
167
|
+
): Promise<GjcLifecycleResult<GjcToggleResult>> {
|
|
168
|
+
return { ok: false, error: { code: "invalid_target", message: "Capture fixtures do not mutate surfaces." } };
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function fixtureFor(stateId: string): GjcBundleSettingsFixture {
|
|
173
|
+
const state = GJC_BUNDLE_SETTINGS_STATES.find(candidate => candidate.id === stateId);
|
|
174
|
+
if (!state) throw new Error(`Unknown GJC Bundle settings state: ${stateId}`);
|
|
175
|
+
return state.fixture;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function viewportFor(viewportId: string): { id: string; cols: number; rows: number } {
|
|
179
|
+
const viewport = GJC_BUNDLE_SETTINGS_VIEWPORTS.find(candidate => candidate.id === viewportId);
|
|
180
|
+
if (viewport) return viewport;
|
|
181
|
+
if (viewportId === "48x36") return { id: "48x36", cols: 48, rows: 36 };
|
|
182
|
+
throw new Error(`Unknown GJC Bundle settings viewport: ${viewportId}`);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
async function settle(): Promise<void> {
|
|
186
|
+
for (let index = 0; index < 8; index += 1) await Promise.resolve();
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export async function renderGjcBundleSettingsEntry(
|
|
190
|
+
entry: FixtureEntry,
|
|
191
|
+
): Promise<{ terminalText: string; terminalAnsiText: string; viewport: { id: string; cols: number; rows: number } }> {
|
|
192
|
+
const fixture = fixtureFor(entry.stateId);
|
|
193
|
+
const viewport = viewportFor(entry.viewportId);
|
|
194
|
+
const runtime: GjcRuntimeSnapshotProvider = { current: () => fixture.runtime };
|
|
195
|
+
await setTheme("red-claw");
|
|
196
|
+
const component = new GjcBundleSettingsComponent(
|
|
197
|
+
"/fixture/project",
|
|
198
|
+
{ onClose: () => {} },
|
|
199
|
+
{
|
|
200
|
+
lifecycle: new FixtureLifecyclePort(fixture),
|
|
201
|
+
runtimeSnapshotProvider: runtime,
|
|
202
|
+
activationGeneration: 7,
|
|
203
|
+
},
|
|
204
|
+
);
|
|
205
|
+
await settle();
|
|
206
|
+
const rendered = component.render(viewport.cols).join("\n");
|
|
207
|
+
component.dispose();
|
|
208
|
+
const terminalAnsiText = entry.renderMode === "ascii-no-color" ? asciiText(Bun.stripANSI(rendered)) : rendered;
|
|
209
|
+
const terminalText = Bun.stripANSI(terminalAnsiText);
|
|
210
|
+
return { terminalText, terminalAnsiText, viewport };
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
export function gjcBundleSettingsCapturePlan(
|
|
214
|
+
entries: readonly FixtureEntry[] = GJC_BUNDLE_SETTINGS_ENTRIES,
|
|
215
|
+
): CapturePlanItem[] {
|
|
216
|
+
return entries.flatMap(entry =>
|
|
217
|
+
GJC_BUNDLE_SETTINGS_CAPTURE_FILES.map(fileName => ({ entryId: entry.entryId, fileName })),
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function assertSafeContent(label: string, content: string): void {
|
|
222
|
+
if (FORBIDDEN_CAPTURE_CONTENT.test(content)) throw new Error(`Unsafe locator content in ${label}`);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
async function artifactContents(entry: FixtureEntry): Promise<Record<CaptureFileName, string>> {
|
|
226
|
+
const rendered = await renderGjcBundleSettingsEntry(entry);
|
|
227
|
+
const terminalHtml = ansiToHtml(rendered.terminalAnsiText);
|
|
228
|
+
const metadata: CaptureMetadata = {
|
|
229
|
+
entryId: entry.entryId,
|
|
230
|
+
stateId: entry.stateId,
|
|
231
|
+
viewport: rendered.viewport,
|
|
232
|
+
variant: { renderMode: entry.renderMode },
|
|
233
|
+
sha256: {
|
|
234
|
+
"terminal.txt": sha256(rendered.terminalText),
|
|
235
|
+
"terminal-ansi.txt": sha256(rendered.terminalAnsiText),
|
|
236
|
+
"terminal.html": sha256(terminalHtml),
|
|
237
|
+
},
|
|
238
|
+
};
|
|
239
|
+
return {
|
|
240
|
+
"terminal.txt": rendered.terminalText,
|
|
241
|
+
"terminal-ansi.txt": rendered.terminalAnsiText,
|
|
242
|
+
"terminal.html": terminalHtml,
|
|
243
|
+
"metadata.json": json(metadata),
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
async function writeEntry(entry: FixtureEntry, outputRoot: string): Promise<void> {
|
|
248
|
+
const artifacts = await artifactContents(entry);
|
|
249
|
+
// The HTML artifact is a pure rendering of the ANSI text, so proving the two
|
|
250
|
+
// text surfaces and the metadata are clean proves the whole entry is clean.
|
|
251
|
+
for (const name of ["terminal.txt", "terminal-ansi.txt", "metadata.json"] as const) {
|
|
252
|
+
assertSafeContent(`${entry.entryId}/${name}`, artifacts[name]);
|
|
253
|
+
}
|
|
254
|
+
const directory = path.join(outputRoot, entry.entryId);
|
|
255
|
+
await fs.mkdir(directory, { recursive: true });
|
|
256
|
+
await Promise.all(
|
|
257
|
+
GJC_BUNDLE_SETTINGS_CAPTURE_FILES.map(fileName => Bun.write(path.join(directory, fileName), artifacts[fileName])),
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
export async function verifyGjcBundleSettingsCapture(outputRoot: string): Promise<void> {
|
|
262
|
+
for (const entry of GJC_BUNDLE_SETTINGS_ENTRIES) {
|
|
263
|
+
const directory = path.join(outputRoot, entry.entryId);
|
|
264
|
+
const names = (await fs.readdir(directory)).sort();
|
|
265
|
+
if (
|
|
266
|
+
names.length !== GJC_BUNDLE_SETTINGS_CAPTURE_FILES.length ||
|
|
267
|
+
names.some((name, index) => name !== GJC_BUNDLE_SETTINGS_CAPTURE_FILES.slice().sort()[index])
|
|
268
|
+
) {
|
|
269
|
+
throw new Error(`Expected exactly four capture files for ${entry.entryId}`);
|
|
270
|
+
}
|
|
271
|
+
const artifacts = await artifactContents(entry);
|
|
272
|
+
for (const fileName of GJC_BUNDLE_SETTINGS_CAPTURE_FILES) {
|
|
273
|
+
const content = await Bun.file(path.join(directory, fileName)).text();
|
|
274
|
+
assertSafeContent(`${entry.entryId}/${fileName}`, content);
|
|
275
|
+
if (content !== artifacts[fileName])
|
|
276
|
+
throw new Error(`Capture does not match deterministic fixture render for ${entry.entryId}/${fileName}`);
|
|
277
|
+
}
|
|
278
|
+
const metadata = JSON.parse(artifacts["metadata.json"]) as CaptureMetadata;
|
|
279
|
+
for (const fileName of ["terminal.txt", "terminal-ansi.txt", "terminal.html"] as const) {
|
|
280
|
+
if (metadata.sha256[fileName] !== sha256(artifacts[fileName]))
|
|
281
|
+
throw new Error(`SHA-256 mismatch for ${entry.entryId}/${fileName}`);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function parseArgs(args: string[]): { mode: "capture" | "verify"; outputRoot: string } {
|
|
287
|
+
if (args.length === 2 && args[0] === "--output" && args[1]) return { mode: "capture", outputRoot: args[1] };
|
|
288
|
+
if (args.length === 2 && args[0] === "--verify" && args[1]) return { mode: "verify", outputRoot: args[1] };
|
|
289
|
+
throw new Error("Usage: bun scripts/capture-gjc-bundle-settings.ts --output <directory> | --verify <directory>");
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
async function main(): Promise<void> {
|
|
293
|
+
const { mode, outputRoot } = parseArgs(process.argv.slice(2));
|
|
294
|
+
const resolvedOutputRoot = path.resolve(outputRoot);
|
|
295
|
+
if (mode === "verify") {
|
|
296
|
+
await verifyGjcBundleSettingsCapture(resolvedOutputRoot);
|
|
297
|
+
process.stdout.write(
|
|
298
|
+
`Verified ${GJC_BUNDLE_SETTINGS_ENTRIES.length} deterministic GJC Bundle settings entries.\n`,
|
|
299
|
+
);
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
for (const entry of GJC_BUNDLE_SETTINGS_ENTRIES) await writeEntry(entry, resolvedOutputRoot);
|
|
303
|
+
process.stdout.write(`Captured ${GJC_BUNDLE_SETTINGS_ENTRIES.length} deterministic GJC Bundle settings entries.\n`);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
if (import.meta.main) await main();
|
|
@@ -125,6 +125,7 @@ const LOCKED_EXCLUSIONS: Readonly<Record<string, string>> = {
|
|
|
125
125
|
"agent_session:getActiveModelProfile": "internal accessor/plumbing, not a user-facing control seam",
|
|
126
126
|
"agent_session:getSessionDefaultModelSelector": "internal accessor/plumbing, not a user-facing control seam",
|
|
127
127
|
"agent_session:recordResumeDefaultModel": "internal accessor/plumbing, not a user-facing control seam",
|
|
128
|
+
"agent_session:resolveConfiguredDefaultModel": "internal accessor/plumbing, not a user-facing control seam",
|
|
128
129
|
"agent_session:setModelTemporary": "internal accessor/plumbing, not a user-facing control seam",
|
|
129
130
|
"agent_session:setModelTemporaryForControl":
|
|
130
131
|
"internal Telegram control wrapper over the reviewed model.set seam, not an independent public SDK operation",
|
|
@@ -150,6 +151,8 @@ const LOCKED_EXCLUSIONS: Readonly<Record<string, string>> = {
|
|
|
150
151
|
"agent_session:runIdleCompaction": "internal accessor/plumbing, not a user-facing control seam",
|
|
151
152
|
"agent_session:abortBranchSummary": "internal accessor/plumbing, not a user-facing control seam",
|
|
152
153
|
"agent_session:abortHandoff": "internal accessor/plumbing, not a user-facing control seam",
|
|
154
|
+
"agent_session:abortPromptAndWait":
|
|
155
|
+
"internal SDK prompt-terminalization resource fence over a host-captured run handle, not an independent public SDK control seam",
|
|
153
156
|
"agent_session:prepareContributionPrep": "internal accessor/plumbing, not a user-facing control seam",
|
|
154
157
|
"agent_session:setResourceSampler": "internal accessor/plumbing, not a user-facing control seam",
|
|
155
158
|
"agent_session:setRetainedMemorySampler": "internal accessor/plumbing, not a user-facing control seam",
|