aiterm-mcp 0.42.2 → 0.43.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.
@@ -1,25 +1,13 @@
1
1
  // Codex親の配送は公式queueを使う。旧中継の選択はsetupで移行するまで維持する。
2
- import { spawn } from "node:child_process";
3
- import { createInterface } from "node:readline";
4
- import * as path from "node:path";
5
- import { resolveAgentBin } from "./agent-resolver.js";
6
- import { realCodexHome } from "./harnesses/codex.js";
2
+ // 公式queueの配送はaiterm-steer-deliveryが持ち、旧中継(relay)だけをAitermに残す。
3
+ import * as steer from "aiterm-steer-delivery";
7
4
  import { CodexDeliveryError } from "./codex-delivery-error.js";
8
5
  import { readRelayConfig, parentRelaySocket } from "./codex-relay-config.js";
9
6
  import { withCodexRelay, verifyLoadedParent } from "./codex-relay-client.js";
10
- import { currentCodexDesktopBinary } from "./codex-desktop-binary.js";
11
- import { finishCodexHookSubmission, assertCodexHookParentCurrent, assertCodexHooksReady, codexHookDirectory, readCodexHookConfig, registerCodexHookInput } from "./codex-hook-state.js";
12
- export { CodexDeliveryError } from "./codex-delivery-error.js";
13
- /** modelの引数ではなく、CodexがMCP要求へ付けるmetadataだけを宛先にする。 */
14
- export function codexParentFromRequest(clientName, metadata) {
15
- if (clientName !== "codex-mcp-client")
16
- return null;
17
- const threadId = metadata?.threadId;
18
- if (typeof threadId !== "string" || !/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(threadId)) {
19
- throw new CodexDeliveryError("CODEX_PARENT_ID_UNAVAILABLE", "MCP要求に親のthreadIdがありません。対応するCodexへ更新してください");
20
- }
21
- return { thread_id: threadId, codex_home: path.resolve(realCodexHome()) };
22
- }
7
+ import { readCodexHookConfig } from "./codex-hook-state.js";
8
+ import { AITERM_PROFILE } from "./steer-profile.js";
9
+ export { CodexDeliveryError };
10
+ export const codexParentFromRequest = steer.codexParentFromRequest;
23
11
  function relaySocket(runtime) {
24
12
  if (runtime)
25
13
  return runtime.socket_path ?? null;
@@ -29,87 +17,7 @@ function relaySocket(runtime) {
29
17
  return config?.enabled ? parentRelaySocket(config) : null;
30
18
  }
31
19
  export async function withCodexReceiver(parent, action, runtime = {}) {
32
- const config = runtime.executable ? null : readCodexHookConfig();
33
- const executable = runtime.executable ?? (config?.enabled ? await currentCodexDesktopBinary(config) : null) ?? resolveAgentBin("codex");
34
- if (!executable)
35
- throw new CodexDeliveryError("CODEX_RECEIVER_UNAVAILABLE", "Codexの実行ファイルを確認できません");
36
- const child = spawn(executable, runtime.args ?? ["app-server", "--listen", "stdio://"], {
37
- stdio: ["pipe", "pipe", "ignore"],
38
- env: { ...process.env, CODEX_HOME: parent.codex_home },
39
- windowsHide: true,
40
- });
41
- const pending = new Map();
42
- let sequence = 0;
43
- let stopped = false;
44
- let transportError = null;
45
- const failTransport = (message) => {
46
- transportError = message;
47
- for (const item of pending.values()) {
48
- clearTimeout(item.timer);
49
- item.reject(new CodexDeliveryError("CODEX_RECEIVER_TRANSPORT_FAILED", message, item.method === "thread/queue/add"));
50
- }
51
- pending.clear();
52
- };
53
- child.on("error", (error) => failTransport(`Codexを起動できません(${error.code ?? "unknown"})`));
54
- child.stdin.on("error", (error) => failTransport(`Codexへの書込みに失敗しました(${error.code ?? "unknown"})`));
55
- const exited = new Promise((resolve) => child.once("close", (code, signal) => {
56
- if (!stopped)
57
- failTransport(`Codexの接続が終了しました(exit=${code}, signal=${signal})`);
58
- resolve();
59
- }));
60
- const reader = createInterface({ input: child.stdout });
61
- reader.on("line", (line) => {
62
- let value;
63
- try {
64
- value = JSON.parse(line);
65
- }
66
- catch {
67
- failTransport("Codexが不正なJSON応答を返しました");
68
- return;
69
- }
70
- const item = pending.get(value?.id);
71
- if (!item)
72
- return;
73
- clearTimeout(item.timer);
74
- pending.delete(value.id);
75
- if (value.error) {
76
- item.reject(new CodexDeliveryError("CODEX_RECEIVER_REJECTED", typeof value.error.message === "string" ? value.error.message : "公式受信口が要求を拒否しました"));
77
- }
78
- else if ("result" in value)
79
- item.resolve(value.result);
80
- else
81
- item.reject(new CodexDeliveryError("CODEX_RECEIVER_INVALID_RESPONSE", "公式受信口の応答にresultがありません", item.method === "thread/queue/add"));
82
- });
83
- const request = (method, params) => new Promise((resolve, reject) => {
84
- if (transportError) {
85
- reject(new CodexDeliveryError("CODEX_RECEIVER_TRANSPORT_FAILED", transportError));
86
- return;
87
- }
88
- const id = ++sequence;
89
- const timer = setTimeout(() => {
90
- pending.delete(id);
91
- reject(new CodexDeliveryError("CODEX_RECEIVER_TIMEOUT", `${method}の応答を確認できません`, method === "thread/queue/add"));
92
- }, runtime.timeout_ms ?? 15_000);
93
- pending.set(id, { resolve, reject, timer, method });
94
- child.stdin.write(JSON.stringify({ id, method, params }) + "\n");
95
- });
96
- try {
97
- await request("initialize", { clientInfo: { name: "aiterm_parent_delivery", version: "1" }, capabilities: { experimentalApi: true } });
98
- child.stdin.write(JSON.stringify({ method: "initialized" }) + "\n");
99
- return await action(request);
100
- }
101
- finally {
102
- stopped = true;
103
- for (const item of pending.values())
104
- clearTimeout(item.timer);
105
- pending.clear();
106
- child.stdin.end();
107
- // stdio終了を公式processへ伝える。終了しない外部processだけを明示的に停止する。
108
- const terminate = setTimeout(() => child.kill("SIGKILL"), 2_000);
109
- await exited;
110
- clearTimeout(terminate);
111
- reader.close();
112
- }
20
+ return steer.withCodexReceiver(AITERM_PROFILE, parent, action, runtime);
113
21
  }
114
22
  /** 子へ送る前に、同じstoreの宛先と公式キューの対応を確認する。本文は保存・表示しない。 */
115
23
  export async function verifyCodexParent(parent, runtime) {
@@ -118,22 +26,7 @@ export async function verifyCodexParent(parent, runtime) {
118
26
  await withCodexRelay(socket, request => verifyLoadedParent(request, parent.thread_id), runtime?.timeout_ms);
119
27
  return;
120
28
  }
121
- await withCodexReceiver(parent, async (request) => {
122
- const response = await request("thread/read", { threadId: parent.thread_id, includeTurns: false });
123
- if (response?.thread?.id !== parent.thread_id) {
124
- throw new CodexDeliveryError("CODEX_PARENT_UNAVAILABLE", "同じCodex環境で親threadを確認できません");
125
- }
126
- const subagent = response.thread.source?.subAgent;
127
- if (subagent && typeof subagent === "object" && "thread_spawn" in subagent) {
128
- throw new CodexDeliveryError("CODEX_PARENT_UNSUPPORTED", "Codexのnative sub-agentは外部processからのキュー入力を受け付けません");
129
- }
130
- await request("thread/queue/list", { threadId: parent.thread_id, limit: 1 });
131
- const config = runtime ? (runtime.hook_directory ? readCodexHookConfig(runtime.hook_directory) : null) : readCodexHookConfig();
132
- if (config?.enabled) {
133
- assertCodexHookParentCurrent(config);
134
- assertCodexHooksReady(await request("hooks/list", { cwds: [response.thread.cwd] }), config.command, path.join(parent.codex_home, "hooks.json"));
135
- }
136
- }, runtime);
29
+ await steer.verifyCodexParent(AITERM_PROFILE, parent, runtime);
137
30
  }
138
31
  export async function submitCodexParentAnswer(parent, deliveryId, text, runtime) {
139
32
  const socket = relaySocket(runtime);
@@ -149,25 +42,5 @@ export async function submitCodexParentAnswer(parent, deliveryId, text, runtime)
149
42
  return { queued_submission_id: null };
150
43
  }, runtime?.timeout_ms);
151
44
  }
152
- const root = runtime ? runtime.hook_directory : codexHookDirectory();
153
- const hook = root && readCodexHookConfig(root)?.enabled;
154
- if (hook)
155
- registerCodexHookInput(parent.codex_home, parent.thread_id, deliveryId, text, root);
156
- try {
157
- return await withCodexReceiver(parent, async (request) => {
158
- const result = await request("thread/queue/add", {
159
- threadId: parent.thread_id,
160
- input: [{ type: "text", text, text_elements: [] }],
161
- clientUserMessageId: deliveryId,
162
- });
163
- if (typeof result?.queuedSubmission?.id !== "string") {
164
- throw new CodexDeliveryError("CODEX_RECEIVER_INVALID_RESPONSE", "キューの受付IDを確認できません", true);
165
- }
166
- return { queued_submission_id: result.queuedSubmission.id };
167
- }, runtime);
168
- }
169
- finally {
170
- if (hook)
171
- finishCodexHookSubmission(parent.codex_home, parent.thread_id, deliveryId, root);
172
- }
45
+ return steer.submitCodexParentAnswer(AITERM_PROFILE, parent, deliveryId, text, runtime);
173
46
  }
@@ -1,35 +1,6 @@
1
1
  #!/usr/bin/env node
2
- // Cursorが起動する公式hook。MCP stdioとは別processで、差し込み文をstdoutのJSONへ返す。
3
- import { fileURLToPath } from "node:url";
4
- import * as fs from "node:fs";
5
- import { handleCursorHook } from "./cursor-parent-receiver.js";
6
- async function main() {
7
- let input = "";
8
- process.stdin.setEncoding("utf8");
9
- for await (const chunk of process.stdin)
10
- input += chunk;
11
- const result = await handleCursorHook(input.length > 0 ? input : "{}");
12
- process.stdout.write(`${JSON.stringify(result)}\n`);
13
- }
14
- function isDirectExecution() {
15
- const entry = process.argv[1];
16
- if (!entry)
17
- return false;
18
- try {
19
- const self = fileURLToPath(import.meta.url);
20
- const a = fs.realpathSync(entry);
21
- const b = fs.realpathSync(self);
22
- if (a === b)
23
- return true;
24
- return process.platform === "win32" && a.toLowerCase() === b.toLowerCase();
25
- }
26
- catch {
27
- return false;
28
- }
29
- }
30
- if (isDirectExecution()) {
31
- main().catch(error => {
32
- process.stderr.write(`${error instanceof Error ? error.message : "CURSOR_PARENT_HOOK_FAILED"}\n`);
33
- process.exitCode = 2;
34
- });
35
- }
2
+ // Cursorが起動する公式hook。MCP stdioとは別processで、差し込み文をstdoutのJSONへ返す。本体はaiterm-steer-delivery。
3
+ import { isDirectExecution, runCursorHookMain } from "aiterm-steer-delivery";
4
+ import { AITERM_PROFILE } from "./steer-profile.js";
5
+ if (isDirectExecution(import.meta.url))
6
+ await runCursorHookMain(AITERM_PROFILE);
@@ -1,56 +1,17 @@
1
1
  #!/usr/bin/env node
2
- // Cursor親がidleのとき、背景で起動して回答本文の到着を待つ受け口。
2
+ // Cursor親がidleのとき、背景で起動して回答本文の到着を待つ受け口。本体はaiterm-steer-delivery。
3
3
  import { fileURLToPath } from "node:url";
4
- import * as fs from "node:fs";
5
- import { cursorHookRoot, receiveCursorAnswer } from "./cursor-parent-receiver.js";
6
- import { windowsStartProcessArgumentList } from "./core.js";
4
+ import * as steer from "aiterm-steer-delivery";
5
+ import { cursorHookRoot } from "./cursor-parent-receiver.js";
7
6
  export function cursorReceiveProcess(deliveryId, executable = process.execPath) {
8
- const args = [fileURLToPath(import.meta.url), "--delivery", deliveryId];
9
- return {
10
- executable,
11
- args,
12
- windows_start_process_argument_list: process.platform === "win32" ? windowsStartProcessArgumentList(args) : null,
13
- };
14
- }
15
- function emit(value) {
16
- process.stdout.write(JSON.stringify(value) + "\n");
7
+ return steer.cursorReceiveProcess(fileURLToPath(import.meta.url), deliveryId, executable);
17
8
  }
18
9
  export async function main(argv) {
19
- if (argv.length !== 2 || argv[0] !== "--delivery" || !/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(argv[1])) {
20
- emit({ ok: false, code: "CURSOR_PARENT_RECEIVE_USAGE", message: "usage: cursor-parent-receive --delivery <uuid>" });
21
- return 1;
22
- }
23
- const result = await receiveCursorAnswer(cursorHookRoot(), argv[1]);
24
- if (result.outcome === "timeout") {
25
- emit({ delivery_id: argv[1], outcome: "timeout" });
26
- return 3;
27
- }
28
- if (result.outcome === "delivered_by_hook") {
29
- emit({ delivery_id: argv[1], outcome: "delivered_by_hook" });
30
- return 0;
31
- }
32
- emit({ delivery_id: argv[1], outcome: "delivered", text: result.text });
33
- return 0;
34
- }
35
- function isDirectExecution() {
36
- const entry = process.argv[1];
37
- if (!entry)
38
- return false;
39
- try {
40
- const self = fileURLToPath(import.meta.url);
41
- const a = fs.realpathSync(entry);
42
- const b = fs.realpathSync(self);
43
- if (a === b)
44
- return true;
45
- return process.platform === "win32" && a.toLowerCase() === b.toLowerCase();
46
- }
47
- catch {
48
- return false;
49
- }
10
+ return steer.runCursorReceive(cursorHookRoot(), argv);
50
11
  }
51
- if (isDirectExecution()) {
12
+ if (steer.isDirectExecution(import.meta.url)) {
52
13
  main(process.argv.slice(2)).then(code => { process.exitCode = code; }).catch(error => {
53
- emit({ ok: false, code: "CURSOR_PARENT_RECEIVE_FAILED", message: error instanceof Error ? error.message : "cursor-parent-receive: operation failed" });
14
+ process.stdout.write(JSON.stringify({ ok: false, code: "CURSOR_PARENT_RECEIVE_FAILED", message: error instanceof Error ? error.message : "cursor-parent-receive: operation failed" }) + "\n");
54
15
  process.exitCode = 1;
55
16
  });
56
17
  }
@@ -1,255 +1,24 @@
1
1
  // Cursor親の受信口。会話への差し込みはCursor公式hookのadditional_context、idle時の起床は受け口processが担う。
2
- import * as fs from "node:fs";
3
- import { homedir } from "node:os";
2
+ // 本体はaiterm-steer-delivery。
4
3
  import * as path from "node:path";
5
4
  import { z } from "zod";
6
- import { ensureStateRoot, waitForFileState, writeJson0600 } from "./agent-shared.js";
7
- import { AitermError } from "./errors.js";
8
- export const cursorParentSchema = z.object({
9
- kind: z.literal("cursor"),
10
- hook_root: z.string(),
11
- }).strict();
12
- const deliveryIdSchema = z.string().uuid();
13
- const dispatchTools = new Set([
14
- "agent_launch", "claude_agent", "codex_agent", "grok_agent", "pty_send", "claude_turn",
15
- ]);
16
- export class CursorDeliveryError extends AitermError {
17
- delivery_code;
18
- outcome_unknown;
19
- constructor(delivery_code, message, outcome_unknown = false) {
20
- super(`${delivery_code}: ${message}`, 2);
21
- this.delivery_code = delivery_code;
22
- this.outcome_unknown = outcome_unknown;
23
- }
24
- }
25
- export function isCursorMcpClient(clientName) {
26
- if (clientName === undefined)
27
- return false;
28
- return clientName === "cursor-vscode" || clientName.startsWith("cursor-vscode ");
29
- }
5
+ import * as steer from "aiterm-steer-delivery";
6
+ import { ensureStateRoot } from "./agent-shared.js";
7
+ import { AITERM_PROFILE } from "./steer-profile.js";
8
+ // 配送記録の検証はAitermのzodで行う(パッケージのschemaと同じ形)。
9
+ export const cursorParentSchema = z.object({ kind: z.literal("cursor"), hook_root: z.string() }).strict();
10
+ export const CursorDeliveryError = steer.CursorDeliveryError;
11
+ export const { isCursorMcpClient, cursorHooksFile, prepareCursorDelivery, submitCursorParentAnswer, receiveCursorAnswer } = steer;
30
12
  export function cursorHookRoot(state = ensureStateRoot()) {
31
13
  return path.join(state, "cursor-parent-hooks");
32
14
  }
33
- export function cursorHooksFile(home = process.env.HOME ?? homedir()) {
34
- return path.join(process.env.CURSOR_HOME ?? path.join(home, ".cursor"), "hooks.json");
35
- }
36
- export function cursorParentHooksRegistered(document) {
37
- if (document === null || typeof document !== "object" || Array.isArray(document))
38
- return false;
39
- const hooks = document.hooks;
40
- if (hooks === null || typeof hooks !== "object" || Array.isArray(hooks))
41
- return false;
42
- const owns = (event) => {
43
- const list = hooks[event];
44
- return Array.isArray(list) && list.some(entry => {
45
- if (entry === null || typeof entry !== "object")
46
- return false;
47
- const command = entry.command;
48
- return typeof command === "string" && command.includes("cursor-parent-hook.js");
49
- });
50
- };
51
- return owns("postToolUse") && owns("afterMCPExecution");
52
- }
53
- export function verifyCursorParent(parent, hooksFile = cursorHooksFile()) {
54
- cursorParentSchema.parse(parent);
55
- let document;
56
- try {
57
- document = JSON.parse(fs.readFileSync(hooksFile, "utf8"));
58
- }
59
- catch {
60
- throw new CursorDeliveryError("CURSOR_PARENT_HOOK_UNAVAILABLE", "Cursorのhookが登録されていません。aiterm-setupを実行してください");
61
- }
62
- if (!cursorParentHooksRegistered(document)) {
63
- throw new CursorDeliveryError("CURSOR_PARENT_HOOK_UNAVAILABLE", "Cursorのhookが登録されていません。aiterm-setupを実行してください");
64
- }
65
- }
15
+ export function cursorParentHooksRegistered(document) { return steer.cursorParentHooksRegistered(AITERM_PROFILE, document); }
16
+ export function verifyCursorParent(parent, hooksFile = cursorHooksFile()) { steer.verifyCursorParent(AITERM_PROFILE, parent, hooksFile); }
66
17
  export function cursorParentFromRequest(clientName, options = {}) {
67
18
  if (!isCursorMcpClient(clientName))
68
19
  return null;
69
- const parent = { kind: "cursor", hook_root: options.hookRoot ?? cursorHookRoot() };
70
- verifyCursorParent(parent, options.hooksFile);
71
- return parent;
72
- }
73
- function deliveryDir(parent, deliveryId) {
74
- return path.join(parent.hook_root, "deliveries", deliveryIdSchema.parse(deliveryId));
75
- }
76
- export function prepareCursorDelivery(parent, deliveryId) {
77
- cursorParentSchema.parse(parent);
78
- fs.mkdirSync(deliveryDir(parent, deliveryId), { recursive: true, mode: 0o700 });
79
- }
80
- export async function submitCursorParentAnswer(parent, deliveryId, text, timeoutMs = 86_400_000) {
81
- const dir = deliveryDir(parent, deliveryId);
82
- writeJson0600(path.join(dir, "answer.json"), { delivery_id: deliveryId, text });
83
- try {
84
- await waitForFileState(dir, () => fs.existsSync(path.join(dir, "claim.json")) ? true : undefined, timeoutMs);
85
- }
86
- catch (error) {
87
- if (error instanceof Error && error.message === "WAIT_FOR_FILE_TIMEOUT") {
88
- throw new CursorDeliveryError("CURSOR_PARENT_DELIVERY_UNCLAIMED", "Cursor親が時間内に回答を受け取りませんでした。回答は保存したままです");
89
- }
90
- throw error;
91
- }
92
- return { queued_submission_id: null };
93
- }
94
- function conversationId(value) {
95
- if (typeof value !== "string" || value.length === 0 || value.length > 200)
96
- return null;
97
- if (value.includes("/") || value.includes("\\") || value.includes(".."))
98
- return null;
99
- return value;
100
- }
101
- function parseJson(value) {
102
- try {
103
- return JSON.parse(value);
104
- }
105
- catch {
106
- return null;
107
- }
108
- }
109
- function deliveryIdFromResult(value, depth = 0) {
110
- if (depth > 3 || value === null || value === undefined)
111
- return null;
112
- const parsed = typeof value === "string" ? parseJson(value) : value;
113
- if (parsed === null || typeof parsed !== "object")
114
- return null;
115
- const record = parsed;
116
- const structured = record.structuredContent;
117
- const carrier = structured !== null && typeof structured === "object" ? structured : record;
118
- const parentDelivery = carrier.parent_delivery;
119
- if (parentDelivery !== null && typeof parentDelivery === "object") {
120
- const id = parentDelivery.delivery_id;
121
- if (typeof id === "string" && deliveryIdSchema.safeParse(id).success)
122
- return id;
123
- }
124
- if (depth === 0 && Array.isArray(record.content)) {
125
- for (const entry of record.content) {
126
- if (entry !== null && typeof entry === "object" && entry.type === "text") {
127
- const text = entry.text;
128
- if (typeof text === "string") {
129
- const id = deliveryIdFromResult(text, depth + 1) ?? deliveryIdInText(text);
130
- if (id)
131
- return id;
132
- }
133
- }
134
- }
135
- }
136
- return null;
137
- }
138
- function deliveryIdInText(text) {
139
- for (const line of text.split("\n")) {
140
- const match = /^delivery_id=([0-9a-f-]{36})$/i.exec(line.trim());
141
- if (match && deliveryIdSchema.safeParse(match[1]).success)
142
- return match[1];
143
- }
144
- return null;
145
- }
146
- function toolBaseName(name) {
147
- const tail = name.split(/[:.]/).pop() ?? name;
148
- return tail.replace(/^mcp__aiterm__/, "");
149
- }
150
- function claim(dir, channel) {
151
- try {
152
- fs.writeFileSync(path.join(dir, "claim.json"), JSON.stringify({ channel, at: new Date().toISOString() }), { flag: "wx", mode: 0o600 });
153
- return true;
154
- }
155
- catch (error) {
156
- if (error.code === "EEXIST")
157
- return false;
158
- throw error;
159
- }
160
- }
161
- function bind(hookRoot, deliveryId, conv) {
162
- const dir = path.join(hookRoot, "deliveries", deliveryId);
163
- if (!fs.existsSync(dir))
164
- return;
165
- const bindFile = path.join(dir, "bind.json");
166
- if (!fs.existsSync(bindFile)) {
167
- writeJson0600(bindFile, { conversation_id: conv, created_at: new Date().toISOString() });
168
- }
169
- const indexDir = path.join(hookRoot, "conversations", conv);
170
- fs.mkdirSync(indexDir, { recursive: true, mode: 0o700 });
171
- const index = path.join(indexDir, deliveryId);
172
- if (!fs.existsSync(index))
173
- fs.writeFileSync(index, "", { mode: 0o600 });
174
- }
175
- function inject(hookRoot, conv) {
176
- const indexDir = path.join(hookRoot, "conversations", conv);
177
- if (!fs.existsSync(indexDir))
178
- return [];
179
- const pending = fs.readdirSync(indexDir).flatMap(deliveryId => {
180
- const dir = path.join(hookRoot, "deliveries", deliveryId);
181
- const answerFile = path.join(dir, "answer.json");
182
- const bindFile = path.join(dir, "bind.json");
183
- if (!fs.existsSync(answerFile) || !fs.existsSync(bindFile))
184
- return [];
185
- const answer = z.object({ delivery_id: z.string(), text: z.string() }).parse(JSON.parse(fs.readFileSync(answerFile, "utf8")));
186
- const bound = z.object({ conversation_id: z.string(), created_at: z.string() }).parse(JSON.parse(fs.readFileSync(bindFile, "utf8")));
187
- if (answer.delivery_id !== deliveryId || bound.conversation_id !== conv)
188
- return [];
189
- return [{ deliveryId, dir, created_at: bound.created_at, text: answer.text }];
190
- }).sort((a, b) => a.created_at.localeCompare(b.created_at));
191
- const texts = [];
192
- for (const item of pending) {
193
- const index = path.join(indexDir, item.deliveryId);
194
- if (!claim(item.dir, "hook")) {
195
- if (fs.existsSync(path.join(item.dir, "claim.json")))
196
- fs.rmSync(index, { force: true });
197
- continue;
198
- }
199
- texts.push(item.text);
200
- fs.rmSync(index, { force: true });
201
- }
202
- return texts;
203
- }
204
- export async function handleCursorHook(raw, hookRoot = cursorHookRoot()) {
205
- let parsed;
206
- try {
207
- parsed = JSON.parse(raw);
208
- }
209
- catch {
210
- return {};
211
- }
212
- if (parsed === null || typeof parsed !== "object")
213
- return {};
214
- const event = parsed;
215
- const name = event.hook_event_name;
216
- if (name !== "afterMCPExecution" && name !== "postToolUse" && name !== "postToolUseFailure")
217
- return {};
218
- const conv = conversationId(event.conversation_id);
219
- if (!conv)
220
- return {};
221
- const toolName = typeof event.tool_name === "string" ? event.tool_name : "";
222
- if (dispatchTools.has(toolBaseName(toolName))) {
223
- const payload = name === "afterMCPExecution" ? event.result_json : event.tool_output;
224
- const deliveryId = deliveryIdFromResult(payload);
225
- if (deliveryId)
226
- bind(hookRoot, deliveryId, conv);
227
- }
228
- if (name === "postToolUse" || name === "postToolUseFailure") {
229
- const texts = inject(hookRoot, conv);
230
- if (texts.length > 0)
231
- return { additional_context: texts.join("\n\n") };
232
- }
233
- return {};
20
+ return steer.cursorParentFromRequest(AITERM_PROFILE, clientName, { hookRoot: options.hookRoot ?? cursorHookRoot(), hooksFile: options.hooksFile });
234
21
  }
235
- export async function receiveCursorAnswer(hookRoot, deliveryId, timeoutMs = 86_400_000) {
236
- const id = deliveryIdSchema.parse(deliveryId);
237
- const dir = path.join(hookRoot, "deliveries", id);
238
- if (!fs.existsSync(dir))
239
- throw new CursorDeliveryError("CURSOR_PARENT_DELIVERY_UNKNOWN", "Cursor配送の記録がありません");
240
- const answerFile = path.join(dir, "answer.json");
241
- try {
242
- await waitForFileState(dir, () => fs.existsSync(answerFile) ? true : undefined, timeoutMs);
243
- }
244
- catch (error) {
245
- if (error instanceof Error && error.message === "WAIT_FOR_FILE_TIMEOUT")
246
- return { outcome: "timeout" };
247
- throw error;
248
- }
249
- const answer = z.object({ delivery_id: z.string(), text: z.string() }).parse(JSON.parse(fs.readFileSync(answerFile, "utf8")));
250
- if (answer.delivery_id !== id)
251
- throw new CursorDeliveryError("CURSOR_PARENT_DELIVERY_MISMATCH", "保存された回答の配送IDが一致しません");
252
- if (!claim(dir, "receiver"))
253
- return { outcome: "delivered_by_hook" };
254
- return { outcome: "delivered", text: answer.text };
22
+ export function handleCursorHook(raw, hookRoot = cursorHookRoot()) {
23
+ return steer.handleCursorHook(AITERM_PROFILE, raw, hookRoot);
255
24
  }