@cargo-ai/cli 1.0.94 → 1.0.96
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/build/commands/ai/chat.d.ts.map +1 -1
- package/build/commands/ai/chat.js +2 -1
- package/build/commands/ai/message.d.ts.map +1 -1
- package/build/commands/ai/message.js +2 -1
- package/build/commands/ai/release.d.ts.map +1 -1
- package/build/commands/ai/release.js +7 -6
- package/build/commands/ask/harness/approvals.d.ts +7 -0
- package/build/commands/ask/harness/approvals.d.ts.map +1 -0
- package/build/commands/ask/harness/approvals.js +81 -0
- package/build/commands/ask/harness/artifacts.d.ts.map +1 -0
- package/build/commands/ask/harness/askUserQuestion.d.ts +69 -0
- package/build/commands/ask/harness/askUserQuestion.d.ts.map +1 -0
- package/build/commands/ask/harness/askUserQuestion.js +218 -0
- package/build/commands/ask/harness/claudeConfig.d.ts.map +1 -0
- package/build/commands/ask/{claudeConfig.js → harness/claudeConfig.js} +28 -13
- package/build/commands/ask/harness/create.d.ts +59 -0
- package/build/commands/ask/harness/create.d.ts.map +1 -0
- package/build/commands/ask/harness/create.js +314 -0
- package/build/commands/ask/harness/index.d.ts +9 -0
- package/build/commands/ask/harness/index.d.ts.map +1 -0
- package/build/commands/ask/harness/index.js +8 -0
- package/build/commands/ask/harness/ingest.d.ts +25 -0
- package/build/commands/ask/harness/ingest.d.ts.map +1 -0
- package/build/commands/ask/harness/ingest.js +35 -0
- package/build/commands/ask/harness/instructions.d.ts +13 -0
- package/build/commands/ask/harness/instructions.d.ts.map +1 -0
- package/build/commands/ask/harness/instructions.js +43 -0
- package/build/commands/ask/harness/layout.d.ts +23 -0
- package/build/commands/ask/harness/layout.d.ts.map +1 -0
- package/build/commands/ask/harness/layout.js +32 -0
- package/build/commands/ask/harness/mcp.d.ts +25 -0
- package/build/commands/ask/harness/mcp.d.ts.map +1 -0
- package/build/commands/ask/harness/mcp.js +72 -0
- package/build/commands/ask/harness/pnpmShim.d.ts.map +1 -0
- package/build/commands/ask/harness/release.d.ts +18 -0
- package/build/commands/ask/harness/release.d.ts.map +1 -0
- package/build/commands/ask/harness/release.js +117 -0
- package/build/commands/ask/harness/sandbox.d.ts +9 -0
- package/build/commands/ask/harness/sandbox.d.ts.map +1 -0
- package/build/commands/ask/harness/sandbox.js +27 -0
- package/build/commands/ask/harness/timing.d.ts +16 -0
- package/build/commands/ask/harness/timing.d.ts.map +1 -0
- package/build/commands/ask/harness/timing.js +72 -0
- package/build/commands/ask/harness/turn.d.ts +152 -0
- package/build/commands/ask/harness/turn.d.ts.map +1 -0
- package/build/commands/ask/harness/turn.js +676 -0
- package/build/commands/ask/harness/usage.d.ts +50 -0
- package/build/commands/ask/harness/usage.d.ts.map +1 -0
- package/build/commands/ask/harness/usage.js +87 -0
- package/build/commands/ask/index.d.ts +9 -0
- package/build/commands/ask/index.d.ts.map +1 -1
- package/build/commands/ask/index.js +7 -7
- package/build/commands/ask/input/completer.d.ts.map +1 -0
- package/build/commands/ask/input/index.d.ts +5 -0
- package/build/commands/ask/input/index.d.ts.map +1 -0
- package/build/commands/ask/input/index.js +4 -0
- package/build/commands/ask/input/prompt.d.ts.map +1 -0
- package/build/commands/ask/{prompt.js → input/prompt.js} +1 -1
- package/build/commands/ask/input/slash.d.ts.map +1 -0
- package/build/commands/ask/{slash.js → input/slash.js} +1 -1
- package/build/commands/ask/input/typeAhead.d.ts.map +1 -0
- package/build/commands/ask/{history.d.ts → persist/history.d.ts} +1 -1
- package/build/commands/ask/persist/history.d.ts.map +1 -0
- package/build/commands/ask/persist/index.d.ts +4 -0
- package/build/commands/ask/persist/index.d.ts.map +1 -0
- package/build/commands/ask/persist/index.js +3 -0
- package/build/commands/ask/persist/persist.d.ts.map +1 -0
- package/build/commands/ask/persist/state.d.ts.map +1 -0
- package/build/commands/ask/session/claim.d.ts +56 -0
- package/build/commands/ask/session/claim.d.ts.map +1 -0
- package/build/commands/ask/session/claim.js +76 -0
- package/build/commands/ask/session/index.d.ts +5 -0
- package/build/commands/ask/session/index.d.ts.map +1 -0
- package/build/commands/ask/session/index.js +4 -0
- package/build/commands/ask/session/run.d.ts +36 -0
- package/build/commands/ask/session/run.d.ts.map +1 -0
- package/build/commands/ask/session/run.js +846 -0
- package/build/commands/ask/session/thinking.d.ts.map +1 -0
- package/build/commands/ask/session/tree.d.ts +99 -0
- package/build/commands/ask/session/tree.d.ts.map +1 -0
- package/build/commands/ask/session/tree.js +356 -0
- package/package.json +6 -5
- package/build/commands/ask/artifacts.d.ts.map +0 -1
- package/build/commands/ask/binding.d.ts +0 -13
- package/build/commands/ask/binding.d.ts.map +0 -1
- package/build/commands/ask/binding.js +0 -19
- package/build/commands/ask/claudeConfig.d.ts.map +0 -1
- package/build/commands/ask/completer.d.ts.map +0 -1
- package/build/commands/ask/history.d.ts.map +0 -1
- package/build/commands/ask/localHarness.d.ts +0 -278
- package/build/commands/ask/localHarness.d.ts.map +0 -1
- package/build/commands/ask/localHarness.js +0 -1300
- package/build/commands/ask/messageStream.d.ts +0 -33
- package/build/commands/ask/messageStream.d.ts.map +0 -1
- package/build/commands/ask/messageStream.js +0 -114
- package/build/commands/ask/persist.d.ts.map +0 -1
- package/build/commands/ask/pnpmShim.d.ts.map +0 -1
- package/build/commands/ask/prompt.d.ts.map +0 -1
- package/build/commands/ask/session.d.ts +0 -101
- package/build/commands/ask/session.d.ts.map +0 -1
- package/build/commands/ask/session.js +0 -1328
- package/build/commands/ask/slash.d.ts.map +0 -1
- package/build/commands/ask/state.d.ts.map +0 -1
- package/build/commands/ask/thinking.d.ts.map +0 -1
- package/build/commands/ask/typeAhead.d.ts.map +0 -1
- /package/build/commands/ask/{artifacts.d.ts → harness/artifacts.d.ts} +0 -0
- /package/build/commands/ask/{artifacts.js → harness/artifacts.js} +0 -0
- /package/build/commands/ask/{claudeConfig.d.ts → harness/claudeConfig.d.ts} +0 -0
- /package/build/commands/ask/{pnpmShim.d.ts → harness/pnpmShim.d.ts} +0 -0
- /package/build/commands/ask/{pnpmShim.js → harness/pnpmShim.js} +0 -0
- /package/build/commands/ask/{completer.d.ts → input/completer.d.ts} +0 -0
- /package/build/commands/ask/{completer.js → input/completer.js} +0 -0
- /package/build/commands/ask/{prompt.d.ts → input/prompt.d.ts} +0 -0
- /package/build/commands/ask/{slash.d.ts → input/slash.d.ts} +0 -0
- /package/build/commands/ask/{typeAhead.d.ts → input/typeAhead.d.ts} +0 -0
- /package/build/commands/ask/{typeAhead.js → input/typeAhead.js} +0 -0
- /package/build/commands/ask/{history.js → persist/history.js} +0 -0
- /package/build/commands/ask/{persist.d.ts → persist/persist.d.ts} +0 -0
- /package/build/commands/ask/{persist.js → persist/persist.js} +0 -0
- /package/build/commands/ask/{state.d.ts → persist/state.d.ts} +0 -0
- /package/build/commands/ask/{state.js → persist/state.js} +0 -0
- /package/build/commands/ask/{thinking.d.ts → session/thinking.d.ts} +0 -0
- /package/build/commands/ask/{thinking.js → session/thinking.js} +0 -0
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
import { HarnessAgent } from "@ai-sdk/harness/agent";
|
|
2
|
+
import { createClaudeCode } from "@ai-sdk/harness-claude-code";
|
|
3
|
+
import { createLocalSandbox } from "ai-sdk-sandbox-local";
|
|
4
|
+
import { pruneHarnessArtifacts } from "./artifacts.js";
|
|
5
|
+
import { buildAskUserQuestionHostTool, denyNativeAskUserQuestion, } from "./askUserQuestion.js";
|
|
6
|
+
import { askClaudeConfigDir } from "./claudeConfig.js";
|
|
7
|
+
import { ingestTurn } from "./ingest.js";
|
|
8
|
+
import { localHarnessInstructions, localHarnessSkills, } from "./instructions.js";
|
|
9
|
+
import { resolveWorkspaceLayout } from "./layout.js";
|
|
10
|
+
import { CARGO_MCP_SERVER_NAME, cargoMcpServer } from "./mcp.js";
|
|
11
|
+
import { ensureHarnessPnpmOnPath } from "./pnpmShim.js";
|
|
12
|
+
import { buildReleaseHostTools, buildReleaseMcpServers } from "./release.js";
|
|
13
|
+
import { withGetPortEndpoint } from "./sandbox.js";
|
|
14
|
+
import { ASK_TIMING_ENABLED, createPhaseLog, reportHarnessDiagnostic, timeStreamParts, } from "./timing.js";
|
|
15
|
+
import { HarnessTurnStuck, runPromptTurn, settleWithin, } from "./turn.js";
|
|
16
|
+
import { createTurnAccumulator, didReachAnthropic } from "./usage.js";
|
|
17
|
+
/**
|
|
18
|
+
* How long teardown may take before the session is abandoned. Short, because
|
|
19
|
+
* the caller is usually someone who just pressed Ctrl-C.
|
|
20
|
+
*/
|
|
21
|
+
const SESSION_DESTROY_TIMEOUT_MS = 5_000;
|
|
22
|
+
/**
|
|
23
|
+
* How long winding down a previous unfinished turn may take before it is cut
|
|
24
|
+
* off and the question refused.
|
|
25
|
+
*/
|
|
26
|
+
const TURN_DRAIN_TIMEOUT_MS = 30_000;
|
|
27
|
+
export async function createLocalHarness(input) {
|
|
28
|
+
const setupPhase = createPhaseLog("setup");
|
|
29
|
+
// Claude Code bootstrap shells out to `pnpm`; Volta often has a dead shim
|
|
30
|
+
// and pnpm 9 refuses this npm packageManager root — put pnpm@10 first.
|
|
31
|
+
ensureHarnessPnpmOnPath();
|
|
32
|
+
setupPhase("pnpm shim written to PATH");
|
|
33
|
+
const workspace = resolveWorkspaceLayout(input.dir);
|
|
34
|
+
pruneHarnessArtifacts(workspace.sandboxCwd);
|
|
35
|
+
// Earlier versions rooted the sandbox at the project, so its artifacts are
|
|
36
|
+
// still in there to collect.
|
|
37
|
+
if (workspace.sandboxCwd !== input.dir) {
|
|
38
|
+
pruneHarnessArtifacts(input.dir);
|
|
39
|
+
}
|
|
40
|
+
setupPhase("stale harness artifacts pruned");
|
|
41
|
+
const anthropicBaseUrl = `${input.apiBaseUrl.replace(/\/$/, "")}/sandbox/anthropic`;
|
|
42
|
+
const release = input.release;
|
|
43
|
+
const releaseTools = input.allowWorkspaceMutations === false || release === undefined
|
|
44
|
+
? {}
|
|
45
|
+
: buildReleaseHostTools(input.api, release.actions);
|
|
46
|
+
// Bound per prompt: the picker lives on the turn, the tool on the session.
|
|
47
|
+
const askUserQuestionAsk = { current: undefined };
|
|
48
|
+
const hostTools = {
|
|
49
|
+
...releaseTools,
|
|
50
|
+
...buildAskUserQuestionHostTool(() => askUserQuestionAsk.current),
|
|
51
|
+
};
|
|
52
|
+
const mcpServers = {
|
|
53
|
+
[CARGO_MCP_SERVER_NAME]: cargoMcpServer(input.apiBaseUrl),
|
|
54
|
+
};
|
|
55
|
+
if (release !== undefined && input.allowWorkspaceMutations === true) {
|
|
56
|
+
const extra = buildReleaseMcpServers(release.mcpClients);
|
|
57
|
+
for (const [name, server] of Object.entries(extra)) {
|
|
58
|
+
mcpServers[name] = server;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
// Pin auth the same way the backend harness stream does. Credentials go
|
|
62
|
+
// through `auth`, not `env`: passing them via `env` alone lets
|
|
63
|
+
// createClaudeCode auto-detect AI Gateway from the host process and ignore
|
|
64
|
+
// the Cargo proxy.
|
|
65
|
+
const claudeCode = createClaudeCode({
|
|
66
|
+
auth: {
|
|
67
|
+
ANTHROPIC_BASE_URL: anthropicBaseUrl,
|
|
68
|
+
ANTHROPIC_API_KEY: input.proxyToken,
|
|
69
|
+
},
|
|
70
|
+
env: { CLAUDE_CONFIG_DIR: askClaudeConfigDir() },
|
|
71
|
+
mcpServers,
|
|
72
|
+
});
|
|
73
|
+
const harnessAgent = new HarnessAgent({
|
|
74
|
+
// Native AskUserQuestion is withheld on the session, not via
|
|
75
|
+
// `inactiveTools` (that would drop the host tool of the same name) and
|
|
76
|
+
// not via settings.json deny (that name matches the replacement too).
|
|
77
|
+
harness: {
|
|
78
|
+
...claudeCode,
|
|
79
|
+
doStart: async (startOpts) => claudeCode.doStart({
|
|
80
|
+
...startOpts,
|
|
81
|
+
builtinToolFiltering: denyNativeAskUserQuestion(startOpts.builtinToolFiltering),
|
|
82
|
+
}),
|
|
83
|
+
},
|
|
84
|
+
id: "cargo-ai-ask",
|
|
85
|
+
sandbox: withGetPortEndpoint(createLocalSandbox({ cwd: workspace.sandboxCwd })),
|
|
86
|
+
sandboxConfig: workspace.workDir === undefined ? {} : { workDir: workspace.workDir },
|
|
87
|
+
instructions: localHarnessInstructions(input.dir, release),
|
|
88
|
+
skills: localHarnessSkills(input.dir),
|
|
89
|
+
model: input.languageModelSlug,
|
|
90
|
+
// Nested @ai-sdk/harness ships its own @ai-sdk/provider-utils, so its
|
|
91
|
+
// ToolSet does not unify with this package's. Runtime shape is the same.
|
|
92
|
+
tools: hostTools,
|
|
93
|
+
permissionMode: "allow-reads",
|
|
94
|
+
...(ASK_TIMING_ENABLED === false
|
|
95
|
+
? {}
|
|
96
|
+
: { debug: { enabled: true }, onLog: reportHarnessDiagnostic }),
|
|
97
|
+
});
|
|
98
|
+
setupPhase("harness agent constructed");
|
|
99
|
+
// Local sandbox cannot broker credentials via request transforms; the
|
|
100
|
+
// harness always warns. Keep that off the Ask tree (stderr mid-reply).
|
|
101
|
+
const session = await withMutedCredentialBrokerWarning(() => harnessAgent.createSession());
|
|
102
|
+
setupPhase("harness session created");
|
|
103
|
+
// Consumed by the first prompt: this session has never seen the chat, so it
|
|
104
|
+
// is told the story once rather than on every question.
|
|
105
|
+
let pendingHistory = input.history;
|
|
106
|
+
let proxyLastUsedAtMs;
|
|
107
|
+
// Approvals the previous turn asked about and never answered — a turn
|
|
108
|
+
// cancelled at the confirmation prompt leaves one here.
|
|
109
|
+
const unanswered = [];
|
|
110
|
+
return {
|
|
111
|
+
streamPrompt: async (question, options) => {
|
|
112
|
+
const turnPhase = createPhaseLog("turn ");
|
|
113
|
+
turnPhase("prompt submitted");
|
|
114
|
+
const abortSignal = options === undefined ? undefined : options.abortSignal;
|
|
115
|
+
// The turn is stored and shown as the user wrote it; only what Claude
|
|
116
|
+
// Code is asked carries the replayed history.
|
|
117
|
+
const prompt = pendingHistory === undefined
|
|
118
|
+
? question
|
|
119
|
+
: `${pendingHistory}\n\n${question}`;
|
|
120
|
+
// A cancelled turn stays unfinished, and the harness refuses a new
|
|
121
|
+
// prompt until it is drained. Do that here rather than making the next
|
|
122
|
+
// question fail with "must be continued before accepting a new prompt".
|
|
123
|
+
if (session.hasUnfinishedTurn() === true) {
|
|
124
|
+
// Anything the last turn asked about and never got an answer for is
|
|
125
|
+
// denied. Draining with no continuations leaves the runtime waiting
|
|
126
|
+
// on a response that is never coming, and the honest reading of a
|
|
127
|
+
// user who walked away from the prompt is "no" — carrying the
|
|
128
|
+
// question into a turn they did not ask for would be worse.
|
|
129
|
+
const denials = unanswered.map((approval) => ({
|
|
130
|
+
approvalResponse: {
|
|
131
|
+
type: "tool-approval-response",
|
|
132
|
+
approvalId: approval.approvalId,
|
|
133
|
+
approved: false,
|
|
134
|
+
},
|
|
135
|
+
toolCall: {
|
|
136
|
+
type: "tool-call",
|
|
137
|
+
toolCallId: approval.toolCallId,
|
|
138
|
+
toolName: approval.toolName,
|
|
139
|
+
input: approval.input,
|
|
140
|
+
},
|
|
141
|
+
}));
|
|
142
|
+
unanswered.length = 0;
|
|
143
|
+
// The drain gets its own controller so the timeout can stop it rather
|
|
144
|
+
// than walk away from it. Nothing watches this call the way the stall
|
|
145
|
+
// watchdog watches a turn's stream, so a runtime that never settles
|
|
146
|
+
// would otherwise hang the question just asked — but abandoning the
|
|
147
|
+
// promise and carrying on is no better: `continueGenerate` keeps
|
|
148
|
+
// running, and submitting the new prompt to the same session races the
|
|
149
|
+
// turn we failed to wind down. Chained to the turn's signal so ctrl-c
|
|
150
|
+
// still gets out of it.
|
|
151
|
+
const drain = new AbortController();
|
|
152
|
+
const abortDrain = () => drain.abort();
|
|
153
|
+
if (abortSignal !== undefined) {
|
|
154
|
+
if (abortSignal.aborted === true) {
|
|
155
|
+
drain.abort();
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
abortSignal.addEventListener("abort", abortDrain, { once: true });
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
const drainTimer = setTimeout(abortDrain, TURN_DRAIN_TIMEOUT_MS);
|
|
162
|
+
try {
|
|
163
|
+
await harnessAgent.continueGenerate({
|
|
164
|
+
session,
|
|
165
|
+
toolApprovalContinuations: denials,
|
|
166
|
+
abortSignal: drain.signal,
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
catch {
|
|
170
|
+
// Whether it failed or was cut off, the session itself is the only
|
|
171
|
+
// thing that can say if a new prompt is allowed.
|
|
172
|
+
}
|
|
173
|
+
finally {
|
|
174
|
+
clearTimeout(drainTimer);
|
|
175
|
+
if (abortSignal !== undefined) {
|
|
176
|
+
abortSignal.removeEventListener("abort", abortDrain);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
if (session.hasUnfinishedTurn() === true) {
|
|
180
|
+
throw new HarnessTurnStuck();
|
|
181
|
+
}
|
|
182
|
+
turnPhase("unfinished previous turn drained");
|
|
183
|
+
}
|
|
184
|
+
const accumulator = createTurnAccumulator();
|
|
185
|
+
let answer;
|
|
186
|
+
askUserQuestionAsk.current =
|
|
187
|
+
options === undefined ? undefined : options.onUserQuestion;
|
|
188
|
+
try {
|
|
189
|
+
answer = await runPromptTurn({
|
|
190
|
+
driver: {
|
|
191
|
+
start: async () => {
|
|
192
|
+
turnPhase("harness stream() called");
|
|
193
|
+
const stream = (await harnessAgent.stream({
|
|
194
|
+
prompt,
|
|
195
|
+
session,
|
|
196
|
+
abortSignal,
|
|
197
|
+
}));
|
|
198
|
+
turnPhase("harness stream() returned");
|
|
199
|
+
// The prompt is in the session now, so the replayed history has
|
|
200
|
+
// been told. Cleared here rather than before submitting: a turn
|
|
201
|
+
// that never reached the harness must keep it for the retry, or
|
|
202
|
+
// a cancelled first question would leave every later one in this
|
|
203
|
+
// process without the earlier conversation.
|
|
204
|
+
pendingHistory = undefined;
|
|
205
|
+
return timeStreamParts(stream, turnPhase);
|
|
206
|
+
},
|
|
207
|
+
continueWithApprovals: async (continuations) => {
|
|
208
|
+
turnPhase("harness continueStream() called");
|
|
209
|
+
const stream = (await harnessAgent.continueStream({
|
|
210
|
+
session,
|
|
211
|
+
toolApprovalContinuations: continuations,
|
|
212
|
+
abortSignal,
|
|
213
|
+
}));
|
|
214
|
+
turnPhase("harness continueStream() returned");
|
|
215
|
+
return timeStreamParts(stream, turnPhase);
|
|
216
|
+
},
|
|
217
|
+
},
|
|
218
|
+
handlers: options,
|
|
219
|
+
accumulator,
|
|
220
|
+
unanswered,
|
|
221
|
+
// Interrupt Claude Code through the turn's abort signal so the
|
|
222
|
+
// bridge stops cleanly. Without this the host walks away from the
|
|
223
|
+
// stream and teardown looks like "bridge closed before the turn
|
|
224
|
+
// finished" rather than an intentional stop.
|
|
225
|
+
onStall: options === undefined ? undefined : options.onStall,
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
catch (error) {
|
|
229
|
+
// Cancelled or failed after Claude Code already spent tokens. Record
|
|
230
|
+
// what it spent, then let the caller handle the failure.
|
|
231
|
+
if (didReachAnthropic(accumulator.usage) === true) {
|
|
232
|
+
proxyLastUsedAtMs = Date.now();
|
|
233
|
+
}
|
|
234
|
+
await ingestTurn({
|
|
235
|
+
api: input.api,
|
|
236
|
+
chatUuid: input.chatUuid,
|
|
237
|
+
agentUuid: input.agentUuid,
|
|
238
|
+
connectorUuid: input.connectorUuid,
|
|
239
|
+
integrationSlug: input.integrationSlug,
|
|
240
|
+
languageModelSlug: input.languageModelSlug,
|
|
241
|
+
question,
|
|
242
|
+
answer: accumulator.chunks.join(""),
|
|
243
|
+
usage: accumulator.usage,
|
|
244
|
+
// Whatever it managed to say is a fragment, not the answer. Stored
|
|
245
|
+
// as cancelled so a later session does not replay it as work done.
|
|
246
|
+
cancelled: true,
|
|
247
|
+
turnOwnerId: input.turnOwnerId,
|
|
248
|
+
onNotice: options === undefined ? undefined : options.onNotice,
|
|
249
|
+
});
|
|
250
|
+
throw error;
|
|
251
|
+
}
|
|
252
|
+
finally {
|
|
253
|
+
askUserQuestionAsk.current = undefined;
|
|
254
|
+
}
|
|
255
|
+
if (didReachAnthropic(accumulator.usage) === true) {
|
|
256
|
+
proxyLastUsedAtMs = Date.now();
|
|
257
|
+
}
|
|
258
|
+
// A turn that spent tokens or edited files is recorded even when it
|
|
259
|
+
// produced no prose, so history and billing match what actually ran.
|
|
260
|
+
await ingestTurn({
|
|
261
|
+
api: input.api,
|
|
262
|
+
chatUuid: input.chatUuid,
|
|
263
|
+
agentUuid: input.agentUuid,
|
|
264
|
+
connectorUuid: input.connectorUuid,
|
|
265
|
+
integrationSlug: input.integrationSlug,
|
|
266
|
+
languageModelSlug: input.languageModelSlug,
|
|
267
|
+
question,
|
|
268
|
+
answer,
|
|
269
|
+
usage: accumulator.usage,
|
|
270
|
+
turnOwnerId: input.turnOwnerId,
|
|
271
|
+
onNotice: options === undefined ? undefined : options.onNotice,
|
|
272
|
+
});
|
|
273
|
+
// Nothing said and nothing spent means the harness never reached
|
|
274
|
+
// Anthropic — a misconfigured proxy rather than a quiet tool-only turn.
|
|
275
|
+
// Same predicate `ingestTurn` uses, so a turn that reached Anthropic
|
|
276
|
+
// is never also reported as unreachable.
|
|
277
|
+
if (answer.length === 0 &&
|
|
278
|
+
didReachAnthropic(accumulator.usage) === false) {
|
|
279
|
+
throw new Error(`Local Claude Code returned no text. The harness called ${anthropicBaseUrl} with the minted proxy token — confirm CARGO_BASE_URL points at your API and that /sandbox/anthropic is reachable.`);
|
|
280
|
+
}
|
|
281
|
+
return answer;
|
|
282
|
+
},
|
|
283
|
+
proxyLastUsedAtMs: () => proxyLastUsedAtMs,
|
|
284
|
+
close: async () => {
|
|
285
|
+
// `detach` would leave the bridge and Claude Code running for a resume
|
|
286
|
+
// that never comes, which is how orphaned bridges piled up. Nothing here
|
|
287
|
+
// is resumable by design, so take the runtime down with the session.
|
|
288
|
+
//
|
|
289
|
+
// Bounded, because Ctrl-C at the prompt now unwinds through here: a
|
|
290
|
+
// destroy that never settles would turn "stop" into a hang, which is
|
|
291
|
+
// what pressing it was meant to escape. Giving up can leave the orphan
|
|
292
|
+
// this call exists to prevent, but a teardown already stuck is no more
|
|
293
|
+
// likely to finish while the user waits on it.
|
|
294
|
+
await settleWithin(session.destroy().catch(() => undefined), SESSION_DESTROY_TIMEOUT_MS);
|
|
295
|
+
},
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
async function withMutedCredentialBrokerWarning(run) {
|
|
299
|
+
const originalWarn = console.warn;
|
|
300
|
+
console.warn = (...args) => {
|
|
301
|
+
const first = args[0];
|
|
302
|
+
if (typeof first === "string" &&
|
|
303
|
+
first.includes("credential brokering does not work")) {
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
originalWarn.apply(console, args);
|
|
307
|
+
};
|
|
308
|
+
try {
|
|
309
|
+
return await run();
|
|
310
|
+
}
|
|
311
|
+
finally {
|
|
312
|
+
console.warn = originalWarn;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { isAlwaysDenied, isAutoApproved } from "./approvals.js";
|
|
2
|
+
export { ASK_USER_QUESTION_DENIED_REASON, ASK_USER_QUESTION_MISSING_INPUT_REASON, ASK_USER_QUESTION_TOOL_NAME, buildAskUserQuestionHostTool, denyNativeAskUserQuestion, formatAskUserQuestionAnswers, readAskUserQuestions, summarizeAskUserQuestion, } from "./askUserQuestion.js";
|
|
3
|
+
export { createLocalHarness, type LocalHarness } from "./create.js";
|
|
4
|
+
export { localHarnessInstructions, localHarnessSkills, } from "./instructions.js";
|
|
5
|
+
export { resolveWorkspaceLayout } from "./layout.js";
|
|
6
|
+
export { CARGO_MCP_SERVER_NAME } from "./mcp.js";
|
|
7
|
+
export { HarnessTurnStalled, HarnessTurnStuck, isBridgeClosedError, type LocalStreamHandlers, type LocalStreamOptions, type LocalToolActivity, type LocalToolApproval, runPromptTurn, settleWithin, type StreamLike, type TurnDriver, } from "./turn.js";
|
|
8
|
+
export { createTurnAccumulator, type LocalTurnAccumulator, type LocalTurnUsage, readClaudeCodeCostUsd, } from "./usage.js";
|
|
9
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { isAlwaysDenied, isAutoApproved } from "./approvals.js";
|
|
2
|
+
export { ASK_USER_QUESTION_DENIED_REASON, ASK_USER_QUESTION_MISSING_INPUT_REASON, ASK_USER_QUESTION_TOOL_NAME, buildAskUserQuestionHostTool, denyNativeAskUserQuestion, formatAskUserQuestionAnswers, readAskUserQuestions, summarizeAskUserQuestion, } from "./askUserQuestion.js";
|
|
3
|
+
export { createLocalHarness } from "./create.js";
|
|
4
|
+
export { localHarnessInstructions, localHarnessSkills, } from "./instructions.js";
|
|
5
|
+
export { resolveWorkspaceLayout } from "./layout.js";
|
|
6
|
+
export { CARGO_MCP_SERVER_NAME } from "./mcp.js";
|
|
7
|
+
export { HarnessTurnStalled, HarnessTurnStuck, isBridgeClosedError, runPromptTurn, settleWithin, } from "./turn.js";
|
|
8
|
+
export { createTurnAccumulator, readClaudeCodeCostUsd, } from "./usage.js";
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { Api } from "../../../api.js";
|
|
2
|
+
import { type LocalTurnUsage } from "./usage.js";
|
|
3
|
+
/**
|
|
4
|
+
* Persist the turn and what it spent. Credits for a credits connector are
|
|
5
|
+
* billed on the sandbox proxy from the forwarded Anthropic response; this
|
|
6
|
+
* call writes history. `costUsd` is stored on the message, not charged.
|
|
7
|
+
*
|
|
8
|
+
* History is best effort: a failed ingest is reported but never turns a reply
|
|
9
|
+
* the user already read into a failed turn.
|
|
10
|
+
*/
|
|
11
|
+
export declare function ingestTurn(input: {
|
|
12
|
+
api: Api;
|
|
13
|
+
chatUuid: string;
|
|
14
|
+
agentUuid?: string;
|
|
15
|
+
connectorUuid: string;
|
|
16
|
+
integrationSlug: string;
|
|
17
|
+
languageModelSlug: string;
|
|
18
|
+
question: string;
|
|
19
|
+
answer: string;
|
|
20
|
+
usage: LocalTurnUsage;
|
|
21
|
+
cancelled?: boolean;
|
|
22
|
+
turnOwnerId?: string;
|
|
23
|
+
onNotice?: (message: string) => void;
|
|
24
|
+
}): Promise<void>;
|
|
25
|
+
//# sourceMappingURL=ingest.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ingest.d.ts","sourceRoot":"","sources":["../../../../src/commands/ask/harness/ingest.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,iBAAiB,CAAC;AAC3C,OAAO,EAAqB,KAAK,cAAc,EAAE,MAAM,YAAY,CAAC;AAEpE;;;;;;;GAOG;AACH,wBAAsB,UAAU,CAAC,KAAK,EAAE;IACtC,GAAG,EAAE,GAAG,CAAC;IACT,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe,EAAE,MAAM,CAAC;IACxB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,cAAc,CAAC;IACtB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACtC,GAAG,OAAO,CAAC,IAAI,CAAC,CA4BhB"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { didReachAnthropic } from "./usage.js";
|
|
2
|
+
/**
|
|
3
|
+
* Persist the turn and what it spent. Credits for a credits connector are
|
|
4
|
+
* billed on the sandbox proxy from the forwarded Anthropic response; this
|
|
5
|
+
* call writes history. `costUsd` is stored on the message, not charged.
|
|
6
|
+
*
|
|
7
|
+
* History is best effort: a failed ingest is reported but never turns a reply
|
|
8
|
+
* the user already read into a failed turn.
|
|
9
|
+
*/
|
|
10
|
+
export async function ingestTurn(input) {
|
|
11
|
+
if (input.answer.length === 0 && didReachAnthropic(input.usage) === false) {
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
try {
|
|
15
|
+
await input.api.ai.message.ingest({
|
|
16
|
+
chatUuid: input.chatUuid,
|
|
17
|
+
agentUuid: input.agentUuid,
|
|
18
|
+
connectorUuid: input.connectorUuid,
|
|
19
|
+
integrationSlug: input.integrationSlug,
|
|
20
|
+
languageModelSlug: input.languageModelSlug,
|
|
21
|
+
userParts: [{ type: "text", text: input.question }],
|
|
22
|
+
// A turn cancelled before it said anything is recorded for its usage
|
|
23
|
+
// alone, so it has no text part rather than an empty one.
|
|
24
|
+
assistantParts: input.answer.length === 0 ? [] : [{ type: "text", text: input.answer }],
|
|
25
|
+
usage: didReachAnthropic(input.usage) === true ? input.usage : undefined,
|
|
26
|
+
cancelled: input.cancelled,
|
|
27
|
+
turnOwnerId: input.turnOwnerId,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
if (input.onNotice !== undefined) {
|
|
32
|
+
input.onNotice(`Could not save this turn to the chat history: ${error instanceof Error ? error.message : "unknown error"}`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Ai } from "@cargo-ai/api";
|
|
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, release?: Ai.Release): string;
|
|
7
|
+
/** Exported for tests. */
|
|
8
|
+
export declare function localHarnessSkills(dir: string): Array<{
|
|
9
|
+
name: string;
|
|
10
|
+
description: string;
|
|
11
|
+
content: string;
|
|
12
|
+
}>;
|
|
13
|
+
//# sourceMappingURL=instructions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"instructions.d.ts","sourceRoot":"","sources":["../../../../src/commands/ask/harness/instructions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,eAAe,CAAC;AAwBxC;;;GAGG;AACH,wBAAgB,wBAAwB,CACtC,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE,EAAE,CAAC,OAAO,GACnB,MAAM,CAkBR;AAED,0BAA0B;AAC1B,wBAAgB,kBAAkB,CAChC,GAAG,EAAE,MAAM,GACV,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CAM/D"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { cdkProjectRoot } from "@cargo-ai/cdk/cli";
|
|
2
|
+
import { buildReleaseInstructionAppendix } from "./release.js";
|
|
3
|
+
const LOCAL_WORKING_COPY_INSTRUCTIONS = `You are working in the user's local working copy, not a sandbox. Prefer plan over deploy.
|
|
4
|
+
|
|
5
|
+
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.
|
|
6
|
+
|
|
7
|
+
When you need a multiple-choice answer, call AskUserQuestion. This session presents the options in the terminal and returns the user's answers as the tool result. Do not wait for a Claude Code question picker.
|
|
8
|
+
|
|
9
|
+
Prefer minimal, focused diffs; match the existing code style; run the project's own checks when they exist. End each task with a short summary of what changed and how it was validated.`;
|
|
10
|
+
const CDK_RAILS_INSTRUCTIONS = `This directory is a Cargo CDK project.
|
|
11
|
+
- Never edit cargo.state.json, cargo.state.lock, or .cargo-ai/ by hand — they are generated. Repoint this project's deploy state with \`cargo-cdk state bind\`.
|
|
12
|
+
- Validate your changes before declaring them done: run \`npx tsc --noEmit\` and \`npx cargo-cdk plan\` (plan only reads — it changes nothing).
|
|
13
|
+
- Never run \`cargo-cdk deploy\`, \`destroy\`, \`import\`, or \`rollback\` unless the user explicitly asked.`;
|
|
14
|
+
const LOCAL_CDK_RAILS_SKILL = {
|
|
15
|
+
name: "cdk-rails",
|
|
16
|
+
description: "Rails for working on a Cargo CDK project on this machine.",
|
|
17
|
+
content: CDK_RAILS_INSTRUCTIONS,
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Always-on local-copy rails, plus CDK rails only when `dir` is a CDK project.
|
|
21
|
+
* Exported for tests.
|
|
22
|
+
*/
|
|
23
|
+
export function localHarnessInstructions(dir, release) {
|
|
24
|
+
const rails = cdkProjectRoot(dir) !== undefined
|
|
25
|
+
? `${LOCAL_WORKING_COPY_INSTRUCTIONS}\n\n${CDK_RAILS_INSTRUCTIONS}`
|
|
26
|
+
: LOCAL_WORKING_COPY_INSTRUCTIONS;
|
|
27
|
+
if (release === undefined) {
|
|
28
|
+
return rails;
|
|
29
|
+
}
|
|
30
|
+
const parts = [release.systemPrompt, rails];
|
|
31
|
+
const appendix = buildReleaseInstructionAppendix(release);
|
|
32
|
+
if (appendix !== undefined) {
|
|
33
|
+
parts.push(appendix);
|
|
34
|
+
}
|
|
35
|
+
return parts.join("\n\n");
|
|
36
|
+
}
|
|
37
|
+
/** Exported for tests. */
|
|
38
|
+
export function localHarnessSkills(dir) {
|
|
39
|
+
if (cdkProjectRoot(dir) !== undefined) {
|
|
40
|
+
return [LOCAL_CDK_RAILS_SKILL];
|
|
41
|
+
}
|
|
42
|
+
return [];
|
|
43
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export type WorkspaceLayout = {
|
|
2
|
+
sandboxCwd: string;
|
|
3
|
+
/** `undefined` leaves the harness its per-session default. */
|
|
4
|
+
workDir: string | undefined;
|
|
5
|
+
};
|
|
6
|
+
/**
|
|
7
|
+
* Where to root the sandbox, and which directory inside it the session works in.
|
|
8
|
+
*
|
|
9
|
+
* The harness gives every session a work directory under the sandbox root and
|
|
10
|
+
* starts Claude Code there — `cwd: workdir` in the bridge — and it refuses to
|
|
11
|
+
* use the root itself. Rooted at the project, that means the agent begins in an
|
|
12
|
+
* empty `claude-code-<sessionId>` directory one level below the working copy it
|
|
13
|
+
* was asked about. Rooting at the project's parent instead makes the session
|
|
14
|
+
* work directory *be* the project, so the agent starts where the user is, and
|
|
15
|
+
* the harness's own artifacts land beside the project rather than in it.
|
|
16
|
+
*
|
|
17
|
+
* Falls back to the project when the parent cannot serve — a project at a
|
|
18
|
+
* filesystem root has no usable name, and a parent we cannot write to would
|
|
19
|
+
* fail the session at startup. The per-session directory returns in that case,
|
|
20
|
+
* and `pruneHarnessArtifacts` sweeps it once it is cold.
|
|
21
|
+
*/
|
|
22
|
+
export declare function resolveWorkspaceLayout(dir: string): WorkspaceLayout;
|
|
23
|
+
//# sourceMappingURL=layout.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"layout.d.ts","sourceRoot":"","sources":["../../../../src/commands/ask/harness/layout.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,eAAe,GAAG;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,8DAA8D;IAC9D,OAAO,EAAE,MAAM,GAAG,SAAS,CAAC;CAC7B,CAAC;AAEF;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,MAAM,GAAG,eAAe,CAenE"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { accessSync, constants } from "node:fs";
|
|
2
|
+
import { basename, dirname } from "node:path";
|
|
3
|
+
/**
|
|
4
|
+
* Where to root the sandbox, and which directory inside it the session works in.
|
|
5
|
+
*
|
|
6
|
+
* The harness gives every session a work directory under the sandbox root and
|
|
7
|
+
* starts Claude Code there — `cwd: workdir` in the bridge — and it refuses to
|
|
8
|
+
* use the root itself. Rooted at the project, that means the agent begins in an
|
|
9
|
+
* empty `claude-code-<sessionId>` directory one level below the working copy it
|
|
10
|
+
* was asked about. Rooting at the project's parent instead makes the session
|
|
11
|
+
* work directory *be* the project, so the agent starts where the user is, and
|
|
12
|
+
* the harness's own artifacts land beside the project rather than in it.
|
|
13
|
+
*
|
|
14
|
+
* Falls back to the project when the parent cannot serve — a project at a
|
|
15
|
+
* filesystem root has no usable name, and a parent we cannot write to would
|
|
16
|
+
* fail the session at startup. The per-session directory returns in that case,
|
|
17
|
+
* and `pruneHarnessArtifacts` sweeps it once it is cold.
|
|
18
|
+
*/
|
|
19
|
+
export function resolveWorkspaceLayout(dir) {
|
|
20
|
+
const parent = dirname(dir);
|
|
21
|
+
const name = basename(dir);
|
|
22
|
+
if (parent === dir || name.length === 0) {
|
|
23
|
+
return { sandboxCwd: dir, workDir: undefined };
|
|
24
|
+
}
|
|
25
|
+
try {
|
|
26
|
+
accessSync(parent, constants.W_OK);
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return { sandboxCwd: dir, workDir: undefined };
|
|
30
|
+
}
|
|
31
|
+
return { sandboxCwd: parent, workDir: name };
|
|
32
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What Claude Code mounts this server as, and so the `mcp__<server>__` prefix
|
|
3
|
+
* its tools carry. Read by `isAutoApproved`, which trusts this server and no
|
|
4
|
+
* other — see the note there.
|
|
5
|
+
*
|
|
6
|
+
* Exported for tests.
|
|
7
|
+
*/
|
|
8
|
+
export declare const CARGO_MCP_SERVER_NAME = "cargo";
|
|
9
|
+
/**
|
|
10
|
+
* The workspace half of local Ask: the hosted Cargo MCP server, reached
|
|
11
|
+
* through this CLI's own `mcp` bridge.
|
|
12
|
+
*
|
|
13
|
+
* Going through the bridge rather than configuring the HTTP endpoint directly
|
|
14
|
+
* is what keeps a long chat working — the bridge rebuilds the `Authorization`
|
|
15
|
+
* header per request from the same credentials every other command uses, so an
|
|
16
|
+
* OAuth session that expires mid-session renews instead of 401ing. `CARGO_BASE_URL`
|
|
17
|
+
* is pinned so the bridge cannot end up on a different API than this session.
|
|
18
|
+
*
|
|
19
|
+
* Everything the workspace can do arrives this way: actions, runs, models and
|
|
20
|
+
* SQL, the context, usage. That includes `execute_action`, which spends credits
|
|
21
|
+
* and writes to connected systems — see the approval note on
|
|
22
|
+
* `isAutoApproved`.
|
|
23
|
+
*/
|
|
24
|
+
export declare function cargoMcpServer(apiBaseUrl: string): Record<string, unknown>;
|
|
25
|
+
//# sourceMappingURL=mcp.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp.d.ts","sourceRoot":"","sources":["../../../../src/commands/ask/harness/mcp.ts"],"names":[],"mappings":"AAEA;;;;;;GAMG;AACH,eAAO,MAAM,qBAAqB,UAAU,CAAC;AAE7C;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,cAAc,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAO1E"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { fileURLToPath } from "node:url";
|
|
2
|
+
/**
|
|
3
|
+
* What Claude Code mounts this server as, and so the `mcp__<server>__` prefix
|
|
4
|
+
* its tools carry. Read by `isAutoApproved`, which trusts this server and no
|
|
5
|
+
* other — see the note there.
|
|
6
|
+
*
|
|
7
|
+
* Exported for tests.
|
|
8
|
+
*/
|
|
9
|
+
export const CARGO_MCP_SERVER_NAME = "cargo";
|
|
10
|
+
/**
|
|
11
|
+
* The workspace half of local Ask: the hosted Cargo MCP server, reached
|
|
12
|
+
* through this CLI's own `mcp` bridge.
|
|
13
|
+
*
|
|
14
|
+
* Going through the bridge rather than configuring the HTTP endpoint directly
|
|
15
|
+
* is what keeps a long chat working — the bridge rebuilds the `Authorization`
|
|
16
|
+
* header per request from the same credentials every other command uses, so an
|
|
17
|
+
* OAuth session that expires mid-session renews instead of 401ing. `CARGO_BASE_URL`
|
|
18
|
+
* is pinned so the bridge cannot end up on a different API than this session.
|
|
19
|
+
*
|
|
20
|
+
* Everything the workspace can do arrives this way: actions, runs, models and
|
|
21
|
+
* SQL, the context, usage. That includes `execute_action`, which spends credits
|
|
22
|
+
* and writes to connected systems — see the approval note on
|
|
23
|
+
* `isAutoApproved`.
|
|
24
|
+
*/
|
|
25
|
+
export function cargoMcpServer(apiBaseUrl) {
|
|
26
|
+
return {
|
|
27
|
+
type: "stdio",
|
|
28
|
+
command: process.execPath,
|
|
29
|
+
args: [...loaderArgs(), cliEntryPath(), "mcp"],
|
|
30
|
+
env: { CARGO_BASE_URL: apiBaseUrl },
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* This CLI's own entry, so the bridge is the same code and version as the
|
|
35
|
+
* session that spawned it — not whatever `cargo-ai` happens to be on PATH.
|
|
36
|
+
*/
|
|
37
|
+
function cliEntryPath() {
|
|
38
|
+
const entry = process.argv[1];
|
|
39
|
+
return entry !== undefined && entry.length > 0
|
|
40
|
+
? entry
|
|
41
|
+
: fileURLToPath(new URL("../../../index.js", import.meta.url));
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Loader flags only. A dev run under `tsx` starts from a `.ts` entry that bare
|
|
45
|
+
* node cannot execute, so the child needs the same loader; the rest of
|
|
46
|
+
* `execArgv` (`--inspect` above all, which would fight over a port) must not
|
|
47
|
+
* be inherited.
|
|
48
|
+
*/
|
|
49
|
+
function loaderArgs() {
|
|
50
|
+
const flags = ["--import", "--loader", "--require"];
|
|
51
|
+
const args = [];
|
|
52
|
+
for (let index = 0; index < process.execArgv.length; index += 1) {
|
|
53
|
+
const arg = process.execArgv[index];
|
|
54
|
+
if (arg === undefined) {
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
// `--import=tsx` carries its value; `--import tsx` is two entries and the
|
|
58
|
+
// second one has to come along.
|
|
59
|
+
if (flags.some((flag) => arg.startsWith(`${flag}=`)) === true) {
|
|
60
|
+
args.push(arg);
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
if (flags.includes(arg) === true) {
|
|
64
|
+
const value = process.execArgv[index + 1];
|
|
65
|
+
if (value !== undefined) {
|
|
66
|
+
args.push(arg, value);
|
|
67
|
+
index += 1;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return args;
|
|
72
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pnpmShim.d.ts","sourceRoot":"","sources":["../../../../src/commands/ask/harness/pnpmShim.ts"],"names":[],"mappings":"AAKA;;;;;;;GAOG;AACH,eAAO,MAAM,oBAAoB,YAAY,CAAC;AAE9C,8EAA8E;AAC9E,wBAAgB,qBAAqB,IAAI,MAAM,CAE9C;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,IAAI,MAAM,CAchD;AAED,mDAAmD;AACnD,wBAAgB,qBAAqB,IAAI,MAAM,CAI9C"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Ai, Orchestration } from "@cargo-ai/api";
|
|
2
|
+
import type { Api } from "../../../api.js";
|
|
3
|
+
/** Mount prefix so agent MCP tools can be auto-approved without widening user MCP. */
|
|
4
|
+
export declare const CARGO_AGENT_MCP_NAME_PREFIX = "cargoagent_";
|
|
5
|
+
type HostTool = {
|
|
6
|
+
description?: string;
|
|
7
|
+
inputSchema: {
|
|
8
|
+
type: "object";
|
|
9
|
+
additionalProperties: true;
|
|
10
|
+
};
|
|
11
|
+
execute: (data: unknown) => Promise<unknown>;
|
|
12
|
+
};
|
|
13
|
+
export declare function buildReleaseInstructionAppendix(release: Ai.Release): string | undefined;
|
|
14
|
+
export declare function buildReleaseMcpServers(mcpClients: Ai.McpClient[]): Record<string, unknown>;
|
|
15
|
+
export declare function buildReleaseHostTools(api: Api, actions: Ai.Action[]): Record<string, HostTool>;
|
|
16
|
+
export declare function toExecuteAction(action: Ai.Action): Orchestration.Api.ExecuteActionPayload["action"];
|
|
17
|
+
export {};
|
|
18
|
+
//# sourceMappingURL=release.d.ts.map
|