@zq-silk/yui 0.15.12 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ARCHITECTURE.md +3 -2
- package/ARCHITECTURE.zh-CN.md +3 -2
- package/dist/artifacts/artifactCommitLock.js +16 -55
- package/dist/artifacts/taskArtifactRepository.js +3 -3
- package/dist/cli/agentConfigurationPicker.js +1 -1
- package/dist/cli/commandCatalog.js +57 -41
- package/dist/cli/completion.js +18 -18
- package/dist/cli/completionWizard.js +1 -1
- package/dist/cli/dynamicCompletion.js +1 -1
- package/dist/cli/interactionPolicy.js +7 -3
- package/dist/cli/invocationAuthority.js +64 -0
- package/dist/cli/managedDiagnostics.js +1 -1
- package/dist/cli/parseRepeatable.js +35 -0
- package/dist/cli/updatePorts.js +26 -5
- package/dist/cli.js +1280 -1243
- package/dist/commands/agentCommands.js +4 -4
- package/dist/commands/capabilityCommands.js +1 -1
- package/dist/commands/configCommands.js +6 -57
- package/dist/commands/configOverview.js +2 -2
- package/dist/commands/durableJobCommands.js +12 -6
- package/dist/commands/globalRoleCommands.js +53 -29
- package/dist/commands/grantCommands.js +0 -3
- package/dist/commands/jobCommands.js +1 -4
- package/dist/commands/operatorCommands.js +3 -3
- package/dist/commands/projectCommands.js +1 -10
- package/dist/commands/resourcesCommands.js +10 -40
- package/dist/commands/roleConfiguration.js +2 -6
- package/dist/commands/taskActivationCommands.js +4 -8
- package/dist/commands/taskAgentCapabilities.js +9 -0
- package/dist/commands/taskCommandSupport.js +155 -0
- package/dist/commands/taskCommandTypes.js +1 -0
- package/dist/commands/taskCommands.js +271 -710
- package/dist/commands/taskCompletionGate.js +1 -1
- package/dist/commands/taskExecutionCommands.js +1 -1
- package/dist/commands/taskFactCommands.js +325 -0
- package/dist/commands/taskInputCommands.js +12 -42
- package/dist/commands/taskIntegrationCommands.js +17 -57
- package/dist/commands/taskNextActionCommand.js +7 -6
- package/dist/commands/taskPublicationAdoptCommand.js +3 -3
- package/dist/commands/taskPublicationCommands.js +1 -1
- package/dist/commands/taskPublicationVerifyCommand.js +1 -1
- package/dist/commands/taskRemoteDeliveryCommand.js +2 -94
- package/dist/commands/taskUpstreamCommands.js +19 -8
- package/dist/commands/workflowCommands.js +6 -1
- package/dist/completion/completionInstaller.js +26 -26
- package/dist/completion/completionState.js +26 -26
- package/dist/completion/fileCompletionManager.js +6 -11
- package/dist/config/configCatalog.js +0 -2
- package/dist/config/timeZone.js +14 -0
- package/dist/config/yuiConfig.js +0 -29
- package/dist/context/runContextPack.js +2 -3
- package/dist/context/taskCatalog.js +3 -5
- package/dist/context/taskContext.js +139 -151
- package/dist/context/wakeRunReferences.js +11 -0
- package/dist/controller/agentCapabilities.js +58 -0
- package/dist/controller/agentRuntimeObserver.js +6 -11
- package/dist/controller/clientRuntime.js +22 -14
- package/dist/controller/controller.js +9 -22
- package/dist/controller/fileSchedulerStoreAdapter.js +117 -212
- package/dist/controller/jobClient.js +4 -4
- package/dist/controller/jobControl.js +60 -25
- package/dist/controller/jobSupervisor.js +4 -4
- package/dist/controller/providerRetryDelivery.js +7 -7
- package/dist/controller/runtime.js +51 -44
- package/dist/controller/runtimeEventProcessor.js +0 -5
- package/dist/controller/sessionOwnerReconciliation.js +5 -0
- package/dist/controller/taskAgentError.js +35 -0
- package/dist/coordination/workMailboxQueue.js +2 -24
- package/dist/core/controllerClient.js +2 -2
- package/dist/core/fileLockOwner.js +74 -0
- package/dist/decision/decision.js +17 -0
- package/dist/doctor/doctor.js +18 -9
- package/dist/event/taskEvent.js +12 -0
- package/dist/execution/workItemExecutionProjection.js +2 -2
- package/dist/executor/agentCapabilityConfig.js +43 -0
- package/dist/executor/agentConfigurationCatalog.js +49 -11
- package/dist/executor/agentExecutor.js +5 -8
- package/dist/executor/fileRoleLaunchPlanner.js +11 -53
- package/dist/executor/taskAgentCapabilities.js +64 -0
- package/dist/integration/deliveryObligation.js +1 -69
- package/dist/integration/gitIntegrationService.js +60 -84
- package/dist/integration/integrationAttempt.js +3 -14
- package/dist/integration/integrationSourceApplication.js +4 -12
- package/dist/integration/manifestTags.js +2 -2
- package/dist/job/durableJob.js +4 -25
- package/dist/job/jobAssignmentScope.js +22 -0
- package/dist/job/jobOperation.js +16 -0
- package/dist/job/jobRunner.js +2 -1
- package/dist/job/stepDirectory.js +15 -0
- package/dist/kernel/builtinCapabilities.js +15 -11
- package/dist/kernel/kernelPorts.js +1 -17
- package/dist/lifecycle/exactRunTerminalization.js +1 -9
- package/dist/message/inputControlResolution.js +3 -4
- package/dist/message/message.js +17 -20
- package/dist/message/messageContinuation.js +1 -1
- package/dist/milestone/milestone.js +11 -0
- package/dist/observability/orchestrationMetrics.js +24 -1
- package/dist/observability/runtimeIdentity.js +1 -17
- package/dist/output/agentConfigurationPresentation.js +2 -0
- package/dist/output/timePresentation.js +1 -14
- package/dist/plugins/pluginService.js +12 -4
- package/dist/release/releaseWorkflowPorts.js +45 -96
- package/dist/release/runtimeRelease.js +9 -15
- package/dist/repository/projectMaintenanceLock.js +23 -64
- package/dist/repository/taskBaseFreshness.js +1 -1
- package/dist/repository/taskWorkspaceCoordinator.js +13 -22
- package/dist/repository/taskWorkspacePreparer.js +29 -25
- package/dist/repository/workspaceCleanupInspection.js +5 -5
- package/dist/resources/autoResourceGc.js +4 -77
- package/dist/resources/liveReferences.js +7 -54
- package/dist/resources/resourceDiscovery.js +5 -118
- package/dist/resources/resourceGc.js +233 -181
- package/dist/resources/resourceRegistrar.js +5 -4
- package/dist/resources/resourceRegistry.js +3 -37
- package/dist/resources/resourceRegistryStore.js +13 -36
- package/dist/resources/sqliteResourceRegistry.js +43 -26
- package/dist/review/deltaRecheck.js +1 -1
- package/dist/review/reviewAcceptance.js +1 -1
- package/dist/review/reviewDecision.js +1 -1
- package/dist/review/reviewRound.js +8 -7
- package/dist/review/taskFinalReviewContractResolution.js +1 -1
- package/dist/runtime/acpProtocol.js +4 -51
- package/dist/runtime/acpSession.js +10 -75
- package/dist/runtime/acpSessionConfiguration.js +1 -8
- package/dist/runtime/agentEndpoint.js +3 -2
- package/dist/runtime/agentFailureContext.js +43 -0
- package/dist/runtime/agentHost.js +15 -15
- package/dist/runtime/codexInteractiveHost.js +2 -2
- package/dist/runtime/index.js +0 -1
- package/dist/runtime/managedCaller.js +21 -1
- package/dist/runtime/providerErrors.js +38 -0
- package/dist/runtime/runtimeObservation.js +0 -5
- package/dist/runtime/runtimeSessionCandidate.js +2 -3
- package/dist/runtime/sessionOwnerIdentity.js +1 -1
- package/dist/runtime/structuredProviderHost.js +4 -41
- package/dist/runtime/tmuxAdapters.js +6 -8
- package/dist/scheduler/activeRoleRunDelivery.js +7 -7
- package/dist/scheduler/leaderWakeupProcessor.js +19 -6
- package/dist/scheduler/operatorEvent.js +12 -20
- package/dist/scheduler/operatorInputNotificationProcessor.js +1 -1
- package/dist/scheduler/ports.js +5 -8
- package/dist/scheduler/roleRunLiveness.js +4 -4
- package/dist/scheduler/roleRunStall.js +16 -23
- package/dist/scheduler/taskExecutionProjection.js +25 -33
- package/dist/scheduler/taskObservabilityProjection.js +0 -1
- package/dist/scheduler/taskWake.js +5 -10
- package/dist/scheduler/wakeReason.js +2 -0
- package/dist/scheduler/wakeupQueue.js +2 -4
- package/dist/setup/setupCommand.js +1 -1
- package/dist/storage/contextRecords.js +106 -0
- package/dist/storage/currentTaskStore.js +1 -1
- package/dist/storage/homeLayout.js +13 -17
- package/dist/storage/migrations/agentFailureContext.js +22 -0
- package/dist/storage/migrations/currentInputContract.js +86 -0
- package/dist/storage/migrations/currentRuntimeContract.js +228 -0
- package/dist/storage/migrations/historicalVerificationPlan.js +35 -0
- package/dist/storage/migrations/integrationContinuation.js +5 -4
- package/dist/storage/migrations/narrowAgentFailureContext.js +65 -0
- package/dist/storage/migrations/notificationOnlyWakes.js +74 -0
- package/dist/storage/migrations/verificationPlanV1.js +162 -0
- package/dist/storage/migrations/verificationPolicy.js +74 -0
- package/dist/storage/migrations/workItemHistory.js +46 -0
- package/dist/storage/persistenceWorker.js +12 -4
- package/dist/storage/recordValidation.js +87 -0
- package/dist/storage/sqliteSchema.js +142 -1
- package/dist/storage/sqliteStore.js +156 -162
- package/dist/storage/storageSchema.js +2 -14
- package/dist/storage/storageVersions.js +1 -1
- package/dist/storage/storeRpc.js +15 -7
- package/dist/storage/taskStore.js +3 -7
- package/dist/storage/upgrade/upgradeOrchestrator.js +25 -37
- package/dist/task/completionReadiness.js +5 -24
- package/dist/task/draftPlan.js +0 -53
- package/dist/task/nextAction.js +7 -13
- package/dist/task/remoteDeliveryService.js +89 -0
- package/dist/task/taskActivation.js +2 -25
- package/dist/task/taskActivationService.js +0 -2
- package/dist/task/taskRecordReference.js +0 -1
- package/dist/task/taskSubmission.js +3 -14
- package/dist/telemetry/sqliteTelemetryBatch.js +29 -0
- package/dist/telemetry/sqliteTelemetryStore.js +49 -49
- package/dist/telemetry/telemetryWiring.js +4 -3
- package/dist/tmux/tmuxManager.js +29 -20
- package/dist/verification/gateArtifact.js +15 -24
- package/dist/verification/gateArtifactStore.js +14 -7
- package/dist/verification/verificationGateService.js +29 -88
- package/dist/verification/verificationPlan.js +27 -72
- package/dist/web/assets/client/app.js +1 -1
- package/dist/web/assets/client/taskSurface.js +4 -4
- package/dist/web/webServer.js +4 -6
- package/dist/web/webSnapshot.js +3 -83
- package/dist/web/webTaskSurface.js +17 -46
- package/dist/workItem/workItem.js +1 -12
- package/docs/architecture/capabilities-and-resources.md +14 -2
- package/docs/architecture/capabilities-and-resources.zh-CN.md +11 -1
- package/docs/managed-turn-and-session-runtime.md +83 -10
- package/docs/managed-turn-and-session-runtime.zh-CN.md +65 -11
- package/docs/plugin-sdk.md +4 -2
- package/docs/provider-runtime.md +3 -1
- package/docs/provider-runtime.zh-CN.md +4 -2
- package/docs/release-workflow.md +190 -7
- package/docs/release-workflow.zh-CN.md +139 -5
- package/docs/roles-and-configuration.md +29 -0
- package/docs/roles-and-configuration.zh-CN.md +21 -0
- package/docs/sqlite-control-plane-design.md +16 -3
- package/docs/sqlite-control-plane-design.zh-CN.md +13 -2
- package/docs/task-dag-semantics.md +3 -3
- package/docs/task-dag-semantics.zh-CN.md +2 -2
- package/docs/task-delivery.md +91 -5
- package/docs/task-delivery.zh-CN.md +72 -4
- package/docs/task-discovery.md +10 -11
- package/docs/task-discovery.zh-CN.md +8 -9
- package/docs/testing/verification-levels.md +145 -9
- package/docs/testing/verification-levels.zh-CN.md +103 -8
- package/package.json +1 -1
- package/skills/yui-leader/SKILL.md +5 -0
- package/skills/yui-leader/references/integration.md +9 -5
- package/skills/yui-leader/references/planning.md +9 -3
- package/skills/yui-operator/SKILL.md +15 -10
- package/skills/yui-runtime/SKILL.md +6 -6
- package/skills/yui-runtime/references/recovery.md +47 -0
- package/dist/agent/agentRegistry.js +0 -10
- package/dist/agentRun/runIdentity.js +0 -22
- package/dist/commands/deliveryGuardPreflight.js +0 -30
- package/dist/commands/taskIntegrationQueueCommands.js +0 -226
- package/dist/commands/taskOverviewCommand.js +0 -377
- package/dist/completion/completionWizard.js +0 -125
- package/dist/context/dispatchContext.js +0 -110
- package/dist/context/wakeNotification.js +0 -144
- package/dist/integration/integrationCheckEvidenceReuse.js +0 -53
- package/dist/integration/integrationQueueEntry.js +0 -191
- package/dist/integration/integrationQueueService.js +0 -810
- package/dist/release/fakeReleasePorts.js +0 -55
- package/dist/runtime/sessionOwnerRegistry.js +0 -137
- package/dist/task/deliveryGuard.js +0 -226
- /package/dist/{commands/taskActor.js → task/taskAuthority.js} +0 -0
|
@@ -2,9 +2,10 @@ import { spawn } from "node:child_process";
|
|
|
2
2
|
import { createHash } from "node:crypto";
|
|
3
3
|
import { mkdir, open, rm } from "node:fs/promises";
|
|
4
4
|
import { join, resolve } from "node:path";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
5
|
+
import { jobStepDirectory } from "../job/stepDirectory.js";
|
|
6
|
+
import { completeGateArtifact, createGateArtifact, gateArtifactRef, isReusableGateArtifact, parseGateArtifactRef, verifyGateArtifactLogs } from "./gateArtifact.js";
|
|
7
|
+
import { findGateArtifact, importGateArtifactSteps, loadGateArtifact, saveGateArtifact } from "./gateArtifactStore.js";
|
|
8
|
+
import { resolveProjectVerificationPlan, resolveToolchain, toolchainDigest, verificationPlanDigest } from "./verificationPlan.js";
|
|
8
9
|
/** Resolve a Project's active plan and the runtime toolchain it gates under. */
|
|
9
10
|
export function resolveVerificationGate(project, _environment = process.env) {
|
|
10
11
|
const plan = resolveProjectVerificationPlan(project);
|
|
@@ -13,26 +14,22 @@ export function resolveVerificationGate(project, _environment = process.env) {
|
|
|
13
14
|
const toolchain = resolveToolchain();
|
|
14
15
|
return Object.freeze({
|
|
15
16
|
plan,
|
|
16
|
-
mode: plan.mode,
|
|
17
17
|
toolchain,
|
|
18
18
|
planDigest: verificationPlanDigest(plan),
|
|
19
19
|
toolchainDigest: toolchainDigest(plan, toolchain)
|
|
20
20
|
});
|
|
21
21
|
}
|
|
22
22
|
export function gateIdentityForCandidate(input) {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
baseHead: input.baseHead ?? input.commit
|
|
27
|
-
}
|
|
28
|
-
: undefined;
|
|
23
|
+
if (input.level !== "L2" || input.targetRef === undefined || input.baseHead === undefined) {
|
|
24
|
+
throw new Error("L2 verification requires its exact target ref and base head.");
|
|
25
|
+
}
|
|
29
26
|
return Object.freeze({
|
|
30
27
|
projectId: input.projectId,
|
|
31
28
|
level: input.level,
|
|
32
29
|
commit: input.commit,
|
|
33
30
|
planDigest: input.gate.planDigest,
|
|
34
31
|
toolchainDigest: input.gate.toolchainDigest,
|
|
35
|
-
|
|
32
|
+
boundary: { targetRef: input.targetRef, baseHead: input.baseHead }
|
|
36
33
|
});
|
|
37
34
|
}
|
|
38
35
|
/**
|
|
@@ -48,6 +45,17 @@ export async function lookupReusableGateArtifact(store, identity) {
|
|
|
48
45
|
const verification = verifyGateArtifactLogs(artifact, logs);
|
|
49
46
|
return verification.ok ? artifact : null;
|
|
50
47
|
}
|
|
48
|
+
/** Withdraw prior reusable evidence before execution. An interrupted attempt
|
|
49
|
+
* remains incomplete, never an excuse to return the previous success. */
|
|
50
|
+
export function beginGateVerification(store, identity, plan, now) {
|
|
51
|
+
const existing = findGateArtifact(store, identity);
|
|
52
|
+
const artifact = {
|
|
53
|
+
...createGateArtifact(identity, { planId: plan.id, planVersion: plan.version, generator: "yui" }, now),
|
|
54
|
+
reuseCount: existing?.reuseCount ?? 0
|
|
55
|
+
};
|
|
56
|
+
saveGateArtifact(store, artifact, new Map());
|
|
57
|
+
return artifact;
|
|
58
|
+
}
|
|
51
59
|
/**
|
|
52
60
|
* Record a GateArtifact from a terminal check DurableJob. The job's step
|
|
53
61
|
* logs are copied into the artifact store and bound to their digests, so the
|
|
@@ -91,38 +99,17 @@ export async function recordGateArtifactFromStepOutcomes(store, identity, plan,
|
|
|
91
99
|
return recordGateArtifact(store, identity, plan, steps, succeeded, now);
|
|
92
100
|
}
|
|
93
101
|
async function recordGateArtifact(store, identity, plan, steps, succeeded, now) {
|
|
94
|
-
const created =
|
|
95
|
-
planId: plan.id,
|
|
96
|
-
planVersion: plan.version,
|
|
97
|
-
generator: "yui"
|
|
98
|
-
}, now);
|
|
102
|
+
const created = beginGateVerification(store, identity, plan, now);
|
|
99
103
|
const { steps: importedSteps, logs } = await importGateArtifactSteps(steps);
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
// (e.g. record mode always re-runs the gate but must not lose the potential
|
|
103
|
-
// reuse observations from earlier runs).
|
|
104
|
-
const existing = findGateArtifact(store, identity);
|
|
105
|
-
if (existing !== null) {
|
|
106
|
-
if (isReusableGateArtifact(existing) && !succeeded) {
|
|
107
|
-
// A failed re-run must not downgrade a proven successful artifact.
|
|
108
|
-
touchGateArtifact(store, existing);
|
|
109
|
-
return existing;
|
|
110
|
-
}
|
|
111
|
-
artifact = validateGateArtifact({
|
|
112
|
-
...artifact,
|
|
113
|
-
potentialReuseCount: existing.potentialReuseCount,
|
|
114
|
-
reuseCount: existing.reuseCount,
|
|
115
|
-
createdAt: existing.createdAt
|
|
116
|
-
});
|
|
117
|
-
}
|
|
104
|
+
const artifact = completeGateArtifact(created, importedSteps, succeeded && importedSteps.length > 0 && importedSteps.every(step => step.outcome === "passed")
|
|
105
|
+
? "succeeded" : "failed", now);
|
|
118
106
|
saveGateArtifact(store, artifact, logs);
|
|
119
107
|
return artifact;
|
|
120
108
|
}
|
|
121
109
|
const GATE_STEP_TIMEOUT_MS = 30 * 60_000;
|
|
122
110
|
const SIGKILL_GRACE_MS = 2_000;
|
|
123
111
|
/**
|
|
124
|
-
* Run structured gate steps in-process
|
|
125
|
-
* L2 fallback). Each step gets its own log file; argv steps run without a
|
|
112
|
+
* Run structured gate steps in-process. Each step gets its own log file; argv steps run without a
|
|
126
113
|
* shell. This is the local form of the gate; the DurableJob form is the
|
|
127
114
|
* Controller-owned one used by production Integration.
|
|
128
115
|
*/
|
|
@@ -144,9 +131,9 @@ export async function runGateStepsInProcess(workspace, steps, environment, logsD
|
|
|
144
131
|
const output = await open(absoluteLogPath, "w", 0o600);
|
|
145
132
|
let timedOut = false;
|
|
146
133
|
let child;
|
|
147
|
-
const stepCwd = step.cwd ?? cwd;
|
|
148
134
|
const stepEnv = step.env === undefined ? env : { ...env, ...step.env };
|
|
149
135
|
try {
|
|
136
|
+
const stepCwd = jobStepDirectory(cwd, step.cwd);
|
|
150
137
|
if (step.argv !== undefined) {
|
|
151
138
|
const [file, ...args] = step.argv;
|
|
152
139
|
child = spawn(file, args, {
|
|
@@ -228,7 +215,7 @@ function sanitizeLogName(name) {
|
|
|
228
215
|
* environment/registry failure is never recorded as a Candidate test
|
|
229
216
|
* failure; the Integration gate has not started when bootstrap fails.
|
|
230
217
|
*/
|
|
231
|
-
export function checkResultsFromGateJob(job,
|
|
218
|
+
export function checkResultsFromGateJob(job, _home) {
|
|
232
219
|
const results = new Map((job.result?.steps ?? []).map((step) => [step.name, step]));
|
|
233
220
|
const checks = [];
|
|
234
221
|
for (const step of job.steps) {
|
|
@@ -285,6 +272,10 @@ export function checkResultsFromGateArtifact(artifact, reused = false) {
|
|
|
285
272
|
outcome: step.outcome,
|
|
286
273
|
...(step.logPath === undefined ? {} : { logPath: step.logPath })
|
|
287
274
|
}));
|
|
275
|
+
if (!isReusableGateArtifact(artifact) && !checks.some(check => check.outcome === "failed")) {
|
|
276
|
+
checks.push({ name: "verification-result", outcome: "failed",
|
|
277
|
+
details: `Verification is ${artifact.status}/${artifact.outcome}; successful evidence is not established.` });
|
|
278
|
+
}
|
|
288
279
|
if (reused) {
|
|
289
280
|
checks.push({
|
|
290
281
|
name: gateArtifactRef(artifact.key),
|
|
@@ -331,56 +322,6 @@ export async function verifyGateArtifactForReview(store, projectId, key, expecte
|
|
|
331
322
|
}
|
|
332
323
|
return { ok: true, artifact };
|
|
333
324
|
}
|
|
334
|
-
/**
|
|
335
|
-
* Enforce mode (rollout step 4): reject ad-hoc full-suite shell checks that
|
|
336
|
-
* duplicate the plan's L2 steps for a configured Project. Explicit targeted
|
|
337
|
-
* diagnostic checks (anything that is not an L2 step command) stay allowed.
|
|
338
|
-
*/
|
|
339
|
-
export function assertNoAdHocFullSuiteChecks(plan, checkCommands) {
|
|
340
|
-
const l2Commands = new Set(plan.l2.steps.map((step) => verificationStepCommand(step)));
|
|
341
|
-
for (const command of checkCommands) {
|
|
342
|
-
if (l2Commands.has(command)) {
|
|
343
|
-
throw new Error(`Ad-hoc full-suite check is rejected for plan-enabled Project `
|
|
344
|
-
+ `${plan.id}: use the VerificationPlan L2 gate (or pass a targeted `
|
|
345
|
-
+ `diagnostic command that is not an L2 step): ${command}`);
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
/**
|
|
350
|
-
* Run an L1 gate for a change: select the affected categories' checks, run
|
|
351
|
-
* them in-process, and record the L1 artifact. Reuse mode returns an existing
|
|
352
|
-
* successful artifact for the same tuple.
|
|
353
|
-
*/
|
|
354
|
-
export async function runL1Gate(input) {
|
|
355
|
-
const identity = gateIdentityForCandidate({
|
|
356
|
-
projectId: input.projectId,
|
|
357
|
-
gate: input.gate,
|
|
358
|
-
level: "L1",
|
|
359
|
-
commit: input.commit
|
|
360
|
-
});
|
|
361
|
-
if (input.gate.mode !== "record") {
|
|
362
|
-
const existing = await lookupReusableGateArtifact(input.store, identity);
|
|
363
|
-
if (existing !== null) {
|
|
364
|
-
const reused = recordGateArtifactReuse(existing, input.now);
|
|
365
|
-
touchGateArtifact(input.store, reused);
|
|
366
|
-
return {
|
|
367
|
-
artifact: reused,
|
|
368
|
-
reused: true,
|
|
369
|
-
checks: checkResultsFromGateArtifact(reused, true)
|
|
370
|
-
};
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
const selected = selectL1Checks(input.gate.plan, input.changedPaths);
|
|
374
|
-
const outcomes = await runGateStepsInProcess(input.workspace, planL1JobSteps(selected), input.environment, input.logsDirectory, input.commit);
|
|
375
|
-
const succeeded = outcomes.length > 0
|
|
376
|
-
&& outcomes.every((outcome) => outcome.exitCode === 0 && outcome.signal === null && !outcome.timedOut);
|
|
377
|
-
const artifact = await recordGateArtifactFromStepOutcomes(input.store, identity, input.gate.plan, outcomes, succeeded, input.now);
|
|
378
|
-
return {
|
|
379
|
-
artifact,
|
|
380
|
-
reused: false,
|
|
381
|
-
checks: checkResultsFromGateArtifact(artifact, false)
|
|
382
|
-
};
|
|
383
|
-
}
|
|
384
325
|
/** Stable digest of a log file, for ad-hoc evidence binding. */
|
|
385
326
|
export function digestLogContent(content) {
|
|
386
327
|
return createHash("sha256").update(content).digest("hex");
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
+
import { isAbsolute, relative, resolve, sep } from "node:path";
|
|
2
3
|
import { requireIdentity, requirePositiveInteger, requireText } from "../domain/validation.js";
|
|
3
4
|
/**
|
|
4
5
|
* Issue 08: the per-Project VerificationPlan contract.
|
|
@@ -10,7 +11,7 @@ import { requireIdentity, requirePositiveInteger, requireText } from "../domain/
|
|
|
10
11
|
* explicit `--check <shell>` path, which is marked unstructured and is not
|
|
11
12
|
* reusable across stages.
|
|
12
13
|
*/
|
|
13
|
-
export const VERIFICATION_PLAN_SCHEMA_VERSION =
|
|
14
|
+
export const VERIFICATION_PLAN_SCHEMA_VERSION = 2;
|
|
14
15
|
export const VERIFICATION_PLAN_KIND = "verification-plan";
|
|
15
16
|
/** The reserved knowledge marker that carries a Project's VerificationPlan. */
|
|
16
17
|
export const VERIFICATION_PLAN_KNOWLEDGE_KIND = VERIFICATION_PLAN_KIND;
|
|
@@ -22,25 +23,21 @@ export function normalizeVerificationPlan(raw) {
|
|
|
22
23
|
if (record.kind !== VERIFICATION_PLAN_KIND) {
|
|
23
24
|
throw new Error(`VerificationPlan kind must be "${VERIFICATION_PLAN_KIND}".`);
|
|
24
25
|
}
|
|
25
|
-
const schemaVersion = record.schemaVersion
|
|
26
|
+
const schemaVersion = record.schemaVersion;
|
|
26
27
|
if (schemaVersion !== VERIFICATION_PLAN_SCHEMA_VERSION) {
|
|
27
28
|
throw new Error(`VerificationPlan schemaVersion must be ${VERIFICATION_PLAN_SCHEMA_VERSION}.`);
|
|
28
29
|
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
if (Object.hasOwn(record, "mode"))
|
|
31
|
+
throw new Error("VerificationPlan mode is retired; request an explicit rerun on the operation.");
|
|
32
|
+
if (Object.hasOwn(record, "l1"))
|
|
33
|
+
throw new Error("VerificationPlan l1 is retired; declare current checks in l2.");
|
|
33
34
|
const plan = {
|
|
34
35
|
schemaVersion: VERIFICATION_PLAN_SCHEMA_VERSION,
|
|
35
36
|
kind: VERIFICATION_PLAN_KIND,
|
|
36
37
|
id: requireIdentity(record.id, "VerificationPlan id"),
|
|
37
38
|
version: requireText(record.version, "VerificationPlan version"),
|
|
38
|
-
mode,
|
|
39
39
|
toolchain: normalizeToolchain(record.toolchain),
|
|
40
40
|
bootstrap: normalizeSteps(record.bootstrap, "bootstrap"),
|
|
41
|
-
l1: {
|
|
42
|
-
categories: normalizeL1Categories(record.l1)
|
|
43
|
-
},
|
|
44
41
|
l2: {
|
|
45
42
|
steps: normalizeSteps(record.l2?.steps, "l2")
|
|
46
43
|
},
|
|
@@ -126,34 +123,6 @@ function normalizeStepEnv(raw, label) {
|
|
|
126
123
|
}
|
|
127
124
|
return Object.freeze(env);
|
|
128
125
|
}
|
|
129
|
-
function normalizeL1Categories(raw) {
|
|
130
|
-
if (raw === undefined)
|
|
131
|
-
return Object.freeze([]);
|
|
132
|
-
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
|
|
133
|
-
throw new Error("VerificationPlan l1 must be an object.");
|
|
134
|
-
}
|
|
135
|
-
const categoriesRaw = raw.categories;
|
|
136
|
-
if (!Array.isArray(categoriesRaw)) {
|
|
137
|
-
throw new Error("VerificationPlan l1 categories must be an array.");
|
|
138
|
-
}
|
|
139
|
-
const ids = new Set();
|
|
140
|
-
return Object.freeze(categoriesRaw.map((entry) => {
|
|
141
|
-
if (typeof entry !== "object" || entry === null || Array.isArray(entry)) {
|
|
142
|
-
throw new Error("VerificationPlan l1 category must be an object.");
|
|
143
|
-
}
|
|
144
|
-
const record = entry;
|
|
145
|
-
const id = requireIdentity(record.id, "VerificationPlan l1 category id");
|
|
146
|
-
if (ids.has(id)) {
|
|
147
|
-
throw new Error(`VerificationPlan l1 category ids must be unique: ${id}.`);
|
|
148
|
-
}
|
|
149
|
-
ids.add(id);
|
|
150
|
-
return Object.freeze({
|
|
151
|
-
id,
|
|
152
|
-
paths: normalizedTextList(record.paths, `VerificationPlan l1 category ${id} paths`),
|
|
153
|
-
checks: normalizeSteps(record.checks, `l1 ${id}`)
|
|
154
|
-
});
|
|
155
|
-
}));
|
|
156
|
-
}
|
|
157
126
|
function normalizedTextList(raw, label) {
|
|
158
127
|
if (!Array.isArray(raw)) {
|
|
159
128
|
throw new Error(`${label} must be an array.`);
|
|
@@ -169,16 +138,18 @@ function normalizedTextList(raw, label) {
|
|
|
169
138
|
}
|
|
170
139
|
/**
|
|
171
140
|
* The stable digest of the gate contract. It deliberately excludes the
|
|
172
|
-
*
|
|
173
|
-
*
|
|
141
|
+
* retention window and documentation fields. Execution semantics belong to
|
|
142
|
+
* the identity so evidence from a prior contract is never silently reused.
|
|
174
143
|
*/
|
|
175
144
|
export function verificationPlanDigest(plan) {
|
|
176
145
|
const canonical = canonicalJson({
|
|
146
|
+
// Old artifacts may have skipped shell commands or run in the wrong cwd.
|
|
147
|
+
// Keep that history, but never reuse it as proof under corrected semantics.
|
|
148
|
+
executionContract: "workspace-argv-or-shell/clean-candidate/l2-only/v5",
|
|
177
149
|
id: plan.id,
|
|
178
150
|
version: plan.version,
|
|
179
151
|
toolchain: plan.toolchain,
|
|
180
152
|
bootstrap: plan.bootstrap,
|
|
181
|
-
l1: plan.l1,
|
|
182
153
|
l2: plan.l2,
|
|
183
154
|
...(plan.l3 === undefined ? {} : { l3: plan.l3 })
|
|
184
155
|
});
|
|
@@ -208,46 +179,30 @@ export function toolchainDigest(plan, toolchain) {
|
|
|
208
179
|
});
|
|
209
180
|
return createHash("sha256").update(canonical).digest("hex");
|
|
210
181
|
}
|
|
211
|
-
/**
|
|
212
|
-
* Select the L1 checks for a change: every category whose path prefixes match
|
|
213
|
-
* a changed path. A category with no path prefixes matches nothing (it must
|
|
214
|
-
* be selected explicitly by the caller).
|
|
215
|
-
*/
|
|
216
|
-
export function selectL1Checks(plan, changedPaths) {
|
|
217
|
-
const selected = new Map();
|
|
218
|
-
for (const category of plan.l1.categories) {
|
|
219
|
-
if (category.paths.length === 0)
|
|
220
|
-
continue;
|
|
221
|
-
const matches = changedPaths.some((path) => category.paths.some((prefix) => path === prefix || path.startsWith(`${prefix}/`)));
|
|
222
|
-
if (!matches)
|
|
223
|
-
continue;
|
|
224
|
-
for (const check of category.checks) {
|
|
225
|
-
if (!selected.has(check.name))
|
|
226
|
-
selected.set(check.name, check);
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
return Object.freeze([...selected.values()]);
|
|
230
|
-
}
|
|
231
182
|
/** Bootstrap steps as DurableJob steps, named `bootstrap-N`. */
|
|
232
|
-
export function planBootstrapJobSteps(plan) {
|
|
233
|
-
return plan.bootstrap.map((step, index) => toDurableJobStep(step, `bootstrap-${index + 1}
|
|
183
|
+
export function planBootstrapJobSteps(plan, workspace) {
|
|
184
|
+
return plan.bootstrap.map((step, index) => toDurableJobStep(step, `bootstrap-${index + 1}`, workspace));
|
|
234
185
|
}
|
|
235
186
|
/** L2 gate steps as DurableJob steps, named `gate-N`. */
|
|
236
|
-
export function planL2JobSteps(plan) {
|
|
237
|
-
return plan.l2.steps.map((step, index) => toDurableJobStep(step, `gate-${index + 1}
|
|
187
|
+
export function planL2JobSteps(plan, workspace) {
|
|
188
|
+
return plan.l2.steps.map((step, index) => toDurableJobStep(step, `gate-${index + 1}`, workspace));
|
|
238
189
|
}
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
190
|
+
function toDurableJobStep(step, name, workspace) {
|
|
191
|
+
if (!isAbsolute(workspace))
|
|
192
|
+
throw new Error("Verification workspace must be absolute.");
|
|
193
|
+
const cwd = step.cwd === undefined ? undefined : resolve(workspace, step.cwd);
|
|
194
|
+
const nested = cwd === undefined ? "" : relative(workspace, cwd);
|
|
195
|
+
if (step.cwd !== undefined && (isAbsolute(step.cwd) || nested === ".."
|
|
196
|
+
|| nested.startsWith(`..${sep}`) || isAbsolute(nested))) {
|
|
197
|
+
throw new Error(`Verification step cwd is outside its workspace: ${step.name}.`);
|
|
198
|
+
}
|
|
244
199
|
return {
|
|
245
200
|
name,
|
|
246
201
|
// The shell-equivalent command stays as the human-readable fallback and
|
|
247
202
|
// the coverage-matching string; argv is the executable form.
|
|
248
203
|
command: step.shell === true ? step.argv.join(" ") : shellQuote(step.argv),
|
|
249
|
-
argv: step.argv,
|
|
250
|
-
...(
|
|
204
|
+
...(step.shell === true ? {} : { argv: step.argv }),
|
|
205
|
+
...(cwd === undefined ? {} : { cwd }),
|
|
251
206
|
...(step.env === undefined ? {} : { env: step.env })
|
|
252
207
|
};
|
|
253
208
|
}
|
|
@@ -504,7 +504,7 @@ async function refreshDashboard(options) {
|
|
|
504
504
|
}
|
|
505
505
|
const previousInputs = state.counts ? state.counts.openInputs : null;
|
|
506
506
|
try {
|
|
507
|
-
const query = new URLSearchParams(
|
|
507
|
+
const query = new URLSearchParams();
|
|
508
508
|
if (state.catalogAll) query.set("all", "true");
|
|
509
509
|
if (state.filter !== "all") query.set("status", state.filter);
|
|
510
510
|
if (state.query.trim()) query.set("search", state.query.trim());
|
|
@@ -527,11 +527,11 @@ function renderSubmissionFacets(container, submission, say) {
|
|
|
527
527
|
const step = submission.nextStep;
|
|
528
528
|
if (step) {
|
|
529
529
|
const nextText = step.kind === "activate-manually"
|
|
530
|
-
? say("Next:
|
|
531
|
-
"
|
|
530
|
+
? say("Next: request activation with an explicit environment (yui task activation request " + step.taskId + " --request-id <id> --environment <plan>).",
|
|
531
|
+
"下一步:明确环境并请求激活(yui task activation request " + step.taskId + " --request-id <id> --environment <plan>)。")
|
|
532
532
|
: step.kind === "start-execution"
|
|
533
|
-
? say("Next: start execution first, then
|
|
534
|
-
"
|
|
533
|
+
? say("Next: start execution first, then request activation (" + step.taskId + ").",
|
|
534
|
+
"下一步:先启动执行,再请求激活(" + step.taskId + ")。")
|
|
535
535
|
: step.kind === "await-pending-activation"
|
|
536
536
|
? say("Next: waiting on the pending activation " + step.activationRef + ".",
|
|
537
537
|
"下一步:等待待处理的激活 " + step.activationRef + "。")
|
package/dist/web/webServer.js
CHANGED
|
@@ -6,7 +6,7 @@ import { parseTaskCatalogOptions } from "../context/taskCatalog.js";
|
|
|
6
6
|
import { WebRequestRejected } from "./webMutation.js";
|
|
7
7
|
import { TASK_SUBMISSION_INTENTS } from "../message/message.js";
|
|
8
8
|
import { DASHBOARD_HTML, findWebAsset } from "./assets/assetManifest.js";
|
|
9
|
-
import {
|
|
9
|
+
import { buildWebTaskCatalog, buildWebTaskDetail } from "./webSnapshot.js";
|
|
10
10
|
const MAX_JSON_BODY_BYTES = 16 * 1024;
|
|
11
11
|
const MAX_TERMINAL_MESSAGE_BYTES = 64 * 1024;
|
|
12
12
|
const MAX_TERMINAL_BUFFERED_BYTES = 1024 * 1024;
|
|
@@ -238,11 +238,9 @@ async function handleHttpRequest(request, response, store, dependencies, token,
|
|
|
238
238
|
}
|
|
239
239
|
else if (pathname === "/api/dashboard") {
|
|
240
240
|
const query = new URL(request.url, "http://localhost").searchParams;
|
|
241
|
-
const snapshot = query.
|
|
242
|
-
?
|
|
243
|
-
|
|
244
|
-
: [`--${key}`, value])))
|
|
245
|
-
: buildWebDashboardSnapshot(store, now());
|
|
241
|
+
const snapshot = buildWebTaskCatalog(store, parseTaskCatalogOptions([...query].flatMap(([key, value]) => key === "all"
|
|
242
|
+
? (value === "true" ? ["--all"] : ["--invalid-all"])
|
|
243
|
+
: [`--${key}`, value])));
|
|
246
244
|
sendJson(response, 200, snapshot, method === "HEAD");
|
|
247
245
|
}
|
|
248
246
|
else if (pathname.startsWith("/api/tasks/")) {
|
package/dist/web/webSnapshot.js
CHANGED
|
@@ -14,57 +14,6 @@ import { projectTaskRemoteDelivery } from "../task/remoteDelivery.js";
|
|
|
14
14
|
export function buildWebTaskCatalog(store, options) {
|
|
15
15
|
return store.transaction(reader => readTaskCatalog(reader, options));
|
|
16
16
|
}
|
|
17
|
-
export function buildWebDashboardSnapshot(store, now = new Date()) {
|
|
18
|
-
return store.transaction((reader) => {
|
|
19
|
-
const statusCounts = {
|
|
20
|
-
draft: 0,
|
|
21
|
-
active: 0,
|
|
22
|
-
completed: 0,
|
|
23
|
-
cancelled: 0,
|
|
24
|
-
archived: 0
|
|
25
|
-
};
|
|
26
|
-
const projectNames = new Map(reader.listProjects().map((project) => [project.id, project.name]));
|
|
27
|
-
let openInputs = 0;
|
|
28
|
-
const attention = [];
|
|
29
|
-
const tasks = reader.listTasks().map((task) => {
|
|
30
|
-
statusCounts[task.status] += 1;
|
|
31
|
-
const taskOpenInputs = reader.listInputRequests(task.id)
|
|
32
|
-
.filter((request) => request.status === "open").length;
|
|
33
|
-
if (task.status !== "archived")
|
|
34
|
-
openInputs += taskOpenInputs;
|
|
35
|
-
const taskOpen = reader.listInputRequests(task.id)
|
|
36
|
-
.filter((request) => request.status === "open");
|
|
37
|
-
for (const request of task.status === "archived" ? [] : taskOpen) {
|
|
38
|
-
attention.push({ taskId: task.id, taskTitle: task.title, request });
|
|
39
|
-
}
|
|
40
|
-
const events = reader.listEvents?.(task.id) ?? [];
|
|
41
|
-
const needsAttentionCount = task.status === "archived" ? 0 : reader.listRuns(task.id)
|
|
42
|
-
.filter((run) => run.status === "active" && isRoleRunStalled(events, run.id))
|
|
43
|
-
.length;
|
|
44
|
-
const execution = buildTaskExecutionProjection(reader, task.id, task, now);
|
|
45
|
-
const names = task.projectBindings.flatMap(({ projectId }) => {
|
|
46
|
-
const name = projectNames.get(projectId);
|
|
47
|
-
return name === undefined ? [] : [name];
|
|
48
|
-
});
|
|
49
|
-
return {
|
|
50
|
-
...task,
|
|
51
|
-
...(names.length === 0 ? {} : { projectNames: names }),
|
|
52
|
-
workItems: countWorkItems(reader.listWorkItems(task.id)),
|
|
53
|
-
roleCount: reader.listRoles(task.id).length,
|
|
54
|
-
openInputCount: taskOpenInputs,
|
|
55
|
-
needsAttentionCount,
|
|
56
|
-
execution,
|
|
57
|
-
executionStatus: execution?.status ?? null
|
|
58
|
-
};
|
|
59
|
-
}).sort(compareDashboardTasks);
|
|
60
|
-
return {
|
|
61
|
-
generatedAt: now.toISOString(),
|
|
62
|
-
counts: { total: tasks.length, ...statusCounts, openInputs },
|
|
63
|
-
attention: attention.sort(compareAttention),
|
|
64
|
-
tasks
|
|
65
|
-
};
|
|
66
|
-
});
|
|
67
|
-
}
|
|
68
17
|
export function buildWebTaskDetail(store, taskId, now = new Date()) {
|
|
69
18
|
return store.transaction((reader) => {
|
|
70
19
|
const task = reader.getTask(taskId);
|
|
@@ -77,7 +26,7 @@ export function buildWebTaskDetail(store, taskId, now = new Date()) {
|
|
|
77
26
|
return name === undefined ? [] : [name];
|
|
78
27
|
});
|
|
79
28
|
const runs = reader.listRuns(taskId);
|
|
80
|
-
const events = reader.listEvents
|
|
29
|
+
const events = reader.listEvents(taskId);
|
|
81
30
|
const retiredMessageIds = retiredTaskRecordIds(events, "message");
|
|
82
31
|
const needsAttentionRuns = runs
|
|
83
32
|
.filter((run) => run.status === "active" && isRoleRunStalled(events, run.id))
|
|
@@ -128,7 +77,7 @@ export function buildWebTaskDetail(store, taskId, now = new Date()) {
|
|
|
128
77
|
&& effectiveLaunch.sourceDesiredRevision !== role.launchRevision
|
|
129
78
|
};
|
|
130
79
|
});
|
|
131
|
-
const execution = buildTaskExecutionProjection(reader, taskId,
|
|
80
|
+
const execution = buildTaskExecutionProjection(reader, taskId, now);
|
|
132
81
|
if (execution === null)
|
|
133
82
|
return null;
|
|
134
83
|
const remoteDelivery = webRemoteDelivery(reader, task);
|
|
@@ -166,7 +115,7 @@ export function buildWebTaskDetail(store, taskId, now = new Date()) {
|
|
|
166
115
|
function webRemoteDelivery(reader, task) {
|
|
167
116
|
return projectTaskRemoteDelivery({
|
|
168
117
|
task,
|
|
169
|
-
events: reader.listEvents
|
|
118
|
+
events: reader.listEvents(task.id),
|
|
170
119
|
publications: reader.listPublicationReferences(task.id),
|
|
171
120
|
managedWorkspaces: reader.listManagedWorkspaces(task.id),
|
|
172
121
|
runs: reader.listRuns(task.id),
|
|
@@ -267,32 +216,3 @@ function latestStallField(events, runId, field) {
|
|
|
267
216
|
.sort((left, right) => Date.parse(right.createdAt) - Date.parse(left.createdAt))[0];
|
|
268
217
|
return stalled?.payload[field];
|
|
269
218
|
}
|
|
270
|
-
function countWorkItems(items) {
|
|
271
|
-
const counts = {
|
|
272
|
-
total: items.length,
|
|
273
|
-
open: 0,
|
|
274
|
-
accepted: 0,
|
|
275
|
-
retired: 0
|
|
276
|
-
};
|
|
277
|
-
const mutable = counts;
|
|
278
|
-
for (const item of items) {
|
|
279
|
-
mutable[item.status] += 1;
|
|
280
|
-
}
|
|
281
|
-
return counts;
|
|
282
|
-
}
|
|
283
|
-
function compareDashboardTasks(left, right) {
|
|
284
|
-
const statusOrder = {
|
|
285
|
-
active: 0,
|
|
286
|
-
draft: 1,
|
|
287
|
-
completed: 2,
|
|
288
|
-
cancelled: 3,
|
|
289
|
-
archived: 4
|
|
290
|
-
};
|
|
291
|
-
return statusOrder[left.status] - statusOrder[right.status]
|
|
292
|
-
|| Date.parse(right.updatedAt) - Date.parse(left.updatedAt)
|
|
293
|
-
|| left.id.localeCompare(right.id);
|
|
294
|
-
}
|
|
295
|
-
function compareAttention(left, right) {
|
|
296
|
-
return Date.parse(left.request.createdAt) - Date.parse(right.request.createdAt)
|
|
297
|
-
|| left.request.id.localeCompare(right.request.id);
|
|
298
|
-
}
|
|
@@ -1,37 +1,17 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { readTaskContext, readTaskContextDelta,
|
|
1
|
+
import { applyGlobalInputControl } from "../commands/globalRoleCommands.js";
|
|
2
|
+
import { applyTaskInputControl, sendTaskMessageCommand, updateTaskMetadataCommand } from "../commands/taskCommands.js";
|
|
3
|
+
import { runTaskInputCommand } from "../commands/taskInputCommands.js";
|
|
4
|
+
import { inspectTaskContext, readTaskContext, readTaskContextDelta, withContextObservations } from "../context/taskContext.js";
|
|
5
5
|
import { BUILTIN_CAPABILITIES } from "../kernel/builtinCapabilities.js";
|
|
6
6
|
import { capabilitySchemaError } from "../kernel/capabilitySchema.js";
|
|
7
|
-
import {
|
|
7
|
+
import { recordGlobalInterruptResult, recordGlobalSteerResult } from "../message/globalInterrupt.js";
|
|
8
|
+
import { recordTaskInterruptResult } from "../message/taskInterrupt.js";
|
|
9
|
+
import { AGENT_HOST_CONTROL_PROTOCOL, foldInterruptLiveReceipt, foldSteerLiveReceipt, sendAgentHostCancelControl, sendAgentHostSteerControl } from "../runtime/agentHost.js";
|
|
8
10
|
import { webLocalMutation, WebRequestRejected } from "./webMutation.js";
|
|
9
|
-
import { runTaskInputCommand } from "../commands/taskInputCommands.js";
|
|
10
|
-
import { sendAgentHostSteerControl, sendAgentHostCancelControl, AGENT_HOST_CONTROL_PROTOCOL, foldSteerLiveReceipt, foldInterruptLiveReceipt } from "../runtime/agentHost.js";
|
|
11
11
|
const DEFAULT_WEB_HOST_CONTROL = {
|
|
12
12
|
steer: sendAgentHostSteerControl,
|
|
13
13
|
cancel: sendAgentHostCancelControl
|
|
14
14
|
};
|
|
15
|
-
/** The store-only CLI argv for the shared application-layer primitive
|
|
16
|
-
* (decision-3 §7). The Web surface never re-implements the queue/steer/interrupt
|
|
17
|
-
* decisions; it drives the exact same command the CLI drives. */
|
|
18
|
-
function controlArgv(taskId, input) {
|
|
19
|
-
if (input.action === "queue") {
|
|
20
|
-
return ["message", "queue", taskId, input.body, "--request-id", input.requestId,
|
|
21
|
-
...(input.to === undefined ? [] : ["--to", input.to]),
|
|
22
|
-
...(input.workItem === undefined ? [] : ["--work-item", input.workItem]),
|
|
23
|
-
...(input.reviewRound === undefined ? [] : ["--review-round", input.reviewRound])];
|
|
24
|
-
}
|
|
25
|
-
if (input.action === "steer") {
|
|
26
|
-
return ["message", "steer", taskId, input.body, "--request-id", input.requestId,
|
|
27
|
-
"--expected-target", input.expectedTarget, "--to", input.to,
|
|
28
|
-
...(input.workItem === undefined ? [] : ["--work-item", input.workItem]),
|
|
29
|
-
...(input.reviewRound === undefined ? [] : ["--review-round", input.reviewRound])];
|
|
30
|
-
}
|
|
31
|
-
return ["role", "interrupt", taskId, input.role, "--expected-target", input.expectedTarget,
|
|
32
|
-
...(input.thenMessage === undefined ? [] : ["--then-message", input.thenMessage]),
|
|
33
|
-
"--request-id", input.requestId];
|
|
34
|
-
}
|
|
35
15
|
/** A queue is delivered to the Leader mailbox only when it is unaddressed or
|
|
36
16
|
* addressed to the Leader; an addressed Worker/Reviewer queue goes to the Task
|
|
37
17
|
* mailbox. A steer/interrupt is a live control op, so it only reconciles the
|
|
@@ -53,12 +33,8 @@ export function createWebTaskSurface(store, options = {}, observations = [], hos
|
|
|
53
33
|
// Notifications are after the outer transaction. Their failure must not be
|
|
54
34
|
// reclassified as a rejection of a mutation that already committed.
|
|
55
35
|
const notify = (taskId, leader = false) => {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
? { kind: "role", taskId, roleName: "leader" } : { kind: "task", taskId });
|
|
59
|
-
}
|
|
60
|
-
else
|
|
61
|
-
options.runtime?.notifyStateChanged(taskId);
|
|
36
|
+
void options.runtime?.notifyMailboxChanged(leader
|
|
37
|
+
? { kind: "role", taskId, roleName: "leader" } : { kind: "task", taskId });
|
|
62
38
|
};
|
|
63
39
|
return {
|
|
64
40
|
globalState: (roleName) => {
|
|
@@ -79,20 +55,15 @@ export function createWebTaskSurface(store, options = {}, observations = [], hos
|
|
|
79
55
|
};
|
|
80
56
|
},
|
|
81
57
|
globalControl: async (roleName, input) => {
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
...(input.thenMessage === undefined ? [] : ["--then-message", input.thenMessage])]
|
|
85
|
-
: ["message", input.action, roleName, input.body, "--request-id", input.requestId,
|
|
86
|
-
...(input.action === "steer" ? ["--expected-target", input.expectedTarget] : [])];
|
|
87
|
-
const result = webLocalMutation(store, tx => runGlobalRoleCommand(argv, tx, {
|
|
88
|
-
env: {}, yuiHome: options.yuiHome, jsonOutput: true
|
|
58
|
+
const result = webLocalMutation(store, tx => applyGlobalInputControl(roleName, input, tx, {
|
|
59
|
+
env: {}, yuiHome: options.yuiHome
|
|
89
60
|
}));
|
|
90
|
-
if (
|
|
61
|
+
if (result.kind === "output") {
|
|
91
62
|
if (input.action === "queue")
|
|
92
|
-
void options.runtime?.notifyMailboxChanged
|
|
63
|
+
void options.runtime?.notifyMailboxChanged({
|
|
93
64
|
kind: "global-role-runtime", roleName
|
|
94
65
|
});
|
|
95
|
-
return { action: input.action, ...
|
|
66
|
+
return { action: input.action, ...result.data };
|
|
96
67
|
}
|
|
97
68
|
if (result.kind !== "input-steer" && result.kind !== "input-interrupt") {
|
|
98
69
|
throw new WebRequestRejected("Global input cannot perform a Session lifecycle operation.");
|
|
@@ -138,7 +109,7 @@ export function createWebTaskSurface(store, options = {}, observations = [], hos
|
|
|
138
109
|
message: (taskId, body, intent, requestId) => {
|
|
139
110
|
// Preserve the submission's intent and frozen receipt separately from
|
|
140
111
|
// queue/steer controls; an omitted intent still means discuss.
|
|
141
|
-
const { message, task, queuedForLeader, feedback } = webLocalMutation(store, (tx) => sendTaskMessageCommand(tx, taskId, body,
|
|
112
|
+
const { message, task, queuedForLeader, feedback } = webLocalMutation(store, (tx) => sendTaskMessageCommand(tx, taskId, body, commandOptions, undefined, intent, requestId));
|
|
142
113
|
notify(taskId, queuedForLeader);
|
|
143
114
|
return { record: message, revision: message.createdAt,
|
|
144
115
|
disposition: queuedForLeader ? "queued" : "saved",
|
|
@@ -149,7 +120,7 @@ export function createWebTaskSurface(store, options = {}, observations = [], hos
|
|
|
149
120
|
/**
|
|
150
121
|
* The decision-3 three-action input-control path for the local-user Web
|
|
151
122
|
* surface. Its store-only phase is the identical shared application-layer
|
|
152
|
-
* primitive the CLI uses
|
|
123
|
+
* typed primitive the CLI uses, run inside `webLocalMutation`
|
|
153
124
|
* so a rejected input is provably not-submitted. A ready steer/interrupt
|
|
154
125
|
* returns a live intent; the single Agent Host edge then runs OUTSIDE the
|
|
155
126
|
* transaction exactly as cli.ts performs it — never a fallback, retarget, or
|
|
@@ -158,7 +129,7 @@ export function createWebTaskSurface(store, options = {}, observations = [], hos
|
|
|
158
129
|
* the durable Message is retained (decision-3 §1/§3/§5, message-5 gap F).
|
|
159
130
|
*/
|
|
160
131
|
control: async (taskId, input) => {
|
|
161
|
-
const execution = webLocalMutation(store, (tx) =>
|
|
132
|
+
const execution = webLocalMutation(store, (tx) => applyTaskInputControl(taskId, input, tx, commandOptions));
|
|
162
133
|
if (execution.kind === "output") {
|
|
163
134
|
// A queue receipt, or a steer/interrupt that was saved-but-not-delivered
|
|
164
135
|
// or an idempotent replay: fully durable, no live edge, exact disposition.
|