@zq-silk/yui 0.11.3 → 0.12.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 +23 -4
- package/README.md +36 -13
- package/dist/cli/commandCatalog.js +2 -4
- package/dist/cli.js +105 -43
- package/dist/commands/configCommands.js +12 -45
- package/dist/commands/executionAuditCommands.js +1 -1
- package/dist/commands/jobCommands.js +4 -10
- package/dist/commands/taskCommands.js +154 -171
- package/dist/commands/taskCompletionGate.js +29 -54
- package/dist/commands/taskContextCommand.js +55 -11
- package/dist/commands/taskInputCommands.js +8 -5
- package/dist/commands/taskNextActionCommand.js +56 -6
- package/dist/commands/taskOverviewCommand.js +17 -31
- package/dist/commands/taskRoleRuntimeStatus.js +24 -0
- package/dist/commands/taskWorkspaceCommands.js +5 -2
- package/dist/config/configCatalog.js +2 -2
- package/dist/config/yuiConfig.js +3 -7
- package/dist/context/wakeNotification.js +20 -5
- package/dist/controller/agentRuntimeObserver.js +247 -51
- package/dist/controller/clientRuntime.js +1 -1
- package/dist/controller/controller.js +8 -12
- package/dist/controller/fileSchedulerStoreAdapter.js +146 -198
- package/dist/controller/runtime.js +3 -3
- package/dist/controller/runtimeEventInbox.js +17 -7
- package/dist/controller/runtimeEventProcessor.js +12 -19
- package/dist/controller/runtimeObservationHook.js +45 -0
- package/dist/execution/resourceBroker.js +5 -4
- package/dist/executor/agentExecutor.js +4 -4
- package/dist/executor/effectiveLaunch.js +8 -48
- package/dist/executor/fileRoleLaunchPlanner.js +36 -16
- package/dist/executor/workspacePreflightClassification.js +23 -2
- package/dist/interaction/operatorPresentation.js +33 -89
- package/dist/lifecycle/exactRunTerminalization.js +1 -2
- package/dist/lifecycle/taskRoleSessionReset.js +5 -13
- package/dist/observability/orchestrationMetrics.js +8 -3
- package/dist/profile/agentProfile.js +1 -1
- package/dist/release/cliHomeReleaseFence.js +123 -0
- package/dist/repository/taskWorkspacePreparer.js +176 -60
- package/dist/resources/sqliteResourceRegistry.js +1 -1
- package/dist/review/deltaRecheck.js +12 -51
- package/dist/review/reviewAcceptance.js +26 -0
- package/dist/review/reviewConfig.js +0 -31
- package/dist/review/reviewDecision.js +113 -0
- package/dist/review/reviewOutcomeClassifier.js +1 -1
- package/dist/review/reviewRound.js +1 -1
- package/dist/review/reviewerAvailability.js +69 -0
- package/dist/runtime/agentDriverObservation.js +24 -10
- package/dist/runtime/builtinAgentDrivers.js +4 -3
- package/dist/runtime/builtinTranscriptObserver.js +301 -64
- package/dist/runtime/builtinTranscriptUsage.js +9 -7
- package/dist/runtime/index.js +1 -0
- package/dist/runtime/runtimeObservation.js +13 -0
- package/dist/runtime/runtimeProjection.js +16 -25
- package/dist/runtime/sessionTokenMetrics.js +181 -0
- package/dist/runtime/structuredProviderHost.js +7 -1
- package/dist/scheduler/activeRoleRunDelivery.js +4 -4
- package/dist/scheduler/activeTaskProgress.js +7 -6
- package/dist/scheduler/leaderWakeupProcessor.js +3 -5
- package/dist/scheduler/operatorEvent.js +34 -0
- package/dist/scheduler/operatorInputNotificationProcessor.js +54 -94
- package/dist/scheduler/roleRunStall.js +0 -14
- package/dist/scheduler/taskExecutionProjection.js +97 -76
- package/dist/scheduler/taskObservabilityProjection.js +10 -11
- package/dist/storage/migration/productionRegistry.js +379 -0
- package/dist/storage/sqliteSchema.js +18 -8
- package/dist/storage/sqliteStore.js +24 -20
- package/dist/storage/storeRpc.js +0 -1
- package/dist/storage/taskStore.js +15 -30
- package/dist/storage/upgrade/recordVersions.js +3 -2
- package/dist/storage/upgrade/sqliteMigrationTarget.js +30 -8
- package/dist/storage/upgrade/sqliteRecordMigrationTarget.js +23 -11
- package/dist/storage/upgrade/sqliteStateMigration.js +66 -8
- package/dist/task/completionReadiness.js +10 -6
- package/dist/task/nextAction.js +82 -24
- package/dist/telemetry/sqliteTelemetryStore.js +1 -1
- package/dist/web/assets/client/components.js +13 -3
- package/dist/web/assets/client/i18n.js +6 -2
- package/dist/web/webSnapshot.js +4 -0
- package/i18n/README.zh-CN.md +2 -2
- package/package.json +1 -1
- package/skills/yui-leader/SKILL.md +53 -16
- package/skills/yui-operator/SKILL.md +9 -0
- package/skills/yui-reviewer/SKILL.md +28 -7
- package/dist/context/sessionContextBudget.js +0 -71
- package/dist/lifecycle/contextBudgetRollover.js +0 -81
- package/dist/scheduler/operatorNotification.js +0 -59
|
@@ -3,7 +3,7 @@ import { GitIntegrationService } from "../integration/gitIntegrationService.js";
|
|
|
3
3
|
import { createIntegrationAttempt } from "../integration/integrationAttempt.js";
|
|
4
4
|
import { NodeGitWorkspace } from "../repository/gitWorkspace.js";
|
|
5
5
|
import { publicationExternalKey } from "../task/publicationReference.js";
|
|
6
|
-
import {
|
|
6
|
+
import { isAcceptedTaskReviewBaseline } from "../review/reviewAcceptance.js";
|
|
7
7
|
import { workspaceProjectEntry } from "../worktree/managedWorkspace.js";
|
|
8
8
|
/**
|
|
9
9
|
* Verify the one supported ancestry waiver before `task complete` mutates any
|
|
@@ -29,15 +29,6 @@ export async function verifyTaskCompletionPublishedTree(taskId, publicationId, s
|
|
|
29
29
|
throw usageError(`Publication ${publication.id} must record exact local and remote commits.`);
|
|
30
30
|
}
|
|
31
31
|
const workspace = requireTaskWorkspace(store, task);
|
|
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.`);
|
|
40
|
-
}
|
|
41
32
|
const git = options.git ?? new NodeGitWorkspace();
|
|
42
33
|
const actualHeads = new Map();
|
|
43
34
|
for (const taskBinding of task.projectBindings) {
|
|
@@ -46,22 +37,18 @@ export async function verifyTaskCompletionPublishedTree(taskId, publicationId, s
|
|
|
46
37
|
throw usageError(`Task ${task.id} has no writable managed main workspace for Project ${taskBinding.projectId}.`);
|
|
47
38
|
}
|
|
48
39
|
const actualCommit = (await git.inspect(entry.path, "HEAD")).baseCommit;
|
|
49
|
-
if (latestReview !== undefined) {
|
|
50
|
-
const reviewedCommit = latestReview.taskCandidate.projects.find(({ projectId }) => (projectId === taskBinding.projectId))?.commit;
|
|
51
|
-
if (reviewedCommit === undefined) {
|
|
52
|
-
throw usageError(`Task-final Review ${latestReview.id} omitted Project ${taskBinding.projectId}.`);
|
|
53
|
-
}
|
|
54
|
-
if (actualCommit !== reviewedCommit) {
|
|
55
|
-
throw usageError(`Task-final Review ${latestReview.id} does not match Task head `
|
|
56
|
-
+ `${taskBinding.projectId}@${actualCommit}.`);
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
40
|
actualHeads.set(taskBinding.projectId, actualCommit);
|
|
60
41
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
42
|
+
const actualCandidate = {
|
|
43
|
+
schemaVersion: 1,
|
|
44
|
+
projects: task.projectBindings.map(({ projectId }) => ({
|
|
45
|
+
projectId,
|
|
46
|
+
commit: actualHeads.get(projectId)
|
|
47
|
+
}))
|
|
48
|
+
};
|
|
49
|
+
const latestReview = acceptedTaskFinalReviews(store, task.id)
|
|
50
|
+
.find((round) => (round.taskCandidate !== undefined
|
|
51
|
+
&& sameTaskCandidate(round.taskCandidate, actualCandidate)));
|
|
65
52
|
const entry = workspaceProjectEntry(workspace, publication.projectId);
|
|
66
53
|
const localCommit = actualHeads.get(publication.projectId);
|
|
67
54
|
if (publication.localCommit !== localCommit) {
|
|
@@ -124,18 +111,14 @@ export function assertTaskCompletionPublishedTreeProof(store, task, publicationI
|
|
|
124
111
|
throw usageError(`Publication evidence changed before Task completion: ${publication.id}.`);
|
|
125
112
|
}
|
|
126
113
|
if (proof.reviewRoundId !== undefined) {
|
|
127
|
-
const
|
|
128
|
-
if (
|
|
129
|
-
||
|
|
130
|
-
||
|
|
131
|
-
||
|
|
132
|
-
|| !sameTaskCandidate(latestReview.taskCandidate, actualCandidate)) {
|
|
114
|
+
const review = store.getReviewRound(task.id, proof.reviewRoundId);
|
|
115
|
+
if (review === null
|
|
116
|
+
|| !isAcceptedTaskReviewBaseline(store, review)
|
|
117
|
+
|| review.taskCandidate === undefined
|
|
118
|
+
|| !sameTaskCandidate(review.taskCandidate, actualCandidate)) {
|
|
133
119
|
throw usageError(`Task-final Review evidence changed before published-tree completion: ${task.id}.`);
|
|
134
120
|
}
|
|
135
121
|
}
|
|
136
|
-
else if (latestTaskFinalReview(store, task.id) !== undefined) {
|
|
137
|
-
throw usageError(`Published-tree proof omitted the Task-final Review: ${task.id}.`);
|
|
138
|
-
}
|
|
139
122
|
const actualCommit = actualCandidate.projects.find(({ projectId }) => (projectId === proof.projectId))?.commit;
|
|
140
123
|
if (actualCommit !== proof.localCommit) {
|
|
141
124
|
throw usageError(`Task head changed before published-tree completion: `
|
|
@@ -157,11 +140,10 @@ function requireCurrentVerifiedPublication(store, taskId, publicationId) {
|
|
|
157
140
|
}
|
|
158
141
|
return publication;
|
|
159
142
|
}
|
|
160
|
-
function
|
|
143
|
+
function acceptedTaskFinalReviews(store, taskId) {
|
|
161
144
|
return store.listReviewRounds(taskId)
|
|
162
|
-
.filter((round) => (round
|
|
163
|
-
.sort((left, right) => (
|
|
164
|
-
.at(-1);
|
|
145
|
+
.filter((round) => isAcceptedTaskReviewBaseline(store, round))
|
|
146
|
+
.sort((left, right) => (right.id.localeCompare(left.id, undefined, { numeric: true })));
|
|
165
147
|
}
|
|
166
148
|
function sameTaskCandidate(left, right) {
|
|
167
149
|
return left.projects.length === right.projects.length
|
|
@@ -326,33 +308,26 @@ function requireTaskWorkspace(store, task) {
|
|
|
326
308
|
return workspace;
|
|
327
309
|
}
|
|
328
310
|
/**
|
|
329
|
-
* Check whether
|
|
311
|
+
* Check whether an accepted Task-final ReviewRound is completed and its
|
|
330
312
|
* immutable candidate exactly matches the current actual heads for every
|
|
331
313
|
* Project in the Task. When this holds, the reviewed head is the final
|
|
332
314
|
* head and remote reconciliation must not change it.
|
|
333
315
|
*/
|
|
334
316
|
async function hasCompletedTaskFinalReviewForCurrentCandidate(store, task, workspace, git) {
|
|
335
|
-
const
|
|
336
|
-
.filter((round) => (round.scope ?? "work-item") === "task")
|
|
337
|
-
.sort((left, right) => left.id.localeCompare(right.id, undefined, { numeric: true }));
|
|
338
|
-
const latest = rounds.at(-1);
|
|
339
|
-
if (latest === undefined || !isSemanticReviewRound(latest, store))
|
|
340
|
-
return false;
|
|
341
|
-
if (latest.taskCandidate === undefined)
|
|
342
|
-
return false;
|
|
343
|
-
const candidateProjects = new Map(latest.taskCandidate.projects.map((entry) => [entry.projectId, entry.commit]));
|
|
317
|
+
const actualProjects = [];
|
|
344
318
|
for (const binding of task.projectBindings) {
|
|
345
|
-
const reviewedCommit = candidateProjects.get(binding.projectId);
|
|
346
|
-
if (reviewedCommit === undefined)
|
|
347
|
-
return false;
|
|
348
319
|
const entry = workspaceProjectEntry(workspace, binding.projectId);
|
|
349
320
|
if (entry === undefined || entry.access !== "write")
|
|
350
321
|
return false;
|
|
351
322
|
const actualHead = (await git.inspect(entry.path, "HEAD")).baseCommit;
|
|
352
|
-
|
|
353
|
-
return false;
|
|
323
|
+
actualProjects.push({ projectId: binding.projectId, commit: actualHead });
|
|
354
324
|
}
|
|
355
|
-
|
|
325
|
+
const actualCandidate = {
|
|
326
|
+
schemaVersion: 1,
|
|
327
|
+
projects: actualProjects
|
|
328
|
+
};
|
|
329
|
+
return acceptedTaskFinalReviews(store, task.id).some((round) => (round.taskCandidate !== undefined
|
|
330
|
+
&& sameTaskCandidate(round.taskCandidate, actualCandidate)));
|
|
356
331
|
}
|
|
357
332
|
function latestCommittedIntegration(store, taskId, projectId) {
|
|
358
333
|
return store.listIntegrationAttempts(taskId)
|
|
@@ -369,7 +344,7 @@ function latestCommittedIntegration(store, taskId, projectId) {
|
|
|
369
344
|
function hasFrozenTaskBaseline(store, taskId, projectId, currentCommit) {
|
|
370
345
|
return store.listReviewRounds(taskId)
|
|
371
346
|
.some((round) => ((round.scope ?? "work-item") === "task"
|
|
372
|
-
&&
|
|
347
|
+
&& isAcceptedTaskReviewBaseline(store, round)
|
|
373
348
|
&& round.taskCandidate !== undefined
|
|
374
349
|
&& round.taskCandidate.projects.some((entry) => (entry.projectId === projectId && entry.commit === currentCommit))));
|
|
375
350
|
}
|
|
@@ -9,6 +9,7 @@ import { summarizeExecutionGroup } from "../execution/executionGroup.js";
|
|
|
9
9
|
import { currentWorkItemExecutionGroup } from "../workItem/workItem.js";
|
|
10
10
|
import { operationalTaskRecords } from "../task/taskRecordRetirement.js";
|
|
11
11
|
import { projectExecutionLaneRunRecoveries } from "../run/recoveryProjection.js";
|
|
12
|
+
import { projectReviewDecision } from "../review/reviewDecision.js";
|
|
12
13
|
const RECENT_RECORD_LIMIT = 5;
|
|
13
14
|
const RELATED_RECORD_LIMIT = 5;
|
|
14
15
|
const SUMMARY_TEXT_LIMIT = 400;
|
|
@@ -17,7 +18,7 @@ const TERMINAL_WORK_ITEM_STATUSES = new Set([
|
|
|
17
18
|
"failed",
|
|
18
19
|
"retired"
|
|
19
20
|
]);
|
|
20
|
-
export function runTaskContextCommand(args, store) {
|
|
21
|
+
export function runTaskContextCommand(args, store, currentTaskReviewCandidate = null) {
|
|
21
22
|
if (args.length !== 1 || args[0]?.trim().length === 0) {
|
|
22
23
|
throw usageError("Task context usage: yui task context <task>.");
|
|
23
24
|
}
|
|
@@ -46,6 +47,7 @@ export function runTaskContextCommand(args, store) {
|
|
|
46
47
|
const events = reader.listEvents(task.id);
|
|
47
48
|
const agentRuns = chronological(operationalTaskRecords(reader.listAgentRuns(task.id), events, "agent-run"));
|
|
48
49
|
const reviewRounds = chronological(reader.listReviewRounds(task.id));
|
|
50
|
+
const reviewConfig = reader.getReviewConfig();
|
|
49
51
|
const changeSets = chronological(reader.listChangeSets(task.id));
|
|
50
52
|
const integrations = chronological(reader.listIntegrationAttempts(task.id));
|
|
51
53
|
const publications = chronological(reader.listPublicationReferences(task.id));
|
|
@@ -56,7 +58,16 @@ export function runTaskContextCommand(args, store) {
|
|
|
56
58
|
return {
|
|
57
59
|
task,
|
|
58
60
|
execution,
|
|
59
|
-
reviewConfig
|
|
61
|
+
reviewConfig,
|
|
62
|
+
reviewDecision: projectReviewDecision({
|
|
63
|
+
store: reader,
|
|
64
|
+
task,
|
|
65
|
+
roles,
|
|
66
|
+
runs: agentRuns,
|
|
67
|
+
rounds: reviewRounds,
|
|
68
|
+
reviewConfig,
|
|
69
|
+
currentCandidate: currentTaskReviewCandidate
|
|
70
|
+
}),
|
|
60
71
|
brief: reader.getTaskBrief(task.id),
|
|
61
72
|
activeDecisions: reader.listDecisions(task.id)
|
|
62
73
|
.filter((decision) => decision.status === "active"),
|
|
@@ -78,20 +89,19 @@ export function runTaskContextCommand(args, store) {
|
|
|
78
89
|
events,
|
|
79
90
|
nextAction: projectNextAction({
|
|
80
91
|
...nextActionFacts,
|
|
92
|
+
currentTaskReviewCandidate,
|
|
81
93
|
executionGroups: execution.executionGroups,
|
|
82
94
|
runRecoveries: projectExecutionLaneRunRecoveries(reader, task.id, execution.executionGroups)
|
|
83
95
|
})
|
|
84
96
|
};
|
|
85
97
|
});
|
|
86
|
-
const { task, execution, reviewConfig, brief, activeDecisions, milestones, roles, managedWorkspaces, roleSessionSets, coordinationMailboxes, roleSessionRecoveries, workItems, agentRuns, reviewRounds, changeSets, integrations, publications, messages, openInputRequests, resolvedInputRequests, events, nextAction } = data;
|
|
98
|
+
const { task, execution, reviewConfig, reviewDecision, brief, activeDecisions, milestones, roles, managedWorkspaces, roleSessionSets, coordinationMailboxes, roleSessionRecoveries, workItems, agentRuns, reviewRounds, changeSets, integrations, publications, messages, openInputRequests, resolvedInputRequests, events, nextAction } = data;
|
|
87
99
|
const timeZone = store.getConfig().timeZone;
|
|
88
100
|
const displayedActiveDecisions = activeDecisions.slice(-RECENT_RECORD_LIMIT);
|
|
89
101
|
const displayedWorkItems = currentAndRecentWorkItems(workItems);
|
|
90
102
|
const displayedOpenInputRequests = openInputRequests.slice(-RECENT_RECORD_LIMIT);
|
|
91
103
|
const displayedResolvedInputRequests = resolvedInputRequests.slice(-RECENT_RECORD_LIMIT);
|
|
92
|
-
const
|
|
93
|
-
? execution.activeRuns
|
|
94
|
-
: [];
|
|
104
|
+
const displayedActiveRuns = execution.activeRuns;
|
|
95
105
|
const executionGroupsById = new Map(execution.executionGroups.map((group) => [
|
|
96
106
|
group.groupId,
|
|
97
107
|
group
|
|
@@ -135,14 +145,26 @@ export function runTaskContextCommand(args, store) {
|
|
|
135
145
|
` Attention: ${execution.attention.length === 0
|
|
136
146
|
? "none"
|
|
137
147
|
: execution.attention.map(({ kind, owner }) => `${kind}/${owner}`).join(", ")}`,
|
|
138
|
-
` Active
|
|
139
|
-
...(
|
|
148
|
+
` Active AgentRuns (${displayedActiveRuns.length}):`,
|
|
149
|
+
...(displayedActiveRuns.length === 0
|
|
140
150
|
? [" None."]
|
|
141
|
-
:
|
|
151
|
+
: displayedActiveRuns.map((run) => (` ${run.roleName}: ${run.id} [${run.status}/${run.purpose}; ${run.delivered ? "accepted" : "delivery-pending"}${run.workItemId === undefined ? "" : `; work-item=${run.workItemId}`}${run.reviewRoundId === undefined ? "" : `; review-round=${run.reviewRoundId}`}${run.executionGroupId === undefined ? "" : `; group=${run.executionGroupId}`}${run.executionLaneId === undefined ? "" : `; lane=${run.executionLaneId}`}]`))),
|
|
142
152
|
"Observability:",
|
|
143
153
|
` DAG: ${observability.dag.nodes.length} node(s), ${observability.dag.edges.length} edge(s); ready=${observability.dag.readyIds.join(", ") || "none"}; blocked=${observability.dag.blockedIds.join(", ") || "none"}`,
|
|
144
154
|
` Cost: tokens=${resourceUsageLabel(observability.cost.tokens, undefined, observability.cost.tokensObservable)}; tools=${resourceUsageLabel(observability.cost.toolCalls, undefined, observability.cost.toolCallsObservable)}; wall=${observability.cost.wallClockSeconds}s; lanes=${observability.cost.laneCount}; groups=${observability.cost.groupCount}; retries=${observability.cost.retryCount}; marginal-value=unavailable`,
|
|
145
|
-
` Context: snapshots=${observability.context.snapshotCount}; bytes=${observability.context.totalBytes === null ? "partial" : observability.context.totalBytes};
|
|
155
|
+
` Context: snapshots=${observability.context.snapshotCount}; bytes=${observability.context.totalBytes === null ? "partial" : observability.context.totalBytes}; compression=unavailable`,
|
|
156
|
+
" Session tokens:",
|
|
157
|
+
...(observability.sessionTokens.length === 0
|
|
158
|
+
? [" unobserved"]
|
|
159
|
+
: observability.sessionTokens.map(({ roleName, agentId, metrics }) => {
|
|
160
|
+
const total = metrics.cumulativeTotal.status === "observed"
|
|
161
|
+
? metrics.cumulativeTotal.totalTokens
|
|
162
|
+
: "unobserved";
|
|
163
|
+
const maximum = metrics.maximumRequestInput.status === "observed"
|
|
164
|
+
? metrics.maximumRequestInput.inputTokens
|
|
165
|
+
: "unobserved";
|
|
166
|
+
return ` ${roleName}/${agentId}: total=${total}; max-request-input=${maximum}`;
|
|
167
|
+
})),
|
|
146
168
|
...(task.projectBindings.length === 0
|
|
147
169
|
? []
|
|
148
170
|
: [
|
|
@@ -166,6 +188,28 @@ export function runTaskContextCommand(args, store) {
|
|
|
166
188
|
`Global review: ${reviewConfig === null
|
|
167
189
|
? "disabled"
|
|
168
190
|
: `${reviewConfig.roleName} (${reviewConfig.trigger})`}`,
|
|
191
|
+
"Review decision support:",
|
|
192
|
+
` Current durable heads: ${reviewDecision.currentCandidate?.projects
|
|
193
|
+
.map(({ projectId, commit }) => `${projectId}@${commit.slice(0, 12)}`).join(", ")
|
|
194
|
+
?? "unavailable"}`,
|
|
195
|
+
" Active Reviews:",
|
|
196
|
+
...(reviewDecision.activeReviews.length === 0
|
|
197
|
+
? [" - none"]
|
|
198
|
+
: reviewDecision.activeReviews.map((review) => (` - ${review.reviewRoundId}/${review.reviewerRoleName}/${review.mode}`
|
|
199
|
+
+ ` [${review.status}; current=${review.candidateRelation}]`
|
|
200
|
+
+ ` heads=${review.frozenCandidate?.projects.map(({ projectId, commit }) => (`${projectId}@${commit.slice(0, 12)}`)).join(", ") ?? "unavailable"}`
|
|
201
|
+
+ `${review.workspaceRoot === undefined
|
|
202
|
+
? ""
|
|
203
|
+
: ` workspace=${review.workspaceRoot}`}`))),
|
|
204
|
+
` Reviewer slots: ${reviewDecision.reviewers.length === 0
|
|
205
|
+
? "none configured"
|
|
206
|
+
: reviewDecision.reviewers.map((reviewer) => (`${reviewer.reviewerRoleName}=${reviewer.status}`
|
|
207
|
+
+ `${reviewer.activeRunId === undefined ? "" : `(${reviewer.activeRunId})`}`)).join(", ")}`,
|
|
208
|
+
` Accepted baseline: ${reviewDecision.latestAcceptedBaseline === null
|
|
209
|
+
? "none"
|
|
210
|
+
: `${reviewDecision.latestAcceptedBaseline.reviewRoundId}`
|
|
211
|
+
+ ` [${reviewDecision.latestAcceptedBaseline.relationToCurrent}]`}`,
|
|
212
|
+
` Delta: ${reviewDecision.delta.technicalAvailability}; ${reviewDecision.delta.reason}`,
|
|
169
213
|
"",
|
|
170
214
|
"Brief:",
|
|
171
215
|
...(brief === null
|
|
@@ -577,5 +621,5 @@ function resourceUsageLabel(used, remaining, observable) {
|
|
|
577
621
|
}
|
|
578
622
|
function renderWorkItemObservability(item) {
|
|
579
623
|
const stages = item.stages.map((stage) => (`${stage.stage ?? "single"}${stage.round === undefined ? "" : `#${stage.round}`}${stage.stageAttempt === undefined ? "" : `/a${stage.stageAttempt}`}`)).join(", ");
|
|
580
|
-
return ` Observability: stages=${stages || "none"}; tokens=${item.cost.tokens}; tools=${item.cost.toolCalls}; wall=${item.cost.wallClockSeconds}s; retries=${item.cost.retryCount}; snapshots=${item.context.snapshotCount};
|
|
624
|
+
return ` Observability: stages=${stages || "none"}; tokens=${item.cost.tokens}; tools=${item.cost.toolCalls}; wall=${item.cost.wallClockSeconds}s; retries=${item.cost.retryCount}; snapshots=${item.context.snapshotCount}; evidence=${item.evidenceCount}; open-findings=${item.openFindingCount}; compression=${item.context.compressionStatus}`;
|
|
581
625
|
}
|
|
@@ -6,7 +6,7 @@ import { defaultTableWidth, renderTable } from "../output/table.js";
|
|
|
6
6
|
import { formatTimestamp } from "../output/timePresentation.js";
|
|
7
7
|
import { agentRunDeliveryReceiptId } from "../run/agentRun.js";
|
|
8
8
|
import { enqueueWork } from "../coordination/workMailboxQueue.js";
|
|
9
|
-
import {
|
|
9
|
+
import { isRoleRunStalled, RUN_RECOVERED_EVENT } from "../scheduler/roleRunStall.js";
|
|
10
10
|
import { terminalizeExactTaskRun } from "../lifecycle/exactRunTerminalization.js";
|
|
11
11
|
import { hasRuntimeCleanupObligation, isRuntimeLaunchReservation, runtimeLifecycleTarget } from "../runtime/lifecycleReservation.js";
|
|
12
12
|
import { isLinuxProcessLive, listOwnedProcessTree } from "../runtime/sessionOwnerIdentity.js";
|
|
@@ -66,9 +66,13 @@ function createRequest(args, store, options) {
|
|
|
66
66
|
const wasStalled = isRoleRunStalled(tx.listEvents(task.id), origin.run.id);
|
|
67
67
|
tx.saveInputRequest(task.id, created);
|
|
68
68
|
enqueueWork(tx, { kind: "operator" }, "input-requested", now, [
|
|
69
|
-
{ type: "input", taskId: task.id, id: created.id }
|
|
70
|
-
|
|
71
|
-
|
|
69
|
+
{ type: "input", taskId: task.id, id: created.id }
|
|
70
|
+
], {
|
|
71
|
+
source: "input-request",
|
|
72
|
+
dedupeKey: `operator-input:${task.id}:${created.id}`,
|
|
73
|
+
deliveryMode: "followup",
|
|
74
|
+
lane: "normal"
|
|
75
|
+
});
|
|
72
76
|
const terminal = terminalizeExactTaskRun(tx, {
|
|
73
77
|
taskId: task.id,
|
|
74
78
|
roleName: LEADER_ROLE,
|
|
@@ -94,7 +98,6 @@ function createRequest(args, store, options) {
|
|
|
94
98
|
kind: "input-request"
|
|
95
99
|
}, now);
|
|
96
100
|
}
|
|
97
|
-
clearMatchingLeaderStallAttention(tx, task.id, origin.run.id);
|
|
98
101
|
recordTaskEvent(tx, task.id, "input.requested", {
|
|
99
102
|
requestId: created.id,
|
|
100
103
|
requesterRunId: created.requester.runId,
|
|
@@ -6,6 +6,7 @@ import { projectTaskOrchestration } from "../observability/orchestrationMetrics.
|
|
|
6
6
|
import { operationalTaskRecords } from "../task/taskRecordRetirement.js";
|
|
7
7
|
import { buildTaskExecutionProjection } from "../scheduler/taskExecutionProjection.js";
|
|
8
8
|
import { projectExecutionLaneRunRecoveries } from "../run/recoveryProjection.js";
|
|
9
|
+
import { projectReviewDecision } from "../review/reviewDecision.js";
|
|
9
10
|
/**
|
|
10
11
|
* Issue 07 (Leader convergence): read-only `yui task next-action <task>`.
|
|
11
12
|
* Folds the existing durable records into exactly one protocol-level next
|
|
@@ -18,7 +19,7 @@ import { projectExecutionLaneRunRecoveries } from "../run/recoveryProjection.js"
|
|
|
18
19
|
* carries the full `completionReadiness` blocker list so the Leader sees every
|
|
19
20
|
* terminalization precondition before attempting `yui task complete`.
|
|
20
21
|
*/
|
|
21
|
-
export function runTaskNextActionCommand(args, store) {
|
|
22
|
+
export function runTaskNextActionCommand(args, store, currentTaskReviewCandidate = null) {
|
|
22
23
|
const usage = "Task next-action usage: yui task next-action <task> [--json].";
|
|
23
24
|
const positionals = args.filter((arg) => arg !== "--json");
|
|
24
25
|
const asJson = args.includes("--json");
|
|
@@ -40,6 +41,7 @@ export function runTaskNextActionCommand(args, store) {
|
|
|
40
41
|
throw taskNotFound(taskId);
|
|
41
42
|
const actionFacts = {
|
|
42
43
|
...facts,
|
|
44
|
+
currentTaskReviewCandidate,
|
|
43
45
|
executionGroups: execution.executionGroups,
|
|
44
46
|
runRecoveries: projectExecutionLaneRunRecoveries(reader, taskId, execution.executionGroups)
|
|
45
47
|
};
|
|
@@ -69,13 +71,25 @@ export function runTaskNextActionCommand(args, store) {
|
|
|
69
71
|
completionReadiness = projectCompletionReadiness(readinessFacts);
|
|
70
72
|
}
|
|
71
73
|
const events = reader.listEvents(taskId);
|
|
74
|
+
const task = reader.getTask(taskId);
|
|
75
|
+
const operationalRuns = operationalTaskRecords(reader.listAgentRuns(taskId), events, "agent-run");
|
|
76
|
+
const reviewRounds = reader.listReviewRounds(taskId);
|
|
77
|
+
const reviewDecision = projectReviewDecision({
|
|
78
|
+
store: reader,
|
|
79
|
+
task,
|
|
80
|
+
roles: reader.listRoles(taskId),
|
|
81
|
+
runs: operationalRuns,
|
|
82
|
+
rounds: reviewRounds,
|
|
83
|
+
reviewConfig: reader.getReviewConfig(),
|
|
84
|
+
currentCandidate: currentTaskReviewCandidate
|
|
85
|
+
});
|
|
72
86
|
const orchestration = projectTaskOrchestration({
|
|
73
|
-
task
|
|
74
|
-
runs:
|
|
87
|
+
task,
|
|
88
|
+
runs: operationalRuns,
|
|
75
89
|
roleSessionSets: reader.listRoleSessionSets(taskId),
|
|
76
90
|
workItems: reader.listWorkItems(taskId),
|
|
77
91
|
changeSets: reader.listChangeSets(taskId),
|
|
78
|
-
reviewRounds
|
|
92
|
+
reviewRounds,
|
|
79
93
|
reviewFindings: reader.listReviewFindings(taskId),
|
|
80
94
|
integrations: reader.listIntegrationAttempts(taskId),
|
|
81
95
|
durableJobs: reader.listDurableJobs(taskId),
|
|
@@ -90,6 +104,8 @@ export function runTaskNextActionCommand(args, store) {
|
|
|
90
104
|
repairWave,
|
|
91
105
|
completionReadiness,
|
|
92
106
|
knowledgeProposals,
|
|
107
|
+
reviewDecision,
|
|
108
|
+
execution: nextActionExecutionView(execution),
|
|
93
109
|
orchestration
|
|
94
110
|
};
|
|
95
111
|
});
|
|
@@ -102,7 +118,7 @@ export function runTaskNextActionCommand(args, store) {
|
|
|
102
118
|
}
|
|
103
119
|
return {
|
|
104
120
|
kind: "output",
|
|
105
|
-
output: renderNextAction(data.action, data.taskType, data.repairWave, data.completionReadiness, data.knowledgeProposals, data.orchestration.advisories),
|
|
121
|
+
output: renderNextAction(data.action, data.taskType, data.repairWave, data.completionReadiness, data.knowledgeProposals, data.reviewDecision, data.execution, data.orchestration.advisories),
|
|
106
122
|
data
|
|
107
123
|
};
|
|
108
124
|
}
|
|
@@ -120,12 +136,17 @@ function repairWaveFor(action, facts) {
|
|
|
120
136
|
return null;
|
|
121
137
|
return planRepairWave(round.id, findings);
|
|
122
138
|
}
|
|
123
|
-
function renderNextAction(action, taskType, repairWave, completionReadiness, knowledgeProposals, orchestrationAdvisories) {
|
|
139
|
+
function renderNextAction(action, taskType, repairWave, completionReadiness, knowledgeProposals, reviewDecision, execution, orchestrationAdvisories) {
|
|
124
140
|
const lines = [
|
|
125
141
|
`Task: ${action.taskId}`,
|
|
126
142
|
`Type: ${taskType ?? "unspecified"}`,
|
|
127
143
|
`Next action: ${action.kind}`,
|
|
128
144
|
`Reason: ${action.reason}`,
|
|
145
|
+
`Execution view: ${execution.status}; owner/action=${execution.owner}/${execution.action}; reason=${execution.reason}; ${execution.summary}`,
|
|
146
|
+
`Active AgentRuns (${execution.activeRuns.length}):`,
|
|
147
|
+
...(execution.activeRuns.length === 0
|
|
148
|
+
? [" none"]
|
|
149
|
+
: execution.activeRuns.map((run) => ` ${renderActiveRun(run)}`)),
|
|
129
150
|
...(action.refs.length === 0
|
|
130
151
|
? ["Refs: none"]
|
|
131
152
|
: ["Refs:", ...action.refs.map((ref) => ` ${ref.kind}: ${ref.id}`)]),
|
|
@@ -142,6 +163,16 @@ function renderNextAction(action, taskType, repairWave, completionReadiness, kno
|
|
|
142
163
|
...(action.judgmentRequired === undefined
|
|
143
164
|
? []
|
|
144
165
|
: [`Judgment: ${action.judgmentRequired}`]),
|
|
166
|
+
`Review state: active=${reviewDecision.activeReviews.length}; reviewers=${reviewDecision.reviewers
|
|
167
|
+
.map(({ reviewerRoleName, status }) => `${reviewerRoleName}:${status}`).join(", ") || "none"}`,
|
|
168
|
+
...(reviewDecision.activeReviews.length === 0
|
|
169
|
+
? []
|
|
170
|
+
: [
|
|
171
|
+
"Frozen Reviews: " + reviewDecision.activeReviews.map((review) => (`${review.reviewRoundId}/${review.reviewerRoleName}`
|
|
172
|
+
+ `[${review.status}; current=${review.candidateRelation}; `
|
|
173
|
+
+ `${review.frozenCandidate?.projects.map(({ projectId, commit }) => (`${projectId}@${commit.slice(0, 12)}`)).join(", ") ?? "heads-unavailable"}]`)).join("; ")
|
|
174
|
+
]),
|
|
175
|
+
`Delta support: ${reviewDecision.delta.technicalAvailability}; ${reviewDecision.delta.reason}`,
|
|
145
176
|
...(action.alternatives === undefined || action.alternatives.length === 0
|
|
146
177
|
? []
|
|
147
178
|
: [
|
|
@@ -188,3 +219,22 @@ function renderNextAction(action, taskType, repairWave, completionReadiness, kno
|
|
|
188
219
|
}
|
|
189
220
|
return `${lines.join("\n")}\n`;
|
|
190
221
|
}
|
|
222
|
+
function renderActiveRun(run) {
|
|
223
|
+
const subject = run.reviewRoundId === undefined
|
|
224
|
+
? run.workItemId === undefined ? "task" : `work-item=${run.workItemId}`
|
|
225
|
+
: `review-round=${run.reviewRoundId}`;
|
|
226
|
+
const lane = run.executionGroupId === undefined
|
|
227
|
+
? ""
|
|
228
|
+
: `; group=${run.executionGroupId}${run.executionLaneId === undefined ? "" : `; lane=${run.executionLaneId}`}`;
|
|
229
|
+
return `${run.id} [${run.purpose}/${run.status}; role=${run.roleName}; ${run.delivered ? "accepted" : "delivery-pending"}; ${subject}${lane}]`;
|
|
230
|
+
}
|
|
231
|
+
function nextActionExecutionView(execution) {
|
|
232
|
+
return {
|
|
233
|
+
status: execution.status,
|
|
234
|
+
owner: execution.owner,
|
|
235
|
+
action: execution.action,
|
|
236
|
+
reason: execution.reason,
|
|
237
|
+
summary: execution.summary,
|
|
238
|
+
activeRuns: execution.activeRuns
|
|
239
|
+
};
|
|
240
|
+
}
|
|
@@ -64,7 +64,6 @@ function buildTaskOverviewEntry(task, store, now, runtimeHealthPolicy) {
|
|
|
64
64
|
const agentRuns = store.listAgentRuns(task.id);
|
|
65
65
|
const events = store.listEvents(task.id);
|
|
66
66
|
const leaderFailure = store.getLeaderFailure(task.id);
|
|
67
|
-
const operatorNotification = store.getOperatorNotification(task.id);
|
|
68
67
|
const leaderMailbox = store.getWorkMailbox({ kind: "role", taskId: task.id, roleName: "leader" });
|
|
69
68
|
const pendingWakeup = pendingWakeupProjection(leaderMailbox);
|
|
70
69
|
const reviewRounds = store.listReviewRounds(task.id);
|
|
@@ -74,7 +73,7 @@ function buildTaskOverviewEntry(task, store, now, runtimeHealthPolicy) {
|
|
|
74
73
|
const session = store.getRoleSession(task.id, role.name);
|
|
75
74
|
return session === null ? [] : [{ roleName: role.name, ...session }];
|
|
76
75
|
});
|
|
77
|
-
const attention = collectAttention(task, agentRuns, events, leaderFailure
|
|
76
|
+
const attention = collectAttention(task, agentRuns, events, leaderFailure);
|
|
78
77
|
const blockers = collectBlockers(workItems, openInputRequests, attention);
|
|
79
78
|
const legacyNext = deriveNextAction(task, brief, workItems, openInputRequests, blockers, pendingWakeup);
|
|
80
79
|
const leader = {
|
|
@@ -119,7 +118,6 @@ function buildTaskOverviewEntry(task, store, now, runtimeHealthPolicy) {
|
|
|
119
118
|
pendingWakeup,
|
|
120
119
|
leaderMailbox,
|
|
121
120
|
leaderFailure,
|
|
122
|
-
operatorNotification,
|
|
123
121
|
roleSessions,
|
|
124
122
|
contextSnapshots: store.listContextSnapshots(task.id),
|
|
125
123
|
now,
|
|
@@ -154,37 +152,16 @@ function buildTaskOverviewEntry(task, store, now, runtimeHealthPolicy) {
|
|
|
154
152
|
execution
|
|
155
153
|
};
|
|
156
154
|
}
|
|
157
|
-
function collectAttention(task, runs, events, failure
|
|
155
|
+
function collectAttention(task, runs, events, failure) {
|
|
158
156
|
const items = [];
|
|
159
|
-
if (failure !== null
|
|
157
|
+
if (failure !== null) {
|
|
160
158
|
items.push({
|
|
161
159
|
kind: "leader-recovery",
|
|
162
160
|
id: `leader-recovery:${task.id}`,
|
|
163
161
|
status: "failed",
|
|
164
162
|
owner: "operator",
|
|
165
|
-
summary: failure
|
|
166
|
-
|
|
167
|
-
? notification.message
|
|
168
|
-
: "Leader recovery failed."),
|
|
169
|
-
updatedAt: failure?.lastFailedAt
|
|
170
|
-
?? (notification?.type === "leader-recovery-failed"
|
|
171
|
-
? notification.updatedAt
|
|
172
|
-
: task.updatedAt)
|
|
173
|
-
});
|
|
174
|
-
}
|
|
175
|
-
const notificationStall = notification?.type === "leader-stalled"
|
|
176
|
-
? notification
|
|
177
|
-
: null;
|
|
178
|
-
if (notificationStall !== null) {
|
|
179
|
-
items.push({
|
|
180
|
-
kind: "leader-stalled",
|
|
181
|
-
id: `leader-stall:${notificationStall.runId}:${notificationStall.progressAt}`,
|
|
182
|
-
status: "needs-attention",
|
|
183
|
-
owner: "operator",
|
|
184
|
-
summary: notificationStall.message,
|
|
185
|
-
updatedAt: notificationStall.updatedAt,
|
|
186
|
-
runId: notificationStall.runId,
|
|
187
|
-
roleName: "leader"
|
|
163
|
+
summary: failure.message,
|
|
164
|
+
updatedAt: failure.lastFailedAt
|
|
188
165
|
});
|
|
189
166
|
}
|
|
190
167
|
for (const run of runs) {
|
|
@@ -192,8 +169,6 @@ function collectAttention(task, runs, events, failure, notification) {
|
|
|
192
169
|
|| run.status !== "active"
|
|
193
170
|
|| !isRoleRunStalled(events, run.id))
|
|
194
171
|
continue;
|
|
195
|
-
if (notificationStall?.runId === run.id)
|
|
196
|
-
continue;
|
|
197
172
|
const progressAt = latestStallProgressAt(events, run.id) ?? run.updatedAt;
|
|
198
173
|
items.push({
|
|
199
174
|
kind: "leader-stalled",
|
|
@@ -399,7 +374,18 @@ function renderVerboseDetails(tasks, timeZone) {
|
|
|
399
374
|
` Monitoring: ${task.execution.monitoring}; attention: ${task.execution.attention.length}`,
|
|
400
375
|
` DAG: ${task.execution.observability.dag.nodes.length} nodes, ${task.execution.observability.dag.edges.length} edges; ready=${task.execution.observability.dag.readyIds.join(", ") || "none"}; blocked=${task.execution.observability.dag.blockedIds.join(", ") || "none"}`,
|
|
401
376
|
` Cost: tokens=${task.execution.observability.cost.tokens}${task.execution.observability.cost.tokensObservable ? "" : " (partial)"}; tools=${task.execution.observability.cost.toolCalls}${task.execution.observability.cost.toolCallsObservable ? "" : " (partial)"}; wall=${task.execution.observability.cost.wallClockSeconds}s; retries=${task.execution.observability.cost.retryCount}`,
|
|
402
|
-
` Context: snapshots=${task.execution.observability.context.snapshotCount}; bytes=${task.execution.observability.context.totalBytes ?? "partial"};
|
|
377
|
+
` Context: snapshots=${task.execution.observability.context.snapshotCount}; bytes=${task.execution.observability.context.totalBytes ?? "partial"}; compression=unavailable`,
|
|
378
|
+
` Session tokens: ${task.execution.observability.sessionTokens.length === 0
|
|
379
|
+
? "unobserved"
|
|
380
|
+
: task.execution.observability.sessionTokens.map(({ roleName, metrics }) => {
|
|
381
|
+
const total = metrics.cumulativeTotal.status === "observed"
|
|
382
|
+
? metrics.cumulativeTotal.totalTokens
|
|
383
|
+
: "unobserved";
|
|
384
|
+
const maximum = metrics.maximumRequestInput.status === "observed"
|
|
385
|
+
? metrics.maximumRequestInput.inputTokens
|
|
386
|
+
: "unobserved";
|
|
387
|
+
return `${roleName}: total=${total}, max-request-input=${maximum}`;
|
|
388
|
+
}).join("; ")}`,
|
|
403
389
|
` Projects: ${task.projectBindings.length === 0
|
|
404
390
|
? "none"
|
|
405
391
|
: task.projectBindings.map(({ directory, projectId, baseRef }) => (`${directory} (${projectId} @ ${baseRef})`)).join(", ")}`
|
|
@@ -8,6 +8,7 @@ import { latestRunDurableProgressAt } from "../scheduler/roleRunStall.js";
|
|
|
8
8
|
import { resolveRuntimeHealth } from "../config/yuiConfig.js";
|
|
9
9
|
import { builtinDriverIdForAdapter } from "../runtime/builtinAgentDrivers.js";
|
|
10
10
|
import { operationalTaskRecords } from "../task/taskRecordRetirement.js";
|
|
11
|
+
import { projectSessionTokenMetrics, resolveSessionTokenIdentity } from "../runtime/sessionTokenMetrics.js";
|
|
11
12
|
export function inspectTaskRoleRuntimeStatuses(taskId, roles, store, panes, now = new Date()) {
|
|
12
13
|
const taskOpenInputRequestCount = store.listInputRequests(taskId)
|
|
13
14
|
.filter((request) => request.status === "open").length;
|
|
@@ -65,6 +66,12 @@ export function renderTaskRoleRuntimeStatus(status) {
|
|
|
65
66
|
? ""
|
|
66
67
|
: ` (${status.runtime.observerDetail})`}`
|
|
67
68
|
].filter((value) => value !== undefined).join("; ");
|
|
69
|
+
const sessionTokens = [
|
|
70
|
+
`total=${sessionCumulativeTokenLabel(status.sessionTokens)}`,
|
|
71
|
+
`max-request-input=${status.sessionTokens.maximumRequestInput.status === "observed"
|
|
72
|
+
? status.sessionTokens.maximumRequestInput.inputTokens
|
|
73
|
+
: "unobserved"}`
|
|
74
|
+
].join("; ");
|
|
68
75
|
return [
|
|
69
76
|
`Task Role status: ${status.taskId}/${status.roleName}`,
|
|
70
77
|
"",
|
|
@@ -87,6 +94,7 @@ export function renderTaskRoleRuntimeStatus(status) {
|
|
|
87
94
|
: "none"}`,
|
|
88
95
|
` Native session ${nativeSession}`,
|
|
89
96
|
` Agent runtime ${runtime}`,
|
|
97
|
+
` Session tokens ${sessionTokens}`,
|
|
90
98
|
` Runtime cleanup ${status.runtimeCleanupPending ? "pending" : "none"}`,
|
|
91
99
|
` Fresh launch ${status.freshLaunchAllowed ? "allowed" : "blocked"}`,
|
|
92
100
|
` tmux pane ${tmux}`,
|
|
@@ -178,6 +186,9 @@ function inspectTaskRoleRuntimeStatus(taskId, role, store, pane, openInputReques
|
|
|
178
186
|
? { managed: false, path: role.workspace }
|
|
179
187
|
: { ...managedWorkspace, managed: true };
|
|
180
188
|
const events = store.listEvents(taskId);
|
|
189
|
+
const sessionTokens = projectSessionTokenMetrics(events, resolveSessionTokenIdentity(nativeSession === null
|
|
190
|
+
? null
|
|
191
|
+
: { taskId, roleName: role.name, ...nativeSession }));
|
|
181
192
|
const runtime = projectTaskRoleRuntime(activeRun, nativeSession, tmux, events, store.getWorkMailbox({ kind: "role", taskId, roleName: role.name }), store, taskId, role.name, now);
|
|
182
193
|
const stalled = activeRun !== null && isRoleRunStalled(events, activeRun.id);
|
|
183
194
|
const stallProgressAt = activeRun === null
|
|
@@ -213,10 +224,23 @@ function inspectTaskRoleRuntimeStatus(taskId, role, store, pane, openInputReques
|
|
|
213
224
|
nativeSession,
|
|
214
225
|
tmux,
|
|
215
226
|
workspace,
|
|
227
|
+
sessionTokens,
|
|
216
228
|
runtime,
|
|
217
229
|
stall
|
|
218
230
|
};
|
|
219
231
|
}
|
|
232
|
+
function sessionCumulativeTokenLabel(metrics) {
|
|
233
|
+
const total = metrics.cumulativeTotal;
|
|
234
|
+
if (total.status === "unobserved")
|
|
235
|
+
return "unobserved";
|
|
236
|
+
const breakdown = [
|
|
237
|
+
`input=${total.inputTokens}`,
|
|
238
|
+
`output=${total.outputTokens}`,
|
|
239
|
+
...(total.cachedInputTokens === undefined ? [] : [`cached-input=${total.cachedInputTokens}`]),
|
|
240
|
+
...(total.reasoningTokens === undefined ? [] : [`reasoning=${total.reasoningTokens}`])
|
|
241
|
+
];
|
|
242
|
+
return `${total.totalTokens} (${breakdown.join(", ")})`;
|
|
243
|
+
}
|
|
220
244
|
function latestStallKind(events, runId) {
|
|
221
245
|
const event = [...events]
|
|
222
246
|
.filter((candidate) => candidate.type === "run.stalled" && candidate.payload.runId === runId)
|
|
@@ -99,8 +99,11 @@ function replaceTaskCommand(args, store, options) {
|
|
|
99
99
|
const old = store.getTask(oldId);
|
|
100
100
|
if (old === null)
|
|
101
101
|
throw usageError(`Task not found: ${oldId}.`);
|
|
102
|
-
if (old.status === "draft"
|
|
103
|
-
throw usageError(`Task
|
|
102
|
+
if (old.status === "draft") {
|
|
103
|
+
throw usageError(`Draft Task ${old.id} owns no workspace to replace or rebuild; activate it or retire it and create a new Draft.`);
|
|
104
|
+
}
|
|
105
|
+
if (old.status === "active") {
|
|
106
|
+
throw usageError(`Active Task can be rebuilt instead of replaced: ${old.id}.`);
|
|
104
107
|
}
|
|
105
108
|
let title = `Replaces ${old.id}: ${old.title}`;
|
|
106
109
|
for (let index = 1; index < args.length; index += 1) {
|
|
@@ -22,8 +22,8 @@ export const CONFIG_DEFINITIONS = Object.freeze([
|
|
|
22
22
|
{ key: "provider-retry-max-window-seconds", domain: "runtime", property: "providerRetryMaxWindowSeconds", label: "Provider retry max window", summary: "Positive-integer total retry budget per Run lineage in seconds (default: 600).", takesEffect: "The next retry episode; an active episode keeps its existing deadline." },
|
|
23
23
|
{ key: "leader-next-action", domain: "workflow", property: "leaderNextActionMode", label: "Leader next-action mode", summary: "Leader next-action mode: display, warn, or enforce (default: display).", takesEffect: "The next Leader next-action projection or gate." },
|
|
24
24
|
{ key: "leader-semantic-budget-turns", domain: "workflow", property: "leaderSemanticBudgetTurns", label: "Leader semantic budget", summary: "Consecutive yielded Leader turns without durable delivery progress before warning, 1-20 (default: 3).", takesEffect: "The next Leader delivery guard evaluation." },
|
|
25
|
-
{ key: "context-budget", domain: "workflow", property: "contextBudget", label: "
|
|
26
|
-
{ key: "review", domain: "workflow", property: "review", label: "Review", summary: "Optional global WorkItem review rule: a configured Role plus always, leader, or final trigger
|
|
25
|
+
{ key: "context-budget", domain: "workflow", property: "contextBudget", label: "Legacy context budget (inactive)", summary: `Compatibility-only soft and hard values in ${MIN_CONTEXT_BUDGET_TOKENS}-${MAX_CONTEXT_BUDGET_TOKENS}; they are parsed but never control Session or workflow behavior.`, takesEffect: "No runtime effect; Session Token metrics are read-only observations." },
|
|
26
|
+
{ key: "review", domain: "workflow", property: "review", label: "Review", summary: "Optional global WorkItem review rule: a configured Role plus always, leader, or final trigger and optional finding-ledger behavior. Setup leaves it disabled so the Leader may review directly or delegate selectively.", takesEffect: "The next Candidate snapshot; in-flight Candidates and ReviewRounds keep their policy." },
|
|
27
27
|
{ key: "resources-gc-mode", domain: "resources", property: "resourcesGcMode", label: "Resources GC mode", summary: "Resource GC mode: report or quarantine (default: report).", takesEffect: "The next resource GC operation." },
|
|
28
28
|
{ key: "resources-gc-auto-quarantine", domain: "resources", property: "resourcesGcAutoQuarantine", label: "Resources GC auto-quarantine", summary: "Automatically quarantine eligible terminal Task resources (default: false).", takesEffect: "The next eligible automatic resource GC operation." },
|
|
29
29
|
{ key: "resources-quarantine-ttl-hours", domain: "resources", property: "resourcesQuarantineTtlHours", label: "Resource quarantine TTL", summary: "Default observation window before purge, 1-720 hours (default: 24).", takesEffect: "The next resource GC command without an explicit TTL override." },
|