@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
|
@@ -1,19 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Persistent Resource registry (Issue 10).
|
|
3
3
|
*
|
|
4
|
-
* The registry
|
|
5
|
-
*
|
|
6
|
-
* JSON file at `$YUI_HOME/runtime/resource-registry/registry.json`.
|
|
4
|
+
* The registry lives only in `resource_registry` inside the current `yui.db`.
|
|
5
|
+
* This module owns its value validation and quarantine paths, not persistence.
|
|
7
6
|
*/
|
|
8
|
-
import {
|
|
9
|
-
import { dirname, join, resolve } from "node:path";
|
|
7
|
+
import { join, resolve } from "node:path";
|
|
10
8
|
import { RESOURCE_REGISTRY_SCHEMA_VERSION } from "./resourceTypes.js";
|
|
11
9
|
export const RESOURCE_REGISTRY_DIRECTORY = "resource-registry";
|
|
12
|
-
export const RESOURCE_REGISTRY_FILE = "registry.json";
|
|
13
10
|
export const RESOURCE_QUARANTINE_DIRECTORY = "quarantine";
|
|
14
|
-
export function resourceRegistryPath(home) {
|
|
15
|
-
return join(resolve(home), "runtime", RESOURCE_REGISTRY_DIRECTORY, RESOURCE_REGISTRY_FILE);
|
|
16
|
-
}
|
|
17
11
|
export function resourceQuarantineRoot(home) {
|
|
18
12
|
return join(resolve(home), "runtime", RESOURCE_REGISTRY_DIRECTORY, RESOURCE_QUARANTINE_DIRECTORY);
|
|
19
13
|
}
|
|
@@ -29,34 +23,6 @@ export function emptyResourceRegistry() {
|
|
|
29
23
|
records: Object.freeze({})
|
|
30
24
|
});
|
|
31
25
|
}
|
|
32
|
-
/**
|
|
33
|
-
* Load the registry. A missing registry is an empty Home. A corrupt or
|
|
34
|
-
* unreadable registry fails closed: GC must never invent ownership from a
|
|
35
|
-
* corrupt registry, and silently dropping quarantine receipts could let a
|
|
36
|
-
* resource be released twice.
|
|
37
|
-
*/
|
|
38
|
-
export function loadResourceRegistry(home) {
|
|
39
|
-
const path = resourceRegistryPath(home);
|
|
40
|
-
if (!existsSync(path))
|
|
41
|
-
return emptyResourceRegistry();
|
|
42
|
-
let parsed;
|
|
43
|
-
try {
|
|
44
|
-
parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
45
|
-
}
|
|
46
|
-
catch (error) {
|
|
47
|
-
throw new Error(`Resource registry is corrupt or unreadable at ${path}: `
|
|
48
|
-
+ `${error instanceof Error ? error.message : "unknown error"}. `
|
|
49
|
-
+ "Fix or remove the registry file before running GC.", { cause: error });
|
|
50
|
-
}
|
|
51
|
-
return parseResourceRegistryState(parsed);
|
|
52
|
-
}
|
|
53
|
-
export function saveResourceRegistry(home, state) {
|
|
54
|
-
const path = resourceRegistryPath(home);
|
|
55
|
-
mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
|
|
56
|
-
const temporary = `${path}.tmp-${process.pid}`;
|
|
57
|
-
writeFileSync(temporary, `${JSON.stringify(state, null, 2)}\n`, { mode: 0o600 });
|
|
58
|
-
renameSync(temporary, path);
|
|
59
|
-
}
|
|
60
26
|
export function upsertResourceRecord(state, record) {
|
|
61
27
|
return Object.freeze({
|
|
62
28
|
schemaVersion: RESOURCE_REGISTRY_SCHEMA_VERSION,
|
|
@@ -1,41 +1,18 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Resource registry store abstraction (Issue 10, DB-only optimal).
|
|
3
|
-
*
|
|
4
|
-
* The GC engine depends on this interface, not on a specific storage backend.
|
|
5
|
-
* When the Home is SQLite-backed the registry lives in the `resource_registry`
|
|
6
|
-
* table inside `yui.db`; otherwise it falls back to a JSON file.
|
|
7
|
-
*/
|
|
8
|
-
import { join } from "node:path";
|
|
9
|
-
import { existsSync } from "node:fs";
|
|
10
|
-
import { loadResourceRegistry, saveResourceRegistry } from "./resourceRegistry.js";
|
|
11
1
|
import { SqliteResourceRegistry } from "./sqliteResourceRegistry.js";
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
*/
|
|
15
|
-
export class FileResourceRegistryStore {
|
|
16
|
-
#home;
|
|
17
|
-
constructor(home) {
|
|
18
|
-
this.#home = home;
|
|
19
|
-
}
|
|
20
|
-
load() {
|
|
21
|
-
return loadResourceRegistry(this.#home);
|
|
22
|
-
}
|
|
23
|
-
save(state) {
|
|
24
|
-
saveResourceRegistry(this.#home, state);
|
|
25
|
-
}
|
|
26
|
-
close() {
|
|
27
|
-
// No persistent connection to close.
|
|
28
|
-
}
|
|
2
|
+
export function createResourceRegistryStore(home) {
|
|
3
|
+
return new SqliteResourceRegistry(home);
|
|
29
4
|
}
|
|
30
|
-
/**
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
* SQLite-backed Homes use the `resource_registry` table in `yui.db`;
|
|
34
|
-
* File-store Homes fall back to the JSON file.
|
|
5
|
+
/** Connections created here never survive a synchronous read/write operation.
|
|
6
|
+
* A borrowed Store remains the caller's responsibility. No DB handle is held
|
|
7
|
+
* across filesystem scans, Git subprocesses or other asynchronous GC work.
|
|
35
8
|
*/
|
|
36
|
-
export function
|
|
37
|
-
|
|
38
|
-
|
|
9
|
+
export function withResourceRegistry(home, borrowed, operation) {
|
|
10
|
+
const store = borrowed ?? createResourceRegistryStore(home);
|
|
11
|
+
try {
|
|
12
|
+
return operation(store);
|
|
13
|
+
}
|
|
14
|
+
finally {
|
|
15
|
+
if (borrowed === undefined)
|
|
16
|
+
store.close();
|
|
39
17
|
}
|
|
40
|
-
return new FileResourceRegistryStore(home);
|
|
41
18
|
}
|
|
@@ -12,16 +12,15 @@
|
|
|
12
12
|
*/
|
|
13
13
|
import { existsSync } from "node:fs";
|
|
14
14
|
import { join } from "node:path";
|
|
15
|
+
import { isDeepStrictEqual } from "node:util";
|
|
15
16
|
import Database from "better-sqlite3";
|
|
16
|
-
import { RESOURCE_REGISTRY_SCHEMA_VERSION } from "./resourceTypes.js";
|
|
17
|
-
import { emptyResourceRegistry, parseResourceRegistryState } from "./resourceRegistry.js";
|
|
18
17
|
import { migrateSqliteSchema } from "../storage/sqliteSchema.js";
|
|
18
|
+
import { emptyResourceRegistry, parseResourceRegistryState } from "./resourceRegistry.js";
|
|
19
|
+
import { RESOURCE_REGISTRY_SCHEMA_VERSION } from "./resourceTypes.js";
|
|
19
20
|
export const SQLITE_RESOURCE_REGISTRY_TABLE = "resource_registry";
|
|
20
21
|
/**
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
* from the in-memory state, so the on-disk table always matches the state
|
|
24
|
-
* the GC engine computed.
|
|
22
|
+
* Each save applies only the caller's delta. Unrelated registrations survive;
|
|
23
|
+
* a changed same-record snapshot fails closed instead of overwriting ownership.
|
|
25
24
|
*/
|
|
26
25
|
export class SqliteResourceRegistry {
|
|
27
26
|
#db;
|
|
@@ -31,10 +30,16 @@ export class SqliteResourceRegistry {
|
|
|
31
30
|
throw new Error(`SQLite database not found at ${dbPath}`);
|
|
32
31
|
}
|
|
33
32
|
this.#db = new Database(dbPath);
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
33
|
+
try {
|
|
34
|
+
this.#db.pragma("journal_mode = WAL");
|
|
35
|
+
this.#db.pragma("foreign_keys = ON");
|
|
36
|
+
this.#db.pragma("busy_timeout = 5000");
|
|
37
|
+
migrateSqliteSchema(this.#db, { mode: "validate" });
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
this.#db.close();
|
|
41
|
+
throw error;
|
|
42
|
+
}
|
|
38
43
|
}
|
|
39
44
|
load() {
|
|
40
45
|
const rows = this.#db.prepare(`SELECT payload FROM ${SQLITE_RESOURCE_REGISTRY_TABLE}`).all();
|
|
@@ -63,7 +68,13 @@ export class SqliteResourceRegistry {
|
|
|
63
68
|
records: Object.freeze(records)
|
|
64
69
|
});
|
|
65
70
|
}
|
|
66
|
-
save(state) {
|
|
71
|
+
save(state, previous) {
|
|
72
|
+
const next = parseResourceRegistryState(state);
|
|
73
|
+
const before = parseResourceRegistryState(previous);
|
|
74
|
+
const ids = [...new Set([...Object.keys(before.records), ...Object.keys(next.records)])]
|
|
75
|
+
.filter(id => !isDeepStrictEqual(before.records[id], next.records[id]));
|
|
76
|
+
if (ids.length === 0)
|
|
77
|
+
return;
|
|
67
78
|
const upsert = this.#db.prepare(`INSERT INTO ${SQLITE_RESOURCE_REGISTRY_TABLE}
|
|
68
79
|
(id, kind, path, disposition, task_id, payload, created_at, updated_at)
|
|
69
80
|
VALUES
|
|
@@ -76,11 +87,24 @@ export class SqliteResourceRegistry {
|
|
|
76
87
|
payload = excluded.payload,
|
|
77
88
|
updated_at = excluded.updated_at`);
|
|
78
89
|
const remove = this.#db.prepare(`DELETE FROM ${SQLITE_RESOURCE_REGISTRY_TABLE} WHERE id = ?`);
|
|
79
|
-
const existingRows = this.#db.prepare(`SELECT id FROM ${SQLITE_RESOURCE_REGISTRY_TABLE}`).all();
|
|
80
|
-
const existingIds = new Set(existingRows.map((row) => row.id));
|
|
81
|
-
const currentIds = new Set(Object.keys(state.records));
|
|
82
90
|
const tx = this.#db.transaction(() => {
|
|
83
|
-
|
|
91
|
+
const read = this.#db.prepare(`SELECT payload FROM ${SQLITE_RESOURCE_REGISTRY_TABLE} WHERE id = ?`);
|
|
92
|
+
for (const id of ids) {
|
|
93
|
+
const row = read.get(id);
|
|
94
|
+
const current = row === undefined ? undefined : parseResourceRegistryState({
|
|
95
|
+
schemaVersion: RESOURCE_REGISTRY_SCHEMA_VERSION,
|
|
96
|
+
records: { [id]: JSON.parse(row.payload) }
|
|
97
|
+
}).records[id];
|
|
98
|
+
if (!isDeepStrictEqual(current, before.records[id])) {
|
|
99
|
+
throw new Error(`Resource registry record changed since inspection: ${id}. Inspect and retry.`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
for (const id of ids) {
|
|
103
|
+
const record = next.records[id];
|
|
104
|
+
if (record === undefined) {
|
|
105
|
+
remove.run(id);
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
84
108
|
upsert.run({
|
|
85
109
|
id: record.id,
|
|
86
110
|
kind: record.kind,
|
|
@@ -92,20 +116,13 @@ export class SqliteResourceRegistry {
|
|
|
92
116
|
updated_at: record.updatedAt
|
|
93
117
|
});
|
|
94
118
|
}
|
|
95
|
-
for (const id of existingIds) {
|
|
96
|
-
if (!currentIds.has(id))
|
|
97
|
-
remove.run(id);
|
|
98
|
-
}
|
|
99
119
|
});
|
|
100
|
-
tx();
|
|
120
|
+
tx.immediate();
|
|
101
121
|
}
|
|
102
122
|
close() {
|
|
103
123
|
this.#db.close();
|
|
104
124
|
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
*/
|
|
109
|
-
export function isSqliteHome(home) {
|
|
110
|
-
return existsSync(join(home, "yui.db"));
|
|
125
|
+
transaction(operation) {
|
|
126
|
+
return this.#db.transaction(operation).immediate();
|
|
127
|
+
}
|
|
111
128
|
}
|
|
@@ -8,7 +8,7 @@ export async function assessDeltaRecheck(input) {
|
|
|
8
8
|
const { repositoryPaths, previousRound, candidate, git } = input;
|
|
9
9
|
if (previousRound.status !== "completed"
|
|
10
10
|
|| previousRound.reviewerRunId === undefined
|
|
11
|
-
||
|
|
11
|
+
|| previousRound.scope !== "task") {
|
|
12
12
|
return {
|
|
13
13
|
kind: "ineligible",
|
|
14
14
|
reason: "Delta recheck requires a semantic completed Task-final ReviewRound."
|
|
@@ -20,7 +20,7 @@ export function isCompletedTaskReviewEvidence(store, round) {
|
|
|
20
20
|
return isCompletedTaskReviewEvidenceFromRuns(round, store.listRuns(round.taskId));
|
|
21
21
|
}
|
|
22
22
|
export function isCompletedTaskReviewEvidenceFromRuns(round, runs) {
|
|
23
|
-
if (
|
|
23
|
+
if (round.scope !== "task"
|
|
24
24
|
|| round.taskCandidate === undefined
|
|
25
25
|
|| round.taskCandidate.projects.length === 0) {
|
|
26
26
|
return false;
|
|
@@ -4,7 +4,7 @@ import { projectReviewerAvailability } from "./reviewerAvailability.js";
|
|
|
4
4
|
/** Read-only facts for Leader judgment; this projection never blocks an action. */
|
|
5
5
|
export function projectReviewDecision(input) {
|
|
6
6
|
const { store, task, roles, runs, rounds, reviewConfig, currentCandidate } = input;
|
|
7
|
-
const taskRounds = rounds.filter((round) =>
|
|
7
|
+
const taskRounds = rounds.filter((round) => round.scope === "task");
|
|
8
8
|
const accepted = [...taskRounds]
|
|
9
9
|
.filter((round) => isCompletedTaskReviewEvidence(store, round))
|
|
10
10
|
.sort(compareRoundIdentity)
|
|
@@ -10,6 +10,7 @@ export function createReviewRound(id, taskId, workItemId, candidateId, reviewerR
|
|
|
10
10
|
taskId: requireIdentity(taskId, "Task id"),
|
|
11
11
|
workItemId: requireIdentity(workItemId, "Work Item id"),
|
|
12
12
|
candidateId: requireIdentity(candidateId, "Candidate id"),
|
|
13
|
+
scope: "work-item",
|
|
13
14
|
reviewerRoleName: requireIdentity(reviewerRoleName, "Reviewer Role"),
|
|
14
15
|
reviewBaseCommit: requireCommit(reviewBaseCommit, "Review base commit"),
|
|
15
16
|
requestedBy: validateReviewRequestSource(requestedBy),
|
|
@@ -134,12 +135,12 @@ export function retryReviewRound(round, requestedBy, now) {
|
|
|
134
135
|
if (round.status !== "failed") {
|
|
135
136
|
throw new Error(`ReviewRound ${round.id} is not retryable from ${round.status}.`);
|
|
136
137
|
}
|
|
137
|
-
const taskScope =
|
|
138
|
+
const taskScope = round.scope === "task";
|
|
138
139
|
return validateReviewRound({
|
|
139
140
|
schemaVersion: round.schemaVersion,
|
|
140
141
|
id: round.id,
|
|
141
142
|
taskId: round.taskId,
|
|
142
|
-
|
|
143
|
+
scope: round.scope,
|
|
143
144
|
...(taskScope
|
|
144
145
|
? {}
|
|
145
146
|
: {
|
|
@@ -185,9 +186,9 @@ export function retryReviewRound(round, requestedBy, now) {
|
|
|
185
186
|
createdAt: round.createdAt
|
|
186
187
|
});
|
|
187
188
|
}
|
|
188
|
-
/**
|
|
189
|
+
/** Scope guard for the Task-final retry operation. */
|
|
189
190
|
export function retryTaskReviewRound(round, requestedBy, now) {
|
|
190
|
-
if (
|
|
191
|
+
if (round.scope !== "task") {
|
|
191
192
|
throw new Error(`Only a Task-final ReviewRound can be retried in place: ${round.id}.`);
|
|
192
193
|
}
|
|
193
194
|
return retryReviewRound(round, requestedBy, now);
|
|
@@ -289,7 +290,7 @@ export function validateReviewRound(round) {
|
|
|
289
290
|
validateTaskRecordReference({ taskId: round.taskId, localId: round.id }, "reviewRound");
|
|
290
291
|
requireIdentity(round.reviewerRoleName, "Reviewer Role");
|
|
291
292
|
requireCommit(round.reviewBaseCommit, "Review base commit");
|
|
292
|
-
const scope = round.scope
|
|
293
|
+
const scope = round.scope;
|
|
293
294
|
if (scope !== "work-item" && scope !== "task") {
|
|
294
295
|
throw new Error(`ReviewRound scope is invalid: ${String(round.scope)}.`);
|
|
295
296
|
}
|
|
@@ -441,10 +442,10 @@ function validateReviewExecutionGroup(group, round) {
|
|
|
441
442
|
|| assignment.taskId !== round.taskId
|
|
442
443
|
|| assignment.reviewRoundId !== round.id
|
|
443
444
|
|| assignment.reviewBaseCommit !== round.reviewBaseCommit
|
|
444
|
-
|| assignment.scope !==
|
|
445
|
+
|| assignment.scope !== round.scope) {
|
|
445
446
|
throw new Error(`ReviewRound ExecutionGroup provenance is invalid: ${round.id}.`);
|
|
446
447
|
}
|
|
447
|
-
if (
|
|
448
|
+
if (round.scope === "work-item"
|
|
448
449
|
&& (assignment.workItemId !== round.workItemId
|
|
449
450
|
|| assignment.candidateId !== round.candidateId)) {
|
|
450
451
|
throw new Error(`ReviewRound ExecutionGroup WorkItem target is invalid: ${round.id}.`);
|
|
@@ -14,7 +14,7 @@ export function resolveRecordedTaskFinalReviewContract(taskId, workItems, review
|
|
|
14
14
|
contract: candidate.taskFinalReviewContract,
|
|
15
15
|
source: `Candidate ${item.id}/${candidate.id}`
|
|
16
16
|
}]))),
|
|
17
|
-
...reviewRounds.flatMap((round) => (
|
|
17
|
+
...reviewRounds.flatMap((round) => (round.scope !== "task"
|
|
18
18
|
|| round.taskFinalReviewContract === undefined
|
|
19
19
|
? []
|
|
20
20
|
: [{
|
|
@@ -183,58 +183,15 @@ export function readAcpConfigOptions(value) {
|
|
|
183
183
|
export const ACP_MODE_CONFIG_ID = "mode";
|
|
184
184
|
export function readAcpSessionConfiguration(value) {
|
|
185
185
|
const result = asObject(value);
|
|
186
|
-
const
|
|
187
|
-
if (
|
|
188
|
-
|
|
186
|
+
const options = readAcpConfigOptions(result?.configOptions);
|
|
187
|
+
if (options === undefined) {
|
|
188
|
+
throw new Error("ACP Session must report configOptions; mode-only peers are unsupported.");
|
|
189
189
|
}
|
|
190
|
-
|
|
191
|
-
return Object.freeze({
|
|
192
|
-
options: legacy === undefined ? Object.freeze([]) : Object.freeze([legacy]),
|
|
193
|
-
legacyModes: legacy !== undefined
|
|
194
|
-
});
|
|
195
|
-
}
|
|
196
|
-
/**
|
|
197
|
-
* Convert the legacy `modes` field into the one config option it describes.
|
|
198
|
-
*
|
|
199
|
-
* The shape is fixed by ACP: `currentModeId` plus `availableModes`. Presenting
|
|
200
|
-
* it as a config option keeps mode selection in one vocabulary without
|
|
201
|
-
* pretending the Agent supports the newer method.
|
|
202
|
-
*/
|
|
203
|
-
function readAcpSessionModes(value) {
|
|
204
|
-
const modes = asObject(value);
|
|
205
|
-
const currentValue = modes === null ? undefined : optionalText(modes.currentModeId);
|
|
206
|
-
if (modes === null || currentValue === undefined)
|
|
207
|
-
return undefined;
|
|
208
|
-
const values = [];
|
|
209
|
-
if (Array.isArray(modes.availableModes)) {
|
|
210
|
-
for (const entry of modes.availableModes) {
|
|
211
|
-
const mode = asObject(entry);
|
|
212
|
-
const id = mode === null ? undefined : optionalText(mode.id);
|
|
213
|
-
if (mode === null || id === undefined)
|
|
214
|
-
continue;
|
|
215
|
-
values.push(Object.freeze({
|
|
216
|
-
value: id,
|
|
217
|
-
name: optionalText(mode.name) ?? id,
|
|
218
|
-
...(optionalText(mode.description) === undefined
|
|
219
|
-
? {}
|
|
220
|
-
: { description: optionalText(mode.description) })
|
|
221
|
-
}));
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
return Object.freeze({
|
|
225
|
-
id: ACP_MODE_CONFIG_ID,
|
|
226
|
-
name: "Mode",
|
|
227
|
-
category: "mode",
|
|
228
|
-
currentValue,
|
|
229
|
-
options: Object.freeze(values)
|
|
230
|
-
});
|
|
190
|
+
return Object.freeze({ options });
|
|
231
191
|
}
|
|
232
192
|
export function acpSetConfigOptionRequest(sessionId, configId, value) {
|
|
233
193
|
return { sessionId, configId, value };
|
|
234
194
|
}
|
|
235
|
-
export function acpSetModeRequest(sessionId, modeId) {
|
|
236
|
-
return { sessionId, modeId };
|
|
237
|
-
}
|
|
238
195
|
export function acpCancelNotification(sessionId) {
|
|
239
196
|
return { sessionId };
|
|
240
197
|
}
|
|
@@ -330,10 +287,6 @@ export function readAcpSessionUpdate(params, sessionId) {
|
|
|
330
287
|
? undefined
|
|
331
288
|
: Object.freeze({ kind: "config-options", options });
|
|
332
289
|
}
|
|
333
|
-
case "current_mode_update": {
|
|
334
|
-
const modeId = optionalText(update.currentModeId);
|
|
335
|
-
return modeId === undefined ? undefined : Object.freeze({ kind: "mode", modeId });
|
|
336
|
-
}
|
|
337
290
|
case "usage_update":
|
|
338
291
|
return Object.freeze({
|
|
339
292
|
kind: "usage",
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { isAbsolute } from "node:path";
|
|
2
2
|
import { MAX_RUN_RESULT_OUTPUT_BYTES } from "../domain/agentResultTransport.js";
|
|
3
|
-
import { ACP_METHOD_NOT_FOUND_CODE, acpCancelNotification, acpDeclinePermission, acpInitializeRequest, acpNewSessionRequest, acpPermissionResult, acpPermissionSummary, acpPromptRequest, acpSetConfigOptionRequest,
|
|
3
|
+
import { ACP_METHOD_NOT_FOUND_CODE, acpCancelNotification, acpDeclinePermission, acpInitializeRequest, acpNewSessionRequest, acpPermissionResult, acpPermissionSummary, acpPromptRequest, acpSetConfigOptionRequest, acpStopReasonDetail, acpTerminalStatus, asObject, optionalText, readAcpConfigOptions, readAcpInitializeResult, readAcpPermissionRequest, readAcpSessionConfiguration, readAcpSessionUpdate, readAcpStopReason } from "./acpProtocol.js";
|
|
4
4
|
import { ACP_CONFIGURATION_ORDER, acpRunConfigurationOptions, confirmAcpConfigurationStep, describeAcpConfigurationRejections, resolveAcpConfigurationField, verifyAcpConfiguration } from "./acpSessionConfiguration.js";
|
|
5
5
|
import { handshakeObservationFrom, unknownAgentRunConfiguration } from "./agentRunConfiguration.js";
|
|
6
6
|
import { JsonLineChannel, terminateProcessGroup } from "./jsonLineChannel.js";
|
|
7
|
-
import { ProviderDeliveryUnknownError, ProviderTurnRejectedError } from "./
|
|
7
|
+
import { ProviderDeliveryUnknownError, ProviderTurnRejectedError } from "./providerErrors.js";
|
|
8
8
|
/**
|
|
9
9
|
* One Agent Client Protocol Session over an Agent's stdio.
|
|
10
10
|
*
|
|
@@ -71,20 +71,10 @@ export class AcpStructuredProviderSession {
|
|
|
71
71
|
* the complete list, so this is replaced wholesale and never merged.
|
|
72
72
|
*/
|
|
73
73
|
#configOptions = [];
|
|
74
|
-
/** Legacy writes invalidate current evidence until the peer reports it again. */
|
|
75
|
-
#unobservedConfigIds = new Set();
|
|
76
|
-
/**
|
|
77
|
-
* True when this Agent described its options through the legacy `modes` field
|
|
78
|
-
* only. Mode changes then go to `session/set_mode`, because an Agent that
|
|
79
|
-
* never advertised config options must not receive
|
|
80
|
-
* `session/set_config_option`.
|
|
81
|
-
*/
|
|
82
|
-
#legacyModes = false;
|
|
83
74
|
/**
|
|
84
75
|
* What Yui asked this Session for and what the Agent did with it, recorded so
|
|
85
76
|
* a launch reports the configuration it actually runs under rather than the
|
|
86
|
-
* one it requested.
|
|
87
|
-
* a legacy mode-only peer cannot prove more than that it accepted the call.
|
|
77
|
+
* one it requested. Requested changes require a reported current value.
|
|
88
78
|
*/
|
|
89
79
|
#appliedConfiguration = [];
|
|
90
80
|
constructor(child, exit, processInstanceId, channel, onTerminal, mirror) {
|
|
@@ -293,7 +283,6 @@ export class AcpStructuredProviderSession {
|
|
|
293
283
|
#readConfiguration(result) {
|
|
294
284
|
const configuration = readAcpSessionConfiguration(result);
|
|
295
285
|
this.#configOptions = configuration.options;
|
|
296
|
-
this.#legacyModes = configuration.legacyModes;
|
|
297
286
|
}
|
|
298
287
|
/**
|
|
299
288
|
* Push this launch's requested run configuration and verify the Agent applied
|
|
@@ -342,33 +331,11 @@ export class AcpStructuredProviderSession {
|
|
|
342
331
|
continue;
|
|
343
332
|
}
|
|
344
333
|
const step = resolution.step;
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
// method-not-found and read as the Agent refusing the value.
|
|
351
|
-
const result = this.#legacyModes
|
|
352
|
-
? await this.#request("session/set_mode", acpSetModeRequest(this.#sessionId, step.value))
|
|
353
|
-
: await this.#request("session/set_config_option", acpSetConfigOptionRequest(this.#sessionId, step.configId, step.value), (response) => {
|
|
354
|
-
const options = readAcpConfigOptions(asObject(response)?.configOptions);
|
|
355
|
-
if (options !== undefined)
|
|
356
|
-
this.#configOptions = options;
|
|
357
|
-
});
|
|
358
|
-
if (this.#legacyModes) {
|
|
359
|
-
// `session/set_mode` answers with no options, so the only fact available
|
|
360
|
-
// is that the Agent accepted the call. Yui's own view is left untouched:
|
|
361
|
-
// writing the requested value into the cached list would manufacture the
|
|
362
|
-
// very confirmation the protocol withheld, and the final verification
|
|
363
|
-
// would then read Yui's own assumption back as the Agent's report.
|
|
364
|
-
outcomes.push(Object.freeze({
|
|
365
|
-
field,
|
|
366
|
-
configId: step.configId,
|
|
367
|
-
value: step.value,
|
|
368
|
-
confirmation: "acknowledged"
|
|
369
|
-
}));
|
|
370
|
-
continue;
|
|
371
|
-
}
|
|
334
|
+
const result = await this.#request("session/set_config_option", acpSetConfigOptionRequest(this.#sessionId, step.configId, step.value), (response) => {
|
|
335
|
+
const options = readAcpConfigOptions(asObject(response)?.configOptions);
|
|
336
|
+
if (options !== undefined)
|
|
337
|
+
this.#configOptions = options;
|
|
338
|
+
});
|
|
372
339
|
// The answer is the whole option list, so it both confirms this step and
|
|
373
340
|
// carries any change the Agent made alongside it.
|
|
374
341
|
const options = readAcpConfigOptions(asObject(result)?.configOptions);
|
|
@@ -385,7 +352,7 @@ export class AcpStructuredProviderSession {
|
|
|
385
352
|
// Every call has landed, so this is the configuration the prompt would run
|
|
386
353
|
// under. Re-checking all requested values here is what catches an axis that a
|
|
387
354
|
// later call reset after its own step had already been confirmed.
|
|
388
|
-
const failures = verifyAcpConfiguration(desired, this.#configOptions, component
|
|
355
|
+
const failures = verifyAcpConfiguration(desired, this.#configOptions, component);
|
|
389
356
|
if (failures.length > 0) {
|
|
390
357
|
throw new Error(`ACP Session run configuration did not hold. ${failures.join(" ")}`);
|
|
391
358
|
}
|
|
@@ -409,11 +376,6 @@ export class AcpStructuredProviderSession {
|
|
|
409
376
|
* rather than as the launch's confirmed one. Reading the private field on every
|
|
410
377
|
* call rather than caching a projection is what makes that true: a snapshot
|
|
411
378
|
* taken at launch would keep reporting a configuration this Session has left.
|
|
412
|
-
*
|
|
413
|
-
* A legacy mode-only peer has no reported value to pair with its
|
|
414
|
-
* `acknowledged` step, because Yui deliberately never wrote the requested value
|
|
415
|
-
* into its own view. That absence is stated as `unobserved` instead of being
|
|
416
|
-
* filled in from the request.
|
|
417
379
|
*/
|
|
418
380
|
get runConfiguration() {
|
|
419
381
|
if (this.#closed !== undefined) {
|
|
@@ -438,24 +400,8 @@ export class AcpStructuredProviderSession {
|
|
|
438
400
|
})))
|
|
439
401
|
});
|
|
440
402
|
}
|
|
441
|
-
/**
|
|
442
|
-
* What the Agent reports for one listed axis.
|
|
443
|
-
*
|
|
444
|
-
* Almost always an observation: the option list ACP sends is the Agent's own
|
|
445
|
-
* report. The exception is an axis Yui changed on a legacy peer, whose value
|
|
446
|
-
* here still dates from the setup reply because `session/set_mode` returned
|
|
447
|
-
* nothing to refresh it with. That entry is listed — the axis is real and its
|
|
448
|
-
* enumeration is accurate — but its value is stale by construction, so it is
|
|
449
|
-
* reported as unobserved rather than as the Agent's current answer.
|
|
450
|
-
*/
|
|
403
|
+
/** The current complete option list is the Agent's own report. */
|
|
451
404
|
#axisValue(option) {
|
|
452
|
-
if (this.#unobservedConfigIds.has(option.id)) {
|
|
453
|
-
return Object.freeze({
|
|
454
|
-
status: "unobserved",
|
|
455
|
-
reason: "Yui set this axis with `session/set_mode`, which reports no "
|
|
456
|
-
+ `configuration, so the Agent last reported \`${option.currentValue}\` before that call.`
|
|
457
|
-
});
|
|
458
|
-
}
|
|
459
405
|
return Object.freeze({ status: "observed", value: option.currentValue });
|
|
460
406
|
}
|
|
461
407
|
/** Read current evidence independently of the request's historical confirmation. */
|
|
@@ -631,17 +577,6 @@ export class AcpStructuredProviderSession {
|
|
|
631
577
|
// already confirmed before any prompt was sent.
|
|
632
578
|
if (update?.kind === "config-options") {
|
|
633
579
|
this.#configOptions = update.options;
|
|
634
|
-
this.#unobservedConfigIds.clear();
|
|
635
|
-
return;
|
|
636
|
-
}
|
|
637
|
-
if (update?.kind === "mode") {
|
|
638
|
-
for (const option of this.#configOptions) {
|
|
639
|
-
if (option.category === "mode")
|
|
640
|
-
this.#unobservedConfigIds.delete(option.id);
|
|
641
|
-
}
|
|
642
|
-
this.#configOptions = this.#configOptions.map((option) => option.category === "mode"
|
|
643
|
-
? Object.freeze({ ...option, currentValue: update.modeId })
|
|
644
|
-
: option);
|
|
645
580
|
return;
|
|
646
581
|
}
|
|
647
582
|
// Streamed content is Provider-visible progress, mirrored for the Turn
|
|
@@ -181,17 +181,10 @@ function resolveRequest(field, desired, options, component) {
|
|
|
181
181
|
*
|
|
182
182
|
* Only stated values are checked. An unrequested axis has no expectation to
|
|
183
183
|
* violate, and `default` permission means Yui asked for nothing.
|
|
184
|
-
*
|
|
185
|
-
* Legacy `session/set_mode` peers answer with no option list, so their mode
|
|
186
|
-
* cannot be re-read; `acknowledged` outcomes carry that and are excluded here
|
|
187
|
-
* rather than being verified against a value Yui wrote into its own cache.
|
|
188
184
|
*/
|
|
189
|
-
export function verifyAcpConfiguration(desired, options, component
|
|
185
|
+
export function verifyAcpConfiguration(desired, options, component) {
|
|
190
186
|
const failures = [];
|
|
191
187
|
for (const field of ACP_CONFIGURATION_ORDER) {
|
|
192
|
-
const outcome = outcomes.find((candidate) => candidate.field === field);
|
|
193
|
-
if (outcome !== undefined && outcome.confirmation === "acknowledged")
|
|
194
|
-
continue;
|
|
195
188
|
const resolution = resolveAcpConfigurationField(field, desired, options, component);
|
|
196
189
|
if (resolution.kind === "unrequested" || resolution.kind === "satisfied")
|
|
197
190
|
continue;
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { builtinAgentDriverRegistry } from "./builtinAgentDrivers.js";
|
|
2
1
|
import { builtinAgentEndpointImplementation, requireBuiltinAgentEndpointImplementation } from "./agentEndpointIdentity.js";
|
|
2
|
+
import { builtinAgentDriverRegistry } from "./builtinAgentDrivers.js";
|
|
3
3
|
import { CodexPreSubmissionError } from "./codexAppServerRuntime.js";
|
|
4
|
+
import { ProviderDeliveryUnknownError, ProviderTurnBusyError, ProviderTurnRejectedError } from "./providerErrors.js";
|
|
5
|
+
import { startStructuredProviderSession } from "./structuredProviderHost.js";
|
|
4
6
|
export { builtinAgentEndpointImplementation } from "./agentEndpointIdentity.js";
|
|
5
|
-
import { ProviderDeliveryUnknownError, ProviderTurnBusyError, ProviderTurnRejectedError, startStructuredProviderSession } from "./structuredProviderHost.js";
|
|
6
7
|
/**
|
|
7
8
|
* The only built-in managed execution factory. Product/protocol codecs remain
|
|
8
9
|
* private to runtime; the Host consumes the same boundary for both providers.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { isAbsolute } from "node:path";
|
|
3
|
+
import { requireIdentity } from "../domain/validation.js";
|
|
4
|
+
import { projectAgentCapabilityConfig, validateAgentCapabilityConfig } from "../executor/agentCapabilityConfig.js";
|
|
5
|
+
export function configuredAgentFingerprint(agent) {
|
|
6
|
+
// Environment entries name bindings, never their resolved secret values.
|
|
7
|
+
return createHash("sha256").update(JSON.stringify({
|
|
8
|
+
id: agent.id, component: agent.component, adapterId: agent.adapterId,
|
|
9
|
+
command: agent.command, baseArgs: agent.baseArgs, environment: agent.environment
|
|
10
|
+
})).digest("hex");
|
|
11
|
+
}
|
|
12
|
+
export function captureAgentFailureContext(effective, agent) {
|
|
13
|
+
const value = effective === undefined || agent === null
|
|
14
|
+
? { status: "unavailable", reason: "The failing execution did not report a complete launch configuration." }
|
|
15
|
+
: agent.id !== effective.agentId || agent.adapterId !== effective.adapterId || agent.component !== effective.component
|
|
16
|
+
? { status: "unavailable", reason: "The configured Agent no longer matches the failing execution's implementation." }
|
|
17
|
+
: { status: "recorded", agentId: effective.agentId, cwd: effective.workspace.root,
|
|
18
|
+
config: projectAgentCapabilityConfig(effective),
|
|
19
|
+
agentFingerprint: configuredAgentFingerprint(agent) };
|
|
20
|
+
return JSON.stringify(value);
|
|
21
|
+
}
|
|
22
|
+
export function readAgentFailureContext(value) {
|
|
23
|
+
if (value === undefined)
|
|
24
|
+
throw new Error("Agent failure is missing its configuration context.");
|
|
25
|
+
const parsed = JSON.parse(value);
|
|
26
|
+
if (parsed?.status === "unavailable" && typeof parsed.reason === "string" && parsed.reason.length > 0)
|
|
27
|
+
return parsed;
|
|
28
|
+
if (parsed?.status !== "recorded" || !/^[a-f0-9]{64}$/.test(parsed.agentFingerprint)) {
|
|
29
|
+
throw new Error("Agent failure configuration context is invalid.");
|
|
30
|
+
}
|
|
31
|
+
if (Object.keys(parsed).some(key => !["status", "agentId", "cwd", "config", "agentFingerprint"].includes(key))) {
|
|
32
|
+
throw new Error("Agent failure context contains unsupported fields.");
|
|
33
|
+
}
|
|
34
|
+
requireIdentity(parsed.agentId, "Failure Agent id");
|
|
35
|
+
if (typeof parsed.cwd !== "string" || !isAbsolute(parsed.cwd) || parsed.cwd.includes("\0")) {
|
|
36
|
+
throw new Error("Agent failure working directory is invalid.");
|
|
37
|
+
}
|
|
38
|
+
validateAgentCapabilityConfig(parsed.config);
|
|
39
|
+
return parsed;
|
|
40
|
+
}
|
|
41
|
+
export function failureCapabilitiesCommand(taskId, roleName, eventId) {
|
|
42
|
+
return `yui task role capabilities ${taskId} ${roleName} --error ${eventId} --refresh`;
|
|
43
|
+
}
|