@vellumai/assistant 0.10.7-dev.202607101106.c87f4e3 → 0.10.7-dev.202607101343.e727d4c
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/package.json +1 -1
- package/src/agent/loop-exclusive-tool.test.ts +28 -5
- package/src/agent/loop.ts +4 -14
- package/src/cli/commands/attachment.help.ts +100 -0
- package/src/cli/commands/attachment.ts +92 -157
- package/src/cli/commands/audit.help.ts +40 -0
- package/src/cli/commands/audit.ts +66 -86
- package/src/cli/commands/auth.help.ts +46 -0
- package/src/cli/commands/auth.ts +10 -43
- package/src/cli/index.help.ts +23 -0
- package/src/cli/lib/__tests__/install-from-github.test.ts +36 -4
- package/src/cli/lib/cli-command-help.ts +80 -0
- package/src/cli/lib/install-from-github.ts +42 -1
- package/src/daemon/conversation.ts +21 -4
- package/src/plugin-api/index.ts +14 -0
- package/src/plugins/defaults/memory/v2/__tests__/cli-command-store.test.ts +6 -0
- package/src/plugins/defaults/memory/v2/cli-command-content.ts +52 -0
- package/src/plugins/defaults/memory/v2/cli-command-store.ts +31 -10
- package/src/telemetry/turn-trace-store.test.ts +12 -24
- package/src/telemetry/turn-trace-store.ts +5 -11
- package/src/tts/synthesize-text.ts +15 -5
package/package.json
CHANGED
|
@@ -1,16 +1,41 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Verifies the agent loop's exclusive-tool dispatch: when a tool the
|
|
3
|
-
*
|
|
2
|
+
* Verifies the agent loop's exclusive-tool dispatch: when a tool the registry
|
|
3
|
+
* marks exclusive appears in a multi-call turn, only that tool runs and the
|
|
4
4
|
* siblings are deferred un-run with a benign result — so the model incorporates
|
|
5
5
|
* the exclusive tool's output before acting on anything else. Drives the REAL
|
|
6
6
|
* loop, mocking only the provider boundary.
|
|
7
7
|
*/
|
|
8
|
-
import { describe, expect, test } from "bun:test";
|
|
8
|
+
import { beforeAll, describe, expect, test } from "bun:test";
|
|
9
9
|
|
|
10
10
|
import { createMockProvider } from "../__tests__/helpers/mock-provider.js";
|
|
11
|
+
import { RiskLevel } from "../permissions/types.js";
|
|
11
12
|
import type { ContentBlock, ProviderResponse } from "../providers/types.js";
|
|
13
|
+
import { registerTool } from "../tools/registry.js";
|
|
14
|
+
import type { ToolContext, ToolExecutionResult } from "../tools/types.js";
|
|
12
15
|
import { AgentLoop } from "./loop.js";
|
|
13
16
|
|
|
17
|
+
// The loop reads exclusivity straight from the registry (`getTool(name)
|
|
18
|
+
// ?.exclusive`), so seed a registered tool the loop can look up. Other tool
|
|
19
|
+
// names in these turns are absent from the registry, so they read as
|
|
20
|
+
// non-exclusive — exactly the mixed state the deferral logic branches on.
|
|
21
|
+
beforeAll(() => {
|
|
22
|
+
registerTool({
|
|
23
|
+
name: "exclusive_tool",
|
|
24
|
+
description: "Exclusive test tool",
|
|
25
|
+
category: "test",
|
|
26
|
+
defaultRiskLevel: RiskLevel.Low,
|
|
27
|
+
executionTarget: "sandbox",
|
|
28
|
+
exclusive: true,
|
|
29
|
+
input_schema: { type: "object", properties: {}, required: [] },
|
|
30
|
+
async execute(
|
|
31
|
+
_input: Record<string, unknown>,
|
|
32
|
+
_context: ToolContext,
|
|
33
|
+
): Promise<ToolExecutionResult> {
|
|
34
|
+
return { content: "ok", isError: false };
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
|
|
14
39
|
const endTurn = (text: string): ProviderResponse => ({
|
|
15
40
|
content: [{ type: "text", text }],
|
|
16
41
|
model: "mock-model",
|
|
@@ -82,7 +107,6 @@ describe("AgentLoop — exclusive tool deferral", () => {
|
|
|
82
107
|
executed.push(name);
|
|
83
108
|
return { content: `ran ${name}`, isError: false };
|
|
84
109
|
},
|
|
85
|
-
isExclusiveTool: (name) => name === "exclusive_tool",
|
|
86
110
|
});
|
|
87
111
|
|
|
88
112
|
const { history } = await loop.run({
|
|
@@ -137,7 +161,6 @@ describe("AgentLoop — exclusive tool deferral", () => {
|
|
|
137
161
|
executed.push(name);
|
|
138
162
|
return { content: `ran ${name}`, isError: false };
|
|
139
163
|
},
|
|
140
|
-
isExclusiveTool: (name) => name === "exclusive_tool",
|
|
141
164
|
});
|
|
142
165
|
|
|
143
166
|
const { history } = await loop.run({
|
package/src/agent/loop.ts
CHANGED
|
@@ -43,6 +43,7 @@ import type {
|
|
|
43
43
|
ToolResultContent,
|
|
44
44
|
} from "../providers/types.js";
|
|
45
45
|
import { isContextOverflowError } from "../providers/types.js";
|
|
46
|
+
import { getTool } from "../tools/registry.js";
|
|
46
47
|
import type { SensitiveOutputBinding } from "../tools/sensitive-output-placeholders.js";
|
|
47
48
|
import {
|
|
48
49
|
applyStreamingSubstitution,
|
|
@@ -708,14 +709,6 @@ export interface AgentLoopConstructorOptions {
|
|
|
708
709
|
tools?: ToolDefinition[];
|
|
709
710
|
toolExecutor?: LoopToolExecutor;
|
|
710
711
|
resolveTools?: (history: Message[]) => ToolDefinition[];
|
|
711
|
-
/**
|
|
712
|
-
* Decide whether a tool runs exclusively in its turn (see
|
|
713
|
-
* {@link ToolDefinition.exclusive}). When it returns true for a tool present
|
|
714
|
-
* in a multi-call turn, the loop runs only that tool and defers the siblings
|
|
715
|
-
* un-run. Injected by the conversation wiring, which can read the tool
|
|
716
|
-
* registry; lightweight loops that omit it never defer.
|
|
717
|
-
*/
|
|
718
|
-
isExclusiveTool?: (toolName: string) => boolean;
|
|
719
712
|
/**
|
|
720
713
|
* Conversation this loop drives. Scopes the loop-held compaction circuit
|
|
721
714
|
* breaker and is the source of truth the loop's pipeline contexts and
|
|
@@ -741,7 +734,6 @@ export class AgentLoop {
|
|
|
741
734
|
private tools: ToolDefinition[];
|
|
742
735
|
private resolveTools: ((history: Message[]) => ToolDefinition[]) | null;
|
|
743
736
|
private toolExecutor: LoopToolExecutor | null;
|
|
744
|
-
private isExclusiveTool: ((toolName: string) => boolean) | null;
|
|
745
737
|
|
|
746
738
|
/**
|
|
747
739
|
* Conversation this loop drives. Source of truth for the `conversationId`
|
|
@@ -771,7 +763,6 @@ export class AgentLoop {
|
|
|
771
763
|
tools,
|
|
772
764
|
toolExecutor,
|
|
773
765
|
resolveTools,
|
|
774
|
-
isExclusiveTool,
|
|
775
766
|
conversationId,
|
|
776
767
|
resolveConversationDir,
|
|
777
768
|
} = options;
|
|
@@ -781,7 +772,6 @@ export class AgentLoop {
|
|
|
781
772
|
this.tools = tools ?? [];
|
|
782
773
|
this.resolveTools = resolveTools ?? null;
|
|
783
774
|
this.toolExecutor = toolExecutor ?? null;
|
|
784
|
-
this.isExclusiveTool = isExclusiveTool ?? null;
|
|
785
775
|
this.conversationId = conversationId;
|
|
786
776
|
this.resolveConversationDir = resolveConversationDir ?? null;
|
|
787
777
|
this.compactionCircuit = new CompactionCircuit(this.conversationId);
|
|
@@ -2069,9 +2059,9 @@ export class AgentLoop {
|
|
|
2069
2059
|
// the siblings with a benign, un-run result so the model re-issues them
|
|
2070
2060
|
// next turn if still needed. Every tool_use still gets a matching
|
|
2071
2061
|
// tool_result, so history stays well-formed.
|
|
2072
|
-
const exclusiveBlock =
|
|
2073
|
-
|
|
2074
|
-
|
|
2062
|
+
const exclusiveBlock = toolUseBlocks.find(
|
|
2063
|
+
(block) => getTool(block.name)?.exclusive === true,
|
|
2064
|
+
);
|
|
2075
2065
|
const deferSiblings =
|
|
2076
2066
|
exclusiveBlock !== undefined && toolUseBlocks.length > 1;
|
|
2077
2067
|
if (deferSiblings) {
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Declarative help for the `assistant attachment` command.
|
|
3
|
+
*
|
|
4
|
+
* Plain data (no action handlers, imports only the help contract type) so the
|
|
5
|
+
* memory capability indexer can read it without pulling in the daemon/IPC action
|
|
6
|
+
* graph. The handlers live in `attachment.ts`, which applies this via
|
|
7
|
+
* `applyCommandHelp` and attaches them.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { CliCommandHelp } from "../lib/cli-command-help.js";
|
|
11
|
+
|
|
12
|
+
export const attachmentHelp: CliCommandHelp = {
|
|
13
|
+
name: "attachment",
|
|
14
|
+
description: "Manage file attachments for conversations",
|
|
15
|
+
helpText: `
|
|
16
|
+
Attachments come in two flavours:
|
|
17
|
+
|
|
18
|
+
File-backed Large files stored by path reference (no memory copy).
|
|
19
|
+
The file must remain on disk for the lifetime of the
|
|
20
|
+
attachment.
|
|
21
|
+
Inline Small payloads encoded directly (handled internally).
|
|
22
|
+
|
|
23
|
+
Use 'register' to record a file-backed attachment and 'lookup' to
|
|
24
|
+
retrieve its stored path by the original source location.
|
|
25
|
+
|
|
26
|
+
Examples:
|
|
27
|
+
$ assistant attachment register --path /tmp/clip.mp4 --mime video/mp4
|
|
28
|
+
$ assistant attachment register --path /tmp/clip.mp4 --mime video/mp4 --filename recording.mp4
|
|
29
|
+
$ assistant attachment lookup --source /tmp/clip.mp4 --conversation conv_abc123`,
|
|
30
|
+
subcommands: [
|
|
31
|
+
{
|
|
32
|
+
name: "register",
|
|
33
|
+
description: "Register a file-backed attachment with the assistant",
|
|
34
|
+
options: [
|
|
35
|
+
{
|
|
36
|
+
flags: "--path <file>",
|
|
37
|
+
description: "Absolute path to the file (required)",
|
|
38
|
+
required: true,
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
flags: "--mime <type>",
|
|
42
|
+
description: "MIME type of the file (required)",
|
|
43
|
+
required: true,
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
flags: "--filename <name>",
|
|
47
|
+
description: "Display filename (defaults to basename of path)",
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
flags: "--json",
|
|
51
|
+
description: "Output result as machine-readable JSON.",
|
|
52
|
+
},
|
|
53
|
+
],
|
|
54
|
+
helpText: `
|
|
55
|
+
Registers a file on disk as a file-backed attachment in the assistant's
|
|
56
|
+
attachment store. The file must exist at the given path and must remain
|
|
57
|
+
on disk for the lifetime of the attachment — the assistant stores a
|
|
58
|
+
path reference, not a copy.
|
|
59
|
+
|
|
60
|
+
Returns the attachment ID and metadata on success.
|
|
61
|
+
|
|
62
|
+
Examples:
|
|
63
|
+
$ assistant attachment register --path /tmp/clip.mp4 --mime video/mp4
|
|
64
|
+
$ assistant attachment register --path /tmp/screen.png --mime image/png --filename screenshot.png
|
|
65
|
+
$ assistant attachment register --path /tmp/audio.wav --mime audio/wav --json`,
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
name: "lookup",
|
|
69
|
+
description: "Look up a stored attachment by its original source path",
|
|
70
|
+
options: [
|
|
71
|
+
{
|
|
72
|
+
flags: "--source <path>",
|
|
73
|
+
description: "Original source path of the file (required)",
|
|
74
|
+
required: true,
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
flags: "--conversation <id>",
|
|
78
|
+
description:
|
|
79
|
+
"Conversation ID to search within (required) — run 'assistant conversations list' to find it",
|
|
80
|
+
required: true,
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
flags: "--json",
|
|
84
|
+
description: "Output result as machine-readable JSON.",
|
|
85
|
+
},
|
|
86
|
+
],
|
|
87
|
+
helpText: `
|
|
88
|
+
Searches for an attachment that was previously registered with the
|
|
89
|
+
given source path, scoped to a specific conversation. Returns the
|
|
90
|
+
stored file path on success.
|
|
91
|
+
|
|
92
|
+
Attachments are linked to messages within conversations. Use
|
|
93
|
+
'assistant conversations list' to find the conversation ID.
|
|
94
|
+
|
|
95
|
+
Examples:
|
|
96
|
+
$ assistant attachment lookup --source /tmp/clip.mp4 --conversation conv_abc123
|
|
97
|
+
$ assistant attachment lookup --source /path/to/recording.mp4 --conversation conv_xyz --json`,
|
|
98
|
+
},
|
|
99
|
+
],
|
|
100
|
+
};
|
|
@@ -3,189 +3,124 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Subcommands: register, lookup — thin wrappers over the daemon's
|
|
5
5
|
* attachment routes (`attachment_register`, `attachment_lookup`).
|
|
6
|
+
*
|
|
7
|
+
* The command's help structure lives in `attachment.help.ts` (import-safe for
|
|
8
|
+
* the memory capability indexer); this module applies it and attaches the
|
|
9
|
+
* action handlers.
|
|
6
10
|
*/
|
|
7
11
|
|
|
8
12
|
import type { Command } from "commander";
|
|
9
13
|
|
|
10
14
|
import { cliIpcCall } from "../../ipc/cli-client.js";
|
|
15
|
+
import { applyCommandHelp, subcommand } from "../lib/cli-command-help.js";
|
|
11
16
|
import { registerCommand } from "../lib/register-command.js";
|
|
12
17
|
import { log } from "../logger.js";
|
|
13
18
|
import { shouldOutputJson, writeOutput } from "../output.js";
|
|
19
|
+
import { attachmentHelp } from "./attachment.help.js";
|
|
14
20
|
|
|
15
21
|
// ── Registration ──────────────────────────────────────────────────────
|
|
16
22
|
|
|
17
23
|
export function registerAttachmentCommand(program: Command): void {
|
|
18
24
|
registerCommand(program, {
|
|
19
|
-
name:
|
|
25
|
+
name: attachmentHelp.name,
|
|
20
26
|
transport: "ipc",
|
|
21
|
-
description:
|
|
27
|
+
description: attachmentHelp.description,
|
|
22
28
|
build: (attachment) => {
|
|
29
|
+
applyCommandHelp(attachment, attachmentHelp);
|
|
30
|
+
|
|
31
|
+
// ── register ───────────────────────────────────────────────────
|
|
32
|
+
subcommand(attachment, "register").action(
|
|
33
|
+
async (
|
|
34
|
+
opts: {
|
|
35
|
+
path: string;
|
|
36
|
+
mime: string;
|
|
37
|
+
filename?: string;
|
|
38
|
+
json?: boolean;
|
|
39
|
+
},
|
|
40
|
+
cmd: Command,
|
|
41
|
+
) => {
|
|
42
|
+
const jsonOutput = opts.json || shouldOutputJson(cmd);
|
|
43
|
+
|
|
44
|
+
const result = await cliIpcCall<{
|
|
45
|
+
id: string;
|
|
46
|
+
originalFilename: string;
|
|
47
|
+
mimeType: string;
|
|
48
|
+
sizeBytes: number;
|
|
49
|
+
kind: string;
|
|
50
|
+
filePath: string;
|
|
51
|
+
createdAt: number;
|
|
52
|
+
}>("attachment_register", {
|
|
53
|
+
body: {
|
|
54
|
+
path: opts.path,
|
|
55
|
+
mimeType: opts.mime,
|
|
56
|
+
filename: opts.filename,
|
|
57
|
+
},
|
|
58
|
+
});
|
|
23
59
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
Use 'register' to record a file-backed attachment and 'lookup' to
|
|
35
|
-
retrieve its stored path by the original source location.
|
|
36
|
-
|
|
37
|
-
Examples:
|
|
38
|
-
$ assistant attachment register --path /tmp/clip.mp4 --mime video/mp4
|
|
39
|
-
$ assistant attachment register --path /tmp/clip.mp4 --mime video/mp4 --filename recording.mp4
|
|
40
|
-
$ assistant attachment lookup --source /tmp/clip.mp4 --conversation conv_abc123`,
|
|
41
|
-
);
|
|
42
|
-
|
|
43
|
-
// ── register ─────────────────────────────────────────────────────
|
|
44
|
-
|
|
45
|
-
attachment
|
|
46
|
-
.command("register")
|
|
47
|
-
.description("Register a file-backed attachment with the assistant")
|
|
48
|
-
.requiredOption("--path <file>", "Absolute path to the file (required)")
|
|
49
|
-
.requiredOption("--mime <type>", "MIME type of the file (required)")
|
|
50
|
-
.option(
|
|
51
|
-
"--filename <name>",
|
|
52
|
-
"Display filename (defaults to basename of path)",
|
|
53
|
-
)
|
|
54
|
-
.option("--json", "Output result as machine-readable JSON.")
|
|
55
|
-
.addHelpText(
|
|
56
|
-
"after",
|
|
57
|
-
`
|
|
58
|
-
Registers a file on disk as a file-backed attachment in the assistant's
|
|
59
|
-
attachment store. The file must exist at the given path and must remain
|
|
60
|
-
on disk for the lifetime of the attachment — the assistant stores a
|
|
61
|
-
path reference, not a copy.
|
|
62
|
-
|
|
63
|
-
Returns the attachment ID and metadata on success.
|
|
64
|
-
|
|
65
|
-
Examples:
|
|
66
|
-
$ assistant attachment register --path /tmp/clip.mp4 --mime video/mp4
|
|
67
|
-
$ assistant attachment register --path /tmp/screen.png --mime image/png --filename screenshot.png
|
|
68
|
-
$ assistant attachment register --path /tmp/audio.wav --mime audio/wav --json`,
|
|
69
|
-
)
|
|
70
|
-
.action(
|
|
71
|
-
async (
|
|
72
|
-
opts: {
|
|
73
|
-
path: string;
|
|
74
|
-
mime: string;
|
|
75
|
-
filename?: string;
|
|
76
|
-
json?: boolean;
|
|
77
|
-
},
|
|
78
|
-
cmd: Command,
|
|
79
|
-
) => {
|
|
80
|
-
const jsonOutput = opts.json || shouldOutputJson(cmd);
|
|
60
|
+
if (!result.ok) {
|
|
61
|
+
if (jsonOutput) {
|
|
62
|
+
writeOutput(cmd, { ok: false, error: result.error });
|
|
63
|
+
} else {
|
|
64
|
+
log.error(result.error ?? "Unknown error");
|
|
65
|
+
}
|
|
66
|
+
process.exitCode = 1;
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
81
69
|
|
|
82
|
-
|
|
83
|
-
id: string;
|
|
84
|
-
originalFilename: string;
|
|
85
|
-
mimeType: string;
|
|
86
|
-
sizeBytes: number;
|
|
87
|
-
kind: string;
|
|
88
|
-
filePath: string;
|
|
89
|
-
createdAt: number;
|
|
90
|
-
}>("attachment_register", {
|
|
91
|
-
body: {
|
|
92
|
-
path: opts.path,
|
|
93
|
-
mimeType: opts.mime,
|
|
94
|
-
filename: opts.filename,
|
|
95
|
-
},
|
|
96
|
-
});
|
|
70
|
+
const record = result.result!;
|
|
97
71
|
|
|
98
|
-
if (!result.ok) {
|
|
99
72
|
if (jsonOutput) {
|
|
100
|
-
writeOutput(cmd, { ok:
|
|
73
|
+
writeOutput(cmd, { ok: true, ...record });
|
|
101
74
|
} else {
|
|
102
|
-
|
|
75
|
+
process.stdout.write(`${record.id}\n`);
|
|
76
|
+
log.info(`Attachment registered: ${record.id}`);
|
|
77
|
+
log.info(` Filename: ${record.originalFilename}`);
|
|
78
|
+
log.info(` MIME: ${record.mimeType}`);
|
|
79
|
+
log.info(` Size: ${record.sizeBytes} bytes`);
|
|
80
|
+
log.info(` Kind: ${record.kind}`);
|
|
81
|
+
log.info(` Path: ${record.filePath}`);
|
|
103
82
|
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
);
|
|
123
|
-
|
|
124
|
-
// ── lookup ───────────────────────────────────────────────────────
|
|
125
|
-
|
|
126
|
-
attachment
|
|
127
|
-
.command("lookup")
|
|
128
|
-
.description("Look up a stored attachment by its original source path")
|
|
129
|
-
.requiredOption(
|
|
130
|
-
"--source <path>",
|
|
131
|
-
"Original source path of the file (required)",
|
|
132
|
-
)
|
|
133
|
-
.requiredOption(
|
|
134
|
-
"--conversation <id>",
|
|
135
|
-
"Conversation ID to search within (required) — run 'assistant conversations list' to find it",
|
|
136
|
-
)
|
|
137
|
-
.option("--json", "Output result as machine-readable JSON.")
|
|
138
|
-
.addHelpText(
|
|
139
|
-
"after",
|
|
140
|
-
`
|
|
141
|
-
Searches for an attachment that was previously registered with the
|
|
142
|
-
given source path, scoped to a specific conversation. Returns the
|
|
143
|
-
stored file path on success.
|
|
144
|
-
|
|
145
|
-
Attachments are linked to messages within conversations. Use
|
|
146
|
-
'assistant conversations list' to find the conversation ID.
|
|
147
|
-
|
|
148
|
-
Examples:
|
|
149
|
-
$ assistant attachment lookup --source /tmp/clip.mp4 --conversation conv_abc123
|
|
150
|
-
$ assistant attachment lookup --source /path/to/recording.mp4 --conversation conv_xyz --json`,
|
|
151
|
-
)
|
|
152
|
-
.action(
|
|
153
|
-
async (
|
|
154
|
-
opts: { source: string; conversation: string; json?: boolean },
|
|
155
|
-
cmd: Command,
|
|
156
|
-
) => {
|
|
157
|
-
const jsonOutput = opts.json || shouldOutputJson(cmd);
|
|
158
|
-
|
|
159
|
-
const result = await cliIpcCall<{ filePath: string }>(
|
|
160
|
-
"attachment_lookup",
|
|
161
|
-
{
|
|
162
|
-
body: {
|
|
163
|
-
sourcePath: opts.source,
|
|
164
|
-
conversationId: opts.conversation,
|
|
83
|
+
},
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
// ── lookup ─────────────────────────────────────────────────────
|
|
87
|
+
subcommand(attachment, "lookup").action(
|
|
88
|
+
async (
|
|
89
|
+
opts: { source: string; conversation: string; json?: boolean },
|
|
90
|
+
cmd: Command,
|
|
91
|
+
) => {
|
|
92
|
+
const jsonOutput = opts.json || shouldOutputJson(cmd);
|
|
93
|
+
|
|
94
|
+
const result = await cliIpcCall<{ filePath: string }>(
|
|
95
|
+
"attachment_lookup",
|
|
96
|
+
{
|
|
97
|
+
body: {
|
|
98
|
+
sourcePath: opts.source,
|
|
99
|
+
conversationId: opts.conversation,
|
|
100
|
+
},
|
|
165
101
|
},
|
|
166
|
-
|
|
167
|
-
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
if (!result.ok) {
|
|
105
|
+
if (jsonOutput) {
|
|
106
|
+
writeOutput(cmd, { ok: false, error: result.error });
|
|
107
|
+
} else {
|
|
108
|
+
log.error(result.error ?? "Unknown error");
|
|
109
|
+
}
|
|
110
|
+
process.exitCode = 1;
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
168
113
|
|
|
169
|
-
if (!result.ok) {
|
|
170
114
|
if (jsonOutput) {
|
|
171
|
-
writeOutput(cmd, {
|
|
115
|
+
writeOutput(cmd, {
|
|
116
|
+
ok: true,
|
|
117
|
+
filePath: result.result!.filePath,
|
|
118
|
+
});
|
|
172
119
|
} else {
|
|
173
|
-
|
|
120
|
+
process.stdout.write(result.result!.filePath + "\n");
|
|
174
121
|
}
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
if (jsonOutput) {
|
|
180
|
-
writeOutput(cmd, {
|
|
181
|
-
ok: true,
|
|
182
|
-
filePath: result.result!.filePath,
|
|
183
|
-
});
|
|
184
|
-
} else {
|
|
185
|
-
process.stdout.write(result.result!.filePath + "\n");
|
|
186
|
-
}
|
|
187
|
-
},
|
|
188
|
-
);
|
|
122
|
+
},
|
|
123
|
+
);
|
|
189
124
|
},
|
|
190
125
|
});
|
|
191
126
|
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Declarative help for the `assistant audit` command.
|
|
3
|
+
*
|
|
4
|
+
* Plain data (no action handlers, imports only the help contract type) so the
|
|
5
|
+
* memory capability indexer can read it without pulling in the daemon/IPC action
|
|
6
|
+
* graph. The handler lives in `audit.ts`, which applies this via
|
|
7
|
+
* `applyCommandHelp` and attaches it.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { CliCommandHelp } from "../lib/cli-command-help.js";
|
|
11
|
+
|
|
12
|
+
export const auditHelp: CliCommandHelp = {
|
|
13
|
+
name: "audit",
|
|
14
|
+
description: "Show recent tool invocations",
|
|
15
|
+
options: [
|
|
16
|
+
{
|
|
17
|
+
flags: "-l, --limit <n>",
|
|
18
|
+
description: "Number of entries to show",
|
|
19
|
+
defaultValue: "20",
|
|
20
|
+
},
|
|
21
|
+
{ flags: "--json", description: "Output raw JSON" },
|
|
22
|
+
],
|
|
23
|
+
helpText: `
|
|
24
|
+
Reads from the tool invocation audit log via the daemon. Each row
|
|
25
|
+
represents one tool call the assistant made, including what was invoked,
|
|
26
|
+
how the approval system classified it, and how long it took.
|
|
27
|
+
|
|
28
|
+
Table columns:
|
|
29
|
+
Timestamp When the tool was invoked (UTC, YYYY-MM-DD HH:MM:SS)
|
|
30
|
+
Tool Tool name (e.g. bash, read_file, write_file, browser)
|
|
31
|
+
Input Truncated summary of the tool input (command, path, etc.)
|
|
32
|
+
Decision Approval decision: allow, deny, or ask
|
|
33
|
+
Risk Risk classification: none, low, medium, high
|
|
34
|
+
Duration Wall-clock execution time (e.g. 120ms, 1.3s)
|
|
35
|
+
|
|
36
|
+
Examples:
|
|
37
|
+
$ assistant audit
|
|
38
|
+
$ assistant audit --limit 50
|
|
39
|
+
$ assistant audit --json`,
|
|
40
|
+
};
|