@stigmer/cli 3.1.7 → 3.1.8

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.
Files changed (70) hide show
  1. package/commands/share.d.ts +3 -0
  2. package/commands/share.d.ts.map +1 -0
  3. package/commands/share.js +75 -0
  4. package/commands/share.js.map +1 -0
  5. package/config/index.d.ts +1 -1
  6. package/config/index.d.ts.map +1 -1
  7. package/config/index.js +1 -1
  8. package/config/index.js.map +1 -1
  9. package/config/resolve.d.ts +19 -2
  10. package/config/resolve.d.ts.map +1 -1
  11. package/config/resolve.js +40 -2
  12. package/config/resolve.js.map +1 -1
  13. package/local/runtime/node.d.ts +9 -2
  14. package/local/runtime/node.d.ts.map +1 -1
  15. package/local/runtime/node.js +38 -23
  16. package/local/runtime/node.js.map +1 -1
  17. package/package.json +6 -6
  18. package/program.d.ts.map +1 -1
  19. package/program.js +2 -0
  20. package/program.js.map +1 -1
  21. package/resources/connect/oauth.d.ts +0 -9
  22. package/resources/connect/oauth.d.ts.map +1 -1
  23. package/resources/connect/oauth.js +1 -17
  24. package/resources/connect/oauth.js.map +1 -1
  25. package/resources/share.d.ts +37 -0
  26. package/resources/share.d.ts.map +1 -0
  27. package/resources/share.js +144 -0
  28. package/resources/share.js.map +1 -0
  29. package/resources/stream/convert.d.ts +6 -2
  30. package/resources/stream/convert.d.ts.map +1 -1
  31. package/resources/stream/convert.js +8 -2
  32. package/resources/stream/convert.js.map +1 -1
  33. package/resources/stream/events.d.ts +14 -2
  34. package/resources/stream/events.d.ts.map +1 -1
  35. package/resources/stream/events.js.map +1 -1
  36. package/resources/stream/render-ndjson.js +2 -0
  37. package/resources/stream/render-ndjson.js.map +1 -1
  38. package/resources/stream/render-plaintext.d.ts.map +1 -1
  39. package/resources/stream/render-plaintext.js +3 -0
  40. package/resources/stream/render-plaintext.js.map +1 -1
  41. package/resources/stream/snapshot.js +6 -1
  42. package/resources/stream/snapshot.js.map +1 -1
  43. package/resources/stream/tool-state.d.ts +1 -1
  44. package/resources/stream/tool-state.d.ts.map +1 -1
  45. package/resources/stream/tool-state.js +13 -4
  46. package/resources/stream/tool-state.js.map +1 -1
  47. package/src/commands/connect.test.ts +26 -4
  48. package/src/commands/share.ts +99 -0
  49. package/src/config/index.ts +2 -0
  50. package/src/config/resolve.test.ts +38 -0
  51. package/src/config/resolve.ts +42 -3
  52. package/src/local/runtime/node.ts +43 -25
  53. package/src/local/runtime/runtime.test.ts +5 -4
  54. package/src/program.test.ts +7 -0
  55. package/src/program.ts +2 -0
  56. package/src/resources/connect/oauth.test.ts +1 -24
  57. package/src/resources/connect/oauth.ts +1 -17
  58. package/src/resources/read-verbs.integration.test.ts +1 -1
  59. package/src/resources/share.test.ts +389 -0
  60. package/src/resources/share.ts +227 -0
  61. package/src/resources/stream/convert.ts +17 -3
  62. package/src/resources/stream/diff.test.ts +22 -0
  63. package/src/resources/stream/events.ts +15 -1
  64. package/src/resources/stream/render-ndjson.test.ts +10 -0
  65. package/src/resources/stream/render-ndjson.ts +2 -0
  66. package/src/resources/stream/render-plaintext.test.ts +8 -0
  67. package/src/resources/stream/render-plaintext.ts +3 -0
  68. package/src/resources/stream/snapshot.test.ts +14 -0
  69. package/src/resources/stream/snapshot.ts +5 -1
  70. package/src/resources/stream/tool-state.ts +13 -4
@@ -15,7 +15,7 @@ import type { JsonObject } from "@bufbuild/protobuf";
15
15
  export interface ToolCallInfo {
16
16
  readonly id: string;
17
17
  readonly name: string;
18
- /** Mapped status: pending|running|completed|failed|waiting_approval|skipped|unknown. */
18
+ /** Mapped status: pending|running|completed|failed|waiting_approval|skipped|interrupted|unknown. */
19
19
  readonly status: string;
20
20
  readonly args?: JsonObject;
21
21
  readonly result: string;
@@ -85,6 +85,19 @@ export interface ToolCompletedEvent {
85
85
  readonly subAgentId: string;
86
86
  }
87
87
 
88
+ /**
89
+ * A tool call the platform settled to INTERRUPTED because its execution
90
+ * terminalized mid-call (issue #207). A distinct terminal event — routing it
91
+ * through toolCompleted would print a dishonest checkmark for a tool that
92
+ * never finished.
93
+ */
94
+ export interface ToolInterruptedEvent {
95
+ readonly kind: "toolInterrupted";
96
+ readonly toolCallId: string;
97
+ readonly toolCall: ToolCallInfo;
98
+ readonly subAgentId: string;
99
+ }
100
+
88
101
  export interface ToolWaitingApprovalEvent {
89
102
  readonly kind: "toolWaitingApproval";
90
103
  readonly toolCallId: string;
@@ -175,6 +188,7 @@ export type StreamEvent =
175
188
  | ToolResultEvent
176
189
  | ToolRunningEvent
177
190
  | ToolCompletedEvent
191
+ | ToolInterruptedEvent
178
192
  | ToolWaitingApprovalEvent
179
193
  | ToolStreamDeltaEvent
180
194
  | SystemMessageEvent
@@ -62,6 +62,16 @@ describe("envelope", () => {
62
62
  });
63
63
 
64
64
  describe("tool events", () => {
65
+ it("emits tool_interrupted as its own wire type (issue #207)", () => {
66
+ const { data } = render([
67
+ { kind: "toolInterrupted", toolCallId: "t1", subAgentId: "", toolCall: toolInfo({ id: "t1", name: "shell", status: "interrupted" }) },
68
+ ]);
69
+ expect(data.lines()[0]).toMatchObject({
70
+ type: "tool_interrupted",
71
+ payload: { tool_call_id: "t1", tool_name: "shell", status: "interrupted" },
72
+ });
73
+ });
74
+
65
75
  it("emits tool_running with only the meaningful fields", () => {
66
76
  const { data } = render([
67
77
  { kind: "toolRunning", toolCallId: "t1", subAgentId: "", toolCall: toolInfo({ id: "t1", result: "out", durationMs: 42 }) },
@@ -77,6 +77,8 @@ function mapToNdjson(event: StreamEvent): MappedEvent | undefined {
77
77
  return { type: "tool_running", payload: toolEventPayload(event.toolCallId, event.toolCall, event.subAgentId) };
78
78
  case "toolCompleted":
79
79
  return { type: "tool_completed", payload: toolEventPayload(event.toolCallId, event.toolCall, event.subAgentId) };
80
+ case "toolInterrupted":
81
+ return { type: "tool_interrupted", payload: toolEventPayload(event.toolCallId, event.toolCall, event.subAgentId) };
80
82
  case "toolWaitingApproval":
81
83
  return {
82
84
  type: "tool_waiting_approval",
@@ -39,6 +39,14 @@ describe("PlaintextRenderer", () => {
39
39
  expect(status).toContain("✗ shell: exploded");
40
40
  });
41
41
 
42
+ it("renders an interrupted tool with the neutral glyph, not a checkmark (issue #207)", () => {
43
+ const { status } = render([
44
+ { kind: "toolInterrupted", toolCallId: "t1", subAgentId: "", toolCall: toolInfo({ name: "shell", status: "interrupted" }) },
45
+ ]);
46
+ expect(status).toContain("⊘ shell (interrupted)");
47
+ expect(status).not.toContain("✓");
48
+ });
49
+
42
50
  it("notes the auto-skip for approvals and resolves to SKIP", () => {
43
51
  const r = new PlaintextRenderer({ data: { write: () => {} }, status: { write: () => {} } });
44
52
  const action = r.resolveApproval({
@@ -49,6 +49,9 @@ export class PlaintextRenderer implements HeadlessRenderer {
49
49
  : ` ✓ ${event.toolCall.name}\n`,
50
50
  );
51
51
  return;
52
+ case "toolInterrupted":
53
+ status.write(` ⊘ ${event.toolCall.name} (interrupted)\n`);
54
+ return;
52
55
  case "approvalNeeded":
53
56
  status.write(` ⚠ Approval needed: ${event.toolName} (auto-skipped in non-TTY mode)\n`);
54
57
  return;
@@ -79,6 +79,20 @@ describe("snapshotToEvents", () => {
79
79
  expect(events[0]).toMatchObject({ kind: "toolCompleted", toolCallId: "tc1" });
80
80
  });
81
81
 
82
+ it("replays a platform-interrupted call as toolInterrupted — never a live running block", () => {
83
+ // Issue #207: before the fix an INTERRUPTED status fell through to
84
+ // "unknown" (non-terminal) and replayed as toolRunning, a block that never
85
+ // closes. It must replay as its own settled event.
86
+ const events = snapshotToEvents([
87
+ exec({
88
+ phase: ExecutionPhase.EXECUTION_CANCELLED,
89
+ messages: [toolMsg(tool("tc1", ToolCallStatus.TOOL_CALL_INTERRUPTED))],
90
+ }),
91
+ ]);
92
+ expect(kinds(events)).toEqual(["toolInterrupted", "done"]);
93
+ expect(events[0]).toMatchObject({ kind: "toolInterrupted", toolCallId: "tc1" });
94
+ });
95
+
82
96
  it("only the final execution emits done", () => {
83
97
  const events = snapshotToEvents([
84
98
  exec({ message: "first", messages: [aiMsg("a")] }),
@@ -159,7 +159,11 @@ function emitOtherMessage(out: StreamEvent[], msg: AgentMessage): void {
159
159
  function emitToolByStatus(out: StreamEvent[], tc: ToolCall): void {
160
160
  const status = mapToolCallStatus(tc.status);
161
161
  const toolCall = convertToolCall(tc);
162
- if (isTerminalToolStatus(status)) {
162
+ if (status === "interrupted") {
163
+ // Terminal but never "completed": replaying a platform-interrupted call as
164
+ // either a checkmark or a live running block would lie (issue #207).
165
+ out.push({ kind: "toolInterrupted", toolCallId: tc.id, toolCall, subAgentId: "" });
166
+ } else if (isTerminalToolStatus(status)) {
163
167
  out.push({ kind: "toolCompleted", toolCallId: tc.id, toolCall, subAgentId: "" });
164
168
  } else if (status === "waiting_approval") {
165
169
  out.push({ kind: "toolWaitingApproval", toolCallId: tc.id, toolCall, subAgentId: "" });
@@ -55,14 +55,14 @@ export class ToolStateTracker {
55
55
  // First sight, already terminal (e.g. re-attach): synthesize completion so
56
56
  // the block exists — otherwise its MESSAGE_TOOL is suppressed and it vanishes.
57
57
  if (!seen && isTerminalToolStatus(current)) {
58
- pending.push(this.completed(tc, subAgentId));
58
+ pending.push(this.terminal(tc, current, subAgentId));
59
59
  this.states.set(tc.id, current);
60
60
  continue;
61
61
  }
62
62
 
63
63
  // running/waiting -> terminal: close the block.
64
64
  if (seen && (prev === "running" || prev === "waiting_approval") && isTerminalToolStatus(current)) {
65
- pending.push(this.completed(tc, subAgentId));
65
+ pending.push(this.terminal(tc, current, subAgentId));
66
66
  this.states.set(tc.id, current);
67
67
  this.results.delete(tc.id);
68
68
  continue;
@@ -113,8 +113,16 @@ export class ToolStateTracker {
113
113
  return { kind: "toolWaitingApproval", toolCallId: tc.id, toolCall: convertToolCall(tc), subAgentId };
114
114
  }
115
115
 
116
- private completed(tc: ToolCall, subAgentId: string): StreamEvent {
117
- return { kind: "toolCompleted", toolCallId: tc.id, toolCall: convertToolCall(tc), subAgentId };
116
+ // The terminal-close event for a settled status: interrupted gets its own
117
+ // kind (issue #207) so renderers never print a checkmark for a tool the
118
+ // platform cut short; every other terminal status closes as completed.
119
+ private terminal(tc: ToolCall, status: string, subAgentId: string): StreamEvent {
120
+ return {
121
+ kind: status === "interrupted" ? "toolInterrupted" : "toolCompleted",
122
+ toolCallId: tc.id,
123
+ toolCall: convertToolCall(tc),
124
+ subAgentId,
125
+ };
118
126
  }
119
127
  }
120
128
 
@@ -123,6 +131,7 @@ export function toolEventId(event: StreamEvent): string {
123
131
  switch (event.kind) {
124
132
  case "toolRunning":
125
133
  case "toolCompleted":
134
+ case "toolInterrupted":
126
135
  case "toolWaitingApproval":
127
136
  case "toolStreamDelta":
128
137
  return event.toolCallId;