anyclaude-sdk 0.1.0
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/LICENSE +21 -0
- package/README.md +295 -0
- package/dist/agent.d.ts +110 -0
- package/dist/agent.js +897 -0
- package/dist/background/index.d.ts +3 -0
- package/dist/background/index.js +9 -0
- package/dist/background/manager.d.ts +32 -0
- package/dist/background/manager.js +108 -0
- package/dist/background/tools.d.ts +5 -0
- package/dist/background/tools.js +98 -0
- package/dist/background/worker.d.ts +19 -0
- package/dist/background/worker.js +30 -0
- package/dist/commands/builtins.d.ts +2 -0
- package/dist/commands/builtins.js +306 -0
- package/dist/commands/index.d.ts +21 -0
- package/dist/commands/index.js +56 -0
- package/dist/commands/types.d.ts +110 -0
- package/dist/commands/types.js +5 -0
- package/dist/compact.d.ts +22 -0
- package/dist/compact.js +67 -0
- package/dist/fs/dexie.d.ts +57 -0
- package/dist/fs/dexie.js +243 -0
- package/dist/fs/index.d.ts +4 -0
- package/dist/fs/index.js +13 -0
- package/dist/fs/linuxTree.d.ts +11 -0
- package/dist/fs/linuxTree.js +43 -0
- package/dist/fs/opfs.d.ts +23 -0
- package/dist/fs/opfs.js +112 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.js +29 -0
- package/dist/llm/anthropic.d.ts +24 -0
- package/dist/llm/anthropic.js +280 -0
- package/dist/llm/index.d.ts +3 -0
- package/dist/llm/index.js +3 -0
- package/dist/llm/inlineTools.d.ts +11 -0
- package/dist/llm/inlineTools.js +72 -0
- package/dist/llm/openai.d.ts +29 -0
- package/dist/llm/openai.js +224 -0
- package/dist/llm/responses.d.ts +18 -0
- package/dist/llm/responses.js +256 -0
- package/dist/mcp/client.d.ts +20 -0
- package/dist/mcp/client.js +156 -0
- package/dist/mcp/index.d.ts +24 -0
- package/dist/mcp/index.js +157 -0
- package/dist/mcp/proxy.d.ts +3 -0
- package/dist/mcp/proxy.js +25 -0
- package/dist/mcp/sdkServer.d.ts +21 -0
- package/dist/mcp/sdkServer.js +28 -0
- package/dist/mcp/types.d.ts +92 -0
- package/dist/mcp/types.js +5 -0
- package/dist/memory/index.d.ts +4 -0
- package/dist/memory/index.js +5 -0
- package/dist/memory/render.d.ts +7 -0
- package/dist/memory/render.js +46 -0
- package/dist/memory/store.d.ts +20 -0
- package/dist/memory/store.js +79 -0
- package/dist/memory/tools.d.ts +5 -0
- package/dist/memory/tools.js +95 -0
- package/dist/memory/types.d.ts +15 -0
- package/dist/memory/types.js +4 -0
- package/dist/permissions/dangerous.d.ts +4 -0
- package/dist/permissions/dangerous.js +24 -0
- package/dist/permissions/gate.d.ts +21 -0
- package/dist/permissions/gate.js +66 -0
- package/dist/permissions/index.d.ts +5 -0
- package/dist/permissions/index.js +6 -0
- package/dist/permissions/match.d.ts +19 -0
- package/dist/permissions/match.js +104 -0
- package/dist/permissions/planMode.d.ts +3 -0
- package/dist/permissions/planMode.js +33 -0
- package/dist/permissions/types.d.ts +19 -0
- package/dist/permissions/types.js +2 -0
- package/dist/persist.d.ts +15 -0
- package/dist/persist.js +58 -0
- package/dist/prompt.d.ts +6 -0
- package/dist/prompt.js +34 -0
- package/dist/query.d.ts +105 -0
- package/dist/query.js +115 -0
- package/dist/queue.d.ts +23 -0
- package/dist/queue.js +43 -0
- package/dist/sandbox/cloudflare.d.ts +48 -0
- package/dist/sandbox/cloudflare.js +124 -0
- package/dist/sandbox/daytona.d.ts +48 -0
- package/dist/sandbox/daytona.js +79 -0
- package/dist/sandbox/e2b.d.ts +54 -0
- package/dist/sandbox/e2b.js +87 -0
- package/dist/sandbox/index.d.ts +8 -0
- package/dist/sandbox/index.js +19 -0
- package/dist/sandbox/local.d.ts +51 -0
- package/dist/sandbox/local.js +155 -0
- package/dist/sandbox/types.d.ts +18 -0
- package/dist/sandbox/types.js +27 -0
- package/dist/sandbox/util.d.ts +15 -0
- package/dist/sandbox/util.js +100 -0
- package/dist/sandbox/vercel.d.ts +48 -0
- package/dist/sandbox/vercel.js +130 -0
- package/dist/session/index.d.ts +2 -0
- package/dist/session/index.js +6 -0
- package/dist/session/store.d.ts +28 -0
- package/dist/session/store.js +122 -0
- package/dist/session/types.d.ts +22 -0
- package/dist/session/types.js +2 -0
- package/dist/settings/index.d.ts +3 -0
- package/dist/settings/index.js +3 -0
- package/dist/settings/load.d.ts +20 -0
- package/dist/settings/load.js +36 -0
- package/dist/settings/merge.d.ts +13 -0
- package/dist/settings/merge.js +65 -0
- package/dist/settings/types.d.ts +17 -0
- package/dist/settings/types.js +3 -0
- package/dist/skills/index.d.ts +4 -0
- package/dist/skills/index.js +5 -0
- package/dist/skills/load.d.ts +23 -0
- package/dist/skills/load.js +54 -0
- package/dist/skills/parse.d.ts +7 -0
- package/dist/skills/parse.js +40 -0
- package/dist/skills/tool.d.ts +2 -0
- package/dist/skills/tool.js +39 -0
- package/dist/skills/types.d.ts +10 -0
- package/dist/skills/types.js +4 -0
- package/dist/team/dispatch.d.ts +2 -0
- package/dist/team/dispatch.js +41 -0
- package/dist/team/index.d.ts +9 -0
- package/dist/team/index.js +11 -0
- package/dist/team/mailbox.d.ts +24 -0
- package/dist/team/mailbox.js +33 -0
- package/dist/team/prompt.d.ts +1 -0
- package/dist/team/prompt.js +12 -0
- package/dist/team/runner.d.ts +20 -0
- package/dist/team/runner.js +45 -0
- package/dist/team/taskBoard.d.ts +41 -0
- package/dist/team/taskBoard.js +73 -0
- package/dist/team/tools.d.ts +7 -0
- package/dist/team/tools.js +190 -0
- package/dist/tools/bash.d.ts +2 -0
- package/dist/tools/bash.js +45 -0
- package/dist/tools/config.d.ts +2 -0
- package/dist/tools/config.js +44 -0
- package/dist/tools/define.d.ts +18 -0
- package/dist/tools/define.js +21 -0
- package/dist/tools/delete_file.d.ts +2 -0
- package/dist/tools/delete_file.js +33 -0
- package/dist/tools/edit_file.d.ts +2 -0
- package/dist/tools/edit_file.js +93 -0
- package/dist/tools/fileTypes.d.ts +32 -0
- package/dist/tools/fileTypes.js +166 -0
- package/dist/tools/glob.d.ts +2 -0
- package/dist/tools/glob.js +53 -0
- package/dist/tools/grep.d.ts +2 -0
- package/dist/tools/grep.js +110 -0
- package/dist/tools/imageProcessor.d.ts +15 -0
- package/dist/tools/imageProcessor.js +83 -0
- package/dist/tools/index.d.ts +28 -0
- package/dist/tools/index.js +45 -0
- package/dist/tools/list_files.d.ts +2 -0
- package/dist/tools/list_files.js +42 -0
- package/dist/tools/multi_edit.d.ts +2 -0
- package/dist/tools/multi_edit.js +112 -0
- package/dist/tools/notebook_edit.d.ts +2 -0
- package/dist/tools/notebook_edit.js +118 -0
- package/dist/tools/plan_mode.d.ts +4 -0
- package/dist/tools/plan_mode.js +44 -0
- package/dist/tools/read_file.d.ts +2 -0
- package/dist/tools/read_file.js +193 -0
- package/dist/tools/task.d.ts +2 -0
- package/dist/tools/task.js +77 -0
- package/dist/tools/todo_write.d.ts +2 -0
- package/dist/tools/todo_write.js +104 -0
- package/dist/tools/tool_search.d.ts +2 -0
- package/dist/tools/tool_search.js +49 -0
- package/dist/tools/types.d.ts +82 -0
- package/dist/tools/types.js +1 -0
- package/dist/tools/walk.d.ts +29 -0
- package/dist/tools/walk.js +82 -0
- package/dist/tools/web_fetch.d.ts +2 -0
- package/dist/tools/web_fetch.js +76 -0
- package/dist/tools/web_search.d.ts +22 -0
- package/dist/tools/web_search.js +195 -0
- package/dist/tools/write_file.d.ts +2 -0
- package/dist/tools/write_file.js +39 -0
- package/dist/types/index.d.ts +363 -0
- package/dist/types/index.js +9 -0
- package/dist/util/ids.d.ts +3 -0
- package/dist/util/ids.js +22 -0
- package/dist/util/paths.d.ts +16 -0
- package/dist/util/paths.js +72 -0
- package/dist/util/pricing.d.ts +15 -0
- package/dist/util/pricing.js +81 -0
- package/dist/workspace/index.d.ts +2 -0
- package/dist/workspace/index.js +2 -0
- package/dist/workspace/memory.d.ts +28 -0
- package/dist/workspace/memory.js +97 -0
- package/dist/workspace/webcontainer.d.ts +65 -0
- package/dist/workspace/webcontainer.js +156 -0
- package/package.json +78 -0
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// Background task system for browser-claude-sdk.
|
|
2
|
+
//
|
|
3
|
+
// - BackgroundTaskManager: run detached, pollable async work on the main thread
|
|
4
|
+
// (background sub-agents, long shell commands) — no worker required.
|
|
5
|
+
// - worker helpers: optional Comlink-based off-main-thread execution.
|
|
6
|
+
// - tools: task_list / task_output / task_stop for the agent to manage tasks.
|
|
7
|
+
export { BackgroundTaskManager, } from './manager.js';
|
|
8
|
+
export { exposeBackgroundWorker, wrapWorker, } from './worker.js';
|
|
9
|
+
export { taskList, taskOutput, taskStop, BACKGROUND_TOOLS } from './tools.js';
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export type BgStatus = 'running' | 'completed' | 'failed' | 'stopped';
|
|
2
|
+
export interface BgTask {
|
|
3
|
+
id: string;
|
|
4
|
+
description: string;
|
|
5
|
+
status: BgStatus;
|
|
6
|
+
output: string;
|
|
7
|
+
error?: string;
|
|
8
|
+
startedAt: number;
|
|
9
|
+
endedAt?: number;
|
|
10
|
+
}
|
|
11
|
+
/** Work performed by a background task. */
|
|
12
|
+
export type BgTaskFn = (signal: AbortSignal, append: (chunk: string) => void) => Promise<string>;
|
|
13
|
+
export declare class BackgroundTaskManager {
|
|
14
|
+
private entries;
|
|
15
|
+
private counter;
|
|
16
|
+
/**
|
|
17
|
+
* Launch `fn` detached and return a task id immediately. The task runs in the
|
|
18
|
+
* background; poll it with get()/output()/list(). Never throws synchronously.
|
|
19
|
+
*/
|
|
20
|
+
start(description: string, fn: BgTaskFn): string;
|
|
21
|
+
get(id: string): BgTask | undefined;
|
|
22
|
+
list(): BgTask[];
|
|
23
|
+
/** Return a task's output, optionally sliced from a char offset. */
|
|
24
|
+
output(id: string, opts?: {
|
|
25
|
+
since?: number;
|
|
26
|
+
}): string | null;
|
|
27
|
+
/** Abort a running task. Returns false if there is no such running task. */
|
|
28
|
+
stop(id: string): boolean;
|
|
29
|
+
stopAll(): void;
|
|
30
|
+
/** Await settlement of a task (resolves regardless of outcome). */
|
|
31
|
+
wait(id: string): Promise<BgTask | undefined>;
|
|
32
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
// In-thread background task manager. Runs detached async work (background
|
|
2
|
+
// sub-agents, long shell commands) without blocking the main agent loop. Tasks
|
|
3
|
+
// are pollable by id; output can stream in via the `append` callback.
|
|
4
|
+
//
|
|
5
|
+
// This is the core — it requires no worker. For true off-main-thread execution,
|
|
6
|
+
// see ./worker.ts (optional, Comlink-based).
|
|
7
|
+
function now() {
|
|
8
|
+
// Read Date.now lazily at runtime (never at module top-level).
|
|
9
|
+
return Date.now();
|
|
10
|
+
}
|
|
11
|
+
export class BackgroundTaskManager {
|
|
12
|
+
constructor() {
|
|
13
|
+
this.entries = new Map();
|
|
14
|
+
this.counter = 0;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Launch `fn` detached and return a task id immediately. The task runs in the
|
|
18
|
+
* background; poll it with get()/output()/list(). Never throws synchronously.
|
|
19
|
+
*/
|
|
20
|
+
start(description, fn) {
|
|
21
|
+
const id = `bg_${++this.counter}`;
|
|
22
|
+
const controller = new AbortController();
|
|
23
|
+
const task = {
|
|
24
|
+
id,
|
|
25
|
+
description,
|
|
26
|
+
status: 'running',
|
|
27
|
+
output: '',
|
|
28
|
+
startedAt: now(),
|
|
29
|
+
};
|
|
30
|
+
const append = (chunk) => {
|
|
31
|
+
// Ignore writes after the task has settled.
|
|
32
|
+
if (task.status === 'running')
|
|
33
|
+
task.output += chunk;
|
|
34
|
+
};
|
|
35
|
+
const promise = (async () => {
|
|
36
|
+
try {
|
|
37
|
+
const final = await fn(controller.signal, append);
|
|
38
|
+
if (task.status !== 'running')
|
|
39
|
+
return; // already stopped
|
|
40
|
+
// Append the final result unless it was already streamed via `append`.
|
|
41
|
+
if (final && !task.output.trimEnd().endsWith(final.trimEnd())) {
|
|
42
|
+
task.output += (task.output ? '\n' : '') + final;
|
|
43
|
+
}
|
|
44
|
+
task.status = 'completed';
|
|
45
|
+
}
|
|
46
|
+
catch (err) {
|
|
47
|
+
if (task.status === 'stopped')
|
|
48
|
+
return;
|
|
49
|
+
if (controller.signal.aborted) {
|
|
50
|
+
task.status = 'stopped';
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
task.status = 'failed';
|
|
54
|
+
task.error = err instanceof Error ? err.message : String(err);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
finally {
|
|
58
|
+
task.endedAt = now();
|
|
59
|
+
}
|
|
60
|
+
})();
|
|
61
|
+
this.entries.set(id, { task, controller, promise });
|
|
62
|
+
return id;
|
|
63
|
+
}
|
|
64
|
+
get(id) {
|
|
65
|
+
return this.entries.get(id)?.task;
|
|
66
|
+
}
|
|
67
|
+
list() {
|
|
68
|
+
return [...this.entries.values()].map((e) => e.task);
|
|
69
|
+
}
|
|
70
|
+
/** Return a task's output, optionally sliced from a char offset. */
|
|
71
|
+
output(id, opts) {
|
|
72
|
+
const entry = this.entries.get(id);
|
|
73
|
+
if (!entry)
|
|
74
|
+
return null;
|
|
75
|
+
const out = entry.task.output;
|
|
76
|
+
return opts?.since ? out.slice(opts.since) : out;
|
|
77
|
+
}
|
|
78
|
+
/** Abort a running task. Returns false if there is no such running task. */
|
|
79
|
+
stop(id) {
|
|
80
|
+
const entry = this.entries.get(id);
|
|
81
|
+
if (!entry)
|
|
82
|
+
return false;
|
|
83
|
+
if (entry.task.status === 'running') {
|
|
84
|
+
entry.task.status = 'stopped';
|
|
85
|
+
entry.task.endedAt = now();
|
|
86
|
+
try {
|
|
87
|
+
entry.controller.abort();
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
/* ignore */
|
|
91
|
+
}
|
|
92
|
+
return true;
|
|
93
|
+
}
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
stopAll() {
|
|
97
|
+
for (const id of this.entries.keys())
|
|
98
|
+
this.stop(id);
|
|
99
|
+
}
|
|
100
|
+
/** Await settlement of a task (resolves regardless of outcome). */
|
|
101
|
+
async wait(id) {
|
|
102
|
+
const entry = this.entries.get(id);
|
|
103
|
+
if (!entry)
|
|
104
|
+
return undefined;
|
|
105
|
+
await entry.promise;
|
|
106
|
+
return entry.task;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
// Tools for inspecting and controlling background tasks. They read
|
|
2
|
+
// `ctx.background` (a BackgroundTaskManager) which the agent loop injects into
|
|
3
|
+
// ToolContext when background tasks are enabled.
|
|
4
|
+
/** Read the (optional) background manager off the tool context. */
|
|
5
|
+
function mgr(ctx) {
|
|
6
|
+
return ctx.background;
|
|
7
|
+
}
|
|
8
|
+
function ageMs(task) {
|
|
9
|
+
const end = task.endedAt ?? Date.now();
|
|
10
|
+
return end - task.startedAt;
|
|
11
|
+
}
|
|
12
|
+
const NOT_ENABLED = {
|
|
13
|
+
content: 'Background tasks are not enabled for this session.',
|
|
14
|
+
isError: true,
|
|
15
|
+
};
|
|
16
|
+
export const taskList = {
|
|
17
|
+
def: {
|
|
18
|
+
type: 'function',
|
|
19
|
+
function: {
|
|
20
|
+
name: 'task_list',
|
|
21
|
+
description: 'List background tasks with their id, status, description, and age. Use to see what background work is running or finished.',
|
|
22
|
+
parameters: { type: 'object', properties: {} },
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
async run(_input, ctx) {
|
|
26
|
+
const m = mgr(ctx);
|
|
27
|
+
if (!m)
|
|
28
|
+
return { ...NOT_ENABLED };
|
|
29
|
+
const tasks = m.list();
|
|
30
|
+
if (!tasks.length)
|
|
31
|
+
return { content: 'No background tasks.' };
|
|
32
|
+
const rows = tasks
|
|
33
|
+
.map((t) => `${t.id} [${t.status}] ${t.description} (${Math.round(ageMs(t) / 1000)}s${t.error ? `, error: ${t.error}` : ''})`)
|
|
34
|
+
.join('\n');
|
|
35
|
+
return { content: rows };
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
export const taskOutput = {
|
|
39
|
+
// Background output can be large; let the loop persist it if needed.
|
|
40
|
+
def: {
|
|
41
|
+
type: 'function',
|
|
42
|
+
function: {
|
|
43
|
+
name: 'task_output',
|
|
44
|
+
description: "Read a background task's accumulated output. Optionally pass `since` (a character offset) to fetch only new output since a previous read.",
|
|
45
|
+
parameters: {
|
|
46
|
+
type: 'object',
|
|
47
|
+
properties: {
|
|
48
|
+
task_id: { type: 'string', description: 'The background task id (e.g. bg_1).' },
|
|
49
|
+
since: {
|
|
50
|
+
type: 'number',
|
|
51
|
+
description: 'Character offset to read from (for incremental polling).',
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
required: ['task_id'],
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
async run(input, ctx) {
|
|
59
|
+
const m = mgr(ctx);
|
|
60
|
+
if (!m)
|
|
61
|
+
return { ...NOT_ENABLED };
|
|
62
|
+
const id = String(input.task_id ?? '');
|
|
63
|
+
const task = m.get(id);
|
|
64
|
+
if (!task)
|
|
65
|
+
return { content: `No background task with id "${id}".`, isError: true };
|
|
66
|
+
const since = typeof input.since === 'number' ? input.since : undefined;
|
|
67
|
+
const out = m.output(id, since !== undefined ? { since } : undefined) ?? '';
|
|
68
|
+
const header = `[${task.id}] status=${task.status}${task.error ? ` error=${task.error}` : ''}`;
|
|
69
|
+
return { content: out ? `${header}\n${out}` : `${header}\n(no output yet)` };
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
export const taskStop = {
|
|
73
|
+
def: {
|
|
74
|
+
type: 'function',
|
|
75
|
+
function: {
|
|
76
|
+
name: 'task_stop',
|
|
77
|
+
description: 'Stop (abort) a running background task by id.',
|
|
78
|
+
parameters: {
|
|
79
|
+
type: 'object',
|
|
80
|
+
properties: {
|
|
81
|
+
task_id: { type: 'string', description: 'The background task id to stop.' },
|
|
82
|
+
},
|
|
83
|
+
required: ['task_id'],
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
async run(input, ctx) {
|
|
88
|
+
const m = mgr(ctx);
|
|
89
|
+
if (!m)
|
|
90
|
+
return { ...NOT_ENABLED };
|
|
91
|
+
const id = String(input.task_id ?? '');
|
|
92
|
+
const stopped = m.stop(id);
|
|
93
|
+
return stopped
|
|
94
|
+
? { content: `Stopped background task ${id}.` }
|
|
95
|
+
: { content: `Task ${id} was not running (no such task or already finished).`, isError: true };
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
export const BACKGROUND_TOOLS = [taskList, taskOutput, taskStop];
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A generic "run this serializable job" surface a worker can expose. `code`
|
|
3
|
+
* identifies the job (e.g. a registered handler name or a stringified task);
|
|
4
|
+
* `input` is structured-clone-serializable data. Keep it serializable —
|
|
5
|
+
* functions, class instances, and live handles do not cross the worker boundary.
|
|
6
|
+
*/
|
|
7
|
+
export interface WorkerRunner {
|
|
8
|
+
run(code: string, input: unknown): Promise<unknown>;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Call inside a Web Worker / Service Worker to expose a WorkerRunner to the main
|
|
12
|
+
* thread via Comlink. No-op (with a console warning) if not in a worker scope.
|
|
13
|
+
*/
|
|
14
|
+
export declare function exposeBackgroundWorker(api: WorkerRunner): Promise<void>;
|
|
15
|
+
/**
|
|
16
|
+
* Main-thread side: wrap a Worker into a WorkerRunner proxy via Comlink. The
|
|
17
|
+
* returned object's `run` calls execute inside the worker.
|
|
18
|
+
*/
|
|
19
|
+
export declare function wrapWorker(worker: Worker): Promise<WorkerRunner>;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// Optional off-main-thread execution via Comlink. The BackgroundTaskManager
|
|
2
|
+
// works fully WITHOUT any of this — use these helpers only when you want a task
|
|
3
|
+
// to run inside a Web Worker (or service worker) instead of on the main thread.
|
|
4
|
+
//
|
|
5
|
+
// Comlink is an optional peer dependency; it is imported lazily so this module
|
|
6
|
+
// can sit in the barrel without forcing Comlink to load for callers that never
|
|
7
|
+
// use a worker.
|
|
8
|
+
/**
|
|
9
|
+
* Call inside a Web Worker / Service Worker to expose a WorkerRunner to the main
|
|
10
|
+
* thread via Comlink. No-op (with a console warning) if not in a worker scope.
|
|
11
|
+
*/
|
|
12
|
+
export async function exposeBackgroundWorker(api) {
|
|
13
|
+
const inWorkerScope = typeof self !== 'undefined' &&
|
|
14
|
+
typeof globalThis.document === 'undefined';
|
|
15
|
+
if (!inWorkerScope) {
|
|
16
|
+
// eslint-disable-next-line no-console
|
|
17
|
+
console.warn('[background] exposeBackgroundWorker called outside a worker scope; ignoring.');
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
const Comlink = await import('comlink');
|
|
21
|
+
Comlink.expose(api);
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Main-thread side: wrap a Worker into a WorkerRunner proxy via Comlink. The
|
|
25
|
+
* returned object's `run` calls execute inside the worker.
|
|
26
|
+
*/
|
|
27
|
+
export async function wrapWorker(worker) {
|
|
28
|
+
const Comlink = await import('comlink');
|
|
29
|
+
return Comlink.wrap(worker);
|
|
30
|
+
}
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
// Built-in slash commands: /help, /clear, /compact, /tools, /cost, /model.
|
|
2
|
+
/** Render the conversation (minus the system message) as a plain transcript. */
|
|
3
|
+
function transcript(history) {
|
|
4
|
+
return history
|
|
5
|
+
.slice(1)
|
|
6
|
+
.map((m) => {
|
|
7
|
+
const body = typeof m.content === 'string'
|
|
8
|
+
? m.content
|
|
9
|
+
: m.content
|
|
10
|
+
.map((b) => b.type === 'text'
|
|
11
|
+
? b.text
|
|
12
|
+
: b.type === 'tool_use'
|
|
13
|
+
? `[tool_use ${b.name} ${JSON.stringify(b.input)}]`
|
|
14
|
+
: b.type === 'tool_result'
|
|
15
|
+
? `[tool_result ${typeof b.content === 'string' ? b.content : '...'}]`
|
|
16
|
+
: `[${b.type}]`)
|
|
17
|
+
.join('\n');
|
|
18
|
+
const calls = m.tool_calls?.length
|
|
19
|
+
? ' ' + m.tool_calls.map((c) => `[call ${c.function.name}(${c.function.arguments})]`).join(' ')
|
|
20
|
+
: '';
|
|
21
|
+
return `${m.role.toUpperCase()}: ${body}${calls}`;
|
|
22
|
+
})
|
|
23
|
+
.join('\n\n');
|
|
24
|
+
}
|
|
25
|
+
const help = {
|
|
26
|
+
name: 'help',
|
|
27
|
+
description: 'List available slash commands',
|
|
28
|
+
run(_args, ctx) {
|
|
29
|
+
const lines = ctx.commands
|
|
30
|
+
.slice()
|
|
31
|
+
.sort((a, b) => a.name.localeCompare(b.name))
|
|
32
|
+
.map((c) => ` /${c.name}${c.argumentHint ? ' ' + c.argumentHint : ''} — ${c.description}`);
|
|
33
|
+
return { systemText: `Available commands:\n${lines.join('\n')}` };
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
const clear = {
|
|
37
|
+
name: 'clear',
|
|
38
|
+
description: 'Clear the conversation history (keeps the system prompt)',
|
|
39
|
+
run(_args, ctx) {
|
|
40
|
+
return {
|
|
41
|
+
newHistory: ctx.history.length ? [ctx.history[0]] : [],
|
|
42
|
+
systemText: 'Conversation cleared.',
|
|
43
|
+
};
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
const compact = {
|
|
47
|
+
name: 'compact',
|
|
48
|
+
description: 'Summarize the conversation so far to free up context',
|
|
49
|
+
argumentHint: '[focus instructions]',
|
|
50
|
+
async run(args, ctx) {
|
|
51
|
+
if (!ctx.llm) {
|
|
52
|
+
return { systemText: 'Compaction requires an LLM client, which is not available here.' };
|
|
53
|
+
}
|
|
54
|
+
if (ctx.history.length <= 1) {
|
|
55
|
+
return { systemText: 'Nothing to compact yet.' };
|
|
56
|
+
}
|
|
57
|
+
const focus = args.trim();
|
|
58
|
+
const instruction = 'Summarize the following conversation transcript concisely but completely. ' +
|
|
59
|
+
'Preserve: the user’s goals, key decisions, files created/edited (with paths), ' +
|
|
60
|
+
'important findings, and any unfinished work or next steps. Use short sections.' +
|
|
61
|
+
(focus ? `\nPay special attention to: ${focus}` : '');
|
|
62
|
+
const messages = [
|
|
63
|
+
{ role: 'system', content: 'You are a precise conversation summarizer.' },
|
|
64
|
+
{ role: 'user', content: `${instruction}\n\n---\n${transcript(ctx.history)}` },
|
|
65
|
+
];
|
|
66
|
+
let summary = '';
|
|
67
|
+
try {
|
|
68
|
+
const res = await ctx.llm.streamChat(messages, {
|
|
69
|
+
model: ctx.model,
|
|
70
|
+
signal: ctx.signal,
|
|
71
|
+
onToken: () => { },
|
|
72
|
+
});
|
|
73
|
+
summary = res.text?.trim() ?? '';
|
|
74
|
+
}
|
|
75
|
+
catch (err) {
|
|
76
|
+
return {
|
|
77
|
+
systemText: `Compaction failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
if (!summary)
|
|
81
|
+
return { systemText: 'Compaction produced no summary; history left unchanged.' };
|
|
82
|
+
const newHistory = [
|
|
83
|
+
ctx.history[0],
|
|
84
|
+
{ role: 'user', content: `Summary of the conversation so far:\n${summary}` },
|
|
85
|
+
];
|
|
86
|
+
return {
|
|
87
|
+
newHistory,
|
|
88
|
+
compacted: true,
|
|
89
|
+
systemText: 'Conversation compacted into a summary.',
|
|
90
|
+
};
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
const tools = {
|
|
94
|
+
name: 'tools',
|
|
95
|
+
description: 'List the tools available to the agent',
|
|
96
|
+
run(_args, ctx) {
|
|
97
|
+
if (!ctx.tools.length)
|
|
98
|
+
return { systemText: 'No tools available.' };
|
|
99
|
+
const lines = ctx.tools.map((t) => ` ${t.name} — ${t.description.split('\n')[0]}`);
|
|
100
|
+
return { systemText: `Available tools (${ctx.tools.length}):\n${lines.join('\n')}` };
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
const cost = {
|
|
104
|
+
name: 'cost',
|
|
105
|
+
description: 'Show token usage for this session',
|
|
106
|
+
run(_args, ctx) {
|
|
107
|
+
if (!ctx.usage)
|
|
108
|
+
return { systemText: 'No usage recorded yet.' };
|
|
109
|
+
const u = ctx.usage;
|
|
110
|
+
return {
|
|
111
|
+
systemText: `Token usage — input: ${u.input_tokens}, output: ${u.output_tokens}${u.cache_read_input_tokens ? `, cache read: ${u.cache_read_input_tokens}` : ''}`,
|
|
112
|
+
};
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
const model = {
|
|
116
|
+
name: 'model',
|
|
117
|
+
description: 'Show the active model',
|
|
118
|
+
run(_args, ctx) {
|
|
119
|
+
return { systemText: `Model: ${ctx.model ?? 'unknown'}` };
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
const sessions = {
|
|
123
|
+
name: 'sessions',
|
|
124
|
+
description: 'List saved sessions',
|
|
125
|
+
async run(_args, ctx) {
|
|
126
|
+
if (!ctx.sessionStore)
|
|
127
|
+
return { systemText: 'No session store configured.' };
|
|
128
|
+
const list = await ctx.sessionStore.list();
|
|
129
|
+
if (!list.length)
|
|
130
|
+
return { systemText: 'No saved sessions.' };
|
|
131
|
+
const lines = list.map((s) => ` ${s.sessionId}${s.sessionId === ctx.sessionId ? ' (current)' : ''} — ${s.title ?? '(untitled)'} · ${s.messageCount} msgs`);
|
|
132
|
+
return { systemText: `Saved sessions:\n${lines.join('\n')}\n\nResume with: /resume <id>` };
|
|
133
|
+
},
|
|
134
|
+
};
|
|
135
|
+
const resume = {
|
|
136
|
+
name: 'resume',
|
|
137
|
+
description: 'Resume a saved session by id',
|
|
138
|
+
argumentHint: '<session-id>',
|
|
139
|
+
async run(args, ctx) {
|
|
140
|
+
if (!ctx.sessionStore)
|
|
141
|
+
return { systemText: 'No session store configured.' };
|
|
142
|
+
const id = args.trim();
|
|
143
|
+
if (!id)
|
|
144
|
+
return { systemText: 'Usage: /resume <session-id> (see /sessions)' };
|
|
145
|
+
const prior = await ctx.sessionStore.load(id);
|
|
146
|
+
if (!prior || !prior.length)
|
|
147
|
+
return { systemText: `No transcript found for session "${id}".` };
|
|
148
|
+
return { newHistory: prior, systemText: `Resumed session ${id} (${prior.length - 1} prior messages).` };
|
|
149
|
+
},
|
|
150
|
+
};
|
|
151
|
+
const rename = {
|
|
152
|
+
name: 'rename',
|
|
153
|
+
description: 'Rename the current session',
|
|
154
|
+
argumentHint: '<title>',
|
|
155
|
+
async run(args, ctx) {
|
|
156
|
+
if (!ctx.sessionStore || !ctx.sessionId)
|
|
157
|
+
return { systemText: 'No active session to rename.' };
|
|
158
|
+
const title = args.trim();
|
|
159
|
+
if (!title)
|
|
160
|
+
return { systemText: 'Usage: /rename <title>' };
|
|
161
|
+
await ctx.sessionStore.rename(ctx.sessionId, title);
|
|
162
|
+
return { systemText: `Session renamed to "${title}".` };
|
|
163
|
+
},
|
|
164
|
+
};
|
|
165
|
+
const diff = {
|
|
166
|
+
name: 'diff',
|
|
167
|
+
description: 'Show uncommitted git changes',
|
|
168
|
+
async run(_args, ctx) {
|
|
169
|
+
if (!ctx.exec)
|
|
170
|
+
return { systemText: 'No shell available for /diff.' };
|
|
171
|
+
const { output, exitCode } = await ctx.exec('git diff --stat && echo "---" && git diff');
|
|
172
|
+
if (exitCode !== 0 && !output.trim())
|
|
173
|
+
return { systemText: 'No changes, or not a git repository.' };
|
|
174
|
+
return { systemText: '```diff\n' + (output.trim() || '(no changes)').slice(0, 8000) + '\n```' };
|
|
175
|
+
},
|
|
176
|
+
};
|
|
177
|
+
const tasks = {
|
|
178
|
+
name: 'tasks',
|
|
179
|
+
description: 'List background tasks',
|
|
180
|
+
run(_args, ctx) {
|
|
181
|
+
if (!ctx.background)
|
|
182
|
+
return { systemText: 'Background tasks are not enabled.' };
|
|
183
|
+
const list = ctx.background.list();
|
|
184
|
+
if (!list.length)
|
|
185
|
+
return { systemText: 'No background tasks.' };
|
|
186
|
+
return { systemText: 'Background tasks:\n' + list.map((t) => ` ${t.id} [${t.status}] ${t.description}`).join('\n') };
|
|
187
|
+
},
|
|
188
|
+
};
|
|
189
|
+
const board = {
|
|
190
|
+
name: 'board',
|
|
191
|
+
description: 'List the team task board',
|
|
192
|
+
run(_args, ctx) {
|
|
193
|
+
if (!ctx.board)
|
|
194
|
+
return { systemText: 'Teammates are not enabled.' };
|
|
195
|
+
const list = ctx.board.list();
|
|
196
|
+
if (!list.length)
|
|
197
|
+
return { systemText: 'Task board is empty.' };
|
|
198
|
+
return { systemText: 'Task board:\n' + list.map((t) => ` ${t.id} [${t.status}]${t.owner ? ' @' + t.owner : ''} — ${t.subject}`).join('\n') };
|
|
199
|
+
},
|
|
200
|
+
};
|
|
201
|
+
const context = {
|
|
202
|
+
name: 'context',
|
|
203
|
+
description: 'Show approximate context usage',
|
|
204
|
+
run(_args, ctx) {
|
|
205
|
+
let chars = 0;
|
|
206
|
+
for (const m of ctx.history) {
|
|
207
|
+
chars += typeof m.content === 'string' ? m.content.length : JSON.stringify(m.content).length;
|
|
208
|
+
}
|
|
209
|
+
const approxTokens = Math.round(chars / 4);
|
|
210
|
+
return {
|
|
211
|
+
systemText: `Context: ${ctx.history.length} messages, ~${approxTokens.toLocaleString()} tokens (${chars.toLocaleString()} chars).`,
|
|
212
|
+
};
|
|
213
|
+
},
|
|
214
|
+
};
|
|
215
|
+
const files = {
|
|
216
|
+
name: 'files',
|
|
217
|
+
description: 'List files read this session',
|
|
218
|
+
run(_args, ctx) {
|
|
219
|
+
const fs = ctx.readFiles ? [...ctx.readFiles] : [];
|
|
220
|
+
if (!fs.length)
|
|
221
|
+
return { systemText: 'No files read yet this session.' };
|
|
222
|
+
return { systemText: `Files read (${fs.length}):\n${fs.map((f) => ' ' + f).join('\n')}` };
|
|
223
|
+
},
|
|
224
|
+
};
|
|
225
|
+
const agents = {
|
|
226
|
+
name: 'agents',
|
|
227
|
+
description: 'List configured sub-agents',
|
|
228
|
+
run(_args, ctx) {
|
|
229
|
+
const a = ctx.agents ? Object.entries(ctx.agents) : [];
|
|
230
|
+
if (!a.length)
|
|
231
|
+
return { systemText: 'No custom agents configured.' };
|
|
232
|
+
return { systemText: 'Agents:\n' + a.map(([n, d]) => ` ${n}${d.model ? ' (' + d.model + ')' : ''} — ${d.description}`).join('\n') };
|
|
233
|
+
},
|
|
234
|
+
};
|
|
235
|
+
const mcp = {
|
|
236
|
+
name: 'mcp',
|
|
237
|
+
description: 'Show MCP server status',
|
|
238
|
+
run(_args, ctx) {
|
|
239
|
+
const s = ctx.mcpServers ?? [];
|
|
240
|
+
if (!s.length)
|
|
241
|
+
return { systemText: 'No MCP servers configured.' };
|
|
242
|
+
return { systemText: 'MCP servers:\n' + s.map((x) => ` ${x.name} — ${x.status}`).join('\n') };
|
|
243
|
+
},
|
|
244
|
+
};
|
|
245
|
+
const permissions = {
|
|
246
|
+
name: 'permissions',
|
|
247
|
+
description: 'Show the active permission mode',
|
|
248
|
+
run(_args, ctx) {
|
|
249
|
+
return { systemText: `Permission mode: ${ctx.permissionMode ?? 'default'}` };
|
|
250
|
+
},
|
|
251
|
+
};
|
|
252
|
+
const memory = {
|
|
253
|
+
name: 'memory',
|
|
254
|
+
description: 'Show persistent memory (store + CLAUDE.md / MEMORY.md)',
|
|
255
|
+
async run(_args, ctx) {
|
|
256
|
+
const parts = [];
|
|
257
|
+
if (ctx.memory) {
|
|
258
|
+
const rendered = await ctx.memory.render();
|
|
259
|
+
if (rendered)
|
|
260
|
+
parts.push(rendered);
|
|
261
|
+
}
|
|
262
|
+
if (ctx.fs) {
|
|
263
|
+
for (const name of ['CLAUDE.md', 'MEMORY.md', '.claude/CLAUDE.md']) {
|
|
264
|
+
const c = await ctx.fs.readFile(name);
|
|
265
|
+
if (c) {
|
|
266
|
+
parts.push(`# ${name}\n\n${c.slice(0, 4000)}`);
|
|
267
|
+
break;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
return { systemText: parts.length ? parts.join('\n\n---\n\n') : 'No memory or CLAUDE.md/MEMORY.md found.' };
|
|
272
|
+
},
|
|
273
|
+
};
|
|
274
|
+
const exportCmd = {
|
|
275
|
+
name: 'export',
|
|
276
|
+
description: 'Export the conversation transcript (markdown)',
|
|
277
|
+
run(_args, ctx) {
|
|
278
|
+
return { systemText: '```markdown\n' + transcript(ctx.history) + '\n```' };
|
|
279
|
+
},
|
|
280
|
+
};
|
|
281
|
+
const review = {
|
|
282
|
+
name: 'review',
|
|
283
|
+
description: 'Ask the agent to review recent changes',
|
|
284
|
+
argumentHint: '[path or focus]',
|
|
285
|
+
run(args) {
|
|
286
|
+
return {
|
|
287
|
+
expandedPrompt: `Review the code changes${args.trim() ? ' in/related to ' + args.trim() : ''} for bugs, security issues, and quality. ` +
|
|
288
|
+
'Use git diff and read the relevant files, then give a prioritized list of findings.',
|
|
289
|
+
};
|
|
290
|
+
},
|
|
291
|
+
};
|
|
292
|
+
const init = {
|
|
293
|
+
name: 'init',
|
|
294
|
+
description: 'Analyze the project and write a CLAUDE.md',
|
|
295
|
+
run() {
|
|
296
|
+
return {
|
|
297
|
+
expandedPrompt: 'Analyze this project (structure, key files, build/test commands, conventions) and write a concise CLAUDE.md ' +
|
|
298
|
+
'at the project root capturing what a coding agent needs to know. Use list_files/read_file/grep to investigate first.',
|
|
299
|
+
};
|
|
300
|
+
},
|
|
301
|
+
};
|
|
302
|
+
export const BUILTIN_COMMANDS = [
|
|
303
|
+
help, clear, compact, tools, cost, model,
|
|
304
|
+
sessions, resume, rename, diff, tasks, board, context, files, agents, mcp,
|
|
305
|
+
permissions, memory, exportCmd, review, init,
|
|
306
|
+
];
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { SlashCommand, SlashCommandContext, SlashOutcome } from './types.js';
|
|
2
|
+
export * from './types.js';
|
|
3
|
+
export { BUILTIN_COMMANDS } from './builtins.js';
|
|
4
|
+
/** Parse `/name args...` → { name (lowercased, no slash), args }. Null if not a command. */
|
|
5
|
+
export declare function parseSlashCommand(text: string): {
|
|
6
|
+
name: string;
|
|
7
|
+
args: string;
|
|
8
|
+
} | null;
|
|
9
|
+
/**
|
|
10
|
+
* Resolve and execute a slash command. Returns the command's SlashOutcome, or
|
|
11
|
+
* null when the text isn't a slash command or names an unknown command (so the
|
|
12
|
+
* caller can fall back to treating it as a normal prompt).
|
|
13
|
+
*/
|
|
14
|
+
export declare function runSlashCommand(text: string, ctx: Omit<SlashCommandContext, 'commands'> & {
|
|
15
|
+
commands?: SlashCommand[];
|
|
16
|
+
}): Promise<SlashOutcome | null>;
|
|
17
|
+
/**
|
|
18
|
+
* Define a custom prompt-template command (like a Claude Code markdown command).
|
|
19
|
+
* `$ARGUMENTS` in the template is replaced with the invocation args.
|
|
20
|
+
*/
|
|
21
|
+
export declare function promptCommand(name: string, description: string, prompt: string, argumentHint?: string): SlashCommand;
|