@bermudi/pi-delegate 0.1.18 → 0.1.20

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/ticket-format.ts CHANGED
@@ -50,12 +50,18 @@ export function formatTicketAgentRoster(progress: TaskProgress[]): string {
50
50
 
51
51
  /**
52
52
  * Copy-pasteable poll/cancel controls for a live ticket. Cancelling tickets
53
- * keep poll (to watch unwind) but drop cancel (already requested).
53
+ * keep poll (to watch unwind) but drop cancel (already requested). Pause/
54
+ * resume needs the ticket's pause controller: without one, handlePause
55
+ * rejects the call, so no snippet is advertised.
54
56
  */
55
57
  export function formatTicketControlSnippets(ticket: AsyncTicket): string {
56
58
  if (ticket.status !== "running" && ticket.status !== "cancelling") return "";
57
59
  let snippets = `\n poll: delegate({ ticketAction: "poll", ticket: "${ticket.id}" })`;
58
60
  if (ticket.status === "running") {
61
+ if (ticket.pause) {
62
+ const action = ticket.pause.state !== "running" ? "resume" : "pause";
63
+ snippets += `\n ${action}: delegate({ ticketAction: "${action}", ticket: "${ticket.id}" })`;
64
+ }
59
65
  snippets += `\n cancel: delegate({ ticketAction: "cancel", ticket: "${ticket.id}", force: true })`;
60
66
  }
61
67
  return snippets;
@@ -71,7 +77,11 @@ export function formatTicketRosterLine(
71
77
  (p) => p.status === "done" || p.status === "failed",
72
78
  ).length;
73
79
  const age = fmtDuration(now - ticket.created);
74
- return `${icon} ${ticket.id}${formatTicketAgentRoster(ticket.progress)} · ${finalized}/${ticket.progress.length} finalized · ${ticket.status} · ${age}${formatTicketControlSnippets(ticket)}`;
80
+ const state =
81
+ ticket.status === "running"
82
+ ? (ticket.pause?.state ?? ticket.status)
83
+ : ticket.status;
84
+ return `${icon} ${ticket.id}${formatTicketAgentRoster(ticket.progress)} · ${finalized}/${ticket.progress.length} finalized · ${state} · ${age}${formatTicketControlSnippets(ticket)}`;
75
85
  }
76
86
 
77
87
  /** Full roster listing when poll is called without a ticket id. */
@@ -88,6 +98,8 @@ export function missingTicketPollText(ticketId: string): string {
88
98
 
89
99
  /** Running-task line shared by poll snapshots and cancel previews. */
90
100
  export function formatInFlightTaskLine(p: TaskProgress): string {
101
+ if (p.paused)
102
+ return `Ⅱ ${p.agent}${resumeMarker(p)}${formatTaskId(p.id)} · paused between turns`;
91
103
  const parts: string[] = [formatActivityLabel(p)];
92
104
  if (p.toolUses > 0)
93
105
  parts.push(`${p.toolUses} tool${p.toolUses === 1 ? "" : "s"}`);
@@ -183,7 +195,10 @@ export function formatLiveTicketHeader(
183
195
  ).length;
184
196
  const totalCount = ticket.progress.length;
185
197
  const runningCount = ticket.progress.filter(
186
- (p) => p.status === "running",
198
+ (p) => p.status === "running" && !p.paused,
199
+ ).length;
200
+ const pausedCount = ticket.progress.filter(
201
+ (p) => p.status === "running" && p.paused,
187
202
  ).length;
188
203
  const pendingCount = ticket.progress.filter(
189
204
  (p) => p.status === "pending",
@@ -191,12 +206,15 @@ export function formatLiveTicketHeader(
191
206
  const totalTools = ticket.progress.reduce((sum, p) => sum + p.toolUses, 0);
192
207
  const totalTokens = ticket.progress.reduce((sum, p) => sum + p.tokens, 0);
193
208
  const headerStatus =
194
- ticket.status === "cancelling" ? "CANCELLING" : "RUNNING";
209
+ ticket.status === "cancelling"
210
+ ? "CANCELLING"
211
+ : (ticket.pause?.state ?? "running").toUpperCase();
195
212
  const headerParts: string[] = [
196
213
  `Ticket ${ticket.id}: ${headerStatus}`,
197
214
  `${settledCount}/${totalCount} finalized`,
198
215
  ];
199
216
  if (runningCount > 0) headerParts.push(`${runningCount} active`);
217
+ if (pausedCount > 0) headerParts.push(`${pausedCount} paused`);
200
218
  if (pendingCount > 0) headerParts.push(`${pendingCount} queued`);
201
219
  if (failedCount > 0) headerParts.push(`${failedCount} failed`);
202
220
  headerParts.push(`${totalTools} tool${totalTools === 1 ? "" : "s"}`);
@@ -209,6 +227,9 @@ export function liveTicketGuidance(ticket: AsyncTicket): string {
209
227
  if (ticket.status === "cancelling") {
210
228
  return "Cancellation requested. Active subagents are aborting and returning partial results. Wait without timeoutMs for final status; do not repeatedly poll.";
211
229
  }
230
+ if (ticket.pause && ticket.pause.state !== "running") {
231
+ return `Pause requested: current turns may finish, then further turns and queued tasks stay blocked. Resume with delegate({ ticketAction: "resume", ticket: "${ticket.id}" }). Wait does not resume a paused ticket.`;
232
+ }
212
233
  const settledCount = ticket.progress.filter(
213
234
  (p) => p.status === "done" || p.status === "failed",
214
235
  ).length;
@@ -246,15 +267,16 @@ export function formatLiveTicketPoll(
246
267
  findTouchedOverlaps(completedForOverlap),
247
268
  );
248
269
  const guidance = liveTicketGuidance(ticket);
270
+ const serializedNotice = ticket.serializedNotice ?? "";
249
271
  const dispatchWarning = ticket.dispatchWarning
250
272
  ? `WARNING: ${ticket.dispatchWarning}`
251
273
  : "";
252
274
  return {
253
275
  text: `${formatLiveTicketHeader(ticket, now)}\n${lines.join("\n")}${
254
276
  guidance ? `\n\n${guidance}` : ""
255
- }${dispatchWarning ? `\n\n${dispatchWarning}` : ""}${
256
- overlapWarning ? `\n\n${overlapWarning}` : ""
257
- }`,
277
+ }${serializedNotice ? `\n\n${serializedNotice}` : ""}${
278
+ dispatchWarning ? `\n\n${dispatchWarning}` : ""
279
+ }${overlapWarning ? `\n\n${overlapWarning}` : ""}`,
258
280
  completedResults,
259
281
  overlapWarning,
260
282
  };