@zq-silk/yui 0.6.2 → 0.6.4
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/ARCHITECTURE.md +28 -4
- package/README.md +60 -97
- package/dist/agent/argumentPolicy.js +1 -1
- package/dist/agent/managedRuntimeEnvironment.js +1 -0
- package/dist/cli/commandCatalog.js +19 -9
- package/dist/cli/interactionPolicy.js +4 -2
- package/dist/cli.js +77 -32
- package/dist/commands/taskCommands.js +46 -11
- package/dist/commands/taskContextCommand.js +1 -1
- package/dist/commands/taskRoleRuntimeStatus.js +170 -10
- package/dist/controller/agentRuntimeObserver.js +210 -0
- package/dist/controller/clientRuntime.js +3 -21
- package/dist/controller/controller.js +47 -7
- package/dist/controller/fileSchedulerStoreAdapter.js +522 -388
- package/dist/controller/runtime.js +9 -3
- package/dist/controller/runtimeEventInbox.js +49 -295
- package/dist/controller/runtimeEventProcessor.js +184 -321
- package/dist/controller/runtimeHookRunFence.js +226 -0
- package/dist/controller/runtimeLaunchCoordinator.js +91 -26
- package/dist/controller/runtimeObservationHook.js +112 -0
- package/dist/core/controllerServer.js +5 -0
- package/dist/executor/agentAdapter.js +18 -3
- package/dist/executor/fileRoleLaunchPlanner.js +64 -15
- package/dist/executor/managedClaudeRunner.js +121 -0
- package/dist/observability/executionAudit.js +6 -3
- package/dist/repository/taskWorkspacePreparer.js +1 -4
- package/dist/run/providerRetryConfig.js +8 -3
- package/dist/runtime/agentDriver.js +229 -0
- package/dist/runtime/agentDriverObservation.js +57 -0
- package/dist/runtime/builtinAgentDrivers.js +235 -0
- package/dist/runtime/builtinTranscriptObserver.js +290 -0
- package/dist/runtime/builtinTranscriptUsage.js +97 -0
- package/dist/runtime/exactControlPlane.js +2 -2
- package/dist/runtime/index.js +1 -1
- package/dist/runtime/ports.js +12 -1
- package/dist/runtime/runtimeObservation.js +297 -0
- package/dist/runtime/runtimeProjection.js +277 -0
- package/dist/runtime/sessionTerminationGuard.js +78 -22
- package/dist/runtime/tmuxAdapters.js +35 -0
- package/dist/scheduler/activeRoleRunDelivery.js +28 -13
- package/dist/scheduler/leaderWakeupProcessor.js +21 -2
- package/dist/scheduler/roleRunLiveness.js +2 -2
- package/dist/scheduler/roleRunStall.js +62 -114
- package/dist/storage/migration/productionRegistry.js +85 -0
- package/dist/storage/sqliteStore.js +3 -3
- package/dist/storage/storageVersions.js +1 -1
- package/dist/storage/upgrade/sqliteRecordMigrationTarget.js +2 -1
- package/dist/storage/upgrade/sqliteStateMigration.js +123 -0
- package/dist/telemetry/sqliteTelemetryStore.js +0 -28
- package/dist/telemetry/telemetryCompaction.js +1 -0
- package/dist/telemetry/telemetryConfig.js +4 -5
- package/dist/tmux/tmuxManager.js +136 -22
- package/dist/web/assets/client/view.js +1 -1
- package/dist/web/tmuxWebTerminal.js +17 -12
- package/dist/web/webSnapshot.js +1 -1
- package/dist/worktree/managedWorkspace.js +14 -0
- package/i18n/README.zh-CN.md +12 -7
- package/package.json +1 -1
- package/dist/controller/claudeLifecycleHook.js +0 -203
- package/dist/controller/codexLifecycleHook.js +0 -108
- package/dist/controller/providerHookRunFence.js +0 -156
- package/dist/lifecycle/providerLifecycleMapping.js +0 -190
- package/dist/telemetry/telemetryRouter.js +0 -32
|
@@ -1,203 +0,0 @@
|
|
|
1
|
-
import { callController } from "../core/controllerClient.js";
|
|
2
|
-
import { runtimeLifecycleSignalKey } from "../runtime/lifecycleReservation.js";
|
|
3
|
-
import { FileRuntimeEventInbox, MAX_RUNTIME_EVENT_FILE_BYTES } from "./runtimeEventInbox.js";
|
|
4
|
-
import { resolveProviderHookRunFence } from "./providerHookRunFence.js";
|
|
5
|
-
/**
|
|
6
|
-
* Hidden CLI entrypoint used by the managed Claude lifecycle plugin. It parses
|
|
7
|
-
* by hook_event_name and writes one immutable runtime-inbox event per fact:
|
|
8
|
-
* SessionStart → native-session-lifecycle (carrying the source variant),
|
|
9
|
-
* UserPromptSubmit → native-prompt-accepted (the exact provider-accepted fence),
|
|
10
|
-
* PostToolUse → native-turn-progress (the exact provider/tool progress fence),
|
|
11
|
-
* StopFailure → claude-stop-failure. The durable write is authoritative; the
|
|
12
|
-
* socket call is only a wake-up hint.
|
|
13
|
-
*/
|
|
14
|
-
export async function runClaudeLifecycleHookCommand(stdinJson, environment = process.env, call = callController) {
|
|
15
|
-
const home = requireIdentity(environment.YUI_HOME, "YUI_HOME");
|
|
16
|
-
const inbox = new FileRuntimeEventInbox(home);
|
|
17
|
-
const envelope = parseClaudeHookEnvelope(stdinJson, environment);
|
|
18
|
-
if (envelope.kind === "session-start") {
|
|
19
|
-
inbox.enqueueSessionLifecycle({
|
|
20
|
-
scope: "task",
|
|
21
|
-
taskId: envelope.taskId,
|
|
22
|
-
roleName: envelope.roleName,
|
|
23
|
-
agentId: envelope.agentId,
|
|
24
|
-
adapterId: "claude",
|
|
25
|
-
launchId: envelope.launchId,
|
|
26
|
-
nativeSessionId: envelope.nativeSessionId,
|
|
27
|
-
runId: envelope.runId,
|
|
28
|
-
sessionSource: envelope.sessionSource
|
|
29
|
-
});
|
|
30
|
-
}
|
|
31
|
-
else if (envelope.kind === "prompt-submit") {
|
|
32
|
-
inbox.enqueuePromptAccepted({
|
|
33
|
-
scope: "task",
|
|
34
|
-
taskId: envelope.taskId,
|
|
35
|
-
roleName: envelope.roleName,
|
|
36
|
-
agentId: envelope.agentId,
|
|
37
|
-
adapterId: "claude",
|
|
38
|
-
launchId: envelope.launchId,
|
|
39
|
-
nativeSessionId: envelope.nativeSessionId,
|
|
40
|
-
runId: envelope.runId,
|
|
41
|
-
receiptId: envelope.receiptId
|
|
42
|
-
});
|
|
43
|
-
}
|
|
44
|
-
else if (envelope.kind === "turn-progress") {
|
|
45
|
-
inbox.enqueueProviderProgress({
|
|
46
|
-
scope: "task",
|
|
47
|
-
taskId: envelope.taskId,
|
|
48
|
-
roleName: envelope.roleName,
|
|
49
|
-
agentId: envelope.agentId,
|
|
50
|
-
adapterId: "claude",
|
|
51
|
-
launchId: envelope.launchId,
|
|
52
|
-
nativeSessionId: envelope.nativeSessionId,
|
|
53
|
-
runId: envelope.runId,
|
|
54
|
-
progressId: envelope.progressId
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
else {
|
|
58
|
-
inbox.enqueueClaudeStopFailure({
|
|
59
|
-
scope: "task",
|
|
60
|
-
taskId: envelope.taskId,
|
|
61
|
-
roleName: envelope.roleName,
|
|
62
|
-
agentId: envelope.agentId,
|
|
63
|
-
adapterId: "claude",
|
|
64
|
-
launchId: envelope.launchId,
|
|
65
|
-
nativeSessionId: envelope.nativeSessionId,
|
|
66
|
-
runId: envelope.runId,
|
|
67
|
-
error: envelope.error,
|
|
68
|
-
...(envelope.errorDetails === undefined
|
|
69
|
-
? {}
|
|
70
|
-
: { errorDetails: envelope.errorDetails }),
|
|
71
|
-
...(envelope.lastAssistantMessage === undefined
|
|
72
|
-
? {}
|
|
73
|
-
: { lastAssistantMessage: envelope.lastAssistantMessage })
|
|
74
|
-
});
|
|
75
|
-
}
|
|
76
|
-
// The immutable event is authoritative; the socket call is only a hint.
|
|
77
|
-
await call(home, "scheduler.signal", {
|
|
78
|
-
key: runtimeLifecycleSignalKey({
|
|
79
|
-
scope: "task",
|
|
80
|
-
taskId: envelope.taskId,
|
|
81
|
-
roleName: envelope.roleName
|
|
82
|
-
})
|
|
83
|
-
}, { timeoutMs: 100 }).catch(() => { });
|
|
84
|
-
}
|
|
85
|
-
/**
|
|
86
|
-
* Parses a Claude hook payload by hook_event_name into the exact fenced
|
|
87
|
-
* envelope. Every event requires the managed launch envelope (task/role/agent/
|
|
88
|
-
* launch/run) and the payload session id must match YUI_NATIVE_SESSION_ID, so a
|
|
89
|
-
* mismatched generation fails closed before any inbox write.
|
|
90
|
-
*/
|
|
91
|
-
export function parseClaudeHookEnvelope(stdinJson, environment) {
|
|
92
|
-
const payload = parseObject(stdinJson);
|
|
93
|
-
if (payload.hook_event_name !== "SessionStart"
|
|
94
|
-
&& payload.hook_event_name !== "UserPromptSubmit"
|
|
95
|
-
&& payload.hook_event_name !== "PostToolUse"
|
|
96
|
-
&& payload.hook_event_name !== "StopFailure") {
|
|
97
|
-
throw new Error("Managed Claude lifecycle ingestion received an unsupported hook event.");
|
|
98
|
-
}
|
|
99
|
-
const sessionSource = payload.hook_event_name === "SessionStart"
|
|
100
|
-
? requireIdentity(payload.source, "Claude SessionStart source")
|
|
101
|
-
: undefined;
|
|
102
|
-
const base = parseClaudeEnvelope(payload, environment, {
|
|
103
|
-
allowPreallocatedClaudeStartup: sessionSource === "startup"
|
|
104
|
-
});
|
|
105
|
-
switch (payload.hook_event_name) {
|
|
106
|
-
case "SessionStart":
|
|
107
|
-
return {
|
|
108
|
-
...base,
|
|
109
|
-
kind: "session-start",
|
|
110
|
-
sessionSource: sessionSource
|
|
111
|
-
};
|
|
112
|
-
case "UserPromptSubmit":
|
|
113
|
-
return {
|
|
114
|
-
...base,
|
|
115
|
-
kind: "prompt-submit",
|
|
116
|
-
receiptId: requireIdentity(base.receiptId, "Claude transport receipt id")
|
|
117
|
-
};
|
|
118
|
-
case "PostToolUse":
|
|
119
|
-
return {
|
|
120
|
-
...base,
|
|
121
|
-
kind: "turn-progress",
|
|
122
|
-
progressId: requireIdentity(payload.tool_use_id, "Claude PostToolUse id")
|
|
123
|
-
};
|
|
124
|
-
case "StopFailure":
|
|
125
|
-
return {
|
|
126
|
-
...base,
|
|
127
|
-
kind: "stop-failure",
|
|
128
|
-
error: requireResult(payload.error, "Claude StopFailure error"),
|
|
129
|
-
...(payload.error_details === undefined
|
|
130
|
-
? {}
|
|
131
|
-
: { errorDetails: requireResult(payload.error_details, "Claude StopFailure error_details") }),
|
|
132
|
-
...(payload.last_assistant_message === undefined
|
|
133
|
-
? {}
|
|
134
|
-
: {
|
|
135
|
-
lastAssistantMessage: requireResult(payload.last_assistant_message, "Claude StopFailure last_assistant_message")
|
|
136
|
-
})
|
|
137
|
-
};
|
|
138
|
-
default:
|
|
139
|
-
throw new Error("Managed Claude lifecycle ingestion received an unsupported hook event.");
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
function parseClaudeEnvelope(payload, environment, options) {
|
|
143
|
-
const nativeSessionId = requireIdentity(payload.session_id, "Claude session id");
|
|
144
|
-
return {
|
|
145
|
-
...resolveProviderHookRunFence(environment, "claude", nativeSessionId, options),
|
|
146
|
-
adapterId: "claude"
|
|
147
|
-
};
|
|
148
|
-
}
|
|
149
|
-
export function parseClaudeStopFailureHookNotification(stdinJson, environment) {
|
|
150
|
-
const envelope = parseClaudeHookEnvelope(stdinJson, environment);
|
|
151
|
-
if (envelope.kind !== "stop-failure") {
|
|
152
|
-
throw new Error("Managed Claude lifecycle ingestion accepts only StopFailure.");
|
|
153
|
-
}
|
|
154
|
-
return {
|
|
155
|
-
taskId: envelope.taskId,
|
|
156
|
-
roleName: envelope.roleName,
|
|
157
|
-
agentId: envelope.agentId,
|
|
158
|
-
adapterId: "claude",
|
|
159
|
-
launchId: envelope.launchId,
|
|
160
|
-
runId: envelope.runId,
|
|
161
|
-
nativeSessionId: envelope.nativeSessionId,
|
|
162
|
-
type: "StopFailure",
|
|
163
|
-
error: envelope.error,
|
|
164
|
-
...(envelope.errorDetails === undefined ? {} : { errorDetails: envelope.errorDetails }),
|
|
165
|
-
...(envelope.lastAssistantMessage === undefined
|
|
166
|
-
? {}
|
|
167
|
-
: { lastAssistantMessage: envelope.lastAssistantMessage })
|
|
168
|
-
};
|
|
169
|
-
}
|
|
170
|
-
function parseObject(value) {
|
|
171
|
-
if (value === undefined
|
|
172
|
-
|| Buffer.byteLength(value, "utf8") > MAX_RUNTIME_EVENT_FILE_BYTES) {
|
|
173
|
-
throw new Error("Claude lifecycle hook stdin JSON is invalid.");
|
|
174
|
-
}
|
|
175
|
-
try {
|
|
176
|
-
const parsed = JSON.parse(value);
|
|
177
|
-
if (!isObject(parsed))
|
|
178
|
-
throw new Error("shape");
|
|
179
|
-
return parsed;
|
|
180
|
-
}
|
|
181
|
-
catch {
|
|
182
|
-
throw new Error("Claude lifecycle hook stdin JSON is invalid.");
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
function requireIdentity(value, label) {
|
|
186
|
-
if (typeof value !== "string" || value.includes("\0")) {
|
|
187
|
-
throw new Error(`${label} is required.`);
|
|
188
|
-
}
|
|
189
|
-
const normalized = value.trim();
|
|
190
|
-
if (normalized.length === 0 || normalized.length > 1_024) {
|
|
191
|
-
throw new Error(`${label} is invalid.`);
|
|
192
|
-
}
|
|
193
|
-
return normalized;
|
|
194
|
-
}
|
|
195
|
-
function requireResult(value, label) {
|
|
196
|
-
if (typeof value !== "string" || value.includes("\0") || value.trim().length === 0) {
|
|
197
|
-
throw new Error(`${label} is required.`);
|
|
198
|
-
}
|
|
199
|
-
return value;
|
|
200
|
-
}
|
|
201
|
-
function isObject(value) {
|
|
202
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
203
|
-
}
|
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
import { callController } from "../core/controllerClient.js";
|
|
2
|
-
import { runtimeLifecycleSignalKey } from "../runtime/lifecycleReservation.js";
|
|
3
|
-
import { FileRuntimeEventInbox, MAX_RUNTIME_EVENT_FILE_BYTES } from "./runtimeEventInbox.js";
|
|
4
|
-
import { resolveProviderHookRunFence } from "./providerHookRunFence.js";
|
|
5
|
-
/**
|
|
6
|
-
* Hidden CLI entrypoint used by the managed Codex lifecycle hooks. Codex 0.145
|
|
7
|
-
* fires SessionStart and UserPromptSubmit inside run_turn(input); this parses
|
|
8
|
-
* by hook_event_name and writes one immutable runtime-inbox event per fact.
|
|
9
|
-
* SessionStart maps (via the adapter) to provider-session-started only — never
|
|
10
|
-
* pre-input readiness; UserPromptSubmit is the exact provider-accepted fence.
|
|
11
|
-
* The durable write is authoritative; the socket call is only a wake-up hint.
|
|
12
|
-
*/
|
|
13
|
-
export async function runCodexLifecycleHookCommand(stdinJson, environment = process.env, call = callController) {
|
|
14
|
-
const home = requireIdentity(environment.YUI_HOME, "YUI_HOME");
|
|
15
|
-
const inbox = new FileRuntimeEventInbox(home);
|
|
16
|
-
const envelope = parseCodexHookEnvelope(stdinJson, environment);
|
|
17
|
-
if (envelope.kind === "session-start") {
|
|
18
|
-
inbox.enqueueSessionLifecycle({
|
|
19
|
-
scope: "task",
|
|
20
|
-
taskId: envelope.taskId,
|
|
21
|
-
roleName: envelope.roleName,
|
|
22
|
-
agentId: envelope.agentId,
|
|
23
|
-
adapterId: "codex",
|
|
24
|
-
launchId: envelope.launchId,
|
|
25
|
-
nativeSessionId: envelope.nativeSessionId,
|
|
26
|
-
runId: envelope.runId
|
|
27
|
-
// Codex SessionStart arrives within the first turn, not before input.
|
|
28
|
-
});
|
|
29
|
-
}
|
|
30
|
-
else {
|
|
31
|
-
inbox.enqueuePromptAccepted({
|
|
32
|
-
scope: "task",
|
|
33
|
-
taskId: envelope.taskId,
|
|
34
|
-
roleName: envelope.roleName,
|
|
35
|
-
agentId: envelope.agentId,
|
|
36
|
-
adapterId: "codex",
|
|
37
|
-
launchId: envelope.launchId,
|
|
38
|
-
nativeSessionId: envelope.nativeSessionId,
|
|
39
|
-
runId: envelope.runId,
|
|
40
|
-
receiptId: envelope.receiptId
|
|
41
|
-
});
|
|
42
|
-
}
|
|
43
|
-
await call(home, "scheduler.signal", {
|
|
44
|
-
key: runtimeLifecycleSignalKey({
|
|
45
|
-
scope: "task",
|
|
46
|
-
taskId: envelope.taskId,
|
|
47
|
-
roleName: envelope.roleName
|
|
48
|
-
})
|
|
49
|
-
}, { timeoutMs: 100 }).catch(() => { });
|
|
50
|
-
}
|
|
51
|
-
/**
|
|
52
|
-
* Parses a Codex hook payload by hook_event_name into the exact fenced envelope.
|
|
53
|
-
* Every event requires the managed launch envelope, and the payload session id
|
|
54
|
-
* must match YUI_NATIVE_SESSION_ID, so a mismatched generation fails closed
|
|
55
|
-
* before any inbox write.
|
|
56
|
-
*/
|
|
57
|
-
export function parseCodexHookEnvelope(stdinJson, environment) {
|
|
58
|
-
const payload = parseObject(stdinJson);
|
|
59
|
-
if (payload.hook_event_name !== "SessionStart"
|
|
60
|
-
&& payload.hook_event_name !== "UserPromptSubmit") {
|
|
61
|
-
throw new Error("Managed Codex lifecycle ingestion received an unsupported hook event.");
|
|
62
|
-
}
|
|
63
|
-
const base = parseCodexEnvelope(payload, environment);
|
|
64
|
-
switch (payload.hook_event_name) {
|
|
65
|
-
case "SessionStart":
|
|
66
|
-
return { ...base, kind: "session-start" };
|
|
67
|
-
case "UserPromptSubmit":
|
|
68
|
-
return {
|
|
69
|
-
...base,
|
|
70
|
-
kind: "prompt-submit",
|
|
71
|
-
receiptId: requireIdentity(base.receiptId, "Codex transport receipt id")
|
|
72
|
-
};
|
|
73
|
-
default:
|
|
74
|
-
throw new Error("Managed Codex lifecycle ingestion received an unsupported hook event.");
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
function parseCodexEnvelope(payload, environment) {
|
|
78
|
-
const nativeSessionId = requireIdentity(payload.session_id, "Codex session id");
|
|
79
|
-
return resolveProviderHookRunFence(environment, "codex", nativeSessionId);
|
|
80
|
-
}
|
|
81
|
-
function parseObject(value) {
|
|
82
|
-
if (value === undefined
|
|
83
|
-
|| Buffer.byteLength(value, "utf8") > MAX_RUNTIME_EVENT_FILE_BYTES) {
|
|
84
|
-
throw new Error("Codex lifecycle hook stdin JSON is invalid.");
|
|
85
|
-
}
|
|
86
|
-
try {
|
|
87
|
-
const parsed = JSON.parse(value);
|
|
88
|
-
if (!isObject(parsed))
|
|
89
|
-
throw new Error("shape");
|
|
90
|
-
return parsed;
|
|
91
|
-
}
|
|
92
|
-
catch {
|
|
93
|
-
throw new Error("Codex lifecycle hook stdin JSON is invalid.");
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
function requireIdentity(value, label) {
|
|
97
|
-
if (typeof value !== "string" || value.includes("\0")) {
|
|
98
|
-
throw new Error(`${label} is required.`);
|
|
99
|
-
}
|
|
100
|
-
const normalized = value.trim();
|
|
101
|
-
if (normalized.length === 0 || normalized.length > 1_024) {
|
|
102
|
-
throw new Error(`${label} is invalid.`);
|
|
103
|
-
}
|
|
104
|
-
return normalized;
|
|
105
|
-
}
|
|
106
|
-
function isObject(value) {
|
|
107
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
108
|
-
}
|
|
@@ -1,156 +0,0 @@
|
|
|
1
|
-
import { openCompatibleFileTaskStore } from "../storage/compatibleTaskStore.js";
|
|
2
|
-
import { hasRuntimeCleanupObligation, isRuntimeLaunchReservation, runtimeLifecycleTarget } from "../runtime/lifecycleReservation.js";
|
|
3
|
-
import { nativeSessionIdForLaunch } from "../runtime/preallocatedNativeSession.js";
|
|
4
|
-
import { YUI_CONTROL_PLANE_DESCRIPTOR, YUI_TASK_RUNTIME_DESCRIPTOR, assertExactTaskRuntimeEnvironment, exactControlPlaneDigest, parseExactControlPlaneDescriptor, refreshReusedTaskRuntimeDescriptorSource } from "../runtime/exactControlPlane.js";
|
|
5
|
-
import { formatAgentRunReceiptId } from "../task/taskRecordReference.js";
|
|
6
|
-
/**
|
|
7
|
-
* Resolves turn identity from the current durable in-flight fence. The one
|
|
8
|
-
* exception is Claude SessionStart(startup), which can arrive synchronously
|
|
9
|
-
* before Session projection and is fenced by the exact Run-bound launch
|
|
10
|
-
* reservation plus deterministic native identity. The immutable hook event is
|
|
11
|
-
* revalidated by the normal inbox fold before changing any state.
|
|
12
|
-
*/
|
|
13
|
-
export function resolveProviderHookRunFence(environment, adapterId, payloadNativeSessionId, options = {}) {
|
|
14
|
-
if (environment.YUI_SESSION_SCOPE !== "task") {
|
|
15
|
-
throw new Error("Provider lifecycle hook requires a Task session scope.");
|
|
16
|
-
}
|
|
17
|
-
if (environment.YUI_ADAPTER_ID !== adapterId) {
|
|
18
|
-
throw new Error(`Provider lifecycle hook requires the ${adapterId} adapter.`);
|
|
19
|
-
}
|
|
20
|
-
const home = requireIdentity(environment.YUI_HOME, "YUI_HOME");
|
|
21
|
-
const taskId = requireIdentity(environment.YUI_TASK_ID, "Task id");
|
|
22
|
-
const roleName = requireIdentity(environment.YUI_ROLE, "Role name");
|
|
23
|
-
const agentId = requireIdentity(environment.YUI_AGENT_ID, "Agent id");
|
|
24
|
-
const workspace = requireIdentity(environment.YUI_WORKSPACE, "YUI workspace");
|
|
25
|
-
const runtimeSource = environment[YUI_TASK_RUNTIME_DESCRIPTOR];
|
|
26
|
-
const runtime = runtimeSource === undefined
|
|
27
|
-
? undefined
|
|
28
|
-
: assertExactTaskRuntimeEnvironment(runtimeSource, environment, exactControlPlaneDigest(parseExactControlPlaneDescriptor(requireIdentity(environment[YUI_CONTROL_PLANE_DESCRIPTOR], "Exact control-plane descriptor"))), home);
|
|
29
|
-
const launchId = requireIdentity(runtime?.launchId ?? environment.YUI_LAUNCH_ID, "Launch id");
|
|
30
|
-
const nativeSessionId = requireIdentity(payloadNativeSessionId, "Provider session id");
|
|
31
|
-
const expectedNativeSessionId = runtime?.nativeSessionId ?? environment.YUI_NATIVE_SESSION_ID;
|
|
32
|
-
if (expectedNativeSessionId !== undefined
|
|
33
|
-
&& nativeSessionId !== requireIdentity(expectedNativeSessionId, "YUI native session id")) {
|
|
34
|
-
throw new Error("Provider lifecycle hook native session does not match its launch envelope.");
|
|
35
|
-
}
|
|
36
|
-
const store = openCompatibleFileTaskStore(home);
|
|
37
|
-
const task = store.getTask(taskId);
|
|
38
|
-
if (task === null || task.status !== "active") {
|
|
39
|
-
throw new Error("Provider lifecycle hook Task is not current and active.");
|
|
40
|
-
}
|
|
41
|
-
const role = store.getRole(taskId, roleName);
|
|
42
|
-
if (role === null || role.activeAgentId !== agentId) {
|
|
43
|
-
throw new Error("Provider lifecycle hook Role or Agent is not current.");
|
|
44
|
-
}
|
|
45
|
-
const sessions = store.getTaskRoleSessionSet(taskId, roleName);
|
|
46
|
-
if (sessions !== null && sessions.activeAgentId !== agentId) {
|
|
47
|
-
throw new Error("Provider lifecycle hook Session Agent is not current.");
|
|
48
|
-
}
|
|
49
|
-
const inFlight = sessions?.inFlight;
|
|
50
|
-
const session = sessions?.sessions[agentId];
|
|
51
|
-
const mailbox = store.getWorkMailbox(runtimeLifecycleTarget({
|
|
52
|
-
scope: "task",
|
|
53
|
-
taskId,
|
|
54
|
-
roleName
|
|
55
|
-
}));
|
|
56
|
-
const exactReservation = isRuntimeLaunchReservation(mailbox?.processing, launchId)
|
|
57
|
-
&& !hasRuntimeCleanupObligation(mailbox);
|
|
58
|
-
const executionRef = mailbox?.processing?.executionRef;
|
|
59
|
-
const startupRunId = options.allowPreallocatedClaudeStartup === true
|
|
60
|
-
? requireIdentity(runtime?.runId ?? environment.YUI_RUN_ID, "Run id")
|
|
61
|
-
: undefined;
|
|
62
|
-
const deterministicClaudeStartup = adapterId === "claude"
|
|
63
|
-
&& options.allowPreallocatedClaudeStartup === true
|
|
64
|
-
&& expectedNativeSessionId !== undefined
|
|
65
|
-
&& session === undefined
|
|
66
|
-
&& exactReservation
|
|
67
|
-
&& executionRef?.type === "run"
|
|
68
|
-
&& executionRef.taskId === taskId
|
|
69
|
-
&& executionRef.id === startupRunId
|
|
70
|
-
&& nativeSessionId === nativeSessionIdForLaunch(home, launchId, agentId, adapterId);
|
|
71
|
-
if ((inFlight === null || inFlight === undefined) && !deterministicClaudeStartup) {
|
|
72
|
-
throw new Error("Provider lifecycle hook has no matching durable in-flight Run.");
|
|
73
|
-
}
|
|
74
|
-
if (inFlight !== null && inFlight !== undefined && inFlight.agentId !== agentId) {
|
|
75
|
-
throw new Error("Provider lifecycle hook has no matching durable in-flight Run.");
|
|
76
|
-
}
|
|
77
|
-
if (deterministicClaudeStartup
|
|
78
|
-
&& inFlight !== null
|
|
79
|
-
&& inFlight !== undefined
|
|
80
|
-
&& (inFlight.runId !== startupRunId
|
|
81
|
-
|| inFlight.receiptId !== formatAgentRunReceiptId(taskId, startupRunId))) {
|
|
82
|
-
throw new Error("Provider lifecycle hook has no matching durable in-flight Run.");
|
|
83
|
-
}
|
|
84
|
-
const runId = inFlight?.runId ?? startupRunId;
|
|
85
|
-
let effectiveRuntime = runtime;
|
|
86
|
-
let effectiveLaunchId = launchId;
|
|
87
|
-
const sessionLaunchId = session?.launchId;
|
|
88
|
-
if (runtime !== undefined
|
|
89
|
-
&& session !== undefined
|
|
90
|
-
&& sessionLaunchId !== undefined
|
|
91
|
-
&& typeof runtimeSource === "string"
|
|
92
|
-
&& !runtimeSource.trimStart().startsWith("{")
|
|
93
|
-
&& (runtime.runId !== runId
|
|
94
|
-
|| runtime.launchId !== sessionLaunchId
|
|
95
|
-
|| runtime.nativeSessionId !== session.nativeSessionId)) {
|
|
96
|
-
// A reused native pane keeps its original descriptor source. Advance only
|
|
97
|
-
// that Hook-owned source to the current durable generation before the
|
|
98
|
-
// volatile fence; the Controller no longer scans history to keep it fresh.
|
|
99
|
-
effectiveRuntime = refreshReusedTaskRuntimeDescriptorSource(runtimeSource, home, store, {
|
|
100
|
-
runId,
|
|
101
|
-
launchId: sessionLaunchId,
|
|
102
|
-
nativeSessionId: session.nativeSessionId
|
|
103
|
-
});
|
|
104
|
-
effectiveLaunchId = effectiveRuntime.launchId;
|
|
105
|
-
}
|
|
106
|
-
if (effectiveRuntime?.runId !== undefined && effectiveRuntime.runId !== runId) {
|
|
107
|
-
throw new Error("Provider lifecycle hook Run does not match its current descriptor.");
|
|
108
|
-
}
|
|
109
|
-
const run = store.getActiveAgentRun(taskId, roleName);
|
|
110
|
-
if (run === null
|
|
111
|
-
|| run.id !== runId
|
|
112
|
-
|| run.status !== "active"
|
|
113
|
-
|| run.effective.agentId !== agentId
|
|
114
|
-
|| run.effective.adapterId !== adapterId) {
|
|
115
|
-
throw new Error("Provider lifecycle hook Run does not match durable active state.");
|
|
116
|
-
}
|
|
117
|
-
if (run.effective.workspace.root !== workspace) {
|
|
118
|
-
throw new Error("Provider lifecycle hook workspace does not match the durable Run snapshot.");
|
|
119
|
-
}
|
|
120
|
-
if (session !== undefined) {
|
|
121
|
-
if (session.adapterId !== adapterId
|
|
122
|
-
|| session.launchId !== effectiveLaunchId
|
|
123
|
-
|| session.nativeSessionId !== nativeSessionId
|
|
124
|
-
|| session.effective.workspace.root !== workspace) {
|
|
125
|
-
throw new Error("Provider lifecycle hook Session does not match its durable generation.");
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
else {
|
|
129
|
-
const runtimeDiscoveredCodex = adapterId === "codex"
|
|
130
|
-
&& expectedNativeSessionId === undefined
|
|
131
|
-
&& exactReservation;
|
|
132
|
-
if (!runtimeDiscoveredCodex && !deterministicClaudeStartup) {
|
|
133
|
-
throw new Error("Provider lifecycle hook launch is not durably reserved.");
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
return {
|
|
137
|
-
taskId,
|
|
138
|
-
roleName,
|
|
139
|
-
agentId,
|
|
140
|
-
launchId: effectiveLaunchId,
|
|
141
|
-
runId,
|
|
142
|
-
...(inFlight?.receiptId === undefined ? {} : { receiptId: inFlight.receiptId }),
|
|
143
|
-
nativeSessionId,
|
|
144
|
-
workspace
|
|
145
|
-
};
|
|
146
|
-
}
|
|
147
|
-
function requireIdentity(value, label) {
|
|
148
|
-
if (typeof value !== "string" || value.includes("\0")) {
|
|
149
|
-
throw new Error(`${label} is required.`);
|
|
150
|
-
}
|
|
151
|
-
const normalized = value.trim();
|
|
152
|
-
if (normalized.length === 0 || normalized.length > 1_024) {
|
|
153
|
-
throw new Error(`${label} is invalid.`);
|
|
154
|
-
}
|
|
155
|
-
return normalized;
|
|
156
|
-
}
|
|
@@ -1,190 +0,0 @@
|
|
|
1
|
-
import { createCanonicalLifecycleEvent, CanonicalLifecycleError } from "./canonicalLifecycleEvent.js";
|
|
2
|
-
/**
|
|
3
|
-
* Claude, mapped from its observed SessionStart / UserPromptSubmit / StopFailure
|
|
4
|
-
* contract (claude 2.1.x). SessionStart with source=startup fires at process
|
|
5
|
-
* startup, *before* the first prompt, so only that exact variant proves pre-input
|
|
6
|
-
* readiness. A resume/clear/compact SessionStart occurs within an existing
|
|
7
|
-
* session and is downgraded to provider-session-started (never pre-input-ready).
|
|
8
|
-
* A single prompt push precedes the only acceptance fence, an identity-matched
|
|
9
|
-
* UserPromptSubmit.
|
|
10
|
-
*/
|
|
11
|
-
const CLAUDE_PRE_INPUT_SESSION_SOURCE = "startup";
|
|
12
|
-
const CLAUDE_LIFECYCLE_MAPPING = {
|
|
13
|
-
adapterId: "claude",
|
|
14
|
-
preInputReadiness: {
|
|
15
|
-
status: "supported",
|
|
16
|
-
nativeEvent: "SessionStart(source=startup)",
|
|
17
|
-
note: "Claude fires SessionStart at process startup before the first prompt, so "
|
|
18
|
-
+ "readiness is proven pre-input by a native durable event. Only the "
|
|
19
|
-
+ "startup variant qualifies; resume/clear/compact are session-started only."
|
|
20
|
-
},
|
|
21
|
-
supportedSignals: [
|
|
22
|
-
"native-session-start",
|
|
23
|
-
"native-prompt-submit",
|
|
24
|
-
"native-turn-progress",
|
|
25
|
-
"native-turn-complete",
|
|
26
|
-
"native-stop-failure"
|
|
27
|
-
],
|
|
28
|
-
map(signal) {
|
|
29
|
-
switch (signal.kind) {
|
|
30
|
-
case "native-session-start": {
|
|
31
|
-
// The SessionStart source discriminator is required for Claude: readiness
|
|
32
|
-
// safety depends on distinguishing startup from later in-session variants.
|
|
33
|
-
if (signal.sessionSource === undefined) {
|
|
34
|
-
throw new CanonicalLifecycleError("Claude native-session-start requires the SessionStart source variant.");
|
|
35
|
-
}
|
|
36
|
-
if (signal.sessionSource !== CLAUDE_PRE_INPUT_SESSION_SOURCE) {
|
|
37
|
-
// A non-startup SessionStart (resume/clear/compact) fires inside an
|
|
38
|
-
// existing session and cannot prove pre-input readiness; downgrade it.
|
|
39
|
-
return createCanonicalLifecycleEvent({
|
|
40
|
-
phase: "provider-session-started",
|
|
41
|
-
source: "provider-native",
|
|
42
|
-
evidence: "provider-native-durable",
|
|
43
|
-
fence: signal.fence
|
|
44
|
-
});
|
|
45
|
-
}
|
|
46
|
-
// SessionStart(startup) proves readiness before any input for Claude.
|
|
47
|
-
return createCanonicalLifecycleEvent({
|
|
48
|
-
phase: "provider-ready",
|
|
49
|
-
source: "provider-native",
|
|
50
|
-
evidence: "provider-native-durable",
|
|
51
|
-
preInputReady: true,
|
|
52
|
-
readinessVariant: `SessionStart(source=${signal.sessionSource})`,
|
|
53
|
-
fence: signal.fence
|
|
54
|
-
});
|
|
55
|
-
}
|
|
56
|
-
case "native-prompt-submit":
|
|
57
|
-
return createCanonicalLifecycleEvent({
|
|
58
|
-
phase: "provider-accepted",
|
|
59
|
-
source: "provider-native",
|
|
60
|
-
evidence: "provider-native-durable",
|
|
61
|
-
fence: signal.fence
|
|
62
|
-
});
|
|
63
|
-
case "native-turn-progress":
|
|
64
|
-
return createCanonicalLifecycleEvent({
|
|
65
|
-
phase: "turn-progress",
|
|
66
|
-
source: "provider-native",
|
|
67
|
-
evidence: "provider-native-durable",
|
|
68
|
-
...(signal.sequence === undefined ? {} : { sequence: signal.sequence }),
|
|
69
|
-
fence: signal.fence
|
|
70
|
-
});
|
|
71
|
-
case "native-turn-complete":
|
|
72
|
-
return createCanonicalLifecycleEvent({
|
|
73
|
-
phase: "turn-terminal",
|
|
74
|
-
source: "provider-native",
|
|
75
|
-
evidence: "provider-native-durable",
|
|
76
|
-
summary: signal.summary,
|
|
77
|
-
fence: signal.fence
|
|
78
|
-
});
|
|
79
|
-
case "native-stop-failure":
|
|
80
|
-
return createCanonicalLifecycleEvent({
|
|
81
|
-
phase: "turn-terminal",
|
|
82
|
-
source: "provider-native",
|
|
83
|
-
evidence: "provider-native-durable",
|
|
84
|
-
summary: signal.summary,
|
|
85
|
-
fence: signal.fence
|
|
86
|
-
});
|
|
87
|
-
default:
|
|
88
|
-
throw unsupportedSignal("claude", signal);
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
};
|
|
92
|
-
/**
|
|
93
|
-
* Codex 0.145, mapped from its observed run_turn(input) -> SessionStart ->
|
|
94
|
-
* UserPromptSubmit ordering. SessionStart fires *inside* run_turn — after the
|
|
95
|
-
* first input — so it can only prove that the session/thread now exists, never
|
|
96
|
-
* pre-input readiness. UserPromptSubmit is the acceptance fence; the notify
|
|
97
|
-
* agent-turn-complete is the terminal fact. Codex emits no StopFailure hook.
|
|
98
|
-
*/
|
|
99
|
-
const CODEX_LIFECYCLE_MAPPING = {
|
|
100
|
-
adapterId: "codex",
|
|
101
|
-
preInputReadiness: {
|
|
102
|
-
status: "unsupported",
|
|
103
|
-
reason: "not-available",
|
|
104
|
-
note: "Codex 0.145 SessionStart fires within run_turn(input), i.e. after the "
|
|
105
|
-
+ "first input; no native event precedes the first prompt, so pre-input "
|
|
106
|
-
+ "readiness is not available and fails closed."
|
|
107
|
-
},
|
|
108
|
-
supportedSignals: [
|
|
109
|
-
"native-session-start",
|
|
110
|
-
"native-prompt-submit",
|
|
111
|
-
"native-turn-progress",
|
|
112
|
-
"native-turn-complete"
|
|
113
|
-
],
|
|
114
|
-
map(signal) {
|
|
115
|
-
switch (signal.kind) {
|
|
116
|
-
case "native-session-start":
|
|
117
|
-
// SessionStart proves the thread exists but arrives after the first
|
|
118
|
-
// input, so it maps to session-started only — never ready, never pre-input.
|
|
119
|
-
return createCanonicalLifecycleEvent({
|
|
120
|
-
phase: "provider-session-started",
|
|
121
|
-
source: "provider-native",
|
|
122
|
-
evidence: "provider-native-durable",
|
|
123
|
-
fence: signal.fence
|
|
124
|
-
});
|
|
125
|
-
case "native-prompt-submit":
|
|
126
|
-
return createCanonicalLifecycleEvent({
|
|
127
|
-
phase: "provider-accepted",
|
|
128
|
-
source: "provider-native",
|
|
129
|
-
evidence: "provider-native-durable",
|
|
130
|
-
fence: signal.fence
|
|
131
|
-
});
|
|
132
|
-
case "native-turn-progress":
|
|
133
|
-
return createCanonicalLifecycleEvent({
|
|
134
|
-
phase: "turn-progress",
|
|
135
|
-
source: "provider-native",
|
|
136
|
-
evidence: "provider-native-durable",
|
|
137
|
-
...(signal.sequence === undefined ? {} : { sequence: signal.sequence }),
|
|
138
|
-
fence: signal.fence
|
|
139
|
-
});
|
|
140
|
-
case "native-turn-complete":
|
|
141
|
-
return createCanonicalLifecycleEvent({
|
|
142
|
-
phase: "turn-terminal",
|
|
143
|
-
source: "provider-native",
|
|
144
|
-
evidence: "provider-native-durable",
|
|
145
|
-
summary: signal.summary,
|
|
146
|
-
fence: signal.fence
|
|
147
|
-
});
|
|
148
|
-
case "native-stop-failure":
|
|
149
|
-
// Codex 0.145 has no StopFailure hook; refuse rather than invent one.
|
|
150
|
-
throw unsupportedSignal("codex", signal);
|
|
151
|
-
default:
|
|
152
|
-
throw unsupportedSignal("codex", signal);
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
};
|
|
156
|
-
const PROVIDER_LIFECYCLE_MAPPINGS = Object.freeze({
|
|
157
|
-
claude: CLAUDE_LIFECYCLE_MAPPING,
|
|
158
|
-
codex: CODEX_LIFECYCLE_MAPPING
|
|
159
|
-
});
|
|
160
|
-
/**
|
|
161
|
-
* Registry lookup: returns the mapping for an adapter without the caller
|
|
162
|
-
* branching on the provider name. Unknown adapters fail closed.
|
|
163
|
-
*/
|
|
164
|
-
export function providerLifecycleMapping(adapterId) {
|
|
165
|
-
const mapping = findProviderLifecycleMapping(adapterId);
|
|
166
|
-
if (mapping === null) {
|
|
167
|
-
throw new CanonicalLifecycleError(`No lifecycle mapping for adapter: ${adapterId}.`);
|
|
168
|
-
}
|
|
169
|
-
return mapping;
|
|
170
|
-
}
|
|
171
|
-
export function findProviderLifecycleMapping(adapterId) {
|
|
172
|
-
return adapterId === "codex" || adapterId === "claude"
|
|
173
|
-
? PROVIDER_LIFECYCLE_MAPPINGS[adapterId]
|
|
174
|
-
: null;
|
|
175
|
-
}
|
|
176
|
-
/** Neutral capability lookup for consumers that need only the readiness fact. */
|
|
177
|
-
export function preInputReadinessCapability(adapterId) {
|
|
178
|
-
return providerLifecycleMapping(adapterId).preInputReadiness;
|
|
179
|
-
}
|
|
180
|
-
/**
|
|
181
|
-
* Maps a native signal through the adapter selected by its fence. The fence's
|
|
182
|
-
* adapterId chooses the mapping, so a Codex signal can never be mapped by
|
|
183
|
-
* Claude's rules or vice versa.
|
|
184
|
-
*/
|
|
185
|
-
export function mapNativeLifecycleSignal(signal) {
|
|
186
|
-
return providerLifecycleMapping(signal.fence.adapterId).map(signal);
|
|
187
|
-
}
|
|
188
|
-
function unsupportedSignal(adapterId, signal) {
|
|
189
|
-
return new CanonicalLifecycleError(`Adapter ${adapterId} does not emit native signal: ${signal.kind}.`);
|
|
190
|
-
}
|