@cargo-ai/cli 1.0.92 → 1.0.95
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 +17 -17
- 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 +82 -0
- package/build/commands/ask/harness/artifacts.d.ts.map +1 -0
- package/build/commands/ask/harness/claudeConfig.d.ts.map +1 -0
- 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 +292 -0
- package/build/commands/ask/harness/index.d.ts +8 -0
- package/build/commands/ask/harness/index.d.ts.map +1 -0
- package/build/commands/ask/harness/index.js +7 -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 +41 -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 +145 -0
- package/build/commands/ask/harness/turn.d.ts.map +1 -0
- package/build/commands/ask/harness/turn.js +626 -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} +2 -2
- 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 +832 -0
- package/build/commands/ask/session/thinking.d.ts.map +1 -0
- package/build/commands/ask/session/tree.d.ts +90 -0
- package/build/commands/ask/session/tree.d.ts.map +1 -0
- package/build/commands/ask/session/tree.js +318 -0
- package/build/commands/start/brief.js +1 -1
- package/build/commands/start/index.d.ts +1 -1
- package/build/commands/start/index.d.ts.map +1 -1
- package/build/commands/start/index.js +7 -7
- package/build/index.js +6 -1
- package/build/paletteLayout.d.ts.map +1 -1
- package/build/paletteLayout.js +9 -8
- package/build/utils/cdkState.js +2 -2
- package/build/utils/loadCompiledAgent.js +1 -1
- 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/{claudeConfig.js → harness/claudeConfig.js} +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,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;AAsBxC;;;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,41 @@
|
|
|
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
|
+
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.`;
|
|
8
|
+
const CDK_RAILS_INSTRUCTIONS = `This directory is a Cargo CDK project.
|
|
9
|
+
- 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\`.
|
|
10
|
+
- Validate your changes before declaring them done: run \`npx tsc --noEmit\` and \`npx cargo-cdk plan\` (plan only reads — it changes nothing).
|
|
11
|
+
- Never run \`cargo-cdk deploy\`, \`destroy\`, \`import\`, or \`rollback\` unless the user explicitly asked.`;
|
|
12
|
+
const LOCAL_CDK_RAILS_SKILL = {
|
|
13
|
+
name: "cdk-rails",
|
|
14
|
+
description: "Rails for working on a Cargo CDK project on this machine.",
|
|
15
|
+
content: CDK_RAILS_INSTRUCTIONS,
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Always-on local-copy rails, plus CDK rails only when `dir` is a CDK project.
|
|
19
|
+
* Exported for tests.
|
|
20
|
+
*/
|
|
21
|
+
export function localHarnessInstructions(dir, release) {
|
|
22
|
+
const rails = cdkProjectRoot(dir) !== undefined
|
|
23
|
+
? `${LOCAL_WORKING_COPY_INSTRUCTIONS}\n\n${CDK_RAILS_INSTRUCTIONS}`
|
|
24
|
+
: LOCAL_WORKING_COPY_INSTRUCTIONS;
|
|
25
|
+
if (release === undefined) {
|
|
26
|
+
return rails;
|
|
27
|
+
}
|
|
28
|
+
const parts = [release.systemPrompt, rails];
|
|
29
|
+
const appendix = buildReleaseInstructionAppendix(release);
|
|
30
|
+
if (appendix !== undefined) {
|
|
31
|
+
parts.push(appendix);
|
|
32
|
+
}
|
|
33
|
+
return parts.join("\n\n");
|
|
34
|
+
}
|
|
35
|
+
/** Exported for tests. */
|
|
36
|
+
export function localHarnessSkills(dir) {
|
|
37
|
+
if (cdkProjectRoot(dir) !== undefined) {
|
|
38
|
+
return [LOCAL_CDK_RAILS_SKILL];
|
|
39
|
+
}
|
|
40
|
+
return [];
|
|
41
|
+
}
|
|
@@ -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
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"release.d.ts","sourceRoot":"","sources":["../../../../src/commands/ask/harness/release.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAEvD,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,iBAAiB,CAAC;AAE3C,sFAAsF;AACtF,eAAO,MAAM,2BAA2B,gBAAgB,CAAC;AAEzD,KAAK,QAAQ,GAAG;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE;QACX,IAAI,EAAE,QAAQ,CAAC;QACf,oBAAoB,EAAE,IAAI,CAAC;KAC5B,CAAC;IACF,OAAO,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CAC9C,CAAC;AAEF,wBAAgB,+BAA+B,CAC7C,OAAO,EAAE,EAAE,CAAC,OAAO,GAClB,MAAM,GAAG,SAAS,CAwCpB;AAED,wBAAgB,sBAAsB,CACpC,UAAU,EAAE,EAAE,CAAC,SAAS,EAAE,GACzB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAiBzB;AAED,wBAAgB,qBAAqB,CACnC,GAAG,EAAE,GAAG,EACR,OAAO,EAAE,EAAE,CAAC,MAAM,EAAE,GACnB,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,CA4B1B;AAED,wBAAgB,eAAe,CAC7B,MAAM,EAAE,EAAE,CAAC,MAAM,GAChB,aAAa,CAAC,GAAG,CAAC,oBAAoB,CAAC,QAAQ,CAAC,CAgClD"}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/** Mount prefix so agent MCP tools can be auto-approved without widening user MCP. */
|
|
2
|
+
export const CARGO_AGENT_MCP_NAME_PREFIX = "cargoagent_";
|
|
3
|
+
export function buildReleaseInstructionAppendix(release) {
|
|
4
|
+
const parts = [];
|
|
5
|
+
const resourceLines = release.resources.map((resource) => {
|
|
6
|
+
const title = resource.name !== undefined ? resource.name : resource.slug;
|
|
7
|
+
if (resource.description === undefined) {
|
|
8
|
+
return `- ${title} (${resource.kind})`;
|
|
9
|
+
}
|
|
10
|
+
return `- ${title} (${resource.kind}): ${resource.description}`;
|
|
11
|
+
});
|
|
12
|
+
if (resourceLines.length > 0) {
|
|
13
|
+
parts.push(["Workspace resources for this agent:", ...resourceLines].join("\n"));
|
|
14
|
+
}
|
|
15
|
+
const capabilityLines = release.capabilities
|
|
16
|
+
.filter((capability) => capability.slug !== "sandbox" && capability.slug !== "suggestedActions")
|
|
17
|
+
.map((capability) => `- ${capability.slug}`);
|
|
18
|
+
if (capabilityLines.length > 0) {
|
|
19
|
+
parts.push([
|
|
20
|
+
"This agent also has these Cargo capabilities; use the Cargo MCP tools for workspace data they cover:",
|
|
21
|
+
...capabilityLines,
|
|
22
|
+
].join("\n"));
|
|
23
|
+
}
|
|
24
|
+
if (parts.length === 0) {
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
return parts.join("\n\n");
|
|
28
|
+
}
|
|
29
|
+
export function buildReleaseMcpServers(mcpClients) {
|
|
30
|
+
const servers = {};
|
|
31
|
+
for (const client of mcpClients) {
|
|
32
|
+
if (client.kind !== "custom") {
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
const name = mcpServerName(client.name);
|
|
36
|
+
const headers = customMcpHeaders(client.authentication);
|
|
37
|
+
servers[name] =
|
|
38
|
+
headers === undefined
|
|
39
|
+
? { type: "http", url: client.url }
|
|
40
|
+
: { type: "http", url: client.url, headers };
|
|
41
|
+
}
|
|
42
|
+
return servers;
|
|
43
|
+
}
|
|
44
|
+
export function buildReleaseHostTools(api, actions) {
|
|
45
|
+
const tools = {};
|
|
46
|
+
for (const action of actions) {
|
|
47
|
+
tools[action.slug] = {
|
|
48
|
+
description: action.description,
|
|
49
|
+
inputSchema: {
|
|
50
|
+
type: "object",
|
|
51
|
+
additionalProperties: true,
|
|
52
|
+
},
|
|
53
|
+
execute: async (data) => {
|
|
54
|
+
try {
|
|
55
|
+
return await api.orchestration.action.execute({
|
|
56
|
+
action: toExecuteAction(action),
|
|
57
|
+
data,
|
|
58
|
+
waitUntilFinished: true,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
return {
|
|
63
|
+
error: error instanceof Error ? error.message : "Action failed to run",
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
return tools;
|
|
70
|
+
}
|
|
71
|
+
export function toExecuteAction(action) {
|
|
72
|
+
if (action.kind === "tool") {
|
|
73
|
+
return {
|
|
74
|
+
kind: "tool",
|
|
75
|
+
toolUuid: action.toolUuid,
|
|
76
|
+
templateSlug: action.templateSlug,
|
|
77
|
+
waitUntilFinished: action.waitUntilFinished,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
if (action.kind === "agent") {
|
|
81
|
+
return {
|
|
82
|
+
kind: "agent",
|
|
83
|
+
agentUuid: action.agentUuid,
|
|
84
|
+
templateSlug: action.templateSlug,
|
|
85
|
+
waitUntilFinished: action.waitUntilFinished,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
if (action.kind === "connector") {
|
|
89
|
+
return {
|
|
90
|
+
kind: "connector",
|
|
91
|
+
integrationSlug: action.integrationSlug,
|
|
92
|
+
connectorUuid: action.connectorUuid,
|
|
93
|
+
actionSlug: action.actionSlug,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
return {
|
|
97
|
+
kind: "native",
|
|
98
|
+
actionSlug: action.actionSlug,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
function mcpServerName(name) {
|
|
102
|
+
const slug = name.toLowerCase().replace(/[^a-z0-9]+/g, "_");
|
|
103
|
+
const trimmed = slug.replace(/^_+|_+$/g, "");
|
|
104
|
+
const body = trimmed.length > 0 ? trimmed : "mcp";
|
|
105
|
+
return `${CARGO_AGENT_MCP_NAME_PREFIX}${body}`;
|
|
106
|
+
}
|
|
107
|
+
function customMcpHeaders(authentication) {
|
|
108
|
+
if (authentication === undefined) {
|
|
109
|
+
return undefined;
|
|
110
|
+
}
|
|
111
|
+
const tokenType = authentication.tokenType !== undefined
|
|
112
|
+
? authentication.tokenType
|
|
113
|
+
: "Bearer";
|
|
114
|
+
return {
|
|
115
|
+
Authorization: `${tokenType} ${authentication.accessToken}`,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { HarnessV1SandboxProvider } from "@ai-sdk/harness";
|
|
2
|
+
/**
|
|
3
|
+
* `ai-sdk-sandbox-local@0.1.2` still ships deprecated `getPortUrl` only.
|
|
4
|
+
* Claude Code's harness treats a session without `getPortEndpoint` as a basic
|
|
5
|
+
* sandbox and then demands an explicit port — wrap until the local provider
|
|
6
|
+
* catches up.
|
|
7
|
+
*/
|
|
8
|
+
export declare function withGetPortEndpoint(provider: HarnessV1SandboxProvider): HarnessV1SandboxProvider;
|
|
9
|
+
//# sourceMappingURL=sandbox.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sandbox.d.ts","sourceRoot":"","sources":["../../../../src/commands/ask/harness/sandbox.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,iBAAiB,CAAC;AAShE;;;;;GAKG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,wBAAwB,GACjC,wBAAwB,CAc1B"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `ai-sdk-sandbox-local@0.1.2` still ships deprecated `getPortUrl` only.
|
|
3
|
+
* Claude Code's harness treats a session without `getPortEndpoint` as a basic
|
|
4
|
+
* sandbox and then demands an explicit port — wrap until the local provider
|
|
5
|
+
* catches up.
|
|
6
|
+
*/
|
|
7
|
+
export function withGetPortEndpoint(provider) {
|
|
8
|
+
const resumeSession = provider.resumeSession;
|
|
9
|
+
return {
|
|
10
|
+
specificationVersion: provider.specificationVersion,
|
|
11
|
+
providerId: provider.providerId,
|
|
12
|
+
createSession: async (options) => ensureGetPortEndpoint(await provider.createSession(options)),
|
|
13
|
+
resumeSession: resumeSession === undefined
|
|
14
|
+
? undefined
|
|
15
|
+
: async (options) => ensureGetPortEndpoint(await resumeSession(options)),
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
function ensureGetPortEndpoint(session) {
|
|
19
|
+
if ("getPortEndpoint" in session) {
|
|
20
|
+
return session;
|
|
21
|
+
}
|
|
22
|
+
const getPortEndpoint = async (options) => ({
|
|
23
|
+
url: await session.getPortUrl(options),
|
|
24
|
+
});
|
|
25
|
+
Object.assign(session, { getPortEndpoint });
|
|
26
|
+
return session;
|
|
27
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { StreamLike } from "./turn.js";
|
|
2
|
+
export declare const ASK_TIMING_ENABLED: boolean;
|
|
3
|
+
export type PhaseLog = (phase: string) => void;
|
|
4
|
+
export declare function createPhaseLog(label: string): PhaseLog;
|
|
5
|
+
/**
|
|
6
|
+
* The harness prints its own diagnostics without a clock, which is what the
|
|
7
|
+
* gap needs. Reprint them relative to process start instead.
|
|
8
|
+
*/
|
|
9
|
+
export declare function reportHarnessDiagnostic(event: {
|
|
10
|
+
level: string;
|
|
11
|
+
subsystem?: string | undefined;
|
|
12
|
+
message: string;
|
|
13
|
+
}): void;
|
|
14
|
+
/** Marks when each part of a turn's stream arrives, and when it ends. */
|
|
15
|
+
export declare function timeStreamParts(stream: StreamLike, phase: PhaseLog): StreamLike;
|
|
16
|
+
//# sourceMappingURL=timing.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"timing.d.ts","sourceRoot":"","sources":["../../../../src/commands/ask/harness/timing.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAgB5C,eAAO,MAAM,kBAAkB,SAC2B,CAAC;AAG3D,MAAM,MAAM,QAAQ,GAAG,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;AAE/C,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,QAAQ,CAkBtD;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE;IAC7C,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B,OAAO,EAAE,MAAM,CAAC;CACjB,GAAG,IAAI,CAMP;AAED,yEAAyE;AACzE,wBAAgB,eAAe,CAC7B,MAAM,EAAE,UAAU,EAClB,KAAK,EAAE,QAAQ,GACd,UAAU,CAMZ"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A local turn spends far longer getting the prompt into Claude Code than it
|
|
3
|
+
* spends generating the answer, and none of that is visible from the tree or
|
|
4
|
+
* from Claude Code's own transcript. `CARGO_ASK_TIMING=1` writes the phase
|
|
5
|
+
* boundaries — and the harness's own diagnostics — to stderr. Unset, every
|
|
6
|
+
* call below is a no-op and nothing is added to the stream.
|
|
7
|
+
*
|
|
8
|
+
* `CARGO_ASK_TIMING=parts` adds the whole of each stream part next to its
|
|
9
|
+
* type. Verbose, and the only way to see what the bridge actually sends: the
|
|
10
|
+
* part names alone say a tool call arrived, not that its result came back
|
|
11
|
+
* empty or that the field a reader is looking for is spelled differently here
|
|
12
|
+
* than in the bridge that emitted it.
|
|
13
|
+
*/
|
|
14
|
+
const ASK_TIMING_LEVEL = process.env["CARGO_ASK_TIMING"];
|
|
15
|
+
export const ASK_TIMING_ENABLED = ASK_TIMING_LEVEL === "1" || ASK_TIMING_LEVEL === "parts";
|
|
16
|
+
const ASK_TIMING_DUMPS_PARTS = ASK_TIMING_LEVEL === "parts";
|
|
17
|
+
export function createPhaseLog(label) {
|
|
18
|
+
if (ASK_TIMING_ENABLED === false) {
|
|
19
|
+
return () => undefined;
|
|
20
|
+
}
|
|
21
|
+
const start = Date.now();
|
|
22
|
+
let previous = start;
|
|
23
|
+
return (phase) => {
|
|
24
|
+
const now = Date.now();
|
|
25
|
+
const total = ((now - start) / 1000).toFixed(2).padStart(6);
|
|
26
|
+
const step = ((now - previous) / 1000).toFixed(2).padStart(5);
|
|
27
|
+
previous = now;
|
|
28
|
+
process.stderr.write(`[ask:timing] ${label} ${total}s (+${step}s) ${phase}\n`);
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* The harness prints its own diagnostics without a clock, which is what the
|
|
33
|
+
* gap needs. Reprint them relative to process start instead.
|
|
34
|
+
*/
|
|
35
|
+
export function reportHarnessDiagnostic(event) {
|
|
36
|
+
const elapsed = process.uptime().toFixed(2).padStart(6);
|
|
37
|
+
process.stderr.write(`[ask:timing] harness ${elapsed}s ${event.level} ${event.subsystem ?? "-"} ${event.message}\n`);
|
|
38
|
+
}
|
|
39
|
+
/** Marks when each part of a turn's stream arrives, and when it ends. */
|
|
40
|
+
export function timeStreamParts(stream, phase) {
|
|
41
|
+
if (ASK_TIMING_ENABLED === false) {
|
|
42
|
+
return stream;
|
|
43
|
+
}
|
|
44
|
+
return { text: stream.text, fullStream: timeParts(stream.fullStream, phase) };
|
|
45
|
+
}
|
|
46
|
+
async function* timeParts(parts, phase) {
|
|
47
|
+
let seen = 0;
|
|
48
|
+
for await (const part of parts) {
|
|
49
|
+
seen += 1;
|
|
50
|
+
const type = typeof part === "object" && part !== null && "type" in part
|
|
51
|
+
? String(part.type)
|
|
52
|
+
: "unknown";
|
|
53
|
+
// Text arrives one delta per token, which would bury everything else.
|
|
54
|
+
if (type.startsWith("text-delta") === false) {
|
|
55
|
+
phase(ASK_TIMING_DUMPS_PARTS === true
|
|
56
|
+
? `part ${String(seen)}: ${type} ${dumpPart(part)}`
|
|
57
|
+
: `part ${String(seen)}: ${type}`);
|
|
58
|
+
}
|
|
59
|
+
yield part;
|
|
60
|
+
}
|
|
61
|
+
phase(`stream ended after ${String(seen)} parts`);
|
|
62
|
+
}
|
|
63
|
+
/** One stream part, flattened onto its log line. Truncated: some are huge. */
|
|
64
|
+
function dumpPart(part) {
|
|
65
|
+
try {
|
|
66
|
+
const json = JSON.stringify(part, (_key, value) => value instanceof Error ? `Error(${value.message})` : value);
|
|
67
|
+
return json === undefined ? "" : json.slice(0, 900);
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
return "<unserializable>";
|
|
71
|
+
}
|
|
72
|
+
}
|