@cargo-ai/cli 1.0.87 → 1.0.89
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 +1 -1
- package/build/commands/ask/claudeConfig.d.ts +2 -0
- package/build/commands/ask/claudeConfig.d.ts.map +1 -1
- package/build/commands/ask/claudeConfig.js +67 -1
- package/build/commands/ask/index.js +1 -1
- package/build/commands/ask/localHarness.d.ts +15 -1
- package/build/commands/ask/localHarness.d.ts.map +1 -1
- package/build/commands/ask/localHarness.js +127 -42
- package/build/commands/ask/messageStream.js +1 -1
- package/build/commands/ask/prompt.d.ts +7 -1
- package/build/commands/ask/prompt.d.ts.map +1 -1
- package/build/commands/ask/prompt.js +9 -3
- package/build/commands/ask/session.d.ts +8 -1
- package/build/commands/ask/session.d.ts.map +1 -1
- package/build/commands/ask/session.js +364 -62
- package/build/commands/ask/thinking.d.ts +21 -0
- package/build/commands/ask/thinking.d.ts.map +1 -0
- package/build/commands/ask/thinking.js +57 -0
- package/build/commands/ask/typeAhead.d.ts +67 -0
- package/build/commands/ask/typeAhead.d.ts.map +1 -0
- package/build/commands/ask/typeAhead.js +169 -0
- package/build/commands/auth/workspace.js +1 -1
- package/build/commands/runHandler.d.ts +7 -0
- package/build/commands/runHandler.d.ts.map +1 -1
- package/build/commands/runHandler.js +39 -4
- package/build/commands/workspaceManagement/envVar.d.ts +4 -0
- package/build/commands/workspaceManagement/envVar.d.ts.map +1 -0
- package/build/commands/workspaceManagement/envVar.js +71 -0
- package/build/commands/workspaceManagement/index.d.ts.map +1 -1
- package/build/commands/workspaceManagement/index.js +3 -1
- package/package.json +8 -6
package/README.md
CHANGED
|
@@ -207,7 +207,7 @@ Other release verbs (`list`, `get <uuid>`, `get-deployed`, `get-draft`, `update-
|
|
|
207
207
|
**Let your agent install everything.** Paste this into Claude Code, Codex, or Cursor and it handles the CLI, sign-in, and skills for its own platform:
|
|
208
208
|
|
|
209
209
|
```
|
|
210
|
-
Install Cargo by following
|
|
210
|
+
Install Cargo by following https://api.getcargo.io/INSTALL.md
|
|
211
211
|
```
|
|
212
212
|
|
|
213
213
|
**Or install manually.** As a plugin (Claude Code, Codex, Cursor — bundles the skills plus approval and session hooks):
|
|
@@ -18,6 +18,8 @@
|
|
|
18
18
|
*
|
|
19
19
|
* Set `CLAUDE_CONFIG_DIR` to share a config with the `claude` CLI instead —
|
|
20
20
|
* that reinstates the user's hooks and MCP servers, and their startup cost.
|
|
21
|
+
* Nothing is written into a directory named that way: it is the user's, and
|
|
22
|
+
* the settings file in it is theirs to own.
|
|
21
23
|
*/
|
|
22
24
|
export declare function askClaudeConfigDir(): string;
|
|
23
25
|
//# sourceMappingURL=claudeConfig.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"claudeConfig.d.ts","sourceRoot":"","sources":["../../../src/commands/ask/claudeConfig.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"claudeConfig.d.ts","sourceRoot":"","sources":["../../../src/commands/ask/claudeConfig.ts"],"names":[],"mappings":"AA2BA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,CAa3C"}
|
|
@@ -1,6 +1,27 @@
|
|
|
1
|
-
import { mkdirSync } from "node:fs";
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { getConfigDir } from "@cargo-ai/cdk/cli";
|
|
4
|
+
/**
|
|
5
|
+
* Tools that spawn a subagent, refused at source.
|
|
6
|
+
*
|
|
7
|
+
* Approving one is a different kind of decision from approving a Bash: the
|
|
8
|
+
* subagent runs its own tools, and whether those come back here for individual
|
|
9
|
+
* approval is not something we control. A prompt that cannot describe what it
|
|
10
|
+
* is granting is worse than not offering the capability.
|
|
11
|
+
*
|
|
12
|
+
* It has to be refused *here*, in Claude Code's own settings, because the
|
|
13
|
+
* approval prompt never sees it. The harness bridge only writes `ask` rules
|
|
14
|
+
* for the tools in its own `NATIVE_TOOL_KINDS` map, and the subagent tools are
|
|
15
|
+
* not in it — so `permissionMode: "allow-reads"` leaves them ungated, Claude
|
|
16
|
+
* Code never asks the bridge, and `isAlwaysDenied` is never consulted. A deny
|
|
17
|
+
* rule withholds the tool instead: Claude Code reports it as "disabled for
|
|
18
|
+
* this session, in subagents as well as here" and the model does the work
|
|
19
|
+
* itself.
|
|
20
|
+
*
|
|
21
|
+
* Both spellings, because the tool has been called each of them across Claude
|
|
22
|
+
* Code versions and a rule for a tool that does not exist costs nothing.
|
|
23
|
+
*/
|
|
24
|
+
const SUBAGENT_TOOL_NAMES = ["Agent", "Task"];
|
|
4
25
|
/**
|
|
5
26
|
* The Claude Agent SDK loads every filesystem settings source it can find when
|
|
6
27
|
* `settingSources` is omitted, and the harness bridge omits it. A local turn
|
|
@@ -21,6 +42,8 @@ import { getConfigDir } from "@cargo-ai/cdk/cli";
|
|
|
21
42
|
*
|
|
22
43
|
* Set `CLAUDE_CONFIG_DIR` to share a config with the `claude` CLI instead —
|
|
23
44
|
* that reinstates the user's hooks and MCP servers, and their startup cost.
|
|
45
|
+
* Nothing is written into a directory named that way: it is the user's, and
|
|
46
|
+
* the settings file in it is theirs to own.
|
|
24
47
|
*/
|
|
25
48
|
export function askClaudeConfigDir() {
|
|
26
49
|
const configured = process.env["CLAUDE_CONFIG_DIR"];
|
|
@@ -29,5 +52,48 @@ export function askClaudeConfigDir() {
|
|
|
29
52
|
}
|
|
30
53
|
const dir = join(getConfigDir(), "ask-claude-config");
|
|
31
54
|
mkdirSync(dir, { recursive: true });
|
|
55
|
+
denySubagentTools(dir);
|
|
32
56
|
return dir;
|
|
33
57
|
}
|
|
58
|
+
/**
|
|
59
|
+
* Add the subagent deny rules to the settings file in Ask's own config
|
|
60
|
+
* directory, keeping whatever else is in there.
|
|
61
|
+
*
|
|
62
|
+
* Merged rather than written whole, because Claude Code keeps its own state
|
|
63
|
+
* under this directory.
|
|
64
|
+
*
|
|
65
|
+
* A failure here is reported rather than swallowed. There is no second line of
|
|
66
|
+
* defence to fall back on: the approval-time refusal in `isAlwaysDenied` is
|
|
67
|
+
* never reached, which is the whole reason this exists — so a settings file
|
|
68
|
+
* that could not be written means subagents run unattended, and the user is
|
|
69
|
+
* the only one who can tell whether that matters. Reported, not thrown: it
|
|
70
|
+
* must not fail the question they just asked.
|
|
71
|
+
*/
|
|
72
|
+
function denySubagentTools(dir) {
|
|
73
|
+
const path = join(dir, "settings.json");
|
|
74
|
+
try {
|
|
75
|
+
const existing = existsSync(path)
|
|
76
|
+
? JSON.parse(readFileSync(path, "utf-8"))
|
|
77
|
+
: {};
|
|
78
|
+
const settings = typeof existing === "object" && existing !== null
|
|
79
|
+
? existing
|
|
80
|
+
: {};
|
|
81
|
+
const permissionsValue = settings["permissions"];
|
|
82
|
+
const permissions = typeof permissionsValue === "object" && permissionsValue !== null
|
|
83
|
+
? permissionsValue
|
|
84
|
+
: {};
|
|
85
|
+
const denyValue = permissions["deny"];
|
|
86
|
+
const deny = Array.isArray(denyValue) ? denyValue : [];
|
|
87
|
+
const missing = SUBAGENT_TOOL_NAMES.filter((name) => deny.includes(name) === false);
|
|
88
|
+
if (missing.length === 0) {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
writeFileSync(path, `${JSON.stringify({
|
|
92
|
+
...settings,
|
|
93
|
+
permissions: { ...permissions, deny: [...deny, ...missing] },
|
|
94
|
+
}, null, 2)}\n`);
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
process.stderr.write(`Could not write ${path}, so subagent tools are not withheld from this session: ${error instanceof Error ? error.message : "unknown error"}\n`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
@@ -30,7 +30,7 @@ turns use POST /v1/ai/messages/stream, like the webapp chat).
|
|
|
30
30
|
|
|
31
31
|
Ask continues the palette tree and streams the reply as it arrives:
|
|
32
32
|
◇ your message
|
|
33
|
-
|
|
33
|
+
■ reply
|
|
34
34
|
◆ to type the next line (slash and path suggestions as you type)
|
|
35
35
|
Empty Enter or /exit returns to the menu. Ctrl-C cancels the turn in flight
|
|
36
36
|
and comes back to the prompt. Slash commands (/help, /clear, /new, /agent,
|
|
@@ -217,6 +217,8 @@ export declare function runPromptTurn(input: {
|
|
|
217
217
|
unanswered?: LocalToolApproval[];
|
|
218
218
|
/** Overridden by tests; `Infinity` waits forever. */
|
|
219
219
|
stallTimeoutMs?: number;
|
|
220
|
+
/** The same, for the stream while a tool is executing. */
|
|
221
|
+
toolStallTimeoutMs?: number;
|
|
220
222
|
/**
|
|
221
223
|
* Called when the stall watchdog fires, before the turn is rejected — abort
|
|
222
224
|
* the harness stream so Claude Code does not keep running after we give up.
|
|
@@ -241,7 +243,7 @@ export declare class HarnessTurnStuck extends Error {
|
|
|
241
243
|
* going silent. Recoverable — replace the session and ask again.
|
|
242
244
|
*/
|
|
243
245
|
export declare class HarnessTurnStalled extends Error {
|
|
244
|
-
constructor(timeoutMs: number);
|
|
246
|
+
constructor(timeoutMs: number, whileToolRunning?: boolean);
|
|
245
247
|
}
|
|
246
248
|
/**
|
|
247
249
|
* The Claude Code bridge dropped mid-turn. Same recovery as a stall: the
|
|
@@ -260,5 +262,17 @@ export declare function settleWithin(work: Promise<unknown>, timeoutMs: number):
|
|
|
260
262
|
export declare function isAutoApproved(toolName: string): boolean;
|
|
261
263
|
/** Exported for tests. */
|
|
262
264
|
export declare function isAlwaysDenied(toolName: string): boolean;
|
|
265
|
+
/**
|
|
266
|
+
* What Claude Code says the turn cost, off the `finish` part.
|
|
267
|
+
*
|
|
268
|
+
* Two keys, because the value is renamed on its way here: the bridge emits
|
|
269
|
+
* `harnessMetadata`, and `HarnessAgent` republishes the same object as
|
|
270
|
+
* `providerMetadata` on the parts this code actually reads. Looking only for
|
|
271
|
+
* the bridge's name found nothing, so every local turn was ingested at a cost
|
|
272
|
+
* of zero and priced into no credits at all.
|
|
273
|
+
*
|
|
274
|
+
* Exported for tests.
|
|
275
|
+
*/
|
|
276
|
+
export declare function readClaudeCodeCostUsd(part: Record<string, unknown>): number | undefined;
|
|
263
277
|
export {};
|
|
264
278
|
//# sourceMappingURL=localHarness.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"localHarness.d.ts","sourceRoot":"","sources":["../../../src/commands/ask/localHarness.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"localHarness.d.ts","sourceRoot":"","sources":["../../../src/commands/ask/localHarness.ts"],"names":[],"mappings":"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"}
|
|
@@ -14,8 +14,16 @@ import { ensureHarnessPnpmOnPath } from "./pnpmShim.js";
|
|
|
14
14
|
* from Claude Code's own transcript. `CARGO_ASK_TIMING=1` writes the phase
|
|
15
15
|
* boundaries — and the harness's own diagnostics — to stderr. Unset, every
|
|
16
16
|
* call below is a no-op and nothing is added to the stream.
|
|
17
|
+
*
|
|
18
|
+
* `CARGO_ASK_TIMING=parts` adds the whole of each stream part next to its
|
|
19
|
+
* type. Verbose, and the only way to see what the bridge actually sends: the
|
|
20
|
+
* part names alone say a tool call arrived, not that its result came back
|
|
21
|
+
* empty or that the field a reader is looking for is spelled differently here
|
|
22
|
+
* than in the bridge that emitted it.
|
|
17
23
|
*/
|
|
18
|
-
const
|
|
24
|
+
const ASK_TIMING_LEVEL = process.env["CARGO_ASK_TIMING"];
|
|
25
|
+
const ASK_TIMING_ENABLED = ASK_TIMING_LEVEL === "1" || ASK_TIMING_LEVEL === "parts";
|
|
26
|
+
const ASK_TIMING_DUMPS_PARTS = ASK_TIMING_LEVEL === "parts";
|
|
19
27
|
function createPhaseLog(label) {
|
|
20
28
|
if (ASK_TIMING_ENABLED === false) {
|
|
21
29
|
return () => undefined;
|
|
@@ -54,12 +62,24 @@ async function* timeParts(parts, phase) {
|
|
|
54
62
|
: "unknown";
|
|
55
63
|
// Text arrives one delta per token, which would bury everything else.
|
|
56
64
|
if (type.startsWith("text-delta") === false) {
|
|
57
|
-
phase(
|
|
65
|
+
phase(ASK_TIMING_DUMPS_PARTS === true
|
|
66
|
+
? `part ${String(seen)}: ${type} ${dumpPart(part)}`
|
|
67
|
+
: `part ${String(seen)}: ${type}`);
|
|
58
68
|
}
|
|
59
69
|
yield part;
|
|
60
70
|
}
|
|
61
71
|
phase(`stream ended after ${String(seen)} parts`);
|
|
62
72
|
}
|
|
73
|
+
/** One stream part, flattened onto its log line. Truncated: some are huge. */
|
|
74
|
+
function dumpPart(part) {
|
|
75
|
+
try {
|
|
76
|
+
const json = JSON.stringify(part, (_key, value) => value instanceof Error ? `Error(${value.message})` : value);
|
|
77
|
+
return json === undefined ? "" : json.slice(0, 900);
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
return "<unserializable>";
|
|
81
|
+
}
|
|
82
|
+
}
|
|
63
83
|
const LOCAL_WORKING_COPY_INSTRUCTIONS = `You are working in the user's local working copy, not a sandbox. Prefer plan over deploy.
|
|
64
84
|
|
|
65
85
|
The user confirms each Bash, Write, Edit and NotebookEdit call, and can only answer one at a time. Ask for a single one of those per step and wait for its result before asking for the next; two in one step leaves the second waiting on a confirmation it will never be offered. Reads (Read, Glob, Grep) and the Cargo \`mcp__cargo__*\` tools need no confirmation — batch those freely.
|
|
@@ -525,6 +545,9 @@ export async function runPromptTurn(input) {
|
|
|
525
545
|
const stallTimeoutMs = input.stallTimeoutMs === undefined
|
|
526
546
|
? TURN_STALL_TIMEOUT_MS
|
|
527
547
|
: input.stallTimeoutMs;
|
|
548
|
+
const toolStallTimeoutMs = input.toolStallTimeoutMs === undefined
|
|
549
|
+
? TURN_TOOL_STALL_TIMEOUT_MS
|
|
550
|
+
: input.toolStallTimeoutMs;
|
|
528
551
|
const assistantChunks = accumulator.chunks;
|
|
529
552
|
const toolCalls = new Map();
|
|
530
553
|
const turnState = {
|
|
@@ -542,6 +565,7 @@ export async function runPromptTurn(input) {
|
|
|
542
565
|
usage: accumulator.usage,
|
|
543
566
|
turnState,
|
|
544
567
|
stallTimeoutMs,
|
|
568
|
+
toolStallTimeoutMs,
|
|
545
569
|
onStall: input.onStall,
|
|
546
570
|
});
|
|
547
571
|
if (approvals.length === 0) {
|
|
@@ -636,11 +660,11 @@ export async function runPromptTurn(input) {
|
|
|
636
660
|
* only notice.
|
|
637
661
|
*
|
|
638
662
|
* The reprieve is for the caller's benefit only — `"abandoning"` is not an
|
|
639
|
-
* executing status, so the stall watchdog
|
|
640
|
-
* the reprieve to the watchdog would deadlock the very hang it
|
|
641
|
-
* prevent: a dropped sibling would hold the timer off, the stream
|
|
642
|
-
* silent, and the next boundary, the thing that resolves the
|
|
643
|
-
* never arrive.
|
|
663
|
+
* executing status, so the stall watchdog keeps its short deadline throughout
|
|
664
|
+
* it. Extending the reprieve to the watchdog would deadlock the very hang it
|
|
665
|
+
* exists to prevent: a dropped sibling would hold the timer off, the stream
|
|
666
|
+
* would stay silent, and the next boundary, the thing that resolves the
|
|
667
|
+
* ambiguity, would never arrive.
|
|
644
668
|
*/
|
|
645
669
|
function retractAbandonedCalls(announced, onActivity) {
|
|
646
670
|
for (const call of [...announced]) {
|
|
@@ -667,8 +691,41 @@ function retractAbandonedCalls(announced, onActivity) {
|
|
|
667
691
|
* Generous on purpose: this is a deadlock detector, not a latency budget. The
|
|
668
692
|
* clock only runs when nothing is in flight, so the wait is over a turn that
|
|
669
693
|
* is neither working nor talking.
|
|
694
|
+
*
|
|
695
|
+
* `CARGO_ASK_STALL_TIMEOUT_MS` shortens it for the end-to-end tests, which
|
|
696
|
+
* would otherwise have to sit out the real deadline to prove a hang is caught.
|
|
670
697
|
*/
|
|
671
|
-
const TURN_STALL_TIMEOUT_MS = 120_000;
|
|
698
|
+
const TURN_STALL_TIMEOUT_MS = readTimeoutEnv("CARGO_ASK_STALL_TIMEOUT_MS", 120_000);
|
|
699
|
+
/**
|
|
700
|
+
* The same deadline, for a turn that has a tool executing.
|
|
701
|
+
*
|
|
702
|
+
* A tool at work is silent by design — a test run says nothing for as long as
|
|
703
|
+
* it takes — so this cannot be the deadline above. But it cannot be *no*
|
|
704
|
+
* deadline either, which is what it used to be: a tool the user approved and
|
|
705
|
+
* that never reported back left the turn waiting forever, spinner and all,
|
|
706
|
+
* with nothing to do but kill the terminal. That is the shape of every "it got
|
|
707
|
+
* stuck on the tool" report.
|
|
708
|
+
*
|
|
709
|
+
* Twelve minutes, from Claude Code's own ten-minute ceiling on a Bash tool.
|
|
710
|
+
* That is the bound for the common case, not for every case: the ceiling is
|
|
711
|
+
* configurable, and it does not apply to an MCP tool at all. A `mcp__cargo__*`
|
|
712
|
+
* call that genuinely runs longer than this is cut off with its work lost, so
|
|
713
|
+
* `CARGO_ASK_TOOL_STALL_TIMEOUT_MS` raises it for a workspace that does that
|
|
714
|
+
* routinely. The trade is deliberate: a deadline that covers every conceivable
|
|
715
|
+
* tool is indistinguishable from the hang it replaced.
|
|
716
|
+
*
|
|
717
|
+
* Recoverable either way — the session is replaced and the chat, its history
|
|
718
|
+
* and the working copy all survive.
|
|
719
|
+
*/
|
|
720
|
+
const TURN_TOOL_STALL_TIMEOUT_MS = readTimeoutEnv("CARGO_ASK_TOOL_STALL_TIMEOUT_MS", 12 * 60_000);
|
|
721
|
+
function readTimeoutEnv(name, fallbackMs) {
|
|
722
|
+
const raw = process.env[name];
|
|
723
|
+
if (raw === undefined) {
|
|
724
|
+
return fallbackMs;
|
|
725
|
+
}
|
|
726
|
+
const parsed = Number(raw);
|
|
727
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallbackMs;
|
|
728
|
+
}
|
|
672
729
|
/**
|
|
673
730
|
* How long teardown may take before the session is abandoned. Short, because
|
|
674
731
|
* the caller is usually someone who just pressed Ctrl-C.
|
|
@@ -700,9 +757,11 @@ export class HarnessTurnStuck extends Error {
|
|
|
700
757
|
* going silent. Recoverable — replace the session and ask again.
|
|
701
758
|
*/
|
|
702
759
|
export class HarnessTurnStalled extends Error {
|
|
703
|
-
constructor(timeoutMs) {
|
|
760
|
+
constructor(timeoutMs, whileToolRunning = false) {
|
|
704
761
|
const seconds = String(Math.round(timeoutMs / 1000));
|
|
705
|
-
super(
|
|
762
|
+
super(whileToolRunning === true
|
|
763
|
+
? `A tool has been running for ${seconds}s without reporting anything back, which is longer than Claude Code lets any tool run. Treating it as never coming back rather than waiting on it.`
|
|
764
|
+
: `Local Claude Code went quiet for ${seconds}s with no tool running. Often this is a step that asked to run several confirmable tools at once (only the first can be answered), or the coding session itself stalled.`);
|
|
706
765
|
this.name = "HarnessTurnStalled";
|
|
707
766
|
}
|
|
708
767
|
}
|
|
@@ -724,11 +783,15 @@ export function isBridgeClosedError(error) {
|
|
|
724
783
|
* `LOCAL_WORKING_COPY_INSTRUCTIONS` asks for one confirmable tool per step to
|
|
725
784
|
* avoid it; this is the backstop for when the model does it anyway.
|
|
726
785
|
*
|
|
727
|
-
*
|
|
728
|
-
*
|
|
786
|
+
* Two deadlines rather than one, because a tool at work is silent by design
|
|
787
|
+
* and a ten-minute test run must not be cut off: `timeoutMs` for a stream with
|
|
788
|
+
* nothing executing, and the far longer `toolTimeoutMs` while a tool is. What
|
|
789
|
+
* there is never again is *no* deadline — a tool that never reports back used
|
|
790
|
+
* to leave the turn on the spinner forever.
|
|
729
791
|
*/
|
|
730
|
-
async function* withStallTimeout(stream, timeoutMs, isToolInFlight, onStall) {
|
|
731
|
-
if (Number.isFinite(timeoutMs) === false
|
|
792
|
+
async function* withStallTimeout(stream, timeoutMs, toolTimeoutMs, isToolInFlight, onStall) {
|
|
793
|
+
if (Number.isFinite(timeoutMs) === false &&
|
|
794
|
+
Number.isFinite(toolTimeoutMs) === false) {
|
|
732
795
|
for await (const part of stream) {
|
|
733
796
|
yield part;
|
|
734
797
|
}
|
|
@@ -737,9 +800,11 @@ async function* withStallTimeout(stream, timeoutMs, isToolInFlight, onStall) {
|
|
|
737
800
|
const iterator = stream[Symbol.asyncIterator]();
|
|
738
801
|
for (;;) {
|
|
739
802
|
const next = iterator.next();
|
|
740
|
-
const
|
|
803
|
+
const running = isToolInFlight();
|
|
804
|
+
const budgetMs = running === true ? toolTimeoutMs : timeoutMs;
|
|
805
|
+
const result = await (Number.isFinite(budgetMs) === false
|
|
741
806
|
? next
|
|
742
|
-
: rejectIfIdle(next,
|
|
807
|
+
: rejectIfIdle(next, budgetMs, onStall, running));
|
|
743
808
|
if (result.done === true) {
|
|
744
809
|
return;
|
|
745
810
|
}
|
|
@@ -769,7 +834,7 @@ export async function settleWithin(work, timeoutMs) {
|
|
|
769
834
|
}
|
|
770
835
|
}
|
|
771
836
|
}
|
|
772
|
-
async function rejectIfIdle(next, timeoutMs, onStall) {
|
|
837
|
+
async function rejectIfIdle(next, timeoutMs, onStall, whileToolRunning) {
|
|
773
838
|
let timer;
|
|
774
839
|
try {
|
|
775
840
|
return await Promise.race([
|
|
@@ -778,7 +843,7 @@ async function rejectIfIdle(next, timeoutMs, onStall) {
|
|
|
778
843
|
timer = setTimeout(() => {
|
|
779
844
|
// Reject first so Promise.race settles on HarnessTurnStalled, not
|
|
780
845
|
// on an AbortError the interrupt below may race onto `next`.
|
|
781
|
-
reject(new HarnessTurnStalled(timeoutMs));
|
|
846
|
+
reject(new HarnessTurnStalled(timeoutMs, whileToolRunning));
|
|
782
847
|
// Then stop the bridge so teardown is an intentional abort, not a
|
|
783
848
|
// mysterious "bridge closed before the turn finished" after we
|
|
784
849
|
// have already walked away from the stream.
|
|
@@ -837,19 +902,23 @@ export function isAutoApproved(toolName) {
|
|
|
837
902
|
return AUTO_APPROVED_TOOL_NAMES.has(toolName);
|
|
838
903
|
}
|
|
839
904
|
/**
|
|
840
|
-
* `Agent` spawns a subagent to run a task of its own
|
|
841
|
-
*
|
|
842
|
-
*
|
|
843
|
-
*
|
|
844
|
-
*
|
|
845
|
-
*
|
|
905
|
+
* `Agent` spawns a subagent to run a task of its own, and approving one is a
|
|
906
|
+
* different kind of decision from approving a `Bash`: the subagent runs its
|
|
907
|
+
* own tools, can be asked for with `mode: "bypassPermissions"`, and whether
|
|
908
|
+
* its calls come back here for individual approval is not something we control
|
|
909
|
+
* or currently know.
|
|
910
|
+
*
|
|
911
|
+
* The refusal that actually stops it is in `claudeConfig.ts`, as a deny rule
|
|
912
|
+
* in Claude Code's own settings — this never fired, because the approval
|
|
913
|
+
* request it waits for is never sent. The bridge writes `ask` rules only for
|
|
914
|
+
* the tools in its `NATIVE_TOOL_KINDS` map, the subagent tools are not in it,
|
|
915
|
+
* and so `allow-reads` left them running unattended.
|
|
846
916
|
*
|
|
847
|
-
*
|
|
848
|
-
*
|
|
849
|
-
*
|
|
850
|
-
* source — the approval is the only lever there is.
|
|
917
|
+
* Kept as the second line: a user who points `CLAUDE_CONFIG_DIR` at their own
|
|
918
|
+
* Claude Code config gets no deny rule written for them, and a future bridge
|
|
919
|
+
* that does surface the approval should find an answer waiting.
|
|
851
920
|
*/
|
|
852
|
-
const DENIED_TOOL_NAMES = new Set(["Agent"]);
|
|
921
|
+
const DENIED_TOOL_NAMES = new Set(["Agent", "Task"]);
|
|
853
922
|
const DENIED_TOOL_REASON = "Spawning a subagent is not available in this session. Do the work in this turn instead, asking for each tool you need.";
|
|
854
923
|
/** Exported for tests. */
|
|
855
924
|
export function isAlwaysDenied(toolName) {
|
|
@@ -872,7 +941,7 @@ async function consumeTurn(input) {
|
|
|
872
941
|
const approvals = [];
|
|
873
942
|
const { handlers } = input;
|
|
874
943
|
const pendingFiles = new Map();
|
|
875
|
-
const parts = withStallTimeout(input.stream.fullStream, input.stallTimeoutMs, () => input.turnState.announced.some((call) => call.status === "running" || call.status === "approved"), input.onStall);
|
|
944
|
+
const parts = withStallTimeout(input.stream.fullStream, input.stallTimeoutMs, input.toolStallTimeoutMs, () => input.turnState.announced.some((call) => call.status === "running" || call.status === "approved"), input.onStall);
|
|
876
945
|
for await (const rawPart of parts) {
|
|
877
946
|
if (typeof rawPart !== "object" || rawPart === null) {
|
|
878
947
|
continue;
|
|
@@ -1084,19 +1153,35 @@ function readCount(value) {
|
|
|
1084
1153
|
? value
|
|
1085
1154
|
: 0;
|
|
1086
1155
|
}
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1156
|
+
/**
|
|
1157
|
+
* What Claude Code says the turn cost, off the `finish` part.
|
|
1158
|
+
*
|
|
1159
|
+
* Two keys, because the value is renamed on its way here: the bridge emits
|
|
1160
|
+
* `harnessMetadata`, and `HarnessAgent` republishes the same object as
|
|
1161
|
+
* `providerMetadata` on the parts this code actually reads. Looking only for
|
|
1162
|
+
* the bridge's name found nothing, so every local turn was ingested at a cost
|
|
1163
|
+
* of zero and priced into no credits at all.
|
|
1164
|
+
*
|
|
1165
|
+
* Exported for tests.
|
|
1166
|
+
*/
|
|
1167
|
+
export function readClaudeCodeCostUsd(part) {
|
|
1168
|
+
for (const key of ["providerMetadata", "harnessMetadata"]) {
|
|
1169
|
+
const metadata = part[key];
|
|
1170
|
+
if (typeof metadata !== "object" || metadata === null) {
|
|
1171
|
+
continue;
|
|
1172
|
+
}
|
|
1173
|
+
const claudeCode = metadata["claude-code"];
|
|
1174
|
+
if (typeof claudeCode !== "object" || claudeCode === null) {
|
|
1175
|
+
continue;
|
|
1176
|
+
}
|
|
1177
|
+
const costUsd = claudeCode["costUsd"];
|
|
1178
|
+
if (typeof costUsd === "number" &&
|
|
1179
|
+
Number.isFinite(costUsd) &&
|
|
1180
|
+
costUsd > 0) {
|
|
1181
|
+
return costUsd;
|
|
1182
|
+
}
|
|
1095
1183
|
}
|
|
1096
|
-
|
|
1097
|
-
return typeof costUsd === "number" && Number.isFinite(costUsd) && costUsd > 0
|
|
1098
|
-
? costUsd
|
|
1099
|
-
: undefined;
|
|
1184
|
+
return undefined;
|
|
1100
1185
|
}
|
|
1101
1186
|
/**
|
|
1102
1187
|
* The AI SDK part nests the whole `toolCall`; the harness bridge part carries
|
|
@@ -16,5 +16,11 @@ export declare class AskInterrupted extends Error {
|
|
|
16
16
|
* Enter submits (accepting the highlighted suggestion when the menu is open).
|
|
17
17
|
* Empty Enter leaves. Tab inserts the highlight without submitting.
|
|
18
18
|
*/
|
|
19
|
-
export declare function readAskLine(input: NodeJS.ReadableStream, cwd: string
|
|
19
|
+
export declare function readAskLine(input: NodeJS.ReadableStream, cwd: string,
|
|
20
|
+
/**
|
|
21
|
+
* What the user had already typed while the last turn was running. The line
|
|
22
|
+
* opens with it in place and the cursor after it, so a question started
|
|
23
|
+
* mid-reply is continued rather than retyped.
|
|
24
|
+
*/
|
|
25
|
+
initialLine?: string): Promise<string | undefined>;
|
|
20
26
|
//# sourceMappingURL=prompt.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prompt.d.ts","sourceRoot":"","sources":["../../../src/commands/ask/prompt.ts"],"names":[],"mappings":";AAiBA;;;;;;GAMG;AACH,qBAAa,cAAe,SAAQ,KAAK;;CAKxC;AASD;;;;;;GAMG;AACH,wBAAsB,WAAW,CAC/B,KAAK,EAAE,MAAM,CAAC,cAAc,EAC5B,GAAG,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"prompt.d.ts","sourceRoot":"","sources":["../../../src/commands/ask/prompt.ts"],"names":[],"mappings":";AAiBA;;;;;;GAMG;AACH,qBAAa,cAAe,SAAQ,KAAK;;CAKxC;AASD;;;;;;GAMG;AACH,wBAAsB,WAAW,CAC/B,KAAK,EAAE,MAAM,CAAC,cAAc,EAC5B,GAAG,EAAE,MAAM;AACX;;;;GAIG;AACH,WAAW,SAAK,GACf,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAiQ7B"}
|
|
@@ -25,10 +25,16 @@ function isTTYStream(stream) {
|
|
|
25
25
|
* Enter submits (accepting the highlighted suggestion when the menu is open).
|
|
26
26
|
* Empty Enter leaves. Tab inserts the highlight without submitting.
|
|
27
27
|
*/
|
|
28
|
-
export async function readAskLine(input, cwd
|
|
28
|
+
export async function readAskLine(input, cwd,
|
|
29
|
+
/**
|
|
30
|
+
* What the user had already typed while the last turn was running. The line
|
|
31
|
+
* opens with it in place and the cursor after it, so a question started
|
|
32
|
+
* mid-reply is continued rather than retyped.
|
|
33
|
+
*/
|
|
34
|
+
initialLine = "") {
|
|
29
35
|
return new Promise((resolvePromise, rejectPromise) => {
|
|
30
|
-
let line =
|
|
31
|
-
let cursor =
|
|
36
|
+
let line = initialLine;
|
|
37
|
+
let cursor = initialLine.length;
|
|
32
38
|
let selected = 0;
|
|
33
39
|
let painted = false;
|
|
34
40
|
let finished = false;
|
|
@@ -68,6 +68,13 @@ type ChatTurnClaim = {
|
|
|
68
68
|
*/
|
|
69
69
|
export declare function claimChatTurn(api: Api, chatUuid: string, ownerId: string): Promise<ChatTurnClaim>;
|
|
70
70
|
export declare function doesChatExist(api: Api, chatUuid: string): Promise<boolean>;
|
|
71
|
+
/**
|
|
72
|
+
* Greedy wrap on spaces. A word longer than the width keeps its own line
|
|
73
|
+
* rather than being cut mid-token.
|
|
74
|
+
*
|
|
75
|
+
* Exported for tests.
|
|
76
|
+
*/
|
|
77
|
+
export declare function wrapLine(text: string, width: number): string[];
|
|
71
78
|
/**
|
|
72
79
|
* A path inside the session directory reads better relative to it; anything
|
|
73
80
|
* outside stays absolute so it is obvious the turn left the project.
|
|
@@ -78,7 +85,7 @@ export declare function displayPath(path: string, dir: string): string;
|
|
|
78
85
|
/**
|
|
79
86
|
* Same tree grammar as the palette:
|
|
80
87
|
* ◇ <your message>
|
|
81
|
-
*
|
|
88
|
+
* ■ reply…
|
|
82
89
|
* ◆
|
|
83
90
|
* Empty Enter or /exit leaves to the parent palette.
|
|
84
91
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../../src/commands/ask/session.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,eAAe,CAAC;AAIxC,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../../src/commands/ask/session.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,eAAe,CAAC;AAIxC,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,cAAc,CAAC;AAmCxC,MAAM,MAAM,iBAAiB,GAAG;IAC9B,GAAG,EAAE,GAAG,CAAC;IACT,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,OAAO,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC;CACb,CAAC;AAEF,KAAK,WAAW,GAAG;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,CAAC;AAkC1D;;;;;;;;;;;GAWG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE;IAC1C,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,KAAK,EAAE,MAAM,CAAC;CACf,GAAG,MAAM,CAKT;AAiBD;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,GAAG,SAAS,GAAG,WAAW,CAErE;AA8ED;;;;;;;;;GASG;AACH;;;;GAIG;AACH,KAAK,aAAa,GACd;IAAE,OAAO,EAAE,SAAS,CAAA;CAAE,GACtB;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,GACnB;IAAE,OAAO,EAAE,aAAa,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,CAAC;AAE/C;;;;GAIG;AACH,wBAAsB,aAAa,CACjC,GAAG,EAAE,GAAG,EACR,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,aAAa,CAAC,CAkBxB;AAyCD,wBAAsB,aAAa,CACjC,GAAG,EAAE,GAAG,EACR,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,OAAO,CAAC,CAqBlB;AAeD;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAe9D;AAicD;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAQ7D;AA2ID;;;;;;GAMG;AACH,wBAAsB,aAAa,CACjC,OAAO,EAAE,iBAAiB,GACzB,OAAO,CAAC,SAAS,GAAG,MAAM,CAAC,CAoqB7B;AAED,wBAAsB,WAAW,CAAC,KAAK,EAAE;IACvC,GAAG,EAAE,GAAG,CAAC;IACT,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC;CACb,GAAG,OAAO,CAAC,IAAI,CAAC,CA8HhB"}
|