@zq-silk/yui 0.15.11 → 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 +11 -6
- package/ARCHITECTURE.zh-CN.md +8 -4
- package/README.md +13 -5
- package/dist/agentRun/agentRun.js +3 -0
- 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 +92 -48
- 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 +11 -3
- package/dist/cli/invocationAuthority.js +64 -0
- package/dist/cli/managedDiagnostics.js +2 -2
- package/dist/cli/parseRepeatable.js +35 -0
- package/dist/cli/updatePorts.js +26 -5
- package/dist/cli.js +1303 -1235
- 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/executionAuditCommands.js +10 -0
- package/dist/commands/globalRoleCommands.js +78 -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 +45 -25
- 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 +355 -732
- 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 +20 -58
- package/dist/commands/taskNextActionCommand.js +7 -6
- package/dist/commands/taskPublicationAdoptCommand.js +127 -0
- package/dist/commands/taskPublicationCommands.js +12 -3
- package/dist/commands/taskPublicationVerifyCommand.js +24 -40
- package/dist/commands/taskRemoteDeliveryCommand.js +5 -86
- 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 +5 -3
- package/dist/context/taskCatalog.js +185 -0
- package/dist/context/taskContext.js +152 -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 +20 -24
- package/dist/controller/fileSchedulerStoreAdapter.js +191 -225
- package/dist/controller/globalInputDelivery.js +13 -0
- package/dist/controller/jobClient.js +4 -4
- package/dist/controller/jobControl.js +60 -25
- package/dist/controller/jobSupervisor.js +4 -4
- package/dist/controller/providerRetryAdmission.js +100 -0
- package/dist/controller/providerRetryDelivery.js +218 -0
- package/dist/controller/runtime.js +85 -43
- 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 +79 -90
- 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 +5 -10
- package/dist/message/globalProviderRetry.js +15 -0
- 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/executionAudit.js +19 -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/gitWorkspace.js +371 -105
- package/dist/repository/projectMaintenanceLock.js +98 -82
- package/dist/repository/taskBaseFreshness.js +1 -1
- package/dist/repository/taskWorkspaceCoordinator.js +82 -144
- package/dist/repository/taskWorkspacePreparer.js +113 -47
- package/dist/repository/workspaceCleanupInspection.js +187 -0
- 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/agentError.js +5 -3
- package/dist/runtime/agentFailureContext.js +43 -0
- package/dist/runtime/agentHost.js +42 -25
- package/dist/runtime/builtinAgentErrorMappers.js +91 -0
- package/dist/runtime/codexAppServerRuntime.js +34 -3
- package/dist/runtime/codexInteractiveHost.js +2 -2
- package/dist/runtime/index.js +0 -1
- package/dist/runtime/managedCaller.js +21 -1
- package/dist/runtime/providerControl.js +5 -1
- package/dist/runtime/providerErrors.js +38 -0
- package/dist/runtime/providerRetry.js +198 -0
- package/dist/runtime/providerRuntimeIdentity.js +28 -2
- package/dist/runtime/runtimeObservation.js +0 -5
- package/dist/runtime/runtimeSessionCandidate.js +2 -3
- package/dist/runtime/sessionOwnerIdentity.js +1 -1
- package/dist/runtime/sessionTokenMetrics.js +15 -5
- package/dist/runtime/structuredProviderHost.js +10 -43
- package/dist/runtime/taskUsageMetrics.js +275 -0
- package/dist/runtime/tmuxAdapters.js +6 -8
- package/dist/scheduler/activeRoleRunDelivery.js +19 -7
- package/dist/scheduler/leaderWakeupProcessor.js +24 -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 +49 -36
- package/dist/scheduler/taskObservabilityProjection.js +6 -45
- 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 +173 -1
- package/dist/storage/sqliteStore.js +172 -161
- package/dist/storage/storageSchema.js +2 -14
- package/dist/storage/storageVersions.js +1 -1
- package/dist/storage/storeRpc.js +16 -7
- package/dist/storage/taskCatalog.js +123 -0
- package/dist/storage/taskStore.js +5 -7
- package/dist/storage/upgrade/upgradeOrchestrator.js +25 -37
- package/dist/task/archiveDiagnostics.js +1 -0
- package/dist/task/archivePreflight.js +124 -0
- package/dist/task/completionReadiness.js +5 -24
- package/dist/task/draftPlan.js +0 -53
- package/dist/task/nextAction.js +7 -13
- package/dist/task/publicationAdoption.js +56 -0
- package/dist/task/publicationReference.js +10 -0
- package/dist/task/remoteDelivery.js +31 -16
- 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 +92 -17
- package/dist/web/assets/client/components.js +55 -13
- package/dist/web/assets/client/i18n.js +72 -4
- package/dist/web/assets/client/taskSurface.js +6 -5
- package/dist/web/assets/client/view.js +35 -7
- package/dist/web/assets/shell.js +6 -0
- package/dist/web/assets/styles/layout.js +7 -0
- package/dist/web/webServer.js +12 -3
- package/dist/web/webSnapshot.js +14 -85
- package/dist/web/webTaskSurface.js +17 -46
- package/dist/workItem/workItem.js +1 -12
- package/dist/workspace/cleanupInspection.js +63 -0
- package/dist/workspace/workItemChangeSetManager.js +110 -50
- package/docs/agent-result-consumption.md +4 -0
- package/docs/agent-result-consumption.zh-CN.md +3 -0
- package/docs/agent-runtime-drivers.md +7 -0
- package/docs/agent-runtime-drivers.zh-CN.md +5 -0
- package/docs/architecture/README.md +2 -0
- package/docs/architecture/README.zh-CN.md +3 -1
- package/docs/architecture/capabilities-and-resources.md +42 -5
- package/docs/architecture/capabilities-and-resources.zh-CN.md +33 -3
- package/docs/managed-turn-and-session-runtime.md +121 -1
- package/docs/managed-turn-and-session-runtime.zh-CN.md +96 -2
- package/docs/observability/README.md +62 -0
- package/docs/observability/README.zh-CN.md +47 -0
- package/docs/plugin-sdk.md +4 -2
- package/docs/project-refresh.md +77 -0
- package/docs/project-refresh.zh-CN.md +59 -0
- package/docs/provider-retry.md +70 -0
- 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 +223 -17
- package/docs/task-delivery.zh-CN.md +171 -14
- package/docs/task-discovery.md +101 -0
- package/docs/task-discovery.zh-CN.md +85 -0
- package/docs/testing/verification-levels.md +161 -9
- package/docs/testing/verification-levels.zh-CN.md +116 -9
- package/i18n/README.zh-CN.md +13 -7
- package/package.json +1 -1
- package/skills/yui-leader/SKILL.md +5 -0
- package/skills/yui-leader/references/execution.md +3 -2
- package/skills/yui-leader/references/integration.md +9 -5
- package/skills/yui-leader/references/planning.md +9 -3
- package/skills/yui-operator/SKILL.md +24 -8
- package/skills/yui-reviewer/SKILL.md +4 -0
- package/skills/yui-runtime/SKILL.md +17 -2
- package/skills/yui-runtime/references/publication.md +26 -4
- package/skills/yui-runtime/references/recovery.md +71 -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 -376
- 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
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { isDeepStrictEqual } from "node:util";
|
|
2
2
|
import { createTaskMessage } from "../message/message.js";
|
|
3
|
+
import { providerRetryPending } from "../runtime/providerRetry.js";
|
|
3
4
|
import { captureRoleRunDispatch, enqueueWork, settleRoleRunDispatch } from "../coordination/workMailboxQueue.js";
|
|
4
5
|
import { finishReviewRound, updateReviewExecutionGroup } from "../review/reviewRound.js";
|
|
5
6
|
import { completeRun, failRun, runPurposeAdmitsTaskState } from "../agentRun/agentRun.js";
|
|
@@ -7,7 +8,6 @@ import { boundedRunFailureDiagnostic } from "../domain/agentResultTransport.js";
|
|
|
7
8
|
import { updateExecutionLane, updateWorkItemExecutionLane } from "../execution/workItemExecution.js";
|
|
8
9
|
import { managedProviderTurnId } from "../runtime/providerRuntimeIdentity.js";
|
|
9
10
|
import { latestRunDurableProgressAt } from "../scheduler/roleRunStall.js";
|
|
10
|
-
import { markTaskWakeConsumed } from "../scheduler/taskWake.js";
|
|
11
11
|
import { workItemExecutionGroupById, updateWorkItemExecutionGroup } from "../workItem/workItem.js";
|
|
12
12
|
/**
|
|
13
13
|
* Validate every immutable identity and frozen Project head needed before a
|
|
@@ -80,7 +80,7 @@ export function validateExactRunReviewRound(store, run, options = {}) {
|
|
|
80
80
|
}
|
|
81
81
|
}
|
|
82
82
|
const task = store.getTask(run.taskId);
|
|
83
|
-
const taskScope =
|
|
83
|
+
const taskScope = round.scope === "task";
|
|
84
84
|
const item = taskScope || round.workItemId === undefined
|
|
85
85
|
? null
|
|
86
86
|
: store.getWorkItem(run.taskId, round.workItemId);
|
|
@@ -394,7 +394,9 @@ export function terminalizeExactTaskRun(store, input, now) {
|
|
|
394
394
|
const message = createTaskMessage(store.nextMessageId(terminal.taskId), terminal.taskId, `Execution ${terminal.id} ${terminal.status}.`, "role-result", { type: "role", roleName: terminal.roleName }, now, { resultRef: { type: "agent-run-result", runId: terminal.id }, ...(terminal.workItemId === undefined
|
|
395
395
|
? {} : { workItemId: terminal.workItemId }) });
|
|
396
396
|
store.saveMessage(terminal.taskId, message);
|
|
397
|
-
|
|
397
|
+
if (!providerRetryPending(store.getTaskRoleSessionSet(terminal.taskId, terminal.roleName)?.providerBinding)) {
|
|
398
|
+
enqueueWork(store, { kind: "role", taskId: terminal.taskId, roleName: "leader" }, "role-result", now, [{ type: "message", taskId: terminal.taskId, id: message.id }], { source: "task-event", dedupeKey: `result:${terminal.taskId}/${terminal.id}` });
|
|
399
|
+
}
|
|
398
400
|
}
|
|
399
401
|
const dispatchIdentity = {
|
|
400
402
|
taskId: terminal.taskId,
|
|
@@ -407,13 +409,6 @@ export function terminalizeExactTaskRun(store, input, now) {
|
|
|
407
409
|
roleName: terminal.roleName
|
|
408
410
|
}), dispatchIdentity);
|
|
409
411
|
settleRoleRunDispatch(store, dispatchIdentity, dispatchToken);
|
|
410
|
-
if (terminal.roleName === "leader") {
|
|
411
|
-
const wake = store.listTaskWakes(input.taskId)
|
|
412
|
-
.find((candidate) => candidate.runId === terminal.id && candidate.status === "dispatched");
|
|
413
|
-
if (wake !== undefined) {
|
|
414
|
-
store.saveTaskWake(input.taskId, markTaskWakeConsumed(wake, now));
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
412
|
if (terminal.executionGroupId !== undefined && terminal.executionLaneId !== undefined) {
|
|
418
413
|
store.clearActiveExecutionLaneRun(input.taskId, terminal.executionGroupId, terminal.executionLaneId);
|
|
419
414
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { markGlobalRoleMessageNotDelivered } from "./message.js";
|
|
2
|
+
/** Settle the original queue entry when recovery ends with exact nondelivery.
|
|
3
|
+
* Cancellation of a submitted/unknown request is not negative Provider proof. */
|
|
4
|
+
export function settleGlobalRetryInput(store, roleName, binding, now) {
|
|
5
|
+
const retry = binding?.retry;
|
|
6
|
+
if (retry === undefined || retry.input.kind !== "message" || retry.input.roleName !== roleName
|
|
7
|
+
|| !["cancelled", "exhausted", "needs-attention"].includes(retry.status)
|
|
8
|
+
|| binding?.run == null || ["submitting", "accepted", "delivery-unknown"].includes(binding.run.status))
|
|
9
|
+
return;
|
|
10
|
+
const messageId = retry.input.messageId;
|
|
11
|
+
const message = store.listGlobalRoleMessages(roleName).find(m => m.id === messageId);
|
|
12
|
+
if (message === undefined || message.delivery !== undefined || message.notDelivered !== undefined)
|
|
13
|
+
return;
|
|
14
|
+
store.updateGlobalRoleMessage(markGlobalRoleMessageNotDelivered(message, `provider-retry-${retry.status}: ${retry.reason ?? retry.failureRef}`, now));
|
|
15
|
+
}
|
|
@@ -36,9 +36,8 @@ function resolveInputControl(sessions, kind, expectedTarget, roleName) {
|
|
|
36
36
|
detail: `Agent plan '${active.adapterId}' has no native interrupt; Yui will not `
|
|
37
37
|
+ "stop the owned process, kill, restart, or detach it." };
|
|
38
38
|
}
|
|
39
|
-
//
|
|
40
|
-
|
|
41
|
-
const binding = "providerBinding" in sessions ? sessions.providerBinding ?? null : null;
|
|
39
|
+
// Both scopes use the same explicit null for an uncontrolled Session.
|
|
40
|
+
const binding = sessions.providerBinding;
|
|
42
41
|
const turn = binding?.run ?? null;
|
|
43
42
|
if (binding === null || turn === null || !PRESENT_TURN_STATUSES.has(turn.status)) {
|
|
44
43
|
return { outcome: "no-active-turn", code: "NO_ACTIVE_TURN",
|
|
@@ -78,7 +77,7 @@ function resolveInputControl(sessions, kind, expectedTarget, roleName) {
|
|
|
78
77
|
...(turn.nativeTurnId === undefined ? {} : { nativeTurnId: turn.nativeTurnId }),
|
|
79
78
|
attemptId: turn.attemptId,
|
|
80
79
|
authority: { epoch: binding.authority.epoch, owner: "controller",
|
|
81
|
-
holderId: binding.authority.holderId
|
|
80
|
+
holderId: binding.authority.holderId }
|
|
82
81
|
} };
|
|
83
82
|
}
|
|
84
83
|
/**
|
package/dist/message/message.js
CHANGED
|
@@ -5,7 +5,7 @@ export const TASK_MESSAGE_KINDS = ["user", "operator", "role-result", "system"];
|
|
|
5
5
|
*
|
|
6
6
|
* - `record`: save only. No Leader wake, no planning, no activation.
|
|
7
7
|
* - `discuss`: save and route to planning (the default when a submission omits
|
|
8
|
-
* an intent
|
|
8
|
+
* an intent).
|
|
9
9
|
* - `develop`: save the requirement and its activation intent; an unplanned
|
|
10
10
|
* Draft records an activation request and queues only activation processing.
|
|
11
11
|
*
|
|
@@ -30,12 +30,9 @@ export function createTaskMessage(id, taskId, body, kind, author, now, context =
|
|
|
30
30
|
kind,
|
|
31
31
|
author: normalizeAuthor(author),
|
|
32
32
|
body: requireBody(body),
|
|
33
|
-
...(
|
|
34
|
-
? {}
|
|
35
|
-
: {
|
|
36
|
-
...(context.intent === undefined
|
|
37
|
-
? {}
|
|
38
|
-
: { intent: context.intent }),
|
|
33
|
+
...(kind === "user" || kind === "operator"
|
|
34
|
+
? { intent: context.intent ?? "discuss" }
|
|
35
|
+
: context.intent === undefined ? {} : { intent: context.intent }),
|
|
39
36
|
...(context.submissionKey === undefined
|
|
40
37
|
? {}
|
|
41
38
|
: { submissionKey: requireText(context.submissionKey, "Message submission key") }),
|
|
@@ -163,18 +160,22 @@ export function expandTaskMessageResult(message, getRun) {
|
|
|
163
160
|
}
|
|
164
161
|
return { ...message, result: run.result };
|
|
165
162
|
}
|
|
166
|
-
/**
|
|
163
|
+
/** Edit unkeyed Draft context. Once an input has a request identity, its body
|
|
164
|
+
* is the retry comparison and must stay frozen, including through a handoff. */
|
|
167
165
|
export function updateDraftTaskMessage(message, update) {
|
|
168
166
|
validateTaskMessage(message);
|
|
169
167
|
if (message.kind !== "user" && message.kind !== "operator") {
|
|
170
168
|
throw new Error(`Only user/operator Task Messages can be updated: ${message.id}.`);
|
|
171
169
|
}
|
|
170
|
+
const body = requireBody(update.body);
|
|
171
|
+
if (body === message.body)
|
|
172
|
+
return message;
|
|
173
|
+
if (message.submissionKey !== undefined || message.inputControl !== undefined || message.interruptThen !== undefined) {
|
|
174
|
+
throw new Error(`Message ${message.id} has a request identity; submit a new Message with a new request ID to change its content.`);
|
|
175
|
+
}
|
|
172
176
|
const updated = {
|
|
173
177
|
...message,
|
|
174
|
-
body
|
|
175
|
-
...(update.wakePolicy === undefined
|
|
176
|
-
? {}
|
|
177
|
-
: { wakePolicy: update.wakePolicy })
|
|
178
|
+
body
|
|
178
179
|
};
|
|
179
180
|
validateTaskMessage(updated);
|
|
180
181
|
return updated;
|
|
@@ -204,15 +205,11 @@ export function validateTaskMessage(message) {
|
|
|
204
205
|
requireText(message.body, "Message body");
|
|
205
206
|
validateKindAndAuthor(message.kind, message.author);
|
|
206
207
|
normalizeAuthor(message.author);
|
|
207
|
-
if (message
|
|
208
|
-
|
|
209
|
-
&& message.wakePolicy !== "none") {
|
|
210
|
-
throw new Error(`Message wakePolicy is invalid: ${String(message.wakePolicy)}.`);
|
|
208
|
+
if (Object.hasOwn(message, "wakePolicy")) {
|
|
209
|
+
throw new Error("Message contains a retired wake policy; use the storage upgrade boundary.");
|
|
211
210
|
}
|
|
212
|
-
if (message.
|
|
213
|
-
|
|
214
|
-
&& message.kind !== "operator") {
|
|
215
|
-
throw new Error("Message wakePolicy is only valid for user/operator messages.");
|
|
211
|
+
if ((message.kind === "user" || message.kind === "operator") && message.intent === undefined) {
|
|
212
|
+
throw new Error("User/operator Message intent is required.");
|
|
216
213
|
}
|
|
217
214
|
if (message.intent !== undefined
|
|
218
215
|
&& !TASK_SUBMISSION_INTENTS.includes(message.intent)) {
|
|
@@ -80,7 +80,7 @@ export function messageContinuationBlocker(store, message) {
|
|
|
80
80
|
const round = store.getReviewRound(message.taskId, target.reviewRoundId);
|
|
81
81
|
if (round === null)
|
|
82
82
|
return "review-round-missing";
|
|
83
|
-
if (
|
|
83
|
+
if (round.scope === "task") {
|
|
84
84
|
if (round.taskCandidate?.projects.some((project) => task.projectBindings.find((binding) => binding.projectId === project.projectId)?.currentCommit !== project.commit)) {
|
|
85
85
|
return "review-candidate-stale";
|
|
86
86
|
}
|
|
@@ -1,4 +1,15 @@
|
|
|
1
1
|
import { validateTaskRecordReference } from "../task/taskRecordReference.js";
|
|
2
|
+
import { requireTimestamp } from "../domain/validation.js";
|
|
3
|
+
export function validateMilestone(milestone) {
|
|
4
|
+
if (milestone.schemaVersion !== 2)
|
|
5
|
+
throw new Error("Milestone must use schemaVersion 2.");
|
|
6
|
+
validateTaskRecordReference({ taskId: milestone.taskId, localId: milestone.id }, "milestone");
|
|
7
|
+
requireText(milestone.title, "Milestone title");
|
|
8
|
+
requireText(milestone.summary, "Milestone summary");
|
|
9
|
+
requireTaskControlActor(milestone.createdBy);
|
|
10
|
+
requireTimestamp(milestone.createdAt, "Milestone createdAt");
|
|
11
|
+
return milestone;
|
|
12
|
+
}
|
|
2
13
|
export function createMilestone(id, taskId, title, summary, createdBy, now) {
|
|
3
14
|
return {
|
|
4
15
|
schemaVersion: 2,
|
|
@@ -15,6 +15,7 @@ import { join } from "node:path";
|
|
|
15
15
|
import { openCurrentTaskStore } from "../storage/currentTaskStore.js";
|
|
16
16
|
import { resolveTaskStoreBackendForHome } from "../storage/sqliteStore.js";
|
|
17
17
|
import { runtimeObservationFromTaskEvent } from "../runtime/runtimeObservation.js";
|
|
18
|
+
import { projectTaskUsageMetrics } from "../runtime/taskUsageMetrics.js";
|
|
18
19
|
import { classifyRunFailure, classifyIntegrationAttempt, classifyReviewRound, countFaultClasses } from "./faultClassification.js";
|
|
19
20
|
import { RUNTIME_LAUNCH_KINDS, RUNTIME_LAUNCH_PHASES } from "../runtime/launchDiagnostics.js";
|
|
20
21
|
import { UNSUPPORTED } from "./runtimeIdentity.js";
|
|
@@ -192,6 +193,7 @@ export function runExecutionAudit(home, options = {}, ports = createProductionEx
|
|
|
192
193
|
agentErrors: section,
|
|
193
194
|
workItems: section,
|
|
194
195
|
orchestration: section,
|
|
196
|
+
usage: section,
|
|
195
197
|
storage: section,
|
|
196
198
|
runtimeProtocol: section,
|
|
197
199
|
topLongRunning: section
|
|
@@ -658,6 +660,22 @@ export function runExecutionAudit(home, options = {}, ports = createProductionEx
|
|
|
658
660
|
return failed(error);
|
|
659
661
|
}
|
|
660
662
|
})();
|
|
663
|
+
const usage = (() => {
|
|
664
|
+
try {
|
|
665
|
+
return ok(taskIds.map((taskId) => {
|
|
666
|
+
const task = store.getTask(taskId);
|
|
667
|
+
if (task === null)
|
|
668
|
+
throw new Error(`Task not found: ${taskId}.`);
|
|
669
|
+
return projectTaskUsageMetrics({
|
|
670
|
+
task, events: store.listEvents(taskId), runs: store.listRuns(taskId),
|
|
671
|
+
now: new Date(generatedAt)
|
|
672
|
+
});
|
|
673
|
+
}));
|
|
674
|
+
}
|
|
675
|
+
catch (error) {
|
|
676
|
+
return failed(error);
|
|
677
|
+
}
|
|
678
|
+
})();
|
|
661
679
|
const storage = (() => {
|
|
662
680
|
try {
|
|
663
681
|
let stateJsonBytes = UNSUPPORTED;
|
|
@@ -796,6 +814,7 @@ export function runExecutionAudit(home, options = {}, ports = createProductionEx
|
|
|
796
814
|
agentErrors,
|
|
797
815
|
workItems,
|
|
798
816
|
orchestration,
|
|
817
|
+
usage,
|
|
799
818
|
storage,
|
|
800
819
|
runtimeProtocol,
|
|
801
820
|
topLongRunning
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { isDeepStrictEqual } from "node:util";
|
|
2
2
|
import { isCompletedReviewExecutionFromRuns } from "../review/reviewAcceptance.js";
|
|
3
3
|
import { projectFirstProgressAdvisory } from "../runtime/firstProgressAdvisory.js";
|
|
4
|
+
import { parseGateArtifactRef } from "../verification/gateArtifact.js";
|
|
4
5
|
/** One Task's orchestration cost and advisory projection, with no writes. */
|
|
5
6
|
export function projectTaskOrchestration(facts) {
|
|
6
7
|
const fullRounds = facts.reviewRounds.filter((round) => round.deltaRecheck === undefined);
|
|
@@ -63,7 +64,7 @@ export function projectTaskOrchestration(facts) {
|
|
|
63
64
|
attempts: facts.integrations.length,
|
|
64
65
|
failed: facts.integrations.filter(({ status }) => status === "failed").length,
|
|
65
66
|
repeatedIdentities,
|
|
66
|
-
evidenceReuses: facts.integrations.filter((attempt) => ((attempt.checks ?? []).some(
|
|
67
|
+
evidenceReuses: facts.integrations.filter((attempt) => ((attempt.checks ?? []).some(check => check.outcome === "passed" && parseGateArtifactRef(check.name) !== undefined))).length
|
|
67
68
|
},
|
|
68
69
|
providerSessionsBeforeFirstProgress: firstProgress.sessionsBeforeFirstProgress,
|
|
69
70
|
publicationToCompletionMs: publicationAt === undefined || facts.task.completedAt === undefined
|
|
@@ -75,6 +76,28 @@ export function projectTaskOrchestration(facts) {
|
|
|
75
76
|
}
|
|
76
77
|
function projectAdvisories(facts, fullRounds, repeatedIdentities, firstProgressAttention) {
|
|
77
78
|
const result = [];
|
|
79
|
+
const scopes = new Map();
|
|
80
|
+
for (const item of facts.workItems) {
|
|
81
|
+
if (item.status !== "open")
|
|
82
|
+
continue;
|
|
83
|
+
const key = JSON.stringify([
|
|
84
|
+
item.title.trim().toLowerCase(), item.objective.trim().toLowerCase(),
|
|
85
|
+
item.acceptance.map(value => value.trim().toLowerCase()).filter(Boolean).sort(),
|
|
86
|
+
[...item.writeProjectIds].sort()
|
|
87
|
+
]);
|
|
88
|
+
const refs = scopes.get(key) ?? [];
|
|
89
|
+
refs.push(`work-item:${item.id}`);
|
|
90
|
+
scopes.set(key, refs);
|
|
91
|
+
}
|
|
92
|
+
for (const refs of scopes.values()) {
|
|
93
|
+
if (refs.length < 2)
|
|
94
|
+
continue;
|
|
95
|
+
result.push({
|
|
96
|
+
code: "work-item-scope-overlap",
|
|
97
|
+
reason: "Open WorkItems have matching scope text. Inspect their original intent and decide whether they are independent; this advisory grants or denies no operation.",
|
|
98
|
+
refs
|
|
99
|
+
});
|
|
100
|
+
}
|
|
78
101
|
if (facts.task.type === "bugfix" && facts.workItems.length > 0) {
|
|
79
102
|
result.push({
|
|
80
103
|
code: "bugfix-workitem-overhead",
|
|
@@ -17,22 +17,6 @@ import { CURRENT_STORAGE_VERSION, MIN_SUPPORTED_STORAGE_VERSION } from "../stora
|
|
|
17
17
|
import { inspectStorageSchema } from "../storage/storageSchema.js";
|
|
18
18
|
import { resolveStoreWorkerEnabledForHome } from "../storage/storeRpc.js";
|
|
19
19
|
export const UNSUPPORTED = "unsupported";
|
|
20
|
-
/**
|
|
21
|
-
* Feature flag for the Issue 11 read-only status/audit presentation.
|
|
22
|
-
*
|
|
23
|
-
* Everything behind this flag is observe-only: identity fields, storage
|
|
24
|
-
* evidence, and metrics snapshots. The flag defaults ON because the fields
|
|
25
|
-
* change no behavior; set `YUI_STATUS_IDENTITY=0` (or `false`) to hide the new
|
|
26
|
-
* sections and the fail-closed health exit. `execution audit` is an explicit
|
|
27
|
-
* command and is not gated by this flag.
|
|
28
|
-
*/
|
|
29
|
-
export function resolveStatusIdentityEnabled(env = process.env) {
|
|
30
|
-
const value = env.YUI_STATUS_IDENTITY;
|
|
31
|
-
if (value === undefined)
|
|
32
|
-
return true;
|
|
33
|
-
const normalized = value.trim().toLowerCase();
|
|
34
|
-
return normalized !== "0" && normalized !== "false" && normalized !== "off";
|
|
35
|
-
}
|
|
36
20
|
export function evaluateStorageHealth(identity) {
|
|
37
21
|
const contradictions = identity.findings.filter(({ severity }) => severity === "contradiction");
|
|
38
22
|
const needsRepair = identity.findings.filter(({ severity }) => severity === "needs-repair");
|
|
@@ -242,7 +226,7 @@ export function collectStorageIdentity(home, ports = createProductionStorageIden
|
|
|
242
226
|
code: "ignored-legacy-storage-manifest",
|
|
243
227
|
severity: "warning",
|
|
244
228
|
message: "schema.json is legacy metadata and is not a storage authority.",
|
|
245
|
-
remediation: "
|
|
229
|
+
remediation: "This file is not used. Preserve or archive it separately after inspecting its contents."
|
|
246
230
|
});
|
|
247
231
|
}
|
|
248
232
|
if (dbPresent && dbHealth !== null && dbHealth !== "ok") {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { agentModelOptionsSummary } from "../executor/agentConfigurationCatalog.js";
|
|
1
2
|
import { renderAgentConfigurationResolutionNotice } from "../cli/agentConfigurationPicker.js";
|
|
2
3
|
import { agentHandshakeLine } from "./agentRunConfigurationPresentation.js";
|
|
3
4
|
import { defaultTableWidth, renderTable } from "./table.js";
|
|
@@ -35,6 +36,7 @@ export function renderAgentConfigurationCatalog(resolved) {
|
|
|
35
36
|
{ header: "Default effort", minWidth: 14, maxWidth: 18 },
|
|
36
37
|
{ header: "Service tiers", minWidth: 13, maxWidth: 24 }
|
|
37
38
|
], modelRows, defaultTableWidth()),
|
|
39
|
+
agentModelOptionsSummary(catalog),
|
|
38
40
|
...(fieldRows.length === 0
|
|
39
41
|
? []
|
|
40
42
|
: [renderTable("Other runtime configuration", [
|
|
@@ -1,17 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
export function resolveTimeZone(value) {
|
|
3
|
-
const timeZone = value ?? DEFAULT_TIME_ZONE;
|
|
4
|
-
if (typeof timeZone !== "string" || timeZone.trim() !== timeZone || timeZone.length === 0) {
|
|
5
|
-
throw new TypeError("timeZone must be a valid IANA timezone.");
|
|
6
|
-
}
|
|
7
|
-
try {
|
|
8
|
-
new Intl.DateTimeFormat("en", { timeZone }).format(0);
|
|
9
|
-
}
|
|
10
|
-
catch {
|
|
11
|
-
throw new TypeError("timeZone must be a valid IANA timezone.");
|
|
12
|
-
}
|
|
13
|
-
return timeZone;
|
|
14
|
-
}
|
|
1
|
+
import { resolveTimeZone } from "../config/timeZone.js";
|
|
15
2
|
/** Formats persisted UTC/RFC 3339 timestamps for human-facing CLI output. */
|
|
16
3
|
export function formatTimestamp(value, configuredTimeZone) {
|
|
17
4
|
const instant = new Date(value);
|
|
@@ -354,10 +354,18 @@ export function createPluginService(store, host, registry) {
|
|
|
354
354
|
// recorded on the plugin's own failure channel — not a Task product
|
|
355
355
|
// artifact (product artifacts are files in the Task's Git repository).
|
|
356
356
|
try {
|
|
357
|
-
store.
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
357
|
+
store.transaction(tx => {
|
|
358
|
+
const current = tx.getPluginIntent(taskId, pkg.manifest.id);
|
|
359
|
+
if (current === null)
|
|
360
|
+
return;
|
|
361
|
+
// A draining generation can fail after another replacement.
|
|
362
|
+
// Attach this diagnostic to the current selection, without
|
|
363
|
+
// changing it or treating its activation as a failure.
|
|
364
|
+
tx.recordPluginIntentFailure(taskId, pkg.manifest.id, current.revision, {
|
|
365
|
+
message: `Plugin instance cleanup failed for ${retired.ref.id}/${retired.ref.generation}: `
|
|
366
|
+
+ (error instanceof Error ? error.message : String(error)),
|
|
367
|
+
occurredAt: new Date().toISOString()
|
|
368
|
+
});
|
|
361
369
|
});
|
|
362
370
|
}
|
|
363
371
|
catch { /* best-effort telemetry; never mask the successful activation */ }
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
import { createHash, randomBytes } from "node:crypto";
|
|
2
2
|
import { chmod, mkdir, mkdtemp, readFile, rename, rm, writeFile } from "node:fs/promises";
|
|
3
|
-
import { dirname, join, resolve } from "node:path";
|
|
4
|
-
import { releaseWorkflowScratchRoot } from "../storage/homeLayout.js";
|
|
3
|
+
import { dirname, isAbsolute, join, resolve } from "node:path";
|
|
5
4
|
import { runUpdate } from "../cli/updateOrchestrator.js";
|
|
6
5
|
import { activatedControllerEntrypoint } from "../cli/updatePorts.js";
|
|
7
|
-
import { restartFileTaskController, stopFileTaskController } from "../controller/clientRuntime.js";
|
|
8
6
|
import { runProjectCommand } from "../commands/projectCommands.js";
|
|
9
|
-
import {
|
|
7
|
+
import { restartFileTaskController, stopFileTaskController } from "../controller/clientRuntime.js";
|
|
10
8
|
import { isConcreteVersion } from "../domain/validation.js";
|
|
9
|
+
import { createExecFileCommandRunner, createPinnedCommandRunner, resolveExecutable } from "../external/pinnedCommandRunner.js";
|
|
11
10
|
import { resolveProject } from "../repository/project.js";
|
|
12
|
-
import {
|
|
11
|
+
import { releaseWorkflowScratchRoot } from "../storage/homeLayout.js";
|
|
13
12
|
import { findL2ArtifactForCommit } from "../verification/gateArtifactStore.js";
|
|
14
|
-
import {
|
|
13
|
+
import { resolveVerificationGate } from "../verification/verificationGateService.js";
|
|
14
|
+
import { createFileReleaseIdempotencyStore } from "./releaseIdempotencyStore.js";
|
|
15
15
|
export { resolveExecutable } from "../external/pinnedCommandRunner.js";
|
|
16
16
|
const defaultRunCommand = createExecFileCommandRunner();
|
|
17
17
|
/**
|
|
@@ -41,8 +41,8 @@ export function createPinnedRunner(base) {
|
|
|
41
41
|
return createPinnedCommandRunner(base, PINNED_EXTERNAL_COMMANDS);
|
|
42
42
|
}
|
|
43
43
|
/**
|
|
44
|
-
* The
|
|
45
|
-
*
|
|
44
|
+
* The production adapter dispatches existing atomic operations. Deterministic
|
|
45
|
+
* tests inject command/idempotency ports and never contact release services.
|
|
46
46
|
*/
|
|
47
47
|
export function createReleaseWorkflowPorts(deps) {
|
|
48
48
|
const run = deps.runCommand !== undefined
|
|
@@ -1032,37 +1032,12 @@ export function createReleaseWorkflowPorts(deps) {
|
|
|
1032
1032
|
return { state: "unknown" };
|
|
1033
1033
|
}
|
|
1034
1034
|
case "controller-home": {
|
|
1035
|
-
//
|
|
1036
|
-
//
|
|
1037
|
-
// legacy identities are bare Home path strings and re-derive the
|
|
1038
|
-
// prefix from the caller's environment.
|
|
1039
|
-
let queriedHome = deps.home;
|
|
1040
|
-
let pinnedPrefix;
|
|
1035
|
+
// Recovery uses the exact persisted activation target, never the
|
|
1036
|
+
// resume caller's current installation.
|
|
1041
1037
|
const parsedIdentity = parseControllerHomeIdentity(externalIdentity.value);
|
|
1042
|
-
if (parsedIdentity
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
queriedHome = parsedIdentity.home;
|
|
1046
|
-
pinnedPrefix = parsedIdentity.globalPrefix;
|
|
1047
|
-
}
|
|
1048
|
-
else {
|
|
1049
|
-
queriedHome = externalIdentity.value;
|
|
1050
|
-
}
|
|
1051
|
-
// P1-3 (rr20): Query the GLOBAL install target — the same binary
|
|
1052
|
-
// `cli-update` activates via `npm install --global` — not the
|
|
1053
|
-
// current checkout module. A checkout that happens to be the target
|
|
1054
|
-
// version must not confirm a global install that is stale or never
|
|
1055
|
-
// activated. A pinned prefix is used as-is; only legacy/unpinned
|
|
1056
|
-
// identities re-derive via `npm prefix --global`, never PATH.
|
|
1057
|
-
let globalPrefix = pinnedPrefix;
|
|
1058
|
-
if (globalPrefix === undefined) {
|
|
1059
|
-
const prefixResult = await run("npm", ["prefix", "--global"]);
|
|
1060
|
-
if (prefixResult.code !== 0)
|
|
1061
|
-
return { state: "unknown" };
|
|
1062
|
-
globalPrefix = prefixResult.stdout.trim();
|
|
1063
|
-
if (globalPrefix.length === 0)
|
|
1064
|
-
return { state: "unknown" };
|
|
1065
|
-
}
|
|
1038
|
+
if (parsedIdentity === undefined)
|
|
1039
|
+
return { state: "unknown" };
|
|
1040
|
+
const { home: queriedHome, globalPrefix } = parsedIdentity;
|
|
1066
1041
|
const globalYui = join(globalPrefix, "bin", "yui");
|
|
1067
1042
|
const homeEnv = { YUI_HOME: queriedHome };
|
|
1068
1043
|
const checked = await run(process.execPath, [globalYui, "--json", "doctor"], undefined, homeEnv);
|
|
@@ -1092,13 +1067,6 @@ export function createReleaseWorkflowPorts(deps) {
|
|
|
1092
1067
|
}
|
|
1093
1068
|
};
|
|
1094
1069
|
}
|
|
1095
|
-
/**
|
|
1096
|
-
* Finds the open PR for a head ref. Prefers `gh pr view --head`; older gh
|
|
1097
|
-
* versions that lack the flag fall back to `gh pr list --head`. A non-zero
|
|
1098
|
-
* exit from both commands is a failed lookup (transport error), not an
|
|
1099
|
-
* authoritative "no PR found". Both paths request the head object id so the
|
|
1100
|
-
* caller can prove the PR names the frozen source commit before reuse.
|
|
1101
|
-
*/
|
|
1102
1070
|
/**
|
|
1103
1071
|
* Resolves the SHA a remote head branch currently points at, or undefined
|
|
1104
1072
|
* when the branch cannot be resolved or the answer is not a 40-hex commit.
|
|
@@ -1122,55 +1090,40 @@ async function resolveRemoteHead(run, repo, head) {
|
|
|
1122
1090
|
return /^[0-9a-f]{40}$/i.test(sha) ? sha : undefined;
|
|
1123
1091
|
}
|
|
1124
1092
|
async function findHeadPullRequest(run, head, repo) {
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
"--repo", repo,
|
|
1128
|
-
"--json", "number,headRefOid"
|
|
1129
|
-
]);
|
|
1130
|
-
if (viewed.code === 0) {
|
|
1131
|
-
const found = parsePrHeadEntry(viewed.stdout);
|
|
1132
|
-
if (found !== undefined) {
|
|
1133
|
-
return { status: "found", prNumber: found.prNumber, headSha: found.headSha };
|
|
1134
|
-
}
|
|
1135
|
-
}
|
|
1136
|
-
// A non-zero exit from `gh pr view` may mean "no PR for head" (exit 1 with
|
|
1137
|
-
// empty stdout) or a transport failure. The list fallback disambiguates.
|
|
1093
|
+
// One documented query. Two matches are enough to prove ambiguity; never
|
|
1094
|
+
// choose an arbitrary PR or mistake malformed output for authoritative absence.
|
|
1138
1095
|
const listed = await run("gh", [
|
|
1139
1096
|
"pr", "list", "--head", head,
|
|
1140
1097
|
"--repo", repo,
|
|
1141
1098
|
"--state", "open",
|
|
1099
|
+
"--limit", "2",
|
|
1142
1100
|
"--json", "number,headRefOid"
|
|
1143
1101
|
]);
|
|
1144
|
-
if (listed.code
|
|
1145
|
-
|
|
1146
|
-
if (found !== undefined) {
|
|
1147
|
-
return { status: "found", prNumber: found.prNumber, headSha: found.headSha };
|
|
1148
|
-
}
|
|
1149
|
-
// Authoritative empty result: no open PR for this head.
|
|
1150
|
-
return { status: "not-found" };
|
|
1102
|
+
if (listed.code !== 0) {
|
|
1103
|
+
return { status: "failed", error: listed.stderr.trim() || "gh PR lookup failed" };
|
|
1151
1104
|
}
|
|
1152
|
-
|
|
1153
|
-
return {
|
|
1154
|
-
status: "failed",
|
|
1155
|
-
error: listed.stderr.trim() || viewed.stderr.trim() || "gh PR lookup failed"
|
|
1156
|
-
};
|
|
1157
|
-
}
|
|
1158
|
-
/** Parses `gh pr view --json number,headRefOid` output. */
|
|
1159
|
-
function parsePrHeadEntry(stdout) {
|
|
1105
|
+
let entries;
|
|
1160
1106
|
try {
|
|
1161
|
-
|
|
1107
|
+
entries = JSON.parse(listed.stdout);
|
|
1162
1108
|
}
|
|
1163
1109
|
catch {
|
|
1164
|
-
return
|
|
1110
|
+
return { status: "failed", error: "gh PR list returned invalid JSON." };
|
|
1165
1111
|
}
|
|
1112
|
+
if (!Array.isArray(entries))
|
|
1113
|
+
return { status: "failed", error: "gh PR list did not return an array." };
|
|
1114
|
+
if (entries.length === 0)
|
|
1115
|
+
return { status: "not-found" };
|
|
1116
|
+
if (entries.length !== 1)
|
|
1117
|
+
return { status: "failed", error: "Multiple open PRs match this head; select the intended PR explicitly." };
|
|
1118
|
+
const found = prHeadFromJson(entries[0]);
|
|
1119
|
+
return found === undefined
|
|
1120
|
+
? { status: "failed", error: "gh PR list returned an invalid PR identity." }
|
|
1121
|
+
: { status: "found", prNumber: found.prNumber, headSha: found.headSha };
|
|
1166
1122
|
}
|
|
1167
|
-
/** Parses
|
|
1168
|
-
function
|
|
1123
|
+
/** Parses `gh pr view --json number,headRefOid` output. */
|
|
1124
|
+
function parsePrHeadEntry(stdout) {
|
|
1169
1125
|
try {
|
|
1170
|
-
|
|
1171
|
-
if (!Array.isArray(parsed) || parsed.length === 0)
|
|
1172
|
-
return undefined;
|
|
1173
|
-
return prHeadFromJson(parsed[0]);
|
|
1126
|
+
return prHeadFromJson(JSON.parse(stdout.trim()));
|
|
1174
1127
|
}
|
|
1175
1128
|
catch {
|
|
1176
1129
|
return undefined;
|
|
@@ -1180,9 +1133,9 @@ function prHeadFromJson(value) {
|
|
|
1180
1133
|
if (typeof value !== "object" || value === null)
|
|
1181
1134
|
return undefined;
|
|
1182
1135
|
const record = value;
|
|
1183
|
-
if (typeof record.number !== "number" || !Number.
|
|
1136
|
+
if (typeof record.number !== "number" || !Number.isSafeInteger(record.number) || record.number < 1)
|
|
1184
1137
|
return undefined;
|
|
1185
|
-
if (typeof record.headRefOid !== "string" || record.headRefOid
|
|
1138
|
+
if (typeof record.headRefOid !== "string" || !/^[0-9a-f]{40}$/iu.test(record.headRefOid))
|
|
1186
1139
|
return undefined;
|
|
1187
1140
|
return { prNumber: String(record.number), headSha: record.headRefOid };
|
|
1188
1141
|
}
|
|
@@ -1283,7 +1236,7 @@ async function queryIdentityFor(step, params, effect, source, home, run) {
|
|
|
1283
1236
|
* idempotency/identity persistence). Only kinds whose identity is fully
|
|
1284
1237
|
* determined by the immutable plan + source can be recovered this way.
|
|
1285
1238
|
*/
|
|
1286
|
-
function deriveIdentityFromPlan(step,
|
|
1239
|
+
function deriveIdentityFromPlan(step, _source) {
|
|
1287
1240
|
switch (step.kind) {
|
|
1288
1241
|
case "version-tag": {
|
|
1289
1242
|
const tag = step.params?.tag;
|
|
@@ -1410,11 +1363,10 @@ async function resolveGlobalPrefix(run) {
|
|
|
1410
1363
|
}
|
|
1411
1364
|
/**
|
|
1412
1365
|
* The controller-home query identity for a cli-update effect. The value is a
|
|
1413
|
-
* JSON envelope carrying the Home and
|
|
1366
|
+
* JSON envelope carrying the Home and the exact global
|
|
1414
1367
|
* prefix that was activated, so a resume query checks the same installation
|
|
1415
1368
|
* instead of re-deriving the target from the caller's npm/PATH environment
|
|
1416
|
-
* (P1-2, rr22).
|
|
1417
|
-
* accepts both shapes via {@link parseControllerHomeIdentity}.
|
|
1369
|
+
* (P1-2, rr22). Without that evidence no identity is issued.
|
|
1418
1370
|
*/
|
|
1419
1371
|
async function controllerHomeIdentity(run, home) {
|
|
1420
1372
|
const globalPrefix = await resolveGlobalPrefix(run);
|
|
@@ -1471,7 +1423,7 @@ async function readPersistedCliUpdateIdentity(home, idempotencyKey) {
|
|
|
1471
1423
|
return undefined;
|
|
1472
1424
|
}
|
|
1473
1425
|
const parsed = parseControllerHomeIdentity(raw);
|
|
1474
|
-
if (parsed === undefined
|
|
1426
|
+
if (parsed === undefined)
|
|
1475
1427
|
return undefined;
|
|
1476
1428
|
return { kind: "controller-home", value: JSON.stringify(parsed) };
|
|
1477
1429
|
}
|
|
@@ -1550,22 +1502,19 @@ async function readPersistedNpmPublishTarget(home, idempotencyKey) {
|
|
|
1550
1502
|
}
|
|
1551
1503
|
}
|
|
1552
1504
|
/**
|
|
1553
|
-
*
|
|
1554
|
-
*
|
|
1555
|
-
* bare Home path string (or any unparseable value) yields undefined so the
|
|
1556
|
-
* caller treats the raw value as the Home.
|
|
1505
|
+
* Read the exact activation target. Missing or malformed identity remains
|
|
1506
|
+
* unknown; no alternative installation can attest the original effect.
|
|
1557
1507
|
*/
|
|
1558
1508
|
function parseControllerHomeIdentity(value) {
|
|
1559
1509
|
try {
|
|
1560
1510
|
const parsed = JSON.parse(value);
|
|
1561
|
-
if (!isRecord(parsed) || typeof parsed.home !== "string" || parsed.home
|
|
1511
|
+
if (!isRecord(parsed) || typeof parsed.home !== "string" || !isAbsolute(parsed.home)
|
|
1512
|
+
|| typeof parsed.globalPrefix !== "string" || !isAbsolute(parsed.globalPrefix)) {
|
|
1562
1513
|
return undefined;
|
|
1563
1514
|
}
|
|
1564
1515
|
return {
|
|
1565
1516
|
home: parsed.home,
|
|
1566
|
-
|
|
1567
|
-
? { globalPrefix: parsed.globalPrefix }
|
|
1568
|
-
: {})
|
|
1517
|
+
globalPrefix: parsed.globalPrefix
|
|
1569
1518
|
};
|
|
1570
1519
|
}
|
|
1571
1520
|
catch {
|