@zq-silk/yui 0.15.12 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ARCHITECTURE.md +3 -2
- package/ARCHITECTURE.zh-CN.md +3 -2
- package/dist/artifacts/artifactCommitLock.js +16 -55
- package/dist/artifacts/taskArtifactRepository.js +3 -3
- package/dist/cli/agentConfigurationPicker.js +1 -1
- package/dist/cli/commandCatalog.js +57 -41
- package/dist/cli/completion.js +18 -18
- package/dist/cli/completionWizard.js +1 -1
- package/dist/cli/dynamicCompletion.js +1 -1
- package/dist/cli/interactionPolicy.js +7 -3
- package/dist/cli/invocationAuthority.js +64 -0
- package/dist/cli/managedDiagnostics.js +1 -1
- package/dist/cli/parseRepeatable.js +35 -0
- package/dist/cli/updatePorts.js +26 -5
- package/dist/cli.js +1280 -1243
- package/dist/commands/agentCommands.js +4 -4
- package/dist/commands/capabilityCommands.js +1 -1
- package/dist/commands/configCommands.js +6 -57
- package/dist/commands/configOverview.js +2 -2
- package/dist/commands/durableJobCommands.js +12 -6
- package/dist/commands/globalRoleCommands.js +53 -29
- package/dist/commands/grantCommands.js +0 -3
- package/dist/commands/jobCommands.js +1 -4
- package/dist/commands/operatorCommands.js +3 -3
- package/dist/commands/projectCommands.js +1 -10
- package/dist/commands/resourcesCommands.js +10 -40
- package/dist/commands/roleConfiguration.js +2 -6
- package/dist/commands/taskActivationCommands.js +4 -8
- package/dist/commands/taskAgentCapabilities.js +9 -0
- package/dist/commands/taskCommandSupport.js +155 -0
- package/dist/commands/taskCommandTypes.js +1 -0
- package/dist/commands/taskCommands.js +271 -710
- package/dist/commands/taskCompletionGate.js +1 -1
- package/dist/commands/taskExecutionCommands.js +1 -1
- package/dist/commands/taskFactCommands.js +325 -0
- package/dist/commands/taskInputCommands.js +12 -42
- package/dist/commands/taskIntegrationCommands.js +17 -57
- package/dist/commands/taskNextActionCommand.js +7 -6
- package/dist/commands/taskPublicationAdoptCommand.js +3 -3
- package/dist/commands/taskPublicationCommands.js +1 -1
- package/dist/commands/taskPublicationVerifyCommand.js +1 -1
- package/dist/commands/taskRemoteDeliveryCommand.js +2 -94
- package/dist/commands/taskUpstreamCommands.js +19 -8
- package/dist/commands/workflowCommands.js +6 -1
- package/dist/completion/completionInstaller.js +26 -26
- package/dist/completion/completionState.js +26 -26
- package/dist/completion/fileCompletionManager.js +6 -11
- package/dist/config/configCatalog.js +0 -2
- package/dist/config/timeZone.js +14 -0
- package/dist/config/yuiConfig.js +0 -29
- package/dist/context/runContextPack.js +2 -3
- package/dist/context/taskCatalog.js +3 -5
- package/dist/context/taskContext.js +139 -151
- package/dist/context/wakeRunReferences.js +11 -0
- package/dist/controller/agentCapabilities.js +58 -0
- package/dist/controller/agentRuntimeObserver.js +6 -11
- package/dist/controller/clientRuntime.js +22 -14
- package/dist/controller/controller.js +9 -22
- package/dist/controller/fileSchedulerStoreAdapter.js +117 -212
- package/dist/controller/jobClient.js +4 -4
- package/dist/controller/jobControl.js +60 -25
- package/dist/controller/jobSupervisor.js +4 -4
- package/dist/controller/providerRetryDelivery.js +7 -7
- package/dist/controller/runtime.js +51 -44
- package/dist/controller/runtimeEventProcessor.js +0 -5
- package/dist/controller/sessionOwnerReconciliation.js +5 -0
- package/dist/controller/taskAgentError.js +35 -0
- package/dist/coordination/workMailboxQueue.js +2 -24
- package/dist/core/controllerClient.js +2 -2
- package/dist/core/fileLockOwner.js +74 -0
- package/dist/decision/decision.js +17 -0
- package/dist/doctor/doctor.js +18 -9
- package/dist/event/taskEvent.js +12 -0
- package/dist/execution/workItemExecutionProjection.js +2 -2
- package/dist/executor/agentCapabilityConfig.js +43 -0
- package/dist/executor/agentConfigurationCatalog.js +49 -11
- package/dist/executor/agentExecutor.js +5 -8
- package/dist/executor/fileRoleLaunchPlanner.js +11 -53
- package/dist/executor/taskAgentCapabilities.js +64 -0
- package/dist/integration/deliveryObligation.js +1 -69
- package/dist/integration/gitIntegrationService.js +60 -84
- package/dist/integration/integrationAttempt.js +3 -14
- package/dist/integration/integrationSourceApplication.js +4 -12
- package/dist/integration/manifestTags.js +2 -2
- package/dist/job/durableJob.js +4 -25
- package/dist/job/jobAssignmentScope.js +22 -0
- package/dist/job/jobOperation.js +16 -0
- package/dist/job/jobRunner.js +2 -1
- package/dist/job/stepDirectory.js +15 -0
- package/dist/kernel/builtinCapabilities.js +15 -11
- package/dist/kernel/kernelPorts.js +1 -17
- package/dist/lifecycle/exactRunTerminalization.js +1 -9
- package/dist/message/inputControlResolution.js +3 -4
- package/dist/message/message.js +17 -20
- package/dist/message/messageContinuation.js +1 -1
- package/dist/milestone/milestone.js +11 -0
- package/dist/observability/orchestrationMetrics.js +24 -1
- package/dist/observability/runtimeIdentity.js +1 -17
- package/dist/output/agentConfigurationPresentation.js +2 -0
- package/dist/output/timePresentation.js +1 -14
- package/dist/plugins/pluginService.js +12 -4
- package/dist/release/releaseWorkflowPorts.js +45 -96
- package/dist/release/runtimeRelease.js +9 -15
- package/dist/repository/projectMaintenanceLock.js +23 -64
- package/dist/repository/taskBaseFreshness.js +1 -1
- package/dist/repository/taskWorkspaceCoordinator.js +13 -22
- package/dist/repository/taskWorkspacePreparer.js +29 -25
- package/dist/repository/workspaceCleanupInspection.js +5 -5
- package/dist/resources/autoResourceGc.js +4 -77
- package/dist/resources/liveReferences.js +7 -54
- package/dist/resources/resourceDiscovery.js +5 -118
- package/dist/resources/resourceGc.js +233 -181
- package/dist/resources/resourceRegistrar.js +5 -4
- package/dist/resources/resourceRegistry.js +3 -37
- package/dist/resources/resourceRegistryStore.js +13 -36
- package/dist/resources/sqliteResourceRegistry.js +43 -26
- package/dist/review/deltaRecheck.js +1 -1
- package/dist/review/reviewAcceptance.js +1 -1
- package/dist/review/reviewDecision.js +1 -1
- package/dist/review/reviewRound.js +8 -7
- package/dist/review/taskFinalReviewContractResolution.js +1 -1
- package/dist/runtime/acpProtocol.js +4 -51
- package/dist/runtime/acpSession.js +10 -75
- package/dist/runtime/acpSessionConfiguration.js +1 -8
- package/dist/runtime/agentEndpoint.js +3 -2
- package/dist/runtime/agentFailureContext.js +43 -0
- package/dist/runtime/agentHost.js +15 -15
- package/dist/runtime/codexInteractiveHost.js +2 -2
- package/dist/runtime/index.js +0 -1
- package/dist/runtime/managedCaller.js +21 -1
- package/dist/runtime/providerErrors.js +38 -0
- package/dist/runtime/runtimeObservation.js +0 -5
- package/dist/runtime/runtimeSessionCandidate.js +2 -3
- package/dist/runtime/sessionOwnerIdentity.js +1 -1
- package/dist/runtime/structuredProviderHost.js +4 -41
- package/dist/runtime/tmuxAdapters.js +6 -8
- package/dist/scheduler/activeRoleRunDelivery.js +7 -7
- package/dist/scheduler/leaderWakeupProcessor.js +19 -6
- package/dist/scheduler/operatorEvent.js +12 -20
- package/dist/scheduler/operatorInputNotificationProcessor.js +1 -1
- package/dist/scheduler/ports.js +5 -8
- package/dist/scheduler/roleRunLiveness.js +4 -4
- package/dist/scheduler/roleRunStall.js +16 -23
- package/dist/scheduler/taskExecutionProjection.js +25 -33
- package/dist/scheduler/taskObservabilityProjection.js +0 -1
- package/dist/scheduler/taskWake.js +5 -10
- package/dist/scheduler/wakeReason.js +2 -0
- package/dist/scheduler/wakeupQueue.js +2 -4
- package/dist/setup/setupCommand.js +1 -1
- package/dist/storage/contextRecords.js +106 -0
- package/dist/storage/currentTaskStore.js +1 -1
- package/dist/storage/homeLayout.js +13 -17
- package/dist/storage/migrations/agentFailureContext.js +22 -0
- package/dist/storage/migrations/currentInputContract.js +86 -0
- package/dist/storage/migrations/currentRuntimeContract.js +228 -0
- package/dist/storage/migrations/historicalVerificationPlan.js +35 -0
- package/dist/storage/migrations/integrationContinuation.js +5 -4
- package/dist/storage/migrations/narrowAgentFailureContext.js +65 -0
- package/dist/storage/migrations/notificationOnlyWakes.js +74 -0
- package/dist/storage/migrations/verificationPlanV1.js +162 -0
- package/dist/storage/migrations/verificationPolicy.js +74 -0
- package/dist/storage/migrations/workItemHistory.js +46 -0
- package/dist/storage/persistenceWorker.js +12 -4
- package/dist/storage/recordValidation.js +87 -0
- package/dist/storage/sqliteSchema.js +142 -1
- package/dist/storage/sqliteStore.js +156 -162
- package/dist/storage/storageSchema.js +2 -14
- package/dist/storage/storageVersions.js +1 -1
- package/dist/storage/storeRpc.js +15 -7
- package/dist/storage/taskStore.js +3 -7
- package/dist/storage/upgrade/upgradeOrchestrator.js +25 -37
- package/dist/task/completionReadiness.js +5 -24
- package/dist/task/draftPlan.js +0 -53
- package/dist/task/nextAction.js +7 -13
- package/dist/task/remoteDeliveryService.js +89 -0
- package/dist/task/taskActivation.js +2 -25
- package/dist/task/taskActivationService.js +0 -2
- package/dist/task/taskRecordReference.js +0 -1
- package/dist/task/taskSubmission.js +3 -14
- package/dist/telemetry/sqliteTelemetryBatch.js +29 -0
- package/dist/telemetry/sqliteTelemetryStore.js +49 -49
- package/dist/telemetry/telemetryWiring.js +4 -3
- package/dist/tmux/tmuxManager.js +29 -20
- package/dist/verification/gateArtifact.js +15 -24
- package/dist/verification/gateArtifactStore.js +14 -7
- package/dist/verification/verificationGateService.js +29 -88
- package/dist/verification/verificationPlan.js +27 -72
- package/dist/web/assets/client/app.js +1 -1
- package/dist/web/assets/client/taskSurface.js +4 -4
- package/dist/web/webServer.js +4 -6
- package/dist/web/webSnapshot.js +3 -83
- package/dist/web/webTaskSurface.js +17 -46
- package/dist/workItem/workItem.js +1 -12
- package/docs/architecture/capabilities-and-resources.md +14 -2
- package/docs/architecture/capabilities-and-resources.zh-CN.md +11 -1
- package/docs/managed-turn-and-session-runtime.md +83 -10
- package/docs/managed-turn-and-session-runtime.zh-CN.md +65 -11
- package/docs/plugin-sdk.md +4 -2
- package/docs/provider-runtime.md +3 -1
- package/docs/provider-runtime.zh-CN.md +4 -2
- package/docs/release-workflow.md +190 -7
- package/docs/release-workflow.zh-CN.md +139 -5
- package/docs/roles-and-configuration.md +29 -0
- package/docs/roles-and-configuration.zh-CN.md +21 -0
- package/docs/sqlite-control-plane-design.md +16 -3
- package/docs/sqlite-control-plane-design.zh-CN.md +13 -2
- package/docs/task-dag-semantics.md +3 -3
- package/docs/task-dag-semantics.zh-CN.md +2 -2
- package/docs/task-delivery.md +91 -5
- package/docs/task-delivery.zh-CN.md +72 -4
- package/docs/task-discovery.md +10 -11
- package/docs/task-discovery.zh-CN.md +8 -9
- package/docs/testing/verification-levels.md +145 -9
- package/docs/testing/verification-levels.zh-CN.md +103 -8
- package/package.json +1 -1
- package/skills/yui-leader/SKILL.md +5 -0
- package/skills/yui-leader/references/integration.md +9 -5
- package/skills/yui-leader/references/planning.md +9 -3
- package/skills/yui-operator/SKILL.md +15 -10
- package/skills/yui-runtime/SKILL.md +6 -6
- package/skills/yui-runtime/references/recovery.md +47 -0
- package/dist/agent/agentRegistry.js +0 -10
- package/dist/agentRun/runIdentity.js +0 -22
- package/dist/commands/deliveryGuardPreflight.js +0 -30
- package/dist/commands/taskIntegrationQueueCommands.js +0 -226
- package/dist/commands/taskOverviewCommand.js +0 -377
- package/dist/completion/completionWizard.js +0 -125
- package/dist/context/dispatchContext.js +0 -110
- package/dist/context/wakeNotification.js +0 -144
- package/dist/integration/integrationCheckEvidenceReuse.js +0 -53
- package/dist/integration/integrationQueueEntry.js +0 -191
- package/dist/integration/integrationQueueService.js +0 -810
- package/dist/release/fakeReleasePorts.js +0 -55
- package/dist/runtime/sessionOwnerRegistry.js +0 -137
- package/dist/task/deliveryGuard.js +0 -226
- /package/dist/{commands/taskActor.js → task/taskAuthority.js} +0 -0
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { lstatSync, readFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
/** One process generation, shared by file-lock producers and reclaim checks. */
|
|
4
|
+
export function currentFileLockOwner() {
|
|
5
|
+
return `${process.pid}:${readProcessStart(process.pid)}`;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* False requires proof of absence or a different process generation.
|
|
9
|
+
* Missing publication, invalid identity and unavailable OS evidence are not
|
|
10
|
+
* dead owners: callers retain the lock and report the bounded diagnosis.
|
|
11
|
+
*/
|
|
12
|
+
export function fileLockOwnerIsLive(lock) {
|
|
13
|
+
let owner;
|
|
14
|
+
try {
|
|
15
|
+
owner = readFileSync(join(lock, "owner"), "utf8").trim();
|
|
16
|
+
}
|
|
17
|
+
catch (error) {
|
|
18
|
+
if (hasCode(error, "ENOENT")) {
|
|
19
|
+
try {
|
|
20
|
+
lstatSync(lock);
|
|
21
|
+
}
|
|
22
|
+
catch (statError) {
|
|
23
|
+
if (hasCode(statError, "ENOENT"))
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
throw unverified(lock);
|
|
28
|
+
}
|
|
29
|
+
const match = /^([1-9][0-9]*):(0|[1-9][0-9]{0,31})$/u.exec(owner);
|
|
30
|
+
const pid = Number(match?.[1]);
|
|
31
|
+
if (match === null || !Number.isSafeInteger(pid))
|
|
32
|
+
throw unverified(lock);
|
|
33
|
+
try {
|
|
34
|
+
return processGenerationIsLive(pid, match[2]);
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
throw unverified(lock);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/** A known generation is live, provably gone, or unverified (throws). */
|
|
41
|
+
export function processGenerationIsLive(pid, startIdentity) {
|
|
42
|
+
if (!Number.isSafeInteger(pid) || pid < 1 || !/^(0|[1-9][0-9]{0,31})$/u.test(startIdentity)) {
|
|
43
|
+
throw new Error(`Unverified process generation for PID ${pid}.`);
|
|
44
|
+
}
|
|
45
|
+
try {
|
|
46
|
+
return readProcessStart(pid) === startIdentity;
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
// PID liveness can prove a process is gone, never prove its generation.
|
|
50
|
+
try {
|
|
51
|
+
process.kill(pid, 0);
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
if (hasCode(error, "ESRCH"))
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
throw new Error(`Unverified process generation for PID ${pid}; OS identity could not be read.`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
function readProcessStart(pid) {
|
|
61
|
+
const stat = readFileSync(`/proc/${pid}/stat`, "utf8");
|
|
62
|
+
const close = stat.lastIndexOf(")");
|
|
63
|
+
const start = close < 0 ? undefined : stat.slice(close + 2).trim().split(/\s+/u)[19];
|
|
64
|
+
if (start === undefined || !/^(0|[1-9][0-9]{0,31})$/u.test(start)) {
|
|
65
|
+
throw new Error(`Process start identity is unavailable for PID ${pid}.`);
|
|
66
|
+
}
|
|
67
|
+
return start;
|
|
68
|
+
}
|
|
69
|
+
function unverified(lock) {
|
|
70
|
+
return new Error(`Unverified lock owner identity: ${lock}. Preserve the lock and inspect its owner before cleanup.`);
|
|
71
|
+
}
|
|
72
|
+
function hasCode(error, code) {
|
|
73
|
+
return error instanceof Error && "code" in error && error.code === code;
|
|
74
|
+
}
|
|
@@ -1,4 +1,21 @@
|
|
|
1
1
|
import { validateTaskRecordReference } from "../task/taskRecordReference.js";
|
|
2
|
+
import { requireTimestamp } from "../domain/validation.js";
|
|
3
|
+
export function validateDecision(decision) {
|
|
4
|
+
if (decision.schemaVersion !== 1)
|
|
5
|
+
throw new Error("Decision must use schemaVersion 1.");
|
|
6
|
+
validateTaskRecordReference({ taskId: decision.taskId, localId: decision.id }, "decision");
|
|
7
|
+
requireText(decision.title, "Decision title");
|
|
8
|
+
requireText(decision.rationale, "Decision rationale");
|
|
9
|
+
requireTimestamp(decision.createdAt, "Decision createdAt");
|
|
10
|
+
requireTimestamp(decision.updatedAt, "Decision updatedAt");
|
|
11
|
+
if (decision.status !== "active" && decision.status !== "superseded")
|
|
12
|
+
throw new Error("Decision status is invalid.");
|
|
13
|
+
if (decision.status === "superseded") {
|
|
14
|
+
requireText(decision.supersededReason, "Decision supersede reason");
|
|
15
|
+
requireTimestamp(decision.supersededAt, "Decision supersededAt");
|
|
16
|
+
}
|
|
17
|
+
return decision;
|
|
18
|
+
}
|
|
2
19
|
export function createDecision(id, taskId, title, rationale, now) {
|
|
3
20
|
const timestamp = now.toISOString();
|
|
4
21
|
return {
|
package/dist/doctor/doctor.js
CHANGED
|
@@ -3,20 +3,20 @@ import { isAbsolute, join } from "node:path";
|
|
|
3
3
|
import Database from "better-sqlite3";
|
|
4
4
|
import { configuredAgentToDefinition, resolveAgentEnvironment } from "../agent/agent.js";
|
|
5
5
|
import { operationalAgentEnvironment } from "../agent/launchEnvironment.js";
|
|
6
|
+
import { resolveTmuxBin } from "../config/yuiConfig.js";
|
|
7
|
+
import { compileRoleSessionContext } from "../context/roleSessionContext.js";
|
|
8
|
+
import { EPHEMERAL_DOMAIN_GRACE_MS, readEphemeralDomainIdentity, readLinuxProcessStartIdentity } from "../controller/domainIdentity.js";
|
|
9
|
+
import { usageError } from "../errors/cliError.js";
|
|
6
10
|
import { inspectAgentCapabilities, resolveAgentAdapter } from "../executor/agentAdapter.js";
|
|
7
11
|
import { assertCodexLaunchOverridesAvailable, inspectCodexLaunchConfig } from "../executor/codexConfigConflict.js";
|
|
8
|
-
import { nativeAdditionalDirectories, nativeAgentWorkspace, withNativeProjectDirectories } from "../executor/fileRoleLaunchPlanner.js";
|
|
9
12
|
import { resolveEffectiveLaunch } from "../executor/effectiveLaunch.js";
|
|
10
|
-
import {
|
|
11
|
-
import { usageError } from "../errors/cliError.js";
|
|
13
|
+
import { nativeAdditionalDirectories, nativeAgentWorkspace, withNativeProjectDirectories } from "../executor/fileRoleLaunchPlanner.js";
|
|
12
14
|
import { defaultTableWidth, renderTable } from "../output/table.js";
|
|
13
|
-
import { resolveYuiHome } from "../storage/taskStore.js";
|
|
14
15
|
import { CURRENT_DATABASE_FILENAME, openCurrentTaskStore } from "../storage/currentTaskStore.js";
|
|
15
16
|
import { inspectStorageSchema } from "../storage/storageSchema.js";
|
|
16
17
|
import { resolveStoreWorkerEnabledForHome } from "../storage/storeRpc.js";
|
|
17
|
-
import {
|
|
18
|
+
import { resolveYuiHome } from "../storage/taskStore.js";
|
|
18
19
|
import { CommandExecutionError } from "../tmux/commandExecutor.js";
|
|
19
|
-
import { EPHEMERAL_DOMAIN_GRACE_MS, readEphemeralDomainIdentity, readLinuxProcessStartIdentity } from "../controller/domainIdentity.js";
|
|
20
20
|
/**
|
|
21
21
|
* The storage-relevant doctor checks, in the order they are produced. Post-update
|
|
22
22
|
* verification (P1-3) keys off exactly these: a green `yui doctor` for the update
|
|
@@ -243,7 +243,7 @@ function checkSchema(state) {
|
|
|
243
243
|
}
|
|
244
244
|
}
|
|
245
245
|
/** Diagnose admission to the exact current storage contract without repair. */
|
|
246
|
-
function inspectCompatibility(
|
|
246
|
+
function inspectCompatibility(_home, homeCheck, schema) {
|
|
247
247
|
const name = "storage compatibility";
|
|
248
248
|
if (homeCheck.status !== "ok") {
|
|
249
249
|
return { check: { name, status: homeCheck.status, detail: homeCheck.detail } };
|
|
@@ -311,8 +311,10 @@ function inspectState(home, homeCheck, schema, compatibility) {
|
|
|
311
311
|
if (!existsSync(databasePath)) {
|
|
312
312
|
return blockedStorage("invalid", "Current storage is incomplete: yui.db is missing.");
|
|
313
313
|
}
|
|
314
|
+
let ownedStore;
|
|
314
315
|
try {
|
|
315
|
-
const store = openCurrentTaskStore(home);
|
|
316
|
+
const store = ownedStore = openCurrentTaskStore(home);
|
|
317
|
+
store.validateCurrentRecords();
|
|
316
318
|
const config = store.getConfig();
|
|
317
319
|
const agents = store.listConfiguredAgents();
|
|
318
320
|
const tasks = store.listTasks();
|
|
@@ -346,6 +348,9 @@ function inspectState(home, homeCheck, schema, compatibility) {
|
|
|
346
348
|
catch (error) {
|
|
347
349
|
return blockedStorage("invalid", errorMessage(error));
|
|
348
350
|
}
|
|
351
|
+
finally {
|
|
352
|
+
ownedStore?.close();
|
|
353
|
+
}
|
|
349
354
|
}
|
|
350
355
|
function storageBlockerDetail(check) {
|
|
351
356
|
const corruptionPrefix = "unsupported (CORRUPTED): ";
|
|
@@ -755,8 +760,9 @@ function errorMessage(error) {
|
|
|
755
760
|
* the Home store cannot be opened (the doctor must still run on broken Homes).
|
|
756
761
|
*/
|
|
757
762
|
function readDurableConfigSafely(home) {
|
|
763
|
+
let ownedStore;
|
|
758
764
|
try {
|
|
759
|
-
const store = openCurrentTaskStore(home);
|
|
765
|
+
const store = ownedStore = openCurrentTaskStore(home);
|
|
760
766
|
const config = store.getConfig();
|
|
761
767
|
return {
|
|
762
768
|
tmuxBin: resolveTmuxBin(config.tmuxBin),
|
|
@@ -766,4 +772,7 @@ function readDurableConfigSafely(home) {
|
|
|
766
772
|
catch {
|
|
767
773
|
return { tmuxBin: "tmux", gitBin: "git" };
|
|
768
774
|
}
|
|
775
|
+
finally {
|
|
776
|
+
ownedStore?.close();
|
|
777
|
+
}
|
|
769
778
|
}
|
package/dist/event/taskEvent.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { validateTaskRecordReference } from "../task/taskRecordReference.js";
|
|
2
|
+
import { requireTimestamp } from "../domain/validation.js";
|
|
2
3
|
export function createTaskEvent(id, taskId, type, payload, now) {
|
|
3
4
|
const event = {
|
|
4
5
|
schemaVersion: 2,
|
|
@@ -8,7 +9,18 @@ export function createTaskEvent(id, taskId, type, payload, now) {
|
|
|
8
9
|
payload: normalizePayload(payload),
|
|
9
10
|
createdAt: now.toISOString()
|
|
10
11
|
};
|
|
12
|
+
return validateTaskEvent(event);
|
|
13
|
+
}
|
|
14
|
+
export function validateTaskEvent(event) {
|
|
15
|
+
if (event.schemaVersion !== 2)
|
|
16
|
+
throw new Error("Task Event must use schemaVersion 2.");
|
|
11
17
|
validateTaskRecordReference({ taskId: event.taskId, localId: event.id }, "event");
|
|
18
|
+
requireText(event.type, "Task event type");
|
|
19
|
+
if (event.payload === null || typeof event.payload !== "object" || Array.isArray(event.payload)) {
|
|
20
|
+
throw new Error("Task event payload must be an object.");
|
|
21
|
+
}
|
|
22
|
+
normalizePayload(event.payload);
|
|
23
|
+
requireTimestamp(event.createdAt, "Task event createdAt");
|
|
12
24
|
return event;
|
|
13
25
|
}
|
|
14
26
|
/**
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { governingWorkItemCandidate, currentWorkItemExecutionGroup } from "../workItem/workItem.js";
|
|
2
1
|
import { synthesisSourceRunIds } from "../context/runContextPack.js";
|
|
2
|
+
import { currentWorkItemExecutionGroup, governingWorkItemCandidate } from "../workItem/workItem.js";
|
|
3
3
|
export function projectWorkItemExecution(item, runs, sessionSets = [], sourceStore) {
|
|
4
4
|
const group = currentWorkItemExecutionGroup(item);
|
|
5
5
|
const relevantRuns = runs.filter((run) => run.workItemId === item.id);
|
|
@@ -164,7 +164,7 @@ function projectMainRun(item, group, runs, sessionsByRole) {
|
|
|
164
164
|
observation: run.result === undefined ? "unobserved" : "observed"
|
|
165
165
|
});
|
|
166
166
|
}
|
|
167
|
-
function projectSynthesis(group,
|
|
167
|
+
function projectSynthesis(group, _lanes, mainRun) {
|
|
168
168
|
if (group === undefined) {
|
|
169
169
|
return Object.freeze({
|
|
170
170
|
status: "not-applicable",
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { isAbsolute } from "node:path";
|
|
2
|
+
import { isAgentAdapterId } from "../agent/adapterCatalog.js";
|
|
3
|
+
/** Project an existing Role/launch config without changing its native values. */
|
|
4
|
+
export function projectAgentCapabilityConfig(config) {
|
|
5
|
+
return validateAgentCapabilityConfig({
|
|
6
|
+
adapterId: config.adapterId,
|
|
7
|
+
...(config.model === undefined ? {} : { model: config.model }),
|
|
8
|
+
...(config.effort === undefined ? {} : { effort: config.effort }),
|
|
9
|
+
...(config.adapterId !== "codex" || config.profile === undefined ? {} : { profile: config.profile }),
|
|
10
|
+
...(config.adapterId !== "claude" || config.settingsFile === undefined ? {} : { settingsFile: config.settingsFile }),
|
|
11
|
+
...(config.adapterId !== "claude" || config.settingsSources === undefined ? {} : { settingsSources: [...config.settingsSources] })
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
export function validateAgentCapabilityConfig(value) {
|
|
15
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
16
|
+
throw new Error("Agent capability configuration is invalid.");
|
|
17
|
+
}
|
|
18
|
+
const config = value;
|
|
19
|
+
if (!isAgentAdapterId(config.adapterId))
|
|
20
|
+
throw new Error("Agent capability adapter is invalid.");
|
|
21
|
+
const keys = ["adapterId", "model", "effort",
|
|
22
|
+
...(config.adapterId === "codex" ? ["profile"] : []),
|
|
23
|
+
...(config.adapterId === "claude" ? ["settingsFile", "settingsSources"] : [])];
|
|
24
|
+
if (Object.keys(config).some(key => !keys.includes(key))) {
|
|
25
|
+
throw new Error("Agent capability configuration contains execution-only or unsupported fields.");
|
|
26
|
+
}
|
|
27
|
+
for (const key of ["model", "effort", "profile", "settingsFile"]) {
|
|
28
|
+
const field = config[key];
|
|
29
|
+
if (field !== undefined && (typeof field !== "string" || field.trim().length === 0 || field.includes("\0"))) {
|
|
30
|
+
throw new Error(`Agent capability ${key} is invalid.`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
if (config.settingsFile !== undefined && !isAbsolute(config.settingsFile)) {
|
|
34
|
+
throw new Error("Agent capability settings file must be absolute.");
|
|
35
|
+
}
|
|
36
|
+
const sources = config.settingsSources;
|
|
37
|
+
if (sources !== undefined && (!Array.isArray(sources)
|
|
38
|
+
|| sources.some(source => typeof source !== "string" || source.trim().length === 0 || source.includes("\0"))
|
|
39
|
+
|| new Set(sources).size !== sources.length)) {
|
|
40
|
+
throw new Error("Agent capability settings sources are invalid.");
|
|
41
|
+
}
|
|
42
|
+
return config;
|
|
43
|
+
}
|
|
@@ -4,6 +4,7 @@ import { homedir } from "node:os";
|
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { writeTextFileAtomically } from "../storage/durableFile.js";
|
|
6
6
|
import { resolveAgentAdapter } from "./agentAdapter.js";
|
|
7
|
+
import { redactAgentErrorText } from "../runtime/agentError.js";
|
|
7
8
|
const DEFAULT_TIMEOUT_MS = 8_000;
|
|
8
9
|
export class AgentConfigurationCatalogService {
|
|
9
10
|
yuiHome;
|
|
@@ -11,20 +12,39 @@ export class AgentConfigurationCatalogService {
|
|
|
11
12
|
#timeoutMs;
|
|
12
13
|
#now;
|
|
13
14
|
#discover;
|
|
15
|
+
#memoryCacheLimit;
|
|
14
16
|
#requests = new Map();
|
|
17
|
+
#completed = new Map();
|
|
15
18
|
constructor(yuiHome, options = {}) {
|
|
16
19
|
this.yuiHome = yuiHome;
|
|
17
20
|
this.#environment = options.environment ?? process.env;
|
|
18
21
|
this.#timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
19
22
|
this.#now = options.now ?? (() => new Date());
|
|
20
23
|
this.#discover = options.discover ?? ((input) => resolveAgentAdapter(input.agent.adapterId).discoverConfiguration(input));
|
|
24
|
+
this.#memoryCacheLimit = options.memoryCacheLimit ?? 64;
|
|
25
|
+
if (!Number.isSafeInteger(this.#memoryCacheLimit) || this.#memoryCacheLimit < 1) {
|
|
26
|
+
throw new Error("Agent metadata memory cache limit must be positive.");
|
|
27
|
+
}
|
|
21
28
|
}
|
|
22
29
|
resolve(input) {
|
|
23
30
|
const fingerprint = catalogFingerprint(input, this.#environment);
|
|
24
|
-
const
|
|
25
|
-
if (
|
|
26
|
-
return
|
|
27
|
-
const
|
|
31
|
+
const active = this.#requests.get(fingerprint);
|
|
32
|
+
if (active !== undefined)
|
|
33
|
+
return active;
|
|
34
|
+
const cached = this.#completed.get(fingerprint);
|
|
35
|
+
if (cached !== undefined && input.refresh !== true) {
|
|
36
|
+
this.#completed.delete(fingerprint);
|
|
37
|
+
this.#completed.set(fingerprint, cached);
|
|
38
|
+
return Promise.resolve(cached.source === "live" ? { ...cached, source: "cache" } : cached);
|
|
39
|
+
}
|
|
40
|
+
const request = this.#resolve(input, fingerprint).then(result => {
|
|
41
|
+
this.#completed.delete(fingerprint);
|
|
42
|
+
this.#completed.set(fingerprint, result);
|
|
43
|
+
while (this.#completed.size > this.#memoryCacheLimit) {
|
|
44
|
+
this.#completed.delete(this.#completed.keys().next().value);
|
|
45
|
+
}
|
|
46
|
+
return result;
|
|
47
|
+
}).finally(() => { this.#requests.delete(fingerprint); });
|
|
28
48
|
this.#requests.set(fingerprint, request);
|
|
29
49
|
return request;
|
|
30
50
|
}
|
|
@@ -174,24 +194,42 @@ export function defaultModel(catalog) {
|
|
|
174
194
|
export function modelChoice(catalog, value) {
|
|
175
195
|
return value === undefined
|
|
176
196
|
? defaultModel(catalog)
|
|
177
|
-
: catalog.models.find((model) => model.value === value)
|
|
197
|
+
: catalog.models.find((model) => model.value === value)
|
|
198
|
+
?? catalog.models.find((model) => model.resolvedModel === value && !model.isDefault)
|
|
199
|
+
?? catalog.models.find((model) => model.resolvedModel === value);
|
|
200
|
+
}
|
|
201
|
+
/** Observed choices, not an invented complete list of a Provider's model IDs. */
|
|
202
|
+
export function agentModelOptionsSummary(catalog) {
|
|
203
|
+
const choices = catalog.models.map(model => `${JSON.stringify(model.value)}${model.resolvedModel === undefined
|
|
204
|
+
|| model.resolvedModel === model.value ? "" : ` -> ${JSON.stringify(model.resolvedModel)}`}`);
|
|
205
|
+
return [
|
|
206
|
+
choices.length === 0
|
|
207
|
+
? "No native model options were reported."
|
|
208
|
+
: `Native model options: ${choices.join(", ")}.`,
|
|
209
|
+
...(configurationField(catalog, "model")?.allowCustom === true
|
|
210
|
+
? ["Custom model IDs are validated by the Provider; this list is not exhaustive."] : [])
|
|
211
|
+
].join(" ");
|
|
178
212
|
}
|
|
179
213
|
/**
|
|
180
|
-
*
|
|
181
|
-
*
|
|
182
|
-
*
|
|
214
|
+
* Validate known model/effort constraints. An explicitly open model field
|
|
215
|
+
* delegates unlisted IDs to the Provider; aliases are not a complete whitelist.
|
|
216
|
+
* A fallback has no model list and makes no claim about native acceptance.
|
|
183
217
|
*/
|
|
184
218
|
export function validateAgentLaunchConfiguration(catalog, config) {
|
|
185
219
|
if (catalog.models.length === 0)
|
|
186
220
|
return;
|
|
187
221
|
const model = modelChoice(catalog, config.model);
|
|
188
222
|
if (model === undefined) {
|
|
189
|
-
|
|
223
|
+
if (config.model !== undefined && configurationField(catalog, "model")?.allowCustom === true)
|
|
224
|
+
return;
|
|
225
|
+
throw new Error(`Unsupported ${catalog.adapterId} launch configuration: field=model actual=${JSON.stringify(config.model ?? "")}. ${agentModelOptionsSummary(catalog)} `
|
|
226
|
+
+ `Inspect current options: yui config agent capabilities ${catalog.agentId}.`);
|
|
190
227
|
}
|
|
191
228
|
if (config.effort !== undefined
|
|
192
229
|
&& model.efforts.length > 0
|
|
193
230
|
&& !model.efforts.some((effort) => effort.value === config.effort)) {
|
|
194
|
-
throw new Error(`Unsupported ${catalog.adapterId} launch configuration: field=effort actual=${JSON.stringify(config.effort)} model=${JSON.stringify(model.value)} supported=${JSON.stringify(model.efforts.map(({ value }) => value))}
|
|
231
|
+
throw new Error(`Unsupported ${catalog.adapterId} launch configuration: field=effort actual=${JSON.stringify(config.effort)} model=${JSON.stringify(model.value)} supported=${JSON.stringify(model.efforts.map(({ value }) => value))}. ${agentModelOptionsSummary(catalog)} `
|
|
232
|
+
+ `Inspect current options: yui config agent capabilities ${catalog.agentId}.`);
|
|
195
233
|
}
|
|
196
234
|
}
|
|
197
235
|
function field(key, choices, allowCustom) {
|
|
@@ -442,7 +480,7 @@ function catalogFailure(error) {
|
|
|
442
480
|
code: code === "ETIMEDOUT" || candidate.name === "AbortError"
|
|
443
481
|
? "timeout"
|
|
444
482
|
: code === "ENOENT" ? "missing-command" : "probe-failed",
|
|
445
|
-
message: candidate.message || "Agent capability discovery failed."
|
|
483
|
+
message: redactAgentErrorText(candidate.message || "Agent capability discovery failed.")
|
|
446
484
|
};
|
|
447
485
|
}
|
|
448
486
|
function record(value) {
|
|
@@ -33,7 +33,7 @@ export function createRoleSessionSet(owner, activeAgentId, now) {
|
|
|
33
33
|
updatedAt: now.toISOString()
|
|
34
34
|
};
|
|
35
35
|
return owner.scope === "global"
|
|
36
|
-
? { ...base, schemaVersion: 5 }
|
|
36
|
+
? { ...base, schemaVersion: 5, providerBinding: null }
|
|
37
37
|
: {
|
|
38
38
|
...base,
|
|
39
39
|
schemaVersion: 12,
|
|
@@ -486,13 +486,10 @@ export function validateRoleSessionSet(set) {
|
|
|
486
486
|
}
|
|
487
487
|
}
|
|
488
488
|
}
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
// Task binding is never accepted here, and its presence never upgrades the
|
|
494
|
-
// schemaVersion these raw-JSON sets are read at.
|
|
495
|
-
if (Object.hasOwn(globalSet, "providerBinding") && globalSet.providerBinding != null) {
|
|
489
|
+
if (!Object.hasOwn(globalSet, "providerBinding")) {
|
|
490
|
+
throw new Error("Global Role session set must contain its Provider Runtime Binding.");
|
|
491
|
+
}
|
|
492
|
+
if (globalSet.providerBinding !== null) {
|
|
496
493
|
const providerBinding = validateProviderRuntimeBinding(globalSet.providerBinding);
|
|
497
494
|
const conversationId = currentProviderConversation(providerBinding).conversationId;
|
|
498
495
|
const session = [...Object.values(globalSet.sessions), ...Object.values(globalSet.history ?? {})]
|
|
@@ -1,30 +1,30 @@
|
|
|
1
|
-
import { randomUUID } from "node:crypto";
|
|
2
1
|
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
3
|
import { mkdirSync, realpathSync } from "node:fs";
|
|
4
4
|
import { resolve } from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
import { isDeepStrictEqual } from "node:util";
|
|
7
|
-
import { assertExecutionEnvironmentCurrent } from "../runtime/executionEnvironment.js";
|
|
8
7
|
import { runPurposeAdmitsTaskState } from "../agentRun/agentRun.js";
|
|
8
|
+
import { assertExecutionEnvironmentCurrent } from "../runtime/executionEnvironment.js";
|
|
9
9
|
import { taskOwnsManagedWorkspace } from "../task/task.js";
|
|
10
10
|
import { configuredAgentToDefinition, resolveAgentEnvironment } from "../agent/agent.js";
|
|
11
11
|
import { NATIVE_AGENT_ENVIRONMENT_NAMES, nativeAgentEnvironmentNames, operationalAgentEnvironment, selectEnvironment } from "../agent/launchEnvironment.js";
|
|
12
|
-
import { activeRoleAgentBinding } from "../role/role.js";
|
|
13
|
-
import { planningRuntimeCwd } from "../storage/homeLayout.js";
|
|
14
12
|
import { compileRoleSessionContext, roleSessionKind } from "../context/roleSessionContext.js";
|
|
15
13
|
import { materializeSessionBootstrap } from "../context/sessionBootstrapManifest.js";
|
|
16
|
-
import { resolveAgentAdapter } from "./agentAdapter.js";
|
|
17
|
-
import { resolveTaskRoleSessionTitle } from "../runtime/sessionTitle.js";
|
|
18
|
-
import { classifyWorkspacePreflight, formatWorkspacePreflightError } from "./workspacePreflightClassification.js";
|
|
19
|
-
import { activeLiveRoleAgentSession, taskRoleControlTarget } from "./agentExecutor.js";
|
|
20
|
-
import { roleSessionMayContinue, effectiveRoleForLaunch, resolveEffectiveLaunch } from "./effectiveLaunch.js";
|
|
21
|
-
import { parseTaskRuntimeIsolationDescriptor, taskRuntimeIsolationEnvironment } from "../runtime/taskRuntimeIsolation.js";
|
|
22
14
|
import { ResourceRegistrar } from "../resources/resourceRegistrar.js";
|
|
23
|
-
import {
|
|
15
|
+
import { activeRoleAgentBinding } from "../role/role.js";
|
|
24
16
|
import { managedRuntimeAdmission } from "../runtime/agentDriver.js";
|
|
25
17
|
import { builtinAgentEndpointImplementation, validateAgentEndpointImplementation } from "../runtime/agentEndpointIdentity.js";
|
|
18
|
+
import { builtinAgentDriverRegistry, builtinDriverIdForAdapter } from "../runtime/builtinAgentDrivers.js";
|
|
26
19
|
import { assertProviderConversationReplaceable } from "../runtime/providerRuntimeIdentity.js";
|
|
20
|
+
import { resolveTaskRoleSessionTitle } from "../runtime/sessionTitle.js";
|
|
21
|
+
import { parseTaskRuntimeIsolationDescriptor, taskRuntimeIsolationEnvironment } from "../runtime/taskRuntimeIsolation.js";
|
|
22
|
+
import { planningRuntimeCwd } from "../storage/homeLayout.js";
|
|
23
|
+
import { resolveAgentAdapter } from "./agentAdapter.js";
|
|
24
|
+
import { activeLiveRoleAgentSession, taskRoleControlTarget } from "./agentExecutor.js";
|
|
27
25
|
import { assertCodexLaunchOverridesAvailable, inspectCodexLaunchConfig } from "./codexConfigConflict.js";
|
|
26
|
+
import { effectiveRoleForLaunch, resolveEffectiveLaunch, roleSessionMayContinue } from "./effectiveLaunch.js";
|
|
27
|
+
import { classifyWorkspacePreflight, formatWorkspacePreflightError } from "./workspacePreflightClassification.js";
|
|
28
28
|
/** Builds managed native Agent launches from the authoritative Task records. */
|
|
29
29
|
export class FileRoleLaunchPlanner {
|
|
30
30
|
home;
|
|
@@ -791,9 +791,6 @@ const inspectWorkspacePhysicalState = (entry) => {
|
|
|
791
791
|
recordedBaseIsAncestor: ancestorResult.status === 0
|
|
792
792
|
};
|
|
793
793
|
};
|
|
794
|
-
function shellQuote(value) {
|
|
795
|
-
return `'${value.replaceAll("'", "'\"'\"'")}'`;
|
|
796
|
-
}
|
|
797
794
|
function patchEnvironment(current, names, values) {
|
|
798
795
|
const next = { ...current };
|
|
799
796
|
for (const name of names)
|
|
@@ -830,45 +827,6 @@ function codexShellEnvironmentConfig(environment) {
|
|
|
830
827
|
.map(([name, value]) => `${JSON.stringify(name)}=${JSON.stringify(value)}`);
|
|
831
828
|
return `shell_environment_policy.set={${entries.join(",")}}`;
|
|
832
829
|
}
|
|
833
|
-
/**
|
|
834
|
-
* Codex 0.145 discovers lifecycle hooks from its effective config. Keep Yui's
|
|
835
|
-
* two handlers invocation-local: this avoids mutating CODEX_HOME or the Task
|
|
836
|
-
* workspace, while the exact launch environment supplies the durable AgentRun fence.
|
|
837
|
-
*/
|
|
838
|
-
function codexLifecycleHooksConfig(cliPath) {
|
|
839
|
-
const command = [
|
|
840
|
-
shellQuote(canonicalPath(process.execPath)),
|
|
841
|
-
shellQuote(canonicalPath(cliPath)),
|
|
842
|
-
"internal",
|
|
843
|
-
"runtime-hook"
|
|
844
|
-
].join(" ");
|
|
845
|
-
const handler = `{hooks=[{type="command",command=${JSON.stringify(command)}}]}`;
|
|
846
|
-
return `hooks={`
|
|
847
|
-
+ `SessionStart=[${handler}],`
|
|
848
|
-
+ `UserPromptSubmit=[${handler}],`
|
|
849
|
-
+ `PreToolUse=[${handler}],`
|
|
850
|
-
+ `PermissionRequest=[${handler}],`
|
|
851
|
-
+ `PostToolUse=[${handler}],`
|
|
852
|
-
+ `SubagentStart=[${handler}],`
|
|
853
|
-
+ `SubagentStop=[${handler}],`
|
|
854
|
-
+ `Stop=[${handler}]`
|
|
855
|
-
+ `}`;
|
|
856
|
-
}
|
|
857
|
-
function addCodexLifecycleHooks(args, mode, cliPath) {
|
|
858
|
-
// Session flags are Yui-owned and exact to this launch. Hook trust bypass is
|
|
859
|
-
// still explicit because these handlers execute a local command.
|
|
860
|
-
const managed = [
|
|
861
|
-
"--enable", "hooks",
|
|
862
|
-
"--config", codexLifecycleHooksConfig(cliPath),
|
|
863
|
-
"--dangerously-bypass-hook-trust"
|
|
864
|
-
];
|
|
865
|
-
if (mode === "new")
|
|
866
|
-
return [...args, ...managed];
|
|
867
|
-
if (args.length < 2 || args.at(-2) !== "resume") {
|
|
868
|
-
throw new Error("Codex resume launch shape is invalid.");
|
|
869
|
-
}
|
|
870
|
-
return [...args.slice(0, -2), ...managed, ...args.slice(-2)];
|
|
871
|
-
}
|
|
872
830
|
function withCodexThreadEnvironment(launch) {
|
|
873
831
|
const control = launch.providerControl;
|
|
874
832
|
if (control?.adapterId !== "codex" || control.codexThread === undefined)
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { usageError, taskNotFound, roleNotFound, agentNotFound } from "../errors/cliError.js";
|
|
2
|
+
import { projectAgentCapabilityConfig } from "./agentCapabilityConfig.js";
|
|
3
|
+
import { activeRoleAgentBinding } from "../role/role.js";
|
|
4
|
+
import { configuredAgentFingerprint, readAgentFailureContext } from "../runtime/agentFailureContext.js";
|
|
5
|
+
import { resolveManagedTaskReader } from "../runtime/managedCaller.js";
|
|
6
|
+
import { assertContextRecordReadable } from "../context/taskContext.js";
|
|
7
|
+
/** Resolve authority and scope before any native metadata process is started. */
|
|
8
|
+
export function resolveTaskAgentCapabilities(query, store, environment = {}) {
|
|
9
|
+
const { taskId, roleName, errorId } = query;
|
|
10
|
+
const reader = resolveManagedTaskReader(store, environment);
|
|
11
|
+
if (reader !== undefined && (reader.taskId !== taskId
|
|
12
|
+
|| reader.roleName !== "leader" && reader.roleName !== roleName)) {
|
|
13
|
+
throw usageError("Agent capability query is outside the caller's Task/Role.");
|
|
14
|
+
}
|
|
15
|
+
const task = store.getTask(taskId);
|
|
16
|
+
if (task === null)
|
|
17
|
+
throw taskNotFound(taskId);
|
|
18
|
+
let agentId, cwd, config;
|
|
19
|
+
let expectedFingerprint;
|
|
20
|
+
let expectedComponent;
|
|
21
|
+
if (errorId !== undefined) {
|
|
22
|
+
assertContextRecordReadable(store, taskId, "task-event", errorId, environment);
|
|
23
|
+
const event = store.listEventsByType(taskId, ["runtime.agent-error"]).find(event => event.id === errorId);
|
|
24
|
+
if (event === undefined || event.payload.roleName !== roleName) {
|
|
25
|
+
throw usageError("Agent failure is not owned by this Task/Role.");
|
|
26
|
+
}
|
|
27
|
+
const captured = readAgentFailureContext(event.payload.capabilityContext);
|
|
28
|
+
if (captured.status === "unavailable") {
|
|
29
|
+
throw usageError(`${captured.reason} No historical configuration was inferred. `
|
|
30
|
+
+ `Inspect current desired configuration explicitly with yui task role capabilities ${taskId} ${roleName} --refresh.`);
|
|
31
|
+
}
|
|
32
|
+
({ agentId, cwd, config } = captured);
|
|
33
|
+
expectedFingerprint = captured.agentFingerprint;
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
const role = store.getRole(taskId, roleName);
|
|
37
|
+
if (role === null)
|
|
38
|
+
throw roleNotFound(roleName);
|
|
39
|
+
const binding = activeRoleAgentBinding(role);
|
|
40
|
+
agentId = binding.agentId;
|
|
41
|
+
expectedComponent = binding.component;
|
|
42
|
+
cwd = role.workspace;
|
|
43
|
+
config = projectAgentCapabilityConfig(binding.config);
|
|
44
|
+
}
|
|
45
|
+
const agent = store.getConfiguredAgent(agentId);
|
|
46
|
+
if (agent === null)
|
|
47
|
+
throw agentNotFound(agentId);
|
|
48
|
+
if (agent.adapterId !== config.adapterId
|
|
49
|
+
|| expectedComponent !== undefined && agent.component !== expectedComponent) {
|
|
50
|
+
throw usageError("The Role capability binding no longer matches the configured Agent implementation.");
|
|
51
|
+
}
|
|
52
|
+
if (expectedFingerprint !== undefined && configuredAgentFingerprint(agent) !== expectedFingerprint) {
|
|
53
|
+
throw usageError("The configured Agent command or environment bindings changed since this failure. "
|
|
54
|
+
+ "The historical capability query was not run; inspect current desired configuration explicitly.");
|
|
55
|
+
}
|
|
56
|
+
return {
|
|
57
|
+
input: { agent, cwd, config,
|
|
58
|
+
refresh: query.refresh === true },
|
|
59
|
+
context: { taskId, roleName, selection: errorId === undefined ? "desired-role" : "failed-launch",
|
|
60
|
+
...(errorId === undefined ? {} : { errorId }), agentId: agent.id,
|
|
61
|
+
...(config.model === undefined ? {} : { requestedModel: config.model }),
|
|
62
|
+
cwd }
|
|
63
|
+
};
|
|
64
|
+
}
|
|
@@ -33,78 +33,10 @@ export function workItemDeliverySettled(delivery, integrations) {
|
|
|
33
33
|
&& attempt.source.startCommit === delivery.startCommit
|
|
34
34
|
&& attempt.source.resultCommit === delivery.resultCommit));
|
|
35
35
|
}
|
|
36
|
-
/**
|
|
37
|
-
* Delivery obligations follow the Candidate that currently governs each
|
|
38
|
-
* WorkItem. Older Candidates and their ChangeSets remain audit evidence, but
|
|
39
|
-
* they do not keep a Task open after a replacement Candidate is accepted.
|
|
40
|
-
*/
|
|
41
|
-
export function governingChangeSets(workItems, changeSets) {
|
|
42
|
-
const selected = new Map();
|
|
43
|
-
for (const item of workItems) {
|
|
44
|
-
const candidate = governingWorkItemCandidate(item);
|
|
45
|
-
if (candidate === undefined)
|
|
46
|
-
continue;
|
|
47
|
-
for (const changeSet of changeSets) {
|
|
48
|
-
if (changeSet.workItemId !== item.id)
|
|
49
|
-
continue;
|
|
50
|
-
const expectedHead = candidateProjectHead(candidate, changeSet.projectId);
|
|
51
|
-
if (expectedHead !== undefined && changeSet.headCommit !== expectedHead)
|
|
52
|
-
continue;
|
|
53
|
-
const key = `${item.id}\0${changeSet.projectId}`;
|
|
54
|
-
const current = selected.get(key);
|
|
55
|
-
if (current === undefined || compareChangeSets(current, changeSet) < 0) {
|
|
56
|
-
selected.set(key, changeSet);
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
return [...selected.values()].sort(compareChangeSets);
|
|
61
|
-
}
|
|
62
|
-
export function changeSetDeliverySettled(changeSet, integrations, queueEntries = []) {
|
|
63
|
-
if (integrations.some((attempt) => (attempt.status === "committed"
|
|
64
|
-
&& attempt.projectId === changeSet.projectId
|
|
65
|
-
&& attempt.source.kind === "work-item"
|
|
66
|
-
&& attempt.source.workItemId === changeSet.workItemId
|
|
67
|
-
&& attempt.source.resultCommit === changeSet.headCommit)))
|
|
68
|
-
return true;
|
|
69
|
-
const latestQueueEntry = queueEntries
|
|
70
|
-
.filter((entry) => entry.changeSetId === changeSet.id)
|
|
71
|
-
.sort(compareQueueEntries)
|
|
72
|
-
.at(-1);
|
|
73
|
-
return latestQueueEntry?.status === "superseded";
|
|
74
|
-
}
|
|
75
|
-
export function latestGoverningQueueEntries(changeSets, queueEntries) {
|
|
76
|
-
const governingIds = new Set(changeSets.map(({ id }) => id));
|
|
77
|
-
const latest = new Map();
|
|
78
|
-
for (const entry of queueEntries) {
|
|
79
|
-
if (!governingIds.has(entry.changeSetId))
|
|
80
|
-
continue;
|
|
81
|
-
const current = latest.get(entry.changeSetId);
|
|
82
|
-
if (current === undefined || compareQueueEntries(current, entry) < 0) {
|
|
83
|
-
latest.set(entry.changeSetId, entry);
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
return [...latest.values()].sort(compareQueueEntries);
|
|
87
|
-
}
|
|
88
|
-
/**
|
|
89
|
-
* Historical blocked attempts are audit evidence once every ChangeSet they
|
|
90
|
-
* reference has been replaced by a newer governing Candidate. Attempts that
|
|
91
|
-
* may still be writing remain blockers regardless of Candidate history.
|
|
92
|
-
*/
|
|
36
|
+
/** Unsettled Integration attempts remain blockers independently of Agent ordering. */
|
|
93
37
|
export function integrationAttemptRequiresSettlement(attempt) {
|
|
94
38
|
return attempt.status === "running"
|
|
95
39
|
|| attempt.status === "validating"
|
|
96
40
|
|| attempt.status === "blocked"
|
|
97
41
|
|| attempt.status === "conflicted";
|
|
98
42
|
}
|
|
99
|
-
function candidateProjectHead(candidate, projectId) {
|
|
100
|
-
return candidate.gitSnapshot?.projects.find((project) => project.projectId === projectId)?.commit
|
|
101
|
-
?? candidate.taskMainSnapshot?.projects.find((project) => project.projectId === projectId)?.headCommit;
|
|
102
|
-
}
|
|
103
|
-
function compareChangeSets(left, right) {
|
|
104
|
-
return left.createdAt.localeCompare(right.createdAt)
|
|
105
|
-
|| left.id.localeCompare(right.id, undefined, { numeric: true });
|
|
106
|
-
}
|
|
107
|
-
function compareQueueEntries(left, right) {
|
|
108
|
-
return left.updatedAt.localeCompare(right.updatedAt)
|
|
109
|
-
|| left.id.localeCompare(right.id, undefined, { numeric: true });
|
|
110
|
-
}
|