@opencoven/coven-code 0.0.3 → 0.0.6
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 +29 -130
- package/bin/coven-code +26 -0
- package/install.js +117 -0
- package/package.json +26 -23
- package/bin/coven-code-sdk.mjs +0 -12
- package/bin/coven-code.mjs +0 -19
- package/docs/CLI.md +0 -256
- package/docs/CONFIGURATION.md +0 -107
- package/docs/DEMO.md +0 -453
- package/docs/DEVELOPMENT.md +0 -104
- package/docs/DOGFOOD-PROTOCOL.md +0 -263
- package/docs/MCP-SKILLS-PLUGINS.md +0 -127
- package/docs/README.md +0 -39
- package/docs/RELEASE.md +0 -33
- package/docs/SDK.md +0 -107
- package/docs/superpowers/plans/2026-05-25-coven-code-panel-tui.md +0 -904
- package/docs/superpowers/plans/2026-05-25-coven-code-rebrand.md +0 -670
- package/docs/superpowers/specs/2026-05-25-coven-code-panel-tui-design.md +0 -235
- package/docs/superpowers/specs/2026-05-26-slash-first-tui-review.md +0 -63
- package/src/agent/fixture.mjs +0 -95
- package/src/agent/lane.mjs +0 -136
- package/src/cli/dispatch.mjs +0 -66
- package/src/cli/execute.mjs +0 -590
- package/src/cli/help.mjs +0 -58
- package/src/cli/interactive-core.mjs +0 -28
- package/src/cli/interactive-io.mjs +0 -101
- package/src/cli/interactive-slash.mjs +0 -184
- package/src/cli/notifications.mjs +0 -13
- package/src/cli/parse.mjs +0 -83
- package/src/cli/reasoning.mjs +0 -45
- package/src/cli/refs.mjs +0 -162
- package/src/cli/repl.mjs +0 -60
- package/src/cli/slash-commands.mjs +0 -375
- package/src/cli/stream-json.mjs +0 -116
- package/src/cli/tui-actions.mjs +0 -72
- package/src/cli/tui-blessed.mjs +0 -198
- package/src/cli/tui-keys.mjs +0 -80
- package/src/cli/tui-lane.mjs +0 -73
- package/src/cli/tui-render.mjs +0 -169
- package/src/cli/tui-submit.mjs +0 -82
- package/src/cli/tui.mjs +0 -174
- package/src/commands/agents.mjs +0 -53
- package/src/commands/config.mjs +0 -27
- package/src/commands/ide.mjs +0 -17
- package/src/commands/login.mjs +0 -84
- package/src/commands/mcp.mjs +0 -176
- package/src/commands/permissions-eval.mjs +0 -122
- package/src/commands/permissions-rules.mjs +0 -53
- package/src/commands/permissions-text.mjs +0 -112
- package/src/commands/permissions.mjs +0 -62
- package/src/commands/plugins.mjs +0 -86
- package/src/commands/review.mjs +0 -74
- package/src/commands/skill.mjs +0 -23
- package/src/commands/threads.mjs +0 -165
- package/src/commands/tools.mjs +0 -77
- package/src/commands/update.mjs +0 -31
- package/src/commands/usage.mjs +0 -34
- package/src/constants.mjs +0 -52
- package/src/main.mjs +0 -87
- package/src/mcp/discover.mjs +0 -154
- package/src/mcp/local.mjs +0 -55
- package/src/mcp/parsers.mjs +0 -46
- package/src/mcp/permissions.mjs +0 -52
- package/src/mcp/probe.mjs +0 -85
- package/src/mcp/registry.mjs +0 -96
- package/src/mcp/remote-oauth.mjs +0 -55
- package/src/mcp/remote-session.mjs +0 -54
- package/src/mcp/remote-sse.mjs +0 -82
- package/src/mcp/remote.mjs +0 -74
- package/src/plugins/api.mjs +0 -187
- package/src/plugins/configuration.mjs +0 -124
- package/src/plugins/discover.mjs +0 -84
- package/src/plugins/helpers.mjs +0 -187
- package/src/plugins/subsystems.mjs +0 -198
- package/src/plugins/validators.mjs +0 -142
- package/src/sdk-execute.mjs +0 -82
- package/src/sdk-install.mjs +0 -187
- package/src/sdk-settings.mjs +0 -88
- package/src/sdk.mjs +0 -163
- package/src/settings/load.mjs +0 -134
- package/src/settings/paths.mjs +0 -101
- package/src/skills/builtin/building-skills/SKILL.md +0 -20
- package/src/skills/discover.mjs +0 -95
- package/src/threads/store.mjs +0 -176
- package/src/tools/builtin/bash.mjs +0 -110
- package/src/tools/builtin/create-file.mjs +0 -66
- package/src/tools/builtin/edit-file.mjs +0 -76
- package/src/tools/builtin/finder.mjs +0 -73
- package/src/tools/builtin/glob.mjs +0 -74
- package/src/tools/builtin/grep.mjs +0 -82
- package/src/tools/builtin/index.mjs +0 -83
- package/src/tools/builtin/librarian.mjs +0 -97
- package/src/tools/builtin/look-at.mjs +0 -92
- package/src/tools/builtin/mcp.mjs +0 -51
- package/src/tools/builtin/mermaid.mjs +0 -59
- package/src/tools/builtin/oracle.mjs +0 -56
- package/src/tools/builtin/painter.mjs +0 -81
- package/src/tools/builtin/plugin-tool.mjs +0 -53
- package/src/tools/builtin/read-mcp-resource.mjs +0 -63
- package/src/tools/builtin/read-web-page.mjs +0 -72
- package/src/tools/builtin/read.mjs +0 -59
- package/src/tools/builtin/runtime-content.mjs +0 -31
- package/src/tools/builtin/runtime-decisions.mjs +0 -115
- package/src/tools/builtin/runtime.mjs +0 -85
- package/src/tools/builtin/task.mjs +0 -63
- package/src/tools/builtin/toolbox-tool.mjs +0 -57
- package/src/tools/builtin/undo-edit.mjs +0 -97
- package/src/tools/builtin/web-search.mjs +0 -128
- package/src/tools/toolbox.mjs +0 -273
- package/src/util/fs.mjs +0 -13
- package/src/util/glob.mjs +0 -46
- package/src/util/html.mjs +0 -21
- package/src/util/media.mjs +0 -13
- package/src/util/shell.mjs +0 -24
- package/src/util/table.mjs +0 -11
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import { resolvePermissionDecision } from '../../commands/permissions.mjs';
|
|
2
|
-
import { listActiveMcpServerEntries } from '../../mcp/discover.mjs';
|
|
3
|
-
import { readMcpResource } from '../../mcp/probe.mjs';
|
|
4
|
-
import { runPluginEventHandlers } from '../../plugins/discover.mjs';
|
|
5
|
-
import { isToolDisabled } from '../toolbox.mjs';
|
|
6
|
-
import {
|
|
7
|
-
applyToolCallDecision,
|
|
8
|
-
createToolUseID,
|
|
9
|
-
permissionDeniedOutput,
|
|
10
|
-
pluginTextToolRunResult,
|
|
11
|
-
pluginToolCallEvent,
|
|
12
|
-
pluginToolResultEvent,
|
|
13
|
-
pluginToolUseBlock,
|
|
14
|
-
toolCallDecisionToolRun,
|
|
15
|
-
validateToolCallDecision,
|
|
16
|
-
} from './runtime.mjs';
|
|
17
|
-
|
|
18
|
-
export const TOOL_NAME = 'read_mcp_resource';
|
|
19
|
-
|
|
20
|
-
export async function executePromptReadMcpResourceToolRequest(request, parsed = {}, plugins = { handlers: {} }, threadId = '') {
|
|
21
|
-
if (isToolDisabled(TOOL_NAME, 'built-in', parsed)) return { output: `Tool disabled: ${TOOL_NAME}` };
|
|
22
|
-
request = { ...request, flags: normalizeReadMcpResourceInput(request.flags) };
|
|
23
|
-
if (!request.flags.server) return { output: 'read_mcp_resource requires --server' };
|
|
24
|
-
if (!request.flags.uri) return { output: 'read_mcp_resource requires --uri' };
|
|
25
|
-
const toolUseID = createToolUseID();
|
|
26
|
-
const callDecision = await runPluginEventHandlers(
|
|
27
|
-
plugins.handlers['tool.call'],
|
|
28
|
-
pluginToolCallEvent(TOOL_NAME, request.flags, threadId, toolUseID),
|
|
29
|
-
validateToolCallDecision,
|
|
30
|
-
);
|
|
31
|
-
const callResult = applyToolCallDecision(TOOL_NAME, request, callDecision);
|
|
32
|
-
if (callResult.output) return toolCallDecisionToolRun(TOOL_NAME, request.flags, toolUseID, callResult);
|
|
33
|
-
request = { ...callResult.request, flags: normalizeReadMcpResourceInput(callResult.request.flags) };
|
|
34
|
-
const decision = resolvePermissionDecision(TOOL_NAME, request.flags, parsed, { threadId });
|
|
35
|
-
if (!parsed.dangerouslyAllowAll && decision.action !== 'allow') {
|
|
36
|
-
return {
|
|
37
|
-
output: permissionDeniedOutput(TOOL_NAME, decision),
|
|
38
|
-
permissionDenials: [{ tool: TOOL_NAME, action: decision.action, reason: 'permission' }],
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
const server = listActiveMcpServerEntries(parsed, '')
|
|
42
|
-
.find((entry) => entry.name === request.flags.server);
|
|
43
|
-
if (!server) return { output: `Unknown MCP server: ${request.flags.server}` };
|
|
44
|
-
const output = await readMcpResource(server.config, request.flags.uri, server.name);
|
|
45
|
-
const resultDecision = await runPluginEventHandlers(
|
|
46
|
-
plugins.handlers['tool.result'],
|
|
47
|
-
pluginToolResultEvent(TOOL_NAME, request.flags, 'done', output, threadId, toolUseID),
|
|
48
|
-
);
|
|
49
|
-
return {
|
|
50
|
-
...pluginTextToolRunResult(resultDecision, output),
|
|
51
|
-
toolUse: pluginToolUseBlock(TOOL_NAME, request.flags, toolUseID),
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
function normalizeReadMcpResourceInput(input = {}) {
|
|
56
|
-
const server = input.server ?? input.name ?? input.mcp_server;
|
|
57
|
-
const uri = input.uri ?? input.url ?? input.resource;
|
|
58
|
-
return {
|
|
59
|
-
...input,
|
|
60
|
-
server: server ? String(server) : '',
|
|
61
|
-
uri: uri ? String(uri) : '',
|
|
62
|
-
};
|
|
63
|
-
}
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
import { resolvePermissionDecision } from '../../commands/permissions.mjs';
|
|
2
|
-
import { runPluginEventHandlers } from '../../plugins/discover.mjs';
|
|
3
|
-
import { htmlToText } from '../../util/html.mjs';
|
|
4
|
-
import { isToolDisabled } from '../toolbox.mjs';
|
|
5
|
-
import {
|
|
6
|
-
applyToolCallDecision,
|
|
7
|
-
createToolUseID,
|
|
8
|
-
permissionDeniedOutput,
|
|
9
|
-
pluginTextToolRunResult,
|
|
10
|
-
pluginToolCallEvent,
|
|
11
|
-
pluginToolResultEvent,
|
|
12
|
-
pluginToolUseBlock,
|
|
13
|
-
toolCallDecisionToolRun,
|
|
14
|
-
validateToolCallDecision,
|
|
15
|
-
} from './runtime.mjs';
|
|
16
|
-
|
|
17
|
-
export const TOOL_NAME = 'read_web_page';
|
|
18
|
-
|
|
19
|
-
export async function executePromptReadWebPageToolRequest(request, parsed = {}, plugins = { handlers: {} }, threadId = '') {
|
|
20
|
-
if (isToolDisabled(TOOL_NAME, 'built-in', parsed)) return { output: `Tool disabled: ${TOOL_NAME}` };
|
|
21
|
-
request = { ...request, flags: normalizeReadWebPageInput(request.flags) };
|
|
22
|
-
if (!request.flags.url) return { output: 'read_web_page requires --url' };
|
|
23
|
-
const toolUseID = createToolUseID();
|
|
24
|
-
const callDecision = await runPluginEventHandlers(
|
|
25
|
-
plugins.handlers['tool.call'],
|
|
26
|
-
pluginToolCallEvent(TOOL_NAME, request.flags, threadId, toolUseID),
|
|
27
|
-
validateToolCallDecision,
|
|
28
|
-
);
|
|
29
|
-
const callResult = applyToolCallDecision(TOOL_NAME, request, callDecision);
|
|
30
|
-
if (callResult.output) return toolCallDecisionToolRun(TOOL_NAME, request.flags, toolUseID, callResult);
|
|
31
|
-
request = { ...callResult.request, flags: normalizeReadWebPageInput(callResult.request.flags) };
|
|
32
|
-
const decision = resolvePermissionDecision(TOOL_NAME, request.flags, parsed, { threadId });
|
|
33
|
-
if (!parsed.dangerouslyAllowAll && decision.action !== 'allow') {
|
|
34
|
-
return {
|
|
35
|
-
output: permissionDeniedOutput(TOOL_NAME, decision),
|
|
36
|
-
permissionDenials: [{ tool: TOOL_NAME, action: decision.action, reason: 'permission' }],
|
|
37
|
-
};
|
|
38
|
-
}
|
|
39
|
-
const output = await readWebPageBuiltin(request.flags.url);
|
|
40
|
-
const resultDecision = await runPluginEventHandlers(
|
|
41
|
-
plugins.handlers['tool.result'],
|
|
42
|
-
pluginToolResultEvent(TOOL_NAME, request.flags, 'done', output, threadId, toolUseID),
|
|
43
|
-
);
|
|
44
|
-
return {
|
|
45
|
-
...pluginTextToolRunResult(resultDecision, output),
|
|
46
|
-
toolUse: pluginToolUseBlock(TOOL_NAME, request.flags, toolUseID),
|
|
47
|
-
};
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
function normalizeReadWebPageInput(input = {}) {
|
|
51
|
-
const url = input.url ?? input.uri ?? input.href;
|
|
52
|
-
return {
|
|
53
|
-
...input,
|
|
54
|
-
url: url ? String(url) : '',
|
|
55
|
-
};
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
async function readWebPageBuiltin(url) {
|
|
59
|
-
try {
|
|
60
|
-
const response = await fetch(url, {
|
|
61
|
-
headers: { 'user-agent': 'coven-code/0.0.0' },
|
|
62
|
-
});
|
|
63
|
-
if (!response.ok) return `Failed to read ${url}: HTTP ${response.status}`;
|
|
64
|
-
const contentType = response.headers.get('content-type') ?? '';
|
|
65
|
-
const body = await response.text();
|
|
66
|
-
return contentType.includes('html') || body.includes('<')
|
|
67
|
-
? htmlToText(body)
|
|
68
|
-
: body.trimEnd();
|
|
69
|
-
} catch (error) {
|
|
70
|
-
return `Failed to read ${url}: ${error.message}`;
|
|
71
|
-
}
|
|
72
|
-
}
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
import { readFileSync } from 'node:fs';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import { resolvePermissionDecision } from '../../commands/permissions.mjs';
|
|
4
|
-
import { runPluginEventHandlers } from '../../plugins/discover.mjs';
|
|
5
|
-
import { isToolDisabled } from '../toolbox.mjs';
|
|
6
|
-
import {
|
|
7
|
-
applyToolCallDecision,
|
|
8
|
-
createToolUseID,
|
|
9
|
-
permissionDeniedOutput,
|
|
10
|
-
pluginTextToolRunResult,
|
|
11
|
-
pluginToolCallEvent,
|
|
12
|
-
pluginToolResultEvent,
|
|
13
|
-
pluginToolUseBlock,
|
|
14
|
-
toolCallDecisionToolRun,
|
|
15
|
-
validateToolCallDecision,
|
|
16
|
-
} from './runtime.mjs';
|
|
17
|
-
|
|
18
|
-
export const TOOL_NAME = 'Read';
|
|
19
|
-
|
|
20
|
-
export async function executePromptReadToolRequest(request, parsed = {}, plugins = { handlers: {} }, threadId = '') {
|
|
21
|
-
if (isToolDisabled(TOOL_NAME, 'built-in', parsed)) return { output: `Tool disabled: ${TOOL_NAME}` };
|
|
22
|
-
request = { ...request, flags: normalizeReadInput(request.flags) };
|
|
23
|
-
if (!request.flags.path) return { output: 'Read requires --path' };
|
|
24
|
-
const toolUseID = createToolUseID();
|
|
25
|
-
const callDecision = await runPluginEventHandlers(
|
|
26
|
-
plugins.handlers['tool.call'],
|
|
27
|
-
pluginToolCallEvent(TOOL_NAME, request.flags, threadId, toolUseID),
|
|
28
|
-
validateToolCallDecision,
|
|
29
|
-
);
|
|
30
|
-
const callResult = applyToolCallDecision(TOOL_NAME, request, callDecision);
|
|
31
|
-
if (callResult.output) return toolCallDecisionToolRun(TOOL_NAME, request.flags, toolUseID, callResult);
|
|
32
|
-
request = { ...callResult.request, flags: normalizeReadInput(callResult.request.flags) };
|
|
33
|
-
const decision = resolvePermissionDecision(TOOL_NAME, request.flags, parsed, { threadId });
|
|
34
|
-
if (!parsed.dangerouslyAllowAll && decision.action !== 'allow') {
|
|
35
|
-
return {
|
|
36
|
-
output: permissionDeniedOutput(TOOL_NAME, decision),
|
|
37
|
-
permissionDenials: [{ tool: TOOL_NAME, action: decision.action, reason: 'permission' }],
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
const output = readBuiltinFile(request.flags.path).trimEnd();
|
|
41
|
-
const resultDecision = await runPluginEventHandlers(
|
|
42
|
-
plugins.handlers['tool.result'],
|
|
43
|
-
pluginToolResultEvent(TOOL_NAME, request.flags, 'done', output, threadId, toolUseID),
|
|
44
|
-
);
|
|
45
|
-
return {
|
|
46
|
-
...pluginTextToolRunResult(resultDecision, output),
|
|
47
|
-
toolUse: pluginToolUseBlock(TOOL_NAME, request.flags, toolUseID),
|
|
48
|
-
};
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
function normalizeReadInput(input = {}) {
|
|
52
|
-
const filePath = input.path ?? input.file ?? input.file_path;
|
|
53
|
-
return { ...input, path: filePath ? String(filePath) : '' };
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function readBuiltinFile(filePath) {
|
|
57
|
-
const absolutePath = path.isAbsolute(filePath) ? filePath : path.resolve(process.cwd(), filePath);
|
|
58
|
-
return readFileSync(absolutePath, 'utf8');
|
|
59
|
-
}
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
export function isPluginContentBlock(block) {
|
|
2
|
-
if (!block || typeof block !== 'object') return false;
|
|
3
|
-
if (block.type === 'text') return typeof block.text === 'string';
|
|
4
|
-
if (block.type === 'image') return typeof block.mimeType === 'string' && typeof block.data === 'string';
|
|
5
|
-
return false;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export function pluginContentBlockText(block) {
|
|
9
|
-
if (block.type === 'text') return block.text;
|
|
10
|
-
return '';
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export function normalizePluginToolOutput(output) {
|
|
14
|
-
if (Array.isArray(output) && output.every(isPluginContentBlock)) {
|
|
15
|
-
const text = output.map(pluginContentBlockText).filter(Boolean).join('\n').trimEnd();
|
|
16
|
-
return { raw: output, text };
|
|
17
|
-
}
|
|
18
|
-
const text = String(output ?? '').trimEnd();
|
|
19
|
-
return { raw: text, text };
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export function normalizePluginToolExecuteOutput(output) {
|
|
23
|
-
if (output === undefined || typeof output === 'string') return normalizePluginToolOutput(output);
|
|
24
|
-
if (Array.isArray(output)) {
|
|
25
|
-
if (!output.every(isPluginContentBlock)) {
|
|
26
|
-
throw new Error('plugin tool result content blocks must be text or image blocks');
|
|
27
|
-
}
|
|
28
|
-
return normalizePluginToolOutput(output);
|
|
29
|
-
}
|
|
30
|
-
throw new Error('plugin tool result must be a string, content blocks, or undefined');
|
|
31
|
-
}
|
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
export function validateToolCallDecision(callDecision) {
|
|
2
|
-
if (!callDecision || typeof callDecision !== 'object') {
|
|
3
|
-
throw new Error('plugin tool.call result must be an object');
|
|
4
|
-
}
|
|
5
|
-
if (
|
|
6
|
-
callDecision.action !== 'allow' &&
|
|
7
|
-
callDecision.action !== 'reject-and-continue' &&
|
|
8
|
-
callDecision.action !== 'modify' &&
|
|
9
|
-
callDecision.action !== 'synthesize' &&
|
|
10
|
-
callDecision.action !== 'error'
|
|
11
|
-
) {
|
|
12
|
-
throw new Error('plugin tool.call action must be allow, reject-and-continue, modify, synthesize, or error');
|
|
13
|
-
}
|
|
14
|
-
const allowedKeys = {
|
|
15
|
-
allow: ['action'],
|
|
16
|
-
'reject-and-continue': ['action', 'message'],
|
|
17
|
-
modify: ['action', 'input'],
|
|
18
|
-
synthesize: ['action', 'result'],
|
|
19
|
-
error: ['action', 'message'],
|
|
20
|
-
}[callDecision.action];
|
|
21
|
-
if (Object.keys(callDecision).some((key) => !allowedKeys.includes(key))) {
|
|
22
|
-
throw new Error('plugin tool.call fields must match the documented union');
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export function applyToolCallDecision(toolName, request, callDecision = { action: 'allow' }) {
|
|
27
|
-
validateToolCallDecision(callDecision);
|
|
28
|
-
if (callDecision.action === 'allow') return { request };
|
|
29
|
-
if (callDecision.action === 'reject-and-continue') {
|
|
30
|
-
if (typeof callDecision.message !== 'string') {
|
|
31
|
-
throw new Error('plugin tool.call reject-and-continue message must be a string');
|
|
32
|
-
}
|
|
33
|
-
return {
|
|
34
|
-
output: {
|
|
35
|
-
output: callDecision.message,
|
|
36
|
-
exitCode: 0,
|
|
37
|
-
},
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
if (callDecision.action === 'synthesize') {
|
|
41
|
-
const result = callDecision.result && isPlainObject(callDecision.result)
|
|
42
|
-
? callDecision.result
|
|
43
|
-
: callDecision;
|
|
44
|
-
if (typeof result.output !== 'string') {
|
|
45
|
-
throw new Error('plugin tool.call synthesize result.output must be a string');
|
|
46
|
-
}
|
|
47
|
-
if (result.exitCode !== undefined && !Number.isInteger(result.exitCode)) {
|
|
48
|
-
throw new Error('plugin tool.call synthesize result.exitCode must be an integer');
|
|
49
|
-
}
|
|
50
|
-
return {
|
|
51
|
-
output: {
|
|
52
|
-
output: result.output.trimEnd(),
|
|
53
|
-
exitCode: result.exitCode ?? 0,
|
|
54
|
-
},
|
|
55
|
-
};
|
|
56
|
-
}
|
|
57
|
-
if (callDecision.action === 'error') {
|
|
58
|
-
if (typeof callDecision.message !== 'string') {
|
|
59
|
-
throw new Error('plugin tool.call error message must be a string');
|
|
60
|
-
}
|
|
61
|
-
return {
|
|
62
|
-
output: {
|
|
63
|
-
output: callDecision.message,
|
|
64
|
-
exitCode: 1,
|
|
65
|
-
},
|
|
66
|
-
};
|
|
67
|
-
}
|
|
68
|
-
if (callDecision.action === 'modify') {
|
|
69
|
-
if (!isPlainObject(callDecision.input)) {
|
|
70
|
-
throw new Error('plugin tool.call modify input must be an object');
|
|
71
|
-
}
|
|
72
|
-
return { request: { ...request, flags: callDecision.input } };
|
|
73
|
-
}
|
|
74
|
-
return { request };
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
export function validateToolResultDecision(resultDecision = {}) {
|
|
78
|
-
if (!resultDecision || typeof resultDecision !== 'object' || !Object.hasOwn(resultDecision, 'status')) return;
|
|
79
|
-
if (resultDecision.status !== 'done' && resultDecision.status !== 'error' && resultDecision.status !== 'cancelled') {
|
|
80
|
-
throw new Error('plugin tool.result status must be done, error, or cancelled');
|
|
81
|
-
}
|
|
82
|
-
const allowedKeys = resultDecision.status === 'done' ? ['output', 'status'] : ['error', 'output', 'status'];
|
|
83
|
-
if (Object.keys(resultDecision).some((key) => !allowedKeys.includes(key))) {
|
|
84
|
-
throw new Error('plugin tool.result fields must match the documented union');
|
|
85
|
-
}
|
|
86
|
-
if (
|
|
87
|
-
(resultDecision.status === 'error' || resultDecision.status === 'cancelled') &&
|
|
88
|
-
resultDecision.error !== undefined &&
|
|
89
|
-
typeof resultDecision.error !== 'string'
|
|
90
|
-
) {
|
|
91
|
-
throw new Error('plugin tool.result error must be a string');
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
export function pluginToolResultDecisionOutput(resultDecision = {}, fallback) {
|
|
96
|
-
validateToolResultDecision(resultDecision);
|
|
97
|
-
if (resultDecision.status === 'error') {
|
|
98
|
-
return resultDecision.error ?? resultDecision.output ?? fallback;
|
|
99
|
-
}
|
|
100
|
-
if (resultDecision.status === 'cancelled') {
|
|
101
|
-
return resultDecision.error ?? resultDecision.output ?? 'Tool cancelled';
|
|
102
|
-
}
|
|
103
|
-
return resultDecision.output !== undefined ? resultDecision.output : fallback;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
export function pluginToolResultDecisionExitCode(resultDecision = {}, fallback = 0) {
|
|
107
|
-
validateToolResultDecision(resultDecision);
|
|
108
|
-
if (resultDecision.status === 'error' || resultDecision.status === 'cancelled') return 1;
|
|
109
|
-
if (resultDecision.status === 'done') return 0;
|
|
110
|
-
return fallback;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
function isPlainObject(value) {
|
|
114
|
-
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
115
|
-
}
|
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
import { randomUUID } from 'node:crypto';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import {
|
|
4
|
-
pluginToolResultDecisionExitCode,
|
|
5
|
-
pluginToolResultDecisionOutput,
|
|
6
|
-
} from './runtime-decisions.mjs';
|
|
7
|
-
|
|
8
|
-
export {
|
|
9
|
-
applyToolCallDecision,
|
|
10
|
-
pluginToolResultDecisionExitCode,
|
|
11
|
-
pluginToolResultDecisionOutput,
|
|
12
|
-
validateToolCallDecision,
|
|
13
|
-
validateToolResultDecision,
|
|
14
|
-
} from './runtime-decisions.mjs';
|
|
15
|
-
export {
|
|
16
|
-
isPluginContentBlock,
|
|
17
|
-
normalizePluginToolExecuteOutput,
|
|
18
|
-
normalizePluginToolOutput,
|
|
19
|
-
pluginContentBlockText,
|
|
20
|
-
} from './runtime-content.mjs';
|
|
21
|
-
|
|
22
|
-
export function createToolUseID() {
|
|
23
|
-
return `toolu_${randomUUID()}`;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export function relativeToolPath(filePath) {
|
|
27
|
-
const relative = path.relative(process.cwd(), filePath);
|
|
28
|
-
return relative && !relative.startsWith('..') ? relative : filePath;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export function pluginToolCallEvent(tool, input, threadId, toolUseID = createToolUseID()) {
|
|
32
|
-
return {
|
|
33
|
-
toolUseID,
|
|
34
|
-
tool,
|
|
35
|
-
input,
|
|
36
|
-
thread: { id: threadId },
|
|
37
|
-
};
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export function pluginToolResultEvent(tool, input, status, output, threadId, toolUseID, error) {
|
|
41
|
-
return {
|
|
42
|
-
toolUseID,
|
|
43
|
-
tool,
|
|
44
|
-
input,
|
|
45
|
-
status,
|
|
46
|
-
output,
|
|
47
|
-
...(error ? { error } : {}),
|
|
48
|
-
thread: { id: threadId },
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export function pluginToolUseBlock(tool, input, toolUseID) {
|
|
53
|
-
return {
|
|
54
|
-
type: 'tool_use',
|
|
55
|
-
id: toolUseID,
|
|
56
|
-
name: tool,
|
|
57
|
-
input,
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
export function toolResultContent(toolRun = {}) {
|
|
62
|
-
return Object.hasOwn(toolRun, 'toolResultOutput') ? toolRun.toolResultOutput : toolRun.output;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
export function toolCallDecisionToolRun(toolName, input, toolUseID, callResult) {
|
|
66
|
-
return {
|
|
67
|
-
...callResult.output,
|
|
68
|
-
toolUse: pluginToolUseBlock(toolName, input, toolUseID),
|
|
69
|
-
};
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
export function pluginResultOutput(resultDecision, output) {
|
|
73
|
-
return String(pluginToolResultDecisionOutput(resultDecision, output) ?? '').trimEnd();
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
export function pluginTextToolRunResult(resultDecision, output, fallbackExitCode = 0) {
|
|
77
|
-
return {
|
|
78
|
-
output: pluginResultOutput(resultDecision, output),
|
|
79
|
-
exitCode: pluginToolResultDecisionExitCode(resultDecision, fallbackExitCode),
|
|
80
|
-
};
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
export function permissionDeniedOutput(toolName, decision) {
|
|
84
|
-
return decision.message ? `Permission denied for ${toolName}: ${decision.message}` : `Permission denied for ${toolName}`;
|
|
85
|
-
}
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import { fixtureAgentResponse } from '../../agent/fixture.mjs';
|
|
2
|
-
import { resolvePermissionDecision } from '../../commands/permissions.mjs';
|
|
3
|
-
import { runPluginEventHandlers } from '../../plugins/discover.mjs';
|
|
4
|
-
import { isToolDisabled } from '../toolbox.mjs';
|
|
5
|
-
import {
|
|
6
|
-
applyToolCallDecision,
|
|
7
|
-
createToolUseID,
|
|
8
|
-
permissionDeniedOutput,
|
|
9
|
-
pluginTextToolRunResult,
|
|
10
|
-
pluginToolCallEvent,
|
|
11
|
-
pluginToolResultEvent,
|
|
12
|
-
pluginToolUseBlock,
|
|
13
|
-
toolCallDecisionToolRun,
|
|
14
|
-
validateToolCallDecision,
|
|
15
|
-
} from './runtime.mjs';
|
|
16
|
-
|
|
17
|
-
export const TOOL_NAME = 'Task';
|
|
18
|
-
|
|
19
|
-
export async function executePromptTaskToolRequest(request, parsed = {}, plugins = { handlers: {} }, threadId = '') {
|
|
20
|
-
if (isToolDisabled(TOOL_NAME, 'built-in', parsed)) return { output: `Tool disabled: ${TOOL_NAME}` };
|
|
21
|
-
request = { ...request, flags: normalizeTaskInput(request.flags) };
|
|
22
|
-
if (!request.flags.prompt) return { output: 'Task requires --prompt' };
|
|
23
|
-
const toolUseID = createToolUseID();
|
|
24
|
-
const callDecision = await runPluginEventHandlers(
|
|
25
|
-
plugins.handlers['tool.call'],
|
|
26
|
-
pluginToolCallEvent(TOOL_NAME, request.flags, threadId, toolUseID),
|
|
27
|
-
validateToolCallDecision,
|
|
28
|
-
);
|
|
29
|
-
const callResult = applyToolCallDecision(TOOL_NAME, request, callDecision);
|
|
30
|
-
if (callResult.output) return toolCallDecisionToolRun(TOOL_NAME, request.flags, toolUseID, callResult);
|
|
31
|
-
request = { ...callResult.request, flags: normalizeTaskInput(callResult.request.flags) };
|
|
32
|
-
const decision = resolvePermissionDecision(TOOL_NAME, request.flags, parsed, { threadId });
|
|
33
|
-
if (!parsed.dangerouslyAllowAll && decision.action !== 'allow') {
|
|
34
|
-
return {
|
|
35
|
-
output: permissionDeniedOutput(TOOL_NAME, decision),
|
|
36
|
-
permissionDenials: [{ tool: TOOL_NAME, action: decision.action, reason: 'permission' }],
|
|
37
|
-
};
|
|
38
|
-
}
|
|
39
|
-
const output = fixtureAgentResponse(request.flags.prompt, '');
|
|
40
|
-
const resultDecision = await runPluginEventHandlers(
|
|
41
|
-
plugins.handlers['tool.result'],
|
|
42
|
-
pluginToolResultEvent(TOOL_NAME, request.flags, 'done', output, threadId, toolUseID),
|
|
43
|
-
);
|
|
44
|
-
const finalResult = pluginTextToolRunResult(resultDecision, output);
|
|
45
|
-
return {
|
|
46
|
-
output: finalResult.output,
|
|
47
|
-
exitCode: finalResult.exitCode,
|
|
48
|
-
subagentMessages: [{ text: finalResult.output }],
|
|
49
|
-
toolResultParentToolUseId: null,
|
|
50
|
-
finalParentToolUseId: null,
|
|
51
|
-
toolUse: pluginToolUseBlock(TOOL_NAME, request.flags, toolUseID),
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
function normalizeTaskInput(input = {}) {
|
|
56
|
-
const prompt = input.prompt ?? input.task ?? input.instructions ?? input.message;
|
|
57
|
-
const description = input.description ?? input.title ?? input.name ?? 'subagent task';
|
|
58
|
-
return {
|
|
59
|
-
...input,
|
|
60
|
-
description: String(description ?? 'subagent task'),
|
|
61
|
-
prompt: prompt ? String(prompt) : '',
|
|
62
|
-
};
|
|
63
|
-
}
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import { resolvePermissionDecision } from '../../commands/permissions.mjs';
|
|
2
|
-
import { runPluginEventHandlers } from '../../plugins/discover.mjs';
|
|
3
|
-
import { executeToolboxTool, isToolDisabled, listToolboxTools, normalizeToolName } from '../toolbox.mjs';
|
|
4
|
-
import {
|
|
5
|
-
applyToolCallDecision,
|
|
6
|
-
createToolUseID,
|
|
7
|
-
permissionDeniedOutput,
|
|
8
|
-
pluginResultOutput,
|
|
9
|
-
pluginToolCallEvent,
|
|
10
|
-
pluginToolResultDecisionExitCode,
|
|
11
|
-
pluginToolResultEvent,
|
|
12
|
-
pluginToolUseBlock,
|
|
13
|
-
validateToolCallDecision,
|
|
14
|
-
} from './runtime.mjs';
|
|
15
|
-
|
|
16
|
-
export async function executePromptToolboxToolRequest(request, stdin = '', parsed = {}, plugins = { handlers: {} }, threadId = '') {
|
|
17
|
-
const toolboxName = normalizeToolName(request.toolName);
|
|
18
|
-
const tool = listToolboxTools(parsed).find((entry) => entry.name === toolboxName);
|
|
19
|
-
if (!tool) return { output: `Unknown tool: ${request.toolName}` };
|
|
20
|
-
if (isToolDisabled(tool.name, 'toolbox', parsed)) return { output: `Tool disabled: ${tool.name}` };
|
|
21
|
-
const toolUseID = createToolUseID();
|
|
22
|
-
const callDecision = await runPluginEventHandlers(
|
|
23
|
-
plugins.handlers['tool.call'],
|
|
24
|
-
pluginToolCallEvent(tool.name, request.flags, threadId, toolUseID),
|
|
25
|
-
validateToolCallDecision,
|
|
26
|
-
);
|
|
27
|
-
const callResult = applyToolCallDecision(tool.name, request, callDecision);
|
|
28
|
-
if (callResult.output) {
|
|
29
|
-
return {
|
|
30
|
-
...callResult.output,
|
|
31
|
-
toolUse: pluginToolUseBlock(tool.name, request.flags, toolUseID),
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
request.flags = callResult.request.flags;
|
|
35
|
-
const decision = resolvePermissionDecision(tool.name, request.flags, parsed, { threadId });
|
|
36
|
-
if (!parsed.dangerouslyAllowAll && decision.action !== 'allow') {
|
|
37
|
-
return {
|
|
38
|
-
output: permissionDeniedOutput(tool.name, decision),
|
|
39
|
-
permissionDenials: [{ tool: tool.name, action: decision.action, reason: 'permission' }],
|
|
40
|
-
};
|
|
41
|
-
}
|
|
42
|
-
const result = executeToolboxTool(tool, request.flags, stdin, threadId);
|
|
43
|
-
if (result.stderr) process.stderr.write(result.stderr);
|
|
44
|
-
process.exitCode = result.status ?? 0;
|
|
45
|
-
const output = result.stdout.trimEnd();
|
|
46
|
-
const resultDecision = await runPluginEventHandlers(
|
|
47
|
-
plugins.handlers['tool.result'],
|
|
48
|
-
pluginToolResultEvent(tool.name, request.flags, (result.status ?? 0) === 0 ? 'done' : 'error', output, threadId, toolUseID),
|
|
49
|
-
);
|
|
50
|
-
const finalOutput = pluginResultOutput(resultDecision, output);
|
|
51
|
-
const exitCode = pluginToolResultDecisionExitCode(resultDecision, result.status ?? 0);
|
|
52
|
-
return {
|
|
53
|
-
output: finalOutput,
|
|
54
|
-
exitCode,
|
|
55
|
-
toolUse: pluginToolUseBlock(tool.name, request.flags, toolUseID),
|
|
56
|
-
};
|
|
57
|
-
}
|
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import { CONFIG_SUBDIR } from '../../constants.mjs';
|
|
4
|
-
import { resolvePermissionDecision } from '../../commands/permissions.mjs';
|
|
5
|
-
import { runPluginEventHandlers } from '../../plugins/discover.mjs';
|
|
6
|
-
import { configDir } from '../../settings/paths.mjs';
|
|
7
|
-
import { isToolDisabled } from '../toolbox.mjs';
|
|
8
|
-
import {
|
|
9
|
-
applyToolCallDecision,
|
|
10
|
-
createToolUseID,
|
|
11
|
-
permissionDeniedOutput,
|
|
12
|
-
pluginTextToolRunResult,
|
|
13
|
-
pluginToolCallEvent,
|
|
14
|
-
pluginToolResultEvent,
|
|
15
|
-
pluginToolUseBlock,
|
|
16
|
-
toolCallDecisionToolRun,
|
|
17
|
-
validateToolCallDecision,
|
|
18
|
-
} from './runtime.mjs';
|
|
19
|
-
|
|
20
|
-
export const TOOL_NAME = 'undo_edit';
|
|
21
|
-
|
|
22
|
-
export async function executePromptUndoEditToolRequest(request, parsed = {}, plugins = { handlers: {} }, threadId = '') {
|
|
23
|
-
if (isToolDisabled(TOOL_NAME, 'built-in', parsed)) return { output: `Tool disabled: ${TOOL_NAME}` };
|
|
24
|
-
request = { ...request, flags: normalizeUndoEditInput(request.flags) };
|
|
25
|
-
const toolUseID = createToolUseID();
|
|
26
|
-
const callDecision = await runPluginEventHandlers(
|
|
27
|
-
plugins.handlers['tool.call'],
|
|
28
|
-
pluginToolCallEvent(TOOL_NAME, request.flags, threadId, toolUseID),
|
|
29
|
-
validateToolCallDecision,
|
|
30
|
-
);
|
|
31
|
-
const callResult = applyToolCallDecision(TOOL_NAME, request, callDecision);
|
|
32
|
-
if (callResult.output) return toolCallDecisionToolRun(TOOL_NAME, request.flags, toolUseID, callResult);
|
|
33
|
-
request = { ...callResult.request, flags: normalizeUndoEditInput(callResult.request.flags) };
|
|
34
|
-
const decision = resolvePermissionDecision(TOOL_NAME, request.flags, parsed, { threadId });
|
|
35
|
-
if (!parsed.dangerouslyAllowAll && decision.action !== 'allow') {
|
|
36
|
-
return {
|
|
37
|
-
output: permissionDeniedOutput(TOOL_NAME, decision),
|
|
38
|
-
permissionDenials: [{ tool: TOOL_NAME, action: decision.action, reason: 'permission' }],
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
const undoResult = undoBuiltinEdit(threadId, request.flags.path);
|
|
42
|
-
const resultDecision = await runPluginEventHandlers(
|
|
43
|
-
plugins.handlers['tool.result'],
|
|
44
|
-
pluginToolResultEvent(TOOL_NAME, request.flags, undoResult.status, undoResult.output, threadId, toolUseID),
|
|
45
|
-
);
|
|
46
|
-
return {
|
|
47
|
-
...pluginTextToolRunResult(resultDecision, undoResult.output, undoResult.status === 'done' ? 0 : 1),
|
|
48
|
-
toolUse: pluginToolUseBlock(TOOL_NAME, request.flags, toolUseID),
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function normalizeUndoEditInput(input = {}) {
|
|
53
|
-
const filePath = input.path ?? input.file ?? input.file_path;
|
|
54
|
-
return {
|
|
55
|
-
...input,
|
|
56
|
-
path: filePath ? String(filePath) : '',
|
|
57
|
-
};
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
export function recordEditUndo(threadId, entry) {
|
|
61
|
-
const entries = readEditUndoEntries(threadId);
|
|
62
|
-
const filePath = editUndoFile(threadId);
|
|
63
|
-
mkdirSync(path.dirname(filePath), { recursive: true });
|
|
64
|
-
writeFileSync(filePath, `${JSON.stringify([...entries, { ...entry, createdAt: new Date().toISOString() }], null, 2)}\n`);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function undoBuiltinEdit(threadId, filePath = '') {
|
|
68
|
-
const entries = readEditUndoEntries(threadId);
|
|
69
|
-
const absolutePath = filePath ? path.resolve(process.cwd(), filePath) : '';
|
|
70
|
-
const index = findUndoEntryIndex(entries, filePath, absolutePath);
|
|
71
|
-
if (index === -1) return { status: 'error', output: filePath ? `No edit to undo for ${filePath}` : 'No edit to undo' };
|
|
72
|
-
const [entry] = entries.splice(index, 1);
|
|
73
|
-
writeFileSync(entry.absolutePath, entry.content, 'utf8');
|
|
74
|
-
writeFileSync(editUndoFile(threadId), `${JSON.stringify(entries, null, 2)}\n`);
|
|
75
|
-
return { status: 'done', output: `Undid edit to ${entry.path}` };
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
function findUndoEntryIndex(entries, filePath, absolutePath) {
|
|
79
|
-
for (let index = entries.length - 1; index >= 0; index -= 1) {
|
|
80
|
-
const entry = entries[index];
|
|
81
|
-
if (!filePath || entry.path === filePath || entry.absolutePath === absolutePath) return index;
|
|
82
|
-
}
|
|
83
|
-
return -1;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
function readEditUndoEntries(threadId) {
|
|
87
|
-
try {
|
|
88
|
-
const parsed = JSON.parse(readFileSync(editUndoFile(threadId), 'utf8'));
|
|
89
|
-
return Array.isArray(parsed) ? parsed : [];
|
|
90
|
-
} catch {
|
|
91
|
-
return [];
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
function editUndoFile(threadId) {
|
|
96
|
-
return path.join(configDir(), CONFIG_SUBDIR, 'undo', `${threadId || 'global'}.json`);
|
|
97
|
-
}
|