@zq-silk/yui 0.13.4 → 0.13.6
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 +13 -13
- package/README.md +19 -20
- package/dist/cli/commandCatalog.js +22 -21
- package/dist/cli/interactionPolicy.js +0 -14
- package/dist/cli.js +42 -0
- package/dist/commands/executionAuditCommands.js +1 -1
- package/dist/commands/taskCommands.js +60 -326
- package/dist/commands/taskContextCommand.js +3 -14
- package/dist/commands/taskExecutionCommands.js +254 -0
- package/dist/commands/taskNextActionCommand.js +1 -3
- package/dist/commands/taskOverviewCommand.js +9 -2
- package/dist/commands/taskRoleRuntimeStatus.js +2 -25
- package/dist/controller/agentRuntimeObserver.js +4 -2
- package/dist/controller/clientRuntime.js +45 -2
- package/dist/controller/controller.js +6 -3
- package/dist/controller/fileSchedulerStoreAdapter.js +59 -188
- package/dist/controller/jobControl.js +3 -2
- package/dist/controller/runtime.js +6 -33
- package/dist/controller/runtimeEventProcessor.js +8 -4
- package/dist/controller/runtimeHookRunFence.js +4 -10
- package/dist/execution/executionHealth.js +8 -16
- package/dist/executor/agentAdapter.js +3 -8
- package/dist/executor/agentExecutor.js +13 -14
- package/dist/executor/fileRoleLaunchPlanner.js +10 -26
- package/dist/lifecycle/exactRunTerminalization.js +24 -322
- package/dist/repository/taskWorkspaceCoordinator.js +0 -9
- package/dist/runtime/agentHost.js +22 -83
- package/dist/runtime/builtinAgentDrivers.js +1 -1
- package/dist/runtime/exactControlPlane.js +15 -9
- package/dist/runtime/launchBroker.js +1 -11
- package/dist/runtime/providerContinuationReconciliationService.js +1 -1
- package/dist/runtime/providerRecoveryDecision.js +1 -1
- package/dist/runtime/providerRuntimeIdentity.js +25 -15
- package/dist/runtime/structuredProviderHost.js +0 -57
- package/dist/scheduler/activeRoleRunDelivery.js +1 -19
- package/dist/scheduler/leaderWakeupProcessor.js +12 -63
- package/dist/scheduler/ports.js +3 -2
- package/dist/scheduler/roleRunLiveness.js +4 -1
- package/dist/scheduler/roleRunStall.js +0 -2
- package/dist/scheduler/taskExecutionProjection.js +18 -1
- package/dist/scheduler/wakeupQueue.js +2 -1
- package/dist/storage/migration/productionRegistry.js +65 -0
- package/dist/storage/sqliteStore.js +10 -2
- package/dist/storage/taskStore.js +11 -3
- package/dist/task/completionReadiness.js +0 -67
- package/dist/task/nextAction.js +16 -32
- package/dist/task/task.js +38 -3
- package/dist/web/assets/client/i18n.js +0 -4
- package/dist/web/assets/client/view.js +0 -18
- package/dist/web/webSnapshot.js +7 -13
- package/i18n/README.zh-CN.md +4 -4
- package/package.json +1 -1
- package/dist/run/recoveryProjection.js +0 -252
- package/dist/runtime/conversationSwitch.js +0 -277
package/dist/scheduler/ports.js
CHANGED
|
@@ -25,13 +25,14 @@ export function selectedActiveSchedulerTasks(store, selection) {
|
|
|
25
25
|
const indexedTaskIds = store.listActiveTaskIds?.();
|
|
26
26
|
if (indexedTaskIds === undefined) {
|
|
27
27
|
return store.listTasks().filter((task) => (task.status === "active"
|
|
28
|
+
&& task.executionGate.state === "enabled"
|
|
28
29
|
&& !selection?.blockedTaskIds?.has(task.id)));
|
|
29
30
|
}
|
|
30
31
|
return [...indexedTaskIds].flatMap((taskId) => {
|
|
31
32
|
if (selection?.blockedTaskIds?.has(taskId))
|
|
32
33
|
return [];
|
|
33
34
|
const task = store.getTask(taskId);
|
|
34
|
-
return task?.status === "active" ? [task] : [];
|
|
35
|
+
return task?.status === "active" && task.executionGate.state === "enabled" ? [task] : [];
|
|
35
36
|
});
|
|
36
37
|
}
|
|
37
38
|
const taskIds = selection.taskIds;
|
|
@@ -39,7 +40,7 @@ export function selectedActiveSchedulerTasks(store, selection) {
|
|
|
39
40
|
if (selection.blockedTaskIds?.has(taskId))
|
|
40
41
|
return [];
|
|
41
42
|
const task = store.getTask(taskId);
|
|
42
|
-
return task?.status === "active" ? [task] : [];
|
|
43
|
+
return task?.status === "active" && task.executionGate.state === "enabled" ? [task] : [];
|
|
43
44
|
});
|
|
44
45
|
}
|
|
45
46
|
/** Resolves either every Role in a selected Task or only explicit Role keys. */
|
|
@@ -196,7 +196,10 @@ function exactBatchInventory(batch, candidates) {
|
|
|
196
196
|
return { statuses, resources, hostExits };
|
|
197
197
|
}
|
|
198
198
|
function isResourceCandidate(task, run, now) {
|
|
199
|
-
if (task.status !== "active"
|
|
199
|
+
if (task.status !== "active"
|
|
200
|
+
|| task.executionGate.state !== "enabled"
|
|
201
|
+
|| run.status !== "active"
|
|
202
|
+
|| run.deliveredAt === undefined) {
|
|
200
203
|
return false;
|
|
201
204
|
}
|
|
202
205
|
const deliveredAt = Date.parse(run.deliveredAt);
|
|
@@ -14,8 +14,6 @@ export const RUN_STALLED_EVENT = "run.stalled";
|
|
|
14
14
|
export const RUN_RECOVERED_EVENT = "run.recovered";
|
|
15
15
|
export const RUN_DIAGNOSTIC_FINISHED_EVENT = "runtime.diagnostic-finished";
|
|
16
16
|
/** Structured, non-Message recovery evidence written by an explicit Leader. */
|
|
17
|
-
export const RUN_RECOVERY_REQUESTED_EVENT = "run.recovery-requested";
|
|
18
|
-
export const RUN_RECOVERY_APPLIED_EVENT = "run.recovery-applied";
|
|
19
17
|
/** Workflow-semantic events that count for the durable progress clock. */
|
|
20
18
|
const ACTIVITY_EVENT_TYPES = new Set([
|
|
21
19
|
RUN_PROGRESS_EVENT,
|
|
@@ -132,12 +132,29 @@ export function projectTaskExecution(facts) {
|
|
|
132
132
|
...(run.executionGroupId === undefined ? {} : { executionGroupId: run.executionGroupId }),
|
|
133
133
|
...(run.executionLaneId === undefined ? {} : { executionLaneId: run.executionLaneId })
|
|
134
134
|
}));
|
|
135
|
-
const monitoring = task.
|
|
135
|
+
const monitoring = task.executionGate.state === "stopped"
|
|
136
|
+
|| task.status === "completed"
|
|
136
137
|
|| task.status === "retired"
|
|
137
138
|
|| task.status === "archived"
|
|
138
139
|
? "stopped"
|
|
139
140
|
: "active";
|
|
140
141
|
if (monitoring === "stopped") {
|
|
142
|
+
if (task.executionGate.state === "stopped" && task.status === "active") {
|
|
143
|
+
return render({
|
|
144
|
+
task,
|
|
145
|
+
status: "stopped",
|
|
146
|
+
owner: "operator",
|
|
147
|
+
action: "start-execution",
|
|
148
|
+
summary: `Task ${task.id} execution is stopped; durable progress is preserved.`,
|
|
149
|
+
reason: "execution-stopped",
|
|
150
|
+
monitoring,
|
|
151
|
+
failClosed: false,
|
|
152
|
+
activeRuns: activeRunViews,
|
|
153
|
+
attention: [],
|
|
154
|
+
blockers: [],
|
|
155
|
+
pendingWakeup
|
|
156
|
+
});
|
|
157
|
+
}
|
|
141
158
|
const stoppedStatus = task.status;
|
|
142
159
|
return render({
|
|
143
160
|
task,
|
|
@@ -11,7 +11,8 @@ export function queueLeaderWakeup(store, taskId, reason, now) {
|
|
|
11
11
|
export function queueLeaderWakeupAfterYield(store, task, run, now) {
|
|
12
12
|
if (run.taskId !== task.id)
|
|
13
13
|
throw new Error(`AgentRun belongs to another Task: ${run.taskId}.`);
|
|
14
|
-
if (task.status !== "active" || run.roleName === "leader")
|
|
14
|
+
if (task.status !== "active" || task.executionGate.state !== "enabled" || run.roleName === "leader") {
|
|
15
15
|
return null;
|
|
16
|
+
}
|
|
16
17
|
return queueLeaderWakeup(store, task.id, wakeReason("role-result"), now);
|
|
17
18
|
}
|
|
@@ -24,6 +24,8 @@ const TASK_FROM_VERSION = 3;
|
|
|
24
24
|
const TASK_TO_VERSION = 4;
|
|
25
25
|
const TASK_INTENT_FROM_VERSION = 4;
|
|
26
26
|
const TASK_INTENT_TO_VERSION = 5;
|
|
27
|
+
const TASK_EXECUTION_GATE_FROM_VERSION = 5;
|
|
28
|
+
const TASK_EXECUTION_GATE_TO_VERSION = 6;
|
|
27
29
|
const WORK_ITEM_FROM_VERSION = 6;
|
|
28
30
|
const WORK_ITEM_TO_VERSION = 7;
|
|
29
31
|
const WORK_ITEM_GIT_SNAPSHOT_FROM_VERSION = 7;
|
|
@@ -161,6 +163,7 @@ export function createProductionStorageRegistry() {
|
|
|
161
163
|
.registerCompatible(projectLifecycleStep())
|
|
162
164
|
.registerOfflineMigration(taskWorkspaceIdentityStep())
|
|
163
165
|
.registerOfflineMigration(taskIntentStep())
|
|
166
|
+
.registerOfflineMigration(taskExecutionGateStep())
|
|
164
167
|
.registerOfflineMigration(recordFamilyStep("workItem", WORK_ITEM_FROM_VERSION, WORK_ITEM_TO_VERSION, "workItems"))
|
|
165
168
|
.registerOfflineMigration(recordFamilyStep("workItem", WORK_ITEM_GIT_SNAPSHOT_FROM_VERSION, WORK_ITEM_GIT_SNAPSHOT_TO_VERSION, "workItems"))
|
|
166
169
|
.registerOfflineMigration(workItemExecutionGroupHistoryStep())
|
|
@@ -3375,6 +3378,68 @@ function migrateTaskV4ToV5(snapshot) {
|
|
|
3375
3378
|
state: { ...snapshot.state, tasks: nextTasks }
|
|
3376
3379
|
};
|
|
3377
3380
|
}
|
|
3381
|
+
/** Task v6 separates semantic lifecycle from the current execution admission gate. */
|
|
3382
|
+
function taskExecutionGateStep() {
|
|
3383
|
+
return {
|
|
3384
|
+
axis: "record",
|
|
3385
|
+
recordKind: "task",
|
|
3386
|
+
fromVersion: TASK_EXECUTION_GATE_FROM_VERSION,
|
|
3387
|
+
toVersion: TASK_EXECUTION_GATE_TO_VERSION,
|
|
3388
|
+
preconditions: requireTaskV5Family,
|
|
3389
|
+
transform: migrateTaskV5ToV6,
|
|
3390
|
+
declaredEffects: []
|
|
3391
|
+
};
|
|
3392
|
+
}
|
|
3393
|
+
function requireTaskV5Family(snapshot) {
|
|
3394
|
+
const manifestVersions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
|
|
3395
|
+
if (manifestVersions.task !== TASK_EXECUTION_GATE_FROM_VERSION) {
|
|
3396
|
+
throw new Error(`Record task migration requires manifest version ${TASK_EXECUTION_GATE_FROM_VERSION}.`);
|
|
3397
|
+
}
|
|
3398
|
+
if (snapshot.state === null)
|
|
3399
|
+
return;
|
|
3400
|
+
const tasks = asObject(snapshot.state.tasks, "state tasks");
|
|
3401
|
+
for (const [taskId, rawTask] of Object.entries(tasks)) {
|
|
3402
|
+
const aggregate = asObject(rawTask, `Task aggregate ${taskId}`);
|
|
3403
|
+
if (aggregate.task === undefined)
|
|
3404
|
+
continue;
|
|
3405
|
+
const record = asObject(aggregate.task, `Task ${taskId}`);
|
|
3406
|
+
if (record.schemaVersion !== TASK_EXECUTION_GATE_FROM_VERSION) {
|
|
3407
|
+
throw new Error(`Task ${taskId} must use schemaVersion ${TASK_EXECUTION_GATE_FROM_VERSION}.`);
|
|
3408
|
+
}
|
|
3409
|
+
}
|
|
3410
|
+
}
|
|
3411
|
+
function migrateTaskV5ToV6(snapshot) {
|
|
3412
|
+
requireTaskV5Family(snapshot);
|
|
3413
|
+
const manifestVersions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
|
|
3414
|
+
const schemaManifest = {
|
|
3415
|
+
...snapshot.schemaManifest,
|
|
3416
|
+
recordVersions: { ...manifestVersions, task: TASK_EXECUTION_GATE_TO_VERSION }
|
|
3417
|
+
};
|
|
3418
|
+
if (snapshot.state === null)
|
|
3419
|
+
return { schemaManifest, state: null };
|
|
3420
|
+
const tasks = asObject(snapshot.state.tasks, "state tasks");
|
|
3421
|
+
const nextTasks = {};
|
|
3422
|
+
for (const [taskId, rawTask] of Object.entries(tasks)) {
|
|
3423
|
+
const aggregate = asObject(rawTask, `Task aggregate ${taskId}`);
|
|
3424
|
+
if (aggregate.task === undefined) {
|
|
3425
|
+
nextTasks[taskId] = { ...aggregate };
|
|
3426
|
+
continue;
|
|
3427
|
+
}
|
|
3428
|
+
const task = asObject(aggregate.task, `Task ${taskId}`);
|
|
3429
|
+
nextTasks[taskId] = {
|
|
3430
|
+
...aggregate,
|
|
3431
|
+
task: {
|
|
3432
|
+
...task,
|
|
3433
|
+
schemaVersion: TASK_EXECUTION_GATE_TO_VERSION,
|
|
3434
|
+
executionGate: { state: "enabled" }
|
|
3435
|
+
}
|
|
3436
|
+
};
|
|
3437
|
+
}
|
|
3438
|
+
return {
|
|
3439
|
+
schemaManifest,
|
|
3440
|
+
state: { ...snapshot.state, tasks: nextTasks }
|
|
3441
|
+
};
|
|
3442
|
+
}
|
|
3378
3443
|
/**
|
|
3379
3444
|
* A version bump is deliverable only when the shared planner resolves the full
|
|
3380
3445
|
* adjacent path. This also covers target-only record families as explicit 0->1
|
|
@@ -773,7 +773,7 @@ export class SqliteTaskStore {
|
|
|
773
773
|
if (found === undefined)
|
|
774
774
|
throw new StorageRecordError(`Task Project not found: ${binding.projectId}`);
|
|
775
775
|
}
|
|
776
|
-
const isActive = task.status === "active" ? 1 : 0;
|
|
776
|
+
const isActive = task.status === "active" && task.executionGate.state === "enabled" ? 1 : 0;
|
|
777
777
|
// The catalog projection is inserted first because task_records FKs it.
|
|
778
778
|
this.#db.prepare(`INSERT INTO tasks_catalog (task_id, status, lifecycle, is_active, created_at, updated_at)
|
|
779
779
|
VALUES (?, ?, ?, ?, ?, ?)
|
|
@@ -807,6 +807,7 @@ export class SqliteTaskStore {
|
|
|
807
807
|
task: {
|
|
808
808
|
id: task.id,
|
|
809
809
|
status: task.status,
|
|
810
|
+
executionGate: task.executionGate,
|
|
810
811
|
projectBindings: task.projectBindings,
|
|
811
812
|
type: task.type
|
|
812
813
|
},
|
|
@@ -835,7 +836,6 @@ export class SqliteTaskStore {
|
|
|
835
836
|
return {
|
|
836
837
|
...base,
|
|
837
838
|
agentRuns: operationalTaskRecords(this.listAgentRuns(taskId), this.listEvents(taskId), "agent-run"),
|
|
838
|
-
roleSessionSets: this.listRoleSessionSets(taskId),
|
|
839
839
|
managedWorkspaces: this.#sortById(this.#listPayload("managed_workspaces", "task_id = ?", [taskId]), (workspace) => managedWorkspaceKey(workspace.owner)),
|
|
840
840
|
durableJobs: this.#sortById(this.#listPayload("durable_jobs", "task_id = ?", [taskId]), (job) => job.id),
|
|
841
841
|
integrationQueueEntries: this.#sortById(this.#listPayload("integration_queue", "task_id = ?", [taskId]), (entry) => entry.id),
|
|
@@ -1754,6 +1754,10 @@ export class SqliteTaskStore {
|
|
|
1754
1754
|
return;
|
|
1755
1755
|
}
|
|
1756
1756
|
this.transaction((store) => {
|
|
1757
|
+
const task = store.getTask(run.taskId);
|
|
1758
|
+
if (task === null || task.status !== "active" || task.executionGate.state !== "enabled") {
|
|
1759
|
+
throw new StorageRecordError(`Task execution is not enabled: ${run.taskId}.`);
|
|
1760
|
+
}
|
|
1757
1761
|
this.#assertActiveRunForWrite(run);
|
|
1758
1762
|
const current = store.getActiveAgentRun(run.taskId, run.roleName);
|
|
1759
1763
|
if (current !== null && current.id !== run.id) {
|
|
@@ -1796,6 +1800,10 @@ export class SqliteTaskStore {
|
|
|
1796
1800
|
throw new StorageRecordError(`Active execution-lane run requires group and lane ids: ${run.id}`);
|
|
1797
1801
|
}
|
|
1798
1802
|
this.transaction((store) => {
|
|
1803
|
+
const task = store.getTask(run.taskId);
|
|
1804
|
+
if (task === null || task.status !== "active" || task.executionGate.state !== "enabled") {
|
|
1805
|
+
throw new StorageRecordError(`Task execution is not enabled: ${run.taskId}.`);
|
|
1806
|
+
}
|
|
1799
1807
|
const key = executionLaneActiveRunKey(run.executionGroupId, run.executionLaneId);
|
|
1800
1808
|
this.#assertActiveRunForWrite(run, {
|
|
1801
1809
|
executionGroupId: run.executionGroupId,
|
|
@@ -61,7 +61,7 @@ export const CURRENT_PROJECT_SCHEMA_VERSION = 5;
|
|
|
61
61
|
export const CURRENT_AGENT_PROFILE_SCHEMA_VERSION = 2;
|
|
62
62
|
export const CURRENT_GLOBAL_ROLE_SCHEMA_VERSION = 3;
|
|
63
63
|
export const CURRENT_GLOBAL_ROLE_SESSION_SET_SCHEMA_VERSION = 3;
|
|
64
|
-
export const CURRENT_TASK_SCHEMA_VERSION =
|
|
64
|
+
export const CURRENT_TASK_SCHEMA_VERSION = 6;
|
|
65
65
|
export const CURRENT_TASK_BRIEF_SCHEMA_VERSION = 2;
|
|
66
66
|
export const CURRENT_CONTEXT_SNAPSHOT_SCHEMA_VERSION = 1;
|
|
67
67
|
export const CURRENT_TASK_ROLE_SCHEMA_VERSION = 3;
|
|
@@ -431,7 +431,7 @@ export class FileTaskStore {
|
|
|
431
431
|
// The file rollback backend has no catalog index, so it filters the loaded
|
|
432
432
|
// aggregate. Layout-7 Controller hot paths use SQLite's bounded selector.
|
|
433
433
|
return this.listTasks()
|
|
434
|
-
.filter((task) => task.status === "active")
|
|
434
|
+
.filter((task) => task.status === "active" && task.executionGate.state === "enabled")
|
|
435
435
|
.map((task) => task.id);
|
|
436
436
|
}
|
|
437
437
|
getStateRevision() { return this.#state().revision; }
|
|
@@ -446,6 +446,7 @@ export class FileTaskStore {
|
|
|
446
446
|
task: {
|
|
447
447
|
id: aggregate.task.id,
|
|
448
448
|
status: aggregate.task.status,
|
|
449
|
+
executionGate: aggregate.task.executionGate,
|
|
449
450
|
projectBindings: aggregate.task.projectBindings,
|
|
450
451
|
type: aggregate.task.type
|
|
451
452
|
},
|
|
@@ -485,7 +486,6 @@ export class FileTaskStore {
|
|
|
485
486
|
return {
|
|
486
487
|
...base,
|
|
487
488
|
agentRuns: operationalTaskRecords(this.listAgentRuns(taskId), values(aggregate.events, "id"), "agent-run"),
|
|
488
|
-
roleSessionSets: this.listRoleSessionSets(taskId),
|
|
489
489
|
managedWorkspaces: values(aggregate.managedWorkspaces, (workspace) => managedWorkspaceKey(workspace.owner)),
|
|
490
490
|
durableJobs: values(aggregate.durableJobs, "id"),
|
|
491
491
|
integrationQueueEntries: values(aggregate.integrationQueue, "id"),
|
|
@@ -1116,6 +1116,10 @@ export class FileTaskStore {
|
|
|
1116
1116
|
if (run.status !== "active")
|
|
1117
1117
|
throw new StorageRecordError(`Active Agent run must have active status: ${run.id}`);
|
|
1118
1118
|
this.transaction((store) => {
|
|
1119
|
+
const task = store.getTask(run.taskId);
|
|
1120
|
+
if (task === null || task.status !== "active" || task.executionGate.state !== "enabled") {
|
|
1121
|
+
throw new StorageRecordError(`Task execution is not enabled: ${run.taskId}.`);
|
|
1122
|
+
}
|
|
1119
1123
|
const current = store.getActiveAgentRun(run.taskId, run.roleName);
|
|
1120
1124
|
if (current !== null && current.id !== run.id) {
|
|
1121
1125
|
throw new StorageRecordError(`Role already has an active Agent run: ${run.taskId}/${run.roleName}`);
|
|
@@ -1177,6 +1181,10 @@ export class FileTaskStore {
|
|
|
1177
1181
|
throw new StorageRecordError(`Lane active Agent run requires execution lineage: ${run.id}`);
|
|
1178
1182
|
}
|
|
1179
1183
|
this.transaction((store) => {
|
|
1184
|
+
const task = store.getTask(run.taskId);
|
|
1185
|
+
if (task === null || task.status !== "active" || task.executionGate.state !== "enabled") {
|
|
1186
|
+
throw new StorageRecordError(`Task execution is not enabled: ${run.taskId}.`);
|
|
1187
|
+
}
|
|
1180
1188
|
const key = executionLaneActiveRunKey(run.executionGroupId, run.executionLaneId);
|
|
1181
1189
|
const current = store.getActiveExecutionLaneRun(run.taskId, run.executionGroupId, run.executionLaneId);
|
|
1182
1190
|
if (current !== null && current.id !== run.id) {
|
|
@@ -5,7 +5,6 @@ import { isSemanticReviewRound } from "../review/reviewOutcomeClassifier.js";
|
|
|
5
5
|
import { reviewFindingLedgerWriteFailedFromEvents } from "../review/reviewFindingLedger.js";
|
|
6
6
|
import { resolveRecordedTaskFinalReviewContract } from "../review/taskFinalReviewContractRebind.js";
|
|
7
7
|
import { sameTaskFinalReviewContract } from "../review/taskFinalReviewContract.js";
|
|
8
|
-
import { blockingProviderContinuations } from "../runtime/runtimeContinuationProjection.js";
|
|
9
8
|
const ACTIVE_JOB_STATUSES = new Set([
|
|
10
9
|
"queued",
|
|
11
10
|
"running",
|
|
@@ -160,19 +159,6 @@ export function projectCompletionReadiness(facts, options = {}) {
|
|
|
160
159
|
fix: `settle integration queue entry ${entry.id} (continue or supersede)`
|
|
161
160
|
});
|
|
162
161
|
}
|
|
163
|
-
// Provider continuations that may still write the Workspace.
|
|
164
|
-
for (const continuation of blockingProviderContinuations(facts.events)) {
|
|
165
|
-
if (!providerContinuationBlocksCompletion(continuation, facts))
|
|
166
|
-
continue;
|
|
167
|
-
const identity = continuation.identity;
|
|
168
|
-
blockers.push({
|
|
169
|
-
code: "blocking-provider-continuation",
|
|
170
|
-
ref: ref("provider-continuation", identity.continuationId),
|
|
171
|
-
reason: `Provider continuation ${identity.continuationId} (run ${continuation.runId}) `
|
|
172
|
-
+ "may still write the Workspace or has an identity conflict.",
|
|
173
|
-
fix: `wait for or recover the Provider turn on run ${continuation.runId}`
|
|
174
|
-
});
|
|
175
|
-
}
|
|
176
162
|
// Terminal child workspaces are cleanup advisories: Task completion is the
|
|
177
163
|
// semantic delivery boundary, while archive remains the fail-closed resource
|
|
178
164
|
// reclamation boundary. Missing/non-terminal ownership stays conservative.
|
|
@@ -183,18 +169,6 @@ export function projectCompletionReadiness(facts, options = {}) {
|
|
|
183
169
|
if (disposition?.kind === "advisory")
|
|
184
170
|
advisories.push(disposition.value);
|
|
185
171
|
}
|
|
186
|
-
// Active non-Leader Runs must finish. The Leader's own Run is terminalized
|
|
187
|
-
// by the completion transaction itself, so it is not a readiness blocker.
|
|
188
|
-
for (const run of facts.activeRuns) {
|
|
189
|
-
if (run.roleName === "leader")
|
|
190
|
-
continue;
|
|
191
|
-
blockers.push({
|
|
192
|
-
code: "active-run",
|
|
193
|
-
ref: ref("agent-run", run.id),
|
|
194
|
-
reason: `Role ${run.roleName} has an active Run ${run.id}.`,
|
|
195
|
-
fix: `wait for Run ${run.id} to yield or fail`
|
|
196
|
-
});
|
|
197
|
-
}
|
|
198
172
|
// Review finding ledger gate (enforce mode only).
|
|
199
173
|
// The transactional completion path runs this gate after final-review
|
|
200
174
|
// preparation: a pending/running Task-final Review is expected to resolve
|
|
@@ -248,47 +222,6 @@ export function projectCompletionReadiness(facts, options = {}) {
|
|
|
248
222
|
advisories: sortedAdvisories
|
|
249
223
|
};
|
|
250
224
|
}
|
|
251
|
-
/**
|
|
252
|
-
* A terminal Run releases only its completion blocker, never its immutable
|
|
253
|
-
* continuation audit. Missing or inconsistent ownership remains ambiguous and
|
|
254
|
-
* therefore fail-closed. A live exact native Session can still deliver or
|
|
255
|
-
* write for a terminal Run, so it retains the blocker until that Session is
|
|
256
|
-
* stopped, broken, or replaced by another Conversation identity.
|
|
257
|
-
*/
|
|
258
|
-
function providerContinuationBlocksCompletion(continuation, facts) {
|
|
259
|
-
if (continuation.identityConflict)
|
|
260
|
-
return true;
|
|
261
|
-
const ownerRun = facts.agentRuns.find(({ id }) => id === continuation.runId);
|
|
262
|
-
if (ownerRun === undefined
|
|
263
|
-
|| ownerRun.taskId !== continuation.taskId
|
|
264
|
-
|| ownerRun.roleName !== continuation.roleName
|
|
265
|
-
|| ownerRun.effective.agentId !== continuation.identity.accountScope) {
|
|
266
|
-
return true;
|
|
267
|
-
}
|
|
268
|
-
if (ownerRun.status !== "failed" && ownerRun.status !== "yielded")
|
|
269
|
-
return true;
|
|
270
|
-
const sessions = facts.roleSessionSets.find(({ owner }) => (owner.taskId === continuation.taskId && owner.roleName === continuation.roleName));
|
|
271
|
-
const session = sessions?.sessions[continuation.identity.accountScope];
|
|
272
|
-
if (session !== undefined
|
|
273
|
-
&& session.status !== "stopped"
|
|
274
|
-
&& session.status !== "broken"
|
|
275
|
-
&& session.nativeSessionId === continuation.identity.conversationId) {
|
|
276
|
-
return true;
|
|
277
|
-
}
|
|
278
|
-
const binding = sessions?.providerBinding;
|
|
279
|
-
if (binding === undefined || binding === null
|
|
280
|
-
|| binding.providerNamespace !== continuation.identity.providerNamespace
|
|
281
|
-
|| binding.accountScope !== continuation.identity.accountScope) {
|
|
282
|
-
return false;
|
|
283
|
-
}
|
|
284
|
-
const conversationIsCurrent = binding.conversations.some((conversation) => (conversation.conversationId === continuation.identity.conversationId
|
|
285
|
-
&& conversation.epoch === binding.currentConversationEpoch
|
|
286
|
-
&& conversation.status === "current"));
|
|
287
|
-
const activationIsLive = binding.activations.some((activation) => (activation.activationId === continuation.identity.activationId
|
|
288
|
-
&& activation.conversationId === continuation.identity.conversationId
|
|
289
|
-
&& activation.status === "active"));
|
|
290
|
-
return conversationIsCurrent && activationIsLive;
|
|
291
|
-
}
|
|
292
225
|
function workspaceCompletionDisposition(facts, taskId, workspace) {
|
|
293
226
|
const owner = workspace.owner;
|
|
294
227
|
switch (owner.type) {
|
package/dist/task/nextAction.js
CHANGED
|
@@ -22,6 +22,17 @@ export function projectNextAction(facts) {
|
|
|
22
22
|
]
|
|
23
23
|
});
|
|
24
24
|
}
|
|
25
|
+
if (task.status === "active" && task.executionGate.state === "stopped") {
|
|
26
|
+
return buildAction(facts, {
|
|
27
|
+
kind: "start-task-execution",
|
|
28
|
+
reason: `Task ${task.id} execution is stopped; durable progress is preserved.`,
|
|
29
|
+
refs: [ref("task", task.id)],
|
|
30
|
+
preconditions: [
|
|
31
|
+
{ fact: "Task execution is stopped", satisfied: true, ref: ref("task", task.id) }
|
|
32
|
+
],
|
|
33
|
+
recommendedCommand: `yui task execution start ${task.id}`
|
|
34
|
+
});
|
|
35
|
+
}
|
|
25
36
|
const openInput = facts.openInputRequests[0];
|
|
26
37
|
if (openInput !== undefined) {
|
|
27
38
|
return buildAction(facts, {
|
|
@@ -597,42 +608,15 @@ function buildExecutionLaneRecoveryAction(facts, lane) {
|
|
|
597
608
|
ref("execution-lane", lane.laneId),
|
|
598
609
|
ref("agent-run", lane.runId)
|
|
599
610
|
];
|
|
600
|
-
if (lane.recovery === "retry-new-agent-run") {
|
|
601
|
-
return buildAction(facts, {
|
|
602
|
-
kind: "recover-execution-lane",
|
|
603
|
-
reason: `Execution Lane ${lane.laneId} is durably failed; retry only exact Run ${lane.runId} and retain sibling results.`,
|
|
604
|
-
refs,
|
|
605
|
-
preconditions: [
|
|
606
|
-
{ fact: "Execution Lane is failed and unresolved", satisfied: true, ref: refs[1] },
|
|
607
|
-
{ fact: "Exact failed AgentRun is retained", satisfied: true, ref: refs[2] }
|
|
608
|
-
],
|
|
609
|
-
recommendedCommand: `yui task run retry ${facts.task.id}/${lane.runId}`
|
|
610
|
-
});
|
|
611
|
-
}
|
|
612
|
-
const action = lane.recovery === "diagnose" ? "diagnose" : "terminate";
|
|
613
|
-
const recovery = facts.runRecoveries?.find(({ runId }) => runId === lane.runId);
|
|
614
|
-
const plan = recovery?.actions.find((candidate) => candidate.action === action);
|
|
615
611
|
return buildAction(facts, {
|
|
616
|
-
kind: "
|
|
617
|
-
reason: lane.
|
|
618
|
-
? `Execution Lane ${lane.laneId} needs bounded diagnostics for exact Run ${lane.runId}.`
|
|
619
|
-
: `Execution Lane ${lane.laneId} has confirmed death evidence; terminate exact Run ${lane.runId}.`,
|
|
612
|
+
kind: "retry-execution-lane",
|
|
613
|
+
reason: `Execution Lane ${lane.laneId} is durably failed; retry only exact Run ${lane.runId} and retain sibling results.`,
|
|
620
614
|
refs,
|
|
621
615
|
preconditions: [
|
|
622
|
-
{ fact:
|
|
623
|
-
{
|
|
624
|
-
fact: `Exact Run exposes a current ${action} recovery plan`,
|
|
625
|
-
satisfied: plan !== undefined,
|
|
626
|
-
ref: refs[2]
|
|
627
|
-
}
|
|
616
|
+
{ fact: "Execution Lane is failed and unresolved", satisfied: true, ref: refs[1] },
|
|
617
|
+
{ fact: "Exact failed AgentRun is retained", satisfied: true, ref: refs[2] }
|
|
628
618
|
],
|
|
629
|
-
|
|
630
|
-
...(plan !== undefined && recovery?.judgmentRequired === undefined
|
|
631
|
-
? {}
|
|
632
|
-
: {
|
|
633
|
-
judgmentRequired: recovery?.judgmentRequired
|
|
634
|
-
?? `Inspect yui task run show ${facts.task.id}/${lane.runId}; its exact recovery fence is unavailable.`
|
|
635
|
-
})
|
|
619
|
+
recommendedCommand: `yui task run retry ${facts.task.id}/${lane.runId}`
|
|
636
620
|
});
|
|
637
621
|
}
|
|
638
622
|
function exhaustedExplorationReason(item, executionGroups) {
|
package/dist/task/task.js
CHANGED
|
@@ -2,11 +2,12 @@ import { validateTaskWorkspaceIdentity } from "../repository/taskWorkspaceIdenti
|
|
|
2
2
|
export function createTask(id, title, now, metadata = {}) {
|
|
3
3
|
const timestamp = now.toISOString();
|
|
4
4
|
return {
|
|
5
|
-
schemaVersion:
|
|
5
|
+
schemaVersion: 6,
|
|
6
6
|
id: requireSafeIdentity(id, "Task id"),
|
|
7
7
|
title: requireText(title, "Task title"),
|
|
8
8
|
...cloneMetadata(metadata),
|
|
9
9
|
status: "draft",
|
|
10
|
+
executionGate: { state: "enabled" },
|
|
10
11
|
createdAt: timestamp,
|
|
11
12
|
updatedAt: timestamp
|
|
12
13
|
};
|
|
@@ -169,9 +170,38 @@ export function updateTaskWorkspace(task, cwd, now) {
|
|
|
169
170
|
export function isTaskArchived(task) {
|
|
170
171
|
return task.status === "archived";
|
|
171
172
|
}
|
|
173
|
+
export function isTaskExecutionEnabled(task) {
|
|
174
|
+
return task.executionGate.state === "enabled";
|
|
175
|
+
}
|
|
176
|
+
export function stopTaskExecution(task, now) {
|
|
177
|
+
validateTask(task);
|
|
178
|
+
if (task.status !== "active") {
|
|
179
|
+
throw new Error(`Only an active Task can be stopped: ${task.id}.`);
|
|
180
|
+
}
|
|
181
|
+
if (task.executionGate.state === "stopped")
|
|
182
|
+
return task;
|
|
183
|
+
return validateTask({
|
|
184
|
+
...task,
|
|
185
|
+
executionGate: { state: "stopped" },
|
|
186
|
+
updatedAt: now.toISOString()
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
export function startTaskExecution(task, now) {
|
|
190
|
+
validateTask(task);
|
|
191
|
+
if (task.status !== "active") {
|
|
192
|
+
throw new Error(`Only an active Task can be started: ${task.id}.`);
|
|
193
|
+
}
|
|
194
|
+
if (task.executionGate.state === "enabled")
|
|
195
|
+
return task;
|
|
196
|
+
return validateTask({
|
|
197
|
+
...task,
|
|
198
|
+
executionGate: { state: "enabled" },
|
|
199
|
+
updatedAt: now.toISOString()
|
|
200
|
+
});
|
|
201
|
+
}
|
|
172
202
|
export function validateTask(task) {
|
|
173
|
-
if (task.schemaVersion !==
|
|
174
|
-
throw new Error("Task must use schemaVersion
|
|
203
|
+
if (task.schemaVersion !== 6)
|
|
204
|
+
throw new Error("Task must use schemaVersion 6.");
|
|
175
205
|
requireSafeIdentity(task.id, "Task id");
|
|
176
206
|
requireText(task.title, "Task title");
|
|
177
207
|
if (task.type !== undefined)
|
|
@@ -179,6 +209,11 @@ export function validateTask(task) {
|
|
|
179
209
|
if (!["draft", "active", "completed", "retired", "archived"].includes(task.status)) {
|
|
180
210
|
throw new Error(`Task status is invalid: ${String(task.status)}.`);
|
|
181
211
|
}
|
|
212
|
+
if (task.executionGate === null
|
|
213
|
+
|| typeof task.executionGate !== "object"
|
|
214
|
+
|| !["enabled", "stopped"].includes(task.executionGate.state)) {
|
|
215
|
+
throw new Error(`Task execution state is invalid: ${String(task.executionGate?.state)}.`);
|
|
216
|
+
}
|
|
182
217
|
requireTimestamp(task.createdAt, "Task createdAt");
|
|
183
218
|
requireTimestamp(task.updatedAt, "Task updatedAt");
|
|
184
219
|
if (Date.parse(task.updatedAt) < Date.parse(task.createdAt)) {
|
|
@@ -86,8 +86,6 @@ const messages = {
|
|
|
86
86
|
"detail.focus": "Current focus",
|
|
87
87
|
"detail.runtimeHealth": "Runtime health",
|
|
88
88
|
"detail.lastProgress": "Last semantic progress",
|
|
89
|
-
"detail.recoveryFence": "Canonical recovery fence (Yui durable)",
|
|
90
|
-
"detail.recoveryProviderObserved": "Provider observation (evidence only)",
|
|
91
89
|
"input.freeText": "Type your answer",
|
|
92
90
|
"input.answered": "Input answered.",
|
|
93
91
|
"input.new": "A new input needs your attention.",
|
|
@@ -424,8 +422,6 @@ const messages = {
|
|
|
424
422
|
"detail.focus": "当前重点",
|
|
425
423
|
"detail.runtimeHealth": "运行健康",
|
|
426
424
|
"detail.lastProgress": "最近语义进展",
|
|
427
|
-
"detail.recoveryFence": "规范恢复 fence(Yui 持久)",
|
|
428
|
-
"detail.recoveryProviderObserved": "Provider 观测(仅证据)",
|
|
429
425
|
"input.freeText": "输入回答",
|
|
430
426
|
"input.answered": "输入已回答。",
|
|
431
427
|
"input.new": "有新的输入请求需要处理。",
|
|
@@ -345,24 +345,6 @@ export function renderTaskDetail(detail, data, t, locale, actions) {
|
|
|
345
345
|
node("p", "record-copy", (run.kind || "workflow-not-progressing") + " · " + (run.classification || "truly-stalled")),
|
|
346
346
|
node("small", "", t("detail.lastProgress") + " · " + formatDateTime(run.progressAt, locale))
|
|
347
347
|
);
|
|
348
|
-
// Issue 08: the same canonical recovery projection as the CLI. The
|
|
349
|
-
// durable fence and the Provider observation stay clearly separated;
|
|
350
|
-
// the exact recovery actions live in "yui task run show".
|
|
351
|
-
if (run.recovery) {
|
|
352
|
-
if (run.recovery.canonicalProgressAt) {
|
|
353
|
-
card.append(node("small", "",
|
|
354
|
-
t("detail.recoveryFence") + " · " + formatDateTime(run.recovery.canonicalProgressAt, locale)));
|
|
355
|
-
}
|
|
356
|
-
if (run.recovery.provider && run.recovery.provider.observedAt) {
|
|
357
|
-
card.append(node("small", "",
|
|
358
|
-
t("detail.recoveryProviderObserved") + " · " + run.recovery.provider.observationKind
|
|
359
|
-
+ " " + formatDateTime(run.recovery.provider.observedAt, locale)));
|
|
360
|
-
}
|
|
361
|
-
if (run.recovery.recoverable) {
|
|
362
|
-
card.append(node("small", "record-copy",
|
|
363
|
-
"yui task run show " + task.id + "/" + run.runId));
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
348
|
runtimeHealthBody.append(card);
|
|
367
349
|
});
|
|
368
350
|
scaffold.append(anchorSection(
|
package/dist/web/webSnapshot.js
CHANGED
|
@@ -2,7 +2,6 @@ import { isRoleRunStalled, latestRunDurableProgressAt } from "../scheduler/roleR
|
|
|
2
2
|
import { buildTaskExecutionProjection } from "../scheduler/taskExecutionProjection.js";
|
|
3
3
|
import { summarizeExecutionGroup } from "../execution/executionGroup.js";
|
|
4
4
|
import { currentWorkItemExecutionGroup } from "../workItem/workItem.js";
|
|
5
|
-
import { projectRunRecovery, readRunRecoveryFacts } from "../run/recoveryProjection.js";
|
|
6
5
|
import { classifyRuntimeHealth, projectRuntimeTaskEvents } from "../runtime/runtimeProjection.js";
|
|
7
6
|
import { builtinDriverIdForAdapter } from "../runtime/builtinAgentDrivers.js";
|
|
8
7
|
import { formatAgentRunReceiptId } from "../task/taskRecordReference.js";
|
|
@@ -73,18 +72,13 @@ export function buildWebTaskDetail(store, taskId, now = new Date()) {
|
|
|
73
72
|
const events = reader.listEvents?.(taskId) ?? [];
|
|
74
73
|
const needsAttentionRuns = runs
|
|
75
74
|
.filter((run) => run.status === "active" && isRoleRunStalled(events, run.id))
|
|
76
|
-
.map((run) => {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
classification: latestStallField(events, run.id, "classification") ?? "truly-stalled",
|
|
84
|
-
// Issue 08: the same canonical recovery projection the CLI exposes.
|
|
85
|
-
...(facts === null ? {} : { recovery: projectRunRecovery(facts) })
|
|
86
|
-
};
|
|
87
|
-
});
|
|
75
|
+
.map((run) => ({
|
|
76
|
+
runId: run.id,
|
|
77
|
+
roleName: run.roleName,
|
|
78
|
+
progressAt: latestStallProgress(events, run.id),
|
|
79
|
+
kind: latestStallField(events, run.id, "kind") ?? "workflow-not-progressing",
|
|
80
|
+
classification: latestStallField(events, run.id, "classification") ?? "truly-stalled"
|
|
81
|
+
}));
|
|
88
82
|
const activeRuns = new Map(runs
|
|
89
83
|
.filter((run) => run.status === "active")
|
|
90
84
|
.map((run) => [run.roleName, run]));
|
package/i18n/README.zh-CN.md
CHANGED
|
@@ -510,9 +510,9 @@ Task 生命周期的交互选择只展示有效来源状态:activate 只展示
|
|
|
510
510
|
|
|
511
511
|
## Session 与 tmux
|
|
512
512
|
|
|
513
|
-
受管理的 Provider 会话仍然是普通用户会话。Yui 只添加对应的 Role Skill 与 Session Manifest 指针,并通过 Provider 原生结构化协议提交 Task 工作;Yui 不接管完整对话历史。受管理输入绝不会作为终端按键、粘贴文本或启动 argv 发送。Codex
|
|
513
|
+
受管理的 Provider 会话仍然是普通用户会话。Yui 只添加对应的 Role Skill 与 Session Manifest 指针,并通过 Provider 原生结构化协议提交 Task 工作;Yui 不接管完整对话历史。受管理输入绝不会作为终端按键、粘贴文本或启动 argv 发送。Codex 由每个存活的 Role Runtime 启动并持有独立的 `app-server` 子进程;Task execution stop 会终止 Agent Host 及其 Provider 进程组,start 会创建新的受控运行时,不再依赖共享 Codex daemon。原生 thread 历史仍由 Codex 保留,Task、WorkItem、代码与持久消息仍由 Yui 保留。Claude 继续使用独立的持久 stream-json 进程,并以精确回放的 user message 作为接收确认。
|
|
514
514
|
|
|
515
|
-
Run、Conversation、Activation 与 Turn 是四个独立身份。Conversation 可以跨多个 Run 和客户端连接;Activation 只代表 Yui 当前的连接,而不是对 Provider thread 的独占所有权;Turn 在写入前先持久化。写入超时或结果不明确会进入 `delivery-unknown
|
|
515
|
+
Run、Conversation、Activation 与 Turn 是四个独立身份。Conversation 可以跨多个 Run 和客户端连接;Activation 只代表 Yui 当前的连接,而不是对 Provider thread 的独占所有权;Turn 在写入前先持久化。写入超时或结果不明确会进入 `delivery-unknown`,不会自动重发。已经存在的原生 active Turn 只会让 Yui 暂时等待,不会导致 Yui Run 失败;存活的受管理 Role 应通过 Yui 的 view/takeover 边界进行人工控制,避免其他客户端并发写入。
|
|
516
516
|
|
|
517
517
|
Task Role 使用以下显式入口:
|
|
518
518
|
|
|
@@ -524,7 +524,7 @@ yui task role takeover <task-id> <role>
|
|
|
524
524
|
yui task role release <task-id> <role>
|
|
525
525
|
```
|
|
526
526
|
|
|
527
|
-
Codex Role thread
|
|
527
|
+
Codex Role thread 仍可在 Desktop 中查看,但存活的受管理 Role 应通过 `view`、`takeover`、`release` 进行人工控制;若要从其他客户端恢复该 thread,应先停止 Task execution。Yui 不写入全局 Hook/config,也不依赖或控制共享 Codex daemon。Global Operator 与 global Role 继续使用原生交互式 CLI,不属于受管理 Task Provider 协议。
|
|
528
528
|
|
|
529
529
|
当新版本需要离线迁移 Home 时,应等待当前 Turn/Run 完成,然后从普通 shell
|
|
530
530
|
执行 `yui session stop --all`,再重新执行 `yui update`。停止命令会先整体预检:
|
|
@@ -560,7 +560,7 @@ state、receipt 与 pane fence。Yui 不会解析 prompt glyph、进度文本、
|
|
|
560
560
|
或其他 Agent 终端输出来推断 ready 或 success。`captureRole()` 只用于显式的人类
|
|
561
561
|
transcript 查看,不具备生命周期权威。
|
|
562
562
|
|
|
563
|
-
稳定的 Role 上下文不会创建额外的 bootstrap Turn。Task execution Run 按角色使用通用 Leader 或 Worker Skill,review Run 则按持久 Run purpose 使用通用 Reviewer Skill;Provider 可以通过安全的追加式原生上下文通道携带 Skill,也可以在普通 Task 投递中指向它。这些都只是 Yui 自己拥有的可移植编排规则。Project Skills 始终是 Project 中正常版本化的文件,由 Agent 通过自身项目机制发现、选择并按需加载;Yui 不扫描、不解析、不复制,也不注入 Project Skills。Managed Codex 保留用户原有的 developer instructions;普通 Task 消息会携带精简的 Session Manifest 绝对路径,Manifest 再指向对应的 Yui Role Skill,供 Codex
|
|
563
|
+
稳定的 Role 上下文不会创建额外的 bootstrap Turn。Task execution Run 按角色使用通用 Leader 或 Worker Skill,review Run 则按持久 Run purpose 使用通用 Reviewer Skill;Provider 可以通过安全的追加式原生上下文通道携带 Skill,也可以在普通 Task 投递中指向它。这些都只是 Yui 自己拥有的可移植编排规则。Project Skills 始终是 Project 中正常版本化的文件,由 Agent 通过自身项目机制发现、选择并按需加载;Yui 不扫描、不解析、不复制,也不注入 Project Skills。Managed Codex 保留用户原有的 developer instructions;普通 Task 消息会携带精简的 Session Manifest 绝对路径,Manifest 再指向对应的 Yui Role Skill,供 Codex 按需读取。每个存活 Role 的 Yui-owned App Server 进程会加载所选 Codex 原生 config profile;model、effort、permission、workspace 与 shell 设置作为线程级 `thread/start` 或 `thread/resume` 配置传入,Yui 不修改底层 Codex 配置文件。App Server 原生通知是 Managed Codex 线程的生命周期权威;Yui 不为它安装 Hook,也不占用 `notify`。交互式 Codex Session 仍可使用 Yui 的结构化 `notify` callback,Doctor 会报告最终生效的配置冲突。`skills.config` 只负责启停已发现 Skill,Yui 不会误用它。Claude 从 Yui 管理的私有 `0600` context 文件读取同一份 Yui Role Skill 内容,不再把大段或敏感文本放进 argv;重试和 resume 会复用按 purpose 区分的稳定路径。非 Operator 的 global Role 保持中性,不会注入 Task 编排 Skill。因此 Operator 会停在空白的原生 composer,用户输入仍是第一条 user message;Leader wake、Worker 和 Reviewer Run assignment 仍是邮箱投递的真实工作消息。
|
|
564
564
|
|
|
565
565
|
## Controller 与失败处理
|
|
566
566
|
|