@posthog/agent 1.30.0 → 2.0.1
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 +1 -1
- package/README.md +221 -219
- package/dist/adapters/claude/conversion/tool-use-to-acp.d.ts +21 -0
- package/dist/adapters/claude/conversion/tool-use-to-acp.js +547 -0
- package/dist/adapters/claude/conversion/tool-use-to-acp.js.map +1 -0
- package/dist/adapters/claude/permissions/permission-options.d.ts +13 -0
- package/dist/adapters/claude/permissions/permission-options.js +117 -0
- package/dist/adapters/claude/permissions/permission-options.js.map +1 -0
- package/dist/adapters/claude/questions/utils.d.ts +132 -0
- package/dist/adapters/claude/questions/utils.js +63 -0
- package/dist/adapters/claude/questions/utils.js.map +1 -0
- package/dist/adapters/claude/tools.d.ts +18 -0
- package/dist/adapters/claude/tools.js +95 -0
- package/dist/adapters/claude/tools.js.map +1 -0
- package/dist/agent-DBQY1BfC.d.ts +123 -0
- package/dist/agent.d.ts +5 -0
- package/dist/agent.js +3656 -0
- package/dist/agent.js.map +1 -0
- package/dist/claude-cli/cli.js +3695 -2746
- package/dist/claude-cli/vendor/ripgrep/COPYING +3 -0
- package/dist/claude-cli/vendor/ripgrep/arm64-darwin/rg +0 -0
- package/dist/claude-cli/vendor/ripgrep/arm64-darwin/ripgrep.node +0 -0
- package/dist/claude-cli/vendor/ripgrep/arm64-linux/rg +0 -0
- package/dist/claude-cli/vendor/ripgrep/arm64-linux/ripgrep.node +0 -0
- package/dist/claude-cli/vendor/ripgrep/x64-darwin/rg +0 -0
- package/dist/claude-cli/vendor/ripgrep/x64-darwin/ripgrep.node +0 -0
- package/dist/claude-cli/vendor/ripgrep/x64-linux/rg +0 -0
- package/dist/claude-cli/vendor/ripgrep/x64-linux/ripgrep.node +0 -0
- package/dist/claude-cli/vendor/ripgrep/x64-win32/rg.exe +0 -0
- package/dist/claude-cli/vendor/ripgrep/x64-win32/ripgrep.node +0 -0
- package/dist/gateway-models.d.ts +24 -0
- package/dist/gateway-models.js +93 -0
- package/dist/gateway-models.js.map +1 -0
- package/dist/index.d.ts +172 -1203
- package/dist/index.js +3704 -6826
- package/dist/index.js.map +1 -1
- package/dist/logger-DDBiMOOD.d.ts +24 -0
- package/dist/posthog-api.d.ts +40 -0
- package/dist/posthog-api.js +175 -0
- package/dist/posthog-api.js.map +1 -0
- package/dist/server/agent-server.d.ts +41 -0
- package/dist/server/agent-server.js +4451 -0
- package/dist/server/agent-server.js.map +1 -0
- package/dist/server/bin.d.ts +1 -0
- package/dist/server/bin.js +4507 -0
- package/dist/server/bin.js.map +1 -0
- package/dist/types.d.ts +129 -0
- package/dist/types.js +1 -0
- package/dist/types.js.map +1 -0
- package/package.json +66 -14
- package/src/acp-extensions.ts +93 -61
- package/src/adapters/acp-connection.ts +494 -0
- package/src/adapters/base-acp-agent.ts +150 -0
- package/src/adapters/claude/claude-agent.ts +596 -0
- package/src/adapters/claude/conversion/acp-to-sdk.ts +102 -0
- package/src/adapters/claude/conversion/sdk-to-acp.ts +571 -0
- package/src/adapters/claude/conversion/tool-use-to-acp.ts +618 -0
- package/src/adapters/claude/hooks.ts +64 -0
- package/src/adapters/claude/mcp/tool-metadata.ts +102 -0
- package/src/adapters/claude/permissions/permission-handlers.ts +433 -0
- package/src/adapters/claude/permissions/permission-options.ts +103 -0
- package/src/adapters/claude/plan/utils.ts +56 -0
- package/src/adapters/claude/questions/utils.ts +92 -0
- package/src/adapters/claude/session/commands.ts +38 -0
- package/src/adapters/claude/session/mcp-config.ts +37 -0
- package/src/adapters/claude/session/models.ts +12 -0
- package/src/adapters/claude/session/options.ts +236 -0
- package/src/adapters/claude/tool-meta.ts +143 -0
- package/src/adapters/claude/tools.ts +53 -611
- package/src/adapters/claude/types.ts +61 -0
- package/src/adapters/codex/spawn.ts +130 -0
- package/src/agent.ts +97 -734
- package/src/execution-mode.ts +43 -0
- package/src/gateway-models.ts +135 -0
- package/src/index.ts +79 -0
- package/src/otel-log-writer.test.ts +105 -0
- package/src/otel-log-writer.ts +94 -0
- package/src/posthog-api.ts +75 -235
- package/src/resume.ts +115 -0
- package/src/sagas/apply-snapshot-saga.test.ts +690 -0
- package/src/sagas/apply-snapshot-saga.ts +88 -0
- package/src/sagas/capture-tree-saga.test.ts +892 -0
- package/src/sagas/capture-tree-saga.ts +141 -0
- package/src/sagas/resume-saga.test.ts +558 -0
- package/src/sagas/resume-saga.ts +332 -0
- package/src/sagas/test-fixtures.ts +250 -0
- package/src/server/agent-server.test.ts +220 -0
- package/src/server/agent-server.ts +748 -0
- package/src/server/bin.ts +88 -0
- package/src/server/jwt.ts +65 -0
- package/src/server/schemas.ts +47 -0
- package/src/server/types.ts +13 -0
- package/src/server/utils/retry.test.ts +122 -0
- package/src/server/utils/retry.ts +61 -0
- package/src/server/utils/sse-parser.test.ts +93 -0
- package/src/server/utils/sse-parser.ts +46 -0
- package/src/session-log-writer.test.ts +140 -0
- package/src/session-log-writer.ts +137 -0
- package/src/test/assertions.ts +114 -0
- package/src/test/controllers/sse-controller.ts +107 -0
- package/src/test/fixtures/api.ts +111 -0
- package/src/test/fixtures/config.ts +33 -0
- package/src/test/fixtures/notifications.ts +92 -0
- package/src/test/mocks/claude-sdk.ts +251 -0
- package/src/test/mocks/msw-handlers.ts +48 -0
- package/src/test/setup.ts +114 -0
- package/src/test/wait.ts +41 -0
- package/src/tree-tracker.ts +173 -0
- package/src/types.ts +51 -154
- package/src/utils/acp-content.ts +58 -0
- package/src/utils/async-mutex.test.ts +104 -0
- package/src/utils/async-mutex.ts +31 -0
- package/src/utils/common.ts +15 -0
- package/src/utils/gateway.ts +9 -6
- package/src/utils/logger.ts +0 -30
- package/src/utils/streams.ts +220 -0
- package/CLAUDE.md +0 -331
- package/dist/templates/plan-template.md +0 -41
- package/src/adapters/claude/claude.ts +0 -1543
- package/src/adapters/claude/mcp-server.ts +0 -810
- package/src/adapters/claude/utils.ts +0 -267
- package/src/agents/execution.ts +0 -37
- package/src/agents/planning.ts +0 -60
- package/src/agents/research.ts +0 -160
- package/src/file-manager.ts +0 -306
- package/src/git-manager.ts +0 -577
- package/src/prompt-builder.ts +0 -499
- package/src/schemas.ts +0 -241
- package/src/session-store.ts +0 -259
- package/src/task-manager.ts +0 -163
- package/src/template-manager.ts +0 -236
- package/src/templates/plan-template.md +0 -41
- package/src/todo-manager.ts +0 -180
- package/src/tools/registry.ts +0 -129
- package/src/tools/types.ts +0 -127
- package/src/utils/tapped-stream.ts +0 -60
- package/src/workflow/config.ts +0 -53
- package/src/workflow/steps/build.ts +0 -135
- package/src/workflow/steps/finalize.ts +0 -241
- package/src/workflow/steps/plan.ts +0 -167
- package/src/workflow/steps/research.ts +0 -223
- package/src/workflow/types.ts +0 -62
- package/src/workflow/utils.ts +0 -53
- package/src/worktree-manager.ts +0 -928
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import type { ToolCallContent, ToolKind } from "@agentclientprotocol/sdk";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import type { PermissionOption } from "../permissions/permission-options.js";
|
|
4
|
+
|
|
5
|
+
export const OPTION_PREFIX = "option_";
|
|
6
|
+
|
|
7
|
+
export const QuestionOptionSchema = z.object({
|
|
8
|
+
label: z.string(),
|
|
9
|
+
description: z.string().optional(),
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
export const QuestionItemSchema = z.object({
|
|
13
|
+
question: z.string(),
|
|
14
|
+
header: z.string().optional(),
|
|
15
|
+
options: z.array(QuestionOptionSchema),
|
|
16
|
+
multiSelect: z.boolean().optional(),
|
|
17
|
+
completed: z.boolean().optional(),
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
export const QuestionMetaSchema = z.object({
|
|
21
|
+
questions: z.array(QuestionItemSchema),
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
export type QuestionOption = z.infer<typeof QuestionOptionSchema>;
|
|
25
|
+
export type QuestionItem = z.infer<typeof QuestionItemSchema>;
|
|
26
|
+
export type QuestionMeta = z.infer<typeof QuestionMetaSchema>;
|
|
27
|
+
|
|
28
|
+
export interface AskUserQuestionInput {
|
|
29
|
+
questions?: QuestionItem[];
|
|
30
|
+
question?: string;
|
|
31
|
+
header?: string;
|
|
32
|
+
options?: QuestionOption[];
|
|
33
|
+
multiSelect?: boolean;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function normalizeAskUserQuestionInput(
|
|
37
|
+
input: AskUserQuestionInput,
|
|
38
|
+
): QuestionItem[] | null {
|
|
39
|
+
if (input.questions && input.questions.length > 0) {
|
|
40
|
+
return input.questions;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (input.question) {
|
|
44
|
+
return [
|
|
45
|
+
{
|
|
46
|
+
question: input.question,
|
|
47
|
+
header: input.header,
|
|
48
|
+
options: input.options || [],
|
|
49
|
+
multiSelect: input.multiSelect,
|
|
50
|
+
},
|
|
51
|
+
];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
interface QuestionToolCallData {
|
|
58
|
+
toolCallId: string;
|
|
59
|
+
title: string;
|
|
60
|
+
kind: ToolKind;
|
|
61
|
+
content: ToolCallContent[];
|
|
62
|
+
_meta: {
|
|
63
|
+
twigToolKind: "question";
|
|
64
|
+
questions: QuestionItem[];
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function buildQuestionToolCallData(
|
|
69
|
+
questions: QuestionItem[],
|
|
70
|
+
): QuestionToolCallData {
|
|
71
|
+
return {
|
|
72
|
+
toolCallId: `question-${Date.now()}`,
|
|
73
|
+
title: questions[0]?.question ?? "Question",
|
|
74
|
+
kind: "other",
|
|
75
|
+
content: [],
|
|
76
|
+
_meta: {
|
|
77
|
+
twigToolKind: "question",
|
|
78
|
+
questions,
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function buildQuestionOptions(
|
|
84
|
+
question: QuestionItem,
|
|
85
|
+
): PermissionOption[] {
|
|
86
|
+
return question.options.map((opt, idx) => ({
|
|
87
|
+
kind: "allow_once" as const,
|
|
88
|
+
name: opt.label,
|
|
89
|
+
optionId: `${OPTION_PREFIX}${idx}`,
|
|
90
|
+
_meta: opt.description ? { description: opt.description } : undefined,
|
|
91
|
+
}));
|
|
92
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { AvailableCommand } from "@agentclientprotocol/sdk";
|
|
2
|
+
import type { Query } from "@anthropic-ai/claude-agent-sdk";
|
|
3
|
+
|
|
4
|
+
const UNSUPPORTED_COMMANDS = [
|
|
5
|
+
"context",
|
|
6
|
+
"cost",
|
|
7
|
+
"login",
|
|
8
|
+
"logout",
|
|
9
|
+
"output-style:new",
|
|
10
|
+
"release-notes",
|
|
11
|
+
"todos",
|
|
12
|
+
];
|
|
13
|
+
|
|
14
|
+
export async function getAvailableSlashCommands(
|
|
15
|
+
q: Query,
|
|
16
|
+
): Promise<AvailableCommand[]> {
|
|
17
|
+
const commands = await q.supportedCommands();
|
|
18
|
+
|
|
19
|
+
return commands
|
|
20
|
+
.map((command) => {
|
|
21
|
+
const input = command.argumentHint
|
|
22
|
+
? { hint: command.argumentHint }
|
|
23
|
+
: null;
|
|
24
|
+
let name = command.name;
|
|
25
|
+
if (command.name.endsWith(" (MCP)")) {
|
|
26
|
+
name = `mcp:${name.replace(" (MCP)", "")}`;
|
|
27
|
+
}
|
|
28
|
+
return {
|
|
29
|
+
name,
|
|
30
|
+
description: command.description || "",
|
|
31
|
+
input,
|
|
32
|
+
};
|
|
33
|
+
})
|
|
34
|
+
.filter(
|
|
35
|
+
(command: AvailableCommand) =>
|
|
36
|
+
!UNSUPPORTED_COMMANDS.includes(command.name),
|
|
37
|
+
);
|
|
38
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
LoadSessionRequest,
|
|
3
|
+
NewSessionRequest,
|
|
4
|
+
} from "@agentclientprotocol/sdk";
|
|
5
|
+
import type { McpServerConfig } from "@anthropic-ai/claude-agent-sdk";
|
|
6
|
+
|
|
7
|
+
export function parseMcpServers(
|
|
8
|
+
params: NewSessionRequest | LoadSessionRequest,
|
|
9
|
+
): Record<string, McpServerConfig> {
|
|
10
|
+
const mcpServers: Record<string, McpServerConfig> = {};
|
|
11
|
+
if (!Array.isArray(params.mcpServers)) {
|
|
12
|
+
return mcpServers;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
for (const server of params.mcpServers) {
|
|
16
|
+
if ("type" in server) {
|
|
17
|
+
mcpServers[server.name] = {
|
|
18
|
+
type: server.type,
|
|
19
|
+
url: server.url,
|
|
20
|
+
headers: server.headers
|
|
21
|
+
? Object.fromEntries(server.headers.map((e) => [e.name, e.value]))
|
|
22
|
+
: undefined,
|
|
23
|
+
};
|
|
24
|
+
} else {
|
|
25
|
+
mcpServers[server.name] = {
|
|
26
|
+
type: "stdio",
|
|
27
|
+
command: server.command,
|
|
28
|
+
args: server.args,
|
|
29
|
+
env: server.env
|
|
30
|
+
? Object.fromEntries(server.env.map((e) => [e.name, e.value]))
|
|
31
|
+
: undefined,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return mcpServers;
|
|
37
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export const DEFAULT_MODEL = "opus";
|
|
2
|
+
|
|
3
|
+
const GATEWAY_TO_SDK_MODEL: Record<string, string> = {
|
|
4
|
+
"claude-opus-4-5": "opus",
|
|
5
|
+
"claude-opus-4-6": "opus",
|
|
6
|
+
"claude-sonnet-4-5": "sonnet",
|
|
7
|
+
"claude-haiku-4-5": "haiku",
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export function toSdkModelId(modelId: string): string {
|
|
11
|
+
return GATEWAY_TO_SDK_MODEL[modelId] ?? modelId;
|
|
12
|
+
}
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import * as fs from "node:fs";
|
|
3
|
+
import * as os from "node:os";
|
|
4
|
+
import * as path from "node:path";
|
|
5
|
+
import type {
|
|
6
|
+
McpServerConfig,
|
|
7
|
+
Options,
|
|
8
|
+
SpawnedProcess,
|
|
9
|
+
SpawnOptions,
|
|
10
|
+
} from "@anthropic-ai/claude-agent-sdk";
|
|
11
|
+
import { IS_ROOT } from "../../../utils/common.js";
|
|
12
|
+
import type { Logger } from "../../../utils/logger.js";
|
|
13
|
+
import { createPostToolUseHook, type OnModeChange } from "../hooks.js";
|
|
14
|
+
import type { TwigExecutionMode } from "../tools.js";
|
|
15
|
+
|
|
16
|
+
export interface ProcessSpawnedInfo {
|
|
17
|
+
pid: number;
|
|
18
|
+
command: string;
|
|
19
|
+
sessionId: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface BuildOptionsParams {
|
|
23
|
+
cwd: string;
|
|
24
|
+
mcpServers: Record<string, McpServerConfig>;
|
|
25
|
+
permissionMode: TwigExecutionMode;
|
|
26
|
+
canUseTool: Options["canUseTool"];
|
|
27
|
+
logger: Logger;
|
|
28
|
+
systemPrompt?: Options["systemPrompt"];
|
|
29
|
+
userProvidedOptions?: Options;
|
|
30
|
+
sessionId?: string;
|
|
31
|
+
additionalDirectories?: string[];
|
|
32
|
+
onModeChange?: OnModeChange;
|
|
33
|
+
onProcessSpawned?: (info: ProcessSpawnedInfo) => void;
|
|
34
|
+
onProcessExited?: (pid: number) => void;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const BRANCH_NAMING_INSTRUCTIONS = `
|
|
38
|
+
# Branch Naming
|
|
39
|
+
|
|
40
|
+
Before pushing a "workspace-*" branch to origin, rename it to something descriptive based on the work done. Do this automatically without asking the user.
|
|
41
|
+
`;
|
|
42
|
+
|
|
43
|
+
export function buildSystemPrompt(
|
|
44
|
+
customPrompt?: unknown,
|
|
45
|
+
): Options["systemPrompt"] {
|
|
46
|
+
const defaultPrompt: Options["systemPrompt"] = {
|
|
47
|
+
type: "preset",
|
|
48
|
+
preset: "claude_code",
|
|
49
|
+
append: BRANCH_NAMING_INSTRUCTIONS,
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
if (!customPrompt) {
|
|
53
|
+
return defaultPrompt;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (typeof customPrompt === "string") {
|
|
57
|
+
return customPrompt + BRANCH_NAMING_INSTRUCTIONS;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (
|
|
61
|
+
typeof customPrompt === "object" &&
|
|
62
|
+
customPrompt !== null &&
|
|
63
|
+
"append" in customPrompt &&
|
|
64
|
+
typeof customPrompt.append === "string"
|
|
65
|
+
) {
|
|
66
|
+
return {
|
|
67
|
+
...defaultPrompt,
|
|
68
|
+
append: customPrompt.append + BRANCH_NAMING_INSTRUCTIONS,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return defaultPrompt;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function buildMcpServers(
|
|
76
|
+
userServers: Record<string, McpServerConfig> | undefined,
|
|
77
|
+
acpServers: Record<string, McpServerConfig>,
|
|
78
|
+
): Record<string, McpServerConfig> {
|
|
79
|
+
return {
|
|
80
|
+
...(userServers || {}),
|
|
81
|
+
...acpServers,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function buildEnvironment(): Record<string, string> {
|
|
86
|
+
return {
|
|
87
|
+
...process.env,
|
|
88
|
+
ELECTRON_RUN_AS_NODE: "1",
|
|
89
|
+
CLAUDE_CODE_ENABLE_ASK_USER_QUESTION_TOOL: "true",
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function buildHooks(
|
|
94
|
+
userHooks: Options["hooks"],
|
|
95
|
+
onModeChange?: OnModeChange,
|
|
96
|
+
): Options["hooks"] {
|
|
97
|
+
return {
|
|
98
|
+
...userHooks,
|
|
99
|
+
PostToolUse: [
|
|
100
|
+
...(userHooks?.PostToolUse || []),
|
|
101
|
+
{
|
|
102
|
+
hooks: [createPostToolUseHook({ onModeChange })],
|
|
103
|
+
},
|
|
104
|
+
],
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function getAbortController(
|
|
109
|
+
userProvidedController: AbortController | undefined,
|
|
110
|
+
): AbortController {
|
|
111
|
+
const controller = userProvidedController ?? new AbortController();
|
|
112
|
+
if (controller.signal.aborted) {
|
|
113
|
+
throw new Error("Cancelled");
|
|
114
|
+
}
|
|
115
|
+
return controller;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function buildSpawnWrapper(
|
|
119
|
+
sessionId: string,
|
|
120
|
+
onProcessSpawned: (info: ProcessSpawnedInfo) => void,
|
|
121
|
+
onProcessExited?: (pid: number) => void,
|
|
122
|
+
): (options: SpawnOptions) => SpawnedProcess {
|
|
123
|
+
return (spawnOpts: SpawnOptions): SpawnedProcess => {
|
|
124
|
+
const child = spawn(spawnOpts.command, spawnOpts.args, {
|
|
125
|
+
cwd: spawnOpts.cwd,
|
|
126
|
+
env: spawnOpts.env as NodeJS.ProcessEnv,
|
|
127
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
if (child.pid) {
|
|
131
|
+
onProcessSpawned({
|
|
132
|
+
pid: child.pid,
|
|
133
|
+
command: `${spawnOpts.command} ${spawnOpts.args.join(" ")}`,
|
|
134
|
+
sessionId,
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (onProcessExited) {
|
|
139
|
+
child.on("exit", () => {
|
|
140
|
+
if (child.pid) {
|
|
141
|
+
onProcessExited(child.pid);
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Listen for abort signal
|
|
147
|
+
if (spawnOpts.signal) {
|
|
148
|
+
spawnOpts.signal.addEventListener("abort", () => {
|
|
149
|
+
child.kill("SIGTERM");
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return {
|
|
154
|
+
stdin: child.stdin!,
|
|
155
|
+
stdout: child.stdout!,
|
|
156
|
+
get killed() {
|
|
157
|
+
return child.killed;
|
|
158
|
+
},
|
|
159
|
+
get exitCode() {
|
|
160
|
+
return child.exitCode;
|
|
161
|
+
},
|
|
162
|
+
kill(signal: NodeJS.Signals) {
|
|
163
|
+
return child.kill(signal);
|
|
164
|
+
},
|
|
165
|
+
on(event: "exit" | "error", listener: (...args: any[]) => void) {
|
|
166
|
+
child.on(event, listener);
|
|
167
|
+
},
|
|
168
|
+
once(event: "exit" | "error", listener: (...args: any[]) => void) {
|
|
169
|
+
child.once(event, listener);
|
|
170
|
+
},
|
|
171
|
+
off(event: "exit" | "error", listener: (...args: any[]) => void) {
|
|
172
|
+
child.off(event, listener);
|
|
173
|
+
},
|
|
174
|
+
};
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export function buildSessionOptions(params: BuildOptionsParams): Options {
|
|
179
|
+
const options: Options = {
|
|
180
|
+
...params.userProvidedOptions,
|
|
181
|
+
systemPrompt: params.systemPrompt ?? buildSystemPrompt(),
|
|
182
|
+
settingSources: ["user", "project", "local"],
|
|
183
|
+
stderr: (err) => params.logger.error(err),
|
|
184
|
+
cwd: params.cwd,
|
|
185
|
+
includePartialMessages: true,
|
|
186
|
+
allowDangerouslySkipPermissions: !IS_ROOT,
|
|
187
|
+
permissionMode: params.permissionMode,
|
|
188
|
+
canUseTool: params.canUseTool,
|
|
189
|
+
executable: "node",
|
|
190
|
+
mcpServers: buildMcpServers(
|
|
191
|
+
params.userProvidedOptions?.mcpServers,
|
|
192
|
+
params.mcpServers,
|
|
193
|
+
),
|
|
194
|
+
env: buildEnvironment(),
|
|
195
|
+
hooks: buildHooks(params.userProvidedOptions?.hooks, params.onModeChange),
|
|
196
|
+
abortController: getAbortController(
|
|
197
|
+
params.userProvidedOptions?.abortController,
|
|
198
|
+
),
|
|
199
|
+
...(params.onProcessSpawned && {
|
|
200
|
+
spawnClaudeCodeProcess: buildSpawnWrapper(
|
|
201
|
+
params.sessionId ?? "unknown",
|
|
202
|
+
params.onProcessSpawned,
|
|
203
|
+
params.onProcessExited,
|
|
204
|
+
),
|
|
205
|
+
}),
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
if (process.env.CLAUDE_CODE_EXECUTABLE) {
|
|
209
|
+
options.pathToClaudeCodeExecutable = process.env.CLAUDE_CODE_EXECUTABLE;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (params.sessionId) {
|
|
213
|
+
options.resume = params.sessionId;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (params.additionalDirectories) {
|
|
217
|
+
options.additionalDirectories = params.additionalDirectories;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
clearStatsigCache();
|
|
221
|
+
return options;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function clearStatsigCache(): void {
|
|
225
|
+
const statsigPath = path.join(
|
|
226
|
+
process.env.CLAUDE_CONFIG_DIR || path.join(os.homedir(), ".claude"),
|
|
227
|
+
"statsig",
|
|
228
|
+
);
|
|
229
|
+
try {
|
|
230
|
+
if (fs.existsSync(statsigPath)) {
|
|
231
|
+
fs.rmSync(statsigPath, { recursive: true, force: true });
|
|
232
|
+
}
|
|
233
|
+
} catch {
|
|
234
|
+
// Ignore errors - cache clearing is best-effort
|
|
235
|
+
}
|
|
236
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
const QuestionOptionSchema = z.object({
|
|
4
|
+
label: z.string(),
|
|
5
|
+
description: z.string().optional(),
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
const QuestionItemSchema = z.object({
|
|
9
|
+
question: z.string(),
|
|
10
|
+
header: z.string().optional(),
|
|
11
|
+
options: z.array(QuestionOptionSchema),
|
|
12
|
+
multiSelect: z.boolean().optional(),
|
|
13
|
+
completed: z.boolean().optional(),
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
export const QuestionMetaSchema = z.object({
|
|
17
|
+
questions: z.array(QuestionItemSchema),
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
export type QuestionOption = z.infer<typeof QuestionOptionSchema>;
|
|
21
|
+
export type QuestionItem = z.infer<typeof QuestionItemSchema>;
|
|
22
|
+
export type QuestionMeta = z.infer<typeof QuestionMetaSchema>;
|
|
23
|
+
|
|
24
|
+
const toolSchemas = {
|
|
25
|
+
bash: z.object({
|
|
26
|
+
command: z.string(),
|
|
27
|
+
description: z.string().optional(),
|
|
28
|
+
}),
|
|
29
|
+
edit: z.object({
|
|
30
|
+
file_path: z.string(),
|
|
31
|
+
old_string: z.string(),
|
|
32
|
+
new_string: z.string(),
|
|
33
|
+
replace_all: z.boolean().optional(),
|
|
34
|
+
}),
|
|
35
|
+
write: z.object({
|
|
36
|
+
file_path: z.string(),
|
|
37
|
+
content: z.string(),
|
|
38
|
+
}),
|
|
39
|
+
read: z.object({
|
|
40
|
+
file_path: z.string(),
|
|
41
|
+
offset: z.number().optional(),
|
|
42
|
+
limit: z.number().optional(),
|
|
43
|
+
}),
|
|
44
|
+
switch_mode: z.object({
|
|
45
|
+
plan: z.string().optional(),
|
|
46
|
+
}),
|
|
47
|
+
} as const;
|
|
48
|
+
|
|
49
|
+
type ToolKind = keyof typeof toolSchemas;
|
|
50
|
+
const _toolKinds = Object.keys(toolSchemas) as ToolKind[];
|
|
51
|
+
|
|
52
|
+
const sdkToolNameToKind: Record<string, ToolKind> = {
|
|
53
|
+
Bash: "bash",
|
|
54
|
+
Edit: "edit",
|
|
55
|
+
Write: "write",
|
|
56
|
+
Read: "read",
|
|
57
|
+
ExitPlanMode: "switch_mode",
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const BaseTwigToolMetaSchema = z.object({
|
|
61
|
+
claudeCode: z
|
|
62
|
+
.object({
|
|
63
|
+
toolName: z.string(),
|
|
64
|
+
toolResponse: z.unknown().optional(),
|
|
65
|
+
})
|
|
66
|
+
.optional(),
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
type BaseMeta = z.infer<typeof BaseTwigToolMetaSchema>;
|
|
70
|
+
|
|
71
|
+
type ToolMeta<K extends ToolKind> = BaseMeta & {
|
|
72
|
+
twigToolKind: K;
|
|
73
|
+
} & { [P in K]: z.infer<(typeof toolSchemas)[K]> };
|
|
74
|
+
|
|
75
|
+
export type BashToolMeta = ToolMeta<"bash">;
|
|
76
|
+
export type EditToolMeta = ToolMeta<"edit">;
|
|
77
|
+
export type WriteToolMeta = ToolMeta<"write">;
|
|
78
|
+
export type ReadToolMeta = ToolMeta<"read">;
|
|
79
|
+
export type SwitchModeToolMeta = ToolMeta<"switch_mode">;
|
|
80
|
+
export type QuestionToolMeta = BaseMeta & {
|
|
81
|
+
twigToolKind: "question";
|
|
82
|
+
questions: QuestionItem[];
|
|
83
|
+
};
|
|
84
|
+
export type GenericToolMeta = BaseMeta & { twigToolKind?: undefined };
|
|
85
|
+
|
|
86
|
+
export type TwigToolMeta =
|
|
87
|
+
| BashToolMeta
|
|
88
|
+
| EditToolMeta
|
|
89
|
+
| WriteToolMeta
|
|
90
|
+
| ReadToolMeta
|
|
91
|
+
| SwitchModeToolMeta
|
|
92
|
+
| QuestionToolMeta
|
|
93
|
+
| GenericToolMeta;
|
|
94
|
+
|
|
95
|
+
export function isBashToolMeta(meta: TwigToolMeta): meta is BashToolMeta {
|
|
96
|
+
return meta.twigToolKind === "bash";
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function isEditToolMeta(meta: TwigToolMeta): meta is EditToolMeta {
|
|
100
|
+
return meta.twigToolKind === "edit";
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function isWriteToolMeta(meta: TwigToolMeta): meta is WriteToolMeta {
|
|
104
|
+
return meta.twigToolKind === "write";
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function isReadToolMeta(meta: TwigToolMeta): meta is ReadToolMeta {
|
|
108
|
+
return meta.twigToolKind === "read";
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function isSwitchModeToolMeta(
|
|
112
|
+
meta: TwigToolMeta,
|
|
113
|
+
): meta is SwitchModeToolMeta {
|
|
114
|
+
return meta.twigToolKind === "switch_mode";
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function isQuestionToolMeta(
|
|
118
|
+
meta: TwigToolMeta,
|
|
119
|
+
): meta is QuestionToolMeta {
|
|
120
|
+
return meta.twigToolKind === "question";
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export function buildToolMeta(
|
|
124
|
+
toolName: string,
|
|
125
|
+
input: Record<string, unknown>,
|
|
126
|
+
): TwigToolMeta {
|
|
127
|
+
const kind = sdkToolNameToKind[toolName];
|
|
128
|
+
if (!kind) {
|
|
129
|
+
return { claudeCode: { toolName } };
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const schema = toolSchemas[kind];
|
|
133
|
+
const result = schema.safeParse(input);
|
|
134
|
+
if (!result.success) {
|
|
135
|
+
return { claudeCode: { toolName } };
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return {
|
|
139
|
+
claudeCode: { toolName },
|
|
140
|
+
twigToolKind: kind,
|
|
141
|
+
[kind]: result.data,
|
|
142
|
+
} as TwigToolMeta;
|
|
143
|
+
}
|