@zq-silk/yui 0.6.0 → 0.6.2
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/README.md +5 -5
- package/dist/agent/managedRuntimeEnvironment.js +2 -1
- package/dist/cli/commandCatalog.js +251 -13
- package/dist/cli/updateOrchestrator.js +8 -0
- package/dist/cli/updatePorts.js +76 -22
- package/dist/cli.js +264 -20
- package/dist/commands/configCommands.js +83 -9
- package/dist/commands/controllerCommands.js +103 -0
- package/dist/commands/deliveryGuardPreflight.js +30 -0
- package/dist/commands/durableJobCommands.js +231 -0
- package/dist/commands/executionAuditCommands.js +193 -0
- package/dist/commands/grantCommands.js +374 -0
- package/dist/commands/projectCommands.js +119 -81
- package/dist/commands/releaseCommands.js +444 -0
- package/dist/commands/resourcesCommands.js +274 -0
- package/dist/commands/sessionCommands.js +104 -0
- package/dist/commands/taskActor.js +117 -0
- package/dist/commands/taskChangeSetCommands.js +60 -0
- package/dist/commands/taskCommands.js +618 -202
- package/dist/commands/taskCompletionGate.js +78 -1
- package/dist/commands/taskContextCommand.js +33 -6
- package/dist/commands/taskInputCommands.js +1 -1
- package/dist/commands/taskIntegrationCommands.js +136 -33
- package/dist/commands/taskIntegrationQueueCommands.js +228 -0
- package/dist/commands/taskNextActionCommand.js +100 -0
- package/dist/commands/taskOverlapCommands.js +120 -0
- package/dist/commands/taskOverviewCommand.js +36 -8
- package/dist/commands/telemetryCommands.js +330 -0
- package/dist/commands/workflowCommands.js +415 -0
- package/dist/config/yuiConfig.js +62 -0
- package/dist/controller/clientRuntime.js +42 -1
- package/dist/controller/controller.js +402 -56
- package/dist/controller/controllerMain.js +25 -2
- package/dist/controller/domainIdentity.js +16 -8
- package/dist/controller/fileSchedulerStoreAdapter.js +423 -31
- package/dist/controller/handoverCandidate.js +168 -0
- package/dist/controller/jobClient.js +102 -0
- package/dist/controller/jobControl.js +613 -0
- package/dist/controller/jobSupervisor.js +498 -0
- package/dist/controller/providerHookRunFence.js +34 -5
- package/dist/controller/resourceCleanupLinux.js +18 -9
- package/dist/controller/resourceInventoryLinux.js +90 -39
- package/dist/controller/runtime.js +165 -15
- package/dist/controller/runtimeEventInbox.js +234 -57
- package/dist/controller/runtimeEventProcessor.js +297 -58
- package/dist/controller/sessionOwnerReconciliation.js +321 -0
- package/dist/core/controllerServer.js +416 -27
- package/dist/core/controllerTelemetry.js +167 -0
- package/dist/doctor/doctor.js +113 -16
- package/dist/domain/validation.js +9 -0
- package/dist/execution/executionGroup.js +40 -3
- package/dist/executor/agentExecutor.js +6 -3
- package/dist/executor/effectiveLaunch.js +52 -0
- package/dist/executor/executorRegistry.js +50 -0
- package/dist/executor/fileRoleLaunchPlanner.js +61 -6
- package/dist/grant/capabilityGrant.js +282 -0
- package/dist/integration/changeSet.js +16 -3
- package/dist/integration/changeSetManifest.js +46 -0
- package/dist/integration/gitIntegrationService.js +528 -147
- package/dist/integration/integrationAttempt.js +54 -5
- package/dist/integration/integrationQueueEntry.js +221 -0
- package/dist/integration/integrationQueueService.js +955 -0
- package/dist/integration/manifestTags.js +99 -0
- package/dist/integration/overlapDiagnostics.js +211 -0
- package/dist/job/durableJob.js +449 -0
- package/dist/job/jobRunner.js +350 -0
- package/dist/lifecycle/exactRunTerminalization.js +24 -2
- package/dist/lifecycle/providerErrorClass.js +126 -0
- package/dist/message/message.js +16 -3
- package/dist/observability/executionAudit.js +545 -0
- package/dist/observability/faultClassification.js +160 -0
- package/dist/observability/runtimeIdentity.js +367 -0
- package/dist/release/fakeReleasePorts.js +55 -0
- package/dist/release/releaseHandover.js +475 -0
- package/dist/release/releaseIdempotencyStore.js +165 -0
- package/dist/release/releaseWorkflow.js +459 -0
- package/dist/release/releaseWorkflowEngine.js +688 -0
- package/dist/release/releaseWorkflowPorts.js +1720 -0
- package/dist/release/runtimeRelease.js +495 -0
- package/dist/release/workflowFileLock.js +218 -0
- package/dist/repository/gitWorkspace.js +177 -1
- package/dist/repository/projectMaintenanceLock.js +315 -0
- package/dist/repository/taskWorkspaceCoordinator.js +87 -17
- package/dist/repository/taskWorkspacePreparer.js +1091 -517
- package/dist/resources/autoResourceGc.js +116 -0
- package/dist/resources/liveReferences.js +574 -0
- package/dist/resources/resourceDiscovery.js +477 -0
- package/dist/resources/resourceGc.js +645 -0
- package/dist/resources/resourceRegistrar.js +256 -0
- package/dist/resources/resourceRegistry.js +150 -0
- package/dist/resources/resourceRegistryStore.js +41 -0
- package/dist/resources/resourceTypes.js +42 -0
- package/dist/resources/sqliteResourceRegistry.js +111 -0
- package/dist/review/reviewConfig.js +10 -0
- package/dist/review/reviewFinding.js +240 -0
- package/dist/review/reviewFindingLedger.js +545 -0
- package/dist/review/reviewOutcomeClassifier.js +61 -0
- package/dist/review/reviewRound.js +56 -4
- package/dist/run/agentRun.js +80 -4
- package/dist/run/providerRetry.js +84 -0
- package/dist/run/providerRetryConfig.js +63 -0
- package/dist/run/yieldReceipt.js +65 -0
- package/dist/runtime/exactControlPlane.js +79 -2
- package/dist/runtime/index.js +4 -0
- package/dist/runtime/sessionOwnerIdentity.js +269 -0
- package/dist/runtime/sessionOwnerRegistry.js +132 -0
- package/dist/runtime/sessionReconciliation.js +93 -0
- package/dist/runtime/sessionTerminationGuard.js +211 -0
- package/dist/runtime/taskRuntimeIsolation.js +13 -0
- package/dist/runtime/tmuxAdapters.js +34 -1
- package/dist/scheduler/actionability.js +155 -0
- package/dist/scheduler/activeRoleRunDelivery.js +14 -5
- package/dist/scheduler/activeTaskProgress.js +60 -0
- package/dist/scheduler/leaderWakeupProcessor.js +22 -11
- package/dist/scheduler/roleRunStall.js +135 -29
- package/dist/scheduler/taskExecutionProjection.js +11 -0
- package/dist/storage/compatibleTaskStore.js +112 -5
- package/dist/storage/migration/productionRegistry.js +736 -1
- package/dist/storage/sqliteSchema.js +264 -3
- package/dist/storage/sqliteStore.js +487 -13
- package/dist/storage/storeRpc.js +21 -0
- package/dist/storage/taskStore.js +974 -21
- package/dist/storage/upgrade/homeClassification.js +120 -2
- package/dist/storage/upgrade/migrationReceipt.js +67 -0
- package/dist/storage/upgrade/pseudoLayoutRepair.js +241 -0
- package/dist/storage/upgrade/recordVersions.js +10 -1
- package/dist/storage/upgrade/sqliteMigrationTarget.js +58 -6
- package/dist/storage/upgrade/sqliteRecordMigrationTarget.js +290 -0
- package/dist/storage/upgrade/sqliteStateMigration.js +258 -2
- package/dist/storage/upgrade/upgradeOrchestrator.js +482 -16
- package/dist/task/deliveryGuard.js +226 -0
- package/dist/task/nextAction.js +738 -0
- package/dist/task/repairWave.js +137 -0
- package/dist/task/taskRecordReference.js +6 -1
- package/dist/telemetry/sqliteTelemetryStore.js +387 -0
- package/dist/telemetry/telemetryCompaction.js +251 -0
- package/dist/telemetry/telemetryConfig.js +64 -0
- package/dist/telemetry/telemetryRouter.js +32 -0
- package/dist/telemetry/telemetryStore.js +19 -0
- package/dist/telemetry/telemetryWiring.js +33 -0
- package/dist/tmux/tmuxManager.js +20 -1
- package/dist/tmux/tmuxSocketEndpoint.js +20 -0
- package/dist/verification/gateArtifact.js +216 -0
- package/dist/verification/gateArtifactStore.js +87 -0
- package/dist/verification/verificationGateService.js +414 -0
- package/dist/verification/verificationPlan.js +308 -0
- package/dist/workspace/gitChangeSetCapture.js +12 -2
- package/dist/workspace/workItemChangeSetManager.js +60 -3
- package/package.json +1 -1
- package/skills/yui-leader/SKILL.md +8 -0
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure-memory, fixed-space command observation for the Controller socket.
|
|
3
|
+
*
|
|
4
|
+
* The core server owns route/built-in command observation: every authenticated
|
|
5
|
+
* request is counted once at the routing layer, and a bounded unrefed scheduler
|
|
6
|
+
* samples event-loop delay so an already-written request's pre-dispatch wait is
|
|
7
|
+
* observable even when the loop is saturated by scheduler projections. The
|
|
8
|
+
* FileTaskController composes these snapshots into its runtime status metric;
|
|
9
|
+
* nothing here is persisted and every snapshot is O(1).
|
|
10
|
+
*/
|
|
11
|
+
const CONTROLLER_DELAY_BUCKETS_MS = [10, 50, 100, 250, 500, 1_000, 3_000];
|
|
12
|
+
const DEFAULT_EVENT_LOOP_DELAY_INTERVAL_MS = 50;
|
|
13
|
+
const BUILTIN_METHODS = new Set([
|
|
14
|
+
"controller.status",
|
|
15
|
+
"controller.identity",
|
|
16
|
+
"controller.stop",
|
|
17
|
+
"controller.begin-handover",
|
|
18
|
+
"controller.commit-handover",
|
|
19
|
+
"controller.rollback-handover",
|
|
20
|
+
"controller.handover-state"
|
|
21
|
+
]);
|
|
22
|
+
export function isBuiltinControllerMethod(method) {
|
|
23
|
+
return BUILTIN_METHODS.has(method);
|
|
24
|
+
}
|
|
25
|
+
export function monotonicMilliseconds() {
|
|
26
|
+
return Number(process.hrtime.bigint()) / 1_000_000;
|
|
27
|
+
}
|
|
28
|
+
export const productionTelemetryScheduler = {
|
|
29
|
+
setInterval: (callback, delayMs) => {
|
|
30
|
+
const timer = setInterval(callback, delayMs);
|
|
31
|
+
timer.unref();
|
|
32
|
+
return {
|
|
33
|
+
unref: () => timer.unref(),
|
|
34
|
+
close: () => clearInterval(timer)
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Counts routed commands once at the routing layer. Built-in routes
|
|
40
|
+
* (controller.status/identity/stop) and dispatcher routes are observed
|
|
41
|
+
* separately so the dispatcher's own service-time metric cannot be mistaken
|
|
42
|
+
* for end-to-end latency and is never double-counted.
|
|
43
|
+
*/
|
|
44
|
+
export class ControllerCommandObserver {
|
|
45
|
+
#clock;
|
|
46
|
+
#received = 0;
|
|
47
|
+
#completed = 0;
|
|
48
|
+
#failed = 0;
|
|
49
|
+
#inFlight = 0;
|
|
50
|
+
/** Monotonic arrival timestamps of in-flight observations (FIFO). */
|
|
51
|
+
#inFlightStarted = [];
|
|
52
|
+
#builtinCompleted = 0;
|
|
53
|
+
#builtinFailed = 0;
|
|
54
|
+
#dispatchedCompleted = 0;
|
|
55
|
+
#dispatchedFailed = 0;
|
|
56
|
+
constructor(clock = monotonicMilliseconds) {
|
|
57
|
+
this.#clock = clock;
|
|
58
|
+
}
|
|
59
|
+
start() {
|
|
60
|
+
this.#received += 1;
|
|
61
|
+
this.#inFlight += 1;
|
|
62
|
+
this.#inFlightStarted.push(this.#clock());
|
|
63
|
+
let completed = false;
|
|
64
|
+
return {
|
|
65
|
+
complete: (kind, outcome) => {
|
|
66
|
+
if (completed)
|
|
67
|
+
return;
|
|
68
|
+
completed = true;
|
|
69
|
+
this.#inFlight = Math.max(0, this.#inFlight - 1);
|
|
70
|
+
this.#inFlightStarted.shift();
|
|
71
|
+
this.#completed += 1;
|
|
72
|
+
if (outcome === "failure")
|
|
73
|
+
this.#failed += 1;
|
|
74
|
+
if (kind === "builtin") {
|
|
75
|
+
if (outcome === "failure")
|
|
76
|
+
this.#builtinFailed += 1;
|
|
77
|
+
else
|
|
78
|
+
this.#builtinCompleted += 1;
|
|
79
|
+
}
|
|
80
|
+
else if (outcome === "failure") {
|
|
81
|
+
this.#dispatchedFailed += 1;
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
this.#dispatchedCompleted += 1;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
snapshot() {
|
|
90
|
+
const oldest = this.#inFlightStarted.length === 0
|
|
91
|
+
? null
|
|
92
|
+
: Math.max(0, this.#clock() - this.#inFlightStarted[0]);
|
|
93
|
+
return {
|
|
94
|
+
received: this.#received,
|
|
95
|
+
completed: this.#completed,
|
|
96
|
+
failed: this.#failed,
|
|
97
|
+
inFlight: this.#inFlight,
|
|
98
|
+
oldestInFlightAgeMs: oldest,
|
|
99
|
+
builtin: {
|
|
100
|
+
completed: this.#builtinCompleted,
|
|
101
|
+
failed: this.#builtinFailed
|
|
102
|
+
},
|
|
103
|
+
dispatched: {
|
|
104
|
+
completed: this.#dispatchedCompleted,
|
|
105
|
+
failed: this.#dispatchedFailed
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Bounded event-loop delay sampler. A fixed-cadence unrefed interval measures
|
|
112
|
+
* how much later than expected it fires; that lag is the pre-dispatch wait an
|
|
113
|
+
* already-written socket request experiences while the loop is busy. State is
|
|
114
|
+
* a fixed counter set plus seven bucket counters, so memory is constant and
|
|
115
|
+
* snapshots are O(1).
|
|
116
|
+
*/
|
|
117
|
+
export class ControllerEventLoopDelay {
|
|
118
|
+
#clock;
|
|
119
|
+
#scheduler;
|
|
120
|
+
#intervalMs;
|
|
121
|
+
#handle;
|
|
122
|
+
#expectedAt = 0;
|
|
123
|
+
#samples = 0;
|
|
124
|
+
#maximumLagMs = 0;
|
|
125
|
+
#buckets = new Map(CONTROLLER_DELAY_BUCKETS_MS.map((threshold) => [threshold, 0]));
|
|
126
|
+
#stopped = false;
|
|
127
|
+
constructor(clock = monotonicMilliseconds, scheduler = productionTelemetryScheduler, intervalMs = DEFAULT_EVENT_LOOP_DELAY_INTERVAL_MS) {
|
|
128
|
+
this.#clock = clock;
|
|
129
|
+
this.#scheduler = scheduler;
|
|
130
|
+
this.#intervalMs = intervalMs;
|
|
131
|
+
}
|
|
132
|
+
start() {
|
|
133
|
+
if (this.#handle !== undefined || this.#stopped)
|
|
134
|
+
return;
|
|
135
|
+
this.#expectedAt = this.#clock() + this.#intervalMs;
|
|
136
|
+
this.#handle = this.#scheduler.setInterval(() => this.#sample(), this.#intervalMs);
|
|
137
|
+
}
|
|
138
|
+
#sample() {
|
|
139
|
+
if (this.#stopped)
|
|
140
|
+
return;
|
|
141
|
+
const lag = Math.max(0, this.#clock() - this.#expectedAt);
|
|
142
|
+
this.#expectedAt = this.#clock() + this.#intervalMs;
|
|
143
|
+
this.#samples += 1;
|
|
144
|
+
const bounded = Math.ceil(lag);
|
|
145
|
+
this.#maximumLagMs = Math.max(this.#maximumLagMs, bounded);
|
|
146
|
+
for (const threshold of CONTROLLER_DELAY_BUCKETS_MS) {
|
|
147
|
+
if (bounded <= threshold) {
|
|
148
|
+
this.#buckets.set(threshold, (this.#buckets.get(threshold) ?? 0) + 1);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
stop() {
|
|
153
|
+
this.#stopped = true;
|
|
154
|
+
this.#handle?.close();
|
|
155
|
+
this.#handle = undefined;
|
|
156
|
+
}
|
|
157
|
+
snapshot() {
|
|
158
|
+
return {
|
|
159
|
+
samples: this.#samples,
|
|
160
|
+
maximumLagMs: this.#maximumLagMs,
|
|
161
|
+
lagBuckets: Object.fromEntries(CONTROLLER_DELAY_BUCKETS_MS.map((threshold) => [
|
|
162
|
+
`le${threshold}ms`,
|
|
163
|
+
this.#buckets.get(threshold) ?? 0
|
|
164
|
+
]))
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
}
|
package/dist/doctor/doctor.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { accessSync, constants, existsSync, lstatSync, readFileSync } from "node:fs";
|
|
2
2
|
import { isAbsolute, join } from "node:path";
|
|
3
|
+
import Database from "better-sqlite3";
|
|
3
4
|
import { configuredAgentToDefinition, resolveAgentEnvironment } from "../agent/agent.js";
|
|
4
5
|
import { operationalAgentEnvironment } from "../agent/launchEnvironment.js";
|
|
5
6
|
import { inspectAgentCapabilities, resolveAgentAdapter } from "../executor/agentAdapter.js";
|
|
@@ -11,8 +12,12 @@ import { defaultTableWidth, renderTable } from "../output/table.js";
|
|
|
11
12
|
import { resolveYuiHome, STORAGE_STATE_FILE } from "../storage/taskStore.js";
|
|
12
13
|
import { createProductionStorageRegistry, openCompatibleFileTaskStore, validateCompatibleFileTaskStore } from "../storage/compatibleTaskStore.js";
|
|
13
14
|
import { inspectStorageSchema } from "../storage/storageSchema.js";
|
|
15
|
+
import { resolveTaskStoreBackendForHome } from "../storage/sqliteStore.js";
|
|
16
|
+
import { resolveStoreWorkerEnabledForHome } from "../storage/storeRpc.js";
|
|
14
17
|
import { classifyHome } from "../storage/upgrade/homeClassification.js";
|
|
18
|
+
import { readMigrationReceipt } from "../storage/upgrade/migrationReceipt.js";
|
|
15
19
|
import { latestStorageVersionState } from "../storage/upgrade/recordVersions.js";
|
|
20
|
+
import { COMMITTED_DATABASE_FILENAME } from "../storage/upgrade/sqliteStateMigration.js";
|
|
16
21
|
import { CommandExecutionError } from "../tmux/commandExecutor.js";
|
|
17
22
|
import { EPHEMERAL_DOMAIN_GRACE_MS, readEphemeralDomainIdentity, readLinuxProcessStartIdentity } from "../controller/domainIdentity.js";
|
|
18
23
|
/**
|
|
@@ -42,12 +47,68 @@ export function summarizeStorageHealth(checks) {
|
|
|
42
47
|
/** Build the full machine-readable doctor report (checks + storage health). */
|
|
43
48
|
export function buildDoctorReport(env, executor, storageOptions = {}) {
|
|
44
49
|
const inspection = inspectDoctor(env, executor, storageOptions);
|
|
50
|
+
const home = resolveYuiHome(env);
|
|
45
51
|
return {
|
|
46
52
|
checks: inspection.checks,
|
|
47
|
-
storage:
|
|
53
|
+
storage: {
|
|
54
|
+
...summarizeStorageHealth(inspection.checks),
|
|
55
|
+
details: inspectStorageDetails(home, env)
|
|
56
|
+
},
|
|
48
57
|
review: inspection.review
|
|
49
58
|
};
|
|
50
59
|
}
|
|
60
|
+
/**
|
|
61
|
+
* Read the physical-backend facts for a Home (Issue 01 observability). Every
|
|
62
|
+
* field is best-effort read-only evidence: an unreadable database or manifest
|
|
63
|
+
* yields `null` for that field, and the `storage state` check reports the
|
|
64
|
+
* structural problem separately.
|
|
65
|
+
*/
|
|
66
|
+
function inspectStorageDetails(home, env) {
|
|
67
|
+
const schema = inspectStorageSchema(home);
|
|
68
|
+
const logicalLayout = schema.status === "current" || schema.status === "unsupported"
|
|
69
|
+
? schema.currentLayoutVersion
|
|
70
|
+
: null;
|
|
71
|
+
const authoritativeBackend = resolveTaskStoreBackendForHome(home, env);
|
|
72
|
+
const dbPath = join(home, COMMITTED_DATABASE_FILENAME);
|
|
73
|
+
const databasePath = existsSync(dbPath) ? dbPath : null;
|
|
74
|
+
let journalMode = null;
|
|
75
|
+
let lastCommittedRevision = null;
|
|
76
|
+
if (databasePath !== null) {
|
|
77
|
+
try {
|
|
78
|
+
const db = new Database(databasePath, { readonly: true });
|
|
79
|
+
try {
|
|
80
|
+
const mode = db.pragma("journal_mode", { simple: true });
|
|
81
|
+
journalMode = typeof mode === "string" ? mode : String(mode);
|
|
82
|
+
const row = db.prepare("SELECT revision FROM home_meta WHERE id = 1").get();
|
|
83
|
+
lastCommittedRevision = typeof row?.revision === "number" ? row.revision : null;
|
|
84
|
+
}
|
|
85
|
+
finally {
|
|
86
|
+
db.close();
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
// Leave both null; the storage state check surfaces the corruption.
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
try {
|
|
95
|
+
const state = JSON.parse(readFileSync(join(home, STORAGE_STATE_FILE), "utf8"));
|
|
96
|
+
lastCommittedRevision = typeof state.revision === "number" ? state.revision : null;
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
lastCommittedRevision = null;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return {
|
|
103
|
+
logicalLayout,
|
|
104
|
+
authoritativeBackend,
|
|
105
|
+
databasePath,
|
|
106
|
+
journalMode,
|
|
107
|
+
workerEnabled: resolveStoreWorkerEnabledForHome(home, env),
|
|
108
|
+
migrationReceipt: readMigrationReceipt(home),
|
|
109
|
+
lastCommittedRevision
|
|
110
|
+
};
|
|
111
|
+
}
|
|
51
112
|
/** Runs the read-only FileTaskStore diagnostics used by `yui doctor`. */
|
|
52
113
|
export function runDoctorCommand(args, env, executor, storageOptions = {}) {
|
|
53
114
|
if (args.length !== 0)
|
|
@@ -170,13 +231,14 @@ function checkSchema(state, compatibility) {
|
|
|
170
231
|
detail: `current=${state.currentVersion} latest=${state.latestVersion}`
|
|
171
232
|
};
|
|
172
233
|
case "unsupported":
|
|
173
|
-
if (compatibility.storageStatus === "compatible-old"
|
|
234
|
+
if ((compatibility.storageStatus === "compatible-old"
|
|
235
|
+
|| compatibility.storageStatus === "needs-storage-repair")
|
|
174
236
|
&& compatibility.check.status === "ok") {
|
|
175
237
|
return {
|
|
176
238
|
name: "storage schema",
|
|
177
239
|
status: "ok",
|
|
178
240
|
detail: `current=${state.currentVersion} latest=${state.latestVersion} `
|
|
179
|
-
+ `direction=${state.direction};
|
|
241
|
+
+ `direction=${state.direction}; ${compatibility.storageStatus} validated`
|
|
180
242
|
};
|
|
181
243
|
}
|
|
182
244
|
return {
|
|
@@ -228,7 +290,11 @@ function inspectCompatibility(home, homeCheck, schema, storageOptions) {
|
|
|
228
290
|
return { check: { name, status: "invalid", detail: errorMessage(error) } };
|
|
229
291
|
}
|
|
230
292
|
const storageStatus = classification.classification.status;
|
|
231
|
-
|
|
293
|
+
// A pseudo-layout-7 Home (needs-storage-repair) opens through the same
|
|
294
|
+
// normalization path as compatible-old, so its records get the same eager
|
|
295
|
+
// validation before the schema check can treat a version mismatch as
|
|
296
|
+
// validated (Issue 01).
|
|
297
|
+
if (storageStatus === "compatible-old" || storageStatus === "needs-storage-repair") {
|
|
232
298
|
try {
|
|
233
299
|
validateCompatibleFileTaskStore(home, resolvedStorageOptions);
|
|
234
300
|
}
|
|
@@ -239,7 +305,7 @@ function inspectCompatibility(home, homeCheck, schema, storageOptions) {
|
|
|
239
305
|
check: {
|
|
240
306
|
name,
|
|
241
307
|
status: "invalid",
|
|
242
|
-
detail: `unsupported
|
|
308
|
+
detail: `unsupported ${storageStatus} shape: ${errorMessage(error)}`
|
|
243
309
|
}
|
|
244
310
|
};
|
|
245
311
|
}
|
|
@@ -276,6 +342,21 @@ function inspectCompatibility(home, homeCheck, schema, storageOptions) {
|
|
|
276
342
|
detail: `migration-required (MIGRATABLE) ${versions}; run yui update when Sessions are clear`
|
|
277
343
|
}
|
|
278
344
|
};
|
|
345
|
+
case "needs-storage-repair":
|
|
346
|
+
return {
|
|
347
|
+
storageStatus,
|
|
348
|
+
storageOptions: resolvedStorageOptions,
|
|
349
|
+
check: {
|
|
350
|
+
name,
|
|
351
|
+
// Diagnostic mode (Issue 01 rollout step 1): the Home is readable
|
|
352
|
+
// and usable, but the manifest claims layout 7 without a yui.db.
|
|
353
|
+
// Surface the repair need in the detail while keeping the Home
|
|
354
|
+
// healthy so ordinary commands keep working.
|
|
355
|
+
status: "ok",
|
|
356
|
+
detail: `needs-storage-repair (NEEDS_STORAGE_REPAIR) ${versions}; `
|
|
357
|
+
+ "the manifest claims layout 7 but yui.db is missing. Run `yui upgrade` to rebuild it from state.json."
|
|
358
|
+
}
|
|
359
|
+
};
|
|
279
360
|
case "unsupported": {
|
|
280
361
|
const unsupported = classification.classification;
|
|
281
362
|
return {
|
|
@@ -308,25 +389,41 @@ function inspectState(home, homeCheck, schema, compatibility) {
|
|
|
308
389
|
return blockedStorage("invalid", schema.detail);
|
|
309
390
|
if (schema.status === "read-error")
|
|
310
391
|
return blockedStorage("invalid", schema.detail);
|
|
311
|
-
|
|
392
|
+
// A pseudo-layout-7 Home (needs-storage-repair) has a readable state.json;
|
|
393
|
+
// the compatibility check surfaces the repair need, but the store itself is
|
|
394
|
+
// readable, so the state and review checks must still run (Issue 01).
|
|
395
|
+
if (compatibility.check.status !== "ok"
|
|
396
|
+
&& compatibility.storageStatus !== "needs-storage-repair") {
|
|
312
397
|
return blockedStorage(compatibility.check.status, storageBlockerDetail(compatibility.check));
|
|
313
398
|
}
|
|
314
399
|
if (compatibility.storageStatus !== "current"
|
|
315
|
-
&& compatibility.storageStatus !== "compatible-old"
|
|
400
|
+
&& compatibility.storageStatus !== "compatible-old"
|
|
401
|
+
&& compatibility.storageStatus !== "needs-storage-repair") {
|
|
316
402
|
return blockedStorage("unsupported", compatibility.check.detail);
|
|
317
403
|
}
|
|
318
404
|
if (compatibility.storageOptions === undefined) {
|
|
319
405
|
return blockedStorage("invalid", "Storage compatibility options were not resolved.");
|
|
320
406
|
}
|
|
321
407
|
const statePath = join(home, STORAGE_STATE_FILE);
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
408
|
+
// A repaired layout-7 Home archived state.json; yui.db is the authoritative
|
|
409
|
+
// backend then, so the state.json gate is skipped (Issue 01).
|
|
410
|
+
const sqliteAuthoritative = !existsSync(statePath)
|
|
411
|
+
&& existsSync(join(home, COMMITTED_DATABASE_FILENAME));
|
|
412
|
+
if (!sqliteAuthoritative) {
|
|
413
|
+
if (!existsSync(statePath))
|
|
414
|
+
return blockedStorage("missing", "run yui setup");
|
|
415
|
+
try {
|
|
416
|
+
const metadata = lstatSync(statePath);
|
|
417
|
+
if (!metadata.isFile() || metadata.isSymbolicLink()) {
|
|
418
|
+
return blockedStorage("invalid", `${STORAGE_STATE_FILE} must be a regular file.`);
|
|
419
|
+
}
|
|
420
|
+
accessSync(statePath, constants.R_OK);
|
|
328
421
|
}
|
|
329
|
-
|
|
422
|
+
catch (error) {
|
|
423
|
+
return blockedStorage("invalid", errorMessage(error));
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
try {
|
|
330
427
|
const store = openCompatibleFileTaskStore(home, compatibility.storageOptions);
|
|
331
428
|
const config = store.getConfig();
|
|
332
429
|
const agents = store.listConfiguredAgents();
|
|
@@ -337,12 +434,12 @@ function inspectState(home, homeCheck, schema, compatibility) {
|
|
|
337
434
|
check: {
|
|
338
435
|
name: "storage state",
|
|
339
436
|
status: "ok",
|
|
340
|
-
detail:
|
|
437
|
+
detail: `${sqliteAuthoritative ? "yui.db readable" : "readable"} agents=${agents.length} tasks=${tasks.length} roles=${roleCount} globalRoles=${globalRoles.length} defaultAgent=${config.defaultAgent ?? "none"}`
|
|
341
438
|
},
|
|
342
439
|
agents,
|
|
343
440
|
review: {
|
|
344
441
|
storageReady: true,
|
|
345
|
-
storageDetail: "state.json is readable",
|
|
442
|
+
storageDetail: sqliteAuthoritative ? "yui.db is readable" : "state.json is readable",
|
|
346
443
|
...(config.review === undefined ? {} : { policy: config.review }),
|
|
347
444
|
...(config.review === undefined
|
|
348
445
|
? {}
|
|
@@ -51,3 +51,12 @@ export function normalizedUniqueIdentities(values, label) {
|
|
|
51
51
|
export function cloneJson(value) {
|
|
52
52
|
return JSON.parse(JSON.stringify(value));
|
|
53
53
|
}
|
|
54
|
+
/**
|
|
55
|
+
* True for a concrete, pinnable package version — a semver-shaped `X.Y.Z` with
|
|
56
|
+
* an optional pre-release/build suffix. Rejects dist-tag sentinels (`latest`,
|
|
57
|
+
* `next`, …), empty/whitespace, and anything not anchored to a numeric
|
|
58
|
+
* `major.minor.patch` so a moving tag can never be frozen into a release plan.
|
|
59
|
+
*/
|
|
60
|
+
export function isConcreteVersion(value) {
|
|
61
|
+
return /^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/.test(value.trim());
|
|
62
|
+
}
|
|
@@ -72,7 +72,8 @@ export function assertExecutionTargetUnchanged(group, target) {
|
|
|
72
72
|
* Enforce the only durable Group/Lane evolution accepted by both domain
|
|
73
73
|
* helpers and storage. Identity, target, prior results, and a final Leader
|
|
74
74
|
* resolution never move backwards. A terminal Lane may only reopen as an
|
|
75
|
-
* explicit retry with a fresh Run identity
|
|
75
|
+
* explicit retry with a fresh Run identity, or reset to pending for a
|
|
76
|
+
* Task-final Review execution retry.
|
|
76
77
|
*/
|
|
77
78
|
export function assertExecutionGroupTransition(existing, candidate) {
|
|
78
79
|
validateExecutionGroup(existing);
|
|
@@ -101,7 +102,7 @@ export function assertExecutionGroupTransition(existing, candidate) {
|
|
|
101
102
|
if (next === undefined) {
|
|
102
103
|
throw new Error(`ExecutionGroup cannot remove Lane: ${lane.id}.`);
|
|
103
104
|
}
|
|
104
|
-
assertExecutionLaneTransition(lane, next, existing.id);
|
|
105
|
+
assertExecutionLaneTransition(lane, next, existing.id, existing.purpose);
|
|
105
106
|
}
|
|
106
107
|
}
|
|
107
108
|
export function isExecutionGroupTransition(existing, candidate) {
|
|
@@ -180,6 +181,32 @@ export function restartExecutionLane(group, laneId, patch, now) {
|
|
|
180
181
|
updatedAt: timestamp
|
|
181
182
|
});
|
|
182
183
|
}
|
|
184
|
+
/**
|
|
185
|
+
* Resets a terminal Reviewer Lane to pending without replacing its ExecutionGroup.
|
|
186
|
+
* The old AgentRun remains the attempt trail; clearing the Lane's Run/session and
|
|
187
|
+
* result lets the same semantic ReviewRound be dispatched again.
|
|
188
|
+
*/
|
|
189
|
+
export function resetReviewExecutionLane(group, laneId, now) {
|
|
190
|
+
validateExecutionGroup(group);
|
|
191
|
+
if (group.purpose !== "review") {
|
|
192
|
+
throw new Error(`Only Review ExecutionLanes can reset to pending: ${group.id}/${laneId}.`);
|
|
193
|
+
}
|
|
194
|
+
if (group.resolution !== undefined) {
|
|
195
|
+
throw new Error(`ExecutionGroup is already resolved: ${group.id}.`);
|
|
196
|
+
}
|
|
197
|
+
const existing = group.lanes.find((lane) => lane.id === laneId);
|
|
198
|
+
if (existing === undefined)
|
|
199
|
+
throw new Error(`ExecutionLane not found: ${group.id}/${laneId}.`);
|
|
200
|
+
if (!isTerminalLane(existing.status))
|
|
201
|
+
return existing;
|
|
202
|
+
const timestamp = now.toISOString();
|
|
203
|
+
const { effective: _effective, runId: _runId, sessionId: _sessionId, result: _result, endedAt: _endedAt, ...base } = existing;
|
|
204
|
+
return validateExecutionLane({
|
|
205
|
+
...base,
|
|
206
|
+
status: "pending",
|
|
207
|
+
updatedAt: timestamp
|
|
208
|
+
}, group);
|
|
209
|
+
}
|
|
183
210
|
export function recordExecutionLaneResult(group, laneId, result, status, now) {
|
|
184
211
|
const checked = validateLaneResult(result);
|
|
185
212
|
validateExecutionGroup(group);
|
|
@@ -509,7 +536,7 @@ function validateResolution(resolution, group) {
|
|
|
509
536
|
throw new Error("Execution accept resolution cannot retain high-priority findings.");
|
|
510
537
|
}
|
|
511
538
|
}
|
|
512
|
-
function assertExecutionLaneTransition(existing, candidate, groupId) {
|
|
539
|
+
function assertExecutionLaneTransition(existing, candidate, groupId, groupPurpose) {
|
|
513
540
|
if (existing.id !== candidate.id
|
|
514
541
|
|| existing.groupId !== candidate.groupId
|
|
515
542
|
|| existing.groupId !== groupId
|
|
@@ -525,6 +552,16 @@ function assertExecutionLaneTransition(existing, candidate, groupId) {
|
|
|
525
552
|
if (isTerminalLane(existing.status)) {
|
|
526
553
|
if (isDeepStrictEqual(existing, candidate))
|
|
527
554
|
return;
|
|
555
|
+
if (groupPurpose === "review"
|
|
556
|
+
&& candidate.status === "pending"
|
|
557
|
+
&& candidate.effective === undefined
|
|
558
|
+
&& candidate.runId === undefined
|
|
559
|
+
&& candidate.sessionId === undefined
|
|
560
|
+
&& candidate.result === undefined
|
|
561
|
+
&& candidate.endedAt === undefined
|
|
562
|
+
&& isDeepStrictEqual(existing.workspace, candidate.workspace)) {
|
|
563
|
+
return;
|
|
564
|
+
}
|
|
528
565
|
if (candidate.status !== "running"
|
|
529
566
|
|| candidate.runId === undefined
|
|
530
567
|
|| candidate.runId === existing.runId) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { hasRecentTurnId, rememberRecentTurnId, validatePendingTurnCompletion, validateRecentTurnIds } from "./turnCompletion.js";
|
|
3
|
-
import { effectiveLaunchSnapshotsCompatible, validateEffectiveLaunchSnapshot } from "./effectiveLaunch.js";
|
|
3
|
+
import { effectiveLaunchSnapshotsCompatible, effectiveLaunchSnapshotsCompatibleForTaskMain, validateEffectiveLaunchSnapshot } from "./effectiveLaunch.js";
|
|
4
4
|
export function createRoleSessionSet(owner, activeAgentId, now) {
|
|
5
5
|
const base = {
|
|
6
6
|
owner: normalizeOwner(owner),
|
|
@@ -241,7 +241,7 @@ export function rememberRoleAgentCompletedTurn(set, agentId, nativeSessionId, tu
|
|
|
241
241
|
};
|
|
242
242
|
return validateRoleSessionSet(updated);
|
|
243
243
|
}
|
|
244
|
-
export function roleAgentSessionResumeMode(set, agentId, desired) {
|
|
244
|
+
export function roleAgentSessionResumeMode(set, agentId, desired, workspace) {
|
|
245
245
|
if (set === null)
|
|
246
246
|
return "new";
|
|
247
247
|
validateRoleSessionSet(set);
|
|
@@ -260,7 +260,10 @@ export function roleAgentSessionResumeMode(set, agentId, desired) {
|
|
|
260
260
|
}
|
|
261
261
|
return "new";
|
|
262
262
|
}
|
|
263
|
-
|
|
263
|
+
const compatible = set.owner.scope === "task"
|
|
264
|
+
? effectiveLaunchSnapshotsCompatibleForTaskMain(session.effective, desired, workspace)
|
|
265
|
+
: effectiveLaunchSnapshotsCompatible(session.effective, desired);
|
|
266
|
+
if (compatible)
|
|
264
267
|
return "resume";
|
|
265
268
|
if (session.status !== "stopped" && session.status !== "broken") {
|
|
266
269
|
throw new Error(`Role Agent session is incompatible with the next effective launch: ${agentId}. `
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { isDeepStrictEqual } from "node:util";
|
|
2
|
+
import { validateManagedWorkspace } from "../worktree/managedWorkspace.js";
|
|
2
3
|
import { resolveAgentAdapter } from "./agentAdapter.js";
|
|
3
4
|
export function resolveEffectiveLaunch(input) {
|
|
4
5
|
validateDesiredRole(input.role);
|
|
@@ -71,6 +72,57 @@ export function effectiveLaunchSnapshotsCompatible(existing, desired) {
|
|
|
71
72
|
};
|
|
72
73
|
return isDeepStrictEqual(withoutDesiredRevision(existing), withoutDesiredRevision(desired));
|
|
73
74
|
}
|
|
75
|
+
/**
|
|
76
|
+
* A Task-owned main workspace is a mutable checkout: Integration advances its
|
|
77
|
+
* durable Git heads while an idle native Session keeps the immutable launch
|
|
78
|
+
* configuration with which it started. A later Run may resume that exact
|
|
79
|
+
* Session only when the current durable Task workspace proves that every
|
|
80
|
+
* non-commit workspace identity and every other launch field is unchanged.
|
|
81
|
+
* WorkItem, ReviewRound and ExecutionLane workspaces remain strict.
|
|
82
|
+
*/
|
|
83
|
+
export function effectiveLaunchSnapshotsCompatibleForTaskMain(existing, desired, workspace) {
|
|
84
|
+
if (effectiveLaunchSnapshotsCompatible(existing, desired))
|
|
85
|
+
return true;
|
|
86
|
+
validateEffectiveLaunchSnapshot(existing);
|
|
87
|
+
validateEffectiveLaunchSnapshot(desired);
|
|
88
|
+
if (workspace === null || workspace === undefined)
|
|
89
|
+
return false;
|
|
90
|
+
validateManagedWorkspace(workspace);
|
|
91
|
+
if (workspace.owner.type !== "task")
|
|
92
|
+
return false;
|
|
93
|
+
const durableWorkspace = {
|
|
94
|
+
root: workspace.root,
|
|
95
|
+
entries: workspace.entries.map((entry) => ({ ...entry }))
|
|
96
|
+
};
|
|
97
|
+
if (!isDeepStrictEqual(desired.workspace, durableWorkspace))
|
|
98
|
+
return false;
|
|
99
|
+
return isDeepStrictEqual(taskMainCompatibleSnapshot(existing), taskMainCompatibleSnapshot(desired));
|
|
100
|
+
}
|
|
101
|
+
/** Preserves a fixed Session's launch configuration while freezing fresh Task-main Git facts. */
|
|
102
|
+
export function effectiveLaunchWithTaskMainWorkspace(existing, workspace) {
|
|
103
|
+
validateEffectiveLaunchSnapshot(existing);
|
|
104
|
+
validateManagedWorkspace(workspace);
|
|
105
|
+
if (workspace.owner.type !== "task") {
|
|
106
|
+
throw new Error("Only a Task-owned main workspace may refresh fixed Session Run evidence.");
|
|
107
|
+
}
|
|
108
|
+
return validateEffectiveLaunchSnapshot({
|
|
109
|
+
...existing,
|
|
110
|
+
workspace: {
|
|
111
|
+
root: workspace.root,
|
|
112
|
+
entries: workspace.entries.map((entry) => ({ ...entry }))
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
function taskMainCompatibleSnapshot(snapshot) {
|
|
117
|
+
const { sourceDesiredRevision: _sourceDesiredRevision, workspace, ...launch } = snapshot;
|
|
118
|
+
return {
|
|
119
|
+
...launch,
|
|
120
|
+
workspace: {
|
|
121
|
+
root: workspace.root,
|
|
122
|
+
entries: workspace.entries.map(({ baseCommit: _baseCommit, ...entry }) => entry)
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
}
|
|
74
126
|
export function validateEffectiveLaunchSnapshot(snapshot) {
|
|
75
127
|
if (snapshot.schemaVersion !== 2) {
|
|
76
128
|
throw new Error("Effective launch snapshot must use schemaVersion 2.");
|
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { createPromptEnvelope, createSessionLaunchRequest } from "../runtime/index.js";
|
|
3
|
+
/**
|
|
4
|
+
* rr13/test: Test-only liveness seam. Integration tests that spawn a real
|
|
5
|
+
* Controller subprocess cannot inject a fake TmuxDeliveryPort, and a saved
|
|
6
|
+
* active Leader Run would be reaped by the startup liveness pass without a
|
|
7
|
+
* real tmux role. When this env var is "1", every role reads "present"
|
|
8
|
+
* without probing tmux. The Controller subprocess inherits it from the
|
|
9
|
+
* test's CLI env. Never set in production.
|
|
10
|
+
*/
|
|
11
|
+
const TEST_ROLE_LIVENESS_PRESENT = process.env.YUI_TEST_ROLE_LIVENESS_PRESENT === "1";
|
|
3
12
|
/**
|
|
4
13
|
* Scheduler-to-tmux adapter. It retains only in-process prepared launch data;
|
|
5
14
|
* durable session identity remains owned by FileTaskStore.
|
|
@@ -39,6 +48,16 @@ export class ExecutorRegistry {
|
|
|
39
48
|
if (this.runtimePorts === undefined) {
|
|
40
49
|
planned = this.planner.plan(input);
|
|
41
50
|
sessionStarted = this.tmux.ensureRoleWindow(input.taskId, planned.role, planned.launch);
|
|
51
|
+
if (sessionStarted
|
|
52
|
+
&& planned.launch.env.YUI_JOB_CALLER_KEY !== undefined
|
|
53
|
+
&& this.planner.commitTaskCallerKey !== undefined) {
|
|
54
|
+
this.planner.commitTaskCallerKey({
|
|
55
|
+
taskId: input.taskId,
|
|
56
|
+
roleName: input.roleName,
|
|
57
|
+
agentId: input.agentId,
|
|
58
|
+
callerKey: planned.launch.env.YUI_JOB_CALLER_KEY
|
|
59
|
+
});
|
|
60
|
+
}
|
|
42
61
|
session = planned.session;
|
|
43
62
|
}
|
|
44
63
|
else {
|
|
@@ -108,6 +127,7 @@ export class ExecutorRegistry {
|
|
|
108
127
|
this.#prepared.set(delivery.deliveryId, {
|
|
109
128
|
delivery,
|
|
110
129
|
session,
|
|
130
|
+
workspace: input.workspace,
|
|
111
131
|
...(planned === undefined ? {} : { planned }),
|
|
112
132
|
...(binding === undefined ? {} : { binding })
|
|
113
133
|
});
|
|
@@ -127,6 +147,27 @@ export class ExecutorRegistry {
|
|
|
127
147
|
if (runId === undefined) {
|
|
128
148
|
throw new Error("Runtime prompt delivery requires a Task-local Run id.");
|
|
129
149
|
}
|
|
150
|
+
// A reused native process retains the stable descriptor path from its
|
|
151
|
+
// original control plane. Publish only the current-control source after
|
|
152
|
+
// the Run/Session fence is durable and immediately before provider input;
|
|
153
|
+
// the reused Hook self-refreshes its own source before the volatile
|
|
154
|
+
// fence instead of the Controller scanning history to keep it fresh.
|
|
155
|
+
if (prepared.binding.hostCreated === false
|
|
156
|
+
&& this.planner.refreshTaskRuntimeDescriptor !== undefined) {
|
|
157
|
+
if (!hasText(prepared.binding.nativeSessionId)) {
|
|
158
|
+
throw new Error("Runtime prompt delivery requires a native Session id.");
|
|
159
|
+
}
|
|
160
|
+
this.planner.refreshTaskRuntimeDescriptor({
|
|
161
|
+
taskId: input.delivery.prepared.taskId,
|
|
162
|
+
roleName: input.delivery.prepared.roleName,
|
|
163
|
+
runId,
|
|
164
|
+
launchId: prepared.binding.launchId,
|
|
165
|
+
nativeSessionId: prepared.binding.nativeSessionId,
|
|
166
|
+
agentId: prepared.binding.agentId,
|
|
167
|
+
adapterId: prepared.binding.adapterId,
|
|
168
|
+
workspace: prepared.workspace
|
|
169
|
+
});
|
|
170
|
+
}
|
|
130
171
|
const outcome = await this.runtimePorts.promptPush.tryPush({
|
|
131
172
|
binding: prepared.binding,
|
|
132
173
|
envelope: createPromptEnvelope({
|
|
@@ -172,6 +213,8 @@ export class ExecutorRegistry {
|
|
|
172
213
|
}
|
|
173
214
|
}
|
|
174
215
|
async inspectRole(input) {
|
|
216
|
+
if (TEST_ROLE_LIVENESS_PRESENT)
|
|
217
|
+
return "present";
|
|
175
218
|
const status = this.tmux.probeRoleStatusAsync === undefined
|
|
176
219
|
? this.tmux.probeRoleStatus(input.taskId, input.roleName)
|
|
177
220
|
: await this.tmux.probeRoleStatusAsync(input.taskId, input.roleName);
|
|
@@ -198,6 +241,13 @@ export class ExecutorRegistry {
|
|
|
198
241
|
}
|
|
199
242
|
}
|
|
200
243
|
async inspectRoles(inputs, resourceInputs) {
|
|
244
|
+
if (TEST_ROLE_LIVENESS_PRESENT) {
|
|
245
|
+
return inputs.map((input) => ({
|
|
246
|
+
taskId: input.taskId,
|
|
247
|
+
roleName: input.roleName,
|
|
248
|
+
status: "present"
|
|
249
|
+
}));
|
|
250
|
+
}
|
|
201
251
|
if (this.tmux.inspectRolePaneInventory === undefined
|
|
202
252
|
&& this.tmux.inspectRolePaneInventoryAsync === undefined) {
|
|
203
253
|
return Promise.all(inputs.map(async (input) => ({
|