@astrosheep/keiyaku 2.8.4 → 2.9.0
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/README.md +35 -15
- package/build/.tsbuildinfo +1 -1
- package/build/agents/{call-terms_v2.js → call-terms.js} +12 -8
- package/build/agents/harness/control-types.js +1 -0
- package/build/agents/harness/execution-handle.js +23 -3
- package/build/agents/harness/index.js +6 -607
- package/build/agents/harness/outcome.js +141 -0
- package/build/agents/harness/projection.js +310 -0
- package/build/agents/harness/router.js +201 -0
- package/build/agents/harness/runtime.js +2 -1
- package/build/agents/{launch-snapshot_v2.js → launch-snapshot/model.js} +69 -74
- package/build/agents/launch-snapshot/resolve.js +72 -0
- package/build/agents/opencode-sdk.js +24 -3
- package/build/agents/providers/{claude-agent-sdk.js → claude-agent-sdk/adapter.js} +46 -368
- package/build/agents/providers/claude-agent-sdk/events.js +218 -0
- package/build/agents/providers/claude-agent-sdk/session.js +158 -0
- package/build/agents/providers/codex-app-server/adapter.js +347 -0
- package/build/agents/providers/codex-app-server/events.js +352 -0
- package/build/agents/providers/codex-app-server/session.js +266 -0
- package/build/agents/providers/codex-sdk.js +23 -5
- package/build/agents/providers/opencode-sdk/adapter.js +344 -0
- package/build/agents/providers/opencode-sdk/events.js +354 -0
- package/build/agents/providers/opencode-sdk/session.js +180 -0
- package/build/agents/providers/pi/adapter.js +269 -0
- package/build/agents/providers/pi/checkpoint.js +41 -0
- package/build/agents/providers/pi/events.js +282 -0
- package/build/agents/{selector_v2.js → selector.js} +8 -6
- package/build/cli/commands/akuma/akuma/handler.js +4 -0
- package/build/cli/commands/akuma/akuma/meta.js +16 -0
- package/build/cli/commands/akuma/catalog.js +8 -0
- package/build/cli/commands/akuma/list/handler.js +15 -0
- package/build/cli/commands/akuma/list/meta-ls.js +9 -0
- package/build/cli/commands/akuma/view/handler.js +34 -0
- package/build/cli/commands/akuma/view/meta.js +8 -0
- package/build/cli/commands/akuma.js +1 -1
- package/build/cli/commands/contract/amend/handler.js +10 -0
- package/build/cli/commands/contract/amend/meta.js +12 -0
- package/build/cli/commands/contract/arc/handler.js +9 -0
- package/build/cli/commands/contract/arc/meta.js +26 -0
- package/build/cli/commands/contract/bind/handler.js +23 -0
- package/build/cli/commands/contract/bind/meta.js +30 -0
- package/build/cli/commands/contract/catalog.js +16 -0
- package/build/cli/commands/contract/forfeit/handler.js +13 -0
- package/build/cli/commands/contract/forfeit/meta.js +12 -0
- package/build/cli/commands/contract/log/handler.js +12 -0
- package/build/cli/commands/contract/log/meta.js +12 -0
- package/build/cli/commands/contract/petition/handler.js +26 -0
- package/build/cli/commands/contract/petition/meta.js +23 -0
- package/build/cli/commands/contract/renew/handler.js +10 -0
- package/build/cli/commands/contract/renew/meta.js +17 -0
- package/build/cli/commands/projection/call/handler.js +39 -0
- package/build/cli/commands/projection/call/meta.js +9 -0
- package/build/cli/commands/projection/catalog.js +14 -0
- package/build/cli/commands/projection/kill/handler.js +35 -0
- package/build/cli/commands/projection/kill/meta.js +11 -0
- package/build/cli/commands/projection/revive/handler.js +53 -0
- package/build/cli/commands/projection/revive/meta.js +9 -0
- package/build/cli/commands/projection/status/handler.js +23 -0
- package/build/cli/commands/projection/status/meta.js +9 -0
- package/build/cli/commands/projection/tell/handler.js +34 -0
- package/build/cli/commands/projection/tell/meta.js +9 -0
- package/build/cli/commands/projection/wait/handler.js +21 -0
- package/build/cli/commands/projection/wait/meta.js +11 -0
- package/build/cli/commands/registry.js +100 -0
- package/build/cli/commands/shared.js +71 -0
- package/build/cli/commands/skills/catalog.js +6 -0
- package/build/cli/commands/skills/install/handler.js +10 -0
- package/build/cli/commands/skills/install/meta.js +9 -0
- package/build/cli/commands/skills/skills/handler.js +4 -0
- package/build/cli/commands/skills/skills/meta.js +10 -0
- package/build/cli/commands/system/catalog.js +8 -0
- package/build/cli/commands/system/completion/handler.js +10 -0
- package/build/cli/commands/system/completion/meta.js +9 -0
- package/build/cli/commands/system/dump-env/handler.js +3 -0
- package/build/cli/commands/system/dump-env/meta.js +10 -0
- package/build/cli/commands/system/guide/handler.js +4 -0
- package/build/cli/commands/system/guide/meta.js +9 -0
- package/build/cli/commands/task/add/handler.js +21 -0
- package/build/cli/commands/task/add/meta.js +8 -0
- package/build/cli/commands/task/catalog.js +22 -0
- package/build/cli/commands/task/doctor/handler.js +4 -0
- package/build/cli/commands/task/doctor/meta.js +8 -0
- package/build/cli/commands/task/done/handler.js +17 -0
- package/build/cli/commands/task/done/meta.js +8 -0
- package/build/cli/commands/task/drop/handler.js +6 -0
- package/build/cli/commands/task/drop/meta.js +8 -0
- package/build/cli/commands/task/ls/handler.js +13 -0
- package/build/cli/commands/task/ls/meta.js +8 -0
- package/build/cli/commands/task/shared.js +131 -0
- package/build/cli/commands/task/start/handler.js +6 -0
- package/build/cli/commands/task/start/meta.js +8 -0
- package/build/cli/commands/task/stop/handler.js +17 -0
- package/build/cli/commands/task/stop/meta.js +8 -0
- package/build/cli/commands/task/task/handler.js +5 -0
- package/build/cli/commands/task/task/meta.js +11 -0
- package/build/cli/commands/task/update/handler.js +18 -0
- package/build/cli/commands/task/update/meta.js +8 -0
- package/build/cli/commands/task/view/handler.js +14 -0
- package/build/cli/commands/task/view/meta.js +8 -0
- package/build/cli/completion.js +26 -27
- package/build/cli/flags.js +79 -2
- package/build/cli/help.js +27 -49
- package/build/cli/index.js +121 -374
- package/build/cli/output-stream-error-policy.js +17 -0
- package/build/cli/parse.js +159 -72
- package/build/cli/projection-address.js +5 -0
- package/build/cli/render/arc.js +1 -1
- package/build/cli/render/errors.js +4 -3
- package/build/cli/render/format.js +14 -0
- package/build/cli/render/kanshi.js +106 -0
- package/build/cli/render/petition.js +24 -1
- package/build/cli/render/projection-activity.js +5 -1
- package/build/cli/render/shared.js +2 -2
- package/build/cli/render/status.js +79 -13
- package/build/cli/render/task.js +113 -0
- package/build/cli/render/wait.js +23 -3
- package/build/cli/types.js +1 -1
- package/build/config/{akuma-loader_v2.js → akuma-loader.js} +2 -2
- package/build/config/env.js +9 -4
- package/build/config/settings/disease.js +26 -0
- package/build/config/settings/knobs.js +8 -0
- package/build/config/settings/loader.js +244 -0
- package/build/config/settings/schema.js +276 -0
- package/build/core/addressing.js +1 -1
- package/build/core/amend.js +3 -3
- package/build/core/arc.js +5 -4
- package/build/core/bind-workspace.js +123 -0
- package/build/core/bind.js +75 -155
- package/build/core/call/call.js +224 -0
- package/build/core/call/context.js +238 -0
- package/build/core/call/execution.js +217 -0
- package/build/core/call/prompt.js +42 -0
- package/build/core/call/reference.js +24 -0
- package/build/core/call-persist.js +1 -1
- package/build/core/claim-delivery.js +247 -0
- package/build/core/claim.js +39 -277
- package/build/core/context.js +2 -1
- package/build/core/{render.js → contract-view.js} +12 -140
- package/build/core/draft.js +107 -9
- package/build/core/entry.js +9 -1
- package/build/core/execution-coordinate.js +4 -20
- package/build/core/execution-pact.js +16 -31
- package/build/core/forfeit.js +35 -22
- package/build/core/hints.js +2 -2
- package/build/core/log.js +2 -2
- package/build/core/petition-claim-gates.js +8 -6
- package/build/core/petition-preview.js +97 -0
- package/build/core/petition-run.js +3 -2
- package/build/core/petition.js +39 -26
- package/build/core/projection/generation/database.js +306 -0
- package/build/core/projection/generation/identity.js +84 -0
- package/build/core/projection/generation/model.js +201 -0
- package/build/core/projection/generation/store.js +73 -0
- package/build/core/projection/generation/transitions.js +258 -0
- package/build/core/projection/heart.js +0 -307
- package/build/core/projection/tell/database.js +109 -0
- package/build/core/projection/tell/model.js +97 -0
- package/build/core/{projection-tells.js → projection/tell/store.js} +3 -158
- package/build/core/projection-activity.js +23 -2
- package/build/core/projection-core.js +2 -1
- package/build/core/projection-execution-observer.js +259 -0
- package/build/core/projection-generation-continuation.js +1 -1
- package/build/core/projection-generation-execution.js +90 -20
- package/build/core/projection-generation-launcher.js +1 -1
- package/build/core/projection-generation-runner.js +198 -4
- package/build/core/projection-identity.js +1 -1
- package/build/core/projection-kill.js +50 -17
- package/build/core/projection-life-observer.js +1 -1
- package/build/core/projection-life-protocol.js +10 -0
- package/build/core/projection-mint.js +1 -1
- package/build/core/projection-status.js +45 -32
- package/build/core/projection-wait.js +12 -2
- package/build/core/projection-wake.js +5 -2
- package/build/core/{queue_v2.js → queue.js} +2 -1
- package/build/core/registry.js +1 -1
- package/build/core/renew.js +7 -7
- package/build/core/response-artifact-catalog-sqlite.js +112 -0
- package/build/core/response-artifact-catalog.js +7 -0
- package/build/core/seal.js +44 -16
- package/build/core/status/board.js +286 -0
- package/build/core/status/drift.js +99 -0
- package/build/core/status/lifecycle.js +84 -0
- package/build/core/status/reconciliation.js +131 -0
- package/build/core/stored-agent-event.js +28 -4
- package/build/core/target-ref.js +10 -0
- package/build/core/task/board.js +175 -0
- package/build/core/task/commands.js +211 -0
- package/build/core/task/document.js +189 -0
- package/build/core/task/model.js +1 -0
- package/build/core/task/settlement-git.js +207 -0
- package/build/core/task/settlement-policy.js +82 -0
- package/build/core/task/source-board.js +84 -0
- package/build/core/task/source-model.js +1 -0
- package/build/core/task/validation.js +44 -0
- package/build/core/task-contract.js +224 -0
- package/build/core/task-doctor.js +14 -0
- package/build/core/task-git-actor.js +38 -0
- package/build/core/task-git-runtime.js +34 -0
- package/build/core/task-git-store.js +375 -0
- package/build/core/task-repository.js +1 -0
- package/build/core/task-store-repository.js +86 -0
- package/build/core/task-store.js +1 -0
- package/build/core/task-worktree.js +51 -0
- package/build/core/task.js +4 -0
- package/build/core/transcripts.js +138 -56
- package/build/core/validation.js +17 -0
- package/build/core/verdict.js +2 -2
- package/build/core/worktree-path.js +24 -6
- package/build/flow-error.js +3 -2
- package/build/generated/version.js +1 -1
- package/build/git/commits.js +3 -0
- package/build/git/diff/parsers.js +0 -94
- package/build/index.js +2 -0
- package/build/keiyaku-constants.js +2 -0
- package/build/keiyaku.js +4 -3
- package/package.json +3 -2
- package/skills/keiyaku/SKILL.md +1 -1
- package/skills/keiyaku-akuma/SKILL.md +11 -6
- package/build/agents/harness/pump.js +0 -158
- package/build/agents/providers/codex-app-server.js +0 -864
- package/build/agents/providers/opencode-sdk.js +0 -363
- package/build/agents/providers/pi.js +0 -557
- package/build/config/settings.js +0 -533
- package/build/core/call.js +0 -686
- package/build/core/projection/generation.js +0 -412
- package/build/core/projection/identity.js +0 -81
- package/build/core/projection/mint.js +0 -174
- package/build/core/projection-generation-store.js +0 -707
- package/build/core/status.js +0 -539
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
import { boundActivityExcerpt, diffstatFromUnifiedPatch } from "../../harness/activity-values.js";
|
|
2
|
+
import { AGENT_PLAN_ITEM_TEXT_MAX_CHARS, AGENT_PLAN_MAX_ITEMS, AGENT_THOUGHT_TEXT_MAX_CHARS, } from "../../harness/events.js";
|
|
3
|
+
import { createAbortError, emitProviderEvidence, coerceString } from "../../harness/runtime.js";
|
|
4
|
+
const CODEX_APP_SERVER_PROVIDER = "codex-app-server";
|
|
5
|
+
const CODEX_APP_SERVER_NOTIFICATION_ACKNOWLEDGED = new Set([
|
|
6
|
+
"item/agentMessage/delta",
|
|
7
|
+
"item/commandExecution/outputDelta",
|
|
8
|
+
"item/fileChange/outputDelta",
|
|
9
|
+
"item/reasoning/summaryTextDelta",
|
|
10
|
+
"item/reasoning/summaryPartAdded",
|
|
11
|
+
"item/reasoning/textDelta",
|
|
12
|
+
]);
|
|
13
|
+
const CODEX_APP_SERVER_NOTIFICATION_MAPPED = new Set([
|
|
14
|
+
"item/completed",
|
|
15
|
+
"turn/started",
|
|
16
|
+
"turn/completed",
|
|
17
|
+
"error",
|
|
18
|
+
]);
|
|
19
|
+
export function codexAppServerNotificationDisposition(method) {
|
|
20
|
+
if (CODEX_APP_SERVER_NOTIFICATION_ACKNOWLEDGED.has(method))
|
|
21
|
+
return "acknowledged-unpersisted";
|
|
22
|
+
if (CODEX_APP_SERVER_NOTIFICATION_MAPPED.has(method))
|
|
23
|
+
return "mapped";
|
|
24
|
+
return "unknown";
|
|
25
|
+
}
|
|
26
|
+
export function appServerRecord(value) {
|
|
27
|
+
return value !== null && typeof value === "object" && !Array.isArray(value)
|
|
28
|
+
? value
|
|
29
|
+
: undefined;
|
|
30
|
+
}
|
|
31
|
+
const CODEX_APP_SERVER_ACTIVITY_ITEM_TYPES = [
|
|
32
|
+
"commandExecution",
|
|
33
|
+
"fileChange",
|
|
34
|
+
"webSearch",
|
|
35
|
+
"mcpToolCall",
|
|
36
|
+
];
|
|
37
|
+
function isCodexAppServerActivityItemType(value) {
|
|
38
|
+
return CODEX_APP_SERVER_ACTIVITY_ITEM_TYPES.includes(value);
|
|
39
|
+
}
|
|
40
|
+
export function appServerActivityCall(itemType, item) {
|
|
41
|
+
switch (itemType) {
|
|
42
|
+
case "commandExecution":
|
|
43
|
+
return typeof item.command === "string"
|
|
44
|
+
? {
|
|
45
|
+
kind: "run",
|
|
46
|
+
command: item.command,
|
|
47
|
+
...(typeof item.description === "string" && item.description ? { description: item.description } : {}),
|
|
48
|
+
}
|
|
49
|
+
: { kind: "other", display: itemType };
|
|
50
|
+
case "fileChange": {
|
|
51
|
+
const changes = Array.isArray(item.changes)
|
|
52
|
+
? item.changes.flatMap((entry) => {
|
|
53
|
+
const change = appServerRecord(entry);
|
|
54
|
+
if (!change || typeof change.path !== "string")
|
|
55
|
+
return [];
|
|
56
|
+
if (change.kind !== "add" && change.kind !== "update" && change.kind !== "delete")
|
|
57
|
+
return [];
|
|
58
|
+
const op = change.kind;
|
|
59
|
+
return [{ op, path: change.path }];
|
|
60
|
+
})
|
|
61
|
+
: [];
|
|
62
|
+
if (changes.length > 0)
|
|
63
|
+
return { kind: "fileChange", changes };
|
|
64
|
+
return { kind: "other", display: typeof item.path === "string" ? `fileChange ${item.path}` : itemType };
|
|
65
|
+
}
|
|
66
|
+
case "webSearch":
|
|
67
|
+
return typeof item.query === "string" ? { kind: "search", query: item.query } : { kind: "other", display: itemType };
|
|
68
|
+
case "mcpToolCall":
|
|
69
|
+
return typeof item.server === "string" && typeof item.tool === "string"
|
|
70
|
+
? { kind: "other", display: `${item.server}.${item.tool}` }
|
|
71
|
+
: { kind: "other", display: itemType };
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
export function appServerActivityResult(item) {
|
|
75
|
+
const exitCode = typeof item.exitCode === "number" && Number.isFinite(item.exitCode) ? item.exitCode : undefined;
|
|
76
|
+
const message = typeof item.aggregatedOutput === "string" && item.aggregatedOutput
|
|
77
|
+
? boundActivityExcerpt(item.aggregatedOutput)
|
|
78
|
+
: typeof item.output === "string" && item.output
|
|
79
|
+
? boundActivityExcerpt(item.output)
|
|
80
|
+
: typeof item.error === "string" && item.error
|
|
81
|
+
? boundActivityExcerpt(item.error)
|
|
82
|
+
: undefined;
|
|
83
|
+
const details = item.details !== null && typeof item.details === "object" && !Array.isArray(item.details)
|
|
84
|
+
? item.details
|
|
85
|
+
: undefined;
|
|
86
|
+
const patch = typeof details?.patch === "string" ? details.patch : undefined;
|
|
87
|
+
const diffstat = patch ? diffstatFromUnifiedPatch(patch) : undefined;
|
|
88
|
+
return {
|
|
89
|
+
status: item.status === "failed" || (exitCode !== undefined && exitCode !== 0) ? "error" : "ok",
|
|
90
|
+
...(message ? { message } : {}),
|
|
91
|
+
...(exitCode === undefined ? {} : { exitCode }),
|
|
92
|
+
...(diffstat ? { diffstat } : {}),
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
export function extractReasoningSummary(item) {
|
|
96
|
+
const summary = item.summary;
|
|
97
|
+
const text = typeof summary === "string"
|
|
98
|
+
? summary
|
|
99
|
+
: Array.isArray(summary)
|
|
100
|
+
? summary.map((part) => {
|
|
101
|
+
if (typeof part === "string")
|
|
102
|
+
return part;
|
|
103
|
+
if (!part || typeof part !== "object")
|
|
104
|
+
return "";
|
|
105
|
+
const record = part;
|
|
106
|
+
if (record.type !== "summary_text" && record.type !== "text")
|
|
107
|
+
return "";
|
|
108
|
+
return typeof record.text === "string" ? record.text : "";
|
|
109
|
+
}).join("\n")
|
|
110
|
+
: "";
|
|
111
|
+
return text.trim().length > 0 ? text : undefined;
|
|
112
|
+
}
|
|
113
|
+
export function extractPlanItems(item) {
|
|
114
|
+
if (!Array.isArray(item.items))
|
|
115
|
+
return [];
|
|
116
|
+
return item.items.slice(0, AGENT_PLAN_MAX_ITEMS).flatMap((value) => {
|
|
117
|
+
if (!value || typeof value !== "object")
|
|
118
|
+
return [];
|
|
119
|
+
const record = value;
|
|
120
|
+
if (typeof record.text !== "string" || record.text.trim().length === 0)
|
|
121
|
+
return [];
|
|
122
|
+
return [{
|
|
123
|
+
text: record.text.slice(0, AGENT_PLAN_ITEM_TEXT_MAX_CHARS),
|
|
124
|
+
done: record.completed === true || record.done === true,
|
|
125
|
+
...(record.active === true ? { active: true } : {}),
|
|
126
|
+
}];
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
export function createCodexAppServerTurnState() {
|
|
130
|
+
return {
|
|
131
|
+
responseSeenTurnIds: new Set(),
|
|
132
|
+
completedBeforeResponseTurnIds: new Set(),
|
|
133
|
+
activeTurnAcceptingSteer: false,
|
|
134
|
+
lastAgentMessage: "",
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
export function recordActiveTurnId(state, candidate, source) {
|
|
138
|
+
const turnId = coerceString(candidate);
|
|
139
|
+
if (!turnId)
|
|
140
|
+
throw new Error(`${CODEX_APP_SERVER_PROVIDER} ${source} omitted active turn id`);
|
|
141
|
+
if (!state.activeTurnId) {
|
|
142
|
+
state.activeTurnId = turnId;
|
|
143
|
+
state.activeTurnSource = source;
|
|
144
|
+
return turnId;
|
|
145
|
+
}
|
|
146
|
+
if (state.activeTurnId !== turnId) {
|
|
147
|
+
throw new Error(`${CODEX_APP_SERVER_PROVIDER} active turn id mismatch: ${state.activeTurnSource ?? "first"}=${state.activeTurnId}, ${source}=${turnId}`);
|
|
148
|
+
}
|
|
149
|
+
return state.activeTurnId;
|
|
150
|
+
}
|
|
151
|
+
export function recordTurnStartResponse(state, candidate) {
|
|
152
|
+
const turnId = coerceString(candidate);
|
|
153
|
+
if (!turnId)
|
|
154
|
+
throw new Error(`${CODEX_APP_SERVER_PROVIDER} response omitted active turn id`);
|
|
155
|
+
if (state.completedBeforeResponseTurnIds.delete(turnId))
|
|
156
|
+
return turnId;
|
|
157
|
+
state.responseSeenTurnIds.add(turnId);
|
|
158
|
+
return recordActiveTurnId(state, turnId, "response");
|
|
159
|
+
}
|
|
160
|
+
export function createCodexAppServerTurnMonitor(options) {
|
|
161
|
+
const { server, builder, state, builderDiagnostic, onEvidence, onSyncCheckpoint, onDeliveryOpportunity, enqueue, drainPendingTells, rejectPendingTells, setFailActiveTurn, pendingTellCount, isDrainingTells, setFinishAfterTellDrain, } = options;
|
|
162
|
+
const turnCompleted = new Promise((resolve, reject) => {
|
|
163
|
+
let unsubNotifications = () => { };
|
|
164
|
+
let unsubServerRequest = () => { };
|
|
165
|
+
let settled = false;
|
|
166
|
+
const failTurn = (error) => {
|
|
167
|
+
if (settled)
|
|
168
|
+
return;
|
|
169
|
+
settled = true;
|
|
170
|
+
rejectPendingTells(error);
|
|
171
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
172
|
+
builder.emit({ type: "diagnostic", text: message });
|
|
173
|
+
builder.emit({ type: "fail", message });
|
|
174
|
+
unsubNotifications();
|
|
175
|
+
unsubServerRequest();
|
|
176
|
+
reject(error);
|
|
177
|
+
};
|
|
178
|
+
const failServerRequest = (request) => {
|
|
179
|
+
// Policy A forbids interactive server→client requests. Fail immediately with named diagnostic.
|
|
180
|
+
const message = `${CODEX_APP_SERVER_PROVIDER} unexpected server request '${request.method}' (id=${String(request.id)}); non-interactive policy forbids server→client requests`;
|
|
181
|
+
failTurn(new Error(message));
|
|
182
|
+
};
|
|
183
|
+
const requestDeliveryOpportunity = () => {
|
|
184
|
+
try {
|
|
185
|
+
onDeliveryOpportunity?.({ enqueue });
|
|
186
|
+
}
|
|
187
|
+
catch {
|
|
188
|
+
// Delivery observation cannot alter provider control flow.
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
unsubServerRequest = server.onServerRequest((request) => {
|
|
192
|
+
try {
|
|
193
|
+
builder.ingress(CODEX_APP_SERVER_PROVIDER, request.method, request, "mapped", () => {
|
|
194
|
+
failServerRequest(request);
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
finally {
|
|
198
|
+
requestDeliveryOpportunity();
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
unsubNotifications = server.onNotification((notification) => {
|
|
202
|
+
try {
|
|
203
|
+
builder.ingress(CODEX_APP_SERVER_PROVIDER, notification.method, notification, codexAppServerNotificationDisposition(notification.method), () => {
|
|
204
|
+
if (notification.method === "item/agentMessage/delta") {
|
|
205
|
+
const delta = coerceString(notification.params?.delta);
|
|
206
|
+
if (delta) {
|
|
207
|
+
state.lastAgentMessage = `${state.lastAgentMessage}${delta}`;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
if (notification.method === "item/completed") {
|
|
211
|
+
const item = notification.params?.item;
|
|
212
|
+
if (!item) {
|
|
213
|
+
throw new Error(`${CODEX_APP_SERVER_PROVIDER} item/completed lacked required item`);
|
|
214
|
+
}
|
|
215
|
+
const id = coerceString(item.id);
|
|
216
|
+
const itemType = coerceString(item.type);
|
|
217
|
+
if (!id || !itemType) {
|
|
218
|
+
throw new Error(`${CODEX_APP_SERVER_PROVIDER} item/completed lacked required item id or type`);
|
|
219
|
+
}
|
|
220
|
+
if (itemType === "agentMessage") {
|
|
221
|
+
const text = coerceString(item.text);
|
|
222
|
+
if (text) {
|
|
223
|
+
state.lastAgentMessage = text;
|
|
224
|
+
builder.emit({ type: "message", id, text });
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
else if (itemType === "reasoning") {
|
|
228
|
+
const text = extractReasoningSummary(item);
|
|
229
|
+
if (text) {
|
|
230
|
+
const truncated = text.length > AGENT_THOUGHT_TEXT_MAX_CHARS;
|
|
231
|
+
builder.emit({
|
|
232
|
+
type: "thought",
|
|
233
|
+
id,
|
|
234
|
+
text: text.slice(0, AGENT_THOUGHT_TEXT_MAX_CHARS),
|
|
235
|
+
final: true,
|
|
236
|
+
...(truncated ? { truncated: true } : {}),
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
else if (itemType === "todo_list" || itemType === "todoList") {
|
|
241
|
+
builder.emit({
|
|
242
|
+
type: "plan",
|
|
243
|
+
id,
|
|
244
|
+
items: extractPlanItems(item),
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
else if (isCodexAppServerActivityItemType(itemType)) {
|
|
248
|
+
builder.emit({
|
|
249
|
+
type: "activity",
|
|
250
|
+
phase: "completed",
|
|
251
|
+
id,
|
|
252
|
+
tool: itemType,
|
|
253
|
+
call: appServerActivityCall(itemType, item),
|
|
254
|
+
result: appServerActivityResult(item),
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
if (notification.method === "turn/started") {
|
|
259
|
+
const turn = notification.params?.turn;
|
|
260
|
+
const turnId = recordActiveTurnId(state, turn?.id, "notification");
|
|
261
|
+
builder.emit({ type: "turn", phase: "started" });
|
|
262
|
+
state.activeTurnAcceptingSteer = true;
|
|
263
|
+
const checkpoint = { fence: { kind: "turn", turnId }, consumptionEvidence: false };
|
|
264
|
+
if (onEvidence) {
|
|
265
|
+
emitProviderEvidence(onEvidence, {
|
|
266
|
+
type: "checkpoint",
|
|
267
|
+
provider: CODEX_APP_SERVER_PROVIDER,
|
|
268
|
+
fence: checkpoint.fence,
|
|
269
|
+
source: "notification",
|
|
270
|
+
}, builderDiagnostic);
|
|
271
|
+
}
|
|
272
|
+
// turn/start may respond before the server has installed its active
|
|
273
|
+
// turn. The native notification is the first steer-safe boundary.
|
|
274
|
+
onSyncCheckpoint?.({ enqueue }, checkpoint);
|
|
275
|
+
void drainPendingTells();
|
|
276
|
+
}
|
|
277
|
+
if (notification.method === "turn/completed") {
|
|
278
|
+
const turn = notification.params?.turn ?? {};
|
|
279
|
+
const status = coerceString(turn.status) ?? "unknown";
|
|
280
|
+
// Either response or notification may mint the turn fence first;
|
|
281
|
+
// every later native source must agree with it.
|
|
282
|
+
const turnId = recordActiveTurnId(state, turn.id, "notification");
|
|
283
|
+
if (!state.responseSeenTurnIds.delete(turnId)) {
|
|
284
|
+
state.completedBeforeResponseTurnIds.add(turnId);
|
|
285
|
+
}
|
|
286
|
+
// Public completed only for exact native status completed.
|
|
287
|
+
if (status === "completed") {
|
|
288
|
+
builder.emit({ type: "turn", phase: "completed" });
|
|
289
|
+
// This must stay in the native notification call stack: the harness
|
|
290
|
+
// checkpoint and its observer evidence describe this exact crossing.
|
|
291
|
+
state.activeTurnAcceptingSteer = false;
|
|
292
|
+
state.activeTurnId = undefined;
|
|
293
|
+
state.activeTurnSource = undefined;
|
|
294
|
+
const checkpoint = { fence: { kind: "turn", turnId }, consumptionEvidence: true };
|
|
295
|
+
if (onEvidence) {
|
|
296
|
+
emitProviderEvidence(onEvidence, {
|
|
297
|
+
type: "checkpoint",
|
|
298
|
+
provider: CODEX_APP_SERVER_PROVIDER,
|
|
299
|
+
fence: checkpoint.fence,
|
|
300
|
+
source: "notification",
|
|
301
|
+
}, builderDiagnostic);
|
|
302
|
+
}
|
|
303
|
+
onSyncCheckpoint?.({ enqueue }, checkpoint);
|
|
304
|
+
void drainPendingTells();
|
|
305
|
+
if (pendingTellCount() > 0 || isDrainingTells()) {
|
|
306
|
+
setFinishAfterTellDrain(() => {
|
|
307
|
+
if (settled || state.activeTurnId !== undefined || pendingTellCount() > 0)
|
|
308
|
+
return;
|
|
309
|
+
settled = true;
|
|
310
|
+
unsubNotifications();
|
|
311
|
+
unsubServerRequest();
|
|
312
|
+
resolve({ status, turn });
|
|
313
|
+
});
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
if (settled)
|
|
318
|
+
return;
|
|
319
|
+
settled = true;
|
|
320
|
+
unsubNotifications();
|
|
321
|
+
unsubServerRequest();
|
|
322
|
+
resolve({ status, turn });
|
|
323
|
+
}
|
|
324
|
+
if (notification.method === "error") {
|
|
325
|
+
const message = coerceString(notification.params?.message) ?? `${CODEX_APP_SERVER_PROVIDER} error`;
|
|
326
|
+
builder.emit({ type: "diagnostic", text: message });
|
|
327
|
+
}
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
catch (error) {
|
|
331
|
+
// Notification listeners run outside the driver's await chain;
|
|
332
|
+
// convert validation failures into the controlled completion path.
|
|
333
|
+
failTurn(error);
|
|
334
|
+
}
|
|
335
|
+
finally {
|
|
336
|
+
if (notification.method !== "turn/completed") {
|
|
337
|
+
requestDeliveryOpportunity();
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
});
|
|
341
|
+
setFailActiveTurn(failTurn);
|
|
342
|
+
builder.signal.addEventListener("abort", () => {
|
|
343
|
+
unsubNotifications();
|
|
344
|
+
unsubServerRequest();
|
|
345
|
+
failTurn(createAbortError(`${CODEX_APP_SERVER_PROVIDER} exec cancelled by client`));
|
|
346
|
+
}, { once: true });
|
|
347
|
+
});
|
|
348
|
+
// Abort can settle this before initialization reaches the await below.
|
|
349
|
+
// Attach a handler immediately while preserving the original awaited error.
|
|
350
|
+
turnCompleted.catch(() => undefined);
|
|
351
|
+
return { turnCompleted };
|
|
352
|
+
}
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { emitProviderEvidence, sha256Utf8 } from "../../harness/runtime.js";
|
|
3
|
+
import { coerceString } from "../../harness/runtime.js";
|
|
4
|
+
const CODEX_APP_SERVER_PROVIDER = "codex-app-server";
|
|
5
|
+
export class CodexAppServerProcess {
|
|
6
|
+
onEvidence;
|
|
7
|
+
onEvidenceDiagnostic;
|
|
8
|
+
child;
|
|
9
|
+
pending = new Map();
|
|
10
|
+
notificationListeners = new Set();
|
|
11
|
+
serverRequestListeners = new Set();
|
|
12
|
+
exitPromise;
|
|
13
|
+
nextId = 1;
|
|
14
|
+
stdoutBuffer = "";
|
|
15
|
+
closed = false;
|
|
16
|
+
exitSettled = false;
|
|
17
|
+
gracefulClose;
|
|
18
|
+
forceClose;
|
|
19
|
+
codexHome;
|
|
20
|
+
constructor(executable, env, codexHome, onEvidence, onEvidenceDiagnostic) {
|
|
21
|
+
this.onEvidence = onEvidence;
|
|
22
|
+
this.onEvidenceDiagnostic = onEvidenceDiagnostic;
|
|
23
|
+
this.codexHome = codexHome;
|
|
24
|
+
const spawnEnv = {
|
|
25
|
+
...env,
|
|
26
|
+
...(codexHome ? { CODEX_HOME: codexHome } : {}),
|
|
27
|
+
};
|
|
28
|
+
this.child = spawn(executable, ["app-server", "--listen", "stdio://"], {
|
|
29
|
+
env: spawnEnv,
|
|
30
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
31
|
+
});
|
|
32
|
+
if (this.onEvidence && this.child.pid)
|
|
33
|
+
emitProviderEvidence(this.onEvidence, { type: "process", provider: CODEX_APP_SERVER_PROVIDER, event: "spawn", pid: this.child.pid }, this.onEvidenceDiagnostic);
|
|
34
|
+
this.child.stdout.setEncoding("utf8");
|
|
35
|
+
this.child.stderr.setEncoding("utf8");
|
|
36
|
+
this.child.stdout.on("data", (chunk) => this.onStdout(chunk));
|
|
37
|
+
// Preserve the pre-evidence diagnostic stream exactly. Process evidence
|
|
38
|
+
// never retains stderr bytes, avoiding credential boundary ambiguity.
|
|
39
|
+
this.child.stderr.on("data", (chunk) => this.onEvidenceDiagnostic?.(chunk));
|
|
40
|
+
this.child.on("error", (error) => this.failAll(error));
|
|
41
|
+
this.exitPromise = new Promise((resolve) => {
|
|
42
|
+
this.child.once("close", (exitCode) => {
|
|
43
|
+
this.exitSettled = true;
|
|
44
|
+
this.closed = true;
|
|
45
|
+
// With no observer, retain the historical error bytes. Evidence adds
|
|
46
|
+
// structured exit facts without changing provider behavior.
|
|
47
|
+
this.failAll(new Error(`${CODEX_APP_SERVER_PROVIDER} process closed`));
|
|
48
|
+
if (this.onEvidence)
|
|
49
|
+
emitProviderEvidence(this.onEvidence, { type: "process", provider: CODEX_APP_SERVER_PROVIDER, event: "exit", exitCode, stderrTail: "" }, this.onEvidenceDiagnostic);
|
|
50
|
+
resolve();
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
onNotification(listener) {
|
|
55
|
+
this.notificationListeners.add(listener);
|
|
56
|
+
return () => this.notificationListeners.delete(listener);
|
|
57
|
+
}
|
|
58
|
+
onServerRequest(listener) {
|
|
59
|
+
this.serverRequestListeners.add(listener);
|
|
60
|
+
return () => this.serverRequestListeners.delete(listener);
|
|
61
|
+
}
|
|
62
|
+
onStdout(chunk) {
|
|
63
|
+
this.stdoutBuffer += chunk;
|
|
64
|
+
let newline = this.stdoutBuffer.indexOf("\n");
|
|
65
|
+
while (newline >= 0) {
|
|
66
|
+
const line = this.stdoutBuffer.slice(0, newline).trim();
|
|
67
|
+
this.stdoutBuffer = this.stdoutBuffer.slice(newline + 1);
|
|
68
|
+
if (line.length > 0) {
|
|
69
|
+
this.handleLine(line);
|
|
70
|
+
}
|
|
71
|
+
newline = this.stdoutBuffer.indexOf("\n");
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
handleLine(line) {
|
|
75
|
+
let message;
|
|
76
|
+
try {
|
|
77
|
+
message = JSON.parse(line);
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
if (!message || typeof message !== "object")
|
|
83
|
+
return;
|
|
84
|
+
const record = message;
|
|
85
|
+
if ("id" in record && ("result" in record || "error" in record)) {
|
|
86
|
+
const id = record.id;
|
|
87
|
+
const pending = this.pending.get(id);
|
|
88
|
+
if (!pending)
|
|
89
|
+
return;
|
|
90
|
+
this.pending.delete(id);
|
|
91
|
+
if ("error" in record && record.error) {
|
|
92
|
+
const error = record.error;
|
|
93
|
+
pending.reject(new Error(error.message ?? `${CODEX_APP_SERVER_PROVIDER} RPC error`));
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
pending.resolve(record.result);
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
// Server→client request: method + id. Never ignore, auto-approve, or auto-deny (faye #316).
|
|
100
|
+
if (typeof record.method === "string" && "id" in record) {
|
|
101
|
+
const request = {
|
|
102
|
+
id: record.id,
|
|
103
|
+
method: record.method,
|
|
104
|
+
params: record.params ?? undefined,
|
|
105
|
+
};
|
|
106
|
+
for (const listener of this.serverRequestListeners) {
|
|
107
|
+
listener(request);
|
|
108
|
+
}
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
if (typeof record.method === "string" && !("id" in record)) {
|
|
112
|
+
const notification = {
|
|
113
|
+
method: record.method,
|
|
114
|
+
params: record.params ?? undefined,
|
|
115
|
+
};
|
|
116
|
+
for (const listener of this.notificationListeners) {
|
|
117
|
+
listener(notification);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
failAll(error) {
|
|
122
|
+
for (const pending of this.pending.values()) {
|
|
123
|
+
pending.reject(error);
|
|
124
|
+
}
|
|
125
|
+
this.pending.clear();
|
|
126
|
+
}
|
|
127
|
+
request(method, params) {
|
|
128
|
+
if (this.closed) {
|
|
129
|
+
return Promise.reject(new Error(`${CODEX_APP_SERVER_PROVIDER} process is closed`));
|
|
130
|
+
}
|
|
131
|
+
const id = this.nextId++;
|
|
132
|
+
const payload = params === undefined ? { id, method } : { id, method, params };
|
|
133
|
+
const serialized = `${JSON.stringify(payload)}\n`;
|
|
134
|
+
return new Promise((resolve, reject) => {
|
|
135
|
+
this.pending.set(id, { resolve, reject });
|
|
136
|
+
this.child.stdin.write(serialized, (error) => {
|
|
137
|
+
if (error) {
|
|
138
|
+
this.pending.delete(id);
|
|
139
|
+
reject(error);
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
// The write call above is the process boundary. Emitting afterward
|
|
143
|
+
// avoids evidence for a synchronous write failure while keeping the
|
|
144
|
+
// outbound fact before any asynchronous RPC acknowledgement.
|
|
145
|
+
this.emitOutbound(method, params);
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
notify(method, params) {
|
|
149
|
+
const payload = params === undefined ? { method } : { method, params };
|
|
150
|
+
this.child.stdin.write(`${JSON.stringify(payload)}\n`);
|
|
151
|
+
}
|
|
152
|
+
emitOutbound(method, params) {
|
|
153
|
+
if (!this.onEvidence)
|
|
154
|
+
return;
|
|
155
|
+
const kind = method === "thread/start" ? "thread-start" : method === "thread/resume" ? "thread-resume" : method === "turn/start" ? "turn-start" : method === "turn/steer" ? "steer" : undefined;
|
|
156
|
+
if (!kind)
|
|
157
|
+
return;
|
|
158
|
+
const text = method === "turn/steer"
|
|
159
|
+
? coerceString((params?.input?.[0]?.text))
|
|
160
|
+
: JSON.stringify(params ?? {});
|
|
161
|
+
if (text === undefined)
|
|
162
|
+
return;
|
|
163
|
+
emitProviderEvidence(this.onEvidence, { type: "outbound", provider: CODEX_APP_SERVER_PROVIDER, direction: "request", kind, ...sha256Utf8(text) }, this.onEvidenceDiagnostic);
|
|
164
|
+
const developerInstructions = coerceString(params?.developerInstructions);
|
|
165
|
+
if ((method === "thread/start" || method === "thread/resume") && developerInstructions !== undefined) {
|
|
166
|
+
emitProviderEvidence(this.onEvidence, { type: "outbound", provider: CODEX_APP_SERVER_PROVIDER, direction: "request", kind: "system-prompt", ...sha256Utf8(developerInstructions) }, this.onEvidenceDiagnostic);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
async close(mode = "graceful") {
|
|
170
|
+
if (this.exitSettled) {
|
|
171
|
+
this.closed = true;
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
if (mode === "force") {
|
|
175
|
+
this.forceClose ??= this.closeForce();
|
|
176
|
+
await this.forceClose;
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
if (this.forceClose) {
|
|
180
|
+
await this.forceClose;
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
this.gracefulClose ??= this.closeGracefully();
|
|
184
|
+
await this.gracefulClose;
|
|
185
|
+
}
|
|
186
|
+
closePipes(propagate) {
|
|
187
|
+
const failures = [];
|
|
188
|
+
for (const close of [
|
|
189
|
+
() => this.child.stdin.end(),
|
|
190
|
+
() => this.child.stdout.destroy(),
|
|
191
|
+
() => this.child.stderr.destroy(),
|
|
192
|
+
]) {
|
|
193
|
+
try {
|
|
194
|
+
close();
|
|
195
|
+
}
|
|
196
|
+
catch (error) {
|
|
197
|
+
if (propagate)
|
|
198
|
+
failures.push(error);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return failures;
|
|
202
|
+
}
|
|
203
|
+
async closeForce() {
|
|
204
|
+
this.closed = true;
|
|
205
|
+
this.failAll(new Error(`${CODEX_APP_SERVER_PROVIDER} process closed`));
|
|
206
|
+
const failures = this.closePipes(true);
|
|
207
|
+
if (!this.exitSettled) {
|
|
208
|
+
try {
|
|
209
|
+
const killed = this.child.kill("SIGKILL");
|
|
210
|
+
if (!killed && !this.exitSettled) {
|
|
211
|
+
failures.push(new Error(`${CODEX_APP_SERVER_PROVIDER} failed to force kill process`));
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
catch (error) {
|
|
215
|
+
failures.push(error);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
const hardBound = new Promise((_, reject) => {
|
|
219
|
+
const timer = setTimeout(() => reject(new Error(`${CODEX_APP_SERVER_PROVIDER} forced process exit was not observed`)), 2000);
|
|
220
|
+
timer.unref?.();
|
|
221
|
+
});
|
|
222
|
+
try {
|
|
223
|
+
await Promise.race([this.exitPromise, hardBound]);
|
|
224
|
+
}
|
|
225
|
+
catch (error) {
|
|
226
|
+
failures.push(error);
|
|
227
|
+
}
|
|
228
|
+
if (failures.length === 1)
|
|
229
|
+
throw failures[0];
|
|
230
|
+
if (failures.length > 1) {
|
|
231
|
+
throw new AggregateError(failures, `${CODEX_APP_SERVER_PROVIDER} forced close failed`);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
async closeGracefully() {
|
|
235
|
+
this.closed = true;
|
|
236
|
+
this.failAll(new Error(`${CODEX_APP_SERVER_PROVIDER} process closed`));
|
|
237
|
+
this.closePipes(false);
|
|
238
|
+
if (!this.exitSettled && this.child.exitCode === null && this.child.signalCode === null) {
|
|
239
|
+
try {
|
|
240
|
+
this.child.kill("SIGTERM");
|
|
241
|
+
}
|
|
242
|
+
catch {
|
|
243
|
+
// Graceful cleanup is best-effort.
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
const forceKill = new Promise((resolve) => {
|
|
247
|
+
const timer = setTimeout(() => {
|
|
248
|
+
if (!this.exitSettled) {
|
|
249
|
+
try {
|
|
250
|
+
this.child.kill("SIGKILL");
|
|
251
|
+
}
|
|
252
|
+
catch {
|
|
253
|
+
// Graceful escalation is best-effort.
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
resolve();
|
|
257
|
+
}, 1000);
|
|
258
|
+
timer.unref?.();
|
|
259
|
+
});
|
|
260
|
+
const hardBound = new Promise((resolve) => {
|
|
261
|
+
const timer = setTimeout(resolve, 2000);
|
|
262
|
+
timer.unref?.();
|
|
263
|
+
});
|
|
264
|
+
await Promise.race([this.exitPromise, Promise.all([forceKill, this.exitPromise]).then(() => undefined), hardBound]);
|
|
265
|
+
}
|
|
266
|
+
}
|