@cargo-ai/cli 1.0.124 → 1.0.126

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.
@@ -1 +1 @@
1
- {"version":3,"file":"create.d.ts","sourceRoot":"","sources":["../../../../src/commands/ask/harness/create.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,eAAe,CAAC;AAIxC,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,iBAAiB,CAAC;AAuB3C,OAAO,EAEL,KAAK,kBAAkB,EAKxB,MAAM,WAAW,CAAC;AAGnB,MAAM,MAAM,YAAY,GAAG;IACzB,YAAY,EAAE,CACZ,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE,kBAAkB,KACzB,OAAO,CAAC,MAAM,CAAC,CAAC;IACrB;;;;OAIG;IACH,iBAAiB,EAAE,MAAM,MAAM,GAAG,SAAS,CAAC;IAC5C,sEAAsE;IACtE,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5B,CAAC;AAcF,wBAAsB,kBAAkB,CAAC,KAAK,EAAE;IAC9C,GAAG,EAAE,GAAG,CAAC;IACT;;;;OAIG;IACH,UAAU,EAAE,MAAM,CAAC;IACnB,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;OAIG;IACH,OAAO,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB;;;;OAIG;IACH,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe,EAAE,MAAM,CAAC;IACxB,iBAAiB,EAAE,MAAM,CAAC;IAC1B;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;OAIG;IACH,uBAAuB,EAAE,OAAO,CAAC;CAClC,GAAG,OAAO,CAAC,YAAY,CAAC,CA4UxB"}
1
+ {"version":3,"file":"create.d.ts","sourceRoot":"","sources":["../../../../src/commands/ask/harness/create.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,eAAe,CAAC;AAIxC,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,iBAAiB,CAAC;AAwB3C,OAAO,EAEL,KAAK,kBAAkB,EAKxB,MAAM,WAAW,CAAC;AAGnB,MAAM,MAAM,YAAY,GAAG;IACzB,YAAY,EAAE,CACZ,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE,kBAAkB,KACzB,OAAO,CAAC,MAAM,CAAC,CAAC;IACrB;;;;OAIG;IACH,iBAAiB,EAAE,MAAM,MAAM,GAAG,SAAS,CAAC;IAC5C,sEAAsE;IACtE,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5B,CAAC;AAcF,wBAAsB,kBAAkB,CAAC,KAAK,EAAE;IAC9C,GAAG,EAAE,GAAG,CAAC;IACT;;;;OAIG;IACH,UAAU,EAAE,MAAM,CAAC;IACnB,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;OAIG;IACH,OAAO,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB;;;;OAIG;IACH,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe,EAAE,MAAM,CAAC;IACxB,iBAAiB,EAAE,MAAM,CAAC;IAC1B;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;OAIG;IACH,uBAAuB,EAAE,OAAO,CAAC;CAClC,GAAG,OAAO,CAAC,YAAY,CAAC,CAiVxB"}
@@ -12,6 +12,7 @@ import { ensureHarnessPnpmOnPath } from "./pnpmShim.js";
12
12
  import { buildReleaseHostTools, buildReleaseMcpServers } from "./release.js";
13
13
  import { withGetPortEndpoint } from "./sandbox.js";
14
14
  import { ASK_TIMING_ENABLED, createPhaseLog, reportHarnessDiagnostic, timeStreamParts, } from "./timing.js";
15
+ import { withToolCallReplay } from "./toolCallReplay.js";
15
16
  import { HarnessTurnStuck, runPromptTurn, settleWithin, } from "./turn.js";
16
17
  import { createTurnAccumulator, didReachAnthropic } from "./usage.js";
17
18
  /**
@@ -62,14 +63,17 @@ export async function createLocalHarness(input) {
62
63
  // through `auth`, not `env`: passing them via `env` alone lets
63
64
  // createClaudeCode auto-detect AI Gateway from the host process and ignore
64
65
  // the Cargo proxy.
65
- const claudeCode = createClaudeCode({
66
+ // Wrapped so a batched tool call stays known to the stream that is asked to
67
+ // approve it, and a second approval asked at the same time reaches the next
68
+ // stream instead of one nobody reads (see `toolCallReplay.ts`).
69
+ const claudeCode = withToolCallReplay(createClaudeCode({
66
70
  auth: {
67
71
  ANTHROPIC_BASE_URL: anthropicBaseUrl,
68
72
  ANTHROPIC_API_KEY: input.proxyToken,
69
73
  },
70
74
  env: { CLAUDE_CONFIG_DIR: askClaudeConfigDir() },
71
75
  mcpServers,
72
- });
76
+ }));
73
77
  const harnessAgent = new HarnessAgent({
74
78
  // Native AskUserQuestion is withheld on the session, not via
75
79
  // `inactiveTools` (that would drop the host tool of the same name) and
@@ -4,6 +4,7 @@ export { createLocalHarness, type LocalHarness } from "./create.js";
4
4
  export { localHarnessInstructions, localHarnessSkills, } from "./instructions.js";
5
5
  export { resolveWorkspaceLayout } from "./layout.js";
6
6
  export { CARGO_MCP_SERVER_NAME } from "./mcp.js";
7
+ export { replayKnownToolCalls, routeSessionEvents, withToolCallReplay, wrapSession, } from "./toolCallReplay.js";
7
8
  export { HarnessTurnStalled, HarnessTurnStuck, isBridgeClosedError, type LocalStreamHandlers, type LocalStreamOptions, type LocalToolActivity, type LocalToolApproval, runPromptTurn, settleWithin, type StreamLike, type TurnDriver, } from "./turn.js";
8
9
  export { createTurnAccumulator, type LocalTurnAccumulator, type LocalTurnUsage, readClaudeCodeCostUsd, } from "./usage.js";
9
10
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/commands/ask/harness/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAChE,OAAO,EACL,+BAA+B,EAC/B,sCAAsC,EACtC,2BAA2B,EAC3B,4BAA4B,EAC5B,yBAAyB,EACzB,4BAA4B,EAC5B,oBAAoB,EACpB,wBAAwB,GACzB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,kBAAkB,EAAE,KAAK,YAAY,EAAE,MAAM,aAAa,CAAC;AACpE,OAAO,EACL,wBAAwB,EACxB,kBAAkB,GACnB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AACrD,OAAO,EAAE,qBAAqB,EAAE,MAAM,UAAU,CAAC;AACjD,OAAO,EACL,kBAAkB,EAClB,gBAAgB,EAChB,mBAAmB,EACnB,KAAK,mBAAmB,EACxB,KAAK,kBAAkB,EACvB,KAAK,iBAAiB,EACtB,KAAK,iBAAiB,EACtB,aAAa,EACb,YAAY,EACZ,KAAK,UAAU,EACf,KAAK,UAAU,GAChB,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,qBAAqB,EACrB,KAAK,oBAAoB,EACzB,KAAK,cAAc,EACnB,qBAAqB,GACtB,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/commands/ask/harness/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAChE,OAAO,EACL,+BAA+B,EAC/B,sCAAsC,EACtC,2BAA2B,EAC3B,4BAA4B,EAC5B,yBAAyB,EACzB,4BAA4B,EAC5B,oBAAoB,EACpB,wBAAwB,GACzB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,kBAAkB,EAAE,KAAK,YAAY,EAAE,MAAM,aAAa,CAAC;AACpE,OAAO,EACL,wBAAwB,EACxB,kBAAkB,GACnB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AACrD,OAAO,EAAE,qBAAqB,EAAE,MAAM,UAAU,CAAC;AACjD,OAAO,EACL,oBAAoB,EACpB,kBAAkB,EAClB,kBAAkB,EAClB,WAAW,GACZ,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,kBAAkB,EAClB,gBAAgB,EAChB,mBAAmB,EACnB,KAAK,mBAAmB,EACxB,KAAK,kBAAkB,EACvB,KAAK,iBAAiB,EACtB,KAAK,iBAAiB,EACtB,aAAa,EACb,YAAY,EACZ,KAAK,UAAU,EACf,KAAK,UAAU,GAChB,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,qBAAqB,EACrB,KAAK,oBAAoB,EACzB,KAAK,cAAc,EACnB,qBAAqB,GACtB,MAAM,YAAY,CAAC"}
@@ -4,5 +4,6 @@ export { createLocalHarness } from "./create.js";
4
4
  export { localHarnessInstructions, localHarnessSkills, } from "./instructions.js";
5
5
  export { resolveWorkspaceLayout } from "./layout.js";
6
6
  export { CARGO_MCP_SERVER_NAME } from "./mcp.js";
7
+ export { replayKnownToolCalls, routeSessionEvents, withToolCallReplay, wrapSession, } from "./toolCallReplay.js";
7
8
  export { HarnessTurnStalled, HarnessTurnStuck, isBridgeClosedError, runPromptTurn, settleWithin, } from "./turn.js";
8
9
  export { createTurnAccumulator, readClaudeCodeCostUsd, } from "./usage.js";
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Keeps a batched tool call known to the turn that is asked to approve it.
3
+ *
4
+ * When Claude asks for several tools that need approval in one message, the
5
+ * Claude Code bridge emits every `tool-call` up front, then one approval
6
+ * request; the rest come one per stream after each answer. The bridge
7
+ * remembers which calls it emitted for the whole turn, so it does not emit
8
+ * them again. The agent forgets them at the end of every stream, and the next
9
+ * stream throws on the second approval:
10
+ *
11
+ * Harness 'claude-code' emitted approval request '…' for unknown tool call '…'.
12
+ *
13
+ * Upstream: https://github.com/vercel/ai/issues/23499 (open, reproduced on
14
+ * `main`). Each `doPromptTurn` / `doContinueTurn` is one agent stream with its
15
+ * own `emit`, so the session remembers every tool call and replays one into a
16
+ * stream that is about to receive an approval for a call it never saw. Remove
17
+ * this once the agent keeps tool calls for the session.
18
+ *
19
+ * The same batch also loses events outright, which is the stall
20
+ * `HarnessTurnStalled` reports. The agent stops reading a stream at the first
21
+ * approval request in it, but the adapter keeps that stream's listener on the
22
+ * bridge until the turn's `finish`, so whatever the bridge says next goes into
23
+ * a stream nobody reads. When Claude Code asks about two tools at once, the
24
+ * second request lands there: the next stream never hears of it, Claude Code
25
+ * waits for an answer, and the turn waits for output. `routeSessionEvents`
26
+ * holds those events and hands them to the next stream instead.
27
+ */
28
+ import type { HarnessV1, HarnessV1Session, HarnessV1StreamPart } from "@ai-sdk/harness";
29
+ type Emit = (event: HarnessV1StreamPart) => void;
30
+ /** Exported for tests: one stream's `emit`, fed from a session-wide memory. */
31
+ export declare function replayKnownToolCalls(known: Map<string, HarnessV1StreamPart>, emit: Emit): Emit;
32
+ /**
33
+ * Exported for tests: delivers every bridge event of a session once, to the
34
+ * stream the agent is still reading.
35
+ *
36
+ * Each stream's listener stays on the bridge after the agent is done with it,
37
+ * so one event reaches every listener still attached — the same object each
38
+ * time, which is what lets it be delivered once. It goes to the stream opened
39
+ * last. Once that stream has carried an approval request the agent has
40
+ * stopped reading it, so the rest is also held, in order, and opening the next
41
+ * stream hands it over.
42
+ *
43
+ * Held events are still delivered to the stopped stream, so if the agent ever
44
+ * does read past an approval, it misses nothing; it would see them twice.
45
+ */
46
+ export declare function routeSessionEvents(): {
47
+ /** The `emit` every stream hands the bridge, current or not. */
48
+ listen: () => Emit;
49
+ /** A new stream is about to be read: send it everything held. */
50
+ open: (emit: Emit, options: {
51
+ freshTurn: boolean;
52
+ }) => void;
53
+ };
54
+ /** Exported for tests. */
55
+ export declare function wrapSession(session: HarnessV1Session): HarnessV1Session;
56
+ /** The harness, with every session it starts keeping its tool calls. */
57
+ export declare function withToolCallReplay(harness: HarnessV1): HarnessV1;
58
+ export {};
59
+ //# sourceMappingURL=toolCallReplay.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"toolCallReplay.d.ts","sourceRoot":"","sources":["../../../../src/commands/ask/harness/toolCallReplay.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,KAAK,EACV,SAAS,EACT,gBAAgB,EAChB,mBAAmB,EACpB,MAAM,iBAAiB,CAAC;AAEzB,KAAK,IAAI,GAAG,CAAC,KAAK,EAAE,mBAAmB,KAAK,IAAI,CAAC;AAEjD,+EAA+E;AAC/E,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,mBAAmB,CAAC,EACvC,IAAI,EAAE,IAAI,GACT,IAAI,CAqBN;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,kBAAkB,IAAI;IACpC,gEAAgE;IAChE,MAAM,EAAE,MAAM,IAAI,CAAC;IACnB,iEAAiE;IACjE,IAAI,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE;QAAE,SAAS,EAAE,OAAO,CAAA;KAAE,KAAK,IAAI,CAAC;CAC7D,CAsDA;AAED,0BAA0B;AAC1B,wBAAgB,WAAW,CAAC,OAAO,EAAE,gBAAgB,GAAG,gBAAgB,CA4CvE;AAED,wEAAwE;AACxE,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,SAAS,GAAG,SAAS,CAKhE"}
@@ -0,0 +1,152 @@
1
+ /**
2
+ * Keeps a batched tool call known to the turn that is asked to approve it.
3
+ *
4
+ * When Claude asks for several tools that need approval in one message, the
5
+ * Claude Code bridge emits every `tool-call` up front, then one approval
6
+ * request; the rest come one per stream after each answer. The bridge
7
+ * remembers which calls it emitted for the whole turn, so it does not emit
8
+ * them again. The agent forgets them at the end of every stream, and the next
9
+ * stream throws on the second approval:
10
+ *
11
+ * Harness 'claude-code' emitted approval request '…' for unknown tool call '…'.
12
+ *
13
+ * Upstream: https://github.com/vercel/ai/issues/23499 (open, reproduced on
14
+ * `main`). Each `doPromptTurn` / `doContinueTurn` is one agent stream with its
15
+ * own `emit`, so the session remembers every tool call and replays one into a
16
+ * stream that is about to receive an approval for a call it never saw. Remove
17
+ * this once the agent keeps tool calls for the session.
18
+ *
19
+ * The same batch also loses events outright, which is the stall
20
+ * `HarnessTurnStalled` reports. The agent stops reading a stream at the first
21
+ * approval request in it, but the adapter keeps that stream's listener on the
22
+ * bridge until the turn's `finish`, so whatever the bridge says next goes into
23
+ * a stream nobody reads. When Claude Code asks about two tools at once, the
24
+ * second request lands there: the next stream never hears of it, Claude Code
25
+ * waits for an answer, and the turn waits for output. `routeSessionEvents`
26
+ * holds those events and hands them to the next stream instead.
27
+ */
28
+ /** Exported for tests: one stream's `emit`, fed from a session-wide memory. */
29
+ export function replayKnownToolCalls(known, emit) {
30
+ const seen = new Set();
31
+ return (event) => {
32
+ if (event.type === "tool-call") {
33
+ known.set(event.toolCallId, event);
34
+ seen.add(event.toolCallId);
35
+ }
36
+ else if (event.type === "tool-approval-request" &&
37
+ seen.has(event.toolCallId) === false) {
38
+ const call = known.get(event.toolCallId);
39
+ if (call !== undefined) {
40
+ seen.add(event.toolCallId);
41
+ emit(call);
42
+ }
43
+ }
44
+ emit(event);
45
+ };
46
+ }
47
+ /**
48
+ * Exported for tests: delivers every bridge event of a session once, to the
49
+ * stream the agent is still reading.
50
+ *
51
+ * Each stream's listener stays on the bridge after the agent is done with it,
52
+ * so one event reaches every listener still attached — the same object each
53
+ * time, which is what lets it be delivered once. It goes to the stream opened
54
+ * last. Once that stream has carried an approval request the agent has
55
+ * stopped reading it, so the rest is also held, in order, and opening the next
56
+ * stream hands it over.
57
+ *
58
+ * Held events are still delivered to the stopped stream, so if the agent ever
59
+ * does read past an approval, it misses nothing; it would see them twice.
60
+ */
61
+ export function routeSessionEvents() {
62
+ const delivered = new WeakSet();
63
+ const held = [];
64
+ const current = {
65
+ emit: undefined,
66
+ stopped: false,
67
+ };
68
+ const deliver = (event) => {
69
+ if (current.emit === undefined) {
70
+ held.push(event);
71
+ return;
72
+ }
73
+ if (current.stopped === true) {
74
+ held.push(event);
75
+ }
76
+ current.emit(event);
77
+ // The agent ends the stream right here to ask; nothing after this in
78
+ // the same stream is read.
79
+ if (event.type === "tool-approval-request") {
80
+ current.stopped = true;
81
+ }
82
+ };
83
+ return {
84
+ listen: () => (event) => {
85
+ if (delivered.has(event) === true) {
86
+ return;
87
+ }
88
+ delivered.add(event);
89
+ deliver(event);
90
+ },
91
+ open: (emit, options) => {
92
+ current.emit = emit;
93
+ current.stopped = false;
94
+ const backlog = held.splice(0, held.length);
95
+ // A new prompt replaces whatever the bridge was still doing, so what
96
+ // the last turn left behind belongs to nobody.
97
+ if (options.freshTurn === true) {
98
+ return;
99
+ }
100
+ for (const event of backlog) {
101
+ deliver(event);
102
+ }
103
+ },
104
+ };
105
+ }
106
+ /** Exported for tests. */
107
+ export function wrapSession(session) {
108
+ const known = new Map();
109
+ const router = routeSessionEvents();
110
+ const doPromptTurn = (options) => {
111
+ router.open(replayKnownToolCalls(known, options.emit), {
112
+ freshTurn: true,
113
+ });
114
+ return session.doPromptTurn({
115
+ ...options,
116
+ emit: router.listen(),
117
+ });
118
+ };
119
+ const doContinueTurn = (options) => {
120
+ // Opened before the adapter wires the stream: its emit already queues,
121
+ // and anything the bridge says in between belongs to this stream too.
122
+ router.open(replayKnownToolCalls(known, options.emit), {
123
+ freshTurn: false,
124
+ });
125
+ return session.doContinueTurn({
126
+ ...options,
127
+ emit: router.listen(),
128
+ });
129
+ };
130
+ // A proxy rather than a spread or a prototype: a session may be a class
131
+ // with private state, so every other member is read from it and its methods
132
+ // run bound to it.
133
+ return new Proxy(session, {
134
+ get(target, key) {
135
+ if (key === "doPromptTurn") {
136
+ return doPromptTurn;
137
+ }
138
+ if (key === "doContinueTurn") {
139
+ return doContinueTurn;
140
+ }
141
+ const value = Reflect.get(target, key, target);
142
+ return typeof value === "function" ? value.bind(target) : value;
143
+ },
144
+ });
145
+ }
146
+ /** The harness, with every session it starts keeping its tool calls. */
147
+ export function withToolCallReplay(harness) {
148
+ return {
149
+ ...harness,
150
+ doStart: async (options) => wrapSession(await harness.doStart(options)),
151
+ };
152
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cargo-ai/cli",
3
- "version": "1.0.124",
3
+ "version": "1.0.126",
4
4
  "mcpName": "io.github.getcargohq/cargo",
5
5
  "private": false,
6
6
  "license": "UNLICENSED",
@@ -76,8 +76,8 @@
76
76
  "@ai-sdk/harness-claude-code": "1.0.148",
77
77
  "@cargo-ai/api": "^1.0.101",
78
78
  "@cargo-ai/app-sdk": "^1.0.11",
79
- "@cargo-ai/cdk": "^1.0.119",
80
- "@cargo-ai/types": "^1.0.102",
79
+ "@cargo-ai/cdk": "^1.0.120",
80
+ "@cargo-ai/types": "^1.0.103",
81
81
  "@cargo-ai/utils": "^1.0.35",
82
82
  "@cargo-ai/worker-sdk": "^1.0.21",
83
83
  "@modelcontextprotocol/sdk": "^1.31.0",