@cargo-ai/cli 1.0.78 → 1.0.81
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 +12 -0
- package/build/commands/ask/argvFlags.d.ts +18 -0
- package/build/commands/ask/argvFlags.d.ts.map +1 -0
- package/build/commands/ask/argvFlags.js +46 -0
- package/build/commands/ask/artifacts.d.ts +2 -0
- package/build/commands/ask/artifacts.d.ts.map +1 -0
- package/build/commands/ask/artifacts.js +100 -0
- package/build/commands/ask/binding.d.ts +13 -0
- package/build/commands/ask/binding.d.ts.map +1 -0
- package/build/commands/ask/binding.js +19 -0
- package/build/commands/ask/claudeConfig.d.ts +23 -0
- package/build/commands/ask/claudeConfig.d.ts.map +1 -0
- package/build/commands/ask/claudeConfig.js +33 -0
- package/build/commands/ask/completer.d.ts +48 -0
- package/build/commands/ask/completer.d.ts.map +1 -0
- package/build/commands/ask/completer.js +217 -0
- package/build/commands/ask/history.d.ts +32 -0
- package/build/commands/ask/history.d.ts.map +1 -0
- package/build/commands/ask/history.js +122 -0
- package/build/commands/ask/index.d.ts +14 -0
- package/build/commands/ask/index.d.ts.map +1 -0
- package/build/commands/ask/index.js +111 -0
- package/build/commands/ask/localHarness.d.ts +225 -0
- package/build/commands/ask/localHarness.d.ts.map +1 -0
- package/build/commands/ask/localHarness.js +1074 -0
- package/build/commands/ask/messageStream.d.ts +33 -0
- package/build/commands/ask/messageStream.d.ts.map +1 -0
- package/build/commands/ask/messageStream.js +114 -0
- package/build/commands/ask/persist.d.ts +5 -0
- package/build/commands/ask/persist.d.ts.map +1 -0
- package/build/commands/ask/persist.js +31 -0
- package/build/commands/ask/pnpmShim.d.ts +20 -0
- package/build/commands/ask/pnpmShim.d.ts.map +1 -0
- package/build/commands/ask/pnpmShim.js +39 -0
- package/build/commands/ask/prompt.d.ts +20 -0
- package/build/commands/ask/prompt.d.ts.map +1 -0
- package/build/commands/ask/prompt.js +228 -0
- package/build/commands/ask/rewriteChatFlag.d.ts +9 -0
- package/build/commands/ask/rewriteChatFlag.d.ts.map +1 -0
- package/build/commands/ask/rewriteChatFlag.js +26 -0
- package/build/commands/ask/session.d.ts +94 -0
- package/build/commands/ask/session.d.ts.map +1 -0
- package/build/commands/ask/session.js +965 -0
- package/build/commands/ask/slash.d.ts +20 -0
- package/build/commands/ask/slash.d.ts.map +1 -0
- package/build/commands/ask/slash.js +73 -0
- package/build/commands/ask/state.d.ts +9 -0
- package/build/commands/ask/state.d.ts.map +1 -0
- package/build/commands/ask/state.js +18 -0
- package/build/commands/bookDemo.d.ts +14 -0
- package/build/commands/bookDemo.d.ts.map +1 -0
- package/build/commands/bookDemo.js +100 -0
- package/build/commands/runHandler.d.ts +9 -1
- package/build/commands/runHandler.d.ts.map +1 -1
- package/build/commands/runHandler.js +16 -1
- package/build/index.js +56 -18
- package/build/paletteLayout.d.ts +6 -1
- package/build/paletteLayout.d.ts.map +1 -1
- package/build/paletteLayout.js +35 -9
- package/build/utils/cdkState.d.ts +17 -4
- package/build/utils/cdkState.d.ts.map +1 -1
- package/build/utils/cdkState.js +26 -4
- package/build/utils/loadCompiledAgent.d.ts.map +1 -1
- package/build/utils/loadCompiledAgent.js +51 -11
- package/build/utils/prompt.d.ts.map +1 -1
- package/build/utils/prompt.js +4 -1
- package/package.json +8 -4
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How many of the newest messages to replay. A chat far longer than this is
|
|
3
|
+
* past the point where replaying all of it helps: the recent turns carry the
|
|
4
|
+
* thread.
|
|
5
|
+
*/
|
|
6
|
+
const HISTORY_MESSAGE_LIMIT = 40;
|
|
7
|
+
/** Chosen so that all but the longest chats are read in a single request. */
|
|
8
|
+
const HISTORY_PAGE_SIZE = 500;
|
|
9
|
+
/** Stop walking rather than paging a pathological chat forever. */
|
|
10
|
+
const HISTORY_MAX_PAGES = 20;
|
|
11
|
+
/**
|
|
12
|
+
* Budget for the replayed transcript. Claude Code prices the preamble as
|
|
13
|
+
* ordinary input on the first turn of the session, so this is a real cost and
|
|
14
|
+
* not just a context-window concern.
|
|
15
|
+
*/
|
|
16
|
+
const HISTORY_MAX_CHARS = 20_000;
|
|
17
|
+
/**
|
|
18
|
+
* The earlier turns of a chat, rendered for a Claude Code session that has
|
|
19
|
+
* never seen them.
|
|
20
|
+
*
|
|
21
|
+
* Local Ask starts a fresh harness session on every CLI run — the harness can
|
|
22
|
+
* only resume Claude Code by rerunning it, and a rerun cancels whatever tool
|
|
23
|
+
* approval is in flight, which Claude Code then records as the user refusing
|
|
24
|
+
* the tool. Continuity therefore comes from the chat history the API already
|
|
25
|
+
* has, replayed into the first prompt.
|
|
26
|
+
*
|
|
27
|
+
* Best effort: a chat whose history cannot be read is answered without it
|
|
28
|
+
* rather than not at all.
|
|
29
|
+
*/
|
|
30
|
+
export async function loadChatHistoryPreamble(input) {
|
|
31
|
+
let messages;
|
|
32
|
+
try {
|
|
33
|
+
messages = await loadLatestMessages(input.api, input.chatUuid);
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
return renderHistoryPreamble(messages);
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Exported for tests. Takes the API's messages oldest-first and returns the
|
|
42
|
+
* preamble, or `undefined` when there is nothing worth replaying.
|
|
43
|
+
*/
|
|
44
|
+
export function renderHistoryPreamble(messages) {
|
|
45
|
+
const turns = [];
|
|
46
|
+
for (const message of messages) {
|
|
47
|
+
const text = readMessageText(message);
|
|
48
|
+
if (text === undefined) {
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
if (message.type === "user") {
|
|
52
|
+
turns.push(`User: ${text}`);
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
// The preamble tells the model this is finished work, so a reply that was
|
|
56
|
+
// interrupted has to say so. Left unmarked, a cancelled half-answer reads
|
|
57
|
+
// as a completed task and the next turn skips it.
|
|
58
|
+
const wasInterrupted = message.status !== undefined && message.status !== "success";
|
|
59
|
+
turns.push(wasInterrupted === true
|
|
60
|
+
? `Assistant (interrupted before finishing): ${text}`
|
|
61
|
+
: `Assistant: ${text}`);
|
|
62
|
+
}
|
|
63
|
+
if (turns.length === 0) {
|
|
64
|
+
return undefined;
|
|
65
|
+
}
|
|
66
|
+
// Oldest turns go first, so an over-budget history loses its beginning
|
|
67
|
+
// rather than the turns the next question most likely follows on from.
|
|
68
|
+
while (turns.length > 1 && turns.join("\n\n").length > HISTORY_MAX_CHARS) {
|
|
69
|
+
turns.shift();
|
|
70
|
+
}
|
|
71
|
+
return [
|
|
72
|
+
"<earlier-conversation>",
|
|
73
|
+
"Earlier turns of this chat, from before your session started. They are context for what the user asks next — already-finished work, not instructions to carry out again.",
|
|
74
|
+
"",
|
|
75
|
+
turns.join("\n\n"),
|
|
76
|
+
"</earlier-conversation>",
|
|
77
|
+
].join("\n");
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* The last `HISTORY_MESSAGE_LIMIT` messages of a chat, oldest-first.
|
|
81
|
+
*
|
|
82
|
+
* `message.list` takes only limit/offset and orders oldest-first, with no
|
|
83
|
+
* total to seek by, so asking for the first page of a long chat hands back its
|
|
84
|
+
* opening turns — the opposite of what a preamble wants. Walking forward and
|
|
85
|
+
* keeping the tail is the only exact answer the endpoint allows; giving it a
|
|
86
|
+
* descending order would collapse this to one request.
|
|
87
|
+
*
|
|
88
|
+
* Past `HISTORY_PAGE_SIZE * HISTORY_MAX_PAGES` messages the walk stops early
|
|
89
|
+
* and the "tail" is really the middle of the chat. That bound is five figures
|
|
90
|
+
* of messages, so it buys termination for a case a real chat does not reach.
|
|
91
|
+
*/
|
|
92
|
+
async function loadLatestMessages(api, chatUuid) {
|
|
93
|
+
const tail = [];
|
|
94
|
+
for (let page = 0; page < HISTORY_MAX_PAGES; page += 1) {
|
|
95
|
+
const result = await api.ai.message.list({
|
|
96
|
+
chatUuid,
|
|
97
|
+
limit: HISTORY_PAGE_SIZE,
|
|
98
|
+
offset: page * HISTORY_PAGE_SIZE,
|
|
99
|
+
});
|
|
100
|
+
tail.push(...result.messages);
|
|
101
|
+
if (tail.length > HISTORY_MESSAGE_LIMIT) {
|
|
102
|
+
tail.splice(0, tail.length - HISTORY_MESSAGE_LIMIT);
|
|
103
|
+
}
|
|
104
|
+
if (result.messages.length < HISTORY_PAGE_SIZE) {
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return tail;
|
|
109
|
+
}
|
|
110
|
+
function readMessageText(message) {
|
|
111
|
+
const texts = [];
|
|
112
|
+
for (const part of message.parts) {
|
|
113
|
+
if (part.type !== "text") {
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
const { text } = part;
|
|
117
|
+
if (typeof text === "string" && text.trim() !== "") {
|
|
118
|
+
texts.push(text.trim());
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return texts.length === 0 ? undefined : texts.join("\n");
|
|
122
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Command } from "commander";
|
|
2
|
+
import type { Api } from "../../api.js";
|
|
3
|
+
export type AskOptions = {
|
|
4
|
+
continue?: boolean;
|
|
5
|
+
print?: boolean;
|
|
6
|
+
agentUuid?: string;
|
|
7
|
+
chatUuid?: string;
|
|
8
|
+
dir?: string;
|
|
9
|
+
};
|
|
10
|
+
export type AskCommandDeps = {
|
|
11
|
+
openPalette: () => Promise<void>;
|
|
12
|
+
};
|
|
13
|
+
export declare function registerAskCommand(program: Command, getApi: () => Api, deps: AskCommandDeps): void;
|
|
14
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/ask/index.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEzC,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,cAAc,CAAC;AAOxC,MAAM,MAAM,UAAU,GAAG;IACvB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,GAAG,CAAC,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC3B,WAAW,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAClC,CAAC;AAEF,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,OAAO,EAChB,MAAM,EAAE,MAAM,GAAG,EACjB,IAAI,EAAE,cAAc,GACnB,IAAI,CAwIN"}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { resolve } from "node:path";
|
|
2
|
+
import { isInteractive } from "@cargo-ai/cdk/cli";
|
|
3
|
+
import { parseBooleanOption } from "../../utils/booleanOption.js";
|
|
4
|
+
import { ExitCodes, failWith } from "../runHandler.js";
|
|
5
|
+
import { loadAskState } from "./persist.js";
|
|
6
|
+
import { AskInterrupted } from "./prompt.js";
|
|
7
|
+
import { doesChatExist, runAskPrint, runAskSession } from "./session.js";
|
|
8
|
+
export function registerAskCommand(program, getApi, deps) {
|
|
9
|
+
program
|
|
10
|
+
.command("ask")
|
|
11
|
+
.description("Chat in the palette tree. Defaults to Claude Code on this machine; optional --agent-uuid streams as that agent.")
|
|
12
|
+
.argument("[message...]", "Question to send as the first message")
|
|
13
|
+
.option("--continue [true|false]", "Reopen the last chat from this machine (defaults to true when passed bare; starts a new chat when there is none)", parseBooleanOption)
|
|
14
|
+
.option("--print [true|false]", "One turn, JSON on stdout (defaults to true when passed bare)", parseBooleanOption)
|
|
15
|
+
.option("--agent-uuid <uuid>", "Talk to this workspace agent instead of the local Claude Code default")
|
|
16
|
+
.option("--chat-uuid <uuid>", "Existing chat to continue")
|
|
17
|
+
.option("--dir <path>", "Working directory for the local Claude Code workspace (default: cwd)")
|
|
18
|
+
.addHelpText("after", `
|
|
19
|
+
Examples:
|
|
20
|
+
$ cargo-ai ask
|
|
21
|
+
$ cargo-ai ask "who touched Acme last week"
|
|
22
|
+
$ cargo-ai ask --continue
|
|
23
|
+
$ cargo-ai ask --print "summarise this workspace"
|
|
24
|
+
$ cargo-ai --chat
|
|
25
|
+
|
|
26
|
+
Default Ask is Claude Code on this machine (--dir, default cwd), with no
|
|
27
|
+
agent. --continue reopens the last chat thread but does not reattach an
|
|
28
|
+
agent. Workspace agents are optional via --agent-uuid or /agent (remote
|
|
29
|
+
turns use POST /v1/ai/messages/stream, like the webapp chat).
|
|
30
|
+
|
|
31
|
+
Ask continues the palette tree and streams the reply as it arrives:
|
|
32
|
+
◇ your message
|
|
33
|
+
🧱 reply
|
|
34
|
+
◆ to type the next line (slash and path suggestions as you type)
|
|
35
|
+
Empty Enter or /exit returns to the menu. Ctrl-C cancels the turn in flight
|
|
36
|
+
and comes back to the prompt. Slash commands (/help, /clear, /new, /agent,
|
|
37
|
+
/cancel, /exit) never reach the model. Cargo CLI commands stay in the
|
|
38
|
+
palette (/orchestration, …).
|
|
39
|
+
|
|
40
|
+
Local turns read files freely and ask before anything that writes or runs a
|
|
41
|
+
shell command; they can also query workspace data read-only (models, schema,
|
|
42
|
+
SQL).
|
|
43
|
+
|
|
44
|
+
--print is the non-interactive path: one turn, JSON on stdout. It denies
|
|
45
|
+
tools that need approval, since nobody is there to answer. Pipes, CI, and
|
|
46
|
+
coding agents must use it.`)
|
|
47
|
+
.action(async (messageParts, opts) => {
|
|
48
|
+
const message = messageParts.length > 0 ? messageParts.join(" ") : undefined;
|
|
49
|
+
const api = getApi();
|
|
50
|
+
const dir = opts.dir !== undefined ? resolve(opts.dir) : process.cwd();
|
|
51
|
+
let chatUuid = opts.chatUuid;
|
|
52
|
+
const agentUuid = opts.agentUuid;
|
|
53
|
+
// Resolved before the --print branch so a non-interactive turn can
|
|
54
|
+
// resume the last thread too. Resumes the chat thread only: the agent
|
|
55
|
+
// stays unset so Ask keeps the local Claude Code default unless the
|
|
56
|
+
// user passed --agent-uuid.
|
|
57
|
+
//
|
|
58
|
+
// The saved chat has to still be there. Deleting one is a soft delete,
|
|
59
|
+
// so it stops being readable while its uuid keeps the primary key —
|
|
60
|
+
// carrying it forward would fail the create on the first question, and
|
|
61
|
+
// the palette opens Ask with --continue every time. A chat that is gone
|
|
62
|
+
// means a fresh thread, not a resurrection of its uuid.
|
|
63
|
+
if (opts.continue === true && chatUuid === undefined) {
|
|
64
|
+
const state = loadAskState();
|
|
65
|
+
if (state !== undefined &&
|
|
66
|
+
(await doesChatExist(api, state.chatUuid)) === true) {
|
|
67
|
+
chatUuid = state.chatUuid;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if (opts.print === true) {
|
|
71
|
+
if (message === undefined) {
|
|
72
|
+
failWith("--print needs a message.", {
|
|
73
|
+
code: ExitCodes.InvalidUsage,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
await runAskPrint({
|
|
77
|
+
api,
|
|
78
|
+
message,
|
|
79
|
+
agentUuid,
|
|
80
|
+
chatUuid,
|
|
81
|
+
dir,
|
|
82
|
+
});
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
if (isInteractive() === false || process.stdout.isTTY !== true) {
|
|
86
|
+
failWith("The chat needs a terminal. Re-run with --print for one JSON turn.", { code: ExitCodes.InvalidUsage });
|
|
87
|
+
}
|
|
88
|
+
try {
|
|
89
|
+
await runAskSession({
|
|
90
|
+
api,
|
|
91
|
+
message,
|
|
92
|
+
continueChat: chatUuid !== undefined,
|
|
93
|
+
agentUuid,
|
|
94
|
+
chatUuid,
|
|
95
|
+
dir,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
// Ctrl-C at the prompt. The session has torn the harness down on its
|
|
100
|
+
// way out, so all that is left is the conventional SIGINT status —
|
|
101
|
+
// and not dropping the user back into the palette they just left.
|
|
102
|
+
if (error instanceof AskInterrupted) {
|
|
103
|
+
process.exit(130);
|
|
104
|
+
}
|
|
105
|
+
throw error;
|
|
106
|
+
}
|
|
107
|
+
if (process.env["CARGO_PALETTE"] !== "1") {
|
|
108
|
+
await deps.openPalette();
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
}
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import type { Api } from "../../api.js";
|
|
2
|
+
/**
|
|
3
|
+
* Always-on local-copy rails, plus CDK rails only when `dir` is a CDK project.
|
|
4
|
+
* Exported for tests.
|
|
5
|
+
*/
|
|
6
|
+
export declare function localHarnessInstructions(dir: string): string;
|
|
7
|
+
/** Exported for tests. */
|
|
8
|
+
export declare function localHarnessSkills(dir: string): Array<{
|
|
9
|
+
name: string;
|
|
10
|
+
description: string;
|
|
11
|
+
content: string;
|
|
12
|
+
}>;
|
|
13
|
+
/**
|
|
14
|
+
* A built-in tool Claude Code wants to run that `permissionMode` does not
|
|
15
|
+
* auto-approve (anything that writes or shells out under `allow-reads`).
|
|
16
|
+
*/
|
|
17
|
+
export type LocalToolApproval = {
|
|
18
|
+
approvalId: string;
|
|
19
|
+
toolCallId: string;
|
|
20
|
+
toolName: string;
|
|
21
|
+
/** Compact one-line rendering of the tool input, for the prompt. */
|
|
22
|
+
summary: string;
|
|
23
|
+
input: unknown;
|
|
24
|
+
};
|
|
25
|
+
/** A tool starting or finishing, so the caller can show what is happening. */
|
|
26
|
+
export type LocalToolActivity = {
|
|
27
|
+
kind: "start";
|
|
28
|
+
toolName: string;
|
|
29
|
+
summary: string;
|
|
30
|
+
} | {
|
|
31
|
+
kind: "end";
|
|
32
|
+
toolName: string;
|
|
33
|
+
isError: boolean;
|
|
34
|
+
};
|
|
35
|
+
export type LocalStreamHandlers = {
|
|
36
|
+
onTextDelta?: (delta: string) => void;
|
|
37
|
+
/**
|
|
38
|
+
* Decide whether the tool may run. Omitted (`--print`, non-interactive)
|
|
39
|
+
* means deny — the turn continues and the model reports what it could not
|
|
40
|
+
* do rather than silently running commands on the user's machine.
|
|
41
|
+
*/
|
|
42
|
+
onApproval?: (approval: LocalToolApproval) => Promise<boolean>;
|
|
43
|
+
onActivity?: (activity: LocalToolActivity) => void;
|
|
44
|
+
/** Path of a file the turn wrote or edited, as the tool named it. */
|
|
45
|
+
onFileTouched?: (path: string) => void;
|
|
46
|
+
/** Something worth saying that is not part of the reply. */
|
|
47
|
+
onNotice?: (message: string) => void;
|
|
48
|
+
};
|
|
49
|
+
export type LocalStreamOptions = LocalStreamHandlers & {
|
|
50
|
+
/** Aborts the turn; the harness interrupts Claude Code gracefully. */
|
|
51
|
+
abortSignal?: AbortSignal;
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* What one turn spent upstream, summed over the streams a suspended turn is
|
|
55
|
+
* made of. `costUsd` is Claude Code's own accounting; the API prices it into
|
|
56
|
+
* credits so the CLI never states a credit count itself.
|
|
57
|
+
*/
|
|
58
|
+
export type LocalTurnUsage = {
|
|
59
|
+
costUsd: number;
|
|
60
|
+
totalTokens: number;
|
|
61
|
+
inputTokens: number;
|
|
62
|
+
outputTokens: number;
|
|
63
|
+
cacheReadTokens: number;
|
|
64
|
+
cacheWriteTokens: number;
|
|
65
|
+
reasoningTokens: number;
|
|
66
|
+
};
|
|
67
|
+
/**
|
|
68
|
+
* Text and usage of a turn in progress, owned by the caller rather than by
|
|
69
|
+
* `runPromptTurn`: a cancelled or failed turn still spent those tokens, and
|
|
70
|
+
* they have to reach the API anyway.
|
|
71
|
+
*/
|
|
72
|
+
export type LocalTurnAccumulator = {
|
|
73
|
+
chunks: string[];
|
|
74
|
+
usage: LocalTurnUsage;
|
|
75
|
+
};
|
|
76
|
+
/** Exported for tests. */
|
|
77
|
+
export declare function createTurnAccumulator(): LocalTurnAccumulator;
|
|
78
|
+
export type LocalHarnessHandle = {
|
|
79
|
+
streamPrompt: (question: string, options?: LocalStreamOptions) => Promise<string>;
|
|
80
|
+
/**
|
|
81
|
+
* When a turn last reached Anthropic, or undefined if none has. The proxy
|
|
82
|
+
* token re-arms on every call it makes, so this — not the mint time — is
|
|
83
|
+
* what its real expiry runs from.
|
|
84
|
+
*/
|
|
85
|
+
proxyLastUsedAtMs: () => number | undefined;
|
|
86
|
+
/** Stops Claude Code and its bridge; the session is not resumable. */
|
|
87
|
+
close: () => Promise<void>;
|
|
88
|
+
};
|
|
89
|
+
/**
|
|
90
|
+
* The workspace half of local Ask: the hosted Cargo MCP server, reached
|
|
91
|
+
* through this CLI's own `mcp` bridge.
|
|
92
|
+
*
|
|
93
|
+
* Going through the bridge rather than configuring the HTTP endpoint directly
|
|
94
|
+
* is what keeps a long chat working — the bridge rebuilds the `Authorization`
|
|
95
|
+
* header per request from the same credentials every other command uses, so an
|
|
96
|
+
* OAuth session that expires mid-session renews instead of 401ing. `CARGO_BASE_URL`
|
|
97
|
+
* is pinned so the bridge cannot end up on a different API than this session.
|
|
98
|
+
*
|
|
99
|
+
* Everything the workspace can do arrives this way: actions, runs, models and
|
|
100
|
+
* SQL, the context, usage. That includes `execute_action`, which spends credits
|
|
101
|
+
* and writes to connected systems — see the approval note on
|
|
102
|
+
* `isAutoApproved`.
|
|
103
|
+
*/
|
|
104
|
+
/**
|
|
105
|
+
* What Claude Code mounts this server as, and so the `mcp__<server>__` prefix
|
|
106
|
+
* its tools carry. Read by `isAutoApproved`, which trusts this server and no
|
|
107
|
+
* other — see the note there.
|
|
108
|
+
*
|
|
109
|
+
* Exported for tests.
|
|
110
|
+
*/
|
|
111
|
+
export declare const CARGO_MCP_SERVER_NAME = "cargo";
|
|
112
|
+
type WorkspaceLayout = {
|
|
113
|
+
sandboxCwd: string;
|
|
114
|
+
/** `undefined` leaves the harness its per-session default. */
|
|
115
|
+
workDir: string | undefined;
|
|
116
|
+
};
|
|
117
|
+
/**
|
|
118
|
+
* Where to root the sandbox, and which directory inside it the session works in.
|
|
119
|
+
*
|
|
120
|
+
* The harness gives every session a work directory under the sandbox root and
|
|
121
|
+
* starts Claude Code there — `cwd: workdir` in the bridge — and it refuses to
|
|
122
|
+
* use the root itself. Rooted at the project, that means the agent begins in an
|
|
123
|
+
* empty `claude-code-<sessionId>` directory one level below the working copy it
|
|
124
|
+
* was asked about. Rooting at the project's parent instead makes the session
|
|
125
|
+
* work directory *be* the project, so the agent starts where the user is, and
|
|
126
|
+
* the harness's own artifacts land beside the project rather than in it.
|
|
127
|
+
*
|
|
128
|
+
* Falls back to the project when the parent cannot serve — a project at a
|
|
129
|
+
* filesystem root has no usable name, and a parent we cannot write to would
|
|
130
|
+
* fail the session at startup. The per-session directory returns in that case,
|
|
131
|
+
* and `pruneHarnessArtifacts` sweeps it once it is cold.
|
|
132
|
+
*/
|
|
133
|
+
export declare function resolveWorkspaceLayout(dir: string): WorkspaceLayout;
|
|
134
|
+
export declare function createLocalHarness(input: {
|
|
135
|
+
api: Api;
|
|
136
|
+
/**
|
|
137
|
+
* Cargo API origin (`config.baseUrl`). The Anthropic proxy is mounted on
|
|
138
|
+
* the API as `/sandbox/anthropic` — same host the backend uses via
|
|
139
|
+
* `config.app.url` (which is the API URL, not the web app).
|
|
140
|
+
*/
|
|
141
|
+
apiBaseUrl: string;
|
|
142
|
+
dir: string;
|
|
143
|
+
chatUuid: string;
|
|
144
|
+
agentUuid?: string;
|
|
145
|
+
proxyToken: string;
|
|
146
|
+
/**
|
|
147
|
+
* Earlier turns of this chat, from `loadChatHistoryPreamble`. The session is
|
|
148
|
+
* always new, so this is the only thing that carries a `--continue` chat
|
|
149
|
+
* across CLI runs.
|
|
150
|
+
*/
|
|
151
|
+
history?: string;
|
|
152
|
+
/**
|
|
153
|
+
* The id the caller claimed the chat with. Sent with the ingest so recording
|
|
154
|
+
* a turn releases this session's claim and not one taken after it.
|
|
155
|
+
*/
|
|
156
|
+
turnOwnerId?: string;
|
|
157
|
+
}): Promise<LocalHarnessHandle>;
|
|
158
|
+
export type StreamLike = {
|
|
159
|
+
fullStream: AsyncIterable<unknown>;
|
|
160
|
+
text: PromiseLike<string>;
|
|
161
|
+
};
|
|
162
|
+
type ApprovalContinuation = {
|
|
163
|
+
approvalResponse: {
|
|
164
|
+
type: "tool-approval-response";
|
|
165
|
+
approvalId: string;
|
|
166
|
+
approved: boolean;
|
|
167
|
+
reason?: string;
|
|
168
|
+
};
|
|
169
|
+
toolCall: {
|
|
170
|
+
type: "tool-call";
|
|
171
|
+
toolCallId: string;
|
|
172
|
+
toolName: string;
|
|
173
|
+
input: unknown;
|
|
174
|
+
};
|
|
175
|
+
};
|
|
176
|
+
/** The two harness calls a turn needs. Split out so tests can fake them. */
|
|
177
|
+
export type TurnDriver = {
|
|
178
|
+
start: () => Promise<StreamLike>;
|
|
179
|
+
continueWithApprovals: (continuations: readonly ApprovalContinuation[]) => Promise<StreamLike>;
|
|
180
|
+
};
|
|
181
|
+
/**
|
|
182
|
+
* Run one turn to completion, answering approval requests along the way.
|
|
183
|
+
*
|
|
184
|
+
* A turn suspends whenever Claude Code wants a tool `permissionMode` does not
|
|
185
|
+
* cover: the stream ends with the approvals still pending, and the turn only
|
|
186
|
+
* finishes once decisions are fed back in. Exported for tests.
|
|
187
|
+
*/
|
|
188
|
+
export declare function runPromptTurn(input: {
|
|
189
|
+
driver: TurnDriver;
|
|
190
|
+
handlers: LocalStreamHandlers | undefined;
|
|
191
|
+
accumulator?: LocalTurnAccumulator;
|
|
192
|
+
/**
|
|
193
|
+
* Filled with approvals the harness asked about and drained as each is
|
|
194
|
+
* answered, so a turn that ends mid-question leaves the unanswered ones
|
|
195
|
+
* behind for the caller to deny.
|
|
196
|
+
*/
|
|
197
|
+
unanswered?: LocalToolApproval[];
|
|
198
|
+
/** Overridden by tests; `Infinity` waits forever. */
|
|
199
|
+
stallTimeoutMs?: number;
|
|
200
|
+
}): Promise<string>;
|
|
201
|
+
/**
|
|
202
|
+
* The previous turn could not be wound down, so this session cannot take
|
|
203
|
+
* another prompt. Raised rather than pressed on with: the harness would either
|
|
204
|
+
* refuse the prompt itself or run it alongside the turn we failed to finish.
|
|
205
|
+
*
|
|
206
|
+
* The caller's recovery is to replace the session — nothing about a wedged
|
|
207
|
+
* runtime gets better by asking it again.
|
|
208
|
+
*/
|
|
209
|
+
export declare class HarnessTurnStuck extends Error {
|
|
210
|
+
constructor();
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Wait for `work`, but give up after `timeoutMs` rather than blocking a
|
|
214
|
+
* teardown forever. Resolves either way; the caller has already decided that
|
|
215
|
+
* an unfinished result is better than waiting.
|
|
216
|
+
*
|
|
217
|
+
* Exported for tests.
|
|
218
|
+
*/
|
|
219
|
+
export declare function settleWithin(work: Promise<unknown>, timeoutMs: number): Promise<void>;
|
|
220
|
+
/** Exported for tests. */
|
|
221
|
+
export declare function isAutoApproved(toolName: string): boolean;
|
|
222
|
+
/** Exported for tests. */
|
|
223
|
+
export declare function isAlwaysDenied(toolName: string): boolean;
|
|
224
|
+
export {};
|
|
225
|
+
//# sourceMappingURL=localHarness.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"localHarness.d.ts","sourceRoot":"","sources":["../../../src/commands/ask/localHarness.ts"],"names":[],"mappings":"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"}
|