@cjhyy/code-shell-core 0.7.0 → 0.7.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/dist/arena/arena.d.ts +2 -0
- package/dist/arena/arena.js +30 -1
- package/dist/arena/phases/adjudication.d.ts +2 -1
- package/dist/arena/phases/adjudication.js +2 -1
- package/dist/arena/phases/build-consensus.d.ts +2 -1
- package/dist/arena/phases/build-consensus.js +3 -1
- package/dist/arena/phases/cross-review.d.ts +3 -1
- package/dist/arena/phases/cross-review.js +8 -2
- package/dist/arena/phases/debate-rounds.d.ts +2 -1
- package/dist/arena/phases/debate-rounds.js +4 -2
- package/dist/arena/phases/participant-research.d.ts +2 -1
- package/dist/arena/phases/participant-research.js +3 -1
- package/dist/arena/phases/planning-detail-expansion.d.ts +2 -1
- package/dist/arena/phases/planning-detail-expansion.js +2 -1
- package/dist/arena/planner.d.ts +2 -1
- package/dist/arena/planner.js +2 -1
- package/dist/arena/types.d.ts +5 -1
- package/dist/cli/agent-server-tcp.js +13 -2
- package/dist/context/manager.d.ts +3 -3
- package/dist/context/manager.js +6 -6
- package/dist/context/token-counter.js +5 -3
- package/dist/context/tool-result-storage.d.ts +6 -0
- package/dist/context/tool-result-storage.js +25 -4
- package/dist/credentials/access.d.ts +10 -1
- package/dist/credentials/access.js +76 -2
- package/dist/credentials/index.d.ts +3 -2
- package/dist/credentials/index.js +2 -1
- package/dist/credentials/oauth.d.ts +6 -1
- package/dist/credentials/oauth.js +66 -1
- package/dist/credentials/store.d.ts +1 -1
- package/dist/credentials/store.js +16 -7
- package/dist/credentials/types.d.ts +38 -1
- package/dist/credentials/types.js +16 -1
- package/dist/engine/engine.d.ts +61 -32
- package/dist/engine/engine.js +289 -55
- package/dist/engine/goal.d.ts +5 -3
- package/dist/engine/session-title.d.ts +2 -1
- package/dist/engine/session-title.js +4 -1
- package/dist/engine/turn-loop.d.ts +18 -0
- package/dist/engine/turn-loop.js +202 -27
- package/dist/engine/types.d.ts +6 -2
- package/dist/git/worktree/crud.d.ts +3 -0
- package/dist/git/worktree/crud.js +32 -3
- package/dist/git/worktree/git-exec.d.ts +2 -2
- package/dist/git/worktree/git-exec.js +47 -11
- package/dist/git/worktree/query.d.ts +8 -7
- package/dist/git/worktree/query.js +27 -20
- package/dist/hooks/events.d.ts +3 -0
- package/dist/hooks/events.js +0 -3
- package/dist/hooks/goal-stop-hook.d.ts +14 -4
- package/dist/hooks/goal-stop-hook.js +626 -71
- package/dist/hooks/registry.js +3 -0
- package/dist/hooks/shell-runner.d.ts +12 -1
- package/dist/hooks/shell-runner.js +160 -9
- package/dist/index.d.ts +7 -6
- package/dist/index.js +6 -5
- package/dist/llm/client-base.js +12 -10
- package/dist/llm/types.d.ts +12 -5
- package/dist/plugins/pluginCommandHook.d.ts +4 -4
- package/dist/plugins/pluginCommandHook.js +111 -13
- package/dist/protocol/chat-session-manager.d.ts +13 -2
- package/dist/protocol/chat-session-manager.js +90 -18
- package/dist/protocol/chat-session.d.ts +12 -0
- package/dist/protocol/chat-session.js +30 -5
- package/dist/protocol/client.d.ts +5 -2
- package/dist/protocol/client.js +22 -1
- package/dist/protocol/server.d.ts +25 -11
- package/dist/protocol/server.js +290 -72
- package/dist/protocol/types.d.ts +34 -2
- package/dist/protocol/types.js +2 -0
- package/dist/services/dream-consolidation.d.ts +3 -0
- package/dist/services/dream-consolidation.js +4 -1
- package/dist/services/index.d.ts +1 -1
- package/dist/services/index.js +1 -1
- package/dist/services/oauth.d.ts +34 -10
- package/dist/services/oauth.js +233 -98
- package/dist/session/session-manager.d.ts +35 -6
- package/dist/session/session-manager.js +362 -26
- package/dist/session/transcript.d.ts +30 -1
- package/dist/session/transcript.js +119 -4
- package/dist/tool-system/builtin/agent.js +5 -1
- package/dist/tool-system/builtin/arena.js +1 -0
- package/dist/tool-system/builtin/background-jobs.d.ts +8 -5
- package/dist/tool-system/builtin/background-jobs.js +15 -11
- package/dist/tool-system/builtin/background-work.d.ts +6 -1
- package/dist/tool-system/builtin/background-work.js +5 -1
- package/dist/tool-system/builtin/bash.d.ts +3 -5
- package/dist/tool-system/builtin/bash.js +10 -5
- package/dist/tool-system/builtin/browser-tools.d.ts +2 -2
- package/dist/tool-system/builtin/edit.d.ts +2 -1
- package/dist/tool-system/builtin/edit.js +12 -4
- package/dist/tool-system/builtin/generate-video.d.ts +4 -0
- package/dist/tool-system/builtin/generate-video.js +138 -21
- package/dist/tool-system/builtin/glob.d.ts +2 -1
- package/dist/tool-system/builtin/glob.js +28 -3
- package/dist/tool-system/builtin/grep.d.ts +1 -0
- package/dist/tool-system/builtin/grep.js +82 -17
- package/dist/tool-system/builtin/index.d.ts +25 -11
- package/dist/tool-system/builtin/index.js +45 -2
- package/dist/tool-system/builtin/video-providers.d.ts +12 -15
- package/dist/tool-system/builtin/video-providers.js +1 -0
- package/dist/tool-system/builtin/view-image.d.ts +2 -2
- package/dist/tool-system/builtin/web-fetch.js +44 -3
- package/dist/tool-system/builtin/worktree.js +25 -7
- package/dist/tool-system/builtin/write.d.ts +2 -1
- package/dist/tool-system/builtin/write.js +14 -4
- package/dist/tool-system/context.d.ts +17 -5
- package/dist/tool-system/executor.js +24 -8
- package/dist/tool-system/mcp-manager.d.ts +6 -0
- package/dist/tool-system/mcp-manager.js +55 -5
- package/dist/tool-system/path-policy.d.ts +19 -0
- package/dist/tool-system/path-policy.js +62 -1
- package/dist/tool-system/permission.d.ts +43 -3
- package/dist/tool-system/permission.js +383 -30
- package/dist/tool-system/registry.d.ts +3 -2
- package/dist/tool-system/registry.js +52 -34
- package/dist/types.d.ts +15 -0
- package/package.json +1 -1
package/dist/hooks/registry.js
CHANGED
|
@@ -101,6 +101,9 @@ export class HookRegistry {
|
|
|
101
101
|
if (result.continueSession) {
|
|
102
102
|
aggregated.continueSession = true;
|
|
103
103
|
}
|
|
104
|
+
if (result.goalTermination !== undefined) {
|
|
105
|
+
aggregated.goalTermination = result.goalTermination;
|
|
106
|
+
}
|
|
104
107
|
if (result.stop) {
|
|
105
108
|
aggregated.stop = true;
|
|
106
109
|
break;
|
|
@@ -28,16 +28,27 @@
|
|
|
28
28
|
* timeout Defaults to 60_000 ms; settings.timeout_ms overrides.
|
|
29
29
|
* On timeout we SIGTERM then SIGKILL, log, and return {}.
|
|
30
30
|
*/
|
|
31
|
+
import type { Writable } from "node:stream";
|
|
31
32
|
import type { HookContext, HookResult } from "./events.js";
|
|
32
33
|
import type { SettingsHookConfig } from "../types.js";
|
|
33
34
|
import { validateHookResult } from "./hook-output.js";
|
|
34
35
|
export { validateHookResult };
|
|
36
|
+
export declare const MAX_HOOK_STDIN_BYTES: number;
|
|
37
|
+
/**
|
|
38
|
+
* Write one hook envelope without leaving a Writable `error` event unhandled.
|
|
39
|
+
* The listener is installed before write(), remains through end(), and the
|
|
40
|
+
* promise does not resolve until both the write callback and any required
|
|
41
|
+
* drain have completed.
|
|
42
|
+
*/
|
|
43
|
+
export declare function writeHookStdin(stdin: Writable | null | undefined, envelope: string): Promise<void>;
|
|
44
|
+
/** Close stdin for an oversized envelope while still absorbing stream errors. */
|
|
45
|
+
export declare function closeHookStdin(stdin: Writable | null | undefined): void;
|
|
35
46
|
/**
|
|
36
47
|
* Run one shell-hook command and return the parsed HookResult. Catches
|
|
37
48
|
* every failure mode (spawn error, timeout, malformed JSON) and
|
|
38
49
|
* returns an empty result so the registry's chain keeps going.
|
|
39
50
|
*/
|
|
40
|
-
export declare function runShellHook(config: SettingsHookConfig, ctx: HookContext): Promise<HookResult>;
|
|
51
|
+
export declare function runShellHook(config: SettingsHookConfig, ctx: HookContext, abortSignal?: AbortSignal): Promise<HookResult>;
|
|
41
52
|
/**
|
|
42
53
|
* Check whether the hook should fire for the current ctx, respecting
|
|
43
54
|
* the optional `matcher` regex. Returns false for non-tool events
|
|
@@ -35,13 +35,105 @@ import { killChildTree } from "../runtime/spawn-common.js";
|
|
|
35
35
|
// "./shell-runner.js" (e.g. tests) keep compiling.
|
|
36
36
|
export { validateHookResult };
|
|
37
37
|
const DEFAULT_TIMEOUT_MS = 60_000;
|
|
38
|
+
export const MAX_HOOK_STDIN_BYTES = 256 * 1024;
|
|
39
|
+
function stdinFailure(error) {
|
|
40
|
+
const err = error;
|
|
41
|
+
return {
|
|
42
|
+
type: "stdin_error",
|
|
43
|
+
code: typeof err?.code === "string" ? err.code : "UNKNOWN",
|
|
44
|
+
message: err instanceof Error ? err.message : String(error),
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Write one hook envelope without leaving a Writable `error` event unhandled.
|
|
49
|
+
* The listener is installed before write(), remains through end(), and the
|
|
50
|
+
* promise does not resolve until both the write callback and any required
|
|
51
|
+
* drain have completed.
|
|
52
|
+
*/
|
|
53
|
+
export function writeHookStdin(stdin, envelope) {
|
|
54
|
+
if (!stdin)
|
|
55
|
+
return Promise.resolve();
|
|
56
|
+
return new Promise((resolve, reject) => {
|
|
57
|
+
let settled = false;
|
|
58
|
+
let writeComplete = false;
|
|
59
|
+
let drained = false;
|
|
60
|
+
let ending = false;
|
|
61
|
+
const cleanup = (removeErrorListener = true) => {
|
|
62
|
+
if (removeErrorListener)
|
|
63
|
+
stdin.off("error", onError);
|
|
64
|
+
stdin.off("drain", onDrain);
|
|
65
|
+
};
|
|
66
|
+
const finish = (error, keepErrorListener = false) => {
|
|
67
|
+
if (settled)
|
|
68
|
+
return;
|
|
69
|
+
settled = true;
|
|
70
|
+
cleanup(!keepErrorListener);
|
|
71
|
+
if (error !== undefined)
|
|
72
|
+
reject(error);
|
|
73
|
+
else
|
|
74
|
+
resolve();
|
|
75
|
+
};
|
|
76
|
+
const onError = (error) => finish(error);
|
|
77
|
+
const onDrain = () => {
|
|
78
|
+
drained = true;
|
|
79
|
+
maybeEnd();
|
|
80
|
+
};
|
|
81
|
+
const maybeEnd = () => {
|
|
82
|
+
if (!writeComplete || !drained || ending || settled)
|
|
83
|
+
return;
|
|
84
|
+
ending = true;
|
|
85
|
+
try {
|
|
86
|
+
stdin.end(() => finish());
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
finish(error, true);
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
// EventEmitter treats an unhandled stream `error` as fatal. Register the
|
|
93
|
+
// one-shot handler before the first byte is handed to the child.
|
|
94
|
+
stdin.once("error", onError);
|
|
95
|
+
try {
|
|
96
|
+
drained = stdin.write(envelope, (error) => {
|
|
97
|
+
if (error) {
|
|
98
|
+
// Some Writable implementations invoke the callback before emitting
|
|
99
|
+
// their fatal `error`. Keep the one-shot guard installed for it.
|
|
100
|
+
finish(error, true);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
writeComplete = true;
|
|
104
|
+
maybeEnd();
|
|
105
|
+
});
|
|
106
|
+
if (!drained)
|
|
107
|
+
stdin.once("drain", onDrain);
|
|
108
|
+
maybeEnd();
|
|
109
|
+
}
|
|
110
|
+
catch (error) {
|
|
111
|
+
finish(error, true);
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
/** Close stdin for an oversized envelope while still absorbing stream errors. */
|
|
116
|
+
export function closeHookStdin(stdin) {
|
|
117
|
+
if (!stdin)
|
|
118
|
+
return;
|
|
119
|
+
stdin.once("error", () => { });
|
|
120
|
+
try {
|
|
121
|
+
stdin.end();
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
// No envelope was written; the hook is still allowed to run without args.
|
|
125
|
+
}
|
|
126
|
+
}
|
|
38
127
|
/**
|
|
39
128
|
* Run one shell-hook command and return the parsed HookResult. Catches
|
|
40
129
|
* every failure mode (spawn error, timeout, malformed JSON) and
|
|
41
130
|
* returns an empty result so the registry's chain keeps going.
|
|
42
131
|
*/
|
|
43
|
-
export async function runShellHook(config, ctx) {
|
|
132
|
+
export async function runShellHook(config, ctx, abortSignal) {
|
|
44
133
|
const timeoutMs = config.timeout_ms ?? DEFAULT_TIMEOUT_MS;
|
|
134
|
+
const signal = abortSignal ?? ctx.data.signal;
|
|
135
|
+
if (signal?.aborted)
|
|
136
|
+
return {};
|
|
45
137
|
return new Promise((resolve) => {
|
|
46
138
|
let child;
|
|
47
139
|
try {
|
|
@@ -64,13 +156,20 @@ export async function runShellHook(config, ctx) {
|
|
|
64
156
|
let stdout = "";
|
|
65
157
|
let stderr = "";
|
|
66
158
|
let settled = false;
|
|
159
|
+
let timer;
|
|
67
160
|
const settle = (value) => {
|
|
68
161
|
if (settled)
|
|
69
162
|
return;
|
|
70
163
|
settled = true;
|
|
164
|
+
clearTimeout(timer);
|
|
165
|
+
signal?.removeEventListener("abort", onAbort);
|
|
71
166
|
resolve(value);
|
|
72
167
|
};
|
|
73
|
-
const
|
|
168
|
+
const onAbort = () => {
|
|
169
|
+
killChildTree(child, 1000);
|
|
170
|
+
settle({});
|
|
171
|
+
};
|
|
172
|
+
timer = setTimeout(() => {
|
|
74
173
|
// eslint-disable-next-line no-console
|
|
75
174
|
console.warn(`[hooks] ${config.event} hook timed out after ${timeoutMs}ms: ${config.command}`);
|
|
76
175
|
// win32: taskkill /T reaps the shell tree; POSIX: SIGTERM → grace →
|
|
@@ -78,6 +177,9 @@ export async function runShellHook(config, ctx) {
|
|
|
78
177
|
killChildTree(child, 1000);
|
|
79
178
|
settle({});
|
|
80
179
|
}, timeoutMs);
|
|
180
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
181
|
+
if (signal?.aborted)
|
|
182
|
+
onAbort();
|
|
81
183
|
// Byte cap: once we cross MAX_HOOK_OUTPUT_BYTES on stdout, kill the
|
|
82
184
|
// child and surface a 'cap exceeded' failure. A misbehaving handler
|
|
83
185
|
// can otherwise hold the engine memory hostage while we accumulate
|
|
@@ -119,8 +221,9 @@ export async function runShellHook(config, ctx) {
|
|
|
119
221
|
console.error(`[hooks] ${config.event} hook failed: ${config.command}`, err.message);
|
|
120
222
|
settle({});
|
|
121
223
|
});
|
|
122
|
-
|
|
123
|
-
|
|
224
|
+
let stdinFinished = false;
|
|
225
|
+
let pendingClose;
|
|
226
|
+
const handleClose = (code) => {
|
|
124
227
|
if (stdoutCapped) {
|
|
125
228
|
// eslint-disable-next-line no-console
|
|
126
229
|
console.error(`[hooks] ${config.event} hook killed due to oversized stdout (> ${MAX_HOOK_OUTPUT_BYTES} bytes)`);
|
|
@@ -166,17 +269,65 @@ export async function runShellHook(config, ctx) {
|
|
|
166
269
|
// eslint-disable-next-line no-console
|
|
167
270
|
console.error(`[hooks] ${config.event} hook exited with code ${code}:`, stderr.slice(0, 500));
|
|
168
271
|
settle({});
|
|
272
|
+
};
|
|
273
|
+
child.on("close", (code) => {
|
|
274
|
+
clearTimeout(timer);
|
|
275
|
+
if (settled)
|
|
276
|
+
return;
|
|
277
|
+
// If close wins the event-loop race, wait for stdin's callback/error so
|
|
278
|
+
// an EPIPE cannot be overwritten by an apparently clean child exit.
|
|
279
|
+
if (!stdinFinished && !stdoutCapped) {
|
|
280
|
+
pendingClose = { code };
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
handleClose(code);
|
|
169
284
|
});
|
|
170
|
-
|
|
285
|
+
const finishStdin = () => {
|
|
286
|
+
stdinFinished = true;
|
|
287
|
+
if (pendingClose && !settled) {
|
|
288
|
+
const { code } = pendingClose;
|
|
289
|
+
pendingClose = undefined;
|
|
290
|
+
handleClose(code);
|
|
291
|
+
}
|
|
292
|
+
};
|
|
293
|
+
let envelope;
|
|
171
294
|
try {
|
|
172
|
-
|
|
173
|
-
child.stdin?.end();
|
|
295
|
+
envelope = JSON.stringify({ eventName: ctx.eventName, data: ctx.data });
|
|
174
296
|
}
|
|
175
|
-
catch (
|
|
297
|
+
catch (error) {
|
|
176
298
|
// eslint-disable-next-line no-console
|
|
177
|
-
console.error(`[hooks] failed to
|
|
299
|
+
console.error(`[hooks] failed to serialize stdin for ${config.event}:`, error instanceof Error ? error.message : String(error));
|
|
178
300
|
settle({});
|
|
301
|
+
return;
|
|
179
302
|
}
|
|
303
|
+
if (Buffer.byteLength(envelope, "utf8") > MAX_HOOK_STDIN_BYTES) {
|
|
304
|
+
// eslint-disable-next-line no-console
|
|
305
|
+
console.warn(`[hooks] ${config.event} hook stdin exceeded ${MAX_HOOK_STDIN_BYTES} bytes; running without envelope`);
|
|
306
|
+
closeHookStdin(child.stdin);
|
|
307
|
+
finishStdin();
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
// Let the spawned shell enter user code before writing. Besides avoiding a
|
|
311
|
+
// needless race, this makes an intentional early stdin close observable as
|
|
312
|
+
// EPIPE/ECONNRESET while the child is still alive.
|
|
313
|
+
setImmediate(() => {
|
|
314
|
+
void (async () => {
|
|
315
|
+
if (settled)
|
|
316
|
+
return;
|
|
317
|
+
try {
|
|
318
|
+
await writeHookStdin(child.stdin, envelope);
|
|
319
|
+
finishStdin();
|
|
320
|
+
}
|
|
321
|
+
catch (error) {
|
|
322
|
+
clearTimeout(timer);
|
|
323
|
+
const failure = stdinFailure(error);
|
|
324
|
+
// eslint-disable-next-line no-console
|
|
325
|
+
console.error(`[hooks] failed to write stdin for ${config.event}:`, failure.message);
|
|
326
|
+
killChildTree(child, 1000);
|
|
327
|
+
settle({ data: { hookFailure: failure } });
|
|
328
|
+
}
|
|
329
|
+
})();
|
|
330
|
+
});
|
|
180
331
|
});
|
|
181
332
|
}
|
|
182
333
|
/**
|
package/dist/index.d.ts
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Public API exports.
|
|
5
5
|
*/
|
|
6
|
-
export declare const VERSION = "0.7.
|
|
7
|
-
export type { Message, ContentBlock, ToolDefinition, ToolCall, ToolResult, RegisteredTool, TranscriptEvent, TranscriptEventType, SessionState, SessionWorkspace, ContextUsageAnchor, SessionStatus, TokenUsage, CompiledInput, PermissionDecision, PermissionMode, PermissionRule, TurnPhase, TurnResult, TerminalReason, StreamEvent, StreamCallback, LLMConfig, ClientDefaults, LLMResponse, Settings, MCPServerConfig, } from "./types.js";
|
|
6
|
+
export declare const VERSION = "0.7.1";
|
|
7
|
+
export type { Message, ContentBlock, ToolDefinition, ToolCall, ToolResult, RegisteredTool, TranscriptEvent, TranscriptEventType, SessionState, SessionWorkspace, SessionForkLineage, ContextUsageAnchor, SessionStatus, TokenUsage, CompiledInput, PermissionDecision, PermissionMode, PermissionRule, TurnPhase, TurnResult, TerminalReason, StreamEvent, StreamCallback, LLMConfig, ClientDefaults, LLMResponse, Settings, MCPServerConfig, } from "./types.js";
|
|
8
8
|
export { FrameworkError, LLMError, LLMRateLimitError, ContextLimitError, ToolError, ToolNotFoundError, ToolExecutionError, ToolTimeoutError, PermissionDeniedError, SessionError, TranscriptError, ConfigError, SandboxUnavailableError, } from "./exceptions.js";
|
|
9
9
|
export { Engine, loadAgentDefinitionsForCwd } from "./engine/engine.js";
|
|
10
10
|
export type { EngineConfig, EngineHookConfig, EngineResult } from "./engine/types.js";
|
|
@@ -30,7 +30,7 @@ export type { ApprovalBackend } from "./tool-system/permission.js";
|
|
|
30
30
|
export { BUILTIN_TOOLS } from "./tool-system/builtin/index.js";
|
|
31
31
|
export type { BuiltinTool, BuiltinToolFn } from "./tool-system/builtin/index.js";
|
|
32
32
|
export { makeUpdateAutomationMemoryTool } from "./tool-system/builtin/update-automation-memory.js";
|
|
33
|
-
export { MCPManager, buildHttpHeaders, buildStdioEnv } from "./tool-system/mcp-manager.js";
|
|
33
|
+
export { MCPManager, buildHttpHeaders, buildStdioEnv, createMcpAuthenticatedFetch, } from "./tool-system/mcp-manager.js";
|
|
34
34
|
export type { AskUserFn } from "./tool-system/builtin/ask-user.js";
|
|
35
35
|
export type { Task, TaskStatus } from "./tool-system/builtin/task.js";
|
|
36
36
|
export { HookRegistry } from "./hooks/registry.js";
|
|
@@ -41,9 +41,9 @@ export { AgentClient, type BackgroundAgentCompletedHandler } from "./protocol/cl
|
|
|
41
41
|
export { createInProcessTransport, StdioTransport, type Transport } from "./protocol/transport.js";
|
|
42
42
|
export { SocketTransport, listenTcp, type TcpListenResult } from "./protocol/tcp-transport.js";
|
|
43
43
|
export { createServer, createClient, type CreateServerOptions, type CreateClientOptions, type ServerHandle, } from "./protocol/factories.js";
|
|
44
|
-
export { Methods, ErrorCodes, type RpcMessage, type RunResult } from "./protocol/types.js";
|
|
44
|
+
export { Methods, ErrorCodes, type RpcMessage, type RunResult, type ForkSessionParams, type ForkSessionResult as ProtocolForkSessionResult, } from "./protocol/types.js";
|
|
45
45
|
export { Transcript } from "./session/transcript.js";
|
|
46
|
-
export { SessionManager } from "./session/session-manager.js";
|
|
46
|
+
export { SessionManager, buildForkState, buildForkTranscript, type ForkSessionOptions, type ForkSessionResult, } from "./session/session-manager.js";
|
|
47
47
|
export { FileHistory } from "./session/file-history.js";
|
|
48
48
|
export { createWorktree, currentBranch, DEFAULT_WORKTREE_BRANCH_PREFIX, getWorktreeDiff, isManagedWorktreeBranch, isValidWorktreeBranchPrefix, findMainWorktreeRoot, listWorktrees, listWorktreesFast, normalizeWorktreeBranchPrefix, removeWorktree, validateWorktreeSlug, worktreeHasUncommittedOrAheadChanges, worktreeHasUncommittedChanges, type CreateWorktreeOptions, type ListWorktreesOptions, type ListWorktreesFastOptions, type RemoveWorktreeResult, type RemoveWorktreeOptions, type WorktreeDiffSummary, type WorktreeInfo, type WorktreeWorkspaceOwner, } from "./git/worktree.js";
|
|
49
49
|
export { latestUndoTarget, earliestSnapshotsPerFile, latestTurnUndoTargets, latestRedoTargets, } from "./session/undo-target.js";
|
|
@@ -51,6 +51,7 @@ export { diffLines, renderDiffPreview, type DiffLine } from "./session/simple-di
|
|
|
51
51
|
export { MemoryManager } from "./session/memory.js";
|
|
52
52
|
export type { MemoryEntry, MemoryOrigin, MemoryScope } from "./session/memory.js";
|
|
53
53
|
export { runDreamConsolidation, type DreamConsolidationInput, type DreamConsolidationResult, } from "./services/dream-consolidation.js";
|
|
54
|
+
export { authorize, refreshToken, generatePKCE, createHardenedOAuthFetch, type OAuthConfig, type OAuthTokens, type OAuthAuthorizeOptions, type OAuthRefreshOptions, type HardenedOAuthFetchOptions, } from "./services/oauth.js";
|
|
54
55
|
export type { FileSnapshot, RedoRecord } from "./session/file-history.js";
|
|
55
56
|
export { PromptComposer } from "./prompt/composer.js";
|
|
56
57
|
export { SectionCache } from "./prompt/section-cache.js";
|
|
@@ -109,7 +110,7 @@ export { migrateConfig, configVersionOf, CURRENT_CONFIG_VERSION, type MigrationS
|
|
|
109
110
|
export { SettingsSchema, validateSettings } from "./settings/schema.js";
|
|
110
111
|
export { settingsJsonSchema, writeSettingsSchemaFile } from "./settings/schema-export.js";
|
|
111
112
|
export { personalizationFrom, type PersonalizationConfig } from "./settings/personalization.js";
|
|
112
|
-
export { CredentialStore, type CredentialScope, type MaskedCredential, type Credential, type CredentialType, type CredentialStoreFile, type OAuthCredentialPublicStatus, type OAuthCredentialSecret, buildOAuthRefreshRequest, isOAuthAccessTokenExpired, oauthCredentialStatus, parseOAuthCredentialSecret, summarizeOAuthCredentialSecret, type OAuthClockOptions, type OAuthRefreshHandler, type OAuthRefreshRequest, formatNetscapeCookies, parseCookieJar, type CookieLike, useCredentialToolDef, useCredentialToolDefFor, sweepStaleCredentialCookies, getCredentialAccess, setDefaultCredentialAccess, createIpcCredentialAccess, localCredentialAccess, credentialAccessScope, isCredentialSecretAvailable, materializeCookieSecret, type CredentialAccess, type CredentialAccessScope, type CredentialMetadata, type CredentialSnapshot, type CredentialSnapshotEntry, type EncryptionCipher, PlaintextCipher, setDefaultCredentialCipher, getDefaultCredentialCipher, } from "./credentials/index.js";
|
|
113
|
+
export { CredentialStore, type CredentialScope, type MaskedCredential, type Credential, type CredentialType, type CredentialStoreFile, type OAuthCredentialPublicStatus, type OAuthCredentialSecret, type OAuthTokenResponse, buildOAuthRefreshRequest, isOAuthAccessTokenExpired, oauthCredentialStatus, parseOAuthCredentialSecret, mergeOAuthTokenResponse, shouldRefreshOAuthCredential, summarizeOAuthCredentialSecret, type OAuthClockOptions, type OAuthRefreshHandler, type OAuthRefreshRequest, formatNetscapeCookies, parseCookieJar, type CookieLike, useCredentialToolDef, useCredentialToolDefFor, sweepStaleCredentialCookies, getCredentialAccess, setDefaultCredentialAccess, createIpcCredentialAccess, localCredentialAccess, credentialAccessScope, credentialAllowsEnvExposure, credentialSecretHint, isCredentialSecretAvailable, materializeCookieSecret, type CredentialAccess, type CredentialAccessScope, type CredentialMetadata, type CredentialSnapshot, type CredentialSnapshotEntry, type EncryptionCipher, PlaintextCipher, setDefaultCredentialCipher, getDefaultCredentialCipher, } from "./credentials/index.js";
|
|
113
114
|
/** @internal Shared with the in-repo TUI/desktop hosts; not stable SDK surface. */
|
|
114
115
|
export { getSessionId, switchSession, getOriginalCwd, setOriginalCwd, getProjectRoot, setProjectRoot, getCwdState, getIsInteractive, updateLastInteractionTime, flushInteractionTime, markScrollActivity, type AttributedCounter, type ChannelEntry, } from "./state.js";
|
|
115
116
|
/** @internal Shared primitives for the in-repo TUI/desktop hosts; not stable SDK surface. */
|
package/dist/index.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Public API exports.
|
|
5
5
|
*/
|
|
6
|
-
export const VERSION = "0.7.
|
|
6
|
+
export const VERSION = "0.7.1";
|
|
7
7
|
// ─── Exceptions ──────────────────────────────────────────────────
|
|
8
8
|
export { FrameworkError, LLMError, LLMRateLimitError, ContextLimitError, ToolError, ToolNotFoundError, ToolExecutionError, ToolTimeoutError, PermissionDeniedError, SessionError, TranscriptError, ConfigError, SandboxUnavailableError, } from "./exceptions.js";
|
|
9
9
|
// ─── Engine (primary API) ────────────────────────────────────────
|
|
@@ -27,7 +27,7 @@ export { ToolExecutor } from "./tool-system/executor.js";
|
|
|
27
27
|
export { PermissionClassifier, HeadlessApprovalBackend, AutoApprovalBackend, } from "./tool-system/permission.js";
|
|
28
28
|
export { BUILTIN_TOOLS } from "./tool-system/builtin/index.js";
|
|
29
29
|
export { makeUpdateAutomationMemoryTool } from "./tool-system/builtin/update-automation-memory.js";
|
|
30
|
-
export { MCPManager, buildHttpHeaders, buildStdioEnv } from "./tool-system/mcp-manager.js";
|
|
30
|
+
export { MCPManager, buildHttpHeaders, buildStdioEnv, createMcpAuthenticatedFetch, } from "./tool-system/mcp-manager.js";
|
|
31
31
|
// ─── Hooks ───────────────────────────────────────────────────────
|
|
32
32
|
export { HookRegistry } from "./hooks/registry.js";
|
|
33
33
|
export { wrapHookMessages } from "./hooks/inject.js";
|
|
@@ -39,16 +39,17 @@ export { SocketTransport, listenTcp } from "./protocol/tcp-transport.js";
|
|
|
39
39
|
// Recommended public factories — see protocol/factories.ts for the
|
|
40
40
|
// stable construction contract referenced by standard §7.
|
|
41
41
|
export { createServer, createClient, } from "./protocol/factories.js";
|
|
42
|
-
export { Methods, ErrorCodes } from "./protocol/types.js";
|
|
42
|
+
export { Methods, ErrorCodes, } from "./protocol/types.js";
|
|
43
43
|
// ─── Session ─────────────────────────────────────────────────────
|
|
44
44
|
export { Transcript } from "./session/transcript.js";
|
|
45
|
-
export { SessionManager } from "./session/session-manager.js";
|
|
45
|
+
export { SessionManager, buildForkState, buildForkTranscript, } from "./session/session-manager.js";
|
|
46
46
|
export { FileHistory } from "./session/file-history.js";
|
|
47
47
|
export { createWorktree, currentBranch, DEFAULT_WORKTREE_BRANCH_PREFIX, getWorktreeDiff, isManagedWorktreeBranch, isValidWorktreeBranchPrefix, findMainWorktreeRoot, listWorktrees, listWorktreesFast, normalizeWorktreeBranchPrefix, removeWorktree, validateWorktreeSlug, worktreeHasUncommittedOrAheadChanges, worktreeHasUncommittedChanges, } from "./git/worktree.js";
|
|
48
48
|
export { latestUndoTarget, earliestSnapshotsPerFile, latestTurnUndoTargets, latestRedoTargets, } from "./session/undo-target.js";
|
|
49
49
|
export { diffLines, renderDiffPreview } from "./session/simple-diff.js";
|
|
50
50
|
export { MemoryManager } from "./session/memory.js";
|
|
51
51
|
export { runDreamConsolidation, } from "./services/dream-consolidation.js";
|
|
52
|
+
export { authorize, refreshToken, generatePKCE, createHardenedOAuthFetch, } from "./services/oauth.js";
|
|
52
53
|
// ─── Prompt ──────────────────────────────────────────────────────
|
|
53
54
|
export { PromptComposer } from "./prompt/composer.js";
|
|
54
55
|
export { SectionCache } from "./prompt/section-cache.js";
|
|
@@ -127,7 +128,7 @@ export { migrateConfig, configVersionOf, CURRENT_CONFIG_VERSION, } from "./setti
|
|
|
127
128
|
export { SettingsSchema, validateSettings } from "./settings/schema.js";
|
|
128
129
|
export { settingsJsonSchema, writeSettingsSchemaFile } from "./settings/schema-export.js";
|
|
129
130
|
export { personalizationFrom } from "./settings/personalization.js";
|
|
130
|
-
export { CredentialStore, buildOAuthRefreshRequest, isOAuthAccessTokenExpired, oauthCredentialStatus, parseOAuthCredentialSecret, summarizeOAuthCredentialSecret, formatNetscapeCookies, parseCookieJar, useCredentialToolDef, useCredentialToolDefFor, sweepStaleCredentialCookies, getCredentialAccess, setDefaultCredentialAccess, createIpcCredentialAccess, localCredentialAccess, credentialAccessScope, isCredentialSecretAvailable, materializeCookieSecret, PlaintextCipher, setDefaultCredentialCipher, getDefaultCredentialCipher, } from "./credentials/index.js";
|
|
131
|
+
export { CredentialStore, buildOAuthRefreshRequest, isOAuthAccessTokenExpired, oauthCredentialStatus, parseOAuthCredentialSecret, mergeOAuthTokenResponse, shouldRefreshOAuthCredential, summarizeOAuthCredentialSecret, formatNetscapeCookies, parseCookieJar, useCredentialToolDef, useCredentialToolDefFor, sweepStaleCredentialCookies, getCredentialAccess, setDefaultCredentialAccess, createIpcCredentialAccess, localCredentialAccess, credentialAccessScope, credentialAllowsEnvExposure, credentialSecretHint, isCredentialSecretAvailable, materializeCookieSecret, PlaintextCipher, setDefaultCredentialCipher, getDefaultCredentialCipher, } from "./credentials/index.js";
|
|
131
132
|
// ─── State (runtime singletons shared with TUI) ──────────────────
|
|
132
133
|
/** @internal Shared with the in-repo TUI/desktop hosts; not stable SDK surface. */
|
|
133
134
|
export { getSessionId, switchSession, getOriginalCwd, setOriginalCwd, getProjectRoot, setProjectRoot, getCwdState, getIsInteractive, updateLastInteractionTime, flushInteractionTime, markScrollActivity, } from "./state.js";
|
package/dist/llm/client-base.js
CHANGED
|
@@ -54,16 +54,18 @@ export class LLMClientBase {
|
|
|
54
54
|
this.initClient();
|
|
55
55
|
}
|
|
56
56
|
recordUsage(usage, options) {
|
|
57
|
-
if (options?.
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
57
|
+
if (options?.requestVisible !== false) {
|
|
58
|
+
this.usage.records.push(usage);
|
|
59
|
+
this.usage.totalPromptTokens += usage.promptTokens;
|
|
60
|
+
this.usage.totalCompletionTokens += usage.completionTokens;
|
|
61
|
+
this.usage.totalTokens += usage.totalTokens;
|
|
62
|
+
this.usage.totalCacheReadTokens += usage.cacheReadTokens ?? 0;
|
|
63
|
+
this.usage.totalCacheCreationTokens += usage.cacheCreationTokens ?? 0;
|
|
64
|
+
this.usage.requestCount++;
|
|
65
|
+
}
|
|
66
|
+
if (options?.billingEnabled !== false) {
|
|
67
|
+
LLMClientBase.onUsage?.(this.model, usage);
|
|
68
|
+
}
|
|
67
69
|
}
|
|
68
70
|
getUsage() {
|
|
69
71
|
return { ...this.usage };
|
package/dist/llm/types.d.ts
CHANGED
|
@@ -12,13 +12,20 @@ export interface CreateMessageOptions {
|
|
|
12
12
|
onChunk?: (chunk: LLMStreamChunk) => void;
|
|
13
13
|
signal?: AbortSignal;
|
|
14
14
|
/**
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
15
|
+
* Whether this real provider request is billed through the process-wide
|
|
16
|
+
* usage hook. This is independent of request visibility: auxiliary calls
|
|
17
|
+
* are normally billed while remaining hidden from the foreground request
|
|
18
|
+
* tracker. Only explicit manual-audit paths should disable billing.
|
|
19
19
|
* @default true
|
|
20
20
|
*/
|
|
21
|
-
|
|
21
|
+
billingEnabled?: boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Whether this request appears in the client's foreground usage tracker.
|
|
24
|
+
* Hidden auxiliary calls must report their usage to their owning Engine's
|
|
25
|
+
* session/Goal recorder instead.
|
|
26
|
+
* @default true
|
|
27
|
+
*/
|
|
28
|
+
requestVisible?: boolean;
|
|
22
29
|
/**
|
|
23
30
|
* Reasoning/thinking setting for this call. Overrides LLMConfig.reasoning.
|
|
24
31
|
* Rich shape ({mode:"off"|"on"} | {mode:"effort",effort} |
|
|
@@ -42,13 +42,13 @@ export interface PluginCommandHookSpec {
|
|
|
42
42
|
timeoutMs?: number;
|
|
43
43
|
}
|
|
44
44
|
/**
|
|
45
|
-
* Run one plugin hook command.
|
|
46
|
-
*
|
|
47
|
-
*
|
|
45
|
+
* Run one plugin hook command. CC's exit-code 2 and explicit deny/reject JSON
|
|
46
|
+
* are normalized to the native HookResult deny contract. Other failures stay
|
|
47
|
+
* non-blocking so a broken plugin does not wedge the engine.
|
|
48
48
|
*
|
|
49
49
|
* Stdin carries the same envelope shape the codeshell-native shell-runner
|
|
50
50
|
* uses (eventName + data) so plugin authors who want richer context can
|
|
51
51
|
* read it; CC plugins typically ignore stdin and emit context derived from
|
|
52
52
|
* their own state (e.g. reading a SKILL.md off disk via PLUGIN_ROOT).
|
|
53
53
|
*/
|
|
54
|
-
export declare function runPluginCommandHook(spec: PluginCommandHookSpec, ctx: HookContext): Promise<HookResult>;
|
|
54
|
+
export declare function runPluginCommandHook(spec: PluginCommandHookSpec, ctx: HookContext, abortSignal?: AbortSignal): Promise<HookResult>;
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
*/
|
|
33
33
|
import { spawn } from "node:child_process";
|
|
34
34
|
import { MAX_HOOK_OUTPUT_BYTES } from "../hooks/hook-output.js";
|
|
35
|
+
import { closeHookStdin, MAX_HOOK_STDIN_BYTES, writeHookStdin } from "../hooks/shell-runner.js";
|
|
35
36
|
import { killChildTree } from "../runtime/spawn-common.js";
|
|
36
37
|
const DEFAULT_TIMEOUT_MS = 60_000;
|
|
37
38
|
/**
|
|
@@ -60,18 +61,49 @@ function extractAdditionalContext(parsed) {
|
|
|
60
61
|
}
|
|
61
62
|
return null;
|
|
62
63
|
}
|
|
64
|
+
function extractExplicitDenial(parsed) {
|
|
65
|
+
if (!parsed || typeof parsed !== "object")
|
|
66
|
+
return null;
|
|
67
|
+
const obj = parsed;
|
|
68
|
+
const nested = obj.hookSpecificOutput && typeof obj.hookSpecificOutput === "object"
|
|
69
|
+
? obj.hookSpecificOutput
|
|
70
|
+
: undefined;
|
|
71
|
+
const decision = obj.decision ?? obj.permissionDecision ?? nested?.decision ?? nested?.permissionDecision;
|
|
72
|
+
if (decision !== "deny" && decision !== "reject")
|
|
73
|
+
return null;
|
|
74
|
+
const message = obj.reason ??
|
|
75
|
+
obj.message ??
|
|
76
|
+
obj.permissionDecisionReason ??
|
|
77
|
+
nested?.reason ??
|
|
78
|
+
nested?.message ??
|
|
79
|
+
nested?.permissionDecisionReason;
|
|
80
|
+
return typeof message === "string" && message.trim() ? { message: message.trim() } : {};
|
|
81
|
+
}
|
|
82
|
+
function parseJson(stdout) {
|
|
83
|
+
if (!stdout)
|
|
84
|
+
return undefined;
|
|
85
|
+
try {
|
|
86
|
+
return JSON.parse(stdout);
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
return undefined;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
63
92
|
/**
|
|
64
|
-
* Run one plugin hook command.
|
|
65
|
-
*
|
|
66
|
-
*
|
|
93
|
+
* Run one plugin hook command. CC's exit-code 2 and explicit deny/reject JSON
|
|
94
|
+
* are normalized to the native HookResult deny contract. Other failures stay
|
|
95
|
+
* non-blocking so a broken plugin does not wedge the engine.
|
|
67
96
|
*
|
|
68
97
|
* Stdin carries the same envelope shape the codeshell-native shell-runner
|
|
69
98
|
* uses (eventName + data) so plugin authors who want richer context can
|
|
70
99
|
* read it; CC plugins typically ignore stdin and emit context derived from
|
|
71
100
|
* their own state (e.g. reading a SKILL.md off disk via PLUGIN_ROOT).
|
|
72
101
|
*/
|
|
73
|
-
export async function runPluginCommandHook(spec, ctx) {
|
|
102
|
+
export async function runPluginCommandHook(spec, ctx, abortSignal) {
|
|
74
103
|
const timeoutMs = spec.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
104
|
+
const signal = abortSignal ?? ctx.data.signal;
|
|
105
|
+
if (signal?.aborted)
|
|
106
|
+
return {};
|
|
75
107
|
return new Promise((resolve) => {
|
|
76
108
|
let child;
|
|
77
109
|
try {
|
|
@@ -101,6 +133,7 @@ export async function runPluginCommandHook(spec, ctx) {
|
|
|
101
133
|
let stdout = "";
|
|
102
134
|
let stderr = "";
|
|
103
135
|
let settled = false;
|
|
136
|
+
let timer;
|
|
104
137
|
// Output byte caps mirror src/hooks/shell-runner.ts so a chatty plugin
|
|
105
138
|
// hook can't hold engine memory hostage. Past the cap we SIGTERM the
|
|
106
139
|
// child and treat the hook as failed; stderr cap truncates with a
|
|
@@ -112,15 +145,24 @@ export async function runPluginCommandHook(spec, ctx) {
|
|
|
112
145
|
if (settled)
|
|
113
146
|
return;
|
|
114
147
|
settled = true;
|
|
148
|
+
clearTimeout(timer);
|
|
149
|
+
signal?.removeEventListener("abort", onAbort);
|
|
115
150
|
resolve(value);
|
|
116
151
|
};
|
|
117
|
-
const
|
|
152
|
+
const onAbort = () => {
|
|
153
|
+
killChildTree(child, 1000);
|
|
154
|
+
settle({});
|
|
155
|
+
};
|
|
156
|
+
timer = setTimeout(() => {
|
|
118
157
|
// eslint-disable-next-line no-console
|
|
119
158
|
console.warn(`[plugin-hook] ${spec.pluginKey} ${ctx.eventName} timed out after ${timeoutMs}ms`);
|
|
120
159
|
// win32: taskkill /T reaps the tree; POSIX: SIGTERM → grace → SIGKILL.
|
|
121
160
|
killChildTree(child, 1000);
|
|
122
161
|
settle({});
|
|
123
162
|
}, timeoutMs);
|
|
163
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
164
|
+
if (signal?.aborted)
|
|
165
|
+
onAbort();
|
|
124
166
|
child.stdout?.on("data", (chunk) => {
|
|
125
167
|
stdoutBytes += chunk.length;
|
|
126
168
|
if (stdoutBytes > MAX_HOOK_OUTPUT_BYTES) {
|
|
@@ -150,21 +192,29 @@ export async function runPluginCommandHook(spec, ctx) {
|
|
|
150
192
|
console.error(`[plugin-hook] ${spec.pluginKey} ${ctx.eventName} spawn error:`, err.message);
|
|
151
193
|
settle({});
|
|
152
194
|
});
|
|
153
|
-
|
|
154
|
-
|
|
195
|
+
let stdinFinished = false;
|
|
196
|
+
let pendingClose;
|
|
197
|
+
const handleClose = (code) => {
|
|
155
198
|
if (stdoutCapped) {
|
|
156
199
|
// eslint-disable-next-line no-console
|
|
157
200
|
console.error(`[plugin-hook] ${spec.pluginKey} ${ctx.eventName} killed due to oversized stdout (> ${MAX_HOOK_OUTPUT_BYTES} bytes)`);
|
|
158
201
|
settle({});
|
|
159
202
|
return;
|
|
160
203
|
}
|
|
204
|
+
const trimmed = stdout.trim();
|
|
205
|
+
const parsedForDecision = parseJson(trimmed);
|
|
206
|
+
const explicitDenial = extractExplicitDenial(parsedForDecision);
|
|
207
|
+
if (code === 2 || explicitDenial) {
|
|
208
|
+
const message = stderr.trim() || explicitDenial?.message || "Plugin denied this tool operation";
|
|
209
|
+
settle({ decision: "deny", messages: [message] });
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
161
212
|
if (code !== 0) {
|
|
162
213
|
// eslint-disable-next-line no-console
|
|
163
214
|
console.warn(`[plugin-hook] ${spec.pluginKey} ${ctx.eventName} exited ${code}; stderr:`, stderr.slice(0, 500));
|
|
164
215
|
settle({});
|
|
165
216
|
return;
|
|
166
217
|
}
|
|
167
|
-
const trimmed = stdout.trim();
|
|
168
218
|
if (trimmed.length === 0) {
|
|
169
219
|
settle({});
|
|
170
220
|
return;
|
|
@@ -185,16 +235,64 @@ export async function runPluginCommandHook(spec, ctx) {
|
|
|
185
235
|
return;
|
|
186
236
|
}
|
|
187
237
|
settle({ messages: [additional] });
|
|
238
|
+
};
|
|
239
|
+
child.on("close", (code) => {
|
|
240
|
+
clearTimeout(timer);
|
|
241
|
+
if (settled)
|
|
242
|
+
return;
|
|
243
|
+
if (!stdinFinished && !stdoutCapped) {
|
|
244
|
+
pendingClose = { code };
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
handleClose(code);
|
|
188
248
|
});
|
|
249
|
+
const finishStdin = () => {
|
|
250
|
+
stdinFinished = true;
|
|
251
|
+
if (pendingClose && !settled) {
|
|
252
|
+
const { code } = pendingClose;
|
|
253
|
+
pendingClose = undefined;
|
|
254
|
+
handleClose(code);
|
|
255
|
+
}
|
|
256
|
+
};
|
|
189
257
|
// Stdin envelope: matches the shape codeshell-native shell hooks use.
|
|
190
|
-
|
|
191
|
-
// keeps the protocol open for plugin authors who want it.
|
|
258
|
+
let envelope;
|
|
192
259
|
try {
|
|
193
|
-
|
|
194
|
-
child.stdin?.end();
|
|
260
|
+
envelope = JSON.stringify({ eventName: ctx.eventName, data: ctx.data });
|
|
195
261
|
}
|
|
196
262
|
catch {
|
|
197
|
-
|
|
263
|
+
closeHookStdin(child.stdin);
|
|
264
|
+
finishStdin();
|
|
265
|
+
return;
|
|
198
266
|
}
|
|
267
|
+
if (Buffer.byteLength(envelope, "utf8") > MAX_HOOK_STDIN_BYTES) {
|
|
268
|
+
// eslint-disable-next-line no-console
|
|
269
|
+
console.warn(`[plugin-hook] ${spec.pluginKey} ${ctx.eventName} stdin exceeded ${MAX_HOOK_STDIN_BYTES} bytes; running without envelope`);
|
|
270
|
+
closeHookStdin(child.stdin);
|
|
271
|
+
finishStdin();
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
setImmediate(() => {
|
|
275
|
+
void (async () => {
|
|
276
|
+
if (settled)
|
|
277
|
+
return;
|
|
278
|
+
try {
|
|
279
|
+
await writeHookStdin(child.stdin, envelope);
|
|
280
|
+
finishStdin();
|
|
281
|
+
}
|
|
282
|
+
catch (error) {
|
|
283
|
+
clearTimeout(timer);
|
|
284
|
+
const err = error;
|
|
285
|
+
const failure = {
|
|
286
|
+
type: "stdin_error",
|
|
287
|
+
code: typeof err?.code === "string" ? err.code : "UNKNOWN",
|
|
288
|
+
message: err instanceof Error ? err.message : String(error),
|
|
289
|
+
};
|
|
290
|
+
// eslint-disable-next-line no-console
|
|
291
|
+
console.error(`[plugin-hook] ${spec.pluginKey} ${ctx.eventName} stdin error:`, failure.message);
|
|
292
|
+
killChildTree(child, 1000);
|
|
293
|
+
settle({ data: { hookFailure: failure } });
|
|
294
|
+
}
|
|
295
|
+
})();
|
|
296
|
+
});
|
|
199
297
|
});
|
|
200
298
|
}
|