@cjhyy/code-shell-pet 0.9.8 → 0.9.10

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.
@@ -30,7 +30,7 @@ export interface PetLongTaskCompletionTarget {
30
30
  replyAttachmentKinds?: PetReplyAttachmentKind[];
31
31
  }
32
32
  export type PetReplyAttachmentKind = "image" | "file" | "audio" | "video";
33
- export type PetLongTaskEventKind = "created" | "started" | "progress" | "waiting" | "paused" | "resumed" | "retrying" | "artifact" | "checkpoint" | "verification-changed" | "completion-target-set" | "interrupted" | "completed" | "failed" | "cancelled" | "closure-decided" | "continuation-started" | "closure-recorded" | "work-memory-recorded";
33
+ export type PetLongTaskEventKind = "created" | "started" | "run-bound" | "run-prepared" | "result-updated" | "progress" | "waiting" | "paused" | "resumed" | "retrying" | "artifact" | "checkpoint" | "verification-changed" | "completion-target-set" | "interrupted" | "completed" | "failed" | "cancelled" | "closure-decided" | "continuation-started" | "closure-recorded" | "work-memory-recorded";
34
34
  export interface PetLongTaskEvent {
35
35
  id: string;
36
36
  sequence: number;
@@ -41,6 +41,9 @@ export interface PetLongTaskEvent {
41
41
  waitingFor?: string;
42
42
  nextAction?: string;
43
43
  artifacts?: PetLongTaskArtifact[];
44
+ attempt?: number;
45
+ runId?: string;
46
+ clientMessageId?: string;
44
47
  }
45
48
  export interface PetLongTask {
46
49
  schemaVersion: typeof PET_LONG_TASK_SCHEMA_VERSION;
@@ -59,6 +62,10 @@ export interface PetLongTask {
59
62
  status: PetLongTaskStatus;
60
63
  phase: PetLongTaskPhase;
61
64
  attempt: number;
65
+ /** Current run's transcript user-message id; absent in historical rows. */
66
+ runId?: string;
67
+ /** Expected launch client id before binding, current run client id afterwards. */
68
+ clientMessageId?: string;
62
69
  revision: number;
63
70
  createdAt: number;
64
71
  updatedAt: number;
@@ -127,6 +134,8 @@ export interface CreatePetLongTaskInput {
127
134
  objective: string;
128
135
  workspacePath: string | null;
129
136
  sessionId: string;
137
+ runId?: string;
138
+ clientMessageId?: string;
130
139
  executionBackend?: PetWorkExecutionBackend;
131
140
  verificationMode?: PetLongTaskVerificationMode;
132
141
  completionTarget?: PetLongTaskCompletionTarget;
@@ -134,6 +143,28 @@ export interface CreatePetLongTaskInput {
134
143
  at: number;
135
144
  }
136
145
  export type PetLongTaskTransition = {
146
+ /** Optimistic fence for observations queued before retry/resume. */
147
+ attempt?: number;
148
+ runId?: string;
149
+ clientMessageId?: string;
150
+ } & PetLongTaskTransitionInput;
151
+ type PetLongTaskTransitionInput = {
152
+ kind: "run-prepared";
153
+ at: number;
154
+ clientMessageId?: string;
155
+ } | {
156
+ kind: "run-bound";
157
+ at: number;
158
+ runId: string;
159
+ previousRunId?: string;
160
+ } | {
161
+ kind: "result-updated";
162
+ at: number;
163
+ attempt: number;
164
+ runId: string;
165
+ summary?: string;
166
+ terminalStatus?: "completed" | "failed";
167
+ } | {
137
168
  kind: "started";
138
169
  at: number;
139
170
  message?: string;
@@ -233,9 +264,9 @@ export type PetLongTaskTransition = {
233
264
  export declare function createPetLongTask(input: CreatePetLongTaskInput): PetLongTask;
234
265
  /**
235
266
  * Apply one durable transition. Terminal tasks only accept retry/cancel or a
236
- * closure-delivery acknowledgement; late
237
- * worker events are ignored so a stale completion cannot undo an explicit user
238
- * action such as pause or cancel.
267
+ * closure-delivery acknowledgement. A completed task can also record an explicit
268
+ * result update fenced to its current run and attempt. Late worker events cannot
269
+ * undo an explicit user action such as pause, cancel, or retry.
239
270
  */
240
271
  export declare function transitionPetLongTask(current: PetLongTask, transition: PetLongTaskTransition): PetLongTask;
241
272
  /** Defensive parser used by durable hosts. Invalid rows are dropped independently. */
@@ -269,3 +300,4 @@ export interface PetLongTaskContext {
269
300
  /** Bounded task continuity injected into each Mimi manager turn. */
270
301
  export declare function buildPetLongTaskContext(tasks: readonly PetLongTask[]): PetLongTaskContext;
271
302
  export declare function petLongTaskResumePrompt(task: PetLongTask): string;
303
+ export {};
package/dist/long-task.js CHANGED
@@ -159,6 +159,8 @@ export function createPetLongTask(input) {
159
159
  status: "queued",
160
160
  phase: "planning",
161
161
  attempt: 1,
162
+ runId: input.runId,
163
+ clientMessageId: input.clientMessageId,
162
164
  revision: 1,
163
165
  createdAt: input.at,
164
166
  updatedAt: input.at,
@@ -169,11 +171,26 @@ export function createPetLongTask(input) {
169
171
  }
170
172
  /**
171
173
  * 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.
174
+ * closure-delivery acknowledgement. A completed task can also record an explicit
175
+ * result update fenced to its current run and attempt. Late worker events cannot
176
+ * undo an explicit user action such as pause, cancel, or retry.
175
177
  */
176
178
  export function transitionPetLongTask(current, transition) {
179
+ if (transition.attempt !== undefined && transition.attempt !== current.attempt)
180
+ return current;
181
+ if (current.status === "paused" && transition.runId !== undefined)
182
+ return current;
183
+ if (transition.kind !== "run-bound" &&
184
+ transition.kind !== "run-prepared" &&
185
+ transition.kind !== "retrying" &&
186
+ ((transition.runId !== undefined &&
187
+ (transition.runId !== current.runId ||
188
+ transition.clientMessageId !== current.clientMessageId)) ||
189
+ (transition.clientMessageId !== undefined &&
190
+ transition.clientMessageId !== current.clientMessageId)))
191
+ return current;
192
+ if (transition.kind === "result-updated" && (current.status !== "completed" || !current.runId))
193
+ return current;
177
194
  if (transition.kind === "closure-decided" && current.closureDecision?.key === transition.key) {
178
195
  return current;
179
196
  }
@@ -202,6 +219,7 @@ export function transitionPetLongTask(current, transition) {
202
219
  transition.kind !== "retrying" &&
203
220
  transition.kind !== "cancelled" &&
204
221
  transition.kind !== "background-wait-recovered" &&
222
+ transition.kind !== "result-updated" &&
205
223
  transition.kind !== "closure-decided" &&
206
224
  transition.kind !== "continuation-started" &&
207
225
  transition.kind !== "closure-recorded" &&
@@ -231,6 +249,50 @@ export function transitionPetLongTask(current, transition) {
231
249
  };
232
250
  let event;
233
251
  switch (transition.kind) {
252
+ case "run-prepared":
253
+ Object.assign(next, { runId: undefined, clientMessageId: transition.clientMessageId });
254
+ event = { kind: transition.kind, at: transition.at, message: "Preparing to continue work" };
255
+ break;
256
+ case "run-bound":
257
+ if (current.runId !== transition.previousRunId)
258
+ return current;
259
+ // A resume can prepare a different launch before a queued start is applied.
260
+ // Unbound launches share undefined previousRunId; the submit id is their CAS.
261
+ if (!current.runId &&
262
+ current.clientMessageId !== undefined &&
263
+ current.clientMessageId !== transition.clientMessageId)
264
+ return current;
265
+ if (current.runId === transition.runId)
266
+ return current;
267
+ Object.assign(next, {
268
+ runId: transition.runId,
269
+ clientMessageId: transition.clientMessageId,
270
+ status: "running",
271
+ phase: "executing",
272
+ resultSummary: undefined,
273
+ waitingFor: undefined,
274
+ });
275
+ event = { kind: transition.kind, at: transition.at, message: "Work session started" };
276
+ break;
277
+ case "result-updated": {
278
+ const summary = bounded(transition.summary, MAX_PET_LONG_TASK_SUMMARY_LENGTH);
279
+ const message = summary ?? current.resultSummary ?? "Work session result confirmed";
280
+ if (current.events.some((item) => item.kind === "result-updated" &&
281
+ item.runId === transition.runId &&
282
+ item.attempt === current.attempt) &&
283
+ (!summary || summary === current.resultSummary) &&
284
+ (!transition.terminalStatus || transition.terminalStatus === current.status))
285
+ return current;
286
+ Object.assign(next, {
287
+ status: transition.terminalStatus ?? current.status,
288
+ ...(summary ? { resultSummary: summary, summary } : {}),
289
+ ...(transition.terminalStatus === "failed"
290
+ ? { lastError: summary, completedAt: transition.at }
291
+ : {}),
292
+ });
293
+ event = { kind: transition.kind, at: transition.at, phase: "finalizing", message };
294
+ break;
295
+ }
234
296
  case "started":
235
297
  Object.assign(next, {
236
298
  status: "running",
@@ -334,6 +396,8 @@ export function transitionPetLongTask(current, transition) {
334
396
  status: "queued",
335
397
  phase: "planning",
336
398
  attempt: current.attempt + 1,
399
+ runId: undefined,
400
+ clientMessageId: transition.clientMessageId,
337
401
  // A retry is a new attempt in the same durable Session. Keeping the
338
402
  // previous attempt's start time would let an old projection terminal
339
403
  // pass the new attempt's time fence.
@@ -593,7 +657,12 @@ export function transitionPetLongTask(current, transition) {
593
657
  };
594
658
  break;
595
659
  }
596
- next.events = appendEvent(current, event);
660
+ next.events = appendEvent(current, {
661
+ ...event,
662
+ attempt: next.attempt,
663
+ runId: next.runId,
664
+ clientMessageId: next.clientMessageId,
665
+ });
597
666
  return next;
598
667
  }
599
668
  function isStatus(value) {
@@ -618,6 +687,9 @@ function isEventKind(value) {
618
687
  [
619
688
  "created",
620
689
  "started",
690
+ "run-bound",
691
+ "run-prepared",
692
+ "result-updated",
621
693
  "progress",
622
694
  "waiting",
623
695
  "paused",
@@ -657,6 +729,19 @@ function parseEvent(value) {
657
729
  sequence: event.sequence,
658
730
  kind: event.kind,
659
731
  at: event.at,
732
+ ...(typeof event.attempt === "number" &&
733
+ Number.isSafeInteger(event.attempt) &&
734
+ event.attempt > 0
735
+ ? { attempt: event.attempt }
736
+ : {}),
737
+ ...(typeof event.runId === "string" && event.runId.length > 0 && event.runId.length <= 256
738
+ ? { runId: event.runId }
739
+ : {}),
740
+ ...(typeof event.clientMessageId === "string" &&
741
+ event.clientMessageId.length > 0 &&
742
+ event.clientMessageId.length <= 256
743
+ ? { clientMessageId: event.clientMessageId }
744
+ : {}),
660
745
  ...(typeof event.message === "string" ? { message: bounded(event.message, 2_000) } : {}),
661
746
  ...(isPhase(event.phase) ? { phase: event.phase } : {}),
662
747
  ...(typeof event.waitingFor === "string" ? { waitingFor: bounded(event.waitingFor, 500) } : {}),
@@ -794,6 +879,14 @@ export function parsePetLongTask(value) {
794
879
  status: record.status,
795
880
  phase: record.phase,
796
881
  attempt: record.attempt,
882
+ ...(typeof record.runId === "string" && record.runId.length > 0 && record.runId.length <= 256
883
+ ? { runId: record.runId }
884
+ : {}),
885
+ ...(typeof record.clientMessageId === "string" &&
886
+ record.clientMessageId.length > 0 &&
887
+ record.clientMessageId.length <= 256
888
+ ? { clientMessageId: record.clientMessageId }
889
+ : {}),
797
890
  revision: record.revision,
798
891
  createdAt: record.createdAt,
799
892
  updatedAt: record.updatedAt,
@@ -75,6 +75,8 @@ export function createPetProjectionObserver(host) {
75
75
  queueDepth: live.queueDepth,
76
76
  lastActivityAt: Math.max(live.lastActivityAt, indexed?.lastActivityAt ?? 0),
77
77
  pendingDecisionCount,
78
+ runId: indexed?.runId,
79
+ clientMessageId: indexed?.clientMessageId,
78
80
  completionKind: liveRunState === "idle" ? indexed?.completionKind : undefined,
79
81
  terminal,
80
82
  freshness: { source: "live-snapshot", observedAt, workerState: "active" },
@@ -54,6 +54,8 @@ function baseProjection(catalog, observedAt, workerState) {
54
54
  owner: LOCAL_PET_OWNER,
55
55
  agentSessionId: catalog.sessionId,
56
56
  coreSessionId: catalog.sessionId,
57
+ runId: catalog.runId,
58
+ clientMessageId: catalog.clientMessageId,
57
59
  title: catalog.title,
58
60
  workspaceDisplayName: catalog.workspaceDisplayName,
59
61
  runState: status ? "terminal" : "dormant",
@@ -68,7 +70,14 @@ function baseProjection(catalog, observedAt, workerState) {
68
70
  : completionKind === "limit_stop"
69
71
  ? "已到运行限制"
70
72
  : undefined,
71
- terminal: status ? { status, at: catalog.updatedAt } : undefined,
73
+ terminal: status
74
+ ? {
75
+ status,
76
+ at: catalog.updatedAt,
77
+ runId: catalog.runId,
78
+ clientMessageId: catalog.clientMessageId,
79
+ }
80
+ : undefined,
72
81
  freshness: { source: "disk", observedAt, workerState },
73
82
  };
74
83
  }
@@ -261,12 +270,44 @@ export class SessionIndex {
261
270
  return true;
262
271
  }
263
272
  reduceEvent(current, event, observedAt) {
273
+ if ("agentId" in event && event.agentId !== undefined) {
274
+ // Child activity is useful progress, but cannot change the parent's lifecycle.
275
+ if (event.type === "turn_complete" || event.type === "error")
276
+ return current;
277
+ return {
278
+ ...current,
279
+ lastActivityAt: observedAt,
280
+ summary: event.type === "tool_use_start"
281
+ ? `正在运行 ${safeToolName(event.toolCall.toolName)}`.slice(0, 64)
282
+ : current.summary,
283
+ freshness: { source: "live-event", observedAt, workerState: "active" },
284
+ };
285
+ }
286
+ if (event.type !== "session_started" &&
287
+ current.runId &&
288
+ (event.runId !== current.runId || event.clientMessageId !== current.clientMessageId)) {
289
+ return current;
290
+ }
264
291
  const next = {
265
292
  ...current,
266
293
  lastActivityAt: observedAt,
267
294
  freshness: { source: "live-event", observedAt, workerState: "active" },
268
295
  };
269
296
  switch (event.type) {
297
+ case "session_started":
298
+ if (current.runId &&
299
+ (event.runId === current.runId || event.previousRunId !== current.runId))
300
+ return current;
301
+ return {
302
+ ...next,
303
+ runId: event.runId,
304
+ clientMessageId: event.clientMessageId,
305
+ runState: "running",
306
+ phase: "model",
307
+ summary: "模型处理中",
308
+ completionKind: undefined,
309
+ terminal: undefined,
310
+ };
270
311
  case "stream_request_start":
271
312
  return {
272
313
  ...next,
@@ -322,7 +363,14 @@ export class SessionIndex {
322
363
  ? "本轮已完成"
323
364
  : "运行已结束",
324
365
  completionKind: undefined,
325
- terminal: current.queueDepth > 0 ? undefined : { status, at: observedAt },
366
+ terminal: current.queueDepth > 0
367
+ ? undefined
368
+ : {
369
+ status,
370
+ at: observedAt,
371
+ runId: current.runId,
372
+ clientMessageId: current.clientMessageId,
373
+ },
326
374
  };
327
375
  }
328
376
  case "error":
@@ -332,7 +380,12 @@ export class SessionIndex {
332
380
  phase: "finalizing",
333
381
  summary: "运行失败",
334
382
  completionKind: undefined,
335
- terminal: { status: "failed", at: observedAt },
383
+ terminal: {
384
+ status: "failed",
385
+ at: observedAt,
386
+ runId: current.runId,
387
+ clientMessageId: current.clientMessageId,
388
+ },
336
389
  };
337
390
  default:
338
391
  return next;
package/dist/types.d.ts CHANGED
@@ -27,16 +27,22 @@ export interface PetSessionProjection {
27
27
  queueDepth: number;
28
28
  lastActivityAt: number;
29
29
  pendingDecisionCount: number;
30
+ runId?: string;
31
+ clientMessageId?: string;
30
32
  /** Exceptional completed-run boundary that is waiting for recovery/continuation. */
31
33
  completionKind?: TurnCompletionKind;
32
34
  terminal?: {
33
35
  status: PetTerminalStatus;
34
36
  at: number;
37
+ runId?: string;
38
+ clientMessageId?: string;
35
39
  };
36
40
  freshness: PetProjectionFreshness;
37
41
  }
38
42
  export interface PetCatalogSession {
39
43
  sessionId: string;
44
+ runId?: string;
45
+ clientMessageId?: string;
40
46
  title?: string;
41
47
  workspaceDisplayName?: string;
42
48
  updatedAt: number;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cjhyy/code-shell-pet",
3
- "version": "0.9.8",
3
+ "version": "0.9.10",
4
4
  "description": "Pet (top-level personal assistant) capability for CodeShell — projection state machine, DelegateWork contract, Mimi behavior profile.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -36,7 +36,7 @@
36
36
  "clean": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\""
37
37
  },
38
38
  "dependencies": {
39
- "@cjhyy/code-shell-core": "0.9.8"
39
+ "@cjhyy/code-shell-core": "0.9.10"
40
40
  },
41
41
  "engines": {
42
42
  "node": ">=20.10"