@zq-silk/yui 0.14.0 → 0.14.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 +64 -35
- package/README.md +258 -125
- package/dist/agent/argumentPolicy.js +2 -1
- package/dist/agent/managedRuntimeEnvironment.js +5 -8
- package/dist/cli/commandCatalog.js +74 -109
- package/dist/cli/helpRenderer.js +1 -2
- package/dist/cli/interactionCandidates.js +10 -0
- package/dist/cli/interactionPolicy.js +76 -37
- package/dist/cli/roleWizard.js +5 -6
- package/dist/cli/updateOrchestrator.js +10 -8
- package/dist/cli/updatePorts.js +17 -13
- package/dist/cli/upgradeCommand.js +2 -5
- package/dist/cli.js +268 -231
- package/dist/commands/agentCommands.js +13 -0
- package/dist/commands/configCommands.js +7 -19
- package/dist/commands/configOverview.js +3 -2
- package/dist/commands/durableJobCommands.js +1 -1
- package/dist/commands/executionAuditCommands.js +4 -9
- package/dist/commands/globalRoleCommands.js +17 -0
- package/dist/commands/profileCommands.js +200 -30
- package/dist/commands/sessionCommands.js +1 -1
- package/dist/commands/taskActor.js +29 -127
- package/dist/commands/taskChangeSetCommands.js +1 -3
- package/dist/commands/taskCommands.js +1771 -2107
- package/dist/commands/taskCompletionGate.js +27 -103
- package/dist/commands/taskContextCommand.js +47 -80
- package/dist/commands/taskExecutionCommands.js +9 -27
- package/dist/commands/taskInputCommands.js +10 -10
- package/dist/commands/taskIntegrationCommands.js +69 -61
- package/dist/commands/taskIntegrationQueueCommands.js +1 -1
- package/dist/commands/taskNextActionCommand.js +8 -29
- package/dist/commands/taskOverviewCommand.js +25 -3
- package/dist/commands/taskPublicationCommands.js +151 -85
- package/dist/commands/taskPublicationVerifyCommand.js +181 -0
- package/dist/commands/taskRemoteDeliveryCommand.js +134 -0
- package/dist/commands/taskRoleRuntimeStatus.js +5 -6
- package/dist/commands/taskUpstreamCommands.js +79 -95
- package/dist/config/configCatalog.js +1 -1
- package/dist/context/contextSnapshot.js +17 -2
- package/dist/context/sessionBootstrapManifest.js +18 -1
- package/dist/context/sourceTurnContext.js +30 -0
- package/dist/context/turnContextPack.js +107 -48
- package/dist/context/turnInputContract.js +11 -4
- package/dist/context/wakeNotification.js +84 -22
- package/dist/controller/agentRuntimeObserver.js +9 -12
- package/dist/controller/clientRuntime.js +13 -13
- package/dist/controller/controller.js +11 -11
- package/dist/controller/fileSchedulerStoreAdapter.js +262 -219
- package/dist/controller/jobControl.js +11 -10
- package/dist/controller/resourceInventory.js +6 -6
- package/dist/controller/resourceInventoryLinux.js +2 -2
- package/dist/controller/runtime.js +18 -61
- package/dist/controller/runtimeEventInbox.js +57 -34
- package/dist/controller/runtimeEventProcessor.js +36 -96
- package/dist/controller/runtimeHookTurnFence.js +33 -57
- package/dist/controller/runtimeLaunchCoordinator.js +44 -40
- package/dist/controller/runtimeObservationHook.js +18 -12
- package/dist/controller/sessionNotify.js +43 -28
- package/dist/controller/sessionOwnerReconciliation.js +21 -21
- package/dist/controller/structuredProviderObservation.js +13 -16
- package/dist/coordination/workMailboxQueue.js +132 -0
- package/dist/core/controllerServer.js +1 -0
- package/dist/domain/agentResultTransport.js +60 -0
- package/dist/execution/executionHealth.js +111 -118
- package/dist/execution/reviewMainTurn.js +161 -0
- package/dist/execution/workItemExecution.js +454 -0
- package/dist/execution/workItemExecutionProjection.js +309 -0
- package/dist/execution/workItemMainTurn.js +164 -0
- package/dist/executor/agentExecutor.js +19 -19
- package/dist/executor/executorRegistry.js +7 -28
- package/dist/executor/fileRoleLaunchPlanner.js +51 -83
- package/dist/external/githubPublicationVerifier.js +114 -0
- package/dist/external/gitlabPublicationVerifier.js +158 -0
- package/dist/external/pinnedCommandRunner.js +73 -0
- package/dist/integration/changeSetManifest.js +0 -3
- package/dist/integration/deliveryObligation.js +43 -6
- package/dist/integration/gitIntegrationService.js +284 -101
- package/dist/integration/integrationAttempt.js +64 -14
- package/dist/integration/integrationQueueEntry.js +5 -35
- package/dist/integration/integrationQueueService.js +65 -194
- package/dist/lifecycle/canonicalLifecycleEvent.js +16 -15
- package/dist/lifecycle/exactTurnTerminalization.js +100 -97
- package/dist/message/message.js +16 -0
- package/dist/observability/executionAudit.js +4 -25
- package/dist/observability/faultClassification.js +58 -70
- package/dist/observability/orchestrationMetrics.js +8 -50
- package/dist/profile/agentProfile.js +60 -15
- package/dist/profile/agentProfileRuntime.js +100 -0
- package/dist/release/releaseWorkflowPorts.js +5 -68
- package/dist/repository/executionLaneGitSnapshot.js +80 -0
- package/dist/repository/gitWorkspace.js +9 -67
- package/dist/repository/taskBaseFreshness.js +3 -1
- package/dist/repository/taskWorkspaceCoordinator.js +32 -4
- package/dist/repository/taskWorkspacePreparer.js +252 -411
- package/dist/resources/liveReferences.js +6 -55
- package/dist/resources/resourceDiscovery.js +1 -30
- package/dist/resources/resourceRegistrar.js +0 -23
- package/dist/review/deltaRecheck.js +8 -65
- package/dist/review/reviewAcceptance.js +22 -19
- package/dist/review/reviewConfig.js +0 -10
- package/dist/review/reviewDecision.js +2 -2
- package/dist/review/reviewRound.js +147 -273
- package/dist/review/reviewerAvailability.js +13 -23
- package/dist/role/role.js +3 -0
- package/dist/runtime/agentDriverObservation.js +1 -1
- package/dist/runtime/agentHost.js +51 -51
- package/dist/runtime/builtinAgentDrivers.js +9 -2
- package/dist/runtime/codexAppServerRuntime.js +23 -5
- package/dist/runtime/exactControlPlane.js +2 -267
- package/dist/runtime/firstProgressAdvisory.js +1 -1
- package/dist/runtime/index.js +1 -1
- package/dist/runtime/launchBroker.js +11 -11
- package/dist/runtime/lifecycleReservation.js +2 -2
- package/dist/runtime/managedCaller.js +111 -0
- package/dist/runtime/ports.js +16 -3
- package/dist/runtime/preallocatedNativeSession.js +2 -2
- package/dist/runtime/processExitObservation.js +3 -2
- package/dist/runtime/providerContinuationReconciliationService.js +1 -1
- package/dist/runtime/runtimeBinding.js +1 -1
- package/dist/runtime/runtimeObservation.js +33 -16
- package/dist/runtime/runtimeSessionCandidate.js +2 -2
- package/dist/runtime/runtimeStopReceipt.js +11 -11
- package/dist/runtime/sessionLaunchRequest.js +2 -2
- package/dist/runtime/sessionOwnerIdentity.js +9 -9
- package/dist/runtime/sessionOwnerRegistry.js +13 -13
- package/dist/runtime/sessionReconciliation.js +4 -4
- package/dist/runtime/sessionTerminationGuard.js +9 -9
- package/dist/runtime/sessionTokenMetrics.js +5 -7
- package/dist/runtime/structuredProviderHost.js +5 -5
- package/dist/runtime/taskRuntimeIsolation.js +24 -24
- package/dist/runtime/tmuxAdapters.js +32 -30
- package/dist/scheduler/activeRoleTurnDelivery.js +37 -15
- package/dist/scheduler/leaderWakeupProcessor.js +78 -55
- package/dist/scheduler/roleTurnLiveness.js +6 -6
- package/dist/scheduler/roleTurnStall.js +8 -9
- package/dist/scheduler/taskExecutionProjection.js +19 -41
- package/dist/scheduler/taskObservabilityProjection.js +75 -122
- package/dist/storage/sqliteSchema.js +1 -21
- package/dist/storage/sqliteStore.js +62 -47
- package/dist/storage/storageSchema.js +1 -8
- package/dist/storage/storageVersions.js +7 -4
- package/dist/storage/taskStore.js +8 -8
- package/dist/storage/upgrade/upgradeOrchestrator.js +112 -83
- package/dist/task/completionReadiness.js +17 -98
- package/dist/task/deliveryGuard.js +9 -11
- package/dist/task/draftPlan.js +142 -0
- package/dist/task/nextAction.js +179 -477
- package/dist/task/publicationVerification.js +1 -0
- package/dist/task/remoteDelivery.js +233 -0
- package/dist/task/task.js +108 -4
- package/dist/task/taskRecordReference.js +0 -1
- package/dist/turn/turn.js +109 -16
- package/dist/verification/verificationGateService.js +0 -26
- package/dist/web/assets/client/components.js +99 -121
- package/dist/web/assets/client/i18n.js +136 -58
- package/dist/web/assets/client/view.js +44 -0
- package/dist/web/assets/styles/cards.js +3 -15
- package/dist/web/assets/styles/widgets.js +4 -0
- package/dist/web/webSnapshot.js +29 -14
- package/dist/workItem/dependencyGate.js +34 -0
- package/dist/workItem/workItem.js +99 -266
- package/dist/workspace/workItemChangeSetManager.js +15 -59
- package/docs/task-local-identity.md +1 -1
- package/i18n/README.zh-CN.md +165 -59
- package/package.json +1 -1
- package/skills/yui-leader/SKILL.md +272 -782
- package/skills/yui-operator/SKILL.md +190 -365
- package/skills/yui-reviewer/SKILL.md +40 -33
- package/skills/yui-runtime/SKILL.md +27 -6
- package/skills/yui-worker/SKILL.md +19 -6
- package/dist/execution/candidateConvergence.js +0 -623
- package/dist/execution/executionGroup.js +0 -854
- package/dist/execution/resourceBroker.js +0 -426
- package/dist/executor/launchPlan.js +0 -16
- package/dist/review/reviewFinding.js +0 -240
- package/dist/review/reviewFindingLedger.js +0 -550
- package/dist/review/reviewOutcomeClassifier.js +0 -255
- package/dist/scheduler/resourceQueueProjection.js +0 -71
- package/dist/task/repairWave.js +0 -150
|
@@ -86,8 +86,8 @@ const PROVIDER_STATE = new Set([
|
|
|
86
86
|
"operation.failed"
|
|
87
87
|
]);
|
|
88
88
|
export function createRuntimeObservation(input) {
|
|
89
|
-
if (input.schemaVersion !==
|
|
90
|
-
throw new Error("Runtime observation schemaVersion must be
|
|
89
|
+
if (input.schemaVersion !== 4)
|
|
90
|
+
throw new Error("Runtime observation schemaVersion must be 4.");
|
|
91
91
|
if (!KINDS.includes(input.kind))
|
|
92
92
|
throw new Error("Runtime observation kind is invalid.");
|
|
93
93
|
if (!AUTHORITIES.includes(input.authority))
|
|
@@ -133,7 +133,7 @@ export function createRuntimeObservation(input) {
|
|
|
133
133
|
throw new Error("Runtime observation ordinal must be a non-negative safe integer.");
|
|
134
134
|
}
|
|
135
135
|
return Object.freeze({
|
|
136
|
-
schemaVersion:
|
|
136
|
+
schemaVersion: 4,
|
|
137
137
|
eventId: requireIdentity(input.eventId, "Runtime observation event id"),
|
|
138
138
|
semanticKey: requireIdentity(input.semanticKey, "Runtime observation semantic key"),
|
|
139
139
|
kind: input.kind,
|
|
@@ -155,8 +155,7 @@ export function runtimeObservationFenceMatches(expected, actual) {
|
|
|
155
155
|
"turnId",
|
|
156
156
|
"agentId",
|
|
157
157
|
"driverId",
|
|
158
|
-
"
|
|
159
|
-
"sessionGenerationId",
|
|
158
|
+
"runtimeGenerationId",
|
|
160
159
|
"conversationId",
|
|
161
160
|
"activationId",
|
|
162
161
|
"continuationId",
|
|
@@ -172,7 +171,7 @@ export function runtimeObservationFenceMatches(expected, actual) {
|
|
|
172
171
|
return true;
|
|
173
172
|
}
|
|
174
173
|
/**
|
|
175
|
-
* Matches observations that belong to one durable Turn/
|
|
174
|
+
* Matches observations that belong to one durable Turn/Runtime generation.
|
|
176
175
|
* A provider may advance its native Turn while background subagents from an
|
|
177
176
|
* earlier Turn are still active and later mailbox activations use their own
|
|
178
177
|
* exactly-once receipt, so nativeTurnId and receiptId are intentionally
|
|
@@ -185,8 +184,7 @@ export function runtimeObservationTurnFenceMatches(expected, actual) {
|
|
|
185
184
|
"turnId",
|
|
186
185
|
"agentId",
|
|
187
186
|
"driverId",
|
|
188
|
-
"
|
|
189
|
-
"sessionGenerationId",
|
|
187
|
+
"runtimeGenerationId",
|
|
190
188
|
"nativeSessionId"
|
|
191
189
|
]) {
|
|
192
190
|
if (expected[field] !== actual[field])
|
|
@@ -203,7 +201,7 @@ export function runtimeObservationTaskEventPayload(input) {
|
|
|
203
201
|
roleName: observation.fence.roleName,
|
|
204
202
|
agentId: observation.fence.agentId,
|
|
205
203
|
driverId: observation.fence.driverId,
|
|
206
|
-
|
|
204
|
+
runtimeGenerationId: observation.fence.runtimeGenerationId,
|
|
207
205
|
...(observation.fence.taskId === undefined ? {} : { taskId: observation.fence.taskId }),
|
|
208
206
|
...(observation.fence.turnId === undefined ? {} : { turnId: observation.fence.turnId }),
|
|
209
207
|
...(observation.fence.nativeSessionId === undefined
|
|
@@ -247,8 +245,7 @@ function normalizeFence(input) {
|
|
|
247
245
|
...(input.turnId === undefined ? {} : { turnId: requireIdentity(input.turnId, "Turn id") }),
|
|
248
246
|
agentId: requireIdentity(input.agentId, "Agent id"),
|
|
249
247
|
driverId: requireDriverId(input.driverId),
|
|
250
|
-
|
|
251
|
-
sessionGenerationId: requireIdentity(input.sessionGenerationId, "Session generation id"),
|
|
248
|
+
runtimeGenerationId: requireIdentity(input.runtimeGenerationId, "Runtime generation id"),
|
|
252
249
|
...(input.nativeSessionId === undefined
|
|
253
250
|
? {}
|
|
254
251
|
: { nativeSessionId: requireIdentity(input.nativeSessionId, "Native Session id") }),
|
|
@@ -323,6 +320,12 @@ function normalizePayload(kind, input) {
|
|
|
323
320
|
if (kind === "turn.failed" && input.failure === undefined) {
|
|
324
321
|
throw new Error("turn.failed requires normalized failure evidence.");
|
|
325
322
|
}
|
|
323
|
+
if (input.resultTransportDiagnostic !== undefined && kind !== "turn.completed") {
|
|
324
|
+
throw new Error("Only turn.completed may carry a result transport diagnostic.");
|
|
325
|
+
}
|
|
326
|
+
if (input.output !== undefined && input.resultTransportDiagnostic !== undefined) {
|
|
327
|
+
throw new Error("A runtime observation cannot carry Agent output and a result diagnostic.");
|
|
328
|
+
}
|
|
326
329
|
if (kind === "conversation.observed"
|
|
327
330
|
&& !["unknown", "recoverable", "unrecoverable"].includes(input.recoverability ?? "")) {
|
|
328
331
|
throw new Error("conversation.observed requires recoverability.");
|
|
@@ -404,6 +407,14 @@ function normalizePayload(kind, input) {
|
|
|
404
407
|
: { observerDetail: requireText(input.observerDetail, "Runtime observer detail") }),
|
|
405
408
|
...(failure === undefined ? {} : { failure }),
|
|
406
409
|
...(input.input === undefined ? {} : { input: requireText(input.input, "Provider-visible input") }),
|
|
410
|
+
...(input.output === undefined
|
|
411
|
+
? {}
|
|
412
|
+
: { output: requireResultText(input.output, "Runtime Turn output") }),
|
|
413
|
+
...(input.resultTransportDiagnostic === undefined
|
|
414
|
+
? {}
|
|
415
|
+
: {
|
|
416
|
+
resultTransportDiagnostic: requireText(input.resultTransportDiagnostic, "Runtime Turn result diagnostic")
|
|
417
|
+
}),
|
|
407
418
|
...(input.summary === undefined ? {} : { summary: requireText(input.summary, "Runtime summary") }),
|
|
408
419
|
...(input.execution === undefined ? {} : { execution: input.execution }),
|
|
409
420
|
...(input.outcome === undefined ? {} : { outcome: input.outcome }),
|
|
@@ -449,8 +460,8 @@ export function runtimeObservationSemanticKey(input) {
|
|
|
449
460
|
const continuationIdentity = [
|
|
450
461
|
fence.driverId,
|
|
451
462
|
fence.agentId,
|
|
452
|
-
fence.conversationId ?? fence.nativeSessionId ?? fence.
|
|
453
|
-
fence.activationId ?? fence.
|
|
463
|
+
fence.conversationId ?? fence.nativeSessionId ?? fence.runtimeGenerationId,
|
|
464
|
+
fence.activationId ?? fence.runtimeGenerationId,
|
|
454
465
|
fence.continuationId ?? "none",
|
|
455
466
|
fence.continuationGeneration ?? "none"
|
|
456
467
|
];
|
|
@@ -475,7 +486,7 @@ export function runtimeObservationSemanticKey(input) {
|
|
|
475
486
|
return [
|
|
476
487
|
"goal-state",
|
|
477
488
|
fence.driverId,
|
|
478
|
-
fence.conversationId ?? fence.nativeSessionId ?? fence.
|
|
489
|
+
fence.conversationId ?? fence.nativeSessionId ?? fence.runtimeGenerationId,
|
|
479
490
|
input.payload?.goalStatus ?? "unknown",
|
|
480
491
|
input.payload?.goalUpdatedAt ?? "unknown"
|
|
481
492
|
].join(":");
|
|
@@ -504,8 +515,8 @@ export function runtimeObservationSemanticKey(input) {
|
|
|
504
515
|
return [
|
|
505
516
|
"provider-sequence",
|
|
506
517
|
fence.driverId,
|
|
507
|
-
fence.conversationId ?? fence.nativeSessionId ?? fence.
|
|
508
|
-
fence.activationId ?? fence.
|
|
518
|
+
fence.conversationId ?? fence.nativeSessionId ?? fence.runtimeGenerationId,
|
|
519
|
+
fence.activationId ?? fence.runtimeGenerationId,
|
|
509
520
|
fence.continuationId ?? fence.nativeTurnId ?? "none",
|
|
510
521
|
fence.continuationGeneration ?? "none",
|
|
511
522
|
input.sequence,
|
|
@@ -589,3 +600,9 @@ function requireText(value, label) {
|
|
|
589
600
|
}
|
|
590
601
|
return normalized;
|
|
591
602
|
}
|
|
603
|
+
function requireResultText(value, label) {
|
|
604
|
+
if (typeof value !== "string" || value.includes("\0") || value.trim().length === 0) {
|
|
605
|
+
throw new Error(`${label} is invalid.`);
|
|
606
|
+
}
|
|
607
|
+
return value;
|
|
608
|
+
}
|
|
@@ -15,9 +15,9 @@ export function projectRuntimeSessionCandidate(sessions) {
|
|
|
15
15
|
agentId: active.agentId,
|
|
16
16
|
adapterId: active.adapterId,
|
|
17
17
|
nativeSessionId: active.nativeSessionId,
|
|
18
|
-
...(active.
|
|
18
|
+
...(active.runtimeGenerationId === undefined ? {} : { runtimeGenerationId: active.runtimeGenerationId }),
|
|
19
19
|
sessionUpdatedAt: active.updatedAt,
|
|
20
|
-
cleanupRequired: active.
|
|
20
|
+
cleanupRequired: active.runtimeGenerationId !== undefined
|
|
21
21
|
};
|
|
22
22
|
}
|
|
23
23
|
/** Deterministic owner order shared by all storage backends. */
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { chmodSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { join, resolve } from "node:path";
|
|
4
|
-
export function writeRuntimeStopReceipt(home,
|
|
4
|
+
export function writeRuntimeStopReceipt(home, runtimeGenerationId, requestedAt) {
|
|
5
5
|
const receipt = Object.freeze({
|
|
6
6
|
schemaVersion: 1,
|
|
7
7
|
receiptId: `runtime-stop-${createHash("sha256")
|
|
8
|
-
.update(`${
|
|
8
|
+
.update(`${runtimeGenerationId}\0${requestedAt.toISOString()}`)
|
|
9
9
|
.digest("hex")}`,
|
|
10
|
-
|
|
10
|
+
runtimeGenerationId,
|
|
11
11
|
requestedAt: requestedAt.toISOString()
|
|
12
12
|
});
|
|
13
|
-
const path = stopReceiptPath(home,
|
|
13
|
+
const path = stopReceiptPath(home, runtimeGenerationId);
|
|
14
14
|
mkdirSync(resolve(join(home, "runtime", "stop-receipts")), { recursive: true, mode: 0o700 });
|
|
15
15
|
const temporary = `${path}.tmp-${process.pid}`;
|
|
16
16
|
writeFileSync(temporary, `${JSON.stringify(receipt)}\n`, { mode: 0o600 });
|
|
@@ -18,10 +18,10 @@ export function writeRuntimeStopReceipt(home, launchId, requestedAt) {
|
|
|
18
18
|
chmodSync(path, 0o600);
|
|
19
19
|
return receipt;
|
|
20
20
|
}
|
|
21
|
-
export function readRuntimeStopReceipt(home,
|
|
21
|
+
export function readRuntimeStopReceipt(home, runtimeGenerationId) {
|
|
22
22
|
try {
|
|
23
|
-
const value = JSON.parse(readFileSync(stopReceiptPath(home,
|
|
24
|
-
if (value.schemaVersion !== 1 || value.
|
|
23
|
+
const value = JSON.parse(readFileSync(stopReceiptPath(home, runtimeGenerationId), "utf8"));
|
|
24
|
+
if (value.schemaVersion !== 1 || value.runtimeGenerationId !== runtimeGenerationId
|
|
25
25
|
|| typeof value.receiptId !== "string" || !Number.isFinite(Date.parse(value.requestedAt))) {
|
|
26
26
|
throw new Error("Runtime stop receipt is invalid.");
|
|
27
27
|
}
|
|
@@ -33,10 +33,10 @@ export function readRuntimeStopReceipt(home, launchId) {
|
|
|
33
33
|
throw error;
|
|
34
34
|
}
|
|
35
35
|
}
|
|
36
|
-
export function removeRuntimeStopReceipt(home,
|
|
37
|
-
rmSync(stopReceiptPath(home,
|
|
36
|
+
export function removeRuntimeStopReceipt(home, runtimeGenerationId) {
|
|
37
|
+
rmSync(stopReceiptPath(home, runtimeGenerationId), { force: true });
|
|
38
38
|
}
|
|
39
|
-
function stopReceiptPath(home,
|
|
40
|
-
const name = createHash("sha256").update(
|
|
39
|
+
function stopReceiptPath(home, runtimeGenerationId) {
|
|
40
|
+
const name = createHash("sha256").update(runtimeGenerationId).digest("hex");
|
|
41
41
|
return resolve(join(home, "runtime", "stop-receipts", `${name}.json`));
|
|
42
42
|
}
|
|
@@ -18,11 +18,11 @@ export function createSessionLaunchRequest(input) {
|
|
|
18
18
|
if (runtimeIsolation !== undefined && (input.owner.scope !== "task"
|
|
19
19
|
|| runtimeIsolation.taskId !== input.owner.taskId
|
|
20
20
|
|| runtimeIsolation.workspace.root !== workspace
|
|
21
|
-
|| runtimeIsolation.generation.
|
|
21
|
+
|| runtimeIsolation.generation.runtimeGenerationId !== input.runtimeGenerationId)) {
|
|
22
22
|
throw new TypeError("Session launch request does not match its Task runtime isolation descriptor.");
|
|
23
23
|
}
|
|
24
24
|
const common = {
|
|
25
|
-
|
|
25
|
+
runtimeGenerationId: requireSafeIdentity(input.runtimeGenerationId, "Runtime generation id"),
|
|
26
26
|
owner: normalizeRuntimeOwner(input.owner),
|
|
27
27
|
agentId,
|
|
28
28
|
adapterId,
|
|
@@ -29,7 +29,7 @@ export function createSessionOwnerIdentity(input) {
|
|
|
29
29
|
}
|
|
30
30
|
const tmux = input.tmux;
|
|
31
31
|
return Object.freeze({
|
|
32
|
-
schemaVersion:
|
|
32
|
+
schemaVersion: 2,
|
|
33
33
|
kind: "yui-session-owner",
|
|
34
34
|
owner: Object.freeze({
|
|
35
35
|
scope: owner.scope,
|
|
@@ -38,7 +38,7 @@ export function createSessionOwnerIdentity(input) {
|
|
|
38
38
|
}),
|
|
39
39
|
agentId: requireSafeIdentity(input.agentId, "Agent id"),
|
|
40
40
|
adapterId: requireText(input.adapterId, "Adapter id"),
|
|
41
|
-
|
|
41
|
+
runtimeGenerationId: requireSafeIdentity(input.runtimeGenerationId, "Runtime generation id"),
|
|
42
42
|
...(input.nativeSessionId === undefined
|
|
43
43
|
? {}
|
|
44
44
|
: { nativeSessionId: requireText(input.nativeSessionId, "Native session id") }),
|
|
@@ -134,12 +134,12 @@ export function isLinuxProcessLive(pid, startIdentity) {
|
|
|
134
134
|
return current.startIdentity === startIdentity;
|
|
135
135
|
}
|
|
136
136
|
/**
|
|
137
|
-
* Discovers the Provider root for one launch by the exact
|
|
137
|
+
* Discovers the Provider root for one launch by the exact YUI_RUNTIME_GENERATION_ID
|
|
138
138
|
* environment fence. Returns undefined when no same-user process carries the
|
|
139
139
|
* fence — the caller must not guess from a process name or cwd.
|
|
140
140
|
*/
|
|
141
|
-
export function discoverProviderRootByLaunchEnv(
|
|
142
|
-
const matches = listLaunchFencedProcesses(
|
|
141
|
+
export function discoverProviderRootByLaunchEnv(runtimeGenerationId) {
|
|
142
|
+
const matches = listLaunchFencedProcesses(runtimeGenerationId);
|
|
143
143
|
for (const pid of matches) {
|
|
144
144
|
const identity = readLinuxProcessIdentity(pid);
|
|
145
145
|
if (identity !== undefined)
|
|
@@ -149,13 +149,13 @@ export function discoverProviderRootByLaunchEnv(launchId) {
|
|
|
149
149
|
}
|
|
150
150
|
/**
|
|
151
151
|
* Lists every live process whose initial environment carries the exact
|
|
152
|
-
* `
|
|
152
|
+
* `YUI_RUNTIME_GENERATION_ID=<runtimeGenerationId>` fence. The fence is inherited by Provider
|
|
153
153
|
* children, so this is the exact, PID-reuse-safe attribution for a surviving
|
|
154
154
|
* child after its root has exited: a recycled PID never carries the fence.
|
|
155
155
|
* Processes without the fence are unattributed and must never be signaled.
|
|
156
156
|
*/
|
|
157
|
-
export function listLaunchFencedProcesses(
|
|
158
|
-
if (!Number.isSafeInteger(
|
|
157
|
+
export function listLaunchFencedProcesses(runtimeGenerationId) {
|
|
158
|
+
if (!Number.isSafeInteger(runtimeGenerationId) && !/^[A-Za-z0-9_.:-]+$/u.test(runtimeGenerationId)) {
|
|
159
159
|
return [];
|
|
160
160
|
}
|
|
161
161
|
let entries;
|
|
@@ -165,7 +165,7 @@ export function listLaunchFencedProcesses(launchId) {
|
|
|
165
165
|
catch {
|
|
166
166
|
return [];
|
|
167
167
|
}
|
|
168
|
-
const needle = `
|
|
168
|
+
const needle = `YUI_RUNTIME_GENERATION_ID=${runtimeGenerationId}`;
|
|
169
169
|
const matches = [];
|
|
170
170
|
for (const entry of entries) {
|
|
171
171
|
if (!/^[0-9]+$/u.test(entry))
|
|
@@ -4,7 +4,7 @@ import { createSessionOwnerIdentity } from "./sessionOwnerIdentity.js";
|
|
|
4
4
|
/**
|
|
5
5
|
* Durable, enumerable physical owner records for runtime generations.
|
|
6
6
|
*
|
|
7
|
-
* One JSON file per
|
|
7
|
+
* One JSON file per runtime generation id under `<home>/runtime/session-owners/`. The
|
|
8
8
|
* directory is runtime state (like the tmux socket), not aggregate domain
|
|
9
9
|
* state, so it needs no schema migration; it survives a Controller restart and
|
|
10
10
|
* stays enumerable after the durable Session map or history is cleared, which
|
|
@@ -22,16 +22,16 @@ export class FileSessionOwnerRegistry {
|
|
|
22
22
|
/** Atomically records (or replaces) one generation's owner identity. */
|
|
23
23
|
record(identity) {
|
|
24
24
|
mkdirSync(this.#directory, { recursive: true, mode: 0o700 });
|
|
25
|
-
const target = this.#path(identity.
|
|
25
|
+
const target = this.#path(identity.runtimeGenerationId);
|
|
26
26
|
const temporary = `${target}.tmp-${process.pid}`;
|
|
27
27
|
writeFileSync(temporary, JSON.stringify(identity, null, 2) + "\n", { mode: 0o600 });
|
|
28
28
|
renameSync(temporary, target);
|
|
29
29
|
}
|
|
30
|
-
get(
|
|
31
|
-
if (!
|
|
30
|
+
get(runtimeGenerationId) {
|
|
31
|
+
if (!isSafeRuntimeGenerationId(runtimeGenerationId))
|
|
32
32
|
return null;
|
|
33
33
|
try {
|
|
34
|
-
return parseOwnerRecord(readFileSync(this.#path(
|
|
34
|
+
return parseOwnerRecord(readFileSync(this.#path(runtimeGenerationId), "utf8"));
|
|
35
35
|
}
|
|
36
36
|
catch {
|
|
37
37
|
return null;
|
|
@@ -66,11 +66,11 @@ export class FileSessionOwnerRegistry {
|
|
|
66
66
|
&& record.owner.roleName === owner.roleName));
|
|
67
67
|
}
|
|
68
68
|
/** Removes a record whose physical resources were proven absent. */
|
|
69
|
-
remove(
|
|
70
|
-
if (!
|
|
69
|
+
remove(runtimeGenerationId) {
|
|
70
|
+
if (!isSafeRuntimeGenerationId(runtimeGenerationId))
|
|
71
71
|
return;
|
|
72
72
|
try {
|
|
73
|
-
rmSync(this.#path(
|
|
73
|
+
rmSync(this.#path(runtimeGenerationId), { force: true });
|
|
74
74
|
}
|
|
75
75
|
catch {
|
|
76
76
|
// A stale record is harmless: reconciliation re-verifies physical
|
|
@@ -80,12 +80,12 @@ export class FileSessionOwnerRegistry {
|
|
|
80
80
|
exists() {
|
|
81
81
|
return existsSync(this.#directory);
|
|
82
82
|
}
|
|
83
|
-
#path(
|
|
84
|
-
return join(this.#directory, `${
|
|
83
|
+
#path(runtimeGenerationId) {
|
|
84
|
+
return join(this.#directory, `${runtimeGenerationId}.json`);
|
|
85
85
|
}
|
|
86
86
|
}
|
|
87
|
-
function
|
|
88
|
-
return /^[A-Za-z0-9_.:-]+$/u.test(
|
|
87
|
+
function isSafeRuntimeGenerationId(runtimeGenerationId) {
|
|
88
|
+
return /^[A-Za-z0-9_.:-]+$/u.test(runtimeGenerationId);
|
|
89
89
|
}
|
|
90
90
|
function parseOwnerRecord(raw) {
|
|
91
91
|
const parsed = JSON.parse(raw);
|
|
@@ -104,7 +104,7 @@ function parseOwnerRecord(raw) {
|
|
|
104
104
|
},
|
|
105
105
|
agentId: String(record.agentId),
|
|
106
106
|
adapterId: String(record.adapterId),
|
|
107
|
-
|
|
107
|
+
runtimeGenerationId: String(record.runtimeGenerationId),
|
|
108
108
|
...(record.nativeSessionId === undefined
|
|
109
109
|
? {}
|
|
110
110
|
: { nativeSessionId: String(record.nativeSessionId) }),
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Bidirectional durable <-> physical reconciliation for
|
|
2
|
+
* Bidirectional durable <-> physical reconciliation for Runtime generations.
|
|
3
3
|
*
|
|
4
4
|
* Durable -> physical: every owner record's Provider root must be absent once
|
|
5
5
|
* its durable Session is terminal; a live root with a terminal durable state
|
|
@@ -34,7 +34,7 @@ function reconcileOne(record, input) {
|
|
|
34
34
|
? input.taskStatus(owner.taskId)
|
|
35
35
|
: undefined;
|
|
36
36
|
const tmuxPane = input.inspectPane(owner.taskId, owner.roleName);
|
|
37
|
-
const lastStopOutcome = input.lastStopOutcome(owner.taskId, owner.roleName, record.
|
|
37
|
+
const lastStopOutcome = input.lastStopOutcome(owner.taskId, owner.roleName, record.runtimeGenerationId);
|
|
38
38
|
let mismatch;
|
|
39
39
|
let verificationGap;
|
|
40
40
|
if (physical?.identityConflict === true) {
|
|
@@ -64,7 +64,7 @@ function reconcileOne(record, input) {
|
|
|
64
64
|
},
|
|
65
65
|
agentId: record.agentId,
|
|
66
66
|
adapterId: record.adapterId,
|
|
67
|
-
|
|
67
|
+
runtimeGenerationId: record.runtimeGenerationId,
|
|
68
68
|
...(record.nativeSessionId === undefined
|
|
69
69
|
? {}
|
|
70
70
|
: { nativeSessionId: record.nativeSessionId }),
|
|
@@ -83,7 +83,7 @@ function matchDurable(record, durable) {
|
|
|
83
83
|
&& fact.roleName === record.owner.roleName
|
|
84
84
|
&& (record.owner.scope === "global" || fact.taskId === record.owner.taskId)
|
|
85
85
|
&& fact.agentId === record.agentId));
|
|
86
|
-
const byLaunch = candidates.find((fact) => fact.
|
|
86
|
+
const byLaunch = candidates.find((fact) => fact.runtimeGenerationId !== undefined && fact.runtimeGenerationId === record.runtimeGenerationId);
|
|
87
87
|
if (byLaunch !== undefined)
|
|
88
88
|
return byLaunch;
|
|
89
89
|
const byNative = candidates.find((fact) => fact.nativeSessionId !== undefined
|
|
@@ -26,7 +26,7 @@ export async function terminateSessionOwners(owner, records, ports, options = {}
|
|
|
26
26
|
ports.emit({
|
|
27
27
|
stage: "stop-requested",
|
|
28
28
|
owner,
|
|
29
|
-
|
|
29
|
+
runtimeGenerationId: record.runtimeGenerationId,
|
|
30
30
|
...(record.nativeSessionId === undefined
|
|
31
31
|
? {}
|
|
32
32
|
: { nativeSessionId: record.nativeSessionId }),
|
|
@@ -37,7 +37,7 @@ export async function terminateSessionOwners(owner, records, ports, options = {}
|
|
|
37
37
|
// inventory: /proc entries can disappear between the directory and
|
|
38
38
|
// environment reads. Retain every exact child identity observed during the
|
|
39
39
|
// stop so one later scan miss cannot be mistaken for physical zero.
|
|
40
|
-
const trackedChildren = new Map(records.map((record) => [record.
|
|
40
|
+
const trackedChildren = new Map(records.map((record) => [record.runtimeGenerationId, new Map()]));
|
|
41
41
|
for (const record of records) {
|
|
42
42
|
refreshTrackedFencedChildren(record, ports, trackedChildren);
|
|
43
43
|
}
|
|
@@ -102,7 +102,7 @@ export async function terminateSessionOwners(owner, records, ports, options = {}
|
|
|
102
102
|
continue;
|
|
103
103
|
}
|
|
104
104
|
if (verdict.kind === "gap") {
|
|
105
|
-
verificationGap = `/proc unreadable for launch ${record.
|
|
105
|
+
verificationGap = `/proc unreadable for launch ${record.runtimeGenerationId}`;
|
|
106
106
|
remaining.push({ record, detail: verificationGap });
|
|
107
107
|
continue;
|
|
108
108
|
}
|
|
@@ -111,7 +111,7 @@ export async function terminateSessionOwners(owner, records, ports, options = {}
|
|
|
111
111
|
record,
|
|
112
112
|
detail: root.kind === "live"
|
|
113
113
|
? `provider root pid ${record.providerRoot.pid} still live`
|
|
114
|
-
: `provider children still live for launch ${record.
|
|
114
|
+
: `provider children still live for launch ${record.runtimeGenerationId}`
|
|
115
115
|
});
|
|
116
116
|
}
|
|
117
117
|
const outcome = remaining.length === 0 ? "stop-confirmed" : "stop-blocked";
|
|
@@ -121,7 +121,7 @@ export async function terminateSessionOwners(owner, records, ports, options = {}
|
|
|
121
121
|
...(outcome === "stop-blocked"
|
|
122
122
|
? {
|
|
123
123
|
detail: remaining
|
|
124
|
-
.map(({ record, detail }) => `${record.
|
|
124
|
+
.map(({ record, detail }) => `${record.runtimeGenerationId}: ${detail}`)
|
|
125
125
|
.join("; ")
|
|
126
126
|
}
|
|
127
127
|
: {}),
|
|
@@ -169,11 +169,11 @@ function treeVerdict(record, ports, trackedChildren) {
|
|
|
169
169
|
return { kind: "absent" };
|
|
170
170
|
}
|
|
171
171
|
function refreshTrackedFencedChildren(record, ports, trackedChildren) {
|
|
172
|
-
const tracked = trackedChildren.get(record.
|
|
172
|
+
const tracked = trackedChildren.get(record.runtimeGenerationId);
|
|
173
173
|
if (tracked === undefined)
|
|
174
174
|
return true;
|
|
175
175
|
let verificationGap = false;
|
|
176
|
-
for (const pid of ports.listLaunchFencedProcesses(record.
|
|
176
|
+
for (const pid of ports.listLaunchFencedProcesses(record.runtimeGenerationId)) {
|
|
177
177
|
if (pid === record.providerRoot.pid)
|
|
178
178
|
continue;
|
|
179
179
|
const identity = ports.processIdentity(pid);
|
|
@@ -189,7 +189,7 @@ function refreshTrackedFencedChildren(record, ports, trackedChildren) {
|
|
|
189
189
|
return verificationGap;
|
|
190
190
|
}
|
|
191
191
|
function trackedChildrenVerdict(record, ports, trackedChildren) {
|
|
192
|
-
const tracked = trackedChildren.get(record.
|
|
192
|
+
const tracked = trackedChildren.get(record.runtimeGenerationId);
|
|
193
193
|
if (tracked === undefined)
|
|
194
194
|
return { kind: "gap" };
|
|
195
195
|
let verificationGap = false;
|
|
@@ -242,7 +242,7 @@ function escalateRecord(record, ports, trackedChildren, signal) {
|
|
|
242
242
|
if (root.kind !== "absent")
|
|
243
243
|
return;
|
|
244
244
|
refreshTrackedFencedChildren(record, ports, trackedChildren);
|
|
245
|
-
const tracked = trackedChildren.get(record.
|
|
245
|
+
const tracked = trackedChildren.get(record.runtimeGenerationId);
|
|
246
246
|
if (tracked === undefined)
|
|
247
247
|
return;
|
|
248
248
|
for (const [pid, startIdentity] of tracked) {
|
|
@@ -6,7 +6,7 @@ export function resolveSessionTokenIdentity(input) {
|
|
|
6
6
|
|| input.roleName === undefined
|
|
7
7
|
|| input.agentId === undefined
|
|
8
8
|
|| input.adapterId === undefined
|
|
9
|
-
|| input.
|
|
9
|
+
|| input.runtimeGenerationId === undefined
|
|
10
10
|
|| input.nativeSessionId === undefined)
|
|
11
11
|
return null;
|
|
12
12
|
let driverId;
|
|
@@ -21,9 +21,8 @@ export function resolveSessionTokenIdentity(input) {
|
|
|
21
21
|
roleName: input.roleName,
|
|
22
22
|
agentId: input.agentId,
|
|
23
23
|
driverId,
|
|
24
|
-
|
|
25
|
-
nativeSessionId: input.nativeSessionId
|
|
26
|
-
sessionGenerationId: input.launchId
|
|
24
|
+
runtimeGenerationId: input.runtimeGenerationId,
|
|
25
|
+
nativeSessionId: input.nativeSessionId
|
|
27
26
|
});
|
|
28
27
|
}
|
|
29
28
|
/**
|
|
@@ -156,9 +155,8 @@ function matchesSessionGeneration(observation, identity) {
|
|
|
156
155
|
&& fence.roleName === identity.roleName
|
|
157
156
|
&& fence.agentId === identity.agentId
|
|
158
157
|
&& fence.driverId === identity.driverId
|
|
159
|
-
&& fence.
|
|
160
|
-
&& fence.nativeSessionId === identity.nativeSessionId
|
|
161
|
-
&& fence.sessionGenerationId === identity.sessionGenerationId;
|
|
158
|
+
&& fence.runtimeGenerationId === identity.runtimeGenerationId
|
|
159
|
+
&& fence.nativeSessionId === identity.nativeSessionId;
|
|
162
160
|
}
|
|
163
161
|
function compareObservations(left, right) {
|
|
164
162
|
return left.receivedAt.localeCompare(right.receivedAt)
|
|
@@ -2,7 +2,7 @@ import { spawn } from "node:child_process";
|
|
|
2
2
|
import { randomUUID } from "node:crypto";
|
|
3
3
|
import { Duplex } from "node:stream";
|
|
4
4
|
import WebSocket from "ws";
|
|
5
|
-
import { CodexAppServerRequestError, CodexAppServerRuntime, codexGoalNotification, codexAppServerErrorIsMissing, codexTurnInput,
|
|
5
|
+
import { CodexAppServerRequestError, CodexAppServerRuntime, codexGoalNotification, codexAppServerErrorIsMissing, codexTurnInput, codexTurnOutput } from "./codexAppServerRuntime.js";
|
|
6
6
|
import { serializeAgentErrorRaw } from "./agentError.js";
|
|
7
7
|
import { PROVIDER_ACCEPT_TIMEOUT_MS } from "./runtimeDeadlines.js";
|
|
8
8
|
import { YUI_VERSION } from "../version.js";
|
|
@@ -433,7 +433,7 @@ class CodexStructuredProviderSession {
|
|
|
433
433
|
? "failed"
|
|
434
434
|
: recovered.status === "interrupted" ? "cancelled" : "completed",
|
|
435
435
|
observedAt: new Date().toISOString(),
|
|
436
|
-
...(recovered.
|
|
436
|
+
...(recovered.output === undefined ? {} : { output: recovered.output }),
|
|
437
437
|
...(recovered.status !== "failed"
|
|
438
438
|
? {}
|
|
439
439
|
: {
|
|
@@ -497,7 +497,7 @@ class CodexStructuredProviderSession {
|
|
|
497
497
|
const failure = status === "failed"
|
|
498
498
|
? providerErrorEvidence(turn.error)
|
|
499
499
|
: undefined;
|
|
500
|
-
const
|
|
500
|
+
const output = codexTurnOutput(turn);
|
|
501
501
|
const input = codexTurnInput(turn);
|
|
502
502
|
const terminal = {
|
|
503
503
|
conversationId: this.conversationId,
|
|
@@ -506,7 +506,7 @@ class CodexStructuredProviderSession {
|
|
|
506
506
|
status,
|
|
507
507
|
observedAt: new Date().toISOString(),
|
|
508
508
|
...(input === undefined ? {} : { input }),
|
|
509
|
-
...(
|
|
509
|
+
...(output === undefined ? {} : { output }),
|
|
510
510
|
...(failure === undefined ? {} : failure)
|
|
511
511
|
};
|
|
512
512
|
if (this.#submissionPending && emit) {
|
|
@@ -744,7 +744,7 @@ class ClaudeStructuredProviderSession {
|
|
|
744
744
|
...(failed
|
|
745
745
|
? { error: result, rawError: providerErrorEvidence(message).rawError }
|
|
746
746
|
: typeof message.result === "string" && message.result.length > 0
|
|
747
|
-
? {
|
|
747
|
+
? { output: message.result }
|
|
748
748
|
: {})
|
|
749
749
|
});
|
|
750
750
|
}
|