@zq-silk/yui 0.16.1 → 0.16.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ARCHITECTURE.md +12 -0
- package/ARCHITECTURE.zh-CN.md +8 -0
- package/README.md +3 -0
- package/dist/agent/managedRuntimeEnvironment.js +0 -4
- package/dist/agentRun/agentRun.js +7 -17
- package/dist/cli/agentConfigurationPicker.js +24 -21
- package/dist/cli/commandCatalog.js +18 -10
- package/dist/cli/roleWizard.js +41 -30
- package/dist/cli/updateCommand.js +35 -13
- package/dist/cli/updateOrchestrator.js +73 -33
- package/dist/cli/updatePorts.js +40 -17
- package/dist/cli.js +35 -26
- package/dist/commands/controllerCommands.js +1 -1
- package/dist/commands/executionAuditCommands.js +2 -2
- package/dist/commands/projectCommands.js +1 -1
- package/dist/commands/releaseCommands.js +9 -38
- package/dist/commands/taskCommands.js +84 -81
- package/dist/commands/taskCompletionGate.js +0 -117
- package/dist/commands/taskRoleRuntimeStatus.js +1 -1
- package/dist/commands/taskUpstreamCommands.js +93 -54
- package/dist/context/runContextPack.js +30 -54
- package/dist/context/runInputContract.js +9 -0
- package/dist/context/sessionBootstrapManifest.js +11 -19
- package/dist/controller/agentHostObservation.js +4 -2
- package/dist/controller/controller.js +8 -6
- package/dist/controller/fileSchedulerStoreAdapter.js +85 -18
- package/dist/controller/globalInputDelivery.js +28 -7
- package/dist/controller/globalRuntimeAttention.js +34 -0
- package/dist/controller/operatorNotification.js +31 -0
- package/dist/controller/providerRetryAdmission.js +3 -1
- package/dist/controller/providerRetryDelivery.js +10 -3
- package/dist/controller/runtime.js +1 -27
- package/dist/controller/sessionOwnerReconciliation.js +42 -24
- package/dist/controller/structuredProviderObservation.js +27 -0
- package/dist/controller/updateReconciliation.js +72 -20
- package/dist/doctor/doctor.js +2 -1
- package/dist/executor/agentAdapter.js +28 -96
- package/dist/executor/agentConfigurationCatalog.js +13 -68
- package/dist/executor/agentConfigurationFields.js +120 -0
- package/dist/executor/agentConfigurationProbe.js +55 -80
- package/dist/executor/agentExecutor.js +5 -2
- package/dist/executor/executorRegistry.js +0 -9
- package/dist/executor/fileRoleLaunchPlanner.js +12 -11
- package/dist/interaction/operatorPresentation.js +2 -1
- package/dist/message/messageContinuation.js +8 -9
- package/dist/observability/executionAudit.js +1 -10
- package/dist/observability/runtimeIdentity.js +0 -23
- package/dist/output/agentConfigurationPresentation.js +4 -2
- package/dist/release/releaseHandover.js +2 -2
- package/dist/release/releaseIdempotencyStore.js +0 -23
- package/dist/release/releaseWorkflowPorts.js +8 -4
- package/dist/repository/gitWorkspace.js +66 -19
- package/dist/repository/taskWorkspacePreparer.js +75 -68
- package/dist/resources/resourceRegistry.js +90 -44
- package/dist/resources/sqliteResourceRegistry.js +3 -5
- package/dist/runtime/acpSession.js +21 -5
- package/dist/runtime/agentEndpoint.js +4 -1
- package/dist/runtime/agentHost.js +22 -62
- package/dist/runtime/agentHostCleanup.js +85 -0
- package/dist/runtime/agentHostProtocol.js +1 -1
- package/dist/runtime/builtinAgentDrivers.js +46 -119
- package/dist/runtime/builtinTranscriptObserver.js +10 -6
- package/dist/runtime/codexAppServerRuntime.js +55 -89
- package/dist/runtime/jsonLineChannel.js +35 -7
- package/dist/runtime/providerContinuationReconciliationService.js +42 -3
- package/dist/runtime/providerRetry.js +15 -0
- package/dist/runtime/providerRuntimeReconciler.js +12 -2
- package/dist/runtime/structuredProviderHost.js +106 -81
- package/dist/scheduler/activeRoleRunDelivery.js +5 -1
- package/dist/scheduler/operatorInputNotificationProcessor.js +7 -17
- package/dist/storage/upgrade/upgradeOrchestrator.js +0 -10
- package/dist/task/nextAction.js +33 -23
- package/dist/task/task.js +2 -1
- package/dist/web/assets/assetManifest.js +2 -0
- package/dist/web/assets/client/app.js +72 -3
- package/dist/web/assets/client/components.js +2 -1
- package/dist/web/assets/client/i18n.js +2 -0
- package/dist/web/assets/client/taskSummary.js +345 -0
- package/dist/web/assets/client/taskSurface.js +44 -26
- package/dist/web/assets/client/view.js +33 -0
- package/dist/web/assets/shell.js +1 -0
- package/dist/web/assets/styles/cards.js +21 -0
- package/dist/web/webServer.js +39 -5
- package/dist/web/webSessions.js +165 -0
- package/dist/web/webSnapshot.js +23 -0
- package/dist/web/webTaskSurface.js +23 -0
- package/docs/managed-turn-and-session-runtime.md +31 -0
- package/docs/managed-turn-and-session-runtime.zh-CN.md +23 -0
- package/docs/observability/README.md +47 -0
- package/docs/observability/README.zh-CN.md +37 -0
- package/docs/project-refresh.md +9 -0
- package/docs/project-refresh.zh-CN.md +8 -0
- package/docs/provider-retry.md +35 -0
- package/docs/release-workflow.md +124 -20
- package/docs/release-workflow.zh-CN.md +103 -14
- package/docs/roles-and-configuration.md +30 -0
- package/docs/roles-and-configuration.zh-CN.md +20 -0
- package/docs/task-delivery.md +109 -0
- package/docs/task-delivery.zh-CN.md +80 -0
- package/docs/task-discovery.md +9 -0
- package/docs/task-discovery.zh-CN.md +6 -0
- package/docs/testing/verification-levels.md +29 -11
- package/docs/testing/verification-levels.zh-CN.md +25 -7
- package/i18n/README.zh-CN.md +2 -0
- package/package.json +1 -1
- package/skills/yui-leader/SKILL.md +11 -0
- package/skills/yui-leader/references/execution.md +22 -0
- package/skills/yui-leader/references/planning.md +6 -2
- package/skills/yui-operator/SKILL.md +45 -13
- package/skills/yui-operator/references/task-delivery.md +99 -0
- package/skills/yui-runtime/SKILL.md +5 -3
- package/skills/yui-runtime/references/publication.md +56 -5
- package/skills/yui-runtime/references/recovery.md +12 -2
- package/dist/runtime/agentHostCompatibility.js +0 -127
|
@@ -5,16 +5,16 @@ import { homedir, tmpdir } from "node:os";
|
|
|
5
5
|
import { dirname, join, resolve } from "node:path";
|
|
6
6
|
import { createInterface } from "node:readline";
|
|
7
7
|
import { withSessionContextPointer } from "../context/sessionBootstrapManifest.js";
|
|
8
|
-
import { publishStructuredConversationRecoverability, publishStructuredProviderAccepted, publishStructuredProviderActivity, publishStructuredProviderAttachmentExit, publishStructuredProviderConnection, publishStructuredProviderGoal, publishStructuredProviderInputObserved, publishStructuredProviderInputSettlement, publishStructuredProviderOpened, publishStructuredProviderStarted, publishStructuredProviderTerminal, structuredProviderEventDelivery } from "../controller/structuredProviderObservation.js";
|
|
8
|
+
import { publishStructuredConversationRecoverability, publishStructuredProviderAccepted, publishStructuredProviderActivity, publishStructuredProviderAttachmentExit, publishStructuredProviderConnection, publishStructuredProviderDiagnostic, publishStructuredProviderGoal, publishStructuredProviderInputObserved, publishStructuredProviderInputSettlement, publishStructuredProviderOpened, publishStructuredProviderStarted, publishStructuredProviderTerminal, structuredProviderEventDelivery } from "../controller/structuredProviderObservation.js";
|
|
9
9
|
import { callController, controllerCallMayHaveApplied, ControllerClientError } from "../core/controllerClient.js";
|
|
10
10
|
import { readHomeFilesystemId } from "../core/homeFilesystemIdentity.js";
|
|
11
|
-
import { FILE_TASK_CONTROLLER_PROTOCOL_VERSION } from "../core/protocol.js";
|
|
12
11
|
import { isForeignHandoverLockHeld } from "../release/runtimeRelease.js";
|
|
13
12
|
import { yuiTmuxServerName, yuiTmuxSessionName } from "../tmux/tmuxManager.js";
|
|
14
13
|
import { tmuxSocketDirectory } from "../tmux/tmuxSocketEndpoint.js";
|
|
15
14
|
import { createAgentEndpointOwner } from "./agentEndpointOwnership.js";
|
|
16
15
|
import { providerDeliveryFailure, providerDeliveryFailureFrom, redactAgentErrorText, serializeAgentErrorRaw } from "./agentError.js";
|
|
17
|
-
import { AGENT_HOST_CONTROL_PROTOCOL
|
|
16
|
+
import { AGENT_HOST_CONTROL_PROTOCOL } from "./agentHostProtocol.js";
|
|
17
|
+
import { closeAgentHostEndpoints } from "./agentHostCleanup.js";
|
|
18
18
|
import { readAgentRunConfigurationObservation, unknownAgentRunConfiguration } from "./agentRunConfiguration.js";
|
|
19
19
|
import { CodexPreSubmissionError } from "./codexAppServerRuntime.js";
|
|
20
20
|
import { runCodexInteractiveHost } from "./codexInteractiveHost.js";
|
|
@@ -99,6 +99,12 @@ export async function runAgentHost(input) {
|
|
|
99
99
|
let conversationRecoverability = "unknown";
|
|
100
100
|
let authority;
|
|
101
101
|
let hostStopRequested = false;
|
|
102
|
+
const reportDiagnostic = async (diagnostic) => {
|
|
103
|
+
const observedPayload = activeRunPayload ?? sessionPayload ?? payload;
|
|
104
|
+
await publishStructuredProviderDiagnostic({
|
|
105
|
+
home: input.home, environment: observedPayload.environment, diagnostic
|
|
106
|
+
});
|
|
107
|
+
};
|
|
102
108
|
let snapshot = hostSnapshot("idle");
|
|
103
109
|
let dispatchTail = Promise.resolve();
|
|
104
110
|
let promptHuman = () => { };
|
|
@@ -361,6 +367,9 @@ export async function runAgentHost(input) {
|
|
|
361
367
|
handleStarted(event.value, currentPayload);
|
|
362
368
|
else if (event.type === "terminal")
|
|
363
369
|
handleTerminal(event.value);
|
|
370
|
+
else if (event.type === "diagnostic")
|
|
371
|
+
void enqueueSerialized(() => reportDiagnostic(event.value))
|
|
372
|
+
.catch(error => process.stderr.write(`Provider diagnostic delivery failed: ${serializeAgentErrorRaw(error)}\n`));
|
|
364
373
|
else if (event.type === "goal")
|
|
365
374
|
handleGoal(event.value);
|
|
366
375
|
else
|
|
@@ -617,6 +626,9 @@ export async function runAgentHost(input) {
|
|
|
617
626
|
handleStarted(event.value, next);
|
|
618
627
|
else if (event.type === "terminal")
|
|
619
628
|
handleTerminal(event.value);
|
|
629
|
+
else if (event.type === "diagnostic")
|
|
630
|
+
void enqueueSerialized(() => reportDiagnostic(event.value))
|
|
631
|
+
.catch(error => process.stderr.write(`Provider diagnostic delivery failed: ${serializeAgentErrorRaw(error)}\n`));
|
|
620
632
|
else if (event.type === "goal")
|
|
621
633
|
handleGoal(event.value);
|
|
622
634
|
else
|
|
@@ -1214,55 +1226,13 @@ export async function runAgentHost(input) {
|
|
|
1214
1226
|
if (forceKillTimer !== undefined)
|
|
1215
1227
|
clearTimeout(forceKillTimer);
|
|
1216
1228
|
humanConsole?.close();
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
if (adapterId !== undefined) {
|
|
1225
|
-
// Stop new acquisition and wait a bounded time for what is already
|
|
1226
|
-
// running. A timeout reports the references and pending effects that are
|
|
1227
|
-
// genuinely still in use; it never escalates to killing a Provider this
|
|
1228
|
-
// Endpoint does not own, and it never reports quiescence it cannot prove.
|
|
1229
|
-
const drain = await endpointOwner.stop(adapterId, ENDPOINT_DRAIN_TIMEOUT_MS);
|
|
1230
|
-
if (!drain.quiescent) {
|
|
1231
|
-
const detail = `Endpoint stop ${drain.timedOut ? "timed out" : "returned"} after ${drain.waitedMs}ms`
|
|
1232
|
-
+ ` (bound ${ENDPOINT_DRAIN_TIMEOUT_MS}ms); ${drain.references} reference(s), `
|
|
1233
|
-
+ `${drain.opening} opening client(s) and `
|
|
1234
|
-
+ `${drain.sessions.reduce((total, held) => total + held.pending.length, 0)} pending effect(s) `
|
|
1235
|
-
+ "may still be in use. Owned client resources are unknown.";
|
|
1236
|
-
updateSnapshot(hostSnapshot(snapshot.state, {
|
|
1237
|
-
...definedFields({
|
|
1238
|
-
adapterId,
|
|
1239
|
-
nativeSessionId: snapshot.nativeSessionId,
|
|
1240
|
-
conversationId: snapshot.conversationId
|
|
1241
|
-
}),
|
|
1242
|
-
...authorityFields(),
|
|
1243
|
-
detail
|
|
1244
|
-
}));
|
|
1245
|
-
// The snapshot is about to stop being reachable, and a stop this process
|
|
1246
|
-
// could not prove quiescent must remain diagnosable afterwards. Written
|
|
1247
|
-
// to the Host's own stream, which its pane and logs retain.
|
|
1248
|
-
process.stderr.write(`${detail}\n`);
|
|
1249
|
-
}
|
|
1250
|
-
}
|
|
1251
|
-
// Final cleanup is bounded on its own deadline. The bounded stop above may
|
|
1252
|
-
// already have given up on a client that never proves exit, and waiting for
|
|
1253
|
-
// that same drain again would leave the control socket open and this function
|
|
1254
|
-
// never returning. Anything still held is reported, not silently released.
|
|
1255
|
-
const remaining = await endpointOwner.close(ENDPOINT_DRAIN_TIMEOUT_MS).catch(() => []);
|
|
1256
|
-
const held = remaining.filter((drain) => !drain.quiescent);
|
|
1257
|
-
if (held.length > 0) {
|
|
1258
|
-
const references = held.reduce((total, drain) => total + drain.references, 0);
|
|
1259
|
-
const opening = held.reduce((total, drain) => total + drain.opening, 0);
|
|
1260
|
-
process.stderr.write(`Endpoint cleanup returned with ${references} reference(s) and ${opening} opening client(s) `
|
|
1261
|
-
+ `still held across ${held.length} implementation(s) (bound ${ENDPOINT_DRAIN_TIMEOUT_MS}ms); `
|
|
1262
|
-
+ "those implementations stay detached and undisposed. Owned client resources are unknown.\n");
|
|
1263
|
-
}
|
|
1264
|
-
await control.close();
|
|
1265
|
-
void sessionPayload;
|
|
1229
|
+
await closeAgentHostEndpoints({
|
|
1230
|
+
owner: endpointOwner, session, lease: endpointLease, adapterId: endpointAdapterId,
|
|
1231
|
+
attemptId: activeRunAttemptId ?? lastTerminal?.attemptId,
|
|
1232
|
+
nativeTurnId: activeNativeTurnId ?? lastTerminal?.nativeTurnId,
|
|
1233
|
+
timeoutMs: ENDPOINT_DRAIN_TIMEOUT_MS,
|
|
1234
|
+
report: reportDiagnostic, closeControl: () => control.close()
|
|
1235
|
+
});
|
|
1266
1236
|
}
|
|
1267
1237
|
}
|
|
1268
1238
|
/** Host-restart-independent OS custody. No secret or launch payload is stored. */
|
|
@@ -1497,16 +1467,6 @@ export async function openAgentHostControl(home, payload, snapshot, dispatch) {
|
|
|
1497
1467
|
...result,
|
|
1498
1468
|
snapshot: {
|
|
1499
1469
|
...result.snapshot,
|
|
1500
|
-
compatibility: {
|
|
1501
|
-
control: AGENT_HOST_CONTROL_PROTOCOL, events: AGENT_HOST_EVENT_PROTOCOL,
|
|
1502
|
-
rpc: FILE_TASK_CONTROLLER_PROTOCOL_VERSION, storage: "controller-owned"
|
|
1503
|
-
},
|
|
1504
|
-
hostProcess: { pid: process.pid, startIdentity: readLinuxProcessIdentity(process.pid).startIdentity },
|
|
1505
|
-
owner: {
|
|
1506
|
-
scope: payload.environment.YUI_SESSION_SCOPE ?? "task",
|
|
1507
|
-
...(payload.environment.YUI_TASK_ID === undefined ? {} : { taskId: payload.environment.YUI_TASK_ID }),
|
|
1508
|
-
roleName: payload.environment.YUI_ROLE ?? "unknown-role"
|
|
1509
|
-
},
|
|
1510
1470
|
...(payload.environment.YUI_SESSION_SCOPE !== "task" ? {} : {
|
|
1511
1471
|
eventDelivery: structuredProviderEventDelivery(home, payload.environment, result.snapshot.nativeSessionId)
|
|
1512
1472
|
})
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { providerDeliveryFailureFrom, serializeAgentErrorRaw } from "./agentError.js";
|
|
2
|
+
/** Finish the Host's owned cleanup steps even when one fails. Neither a failed
|
|
3
|
+
* inspection nor a timeout is proof that an implementation or client drained. */
|
|
4
|
+
export async function closeAgentHostEndpoints(input) {
|
|
5
|
+
const errors = [];
|
|
6
|
+
let lastObservedDrain;
|
|
7
|
+
const report = async (operation, error, drains) => {
|
|
8
|
+
const failure = providerDeliveryFailureFrom(error, {
|
|
9
|
+
phase: "host-stop", inputDisposition: "unknown",
|
|
10
|
+
detail: `Endpoint ${operation}: ${error instanceof Error ? error.message : String(error)}. Owned client resources are unknown.`
|
|
11
|
+
});
|
|
12
|
+
const raw = serializeAgentErrorRaw({
|
|
13
|
+
operation, error, adapterId: input.adapterId, implementation: input.lease?.implementation,
|
|
14
|
+
nativeSessionId: input.session?.nativeSessionId, processInstanceId: input.session?.processInstanceId,
|
|
15
|
+
attemptId: input.attemptId, timeoutMs: input.timeoutMs,
|
|
16
|
+
...(drains === undefined ? { lastObservedDrain } : { drains }),
|
|
17
|
+
unknown: "Current resource quiescence and pending Provider effects; no input is replayed."
|
|
18
|
+
});
|
|
19
|
+
// The Host stream remains evidence even if its Inbox cannot be written.
|
|
20
|
+
process.stderr.write(`${raw}\n`);
|
|
21
|
+
if (input.session === undefined)
|
|
22
|
+
return;
|
|
23
|
+
try {
|
|
24
|
+
await input.report({
|
|
25
|
+
nativeSessionId: input.session.nativeSessionId,
|
|
26
|
+
...(input.attemptId === undefined ? {} : { attemptId: input.attemptId }),
|
|
27
|
+
...(input.nativeTurnId === undefined ? {} : { nativeTurnId: input.nativeTurnId }),
|
|
28
|
+
failure: { ...failure, raw }
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
errors.push(error);
|
|
33
|
+
process.stderr.write(`Endpoint cleanup diagnostic delivery failed: ${serializeAgentErrorRaw(error)}\n`);
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
try {
|
|
37
|
+
try {
|
|
38
|
+
input.session?.detach();
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
errors.push(error);
|
|
42
|
+
await report("detach failed", error);
|
|
43
|
+
}
|
|
44
|
+
try {
|
|
45
|
+
await input.lease?.release();
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
errors.push(error);
|
|
49
|
+
await report("lease release failed", error);
|
|
50
|
+
}
|
|
51
|
+
if (input.adapterId !== undefined) {
|
|
52
|
+
try {
|
|
53
|
+
lastObservedDrain = await input.owner.stop(input.adapterId, input.timeoutMs);
|
|
54
|
+
if (!lastObservedDrain.quiescent) {
|
|
55
|
+
await report("stop incomplete", new Error("The bounded stop did not prove quiescence."), [lastObservedDrain]);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
errors.push(error);
|
|
60
|
+
await report("stop failed", error);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
try {
|
|
64
|
+
const remaining = await input.owner.close(input.timeoutMs);
|
|
65
|
+
const held = remaining.filter(drain => !drain.quiescent);
|
|
66
|
+
if (held.length > 0)
|
|
67
|
+
await report("close incomplete", new Error("Endpoint references remain held."), held);
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
errors.push(error);
|
|
71
|
+
await report("close failed", error);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
finally {
|
|
75
|
+
try {
|
|
76
|
+
await input.closeControl();
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
errors.push(error);
|
|
80
|
+
await report("control close failed", error);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
if (errors.length > 0)
|
|
84
|
+
throw new AggregateError(errors, "Agent Host cleanup failed; resource effects remain unknown.");
|
|
85
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createSessionOwnerIdentity } from "./sessionOwnerIdentity.js";
|
|
2
|
-
/**
|
|
2
|
+
/** Current Host event source contract, independent of Home storage access. */
|
|
3
3
|
export const AGENT_HOST_EVENT_PROTOCOL = "yui-agent-host-events/v1";
|
|
4
4
|
export const AGENT_HOST_CONTROL_PROTOCOL = "yui-agent-host/v5";
|
|
5
5
|
export function readAgentHostObservationSource(value) {
|
|
@@ -93,7 +93,8 @@ export const BUILTIN_AGENT_DRIVERS = Object.freeze([
|
|
|
93
93
|
}),
|
|
94
94
|
runtime: Object.freeze({
|
|
95
95
|
nativeSessionId: ({ payload }) => (optionalIdentityFrom(payload, ["session_id"])),
|
|
96
|
-
nativeTurnId: ({ payload }) => (optionalIdentityFrom(payload,
|
|
96
|
+
nativeTurnId: ({ hookEventName, payload }) => (optionalIdentityFrom(payload, hookEventName === "MessageDisplay"
|
|
97
|
+
? ["prompt_id", "turn_id"] : ["prompt_id"])),
|
|
97
98
|
mapError: mapClaudeAgentError,
|
|
98
99
|
mapHook: ({ hookEventName, payload, occurrenceId }) => (mapClaudeHook(hookEventName, payload, occurrenceId)),
|
|
99
100
|
classifyHook: ({ hookEventName, payload }) => Object.freeze({
|
|
@@ -102,7 +103,7 @@ export const BUILTIN_AGENT_DRIVERS = Object.freeze([
|
|
|
102
103
|
: {}),
|
|
103
104
|
terminal: isTerminalHook(hookEventName),
|
|
104
105
|
...(hookEventName !== "SubagentStop" ? {} : {
|
|
105
|
-
continuationId:
|
|
106
|
+
continuationId: firstIdentity(payload, ["agent_id"], "Claude subagent id"),
|
|
106
107
|
})
|
|
107
108
|
}),
|
|
108
109
|
observer: Object.freeze({
|
|
@@ -295,16 +296,6 @@ export function builtinAgentDriverRegistry() {
|
|
|
295
296
|
return registry;
|
|
296
297
|
}
|
|
297
298
|
function mapClaudeHook(name, payload, occurrenceId) {
|
|
298
|
-
const lifecycle = mapClaudeLifecycleHook(name, payload, occurrenceId);
|
|
299
|
-
const goal = claudeGoalObservation(payload, occurrenceId);
|
|
300
|
-
if (goal === undefined)
|
|
301
|
-
return lifecycle;
|
|
302
|
-
return Object.freeze([
|
|
303
|
-
...(Array.isArray(lifecycle) ? lifecycle : [lifecycle]),
|
|
304
|
-
goal
|
|
305
|
-
]);
|
|
306
|
-
}
|
|
307
|
-
function mapClaudeLifecycleHook(name, payload, occurrenceId) {
|
|
308
299
|
switch (name) {
|
|
309
300
|
case "SessionStart":
|
|
310
301
|
return [
|
|
@@ -316,15 +307,15 @@ function mapClaudeLifecycleHook(name, payload, occurrenceId) {
|
|
|
316
307
|
case "UserPromptSubmit":
|
|
317
308
|
return mapped("turn.accepted");
|
|
318
309
|
case "PreToolUse":
|
|
319
|
-
return operation("operation.started", "tool",
|
|
310
|
+
return operation("operation.started", "tool", firstIdentity(payload, ["tool_use_id"], "Claude tool id"));
|
|
320
311
|
case "PostToolUse":
|
|
321
|
-
return operation("operation.completed", "tool",
|
|
312
|
+
return operation("operation.completed", "tool", firstIdentity(payload, ["tool_use_id"], "Claude tool id"));
|
|
322
313
|
case "PostToolUseFailure":
|
|
323
|
-
return operation("operation.failed", "tool",
|
|
314
|
+
return operation("operation.failed", "tool", firstIdentity(payload, ["tool_use_id"], "Claude tool id"));
|
|
324
315
|
case "PermissionRequest":
|
|
325
316
|
return mapped("turn.waiting", {
|
|
326
317
|
reason: "permission",
|
|
327
|
-
waitId:
|
|
318
|
+
waitId: requireOccurrence(occurrenceId)
|
|
328
319
|
});
|
|
329
320
|
case "MessageDisplay": {
|
|
330
321
|
const messageId = optionalIdentityFrom(payload, ["message_id"]);
|
|
@@ -338,48 +329,60 @@ function mapClaudeLifecycleHook(name, payload, occurrenceId) {
|
|
|
338
329
|
: `${messageId}:${index ?? "message"}`
|
|
339
330
|
});
|
|
340
331
|
}
|
|
341
|
-
case "SubagentStart":
|
|
332
|
+
case "SubagentStart": {
|
|
333
|
+
const continuationId = firstIdentity(payload, ["agent_id"], "Claude subagent id");
|
|
342
334
|
return [
|
|
343
|
-
operation("operation.started", "subagent",
|
|
344
|
-
|
|
335
|
+
operation("operation.started", "subagent", continuationId),
|
|
336
|
+
mapped("continuation.started", {
|
|
345
337
|
execution: "active",
|
|
346
338
|
outcome: "pending",
|
|
347
339
|
attachment: "attached",
|
|
348
340
|
observationQuality: "exact",
|
|
349
341
|
mayWriteWorkspace: true
|
|
350
|
-
})
|
|
342
|
+
}, { continuationId })
|
|
351
343
|
];
|
|
352
|
-
|
|
344
|
+
}
|
|
345
|
+
case "SubagentStop": {
|
|
346
|
+
const continuationId = firstIdentity(payload, ["agent_id"], "Claude subagent id");
|
|
347
|
+
const summary = claudeSummary(payload);
|
|
353
348
|
return [
|
|
354
|
-
operation("operation.completed", "subagent",
|
|
355
|
-
...(
|
|
356
|
-
|
|
349
|
+
operation("operation.completed", "subagent", continuationId),
|
|
350
|
+
...(summary.summary === undefined ? [] : [
|
|
351
|
+
mapped("continuation.reported", {
|
|
357
352
|
execution: "quiescent",
|
|
358
|
-
outcome: "
|
|
353
|
+
outcome: "unknown",
|
|
359
354
|
attachment: "attached",
|
|
360
355
|
observationQuality: "exact",
|
|
361
356
|
mayWriteWorkspace: false,
|
|
362
|
-
reportId:
|
|
363
|
-
...
|
|
364
|
-
})
|
|
357
|
+
reportId: continuationId,
|
|
358
|
+
...summary
|
|
359
|
+
}, { continuationId })
|
|
365
360
|
]),
|
|
366
|
-
|
|
361
|
+
mapped("continuation.settled", {
|
|
367
362
|
execution: "quiescent",
|
|
368
|
-
|
|
363
|
+
// SubagentStop also fires on interrupted queries. Its current
|
|
364
|
+
// payload has no outcome/status field; a report is not success.
|
|
365
|
+
outcome: "unknown",
|
|
369
366
|
attachment: "attached",
|
|
370
367
|
observationQuality: "exact",
|
|
371
368
|
mayWriteWorkspace: false,
|
|
372
|
-
...
|
|
373
|
-
})
|
|
369
|
+
...summary
|
|
370
|
+
}, { continuationId })
|
|
374
371
|
];
|
|
375
|
-
|
|
372
|
+
}
|
|
373
|
+
case "Stop": {
|
|
374
|
+
const complete = claudeBackgroundEmpty(payload);
|
|
376
375
|
return [
|
|
377
376
|
mapped("turn.completed", optionalResultOutput(payload)),
|
|
378
377
|
mapped("native-work.snapshot", {
|
|
379
|
-
|
|
380
|
-
|
|
378
|
+
// Current Stop fields are optional lists, never the invented
|
|
379
|
+
// background_tasks_complete boolean. Only explicit empty lists
|
|
380
|
+
// prove that no background work or scheduled wake remains.
|
|
381
|
+
snapshotComplete: complete,
|
|
382
|
+
observationQuality: complete ? "exact" : "partial"
|
|
381
383
|
})
|
|
382
384
|
];
|
|
385
|
+
}
|
|
383
386
|
case "StopFailure":
|
|
384
387
|
return mapped("turn.failed", claudeFailure(payload));
|
|
385
388
|
case "SessionEnd":
|
|
@@ -391,70 +394,13 @@ function mapClaudeLifecycleHook(name, payload, occurrenceId) {
|
|
|
391
394
|
throw new Error(`Claude Code Driver does not support Hook event: ${name}.`);
|
|
392
395
|
}
|
|
393
396
|
}
|
|
394
|
-
function
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
if (payload.active_goal === null)
|
|
398
|
-
return mapped("goal.cleared");
|
|
399
|
-
const goal = objectValue(payload.active_goal);
|
|
400
|
-
if (goal === undefined)
|
|
401
|
-
return undefined;
|
|
402
|
-
const objective = textValue(goal.objective);
|
|
403
|
-
const status = normalizedGoalStatus(goal.status);
|
|
404
|
-
const updatedAt = timestampValue(goal.updated_at ?? goal.updatedAt)
|
|
405
|
-
?? timestampValue(payload.timestamp)
|
|
406
|
-
?? occurrenceTimestamp(occurrenceId);
|
|
407
|
-
if (objective === undefined || status === undefined || updatedAt === undefined)
|
|
408
|
-
return undefined;
|
|
409
|
-
const tokenBudget = positiveIntegerValue(goal.token_budget ?? goal.tokenBudget);
|
|
410
|
-
return mapped("goal.updated", {
|
|
411
|
-
goalStatus: status,
|
|
412
|
-
goalObjective: objective,
|
|
413
|
-
goalUpdatedAt: updatedAt,
|
|
414
|
-
...(optionalIdentityFrom(goal, ["turn_id", "turnId"]) === undefined
|
|
415
|
-
? {}
|
|
416
|
-
: { goalNativeTurnId: optionalIdentityFrom(goal, ["turn_id", "turnId"]) }),
|
|
417
|
-
...(tokenBudget === undefined ? {} : { goalTokenBudget: tokenBudget })
|
|
418
|
-
});
|
|
419
|
-
}
|
|
420
|
-
function normalizedGoalStatus(value) {
|
|
421
|
-
if (value === "active" || value === "paused" || value === "blocked" || value === "complete") {
|
|
422
|
-
return value;
|
|
423
|
-
}
|
|
424
|
-
if (value === "usageLimited" || value === "usage_limited" || value === "usage-limited") {
|
|
425
|
-
return "usage-limited";
|
|
426
|
-
}
|
|
427
|
-
if (value === "budgetLimited" || value === "budget_limited" || value === "budget-limited") {
|
|
428
|
-
return "budget-limited";
|
|
429
|
-
}
|
|
430
|
-
return undefined;
|
|
431
|
-
}
|
|
432
|
-
function objectValue(value) {
|
|
433
|
-
return value !== null && typeof value === "object" && !Array.isArray(value)
|
|
434
|
-
? value
|
|
435
|
-
: undefined;
|
|
436
|
-
}
|
|
437
|
-
function textValue(value) {
|
|
438
|
-
return typeof value === "string" && value.trim().length > 0 ? value.trim() : undefined;
|
|
397
|
+
function claudeSummary(payload) {
|
|
398
|
+
const summary = optionalText(payload.last_assistant_message);
|
|
399
|
+
return summary === undefined ? {} : { summary };
|
|
439
400
|
}
|
|
440
|
-
function
|
|
441
|
-
return
|
|
442
|
-
|
|
443
|
-
function timestampValue(value) {
|
|
444
|
-
if (typeof value === "string" && Number.isFinite(Date.parse(value))) {
|
|
445
|
-
return new Date(value).toISOString();
|
|
446
|
-
}
|
|
447
|
-
if (Number.isSafeInteger(value) && value >= 0) {
|
|
448
|
-
const epoch = value;
|
|
449
|
-
return new Date(epoch < 1_000_000_000_000 ? epoch * 1_000 : epoch).toISOString();
|
|
450
|
-
}
|
|
451
|
-
return undefined;
|
|
452
|
-
}
|
|
453
|
-
function occurrenceTimestamp(value) {
|
|
454
|
-
const prefix = value?.slice(0, 24);
|
|
455
|
-
return prefix !== undefined && Number.isFinite(Date.parse(prefix))
|
|
456
|
-
? new Date(prefix).toISOString()
|
|
457
|
-
: undefined;
|
|
401
|
+
function claudeBackgroundEmpty(payload) {
|
|
402
|
+
return Array.isArray(payload.background_tasks) && payload.background_tasks.length === 0
|
|
403
|
+
&& Array.isArray(payload.session_crons) && payload.session_crons.length === 0;
|
|
458
404
|
}
|
|
459
405
|
function mapCodexHook(name, payload, occurrenceId) {
|
|
460
406
|
switch (name) {
|
|
@@ -531,16 +477,6 @@ function reportId(payload) {
|
|
|
531
477
|
return optionalIdentityFrom(payload, ["report_id", "message_id", "agent_id", "subagent_id"])
|
|
532
478
|
?? subagentId(payload);
|
|
533
479
|
}
|
|
534
|
-
function continuationOutcome(payload) {
|
|
535
|
-
if (payload.cancelled === true || payload.status === "cancelled")
|
|
536
|
-
return "cancelled";
|
|
537
|
-
if (payload.error !== undefined || payload.status === "failed")
|
|
538
|
-
return "failed";
|
|
539
|
-
if (payload.status === undefined || payload.status === "completed" || payload.status === "succeeded") {
|
|
540
|
-
return "succeeded";
|
|
541
|
-
}
|
|
542
|
-
return "unknown";
|
|
543
|
-
}
|
|
544
480
|
function operation(kind, operationKind, operationId) {
|
|
545
481
|
return mapped(kind, { operationId, operation: operationKind });
|
|
546
482
|
}
|
|
@@ -589,11 +525,6 @@ function claudeFailure(payload) {
|
|
|
589
525
|
const lastOutput = optionalText(payload.last_assistant_message);
|
|
590
526
|
const raw = serializeAgentErrorRaw(payload);
|
|
591
527
|
const classification = mapClaudeAgentError({ message: code, raw });
|
|
592
|
-
const retryAfterMs = typeof payload.retry_after_ms === "number"
|
|
593
|
-
&& Number.isSafeInteger(payload.retry_after_ms)
|
|
594
|
-
&& payload.retry_after_ms > 0
|
|
595
|
-
? payload.retry_after_ms
|
|
596
|
-
: undefined;
|
|
597
528
|
return {
|
|
598
529
|
failure: {
|
|
599
530
|
error: standardAgentError({
|
|
@@ -602,13 +533,9 @@ function claudeFailure(payload) {
|
|
|
602
533
|
classification,
|
|
603
534
|
message: code,
|
|
604
535
|
raw,
|
|
605
|
-
inputDisposition: "accepted"
|
|
606
|
-
...(retryAfterMs === undefined ? {} : { retryAfterMs })
|
|
536
|
+
inputDisposition: "accepted"
|
|
607
537
|
}),
|
|
608
|
-
...(lastOutput === undefined ? {} : { lastOutput })
|
|
609
|
-
...(payload.run_terminal === true || payload.unrecoverable === true
|
|
610
|
-
? { runTerminal: true }
|
|
611
|
-
: {})
|
|
538
|
+
...(lastOutput === undefined ? {} : { lastOutput })
|
|
612
539
|
},
|
|
613
540
|
summary: [
|
|
614
541
|
"Agent turn failed.",
|
|
@@ -418,21 +418,25 @@ function normalizeCursor(input) {
|
|
|
418
418
|
if (!Number.isSafeInteger(offset) || offset < 0
|
|
419
419
|
|| typeof remainder !== "string"
|
|
420
420
|
|| state === null || typeof state !== "object" || Array.isArray(state)
|
|
421
|
-
||
|
|
422
|
-
&& (typeof continuityEpoch !== "string" || continuityEpoch.length === 0))
|
|
421
|
+
|| typeof continuityEpoch !== "string" || continuityEpoch.length === 0
|
|
423
422
|
|| (fileFingerprint !== undefined
|
|
424
423
|
&& (typeof fileFingerprint !== "string" || fileFingerprint.length === 0))
|
|
425
424
|
|| (fileCtimeMs !== undefined
|
|
426
425
|
&& (typeof fileCtimeMs !== "number" || !Number.isFinite(fileCtimeMs) || fileCtimeMs < 0))) {
|
|
427
|
-
|
|
426
|
+
throw new Error("Transcript observer cursor is invalid or unsupported.");
|
|
427
|
+
}
|
|
428
|
+
// Only our unavailable-before-first-read cursor lacks file metadata. An old
|
|
429
|
+
// partially shaped cursor must not silently reset/replay a healthy source.
|
|
430
|
+
const unstarted = offset === 0 && remainder === "" && Object.keys(state).length === 0
|
|
431
|
+
&& continuityEpoch === INITIAL_CONTINUITY_EPOCH;
|
|
432
|
+
if (!unstarted && (fileFingerprint === undefined || fileCtimeMs === undefined)) {
|
|
433
|
+
throw new Error("Transcript observer cursor has no current file identity.");
|
|
428
434
|
}
|
|
429
435
|
return Object.freeze({
|
|
430
436
|
offset: offset,
|
|
431
437
|
remainder,
|
|
432
438
|
state: state,
|
|
433
|
-
continuityEpoch
|
|
434
|
-
? continuityEpoch
|
|
435
|
-
: INITIAL_CONTINUITY_EPOCH,
|
|
439
|
+
continuityEpoch,
|
|
436
440
|
...(typeof fileFingerprint === "string" ? { fileFingerprint } : {}),
|
|
437
441
|
...(typeof fileCtimeMs === "number" ? { fileCtimeMs } : {})
|
|
438
442
|
});
|