@zq-silk/yui 0.8.9 → 0.10.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 +48 -46
- package/README.md +72 -42
- package/dist/cli/commandCatalog.js +16 -8
- package/dist/cli.js +27 -32
- package/dist/commands/executionAuditCommands.js +2 -2
- package/dist/commands/globalRoleCommands.js +0 -12
- package/dist/commands/sessionCommands.js +116 -0
- package/dist/commands/taskBaseCommands.js +1 -11
- package/dist/commands/taskCommands.js +123 -340
- package/dist/commands/taskCompletionGate.js +15 -12
- package/dist/commands/taskContextCommand.js +18 -11
- package/dist/commands/taskNextActionCommand.js +4 -5
- package/dist/commands/taskWorkspaceCommands.js +2 -2
- package/dist/controller/clientRuntime.js +65 -0
- package/dist/controller/fileSchedulerStoreAdapter.js +2 -49
- package/dist/doctor/doctor.js +16 -12
- package/dist/execution/executionGroup.js +0 -3
- package/dist/executor/agentAdapter.js +39 -42
- package/dist/executor/agentExecutor.js +4 -2
- package/dist/executor/codexConfigConflict.js +40 -16
- package/dist/executor/effectiveLaunch.js +33 -3
- package/dist/executor/fileRoleLaunchPlanner.js +15 -24
- package/dist/integration/deliveryObligation.js +72 -0
- package/dist/integration/gitIntegrationService.js +1 -1
- package/dist/lifecycle/exactRunTerminalization.js +12 -8
- package/dist/observability/orchestrationMetrics.js +12 -26
- package/dist/profile/agentProfile.js +1 -1
- package/dist/repository/taskBaseFreshness.js +5 -11
- package/dist/repository/taskWorkspaceCoordinator.js +2 -0
- package/dist/repository/taskWorkspacePreparer.js +173 -26
- package/dist/review/reviewRound.js +41 -24
- package/dist/role/role.js +0 -9
- package/dist/runtime/{firstProgressStopLoss.js → firstProgressAdvisory.js} +7 -21
- package/dist/scheduler/leaderWakeupProcessor.js +0 -25
- package/dist/setup/setupCommand.js +0 -5
- package/dist/storage/migration/productionRegistry.js +138 -0
- package/dist/storage/sqliteStore.js +2 -1
- package/dist/storage/taskStore.js +27 -27
- package/dist/storage/upgrade/upgradeOrchestrator.js +35 -7
- package/dist/task/completionReadiness.js +35 -25
- package/dist/task/nextAction.js +70 -78
- package/dist/task/task.js +12 -19
- package/dist/web/assets/client/components.js +3 -1
- package/dist/web/assets/client/i18n.js +6 -0
- package/dist/web/webSnapshot.js +0 -3
- package/i18n/README.zh-CN.md +33 -24
- package/package.json +1 -1
- package/skills/yui-leader/SKILL.md +55 -52
- package/skills/yui-operator/SKILL.md +31 -40
- package/skills/yui-reviewer/SKILL.md +18 -12
- package/skills/yui-worker/SKILL.md +5 -3
|
@@ -2,14 +2,13 @@ import { usageError } from "../errors/cliError.js";
|
|
|
2
2
|
import { GitIntegrationService } from "../integration/gitIntegrationService.js";
|
|
3
3
|
import { createIntegrationAttempt } from "../integration/integrationAttempt.js";
|
|
4
4
|
import { NodeGitWorkspace } from "../repository/gitWorkspace.js";
|
|
5
|
-
import { taskDeliveryPath } from "../task/task.js";
|
|
6
5
|
import { publicationExternalKey } from "../task/publicationReference.js";
|
|
7
6
|
import { isSemanticReviewRound } from "../review/reviewOutcomeClassifier.js";
|
|
8
7
|
import { workspaceProjectEntry } from "../worktree/managedWorkspace.js";
|
|
9
8
|
/**
|
|
10
9
|
* Verify the one supported ancestry waiver before `task complete` mutates any
|
|
11
10
|
* durable state. The explicit Publication must be the current verified merged
|
|
12
|
-
* record, bind the exact physical Task head (and,
|
|
11
|
+
* record, bind the exact physical Task head (and, when WorkItems exist, its
|
|
13
12
|
* completed final Review), and name an ancestry-divergent commit with the
|
|
14
13
|
* exact same Git tree.
|
|
15
14
|
*/
|
|
@@ -30,12 +29,14 @@ export async function verifyTaskCompletionPublishedTree(taskId, publicationId, s
|
|
|
30
29
|
throw usageError(`Publication ${publication.id} must record exact local and remote commits.`);
|
|
31
30
|
}
|
|
32
31
|
const workspace = requireTaskWorkspace(store, task);
|
|
33
|
-
const
|
|
34
|
-
const latestReview =
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
32
|
+
const latestRecordedReview = latestTaskFinalReview(store, task.id);
|
|
33
|
+
const latestReview = latestRecordedReview !== undefined
|
|
34
|
+
&& isSemanticReviewRound(latestRecordedReview, store)
|
|
35
|
+
&& latestRecordedReview.taskCandidate !== undefined
|
|
36
|
+
? latestRecordedReview
|
|
37
|
+
: undefined;
|
|
38
|
+
if (latestRecordedReview !== undefined && latestReview === undefined) {
|
|
39
|
+
throw usageError(`Task ${task.id} has an unsettled Task-final Review obligation before accepting a published tree.`);
|
|
39
40
|
}
|
|
40
41
|
const git = options.git ?? new NodeGitWorkspace();
|
|
41
42
|
const actualHeads = new Map();
|
|
@@ -122,7 +123,7 @@ export function assertTaskCompletionPublishedTreeProof(store, task, publicationI
|
|
|
122
123
|
|| publication.remoteCommit !== proof.remoteCommit) {
|
|
123
124
|
throw usageError(`Publication evidence changed before Task completion: ${publication.id}.`);
|
|
124
125
|
}
|
|
125
|
-
if (
|
|
126
|
+
if (proof.reviewRoundId !== undefined) {
|
|
126
127
|
const latestReview = latestTaskFinalReview(store, task.id);
|
|
127
128
|
if (latestReview === undefined
|
|
128
129
|
|| latestReview.id !== proof.reviewRoundId
|
|
@@ -132,8 +133,8 @@ export function assertTaskCompletionPublishedTreeProof(store, task, publicationI
|
|
|
132
133
|
throw usageError(`Task-final Review evidence changed before published-tree completion: ${task.id}.`);
|
|
133
134
|
}
|
|
134
135
|
}
|
|
135
|
-
else if (
|
|
136
|
-
throw usageError(`
|
|
136
|
+
else if (latestTaskFinalReview(store, task.id) !== undefined) {
|
|
137
|
+
throw usageError(`Published-tree proof omitted the Task-final Review: ${task.id}.`);
|
|
137
138
|
}
|
|
138
139
|
const actualCommit = actualCandidate.projects.find(({ projectId }) => (projectId === proof.projectId))?.commit;
|
|
139
140
|
if (actualCommit !== proof.localCommit) {
|
|
@@ -183,8 +184,10 @@ export async function reconcileTaskRemoteBaselines(taskId, store, home, options
|
|
|
183
184
|
// The delivery contract opts Project-backed Tasks into committed
|
|
184
185
|
// Integration evidence. Metadata-only Tasks retain their existing local
|
|
185
186
|
// completion semantics and have no remote baseline to reconcile here.
|
|
186
|
-
if (task.projectBindings.length === 0
|
|
187
|
+
if (task.projectBindings.length === 0
|
|
188
|
+
|| !store.listIntegrationAttempts(task.id).some(({ status }) => status === "committed")) {
|
|
187
189
|
return [];
|
|
190
|
+
}
|
|
188
191
|
const workspace = requireTaskWorkspace(store, task);
|
|
189
192
|
const git = options.git ?? new NodeGitWorkspace();
|
|
190
193
|
if (git.fetchRemoteHeadIntoWorktree === undefined
|
|
@@ -4,7 +4,6 @@ import { formatTimestamp } from "../output/timePresentation.js";
|
|
|
4
4
|
import { isRoleRunStalled, latestStallProgressAt } from "../scheduler/roleRunStall.js";
|
|
5
5
|
import { buildTaskExecutionProjection } from "../scheduler/taskExecutionProjection.js";
|
|
6
6
|
import { projectNextAction } from "../task/nextAction.js";
|
|
7
|
-
import { taskDeliveryPath } from "../task/task.js";
|
|
8
7
|
import { inspectTaskRoleSessionRecovery } from "./taskRoleRuntimeStatus.js";
|
|
9
8
|
import { summarizeExecutionGroup } from "../execution/executionGroup.js";
|
|
10
9
|
import { currentWorkItemExecutionGroup } from "../workItem/workItem.js";
|
|
@@ -54,7 +53,6 @@ export function runTaskContextCommand(args, store) {
|
|
|
54
53
|
}
|
|
55
54
|
return {
|
|
56
55
|
task,
|
|
57
|
-
deliveryPath: taskDeliveryPath(task),
|
|
58
56
|
execution,
|
|
59
57
|
reviewConfig: reader.getReviewConfig(),
|
|
60
58
|
brief: reader.getTaskBrief(task.id),
|
|
@@ -141,12 +139,15 @@ export function runTaskContextCommand(args, store) {
|
|
|
141
139
|
...(managedWorkspaces.length === 0
|
|
142
140
|
? [" None."]
|
|
143
141
|
: managedWorkspaces.map((workspace) => (` ${managedWorkspaceLabel(workspace)}: ${workspace.root} (${workspace.entries.filter(({ access }) => access === "write").length} writable / ${workspace.entries.length} Projects)`))),
|
|
144
|
-
`
|
|
145
|
-
`
|
|
146
|
-
? "
|
|
147
|
-
:
|
|
142
|
+
`Type: ${task.type ?? "unspecified"}`,
|
|
143
|
+
`Execution topology: ${workItems.length === 0
|
|
144
|
+
? "Leader-owned Task main"
|
|
145
|
+
: `${workItems.length} independently owned WorkItem(s), integrated on Task main`}`,
|
|
146
|
+
`Completion evidence: ${task.projectBindings.length === 0
|
|
147
|
+
? "no Project evidence required"
|
|
148
|
+
: workItems.length === 0
|
|
148
149
|
? "clean committed Task main required"
|
|
149
|
-
: "
|
|
150
|
+
: "each delivered WorkItem requires a ChangeSet and committed Integration"}`,
|
|
150
151
|
`Global review: ${reviewConfig === null
|
|
151
152
|
? "disabled"
|
|
152
153
|
: `${reviewConfig.roleName} (${reviewConfig.trigger})`}`,
|
|
@@ -259,10 +260,13 @@ export function runTaskContextCommand(args, store) {
|
|
|
259
260
|
? []
|
|
260
261
|
: [` Summary: ${compactText(latestRun.summary)}`])
|
|
261
262
|
]),
|
|
262
|
-
...
|
|
263
|
+
...renderReviewRounds(reviewRounds.filter((round) => round.workItemId === item.id))
|
|
263
264
|
];
|
|
264
265
|
})),
|
|
265
266
|
"",
|
|
267
|
+
"Task-final reviews:",
|
|
268
|
+
...renderReviewRounds(reviewRounds.filter((round) => (round.scope ?? "work-item") === "task")),
|
|
269
|
+
"",
|
|
266
270
|
...recentSection("AgentRuns", agentRuns, (run) => [
|
|
267
271
|
` ${run.id} [${run.status}/${run.purpose}] ${run.roleName} via ${run.effective.agentId}/${run.effective.adapterId}`,
|
|
268
272
|
` Effective: r${run.effective.sourceDesiredRevision}; Profile intent: ${run.effective.profileAccess}; permission: ${run.effective.permission.strategy}; model: ${run.effective.model ?? "default"}; effort: ${run.effective.effort ?? "default"}`,
|
|
@@ -358,16 +362,19 @@ function latestStallKind(events, runId) {
|
|
|
358
362
|
.sort((left, right) => Date.parse(right.createdAt) - Date.parse(left.createdAt))[0];
|
|
359
363
|
return event?.payload.kind ?? "workflow-not-progressing";
|
|
360
364
|
}
|
|
361
|
-
function
|
|
365
|
+
function renderReviewRounds(rounds) {
|
|
362
366
|
const latest = rounds.at(-1);
|
|
363
367
|
if (latest === undefined)
|
|
364
368
|
return [" ReviewRounds: none."];
|
|
369
|
+
const target = (latest.scope ?? "work-item") === "task"
|
|
370
|
+
? "frozen Task candidate"
|
|
371
|
+
: `Candidate ${latest.candidateId ?? "unavailable"}`;
|
|
365
372
|
return [
|
|
366
|
-
` ReviewRounds: ${rounds.length}; latest ${latest.id} [${latest.status}] ${latest.scope === "task" ? "Task-final" : "WorkItem"} for ${
|
|
373
|
+
` ReviewRounds: ${rounds.length}; latest ${latest.id} [${latest.status}] ${latest.scope === "task" ? "Task-final" : "WorkItem"} for ${target} via ${latest.reviewerRoleName} (${latest.requestedBy})`,
|
|
367
374
|
` Review base: ${latest.reviewBaseCommit}`,
|
|
368
375
|
...(latest.scope === "task"
|
|
369
376
|
? [
|
|
370
|
-
` Frozen
|
|
377
|
+
` Frozen Task heads: ${latest.taskCandidate?.projects
|
|
371
378
|
.map(({ projectId, commit }) => `${projectId}@${commit}`).join(", ") ?? "unavailable"}`,
|
|
372
379
|
` Task-final contract: ${latest.taskFinalReviewContract?.digest ?? "global policy"}`,
|
|
373
380
|
...(latest.deltaRecheck === undefined
|
|
@@ -2,7 +2,6 @@ import { taskNotFound, usageError } from "../errors/cliError.js";
|
|
|
2
2
|
import { projectNextAction } from "../task/nextAction.js";
|
|
3
3
|
import { projectCompletionReadiness } from "../task/completionReadiness.js";
|
|
4
4
|
import { extractReviewFindings, planRepairWave } from "../task/repairWave.js";
|
|
5
|
-
import { taskDeliveryPath } from "../task/task.js";
|
|
6
5
|
import { projectTaskOrchestration } from "../observability/orchestrationMetrics.js";
|
|
7
6
|
import { operationalTaskRecords } from "../task/taskRecordRetirement.js";
|
|
8
7
|
/**
|
|
@@ -75,7 +74,7 @@ export function runTaskNextActionCommand(args, store) {
|
|
|
75
74
|
managedWorkspaces: reader.listManagedWorkspaces(taskId)
|
|
76
75
|
});
|
|
77
76
|
return {
|
|
78
|
-
|
|
77
|
+
taskType: facts.task.type ?? null,
|
|
79
78
|
action,
|
|
80
79
|
repairWave,
|
|
81
80
|
completionReadiness,
|
|
@@ -92,7 +91,7 @@ export function runTaskNextActionCommand(args, store) {
|
|
|
92
91
|
}
|
|
93
92
|
return {
|
|
94
93
|
kind: "output",
|
|
95
|
-
output: renderNextAction(data.action, data.
|
|
94
|
+
output: renderNextAction(data.action, data.taskType, data.repairWave, data.completionReadiness, data.knowledgeProposals, data.orchestration.advisories),
|
|
96
95
|
data
|
|
97
96
|
};
|
|
98
97
|
}
|
|
@@ -110,10 +109,10 @@ function repairWaveFor(action, facts) {
|
|
|
110
109
|
return null;
|
|
111
110
|
return planRepairWave(round.id, findings);
|
|
112
111
|
}
|
|
113
|
-
function renderNextAction(action,
|
|
112
|
+
function renderNextAction(action, taskType, repairWave, completionReadiness, knowledgeProposals, orchestrationAdvisories) {
|
|
114
113
|
const lines = [
|
|
115
114
|
`Task: ${action.taskId}`,
|
|
116
|
-
`
|
|
115
|
+
`Type: ${taskType ?? "unspecified"}`,
|
|
117
116
|
`Next action: ${action.kind}`,
|
|
118
117
|
`Reason: ${action.reason}`,
|
|
119
118
|
...(action.refs.length === 0
|
|
@@ -119,8 +119,8 @@ function replaceTaskCommand(args, store, options) {
|
|
|
119
119
|
createArgs.push("--project", binding.projectId);
|
|
120
120
|
createArgs.push("--base", `${binding.projectId}=${binding.baseRef}`);
|
|
121
121
|
}
|
|
122
|
-
if (old.
|
|
123
|
-
createArgs.push("--
|
|
122
|
+
if (old.type !== undefined)
|
|
123
|
+
createArgs.push("--type", old.type);
|
|
124
124
|
const created = runTaskCommand(createArgs, store);
|
|
125
125
|
if (created.kind !== "output") {
|
|
126
126
|
throw new Error(`Task replacement could not be created for ${old.id}.`);
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
2
|
import { fileURLToPath } from "node:url";
|
|
3
3
|
import { callController, readControllerDiscovery, stopOrphanedFileTaskController, stopPreviousFileTaskController } from "../core/controllerClient.js";
|
|
4
|
+
import { controllerSocketPath } from "../core/controllerEndpoint.js";
|
|
4
5
|
import { FILE_TASK_CONTROLLER_PROTOCOL_VERSION } from "../core/protocol.js";
|
|
5
6
|
import { AGENT_OPERATIONAL_ENVIRONMENT_NAMES, nativeAgentEnvironmentNames, operationalAgentEnvironment, selectEnvironment, YUI_MANAGED_RUNTIME_ENVIRONMENT_NAMES } from "../agent/launchEnvironment.js";
|
|
6
7
|
import { openCompatibleFileTaskStore } from "../storage/compatibleTaskStore.js";
|
|
8
|
+
import { yuiTmuxServerName } from "../tmux/tmuxManager.js";
|
|
7
9
|
import { hasRuntimeLifecycleWork } from "../runtime/lifecycleReservation.js";
|
|
8
10
|
import { assertControllerStatusIdentity } from "../runtime/exactControlPlane.js";
|
|
9
11
|
import { EPHEMERAL_DOMAIN_ENVIRONMENT_NAMES } from "./domainIdentity.js";
|
|
@@ -11,6 +13,7 @@ import { yuiVersionIdentity } from "../version.js";
|
|
|
11
13
|
import { SessionOwnerReconciliation } from "./sessionOwnerReconciliation.js";
|
|
12
14
|
import { WorkspaceCleanupBlockedError } from "../repository/taskWorkspacePreparer.js";
|
|
13
15
|
import { CONTROLLER_SHUTDOWN_TIMEOUT_MS, LIFECYCLE_REQUEST_TIMEOUT_MS } from "../runtime/runtimeDeadlines.js";
|
|
16
|
+
import { FileTaskRuntimeIsolation } from "../runtime/taskRuntimeIsolation.js";
|
|
14
17
|
const STARTUP_TIMEOUT_MS = 5_000;
|
|
15
18
|
const POLL_INTERVAL_MS = 50;
|
|
16
19
|
const ENVIRONMENT_REFRESH_TIMEOUT_MS = 500;
|
|
@@ -502,6 +505,63 @@ export class FileTaskWorkflowRuntime {
|
|
|
502
505
|
throw new Error(`Global Role runtime session is still active: ${roleName}.`);
|
|
503
506
|
}
|
|
504
507
|
}
|
|
508
|
+
/** Drain pending runtime facts while an external maintenance fence is held. */
|
|
509
|
+
async drainController() {
|
|
510
|
+
await ensureFileTaskController(this.home, {
|
|
511
|
+
environment: this.clientOptions.environment
|
|
512
|
+
});
|
|
513
|
+
await callFileTaskController(this.home, "scheduler.scan", {}, {
|
|
514
|
+
...this.clientOptions,
|
|
515
|
+
requestTimeoutMs: LIFECYCLE_REQUEST_TIMEOUT_MS
|
|
516
|
+
});
|
|
517
|
+
}
|
|
518
|
+
/**
|
|
519
|
+
* Maintenance-only exact stop used after the Controller has fully exited.
|
|
520
|
+
* The dormant candidate fences the durable cleanup request; physical owner
|
|
521
|
+
* records and Task runtime isolation are cleared through the same primitives
|
|
522
|
+
* as the Controller lifecycle path before the Session becomes stopped.
|
|
523
|
+
*/
|
|
524
|
+
async stopDormantSession(candidate) {
|
|
525
|
+
const queuedAt = new Date();
|
|
526
|
+
const target = this.schedulerStore.enqueueRuntimeCleanup(candidate.owner, queuedAt, candidate);
|
|
527
|
+
if (target === null) {
|
|
528
|
+
throw new Error(`Dormant Session changed before maintenance cleanup: ${runtimeOwnerLabel(candidate.owner)}.`);
|
|
529
|
+
}
|
|
530
|
+
const reconciliation = new SessionOwnerReconciliation({
|
|
531
|
+
home: this.home,
|
|
532
|
+
store: this.store,
|
|
533
|
+
environment: this.clientOptions.environment,
|
|
534
|
+
tmux: this.tmux
|
|
535
|
+
});
|
|
536
|
+
const termination = await reconciliation.terminateOwner(candidate.owner);
|
|
537
|
+
if (termination.outcome !== "stop-confirmed") {
|
|
538
|
+
throw new Error(`Role runtime cleanup could not prove physical exit: ${runtimeOwnerLabel(candidate.owner)}; `
|
|
539
|
+
+ termination.remaining
|
|
540
|
+
.map(({ record, detail }) => `${record.launchId}: ${detail}`)
|
|
541
|
+
.join("; "));
|
|
542
|
+
}
|
|
543
|
+
if (candidate.owner.scope === "task" && candidate.launchId !== undefined) {
|
|
544
|
+
const isolation = new FileTaskRuntimeIsolation({
|
|
545
|
+
runtimeRoot: `${this.home}.task-runtimes`,
|
|
546
|
+
controlPlane: {
|
|
547
|
+
yuiHome: this.home,
|
|
548
|
+
controllerSocketPath: controllerSocketPath(this.store.getHomeIdentity().homeId),
|
|
549
|
+
tmuxNamespace: yuiTmuxServerName(this.home),
|
|
550
|
+
globalInstallPaths: [process.execPath]
|
|
551
|
+
}
|
|
552
|
+
});
|
|
553
|
+
isolation.cleanupTaskLaunch({
|
|
554
|
+
taskId: candidate.owner.taskId,
|
|
555
|
+
launchId: candidate.launchId,
|
|
556
|
+
reason: this.store.getTask(candidate.owner.taskId)?.status === "completed"
|
|
557
|
+
? "completion"
|
|
558
|
+
: "interruption"
|
|
559
|
+
});
|
|
560
|
+
}
|
|
561
|
+
if (!this.schedulerStore.completeRuntimeCleanup(target, new Date())) {
|
|
562
|
+
throw new Error(`Role runtime cleanup state changed before completion: ${runtimeOwnerLabel(candidate.owner)}.`);
|
|
563
|
+
}
|
|
564
|
+
}
|
|
505
565
|
inspectTaskRolePanes(taskId) {
|
|
506
566
|
return this.tmux.inspectTaskRolePanes(taskId);
|
|
507
567
|
}
|
|
@@ -532,6 +592,11 @@ export class FileTaskWorkflowRuntime {
|
|
|
532
592
|
});
|
|
533
593
|
}
|
|
534
594
|
}
|
|
595
|
+
function runtimeOwnerLabel(owner) {
|
|
596
|
+
return owner.scope === "task"
|
|
597
|
+
? `${owner.taskId}/${owner.roleName}`
|
|
598
|
+
: `global/${owner.roleName}`;
|
|
599
|
+
}
|
|
535
600
|
const MANAGED_RUNTIME_ENVIRONMENT = new Set(YUI_MANAGED_RUNTIME_ENVIRONMENT_NAMES);
|
|
536
601
|
function foregroundGlobalRoleEnvironment(store, roleName, source) {
|
|
537
602
|
const role = store.getGlobalRole?.(roleName);
|
|
@@ -3,7 +3,6 @@ import { isDeepStrictEqual } from "node:util";
|
|
|
3
3
|
import { activeLiveRoleAgentSession, bindTaskRoleProviderRuntime, bindTaskRoleRun, clearTaskRoleProviderRuntimeForCleanup, clearTaskRoleRun, createRoleSessionSet, markTaskRoleRunDelivered, markTaskRoleRunPushed, prepareTaskRoleRunRedispatch, recordRoleAgentSession, recordTaskRoleTurnBoundary, rememberRoleAgentCompletedTurn, updateRoleAgentSessionStatus, updateTaskRoleProviderRuntime } from "../executor/agentExecutor.js";
|
|
4
4
|
import { acceptProviderTurn, beginProviderTurn, createProviderRuntimeBinding, endProviderActivation, currentProviderActivation, currentProviderConversation, markProviderTurnDeliveryUnknown, rejectProviderTurn, settleProviderTurn, startProviderActivation, supersedeProviderConversation, updateProviderConversationRecoverability } from "../runtime/providerRuntimeIdentity.js";
|
|
5
5
|
import { decideProviderRecovery } from "../runtime/providerRecoveryDecision.js";
|
|
6
|
-
import { boundProviderRetryBeforeFirstProgress, projectFirstProgressStopLoss } from "../runtime/firstProgressStopLoss.js";
|
|
7
6
|
import { hasRecentTurnId } from "../executor/turnCompletion.js";
|
|
8
7
|
import { createTaskEvent } from "../event/taskEvent.js";
|
|
9
8
|
import { operationalTaskRecords } from "../task/taskRecordRetirement.js";
|
|
@@ -1300,40 +1299,6 @@ export class FileSchedulerStoreAdapter {
|
|
|
1300
1299
|
clearPendingWakeup(taskId) { this.store.clearPendingWakeup(taskId); }
|
|
1301
1300
|
getLeaderFailure(taskId) { return this.store.getLeaderFailure(taskId); }
|
|
1302
1301
|
getOperatorNotification(taskId) { return this.store.getOperatorNotification(taskId); }
|
|
1303
|
-
saveLeaderFirstProgressStopLoss(input) {
|
|
1304
|
-
return this.store.transaction((store) => {
|
|
1305
|
-
const task = store.getTask(input.taskId);
|
|
1306
|
-
const role = store.getRole(input.taskId, input.roleName);
|
|
1307
|
-
if (task === null || task.status !== "active" || role === null
|
|
1308
|
-
|| store.getLeaderFailure(input.taskId) !== null) {
|
|
1309
|
-
return "state-changed";
|
|
1310
|
-
}
|
|
1311
|
-
const stopLoss = projectFirstProgressStopLoss({
|
|
1312
|
-
sessions: store.getTaskRoleSessionSet(input.taskId, input.roleName),
|
|
1313
|
-
events: store.listEvents(input.taskId),
|
|
1314
|
-
workItems: store.listWorkItems(input.taskId),
|
|
1315
|
-
reviewRounds: store.listReviewRounds(input.taskId),
|
|
1316
|
-
integrations: store.listIntegrationAttempts(input.taskId)
|
|
1317
|
-
});
|
|
1318
|
-
if (!stopLoss.exhausted || stopLoss.fingerprint !== input.expectedFingerprint) {
|
|
1319
|
-
return "state-changed";
|
|
1320
|
-
}
|
|
1321
|
-
const sessions = store.getTaskRoleSessionSet(input.taskId, input.roleName);
|
|
1322
|
-
const lastSession = sessions === null
|
|
1323
|
-
? undefined
|
|
1324
|
-
: [...(sessions.history ?? []), ...Object.values(sessions.sessions)]
|
|
1325
|
-
.sort((left, right) => left.createdAt.localeCompare(right.createdAt))
|
|
1326
|
-
.at(-1);
|
|
1327
|
-
const message = `Leader first-progress stop-loss: ${stopLoss.reason}`;
|
|
1328
|
-
store.saveRole(input.taskId, updateRoleStatus(role, "failed", input.now));
|
|
1329
|
-
store.saveLeaderFailure(recordLeaderFailure(input.taskId, lastSession?.nativeSessionId ?? "(unregistered)", message, input.now, null));
|
|
1330
|
-
store.saveOperatorNotification(createLeaderRecoveryNotification(input.taskId, message, input.now, store.getOperatorNotification(input.taskId)));
|
|
1331
|
-
enqueueWork(store, { kind: "operator" }, "leader-first-progress-stop-loss", input.now, [
|
|
1332
|
-
{ type: "task", id: input.taskId }
|
|
1333
|
-
]);
|
|
1334
|
-
return "recorded";
|
|
1335
|
-
});
|
|
1336
|
-
}
|
|
1337
1302
|
saveLeaderDispatch(input) {
|
|
1338
1303
|
return this.store.transaction((store) => {
|
|
1339
1304
|
const task = store.getTask(input.task.id);
|
|
@@ -2287,7 +2252,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
2287
2252
|
failedNativeTurnId: input.nativeTurnId,
|
|
2288
2253
|
lastErrorSummary: summary,
|
|
2289
2254
|
...(input.retryAfterMs === undefined ? {} : { retryAfterMs: input.retryAfterMs })
|
|
2290
|
-
}, now, configuredProviderRetryPolicy(store
|
|
2255
|
+
}, now, configuredProviderRetryPolicy(store));
|
|
2291
2256
|
if (retryDecision.outcome === "exhausted") {
|
|
2292
2257
|
this.recordProviderRetryClassified(store, input, classification.errorClass, {
|
|
2293
2258
|
wouldRetry: "false",
|
|
@@ -4069,19 +4034,7 @@ function compareCanonicalObservationOrder(left, right) {
|
|
|
4069
4034
|
|| (left.ordinal ?? -1) - (right.ordinal ?? -1)
|
|
4070
4035
|
|| left.eventId.localeCompare(right.eventId);
|
|
4071
4036
|
}
|
|
4072
|
-
function configuredProviderRetryPolicy(store
|
|
4037
|
+
function configuredProviderRetryPolicy(store) {
|
|
4073
4038
|
const config = providerRetryConfig(store.getConfig());
|
|
4074
|
-
if (taskId !== undefined && roleName === "leader") {
|
|
4075
|
-
const progress = projectFirstProgressStopLoss({
|
|
4076
|
-
sessions: store.getTaskRoleSessionSet(taskId, roleName),
|
|
4077
|
-
events: store.listEvents(taskId),
|
|
4078
|
-
workItems: store.listWorkItems(taskId),
|
|
4079
|
-
reviewRounds: store.listReviewRounds(taskId),
|
|
4080
|
-
integrations: store.listIntegrationAttempts(taskId)
|
|
4081
|
-
});
|
|
4082
|
-
if (progress.firstProgressAt === undefined) {
|
|
4083
|
-
return boundProviderRetryBeforeFirstProgress(config, progress);
|
|
4084
|
-
}
|
|
4085
|
-
}
|
|
4086
4039
|
return { delaysMs: config.delaysMs, maxWindowMs: config.maxWindowMs };
|
|
4087
4040
|
}
|
package/dist/doctor/doctor.js
CHANGED
|
@@ -4,7 +4,7 @@ import Database from "better-sqlite3";
|
|
|
4
4
|
import { configuredAgentToDefinition, resolveAgentEnvironment } from "../agent/agent.js";
|
|
5
5
|
import { operationalAgentEnvironment } from "../agent/launchEnvironment.js";
|
|
6
6
|
import { inspectAgentCapabilities, resolveAgentAdapter } from "../executor/agentAdapter.js";
|
|
7
|
-
import { inspectCodexLaunchConfig } from "../executor/codexConfigConflict.js";
|
|
7
|
+
import { assertCodexLaunchOverridesAvailable, inspectCodexLaunchConfig } from "../executor/codexConfigConflict.js";
|
|
8
8
|
import { nativeAdditionalDirectories, nativeAgentWorkspace, withNativeProjectDirectories } from "../executor/fileRoleLaunchPlanner.js";
|
|
9
9
|
import { resolveEffectiveLaunch } from "../executor/effectiveLaunch.js";
|
|
10
10
|
import { compileRoleSessionContext } from "../context/roleSessionContext.js";
|
|
@@ -789,14 +789,8 @@ function checkReviewerLaunch(agent, role, binding, adapter, environment, home) {
|
|
|
789
789
|
trustWorkspace: true
|
|
790
790
|
})
|
|
791
791
|
: undefined;
|
|
792
|
-
if (codexConfig
|
|
793
|
-
|
|
794
|
-
name: "reviewer launch",
|
|
795
|
-
status: "invalid",
|
|
796
|
-
detail: "Codex notify is already configured by "
|
|
797
|
-
+ `${codexConfig.notify.source}; Yui requires exclusive ownership of the structured `
|
|
798
|
-
+ "notify callback and refuses to replace or be replaced by native configuration."
|
|
799
|
-
};
|
|
792
|
+
if (codexConfig !== undefined) {
|
|
793
|
+
assertCodexLaunchOverridesAvailable(codexConfig, ["developerInstructions", "notify"]);
|
|
800
794
|
}
|
|
801
795
|
const reviewerContext = adapter.id === "codex"
|
|
802
796
|
? compileRoleSessionContext(home, role, { scope: "global" }, { purpose: "review" })
|
|
@@ -810,8 +804,7 @@ function checkReviewerLaunch(agent, role, binding, adapter, environment, home) {
|
|
|
810
804
|
? {}
|
|
811
805
|
: {
|
|
812
806
|
developerInstructions: reviewerContext.developerInstructions,
|
|
813
|
-
skills: reviewerContext.skills
|
|
814
|
-
codexDeveloperInstructions: codexConfig?.developerInstructions
|
|
807
|
+
skills: reviewerContext.skills
|
|
815
808
|
})
|
|
816
809
|
});
|
|
817
810
|
if (compiled.argv.length === 0)
|
|
@@ -819,7 +812,18 @@ function checkReviewerLaunch(agent, role, binding, adapter, environment, home) {
|
|
|
819
812
|
return {
|
|
820
813
|
name: "reviewer launch",
|
|
821
814
|
status: "ok",
|
|
822
|
-
detail:
|
|
815
|
+
detail: [
|
|
816
|
+
`adapter=${adapter.id}`,
|
|
817
|
+
`strategy=${compiled.sessionStrategy}`,
|
|
818
|
+
`command=${agent.command}`,
|
|
819
|
+
`addDirs=${launchConfig.additionalDirectories?.length ?? 0}`,
|
|
820
|
+
...(codexConfig?.developerInstructions.status === "configured"
|
|
821
|
+
? [`override=developer_instructions@${codexConfig.developerInstructions.source}`]
|
|
822
|
+
: []),
|
|
823
|
+
...(codexConfig?.notify.status === "configured"
|
|
824
|
+
? [`override=notify@${codexConfig.notify.source}`]
|
|
825
|
+
: [])
|
|
826
|
+
].join(" ")
|
|
823
827
|
};
|
|
824
828
|
}
|
|
825
829
|
catch (error) {
|
|
@@ -435,9 +435,6 @@ export function validateExecutionTarget(target, taskId) {
|
|
|
435
435
|
if (target.kind === "work-item" && target.workItemId === undefined) {
|
|
436
436
|
throw new Error("WorkItem ExecutionTarget requires a Work Item id.");
|
|
437
437
|
}
|
|
438
|
-
if (target.kind === "task-final-review" && target.candidateId === undefined) {
|
|
439
|
-
throw new Error("Task-final ExecutionTarget requires a Candidate id.");
|
|
440
|
-
}
|
|
441
438
|
if (target.workItemId !== undefined)
|
|
442
439
|
requireIdentity(target.workItemId, "Work Item id");
|
|
443
440
|
if (target.candidateId !== undefined)
|
|
@@ -4,7 +4,6 @@ import { isAbsolute, resolve } from "node:path";
|
|
|
4
4
|
import { supportedAgentAdapterIds } from "../agent/adapterCatalog.js";
|
|
5
5
|
import { ownedArgumentsForAdapter, validateAgentAdvancedArguments, validateAgentBaseArguments } from "../agent/argumentPolicy.js";
|
|
6
6
|
import { writeTextFileAtomically } from "../storage/durableFile.js";
|
|
7
|
-
import { inspectCodexDeveloperInstructions } from "./codexConfigConflict.js";
|
|
8
7
|
import { discoverClaudeConfiguration, discoverCodexConfiguration } from "./agentConfigurationProbe.js";
|
|
9
8
|
import { builtinAgentDriverRegistry } from "../runtime/builtinAgentDrivers.js";
|
|
10
9
|
const SANDBOXES = ["read-only", "workspace-write", "danger-full-access"];
|
|
@@ -139,16 +138,6 @@ class CodexAdapter extends BaseAdapter {
|
|
|
139
138
|
];
|
|
140
139
|
if (instructions.length === 0)
|
|
141
140
|
return workspaceTrust;
|
|
142
|
-
const nativeInstructions = input.codexDeveloperInstructions
|
|
143
|
-
?? inspectCodexDeveloperInstructions({
|
|
144
|
-
workspace: input.workspace,
|
|
145
|
-
profile: input.config.profile,
|
|
146
|
-
trustWorkspace: true
|
|
147
|
-
});
|
|
148
|
-
if (nativeInstructions.status === "configured") {
|
|
149
|
-
throw new Error("Codex developer_instructions is already configured by "
|
|
150
|
-
+ `${nativeInstructions.source}; Yui refuses to replace native developer instructions.`);
|
|
151
|
-
}
|
|
152
141
|
return [
|
|
153
142
|
...workspaceTrust,
|
|
154
143
|
"--config",
|
|
@@ -375,37 +364,33 @@ export function inspectAgentCapabilities(agent, optionsOrNow = {}) {
|
|
|
375
364
|
if (!supported) {
|
|
376
365
|
return snapshot(agent, adapter, {
|
|
377
366
|
status: "unsupported-version", command: agent.command, version,
|
|
378
|
-
reason: adapter.
|
|
379
|
-
? `Minimum supported version is ${adapter.supportedVersion}.`
|
|
380
|
-
: `Supported version line starts at ${adapter.supportedVersion}.`,
|
|
367
|
+
reason: `Minimum supported version is ${adapter.supportedVersion}.`,
|
|
381
368
|
probedAt: at
|
|
382
369
|
}, fields, at, [`Installed version ${version} is not supported by adapter ${adapter.id}.`]);
|
|
383
370
|
}
|
|
384
371
|
const help = run(agent.command, ["--help"]);
|
|
385
372
|
const helpFailure = failed(help);
|
|
386
|
-
if (
|
|
373
|
+
if (helpFailure !== undefined) {
|
|
387
374
|
return snapshot(agent, adapter, {
|
|
388
375
|
status: "probe-failed", command: agent.command, version,
|
|
389
|
-
reason: `Required
|
|
376
|
+
reason: `Required ${adapter.label} capability probe failed: ${helpFailure}`,
|
|
377
|
+
probedAt: at
|
|
390
378
|
}, fields, at);
|
|
391
379
|
}
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
+ `${CODEX_TESTED_THROUGH_VERSION}; required capabilities were detected.`);
|
|
407
|
-
}
|
|
408
|
-
}
|
|
380
|
+
const helpOutput = output(help.stdout, help.stderr);
|
|
381
|
+
fields = fromHelp(agent.adapterId, helpOutput);
|
|
382
|
+
const missing = missingRequiredCapabilities(adapter.id, helpOutput);
|
|
383
|
+
if (missing.length > 0) {
|
|
384
|
+
return snapshot(agent, adapter, {
|
|
385
|
+
status: "unsupported-version", command: agent.command, version,
|
|
386
|
+
reason: `${adapter.label} CLI is missing required capabilities: ${missing.join(", ")}.`,
|
|
387
|
+
probedAt: at
|
|
388
|
+
}, fields, at);
|
|
389
|
+
}
|
|
390
|
+
if (adapter.id === "codex"
|
|
391
|
+
&& compareVersions(version, CODEX_TESTED_THROUGH_VERSION) > 0) {
|
|
392
|
+
warnings.push(`Installed Codex version ${version} is newer than the latest tested version `
|
|
393
|
+
+ `${CODEX_TESTED_THROUGH_VERSION}; required capabilities were detected.`);
|
|
409
394
|
}
|
|
410
395
|
return snapshot(agent, adapter, {
|
|
411
396
|
status: "installed", command: agent.command, version, probedAt: at
|
|
@@ -498,10 +483,7 @@ function output(stdout, stderr) {
|
|
|
498
483
|
return value;
|
|
499
484
|
}
|
|
500
485
|
function supports(version, adapter) {
|
|
501
|
-
|
|
502
|
-
return compareVersions(version, adapter.supportedVersion) >= 0;
|
|
503
|
-
const left = version.split(".").map(Number), right = adapter.supportedVersion.split(".").map(Number);
|
|
504
|
-
return left[0] === right[0] && left[1] === right[1] && left[2] >= right[2];
|
|
486
|
+
return compareVersions(version, adapter.supportedVersion) >= 0;
|
|
505
487
|
}
|
|
506
488
|
function compareVersions(leftVersion, rightVersion) {
|
|
507
489
|
const left = leftVersion.split(".").map(Number);
|
|
@@ -513,11 +495,26 @@ function compareVersions(leftVersion, rightVersion) {
|
|
|
513
495
|
}
|
|
514
496
|
return 0;
|
|
515
497
|
}
|
|
516
|
-
function
|
|
517
|
-
const required =
|
|
518
|
-
[
|
|
519
|
-
|
|
520
|
-
|
|
498
|
+
function missingRequiredCapabilities(id, help) {
|
|
499
|
+
const required = id === "codex"
|
|
500
|
+
? [
|
|
501
|
+
[/(?:^|\s)--config(?:\s|[=<,]|$)/m, "--config"],
|
|
502
|
+
[/^\s*resume(?:\s|$)/m, "resume"]
|
|
503
|
+
]
|
|
504
|
+
: [
|
|
505
|
+
[/(?:^|\s)--append-system-prompt(?:-file|\[-file\])(?:\s|[=<,]|$)/m,
|
|
506
|
+
"--append-system-prompt-file"],
|
|
507
|
+
[/(?:^|\s)--resume(?:\s|[=<,]|$)/m, "--resume"],
|
|
508
|
+
[/(?:^|\s)--session-id(?:\s|[=<,]|$)/m, "--session-id"],
|
|
509
|
+
[/(?:^|\s)-p(?:\s|[=<,]|$)/m, "-p"],
|
|
510
|
+
[/(?:^|\s)--output-format(?:\s|[=<,]|$)/m, "--output-format"],
|
|
511
|
+
[/(?:^|\s)--input-format(?:\s|[=<,]|$)/m, "--input-format"],
|
|
512
|
+
[/(?:^|\s)--verbose(?:\s|[=<,]|$)/m, "--verbose"],
|
|
513
|
+
[/(?:^|\s)--replay-user-messages(?:\s|[=<,]|$)/m,
|
|
514
|
+
"--replay-user-messages"],
|
|
515
|
+
[/(?:^|\s)--plugin-dir(?:\s|[=<,]|$)/m, "--plugin-dir"],
|
|
516
|
+
[/(?:^|\s)--name(?:\s|[=<,]|$)/m, "--name"]
|
|
517
|
+
];
|
|
521
518
|
return required.flatMap(([pattern, label]) => pattern.test(help) ? [] : [label]);
|
|
522
519
|
}
|
|
523
520
|
function cloneConfig(config, paths) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { hasRecentTurnId, rememberRecentTurnId, validatePendingTurnCompletion, validateRecentTurnIds } from "./turnCompletion.js";
|
|
3
|
-
import { effectiveLaunchSnapshotsCompatible, effectiveLaunchSnapshotsCompatibleForTaskMain, validateEffectiveLaunchSnapshot } from "./effectiveLaunch.js";
|
|
3
|
+
import { effectiveLaunchSnapshotsCompatible, effectiveLaunchSnapshotsCompatibleForTaskMain, effectiveLaunchSnapshotsCompatibleForTaskReview, validateEffectiveLaunchSnapshot } from "./effectiveLaunch.js";
|
|
4
4
|
import { rebindProviderRuntimeRun, validateProviderRuntimeBinding } from "../runtime/providerRuntimeIdentity.js";
|
|
5
5
|
import { builtinDriverIdForAdapter } from "../runtime/builtinAgentDrivers.js";
|
|
6
6
|
export function createRoleSessionSet(owner, activeAgentId, now) {
|
|
@@ -67,7 +67,9 @@ export function recordRoleAgentSession(set, input, now) {
|
|
|
67
67
|
throw new Error(`Role Agent session effective identity is inconsistent: ${agentId}.`);
|
|
68
68
|
}
|
|
69
69
|
if (existing !== undefined && existing.nativeSessionId === nativeSessionId
|
|
70
|
-
&& !effectiveLaunchSnapshotsCompatible(existing.effective, effective)
|
|
70
|
+
&& !effectiveLaunchSnapshotsCompatible(existing.effective, effective)
|
|
71
|
+
&& !(set.owner.scope === "task"
|
|
72
|
+
&& effectiveLaunchSnapshotsCompatibleForTaskReview(existing.effective, effective))) {
|
|
71
73
|
throw new Error(`Role Agent session effective launch cannot change: ${agentId}.`);
|
|
72
74
|
}
|
|
73
75
|
if (existing !== undefined && existing.nativeSessionId !== nativeSessionId
|