@xfey/tutti 0.1.20 → 0.1.22
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/dist/collaboration-state/index.d.ts +1 -1
- package/dist/collaboration-state/index.js +1 -1
- package/dist/collaboration-state/messages.js +50 -1
- package/dist/collaboration-state/scratchpad.d.ts +2 -0
- package/dist/collaboration-state/scratchpad.js +63 -0
- package/dist/collaboration-state/storage-types.d.ts +5 -0
- package/dist/collaboration-state/types.d.ts +8 -1
- package/dist/control-plane/event-publishers.js +2 -2
- package/dist/control-plane/invalidations.d.ts +2 -1
- package/dist/control-plane/invalidations.js +25 -0
- package/dist/control-plane/scratchpad-refresh-start.d.ts +2 -0
- package/dist/control-plane/scratchpad-refresh-start.js +10 -0
- package/dist/control-plane/scratchpad-refresh.d.ts +2 -0
- package/dist/control-plane/scratchpad-refresh.js +1 -0
- package/dist/project-timeline/index.d.ts +17 -3
- package/dist/server-shell/http/routes/project-api/openapi-collaboration-routes.d.ts +1 -0
- package/dist/server-shell/http/routes/project-api/openapi-timeline-routes.d.ts +94 -5
- package/dist/server-shell/http/routes/project-api/openapi.d.ts +95 -5
- package/dist/server-shell/http/routes/project-api/payload-validation.d.ts +6 -2
- package/dist/server-shell/http/routes/project-api/payload-validation.js +3 -0
- package/dist/server-shell/http/routes/project-api/project-timeline-projection.js +694 -502
- package/migrations/0013_scratchpad_receipt_sources.sql +24 -0
- package/migrations/README.md +3 -2
- package/node_modules/@tutti/shared/dist/schemas/api/index.d.ts +1 -1
- package/node_modules/@tutti/shared/dist/schemas/api/index.js +1 -1
- package/node_modules/@tutti/shared/dist/schemas/api/messages.d.ts +1 -0
- package/node_modules/@tutti/shared/dist/schemas/api/messages.js +1 -0
- package/node_modules/@tutti/shared/dist/schemas/api/project-timeline.d.ts +313 -12
- package/node_modules/@tutti/shared/dist/schemas/api/project-timeline.js +131 -14
- package/node_modules/@tutti/shared/dist/schemas/api/types.d.ts +5 -1
- package/package.json +1 -1
- package/prompts/procedures/README.md +1 -1
- package/prompts/procedures/task-compile.md +9 -2
- package/prompts/runs/README.md +2 -2
- package/prompts/runs/task-continuation.md +11 -4
- package/prompts/runs/task-retry.md +11 -4
- package/prompts/runs/task-run.md +11 -4
- package/web/assets/index-BfFYfzeO.js +29 -0
- package/web/assets/index-De6mK5Q2.css +1 -0
- package/web/index.html +2 -2
- package/web/assets/index-BtneECwp.js +0 -29
- package/web/assets/index-CM9mbbeW.css +0 -1
|
@@ -1,22 +1,24 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { readProjectTimelineEventItems } from "../../../../project-timeline/index.js";
|
|
4
|
-
import { HOST_MAINLINE_BRANCH } from "./constants.js";
|
|
1
|
+
import { readProviderUsageBreakdownForAnchors, readProviderUsageModelSummariesForAnchors, readProviderUsageProjection, readProviderUsageSourceSummariesForAnchors, } from "../../../../provider-usage/index.js";
|
|
2
|
+
import { runGitRaw } from "../../../../workspace-ops/git.js";
|
|
5
3
|
const TIMELINE_LIMIT = 50;
|
|
6
|
-
const
|
|
4
|
+
const TASK_ACCEPTED_TIMELINE_LIMIT = 50;
|
|
7
5
|
const RUN_RESULT_TIMELINE_LIMIT = 50;
|
|
8
|
-
const
|
|
9
|
-
const CLARIFICATION_TIMELINE_LIMIT = 50;
|
|
10
|
-
const RECOVERY_TIMELINE_LIMIT = 30;
|
|
11
|
-
const DURABLE_EVENT_TIMELINE_LIMIT = 50;
|
|
6
|
+
const CHECKPOINT_TIMELINE_LIMIT = 30;
|
|
12
7
|
const RUN_USAGE_SOURCES = [
|
|
13
8
|
"workspace_write_run",
|
|
14
9
|
"follow_up_run",
|
|
15
10
|
"pipeline_self_correction",
|
|
16
11
|
];
|
|
17
|
-
const
|
|
18
|
-
const
|
|
19
|
-
const
|
|
12
|
+
const TASK_ACCEPTED_USAGE_SOURCES = ["task_compile"];
|
|
13
|
+
const TRACE_CONSENSUS_LIMIT = 3;
|
|
14
|
+
const TRACE_TASK_CHANGE_LIMIT = 2;
|
|
15
|
+
const TRACE_CONTRACT_SCOPE_LIMIT = 5;
|
|
16
|
+
const TRACE_CHANGED_PATH_LIMIT = 6;
|
|
17
|
+
const TRACE_CHECKPOINT_ITEM_LIMIT = 5;
|
|
18
|
+
const TRACE_GENERATED_FILE_LIMIT = 5;
|
|
19
|
+
const TRACE_SOURCE_SNIPPET_LIMIT = 3;
|
|
20
|
+
const TRACE_SOURCE_SNIPPET_BODY_LIMIT = 280;
|
|
21
|
+
const TRACE_CHANGE_LINK_LIMIT = 3;
|
|
20
22
|
function compareTimelineItemsByTimeDesc(left, right) {
|
|
21
23
|
const leftTime = Date.parse(left.occurred_at);
|
|
22
24
|
const rightTime = Date.parse(right.occurred_at);
|
|
@@ -25,62 +27,26 @@ function compareTimelineItemsByTimeDesc(left, right) {
|
|
|
25
27
|
: right.occurred_at.localeCompare(left.occurred_at);
|
|
26
28
|
return byTime === 0 ? right.id.localeCompare(left.id) : byTime;
|
|
27
29
|
}
|
|
28
|
-
function
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
}
|
|
49
|
-
if (anchors.workflow_ref !== undefined) {
|
|
50
|
-
consumed.push({ source, workflow_ref: anchors.workflow_ref });
|
|
51
|
-
}
|
|
52
|
-
if (anchors.task_id !== undefined) {
|
|
53
|
-
consumed.push({ source, task_id: anchors.task_id });
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
function isProcedureUsageConsumed(row, consumed) {
|
|
58
|
-
return consumed.some((anchor) => {
|
|
59
|
-
if (anchor.source !== row.source) {
|
|
60
|
-
return false;
|
|
61
|
-
}
|
|
62
|
-
if (anchor.activity_ref !== undefined && anchor.activity_ref !== row.activity_ref) {
|
|
63
|
-
return false;
|
|
64
|
-
}
|
|
65
|
-
if (anchor.workflow_ref !== undefined && anchor.workflow_ref !== row.workflow_ref) {
|
|
66
|
-
return false;
|
|
67
|
-
}
|
|
68
|
-
if (anchor.task_id !== undefined && anchor.task_id !== row.task_id) {
|
|
69
|
-
return false;
|
|
70
|
-
}
|
|
71
|
-
return true;
|
|
72
|
-
});
|
|
73
|
-
}
|
|
74
|
-
function runGit(args, cwd) {
|
|
75
|
-
const result = spawnSync("git", [...args], {
|
|
76
|
-
cwd,
|
|
77
|
-
encoding: "utf8",
|
|
78
|
-
shell: false,
|
|
79
|
-
});
|
|
80
|
-
if (result.status !== 0) {
|
|
81
|
-
return null;
|
|
82
|
-
}
|
|
83
|
-
return (result.stdout ?? "").trim();
|
|
30
|
+
function emptyUsageOverview() {
|
|
31
|
+
const emptyBreakdown = {
|
|
32
|
+
input_tokens: 0,
|
|
33
|
+
cached_input_tokens: 0,
|
|
34
|
+
output_tokens: 0,
|
|
35
|
+
reasoning_output_tokens: 0,
|
|
36
|
+
total_tokens: 0,
|
|
37
|
+
event_count: 0,
|
|
38
|
+
};
|
|
39
|
+
return {
|
|
40
|
+
total_tokens: 0,
|
|
41
|
+
totals: { ...emptyBreakdown },
|
|
42
|
+
by_source: [],
|
|
43
|
+
by_model: [],
|
|
44
|
+
recent_windows: {
|
|
45
|
+
today: { ...emptyBreakdown },
|
|
46
|
+
last_7_days: { ...emptyBreakdown },
|
|
47
|
+
last_30_days: { ...emptyBreakdown },
|
|
48
|
+
},
|
|
49
|
+
};
|
|
84
50
|
}
|
|
85
51
|
function parseJsonObject(value) {
|
|
86
52
|
try {
|
|
@@ -95,6 +61,9 @@ function parseJsonObject(value) {
|
|
|
95
61
|
return {};
|
|
96
62
|
}
|
|
97
63
|
function parseJsonStringArray(value) {
|
|
64
|
+
if (value === null) {
|
|
65
|
+
return [];
|
|
66
|
+
}
|
|
98
67
|
try {
|
|
99
68
|
const parsed = JSON.parse(value);
|
|
100
69
|
if (!Array.isArray(parsed)) {
|
|
@@ -106,89 +75,324 @@ function parseJsonStringArray(value) {
|
|
|
106
75
|
return [];
|
|
107
76
|
}
|
|
108
77
|
}
|
|
109
|
-
function
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
}
|
|
113
|
-
const output = runGit([
|
|
114
|
-
"log",
|
|
115
|
-
HOST_MAINLINE_BRANCH,
|
|
116
|
-
`--max-count=${COMMIT_TIMELINE_LIMIT}`,
|
|
117
|
-
"--date=iso-strict",
|
|
118
|
-
"--pretty=format:%H%x00%cI%x00%s",
|
|
119
|
-
], workspaceRoot);
|
|
120
|
-
if (output === null || output.length === 0) {
|
|
121
|
-
return [];
|
|
122
|
-
}
|
|
123
|
-
return output
|
|
124
|
-
.split("\n")
|
|
125
|
-
.map((line) => {
|
|
126
|
-
const [oid, committedAt, subject] = line.split("\0");
|
|
127
|
-
if (oid === undefined || committedAt === undefined || subject === undefined) {
|
|
128
|
-
return null;
|
|
129
|
-
}
|
|
130
|
-
return {
|
|
131
|
-
id: `commit:${oid}`,
|
|
132
|
-
kind: "commit",
|
|
133
|
-
occurred_at: committedAt,
|
|
134
|
-
title: subject.trim() === "" ? "Mainline commit" : subject,
|
|
135
|
-
commit_oid: oid,
|
|
136
|
-
commit_short_oid: oid.slice(0, 12),
|
|
137
|
-
status: "completed",
|
|
138
|
-
};
|
|
139
|
-
})
|
|
140
|
-
.filter((item) => item !== null);
|
|
78
|
+
function stringValue(record, key) {
|
|
79
|
+
const value = record[key];
|
|
80
|
+
return typeof value === "string" && value.trim() !== "" ? value : undefined;
|
|
141
81
|
}
|
|
142
|
-
function
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
if (row.result_kind === "failed" || result.outcome === "failed") {
|
|
147
|
-
return "failed";
|
|
82
|
+
function stringArrayValue(record, key) {
|
|
83
|
+
const value = record[key];
|
|
84
|
+
if (!Array.isArray(value)) {
|
|
85
|
+
return undefined;
|
|
148
86
|
}
|
|
149
|
-
return "
|
|
87
|
+
return value.filter((item) => typeof item === "string" && item.trim() !== "");
|
|
150
88
|
}
|
|
151
|
-
function
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
return `Needs input: ${taskTitle}`;
|
|
89
|
+
function parseTaskContract(value) {
|
|
90
|
+
if (value === null) {
|
|
91
|
+
return { goal: "", scope: [] };
|
|
155
92
|
}
|
|
156
|
-
|
|
157
|
-
|
|
93
|
+
const parsed = parseJsonObject(value);
|
|
94
|
+
return {
|
|
95
|
+
goal: stringValue(parsed, "goal") ?? "",
|
|
96
|
+
scope: stringArrayValue(parsed, "scope") ?? [],
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
function attachProviderUsageToItem(db, item, anchors, options = {}) {
|
|
100
|
+
const tokenUsage = readProviderUsageBreakdownForAnchors(db, anchors, options);
|
|
101
|
+
if (tokenUsage.event_count <= 0) {
|
|
102
|
+
return;
|
|
158
103
|
}
|
|
159
|
-
|
|
104
|
+
item.token_usage = tokenUsage;
|
|
105
|
+
item.usage_sources = readProviderUsageSourceSummariesForAnchors(db, anchors, options);
|
|
106
|
+
item.usage_models = readProviderUsageModelSummariesForAnchors(db, anchors, options);
|
|
160
107
|
}
|
|
161
|
-
function
|
|
162
|
-
|
|
163
|
-
|
|
108
|
+
function worklistTaskLink(taskId) {
|
|
109
|
+
return {
|
|
110
|
+
kind: "worklist_task",
|
|
111
|
+
label: "Open Worklist Task",
|
|
112
|
+
task_id: taskId,
|
|
113
|
+
};
|
|
164
114
|
}
|
|
165
|
-
function
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
115
|
+
function chatMessageLink(messageId) {
|
|
116
|
+
return {
|
|
117
|
+
kind: "chat_message",
|
|
118
|
+
label: "Open in Chat",
|
|
119
|
+
message_id: messageId,
|
|
120
|
+
};
|
|
171
121
|
}
|
|
172
|
-
function
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
const docs = stringValue(result, "docs");
|
|
176
|
-
const promotion = result.promotion;
|
|
177
|
-
if (checks !== undefined) {
|
|
178
|
-
meta.push(`checks: ${checks}`);
|
|
122
|
+
function authorLabel(row) {
|
|
123
|
+
if (row.author_display_name !== null && row.author_display_name.trim() !== "") {
|
|
124
|
+
return row.author_display_name;
|
|
179
125
|
}
|
|
180
|
-
if (
|
|
181
|
-
|
|
126
|
+
if (row.author_kind === "agent") {
|
|
127
|
+
return "Tutti";
|
|
182
128
|
}
|
|
183
|
-
if (
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
129
|
+
if (row.author_kind === "system") {
|
|
130
|
+
return "System";
|
|
131
|
+
}
|
|
132
|
+
return "Member";
|
|
133
|
+
}
|
|
134
|
+
function truncateSnippetBody(value) {
|
|
135
|
+
const trimmed = value.trim();
|
|
136
|
+
if (trimmed.length <= TRACE_SOURCE_SNIPPET_BODY_LIMIT) {
|
|
137
|
+
return trimmed;
|
|
188
138
|
}
|
|
189
|
-
return
|
|
139
|
+
return `${trimmed.slice(0, TRACE_SOURCE_SNIPPET_BODY_LIMIT - 3)}...`;
|
|
140
|
+
}
|
|
141
|
+
function readReceiptSourceCount(db, workflowRef) {
|
|
142
|
+
const row = db
|
|
143
|
+
.prepare(`
|
|
144
|
+
SELECT COUNT(*) AS source_count
|
|
145
|
+
FROM scratchpad_receipt_sources
|
|
146
|
+
WHERE workflow_ref = ?
|
|
147
|
+
`)
|
|
148
|
+
.get(workflowRef);
|
|
149
|
+
return row?.source_count ?? 0;
|
|
150
|
+
}
|
|
151
|
+
function readReceiptSourceSnippets(db, workflowRef) {
|
|
152
|
+
const rows = db
|
|
153
|
+
.prepare(`
|
|
154
|
+
SELECT
|
|
155
|
+
messages.id AS message_id,
|
|
156
|
+
messages.author_kind,
|
|
157
|
+
messages.author_display_name,
|
|
158
|
+
messages.body,
|
|
159
|
+
messages.created_at
|
|
160
|
+
FROM scratchpad_receipt_sources
|
|
161
|
+
JOIN messages ON messages.id = scratchpad_receipt_sources.message_id
|
|
162
|
+
WHERE scratchpad_receipt_sources.workflow_ref = ?
|
|
163
|
+
ORDER BY scratchpad_receipt_sources.created_at ASC, scratchpad_receipt_sources.message_id ASC
|
|
164
|
+
LIMIT ${TRACE_SOURCE_SNIPPET_LIMIT}
|
|
165
|
+
`)
|
|
166
|
+
.all(workflowRef);
|
|
167
|
+
return rows.map((source) => ({
|
|
168
|
+
message_id: source.message_id,
|
|
169
|
+
author: authorLabel(source),
|
|
170
|
+
body: truncateSnippetBody(source.body),
|
|
171
|
+
created_at: source.created_at,
|
|
172
|
+
}));
|
|
173
|
+
}
|
|
174
|
+
function clarificationChatLink(messageId) {
|
|
175
|
+
return {
|
|
176
|
+
kind: "chat_message",
|
|
177
|
+
label: "Open clarification in Chat",
|
|
178
|
+
message_id: messageId,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
function readClarificationAnswerSnippets(db, submittedRefsJson) {
|
|
182
|
+
const submittedRefs = parseJsonStringArray(submittedRefsJson);
|
|
183
|
+
if (submittedRefs.length === 0) {
|
|
184
|
+
return { answer_count: 0, answers: [] };
|
|
185
|
+
}
|
|
186
|
+
const readMessage = db.prepare(`
|
|
187
|
+
SELECT
|
|
188
|
+
id AS message_id,
|
|
189
|
+
author_kind,
|
|
190
|
+
author_display_name,
|
|
191
|
+
body,
|
|
192
|
+
created_at
|
|
193
|
+
FROM messages
|
|
194
|
+
WHERE id = ?
|
|
195
|
+
AND message_kind = 'user_text'
|
|
196
|
+
AND author_kind = 'human'
|
|
197
|
+
LIMIT 1
|
|
198
|
+
`);
|
|
199
|
+
const answers = submittedRefs.flatMap((messageId) => {
|
|
200
|
+
const row = readMessage.get(messageId);
|
|
201
|
+
return row === undefined ? [] : [row];
|
|
202
|
+
});
|
|
203
|
+
const visibleAnswers = answers.slice(0, TRACE_SOURCE_SNIPPET_LIMIT).map((answer) => ({
|
|
204
|
+
message_id: answer.message_id,
|
|
205
|
+
author: authorLabel(answer),
|
|
206
|
+
body: truncateSnippetBody(answer.body),
|
|
207
|
+
created_at: answer.created_at,
|
|
208
|
+
}));
|
|
209
|
+
return {
|
|
210
|
+
answer_count: answers.length,
|
|
211
|
+
answers: visibleAnswers,
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
function clarificationTraceFromRow(db, row) {
|
|
215
|
+
const payload = parseJsonObject(row.request_payload_json);
|
|
216
|
+
const answers = readClarificationAnswerSnippets(db, row.submitted_message_refs_json);
|
|
217
|
+
const visibleAnswers = answers.answers;
|
|
218
|
+
const title = stringValue(payload, "title");
|
|
219
|
+
const summary = stringValue(payload, "summary");
|
|
220
|
+
const request = stringValue(payload, "request");
|
|
221
|
+
const clarification = {
|
|
222
|
+
...(title === undefined ? {} : { title }),
|
|
223
|
+
...(summary === undefined ? {} : { summary }),
|
|
224
|
+
...(request === undefined ? {} : { request }),
|
|
225
|
+
status: row.round_status === "sealed" ? "resolved" : "active",
|
|
226
|
+
answer_count: answers.answer_count,
|
|
227
|
+
...(visibleAnswers.length === 0 ? {} : { answers: visibleAnswers }),
|
|
228
|
+
};
|
|
229
|
+
const firstAnswer = visibleAnswers[0]?.body;
|
|
230
|
+
return {
|
|
231
|
+
clarification,
|
|
232
|
+
links: [clarificationChatLink(row.card_message_id)],
|
|
233
|
+
...(firstAnswer === undefined
|
|
234
|
+
? summary === undefined
|
|
235
|
+
? {}
|
|
236
|
+
: { decision_summary: summary }
|
|
237
|
+
: { decision_summary: firstAnswer }),
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
function readLatestTaskCompileClarificationTrace(db, workflowRef) {
|
|
241
|
+
const row = db
|
|
242
|
+
.prepare(`
|
|
243
|
+
SELECT
|
|
244
|
+
clarification_threads.id AS thread_id,
|
|
245
|
+
clarification_threads.status AS thread_status,
|
|
246
|
+
clarification_rounds.id AS round_id,
|
|
247
|
+
clarification_rounds.status AS round_status,
|
|
248
|
+
clarification_rounds.request_payload_json,
|
|
249
|
+
clarification_rounds.card_message_id,
|
|
250
|
+
clarification_rounds.submitted_message_refs_json,
|
|
251
|
+
clarification_rounds.submitted_at,
|
|
252
|
+
clarification_rounds.updated_at
|
|
253
|
+
FROM clarification_rounds
|
|
254
|
+
JOIN clarification_threads
|
|
255
|
+
ON clarification_threads.id = clarification_rounds.clarification_thread_id
|
|
256
|
+
WHERE clarification_threads.owner_kind = 'task_compile'
|
|
257
|
+
AND clarification_threads.owner_workflow_ref = ?
|
|
258
|
+
AND clarification_rounds.status = 'sealed'
|
|
259
|
+
AND clarification_rounds.submitted_at IS NOT NULL
|
|
260
|
+
ORDER BY clarification_rounds.round_index DESC, clarification_rounds.submitted_at DESC
|
|
261
|
+
LIMIT 1
|
|
262
|
+
`)
|
|
263
|
+
.get(workflowRef);
|
|
264
|
+
return row === undefined ? undefined : clarificationTraceFromRow(db, row);
|
|
190
265
|
}
|
|
191
|
-
function
|
|
266
|
+
function readLatestClarificationTraceByThread(db, threadId) {
|
|
267
|
+
const row = db
|
|
268
|
+
.prepare(`
|
|
269
|
+
SELECT
|
|
270
|
+
clarification_threads.id AS thread_id,
|
|
271
|
+
clarification_threads.status AS thread_status,
|
|
272
|
+
clarification_rounds.id AS round_id,
|
|
273
|
+
clarification_rounds.status AS round_status,
|
|
274
|
+
clarification_rounds.request_payload_json,
|
|
275
|
+
clarification_rounds.card_message_id,
|
|
276
|
+
clarification_rounds.submitted_message_refs_json,
|
|
277
|
+
clarification_rounds.submitted_at,
|
|
278
|
+
clarification_rounds.updated_at
|
|
279
|
+
FROM clarification_threads
|
|
280
|
+
JOIN clarification_rounds
|
|
281
|
+
ON clarification_rounds.clarification_thread_id = clarification_threads.id
|
|
282
|
+
WHERE clarification_threads.id = ?
|
|
283
|
+
ORDER BY
|
|
284
|
+
CASE
|
|
285
|
+
WHEN clarification_rounds.id = clarification_threads.current_round_id THEN 0
|
|
286
|
+
ELSE 1
|
|
287
|
+
END,
|
|
288
|
+
clarification_rounds.round_index DESC,
|
|
289
|
+
clarification_rounds.updated_at DESC
|
|
290
|
+
LIMIT 1
|
|
291
|
+
`)
|
|
292
|
+
.get(threadId);
|
|
293
|
+
return row === undefined ? undefined : clarificationTraceFromRow(db, row);
|
|
294
|
+
}
|
|
295
|
+
function readLatestResolvedTaskClarificationTrace(db, taskId, throughCreatedAt) {
|
|
296
|
+
const row = db
|
|
297
|
+
.prepare(`
|
|
298
|
+
SELECT
|
|
299
|
+
clarification_threads.id AS thread_id,
|
|
300
|
+
clarification_threads.status AS thread_status,
|
|
301
|
+
clarification_rounds.id AS round_id,
|
|
302
|
+
clarification_rounds.status AS round_status,
|
|
303
|
+
clarification_rounds.request_payload_json,
|
|
304
|
+
clarification_rounds.card_message_id,
|
|
305
|
+
clarification_rounds.submitted_message_refs_json,
|
|
306
|
+
clarification_rounds.submitted_at,
|
|
307
|
+
clarification_rounds.updated_at
|
|
308
|
+
FROM clarification_rounds
|
|
309
|
+
JOIN clarification_threads
|
|
310
|
+
ON clarification_threads.id = clarification_rounds.clarification_thread_id
|
|
311
|
+
WHERE clarification_threads.owner_kind = 'task'
|
|
312
|
+
AND clarification_threads.owner_task_id = ?
|
|
313
|
+
AND clarification_threads.status = 'closed'
|
|
314
|
+
AND clarification_rounds.status = 'sealed'
|
|
315
|
+
AND clarification_rounds.submitted_at IS NOT NULL
|
|
316
|
+
AND clarification_rounds.submitted_at <= ?
|
|
317
|
+
ORDER BY clarification_rounds.submitted_at DESC, clarification_rounds.round_index DESC
|
|
318
|
+
LIMIT 1
|
|
319
|
+
`)
|
|
320
|
+
.get(taskId, throughCreatedAt);
|
|
321
|
+
return row === undefined ? undefined : clarificationTraceFromRow(db, row);
|
|
322
|
+
}
|
|
323
|
+
function taskAcceptedTraceCard(db, row) {
|
|
324
|
+
const consensus = parseJsonStringArray(row.receipt_current_consensus_json);
|
|
325
|
+
const taskChanges = parseJsonStringArray(row.receipt_task_changes_json);
|
|
326
|
+
const contract = parseTaskContract(row.contract_json);
|
|
327
|
+
const sourceCount = readReceiptSourceCount(db, row.created_from_workflow_ref);
|
|
328
|
+
const sourceSnippets = sourceCount === 0 ? [] : readReceiptSourceSnippets(db, row.created_from_workflow_ref);
|
|
329
|
+
const clarification = readLatestTaskCompileClarificationTrace(db, row.created_from_workflow_ref);
|
|
330
|
+
const decisions = [
|
|
331
|
+
...consensus.slice(0, TRACE_CONSENSUS_LIMIT).map((summary) => ({
|
|
332
|
+
title: "Consensus",
|
|
333
|
+
summary,
|
|
334
|
+
source: "scratchpad",
|
|
335
|
+
})),
|
|
336
|
+
...taskChanges.slice(0, TRACE_TASK_CHANGE_LIMIT).map((summary) => ({
|
|
337
|
+
title: "Task change",
|
|
338
|
+
summary,
|
|
339
|
+
source: "scratchpad",
|
|
340
|
+
})),
|
|
341
|
+
...(clarification?.decision_summary === undefined
|
|
342
|
+
? []
|
|
343
|
+
: [
|
|
344
|
+
{
|
|
345
|
+
title: "Clarified decision",
|
|
346
|
+
summary: clarification.decision_summary,
|
|
347
|
+
source: "clarification",
|
|
348
|
+
},
|
|
349
|
+
]),
|
|
350
|
+
];
|
|
351
|
+
const scope = contract.scope.slice(0, TRACE_CONTRACT_SCOPE_LIMIT);
|
|
352
|
+
const hasReceipt = row.receipt_topic !== null ||
|
|
353
|
+
row.receipt_background_summary !== null ||
|
|
354
|
+
consensus.length > 0 ||
|
|
355
|
+
taskChanges.length > 0;
|
|
356
|
+
return {
|
|
357
|
+
tone: "success",
|
|
358
|
+
summary: row.summary,
|
|
359
|
+
origin: {
|
|
360
|
+
title: row.receipt_topic !== null && row.receipt_topic.trim() !== ""
|
|
361
|
+
? row.receipt_topic
|
|
362
|
+
: row.title,
|
|
363
|
+
...(row.receipt_background_summary === null
|
|
364
|
+
? {}
|
|
365
|
+
: { summary: row.receipt_background_summary }),
|
|
366
|
+
...(sourceCount === 0
|
|
367
|
+
? {
|
|
368
|
+
unavailable_reason: hasReceipt
|
|
369
|
+
? "Source message refs unavailable; showing Scratchpad summary."
|
|
370
|
+
: "Scratchpad receipt unavailable.",
|
|
371
|
+
}
|
|
372
|
+
: {
|
|
373
|
+
source_count: sourceCount,
|
|
374
|
+
snippets: sourceSnippets,
|
|
375
|
+
}),
|
|
376
|
+
},
|
|
377
|
+
...(decisions.length === 0 ? {} : { decisions }),
|
|
378
|
+
...(clarification === undefined ? {} : { clarification: clarification.clarification }),
|
|
379
|
+
contract: {
|
|
380
|
+
goal: contract.goal,
|
|
381
|
+
scope,
|
|
382
|
+
...(contract.scope.length > scope.length ? { truncated: true } : {}),
|
|
383
|
+
},
|
|
384
|
+
links: [
|
|
385
|
+
worklistTaskLink(row.id),
|
|
386
|
+
...sourceSnippets.map((snippet, index) => ({
|
|
387
|
+
kind: "chat_message",
|
|
388
|
+
label: index === 0 ? "Open source in Chat" : `Open source ${index + 1} in Chat`,
|
|
389
|
+
message_id: snippet.message_id,
|
|
390
|
+
})),
|
|
391
|
+
...(clarification?.links ?? []),
|
|
392
|
+
],
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
function readTaskAcceptedTimelineItems(db) {
|
|
192
396
|
if (db === undefined) {
|
|
193
397
|
return [];
|
|
194
398
|
}
|
|
@@ -198,39 +402,37 @@ function readTaskTimelineItems(db, consumedProcedureUsageAnchors) {
|
|
|
198
402
|
tasks.id,
|
|
199
403
|
tasks.title,
|
|
200
404
|
tasks.summary,
|
|
201
|
-
tasks.status,
|
|
202
405
|
tasks.created_from_workflow_ref,
|
|
203
406
|
tasks.created_at,
|
|
204
|
-
task_modules.name AS module_name
|
|
407
|
+
task_modules.name AS module_name,
|
|
408
|
+
task_details.contract_json,
|
|
409
|
+
scratchpad_receipts.topic AS receipt_topic,
|
|
410
|
+
scratchpad_receipts.background_summary AS receipt_background_summary,
|
|
411
|
+
scratchpad_receipts.current_consensus_json AS receipt_current_consensus_json,
|
|
412
|
+
scratchpad_receipts.task_changes_json AS receipt_task_changes_json
|
|
205
413
|
FROM tasks
|
|
206
414
|
LEFT JOIN task_modules ON task_modules.id = tasks.module_id
|
|
415
|
+
LEFT JOIN task_details ON task_details.task_id = tasks.id
|
|
416
|
+
LEFT JOIN scratchpad_receipts
|
|
417
|
+
ON scratchpad_receipts.workflow_ref = tasks.created_from_workflow_ref
|
|
207
418
|
ORDER BY tasks.created_at DESC, tasks.id DESC
|
|
208
|
-
LIMIT ${
|
|
419
|
+
LIMIT ${TASK_ACCEPTED_TIMELINE_LIMIT}
|
|
209
420
|
`)
|
|
210
421
|
.all();
|
|
211
422
|
return rows.map((row) => {
|
|
212
|
-
const meta = [`status: ${row.status}`];
|
|
213
|
-
if (row.module_name !== null && row.module_name.trim() !== "") {
|
|
214
|
-
meta.unshift(`module: ${row.module_name}`);
|
|
215
|
-
}
|
|
216
423
|
const item = {
|
|
217
|
-
id: `
|
|
218
|
-
kind: "
|
|
424
|
+
id: `task_accepted:${row.id}`,
|
|
425
|
+
kind: "task_accepted",
|
|
219
426
|
occurred_at: row.created_at,
|
|
220
|
-
title: `Task
|
|
427
|
+
title: `Task accepted: ${row.title}`,
|
|
221
428
|
summary: row.summary,
|
|
222
429
|
task_id: row.id,
|
|
223
430
|
task_title: row.title,
|
|
224
431
|
workflow_ref: row.created_from_workflow_ref,
|
|
225
432
|
status: "completed",
|
|
226
|
-
|
|
433
|
+
trace_card: taskAcceptedTraceCard(db, row),
|
|
227
434
|
};
|
|
228
|
-
|
|
229
|
-
if (attachProviderUsageToItem(db, item, anchors, {
|
|
230
|
-
sources: TASK_ADDED_USAGE_SOURCES,
|
|
231
|
-
})) {
|
|
232
|
-
pushConsumedProcedureUsageAnchors(consumedProcedureUsageAnchors, TASK_ADDED_USAGE_SOURCES, anchors);
|
|
233
|
-
}
|
|
435
|
+
attachProviderUsageToItem(db, item, { workflow_ref: row.created_from_workflow_ref }, { sources: TASK_ACCEPTED_USAGE_SOURCES });
|
|
234
436
|
return item;
|
|
235
437
|
});
|
|
236
438
|
}
|
|
@@ -244,414 +446,404 @@ function readRunResultTimelineRows(db) {
|
|
|
244
446
|
run_results.result_kind,
|
|
245
447
|
run_results.result_json,
|
|
246
448
|
run_results.created_at,
|
|
247
|
-
tasks.title AS task_title
|
|
449
|
+
tasks.title AS task_title,
|
|
450
|
+
task_details.contract_json
|
|
248
451
|
FROM run_results
|
|
249
452
|
LEFT JOIN tasks ON tasks.id = run_results.task_id
|
|
453
|
+
LEFT JOIN task_details ON task_details.task_id = run_results.task_id
|
|
250
454
|
ORDER BY run_results.created_at DESC, run_results.id DESC
|
|
251
455
|
LIMIT ${RUN_RESULT_TIMELINE_LIMIT}
|
|
252
456
|
`)
|
|
253
457
|
.all();
|
|
254
458
|
}
|
|
255
|
-
function
|
|
256
|
-
if (
|
|
257
|
-
return
|
|
459
|
+
function runUpdateState(row, result) {
|
|
460
|
+
if (row.result_kind === "needs_human") {
|
|
461
|
+
return "needs_input";
|
|
258
462
|
}
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
463
|
+
if (row.result_kind === "failed" || result.outcome === "failed") {
|
|
464
|
+
return "failed";
|
|
465
|
+
}
|
|
466
|
+
return "completed";
|
|
467
|
+
}
|
|
468
|
+
function timelineStatusForUpdate(updateState) {
|
|
469
|
+
if (updateState === "needs_input") {
|
|
470
|
+
return "needs_human";
|
|
471
|
+
}
|
|
472
|
+
if (updateState === "failed") {
|
|
473
|
+
return "failed";
|
|
474
|
+
}
|
|
475
|
+
return "completed";
|
|
476
|
+
}
|
|
477
|
+
function runUpdateTitle(row, updateState) {
|
|
478
|
+
const taskTitle = row.task_title ?? "Task";
|
|
479
|
+
if (updateState === "needs_input") {
|
|
480
|
+
return `Task needs input: ${taskTitle}`;
|
|
481
|
+
}
|
|
482
|
+
if (updateState === "failed") {
|
|
483
|
+
return `Task failed: ${taskTitle}`;
|
|
484
|
+
}
|
|
485
|
+
return `Task completed: ${taskTitle}`;
|
|
486
|
+
}
|
|
487
|
+
function traceToneForUpdate(updateState) {
|
|
488
|
+
if (updateState === "needs_input") {
|
|
489
|
+
return "warning";
|
|
490
|
+
}
|
|
491
|
+
if (updateState === "failed") {
|
|
492
|
+
return "error";
|
|
493
|
+
}
|
|
494
|
+
return "success";
|
|
495
|
+
}
|
|
496
|
+
function gitChangeStatus(status) {
|
|
497
|
+
if (status.startsWith("R")) {
|
|
498
|
+
return "renamed";
|
|
499
|
+
}
|
|
500
|
+
switch (status) {
|
|
501
|
+
case "A":
|
|
502
|
+
return "added";
|
|
503
|
+
case "M":
|
|
504
|
+
return "modified";
|
|
505
|
+
case "D":
|
|
506
|
+
return "deleted";
|
|
507
|
+
default:
|
|
508
|
+
return "unknown";
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
function isSafeRepoRelativePath(path) {
|
|
512
|
+
return (path.trim() !== "" &&
|
|
513
|
+
!path.startsWith("/") &&
|
|
514
|
+
!path.includes("\0") &&
|
|
515
|
+
!path.split("/").includes(".."));
|
|
516
|
+
}
|
|
517
|
+
function promotedCommitOid(result) {
|
|
518
|
+
const promotion = result.promotion;
|
|
519
|
+
if (typeof promotion !== "object" || promotion === null || Array.isArray(promotion)) {
|
|
520
|
+
return undefined;
|
|
521
|
+
}
|
|
522
|
+
const promotionRecord = promotion;
|
|
523
|
+
if (promotionRecord.status !== "promoted") {
|
|
524
|
+
return undefined;
|
|
525
|
+
}
|
|
526
|
+
return stringValue(promotionRecord, "commit_oid");
|
|
527
|
+
}
|
|
528
|
+
function readPromotedCommitChanges(workspaceRoot, commitOid) {
|
|
529
|
+
if (workspaceRoot === undefined || commitOid === undefined) {
|
|
530
|
+
return undefined;
|
|
531
|
+
}
|
|
532
|
+
const result = runGitRaw(["show", "--name-status", "--find-renames", "-z", "--format=", commitOid, "--"], workspaceRoot, 1024 * 1024);
|
|
533
|
+
if (result.status !== 0) {
|
|
534
|
+
return undefined;
|
|
535
|
+
}
|
|
536
|
+
const fields = result.stdout.toString("utf8").split("\0").filter((field) => field !== "");
|
|
537
|
+
const changes = [];
|
|
538
|
+
for (let index = 0; index < fields.length;) {
|
|
539
|
+
const status = fields[index];
|
|
540
|
+
if (status === undefined) {
|
|
541
|
+
break;
|
|
276
542
|
}
|
|
277
|
-
if (
|
|
278
|
-
|
|
543
|
+
if (status.startsWith("R")) {
|
|
544
|
+
const previousPath = fields[index + 1];
|
|
545
|
+
const nextPath = fields[index + 2];
|
|
546
|
+
index += 3;
|
|
547
|
+
if (previousPath !== undefined &&
|
|
548
|
+
nextPath !== undefined &&
|
|
549
|
+
isSafeRepoRelativePath(previousPath) &&
|
|
550
|
+
isSafeRepoRelativePath(nextPath)) {
|
|
551
|
+
changes.push({
|
|
552
|
+
status: "renamed",
|
|
553
|
+
path: nextPath,
|
|
554
|
+
previous_path: previousPath,
|
|
555
|
+
});
|
|
556
|
+
}
|
|
557
|
+
continue;
|
|
279
558
|
}
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
559
|
+
const path = fields[index + 1];
|
|
560
|
+
index += 2;
|
|
561
|
+
if (path === undefined || !isSafeRepoRelativePath(path)) {
|
|
562
|
+
continue;
|
|
283
563
|
}
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
}, {
|
|
288
|
-
sources: RUN_USAGE_SOURCES,
|
|
564
|
+
changes.push({
|
|
565
|
+
status: gitChangeStatus(status),
|
|
566
|
+
path,
|
|
289
567
|
});
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
item.usage_sources = readProviderUsageSourceSummariesForAnchors(db, {
|
|
293
|
-
activity_ref: row.activity_ref,
|
|
294
|
-
run_result_id: row.id,
|
|
295
|
-
}, {
|
|
296
|
-
sources: RUN_USAGE_SOURCES,
|
|
297
|
-
});
|
|
298
|
-
item.usage_models = readProviderUsageModelSummariesForAnchors(db, {
|
|
299
|
-
activity_ref: row.activity_ref,
|
|
300
|
-
run_result_id: row.id,
|
|
301
|
-
}, {
|
|
302
|
-
sources: RUN_USAGE_SOURCES,
|
|
303
|
-
});
|
|
304
|
-
}
|
|
305
|
-
return item;
|
|
306
|
-
});
|
|
568
|
+
}
|
|
569
|
+
return changes;
|
|
307
570
|
}
|
|
308
|
-
function
|
|
309
|
-
|
|
310
|
-
|
|
571
|
+
function changesTrace(result, workspaceRoot) {
|
|
572
|
+
const changedPaths = stringArrayValue(result, "changed_paths");
|
|
573
|
+
const commitChanges = readPromotedCommitChanges(workspaceRoot, promotedCommitOid(result));
|
|
574
|
+
const changeItems = commitChanges ??
|
|
575
|
+
changedPaths?.filter(isSafeRepoRelativePath).map((path) => ({
|
|
576
|
+
status: "unknown",
|
|
577
|
+
path,
|
|
578
|
+
}));
|
|
579
|
+
if (changeItems === undefined) {
|
|
580
|
+
return undefined;
|
|
311
581
|
}
|
|
312
|
-
|
|
313
|
-
|
|
582
|
+
const visibleChanges = changeItems.slice(0, TRACE_CHANGED_PATH_LIMIT);
|
|
583
|
+
return {
|
|
584
|
+
items: visibleChanges,
|
|
585
|
+
total_count: changeItems.length,
|
|
586
|
+
truncated: changeItems.length > visibleChanges.length,
|
|
587
|
+
};
|
|
588
|
+
}
|
|
589
|
+
function changeLinks(changes) {
|
|
590
|
+
if (changes === undefined) {
|
|
591
|
+
return [];
|
|
314
592
|
}
|
|
315
|
-
|
|
316
|
-
|
|
593
|
+
return changes.items
|
|
594
|
+
.filter((change) => change.status !== "deleted")
|
|
595
|
+
.slice(0, TRACE_CHANGE_LINK_LIMIT)
|
|
596
|
+
.map((change, index) => ({
|
|
597
|
+
kind: "repo_path",
|
|
598
|
+
label: index === 0 ? "Open changed file" : `Open changed file ${index + 1}`,
|
|
599
|
+
path: change.path,
|
|
600
|
+
}));
|
|
601
|
+
}
|
|
602
|
+
function verificationTrace(result) {
|
|
603
|
+
const checks = result.checks;
|
|
604
|
+
if (checks !== "passed" && checks !== "failed" && checks !== "skipped") {
|
|
605
|
+
return undefined;
|
|
317
606
|
}
|
|
318
|
-
|
|
607
|
+
const skipReasonCode = stringValue(result, "skip_reason_code");
|
|
608
|
+
const skipReasonSummary = stringValue(result, "skip_reason_summary");
|
|
609
|
+
return {
|
|
610
|
+
checks,
|
|
611
|
+
...(skipReasonCode === undefined ? {} : { skip_reason_code: skipReasonCode }),
|
|
612
|
+
...(skipReasonSummary === undefined ? {} : { skip_reason_summary: skipReasonSummary }),
|
|
613
|
+
};
|
|
319
614
|
}
|
|
320
|
-
function
|
|
321
|
-
|
|
615
|
+
function promotionTrace(result) {
|
|
616
|
+
const promotion = result.promotion;
|
|
617
|
+
if (typeof promotion !== "object" || promotion === null || Array.isArray(promotion)) {
|
|
618
|
+
return undefined;
|
|
619
|
+
}
|
|
620
|
+
const promotionRecord = promotion;
|
|
621
|
+
const status = promotionRecord.status;
|
|
622
|
+
if (status !== "promoted" && status !== "not_promoted" && status !== "not_attempted") {
|
|
623
|
+
return undefined;
|
|
624
|
+
}
|
|
625
|
+
const commitOid = stringValue(promotionRecord, "commit_oid");
|
|
626
|
+
const summary = stringValue(promotionRecord, "summary");
|
|
627
|
+
const reasonCode = stringValue(promotionRecord, "reason_code");
|
|
628
|
+
return {
|
|
629
|
+
status,
|
|
630
|
+
...(commitOid === undefined
|
|
631
|
+
? {}
|
|
632
|
+
: { commit_oid: commitOid, commit_short_oid: commitOid.slice(0, 12) }),
|
|
633
|
+
...(summary === undefined ? {} : { summary }),
|
|
634
|
+
...(reasonCode === undefined ? {} : { reason_code: reasonCode }),
|
|
635
|
+
};
|
|
636
|
+
}
|
|
637
|
+
function runUpdateTraceCard(db, row, result, updateState, workspaceRoot) {
|
|
638
|
+
const contract = parseTaskContract(row.contract_json);
|
|
639
|
+
const summary = stringValue(result, "summary") ?? "";
|
|
640
|
+
const errorCode = stringValue(result, "error_code");
|
|
641
|
+
const changes = changesTrace(result, workspaceRoot);
|
|
642
|
+
const verification = verificationTrace(result);
|
|
643
|
+
const promotion = promotionTrace(result);
|
|
644
|
+
const clarificationThreadRef = stringValue(result, "clarification_thread_ref");
|
|
645
|
+
const clarification = updateState === "needs_input" && clarificationThreadRef !== undefined
|
|
646
|
+
? readLatestClarificationTraceByThread(db, clarificationThreadRef)
|
|
647
|
+
: updateState === "completed" || updateState === "failed"
|
|
648
|
+
? readLatestResolvedTaskClarificationTrace(db, row.task_id, row.created_at)
|
|
649
|
+
: undefined;
|
|
650
|
+
return {
|
|
651
|
+
tone: traceToneForUpdate(updateState),
|
|
652
|
+
summary,
|
|
653
|
+
result: {
|
|
654
|
+
summary,
|
|
655
|
+
...(errorCode === undefined ? {} : { error_code: errorCode }),
|
|
656
|
+
},
|
|
657
|
+
contract: {
|
|
658
|
+
goal: contract.goal,
|
|
659
|
+
scope: contract.scope.slice(0, TRACE_CONTRACT_SCOPE_LIMIT),
|
|
660
|
+
...(contract.scope.length > TRACE_CONTRACT_SCOPE_LIMIT ? { truncated: true } : {}),
|
|
661
|
+
},
|
|
662
|
+
...(changes === undefined ? {} : { changes }),
|
|
663
|
+
...(verification === undefined ? {} : { verification }),
|
|
664
|
+
...(promotion === undefined ? {} : { promotion }),
|
|
665
|
+
...(clarification === undefined ? {} : { clarification: clarification.clarification }),
|
|
666
|
+
links: [
|
|
667
|
+
worklistTaskLink(row.task_id),
|
|
668
|
+
...(clarification?.links ?? []),
|
|
669
|
+
...changeLinks(changes),
|
|
670
|
+
],
|
|
671
|
+
};
|
|
672
|
+
}
|
|
673
|
+
function readTaskUpdateTimelineItems(db, rows, workspaceRoot) {
|
|
674
|
+
if (db === undefined) {
|
|
322
675
|
return [];
|
|
323
676
|
}
|
|
324
|
-
return rows.
|
|
677
|
+
return rows.map((row) => {
|
|
325
678
|
const result = parseJsonObject(row.result_json);
|
|
326
|
-
const
|
|
327
|
-
const status =
|
|
328
|
-
if (checks === undefined || status === undefined) {
|
|
329
|
-
return [];
|
|
330
|
-
}
|
|
331
|
-
const taskTitle = row.task_title ?? "Task";
|
|
679
|
+
const updateState = runUpdateState(row, result);
|
|
680
|
+
const status = timelineStatusForUpdate(updateState);
|
|
332
681
|
const item = {
|
|
333
|
-
id: `
|
|
334
|
-
kind: "
|
|
682
|
+
id: `task_update:${row.id}`,
|
|
683
|
+
kind: "task_update",
|
|
335
684
|
occurred_at: row.created_at,
|
|
336
|
-
title:
|
|
685
|
+
title: runUpdateTitle(row, updateState),
|
|
337
686
|
task_id: row.task_id,
|
|
338
687
|
activity_ref: row.activity_ref,
|
|
688
|
+
run_result_id: row.id,
|
|
339
689
|
status,
|
|
340
|
-
|
|
690
|
+
update_state: updateState,
|
|
691
|
+
trace_card: runUpdateTraceCard(db, row, result, updateState, workspaceRoot),
|
|
341
692
|
};
|
|
342
693
|
if (row.task_title !== null && row.task_title.trim() !== "") {
|
|
343
694
|
item.task_title = row.task_title;
|
|
344
695
|
}
|
|
345
|
-
const summary = stringValue(result, "
|
|
346
|
-
(checks === "skipped" ? stringValue(result, "skip_reason_code") : undefined);
|
|
696
|
+
const summary = stringValue(result, "summary");
|
|
347
697
|
if (summary !== undefined) {
|
|
348
698
|
item.summary = summary;
|
|
349
699
|
}
|
|
350
|
-
|
|
700
|
+
attachProviderUsageToItem(db, item, { activity_ref: row.activity_ref, run_result_id: row.id }, { sources: RUN_USAGE_SOURCES });
|
|
701
|
+
return item;
|
|
351
702
|
});
|
|
352
703
|
}
|
|
353
|
-
function
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
reasoning_output_tokens: row.reasoning_output_tokens ?? 0,
|
|
359
|
-
total_tokens: row.total_tokens ?? 0,
|
|
360
|
-
event_count: row.event_count ?? 0,
|
|
361
|
-
};
|
|
362
|
-
}
|
|
363
|
-
function readProcedureUsageTimelineItems(db, consumedProcedureUsageAnchors) {
|
|
364
|
-
if (db === undefined) {
|
|
365
|
-
return [];
|
|
704
|
+
function parseCheckpointFeedback(refsJson) {
|
|
705
|
+
const refs = parseJsonObject(refsJson);
|
|
706
|
+
const feedback = refs.worklist_feedback;
|
|
707
|
+
if (typeof feedback !== "object" || feedback === null || Array.isArray(feedback)) {
|
|
708
|
+
return null;
|
|
366
709
|
}
|
|
367
|
-
const
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
WHERE
|
|
383
|
-
(activity_ref IS NOT NULL OR workflow_ref IS NOT NULL) AND
|
|
384
|
-
source NOT IN ('workspace_write_run', 'follow_up_run', 'pipeline_self_correction')
|
|
385
|
-
GROUP BY source, activity_ref, workflow_ref, task_id
|
|
386
|
-
ORDER BY occurred_at DESC
|
|
387
|
-
LIMIT 50
|
|
388
|
-
`)
|
|
389
|
-
.all();
|
|
390
|
-
return rows
|
|
391
|
-
.filter((row) => !isProcedureUsageConsumed(row, consumedProcedureUsageAnchors))
|
|
392
|
-
.map((row) => {
|
|
393
|
-
const source = providerUsageSourceMetadata(row.source);
|
|
394
|
-
const anchor = row.activity_ref ?? row.workflow_ref ?? row.source;
|
|
395
|
-
const item = {
|
|
396
|
-
id: `procedure:${anchor}:${row.source}`,
|
|
397
|
-
kind: "procedure",
|
|
398
|
-
occurred_at: row.occurred_at,
|
|
399
|
-
title: source.label,
|
|
400
|
-
status: "completed",
|
|
401
|
-
token_usage: usageBreakdownFromTimelineRow(row),
|
|
402
|
-
usage_sources: [
|
|
403
|
-
{
|
|
404
|
-
source: row.source,
|
|
405
|
-
...source,
|
|
406
|
-
...usageBreakdownFromTimelineRow(row),
|
|
407
|
-
},
|
|
408
|
-
],
|
|
409
|
-
meta: [`category: ${source.category}`],
|
|
410
|
-
};
|
|
411
|
-
if (row.activity_ref !== null) {
|
|
412
|
-
item.activity_ref = row.activity_ref;
|
|
413
|
-
}
|
|
414
|
-
if (row.workflow_ref !== null) {
|
|
415
|
-
item.workflow_ref = row.workflow_ref;
|
|
710
|
+
const record = feedback;
|
|
711
|
+
const status = record.status;
|
|
712
|
+
const counters = record.counters;
|
|
713
|
+
const rawItems = record.items;
|
|
714
|
+
if ((status !== "completed" && status !== "failed") ||
|
|
715
|
+
typeof counters !== "object" ||
|
|
716
|
+
counters === null ||
|
|
717
|
+
Array.isArray(counters) ||
|
|
718
|
+
!Array.isArray(rawItems)) {
|
|
719
|
+
return null;
|
|
720
|
+
}
|
|
721
|
+
const counterRecord = counters;
|
|
722
|
+
const parsedItems = rawItems.flatMap((item) => {
|
|
723
|
+
if (typeof item !== "object" || item === null || Array.isArray(item)) {
|
|
724
|
+
return [];
|
|
416
725
|
}
|
|
417
|
-
|
|
418
|
-
|
|
726
|
+
const itemRecord = item;
|
|
727
|
+
if (typeof itemRecord.task_id !== "string" ||
|
|
728
|
+
typeof itemRecord.title !== "string" ||
|
|
729
|
+
(itemRecord.status !== "done" && itemRecord.status !== "failed")) {
|
|
730
|
+
return [];
|
|
419
731
|
}
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
732
|
+
return [
|
|
733
|
+
{
|
|
734
|
+
task_id: itemRecord.task_id,
|
|
735
|
+
title: itemRecord.title,
|
|
736
|
+
status: itemRecord.status,
|
|
737
|
+
summary: typeof itemRecord.summary === "string" ? itemRecord.summary : "",
|
|
738
|
+
},
|
|
739
|
+
];
|
|
426
740
|
});
|
|
741
|
+
const rawGeneratedFiles = record.generated_files;
|
|
742
|
+
const generatedFiles = Array.isArray(rawGeneratedFiles)
|
|
743
|
+
? rawGeneratedFiles.flatMap((file) => {
|
|
744
|
+
if (typeof file !== "object" || file === null || Array.isArray(file)) {
|
|
745
|
+
return [];
|
|
746
|
+
}
|
|
747
|
+
const fileRecord = file;
|
|
748
|
+
if (typeof fileRecord.path !== "string" || typeof fileRecord.name !== "string") {
|
|
749
|
+
return [];
|
|
750
|
+
}
|
|
751
|
+
return [{ path: fileRecord.path, name: fileRecord.name }];
|
|
752
|
+
})
|
|
753
|
+
: undefined;
|
|
754
|
+
const finalNote = stringValue(record, "final_note");
|
|
755
|
+
return {
|
|
756
|
+
status,
|
|
757
|
+
title: stringValue(record, "title") ?? "Worklist checkpoint",
|
|
758
|
+
counters: {
|
|
759
|
+
completed: typeof counterRecord.completed === "number" ? counterRecord.completed : 0,
|
|
760
|
+
failed: typeof counterRecord.failed === "number" ? counterRecord.failed : 0,
|
|
761
|
+
remaining: typeof counterRecord.remaining === "number" ? counterRecord.remaining : 0,
|
|
762
|
+
},
|
|
763
|
+
items: parsedItems,
|
|
764
|
+
...(finalNote === undefined ? {} : { final_note: finalNote }),
|
|
765
|
+
...(generatedFiles === undefined || generatedFiles.length === 0
|
|
766
|
+
? {}
|
|
767
|
+
: { generated_files: generatedFiles }),
|
|
768
|
+
...(typeof record.primary_task_id === "string"
|
|
769
|
+
? { primary_task_id: record.primary_task_id }
|
|
770
|
+
: {}),
|
|
771
|
+
};
|
|
427
772
|
}
|
|
428
|
-
function
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
}
|
|
434
|
-
function clarificationSummary(row) {
|
|
435
|
-
return stringValue(parseJsonObject(row.request_payload_json), "summary");
|
|
436
|
-
}
|
|
437
|
-
function clarificationMeta(row, phase) {
|
|
438
|
-
const meta = [
|
|
439
|
-
row.owner_kind === "task" ? "owner: task" : "owner: task_compile",
|
|
440
|
-
`round: ${row.round_index}`,
|
|
441
|
-
];
|
|
442
|
-
if (phase === "answered") {
|
|
443
|
-
meta.push(`successor: ${row.successor_status}`);
|
|
444
|
-
}
|
|
445
|
-
return meta;
|
|
773
|
+
function firstNonEmptyLine(value) {
|
|
774
|
+
return value
|
|
775
|
+
.split(/\r?\n/u)
|
|
776
|
+
.map((line) => line.trim())
|
|
777
|
+
.find((line) => line.length > 0);
|
|
446
778
|
}
|
|
447
|
-
function
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
779
|
+
function checkpointTraceCard(feedback, messageId, body) {
|
|
780
|
+
const visibleItems = feedback.items.slice(0, TRACE_CHECKPOINT_ITEM_LIMIT);
|
|
781
|
+
const visibleGeneratedFiles = feedback.generated_files?.slice(0, TRACE_GENERATED_FILE_LIMIT);
|
|
782
|
+
return {
|
|
783
|
+
tone: feedback.status === "completed" ? "success" : "error",
|
|
784
|
+
summary: feedback.final_note ?? firstNonEmptyLine(body) ?? feedback.title,
|
|
785
|
+
checkpoint: {
|
|
786
|
+
counters: feedback.counters,
|
|
787
|
+
items: visibleItems,
|
|
788
|
+
...(visibleGeneratedFiles === undefined ? {} : { generated_files: visibleGeneratedFiles }),
|
|
789
|
+
...(feedback.items.length > visibleItems.length ||
|
|
790
|
+
(feedback.generated_files?.length ?? 0) > (visibleGeneratedFiles?.length ?? 0)
|
|
791
|
+
? { truncated: true }
|
|
792
|
+
: {}),
|
|
793
|
+
},
|
|
794
|
+
links: [chatMessageLink(messageId)],
|
|
795
|
+
};
|
|
460
796
|
}
|
|
461
|
-
function
|
|
797
|
+
function readCheckpointTimelineItems(db) {
|
|
462
798
|
if (db === undefined) {
|
|
463
799
|
return [];
|
|
464
800
|
}
|
|
465
801
|
const rows = db
|
|
466
802
|
.prepare(`
|
|
467
|
-
SELECT
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
clarification_rounds.created_at,
|
|
475
|
-
clarification_threads.owner_kind,
|
|
476
|
-
clarification_threads.owner_workflow_ref,
|
|
477
|
-
clarification_threads.owner_task_id,
|
|
478
|
-
clarification_threads.created_from_activity_ref,
|
|
479
|
-
tasks.title AS task_title
|
|
480
|
-
FROM clarification_rounds
|
|
481
|
-
JOIN clarification_threads
|
|
482
|
-
ON clarification_threads.id = clarification_rounds.clarification_thread_id
|
|
483
|
-
LEFT JOIN tasks ON tasks.id = clarification_threads.owner_task_id
|
|
484
|
-
ORDER BY clarification_rounds.created_at DESC, clarification_rounds.id DESC
|
|
485
|
-
LIMIT ${CLARIFICATION_TIMELINE_LIMIT}
|
|
803
|
+
SELECT id, body, refs_json, created_at
|
|
804
|
+
FROM messages
|
|
805
|
+
WHERE scope_kind = 'main_chat'
|
|
806
|
+
AND refs_json IS NOT NULL
|
|
807
|
+
AND json_type(refs_json, '$.worklist_feedback') = 'object'
|
|
808
|
+
ORDER BY created_at DESC, id DESC
|
|
809
|
+
LIMIT ${CHECKPOINT_TIMELINE_LIMIT}
|
|
486
810
|
`)
|
|
487
811
|
.all();
|
|
488
812
|
return rows.flatMap((row) => {
|
|
489
|
-
const
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
id: `clarification:${row.id}:requested`,
|
|
493
|
-
kind: "clarification",
|
|
494
|
-
occurred_at: row.created_at,
|
|
495
|
-
title: `Clarification requested: ${ownerLabel}`,
|
|
496
|
-
status: row.status === "writable" ? "needs_human" : "completed",
|
|
497
|
-
meta: clarificationMeta(row, "requested"),
|
|
498
|
-
};
|
|
499
|
-
if (summary !== undefined) {
|
|
500
|
-
requested.summary = summary;
|
|
501
|
-
}
|
|
502
|
-
assignClarificationAnchors(requested, row);
|
|
503
|
-
if (row.submitted_at === null) {
|
|
504
|
-
return [requested];
|
|
813
|
+
const feedback = parseCheckpointFeedback(row.refs_json);
|
|
814
|
+
if (feedback === null) {
|
|
815
|
+
return [];
|
|
505
816
|
}
|
|
506
|
-
const answered = {
|
|
507
|
-
id: `clarification:${row.id}:answered`,
|
|
508
|
-
kind: "clarification",
|
|
509
|
-
occurred_at: row.submitted_at,
|
|
510
|
-
title: `Clarification answered: ${ownerLabel}`,
|
|
511
|
-
summary: "Human input submitted.",
|
|
512
|
-
status: "completed",
|
|
513
|
-
meta: clarificationMeta(row, "answered"),
|
|
514
|
-
};
|
|
515
|
-
assignClarificationAnchors(answered, row);
|
|
516
|
-
return [requested, answered];
|
|
517
|
-
});
|
|
518
|
-
}
|
|
519
|
-
function readRecoveryTimelineItems(db) {
|
|
520
|
-
if (db === undefined) {
|
|
521
|
-
return [];
|
|
522
|
-
}
|
|
523
|
-
const rows = db
|
|
524
|
-
.prepare(`
|
|
525
|
-
SELECT
|
|
526
|
-
id,
|
|
527
|
-
affected_activities_json,
|
|
528
|
-
summary,
|
|
529
|
-
created_at
|
|
530
|
-
FROM recovery_summaries
|
|
531
|
-
ORDER BY created_at DESC, id DESC
|
|
532
|
-
LIMIT ${RECOVERY_TIMELINE_LIMIT}
|
|
533
|
-
`)
|
|
534
|
-
.all();
|
|
535
|
-
return rows.map((row) => {
|
|
536
|
-
const affectedActivities = parseJsonStringArray(row.affected_activities_json);
|
|
537
817
|
const item = {
|
|
538
|
-
id: `
|
|
539
|
-
kind: "
|
|
818
|
+
id: `checkpoint:${row.id}`,
|
|
819
|
+
kind: "checkpoint",
|
|
540
820
|
occurred_at: row.created_at,
|
|
541
|
-
title:
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
821
|
+
title: feedback.title,
|
|
822
|
+
status: feedback.status === "completed" ? "completed" : "failed",
|
|
823
|
+
message_id: row.id,
|
|
824
|
+
trace_card: checkpointTraceCard(feedback, row.id, row.body),
|
|
545
825
|
};
|
|
546
|
-
|
|
547
|
-
|
|
826
|
+
const summary = feedback.final_note ?? firstNonEmptyLine(row.body);
|
|
827
|
+
if (summary !== undefined) {
|
|
828
|
+
item.summary = summary;
|
|
548
829
|
}
|
|
549
|
-
|
|
830
|
+
if (feedback.primary_task_id !== undefined) {
|
|
831
|
+
item.task_id = feedback.primary_task_id;
|
|
832
|
+
}
|
|
833
|
+
return [item];
|
|
550
834
|
});
|
|
551
835
|
}
|
|
552
|
-
function usageSourcesForDurableEventItem(item) {
|
|
553
|
-
if (item.title === "Docs sync completed" || item.title === "Docs sync failed") {
|
|
554
|
-
return CONTEXT_SYNC_USAGE_SOURCES;
|
|
555
|
-
}
|
|
556
|
-
if (item.title === "Reference summaries refreshed" ||
|
|
557
|
-
item.title === "Reference summary refresh failed") {
|
|
558
|
-
return REFERENCE_SUMMARY_USAGE_SOURCES;
|
|
559
|
-
}
|
|
560
|
-
return [];
|
|
561
|
-
}
|
|
562
|
-
function attachDurableEventUsage(db, item, consumedProcedureUsageAnchors) {
|
|
563
|
-
if (db === undefined) {
|
|
564
|
-
return item;
|
|
565
|
-
}
|
|
566
|
-
const sources = usageSourcesForDurableEventItem(item);
|
|
567
|
-
if (sources.length === 0) {
|
|
568
|
-
return item;
|
|
569
|
-
}
|
|
570
|
-
const anchors = {};
|
|
571
|
-
if (item.activity_ref !== undefined) {
|
|
572
|
-
anchors.activity_ref = item.activity_ref;
|
|
573
|
-
}
|
|
574
|
-
if (item.workflow_ref !== undefined) {
|
|
575
|
-
anchors.workflow_ref = item.workflow_ref;
|
|
576
|
-
}
|
|
577
|
-
if (item.task_id !== undefined) {
|
|
578
|
-
anchors.task_id = item.task_id;
|
|
579
|
-
}
|
|
580
|
-
if (attachProviderUsageToItem(db, item, anchors, { sources })) {
|
|
581
|
-
pushConsumedProcedureUsageAnchors(consumedProcedureUsageAnchors, sources, anchors);
|
|
582
|
-
}
|
|
583
|
-
return item;
|
|
584
|
-
}
|
|
585
|
-
function shouldDisplayDurableEventItem(item) {
|
|
586
|
-
if (item.kind === "promotion") {
|
|
587
|
-
return false;
|
|
588
|
-
}
|
|
589
|
-
return item.title !== "Docs sync completed" && item.title !== "Reference summaries refreshed";
|
|
590
|
-
}
|
|
591
836
|
export function readProjectTimelineProjection(input) {
|
|
592
837
|
const now = input.now ?? (() => new Date());
|
|
593
838
|
const runRows = input.db === undefined ? [] : readRunResultTimelineRows(input.db);
|
|
594
|
-
const consumedProcedureUsageAnchors = [];
|
|
595
|
-
const durableEventItems = input.db === undefined
|
|
596
|
-
? []
|
|
597
|
-
: readProjectTimelineEventItems(input.db, { limit: DURABLE_EVENT_TIMELINE_LIMIT })
|
|
598
|
-
.filter(shouldDisplayDurableEventItem)
|
|
599
|
-
.map((item) => attachDurableEventUsage(input.db, item, consumedProcedureUsageAnchors));
|
|
600
839
|
const items = [
|
|
601
|
-
...
|
|
602
|
-
...
|
|
603
|
-
...
|
|
604
|
-
|
|
605
|
-
...readClarificationTimelineItems(input.db),
|
|
606
|
-
...readRecoveryTimelineItems(input.db),
|
|
607
|
-
...durableEventItems,
|
|
608
|
-
...readCommitTimelineItems(input.workspaceRoot),
|
|
609
|
-
]
|
|
610
|
-
.filter((item) => item.status !== "skipped")
|
|
611
|
-
.sort(compareTimelineItemsByTimeDesc);
|
|
840
|
+
...readTaskAcceptedTimelineItems(input.db),
|
|
841
|
+
...readTaskUpdateTimelineItems(input.db, runRows, input.workspaceRoot),
|
|
842
|
+
...readCheckpointTimelineItems(input.db),
|
|
843
|
+
].sort(compareTimelineItemsByTimeDesc);
|
|
612
844
|
return {
|
|
613
845
|
generated_at: now().toISOString(),
|
|
614
|
-
usage_overview: input.db === undefined
|
|
615
|
-
? {
|
|
616
|
-
total_tokens: 0,
|
|
617
|
-
totals: {
|
|
618
|
-
input_tokens: 0,
|
|
619
|
-
cached_input_tokens: 0,
|
|
620
|
-
output_tokens: 0,
|
|
621
|
-
reasoning_output_tokens: 0,
|
|
622
|
-
total_tokens: 0,
|
|
623
|
-
event_count: 0,
|
|
624
|
-
},
|
|
625
|
-
by_source: [],
|
|
626
|
-
by_model: [],
|
|
627
|
-
recent_windows: {
|
|
628
|
-
today: {
|
|
629
|
-
input_tokens: 0,
|
|
630
|
-
cached_input_tokens: 0,
|
|
631
|
-
output_tokens: 0,
|
|
632
|
-
reasoning_output_tokens: 0,
|
|
633
|
-
total_tokens: 0,
|
|
634
|
-
event_count: 0,
|
|
635
|
-
},
|
|
636
|
-
last_7_days: {
|
|
637
|
-
input_tokens: 0,
|
|
638
|
-
cached_input_tokens: 0,
|
|
639
|
-
output_tokens: 0,
|
|
640
|
-
reasoning_output_tokens: 0,
|
|
641
|
-
total_tokens: 0,
|
|
642
|
-
event_count: 0,
|
|
643
|
-
},
|
|
644
|
-
last_30_days: {
|
|
645
|
-
input_tokens: 0,
|
|
646
|
-
cached_input_tokens: 0,
|
|
647
|
-
output_tokens: 0,
|
|
648
|
-
reasoning_output_tokens: 0,
|
|
649
|
-
total_tokens: 0,
|
|
650
|
-
event_count: 0,
|
|
651
|
-
},
|
|
652
|
-
},
|
|
653
|
-
}
|
|
654
|
-
: readProviderUsageProjection(input.db, { now }),
|
|
846
|
+
usage_overview: input.db === undefined ? emptyUsageOverview() : readProviderUsageProjection(input.db, { now }),
|
|
655
847
|
items: items.slice(0, TIMELINE_LIMIT),
|
|
656
848
|
};
|
|
657
849
|
}
|