@cjhyy/code-shell-pet 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +99 -0
- package/dist/capability.d.ts +15 -0
- package/dist/capability.js +251 -0
- package/dist/current-time.d.ts +5 -0
- package/dist/current-time.js +43 -0
- package/dist/delegate-work.d.ts +7 -0
- package/dist/delegate-work.js +142 -0
- package/dist/delegation.d.ts +33 -0
- package/dist/delegation.js +6 -0
- package/dist/disclosure/catalog.d.ts +22 -0
- package/dist/disclosure/catalog.js +104 -0
- package/dist/disclosure/constants.d.ts +7 -0
- package/dist/disclosure/constants.js +7 -0
- package/dist/disclosure/jsonl.d.ts +9 -0
- package/dist/disclosure/jsonl.js +61 -0
- package/dist/disclosure/latest-result.d.ts +8 -0
- package/dist/disclosure/latest-result.js +35 -0
- package/dist/disclosure/search.d.ts +21 -0
- package/dist/disclosure/search.js +124 -0
- package/dist/disclosure/selector.d.ts +2 -0
- package/dist/disclosure/selector.js +5 -0
- package/dist/disclosure/todo-snapshot.d.ts +8 -0
- package/dist/disclosure/todo-snapshot.js +54 -0
- package/dist/follow-ups.d.ts +21 -0
- package/dist/follow-ups.js +102 -0
- package/dist/gateway.d.ts +41 -0
- package/dist/gateway.js +263 -0
- package/dist/host-actions.d.ts +51 -0
- package/dist/host-actions.js +405 -0
- package/dist/index.capability.d.ts +7 -0
- package/dist/index.capability.js +7 -0
- package/dist/index.d.ts +32 -0
- package/dist/index.disclosure.d.ts +13 -0
- package/dist/index.disclosure.js +13 -0
- package/dist/index.js +32 -0
- package/dist/index.protocol.d.ts +8 -0
- package/dist/index.protocol.js +8 -0
- package/dist/index.team.d.ts +2 -0
- package/dist/index.team.js +2 -0
- package/dist/long-task.d.ts +251 -0
- package/dist/long-task.js +795 -0
- package/dist/mobile-remote.d.ts +14 -0
- package/dist/mobile-remote.js +39 -0
- package/dist/outbound-message.d.ts +15 -0
- package/dist/outbound-message.js +127 -0
- package/dist/pending-decision-index.d.ts +24 -0
- package/dist/pending-decision-index.js +110 -0
- package/dist/profile.d.ts +27 -0
- package/dist/profile.js +120 -0
- package/dist/projection-extension.d.ts +8 -0
- package/dist/projection-extension.js +238 -0
- package/dist/protocol.d.ts +58 -0
- package/dist/protocol.js +15 -0
- package/dist/report-to-mimi.d.ts +7 -0
- package/dist/report-to-mimi.js +87 -0
- package/dist/run-params.d.ts +34 -0
- package/dist/run-params.js +387 -0
- package/dist/segment-closure.d.ts +52 -0
- package/dist/segment-closure.js +130 -0
- package/dist/session-control.d.ts +5 -0
- package/dist/session-control.js +46 -0
- package/dist/session-index.d.ts +22 -0
- package/dist/session-index.js +324 -0
- package/dist/sessions-tool.d.ts +21 -0
- package/dist/sessions-tool.js +145 -0
- package/dist/team.d.ts +13 -0
- package/dist/team.js +38 -0
- package/dist/tool-arguments.d.ts +1 -0
- package/dist/tool-arguments.js +10 -0
- package/dist/topic-segment.d.ts +55 -0
- package/dist/topic-segment.js +37 -0
- package/dist/types.d.ts +95 -0
- package/dist/types.js +1 -0
- package/package.json +56 -0
|
@@ -0,0 +1,795 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Durable Pet long-task domain model.
|
|
3
|
+
*
|
|
4
|
+
* The package owns the state machine and the bounded context contract. Hosts
|
|
5
|
+
* own persistence, worker control, and UI transport. Keeping those effects out
|
|
6
|
+
* of this module lets Desktop and a future headless host share identical task
|
|
7
|
+
* semantics without teaching core about Pet.
|
|
8
|
+
*/
|
|
9
|
+
export const PET_LONG_TASK_SCHEMA_VERSION = 1;
|
|
10
|
+
export const MAX_PET_LONG_TASK_EVENTS = 120;
|
|
11
|
+
export const MAX_PET_LONG_TASK_SUMMARY_LENGTH = 8_000;
|
|
12
|
+
const TERMINAL = new Set(["completed", "failed", "cancelled"]);
|
|
13
|
+
function bounded(value, maximum) {
|
|
14
|
+
const normalized = value?.replace(/\s+/gu, " ").trim();
|
|
15
|
+
if (!normalized)
|
|
16
|
+
return undefined;
|
|
17
|
+
return normalized.length > maximum ? `${normalized.slice(0, maximum - 1)}…` : normalized;
|
|
18
|
+
}
|
|
19
|
+
function normalizeReplyAttachmentPaths(value) {
|
|
20
|
+
if (!Array.isArray(value) || value.length < 1 || value.length > 4)
|
|
21
|
+
return undefined;
|
|
22
|
+
const paths = [];
|
|
23
|
+
for (const entry of value) {
|
|
24
|
+
if (typeof entry !== "string")
|
|
25
|
+
return undefined;
|
|
26
|
+
const path = entry.trim();
|
|
27
|
+
if (!path || path.length > 4_096 || /[\u0000-\u001f\u007f]/u.test(path))
|
|
28
|
+
return undefined;
|
|
29
|
+
paths.push(path);
|
|
30
|
+
}
|
|
31
|
+
return new Set(paths).size === paths.length ? paths : undefined;
|
|
32
|
+
}
|
|
33
|
+
function normalizeReplyButton(value) {
|
|
34
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
35
|
+
return undefined;
|
|
36
|
+
const record = value;
|
|
37
|
+
if (Object.keys(record).some((key) => key !== "text" && key !== "url") ||
|
|
38
|
+
typeof record.text !== "string" ||
|
|
39
|
+
!record.text.trim() ||
|
|
40
|
+
record.text.length > 80 ||
|
|
41
|
+
/[\u0000-\u001f\u007f]/u.test(record.text) ||
|
|
42
|
+
typeof record.url !== "string" ||
|
|
43
|
+
record.url.length > 2_048 ||
|
|
44
|
+
record.url !== record.url.trim()) {
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|
|
47
|
+
try {
|
|
48
|
+
const url = new URL(record.url);
|
|
49
|
+
if (url.protocol !== "https:" && url.protocol !== "http:")
|
|
50
|
+
return undefined;
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
55
|
+
return { text: record.text.trim(), url: record.url };
|
|
56
|
+
}
|
|
57
|
+
function normalizeCompletionTarget(value) {
|
|
58
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
59
|
+
return undefined;
|
|
60
|
+
const record = value;
|
|
61
|
+
const channel = typeof record.channel === "string" ? record.channel.trim().toLowerCase() : "";
|
|
62
|
+
const target = typeof record.target === "string" ? record.target.trim() : "";
|
|
63
|
+
if (record.kind !== "im-gateway" ||
|
|
64
|
+
!/^[a-z0-9_-]{1,32}$/u.test(channel) ||
|
|
65
|
+
!target ||
|
|
66
|
+
target.length > 4_096 ||
|
|
67
|
+
/[\u0000-\u001f\u007f]/u.test(target)) {
|
|
68
|
+
return undefined;
|
|
69
|
+
}
|
|
70
|
+
const replyAttachmentKinds = Array.isArray(record.replyAttachmentKinds) &&
|
|
71
|
+
record.replyAttachmentKinds.length <= 4 &&
|
|
72
|
+
record.replyAttachmentKinds.every((kind) => ["image", "file", "audio", "video"].includes(String(kind)))
|
|
73
|
+
? [...new Set(record.replyAttachmentKinds)]
|
|
74
|
+
: [];
|
|
75
|
+
const replyButton = record.replyButton === "native" || record.replyButton === "link"
|
|
76
|
+
? record.replyButton
|
|
77
|
+
: undefined;
|
|
78
|
+
return {
|
|
79
|
+
kind: "im-gateway",
|
|
80
|
+
channel,
|
|
81
|
+
target,
|
|
82
|
+
...(replyButton ? { replyButton } : {}),
|
|
83
|
+
...(replyAttachmentKinds.length > 0 ? { replyAttachmentKinds } : {}),
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
function normalizeArtifacts(value) {
|
|
87
|
+
if (!value)
|
|
88
|
+
return [];
|
|
89
|
+
const seen = new Set();
|
|
90
|
+
const artifacts = [];
|
|
91
|
+
for (const artifact of value) {
|
|
92
|
+
if (!artifact || !["session", "result", "file", "url"].includes(artifact.kind))
|
|
93
|
+
continue;
|
|
94
|
+
const label = bounded(artifact.label, 160);
|
|
95
|
+
const reference = bounded(artifact.reference, 2_048);
|
|
96
|
+
if (!label || !reference)
|
|
97
|
+
continue;
|
|
98
|
+
const key = `${artifact.kind}\u0000${reference}`;
|
|
99
|
+
if (seen.has(key))
|
|
100
|
+
continue;
|
|
101
|
+
seen.add(key);
|
|
102
|
+
artifacts.push({ kind: artifact.kind, label, reference });
|
|
103
|
+
if (artifacts.length >= 24)
|
|
104
|
+
break;
|
|
105
|
+
}
|
|
106
|
+
return artifacts;
|
|
107
|
+
}
|
|
108
|
+
function mergeArtifacts(current, incoming) {
|
|
109
|
+
return normalizeArtifacts([...current, ...(incoming ?? [])]);
|
|
110
|
+
}
|
|
111
|
+
function appendEvent(task, input) {
|
|
112
|
+
const sequence = (task.events.at(-1)?.sequence ?? 0) + 1;
|
|
113
|
+
const event = {
|
|
114
|
+
...input,
|
|
115
|
+
id: `${task.id}:${sequence}`,
|
|
116
|
+
sequence,
|
|
117
|
+
message: bounded(input.message, 2_000),
|
|
118
|
+
waitingFor: bounded(input.waitingFor, 500),
|
|
119
|
+
nextAction: bounded(input.nextAction, 500),
|
|
120
|
+
artifacts: input.artifacts ? normalizeArtifacts(input.artifacts) : undefined,
|
|
121
|
+
};
|
|
122
|
+
return [...task.events, event].slice(-MAX_PET_LONG_TASK_EVENTS);
|
|
123
|
+
}
|
|
124
|
+
export function createPetLongTask(input) {
|
|
125
|
+
const id = bounded(input.id, 128);
|
|
126
|
+
const originClientMessageId = bounded(input.originClientMessageId, 256);
|
|
127
|
+
const objective = bounded(input.objective, 8_000);
|
|
128
|
+
const sessionId = bounded(input.sessionId, 256);
|
|
129
|
+
if (!id || !originClientMessageId || !objective || !sessionId) {
|
|
130
|
+
throw new Error("invalid Pet long-task identity or objective");
|
|
131
|
+
}
|
|
132
|
+
const workspacePath = input.workspacePath ? bounded(input.workspacePath, 4_096) : null;
|
|
133
|
+
const completionTarget = normalizeCompletionTarget(input.completionTarget);
|
|
134
|
+
const continuationDepth = typeof input.continuationDepth === "number" &&
|
|
135
|
+
Number.isSafeInteger(input.continuationDepth) &&
|
|
136
|
+
input.continuationDepth > 0
|
|
137
|
+
? input.continuationDepth
|
|
138
|
+
: 0;
|
|
139
|
+
const created = {
|
|
140
|
+
id: `${id}:1`,
|
|
141
|
+
sequence: 1,
|
|
142
|
+
kind: "created",
|
|
143
|
+
at: input.at,
|
|
144
|
+
message: "Long-running work accepted and queued",
|
|
145
|
+
phase: "planning",
|
|
146
|
+
nextAction: "Start the work session",
|
|
147
|
+
};
|
|
148
|
+
return {
|
|
149
|
+
schemaVersion: PET_LONG_TASK_SCHEMA_VERSION,
|
|
150
|
+
id,
|
|
151
|
+
originClientMessageId,
|
|
152
|
+
objective,
|
|
153
|
+
workspacePath: workspacePath ?? null,
|
|
154
|
+
sessionId,
|
|
155
|
+
...(input.executionBackend === "codex" ? { executionBackend: "codex" } : {}),
|
|
156
|
+
verificationMode: input.verificationMode === "goal" ? "goal" : "turn",
|
|
157
|
+
...(completionTarget ? { completionTarget } : {}),
|
|
158
|
+
...(continuationDepth > 0 ? { continuationDepth } : {}),
|
|
159
|
+
status: "queued",
|
|
160
|
+
phase: "planning",
|
|
161
|
+
attempt: 1,
|
|
162
|
+
revision: 1,
|
|
163
|
+
createdAt: input.at,
|
|
164
|
+
updatedAt: input.at,
|
|
165
|
+
nextAction: "Start the work session",
|
|
166
|
+
artifacts: [{ kind: "session", label: "Work session", reference: sessionId }],
|
|
167
|
+
events: [created],
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Apply one durable transition. Terminal tasks only accept retry/cancel or a
|
|
172
|
+
* closure-delivery acknowledgement; late
|
|
173
|
+
* worker events are ignored so a stale completion cannot undo an explicit user
|
|
174
|
+
* action such as pause or cancel.
|
|
175
|
+
*/
|
|
176
|
+
export function transitionPetLongTask(current, transition) {
|
|
177
|
+
if (transition.kind === "closure-decided" && current.closureDecision?.key === transition.key) {
|
|
178
|
+
return current;
|
|
179
|
+
}
|
|
180
|
+
if (transition.kind === "continuation-started" &&
|
|
181
|
+
current.closureDecision?.key === transition.key &&
|
|
182
|
+
current.closureDecision.launch) {
|
|
183
|
+
return current;
|
|
184
|
+
}
|
|
185
|
+
// Stream and projection observations are handled on separate async paths.
|
|
186
|
+
// An older projection can therefore arrive after a newer assistant checkpoint;
|
|
187
|
+
// never let that stale progress erase the final result we will notify with.
|
|
188
|
+
if (transition.at < current.updatedAt &&
|
|
189
|
+
(transition.kind === "started" ||
|
|
190
|
+
transition.kind === "progress" ||
|
|
191
|
+
transition.kind === "waiting" ||
|
|
192
|
+
transition.kind === "resumed" ||
|
|
193
|
+
transition.kind === "checkpoint" ||
|
|
194
|
+
transition.kind === "interrupted" ||
|
|
195
|
+
transition.kind === "completed" ||
|
|
196
|
+
transition.kind === "failed" ||
|
|
197
|
+
transition.kind === "cancelled")) {
|
|
198
|
+
return current;
|
|
199
|
+
}
|
|
200
|
+
if (TERMINAL.has(current.status) &&
|
|
201
|
+
transition.kind !== "retrying" &&
|
|
202
|
+
transition.kind !== "cancelled" &&
|
|
203
|
+
transition.kind !== "closure-decided" &&
|
|
204
|
+
transition.kind !== "continuation-started" &&
|
|
205
|
+
transition.kind !== "closure-recorded") {
|
|
206
|
+
return current;
|
|
207
|
+
}
|
|
208
|
+
if (current.status === "cancelled" &&
|
|
209
|
+
transition.kind !== "retrying" &&
|
|
210
|
+
transition.kind !== "closure-decided" &&
|
|
211
|
+
transition.kind !== "continuation-started" &&
|
|
212
|
+
transition.kind !== "closure-recorded") {
|
|
213
|
+
return current;
|
|
214
|
+
}
|
|
215
|
+
const next = {
|
|
216
|
+
...current,
|
|
217
|
+
revision: current.revision + 1,
|
|
218
|
+
updatedAt: Math.max(current.updatedAt, transition.at),
|
|
219
|
+
events: current.events,
|
|
220
|
+
};
|
|
221
|
+
let event;
|
|
222
|
+
switch (transition.kind) {
|
|
223
|
+
case "started":
|
|
224
|
+
Object.assign(next, {
|
|
225
|
+
status: "running",
|
|
226
|
+
phase: "executing",
|
|
227
|
+
startedAt: current.startedAt ?? transition.at,
|
|
228
|
+
waitingFor: undefined,
|
|
229
|
+
nextAction: "Continue until the objective is verified complete",
|
|
230
|
+
lastError: undefined,
|
|
231
|
+
});
|
|
232
|
+
event = {
|
|
233
|
+
kind: transition.kind,
|
|
234
|
+
at: transition.at,
|
|
235
|
+
phase: "executing",
|
|
236
|
+
message: transition.message ?? "Work session started",
|
|
237
|
+
nextAction: next.nextAction,
|
|
238
|
+
};
|
|
239
|
+
break;
|
|
240
|
+
case "progress":
|
|
241
|
+
Object.assign(next, {
|
|
242
|
+
status: "running",
|
|
243
|
+
phase: transition.phase,
|
|
244
|
+
summary: bounded(transition.summary, MAX_PET_LONG_TASK_SUMMARY_LENGTH),
|
|
245
|
+
waitingFor: undefined,
|
|
246
|
+
nextAction: "Continue until the objective is verified complete",
|
|
247
|
+
});
|
|
248
|
+
event = {
|
|
249
|
+
kind: transition.kind,
|
|
250
|
+
at: transition.at,
|
|
251
|
+
phase: transition.phase,
|
|
252
|
+
message: next.summary,
|
|
253
|
+
nextAction: next.nextAction,
|
|
254
|
+
};
|
|
255
|
+
break;
|
|
256
|
+
case "waiting":
|
|
257
|
+
Object.assign(next, {
|
|
258
|
+
status: "waiting",
|
|
259
|
+
phase: "waiting-user",
|
|
260
|
+
waitingFor: bounded(transition.waitingFor, 500),
|
|
261
|
+
nextAction: "Open the work session and resolve the pending decision",
|
|
262
|
+
});
|
|
263
|
+
event = {
|
|
264
|
+
kind: transition.kind,
|
|
265
|
+
at: transition.at,
|
|
266
|
+
phase: "waiting-user",
|
|
267
|
+
message: transition.message,
|
|
268
|
+
waitingFor: next.waitingFor,
|
|
269
|
+
nextAction: next.nextAction,
|
|
270
|
+
};
|
|
271
|
+
break;
|
|
272
|
+
case "paused":
|
|
273
|
+
Object.assign(next, {
|
|
274
|
+
status: "paused",
|
|
275
|
+
phase: "waiting-worker",
|
|
276
|
+
waitingFor: bounded(transition.reason, 500) ?? "Paused by user",
|
|
277
|
+
nextAction: "Resume when ready",
|
|
278
|
+
});
|
|
279
|
+
event = {
|
|
280
|
+
kind: transition.kind,
|
|
281
|
+
at: transition.at,
|
|
282
|
+
phase: "waiting-worker",
|
|
283
|
+
message: next.waitingFor,
|
|
284
|
+
nextAction: next.nextAction,
|
|
285
|
+
};
|
|
286
|
+
break;
|
|
287
|
+
case "resumed":
|
|
288
|
+
Object.assign(next, {
|
|
289
|
+
status: "running",
|
|
290
|
+
phase: "executing",
|
|
291
|
+
waitingFor: undefined,
|
|
292
|
+
nextAction: "Continue from the latest durable checkpoint",
|
|
293
|
+
lastError: undefined,
|
|
294
|
+
});
|
|
295
|
+
event = {
|
|
296
|
+
kind: transition.kind,
|
|
297
|
+
at: transition.at,
|
|
298
|
+
phase: "executing",
|
|
299
|
+
message: transition.message ?? "Long-running work resumed",
|
|
300
|
+
nextAction: next.nextAction,
|
|
301
|
+
};
|
|
302
|
+
break;
|
|
303
|
+
case "retrying":
|
|
304
|
+
Object.assign(next, {
|
|
305
|
+
status: "queued",
|
|
306
|
+
phase: "planning",
|
|
307
|
+
attempt: current.attempt + 1,
|
|
308
|
+
// A retry is a new attempt in the same durable Session. Keeping the
|
|
309
|
+
// previous attempt's start time would let an old projection terminal
|
|
310
|
+
// pass the new attempt's time fence.
|
|
311
|
+
startedAt: undefined,
|
|
312
|
+
completedAt: undefined,
|
|
313
|
+
closureRecordedAt: undefined,
|
|
314
|
+
closureDecision: undefined,
|
|
315
|
+
waitingFor: undefined,
|
|
316
|
+
nextAction: "Retry from the existing work session and checkpoint",
|
|
317
|
+
lastError: undefined,
|
|
318
|
+
});
|
|
319
|
+
event = {
|
|
320
|
+
kind: transition.kind,
|
|
321
|
+
at: transition.at,
|
|
322
|
+
phase: "planning",
|
|
323
|
+
message: transition.reason ?? `Retry attempt ${next.attempt}`,
|
|
324
|
+
nextAction: next.nextAction,
|
|
325
|
+
};
|
|
326
|
+
break;
|
|
327
|
+
case "artifact":
|
|
328
|
+
Object.assign(next, {
|
|
329
|
+
artifacts: mergeArtifacts(current.artifacts, transition.artifacts),
|
|
330
|
+
});
|
|
331
|
+
event = {
|
|
332
|
+
kind: transition.kind,
|
|
333
|
+
at: transition.at,
|
|
334
|
+
phase: next.phase,
|
|
335
|
+
message: "Work session produced an artifact",
|
|
336
|
+
artifacts: transition.artifacts,
|
|
337
|
+
};
|
|
338
|
+
break;
|
|
339
|
+
case "checkpoint":
|
|
340
|
+
Object.assign(next, {
|
|
341
|
+
summary: bounded(transition.summary, MAX_PET_LONG_TASK_SUMMARY_LENGTH),
|
|
342
|
+
resultSummary: bounded(transition.summary, MAX_PET_LONG_TASK_SUMMARY_LENGTH),
|
|
343
|
+
nextAction: bounded(transition.nextAction, 500) ?? current.nextAction,
|
|
344
|
+
artifacts: mergeArtifacts(current.artifacts, transition.artifacts),
|
|
345
|
+
});
|
|
346
|
+
event = {
|
|
347
|
+
kind: transition.kind,
|
|
348
|
+
at: transition.at,
|
|
349
|
+
phase: next.phase,
|
|
350
|
+
message: next.summary,
|
|
351
|
+
nextAction: next.nextAction,
|
|
352
|
+
artifacts: transition.artifacts,
|
|
353
|
+
};
|
|
354
|
+
break;
|
|
355
|
+
case "verification-changed":
|
|
356
|
+
Object.assign(next, { verificationMode: transition.mode });
|
|
357
|
+
event = {
|
|
358
|
+
kind: transition.kind,
|
|
359
|
+
at: transition.at,
|
|
360
|
+
phase: next.phase,
|
|
361
|
+
message: transition.mode === "goal"
|
|
362
|
+
? "Completion now requires a verified Goal verdict"
|
|
363
|
+
: "Completion now follows the ordinary Work Session result",
|
|
364
|
+
};
|
|
365
|
+
break;
|
|
366
|
+
case "interrupted":
|
|
367
|
+
Object.assign(next, {
|
|
368
|
+
status: "interrupted",
|
|
369
|
+
phase: "waiting-worker",
|
|
370
|
+
waitingFor: bounded(transition.reason, 500),
|
|
371
|
+
nextAction: "Resume from the durable work session",
|
|
372
|
+
});
|
|
373
|
+
event = {
|
|
374
|
+
kind: transition.kind,
|
|
375
|
+
at: transition.at,
|
|
376
|
+
phase: "waiting-worker",
|
|
377
|
+
message: next.waitingFor,
|
|
378
|
+
nextAction: next.nextAction,
|
|
379
|
+
};
|
|
380
|
+
break;
|
|
381
|
+
case "completed": {
|
|
382
|
+
const resultSummary = bounded(transition.summary, MAX_PET_LONG_TASK_SUMMARY_LENGTH) ?? current.resultSummary;
|
|
383
|
+
Object.assign(next, {
|
|
384
|
+
status: "completed",
|
|
385
|
+
phase: "finalizing",
|
|
386
|
+
completedAt: transition.at,
|
|
387
|
+
...(resultSummary ? { resultSummary } : {}),
|
|
388
|
+
summary: resultSummary ?? current.summary ?? "Objective completed",
|
|
389
|
+
waitingFor: undefined,
|
|
390
|
+
nextAction: undefined,
|
|
391
|
+
lastError: undefined,
|
|
392
|
+
artifacts: mergeArtifacts(current.artifacts, transition.artifacts),
|
|
393
|
+
});
|
|
394
|
+
event = {
|
|
395
|
+
kind: transition.kind,
|
|
396
|
+
at: transition.at,
|
|
397
|
+
phase: "finalizing",
|
|
398
|
+
message: next.summary,
|
|
399
|
+
artifacts: transition.artifacts,
|
|
400
|
+
};
|
|
401
|
+
break;
|
|
402
|
+
}
|
|
403
|
+
case "failed":
|
|
404
|
+
Object.assign(next, {
|
|
405
|
+
status: "failed",
|
|
406
|
+
phase: "finalizing",
|
|
407
|
+
completedAt: transition.at,
|
|
408
|
+
summary: bounded(transition.summary, MAX_PET_LONG_TASK_SUMMARY_LENGTH) ?? current.summary,
|
|
409
|
+
waitingFor: undefined,
|
|
410
|
+
nextAction: "Inspect the failure, then retry or cancel",
|
|
411
|
+
lastError: bounded(transition.error, 2_000),
|
|
412
|
+
});
|
|
413
|
+
event = {
|
|
414
|
+
kind: transition.kind,
|
|
415
|
+
at: transition.at,
|
|
416
|
+
phase: "finalizing",
|
|
417
|
+
message: next.lastError,
|
|
418
|
+
nextAction: next.nextAction,
|
|
419
|
+
};
|
|
420
|
+
break;
|
|
421
|
+
case "cancelled":
|
|
422
|
+
Object.assign(next, {
|
|
423
|
+
status: "cancelled",
|
|
424
|
+
phase: "finalizing",
|
|
425
|
+
completedAt: transition.at,
|
|
426
|
+
waitingFor: undefined,
|
|
427
|
+
nextAction: undefined,
|
|
428
|
+
});
|
|
429
|
+
event = {
|
|
430
|
+
kind: transition.kind,
|
|
431
|
+
at: transition.at,
|
|
432
|
+
phase: "finalizing",
|
|
433
|
+
message: transition.reason ?? "Cancelled by user",
|
|
434
|
+
};
|
|
435
|
+
break;
|
|
436
|
+
case "closure-decided": {
|
|
437
|
+
const key = bounded(transition.key, 512);
|
|
438
|
+
const text = transition.text.trim().slice(0, MAX_PET_LONG_TASK_SUMMARY_LENGTH);
|
|
439
|
+
if (!key || !text)
|
|
440
|
+
return current;
|
|
441
|
+
const replyAttachmentPaths = normalizeReplyAttachmentPaths(transition.replyAttachmentPaths);
|
|
442
|
+
if (transition.replyAttachmentPaths !== undefined && !replyAttachmentPaths)
|
|
443
|
+
return current;
|
|
444
|
+
const replyButton = normalizeReplyButton(transition.replyButton);
|
|
445
|
+
if (transition.replyButton !== undefined && !replyButton)
|
|
446
|
+
return current;
|
|
447
|
+
const continuation = transition.continuation;
|
|
448
|
+
const normalizedContinuation = continuation
|
|
449
|
+
? {
|
|
450
|
+
clientMessageId: continuation.clientMessageId.slice(0, 256),
|
|
451
|
+
objective: continuation.objective.trim().slice(0, 8_000),
|
|
452
|
+
workspacePath: typeof continuation.workspacePath === "string"
|
|
453
|
+
? continuation.workspacePath.slice(0, 4_096)
|
|
454
|
+
: null,
|
|
455
|
+
...(continuation.executionBackend === "codex"
|
|
456
|
+
? { executionBackend: "codex" }
|
|
457
|
+
: {}),
|
|
458
|
+
}
|
|
459
|
+
: undefined;
|
|
460
|
+
if (normalizedContinuation &&
|
|
461
|
+
(!normalizedContinuation.clientMessageId || !normalizedContinuation.objective)) {
|
|
462
|
+
return current;
|
|
463
|
+
}
|
|
464
|
+
Object.assign(next, {
|
|
465
|
+
closureDecision: {
|
|
466
|
+
key,
|
|
467
|
+
text,
|
|
468
|
+
decidedAt: transition.at,
|
|
469
|
+
...(replyAttachmentPaths ? { replyAttachmentPaths } : {}),
|
|
470
|
+
...(replyButton ? { replyButton } : {}),
|
|
471
|
+
...(normalizedContinuation ? { continuation: normalizedContinuation } : {}),
|
|
472
|
+
},
|
|
473
|
+
});
|
|
474
|
+
event = {
|
|
475
|
+
kind: transition.kind,
|
|
476
|
+
at: transition.at,
|
|
477
|
+
phase: current.phase,
|
|
478
|
+
message: normalizedContinuation
|
|
479
|
+
? "Mimi recorded a durable continuation decision"
|
|
480
|
+
: "Mimi recorded a durable closure reply",
|
|
481
|
+
};
|
|
482
|
+
break;
|
|
483
|
+
}
|
|
484
|
+
case "continuation-started": {
|
|
485
|
+
if (current.closureDecision?.key !== transition.key ||
|
|
486
|
+
!current.closureDecision.continuation) {
|
|
487
|
+
return current;
|
|
488
|
+
}
|
|
489
|
+
const sessionId = transition.sessionId.trim().slice(0, 256);
|
|
490
|
+
if (!sessionId)
|
|
491
|
+
return current;
|
|
492
|
+
Object.assign(next, {
|
|
493
|
+
closureDecision: {
|
|
494
|
+
...current.closureDecision,
|
|
495
|
+
launch: {
|
|
496
|
+
sessionId,
|
|
497
|
+
...(transition.taskId ? { taskId: transition.taskId.slice(0, 128) } : {}),
|
|
498
|
+
at: transition.at,
|
|
499
|
+
},
|
|
500
|
+
},
|
|
501
|
+
});
|
|
502
|
+
event = {
|
|
503
|
+
kind: transition.kind,
|
|
504
|
+
at: transition.at,
|
|
505
|
+
phase: current.phase,
|
|
506
|
+
message: "The durable continuation task was started",
|
|
507
|
+
};
|
|
508
|
+
break;
|
|
509
|
+
}
|
|
510
|
+
case "closure-recorded":
|
|
511
|
+
Object.assign(next, { closureRecordedAt: transition.at });
|
|
512
|
+
event = {
|
|
513
|
+
kind: transition.kind,
|
|
514
|
+
at: transition.at,
|
|
515
|
+
phase: current.phase,
|
|
516
|
+
message: "Terminal outcome recorded in Pet work memory",
|
|
517
|
+
};
|
|
518
|
+
break;
|
|
519
|
+
}
|
|
520
|
+
next.events = appendEvent(current, event);
|
|
521
|
+
return next;
|
|
522
|
+
}
|
|
523
|
+
function isStatus(value) {
|
|
524
|
+
return (typeof value === "string" &&
|
|
525
|
+
[
|
|
526
|
+
"queued",
|
|
527
|
+
"running",
|
|
528
|
+
"waiting",
|
|
529
|
+
"paused",
|
|
530
|
+
"interrupted",
|
|
531
|
+
"completed",
|
|
532
|
+
"failed",
|
|
533
|
+
"cancelled",
|
|
534
|
+
].includes(value));
|
|
535
|
+
}
|
|
536
|
+
function isPhase(value) {
|
|
537
|
+
return (typeof value === "string" &&
|
|
538
|
+
["planning", "executing", "waiting-user", "waiting-worker", "finalizing"].includes(value));
|
|
539
|
+
}
|
|
540
|
+
function isEventKind(value) {
|
|
541
|
+
return (typeof value === "string" &&
|
|
542
|
+
[
|
|
543
|
+
"created",
|
|
544
|
+
"started",
|
|
545
|
+
"progress",
|
|
546
|
+
"waiting",
|
|
547
|
+
"paused",
|
|
548
|
+
"resumed",
|
|
549
|
+
"retrying",
|
|
550
|
+
"artifact",
|
|
551
|
+
"checkpoint",
|
|
552
|
+
"verification-changed",
|
|
553
|
+
"interrupted",
|
|
554
|
+
"completed",
|
|
555
|
+
"failed",
|
|
556
|
+
"cancelled",
|
|
557
|
+
"closure-decided",
|
|
558
|
+
"continuation-started",
|
|
559
|
+
"closure-recorded",
|
|
560
|
+
].includes(value));
|
|
561
|
+
}
|
|
562
|
+
function parseEvent(value) {
|
|
563
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
564
|
+
return null;
|
|
565
|
+
const event = value;
|
|
566
|
+
if (typeof event.id !== "string" ||
|
|
567
|
+
!event.id ||
|
|
568
|
+
typeof event.sequence !== "number" ||
|
|
569
|
+
!Number.isSafeInteger(event.sequence) ||
|
|
570
|
+
event.sequence < 1 ||
|
|
571
|
+
!isEventKind(event.kind) ||
|
|
572
|
+
typeof event.at !== "number" ||
|
|
573
|
+
!Number.isFinite(event.at) ||
|
|
574
|
+
(event.phase !== undefined && !isPhase(event.phase))) {
|
|
575
|
+
return null;
|
|
576
|
+
}
|
|
577
|
+
return {
|
|
578
|
+
id: event.id.slice(0, 256),
|
|
579
|
+
sequence: event.sequence,
|
|
580
|
+
kind: event.kind,
|
|
581
|
+
at: event.at,
|
|
582
|
+
...(typeof event.message === "string" ? { message: bounded(event.message, 2_000) } : {}),
|
|
583
|
+
...(isPhase(event.phase) ? { phase: event.phase } : {}),
|
|
584
|
+
...(typeof event.waitingFor === "string" ? { waitingFor: bounded(event.waitingFor, 500) } : {}),
|
|
585
|
+
...(typeof event.nextAction === "string" ? { nextAction: bounded(event.nextAction, 500) } : {}),
|
|
586
|
+
...(Array.isArray(event.artifacts)
|
|
587
|
+
? { artifacts: normalizeArtifacts(event.artifacts) }
|
|
588
|
+
: {}),
|
|
589
|
+
};
|
|
590
|
+
}
|
|
591
|
+
function parseClosureDecision(value) {
|
|
592
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
593
|
+
return undefined;
|
|
594
|
+
const record = value;
|
|
595
|
+
if (typeof record.key !== "string" ||
|
|
596
|
+
!record.key.trim() ||
|
|
597
|
+
typeof record.text !== "string" ||
|
|
598
|
+
!record.text.trim() ||
|
|
599
|
+
typeof record.decidedAt !== "number" ||
|
|
600
|
+
!Number.isFinite(record.decidedAt)) {
|
|
601
|
+
return undefined;
|
|
602
|
+
}
|
|
603
|
+
const replyAttachmentPaths = normalizeReplyAttachmentPaths(record.replyAttachmentPaths);
|
|
604
|
+
if (record.replyAttachmentPaths !== undefined && !replyAttachmentPaths)
|
|
605
|
+
return undefined;
|
|
606
|
+
const replyButton = normalizeReplyButton(record.replyButton);
|
|
607
|
+
if (record.replyButton !== undefined && !replyButton)
|
|
608
|
+
return undefined;
|
|
609
|
+
let continuation;
|
|
610
|
+
if (record.continuation !== undefined) {
|
|
611
|
+
if (!record.continuation ||
|
|
612
|
+
typeof record.continuation !== "object" ||
|
|
613
|
+
Array.isArray(record.continuation)) {
|
|
614
|
+
return undefined;
|
|
615
|
+
}
|
|
616
|
+
const candidate = record.continuation;
|
|
617
|
+
if (typeof candidate.clientMessageId !== "string" ||
|
|
618
|
+
!candidate.clientMessageId.trim() ||
|
|
619
|
+
typeof candidate.objective !== "string" ||
|
|
620
|
+
!candidate.objective.trim() ||
|
|
621
|
+
(candidate.workspacePath !== null && typeof candidate.workspacePath !== "string")) {
|
|
622
|
+
return undefined;
|
|
623
|
+
}
|
|
624
|
+
continuation = {
|
|
625
|
+
clientMessageId: candidate.clientMessageId.slice(0, 256),
|
|
626
|
+
objective: candidate.objective.trim().slice(0, 8_000),
|
|
627
|
+
workspacePath: typeof candidate.workspacePath === "string"
|
|
628
|
+
? candidate.workspacePath.slice(0, 4_096)
|
|
629
|
+
: null,
|
|
630
|
+
...(candidate.executionBackend === "codex" ? { executionBackend: "codex" } : {}),
|
|
631
|
+
};
|
|
632
|
+
}
|
|
633
|
+
let launch;
|
|
634
|
+
if (record.launch !== undefined) {
|
|
635
|
+
if (!record.launch || typeof record.launch !== "object" || Array.isArray(record.launch)) {
|
|
636
|
+
return undefined;
|
|
637
|
+
}
|
|
638
|
+
const candidate = record.launch;
|
|
639
|
+
if (typeof candidate.sessionId !== "string" ||
|
|
640
|
+
!candidate.sessionId.trim() ||
|
|
641
|
+
typeof candidate.at !== "number" ||
|
|
642
|
+
!Number.isFinite(candidate.at) ||
|
|
643
|
+
(candidate.taskId !== undefined && typeof candidate.taskId !== "string")) {
|
|
644
|
+
return undefined;
|
|
645
|
+
}
|
|
646
|
+
launch = {
|
|
647
|
+
sessionId: candidate.sessionId.slice(0, 256),
|
|
648
|
+
...(typeof candidate.taskId === "string" && candidate.taskId
|
|
649
|
+
? { taskId: candidate.taskId.slice(0, 128) }
|
|
650
|
+
: {}),
|
|
651
|
+
at: candidate.at,
|
|
652
|
+
};
|
|
653
|
+
}
|
|
654
|
+
return {
|
|
655
|
+
key: record.key.trim().slice(0, 512),
|
|
656
|
+
text: record.text.trim().slice(0, MAX_PET_LONG_TASK_SUMMARY_LENGTH),
|
|
657
|
+
decidedAt: record.decidedAt,
|
|
658
|
+
...(replyAttachmentPaths ? { replyAttachmentPaths } : {}),
|
|
659
|
+
...(replyButton ? { replyButton } : {}),
|
|
660
|
+
...(continuation ? { continuation } : {}),
|
|
661
|
+
...(launch ? { launch } : {}),
|
|
662
|
+
};
|
|
663
|
+
}
|
|
664
|
+
/** Defensive parser used by durable hosts. Invalid rows are dropped independently. */
|
|
665
|
+
export function parsePetLongTask(value) {
|
|
666
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
667
|
+
return null;
|
|
668
|
+
const record = value;
|
|
669
|
+
if (record.schemaVersion !== PET_LONG_TASK_SCHEMA_VERSION ||
|
|
670
|
+
typeof record.id !== "string" ||
|
|
671
|
+
!record.id ||
|
|
672
|
+
typeof record.originClientMessageId !== "string" ||
|
|
673
|
+
!record.originClientMessageId ||
|
|
674
|
+
typeof record.objective !== "string" ||
|
|
675
|
+
!record.objective ||
|
|
676
|
+
typeof record.sessionId !== "string" ||
|
|
677
|
+
!record.sessionId ||
|
|
678
|
+
!isStatus(record.status) ||
|
|
679
|
+
!isPhase(record.phase) ||
|
|
680
|
+
typeof record.attempt !== "number" ||
|
|
681
|
+
!Number.isSafeInteger(record.attempt) ||
|
|
682
|
+
record.attempt < 1 ||
|
|
683
|
+
typeof record.revision !== "number" ||
|
|
684
|
+
!Number.isSafeInteger(record.revision) ||
|
|
685
|
+
record.revision < 1 ||
|
|
686
|
+
typeof record.createdAt !== "number" ||
|
|
687
|
+
typeof record.updatedAt !== "number") {
|
|
688
|
+
return null;
|
|
689
|
+
}
|
|
690
|
+
const events = Array.isArray(record.events)
|
|
691
|
+
? record.events
|
|
692
|
+
.map(parseEvent)
|
|
693
|
+
.filter((event) => event !== null)
|
|
694
|
+
.slice(-MAX_PET_LONG_TASK_EVENTS)
|
|
695
|
+
: [];
|
|
696
|
+
const completionTarget = normalizeCompletionTarget(record.completionTarget);
|
|
697
|
+
const continuationDepth = typeof record.continuationDepth === "number" &&
|
|
698
|
+
Number.isSafeInteger(record.continuationDepth) &&
|
|
699
|
+
record.continuationDepth > 0
|
|
700
|
+
? record.continuationDepth
|
|
701
|
+
: 0;
|
|
702
|
+
const closureDecision = parseClosureDecision(record.closureDecision);
|
|
703
|
+
return {
|
|
704
|
+
schemaVersion: PET_LONG_TASK_SCHEMA_VERSION,
|
|
705
|
+
id: record.id.slice(0, 128),
|
|
706
|
+
originClientMessageId: record.originClientMessageId.slice(0, 256),
|
|
707
|
+
objective: record.objective.slice(0, 8_000),
|
|
708
|
+
workspacePath: typeof record.workspacePath === "string" ? record.workspacePath.slice(0, 4_096) : null,
|
|
709
|
+
sessionId: record.sessionId.slice(0, 256),
|
|
710
|
+
...(record.executionBackend === "codex" ? { executionBackend: "codex" } : {}),
|
|
711
|
+
// Rows written before verificationMode existed were all launched with a
|
|
712
|
+
// forced Goal. Preserve that meaning while new rows default to turn mode.
|
|
713
|
+
verificationMode: record.verificationMode === "turn" ? "turn" : "goal",
|
|
714
|
+
...(completionTarget ? { completionTarget } : {}),
|
|
715
|
+
...(continuationDepth > 0 ? { continuationDepth } : {}),
|
|
716
|
+
status: record.status,
|
|
717
|
+
phase: record.phase,
|
|
718
|
+
attempt: record.attempt,
|
|
719
|
+
revision: record.revision,
|
|
720
|
+
createdAt: record.createdAt,
|
|
721
|
+
updatedAt: record.updatedAt,
|
|
722
|
+
...(typeof record.startedAt === "number" ? { startedAt: record.startedAt } : {}),
|
|
723
|
+
...(typeof record.completedAt === "number" ? { completedAt: record.completedAt } : {}),
|
|
724
|
+
...(typeof record.closureRecordedAt === "number"
|
|
725
|
+
? { closureRecordedAt: record.closureRecordedAt }
|
|
726
|
+
: {}),
|
|
727
|
+
...(closureDecision ? { closureDecision } : {}),
|
|
728
|
+
...(typeof record.resultSummary === "string"
|
|
729
|
+
? { resultSummary: record.resultSummary.slice(0, MAX_PET_LONG_TASK_SUMMARY_LENGTH) }
|
|
730
|
+
: {}),
|
|
731
|
+
...(typeof record.summary === "string"
|
|
732
|
+
? { summary: record.summary.slice(0, MAX_PET_LONG_TASK_SUMMARY_LENGTH) }
|
|
733
|
+
: {}),
|
|
734
|
+
...(typeof record.waitingFor === "string"
|
|
735
|
+
? { waitingFor: record.waitingFor.slice(0, 500) }
|
|
736
|
+
: {}),
|
|
737
|
+
...(typeof record.nextAction === "string"
|
|
738
|
+
? { nextAction: record.nextAction.slice(0, 500) }
|
|
739
|
+
: {}),
|
|
740
|
+
...(typeof record.lastError === "string"
|
|
741
|
+
? { lastError: record.lastError.slice(0, 2_000) }
|
|
742
|
+
: {}),
|
|
743
|
+
artifacts: normalizeArtifacts(Array.isArray(record.artifacts) ? record.artifacts : []),
|
|
744
|
+
events,
|
|
745
|
+
};
|
|
746
|
+
}
|
|
747
|
+
/** Bounded task continuity injected into each Mimi manager turn. */
|
|
748
|
+
export function buildPetLongTaskContext(tasks) {
|
|
749
|
+
const sorted = [...tasks].sort((left, right) => right.updatedAt - left.updatedAt);
|
|
750
|
+
const active = sorted
|
|
751
|
+
.filter((task) => !TERMINAL.has(task.status))
|
|
752
|
+
.slice(0, 20)
|
|
753
|
+
.map((task) => ({
|
|
754
|
+
taskId: task.id,
|
|
755
|
+
objective: task.objective.slice(0, 800),
|
|
756
|
+
status: task.status,
|
|
757
|
+
phase: task.phase,
|
|
758
|
+
sessionId: task.sessionId,
|
|
759
|
+
...(task.workspacePath ? { workspace: task.workspacePath.slice(0, 500) } : {}),
|
|
760
|
+
attempt: task.attempt,
|
|
761
|
+
updatedAt: task.updatedAt,
|
|
762
|
+
...(task.summary ? { summary: task.summary.slice(0, 1_000) } : {}),
|
|
763
|
+
...(task.waitingFor ? { waitingFor: task.waitingFor.slice(0, 500) } : {}),
|
|
764
|
+
...(task.nextAction ? { nextAction: task.nextAction.slice(0, 500) } : {}),
|
|
765
|
+
}));
|
|
766
|
+
const recent = sorted
|
|
767
|
+
.filter((task) => TERMINAL.has(task.status))
|
|
768
|
+
.slice(0, 10)
|
|
769
|
+
.map((task) => ({
|
|
770
|
+
taskId: task.id,
|
|
771
|
+
objective: task.objective.slice(0, 500),
|
|
772
|
+
status: task.status,
|
|
773
|
+
sessionId: task.sessionId,
|
|
774
|
+
...(task.workspacePath ? { workspace: task.workspacePath.slice(0, 500) } : {}),
|
|
775
|
+
updatedAt: task.updatedAt,
|
|
776
|
+
...(task.summary ? { summary: task.summary.slice(0, 800) } : {}),
|
|
777
|
+
...(task.artifacts.some((artifact) => artifact.kind === "file")
|
|
778
|
+
? {
|
|
779
|
+
artifacts: task.artifacts
|
|
780
|
+
.filter((artifact) => artifact.kind === "file")
|
|
781
|
+
.slice(0, 8)
|
|
782
|
+
.map((artifact) => ({ ...artifact })),
|
|
783
|
+
}
|
|
784
|
+
: {}),
|
|
785
|
+
}));
|
|
786
|
+
return { version: 1, active, recent };
|
|
787
|
+
}
|
|
788
|
+
export function petLongTaskResumePrompt(task) {
|
|
789
|
+
const durableCheckpoint = task.resultSummary ?? task.summary;
|
|
790
|
+
const checkpoint = durableCheckpoint ? `\nLatest durable checkpoint: ${durableCheckpoint}` : "";
|
|
791
|
+
const next = task.nextAction ? `\nExpected next action: ${task.nextAction}` : "";
|
|
792
|
+
return (`Resume the existing long-running task. Preserve prior work in this session, inspect the current state, ` +
|
|
793
|
+
`and continue until the original objective is genuinely verified complete.\n\nOriginal objective: ${task.objective}` +
|
|
794
|
+
`${checkpoint}${next}`);
|
|
795
|
+
}
|