@zq-silk/yui 0.8.3 → 0.8.7
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 +40 -22
- package/README.md +66 -19
- package/dist/cli/commandCatalog.js +43 -14
- package/dist/cli/operatorWizard.js +10 -20
- package/dist/cli/updatePorts.js +6 -0
- package/dist/cli.js +252 -37
- package/dist/commands/executionAuditCommands.js +30 -0
- package/dist/commands/globalRoleCommands.js +8 -4
- package/dist/commands/operatorCommands.js +42 -1
- package/dist/commands/taskCommands.js +527 -147
- package/dist/commands/taskCompletionGate.js +36 -24
- package/dist/commands/taskContextCommand.js +11 -4
- package/dist/commands/taskInputCommands.js +48 -10
- package/dist/commands/taskNextActionCommand.js +38 -3
- package/dist/commands/taskOverviewCommand.js +2 -1
- package/dist/commands/taskRoleRuntimeStatus.js +2 -1
- package/dist/context/runContextPack.js +9 -5
- package/dist/context/sessionBootstrapManifest.js +158 -11
- package/dist/context/wakeNotification.js +5 -3
- package/dist/controller/clientRuntime.js +15 -15
- package/dist/controller/controller.js +16 -8
- package/dist/controller/fileSchedulerStoreAdapter.js +67 -7
- package/dist/controller/handoverCandidate.js +10 -3
- package/dist/controller/sessionNotify.js +4 -22
- package/dist/executor/agentAdapter.js +2 -2
- package/dist/executor/agentExecutor.js +29 -9
- package/dist/executor/fileRoleLaunchPlanner.js +37 -45
- package/dist/integration/gitIntegrationService.js +50 -2
- package/dist/integration/integrationCheckEvidenceReuse.js +53 -0
- package/dist/observability/executionAudit.js +47 -1
- package/dist/observability/faultClassification.js +6 -4
- package/dist/observability/orchestrationMetrics.js +196 -0
- package/dist/operator/operatorSessionHistory.js +36 -0
- package/dist/release/releaseHandover.js +7 -5
- package/dist/release/runtimeRelease.js +15 -0
- package/dist/repository/taskWorkspaceCoordinator.js +13 -10
- package/dist/review/deltaRecheck.js +3 -2
- package/dist/review/reviewFindingLedger.js +5 -4
- package/dist/review/reviewOutcomeClassifier.js +263 -54
- package/dist/review/taskFinalReviewContractEvent.js +1 -0
- package/dist/review/taskFinalReviewContractRebind.js +367 -0
- package/dist/run/runIdentity.js +10 -70
- package/dist/runtime/agentHost.js +3 -4
- package/dist/runtime/codexAppServerRuntime.js +6 -0
- package/dist/runtime/exactControlPlane.js +47 -37
- package/dist/runtime/firstProgressStopLoss.js +54 -0
- package/dist/runtime/launchBroker.js +10 -2
- package/dist/runtime/runtimeDeadlines.js +14 -0
- package/dist/runtime/sessionTitle.js +24 -12
- package/dist/runtime/structuredProviderHost.js +7 -1
- package/dist/runtime/tmuxAdapters.js +10 -3
- package/dist/scheduler/actionability.js +4 -2
- package/dist/scheduler/activeRoleRunDelivery.js +20 -18
- package/dist/scheduler/activeTaskProgress.js +2 -1
- package/dist/scheduler/leaderWakeupProcessor.js +33 -2
- package/dist/scheduler/taskExecutionProjection.js +13 -4
- package/dist/scheduler/wakeReason.js +1 -0
- package/dist/storage/sqliteStore.js +18 -3
- package/dist/storage/taskStore.js +14 -3
- package/dist/task/completionReadiness.js +48 -19
- package/dist/task/deliveryGuard.js +3 -1
- package/dist/task/nextAction.js +153 -55
- package/dist/task/repairWave.js +14 -1
- package/dist/task/task.js +10 -0
- package/dist/task/taskRecordRetirement.js +72 -0
- package/dist/web/webSnapshot.js +7 -1
- package/dist/workItem/workItem.js +6 -4
- package/i18n/README.zh-CN.md +48 -9
- package/package.json +1 -1
- package/skills/yui-leader/SKILL.md +73 -31
- package/skills/yui-operator/SKILL.md +58 -10
- package/skills/yui-reviewer/SKILL.md +23 -0
- package/skills/yui-runtime/SKILL.md +6 -6
|
@@ -2,13 +2,16 @@ import { usageError } from "../errors/cliError.js";
|
|
|
2
2
|
import { GitIntegrationService } from "../integration/gitIntegrationService.js";
|
|
3
3
|
import { createIntegrationAttempt } from "../integration/integrationAttempt.js";
|
|
4
4
|
import { NodeGitWorkspace } from "../repository/gitWorkspace.js";
|
|
5
|
+
import { taskDeliveryPath } from "../task/task.js";
|
|
5
6
|
import { publicationExternalKey } from "../task/publicationReference.js";
|
|
7
|
+
import { isSemanticReviewRound } from "../review/reviewOutcomeClassifier.js";
|
|
6
8
|
import { workspaceProjectEntry } from "../worktree/managedWorkspace.js";
|
|
7
9
|
/**
|
|
8
10
|
* Verify the one supported ancestry waiver before `task complete` mutates any
|
|
9
11
|
* durable state. The explicit Publication must be the current verified merged
|
|
10
|
-
* record, bind the exact
|
|
11
|
-
* ancestry-divergent commit with the
|
|
12
|
+
* record, bind the exact physical Task head (and, for integrated delivery, its
|
|
13
|
+
* completed final Review), and name an ancestry-divergent commit with the
|
|
14
|
+
* exact same Git tree.
|
|
12
15
|
*/
|
|
13
16
|
export async function verifyTaskCompletionPublishedTree(taskId, publicationId, store, options = {}) {
|
|
14
17
|
const task = store.getTask(taskId);
|
|
@@ -27,10 +30,11 @@ export async function verifyTaskCompletionPublishedTree(taskId, publicationId, s
|
|
|
27
30
|
throw usageError(`Publication ${publication.id} must record exact local and remote commits.`);
|
|
28
31
|
}
|
|
29
32
|
const workspace = requireTaskWorkspace(store, task);
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|| latestReview
|
|
33
|
+
const integrated = taskDeliveryPath(task) === "integrated";
|
|
34
|
+
const latestReview = integrated ? latestTaskFinalReview(store, task.id) : undefined;
|
|
35
|
+
if (integrated && (latestReview === undefined
|
|
36
|
+
|| !isSemanticReviewRound(latestReview, store)
|
|
37
|
+
|| latestReview.taskCandidate === undefined)) {
|
|
34
38
|
throw usageError(`Task ${task.id} requires a latest completed Task-final Review before accepting a published tree.`);
|
|
35
39
|
}
|
|
36
40
|
const git = options.git ?? new NodeGitWorkspace();
|
|
@@ -40,18 +44,21 @@ export async function verifyTaskCompletionPublishedTree(taskId, publicationId, s
|
|
|
40
44
|
if (entry === undefined || entry.access !== "write") {
|
|
41
45
|
throw usageError(`Task ${task.id} has no writable managed main workspace for Project ${taskBinding.projectId}.`);
|
|
42
46
|
}
|
|
43
|
-
const reviewedCommit = latestReview.taskCandidate.projects.find(({ projectId }) => (projectId === taskBinding.projectId))?.commit;
|
|
44
|
-
if (reviewedCommit === undefined) {
|
|
45
|
-
throw usageError(`Task-final Review ${latestReview.id} omitted Project ${taskBinding.projectId}.`);
|
|
46
|
-
}
|
|
47
47
|
const actualCommit = (await git.inspect(entry.path, "HEAD")).baseCommit;
|
|
48
|
-
if (
|
|
49
|
-
|
|
50
|
-
|
|
48
|
+
if (latestReview !== undefined) {
|
|
49
|
+
const reviewedCommit = latestReview.taskCandidate.projects.find(({ projectId }) => (projectId === taskBinding.projectId))?.commit;
|
|
50
|
+
if (reviewedCommit === undefined) {
|
|
51
|
+
throw usageError(`Task-final Review ${latestReview.id} omitted Project ${taskBinding.projectId}.`);
|
|
52
|
+
}
|
|
53
|
+
if (actualCommit !== reviewedCommit) {
|
|
54
|
+
throw usageError(`Task-final Review ${latestReview.id} does not match Task head `
|
|
55
|
+
+ `${taskBinding.projectId}@${actualCommit}.`);
|
|
56
|
+
}
|
|
51
57
|
}
|
|
52
58
|
actualHeads.set(taskBinding.projectId, actualCommit);
|
|
53
59
|
}
|
|
54
|
-
if (
|
|
60
|
+
if (latestReview !== undefined
|
|
61
|
+
&& actualHeads.size !== latestReview.taskCandidate.projects.length) {
|
|
55
62
|
throw usageError(`Task-final Review ${latestReview.id} Project set does not match Task ${task.id}.`);
|
|
56
63
|
}
|
|
57
64
|
const entry = workspaceProjectEntry(workspace, publication.projectId);
|
|
@@ -94,7 +101,7 @@ export async function verifyTaskCompletionPublishedTree(taskId, publicationId, s
|
|
|
94
101
|
taskId: task.id,
|
|
95
102
|
projectId: publication.projectId,
|
|
96
103
|
publicationId: publication.id,
|
|
97
|
-
reviewRoundId: latestReview.id,
|
|
104
|
+
...(latestReview === undefined ? {} : { reviewRoundId: latestReview.id }),
|
|
98
105
|
localCommit,
|
|
99
106
|
remoteCommit,
|
|
100
107
|
tree: localTree
|
|
@@ -115,13 +122,18 @@ export function assertTaskCompletionPublishedTreeProof(store, task, publicationI
|
|
|
115
122
|
|| publication.remoteCommit !== proof.remoteCommit) {
|
|
116
123
|
throw usageError(`Publication evidence changed before Task completion: ${publication.id}.`);
|
|
117
124
|
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
+
if (taskDeliveryPath(task) === "integrated") {
|
|
126
|
+
const latestReview = latestTaskFinalReview(store, task.id);
|
|
127
|
+
if (latestReview === undefined
|
|
128
|
+
|| latestReview.id !== proof.reviewRoundId
|
|
129
|
+
|| !isSemanticReviewRound(latestReview, store)
|
|
130
|
+
|| latestReview.taskCandidate === undefined
|
|
131
|
+
|| !sameTaskCandidate(latestReview.taskCandidate, actualCandidate)) {
|
|
132
|
+
throw usageError(`Task-final Review evidence changed before published-tree completion: ${task.id}.`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
else if (proof.reviewRoundId !== undefined) {
|
|
136
|
+
throw usageError(`Direct published-tree proof unexpectedly binds a final Review: ${task.id}.`);
|
|
125
137
|
}
|
|
126
138
|
const actualCommit = actualCandidate.projects.find(({ projectId }) => (projectId === proof.projectId))?.commit;
|
|
127
139
|
if (actualCommit !== proof.localCommit) {
|
|
@@ -321,7 +333,7 @@ async function hasCompletedTaskFinalReviewForCurrentCandidate(store, task, works
|
|
|
321
333
|
.filter((round) => (round.scope ?? "work-item") === "task")
|
|
322
334
|
.sort((left, right) => left.id.localeCompare(right.id, undefined, { numeric: true }));
|
|
323
335
|
const latest = rounds.at(-1);
|
|
324
|
-
if (latest === undefined || latest
|
|
336
|
+
if (latest === undefined || !isSemanticReviewRound(latest, store))
|
|
325
337
|
return false;
|
|
326
338
|
if (latest.taskCandidate === undefined)
|
|
327
339
|
return false;
|
|
@@ -354,7 +366,7 @@ function latestCommittedIntegration(store, taskId, projectId) {
|
|
|
354
366
|
function hasFrozenTaskBaseline(store, taskId, projectId, currentCommit) {
|
|
355
367
|
return store.listReviewRounds(taskId)
|
|
356
368
|
.some((round) => ((round.scope ?? "work-item") === "task"
|
|
357
|
-
&& round
|
|
369
|
+
&& isSemanticReviewRound(round, store)
|
|
358
370
|
&& round.taskCandidate !== undefined
|
|
359
371
|
&& round.taskCandidate.projects.some((entry) => (entry.projectId === projectId && entry.commit === currentCommit))));
|
|
360
372
|
}
|
|
@@ -4,9 +4,11 @@ import { formatTimestamp } from "../output/timePresentation.js";
|
|
|
4
4
|
import { isRoleRunStalled, latestStallProgressAt } from "../scheduler/roleRunStall.js";
|
|
5
5
|
import { buildTaskExecutionProjection } from "../scheduler/taskExecutionProjection.js";
|
|
6
6
|
import { projectNextAction } from "../task/nextAction.js";
|
|
7
|
+
import { taskDeliveryPath } from "../task/task.js";
|
|
7
8
|
import { inspectTaskRoleSessionRecovery } from "./taskRoleRuntimeStatus.js";
|
|
8
9
|
import { summarizeExecutionGroup } from "../execution/executionGroup.js";
|
|
9
10
|
import { currentWorkItemExecutionGroup } from "../workItem/workItem.js";
|
|
11
|
+
import { operationalTaskRecords } from "../task/taskRecordRetirement.js";
|
|
10
12
|
const RECENT_RECORD_LIMIT = 5;
|
|
11
13
|
const RELATED_RECORD_LIMIT = 5;
|
|
12
14
|
const SUMMARY_TEXT_LIMIT = 400;
|
|
@@ -40,7 +42,8 @@ export function runTaskContextCommand(args, store) {
|
|
|
40
42
|
roleName: role.name
|
|
41
43
|
}))
|
|
42
44
|
].filter((mailbox) => mailbox !== null);
|
|
43
|
-
const
|
|
45
|
+
const events = reader.listEvents(task.id);
|
|
46
|
+
const agentRuns = chronological(operationalTaskRecords(reader.listAgentRuns(task.id), events, "agent-run"));
|
|
44
47
|
const reviewRounds = chronological(reader.listReviewRounds(task.id));
|
|
45
48
|
const changeSets = chronological(reader.listChangeSets(task.id));
|
|
46
49
|
const integrations = chronological(reader.listIntegrationAttempts(task.id));
|
|
@@ -51,6 +54,7 @@ export function runTaskContextCommand(args, store) {
|
|
|
51
54
|
}
|
|
52
55
|
return {
|
|
53
56
|
task,
|
|
57
|
+
deliveryPath: taskDeliveryPath(task),
|
|
54
58
|
execution,
|
|
55
59
|
reviewConfig: reader.getReviewConfig(),
|
|
56
60
|
brief: reader.getTaskBrief(task.id),
|
|
@@ -68,10 +72,10 @@ export function runTaskContextCommand(args, store) {
|
|
|
68
72
|
changeSets,
|
|
69
73
|
integrations,
|
|
70
74
|
publications,
|
|
71
|
-
messages: reader.listMessages(task.id),
|
|
75
|
+
messages: operationalTaskRecords(reader.listMessages(task.id), events, "message"),
|
|
72
76
|
openInputRequests: inputRequests.filter((request) => request.status === "open"),
|
|
73
77
|
resolvedInputRequests: inputRequests.filter((request) => request.status !== "open"),
|
|
74
|
-
events
|
|
78
|
+
events,
|
|
75
79
|
nextAction: projectNextAction(nextActionFacts)
|
|
76
80
|
};
|
|
77
81
|
});
|
|
@@ -137,9 +141,12 @@ export function runTaskContextCommand(args, store) {
|
|
|
137
141
|
...(managedWorkspaces.length === 0
|
|
138
142
|
? [" None."]
|
|
139
143
|
: managedWorkspaces.map((workspace) => (` ${managedWorkspaceLabel(workspace)}: ${workspace.root} (${workspace.entries.filter(({ access }) => access === "write").length} writable / ${workspace.entries.length} Projects)`))),
|
|
144
|
+
`Delivery: ${taskDeliveryPath(task)}`,
|
|
140
145
|
`Completion evidence: ${task.requireIntegration
|
|
141
146
|
? "WorkItem, ChangeSet, and committed Integration required"
|
|
142
|
-
:
|
|
147
|
+
: task.projectBindings.length > 0
|
|
148
|
+
? "clean committed Task main required"
|
|
149
|
+
: "no Project evidence required"}`,
|
|
143
150
|
`Global review: ${reviewConfig === null
|
|
144
151
|
? "disabled"
|
|
145
152
|
: `${reviewConfig.roleName} (${reviewConfig.trigger})`}`,
|
|
@@ -8,6 +8,8 @@ import { agentRunDeliveryReceiptId } from "../run/agentRun.js";
|
|
|
8
8
|
import { enqueueWork } from "../coordination/workMailboxQueue.js";
|
|
9
9
|
import { clearMatchingLeaderStallAttention, isRoleRunStalled, RUN_RECOVERED_EVENT } from "../scheduler/roleRunStall.js";
|
|
10
10
|
import { terminalizeExactTaskRun } from "../lifecycle/exactRunTerminalization.js";
|
|
11
|
+
import { hasRuntimeCleanupObligation, isRuntimeLaunchReservation, runtimeLifecycleTarget } from "../runtime/lifecycleReservation.js";
|
|
12
|
+
import { isLinuxProcessLive, listOwnedProcessTree } from "../runtime/sessionOwnerIdentity.js";
|
|
11
13
|
import { resolveTaskRecordReference } from "../task/taskRecordReference.js";
|
|
12
14
|
const LEADER_ROLE = "leader";
|
|
13
15
|
export function runTaskInputCommand(args, store, options) {
|
|
@@ -270,16 +272,33 @@ export function isCurrentGlobalOperator(store, environment) {
|
|
|
270
272
|
const role = store.getGlobalRole("operator");
|
|
271
273
|
if (role === null)
|
|
272
274
|
return false;
|
|
273
|
-
const sessions = store.getGlobalRoleSessionSet(role.name);
|
|
274
|
-
const session = activeLiveRoleAgentSession(sessions);
|
|
275
|
-
if (sessions === null || session === null || sessions.activeAgentId !== role.activeAgentId) {
|
|
276
|
-
return false;
|
|
277
|
-
}
|
|
278
275
|
const agentId = exactIdentity(environment.YUI_AGENT_ID);
|
|
279
276
|
const adapterId = exactIdentity(environment.YUI_ADAPTER_ID);
|
|
280
277
|
const launchId = exactIdentity(environment.YUI_LAUNCH_ID);
|
|
281
278
|
const nativeSessionId = exactIdentity(environment.YUI_NATIVE_SESSION_ID);
|
|
282
279
|
const binding = role.agentBindings[role.activeAgentId];
|
|
280
|
+
if (agentId === undefined
|
|
281
|
+
|| adapterId === undefined
|
|
282
|
+
|| launchId === undefined
|
|
283
|
+
|| binding === undefined
|
|
284
|
+
|| binding.agentId !== agentId
|
|
285
|
+
|| binding.adapterId !== adapterId)
|
|
286
|
+
return false;
|
|
287
|
+
const sessions = store.getGlobalRoleSessionSet(role.name);
|
|
288
|
+
const session = activeLiveRoleAgentSession(sessions);
|
|
289
|
+
if (sessions === null || session === null || sessions.activeAgentId !== role.activeAgentId) {
|
|
290
|
+
// Codex learns its native Session ID only after the first Turn. During
|
|
291
|
+
// that narrow bootstrap window, authenticate against the durable launch
|
|
292
|
+
// reservation and its strongly attributed live process owner instead.
|
|
293
|
+
return nativeSessionId === undefined
|
|
294
|
+
&& binding.adapterId === "codex"
|
|
295
|
+
&& currentProcessBelongsToReservedGlobalLaunch(store, {
|
|
296
|
+
roleName: role.name,
|
|
297
|
+
agentId,
|
|
298
|
+
adapterId,
|
|
299
|
+
launchId
|
|
300
|
+
});
|
|
301
|
+
}
|
|
283
302
|
// A fresh Codex launch discovers its native Session asynchronously. Its
|
|
284
303
|
// launch envelope therefore cannot carry YUI_NATIVE_SESSION_ID, but the
|
|
285
304
|
// durable Session still binds that provider identity to the exact launch
|
|
@@ -292,11 +311,7 @@ export function isCurrentGlobalOperator(store, environment) {
|
|
|
292
311
|
&& session.adapterId === "codex"
|
|
293
312
|
&& session.launchId !== undefined
|
|
294
313
|
&& session.launchId === launchId));
|
|
295
|
-
return agentId
|
|
296
|
-
&& adapterId !== undefined
|
|
297
|
-
&& launchId !== undefined
|
|
298
|
-
&& binding !== undefined
|
|
299
|
-
&& binding.agentId === session.agentId
|
|
314
|
+
return binding.agentId === session.agentId
|
|
300
315
|
&& binding.adapterId === session.adapterId
|
|
301
316
|
&& session.agentId === agentId
|
|
302
317
|
&& session.adapterId === adapterId
|
|
@@ -304,6 +319,29 @@ export function isCurrentGlobalOperator(store, environment) {
|
|
|
304
319
|
&& session.launchId === launchId
|
|
305
320
|
&& nativeSessionMatches;
|
|
306
321
|
}
|
|
322
|
+
function currentProcessBelongsToReservedGlobalLaunch(store, input) {
|
|
323
|
+
if (store.getSessionOwner === undefined || store.getWorkMailbox === undefined)
|
|
324
|
+
return false;
|
|
325
|
+
const mailbox = store.getWorkMailbox(runtimeLifecycleTarget({
|
|
326
|
+
scope: "global",
|
|
327
|
+
roleName: input.roleName
|
|
328
|
+
}));
|
|
329
|
+
if (!isRuntimeLaunchReservation(mailbox?.processing, input.launchId)
|
|
330
|
+
|| hasRuntimeCleanupObligation(mailbox))
|
|
331
|
+
return false;
|
|
332
|
+
const owner = store.getSessionOwner(input.launchId);
|
|
333
|
+
if (owner === null
|
|
334
|
+
|| owner.owner.scope !== "global"
|
|
335
|
+
|| owner.owner.roleName !== input.roleName
|
|
336
|
+
|| owner.agentId !== input.agentId
|
|
337
|
+
|| owner.adapterId !== input.adapterId
|
|
338
|
+
|| owner.launchId !== input.launchId
|
|
339
|
+
|| owner.providerRoot.attribution !== "launch-env"
|
|
340
|
+
|| !isLinuxProcessLive(owner.providerRoot.pid, owner.providerRoot.startIdentity)) {
|
|
341
|
+
return false;
|
|
342
|
+
}
|
|
343
|
+
return listOwnedProcessTree(owner.providerRoot.pid, owner.providerRoot.processGroupId).some(({ pid }) => pid === process.pid);
|
|
344
|
+
}
|
|
307
345
|
function inputAnswerer(environment) {
|
|
308
346
|
const env = environment ?? {};
|
|
309
347
|
if (env.YUI_SESSION_SCOPE === undefined && env.YUI_ROLE === undefined)
|
|
@@ -2,6 +2,9 @@ import { taskNotFound, usageError } from "../errors/cliError.js";
|
|
|
2
2
|
import { projectNextAction } from "../task/nextAction.js";
|
|
3
3
|
import { projectCompletionReadiness } from "../task/completionReadiness.js";
|
|
4
4
|
import { extractReviewFindings, planRepairWave } from "../task/repairWave.js";
|
|
5
|
+
import { taskDeliveryPath } from "../task/task.js";
|
|
6
|
+
import { projectTaskOrchestration } from "../observability/orchestrationMetrics.js";
|
|
7
|
+
import { operationalTaskRecords } from "../task/taskRecordRetirement.js";
|
|
5
8
|
/**
|
|
6
9
|
* Issue 07 (Leader convergence): read-only `yui task next-action <task>`.
|
|
7
10
|
* Folds the existing durable records into exactly one protocol-level next
|
|
@@ -55,7 +58,30 @@ export function runTaskNextActionCommand(args, store) {
|
|
|
55
58
|
throw taskNotFound(taskId);
|
|
56
59
|
completionReadiness = projectCompletionReadiness(readinessFacts);
|
|
57
60
|
}
|
|
58
|
-
|
|
61
|
+
const events = reader.listEvents(taskId);
|
|
62
|
+
const orchestration = projectTaskOrchestration({
|
|
63
|
+
task: reader.getTask(taskId),
|
|
64
|
+
runs: operationalTaskRecords(reader.listAgentRuns(taskId), events, "agent-run"),
|
|
65
|
+
roleSessionSets: reader.listRoleSessionSets(taskId),
|
|
66
|
+
workItems: reader.listWorkItems(taskId),
|
|
67
|
+
changeSets: reader.listChangeSets(taskId),
|
|
68
|
+
reviewRounds: reader.listReviewRounds(taskId),
|
|
69
|
+
reviewFindings: reader.listReviewFindings(taskId),
|
|
70
|
+
integrations: reader.listIntegrationAttempts(taskId),
|
|
71
|
+
durableJobs: reader.listDurableJobs(taskId),
|
|
72
|
+
publications: reader.listPublicationReferences(taskId),
|
|
73
|
+
decisions: reader.listDecisions(taskId),
|
|
74
|
+
events,
|
|
75
|
+
managedWorkspaces: reader.listManagedWorkspaces(taskId)
|
|
76
|
+
});
|
|
77
|
+
return {
|
|
78
|
+
deliveryPath: taskDeliveryPath(facts.task),
|
|
79
|
+
action,
|
|
80
|
+
repairWave,
|
|
81
|
+
completionReadiness,
|
|
82
|
+
knowledgeProposals,
|
|
83
|
+
orchestration
|
|
84
|
+
};
|
|
59
85
|
});
|
|
60
86
|
if (asJson) {
|
|
61
87
|
return {
|
|
@@ -66,7 +92,7 @@ export function runTaskNextActionCommand(args, store) {
|
|
|
66
92
|
}
|
|
67
93
|
return {
|
|
68
94
|
kind: "output",
|
|
69
|
-
output: renderNextAction(data.action, data.repairWave, data.completionReadiness, data.knowledgeProposals),
|
|
95
|
+
output: renderNextAction(data.action, data.deliveryPath, data.repairWave, data.completionReadiness, data.knowledgeProposals, data.orchestration.advisories),
|
|
70
96
|
data
|
|
71
97
|
};
|
|
72
98
|
}
|
|
@@ -84,9 +110,10 @@ function repairWaveFor(action, facts) {
|
|
|
84
110
|
return null;
|
|
85
111
|
return planRepairWave(round.id, findings);
|
|
86
112
|
}
|
|
87
|
-
function renderNextAction(action, repairWave, completionReadiness, knowledgeProposals) {
|
|
113
|
+
function renderNextAction(action, deliveryPath, repairWave, completionReadiness, knowledgeProposals, orchestrationAdvisories) {
|
|
88
114
|
const lines = [
|
|
89
115
|
`Task: ${action.taskId}`,
|
|
116
|
+
`Delivery: ${deliveryPath}`,
|
|
90
117
|
`Next action: ${action.kind}`,
|
|
91
118
|
`Reason: ${action.reason}`,
|
|
92
119
|
...(action.refs.length === 0
|
|
@@ -131,6 +158,10 @@ function renderNextAction(action, repairWave, completionReadiness, knowledgeProp
|
|
|
131
158
|
lines.push(`Completion readiness: ${completionReadiness.blockers.length} blocker(s)`, ...completionReadiness.blockers.map((blocker) => ` ${blocker.code} (${blocker.ref.kind} ${blocker.ref.id}): ${blocker.reason}`
|
|
132
159
|
+ ` — fix: ${blocker.fix}`));
|
|
133
160
|
}
|
|
161
|
+
if (completionReadiness.advisories.length > 0) {
|
|
162
|
+
lines.push(`Completion advisories (non-blocking): ${completionReadiness.advisories.length}`, ...completionReadiness.advisories.map((advisory) => ` ${advisory.code} (${advisory.ref.kind} ${advisory.ref.id}): ${advisory.reason}`
|
|
163
|
+
+ ` — fix before archive: ${advisory.fix}`));
|
|
164
|
+
}
|
|
134
165
|
}
|
|
135
166
|
if (repairWave !== null) {
|
|
136
167
|
lines.push(`Repair wave (${repairWave.openFindingCount} open finding(s), ${repairWave.groups.length} group(s)):`, ...repairWave.groups.map((group) => ` ${group.id}: findings ${group.findingIds.join(", ")}`
|
|
@@ -141,5 +172,9 @@ function renderNextAction(action, repairWave, completionReadiness, knowledgeProp
|
|
|
141
172
|
lines.push(`Knowledge proposals (non-blocking): ${knowledgeProposals.length} pending`, ...knowledgeProposals.map((proposal) => ` ${proposal.projectId}/${proposal.proposalId}: ${proposal.title}`
|
|
142
173
|
+ ` — review: yui project knowledge proposals list ${proposal.projectId}`));
|
|
143
174
|
}
|
|
175
|
+
if (orchestrationAdvisories.length > 0) {
|
|
176
|
+
lines.push(`Orchestration advisories (non-blocking): ${orchestrationAdvisories.length}`, ...orchestrationAdvisories.map((advisory) => ` ${advisory.code}: ${advisory.reason}`
|
|
177
|
+
+ (advisory.refs.length === 0 ? "" : ` — refs ${advisory.refs.join(", ")}`)));
|
|
178
|
+
}
|
|
144
179
|
return `${lines.join("\n")}\n`;
|
|
145
180
|
}
|
|
@@ -226,7 +226,8 @@ function collectBlockers(workItems, openInputRequests, attention) {
|
|
|
226
226
|
}
|
|
227
227
|
if (item.status !== "pending")
|
|
228
228
|
continue;
|
|
229
|
-
const dependencies = item.dependsOn.filter((dependency) => (workById.get(dependency)?.status !== "completed"
|
|
229
|
+
const dependencies = item.dependsOn.filter((dependency) => (workById.get(dependency)?.status !== "completed"
|
|
230
|
+
&& workById.get(dependency)?.status !== "retired"));
|
|
230
231
|
if (dependencies.length === 0)
|
|
231
232
|
continue;
|
|
232
233
|
blockers.push({
|
|
@@ -7,6 +7,7 @@ import { classifyRuntimeHealth, projectRuntimeMailbox, projectRuntimeObservation
|
|
|
7
7
|
import { latestRunDurableProgressAt } from "../scheduler/roleRunStall.js";
|
|
8
8
|
import { resolveRuntimeHealth } from "../config/yuiConfig.js";
|
|
9
9
|
import { builtinDriverIdForAdapter } from "../runtime/builtinAgentDrivers.js";
|
|
10
|
+
import { operationalTaskRecords } from "../task/taskRecordRetirement.js";
|
|
10
11
|
export function inspectTaskRoleRuntimeStatuses(taskId, roles, store, panes, now = new Date()) {
|
|
11
12
|
const taskOpenInputRequestCount = store.listInputRequests(taskId)
|
|
12
13
|
.filter((request) => request.status === "open").length;
|
|
@@ -140,7 +141,7 @@ function inspectTaskRoleRuntimeStatus(taskId, role, store, pane, openInputReques
|
|
|
140
141
|
// Issue 09: the last Run outcome is a separate axis from the Session
|
|
141
142
|
// lifecycle. A Session that stops after its Run yielded must not retroactively
|
|
142
143
|
// turn that Run into a failure; the status display keeps both visible.
|
|
143
|
-
const lastRun = store.listAgentRuns(taskId)
|
|
144
|
+
const lastRun = operationalTaskRecords(store.listAgentRuns(taskId), store.listEvents(taskId), "agent-run")
|
|
144
145
|
.filter((candidate) => candidate.roleName === role.name)
|
|
145
146
|
.sort((left, right) => Date.parse(right.updatedAt) - Date.parse(left.updatedAt))[0]
|
|
146
147
|
?? null;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { operationalTaskRecords } from "../task/taskRecordRetirement.js";
|
|
1
2
|
import { TASK_COMPLETION_PUBLISHED_TREE_AUTHORIZED_EVENT } from "../task/publicationReference.js";
|
|
2
3
|
import { RUN_BOOTSTRAP_MAX_DELTAS } from "./runContextContract.js";
|
|
3
4
|
import { contextContentDigest, contextSnapshotRef, createContextSnapshot, validateContextSnapshot } from "./contextSnapshot.js";
|
|
@@ -213,9 +214,12 @@ function collectAuthorizedContext(store, run) {
|
|
|
213
214
|
if (view === "worker") {
|
|
214
215
|
for (const dependencyId of item.dependsOn) {
|
|
215
216
|
const dependency = store.getWorkItem(task.id, dependencyId);
|
|
216
|
-
if (dependency === null
|
|
217
|
+
if (dependency === null
|
|
218
|
+
|| (dependency.status !== "completed" && dependency.status !== "retired")) {
|
|
217
219
|
throw new Error(`Run WorkItem dependency is not accepted: ${dependencyId}.`);
|
|
218
220
|
}
|
|
221
|
+
if (dependency.status === "retired")
|
|
222
|
+
continue;
|
|
219
223
|
result.push(materialize("L3", "accepted-work-item", dependency.id, dependency));
|
|
220
224
|
}
|
|
221
225
|
}
|
|
@@ -242,7 +246,8 @@ function collectAuthorizedContext(store, run) {
|
|
|
242
246
|
}
|
|
243
247
|
}
|
|
244
248
|
if (view === "leader") {
|
|
245
|
-
|
|
249
|
+
const events = store.listEvents(task.id);
|
|
250
|
+
for (const item of store.listWorkItems(task.id).filter(({ status }) => status !== "retired")) {
|
|
246
251
|
result.push(materialize("L3", "work-item", item.id, item));
|
|
247
252
|
}
|
|
248
253
|
for (const decision of store.listDecisions(task.id)) {
|
|
@@ -257,14 +262,13 @@ function collectAuthorizedContext(store, run) {
|
|
|
257
262
|
for (const finding of store.listReviewFindings(task.id)) {
|
|
258
263
|
result.push(materialize("L3", "review-finding", finding.id, finding));
|
|
259
264
|
}
|
|
260
|
-
for (const agentRun of store.listAgentRuns(task.id).slice(-24)) {
|
|
265
|
+
for (const agentRun of operationalTaskRecords(store.listAgentRuns(task.id), events, "agent-run").slice(-24)) {
|
|
261
266
|
result.push(materialize("L4", "agent-run", agentRun.id, agentRun));
|
|
262
267
|
}
|
|
263
|
-
for (const message of store.listMessages(task.id).slice(-16)) {
|
|
268
|
+
for (const message of operationalTaskRecords(store.listMessages(task.id), events, "message").slice(-16)) {
|
|
264
269
|
result.push(materialize("L4", "task-message", message.id, message));
|
|
265
270
|
}
|
|
266
271
|
const publishedTreeAuthorizations = [];
|
|
267
|
-
const events = store.listEvents(task.id);
|
|
268
272
|
for (let index = events.length - 1; index >= 0; index -= 1) {
|
|
269
273
|
const event = events[index];
|
|
270
274
|
if (event.type === "task.completed" || event.type === "task.reopened")
|
|
@@ -1,20 +1,93 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
-
import { chmodSync } from "node:fs";
|
|
3
|
-
import { join, resolve } from "node:path";
|
|
4
|
-
import {
|
|
2
|
+
import { chmodSync, existsSync, readFileSync, readdirSync } from "node:fs";
|
|
3
|
+
import { dirname, join, resolve } from "node:path";
|
|
4
|
+
import { exactControlPlaneDigest, serializeExactDescriptor } from "../runtime/exactControlPlane.js";
|
|
5
5
|
import { writeTextFileAtomically } from "../storage/durableFile.js";
|
|
6
6
|
import { SESSION_BOOTSTRAP_MANIFEST_SCHEMA_VERSION, SESSION_CONTEXT_PROTOCOL, sessionManifestCompatibilityDigest } from "./sessionProtocolIdentity.js";
|
|
7
7
|
export { SESSION_BOOTSTRAP_MANIFEST_SCHEMA_VERSION, SESSION_CONTEXT_PROTOCOL, sessionManifestCompatibilityDigest } from "./sessionProtocolIdentity.js";
|
|
8
|
+
const ORDINARY_SESSION_CLI = [
|
|
9
|
+
"#!/bin/sh",
|
|
10
|
+
"exec yui \"$@\"",
|
|
11
|
+
""
|
|
12
|
+
].join("\n");
|
|
13
|
+
/** Read back one immutable Session Manifest and verify its content digest. */
|
|
14
|
+
export function readSessionBootstrapManifest(path) {
|
|
15
|
+
const source = resolve(path);
|
|
16
|
+
let parsed;
|
|
17
|
+
try {
|
|
18
|
+
parsed = JSON.parse(readFileSync(source, "utf8"));
|
|
19
|
+
}
|
|
20
|
+
catch (error) {
|
|
21
|
+
throw new Error(`Session Manifest is unreadable: ${source}.`, { cause: error });
|
|
22
|
+
}
|
|
23
|
+
if (parsed === null || typeof parsed !== "object") {
|
|
24
|
+
throw new Error("Session Manifest is invalid.");
|
|
25
|
+
}
|
|
26
|
+
const record = parsed;
|
|
27
|
+
const claimedDigest = requireDigest(record.digest, "Session Manifest digest");
|
|
28
|
+
const { digest: _digest, ...body } = record;
|
|
29
|
+
if (digest(body) !== claimedDigest) {
|
|
30
|
+
throw new Error("Session Manifest digest does not match its immutable content.");
|
|
31
|
+
}
|
|
32
|
+
if (record.schemaVersion !== SESSION_BOOTSTRAP_MANIFEST_SCHEMA_VERSION
|
|
33
|
+
|| record.protocol !== SESSION_CONTEXT_PROTOCOL
|
|
34
|
+
|| record.owner === null
|
|
35
|
+
|| typeof record.owner !== "object"
|
|
36
|
+
|| (record.owner.scope !== "global"
|
|
37
|
+
&& record.owner.scope !== "task")
|
|
38
|
+
|| typeof record.effectiveRevision !== "number"
|
|
39
|
+
|| !Number.isSafeInteger(record.effectiveRevision)
|
|
40
|
+
|| record.effectiveRevision < 1
|
|
41
|
+
|| (record.roleKind !== "operator"
|
|
42
|
+
&& record.roleKind !== "global"
|
|
43
|
+
&& record.roleKind !== "leader"
|
|
44
|
+
&& record.roleKind !== "worker"
|
|
45
|
+
&& record.roleKind !== "reviewer")
|
|
46
|
+
|| record.controlPlane === null
|
|
47
|
+
|| typeof record.controlPlane !== "object"
|
|
48
|
+
|| !Array.isArray(record.skills)
|
|
49
|
+
|| record.roleProfileRef === null
|
|
50
|
+
|| typeof record.roleProfileRef !== "object"
|
|
51
|
+
|| record.contextProtocol === null
|
|
52
|
+
|| typeof record.contextProtocol !== "object") {
|
|
53
|
+
throw new Error("Session Manifest shape is invalid.");
|
|
54
|
+
}
|
|
55
|
+
const owner = record.owner;
|
|
56
|
+
if (owner.scope === "task" && typeof owner.taskId !== "string") {
|
|
57
|
+
throw new Error("Task Session Manifest owner is invalid.");
|
|
58
|
+
}
|
|
59
|
+
const control = record.controlPlane;
|
|
60
|
+
requireText(control.descriptorPath, "Session Manifest control descriptor path");
|
|
61
|
+
requireText(control.sessionCliPath, "Session Manifest CLI path");
|
|
62
|
+
requireDigest(control.digest, "Session Manifest control-plane digest");
|
|
63
|
+
for (const skill of record.skills) {
|
|
64
|
+
if (skill === null || typeof skill !== "object") {
|
|
65
|
+
throw new Error("Session Manifest Skill entry is invalid.");
|
|
66
|
+
}
|
|
67
|
+
const entry = skill;
|
|
68
|
+
requireText(entry.id, "Session Manifest Skill id");
|
|
69
|
+
requireText(entry.path, "Session Manifest Skill path");
|
|
70
|
+
requireDigest(entry.digest, "Session Manifest Skill digest");
|
|
71
|
+
}
|
|
72
|
+
const profile = record.roleProfileRef;
|
|
73
|
+
requireDigest(profile.digest, "Session Manifest Role Profile digest");
|
|
74
|
+
requireText(profile.path, "Session Manifest Role Profile path");
|
|
75
|
+
const protocol = record.contextProtocol;
|
|
76
|
+
requireText(protocol.loadCommand, "Session Manifest Context load command");
|
|
77
|
+
if (protocol.expandCommand !== undefined) {
|
|
78
|
+
requireText(protocol.expandCommand, "Session Manifest Context expand command");
|
|
79
|
+
}
|
|
80
|
+
return Object.freeze(parsed);
|
|
81
|
+
}
|
|
8
82
|
export function materializeSessionBootstrap(input) {
|
|
9
83
|
const home = resolve(input.yuiHome);
|
|
10
84
|
const controlDigest = exactControlPlaneDigest(input.controlPlane);
|
|
11
85
|
const descriptorPath = resolve(join(home, "runtime", "control-plane", `${controlDigest}.json`));
|
|
12
86
|
writeImmutableText(descriptorPath, `${serializeExactDescriptor(input.controlPlane)}\n`);
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
].join("\n");
|
|
87
|
+
// Session identity is carried by the immutable Manifest and durable Role/
|
|
88
|
+
// Run fences. Resolve the ordinary CLI on every invocation so package or
|
|
89
|
+
// release upgrades do not invalidate a still-current native Session.
|
|
90
|
+
const sessionCliContent = ORDINARY_SESSION_CLI;
|
|
18
91
|
const sessionCliDigest = digest(sessionCliContent);
|
|
19
92
|
const sessionCliPath = resolve(join(home, "runtime", "session-cli", `yui-${sessionCliDigest}.sh`));
|
|
20
93
|
writeImmutableText(sessionCliPath, sessionCliContent);
|
|
@@ -53,11 +126,11 @@ export function materializeSessionBootstrap(input) {
|
|
|
53
126
|
roleProfileRef: { digest: profileDigest, path: roleProfilePath },
|
|
54
127
|
contextProtocol: input.owner.scope === "global"
|
|
55
128
|
? {
|
|
56
|
-
loadCommand:
|
|
129
|
+
loadCommand: "yui session context \"$YUI_ROLE\" --json"
|
|
57
130
|
}
|
|
58
131
|
: {
|
|
59
|
-
loadCommand:
|
|
60
|
-
expandCommand:
|
|
132
|
+
loadCommand: "yui task run context \"$YUI_TASK_ID/<run-id>\" --json",
|
|
133
|
+
expandCommand: "yui task run context expand \"$YUI_TASK_ID/<run-id>\" <ref-id> --store <store> --mode full --json"
|
|
61
134
|
}
|
|
62
135
|
};
|
|
63
136
|
const manifest = Object.freeze({ ...body, digest: digest(body) });
|
|
@@ -71,6 +144,68 @@ export function materializeSessionBootstrap(input) {
|
|
|
71
144
|
descriptorPath
|
|
72
145
|
});
|
|
73
146
|
}
|
|
147
|
+
/**
|
|
148
|
+
* Converts wrappers produced before the protocol-compatible Session CLI to an
|
|
149
|
+
* ordinary `yui` invocation. Only a valid Session Manifest may nominate a
|
|
150
|
+
* wrapper, and only the exact legacy two-line wrapper shape is changed. The
|
|
151
|
+
* Manifest and its frozen descriptor stay immutable and continue to
|
|
152
|
+
* authenticate the Session; repeated refreshes are no-ops.
|
|
153
|
+
*/
|
|
154
|
+
export function refreshManagedSessionCliWrappers(homeInput) {
|
|
155
|
+
const home = resolve(homeInput);
|
|
156
|
+
const manifestDirectory = resolve(join(home, "runtime", "session-manifests"));
|
|
157
|
+
const sessionCliDirectory = resolve(join(home, "runtime", "session-cli"));
|
|
158
|
+
if (!existsSync(manifestDirectory)) {
|
|
159
|
+
return Object.freeze({ refreshed: 0, current: 0, skipped: 0 });
|
|
160
|
+
}
|
|
161
|
+
const wrapperPaths = new Set();
|
|
162
|
+
let skipped = 0;
|
|
163
|
+
for (const name of readdirSync(manifestDirectory).filter((entry) => entry.endsWith(".json"))) {
|
|
164
|
+
const manifestPath = resolve(join(manifestDirectory, name));
|
|
165
|
+
try {
|
|
166
|
+
const manifest = readSessionBootstrapManifest(manifestPath);
|
|
167
|
+
if (manifestPath !== resolve(join(manifestDirectory, `${manifest.digest}.json`))) {
|
|
168
|
+
skipped += 1;
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
const wrapperPath = resolve(manifest.controlPlane.sessionCliPath);
|
|
172
|
+
if (dirname(wrapperPath) !== sessionCliDirectory) {
|
|
173
|
+
skipped += 1;
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
wrapperPaths.add(wrapperPath);
|
|
177
|
+
}
|
|
178
|
+
catch {
|
|
179
|
+
// Historical or incomplete manifests are audit material. They must not
|
|
180
|
+
// block current Sessions or an otherwise compatible package update.
|
|
181
|
+
skipped += 1;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
let refreshed = 0;
|
|
185
|
+
let current = 0;
|
|
186
|
+
for (const wrapperPath of wrapperPaths) {
|
|
187
|
+
if (!existsSync(wrapperPath)) {
|
|
188
|
+
skipped += 1;
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
const content = readFileSync(wrapperPath, "utf8");
|
|
192
|
+
if (content === ORDINARY_SESSION_CLI) {
|
|
193
|
+
current += 1;
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
if (!isLegacyExactSessionCli(content)) {
|
|
197
|
+
skipped += 1;
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
writeTextFileAtomically(wrapperPath, ORDINARY_SESSION_CLI);
|
|
201
|
+
chmodSync(wrapperPath, 0o700);
|
|
202
|
+
refreshed += 1;
|
|
203
|
+
}
|
|
204
|
+
return Object.freeze({ refreshed, current, skipped });
|
|
205
|
+
}
|
|
206
|
+
function isLegacyExactSessionCli(content) {
|
|
207
|
+
return /^#!\/bin\/sh\nexec [^\n]+ '--yui-control' '[a-f0-9]{64}' "\$@"\n$/u.test(content);
|
|
208
|
+
}
|
|
74
209
|
function writeImmutableText(path, content) {
|
|
75
210
|
writeTextFileAtomically(path, content);
|
|
76
211
|
chmodSync(path, 0o600);
|
|
@@ -79,3 +214,15 @@ function digest(value) {
|
|
|
79
214
|
const bytes = typeof value === "string" ? value : JSON.stringify(value);
|
|
80
215
|
return createHash("sha256").update(bytes).digest("hex");
|
|
81
216
|
}
|
|
217
|
+
function requireDigest(value, label) {
|
|
218
|
+
if (typeof value !== "string" || !/^[a-f0-9]{64}$/u.test(value)) {
|
|
219
|
+
throw new Error(`${label} is invalid.`);
|
|
220
|
+
}
|
|
221
|
+
return value;
|
|
222
|
+
}
|
|
223
|
+
function requireText(value, label) {
|
|
224
|
+
if (typeof value !== "string" || value.trim().length === 0 || value.includes("\0")) {
|
|
225
|
+
throw new Error(`${label} is invalid.`);
|
|
226
|
+
}
|
|
227
|
+
return value.trim();
|
|
228
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { renderWakeReason } from "../scheduler/wakeReason.js";
|
|
2
|
+
import { operationalTaskRecords } from "../task/taskRecordRetirement.js";
|
|
2
3
|
/**
|
|
3
4
|
* Issue 04 (context token budget) — long-term design:
|
|
4
5
|
*
|
|
@@ -25,12 +26,13 @@ export function buildTaskWakeEnvelope(reader, request) {
|
|
|
25
26
|
throw new Error(`Wake envelope ${request.wakeId} must carry at least one reason.`);
|
|
26
27
|
}
|
|
27
28
|
const fromTime = Date.parse(request.fromCursor);
|
|
29
|
+
const events = reader.listEvents(request.taskId);
|
|
28
30
|
const counts = {
|
|
29
|
-
events:
|
|
31
|
+
events: events
|
|
30
32
|
.filter((record) => Date.parse(record.createdAt) > fromTime).length,
|
|
31
|
-
messages: reader.listMessages(request.taskId)
|
|
33
|
+
messages: operationalTaskRecords(reader.listMessages(request.taskId), events, "message")
|
|
32
34
|
.filter((record) => Date.parse(record.createdAt) > fromTime).length,
|
|
33
|
-
runs: reader.listAgentRuns(request.taskId)
|
|
35
|
+
runs: operationalTaskRecords(reader.listAgentRuns(request.taskId), events, "agent-run")
|
|
34
36
|
.filter((record) => Date.parse(record.createdAt) > fromTime).length
|
|
35
37
|
};
|
|
36
38
|
const lines = [
|