@zq-silk/yui 0.6.13 → 0.6.15
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 +34 -5
- package/dist/cli/commandCatalog.js +172 -62
- package/dist/cli/helpRenderer.js +3 -1
- package/dist/cli.js +102 -11
- package/dist/commands/configCommands.js +521 -171
- package/dist/commands/deliveryGuardPreflight.js +2 -2
- package/dist/commands/executionAuditCommands.js +56 -3
- package/dist/commands/projectCommands.js +518 -55
- package/dist/commands/releaseCommands.js +0 -1
- package/dist/commands/taskActor.js +17 -0
- package/dist/commands/taskBaseCommands.js +29 -0
- package/dist/commands/taskCommands.js +577 -126
- package/dist/commands/taskContextCommand.js +36 -2
- package/dist/commands/taskNextActionCommand.js +48 -3
- package/dist/commands/taskPublicationCommands.js +319 -0
- package/dist/commands/taskRoleRuntimeStatus.js +83 -59
- package/dist/commands/telemetryCommands.js +14 -13
- package/dist/config/yuiConfig.js +161 -8
- package/dist/context/sessionContextBudget.js +71 -0
- package/dist/context/wakeNotification.js +65 -0
- package/dist/controller/clientRuntime.js +2 -1
- package/dist/controller/ephemeralResourceReaper.js +2 -1
- package/dist/controller/fileSchedulerStoreAdapter.js +191 -16
- package/dist/controller/jobSupervisor.js +5 -4
- package/dist/controller/resourceCleanupLinux.js +6 -6
- package/dist/controller/resourceInventoryLinux.js +3 -3
- package/dist/controller/runtime.js +88 -10
- package/dist/controller/updateReconciliation.js +4 -3
- package/dist/doctor/doctor.js +26 -7
- package/dist/executor/agentConfigurationCatalog.js +18 -0
- package/dist/executor/fileRoleLaunchPlanner.js +3 -3
- package/dist/lifecycle/contextBudgetRollover.js +81 -0
- package/dist/lifecycle/exactRunTerminalization.js +16 -2
- package/dist/lifecycle/providerErrorClass.js +33 -12
- package/dist/observability/executionAudit.js +214 -6
- package/dist/output/table.js +18 -0
- package/dist/repository/gitWorkspace.js +92 -0
- package/dist/repository/project.js +218 -4
- package/dist/repository/taskBaseFreshness.js +318 -0
- package/dist/repository/taskWorkspacePreparer.js +16 -2
- package/dist/review/deltaRecheck.js +253 -0
- package/dist/review/reviewConfig.js +31 -0
- package/dist/review/reviewFindingLedger.js +5 -1
- package/dist/review/reviewRound.js +156 -1
- package/dist/run/providerRetry.js +21 -3
- package/dist/run/providerRetryConfig.js +13 -60
- package/dist/run/recoveryProjection.js +198 -0
- package/dist/runtime/builtinAgentDrivers.js +3 -0
- package/dist/runtime/builtinTranscriptUsage.js +76 -32
- package/dist/runtime/continuationManager.js +17 -0
- package/dist/runtime/index.js +2 -0
- package/dist/runtime/launchDiagnostics.js +154 -0
- package/dist/runtime/lifecycleReservation.js +13 -0
- package/dist/runtime/providerContinuation.js +38 -0
- package/dist/runtime/providerContinuationReconciliationService.js +1 -0
- package/dist/runtime/providerErrorCodes.js +278 -0
- package/dist/runtime/runtimeHealthPolicy.js +20 -0
- package/dist/runtime/runtimeObservation.js +7 -1
- package/dist/runtime/runtimeProjection.js +115 -23
- package/dist/runtime/tmuxAdapters.js +242 -48
- package/dist/scheduler/activeRoleRunDelivery.js +139 -3
- package/dist/scheduler/activeTaskProgress.js +4 -3
- package/dist/scheduler/leaderWakeupProcessor.js +105 -15
- package/dist/scheduler/roleRunLiveness.js +2 -1
- package/dist/scheduler/roleRunStall.js +3 -2
- package/dist/scheduler/taskWake.js +72 -0
- package/dist/scheduler/wakeReason.js +64 -0
- package/dist/scheduler/wakeupQueue.js +2 -1
- package/dist/setup/setupCommand.js +1 -1
- package/dist/storage/migration/productionRegistry.js +325 -1
- package/dist/storage/sqliteSchema.js +61 -2
- package/dist/storage/sqliteStore.js +129 -2
- package/dist/storage/storeRpc.js +1 -0
- package/dist/storage/taskStore.js +262 -5
- package/dist/storage/upgrade/recordVersions.js +6 -1
- package/dist/storage/upgrade/sqliteStateMigration.js +22 -2
- package/dist/task/completionReadiness.js +289 -0
- package/dist/task/publicationReference.js +123 -0
- package/dist/task/taskRecordReference.js +3 -1
- package/dist/telemetry/telemetryConfig.js +23 -18
- package/dist/telemetry/telemetryWiring.js +8 -8
- package/dist/tmux/tmuxManager.js +50 -9
- package/dist/web/assets/client/i18n.js +4 -0
- package/dist/web/assets/client/view.js +18 -0
- package/dist/web/webSnapshot.js +100 -10
- package/i18n/README.zh-CN.md +5 -5
- package/package.json +1 -1
- package/skills/yui-leader/SKILL.md +49 -10
- package/skills/yui-operator/SKILL.md +17 -3
- package/skills/yui-worker/SKILL.md +8 -0
package/dist/tmux/tmuxManager.js
CHANGED
|
@@ -97,6 +97,8 @@ export class TmuxManager {
|
|
|
97
97
|
";",
|
|
98
98
|
"set-option", "-g", "history-limit", String(this.#historyLimit),
|
|
99
99
|
";",
|
|
100
|
+
"set-option", "-g", "remain-on-exit", "on",
|
|
101
|
+
";",
|
|
100
102
|
"new-session", "-d",
|
|
101
103
|
"-x", String(this.#initialColumns),
|
|
102
104
|
"-y", String(this.#initialRows),
|
|
@@ -109,6 +111,7 @@ export class TmuxManager {
|
|
|
109
111
|
}
|
|
110
112
|
else {
|
|
111
113
|
this.configureServerHistory();
|
|
114
|
+
this.run(["set-option", "-g", "remain-on-exit", "on"]);
|
|
112
115
|
this.run([
|
|
113
116
|
"new-window",
|
|
114
117
|
"-t", this.sessionName(taskId),
|
|
@@ -141,6 +144,8 @@ export class TmuxManager {
|
|
|
141
144
|
";",
|
|
142
145
|
"set-option", "-g", "history-limit", String(this.#historyLimit),
|
|
143
146
|
";",
|
|
147
|
+
"set-option", "-g", "remain-on-exit", "on",
|
|
148
|
+
";",
|
|
144
149
|
"new-session", "-d",
|
|
145
150
|
"-x", String(this.#initialColumns),
|
|
146
151
|
"-y", String(this.#initialRows),
|
|
@@ -153,6 +158,7 @@ export class TmuxManager {
|
|
|
153
158
|
}
|
|
154
159
|
else {
|
|
155
160
|
await this.configureServerHistoryAsync();
|
|
161
|
+
await this.runAsync(["set-option", "-g", "remain-on-exit", "on"]);
|
|
156
162
|
await this.runAsync([
|
|
157
163
|
"new-window",
|
|
158
164
|
"-t", this.sessionName(taskId),
|
|
@@ -324,6 +330,9 @@ export class TmuxManager {
|
|
|
324
330
|
"capture-pane", "-p", "-t", this.target(taskId, roleName), "-S", `-${lines}`
|
|
325
331
|
]);
|
|
326
332
|
}
|
|
333
|
+
captureRolePane(taskId, roleName, lines = 80) {
|
|
334
|
+
return this.captureRole(taskId, roleName, lines);
|
|
335
|
+
}
|
|
327
336
|
dispatchRole(taskId, role, launch, input, options) {
|
|
328
337
|
if (options.replaceExisting === true) {
|
|
329
338
|
try {
|
|
@@ -356,24 +365,33 @@ export class TmuxManager {
|
|
|
356
365
|
const target = this.target(taskId, roleName);
|
|
357
366
|
const output = this.run([
|
|
358
367
|
"display-message", "-p", "-t", target,
|
|
359
|
-
|
|
368
|
+
[
|
|
369
|
+
"#{pane_dead}",
|
|
370
|
+
"#{pane_pid}",
|
|
371
|
+
"#{pane_current_command}",
|
|
372
|
+
"#{pane_dead_status}"
|
|
373
|
+
].join("|")
|
|
360
374
|
]).trim();
|
|
361
375
|
const separator = output.indexOf("|");
|
|
362
376
|
const secondSeparator = separator < 0 ? -1 : output.indexOf("|", separator + 1);
|
|
363
|
-
|
|
377
|
+
const lastSeparator = secondSeparator < 0 ? -1 : output.lastIndexOf("|");
|
|
378
|
+
if (separator < 0 || secondSeparator < 0 || lastSeparator <= secondSeparator) {
|
|
364
379
|
throw runtimeError(`Tmux returned an invalid pane state for ${roleName}.`);
|
|
365
380
|
}
|
|
366
381
|
const dead = output.slice(0, separator);
|
|
367
382
|
const pidText = output.slice(separator + 1, secondSeparator);
|
|
368
|
-
const currentCommand = output.slice(secondSeparator + 1);
|
|
383
|
+
const currentCommand = output.slice(secondSeparator + 1, lastSeparator);
|
|
384
|
+
const exitStatusText = output.slice(lastSeparator + 1);
|
|
369
385
|
const pid = Number(pidText);
|
|
386
|
+
const exitStatus = nonNegativeNumber(exitStatusText);
|
|
370
387
|
return {
|
|
371
388
|
taskId,
|
|
372
389
|
roleName,
|
|
373
390
|
target,
|
|
374
391
|
dead: dead === "1",
|
|
375
392
|
...(Number.isSafeInteger(pid) && pid > 0 ? { pid } : {}),
|
|
376
|
-
currentCommand
|
|
393
|
+
currentCommand,
|
|
394
|
+
...(dead === "1" && exitStatus !== undefined ? { exitStatus } : {})
|
|
377
395
|
};
|
|
378
396
|
}
|
|
379
397
|
async inspectPaneAsync(taskId, roleName) {
|
|
@@ -396,14 +414,33 @@ export class TmuxManager {
|
|
|
396
414
|
"#{cursor_y}",
|
|
397
415
|
"#{history_size}",
|
|
398
416
|
"#{pane_current_command}",
|
|
417
|
+
"#{pane_dead_status}",
|
|
399
418
|
receiptFormat
|
|
400
419
|
].join("|")
|
|
401
420
|
]);
|
|
402
|
-
const
|
|
403
|
-
|
|
421
|
+
const normalizedOutput = output.trimEnd();
|
|
422
|
+
const fieldSeparators = [...normalizedOutput.matchAll(/\|/g)].map((match) => match.index);
|
|
423
|
+
const lastSeparator = fieldSeparators.at(-1);
|
|
424
|
+
const exitSeparator = fieldSeparators.at(-2);
|
|
425
|
+
if (fieldSeparators.length < 8
|
|
426
|
+
|| lastSeparator === undefined
|
|
427
|
+
|| exitSeparator === undefined
|
|
428
|
+
|| !normalizedOutput.startsWith(PANE_STATE_MARKER)) {
|
|
429
|
+
throw runtimeError(`Tmux returned an invalid pane state for ${roleName}.`);
|
|
430
|
+
}
|
|
431
|
+
const [markerEnd, deadEnd, pidEnd, cursorXEnd, cursorYEnd, historyEnd] = fieldSeparators;
|
|
432
|
+
const receiptSeparator = lastSeparator;
|
|
433
|
+
const deadText = normalizedOutput.slice(markerEnd + 1, deadEnd);
|
|
434
|
+
const pidText = normalizedOutput.slice(deadEnd + 1, pidEnd);
|
|
435
|
+
const cursorXText = normalizedOutput.slice(pidEnd + 1, cursorXEnd);
|
|
436
|
+
const cursorYText = normalizedOutput.slice(cursorXEnd + 1, cursorYEnd);
|
|
437
|
+
const historySizeText = normalizedOutput.slice(cursorYEnd + 1, historyEnd);
|
|
438
|
+
const currentCommand = normalizedOutput.slice(historyEnd + 1, exitSeparator);
|
|
439
|
+
const exitStatusText = normalizedOutput.slice(exitSeparator + 1, receiptSeparator);
|
|
440
|
+
const receiptText = normalizedOutput.slice(receiptSeparator + 1);
|
|
441
|
+
if (!normalizedOutput.slice(0, markerEnd).startsWith(PANE_STATE_MARKER)) {
|
|
404
442
|
throw runtimeError(`Tmux returned an invalid pane state for ${roleName}.`);
|
|
405
443
|
}
|
|
406
|
-
const [, deadText, pidText, cursorXText, cursorYText, historySizeText, currentCommand, receiptText] = state;
|
|
407
444
|
if ((deadText !== "0" && deadText !== "1")
|
|
408
445
|
|| currentCommand === undefined
|
|
409
446
|
|| receiptText === undefined
|
|
@@ -414,6 +451,7 @@ export class TmuxManager {
|
|
|
414
451
|
const cursorX = nonNegativeNumber(cursorXText);
|
|
415
452
|
const cursorY = nonNegativeNumber(cursorYText);
|
|
416
453
|
const historySize = nonNegativeNumber(historySizeText);
|
|
454
|
+
const exitStatus = nonNegativeNumber(exitStatusText);
|
|
417
455
|
if (cursorX === undefined || cursorY === undefined || historySize === undefined) {
|
|
418
456
|
throw runtimeError(`Tmux returned an invalid pane state for ${roleName}.`);
|
|
419
457
|
}
|
|
@@ -425,6 +463,7 @@ export class TmuxManager {
|
|
|
425
463
|
dead: deadText === "1",
|
|
426
464
|
...(pid === undefined ? {} : { pid }),
|
|
427
465
|
currentCommand,
|
|
466
|
+
...(deadText === "1" && exitStatus !== undefined ? { exitStatus } : {}),
|
|
428
467
|
cursorX,
|
|
429
468
|
cursorY,
|
|
430
469
|
historySize
|
|
@@ -775,7 +814,8 @@ export class TmuxManager {
|
|
|
775
814
|
...(pane.pid === undefined ? {} : { pid: pane.pid }),
|
|
776
815
|
target: pane.target,
|
|
777
816
|
dead: pane.dead,
|
|
778
|
-
currentCommand: pane.currentCommand
|
|
817
|
+
currentCommand: pane.currentCommand,
|
|
818
|
+
...(pane.exitStatus === undefined ? {} : { exitStatus: pane.exitStatus })
|
|
779
819
|
};
|
|
780
820
|
}
|
|
781
821
|
async inspectRolePaneAsync(taskId, roleName) {
|
|
@@ -784,7 +824,8 @@ export class TmuxManager {
|
|
|
784
824
|
...(pane.pid === undefined ? {} : { pid: pane.pid }),
|
|
785
825
|
target: pane.target,
|
|
786
826
|
dead: pane.dead,
|
|
787
|
-
currentCommand: pane.currentCommand
|
|
827
|
+
currentCommand: pane.currentCommand,
|
|
828
|
+
...(pane.exitStatus === undefined ? {} : { exitStatus: pane.exitStatus })
|
|
788
829
|
};
|
|
789
830
|
}
|
|
790
831
|
stopTask(taskId) {
|
|
@@ -66,6 +66,8 @@ const messages = {
|
|
|
66
66
|
"detail.focus": "Current focus",
|
|
67
67
|
"detail.runtimeHealth": "Runtime health",
|
|
68
68
|
"detail.lastProgress": "Last semantic progress",
|
|
69
|
+
"detail.recoveryFence": "Canonical recovery fence (Yui durable)",
|
|
70
|
+
"detail.recoveryProviderObserved": "Provider observation (evidence only)",
|
|
69
71
|
"input.freeText": "Type your answer",
|
|
70
72
|
"input.answered": "Input answered.",
|
|
71
73
|
"input.new": "A new input needs your attention.",
|
|
@@ -364,6 +366,8 @@ const messages = {
|
|
|
364
366
|
"detail.focus": "当前重点",
|
|
365
367
|
"detail.runtimeHealth": "运行健康",
|
|
366
368
|
"detail.lastProgress": "最近语义进展",
|
|
369
|
+
"detail.recoveryFence": "规范恢复 fence(Yui 持久)",
|
|
370
|
+
"detail.recoveryProviderObserved": "Provider 观测(仅证据)",
|
|
367
371
|
"input.freeText": "输入回答",
|
|
368
372
|
"input.answered": "输入已回答。",
|
|
369
373
|
"input.new": "有新的输入请求需要处理。",
|
|
@@ -340,6 +340,24 @@ export function renderTaskDetail(detail, data, t, locale, actions) {
|
|
|
340
340
|
node("p", "record-copy", (run.kind || "workflow-not-progressing") + " · " + (run.classification || "truly-stalled")),
|
|
341
341
|
node("small", "", t("detail.lastProgress") + " · " + formatDateTime(run.progressAt, locale))
|
|
342
342
|
);
|
|
343
|
+
// Issue 08: the same canonical recovery projection as the CLI. The
|
|
344
|
+
// durable fence and the Provider observation stay clearly separated;
|
|
345
|
+
// the exact recovery actions live in "yui task run show".
|
|
346
|
+
if (run.recovery) {
|
|
347
|
+
if (run.recovery.canonicalProgressAt) {
|
|
348
|
+
card.append(node("small", "",
|
|
349
|
+
t("detail.recoveryFence") + " · " + formatDateTime(run.recovery.canonicalProgressAt, locale)));
|
|
350
|
+
}
|
|
351
|
+
if (run.recovery.provider && run.recovery.provider.observedAt) {
|
|
352
|
+
card.append(node("small", "",
|
|
353
|
+
t("detail.recoveryProviderObserved") + " · " + run.recovery.provider.observationKind
|
|
354
|
+
+ " " + formatDateTime(run.recovery.provider.observedAt, locale)));
|
|
355
|
+
}
|
|
356
|
+
if (run.recovery.recoverable) {
|
|
357
|
+
card.append(node("small", "record-copy",
|
|
358
|
+
"yui task run show " + task.id + "/" + run.runId));
|
|
359
|
+
}
|
|
360
|
+
}
|
|
343
361
|
runtimeHealthBody.append(card);
|
|
344
362
|
});
|
|
345
363
|
scaffold.append(anchorSection(
|
package/dist/web/webSnapshot.js
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
|
-
import { isRoleRunStalled } from "../scheduler/roleRunStall.js";
|
|
1
|
+
import { isRoleRunStalled, latestRunDurableProgressAt } from "../scheduler/roleRunStall.js";
|
|
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
|
+
import { classifyRuntimeHealth, projectRuntimeTaskEvents } from "../runtime/runtimeProjection.js";
|
|
7
|
+
import { builtinDriverIdForAdapter } from "../runtime/builtinAgentDrivers.js";
|
|
8
|
+
import { formatAgentRunReceiptId } from "../task/taskRecordReference.js";
|
|
5
9
|
export function buildWebDashboardSnapshot(store, now = new Date()) {
|
|
6
10
|
return store.transaction((reader) => {
|
|
7
11
|
const statusCounts = {
|
|
@@ -52,7 +56,7 @@ export function buildWebDashboardSnapshot(store, now = new Date()) {
|
|
|
52
56
|
};
|
|
53
57
|
});
|
|
54
58
|
}
|
|
55
|
-
export function buildWebTaskDetail(store, taskId) {
|
|
59
|
+
export function buildWebTaskDetail(store, taskId, now = new Date()) {
|
|
56
60
|
return store.transaction((reader) => {
|
|
57
61
|
const task = reader.getTask(taskId);
|
|
58
62
|
if (task === null)
|
|
@@ -67,16 +71,24 @@ export function buildWebTaskDetail(store, taskId) {
|
|
|
67
71
|
const events = reader.listEvents?.(taskId) ?? [];
|
|
68
72
|
const needsAttentionRuns = runs
|
|
69
73
|
.filter((run) => run.status === "active" && isRoleRunStalled(events, run.id))
|
|
70
|
-
.map((run) =>
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
74
|
+
.map((run) => {
|
|
75
|
+
const facts = readRunRecoveryFacts(reader, taskId, run.id);
|
|
76
|
+
return {
|
|
77
|
+
runId: run.id,
|
|
78
|
+
roleName: run.roleName,
|
|
79
|
+
progressAt: latestStallProgress(events, run.id),
|
|
80
|
+
kind: latestStallField(events, run.id, "kind") ?? "workflow-not-progressing",
|
|
81
|
+
classification: latestStallField(events, run.id, "classification") ?? "truly-stalled",
|
|
82
|
+
// Issue 08: the same canonical recovery projection the CLI exposes.
|
|
83
|
+
...(facts === null ? {} : { recovery: projectRunRecovery(facts) })
|
|
84
|
+
};
|
|
85
|
+
});
|
|
77
86
|
const activeRuns = new Map(runs
|
|
78
87
|
.filter((run) => run.status === "active")
|
|
79
88
|
.map((run) => [run.roleName, run]));
|
|
89
|
+
const activeRunHealth = runs
|
|
90
|
+
.filter((run) => run.status === "active")
|
|
91
|
+
.map((run) => projectWebRunRuntimeHealth(reader, taskId, run, events, now));
|
|
80
92
|
const roles = reader.listRoles(taskId).map((role) => {
|
|
81
93
|
const activeRun = activeRuns.get(role.name);
|
|
82
94
|
const sessions = reader.getTaskRoleSessionSet(taskId, role.name);
|
|
@@ -105,7 +117,7 @@ export function buildWebTaskDetail(store, taskId) {
|
|
|
105
117
|
};
|
|
106
118
|
}),
|
|
107
119
|
runs,
|
|
108
|
-
runtimeHealth: { needsAttentionRuns },
|
|
120
|
+
runtimeHealth: { needsAttentionRuns, activeRuns: activeRunHealth },
|
|
109
121
|
reviewRounds: reader.listReviewRounds(taskId),
|
|
110
122
|
openInputs: inputs.filter((request) => request.status === "open"),
|
|
111
123
|
messages: reader.listMessages(taskId),
|
|
@@ -117,6 +129,84 @@ export function buildWebTaskDetail(store, taskId) {
|
|
|
117
129
|
function latestStallProgress(events, runId) {
|
|
118
130
|
return latestStallField(events, runId, "progressAt");
|
|
119
131
|
}
|
|
132
|
+
/**
|
|
133
|
+
* Layered runtime health for one active Run, computed from the same stored
|
|
134
|
+
* observations and durable semantic fold as the CLI status projection. The
|
|
135
|
+
* Web snapshot has no live tmux pane, so host state stays "unknown"; the
|
|
136
|
+
* classifier still surfaces session/turn/operation/observer layers and the
|
|
137
|
+
* scheduler's durable `run.stalled` episode is surfaced as
|
|
138
|
+
* `stalled-candidate`.
|
|
139
|
+
*/
|
|
140
|
+
function projectWebRunRuntimeHealth(reader, taskId, run, events, now) {
|
|
141
|
+
const stalled = isRoleRunStalled(events, run.id);
|
|
142
|
+
const sessions = reader.getTaskRoleSessionSet(taskId, run.roleName);
|
|
143
|
+
const session = sessions?.sessions[run.effective.agentId];
|
|
144
|
+
const stallReason = "the live active Run has no durable progress in the configured stall window";
|
|
145
|
+
if (run.deliveredAt === undefined || session?.launchId === undefined) {
|
|
146
|
+
return {
|
|
147
|
+
runId: run.id,
|
|
148
|
+
roleName: run.roleName,
|
|
149
|
+
layer: stalled ? "stalled-candidate" : "awaiting-provider-acceptance",
|
|
150
|
+
reason: stalled ? stallReason : "the pushed active Run is awaiting provider acceptance",
|
|
151
|
+
stalled,
|
|
152
|
+
lastSemanticProgressAt: run.createdAt
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
let driverId;
|
|
156
|
+
try {
|
|
157
|
+
driverId = builtinDriverIdForAdapter(run.effective.adapterId);
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
return {
|
|
161
|
+
runId: run.id,
|
|
162
|
+
roleName: run.roleName,
|
|
163
|
+
layer: stalled ? "stalled-candidate" : "runtime-unobservable",
|
|
164
|
+
reason: stalled ? stallReason : "the Agent Driver is not a built-in driver",
|
|
165
|
+
stalled,
|
|
166
|
+
lastSemanticProgressAt: run.deliveredAt
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
const fence = {
|
|
170
|
+
taskId,
|
|
171
|
+
roleName: run.roleName,
|
|
172
|
+
runId: run.id,
|
|
173
|
+
agentId: run.effective.agentId,
|
|
174
|
+
driverId,
|
|
175
|
+
launchId: session.launchId,
|
|
176
|
+
sessionGenerationId: session.launchId,
|
|
177
|
+
nativeSessionId: session.nativeSessionId,
|
|
178
|
+
nativeTurnId: run.id,
|
|
179
|
+
receiptId: formatAgentRunReceiptId(taskId, run.id)
|
|
180
|
+
};
|
|
181
|
+
const projection = projectRuntimeTaskEvents(fence, run.createdAt, events);
|
|
182
|
+
const view = {
|
|
183
|
+
getAgentRun: (taskId, runId) => reader.listAgentRuns(taskId).find((candidate) => candidate.id === runId) ?? null,
|
|
184
|
+
listEvents: () => events,
|
|
185
|
+
getWorkItem: (workItemTaskId, workItemId) => reader.listWorkItems(workItemTaskId).find((item) => item.id === workItemId) ?? null,
|
|
186
|
+
listReviewRounds: (taskId) => reader.listReviewRounds(taskId),
|
|
187
|
+
listChangeSets: (taskId) => reader.listChangeSets(taskId),
|
|
188
|
+
listIntegrationAttempts: (taskId) => reader.listIntegrationAttempts(taskId),
|
|
189
|
+
listInputRequests: (taskId) => reader.listInputRequests(taskId)
|
|
190
|
+
};
|
|
191
|
+
const semanticProgress = latestRunDurableProgressAt(view, taskId, run.roleName, run.id)
|
|
192
|
+
?? { progressAt: run.deliveredAt };
|
|
193
|
+
const classification = classifyRuntimeHealth({
|
|
194
|
+
projection,
|
|
195
|
+
semanticProgressAt: semanticProgress.progressAt,
|
|
196
|
+
now
|
|
197
|
+
});
|
|
198
|
+
return {
|
|
199
|
+
runId: run.id,
|
|
200
|
+
roleName: run.roleName,
|
|
201
|
+
layer: stalled ? "stalled-candidate" : classification.layer,
|
|
202
|
+
reason: stalled ? stallReason : classification.reason,
|
|
203
|
+
stalled,
|
|
204
|
+
...(classification.lastRuntimeActivityAt === undefined
|
|
205
|
+
? {}
|
|
206
|
+
: { lastRuntimeActivityAt: classification.lastRuntimeActivityAt }),
|
|
207
|
+
lastSemanticProgressAt: classification.lastSemanticProgressAt
|
|
208
|
+
};
|
|
209
|
+
}
|
|
120
210
|
function latestStallField(events, runId, field) {
|
|
121
211
|
const stalled = events
|
|
122
212
|
.filter((event) => event.type === "run.stalled" && event.payload.runId === runId)
|
package/i18n/README.zh-CN.md
CHANGED
|
@@ -116,16 +116,16 @@ yui task activate <task-id>
|
|
|
116
116
|
|
|
117
117
|
```sh
|
|
118
118
|
yui config show
|
|
119
|
-
yui config set
|
|
119
|
+
yui config set time-zone Europe/London
|
|
120
120
|
```
|
|
121
121
|
|
|
122
122
|
WorkItem 审查只有一条可选的全局规则,并直接复用已有 Global Role 的
|
|
123
123
|
Agent、model、权限、prompt 和 Skills:
|
|
124
124
|
|
|
125
125
|
```sh
|
|
126
|
-
yui config review
|
|
127
|
-
yui config
|
|
128
|
-
yui config review
|
|
126
|
+
yui config set review --role reviewer --trigger always
|
|
127
|
+
yui config show
|
|
128
|
+
yui config clear review
|
|
129
129
|
```
|
|
130
130
|
|
|
131
131
|
对带 Project 的软件交付,可使用 `--trigger final`:WorkItem 验收与
|
|
@@ -133,7 +133,7 @@ Integration 保持独立,在 Task 完成前只对所有已集成 Project 的
|
|
|
133
133
|
一次 Task 级 ReviewRound:
|
|
134
134
|
|
|
135
135
|
```sh
|
|
136
|
-
yui config review
|
|
136
|
+
yui config set review --role reviewer --trigger final
|
|
137
137
|
```
|
|
138
138
|
|
|
139
139
|
每个进入 Leader 验收阶段的结果,都会成为原 WorkItem 上一个明确的候选。
|
package/package.json
CHANGED
|
@@ -96,6 +96,19 @@ children through adapter metadata, and routes a later result reference through
|
|
|
96
96
|
the durable inbox. Do not poll, send a waiting Message, rewrite a checkpoint,
|
|
97
97
|
or yield merely to preserve that native wait.
|
|
98
98
|
|
|
99
|
+
Native child results have an explicit durability boundary. A native subagent is
|
|
100
|
+
best-effort by default: its result returns through the parent Conversation, and
|
|
101
|
+
if the parent Session is lost before Yui externalizes the result, rerun the
|
|
102
|
+
child — Yui does not claim it was durably received. When Yui persists a
|
|
103
|
+
continuation report with result content, the child becomes durable-result:
|
|
104
|
+
`yui task continuation list <task>` shows its mode, content digest, and the
|
|
105
|
+
Task event holding the full result. After a parent crash, read durable-result
|
|
106
|
+
children by their event reference instead of rerunning them; rerun only
|
|
107
|
+
best-effort children whose result was never externalized. Critical,
|
|
108
|
+
non-repeatable, or independently verifiable work must use a Yui
|
|
109
|
+
WorkItem/ExecutionGroup, not a native subagent, because only a managed Lane
|
|
110
|
+
owns an independent Run, receipt, and workspace.
|
|
111
|
+
|
|
99
112
|
For a managed Task Role or Reviewer Run, persist a necessary changed checkpoint,
|
|
100
113
|
yield the active Leader Run, and stop the turn. Its durable mailbox result or an
|
|
101
114
|
attention event wakes a later Leader Run. An unchanged healthy managed wait is
|
|
@@ -191,8 +204,17 @@ character budget: choose the smallest useful abstraction for the recipient.
|
|
|
191
204
|
|
|
192
205
|
## Recover and persist Task context
|
|
193
206
|
|
|
194
|
-
A launch or wake message is a pointer, not the full context.
|
|
195
|
-
|
|
207
|
+
A launch or wake message is a pointer, not the full context. A wake carries a
|
|
208
|
+
minimal envelope: the wake id, the aggregated reason tags, and the delta
|
|
209
|
+
window. It never embeds context content. Read the delta on demand:
|
|
210
|
+
|
|
211
|
+
```sh
|
|
212
|
+
yui task wake show <task-id> <wake-id>
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
For a fresh generation (no native history), or when the envelope indicates a
|
|
216
|
+
major change, start with the complete Task projection, then follow its Project
|
|
217
|
+
Policy references:
|
|
196
218
|
|
|
197
219
|
```sh
|
|
198
220
|
yui task context <task-id>
|
|
@@ -200,10 +222,18 @@ yui project show <project>
|
|
|
200
222
|
yui project knowledge list <project>
|
|
201
223
|
```
|
|
202
224
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
225
|
+
The `wake show` delta lists the exact events, messages, and Runs that arrived
|
|
226
|
+
in the window. Inspect `task work`, `task role`, `task integration`, `task
|
|
227
|
+
input`, and other narrower records only when the projection or delta
|
|
228
|
+
identifies a specific record that needs closer evidence. Use exact IDs
|
|
229
|
+
returned by Yui. Never edit `state.json`, managed refs, worktrees, Sessions,
|
|
230
|
+
or provider IDs directly.
|
|
231
|
+
|
|
232
|
+
For a `role-run-stalled` or runtime-health wake, diagnose from the exact
|
|
233
|
+
Run/Event/Session and related WorkItem/Review/Integration records. Preserve
|
|
234
|
+
the current fence and write a Task Message only for a new root cause, impact,
|
|
235
|
+
recovery action, acceptance decision, or user-relevant conclusion; an
|
|
236
|
+
unchanged healthy wait is zero Message.
|
|
207
237
|
|
|
208
238
|
Maintain durable context throughout a long-running Task:
|
|
209
239
|
|
|
@@ -220,8 +250,11 @@ Maintain durable context throughout a long-running Task:
|
|
|
220
250
|
work. Supersede it explicitly when the choice changes.
|
|
221
251
|
- Add a Milestone for a phase result that can be independently reported or
|
|
222
252
|
resumed.
|
|
223
|
-
-
|
|
224
|
-
|
|
253
|
+
- Propose Project Knowledge promotion for stable conclusions useful across
|
|
254
|
+
Tasks. Project Knowledge is an Operator authority: a Leader proposes a
|
|
255
|
+
candidate (with its source Task/Decision/Milestone evidence) and an Operator
|
|
256
|
+
reviews and accepts it; the Leader cannot write the authoritative Knowledge
|
|
257
|
+
list directly. Do not use Knowledge as a Task log, transcript, or scratchpad.
|
|
225
258
|
- Before requesting user input, persist the current focus, known evidence, and
|
|
226
259
|
exact blocker.
|
|
227
260
|
|
|
@@ -236,10 +269,16 @@ yui task decision record <task-id> \
|
|
|
236
269
|
--title "<material choice>" --rationale "<reason and consequences>"
|
|
237
270
|
yui task milestone add <task-id> \
|
|
238
271
|
--title "<phase>" --summary "<delivered result and evidence>"
|
|
239
|
-
yui project knowledge
|
|
272
|
+
yui project knowledge propose <project> \
|
|
273
|
+
--title "<stable conclusion>" --body "<self-contained project-level knowledge>" \
|
|
274
|
+
--task <task-id> [--decision <id>] [--milestone <id>]
|
|
240
275
|
```
|
|
241
276
|
|
|
242
|
-
|
|
277
|
+
Resubmitting the same candidate is deduplicated to the existing proposal. A
|
|
278
|
+
proposal that conflicts with an existing Knowledge entry is never silently
|
|
279
|
+
overwritten: the Operator chooses an explicit update, supersede, or reject.
|
|
280
|
+
Pending proposals appear in `yui task next-action <task-id>` as a non-blocking
|
|
281
|
+
advisory; they do not block Task completion.
|
|
243
282
|
|
|
244
283
|
## Choose the execution path
|
|
245
284
|
|
|
@@ -139,15 +139,29 @@ yui project discover [name]
|
|
|
139
139
|
yui project show <project>
|
|
140
140
|
yui project knowledge list <project>
|
|
141
141
|
yui project knowledge show <project> <knowledge-id>
|
|
142
|
+
yui project knowledge proposals list <project>
|
|
143
|
+
yui project knowledge proposals show <project> <proposal-id>
|
|
144
|
+
yui project knowledge accept <project> <proposal-id>
|
|
145
|
+
yui project knowledge accept <project> <proposal-id> --update <knowledge-id>
|
|
146
|
+
yui project knowledge reject <project> <proposal-id> --reason "<text>"
|
|
142
147
|
yui task create "<title>" \
|
|
143
148
|
--project <project-a> --project <project-b> \
|
|
144
149
|
--base <project-a>=<ref> --base <project-b>=<ref>
|
|
145
150
|
yui task activate <task-id>
|
|
146
151
|
```
|
|
147
152
|
|
|
148
|
-
Keep catalog metadata current with `project update`.
|
|
149
|
-
|
|
150
|
-
|
|
153
|
+
Keep catalog metadata current with `project update`. Project Knowledge is an
|
|
154
|
+
Operator authority: a Leader proposes promotion candidates (with source
|
|
155
|
+
Task/Decision/Milestone evidence) and the Operator reviews and accepts or
|
|
156
|
+
rejects them. Acceptance writes the Knowledge entry with its provenance; a
|
|
157
|
+
candidate that duplicates an existing entry is deduplicated, and one that
|
|
158
|
+
conflicts with an existing title fails closed so the Operator must choose an
|
|
159
|
+
explicit proposal-backed `accept --update`, supersede, or reject. Direct
|
|
160
|
+
Knowledge mutation is not supported because it would erase version history;
|
|
161
|
+
`--update` is allowed only when the current version is already traceable to an
|
|
162
|
+
accepted proposal. Otherwise submit the replacement with `--supersedes` so the
|
|
163
|
+
old Knowledge entry remains retired and readable. If discovery finds an
|
|
164
|
+
existing stable checkout, bind it with `project add`. If only a remote is
|
|
151
165
|
known, explain the clone destination and impact, obtain confirmation, then run
|
|
152
166
|
`project clone`; do not send the user mechanical clone steps.
|
|
153
167
|
|
|
@@ -114,6 +114,14 @@ child Session or Run record. The Leader reviews
|
|
|
114
114
|
the result and records the actual Profile revision, runtime model/effort,
|
|
115
115
|
round, result, and checks in the WorkItem summary.
|
|
116
116
|
|
|
117
|
+
A native child result is best-effort until Yui externalizes it: the result
|
|
118
|
+
returns through the parent Conversation, and if that Session is lost before
|
|
119
|
+
the Leader consumes it, the child may need to rerun. Do not claim Yui
|
|
120
|
+
durability for a native result you only emitted in the provider transcript.
|
|
121
|
+
When the child brief requires a durable, independently recoverable result,
|
|
122
|
+
the Leader must dispatch the work as a Yui WorkItem/ExecutionGroup instead —
|
|
123
|
+
native subagents never own a Yui Run, receipt, or workspace.
|
|
124
|
+
|
|
117
125
|
## Task Role AgentRun
|
|
118
126
|
|
|
119
127
|
The managed input names the current Run ID. Before ending a managed Codex or
|