@zq-silk/yui 0.15.7 → 0.15.8
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 +192 -399
- package/README.md +127 -1149
- package/dist/agent/adapterCatalog.js +15 -2
- package/dist/agent/agent.js +23 -3
- package/dist/agent/argumentPolicy.js +7 -1
- package/dist/agent/connectionPlan.js +62 -0
- package/dist/agent/executionComponents.js +158 -0
- package/dist/agent/launchEnvironment.js +31 -3
- package/dist/agent/managedRuntimeEnvironment.js +3 -5
- package/dist/{turn/turn.js → agentRun/agentRun.js} +166 -109
- package/dist/{turn/turnIdentity.js → agentRun/runIdentity.js} +4 -4
- package/dist/brief/taskBrief.js +12 -0
- package/dist/cli/agentConfigurationPicker.js +13 -0
- package/dist/cli/commandCatalog.js +157 -70
- package/dist/cli/interactionCandidates.js +5 -5
- package/dist/cli/interactionPolicy.js +38 -8
- package/dist/cli/invocationRouter.js +1 -1
- package/dist/cli/managedDiagnostics.js +28 -0
- package/dist/cli/operatorWizard.js +1 -7
- package/dist/cli/roleOptionOrder.js +27 -0
- package/dist/cli/roleWizard.js +50 -14
- package/dist/cli/updateOrchestrator.js +1 -1
- package/dist/cli/updatePorts.js +3 -4
- package/dist/cli.js +178 -95
- package/dist/commands/agentCommands.js +72 -14
- package/dist/commands/capabilityCommands.js +9 -6
- package/dist/commands/configCommands.js +20 -20
- package/dist/commands/deliveryGuardPreflight.js +2 -2
- package/dist/commands/executionAuditCommands.js +24 -24
- package/dist/commands/globalRoleCommands.js +1 -1
- package/dist/commands/grantCommands.js +4 -4
- package/dist/commands/operatorCommands.js +1 -7
- package/dist/commands/projectCommands.js +4 -4
- package/dist/commands/resourcesCommands.js +2 -2
- package/dist/commands/roleConfiguration.js +25 -5
- package/dist/commands/roleRuntimeGuard.js +4 -5
- package/dist/commands/sessionCommands.js +3 -7
- package/dist/commands/taskActivationCommands.js +259 -0
- package/dist/commands/taskActor.js +28 -49
- package/dist/commands/taskCommands.js +1188 -710
- package/dist/commands/taskContextCommand.js +39 -583
- package/dist/commands/taskExecutionCommands.js +32 -32
- package/dist/commands/taskInputCommands.js +40 -104
- package/dist/commands/taskIntegrationCommands.js +3 -2
- package/dist/commands/taskIntegrationQueueCommands.js +1 -1
- package/dist/commands/taskNextActionCommand.js +8 -8
- package/dist/commands/taskOverviewCommand.js +33 -45
- package/dist/commands/taskRemoteDeliveryCommand.js +2 -2
- package/dist/commands/taskRoleRuntimeStatus.js +133 -102
- package/dist/commands/telemetryCommands.js +36 -38
- package/dist/config/configCatalog.js +4 -4
- package/dist/config/yuiConfig.js +8 -8
- package/dist/context/contextSnapshot.js +10 -10
- package/dist/context/dispatchContext.js +11 -11
- package/dist/context/roleSessionContext.js +6 -3
- package/dist/context/{turnContextPack.js → runContextPack.js} +146 -81
- package/dist/context/{turnInputContract.js → runInputContract.js} +73 -60
- package/dist/context/sessionBootstrapManifest.js +21 -2
- package/dist/context/sourceRunContext.js +30 -0
- package/dist/context/taskContext.js +458 -0
- package/dist/context/wakeNotification.js +27 -27
- package/dist/controller/agentRuntimeObserver.js +21 -24
- package/dist/controller/capabilityBridge.js +17 -6
- package/dist/controller/clientRuntime.js +65 -92
- package/dist/controller/controller.js +114 -188
- package/dist/controller/fileSchedulerStoreAdapter.js +787 -887
- package/dist/controller/jobControl.js +54 -85
- package/dist/controller/resourceInventory.js +8 -27
- package/dist/controller/resourceInventoryLinux.js +12 -13
- package/dist/controller/runtime.js +529 -479
- package/dist/controller/runtimeEventInbox.js +55 -25
- package/dist/controller/runtimeEventProcessor.js +22 -31
- package/dist/controller/{runtimeHookTurnFence.js → runtimeHookRunFence.js} +91 -115
- package/dist/controller/runtimeLaunchCoordinator.js +80 -426
- package/dist/controller/runtimeObservationHook.js +14 -18
- package/dist/controller/sessionNotify.js +16 -24
- package/dist/controller/sessionOwnerReconciliation.js +168 -50
- package/dist/controller/structuredProviderObservation.js +138 -99
- package/dist/coordination/workMailbox.js +3 -3
- package/dist/coordination/workMailboxQueue.js +36 -33
- package/dist/core/boundedRpc.js +8 -1
- package/dist/core/controllerClient.js +20 -1
- package/dist/core/controllerServer.js +4 -4
- package/dist/doctor/doctor.js +13 -2
- package/dist/domain/agentResultTransport.js +9 -9
- package/dist/execution/codexThreadNaming.js +2 -8
- package/dist/execution/executionHealth.js +51 -63
- package/dist/execution/reviewMainRun.js +137 -0
- package/dist/execution/workItemExecution.js +28 -29
- package/dist/execution/workItemExecutionProjection.js +99 -107
- package/dist/execution/workItemMainRun.js +141 -0
- package/dist/executor/agentAdapter.js +227 -20
- package/dist/executor/agentConfigurationCatalog.js +126 -4
- package/dist/executor/agentConfigurationProbe.js +162 -4
- package/dist/executor/agentExecutor.js +79 -78
- package/dist/executor/effectiveLaunch.js +105 -18
- package/dist/executor/executorRegistry.js +29 -44
- package/dist/executor/fileRoleLaunchPlanner.js +229 -154
- package/dist/executor/workspacePreflightClassification.js +16 -16
- package/dist/grant/capabilityGrant.js +6 -3
- package/dist/input/inputRequest.js +12 -10
- package/dist/integration/gitIntegrationService.js +4 -11
- package/dist/integration/integrationQueueService.js +4 -4
- package/dist/interaction/operatorPresentation.js +1 -1
- package/dist/kernel/builtinCapabilities.js +247 -12
- package/dist/kernel/capabilityRegistry.js +64 -18
- package/dist/kernel/instanceHost.js +12 -1
- package/dist/kernel/kernelPorts.js +2 -2
- package/dist/lifecycle/canonicalLifecycleEvent.js +44 -49
- package/dist/lifecycle/exactRunTerminalization.js +449 -0
- package/dist/message/message.js +62 -6
- package/dist/message/messageContinuation.js +204 -0
- package/dist/observability/executionAudit.js +70 -72
- package/dist/observability/faultClassification.js +2 -2
- package/dist/observability/orchestrationMetrics.js +8 -8
- package/dist/operator/operatorSessionHistory.js +1 -7
- package/dist/output/agentConfigurationPresentation.js +8 -3
- package/dist/output/agentRunConfigurationPresentation.js +128 -0
- package/dist/output/rolePresentation.js +54 -3
- package/dist/plugins/pluginChild.js +104 -0
- package/dist/plugins/pluginIntent.js +26 -0
- package/dist/plugins/pluginInterpreter.js +43 -0
- package/dist/plugins/pluginPackage.js +101 -0
- package/dist/plugins/pluginProcess.js +112 -0
- package/dist/plugins/pluginService.js +380 -0
- package/dist/profile/agentProfile.js +1 -1
- package/dist/repository/gitWorkspace.js +26 -4
- package/dist/repository/project.js +19 -4
- package/dist/repository/taskBaseFreshness.js +13 -13
- package/dist/repository/taskWorkspaceCoordinator.js +20 -27
- package/dist/repository/taskWorkspacePreparer.js +344 -83
- package/dist/resources/autoResourceGc.js +3 -3
- package/dist/resources/liveReferences.js +3 -3
- package/dist/resources/projectResource.js +123 -0
- package/dist/resources/projectResourceService.js +421 -0
- package/dist/resources/resourceDiscovery.js +6 -6
- package/dist/resources/resourceGc.js +1 -1
- package/dist/resources/resourceRegistrar.js +1 -1
- package/dist/resources/resourceTypes.js +1 -1
- package/dist/review/deltaRecheck.js +3 -3
- package/dist/review/reviewAcceptance.js +16 -16
- package/dist/review/reviewDecision.js +7 -7
- package/dist/review/reviewRound.js +21 -20
- package/dist/review/reviewerAvailability.js +2 -2
- package/dist/role/role.js +51 -7
- package/dist/role/taskRoleUpdate.js +30 -0
- package/dist/runtime/acpProtocol.js +425 -0
- package/dist/runtime/acpSession.js +731 -0
- package/dist/runtime/acpSessionConfiguration.js +260 -0
- package/dist/runtime/agentDriver.js +30 -11
- package/dist/runtime/agentEndpoint.js +278 -0
- package/dist/runtime/agentEndpointIdentity.js +86 -0
- package/dist/runtime/agentEndpointOwnership.js +239 -0
- package/dist/runtime/agentError.js +2 -10
- package/dist/runtime/agentHost.js +565 -314
- package/dist/runtime/agentRunConfiguration.js +258 -0
- package/dist/runtime/builtinAgentDrivers.js +134 -18
- package/dist/runtime/builtinAgentErrorMappers.js +55 -3
- package/dist/runtime/builtinTranscriptUsage.js +1 -1
- package/dist/runtime/claude-process-owner +0 -0
- package/dist/runtime/codexAppServerRuntime.js +38 -30
- package/dist/runtime/codexInteractiveHost.js +41 -6
- package/dist/runtime/continuationManager.js +2 -6
- package/dist/runtime/executionEnvironment.js +30 -0
- package/dist/runtime/firstProgressAdvisory.js +11 -11
- package/dist/runtime/index.js +4 -3
- package/dist/runtime/jsonLineChannel.js +109 -0
- package/dist/runtime/launchBroker.js +91 -16
- package/dist/runtime/launchDiagnostics.js +2 -2
- package/dist/runtime/lifecycleReservation.js +10 -18
- package/dist/runtime/managedCaller.js +61 -17
- package/dist/runtime/nativeSessionControl.js +102 -0
- package/dist/runtime/ports.js +6 -21
- package/dist/runtime/processExitObservation.js +8 -7
- package/dist/runtime/promptEnvelope.js +17 -6
- package/dist/runtime/providerContinuation.js +3 -9
- package/dist/runtime/providerContinuationReconciliationService.js +4 -13
- package/dist/runtime/providerControl.js +2 -7
- package/dist/runtime/providerRuntimeIdentity.js +110 -222
- package/dist/runtime/providerRuntimeReconciler.js +5 -9
- package/dist/runtime/runtimeBinding.js +0 -1
- package/dist/runtime/runtimeContinuationProjection.js +4 -7
- package/dist/runtime/runtimeDeadlines.js +9 -0
- package/dist/runtime/runtimeHealthPolicy.js +1 -1
- package/dist/runtime/runtimeObservation.js +29 -65
- package/dist/runtime/runtimeProjection.js +43 -51
- package/dist/runtime/runtimeSessionCandidate.js +1 -3
- package/dist/runtime/sessionLaunchRequest.js +3 -7
- package/dist/runtime/sessionOwnerIdentity.js +7 -54
- package/dist/runtime/sessionOwnerRegistry.js +22 -17
- package/dist/runtime/sessionReconciliation.js +4 -8
- package/dist/runtime/sessionTerminationGuard.js +70 -259
- package/dist/runtime/sessionTokenMetrics.js +5 -16
- package/dist/runtime/structuredProviderHost.js +237 -117
- package/dist/runtime/taskRuntimeIsolation.js +39 -122
- package/dist/runtime/tmuxAdapters.js +39 -86
- package/dist/scheduler/activeRoleRunDelivery.js +354 -0
- package/dist/scheduler/leaderWakeupProcessor.js +75 -266
- package/dist/scheduler/operatorInputNotificationProcessor.js +1 -1
- package/dist/scheduler/ports.js +80 -9
- package/dist/scheduler/{roleTurnLiveness.js → roleRunLiveness.js} +26 -30
- package/dist/scheduler/{roleTurnStall.js → roleRunStall.js} +128 -139
- package/dist/scheduler/taskExecutionProjection.js +120 -124
- package/dist/scheduler/taskObservabilityProjection.js +29 -29
- package/dist/scheduler/taskWake.js +11 -4
- package/dist/scheduler/wakeReason.js +9 -1
- package/dist/setup/setupCommand.js +1 -0
- package/dist/storage/migrations/agentRunContract.js +159 -0
- package/dist/storage/migrations/removeRuntimeGeneration.js +207 -0
- package/dist/storage/sqliteSchema.js +431 -5
- package/dist/storage/sqliteStore.js +375 -220
- package/dist/storage/storageVersions.js +1 -1
- package/dist/storage/storeRpc.js +10 -5
- package/dist/storage/taskStore.js +13 -11
- package/dist/storage/upgrade/upgradeOrchestrator.js +5 -7
- package/dist/surface/surfaceContributions.js +102 -0
- package/dist/task/completionReadiness.js +32 -6
- package/dist/task/deliveryGuard.js +16 -16
- package/dist/task/draftPlan.js +72 -12
- package/dist/task/nextAction.js +144 -128
- package/dist/task/remoteDelivery.js +6 -6
- package/dist/task/task.js +184 -18
- package/dist/task/taskActivation.js +301 -0
- package/dist/task/taskActivationService.js +392 -0
- package/dist/task/taskRecordReference.js +5 -4
- package/dist/task/taskRecordRetirement.js +1 -1
- package/dist/telemetry/sqliteTelemetryStore.js +55 -68
- package/dist/telemetry/telemetryConfig.js +14 -14
- package/dist/telemetry/telemetryWiring.js +2 -2
- package/dist/web/assets/assetManifest.js +2 -0
- package/dist/web/assets/client/app.js +120 -20
- package/dist/web/assets/client/components.js +87 -54
- package/dist/web/assets/client/i18n.js +83 -41
- package/dist/web/assets/client/markdown.js +1 -1
- package/dist/web/assets/client/taskSurface.js +353 -0
- package/dist/web/assets/client/view.js +49 -44
- package/dist/web/assets/shell.js +1 -1
- package/dist/web/assets/styles/cards.js +22 -4
- package/dist/web/controllerWeb.js +60 -0
- package/dist/web/webMutation.js +28 -0
- package/dist/web/webServer.js +118 -8
- package/dist/web/webSnapshot.js +81 -74
- package/dist/web/webTaskSurface.js +64 -0
- package/dist/workItem/dependencyGate.js +1 -1
- package/dist/workItem/workItem.js +80 -48
- package/dist/workspace/workItemChangeSetManager.js +16 -9
- package/docs/agent-result-consumption.md +94 -0
- package/docs/agent-runtime-drivers.md +91 -0
- package/docs/architecture/README.md +38 -0
- package/docs/architecture/capabilities-and-resources.md +79 -0
- package/docs/managed-turn-and-session-runtime.md +222 -0
- package/docs/observability/README.md +81 -0
- package/docs/plugin-sdk.md +290 -0
- package/docs/provider-runtime.md +163 -0
- package/docs/release-workflow.md +303 -0
- package/docs/roles-and-configuration.md +113 -0
- package/docs/sqlite-control-plane-design.md +76 -0
- package/docs/task-dag-semantics.md +57 -0
- package/docs/task-delivery.md +103 -0
- package/docs/task-local-identity.md +6 -6
- package/docs/testing/verification-levels.md +86 -0
- package/i18n/README.zh-CN.md +98 -739
- package/package.json +2 -2
- package/skills/yui-leader/SKILL.md +130 -103
- package/skills/yui-leader/references/integration.md +39 -0
- package/skills/yui-leader/references/replicated-execution.md +42 -0
- package/skills/yui-leader/references/task-plugins.md +33 -0
- package/skills/yui-operator/SKILL.md +30 -59
- package/skills/yui-reviewer/SKILL.md +35 -36
- package/skills/yui-runtime/SKILL.md +88 -24
- package/skills/yui-runtime/references/publication.md +22 -0
- package/skills/yui-runtime/references/recovery.md +64 -0
- package/skills/yui-worker/SKILL.md +37 -39
- package/dist/cli/roleOptionCatalog.js +0 -68
- package/dist/context/sourceTurnContext.js +0 -30
- package/dist/execution/reviewMainTurn.js +0 -161
- package/dist/execution/workItemMainTurn.js +0 -164
- package/dist/lifecycle/exactTurnTerminalization.js +0 -407
- package/dist/runtime/preallocatedNativeSession.js +0 -13
- package/dist/runtime/runtimeStopReceipt.js +0 -42
- package/dist/scheduler/activeRoleTurnDelivery.js +0 -315
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { mkdirSync, mkdtempSync, realpathSync, rmSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { dirname, join, relative, resolve } from "node:path";
|
|
4
|
+
import { execFile } from "node:child_process";
|
|
5
|
+
import { promisify } from "node:util";
|
|
6
|
+
import { checkGrant, recordGrantUse } from "../grant/capabilityGrant.js";
|
|
7
|
+
import { createProjectResources } from "../resources/projectResourceService.js";
|
|
8
|
+
import { CapabilityExecutionError } from "../kernel/capabilityRegistry.js";
|
|
9
|
+
import { validatePluginId } from "./pluginIntent.js";
|
|
10
|
+
import { packagePath, readPluginPackage } from "./pluginPackage.js";
|
|
11
|
+
import { interpretPlugin } from "./pluginInterpreter.js";
|
|
12
|
+
import { PluginProcess, pluginProcessEnvironment } from "./pluginProcess.js";
|
|
13
|
+
const execute = promisify(execFile);
|
|
14
|
+
/** SDK management owner. The Store owns desired selection and validation;
|
|
15
|
+
* the Host alone owns live implementations. Reading/restarting never executes
|
|
16
|
+
* author code or treats saved intent as a grant. */
|
|
17
|
+
export function createPluginService(store, host, registry) {
|
|
18
|
+
const resources = createProjectResources(store);
|
|
19
|
+
const active = new Map();
|
|
20
|
+
// Resource references mirror actual SDK operations/Host ownership only.
|
|
21
|
+
// They prevent explicit environment release while those owners still use it.
|
|
22
|
+
const environmentUsers = new Map();
|
|
23
|
+
const retainEnvironment = (taskId, preparationId) => {
|
|
24
|
+
const token = {};
|
|
25
|
+
environmentUsers.set(token, { taskId, preparationId });
|
|
26
|
+
return () => { environmentUsers.delete(token); };
|
|
27
|
+
};
|
|
28
|
+
const keyFor = (taskId, pluginId) => `plugin:${taskId}:${pluginId}`;
|
|
29
|
+
const selection = (taskId, validationId) => {
|
|
30
|
+
const report = store.getPluginValidation(taskId, validationId);
|
|
31
|
+
if (!report)
|
|
32
|
+
throw new Error("Plugin selection references missing validation evidence.");
|
|
33
|
+
return { validationId, version: report.package.manifest.version, digest: report.package.digest };
|
|
34
|
+
};
|
|
35
|
+
const inspect = (taskId, pluginId) => {
|
|
36
|
+
validatePluginId(pluginId);
|
|
37
|
+
return store.transaction(() => {
|
|
38
|
+
const desired = store.getPluginIntent(taskId, pluginId);
|
|
39
|
+
const selected = active.get(keyFor(taskId, pluginId));
|
|
40
|
+
const actual = selected && host.isAvailable(selected.ref)
|
|
41
|
+
? { ...selection(taskId, selected.validationId), provider: selected.ref } : null;
|
|
42
|
+
return {
|
|
43
|
+
pluginId, scope: { kind: "task", id: taskId },
|
|
44
|
+
desired: desired === null ? null : {
|
|
45
|
+
...desired, ...(desired.validationId === undefined ? {} : selection(taskId, desired.validationId))
|
|
46
|
+
},
|
|
47
|
+
actual,
|
|
48
|
+
instances: host.inspect(keyFor(taskId, pluginId)),
|
|
49
|
+
needsActivation: desired?.enabled === true && desired.validationId !== actual?.validationId
|
|
50
|
+
};
|
|
51
|
+
});
|
|
52
|
+
};
|
|
53
|
+
const choose = (taskId, pluginId, enabled, validationId) => store.transaction(() => {
|
|
54
|
+
const previous = store.getPluginIntent(taskId, pluginId);
|
|
55
|
+
const chosen = validationId ?? previous?.validationId;
|
|
56
|
+
const intent = {
|
|
57
|
+
schemaVersion: 1, taskId, pluginId, revision: (previous?.revision ?? 0) + 1,
|
|
58
|
+
enabled, ...(chosen === undefined ? {} : { validationId: chosen }), updatedAt: new Date().toISOString()
|
|
59
|
+
};
|
|
60
|
+
store.savePluginIntent(intent);
|
|
61
|
+
return intent;
|
|
62
|
+
});
|
|
63
|
+
const fail = (intent, error) => {
|
|
64
|
+
let message = error instanceof Error ? error.message : String(error);
|
|
65
|
+
try {
|
|
66
|
+
store.recordPluginIntentFailure(intent.taskId, intent.pluginId, intent.revision, { message, occurredAt: new Date().toISOString() });
|
|
67
|
+
}
|
|
68
|
+
catch (recordError) {
|
|
69
|
+
message += `; failure diagnostic could not be saved: ${recordError instanceof Error ? recordError.message : String(recordError)}`;
|
|
70
|
+
}
|
|
71
|
+
return new CapabilityExecutionError(message, inspect(intent.taskId, intent.pluginId));
|
|
72
|
+
};
|
|
73
|
+
const environment = (taskId, preparationId) => {
|
|
74
|
+
const task = store.getTask(taskId);
|
|
75
|
+
if (!task || !["draft", "active"].includes(task.status))
|
|
76
|
+
throw new Error("Plugin environment requires an open Task.");
|
|
77
|
+
const prepared = store.getEnvironmentPreparation(taskId, preparationId);
|
|
78
|
+
if (!prepared || prepared.disposition !== "adopted" || !prepared.directory || prepared.access !== "write") {
|
|
79
|
+
throw new Error("Plugin development/execution requires an adopted writable T05 environment.");
|
|
80
|
+
}
|
|
81
|
+
// Reuse the same current-adoption proof as native execution, including
|
|
82
|
+
// the resource grant reservation. A new grant alone is not re-adoption.
|
|
83
|
+
resources.resolveExecutionEnvironment(taskId, preparationId);
|
|
84
|
+
return prepared;
|
|
85
|
+
};
|
|
86
|
+
const packageDirectory = (taskId, preparationId, directory) => {
|
|
87
|
+
const env = environment(taskId, preparationId);
|
|
88
|
+
const root = env.directory.path;
|
|
89
|
+
const path = resolve(root, directory);
|
|
90
|
+
const child = relative(root, path);
|
|
91
|
+
packagePath(child);
|
|
92
|
+
if (realpathSync(path) !== path)
|
|
93
|
+
throw new Error("Plugin directory must not traverse symlinks.");
|
|
94
|
+
return { env, path };
|
|
95
|
+
};
|
|
96
|
+
const descriptors = (taskId, pkg, ref) => pkg.manifest.capabilities.map((capability) => ({
|
|
97
|
+
...capability, provider: ref, scope: { kind: "task", id: taskId },
|
|
98
|
+
source: `plugin:${pkg.manifest.id}@${pkg.digest}`, required: pkg.manifest.required
|
|
99
|
+
}));
|
|
100
|
+
const refFor = (taskId, pkg) => ({ id: keyFor(taskId, pkg.manifest.id), generation: randomUUID() });
|
|
101
|
+
/** Each execution consumes one use. A live call's bound closure carries its
|
|
102
|
+
* admission; it is never resumed from a durable reservation or sent to code.
|
|
103
|
+
* Subsequent actions still check current authority, not cached permission. */
|
|
104
|
+
const execution = (taskId, preparationId, pkg, phase) => {
|
|
105
|
+
const params = { pluginId: pkg.manifest.id, digest: pkg.digest,
|
|
106
|
+
environmentRef: `${taskId}/${preparationId}`, trust: "trusted-local", phase };
|
|
107
|
+
// This grants the capability to run code without OS effect confinement;
|
|
108
|
+
// it does not claim that every invocation actually has irreversible effects.
|
|
109
|
+
const request = { action: "plugin.execute", params, irreversibility: "irreversible" };
|
|
110
|
+
const reservation = phase === "call" ? undefined : `plugin/${randomUUID()}`;
|
|
111
|
+
const grantId = store.transaction((tx) => {
|
|
112
|
+
const env = environment(taskId, preparationId);
|
|
113
|
+
const grant = tx.listCapabilityGrants(taskId).find((candidate) => Object.entries(params).every(([key, value]) => candidate.parameterBounds[key]?.includes(value))
|
|
114
|
+
&& (candidate.scope.taskId === undefined || candidate.scope.taskId === taskId)
|
|
115
|
+
&& candidate.scope.projectIds === undefined && candidate.scope.repositories === undefined
|
|
116
|
+
&& candidate.scope.packages === undefined
|
|
117
|
+
&& (candidate.scope.homePath === undefined || candidate.scope.homePath === env.directory.path)
|
|
118
|
+
&& checkGrant(candidate, request, new Date()).allowed);
|
|
119
|
+
if (!grant)
|
|
120
|
+
throw new Error(`Explicit trusted-local execution grant unavailable (${phase}); pluginId=${params.pluginId}, digest=${params.digest}, environmentRef=${params.environmentRef}.`);
|
|
121
|
+
tx.saveCapabilityGrant(taskId, recordGrantUse(grant, new Date(), reservation));
|
|
122
|
+
return grant.id;
|
|
123
|
+
});
|
|
124
|
+
return () => {
|
|
125
|
+
environment(taskId, preparationId);
|
|
126
|
+
const grant = store.listCapabilityGrants(taskId).find((candidate) => candidate.id === grantId);
|
|
127
|
+
if (!grant || (reservation !== undefined && !grant.useReservations.includes(reservation))
|
|
128
|
+
|| !checkGrant(grant, request, new Date(), { skipUsesCheck: true }).allowed) {
|
|
129
|
+
throw new Error("Plugin execution authority was revoked.");
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
};
|
|
133
|
+
const prepare = async (taskId, preparationId, pkg, phase, executionInput = pkg, admittedExecution) => {
|
|
134
|
+
if (pkg.manifest.kind === "declarative") {
|
|
135
|
+
const implementation = interpretPlugin(pkg);
|
|
136
|
+
const release = retainEnvironment(taskId, preparationId);
|
|
137
|
+
return { implementation, dispose: async () => { release(); } };
|
|
138
|
+
}
|
|
139
|
+
const check = admittedExecution ?? execution(taskId, preparationId, executionInput, phase);
|
|
140
|
+
const env = environment(taskId, preparationId);
|
|
141
|
+
const process = new PluginProcess(env.directory.path);
|
|
142
|
+
const release = retainEnvironment(taskId, preparationId);
|
|
143
|
+
const dispose = async () => { try {
|
|
144
|
+
await process.dispose();
|
|
145
|
+
}
|
|
146
|
+
finally {
|
|
147
|
+
release();
|
|
148
|
+
} };
|
|
149
|
+
try {
|
|
150
|
+
check();
|
|
151
|
+
await process.load(pkg);
|
|
152
|
+
check();
|
|
153
|
+
if (phase === "validate") {
|
|
154
|
+
await process.test();
|
|
155
|
+
check();
|
|
156
|
+
}
|
|
157
|
+
const implementation = {
|
|
158
|
+
invoke(name, input, invocation) {
|
|
159
|
+
const checkCall = execution(taskId, preparationId, pkg, "call");
|
|
160
|
+
return process.invoke(name, input, invocation, checkCall);
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
return { implementation, dispose };
|
|
164
|
+
}
|
|
165
|
+
catch (error) {
|
|
166
|
+
try {
|
|
167
|
+
await dispose();
|
|
168
|
+
}
|
|
169
|
+
catch (cleanup) {
|
|
170
|
+
throw new AggregateError([error, cleanup], "Plugin initialization and candidate cleanup failed.");
|
|
171
|
+
}
|
|
172
|
+
throw error;
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
return {
|
|
176
|
+
current: inspect,
|
|
177
|
+
list(taskId) {
|
|
178
|
+
return store.transaction(() => store.listPluginIntents(taskId)
|
|
179
|
+
.sort((left, right) => left.pluginId.localeCompare(right.pluginId))
|
|
180
|
+
.map((intent) => inspect(taskId, intent.pluginId)));
|
|
181
|
+
},
|
|
182
|
+
assertEnvironmentUnused(taskId, preparationId) {
|
|
183
|
+
if ([...environmentUsers.values()].some((owner) => owner.taskId === taskId && owner.preparationId === preparationId)) {
|
|
184
|
+
throw new Error("Environment has live plugin references; disable and drain its plugins or finish validation first.");
|
|
185
|
+
}
|
|
186
|
+
},
|
|
187
|
+
scan(taskId, preparationId, directory) {
|
|
188
|
+
const { path } = packageDirectory(taskId, preparationId, directory);
|
|
189
|
+
const pkg = readPluginPackage(path, false);
|
|
190
|
+
return { directory: path, preparationId, digest: pkg.digest, manifest: pkg.manifest,
|
|
191
|
+
files: Object.keys(pkg.files), executedAuthorCode: false };
|
|
192
|
+
},
|
|
193
|
+
create(taskId, preparationId, id, kind) {
|
|
194
|
+
if (!/^[a-z][a-z0-9-]*$/u.test(id) || !["declarative", "trusted-local"].includes(kind))
|
|
195
|
+
throw new Error("Invalid plugin template.");
|
|
196
|
+
const env = environment(taskId, preparationId);
|
|
197
|
+
const directory = join(env.directory.path, id);
|
|
198
|
+
mkdirSync(directory, { mode: 0o700 }); // Never overwrite an author's directory.
|
|
199
|
+
const name = `${id}.echo`;
|
|
200
|
+
const manifest = {
|
|
201
|
+
id, version: "1.0.0", apiVersion: "1", kind, entry: kind === "declarative" ? "entry.json" : "entry.mjs",
|
|
202
|
+
capabilities: [{ name, contractVersion: "1", summary: "Return JSON input.", inputSchema: {}, outputSchema: {},
|
|
203
|
+
effect: "query", requiredPermissions: [] }], required: [], permissions: [], reloadMode: "manual"
|
|
204
|
+
};
|
|
205
|
+
writeFileSync(join(directory, "plugin.json"), JSON.stringify(manifest, null, 2) + "\n", { flag: "wx", mode: 0o600 });
|
|
206
|
+
writeFileSync(join(directory, manifest.entry), kind === "declarative"
|
|
207
|
+
? JSON.stringify({ [name]: { type: "echo" } }, null, 2) + "\n"
|
|
208
|
+
: `export function initialize() {\n return { handlers: { ${JSON.stringify(name)}: async (input) => input }, dispose() {} };\n}\nexport function selfTest() { return true; }\n`, { flag: "wx", mode: 0o600 });
|
|
209
|
+
return { directory, preparationId, kind, executedAuthorCode: false };
|
|
210
|
+
},
|
|
211
|
+
async validate(context, preparationId, directory) {
|
|
212
|
+
const taskId = context.targetId;
|
|
213
|
+
const { env, path } = packageDirectory(taskId, preparationId, directory);
|
|
214
|
+
const source = readPluginPackage(path, false);
|
|
215
|
+
registry.checkRegistration(context, descriptors(taskId, source, refFor(taskId, source)));
|
|
216
|
+
const checks = ["data-only manifest/schema/namespace/permissions/dependencies"];
|
|
217
|
+
let pkg = source;
|
|
218
|
+
if (source.manifest.build) {
|
|
219
|
+
const check = execution(taskId, preparationId, source, "build");
|
|
220
|
+
const [script, ...args] = source.manifest.build;
|
|
221
|
+
packagePath(script);
|
|
222
|
+
if (!Object.hasOwn(source.files, script))
|
|
223
|
+
throw new Error("Build script must be inside the captured source package.");
|
|
224
|
+
const buildDirectory = mkdtempSync(join(env.directory.path, ".plugin-build-"));
|
|
225
|
+
const release = retainEnvironment(taskId, preparationId);
|
|
226
|
+
try {
|
|
227
|
+
for (const [file, content] of Object.entries(source.files)) {
|
|
228
|
+
const target = join(buildDirectory, file);
|
|
229
|
+
mkdirSync(dirname(target), { recursive: true });
|
|
230
|
+
writeFileSync(target, content, { flag: "wx", mode: 0o600 });
|
|
231
|
+
}
|
|
232
|
+
check();
|
|
233
|
+
await execute(process.execPath, [join(buildDirectory, script), ...args], {
|
|
234
|
+
cwd: buildDirectory, env: pluginProcessEnvironment(buildDirectory),
|
|
235
|
+
timeout: 30_000, maxBuffer: 64 * 1024
|
|
236
|
+
});
|
|
237
|
+
check();
|
|
238
|
+
pkg = readPluginPackage(buildDirectory);
|
|
239
|
+
checks.push(`build cwd=${buildDirectory}; executable=${process.execPath}; argv=${JSON.stringify(source.manifest.build)}; exit=0`);
|
|
240
|
+
}
|
|
241
|
+
finally {
|
|
242
|
+
try {
|
|
243
|
+
if (realpathSync(buildDirectory) !== buildDirectory)
|
|
244
|
+
throw new Error("Build directory identity changed; retained for inspection.");
|
|
245
|
+
rmSync(buildDirectory, { recursive: true });
|
|
246
|
+
}
|
|
247
|
+
finally {
|
|
248
|
+
release();
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
checks.push("trusted-local Node build completed");
|
|
252
|
+
}
|
|
253
|
+
else {
|
|
254
|
+
if (!Object.hasOwn(source.files, source.manifest.entry))
|
|
255
|
+
throw new Error("Plugin entry is missing.");
|
|
256
|
+
checks.push("no build command; package is directly loadable");
|
|
257
|
+
}
|
|
258
|
+
// Builds may change products, not silently replace permissions or contract.
|
|
259
|
+
if (JSON.stringify(pkg.manifest) !== JSON.stringify(source.manifest))
|
|
260
|
+
throw new Error("Build changed plugin manifest; inspect and validate explicitly.");
|
|
261
|
+
registry.checkRegistration(context, descriptors(taskId, pkg, refFor(taskId, pkg)));
|
|
262
|
+
const candidate = await prepare(taskId, preparationId, pkg, "validate", source);
|
|
263
|
+
await candidate.dispose();
|
|
264
|
+
checks.push(pkg.manifest.kind === "declarative" ? "trusted interpreter parsed complete entry" : "child initialize/full contribution set/selfTest/dispose");
|
|
265
|
+
const current = packageDirectory(taskId, preparationId, directory);
|
|
266
|
+
if (readPluginPackage(current.path, false).digest !== source.digest)
|
|
267
|
+
throw new Error("Plugin changed during validation.");
|
|
268
|
+
registry.checkRegistration(context, descriptors(taskId, pkg, refFor(taskId, pkg)));
|
|
269
|
+
const validation = {
|
|
270
|
+
schemaVersion: 1, id: `validation-${randomUUID()}`, taskId, directory: path, preparationId, sourceDigest: source.digest, package: pkg,
|
|
271
|
+
environment: { ...env.directory, isolation: "trusted-local", node: process.version },
|
|
272
|
+
checks, createdAt: new Date().toISOString()
|
|
273
|
+
};
|
|
274
|
+
store.savePluginValidation(validation);
|
|
275
|
+
return { ...validation, package: { manifest: pkg.manifest, digest: pkg.digest } };
|
|
276
|
+
},
|
|
277
|
+
inspect(taskId, validationId) {
|
|
278
|
+
const validation = store.getPluginValidation(taskId, validationId);
|
|
279
|
+
if (!validation)
|
|
280
|
+
throw new Error("Plugin validation not found in this Task.");
|
|
281
|
+
const { files: _files, ...pkg } = validation.package;
|
|
282
|
+
return { ...validation, package: pkg };
|
|
283
|
+
},
|
|
284
|
+
async activate(context, validationId) {
|
|
285
|
+
const taskId = context.targetId;
|
|
286
|
+
const validation = store.getPluginValidation(taskId, validationId);
|
|
287
|
+
if (!validation)
|
|
288
|
+
throw new Error("Plugin validation not found in this Task.");
|
|
289
|
+
const { package: pkg, preparationId, directory } = validation;
|
|
290
|
+
const verify = () => {
|
|
291
|
+
const current = packageDirectory(taskId, preparationId, directory);
|
|
292
|
+
const expected = validation.environment;
|
|
293
|
+
if (current.env.directory.device !== expected.device || current.env.directory.inode !== expected.inode
|
|
294
|
+
|| current.env.directory.path !== expected.path || expected.node !== process.version) {
|
|
295
|
+
throw new Error("Validation execution environment changed; validate again.");
|
|
296
|
+
}
|
|
297
|
+
if (readPluginPackage(current.path, false).digest !== validation.sourceDigest)
|
|
298
|
+
throw new Error("Plugin bytes differ from validated source.");
|
|
299
|
+
};
|
|
300
|
+
verify();
|
|
301
|
+
const ref = refFor(taskId, pkg);
|
|
302
|
+
const entries = descriptors(taskId, pkg, ref);
|
|
303
|
+
registry.checkRegistration(context, entries);
|
|
304
|
+
// Intent is admitted only after input, content, environment, caller and
|
|
305
|
+
// execution authorization pass. Grant consumption and selection commit
|
|
306
|
+
// together, before any author initialization or publication.
|
|
307
|
+
let admittedExecution;
|
|
308
|
+
const intent = store.transaction(() => {
|
|
309
|
+
if (pkg.manifest.kind === "trusted-local")
|
|
310
|
+
admittedExecution = execution(taskId, preparationId, pkg, "activate");
|
|
311
|
+
return choose(taskId, pkg.manifest.id, true, validationId);
|
|
312
|
+
});
|
|
313
|
+
let candidate;
|
|
314
|
+
let attached = false;
|
|
315
|
+
let previous;
|
|
316
|
+
try {
|
|
317
|
+
candidate = await prepare(taskId, preparationId, pkg, "activate", pkg, admittedExecution);
|
|
318
|
+
verify();
|
|
319
|
+
registry.checkRegistration(context, entries);
|
|
320
|
+
if (store.getPluginIntent(taskId, pkg.manifest.id)?.revision !== intent.revision) {
|
|
321
|
+
throw new Error("Plugin activation superseded by another explicit management action.");
|
|
322
|
+
}
|
|
323
|
+
const implementation = candidate.implementation;
|
|
324
|
+
host.attach(ref, {
|
|
325
|
+
invoke: (name, input, invocation) => {
|
|
326
|
+
environment(taskId, preparationId);
|
|
327
|
+
return implementation.invoke(name, input, invocation);
|
|
328
|
+
}
|
|
329
|
+
}, [candidate.dispose]);
|
|
330
|
+
attached = true;
|
|
331
|
+
previous = active.get(ref.id);
|
|
332
|
+
registry.register(entries);
|
|
333
|
+
active.set(ref.id, { ref, validationId });
|
|
334
|
+
}
|
|
335
|
+
catch (error) {
|
|
336
|
+
let failure = error;
|
|
337
|
+
try {
|
|
338
|
+
if (attached)
|
|
339
|
+
await host.detach(ref);
|
|
340
|
+
else
|
|
341
|
+
await candidate?.dispose();
|
|
342
|
+
}
|
|
343
|
+
catch (cleanup) {
|
|
344
|
+
failure = new Error(`${error instanceof Error ? error.message : String(error)}; candidate cleanup failed: ${String(cleanup)}`);
|
|
345
|
+
}
|
|
346
|
+
throw fail(intent, failure);
|
|
347
|
+
}
|
|
348
|
+
// Publication has committed. A later diagnostic/read failure must never
|
|
349
|
+
// enter candidate rollback or detach the new selected implementation.
|
|
350
|
+
if (previous) {
|
|
351
|
+
const retired = previous;
|
|
352
|
+
void host.detach(retired.ref).catch((error) => {
|
|
353
|
+
resources.saveArtifact(taskId, { kind: "content", displayName: "Plugin instance cleanup failure",
|
|
354
|
+
provenance: `PluginService.dispose ${retired.ref.id}/${retired.ref.generation}`,
|
|
355
|
+
content: error instanceof Error ? error.message : String(error) });
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
return { provider: ref, validationId, digest: pkg.digest, capabilities: entries.map((entry) => entry.name),
|
|
359
|
+
lifetime: "current-controller", previousDraining: previous?.ref, ...inspect(taskId, pkg.manifest.id) };
|
|
360
|
+
},
|
|
361
|
+
async disable(taskId, pluginId) {
|
|
362
|
+
validatePluginId(pluginId);
|
|
363
|
+
const key = keyFor(taskId, pluginId);
|
|
364
|
+
// A failed commit must leave the currently selected implementation alone.
|
|
365
|
+
const intent = choose(taskId, pluginId, false);
|
|
366
|
+
const current = active.get(key);
|
|
367
|
+
if (!current)
|
|
368
|
+
return { disabled: true, providerId: key, ...inspect(taskId, pluginId) };
|
|
369
|
+
registry.disable(key);
|
|
370
|
+
active.delete(key);
|
|
371
|
+
try {
|
|
372
|
+
await host.detach(current.ref);
|
|
373
|
+
}
|
|
374
|
+
catch (error) {
|
|
375
|
+
throw fail(intent, new Error(`Plugin disabled; owned cleanup failed: ${error instanceof Error ? error.message : String(error)}`));
|
|
376
|
+
}
|
|
377
|
+
return { disabled: true, provider: current.ref, drained: true, ...inspect(taskId, pluginId) };
|
|
378
|
+
}
|
|
379
|
+
};
|
|
380
|
+
}
|
|
@@ -75,7 +75,7 @@ export function builtinAgentProfileInputs() {
|
|
|
75
75
|
{
|
|
76
76
|
id: "reviewer",
|
|
77
77
|
description: "Review one candidate against the user's core outcome, supported behavior, and direct evidence.",
|
|
78
|
-
instructions: "Start from user intent and acceptance criteria. Inspect the complete relevant change and report only reachable, material, actionable problems with direct evidence. Separate defects from verification gaps, and prefer the smallest sufficient correction. Follow the bound Project's Policy and Knowledge for build, test, migration, release, and review expectations; do not import rules from another Project or Task. For normal software delivery, review the frozen Task result as one final ReviewRound rather than inventing a per-WorkItem protocol unless the Project Policy explicitly requires one. A Task-final Round has no synthetic WorkItem anchor, and the Reviewer Session and physical workspace continue across changed-head Rounds without reusing an earlier verdict. The current
|
|
78
|
+
instructions: "Start from user intent and acceptance criteria. Inspect the complete relevant change and report only reachable, material, actionable problems with direct evidence. Separate defects from verification gaps, and prefer the smallest sufficient correction. Follow the bound Project's Policy and Knowledge for build, test, migration, release, and review expectations; do not import rules from another Project or Task. For normal software delivery, review the frozen Task result as one final ReviewRound rather than inventing a per-WorkItem protocol unless the Project Policy explicitly requires one. A Task-final Round has no synthetic WorkItem anchor, and the Reviewer Session and physical workspace continue across changed-head Rounds without reusing an earlier verdict. The current AgentRun's frozen candidate remains the only scope even if Task main advances. For Delta Recheck, return equivalent-and-accepted, finding, or requires-full-review with explicit reasoning; never create the next Round. Do not turn speculative or extreme edge cases into new state, retries, fallbacks, or protocol. In the Review workspace you may edit source or tests, run local checks, and optionally commit diagnostic evidence. Never push, integrate, mutate Task state, touch another workspace or stable checkout, or write the real Yui control-plane home. End the Provider turn with complete findings, checks actually run, uncertainty, and bounded next actions; Yui preserves the full free-form report automatically. Expose evidence and options to the Leader, who decides.",
|
|
79
79
|
defaultAccess: "write",
|
|
80
80
|
runtime: { source: "global-worker" }
|
|
81
81
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { execFile } from "node:child_process";
|
|
2
2
|
import { createHash, randomBytes } from "node:crypto";
|
|
3
|
-
import { lstat, mkdir, realpath, rm } from "node:fs/promises";
|
|
3
|
+
import { lstat, mkdir, readFile, realpath, rm } from "node:fs/promises";
|
|
4
4
|
import { dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
5
5
|
import { promisify } from "node:util";
|
|
6
6
|
const executeFile = promisify(execFile);
|
|
@@ -688,7 +688,8 @@ export class NodeGitWorkspace {
|
|
|
688
688
|
repositoryPath: input.repositoryPath,
|
|
689
689
|
container: input.container,
|
|
690
690
|
identity: integrationWorktreeIdentity(input.taskSegment, input.integrationId),
|
|
691
|
-
baseRef: input.baseRef
|
|
691
|
+
baseRef: input.baseRef,
|
|
692
|
+
allowRebase: true
|
|
692
693
|
});
|
|
693
694
|
}
|
|
694
695
|
async #ensureManagedWorktree(input) {
|
|
@@ -703,7 +704,7 @@ export class NodeGitWorkspace {
|
|
|
703
704
|
// original base branch/tag is later deleted.
|
|
704
705
|
const project = await this.inspect(input.repositoryPath);
|
|
705
706
|
await assertOwnedWorktree(project, container, path);
|
|
706
|
-
await assertExpectedBranch(path, identity.branch);
|
|
707
|
+
await assertExpectedBranch(path, identity.branch, input.allowRebase);
|
|
707
708
|
const head = await gitLine([
|
|
708
709
|
"-C", path, "rev-parse", "--verify", "--end-of-options", "HEAD^{commit}"
|
|
709
710
|
]);
|
|
@@ -917,7 +918,28 @@ function gitRefSegment(identity) {
|
|
|
917
918
|
const digest = createHash("sha256").update(identity).digest("hex").slice(0, 24);
|
|
918
919
|
return `encoded-${digest}`;
|
|
919
920
|
}
|
|
920
|
-
async function assertExpectedBranch(path, expected) {
|
|
921
|
+
async function assertExpectedBranch(path, expected, allowRebase = false) {
|
|
922
|
+
if (allowRebase && !await gitSucceeds(["-C", path, "symbolic-ref", "--quiet", "HEAD"])) {
|
|
923
|
+
// Rebase temporarily detaches HEAD. Git's per-worktree metadata must still
|
|
924
|
+
// identify this exact Integration branch; unrelated detachment is invalid.
|
|
925
|
+
for (const backend of ["rebase-merge", "rebase-apply"]) {
|
|
926
|
+
const headNamePath = await gitLine(["-C", path, "rev-parse", "--path-format=absolute",
|
|
927
|
+
"--git-path", `${backend}/head-name`]);
|
|
928
|
+
let headName;
|
|
929
|
+
try {
|
|
930
|
+
headName = (await readFile(headNamePath, "utf8")).trim();
|
|
931
|
+
}
|
|
932
|
+
catch (error) {
|
|
933
|
+
if (error.code === "ENOENT")
|
|
934
|
+
continue;
|
|
935
|
+
throw error;
|
|
936
|
+
}
|
|
937
|
+
if (headName === `refs/heads/${expected}`)
|
|
938
|
+
return;
|
|
939
|
+
throw new Error(`Managed worktree is rebasing an unexpected branch: ${headName}.`);
|
|
940
|
+
}
|
|
941
|
+
throw new Error("Managed worktree has detached HEAD without its expected rebase.");
|
|
942
|
+
}
|
|
921
943
|
const branch = await gitLine(["-C", path, "symbolic-ref", "--short", "HEAD"]);
|
|
922
944
|
if (branch !== expected) {
|
|
923
945
|
throw new Error(`Managed worktree is on an unexpected branch: ${branch}.`);
|
|
@@ -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: 6,
|
|
12
12
|
id: requireIdentity(id, "Project id"),
|
|
13
13
|
name: validateProjectName(name),
|
|
14
14
|
aliases: normalizeAliases(metadata.aliases ?? [], name),
|
|
@@ -21,6 +21,8 @@ export function createProject(id, name, path, branches, now, metadata = {}) {
|
|
|
21
21
|
developmentBranch: requireGitRef(branches.development, "Project development branch"),
|
|
22
22
|
status: "active",
|
|
23
23
|
knowledge: [],
|
|
24
|
+
resourceRefs: [],
|
|
25
|
+
defaultCapabilityProviders: {},
|
|
24
26
|
knowledgeProposals: [],
|
|
25
27
|
createdAt: timestamp,
|
|
26
28
|
updatedAt: timestamp
|
|
@@ -31,7 +33,7 @@ export function validateProjectName(value) {
|
|
|
31
33
|
}
|
|
32
34
|
/**
|
|
33
35
|
* Soft-retire a Project: mark it retired and record the audit trail. The
|
|
34
|
-
* catalog record, checkout, and every historical Task/
|
|
36
|
+
* catalog record, checkout, and every historical Task/AgentRun/Review/Integration/
|
|
35
37
|
* Publication reference are retained. Retiring an already-retired Project
|
|
36
38
|
* fails closed; a retired Project must be deleted (or kept) as-is.
|
|
37
39
|
*/
|
|
@@ -276,8 +278,21 @@ export function assertProjectCatalog(projects) {
|
|
|
276
278
|
}
|
|
277
279
|
}
|
|
278
280
|
export function validateProject(project) {
|
|
279
|
-
if (project.schemaVersion !==
|
|
280
|
-
throw new Error("Project must use schemaVersion
|
|
281
|
+
if (project.schemaVersion !== 6) {
|
|
282
|
+
throw new Error("Project must use schemaVersion 6.");
|
|
283
|
+
}
|
|
284
|
+
if (!Array.isArray(project.resourceRefs))
|
|
285
|
+
throw new Error("Project resourceRefs must be an array.");
|
|
286
|
+
for (const id of project.resourceRefs)
|
|
287
|
+
requireIdentity(id, "Project resource");
|
|
288
|
+
if (new Set(project.resourceRefs).size !== project.resourceRefs.length)
|
|
289
|
+
throw new Error("Duplicate Project resource.");
|
|
290
|
+
if (!project.defaultCapabilityProviders || typeof project.defaultCapabilityProviders !== "object"
|
|
291
|
+
|| Array.isArray(project.defaultCapabilityProviders))
|
|
292
|
+
throw new Error("Invalid Project capability defaults.");
|
|
293
|
+
for (const [name, provider] of Object.entries(project.defaultCapabilityProviders)) {
|
|
294
|
+
requireText(name, "Capability name");
|
|
295
|
+
requireText(provider, "Capability provider reference");
|
|
281
296
|
}
|
|
282
297
|
requireIdentity(project.id, "Project id");
|
|
283
298
|
requireIdentity(project.name, "Project name");
|
|
@@ -61,10 +61,10 @@ export async function inspectTaskBaseFreshness(taskId, store, options = {}) {
|
|
|
61
61
|
const task = requireTask(store, taskId);
|
|
62
62
|
const git = options.git ?? newGitWorkspace();
|
|
63
63
|
const workspace = store.getTaskWorkspace(taskId);
|
|
64
|
-
// Collect active
|
|
65
|
-
// report whether a live
|
|
66
|
-
const
|
|
67
|
-
.filter((
|
|
64
|
+
// Collect active AgentRun workspace snapshots once so every Project entry can
|
|
65
|
+
// report whether a live AgentRun is pinned to a different commit.
|
|
66
|
+
const activeRuns = store.listRuns(taskId)
|
|
67
|
+
.filter((run) => run.status === "active" && run.workspace !== undefined);
|
|
68
68
|
const entries = await Promise.all(task.projectBindings.map(async (binding) => {
|
|
69
69
|
const project = requireProject(store, binding.projectId);
|
|
70
70
|
const entry = workspace === null ? undefined : workspaceProjectEntry(workspace, project.id);
|
|
@@ -77,12 +77,12 @@ export async function inspectTaskBaseFreshness(taskId, store, options = {}) {
|
|
|
77
77
|
catch {
|
|
78
78
|
physicalHead = undefined;
|
|
79
79
|
}
|
|
80
|
-
// Find the first active
|
|
81
|
-
const
|
|
82
|
-
if (
|
|
80
|
+
// Find the first active AgentRun whose workspace snapshot covers this Project.
|
|
81
|
+
const runSnapshot = activeRuns.find((run) => {
|
|
82
|
+
if (run.workspace === undefined)
|
|
83
83
|
return false;
|
|
84
|
-
const
|
|
85
|
-
return
|
|
84
|
+
const runEntry = workspaceProjectEntry(run.workspace, project.id);
|
|
85
|
+
return runEntry !== undefined;
|
|
86
86
|
});
|
|
87
87
|
const provenance = latestBaseProvenance(store.listEvents(taskId), project.id);
|
|
88
88
|
const tracked = await resolveTracked(git, project, workspacePath, options.refresh === true);
|
|
@@ -99,11 +99,11 @@ export async function inspectTaskBaseFreshness(taskId, store, options = {}) {
|
|
|
99
99
|
workspacePath,
|
|
100
100
|
workspaceClean,
|
|
101
101
|
...(physicalHead === undefined ? {} : { physicalHead }),
|
|
102
|
-
...(
|
|
102
|
+
...(runSnapshot?.workspace === undefined
|
|
103
103
|
? {}
|
|
104
104
|
: {
|
|
105
|
-
|
|
106
|
-
|
|
105
|
+
runSnapshotCommit: workspaceProjectEntry(runSnapshot.workspace, project.id)?.baseCommit,
|
|
106
|
+
runSnapshotId: runSnapshot.id
|
|
107
107
|
}),
|
|
108
108
|
...(tracked === undefined ? {} : {
|
|
109
109
|
trackedRef: tracked.ref,
|
|
@@ -161,7 +161,7 @@ export function renderTaskBaseFreshnessReport(report) {
|
|
|
161
161
|
`Task base freshness: ${report.taskId} (source: ${report.refreshed ? "remote refresh" : "local tracking"})`
|
|
162
162
|
];
|
|
163
163
|
for (const entry of report.entries) {
|
|
164
|
-
lines.push(`- ${entry.directory}: ${entry.projectId} @ ${entry.baseCommit}`, ` status: ${entry.status}; workspace: ${entry.workspaceClean === null ? "unknown" : entry.workspaceClean ? "clean" : "dirty"}`, ` physical HEAD: ${entry.physicalHead ?? "-"}`, `
|
|
164
|
+
lines.push(`- ${entry.directory}: ${entry.projectId} @ ${entry.baseCommit}`, ` status: ${entry.status}; workspace: ${entry.workspaceClean === null ? "unknown" : entry.workspaceClean ? "clean" : "dirty"}`, ` physical HEAD: ${entry.physicalHead ?? "-"}`, ` AgentRun snapshot: ${entry.runSnapshotCommit ?? "-"}${entry.runSnapshotId === undefined ? "" : ` (${entry.runSnapshotId})`}`, ` observed remote: ${entry.observedRemoteUrl ?? "-"}`, ` tracked: ${entry.trackedCommit ?? "-"}${entry.trackedRef === undefined ? "" : ` (${entry.trackedRef})`}`, ` observed: ${entry.observedTrackingCommit ?? "-"}${entry.observedAt === undefined ? "" : ` at ${entry.observedAt}`}`);
|
|
165
165
|
if (entry.risk !== undefined)
|
|
166
166
|
lines.push(` risk: ${entry.risk}`);
|
|
167
167
|
if (entry.error !== undefined)
|