@zq-silk/yui 0.16.1 → 1.0.0-alpha
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ARCHITECTURE.md +12 -0
- package/ARCHITECTURE.zh-CN.md +8 -0
- package/README.md +9 -4
- package/dist/agent/agent.js +4 -9
- package/dist/agent/executionComponents.js +4 -4
- package/dist/agent/managedRuntimeEnvironment.js +0 -4
- package/dist/agentRun/agentRun.js +13 -23
- package/dist/artifacts/managedGit.js +3 -56
- package/dist/brief/taskBrief.js +3 -3
- package/dist/cli/agentConfigurationPicker.js +24 -21
- package/dist/cli/commandCatalog.js +18 -10
- package/dist/cli/roleWizard.js +41 -30
- package/dist/cli/updateCommand.js +35 -13
- package/dist/cli/updateOrchestrator.js +73 -44
- package/dist/cli/updatePorts.js +76 -86
- package/dist/cli/upgradeCommand.js +4 -8
- package/dist/cli.js +39 -33
- package/dist/commands/controllerCommands.js +2 -2
- package/dist/commands/executionAuditCommands.js +2 -2
- package/dist/commands/projectCommands.js +1 -1
- package/dist/commands/releaseCommands.js +9 -38
- package/dist/commands/taskCommands.js +91 -81
- package/dist/commands/taskCompletionGate.js +0 -117
- package/dist/commands/taskRoleRuntimeStatus.js +2 -2
- package/dist/commands/taskUpstreamCommands.js +93 -54
- package/dist/context/runContextPack.js +30 -54
- package/dist/context/runInputContract.js +9 -0
- package/dist/context/sessionBootstrapManifest.js +11 -19
- package/dist/controller/agentHostObservation.js +4 -2
- package/dist/controller/agentRuntimeObserver.js +4 -4
- package/dist/controller/clientRuntime.js +19 -44
- package/dist/controller/controller.js +24 -10
- package/dist/controller/fileSchedulerStoreAdapter.js +91 -24
- package/dist/controller/globalInputDelivery.js +31 -8
- package/dist/controller/globalRuntimeAttention.js +34 -0
- package/dist/controller/jobSupervisor.js +3 -3
- package/dist/controller/operatorNotification.js +31 -0
- package/dist/controller/providerRetryAdmission.js +3 -1
- package/dist/controller/providerRetryDelivery.js +136 -119
- package/dist/controller/runtime.js +2 -28
- package/dist/controller/sessionOwnerReconciliation.js +42 -24
- package/dist/controller/structuredProviderObservation.js +28 -1
- package/dist/controller/updateReconciliation.js +72 -20
- package/dist/coordination/workMailbox.js +4 -4
- package/dist/core/controllerIdentity.js +25 -0
- package/dist/core/controllerProcessIdentity.js +2 -2
- package/dist/core/controllerServer.js +4 -2
- package/dist/core/protocol.js +1 -1
- package/dist/doctor/doctor.js +2 -1
- package/dist/domain/validation.js +6 -0
- package/dist/event/taskEvent.js +3 -3
- package/dist/execution/workItemExecution.js +2 -2
- package/dist/executor/agentAdapter.js +28 -96
- package/dist/executor/agentConfigurationCatalog.js +13 -68
- package/dist/executor/agentConfigurationFields.js +120 -0
- package/dist/executor/agentConfigurationProbe.js +55 -80
- package/dist/executor/agentExecutor.js +11 -8
- package/dist/executor/effectiveLaunch.js +3 -3
- package/dist/executor/executorRegistry.js +0 -9
- package/dist/executor/fileRoleLaunchPlanner.js +12 -11
- package/dist/grant/capabilityGrant.js +2 -2
- package/dist/input/inputRequest.js +4 -4
- package/dist/integration/changeSet.js +3 -3
- package/dist/integration/integrationAttempt.js +3 -3
- package/dist/integration/integrationSourceApplication.js +1 -1
- package/dist/interaction/operatorPresentation.js +2 -1
- package/dist/job/durableJob.js +1 -1
- package/dist/job/jobRunner.js +2 -2
- package/dist/message/message.js +8 -6
- package/dist/message/messageContinuation.js +8 -9
- package/dist/milestone/milestone.js +3 -3
- package/dist/observability/executionAudit.js +1 -10
- package/dist/observability/runtimeIdentity.js +0 -23
- package/dist/output/agentConfigurationPresentation.js +4 -2
- package/dist/profile/agentProfile.js +3 -3
- package/dist/release/releaseHandover.js +2 -2
- package/dist/release/releaseIdempotencyStore.js +0 -23
- package/dist/release/releaseWorkflowPorts.js +8 -4
- package/dist/release/runtimeRelease.js +9 -7
- package/dist/repository/gitWorkspace.js +66 -19
- package/dist/repository/project.js +3 -3
- package/dist/repository/taskWorkspacePreparer.js +75 -68
- package/dist/resources/liveReferences.js +1 -1
- package/dist/resources/resourceRegistry.js +90 -44
- package/dist/resources/sqliteResourceRegistry.js +5 -7
- package/dist/review/reviewRound.js +5 -5
- package/dist/role/role.js +7 -8
- package/dist/runtime/acpProtocol.js +2 -3
- package/dist/runtime/acpSession.js +21 -5
- package/dist/runtime/agentDriverObservation.js +1 -1
- package/dist/runtime/agentEndpoint.js +4 -1
- package/dist/runtime/agentHost.js +25 -65
- package/dist/runtime/agentHostCleanup.js +85 -0
- package/dist/runtime/agentHostProtocol.js +3 -3
- package/dist/runtime/builtinAgentDrivers.js +46 -119
- package/dist/runtime/builtinTranscriptObserver.js +10 -6
- package/dist/runtime/codexAppServerRuntime.js +55 -89
- package/dist/runtime/codexInteractiveHost.js +1 -1
- package/dist/runtime/jsonLineChannel.js +35 -7
- package/dist/runtime/launchBroker.js +1 -1
- package/dist/runtime/processExitObservation.js +1 -1
- package/dist/runtime/providerContinuationReconciliationService.js +86 -38
- package/dist/runtime/providerRetry.js +15 -0
- package/dist/runtime/providerRuntimeIdentity.js +3 -3
- package/dist/runtime/providerRuntimeReconciler.js +12 -2
- package/dist/runtime/runtimeCoherence.js +7 -3
- package/dist/runtime/runtimeObservation.js +3 -3
- package/dist/runtime/sessionOwnerIdentity.js +1 -1
- package/dist/runtime/structuredProviderHost.js +106 -81
- package/dist/runtime/taskRuntimeIsolation.js +3 -3
- package/dist/runtime/tmuxAdapters.js +3 -3
- package/dist/scheduler/activeRoleRunDelivery.js +5 -1
- package/dist/scheduler/operatorInputNotificationProcessor.js +7 -17
- package/dist/scheduler/taskWake.js +1 -1
- package/dist/storage/baselineSchema.js +606 -0
- package/dist/storage/homeLayout.js +5 -16
- package/dist/storage/recordValidation.js +16 -4
- package/dist/storage/sqliteSchema.js +106 -1741
- package/dist/storage/sqliteStore.js +16 -14
- package/dist/storage/storageSchema.js +8 -7
- package/dist/storage/storageVersions.js +23 -16
- package/dist/storage/taskStore.js +3 -29
- package/dist/storage/upgrade/upgradeOrchestrator.js +14 -112
- package/dist/task/nextAction.js +33 -23
- package/dist/task/task.js +17 -8
- package/dist/task/taskActivation.js +5 -4
- package/dist/telemetry/sqliteTelemetryStore.js +2 -2
- package/dist/verification/gateArtifact.js +5 -3
- package/dist/verification/verificationPlan.js +6 -7
- package/dist/web/assets/assetManifest.js +2 -0
- package/dist/web/assets/client/app.js +72 -3
- package/dist/web/assets/client/components.js +2 -1
- package/dist/web/assets/client/i18n.js +2 -0
- package/dist/web/assets/client/taskSummary.js +345 -0
- package/dist/web/assets/client/taskSurface.js +44 -26
- package/dist/web/assets/client/view.js +33 -0
- package/dist/web/assets/shell.js +1 -0
- package/dist/web/assets/styles/cards.js +21 -0
- package/dist/web/webServer.js +39 -5
- package/dist/web/webSessions.js +165 -0
- package/dist/web/webSnapshot.js +23 -0
- package/dist/web/webTaskSurface.js +23 -0
- package/dist/workItem/workItem.js +6 -6
- package/dist/workspace/cleanupInspection.js +1 -9
- package/dist/worktree/managedWorkspace.js +3 -3
- package/docs/managed-turn-and-session-runtime.md +50 -7
- package/docs/managed-turn-and-session-runtime.zh-CN.md +36 -5
- package/docs/observability/README.md +47 -0
- package/docs/observability/README.zh-CN.md +37 -0
- package/docs/project-refresh.md +9 -0
- package/docs/project-refresh.zh-CN.md +8 -0
- package/docs/provider-retry.md +35 -0
- package/docs/release-workflow.md +70 -214
- package/docs/release-workflow.zh-CN.md +53 -157
- package/docs/roles-and-configuration.md +30 -0
- package/docs/roles-and-configuration.zh-CN.md +20 -0
- package/docs/sqlite-control-plane-design.md +48 -289
- package/docs/sqlite-control-plane-design.zh-CN.md +37 -53
- package/docs/storage-baseline.md +132 -0
- package/docs/storage-baseline.zh-CN.md +106 -0
- package/docs/task-delivery.md +110 -2
- package/docs/task-delivery.zh-CN.md +82 -2
- package/docs/task-discovery.md +9 -0
- package/docs/task-discovery.zh-CN.md +6 -0
- package/docs/testing/verification-levels.md +42 -164
- package/docs/testing/verification-levels.zh-CN.md +29 -115
- package/i18n/README.zh-CN.md +7 -4
- package/package.json +1 -1
- package/skills/yui-leader/SKILL.md +11 -0
- package/skills/yui-leader/references/execution.md +22 -0
- package/skills/yui-leader/references/planning.md +6 -2
- package/skills/yui-operator/SKILL.md +45 -13
- package/skills/yui-operator/references/task-delivery.md +99 -0
- package/skills/yui-runtime/SKILL.md +5 -3
- package/skills/yui-runtime/references/publication.md +56 -5
- package/skills/yui-runtime/references/recovery.md +12 -2
- package/dist/runtime/agentHostCompatibility.js +0 -127
- package/dist/storage/migrations/agentFailureContext.js +0 -22
- package/dist/storage/migrations/agentRunContract.js +0 -159
- package/dist/storage/migrations/artifactsToGit.js +0 -338
- package/dist/storage/migrations/collapseWorktreeLayout.js +0 -963
- package/dist/storage/migrations/currentInputContract.js +0 -86
- package/dist/storage/migrations/currentRuntimeContract.js +0 -228
- package/dist/storage/migrations/historicalVerificationPlan.js +0 -35
- package/dist/storage/migrations/integrationContinuation.js +0 -105
- package/dist/storage/migrations/narrowAgentFailureContext.js +0 -65
- package/dist/storage/migrations/notificationOnlyWakes.js +0 -74
- package/dist/storage/migrations/removeRuntimeGeneration.js +0 -207
- package/dist/storage/migrations/submitIntent.js +0 -126
- package/dist/storage/migrations/unifyHomeLayout.js +0 -925
- package/dist/storage/migrations/verificationPlanV1.js +0 -162
- package/dist/storage/migrations/verificationPolicy.js +0 -74
- package/dist/storage/migrations/workItemHistory.js +0 -46
package/dist/message/message.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { validateTaskRecordReference } from "../task/taskRecordReference.js";
|
|
2
|
+
import { requireKnownFields } from "../domain/validation.js";
|
|
2
3
|
export const TASK_MESSAGE_KINDS = ["user", "operator", "role-result", "system"];
|
|
3
4
|
/**
|
|
4
5
|
* How a user/operator submission asks its Task to react (task-32 Requirement A).
|
|
@@ -24,7 +25,7 @@ export const TASK_MESSAGE_INPUT_CONTROL_OUTCOMES = ["pending", "accepted", "reje
|
|
|
24
25
|
export function createTaskMessage(id, taskId, body, kind, author, now, context = {}) {
|
|
25
26
|
validateKindAndAuthor(kind, author);
|
|
26
27
|
const message = {
|
|
27
|
-
schemaVersion:
|
|
28
|
+
schemaVersion: 1,
|
|
28
29
|
id: requireSafeIdentity(id, "Message id"),
|
|
29
30
|
taskId: requireSafeIdentity(taskId, "Message Task id"),
|
|
30
31
|
kind,
|
|
@@ -199,15 +200,16 @@ export function withSubmissionReceipt(message, receipt) {
|
|
|
199
200
|
return updated;
|
|
200
201
|
}
|
|
201
202
|
export function validateTaskMessage(message) {
|
|
202
|
-
if (message.schemaVersion !==
|
|
203
|
-
throw new Error("Task Message must use schemaVersion
|
|
203
|
+
if (message.schemaVersion !== 1)
|
|
204
|
+
throw new Error("Task Message must use schemaVersion 1.");
|
|
205
|
+
requireKnownFields(message, [
|
|
206
|
+
"schemaVersion", "id", "taskId", "kind", "author", "body", "intent", "submissionKey", "submissionReceipt",
|
|
207
|
+
"inputControl", "interruptThen", "control", "runId", "resultRef", "workItemId", "recipient", "continuation", "handovers", "createdAt"
|
|
208
|
+
], "Task Message");
|
|
204
209
|
validateTaskRecordReference({ taskId: message.taskId, localId: message.id }, "message");
|
|
205
210
|
requireText(message.body, "Message body");
|
|
206
211
|
validateKindAndAuthor(message.kind, message.author);
|
|
207
212
|
normalizeAuthor(message.author);
|
|
208
|
-
if (Object.hasOwn(message, "wakePolicy")) {
|
|
209
|
-
throw new Error("Message contains a retired wake policy; use the storage upgrade boundary.");
|
|
210
|
-
}
|
|
211
213
|
if ((message.kind === "user" || message.kind === "operator") && message.intent === undefined) {
|
|
212
214
|
throw new Error("User/operator Message intent is required.");
|
|
213
215
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { isDeepStrictEqual } from "node:util";
|
|
2
|
-
import { createRun
|
|
2
|
+
import { createRun } from "../agentRun/agentRun.js";
|
|
3
3
|
import { createRunInput } from "../context/runInputContract.js";
|
|
4
4
|
import { contextContentDigest, contextSnapshotRef, createContextSnapshot } from "../context/contextSnapshot.js";
|
|
5
|
+
import { readRunContextSnapshot } from "../context/runContextPack.js";
|
|
5
6
|
import { roleSessionMayContinue } from "../executor/effectiveLaunch.js";
|
|
6
7
|
import { enqueueRoleRunDispatch } from "../coordination/workMailboxQueue.js";
|
|
7
8
|
import { createTaskEvent } from "../event/taskEvent.js";
|
|
@@ -265,12 +266,9 @@ export function prepareMessageContinuations(store, taskId, now, roleName) {
|
|
|
265
266
|
continue;
|
|
266
267
|
const previous = store.listRuns(taskId).filter((run) => run.roleName === owner.roleName
|
|
267
268
|
&& run.workItemId === owner.workItemId && run.reviewRoundId === owner.reviewRoundId).at(-1);
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
markNotDelivered(store, message, "assignment-context-unavailable");
|
|
272
|
-
continue;
|
|
273
|
-
}
|
|
269
|
+
// Missing or drifted evidence is diagnosed by the caller; never reconstruct
|
|
270
|
+
// an Assignment from today's Task facts.
|
|
271
|
+
const baseline = readRunContextSnapshot(store, previous);
|
|
274
272
|
const runId = store.nextRunId(taskId);
|
|
275
273
|
const round = owner.reviewRoundId === undefined ? null : store.getReviewRound(taskId, owner.reviewRoundId);
|
|
276
274
|
const continuingRound = round === null ? null : (() => {
|
|
@@ -300,9 +298,10 @@ export function prepareMessageContinuations(store, taskId, now, roleName) {
|
|
|
300
298
|
resources, refs: resources.map(({ ref }) => ref)
|
|
301
299
|
});
|
|
302
300
|
store.saveContextSnapshot(snapshot);
|
|
303
|
-
const run =
|
|
301
|
+
const run = createRun(runId, taskId, recipient.roleName, "resume", createRunInput({ source: { type: "yui", channel: "message-continuation" },
|
|
304
302
|
directive: `Continue the same Assignment in its existing workspace. Read Messages ${batch.map((m) => m.id).join(", ")} from this exact Context. Message receipt is not implementation or acceptance.`,
|
|
305
|
-
|
|
303
|
+
contextSnapshotRef: contextSnapshotRef(snapshot),
|
|
304
|
+
deltaRefIds: batch.map((entry) => entry.id) }), now, copyAssignment(owner));
|
|
306
305
|
store.saveRun(run);
|
|
307
306
|
store.saveActiveRun(run);
|
|
308
307
|
if (continuingRound !== null)
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { validateTaskRecordReference } from "../task/taskRecordReference.js";
|
|
2
2
|
import { requireTimestamp } from "../domain/validation.js";
|
|
3
3
|
export function validateMilestone(milestone) {
|
|
4
|
-
if (milestone.schemaVersion !==
|
|
5
|
-
throw new Error("Milestone must use schemaVersion
|
|
4
|
+
if (milestone.schemaVersion !== 1)
|
|
5
|
+
throw new Error("Milestone must use schemaVersion 1.");
|
|
6
6
|
validateTaskRecordReference({ taskId: milestone.taskId, localId: milestone.id }, "milestone");
|
|
7
7
|
requireText(milestone.title, "Milestone title");
|
|
8
8
|
requireText(milestone.summary, "Milestone summary");
|
|
@@ -12,7 +12,7 @@ export function validateMilestone(milestone) {
|
|
|
12
12
|
}
|
|
13
13
|
export function createMilestone(id, taskId, title, summary, createdBy, now) {
|
|
14
14
|
return {
|
|
15
|
-
schemaVersion:
|
|
15
|
+
schemaVersion: 1,
|
|
16
16
|
id: validateTaskRecordReference({ taskId, localId: id }, "milestone").localId,
|
|
17
17
|
taskId: requireSafeIdentity(taskId, "Task id"),
|
|
18
18
|
title: requireText(title, "Milestone title"),
|
|
@@ -678,13 +678,6 @@ export function runExecutionAudit(home, options = {}, ports = createProductionEx
|
|
|
678
678
|
})();
|
|
679
679
|
const storage = (() => {
|
|
680
680
|
try {
|
|
681
|
-
let stateJsonBytes = UNSUPPORTED;
|
|
682
|
-
try {
|
|
683
|
-
stateJsonBytes = statSync(join(home, "state.json")).size;
|
|
684
|
-
}
|
|
685
|
-
catch {
|
|
686
|
-
stateJsonBytes = UNSUPPORTED;
|
|
687
|
-
}
|
|
688
681
|
let databaseBytes = UNSUPPORTED;
|
|
689
682
|
try {
|
|
690
683
|
databaseBytes = statSync(join(home, "yui.db")).size;
|
|
@@ -693,15 +686,13 @@ export function runExecutionAudit(home, options = {}, ports = createProductionEx
|
|
|
693
686
|
databaseBytes = UNSUPPORTED;
|
|
694
687
|
}
|
|
695
688
|
return ok({
|
|
696
|
-
stateJsonBytes,
|
|
697
689
|
databaseBytes,
|
|
698
690
|
// The audit aggregates history; live db integrity is the status
|
|
699
691
|
// command's job (it runs PRAGMA quick_check). Report presence/size
|
|
700
692
|
// and the authoritative backend, not a duplicate health probe.
|
|
701
693
|
databaseHealth: UNSUPPORTED,
|
|
702
694
|
backend: resolveTaskStoreBackendForHome(home),
|
|
703
|
-
runtimeDirBytes: ports.directorySize(join(home, "runtime")) ?? UNSUPPORTED
|
|
704
|
-
deploymentsBytes: ports.directorySize(join(home, "runtime", "deployments")) ?? UNSUPPORTED
|
|
695
|
+
runtimeDirBytes: ports.directorySize(join(home, "runtime")) ?? UNSUPPORTED
|
|
705
696
|
});
|
|
706
697
|
}
|
|
707
698
|
catch (error) {
|
|
@@ -165,9 +165,6 @@ export function collectStorageIdentity(home, ports = createProductionStorageIden
|
|
|
165
165
|
|| storage.status === "unsupported"
|
|
166
166
|
? storage.currentVersion
|
|
167
167
|
: UNSUPPORTED;
|
|
168
|
-
const statePath = join(home, "state.json");
|
|
169
|
-
const statePresent = ports.exists(statePath);
|
|
170
|
-
const stateBytes = ports.fileSize(statePath);
|
|
171
168
|
const dbPath = join(home, "yui.db");
|
|
172
169
|
const dbPresent = ports.exists(dbPath);
|
|
173
170
|
const dbBytes = ports.fileSize(dbPath);
|
|
@@ -221,14 +218,6 @@ export function collectStorageIdentity(home, ports = createProductionStorageIden
|
|
|
221
218
|
remediation: "Preserve this Home for diagnosis and restore a known-good backup."
|
|
222
219
|
});
|
|
223
220
|
}
|
|
224
|
-
if (ports.exists(join(home, "schema.json"))) {
|
|
225
|
-
findings.push({
|
|
226
|
-
code: "ignored-legacy-storage-manifest",
|
|
227
|
-
severity: "warning",
|
|
228
|
-
message: "schema.json is legacy metadata and is not a storage authority.",
|
|
229
|
-
remediation: "This file is not used. Preserve or archive it separately after inspecting its contents."
|
|
230
|
-
});
|
|
231
|
-
}
|
|
232
221
|
if (dbPresent && dbHealth !== null && dbHealth !== "ok") {
|
|
233
222
|
findings.push({
|
|
234
223
|
code: "database-unhealthy",
|
|
@@ -237,14 +226,6 @@ export function collectStorageIdentity(home, ports = createProductionStorageIden
|
|
|
237
226
|
remediation: "Preserve this Home for diagnosis and initialize a new Home."
|
|
238
227
|
});
|
|
239
228
|
}
|
|
240
|
-
if (statePresent) {
|
|
241
|
-
findings.push({
|
|
242
|
-
code: "ignored-historical-store",
|
|
243
|
-
severity: "warning",
|
|
244
|
-
message: "state.json is present but is not read or written by the current store.",
|
|
245
|
-
remediation: "Keep it only as read-only historical evidence or archive it outside the active Home."
|
|
246
|
-
});
|
|
247
|
-
}
|
|
248
229
|
const rawWorkerFlag = ports.env.YUI_STORE_WORKER;
|
|
249
230
|
if (rawWorkerFlag !== undefined && !["0", "1", "false", "true"].includes(rawWorkerFlag.toLowerCase())) {
|
|
250
231
|
findings.push({
|
|
@@ -261,10 +242,6 @@ export function collectStorageIdentity(home, ports = createProductionStorageIden
|
|
|
261
242
|
minimumStorageVersion: MIN_SUPPORTED_STORAGE_VERSION,
|
|
262
243
|
configuredBackend,
|
|
263
244
|
workerEnabled,
|
|
264
|
-
physicalStateJson: {
|
|
265
|
-
present: statePresent,
|
|
266
|
-
bytes: stateBytes ?? UNSUPPORTED
|
|
267
|
-
},
|
|
268
245
|
physicalDatabase: {
|
|
269
246
|
present: dbPresent,
|
|
270
247
|
bytes: dbBytes ?? UNSUPPORTED,
|
|
@@ -23,7 +23,9 @@ export function renderAgentConfigurationCatalog(resolved) {
|
|
|
23
23
|
// query rather than a deliberate boundary, so it is shown either way.
|
|
24
24
|
field.available === false
|
|
25
25
|
? `unavailable${field.reason === undefined ? "" : `: ${field.reason}`}`
|
|
26
|
-
: field.
|
|
26
|
+
: field.available === true
|
|
27
|
+
? `available${field.reason === undefined ? "" : `: ${field.reason}`}`
|
|
28
|
+
: field.reason ?? "Native support not confirmed",
|
|
27
29
|
field.allowCustom ? "yes" : "no"
|
|
28
30
|
]);
|
|
29
31
|
const sections = [
|
|
@@ -39,7 +41,7 @@ export function renderAgentConfigurationCatalog(resolved) {
|
|
|
39
41
|
agentModelOptionsSummary(catalog),
|
|
40
42
|
...(fieldRows.length === 0
|
|
41
43
|
? []
|
|
42
|
-
: [renderTable("
|
|
44
|
+
: [renderTable("Configuration fields (native observations and static adapter contracts)", [
|
|
43
45
|
{ header: "Field", minWidth: 12, maxWidth: 30 },
|
|
44
46
|
{ header: "Values", minWidth: 18, maxWidth: 58 },
|
|
45
47
|
{ header: "Status", minWidth: 10, maxWidth: 38 },
|
|
@@ -8,7 +8,7 @@ export const BUILTIN_PROFILE_IDS = Object.freeze([
|
|
|
8
8
|
export function createAgentProfile(input, now) {
|
|
9
9
|
const timestamp = now.toISOString();
|
|
10
10
|
return validateAgentProfile({
|
|
11
|
-
schemaVersion:
|
|
11
|
+
schemaVersion: 1,
|
|
12
12
|
id: requireIdentity(input.id, "Agent Profile id"),
|
|
13
13
|
revision: 1,
|
|
14
14
|
...normalizeInput(input),
|
|
@@ -31,8 +31,8 @@ export function updateAgentProfile(profile, patch, now) {
|
|
|
31
31
|
return validateAgentProfile(candidate);
|
|
32
32
|
}
|
|
33
33
|
export function validateAgentProfile(profile) {
|
|
34
|
-
if (profile.schemaVersion !==
|
|
35
|
-
throw new Error("AgentProfile must use schemaVersion
|
|
34
|
+
if (profile.schemaVersion !== 1) {
|
|
35
|
+
throw new Error("AgentProfile must use schemaVersion 1.");
|
|
36
36
|
}
|
|
37
37
|
requireIdentity(profile.id, "Agent Profile id");
|
|
38
38
|
requirePositiveInteger(profile.revision, "Agent Profile revision");
|
|
@@ -131,14 +131,14 @@ async function activateLocked(ports, locked) {
|
|
|
131
131
|
catch (rollbackError) {
|
|
132
132
|
return {
|
|
133
133
|
outcome: "aborted", phase: "preflight",
|
|
134
|
-
message: `
|
|
134
|
+
message: `Storage preflight failed: ${messageOf(error)}; rollback failed: ${messageOf(rollbackError)}`,
|
|
135
135
|
action: "No candidate was started and the release pointer is unchanged. Preserve the fence and inspect the old Controller.",
|
|
136
136
|
recoverable: true
|
|
137
137
|
};
|
|
138
138
|
}
|
|
139
139
|
return {
|
|
140
140
|
outcome: "aborted", phase: "preflight",
|
|
141
|
-
message: `Fenced
|
|
141
|
+
message: `Fenced storage preflight failed: ${messageOf(error)}`,
|
|
142
142
|
action: "The old Controller is serving again; the active release and Home are unchanged.",
|
|
143
143
|
recoverable: true
|
|
144
144
|
};
|
|
@@ -38,41 +38,19 @@ import { writeTextFileAtomically } from "../storage/durableFile.js";
|
|
|
38
38
|
* an unpersisted success into an ambiguous/fail-closed outcome; reporting
|
|
39
39
|
* success for an effect whose dedup record did not land would let a crash
|
|
40
40
|
* re-run the effect.
|
|
41
|
-
*
|
|
42
|
-
* Unsupported layouts
|
|
43
|
-
* -------------------
|
|
44
|
-
* The per-key `schemaVersion: 1` layout is the only supported format. A
|
|
45
|
-
* legacy whole-map file (`release-idempotency.json`) from an unsupported
|
|
46
|
-
* older layout is never auto-migrated: it fails closed with a bounded
|
|
47
|
-
* diagnostic so the operator can inspect and clean it up deliberately.
|
|
48
41
|
*/
|
|
49
42
|
export const RELEASE_IDEMPOTENCY_SCHEMA_VERSION = 1;
|
|
50
43
|
const DIRECTORY_NAME = "release-idempotency";
|
|
51
|
-
const LEGACY_FILE_NAME = "release-idempotency.json";
|
|
52
44
|
export function createFileReleaseIdempotencyStore(home, now = () => new Date()) {
|
|
53
45
|
const directory = join(home, DIRECTORY_NAME);
|
|
54
|
-
const legacyPath = join(home, LEGACY_FILE_NAME);
|
|
55
46
|
function recordPath(key) {
|
|
56
47
|
// encodeURIComponent is injective and leaves only Linux-filename-safe
|
|
57
48
|
// characters unescaped, so distinct keys never collide on disk.
|
|
58
49
|
return join(directory, `${encodeURIComponent(key)}.json`);
|
|
59
50
|
}
|
|
60
|
-
/**
|
|
61
|
-
* Fail closed when an unsupported legacy whole-map file is present.
|
|
62
|
-
* The per-key layout is the only supported format; a whole-map file
|
|
63
|
-
* from an unsupported older layout must not be silently trusted.
|
|
64
|
-
*/
|
|
65
|
-
function rejectLegacyLayout() {
|
|
66
|
-
if (existsSync(legacyPath)) {
|
|
67
|
-
throw new Error(`unsupported legacy release idempotency layout: ${legacyPath} ` +
|
|
68
|
-
`(whole-map file). Remove it after inspecting its contents; ` +
|
|
69
|
-
`the per-key schemaVersion 1 layout is the only supported format.`);
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
51
|
return {
|
|
73
52
|
async load(key) {
|
|
74
53
|
try {
|
|
75
|
-
rejectLegacyLayout();
|
|
76
54
|
const path = recordPath(key);
|
|
77
55
|
if (!existsSync(path))
|
|
78
56
|
return undefined;
|
|
@@ -88,7 +66,6 @@ export function createFileReleaseIdempotencyStore(home, now = () => new Date())
|
|
|
88
66
|
throw new Error(`release idempotency store only records succeeded effects: ${effect.outcome}`);
|
|
89
67
|
}
|
|
90
68
|
try {
|
|
91
|
-
rejectLegacyLayout();
|
|
92
69
|
const record = Object.freeze({
|
|
93
70
|
schemaVersion: RELEASE_IDEMPOTENCY_SCHEMA_VERSION,
|
|
94
71
|
key,
|
|
@@ -2,6 +2,7 @@ import { createHash, randomBytes } from "node:crypto";
|
|
|
2
2
|
import { chmod, mkdir, mkdtemp, readFile, rename, rm, writeFile } from "node:fs/promises";
|
|
3
3
|
import { dirname, isAbsolute, join, resolve } from "node:path";
|
|
4
4
|
import { runUpdate } from "../cli/updateOrchestrator.js";
|
|
5
|
+
import { renderUpdateResult } from "../cli/updateCommand.js";
|
|
5
6
|
import { activatedControllerEntrypoint } from "../cli/updatePorts.js";
|
|
6
7
|
import { runProjectCommand } from "../commands/projectCommands.js";
|
|
7
8
|
import { restartFileTaskController, stopFileTaskController } from "../controller/clientRuntime.js";
|
|
@@ -116,11 +117,14 @@ export function createReleaseWorkflowPorts(deps) {
|
|
|
116
117
|
// Best-effort persistence; the effect may still succeed.
|
|
117
118
|
}
|
|
118
119
|
const result = runUpdate(inspectedPorts, { home: deps.home });
|
|
120
|
+
// Preserve cleanup/restore receipts in the existing step log, even
|
|
121
|
+
// when the binary result is success or a later phase is blocked.
|
|
122
|
+
const updateLog = renderUpdateResult(result);
|
|
119
123
|
if (result.outcome === "updated" || result.outcome === "already-current") {
|
|
120
124
|
return {
|
|
121
125
|
outcome: "succeeded",
|
|
122
126
|
externalId: result.version,
|
|
123
|
-
logs: [
|
|
127
|
+
logs: [updateLog]
|
|
124
128
|
};
|
|
125
129
|
}
|
|
126
130
|
if (result.outcome === "aborted" && !result.recoverable) {
|
|
@@ -143,7 +147,7 @@ export function createReleaseWorkflowPorts(deps) {
|
|
|
143
147
|
if (result.controllerOwnershipUnknown === true) {
|
|
144
148
|
return {
|
|
145
149
|
outcome: "timeout",
|
|
146
|
-
logs: [
|
|
150
|
+
logs: [updateLog]
|
|
147
151
|
};
|
|
148
152
|
}
|
|
149
153
|
// P1-2 (rr22): persist the EXACT activation target (the resolved
|
|
@@ -154,13 +158,13 @@ export function createReleaseWorkflowPorts(deps) {
|
|
|
154
158
|
return {
|
|
155
159
|
outcome: "timeout",
|
|
156
160
|
...(identity === undefined ? {} : { externalIdentity: identity }),
|
|
157
|
-
logs: [
|
|
161
|
+
logs: [updateLog]
|
|
158
162
|
};
|
|
159
163
|
}
|
|
160
164
|
return {
|
|
161
165
|
outcome: "failed",
|
|
162
166
|
error: `cli-update aborted at phase ${result.phase}: ${result.message}`,
|
|
163
|
-
logs: [
|
|
167
|
+
logs: [updateLog]
|
|
164
168
|
};
|
|
165
169
|
}
|
|
166
170
|
case "controller-replace": {
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
* deterministic and recoverable.
|
|
16
16
|
*/
|
|
17
17
|
import { createHash, randomBytes, randomUUID } from "node:crypto";
|
|
18
|
+
import { isStorageVersion, compareStorageVersions } from "../storage/storageVersions.js";
|
|
18
19
|
import { existsSync, lstatSync, mkdirSync, readdirSync, readFileSync, readlinkSync, rmSync, writeFileSync } from "node:fs";
|
|
19
20
|
import { dirname, join, resolve } from "node:path";
|
|
20
21
|
import { readLinuxProcessStartIdentity } from "../controller/domainIdentity.js";
|
|
@@ -133,7 +134,7 @@ export function writeActiveReleasePointer(home, pointer) {
|
|
|
133
134
|
}
|
|
134
135
|
/**
|
|
135
136
|
* Resolves the Home's active release. Returns null when no pointer exists
|
|
136
|
-
* (
|
|
137
|
+
* (no immutable release selected). Fails closed when the pointer names a release that is
|
|
137
138
|
* missing or drifted.
|
|
138
139
|
*/
|
|
139
140
|
export function resolveActiveRelease(home) {
|
|
@@ -478,7 +479,7 @@ function validatePointer(value) {
|
|
|
478
479
|
function validateRuntimeIdentity(value) {
|
|
479
480
|
if (value === null
|
|
480
481
|
|| typeof value !== "object"
|
|
481
|
-
|| value.schemaVersion !==
|
|
482
|
+
|| value.schemaVersion !== 1
|
|
482
483
|
|| typeof value.version !== "string"
|
|
483
484
|
|| typeof value.executablePath !== "string"
|
|
484
485
|
|| value.executablePath === ""
|
|
@@ -487,15 +488,16 @@ function validateRuntimeIdentity(value) {
|
|
|
487
488
|
|| typeof value.cliRealpath !== "string"
|
|
488
489
|
|| typeof value.controllerRealpath !== "string"
|
|
489
490
|
|| !isPositiveInteger(value.controllerProtocolVersion)
|
|
490
|
-
|| !
|
|
491
|
-
|| !
|
|
492
|
-
|| value.minimumStorageVersion
|
|
493
|
-
> value.storageVersion
|
|
491
|
+
|| !isStorageVersion(value.storageVersion)
|
|
492
|
+
|| !isStorageVersion(value.minimumStorageVersion)
|
|
493
|
+
|| compareStorageVersions(value.minimumStorageVersion, value.storageVersion) > 0
|
|
494
494
|
|| typeof value.pid !== "number"
|
|
495
495
|
|| typeof value.processStartIdentity !== "string"
|
|
496
496
|
|| (value.mode !== "primary"
|
|
497
497
|
&& value.mode !== "candidate")
|
|
498
|
-
|| typeof value.dualOwner !== "boolean"
|
|
498
|
+
|| typeof value.dualOwner !== "boolean"
|
|
499
|
+
|| value.storageBackend !== "sqlite"
|
|
500
|
+
|| typeof value.workerEnabled !== "boolean") {
|
|
499
501
|
throw new Error("Runtime identity receipt is invalid.");
|
|
500
502
|
}
|
|
501
503
|
return value;
|
|
@@ -348,6 +348,11 @@ export class NodeGitWorkspace {
|
|
|
348
348
|
}
|
|
349
349
|
await canonicalContainer(dirname(destination), true);
|
|
350
350
|
const branch = input.branch === undefined ? undefined : safeRef(input.branch);
|
|
351
|
+
// Reserve exactly this operation's directory before invoking Git. A clone
|
|
352
|
+
// rejection because somebody else created the destination cannot authorize
|
|
353
|
+
// removal of that other directory.
|
|
354
|
+
await mkdir(destination, { mode: 0o700 });
|
|
355
|
+
const createdDirectory = await lstat(destination);
|
|
351
356
|
try {
|
|
352
357
|
await git([
|
|
353
358
|
"clone",
|
|
@@ -363,12 +368,31 @@ export class NodeGitWorkspace {
|
|
|
363
368
|
}
|
|
364
369
|
await git(["-C", destination, "branch", "-M", "--", localBranch]);
|
|
365
370
|
}
|
|
371
|
+
return await this.inspect(destination, "HEAD");
|
|
366
372
|
}
|
|
367
373
|
catch (error) {
|
|
368
|
-
|
|
374
|
+
try {
|
|
375
|
+
const current = await lstat(destination).catch(failure => {
|
|
376
|
+
if (isErrno(failure, "ENOENT"))
|
|
377
|
+
return undefined;
|
|
378
|
+
throw failure;
|
|
379
|
+
});
|
|
380
|
+
if (current !== undefined) {
|
|
381
|
+
if (!current.isDirectory() || current.dev !== createdDirectory.dev || current.ino !== createdDirectory.ino) {
|
|
382
|
+
throw new Error("Clone destination ownership changed; no direct deletion attempted.");
|
|
383
|
+
}
|
|
384
|
+
await rm(destination, { recursive: true });
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
catch (cleanupError) {
|
|
388
|
+
throw new AggregateError([error, cleanupError], `Clone failed at ${destination}: ${error instanceof Error ? error.message : String(error)}. `
|
|
389
|
+
+ `Temporary compensation failed: ${cleanupError instanceof Error ? cleanupError.message : String(cleanupError)}. `
|
|
390
|
+
+ "Cleanup effects are unconfirmed; inspect this exact destination before retrying.");
|
|
391
|
+
}
|
|
392
|
+
if (error instanceof Error)
|
|
393
|
+
error.message += ` Exact temporary clone directory removed or already absent: ${destination}.`;
|
|
369
394
|
throw error;
|
|
370
395
|
}
|
|
371
|
-
return this.inspect(destination, "HEAD");
|
|
372
396
|
}
|
|
373
397
|
async ensureLocalBranch(repositoryPath, branch) {
|
|
374
398
|
try {
|
|
@@ -822,31 +846,54 @@ export class NodeGitWorkspace {
|
|
|
822
846
|
* worktree's own Git identity instead. Only call for unadopted worktrees
|
|
823
847
|
* that are safe to discard (e.g. Lane preparation compensation).
|
|
824
848
|
*/
|
|
825
|
-
async removeStrandedWorktree(path) {
|
|
826
|
-
|
|
827
|
-
if (kind === undefined)
|
|
828
|
-
return "missing";
|
|
829
|
-
if (kind === "symlink")
|
|
830
|
-
throw new Error("Stranded worktree path must not be a symbolic link.");
|
|
831
|
-
// Inspect dirty state through the worktree's own Git, bypassing the
|
|
832
|
-
// Project ownership check that fails after a catalog switch.
|
|
833
|
-
const porcelain = await git(["-C", path, "status", "--porcelain=v1", "--untracked-files=all"]);
|
|
834
|
-
if (porcelain.length > 0)
|
|
835
|
-
return "dirty";
|
|
849
|
+
async removeStrandedWorktree(path, identity) {
|
|
850
|
+
let worktreeRemoved = false;
|
|
836
851
|
try {
|
|
837
|
-
|
|
852
|
+
const expected = managedPath(resolve(identity.container), safeIdentity(identity.directory, "Project directory"));
|
|
853
|
+
if (path !== expected)
|
|
854
|
+
throw new Error("Unadopted worktree does not match its exact managed path.");
|
|
855
|
+
const kind = await pathKind(path);
|
|
856
|
+
if (kind === undefined)
|
|
857
|
+
return "missing";
|
|
858
|
+
if (kind === "symlink")
|
|
859
|
+
throw new Error("Stranded worktree path must not be a symbolic link.");
|
|
860
|
+
await canonicalContainer(identity.container, false);
|
|
861
|
+
if (!(await lstat(join(path, ".git"))).isFile()) {
|
|
862
|
+
throw new Error("Expected a linked worktree, not a standalone clone.");
|
|
863
|
+
}
|
|
864
|
+
const project = await this.inspect(path, "HEAD");
|
|
865
|
+
await assertOwnedWorktree(project, resolve(identity.container), path);
|
|
866
|
+
const branch = worktreeIdentity(identity.taskSegment, identity.roleName).branch;
|
|
867
|
+
await assertExpectedBranch(path, branch);
|
|
868
|
+
if (project.baseCommit !== identity.expectedCommit) {
|
|
869
|
+
throw new Error("Unadopted worktree HEAD changed since preparation; retained.");
|
|
870
|
+
}
|
|
871
|
+
if (!await this.inspectClean(path))
|
|
872
|
+
return "dirty";
|
|
873
|
+
// Use the worktree's proven common directory, never the changed catalog.
|
|
874
|
+
// Git still enforces locks/submodule restrictions. A Git error is not
|
|
875
|
+
// authority for direct filesystem deletion, even for unadopted work.
|
|
876
|
+
await git(["--git-dir", project.gitDirectory, "worktree", "remove", "--", path]);
|
|
877
|
+
worktreeRemoved = true;
|
|
878
|
+
if (identity.deleteBranch === true) {
|
|
879
|
+
await git(["--git-dir", project.gitDirectory, "update-ref", "-d", `refs/heads/${branch}`, identity.expectedCommit]);
|
|
880
|
+
}
|
|
881
|
+
return "removed";
|
|
838
882
|
}
|
|
839
|
-
catch {
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
883
|
+
catch (error) {
|
|
884
|
+
throw new Error(`Unadopted worktree cleanup failed at ${path}: ${error instanceof Error ? error.message : String(error)} `
|
|
885
|
+
+ `Confirmed worktree removal: ${worktreeRemoved}; failed-command effects remain unconfirmed. `
|
|
886
|
+
+ "No direct deletion was attempted. Inspect this exact path and its Git registration before choosing recovery.", { cause: error });
|
|
843
887
|
}
|
|
844
|
-
return "removed";
|
|
845
888
|
}
|
|
846
889
|
async removeTaskClone(input) {
|
|
847
890
|
const state = await this.inspectTaskClone(input);
|
|
848
891
|
if (state !== "clean")
|
|
849
892
|
return state;
|
|
893
|
+
if (input.expectedCommit !== undefined
|
|
894
|
+
&& await resolveRefCommit(input.path, "HEAD") !== input.expectedCommit) {
|
|
895
|
+
throw new Error(`Unadopted Task clone HEAD changed since preparation; retained at ${input.path}.`);
|
|
896
|
+
}
|
|
850
897
|
await rm(managedPath(resolve(input.container), input.directory), { recursive: true });
|
|
851
898
|
return "removed";
|
|
852
899
|
}
|
|
@@ -8,7 +8,7 @@ export function managedProjectPath(home, projectId) {
|
|
|
8
8
|
export function createProject(id, name, path, branches, now, metadata = {}) {
|
|
9
9
|
const timestamp = now.toISOString();
|
|
10
10
|
return validateProject({
|
|
11
|
-
schemaVersion:
|
|
11
|
+
schemaVersion: 1,
|
|
12
12
|
id: requireIdentity(id, "Project id"),
|
|
13
13
|
name: validateProjectName(name),
|
|
14
14
|
aliases: normalizeAliases(metadata.aliases ?? [], name),
|
|
@@ -278,8 +278,8 @@ export function assertProjectCatalog(projects) {
|
|
|
278
278
|
}
|
|
279
279
|
}
|
|
280
280
|
export function validateProject(project) {
|
|
281
|
-
if (project.schemaVersion !==
|
|
282
|
-
throw new Error("Project must use schemaVersion
|
|
281
|
+
if (project.schemaVersion !== 1) {
|
|
282
|
+
throw new Error("Project must use schemaVersion 1.");
|
|
283
283
|
}
|
|
284
284
|
if (!Array.isArray(project.resourceRefs))
|
|
285
285
|
throw new Error("Project resourceRefs must be an array.");
|