akemon 0.3.6 → 0.3.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/DATA_POLICY.md +11 -3
- package/README.md +133 -21
- package/dist/akemon-home.js +56 -0
- package/dist/akemon-message.js +107 -0
- package/dist/best-effort.js +8 -0
- package/dist/cli.js +1188 -100
- package/dist/cognitive-artifact-store.js +101 -0
- package/dist/cognitive-event-log.js +47 -0
- package/dist/config.js +45 -9
- package/dist/context.js +27 -6
- package/dist/core/contracts/layers.js +1 -0
- package/dist/core/contracts/permission.js +1 -0
- package/dist/core/contracts/workspace.js +1 -0
- package/dist/core-cognitive-module.js +768 -0
- package/dist/engine-peripheral.js +127 -26
- package/dist/engine-routing.js +58 -17
- package/dist/interactive-session.js +361 -0
- package/dist/local-interconnect.js +156 -0
- package/dist/local-registry.js +178 -0
- package/dist/mcp-server.js +4 -1
- package/dist/memory-proposal.js +379 -0
- package/dist/memory-recorder.js +368 -0
- package/dist/orphan-scan.js +36 -24
- package/dist/passive-reflection-cognitive-module.js +172 -0
- package/dist/peripheral-registry.js +235 -0
- package/dist/permission-audit.js +132 -0
- package/dist/relay-client.js +68 -9
- package/dist/relay-mode.js +34 -0
- package/dist/relay-peripheral.js +139 -49
- package/dist/runtime-platform.js +122 -0
- package/dist/secretariat/client.js +87 -0
- package/dist/self.js +15 -6
- package/dist/server.js +3675 -512
- package/dist/social-discovery.js +231 -0
- package/dist/software-agent-peripheral.js +185 -244
- package/dist/software-agent-transport.js +177 -0
- package/dist/task-module.js +243 -0
- package/dist/task-registry.js +756 -0
- package/dist/vendor/xterm/addon-fit.js +2 -0
- package/dist/vendor/xterm/addon-search.js +2 -0
- package/dist/vendor/xterm/addon-web-links.js +2 -0
- package/dist/vendor/xterm/xterm.css +285 -0
- package/dist/vendor/xterm/xterm.js +2 -0
- package/dist/work-memory.js +59 -15
- package/dist/workbench-peripheral-guide.js +79 -0
- package/dist/workbench-session.js +1074 -0
- package/dist/workbench.html +4011 -0
- package/package.json +8 -3
- package/scripts/build.cjs +24 -0
- package/scripts/check-architecture-baseline.cjs +68 -0
- package/scripts/test.cjs +38 -0
|
@@ -0,0 +1,756 @@
|
|
|
1
|
+
import { mkdir, readdir, readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { redactSecrets } from "./redaction.js";
|
|
4
|
+
const DEFAULT_TASK_SOURCE = "unknown";
|
|
5
|
+
export function taskRegistryDir(workdir, agentName) {
|
|
6
|
+
return join(workdir, ".akemon", "agents", agentName, "tasks");
|
|
7
|
+
}
|
|
8
|
+
export function taskRegistryRecordPath(workdir, agentName, taskId) {
|
|
9
|
+
return join(taskRegistryDir(workdir, agentName), `${safeTaskFilename(taskId)}.json`);
|
|
10
|
+
}
|
|
11
|
+
export async function upsertTaskRegistryRecord(input) {
|
|
12
|
+
const now = input.now || new Date().toISOString();
|
|
13
|
+
const existing = await readTaskRegistryRecord({
|
|
14
|
+
workdir: input.workdir,
|
|
15
|
+
agentName: input.agentName,
|
|
16
|
+
taskId: input.patch.taskId,
|
|
17
|
+
});
|
|
18
|
+
const status = input.patch.status || existing?.status || "pending";
|
|
19
|
+
const stage = normalizeTaskRegistryStage(input.patch.stage)
|
|
20
|
+
|| (input.patch.status ? inferTaskStageFromStatus(status) : existing?.stage)
|
|
21
|
+
|| inferTaskStageFromStatus(status);
|
|
22
|
+
const completedAt = input.patch.completedAt !== undefined
|
|
23
|
+
? input.patch.completedAt
|
|
24
|
+
: input.patch.status && input.patch.status !== "succeeded" && input.patch.status !== "failed"
|
|
25
|
+
? undefined
|
|
26
|
+
: existing?.completedAt;
|
|
27
|
+
const mergedData = mergeTaskData(existing?.data, input.patch.data);
|
|
28
|
+
const draft = {
|
|
29
|
+
taskId: input.patch.taskId,
|
|
30
|
+
source: existing?.source && existing.source !== DEFAULT_TASK_SOURCE
|
|
31
|
+
? existing.source
|
|
32
|
+
: input.patch.source || DEFAULT_TASK_SOURCE,
|
|
33
|
+
status,
|
|
34
|
+
stage,
|
|
35
|
+
objective: existing?.objective || input.patch.objective || input.patch.summary || input.patch.taskId,
|
|
36
|
+
route: input.patch.route ?? existing?.route,
|
|
37
|
+
summary: input.patch.summary ?? existing?.summary,
|
|
38
|
+
conversationId: input.patch.conversationId ?? existing?.conversationId,
|
|
39
|
+
refs: input.patch.refs ?? existing?.refs,
|
|
40
|
+
data: mergedData,
|
|
41
|
+
createdAt: existing?.createdAt || now,
|
|
42
|
+
updatedAt: now,
|
|
43
|
+
completedAt,
|
|
44
|
+
};
|
|
45
|
+
const presentation = deriveTaskPresentation({
|
|
46
|
+
...draft,
|
|
47
|
+
kind: input.patch.kind || existing?.kind,
|
|
48
|
+
visibility: input.patch.visibility || existing?.visibility,
|
|
49
|
+
title: input.patch.title,
|
|
50
|
+
ownerStatus: input.patch.ownerStatus,
|
|
51
|
+
nextAction: input.patch.nextAction,
|
|
52
|
+
parentTaskId: input.patch.parentTaskId ?? existing?.parentTaskId,
|
|
53
|
+
});
|
|
54
|
+
const next = {
|
|
55
|
+
schemaVersion: 1,
|
|
56
|
+
...draft,
|
|
57
|
+
kind: input.patch.kind || existing?.kind || presentation.kind,
|
|
58
|
+
visibility: input.patch.visibility || existing?.visibility || presentation.visibility,
|
|
59
|
+
title: input.patch.title || existing?.title || presentation.title,
|
|
60
|
+
ownerStatus: input.patch.ownerStatus || presentation.ownerStatus,
|
|
61
|
+
nextAction: input.patch.nextAction !== undefined ? input.patch.nextAction : presentation.nextAction,
|
|
62
|
+
parentTaskId: input.patch.parentTaskId ?? existing?.parentTaskId ?? presentation.parentTaskId,
|
|
63
|
+
};
|
|
64
|
+
if (!next.completedAt && (next.status === "succeeded" || next.status === "failed")) {
|
|
65
|
+
next.completedAt = now;
|
|
66
|
+
}
|
|
67
|
+
await mkdir(taskRegistryDir(input.workdir, input.agentName), { recursive: true });
|
|
68
|
+
await writeFile(taskRegistryRecordPath(input.workdir, input.agentName, next.taskId), `${JSON.stringify(redactSecrets(next), null, 2)}\n`, "utf-8");
|
|
69
|
+
return next;
|
|
70
|
+
}
|
|
71
|
+
export async function upsertTaskRegistryRecordFromEvent(input) {
|
|
72
|
+
return upsertTaskRegistryRecord({
|
|
73
|
+
workdir: input.workdir,
|
|
74
|
+
agentName: input.agentName,
|
|
75
|
+
now: input.event.createdAt,
|
|
76
|
+
patch: {
|
|
77
|
+
taskId: input.event.taskId,
|
|
78
|
+
status: input.event.status,
|
|
79
|
+
stage: input.event.stage || inferTaskStageFromEvent(input.event),
|
|
80
|
+
objective: taskObjectiveFromEvent(input.event),
|
|
81
|
+
route: input.event.route,
|
|
82
|
+
summary: input.event.summary,
|
|
83
|
+
conversationId: input.event.conversationId,
|
|
84
|
+
refs: input.event.refs,
|
|
85
|
+
source: taskSourceFromEvent(input.event),
|
|
86
|
+
data: input.event.data,
|
|
87
|
+
completedAt: input.event.phase === "completed" || input.event.phase === "failed"
|
|
88
|
+
? input.event.createdAt
|
|
89
|
+
: undefined,
|
|
90
|
+
},
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
export async function readTaskRegistryRecord(input) {
|
|
94
|
+
try {
|
|
95
|
+
const raw = await readFile(taskRegistryRecordPath(input.workdir, input.agentName, input.taskId), "utf-8");
|
|
96
|
+
return normalizeTaskRegistryRecord(JSON.parse(raw));
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
export async function listTaskRegistryRecords(input) {
|
|
103
|
+
const limit = normalizeLimit(input.limit);
|
|
104
|
+
try {
|
|
105
|
+
const dir = taskRegistryDir(input.workdir, input.agentName);
|
|
106
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
107
|
+
const records = await Promise.all(entries
|
|
108
|
+
.filter((entry) => entry.isFile() && entry.name.endsWith(".json"))
|
|
109
|
+
.map(async (entry) => {
|
|
110
|
+
try {
|
|
111
|
+
const raw = await readFile(join(dir, entry.name), "utf-8");
|
|
112
|
+
return normalizeTaskRegistryRecord(JSON.parse(raw));
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
}));
|
|
118
|
+
return records
|
|
119
|
+
.filter((record) => !!record)
|
|
120
|
+
.filter((record) => !input.status || record.status === input.status)
|
|
121
|
+
.filter((record) => !input.route || record.route === input.route)
|
|
122
|
+
.filter((record) => !input.visibility || record.visibility === input.visibility)
|
|
123
|
+
.filter((record) => !input.parentTaskId || record.parentTaskId === input.parentTaskId)
|
|
124
|
+
.sort((left, right) => right.updatedAt.localeCompare(left.updatedAt) || right.createdAt.localeCompare(left.createdAt))
|
|
125
|
+
.slice(0, limit);
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
return [];
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
const TASK_SURFACE_STAGES = [
|
|
132
|
+
"queued",
|
|
133
|
+
"framing",
|
|
134
|
+
"dispatching",
|
|
135
|
+
"executing",
|
|
136
|
+
"observing",
|
|
137
|
+
"reviewing",
|
|
138
|
+
"done",
|
|
139
|
+
"blocked",
|
|
140
|
+
];
|
|
141
|
+
export function createTaskSurfaceView(record) {
|
|
142
|
+
const dispatches = taskSurfaceDispatches(record.data);
|
|
143
|
+
const reviews = taskSurfaceReviews(record.data);
|
|
144
|
+
const followUps = taskSurfaceFollowUps(record.data);
|
|
145
|
+
const memoryProposalRefs = uniqueMemoryProposalRefs([
|
|
146
|
+
...reviews.flatMap((review) => review.memoryProposalRefs),
|
|
147
|
+
...taskSurfaceMemoryProposalRefs(record.data),
|
|
148
|
+
]);
|
|
149
|
+
const report = taskSurfaceReport(record, reviews);
|
|
150
|
+
const related = taskSurfaceRelated(record);
|
|
151
|
+
return {
|
|
152
|
+
schemaVersion: 1,
|
|
153
|
+
surface: "task_lifecycle",
|
|
154
|
+
taskId: record.taskId,
|
|
155
|
+
title: record.title || record.objective || record.taskId,
|
|
156
|
+
kind: record.kind,
|
|
157
|
+
visibility: record.visibility,
|
|
158
|
+
route: record.route,
|
|
159
|
+
status: record.status,
|
|
160
|
+
stage: {
|
|
161
|
+
key: record.stage,
|
|
162
|
+
label: ownerStatusFromTaskStage(record.stage),
|
|
163
|
+
description: taskStageDescription(record.stage, record.summary || record.nextAction),
|
|
164
|
+
state: record.stage === "blocked" ? "blocked" : record.stage === "done" ? "done" : "active",
|
|
165
|
+
},
|
|
166
|
+
ownerStatus: record.ownerStatus,
|
|
167
|
+
summary: record.summary || report?.text,
|
|
168
|
+
nextAction: record.nextAction,
|
|
169
|
+
conversationId: record.conversationId,
|
|
170
|
+
parentTaskId: record.parentTaskId,
|
|
171
|
+
timestamps: {
|
|
172
|
+
createdAt: record.createdAt,
|
|
173
|
+
updatedAt: record.updatedAt,
|
|
174
|
+
completedAt: record.completedAt,
|
|
175
|
+
},
|
|
176
|
+
lifecycle: taskSurfaceLifecycle(record.stage),
|
|
177
|
+
counts: {
|
|
178
|
+
dispatches: dispatches.length,
|
|
179
|
+
reviews: reviews.length,
|
|
180
|
+
followUps: followUps.length,
|
|
181
|
+
memoryProposals: memoryProposalRefs.length,
|
|
182
|
+
},
|
|
183
|
+
related,
|
|
184
|
+
dispatches,
|
|
185
|
+
reviews,
|
|
186
|
+
followUps,
|
|
187
|
+
report,
|
|
188
|
+
sections: taskSurfaceSections({
|
|
189
|
+
record,
|
|
190
|
+
related,
|
|
191
|
+
dispatches,
|
|
192
|
+
reviews,
|
|
193
|
+
followUps,
|
|
194
|
+
report,
|
|
195
|
+
}),
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
function taskSurfaceLifecycle(currentStage) {
|
|
199
|
+
const currentIndex = TASK_SURFACE_STAGES.indexOf(currentStage);
|
|
200
|
+
return TASK_SURFACE_STAGES.map((stage, index) => {
|
|
201
|
+
let state = "pending";
|
|
202
|
+
if (currentStage === "blocked") {
|
|
203
|
+
state = stage === "blocked" ? "blocked" : index < TASK_SURFACE_STAGES.length - 1 ? "done" : "pending";
|
|
204
|
+
}
|
|
205
|
+
else if (currentStage === "done") {
|
|
206
|
+
state = stage === "blocked" ? "pending" : "done";
|
|
207
|
+
}
|
|
208
|
+
else if (index < currentIndex) {
|
|
209
|
+
state = "done";
|
|
210
|
+
}
|
|
211
|
+
else if (index === currentIndex) {
|
|
212
|
+
state = "active";
|
|
213
|
+
}
|
|
214
|
+
return {
|
|
215
|
+
key: stage,
|
|
216
|
+
label: ownerStatusFromTaskStage(stage),
|
|
217
|
+
state,
|
|
218
|
+
};
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
function taskStageDescription(stage, summary) {
|
|
222
|
+
const base = (() => {
|
|
223
|
+
if (stage === "queued")
|
|
224
|
+
return "Queued and waiting to start.";
|
|
225
|
+
if (stage === "framing")
|
|
226
|
+
return "Akemon is framing the owner request.";
|
|
227
|
+
if (stage === "dispatching")
|
|
228
|
+
return "Akemon is dispatching work to a capability.";
|
|
229
|
+
if (stage === "executing")
|
|
230
|
+
return "Execution is in progress.";
|
|
231
|
+
if (stage === "observing")
|
|
232
|
+
return "Akemon is observing the execution result.";
|
|
233
|
+
if (stage === "reviewing")
|
|
234
|
+
return "Akemon is reviewing the result for the owner report.";
|
|
235
|
+
if (stage === "done")
|
|
236
|
+
return "Finished.";
|
|
237
|
+
if (stage === "blocked")
|
|
238
|
+
return "Blocked.";
|
|
239
|
+
return "Queued.";
|
|
240
|
+
})();
|
|
241
|
+
return summary && summary !== base ? `${base} ${summary}` : base;
|
|
242
|
+
}
|
|
243
|
+
function taskSurfaceDispatches(data) {
|
|
244
|
+
const records = Array.isArray(data?.dispatchRecords) ? data.dispatchRecords : [];
|
|
245
|
+
return records.flatMap((item) => {
|
|
246
|
+
if (!item || typeof item !== "object")
|
|
247
|
+
return [];
|
|
248
|
+
const record = item;
|
|
249
|
+
const targetPeripheral = readObjectString(record, "targetPeripheral");
|
|
250
|
+
const capability = readObjectString(record, "capability");
|
|
251
|
+
if (!targetPeripheral && !capability)
|
|
252
|
+
return [];
|
|
253
|
+
return [{
|
|
254
|
+
id: readObjectString(record, "id"),
|
|
255
|
+
targetPeripheral: targetPeripheral || "unknown",
|
|
256
|
+
capability: capability || "unknown",
|
|
257
|
+
brief: readObjectString(record, "brief"),
|
|
258
|
+
expectedDeliverable: readObjectString(record, "expectedDeliverable"),
|
|
259
|
+
createdAt: readObjectString(record, "createdAt"),
|
|
260
|
+
}];
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
function taskSurfaceReviews(data) {
|
|
264
|
+
const records = Array.isArray(data?.reviewRecords) ? data.reviewRecords : [];
|
|
265
|
+
return records.flatMap((item) => {
|
|
266
|
+
if (!item || typeof item !== "object")
|
|
267
|
+
return [];
|
|
268
|
+
const record = item;
|
|
269
|
+
const reportText = readObjectString(record, "reportText");
|
|
270
|
+
const resultQuality = readObjectString(record, "resultQuality");
|
|
271
|
+
const completionDecision = readObjectString(record, "completionDecision");
|
|
272
|
+
if (!reportText && !resultQuality && !completionDecision)
|
|
273
|
+
return [];
|
|
274
|
+
return [{
|
|
275
|
+
id: readObjectString(record, "id"),
|
|
276
|
+
resultQuality,
|
|
277
|
+
completionDecision,
|
|
278
|
+
followUpNeeded: record.followUpNeeded === true,
|
|
279
|
+
summary: readObjectString(record, "summary"),
|
|
280
|
+
reportText,
|
|
281
|
+
createdAt: readObjectString(record, "createdAt"),
|
|
282
|
+
memoryProposalRefs: normalizeSurfaceMemoryProposalRefs(record.memoryProposalRefs),
|
|
283
|
+
}];
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
function taskSurfaceFollowUps(data) {
|
|
287
|
+
const records = Array.isArray(data?.followUps) ? data.followUps : [];
|
|
288
|
+
return records.flatMap((item) => {
|
|
289
|
+
if (!item || typeof item !== "object")
|
|
290
|
+
return [];
|
|
291
|
+
const record = item;
|
|
292
|
+
const text = readObjectString(record, "text");
|
|
293
|
+
if (!text)
|
|
294
|
+
return [];
|
|
295
|
+
return [{
|
|
296
|
+
id: readObjectString(record, "id"),
|
|
297
|
+
kind: readObjectString(record, "kind") || "note",
|
|
298
|
+
text,
|
|
299
|
+
createdAt: readObjectString(record, "createdAt"),
|
|
300
|
+
status: readObjectString(record, "status") || "queued",
|
|
301
|
+
absorbedAt: readObjectString(record, "absorbedAt"),
|
|
302
|
+
}];
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
function taskSurfaceMemoryProposalRefs(data) {
|
|
306
|
+
const refs = normalizeSurfaceMemoryProposalRefs(data?.memoryProposalRefs);
|
|
307
|
+
const memoryProposalId = readRecordString(data, "memoryProposalId");
|
|
308
|
+
if (!memoryProposalId)
|
|
309
|
+
return refs;
|
|
310
|
+
return uniqueMemoryProposalRefs([
|
|
311
|
+
...refs,
|
|
312
|
+
{ memoryProposalId },
|
|
313
|
+
]);
|
|
314
|
+
}
|
|
315
|
+
function taskSurfaceReport(record, reviews) {
|
|
316
|
+
const latestReview = reviews[reviews.length - 1];
|
|
317
|
+
const text = latestReview?.reportText || readRecordString(record.data, "reportText");
|
|
318
|
+
if (!text)
|
|
319
|
+
return undefined;
|
|
320
|
+
return {
|
|
321
|
+
text,
|
|
322
|
+
resultQuality: latestReview?.resultQuality || readRecordString(record.data, "latestReviewQuality"),
|
|
323
|
+
completionDecision: latestReview?.completionDecision || readRecordString(record.data, "latestCompletionDecision"),
|
|
324
|
+
followUpNeeded: typeof record.data?.followUpNeeded === "boolean"
|
|
325
|
+
? record.data.followUpNeeded
|
|
326
|
+
: latestReview?.followUpNeeded,
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
function taskSurfaceRelated(record) {
|
|
330
|
+
const related = [];
|
|
331
|
+
if (record.conversationId) {
|
|
332
|
+
related.push({ kind: "conversation", id: record.conversationId, label: `Conversation ${shortTaskId(record.conversationId)}` });
|
|
333
|
+
}
|
|
334
|
+
const orderId = readRecordString(record.data, "orderId");
|
|
335
|
+
const productName = readRecordString(record.data, "productName");
|
|
336
|
+
const productId = readRecordString(record.data, "productId");
|
|
337
|
+
if (orderId)
|
|
338
|
+
related.push({ kind: "order", id: orderId, label: `Order ${shortTaskId(orderId)}` });
|
|
339
|
+
if (productName || productId) {
|
|
340
|
+
related.push({
|
|
341
|
+
kind: "product",
|
|
342
|
+
id: productId,
|
|
343
|
+
label: productName || `Product ${shortTaskId(productId || "")}`,
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
const sessionId = readRecordString(record.data, "sessionId");
|
|
347
|
+
if (sessionId)
|
|
348
|
+
related.push({ kind: "interactive_session", id: sessionId, label: `Interactive session ${shortTaskId(sessionId)}` });
|
|
349
|
+
const softwareAgentId = readRecordString(record.data, "softwareAgentId");
|
|
350
|
+
if (softwareAgentId)
|
|
351
|
+
related.push({ kind: "software_agent", id: softwareAgentId, label: prettifyTaskToken(softwareAgentId) });
|
|
352
|
+
const taskKey = readRecordString(record.data, "taskKey");
|
|
353
|
+
if (taskKey)
|
|
354
|
+
related.push({ kind: "task_key", id: taskKey, label: taskKey });
|
|
355
|
+
return related;
|
|
356
|
+
}
|
|
357
|
+
function taskSurfaceSections(input) {
|
|
358
|
+
const sections = [{
|
|
359
|
+
kind: "state",
|
|
360
|
+
title: "Current state",
|
|
361
|
+
text: taskStageDescription(input.record.stage, input.record.summary || input.record.nextAction),
|
|
362
|
+
}];
|
|
363
|
+
if (input.related.length) {
|
|
364
|
+
sections.push({
|
|
365
|
+
kind: "related",
|
|
366
|
+
title: "Related",
|
|
367
|
+
text: input.related.map((item) => item.label).join("\n"),
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
if (input.dispatches.length) {
|
|
371
|
+
sections.push({
|
|
372
|
+
kind: "dispatch",
|
|
373
|
+
title: "Dispatch records",
|
|
374
|
+
text: input.dispatches.slice(-5).map((record) => {
|
|
375
|
+
const head = [record.targetPeripheral, record.capability].filter(Boolean).join(".");
|
|
376
|
+
return [head, record.brief, record.expectedDeliverable].filter(Boolean).join("\n");
|
|
377
|
+
}).join("\n\n"),
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
if (input.reviews.length) {
|
|
381
|
+
sections.push({
|
|
382
|
+
kind: "review",
|
|
383
|
+
title: "Review records",
|
|
384
|
+
text: input.reviews.slice(-5).map((record) => {
|
|
385
|
+
const decision = [
|
|
386
|
+
record.resultQuality,
|
|
387
|
+
record.completionDecision,
|
|
388
|
+
record.followUpNeeded ? "follow-up needed" : "",
|
|
389
|
+
].filter(Boolean).join(" / ");
|
|
390
|
+
const memoryRefs = record.memoryProposalRefs.length
|
|
391
|
+
? `Memory proposals: ${record.memoryProposalRefs.map((item) => item.memoryProposalId).join(", ")}`
|
|
392
|
+
: "";
|
|
393
|
+
return [decision, record.summary, memoryRefs, record.reportText].filter(Boolean).join("\n");
|
|
394
|
+
}).join("\n\n"),
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
if (input.followUps.length) {
|
|
398
|
+
sections.push({
|
|
399
|
+
kind: "follow_up",
|
|
400
|
+
title: "Follow-ups",
|
|
401
|
+
text: input.followUps.slice(-5).map((item) => {
|
|
402
|
+
const status = item.status || "queued";
|
|
403
|
+
return `${prettifyTaskToken(item.kind)} (${prettifyTaskToken(status)}): ${item.text}`;
|
|
404
|
+
}).join("\n"),
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
if (input.report?.text) {
|
|
408
|
+
sections.push({
|
|
409
|
+
kind: "report",
|
|
410
|
+
title: "Report",
|
|
411
|
+
text: input.report.text,
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
else if (input.record.status === "failed" || input.record.status === "succeeded") {
|
|
415
|
+
sections.push({
|
|
416
|
+
kind: "result",
|
|
417
|
+
title: "Result",
|
|
418
|
+
text: input.record.summary || input.record.objective,
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
return sections;
|
|
422
|
+
}
|
|
423
|
+
function normalizeSurfaceMemoryProposalRefs(value) {
|
|
424
|
+
if (!Array.isArray(value))
|
|
425
|
+
return [];
|
|
426
|
+
return uniqueMemoryProposalRefs(value.flatMap((item) => {
|
|
427
|
+
if (!item || typeof item !== "object")
|
|
428
|
+
return [];
|
|
429
|
+
const record = item;
|
|
430
|
+
const memoryProposalId = readObjectString(record, "memoryProposalId");
|
|
431
|
+
if (!memoryProposalId)
|
|
432
|
+
return [];
|
|
433
|
+
return [{
|
|
434
|
+
memoryProposalId,
|
|
435
|
+
source: readObjectString(record, "source"),
|
|
436
|
+
summary: readObjectString(record, "summary"),
|
|
437
|
+
}];
|
|
438
|
+
}));
|
|
439
|
+
}
|
|
440
|
+
function uniqueMemoryProposalRefs(refs) {
|
|
441
|
+
const seen = new Set();
|
|
442
|
+
return refs.filter((ref) => {
|
|
443
|
+
if (!ref.memoryProposalId || seen.has(ref.memoryProposalId))
|
|
444
|
+
return false;
|
|
445
|
+
seen.add(ref.memoryProposalId);
|
|
446
|
+
return true;
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
function taskObjectiveFromEvent(event) {
|
|
450
|
+
const data = event.data || {};
|
|
451
|
+
const goal = data.goal;
|
|
452
|
+
if (typeof goal === "string" && goal.trim())
|
|
453
|
+
return goal.trim();
|
|
454
|
+
const objective = data.objective;
|
|
455
|
+
if (typeof objective === "string" && objective.trim())
|
|
456
|
+
return objective.trim();
|
|
457
|
+
return event.summary || event.taskId;
|
|
458
|
+
}
|
|
459
|
+
function taskSourceFromEvent(event) {
|
|
460
|
+
const source = event.data?.source;
|
|
461
|
+
if (typeof source === "string" && source.trim())
|
|
462
|
+
return source.trim();
|
|
463
|
+
if (event.route)
|
|
464
|
+
return event.route;
|
|
465
|
+
return DEFAULT_TASK_SOURCE;
|
|
466
|
+
}
|
|
467
|
+
function normalizeTaskRegistryRecord(value) {
|
|
468
|
+
if (!value || typeof value !== "object")
|
|
469
|
+
return null;
|
|
470
|
+
const record = value;
|
|
471
|
+
if (record.schemaVersion !== 1 || typeof record.taskId !== "string" || !record.taskId)
|
|
472
|
+
return null;
|
|
473
|
+
const data = record.data && typeof record.data === "object" && !Array.isArray(record.data)
|
|
474
|
+
? record.data
|
|
475
|
+
: undefined;
|
|
476
|
+
const normalizedStatus = isTaskRegistryStatus(record.status) ? record.status : "pending";
|
|
477
|
+
const normalizedStage = isTaskRegistryStage(record.stage) ? record.stage : inferTaskStageFromStatus(normalizedStatus);
|
|
478
|
+
const normalizedSource = typeof record.source === "string" && record.source ? record.source : DEFAULT_TASK_SOURCE;
|
|
479
|
+
const normalizedObjective = typeof record.objective === "string" && record.objective ? record.objective : record.taskId;
|
|
480
|
+
const presentation = deriveTaskPresentation({
|
|
481
|
+
taskId: record.taskId,
|
|
482
|
+
source: normalizedSource,
|
|
483
|
+
status: normalizedStatus,
|
|
484
|
+
stage: normalizedStage,
|
|
485
|
+
objective: normalizedObjective,
|
|
486
|
+
kind: record.kind,
|
|
487
|
+
visibility: record.visibility,
|
|
488
|
+
title: record.title,
|
|
489
|
+
ownerStatus: record.ownerStatus,
|
|
490
|
+
nextAction: record.nextAction,
|
|
491
|
+
parentTaskId: record.parentTaskId,
|
|
492
|
+
route: typeof record.route === "string" ? record.route : undefined,
|
|
493
|
+
summary: typeof record.summary === "string" ? record.summary : undefined,
|
|
494
|
+
data,
|
|
495
|
+
});
|
|
496
|
+
return {
|
|
497
|
+
schemaVersion: 1,
|
|
498
|
+
taskId: record.taskId,
|
|
499
|
+
source: normalizedSource,
|
|
500
|
+
status: normalizedStatus,
|
|
501
|
+
stage: normalizedStage,
|
|
502
|
+
objective: normalizedObjective,
|
|
503
|
+
kind: isTaskRegistryKind(record.kind) ? record.kind : presentation.kind,
|
|
504
|
+
visibility: isTaskRegistryVisibility(record.visibility) ? record.visibility : presentation.visibility,
|
|
505
|
+
title: typeof record.title === "string" && record.title ? record.title : presentation.title,
|
|
506
|
+
ownerStatus: typeof record.ownerStatus === "string" && record.ownerStatus ? record.ownerStatus : presentation.ownerStatus,
|
|
507
|
+
nextAction: typeof record.nextAction === "string" ? record.nextAction : presentation.nextAction,
|
|
508
|
+
parentTaskId: typeof record.parentTaskId === "string" ? record.parentTaskId : presentation.parentTaskId,
|
|
509
|
+
route: typeof record.route === "string" ? record.route : undefined,
|
|
510
|
+
summary: typeof record.summary === "string" ? record.summary : undefined,
|
|
511
|
+
conversationId: typeof record.conversationId === "string" ? record.conversationId : undefined,
|
|
512
|
+
refs: Array.isArray(record.refs) ? record.refs : undefined,
|
|
513
|
+
data,
|
|
514
|
+
createdAt: typeof record.createdAt === "string" ? record.createdAt : new Date(0).toISOString(),
|
|
515
|
+
updatedAt: typeof record.updatedAt === "string" ? record.updatedAt : new Date(0).toISOString(),
|
|
516
|
+
completedAt: typeof record.completedAt === "string" ? record.completedAt : undefined,
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
function deriveTaskPresentation(record) {
|
|
520
|
+
const kind = isTaskRegistryKind(record.kind) ? record.kind : inferTaskKind(record);
|
|
521
|
+
const capability = readRecordString(record.data, "capability");
|
|
522
|
+
const visibility = isTaskRegistryVisibility(record.visibility)
|
|
523
|
+
? record.visibility
|
|
524
|
+
: inferTaskVisibility(record, kind, capability);
|
|
525
|
+
return {
|
|
526
|
+
kind,
|
|
527
|
+
visibility,
|
|
528
|
+
title: record.title || inferTaskTitle(record, kind, capability),
|
|
529
|
+
ownerStatus: record.ownerStatus || ownerStatusFromTaskStage(record.stage),
|
|
530
|
+
nextAction: record.nextAction ?? inferTaskNextAction(record),
|
|
531
|
+
parentTaskId: record.parentTaskId || inferTaskParentId(record, visibility),
|
|
532
|
+
};
|
|
533
|
+
}
|
|
534
|
+
function inferTaskKind(record) {
|
|
535
|
+
const route = record.route || "";
|
|
536
|
+
const source = record.source || readRecordString(record.data, "source") || "";
|
|
537
|
+
if (route === "interactive-session" || source === "workbench_http")
|
|
538
|
+
return "terminal";
|
|
539
|
+
if (route === "software-agent")
|
|
540
|
+
return "software_agent";
|
|
541
|
+
if (route === "relay-order" || source === "relay_order")
|
|
542
|
+
return "order";
|
|
543
|
+
if (route === "user-task" || source === "owner_recurring_task")
|
|
544
|
+
return "scheduled_task";
|
|
545
|
+
if (route === "relay-task" || source === "relay_platform_task")
|
|
546
|
+
return "relay_task";
|
|
547
|
+
if (route === "memory_cm")
|
|
548
|
+
return "memory";
|
|
549
|
+
if (route === "core_cm" || source === "local_chat" || source === "local_chat_command")
|
|
550
|
+
return "chat";
|
|
551
|
+
return "unknown";
|
|
552
|
+
}
|
|
553
|
+
function inferTaskVisibility(record, kind, capability) {
|
|
554
|
+
const source = record.source || readRecordString(record.data, "source") || "";
|
|
555
|
+
if (kind === "terminal" && source === "workbench_http")
|
|
556
|
+
return "technical";
|
|
557
|
+
if (kind === "terminal" && capability && ["resize_session", "send_input", "inspect_session", "set_input_mode", "list_sessions"].includes(capability)) {
|
|
558
|
+
return "technical";
|
|
559
|
+
}
|
|
560
|
+
return "primary";
|
|
561
|
+
}
|
|
562
|
+
function inferTaskTitle(record, kind, capability) {
|
|
563
|
+
if (kind === "terminal" && capability)
|
|
564
|
+
return terminalCapabilityTitle(capability, record.data);
|
|
565
|
+
const productName = readRecordString(record.data, "productName");
|
|
566
|
+
const orderId = readRecordString(record.data, "orderId");
|
|
567
|
+
if (productName)
|
|
568
|
+
return orderId ? `Order: ${productName}` : productName;
|
|
569
|
+
if (orderId)
|
|
570
|
+
return `Order ${shortTaskId(orderId)}`;
|
|
571
|
+
const goal = readRecordString(record.data, "goal");
|
|
572
|
+
if (goal)
|
|
573
|
+
return goal;
|
|
574
|
+
return record.objective || record.summary || record.taskId;
|
|
575
|
+
}
|
|
576
|
+
function inferTaskNextAction(record) {
|
|
577
|
+
if (record.stage === "queued")
|
|
578
|
+
return "Waiting to start";
|
|
579
|
+
if (record.stage === "framing")
|
|
580
|
+
return "Akemon is framing the request";
|
|
581
|
+
if (record.stage === "dispatching")
|
|
582
|
+
return "Dispatching to a capability";
|
|
583
|
+
if (record.stage === "executing")
|
|
584
|
+
return "Waiting for execution";
|
|
585
|
+
if (record.stage === "observing")
|
|
586
|
+
return "Observing execution result";
|
|
587
|
+
if (record.stage === "reviewing")
|
|
588
|
+
return "Reviewing result for report";
|
|
589
|
+
if (record.stage === "blocked")
|
|
590
|
+
return "Review blocked state";
|
|
591
|
+
if (record.status === "waiting") {
|
|
592
|
+
const nextRetryAt = readRecordString(record.data, "nextRetryAt");
|
|
593
|
+
return nextRetryAt ? `Retry at ${nextRetryAt}` : "Waiting for retry or external progress";
|
|
594
|
+
}
|
|
595
|
+
if (record.status === "pending")
|
|
596
|
+
return "Waiting to start";
|
|
597
|
+
if (record.status === "running")
|
|
598
|
+
return "Wait for completion";
|
|
599
|
+
if (record.status === "failed")
|
|
600
|
+
return "Review error";
|
|
601
|
+
return undefined;
|
|
602
|
+
}
|
|
603
|
+
function inferTaskParentId(record, visibility) {
|
|
604
|
+
if (visibility !== "technical")
|
|
605
|
+
return undefined;
|
|
606
|
+
const sessionId = readRecordString(record.data, "sessionId");
|
|
607
|
+
return sessionId ? `interactive-session:${sessionId}` : undefined;
|
|
608
|
+
}
|
|
609
|
+
function ownerStatusFromTaskStage(stage) {
|
|
610
|
+
if (stage === "queued")
|
|
611
|
+
return "Queued";
|
|
612
|
+
if (stage === "framing")
|
|
613
|
+
return "Framing";
|
|
614
|
+
if (stage === "dispatching")
|
|
615
|
+
return "Dispatching";
|
|
616
|
+
if (stage === "executing")
|
|
617
|
+
return "Executing";
|
|
618
|
+
if (stage === "observing")
|
|
619
|
+
return "Observing";
|
|
620
|
+
if (stage === "reviewing")
|
|
621
|
+
return "Reviewing";
|
|
622
|
+
if (stage === "done")
|
|
623
|
+
return "Done";
|
|
624
|
+
if (stage === "blocked")
|
|
625
|
+
return "Blocked";
|
|
626
|
+
return "Queued";
|
|
627
|
+
}
|
|
628
|
+
function terminalCapabilityTitle(capability, data) {
|
|
629
|
+
const sessionId = readRecordString(data, "sessionId");
|
|
630
|
+
const suffix = sessionId ? ` ${shortTaskId(sessionId)}` : "";
|
|
631
|
+
switch (capability) {
|
|
632
|
+
case "start_session": return `Start terminal${suffix}`;
|
|
633
|
+
case "send_input": return `Send terminal input${suffix}`;
|
|
634
|
+
case "resize_session": return `Resize terminal${suffix}`;
|
|
635
|
+
case "stop_session": return `Stop terminal${suffix}`;
|
|
636
|
+
case "inspect_session": return `Inspect terminal${suffix}`;
|
|
637
|
+
case "set_input_mode": return `Change terminal input mode${suffix}`;
|
|
638
|
+
case "list_sessions": return "List terminal sessions";
|
|
639
|
+
default: return `Terminal action ${capability}`;
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
function readRecordString(data, key) {
|
|
643
|
+
const value = data?.[key];
|
|
644
|
+
if (typeof value === "string" && value.trim())
|
|
645
|
+
return value.trim();
|
|
646
|
+
if (typeof value === "number" || typeof value === "boolean")
|
|
647
|
+
return String(value);
|
|
648
|
+
return undefined;
|
|
649
|
+
}
|
|
650
|
+
function readObjectString(data, key) {
|
|
651
|
+
const value = data[key];
|
|
652
|
+
if (typeof value === "string" && value.trim())
|
|
653
|
+
return value.trim();
|
|
654
|
+
if (typeof value === "number" || typeof value === "boolean")
|
|
655
|
+
return String(value);
|
|
656
|
+
return undefined;
|
|
657
|
+
}
|
|
658
|
+
function shortTaskId(value) {
|
|
659
|
+
if (value.length <= 18)
|
|
660
|
+
return value;
|
|
661
|
+
return `${value.slice(0, 8)}...${value.slice(-6)}`;
|
|
662
|
+
}
|
|
663
|
+
function prettifyTaskToken(value) {
|
|
664
|
+
return value
|
|
665
|
+
.replace(/[_-]+/g, " ")
|
|
666
|
+
.replace(/\s+/g, " ")
|
|
667
|
+
.trim()
|
|
668
|
+
.replace(/\b\w/g, (char) => char.toUpperCase());
|
|
669
|
+
}
|
|
670
|
+
function isTaskRegistryStatus(value) {
|
|
671
|
+
return value === "pending"
|
|
672
|
+
|| value === "running"
|
|
673
|
+
|| value === "waiting"
|
|
674
|
+
|| value === "succeeded"
|
|
675
|
+
|| value === "failed"
|
|
676
|
+
|| value === "info";
|
|
677
|
+
}
|
|
678
|
+
function isTaskRegistryStage(value) {
|
|
679
|
+
return value === "queued"
|
|
680
|
+
|| value === "framing"
|
|
681
|
+
|| value === "dispatching"
|
|
682
|
+
|| value === "executing"
|
|
683
|
+
|| value === "observing"
|
|
684
|
+
|| value === "reviewing"
|
|
685
|
+
|| value === "done"
|
|
686
|
+
|| value === "blocked";
|
|
687
|
+
}
|
|
688
|
+
function normalizeTaskRegistryStage(value) {
|
|
689
|
+
return isTaskRegistryStage(value) ? value : undefined;
|
|
690
|
+
}
|
|
691
|
+
function inferTaskStageFromEvent(event) {
|
|
692
|
+
if (event.status === "failed" || event.phase === "failed")
|
|
693
|
+
return "blocked";
|
|
694
|
+
if (event.status === "waiting")
|
|
695
|
+
return "blocked";
|
|
696
|
+
if (event.status === "succeeded" || event.status === "info" || event.phase === "completed")
|
|
697
|
+
return "done";
|
|
698
|
+
if (event.phase === "reported")
|
|
699
|
+
return "reviewing";
|
|
700
|
+
if (event.phase === "observed")
|
|
701
|
+
return "observing";
|
|
702
|
+
if (event.phase === "delegated")
|
|
703
|
+
return "dispatching";
|
|
704
|
+
if (event.phase === "routed")
|
|
705
|
+
return "framing";
|
|
706
|
+
return inferTaskStageFromStatus(event.status);
|
|
707
|
+
}
|
|
708
|
+
function inferTaskStageFromStatus(status) {
|
|
709
|
+
if (status === "pending")
|
|
710
|
+
return "queued";
|
|
711
|
+
if (status === "waiting" || status === "failed")
|
|
712
|
+
return "blocked";
|
|
713
|
+
if (status === "succeeded" || status === "info")
|
|
714
|
+
return "done";
|
|
715
|
+
if (status === "running")
|
|
716
|
+
return "executing";
|
|
717
|
+
return "queued";
|
|
718
|
+
}
|
|
719
|
+
function isTaskRegistryKind(value) {
|
|
720
|
+
return value === "chat"
|
|
721
|
+
|| value === "memory"
|
|
722
|
+
|| value === "order"
|
|
723
|
+
|| value === "relay_task"
|
|
724
|
+
|| value === "scheduled_task"
|
|
725
|
+
|| value === "software_agent"
|
|
726
|
+
|| value === "terminal"
|
|
727
|
+
|| value === "unknown";
|
|
728
|
+
}
|
|
729
|
+
function isTaskRegistryVisibility(value) {
|
|
730
|
+
return value === "primary" || value === "technical";
|
|
731
|
+
}
|
|
732
|
+
function mergeTaskData(existing, patch) {
|
|
733
|
+
if (!existing && !patch)
|
|
734
|
+
return undefined;
|
|
735
|
+
const merged = { ...(existing || {}), ...(patch || {}) };
|
|
736
|
+
if (typeof existing?.source === "string" && existing.source.trim()) {
|
|
737
|
+
merged.source = existing.source;
|
|
738
|
+
}
|
|
739
|
+
if (typeof existing?.objective === "string" && existing.objective.trim()) {
|
|
740
|
+
merged.objective = existing.objective;
|
|
741
|
+
}
|
|
742
|
+
return merged;
|
|
743
|
+
}
|
|
744
|
+
function normalizeLimit(value) {
|
|
745
|
+
if (!Number.isInteger(value) || !value || value <= 0)
|
|
746
|
+
return 50;
|
|
747
|
+
return Math.min(value, 200);
|
|
748
|
+
}
|
|
749
|
+
function safeTaskFilename(taskId) {
|
|
750
|
+
const safe = taskId
|
|
751
|
+
.replace(/[^A-Za-z0-9._:-]+/g, "_")
|
|
752
|
+
.replace(/^\.+/, "")
|
|
753
|
+
.replace(/[. ]+$/g, "")
|
|
754
|
+
.slice(0, 160);
|
|
755
|
+
return safe || "task";
|
|
756
|
+
}
|