steward-cli 0.23.0 → 0.26.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -0
- package/dist/cli.js +1187 -231
- package/package.json +17 -31
package/dist/cli.js
CHANGED
|
@@ -41285,7 +41285,7 @@ var require_dist = __commonJS(function(exports) {
|
|
|
41285
41285
|
});
|
|
41286
41286
|
|
|
41287
41287
|
// src/app/app.ts
|
|
41288
|
-
import { existsSync as
|
|
41288
|
+
import { existsSync as existsSync18 } from "node:fs";
|
|
41289
41289
|
|
|
41290
41290
|
// src/packages/tui/src/engine/HistoryStore.ts
|
|
41291
41291
|
class HistoryStore {
|
|
@@ -44139,7 +44139,7 @@ async function loginGitHubCopilot(interaction) {
|
|
|
44139
44139
|
signal
|
|
44140
44140
|
});
|
|
44141
44141
|
} catch (err) {
|
|
44142
|
-
throw new AIError(`Failed to initiate GitHub device authorization: ${err.message}`, {
|
|
44142
|
+
throw new AIError(`Failed to initiate GitHub device authorization: ${err instanceof Error ? err.message : String(err)}`, {
|
|
44143
44143
|
code: "oauth",
|
|
44144
44144
|
provider: "github-copilot",
|
|
44145
44145
|
cause: err
|
|
@@ -44180,18 +44180,21 @@ async function loginGitHubCopilot(interaction) {
|
|
|
44180
44180
|
signal
|
|
44181
44181
|
});
|
|
44182
44182
|
const data = await res.json();
|
|
44183
|
-
if (data?.access_token) {
|
|
44183
|
+
if (typeof data?.access_token === "string") {
|
|
44184
44184
|
return { status: "complete", value: data.access_token };
|
|
44185
44185
|
}
|
|
44186
44186
|
if (data?.error === "authorization_pending") {
|
|
44187
44187
|
return { status: "pending" };
|
|
44188
44188
|
}
|
|
44189
44189
|
if (data?.error === "slow_down") {
|
|
44190
|
-
return {
|
|
44190
|
+
return {
|
|
44191
|
+
status: "slow_down",
|
|
44192
|
+
intervalSeconds: typeof data.interval === "number" ? data.interval : undefined
|
|
44193
|
+
};
|
|
44191
44194
|
}
|
|
44192
44195
|
return {
|
|
44193
44196
|
status: "failed",
|
|
44194
|
-
message: data?.error_description
|
|
44197
|
+
message: typeof data?.error_description === "string" ? data.error_description : typeof data?.error === "string" ? data.error : "Authorization failed"
|
|
44195
44198
|
};
|
|
44196
44199
|
}
|
|
44197
44200
|
});
|
|
@@ -44215,7 +44218,7 @@ async function refreshGitHubCopilotToken(githubAccessToken, signal) {
|
|
|
44215
44218
|
signal
|
|
44216
44219
|
});
|
|
44217
44220
|
} catch (err) {
|
|
44218
|
-
throw new AIError(`Failed to fetch Copilot token: ${err.message}`, {
|
|
44221
|
+
throw new AIError(`Failed to fetch Copilot token: ${err instanceof Error ? err.message : String(err)}`, {
|
|
44219
44222
|
code: "oauth",
|
|
44220
44223
|
provider: "github-copilot",
|
|
44221
44224
|
cause: err
|
|
@@ -44902,6 +44905,12 @@ function parseSSEEvent(raw) {
|
|
|
44902
44905
|
|
|
44903
44906
|
// src/packages/ai/src/json.ts
|
|
44904
44907
|
function sanitizeSurrogates(text) {
|
|
44908
|
+
if (typeof text !== "string") {
|
|
44909
|
+
if (text === null || text === undefined) {
|
|
44910
|
+
return "";
|
|
44911
|
+
}
|
|
44912
|
+
return sanitizeSurrogates(typeof text === "object" ? JSON.stringify(text) : String(text));
|
|
44913
|
+
}
|
|
44905
44914
|
return text.replace(/[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g, "");
|
|
44906
44915
|
}
|
|
44907
44916
|
function parseStreamingJson(partial) {
|
|
@@ -45876,11 +45885,12 @@ ${sanitized}` : sanitized;
|
|
|
45876
45885
|
if (msg.role === "tool") {
|
|
45877
45886
|
const parts = [];
|
|
45878
45887
|
for (const res of msg.content) {
|
|
45888
|
+
const outStr = typeof res.output === "string" ? res.output : JSON.stringify(res.output);
|
|
45879
45889
|
parts.push({
|
|
45880
45890
|
functionResponse: {
|
|
45881
45891
|
name: res.toolName,
|
|
45882
45892
|
response: {
|
|
45883
|
-
result: sanitizeSurrogates(
|
|
45893
|
+
result: sanitizeSurrogates(outStr)
|
|
45884
45894
|
}
|
|
45885
45895
|
}
|
|
45886
45896
|
});
|
|
@@ -47144,6 +47154,9 @@ class TerminalEngine {
|
|
|
47144
47154
|
}
|
|
47145
47155
|
var TerminalEngine_default = TerminalEngine;
|
|
47146
47156
|
|
|
47157
|
+
// src/packages/agents/src/engine/agent-session.ts
|
|
47158
|
+
import { randomUUID as randomUUID7 } from "node:crypto";
|
|
47159
|
+
|
|
47147
47160
|
// src/packages/services/src/session/store.ts
|
|
47148
47161
|
import { randomUUID } from "node:crypto";
|
|
47149
47162
|
import {
|
|
@@ -53526,7 +53539,9 @@ function rehydrateSessionHistory(sessionData, projection) {
|
|
|
53526
53539
|
textAccum += (textAccum ? `
|
|
53527
53540
|
` : "") + part.text;
|
|
53528
53541
|
} else if (part.type === "tool-call") {
|
|
53529
|
-
const
|
|
53542
|
+
const callId = part.id ?? part.toolCallId;
|
|
53543
|
+
const toolName = part.name ?? part.toolName;
|
|
53544
|
+
const rawInput = part.arguments ?? part.args ?? part.input;
|
|
53530
53545
|
let parsedInput = rawInput ?? {};
|
|
53531
53546
|
if (typeof rawInput === "string") {
|
|
53532
53547
|
const trimmed = rawInput.trim();
|
|
@@ -53536,12 +53551,14 @@ function rehydrateSessionHistory(sessionData, projection) {
|
|
|
53536
53551
|
} catch {}
|
|
53537
53552
|
}
|
|
53538
53553
|
}
|
|
53539
|
-
|
|
53540
|
-
|
|
53541
|
-
|
|
53542
|
-
|
|
53543
|
-
|
|
53544
|
-
|
|
53554
|
+
if (callId) {
|
|
53555
|
+
toolCallsMap.set(callId, {
|
|
53556
|
+
id: callId,
|
|
53557
|
+
name: toolName,
|
|
53558
|
+
args: parsedInput,
|
|
53559
|
+
isError: false
|
|
53560
|
+
});
|
|
53561
|
+
}
|
|
53545
53562
|
}
|
|
53546
53563
|
}
|
|
53547
53564
|
if (textAccum.trim()) {
|
|
@@ -53556,47 +53573,49 @@ function rehydrateSessionHistory(sessionData, projection) {
|
|
|
53556
53573
|
} else if (msg.role === "tool") {
|
|
53557
53574
|
if (Array.isArray(msg.content)) {
|
|
53558
53575
|
for (const part of msg.content) {
|
|
53559
|
-
|
|
53560
|
-
|
|
53561
|
-
|
|
53562
|
-
|
|
53563
|
-
|
|
53564
|
-
|
|
53565
|
-
|
|
53566
|
-
|
|
53567
|
-
|
|
53568
|
-
|
|
53569
|
-
|
|
53570
|
-
|
|
53571
|
-
|
|
53572
|
-
|
|
53573
|
-
|
|
53574
|
-
|
|
53576
|
+
const callId = part.toolCallId ?? part.id;
|
|
53577
|
+
if (!callId)
|
|
53578
|
+
continue;
|
|
53579
|
+
const existing = toolCallsMap.get(callId);
|
|
53580
|
+
const isError = Boolean(part.isError);
|
|
53581
|
+
const toolName = existing?.name ?? part.toolName ?? part.name ?? "tool";
|
|
53582
|
+
const args = existing?.args;
|
|
53583
|
+
const argsSummary = args ? JSON.stringify(args) : "";
|
|
53584
|
+
let outputVal = part.output ?? part.result;
|
|
53585
|
+
if (outputVal && typeof outputVal === "object" && outputVal.type === "json" && "value" in outputVal) {
|
|
53586
|
+
outputVal = outputVal.value;
|
|
53587
|
+
}
|
|
53588
|
+
if (typeof outputVal === "string") {
|
|
53589
|
+
const trimmed = outputVal.trim();
|
|
53590
|
+
if (trimmed.startsWith("{") && trimmed.endsWith("}")) {
|
|
53591
|
+
try {
|
|
53592
|
+
outputVal = JSON.parse(trimmed);
|
|
53593
|
+
} catch {}
|
|
53575
53594
|
}
|
|
53576
|
-
const canonicalOutputSummary = formatToolOutputSummary(outputVal, isError);
|
|
53577
|
-
const toolLog = turnPresentation?.tools.get(part.toolCallId);
|
|
53578
|
-
const status = toolLog ? toolLog.status === "failed" ? "failed" : "completed" : isError ? "failed" : "completed";
|
|
53579
|
-
const error = toolLog?.errorMessage ? toolLog.errorMessage : isError ? typeof outputVal === "object" && outputVal !== null ? outputVal.message ?? JSON.stringify(outputVal) : String(outputVal) : undefined;
|
|
53580
|
-
const toolOutput = toolLog?.outputSummary ?? canonicalOutputSummary;
|
|
53581
|
-
restoredItems.push({
|
|
53582
|
-
id: `tool-${part.toolCallId}`,
|
|
53583
|
-
turnId: turn.id,
|
|
53584
|
-
type: "tool",
|
|
53585
|
-
content: "",
|
|
53586
|
-
toolData: {
|
|
53587
|
-
toolName,
|
|
53588
|
-
displayName: toolLog?.displayName,
|
|
53589
|
-
icon: toolLog?.icon,
|
|
53590
|
-
argsSummary,
|
|
53591
|
-
args: existing?.args,
|
|
53592
|
-
result: outputVal,
|
|
53593
|
-
status,
|
|
53594
|
-
durationMs: toolLog?.durationMs,
|
|
53595
|
-
error,
|
|
53596
|
-
toolOutput
|
|
53597
|
-
}
|
|
53598
|
-
});
|
|
53599
53595
|
}
|
|
53596
|
+
const canonicalOutputSummary = formatToolOutputSummary(outputVal, isError);
|
|
53597
|
+
const toolLog = turnPresentation?.tools.get(callId);
|
|
53598
|
+
const status = toolLog ? toolLog.status === "failed" ? "failed" : "completed" : isError ? "failed" : "completed";
|
|
53599
|
+
const error = toolLog?.errorMessage ? toolLog.errorMessage : isError ? typeof outputVal === "object" && outputVal !== null ? outputVal.message ?? JSON.stringify(outputVal) : String(outputVal) : undefined;
|
|
53600
|
+
const toolOutput = toolLog?.outputSummary ?? canonicalOutputSummary;
|
|
53601
|
+
restoredItems.push({
|
|
53602
|
+
id: `tool-${callId}`,
|
|
53603
|
+
turnId: turn.id,
|
|
53604
|
+
type: "tool",
|
|
53605
|
+
content: "",
|
|
53606
|
+
toolData: {
|
|
53607
|
+
toolName,
|
|
53608
|
+
displayName: toolLog?.displayName,
|
|
53609
|
+
icon: toolLog?.icon,
|
|
53610
|
+
argsSummary,
|
|
53611
|
+
args,
|
|
53612
|
+
result: outputVal,
|
|
53613
|
+
status,
|
|
53614
|
+
durationMs: toolLog?.durationMs,
|
|
53615
|
+
error,
|
|
53616
|
+
toolOutput
|
|
53617
|
+
}
|
|
53618
|
+
});
|
|
53600
53619
|
}
|
|
53601
53620
|
}
|
|
53602
53621
|
}
|
|
@@ -54299,16 +54318,16 @@ var writeFileTool = {
|
|
|
54299
54318
|
parameters: writeFileInputSchema,
|
|
54300
54319
|
confirmationPolicy: "never",
|
|
54301
54320
|
summarize: (args, result) => {
|
|
54302
|
-
const lines = result?.linesWritten ?? (args
|
|
54303
|
-
const filePath = args
|
|
54321
|
+
const lines = result?.linesWritten ?? (args?.content ? args.content.split(/\r?\n/).length : 0);
|
|
54322
|
+
const filePath = args?.file_path ?? "file";
|
|
54304
54323
|
const isNew = result?.isNew ?? true;
|
|
54305
54324
|
const headline = isNew ? `Created ${filePath} (${lines} line${lines === 1 ? "" : "s"})` : `Wrote ${lines} line${lines === 1 ? "" : "s"} to ${filePath}`;
|
|
54306
54325
|
return {
|
|
54307
54326
|
headline,
|
|
54308
54327
|
detail: {
|
|
54309
54328
|
kind: "code",
|
|
54310
|
-
filePath: args
|
|
54311
|
-
text: args
|
|
54329
|
+
filePath: args?.file_path ?? filePath,
|
|
54330
|
+
text: args?.content ?? "",
|
|
54312
54331
|
totalLines: lines
|
|
54313
54332
|
}
|
|
54314
54333
|
};
|
|
@@ -54647,10 +54666,13 @@ var editFileTool = {
|
|
|
54647
54666
|
parameters: editFileInputSchema,
|
|
54648
54667
|
confirmationPolicy: "never",
|
|
54649
54668
|
summarize: (args, result) => {
|
|
54650
|
-
|
|
54651
|
-
|
|
54669
|
+
const oldStr = args?.old_string ?? "";
|
|
54670
|
+
const newStr = args?.new_string ?? "";
|
|
54671
|
+
const filePath = args?.file_path ?? "file";
|
|
54672
|
+
if (oldStr === newStr) {
|
|
54673
|
+
return { headline: `No changes made to ${filePath}` };
|
|
54652
54674
|
}
|
|
54653
|
-
const diff = buildUnifiedDiff(
|
|
54675
|
+
const diff = buildUnifiedDiff(oldStr, newStr, 2);
|
|
54654
54676
|
let diffAdded = 0;
|
|
54655
54677
|
let diffRemoved = 0;
|
|
54656
54678
|
for (const hunk of diff.hunks) {
|
|
@@ -54668,7 +54690,7 @@ var editFileTool = {
|
|
|
54668
54690
|
headline,
|
|
54669
54691
|
detail: {
|
|
54670
54692
|
kind: "diff",
|
|
54671
|
-
filePath
|
|
54693
|
+
filePath,
|
|
54672
54694
|
hunks: diff.hunks
|
|
54673
54695
|
}
|
|
54674
54696
|
};
|
|
@@ -55879,7 +55901,7 @@ var taskReadTool = {
|
|
|
55879
55901
|
parameters: taskReadInputSchema,
|
|
55880
55902
|
confirmationPolicy: "never",
|
|
55881
55903
|
summarize: (args, result) => {
|
|
55882
|
-
return `Task ${args
|
|
55904
|
+
return `Task ${args?.task_id ?? ""} · status: ${result?.status ?? "unknown"}`;
|
|
55883
55905
|
},
|
|
55884
55906
|
execute: async (args, context) => {
|
|
55885
55907
|
if (!context.shellTasks) {
|
|
@@ -55914,7 +55936,7 @@ var taskSendInputTool = {
|
|
|
55914
55936
|
parameters: taskSendInputSchema,
|
|
55915
55937
|
confirmationPolicy: "never",
|
|
55916
55938
|
summarize: (args) => {
|
|
55917
|
-
return `Sent input to ${args
|
|
55939
|
+
return `Sent input to ${args?.task_id ?? ""} (${args?.input?.length ?? 0} chars)`;
|
|
55918
55940
|
},
|
|
55919
55941
|
execute: async (args, context) => {
|
|
55920
55942
|
if (!context.shellTasks) {
|
|
@@ -55938,7 +55960,7 @@ var taskKillTool = {
|
|
|
55938
55960
|
parameters: taskKillInputSchema,
|
|
55939
55961
|
confirmationPolicy: "never",
|
|
55940
55962
|
summarize: (args, result) => {
|
|
55941
|
-
return `Stopped task ${args
|
|
55963
|
+
return `Stopped task ${args?.task_id ?? ""} · status: ${result?.status ?? "killed"}`;
|
|
55942
55964
|
},
|
|
55943
55965
|
execute: async (args, context) => {
|
|
55944
55966
|
if (!context.shellTasks) {
|
|
@@ -55979,10 +56001,21 @@ import { isIP } from "node:net";
|
|
|
55979
56001
|
// package.json
|
|
55980
56002
|
var package_default = {
|
|
55981
56003
|
name: "steward-cli",
|
|
55982
|
-
version: "0.
|
|
56004
|
+
version: "0.26.0",
|
|
55983
56005
|
description: "Interactive AI engineering assistant for the terminal.",
|
|
55984
56006
|
author: "sapirrior",
|
|
55985
56007
|
license: "MIT",
|
|
56008
|
+
repository: {
|
|
56009
|
+
type: "git",
|
|
56010
|
+
url: "git+https://github.com/sapirrior/steward.git"
|
|
56011
|
+
},
|
|
56012
|
+
bugs: {
|
|
56013
|
+
url: "https://github.com/sapirrior/steward/issues"
|
|
56014
|
+
},
|
|
56015
|
+
homepage: "https://github.com/sapirrior/steward#readme",
|
|
56016
|
+
publishConfig: {
|
|
56017
|
+
access: "public"
|
|
56018
|
+
},
|
|
55986
56019
|
files: [
|
|
55987
56020
|
"dist"
|
|
55988
56021
|
],
|
|
@@ -56165,14 +56198,15 @@ var webFetchTool = {
|
|
|
56165
56198
|
confirmationPolicy: "never",
|
|
56166
56199
|
summarize: (args, result) => {
|
|
56167
56200
|
if (!result)
|
|
56168
|
-
return `Fetching ${args
|
|
56169
|
-
const chars = result
|
|
56170
|
-
const truncated = result
|
|
56171
|
-
const type = result
|
|
56201
|
+
return `Fetching ${args?.url ?? ""}`;
|
|
56202
|
+
const chars = result?.content?.length ?? 0;
|
|
56203
|
+
const truncated = result?.isTruncated ? " (truncated)" : "";
|
|
56204
|
+
const type = result?.contentType?.split(";")[0]?.trim() ?? "text";
|
|
56172
56205
|
const label = type.includes("html") ? "HTML" : type.includes("json") ? "JSON" : type.includes("markdown") || type.includes("text/plain") ? "text" : type.split("/").pop() ?? "content";
|
|
56173
|
-
let host = args
|
|
56206
|
+
let host = args?.url ?? "";
|
|
56174
56207
|
try {
|
|
56175
|
-
|
|
56208
|
+
if (args?.url)
|
|
56209
|
+
host = new URL(args.url).hostname;
|
|
56176
56210
|
} catch {}
|
|
56177
56211
|
return `Fetched ${chars.toLocaleString()} chars of ${label} from ${host}${truncated}`;
|
|
56178
56212
|
},
|
|
@@ -56312,9 +56346,9 @@ var webSearchTool = {
|
|
|
56312
56346
|
confirmationPolicy: "never",
|
|
56313
56347
|
summarize: (args, result) => {
|
|
56314
56348
|
if (!result)
|
|
56315
|
-
return `Searching "${args
|
|
56316
|
-
const count = result
|
|
56317
|
-
const site = args
|
|
56349
|
+
return `Searching "${args?.query ?? ""}"`;
|
|
56350
|
+
const count = result?.resultCount ?? 0;
|
|
56351
|
+
const site = args?.site ? ` on ${args.site}` : "";
|
|
56318
56352
|
return `Found ${count} result${count === 1 ? "" : "s"}${site} via DuckDuckGo`;
|
|
56319
56353
|
},
|
|
56320
56354
|
execute: async (args, context) => {
|
|
@@ -56517,17 +56551,17 @@ var skillReadTool = {
|
|
|
56517
56551
|
description: "Loads and reads the instructions (SKILL.md) or a relative resource file of a specialized skill by name. Use this when a task requires specialized guidelines.",
|
|
56518
56552
|
parameters: skillReadParamsSchema,
|
|
56519
56553
|
summarizeArgs(args) {
|
|
56520
|
-
if (args
|
|
56554
|
+
if (args?.path) {
|
|
56521
56555
|
return `name="${args.name}" path="${args.path}"`;
|
|
56522
56556
|
}
|
|
56523
|
-
return `name="${args
|
|
56557
|
+
return `name="${args?.name ?? ""}"`;
|
|
56524
56558
|
},
|
|
56525
56559
|
async execute(args, context) {
|
|
56526
56560
|
const cwd = context?.cwd ?? process.cwd();
|
|
56527
56561
|
return readSkillResource(args.name, args.path, cwd);
|
|
56528
56562
|
},
|
|
56529
56563
|
summarize(args, result) {
|
|
56530
|
-
const target = args
|
|
56564
|
+
const target = args?.path ? `${args.name}/${args.path}` : args?.name ?? "skill";
|
|
56531
56565
|
const len = result?.content ? ` (${result.content.length} bytes)` : "";
|
|
56532
56566
|
return `SkillRead: ${target}${len}`;
|
|
56533
56567
|
}
|
|
@@ -56564,17 +56598,19 @@ function summarizeToolResult(toolDef, args, result, isError = false) {
|
|
|
56564
56598
|
return;
|
|
56565
56599
|
}
|
|
56566
56600
|
if (toolDef.summarize) {
|
|
56567
|
-
|
|
56568
|
-
|
|
56569
|
-
|
|
56601
|
+
try {
|
|
56602
|
+
const sum = toolDef.summarize(args, result);
|
|
56603
|
+
if (typeof sum === "string") {
|
|
56604
|
+
const [headline, ...rest] = sum.split(`
|
|
56570
56605
|
`);
|
|
56571
|
-
|
|
56572
|
-
|
|
56573
|
-
|
|
56606
|
+
return {
|
|
56607
|
+
headline: headline ?? "",
|
|
56608
|
+
detail: rest.length > 0 ? { kind: "text", text: rest.join(`
|
|
56574
56609
|
`) } : undefined
|
|
56575
|
-
|
|
56576
|
-
|
|
56577
|
-
|
|
56610
|
+
};
|
|
56611
|
+
}
|
|
56612
|
+
return sum;
|
|
56613
|
+
} catch {}
|
|
56578
56614
|
}
|
|
56579
56615
|
if (typeof result === "string") {
|
|
56580
56616
|
const firstLine = result.split(`
|
|
@@ -56942,6 +56978,723 @@ function getEnvConfig() {
|
|
|
56942
56978
|
}
|
|
56943
56979
|
};
|
|
56944
56980
|
}
|
|
56981
|
+
// src/packages/plugins/src/hooks/types.ts
|
|
56982
|
+
var HOOK_EVENT_NAMES = [
|
|
56983
|
+
"SessionStart",
|
|
56984
|
+
"UserPromptSubmit",
|
|
56985
|
+
"BeforeToolUse",
|
|
56986
|
+
"AfterToolUse",
|
|
56987
|
+
"ToolUseFailure",
|
|
56988
|
+
"AgentStop"
|
|
56989
|
+
];
|
|
56990
|
+
var DEFAULT_HOOK_TIMEOUT_MS = 5000;
|
|
56991
|
+
var MIN_HOOK_TIMEOUT_MS = 100;
|
|
56992
|
+
var MAX_HOOK_TIMEOUT_MS = 30000;
|
|
56993
|
+
var MAX_HOOK_INPUT_BYTES = 256 * 1024;
|
|
56994
|
+
var MAX_HOOK_STDOUT_BYTES = 32 * 1024;
|
|
56995
|
+
var MAX_HOOK_STDERR_BYTES = 16 * 1024;
|
|
56996
|
+
var MAX_HOOK_CONTEXT_CHARS = 8 * 1024;
|
|
56997
|
+
var MAX_AGENT_STOP_CONTINUATIONS = 1;
|
|
56998
|
+
// src/packages/plugins/src/hooks/config.ts
|
|
56999
|
+
import { existsSync as existsSync12, readFileSync as readFileSync9 } from "node:fs";
|
|
57000
|
+
import { join as join13 } from "node:path";
|
|
57001
|
+
import { homedir as homedir8 } from "node:os";
|
|
57002
|
+
|
|
57003
|
+
// src/packages/plugins/src/hooks/match.ts
|
|
57004
|
+
var EVENTS_SUPPORTING_MATCHER = [
|
|
57005
|
+
"SessionStart",
|
|
57006
|
+
"BeforeToolUse",
|
|
57007
|
+
"AfterToolUse",
|
|
57008
|
+
"ToolUseFailure"
|
|
57009
|
+
];
|
|
57010
|
+
function supportsMatcher(event) {
|
|
57011
|
+
return EVENTS_SUPPORTING_MATCHER.includes(event);
|
|
57012
|
+
}
|
|
57013
|
+
function compileMatcher(event, rawMatcher) {
|
|
57014
|
+
if (!supportsMatcher(event)) {
|
|
57015
|
+
return {};
|
|
57016
|
+
}
|
|
57017
|
+
const trimmed = rawMatcher?.trim();
|
|
57018
|
+
if (!trimmed || trimmed === "*") {
|
|
57019
|
+
return { allTools: true };
|
|
57020
|
+
}
|
|
57021
|
+
const parts = trimmed.split("|").map((p) => p.trim()).filter(Boolean);
|
|
57022
|
+
if (parts.length === 0 || parts.includes("*")) {
|
|
57023
|
+
return { allTools: true };
|
|
57024
|
+
}
|
|
57025
|
+
return {
|
|
57026
|
+
allTools: false,
|
|
57027
|
+
matcherSet: new Set(parts)
|
|
57028
|
+
};
|
|
57029
|
+
}
|
|
57030
|
+
function hasHooksForEvent(hooks, event) {
|
|
57031
|
+
for (let i = 0;i < hooks.length; i++) {
|
|
57032
|
+
const h = hooks[i];
|
|
57033
|
+
if (h.enabled && h.event === event) {
|
|
57034
|
+
return true;
|
|
57035
|
+
}
|
|
57036
|
+
}
|
|
57037
|
+
return false;
|
|
57038
|
+
}
|
|
57039
|
+
function matchesHook(hook, payload) {
|
|
57040
|
+
if (!hook.enabled || hook.event !== payload.hook_event_name) {
|
|
57041
|
+
return false;
|
|
57042
|
+
}
|
|
57043
|
+
switch (payload.hook_event_name) {
|
|
57044
|
+
case "SessionStart": {
|
|
57045
|
+
if (hook.allTools || !hook.matcherSet || hook.matcherSet.size === 0) {
|
|
57046
|
+
return true;
|
|
57047
|
+
}
|
|
57048
|
+
return hook.matcherSet.has(payload.source);
|
|
57049
|
+
}
|
|
57050
|
+
case "BeforeToolUse":
|
|
57051
|
+
case "AfterToolUse":
|
|
57052
|
+
case "ToolUseFailure": {
|
|
57053
|
+
if (hook.allTools || !hook.matcherSet || hook.matcherSet.size === 0) {
|
|
57054
|
+
return true;
|
|
57055
|
+
}
|
|
57056
|
+
const toolName = payload.tool_name;
|
|
57057
|
+
return hook.matcherSet.has(toolName);
|
|
57058
|
+
}
|
|
57059
|
+
case "UserPromptSubmit":
|
|
57060
|
+
case "AgentStop":
|
|
57061
|
+
return true;
|
|
57062
|
+
default:
|
|
57063
|
+
return false;
|
|
57064
|
+
}
|
|
57065
|
+
}
|
|
57066
|
+
|
|
57067
|
+
// src/packages/plugins/src/hooks/config.ts
|
|
57068
|
+
var rawHookDefinitionSchema = object({
|
|
57069
|
+
name: string2().min(1, "Hook name must not be empty."),
|
|
57070
|
+
command: string2().min(1, "Hook command must not be empty."),
|
|
57071
|
+
matcher: string2().optional(),
|
|
57072
|
+
timeoutMs: number2().int().min(MIN_HOOK_TIMEOUT_MS, `timeoutMs must be at least ${MIN_HOOK_TIMEOUT_MS}ms.`).max(MAX_HOOK_TIMEOUT_MS, `timeoutMs must not exceed ${MAX_HOOK_TIMEOUT_MS}ms.`).optional(),
|
|
57073
|
+
enabled: boolean2().optional(),
|
|
57074
|
+
description: string2().optional()
|
|
57075
|
+
}).strict();
|
|
57076
|
+
var rawHooksConfigSchema = object({
|
|
57077
|
+
version: literal(1, {
|
|
57078
|
+
errorMap: () => ({ message: "Hooks config version must be 1." })
|
|
57079
|
+
}),
|
|
57080
|
+
hooks: object({
|
|
57081
|
+
SessionStart: array(rawHookDefinitionSchema).optional(),
|
|
57082
|
+
UserPromptSubmit: array(rawHookDefinitionSchema).optional(),
|
|
57083
|
+
BeforeToolUse: array(rawHookDefinitionSchema).optional(),
|
|
57084
|
+
AfterToolUse: array(rawHookDefinitionSchema).optional(),
|
|
57085
|
+
ToolUseFailure: array(rawHookDefinitionSchema).optional(),
|
|
57086
|
+
AgentStop: array(rawHookDefinitionSchema).optional()
|
|
57087
|
+
}).strict().optional()
|
|
57088
|
+
}).strict();
|
|
57089
|
+
function compileHooksConfig(rawConfig, source, sourcePath) {
|
|
57090
|
+
const parseResult = rawHooksConfigSchema.safeParse(rawConfig);
|
|
57091
|
+
if (!parseResult.success) {
|
|
57092
|
+
const errorMsg = parseResult.error.issues.map((i) => `${i.path.join(".") || "root"}: ${i.message}`).join("; ");
|
|
57093
|
+
return { hooks: [], error: `Invalid hooks configuration: ${errorMsg}` };
|
|
57094
|
+
}
|
|
57095
|
+
const validConfig = parseResult.data;
|
|
57096
|
+
const compiled = [];
|
|
57097
|
+
if (!validConfig.hooks) {
|
|
57098
|
+
return { hooks: compiled };
|
|
57099
|
+
}
|
|
57100
|
+
for (const eventName of HOOK_EVENT_NAMES) {
|
|
57101
|
+
const hookDefs = validConfig.hooks[eventName];
|
|
57102
|
+
if (!hookDefs || !Array.isArray(hookDefs)) {
|
|
57103
|
+
continue;
|
|
57104
|
+
}
|
|
57105
|
+
for (let index = 0;index < hookDefs.length; index++) {
|
|
57106
|
+
const def = hookDefs[index];
|
|
57107
|
+
if (def.matcher !== undefined && !supportsMatcher(eventName)) {
|
|
57108
|
+
return {
|
|
57109
|
+
hooks: [],
|
|
57110
|
+
error: `Hook "${def.name}" on event "${eventName}" cannot define a matcher; matchers are only supported on SessionStart, BeforeToolUse, AfterToolUse, and ToolUseFailure.`
|
|
57111
|
+
};
|
|
57112
|
+
}
|
|
57113
|
+
const { allTools, matcherSet } = compileMatcher(eventName, def.matcher);
|
|
57114
|
+
compiled.push({
|
|
57115
|
+
source,
|
|
57116
|
+
sourcePath,
|
|
57117
|
+
event: eventName,
|
|
57118
|
+
name: def.name,
|
|
57119
|
+
command: def.command,
|
|
57120
|
+
matcher: def.matcher,
|
|
57121
|
+
allTools,
|
|
57122
|
+
matcherSet,
|
|
57123
|
+
timeoutMs: def.timeoutMs ?? DEFAULT_HOOK_TIMEOUT_MS,
|
|
57124
|
+
enabled: def.enabled ?? true,
|
|
57125
|
+
description: def.description
|
|
57126
|
+
});
|
|
57127
|
+
}
|
|
57128
|
+
}
|
|
57129
|
+
return { hooks: compiled };
|
|
57130
|
+
}
|
|
57131
|
+
function getUserHooksPath() {
|
|
57132
|
+
const dir = process.env.STEWARD_SETTINGS_DIR || join13(homedir8(), ".steward");
|
|
57133
|
+
return join13(dir, "hooks.json");
|
|
57134
|
+
}
|
|
57135
|
+
function getProjectHooksPath(projectDir) {
|
|
57136
|
+
return join13(projectDir, ".steward", "hooks.json");
|
|
57137
|
+
}
|
|
57138
|
+
function loadHooksConfigFile(filePath, source) {
|
|
57139
|
+
if (!existsSync12(filePath)) {
|
|
57140
|
+
return { hooks: [] };
|
|
57141
|
+
}
|
|
57142
|
+
let content;
|
|
57143
|
+
try {
|
|
57144
|
+
content = readFileSync9(filePath, "utf-8");
|
|
57145
|
+
} catch (err) {
|
|
57146
|
+
return {
|
|
57147
|
+
hooks: [],
|
|
57148
|
+
error: `Failed to read hooks configuration from ${filePath}: ${err.message}`
|
|
57149
|
+
};
|
|
57150
|
+
}
|
|
57151
|
+
const trimmed = content.trim();
|
|
57152
|
+
if (!trimmed) {
|
|
57153
|
+
return { hooks: [] };
|
|
57154
|
+
}
|
|
57155
|
+
let parsed;
|
|
57156
|
+
try {
|
|
57157
|
+
parsed = JSON.parse(trimmed);
|
|
57158
|
+
} catch (err) {
|
|
57159
|
+
return {
|
|
57160
|
+
hooks: [],
|
|
57161
|
+
error: `Failed to parse JSON in hooks configuration from ${filePath}: ${err.message}`
|
|
57162
|
+
};
|
|
57163
|
+
}
|
|
57164
|
+
return compileHooksConfig(parsed, source, filePath);
|
|
57165
|
+
}
|
|
57166
|
+
// src/packages/plugins/src/hooks/execute.ts
|
|
57167
|
+
import { spawn as spawn2 } from "node:child_process";
|
|
57168
|
+
var BLOCKING_EVENTS = new Set(["UserPromptSubmit", "BeforeToolUse", "AgentStop"]);
|
|
57169
|
+
function killProcessTree(pid, child) {
|
|
57170
|
+
if (!pid)
|
|
57171
|
+
return;
|
|
57172
|
+
try {
|
|
57173
|
+
child.stdin?.destroy();
|
|
57174
|
+
} catch {}
|
|
57175
|
+
if (process.platform === "win32") {
|
|
57176
|
+
try {
|
|
57177
|
+
spawn2("taskkill", ["/pid", String(pid), "/T", "/F"], { stdio: "ignore" });
|
|
57178
|
+
} catch {
|
|
57179
|
+
try {
|
|
57180
|
+
child.kill("SIGKILL");
|
|
57181
|
+
} catch {}
|
|
57182
|
+
}
|
|
57183
|
+
} else {
|
|
57184
|
+
try {
|
|
57185
|
+
process.kill(-pid, "SIGKILL");
|
|
57186
|
+
} catch (err) {
|
|
57187
|
+
if (err.code !== "ESRCH") {
|
|
57188
|
+
try {
|
|
57189
|
+
child.kill("SIGKILL");
|
|
57190
|
+
} catch {}
|
|
57191
|
+
}
|
|
57192
|
+
}
|
|
57193
|
+
try {
|
|
57194
|
+
child.kill("SIGKILL");
|
|
57195
|
+
} catch {}
|
|
57196
|
+
}
|
|
57197
|
+
}
|
|
57198
|
+
async function executeHook(options) {
|
|
57199
|
+
const { hook, payload, abortSignal } = options;
|
|
57200
|
+
if (hook.builtinHandler) {
|
|
57201
|
+
try {
|
|
57202
|
+
const result = await hook.builtinHandler(payload);
|
|
57203
|
+
return { result };
|
|
57204
|
+
} catch (err) {
|
|
57205
|
+
return {
|
|
57206
|
+
diagnostic: {
|
|
57207
|
+
hookName: hook.name,
|
|
57208
|
+
source: hook.source,
|
|
57209
|
+
event: hook.event,
|
|
57210
|
+
error: `Built-in hook threw error: ${err.message}`
|
|
57211
|
+
}
|
|
57212
|
+
};
|
|
57213
|
+
}
|
|
57214
|
+
}
|
|
57215
|
+
if (abortSignal?.aborted) {
|
|
57216
|
+
return {
|
|
57217
|
+
diagnostic: {
|
|
57218
|
+
hookName: hook.name,
|
|
57219
|
+
source: hook.source,
|
|
57220
|
+
event: hook.event,
|
|
57221
|
+
error: "Execution aborted before spawning."
|
|
57222
|
+
}
|
|
57223
|
+
};
|
|
57224
|
+
}
|
|
57225
|
+
let serializedPayload;
|
|
57226
|
+
try {
|
|
57227
|
+
serializedPayload = JSON.stringify(payload);
|
|
57228
|
+
} catch (err) {
|
|
57229
|
+
return {
|
|
57230
|
+
diagnostic: {
|
|
57231
|
+
hookName: hook.name,
|
|
57232
|
+
source: hook.source,
|
|
57233
|
+
event: hook.event,
|
|
57234
|
+
error: `Failed to serialize payload: ${err.message}`
|
|
57235
|
+
}
|
|
57236
|
+
};
|
|
57237
|
+
}
|
|
57238
|
+
if (Buffer.byteLength(serializedPayload, "utf-8") > MAX_HOOK_INPUT_BYTES) {
|
|
57239
|
+
return {
|
|
57240
|
+
diagnostic: {
|
|
57241
|
+
hookName: hook.name,
|
|
57242
|
+
source: hook.source,
|
|
57243
|
+
event: hook.event,
|
|
57244
|
+
error: `Payload exceeds max input limit of ${MAX_HOOK_INPUT_BYTES} bytes.`
|
|
57245
|
+
}
|
|
57246
|
+
};
|
|
57247
|
+
}
|
|
57248
|
+
const env = {
|
|
57249
|
+
...getFilteredChildEnv(),
|
|
57250
|
+
STEWARD_PROJECT_DIR: payload.project_dir,
|
|
57251
|
+
STEWARD_CWD: payload.cwd,
|
|
57252
|
+
STEWARD_SESSION_ID: payload.session_id,
|
|
57253
|
+
STEWARD_TURN_ID: payload.turn_id ?? "",
|
|
57254
|
+
STEWARD_HOOK_EVENT: payload.hook_event_name,
|
|
57255
|
+
STEWARD_HOOK_NAME: hook.name
|
|
57256
|
+
};
|
|
57257
|
+
const { shell, args } = getPlatformShell();
|
|
57258
|
+
const childArgs = [...args, hook.command];
|
|
57259
|
+
return new Promise((resolve) => {
|
|
57260
|
+
let child;
|
|
57261
|
+
let timer = null;
|
|
57262
|
+
let stdoutBuffer = "";
|
|
57263
|
+
let stderrBuffer = "";
|
|
57264
|
+
let hasExited = false;
|
|
57265
|
+
let abortListener = null;
|
|
57266
|
+
try {
|
|
57267
|
+
child = spawn2(shell, childArgs, {
|
|
57268
|
+
cwd: payload.project_dir,
|
|
57269
|
+
env,
|
|
57270
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
57271
|
+
detached: process.platform !== "win32"
|
|
57272
|
+
});
|
|
57273
|
+
} catch (err) {
|
|
57274
|
+
return resolve({
|
|
57275
|
+
diagnostic: {
|
|
57276
|
+
hookName: hook.name,
|
|
57277
|
+
source: hook.source,
|
|
57278
|
+
event: hook.event,
|
|
57279
|
+
error: `Failed to spawn process: ${err.message}`
|
|
57280
|
+
}
|
|
57281
|
+
});
|
|
57282
|
+
}
|
|
57283
|
+
const cleanup = () => {
|
|
57284
|
+
if (timer) {
|
|
57285
|
+
clearTimeout(timer);
|
|
57286
|
+
timer = null;
|
|
57287
|
+
}
|
|
57288
|
+
if (abortListener && abortSignal) {
|
|
57289
|
+
abortSignal.removeEventListener("abort", abortListener);
|
|
57290
|
+
abortListener = null;
|
|
57291
|
+
}
|
|
57292
|
+
};
|
|
57293
|
+
timer = setTimeout(() => {
|
|
57294
|
+
if (hasExited)
|
|
57295
|
+
return;
|
|
57296
|
+
hasExited = true;
|
|
57297
|
+
cleanup();
|
|
57298
|
+
killProcessTree(child.pid, child);
|
|
57299
|
+
resolve({
|
|
57300
|
+
diagnostic: {
|
|
57301
|
+
hookName: hook.name,
|
|
57302
|
+
source: hook.source,
|
|
57303
|
+
event: hook.event,
|
|
57304
|
+
error: `Hook timed out after ${hook.timeoutMs}ms.`,
|
|
57305
|
+
stderr: stderrBuffer
|
|
57306
|
+
}
|
|
57307
|
+
});
|
|
57308
|
+
}, hook.timeoutMs);
|
|
57309
|
+
if (abortSignal) {
|
|
57310
|
+
abortListener = () => {
|
|
57311
|
+
if (hasExited)
|
|
57312
|
+
return;
|
|
57313
|
+
hasExited = true;
|
|
57314
|
+
cleanup();
|
|
57315
|
+
killProcessTree(child.pid, child);
|
|
57316
|
+
resolve({
|
|
57317
|
+
diagnostic: {
|
|
57318
|
+
hookName: hook.name,
|
|
57319
|
+
source: hook.source,
|
|
57320
|
+
event: hook.event,
|
|
57321
|
+
error: "Hook execution aborted by signal.",
|
|
57322
|
+
stderr: stderrBuffer
|
|
57323
|
+
}
|
|
57324
|
+
});
|
|
57325
|
+
};
|
|
57326
|
+
abortSignal.addEventListener("abort", abortListener, { once: true });
|
|
57327
|
+
}
|
|
57328
|
+
child.stdin?.on("error", () => {});
|
|
57329
|
+
child.stdout?.on("error", () => {});
|
|
57330
|
+
child.stderr?.on("error", () => {});
|
|
57331
|
+
child.stdout?.on("data", (chunk) => {
|
|
57332
|
+
if (stdoutBuffer.length < MAX_HOOK_STDOUT_BYTES) {
|
|
57333
|
+
stdoutBuffer += chunk.toString("utf-8");
|
|
57334
|
+
if (stdoutBuffer.length > MAX_HOOK_STDOUT_BYTES) {
|
|
57335
|
+
stdoutBuffer = stdoutBuffer.slice(0, MAX_HOOK_STDOUT_BYTES);
|
|
57336
|
+
}
|
|
57337
|
+
}
|
|
57338
|
+
});
|
|
57339
|
+
child.stderr?.on("data", (chunk) => {
|
|
57340
|
+
if (stderrBuffer.length < MAX_HOOK_STDERR_BYTES) {
|
|
57341
|
+
stderrBuffer += chunk.toString("utf-8");
|
|
57342
|
+
if (stderrBuffer.length > MAX_HOOK_STDERR_BYTES) {
|
|
57343
|
+
stderrBuffer = stderrBuffer.slice(0, MAX_HOOK_STDERR_BYTES);
|
|
57344
|
+
}
|
|
57345
|
+
}
|
|
57346
|
+
});
|
|
57347
|
+
try {
|
|
57348
|
+
child.stdin?.write(serializedPayload, "utf-8", () => {
|
|
57349
|
+
try {
|
|
57350
|
+
child.stdin?.end();
|
|
57351
|
+
} catch {}
|
|
57352
|
+
});
|
|
57353
|
+
} catch {
|
|
57354
|
+
try {
|
|
57355
|
+
child.stdin?.end();
|
|
57356
|
+
} catch {}
|
|
57357
|
+
}
|
|
57358
|
+
const onExit = (code) => {
|
|
57359
|
+
if (hasExited)
|
|
57360
|
+
return;
|
|
57361
|
+
hasExited = true;
|
|
57362
|
+
cleanup();
|
|
57363
|
+
const trimmedStdout = stdoutBuffer.trim();
|
|
57364
|
+
const trimmedStderr = stderrBuffer.trim();
|
|
57365
|
+
let parsedJsonResult;
|
|
57366
|
+
let jsonParseError;
|
|
57367
|
+
if (trimmedStdout.length > 0) {
|
|
57368
|
+
try {
|
|
57369
|
+
const parsed = JSON.parse(trimmedStdout);
|
|
57370
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
57371
|
+
const decision = parsed.decision === "block" || parsed.decision === "allow" ? parsed.decision : undefined;
|
|
57372
|
+
const reason = typeof parsed.reason === "string" ? parsed.reason : undefined;
|
|
57373
|
+
let additionalContext = typeof parsed.additionalContext === "string" ? parsed.additionalContext : undefined;
|
|
57374
|
+
if (additionalContext && additionalContext.length > MAX_HOOK_CONTEXT_CHARS) {
|
|
57375
|
+
additionalContext = additionalContext.slice(0, MAX_HOOK_CONTEXT_CHARS);
|
|
57376
|
+
}
|
|
57377
|
+
parsedJsonResult = {
|
|
57378
|
+
decision,
|
|
57379
|
+
reason,
|
|
57380
|
+
additionalContext
|
|
57381
|
+
};
|
|
57382
|
+
} else {
|
|
57383
|
+
jsonParseError = "Stdout was not a valid JSON object.";
|
|
57384
|
+
}
|
|
57385
|
+
} catch (err) {
|
|
57386
|
+
jsonParseError = `Failed to parse stdout JSON: ${err.message}`;
|
|
57387
|
+
}
|
|
57388
|
+
}
|
|
57389
|
+
if (code === 0) {
|
|
57390
|
+
if (jsonParseError) {
|
|
57391
|
+
return resolve({
|
|
57392
|
+
diagnostic: {
|
|
57393
|
+
hookName: hook.name,
|
|
57394
|
+
source: hook.source,
|
|
57395
|
+
event: hook.event,
|
|
57396
|
+
error: jsonParseError,
|
|
57397
|
+
exitCode: code,
|
|
57398
|
+
stderr: trimmedStderr
|
|
57399
|
+
}
|
|
57400
|
+
});
|
|
57401
|
+
}
|
|
57402
|
+
return resolve({ result: parsedJsonResult ?? {} });
|
|
57403
|
+
}
|
|
57404
|
+
if (code === 2 && BLOCKING_EVENTS.has(payload.hook_event_name)) {
|
|
57405
|
+
if (parsedJsonResult && parsedJsonResult.decision === "block") {
|
|
57406
|
+
return resolve({ result: parsedJsonResult });
|
|
57407
|
+
}
|
|
57408
|
+
const blockReason = parsedJsonResult?.reason || (trimmedStderr.length > 0 ? trimmedStderr.slice(0, 500) : `Hook "${hook.name}" blocked the action.`);
|
|
57409
|
+
return resolve({
|
|
57410
|
+
result: {
|
|
57411
|
+
decision: "block",
|
|
57412
|
+
reason: blockReason,
|
|
57413
|
+
additionalContext: parsedJsonResult?.additionalContext
|
|
57414
|
+
},
|
|
57415
|
+
diagnostic: trimmedStderr ? {
|
|
57416
|
+
hookName: hook.name,
|
|
57417
|
+
source: hook.source,
|
|
57418
|
+
event: hook.event,
|
|
57419
|
+
error: `Hook exited with code 2: ${trimmedStderr}`,
|
|
57420
|
+
exitCode: code,
|
|
57421
|
+
stderr: trimmedStderr
|
|
57422
|
+
} : undefined
|
|
57423
|
+
});
|
|
57424
|
+
}
|
|
57425
|
+
return resolve({
|
|
57426
|
+
diagnostic: {
|
|
57427
|
+
hookName: hook.name,
|
|
57428
|
+
source: hook.source,
|
|
57429
|
+
event: hook.event,
|
|
57430
|
+
error: trimmedStderr.length > 0 ? `Hook exited with code ${code}: ${trimmedStderr}` : `Hook exited with code ${code}.`,
|
|
57431
|
+
exitCode: code,
|
|
57432
|
+
stderr: trimmedStderr
|
|
57433
|
+
}
|
|
57434
|
+
});
|
|
57435
|
+
};
|
|
57436
|
+
child.on("exit", (code) => onExit(code));
|
|
57437
|
+
child.on("close", (code) => onExit(code));
|
|
57438
|
+
child.on("error", (err) => {
|
|
57439
|
+
if (hasExited)
|
|
57440
|
+
return;
|
|
57441
|
+
hasExited = true;
|
|
57442
|
+
cleanup();
|
|
57443
|
+
resolve({
|
|
57444
|
+
diagnostic: {
|
|
57445
|
+
hookName: hook.name,
|
|
57446
|
+
source: hook.source,
|
|
57447
|
+
event: hook.event,
|
|
57448
|
+
error: `Process execution error: ${err.message}`
|
|
57449
|
+
}
|
|
57450
|
+
});
|
|
57451
|
+
});
|
|
57452
|
+
});
|
|
57453
|
+
}
|
|
57454
|
+
// src/packages/plugins/src/builtins/index.ts
|
|
57455
|
+
function createConfigHealthBuiltin(options = {}) {
|
|
57456
|
+
return {
|
|
57457
|
+
source: "builtin",
|
|
57458
|
+
event: "SessionStart",
|
|
57459
|
+
name: "HookConfigHealth",
|
|
57460
|
+
command: "builtin:hook-config-health",
|
|
57461
|
+
allTools: true,
|
|
57462
|
+
timeoutMs: 1000,
|
|
57463
|
+
enabled: true,
|
|
57464
|
+
description: "Surfaces hook configuration health and loaded hook counts on session start.",
|
|
57465
|
+
builtinHandler: (payload) => {
|
|
57466
|
+
const startPayload = payload;
|
|
57467
|
+
if (options.configErrors && options.configErrors.length > 0) {
|
|
57468
|
+
const errorLines = options.configErrors.map((e) => `- [${e.source}] ${e.error}`).join(`
|
|
57469
|
+
`);
|
|
57470
|
+
return {
|
|
57471
|
+
additionalContext: `[Hook Configuration Diagnostics]
|
|
57472
|
+
The following hook configuration warnings were encountered during ${startPayload.source}:
|
|
57473
|
+
${errorLines}`
|
|
57474
|
+
};
|
|
57475
|
+
}
|
|
57476
|
+
return {};
|
|
57477
|
+
}
|
|
57478
|
+
};
|
|
57479
|
+
}
|
|
57480
|
+
|
|
57481
|
+
// src/packages/plugins/src/hooks/runtime.ts
|
|
57482
|
+
class HookRuntime {
|
|
57483
|
+
hooks;
|
|
57484
|
+
isTrusted;
|
|
57485
|
+
pendingSessionContext = [];
|
|
57486
|
+
stopHookContinuationsUsed = 0;
|
|
57487
|
+
constructor(options = {}) {
|
|
57488
|
+
this.isTrusted = options.isTrusted ?? false;
|
|
57489
|
+
this.hooks = Object.freeze([...options.hooks ?? []]);
|
|
57490
|
+
}
|
|
57491
|
+
static load(options) {
|
|
57492
|
+
const {
|
|
57493
|
+
projectDir,
|
|
57494
|
+
isTrusted,
|
|
57495
|
+
userHooksPath = getUserHooksPath(),
|
|
57496
|
+
projectHooksPath = getProjectHooksPath(projectDir),
|
|
57497
|
+
pluginHooks = [],
|
|
57498
|
+
builtins = []
|
|
57499
|
+
} = options;
|
|
57500
|
+
const configErrors = [];
|
|
57501
|
+
const orderedHooks = [];
|
|
57502
|
+
for (const b of builtins) {
|
|
57503
|
+
orderedHooks.push(b);
|
|
57504
|
+
}
|
|
57505
|
+
if (isTrusted) {
|
|
57506
|
+
const userRes = loadHooksConfigFile(userHooksPath, "user");
|
|
57507
|
+
if (userRes.error) {
|
|
57508
|
+
configErrors.push({ source: `User: ${userHooksPath}`, error: userRes.error });
|
|
57509
|
+
} else {
|
|
57510
|
+
for (const h of userRes.hooks) {
|
|
57511
|
+
orderedHooks.push(h);
|
|
57512
|
+
}
|
|
57513
|
+
}
|
|
57514
|
+
const projRes = loadHooksConfigFile(projectHooksPath, "project");
|
|
57515
|
+
if (projRes.error) {
|
|
57516
|
+
configErrors.push({ source: `Project: ${projectHooksPath}`, error: projRes.error });
|
|
57517
|
+
} else {
|
|
57518
|
+
for (const h of projRes.hooks) {
|
|
57519
|
+
orderedHooks.push(h);
|
|
57520
|
+
}
|
|
57521
|
+
}
|
|
57522
|
+
for (const p of pluginHooks) {
|
|
57523
|
+
orderedHooks.push(p);
|
|
57524
|
+
}
|
|
57525
|
+
}
|
|
57526
|
+
const healthBuiltin = createConfigHealthBuiltin({
|
|
57527
|
+
configErrors,
|
|
57528
|
+
loadedHooksCount: orderedHooks.length
|
|
57529
|
+
});
|
|
57530
|
+
orderedHooks.unshift(healthBuiltin);
|
|
57531
|
+
return new HookRuntime({
|
|
57532
|
+
hooks: orderedHooks,
|
|
57533
|
+
isTrusted
|
|
57534
|
+
});
|
|
57535
|
+
}
|
|
57536
|
+
get compiledHooks() {
|
|
57537
|
+
return this.hooks;
|
|
57538
|
+
}
|
|
57539
|
+
get trusted() {
|
|
57540
|
+
return this.isTrusted;
|
|
57541
|
+
}
|
|
57542
|
+
hasHooksForEvent(event) {
|
|
57543
|
+
return hasHooksForEvent(this.hooks, event);
|
|
57544
|
+
}
|
|
57545
|
+
getPendingSessionContext() {
|
|
57546
|
+
return [...this.pendingSessionContext];
|
|
57547
|
+
}
|
|
57548
|
+
consumePendingSessionContext() {
|
|
57549
|
+
const ctx = [...this.pendingSessionContext];
|
|
57550
|
+
this.pendingSessionContext = [];
|
|
57551
|
+
return ctx;
|
|
57552
|
+
}
|
|
57553
|
+
canContinueAgentStop() {
|
|
57554
|
+
return this.stopHookContinuationsUsed < MAX_AGENT_STOP_CONTINUATIONS;
|
|
57555
|
+
}
|
|
57556
|
+
recordAgentStopContinuation() {
|
|
57557
|
+
this.stopHookContinuationsUsed++;
|
|
57558
|
+
}
|
|
57559
|
+
resetTurnContinuationState() {
|
|
57560
|
+
this.stopHookContinuationsUsed = 0;
|
|
57561
|
+
}
|
|
57562
|
+
async executeLifecycle(payload, abortSignal) {
|
|
57563
|
+
if (!this.hasHooksForEvent(payload.hook_event_name)) {
|
|
57564
|
+
return {
|
|
57565
|
+
blocked: false,
|
|
57566
|
+
context: [],
|
|
57567
|
+
errors: []
|
|
57568
|
+
};
|
|
57569
|
+
}
|
|
57570
|
+
const matchingHooks = this.hooks.filter((h) => matchesHook(h, payload));
|
|
57571
|
+
if (matchingHooks.length === 0) {
|
|
57572
|
+
return {
|
|
57573
|
+
blocked: false,
|
|
57574
|
+
context: [],
|
|
57575
|
+
errors: []
|
|
57576
|
+
};
|
|
57577
|
+
}
|
|
57578
|
+
let blocked = false;
|
|
57579
|
+
let firstBlockReason;
|
|
57580
|
+
const contextList = [];
|
|
57581
|
+
const errorDiagnostics = [];
|
|
57582
|
+
for (const hook of matchingHooks) {
|
|
57583
|
+
if (abortSignal?.aborted) {
|
|
57584
|
+
break;
|
|
57585
|
+
}
|
|
57586
|
+
const execResult = await executeHook({
|
|
57587
|
+
hook,
|
|
57588
|
+
payload,
|
|
57589
|
+
abortSignal
|
|
57590
|
+
});
|
|
57591
|
+
if (execResult.diagnostic) {
|
|
57592
|
+
errorDiagnostics.push(execResult.diagnostic);
|
|
57593
|
+
}
|
|
57594
|
+
if (execResult.result) {
|
|
57595
|
+
if (execResult.result.decision === "block") {
|
|
57596
|
+
blocked = true;
|
|
57597
|
+
if (!firstBlockReason) {
|
|
57598
|
+
firstBlockReason = execResult.result.reason || `Hook "${hook.name}" blocked the action.`;
|
|
57599
|
+
}
|
|
57600
|
+
}
|
|
57601
|
+
if (execResult.result.additionalContext && execResult.result.additionalContext.trim()) {
|
|
57602
|
+
contextList.push(execResult.result.additionalContext.trim());
|
|
57603
|
+
}
|
|
57604
|
+
}
|
|
57605
|
+
}
|
|
57606
|
+
return {
|
|
57607
|
+
blocked,
|
|
57608
|
+
firstBlockReason,
|
|
57609
|
+
context: contextList,
|
|
57610
|
+
errors: errorDiagnostics
|
|
57611
|
+
};
|
|
57612
|
+
}
|
|
57613
|
+
async runSessionStart(params) {
|
|
57614
|
+
const payload = {
|
|
57615
|
+
hook_event_name: "SessionStart",
|
|
57616
|
+
session_id: params.sessionId,
|
|
57617
|
+
project_dir: params.projectDir,
|
|
57618
|
+
cwd: params.cwd,
|
|
57619
|
+
source: params.source
|
|
57620
|
+
};
|
|
57621
|
+
const result = await this.executeLifecycle(payload, params.abortSignal);
|
|
57622
|
+
if (result.context.length > 0) {
|
|
57623
|
+
this.pendingSessionContext.push(...result.context);
|
|
57624
|
+
}
|
|
57625
|
+
return result;
|
|
57626
|
+
}
|
|
57627
|
+
async runUserPromptSubmit(params) {
|
|
57628
|
+
this.resetTurnContinuationState();
|
|
57629
|
+
const payload = {
|
|
57630
|
+
hook_event_name: "UserPromptSubmit",
|
|
57631
|
+
session_id: params.sessionId,
|
|
57632
|
+
turn_id: params.turnId,
|
|
57633
|
+
project_dir: params.projectDir,
|
|
57634
|
+
cwd: params.cwd,
|
|
57635
|
+
prompt: params.prompt
|
|
57636
|
+
};
|
|
57637
|
+
return this.executeLifecycle(payload, params.abortSignal);
|
|
57638
|
+
}
|
|
57639
|
+
async runBeforeToolUse(params) {
|
|
57640
|
+
const payload = {
|
|
57641
|
+
hook_event_name: "BeforeToolUse",
|
|
57642
|
+
session_id: params.sessionId,
|
|
57643
|
+
turn_id: params.turnId,
|
|
57644
|
+
project_dir: params.projectDir,
|
|
57645
|
+
cwd: params.cwd,
|
|
57646
|
+
tool_call_id: params.toolCallId,
|
|
57647
|
+
tool_name: params.toolName,
|
|
57648
|
+
tool_input: params.toolInput
|
|
57649
|
+
};
|
|
57650
|
+
return this.executeLifecycle(payload, params.abortSignal);
|
|
57651
|
+
}
|
|
57652
|
+
async runAfterToolUse(params) {
|
|
57653
|
+
const payload = {
|
|
57654
|
+
hook_event_name: "AfterToolUse",
|
|
57655
|
+
session_id: params.sessionId,
|
|
57656
|
+
turn_id: params.turnId,
|
|
57657
|
+
project_dir: params.projectDir,
|
|
57658
|
+
cwd: params.cwd,
|
|
57659
|
+
tool_call_id: params.toolCallId,
|
|
57660
|
+
tool_name: params.toolName,
|
|
57661
|
+
tool_input: params.toolInput,
|
|
57662
|
+
tool_output: params.toolOutput,
|
|
57663
|
+
is_error: params.isError,
|
|
57664
|
+
duration_ms: params.durationMs
|
|
57665
|
+
};
|
|
57666
|
+
return this.executeLifecycle(payload, params.abortSignal);
|
|
57667
|
+
}
|
|
57668
|
+
async runToolUseFailure(params) {
|
|
57669
|
+
const payload = {
|
|
57670
|
+
hook_event_name: "ToolUseFailure",
|
|
57671
|
+
session_id: params.sessionId,
|
|
57672
|
+
turn_id: params.turnId,
|
|
57673
|
+
project_dir: params.projectDir,
|
|
57674
|
+
cwd: params.cwd,
|
|
57675
|
+
tool_call_id: params.toolCallId,
|
|
57676
|
+
tool_name: params.toolName,
|
|
57677
|
+
tool_input: params.toolInput,
|
|
57678
|
+
error: params.error,
|
|
57679
|
+
duration_ms: params.durationMs
|
|
57680
|
+
};
|
|
57681
|
+
return this.executeLifecycle(payload, params.abortSignal);
|
|
57682
|
+
}
|
|
57683
|
+
async runAgentStop(params) {
|
|
57684
|
+
const payload = {
|
|
57685
|
+
hook_event_name: "AgentStop",
|
|
57686
|
+
session_id: params.sessionId,
|
|
57687
|
+
turn_id: params.turnId,
|
|
57688
|
+
project_dir: params.projectDir,
|
|
57689
|
+
cwd: params.cwd,
|
|
57690
|
+
stop_reason: params.stopReason,
|
|
57691
|
+
finish_reason: params.finishReason,
|
|
57692
|
+
assistant_text: params.assistantText,
|
|
57693
|
+
stop_hook_active: params.stopHookActive
|
|
57694
|
+
};
|
|
57695
|
+
return this.executeLifecycle(payload, params.abortSignal);
|
|
57696
|
+
}
|
|
57697
|
+
}
|
|
56945
57698
|
// src/packages/agents/src/engine/constants.ts
|
|
56946
57699
|
var SAFETY_STEP_CEILING = 50;
|
|
56947
57700
|
|
|
@@ -56958,9 +57711,12 @@ async function runAgentTurn(options) {
|
|
|
56958
57711
|
const toolResults = [];
|
|
56959
57712
|
const turnStartedAt = new Date().toISOString();
|
|
56960
57713
|
const turnStartMonotonic = performance.now();
|
|
57714
|
+
let baseInstructions = options.instructions ?? "";
|
|
57715
|
+
let pendingStepContext = [];
|
|
57716
|
+
let stopHookContinuations = 0;
|
|
56961
57717
|
const activeMessages = [...options.messages];
|
|
56962
|
-
if (
|
|
56963
|
-
activeMessages.unshift({ role: "system", content:
|
|
57718
|
+
if (baseInstructions) {
|
|
57719
|
+
activeMessages.unshift({ role: "system", content: baseInstructions });
|
|
56964
57720
|
}
|
|
56965
57721
|
const accumulatedUsage = {
|
|
56966
57722
|
inputTokens: 0,
|
|
@@ -56980,6 +57736,29 @@ async function runAgentTurn(options) {
|
|
|
56980
57736
|
if (options.abortSignal?.aborted) {
|
|
56981
57737
|
break;
|
|
56982
57738
|
}
|
|
57739
|
+
if (pendingStepContext.length > 0) {
|
|
57740
|
+
const extraSection = `
|
|
57741
|
+
|
|
57742
|
+
[Hook Context]
|
|
57743
|
+
` + pendingStepContext.join(`
|
|
57744
|
+
`);
|
|
57745
|
+
pendingStepContext = [];
|
|
57746
|
+
const systemMsgIdx = activeMessages.findIndex((m) => m.role === "system");
|
|
57747
|
+
if (systemMsgIdx >= 0) {
|
|
57748
|
+
const currentSys = activeMessages[systemMsgIdx];
|
|
57749
|
+
const currentContent = typeof currentSys.content === "string" ? currentSys.content : Array.isArray(currentSys.content) ? currentSys.content.filter((c) => c.type === "text").map((c) => c.text).join(`
|
|
57750
|
+
`) : "";
|
|
57751
|
+
activeMessages[systemMsgIdx] = {
|
|
57752
|
+
role: "system",
|
|
57753
|
+
content: currentContent + extraSection
|
|
57754
|
+
};
|
|
57755
|
+
} else {
|
|
57756
|
+
activeMessages.unshift({
|
|
57757
|
+
role: "system",
|
|
57758
|
+
content: extraSection.trim()
|
|
57759
|
+
});
|
|
57760
|
+
}
|
|
57761
|
+
}
|
|
56983
57762
|
const inferenceRequest = {
|
|
56984
57763
|
model: {
|
|
56985
57764
|
...options.model,
|
|
@@ -57064,6 +57843,21 @@ async function runAgentTurn(options) {
|
|
|
57064
57843
|
usage: stepResult.usage
|
|
57065
57844
|
});
|
|
57066
57845
|
if (stepToolCalls.length === 0) {
|
|
57846
|
+
if (options.callbacks?.agentStop && !options.abortSignal?.aborted && stopHookContinuations === 0) {
|
|
57847
|
+
const stopOutcome = await options.callbacks.agentStop({
|
|
57848
|
+
stopReason: "natural",
|
|
57849
|
+
finishReason: finalFinishReason,
|
|
57850
|
+
assistantText: accumulatedText,
|
|
57851
|
+
stopHookActive: false
|
|
57852
|
+
});
|
|
57853
|
+
if (stopOutcome.blocked && stopHookContinuations === 0 && stepIndex < maxSteps) {
|
|
57854
|
+
stopHookContinuations++;
|
|
57855
|
+
const retryGuidance = stopOutcome.reason || "AgentStop requested continuation. Please continue your turn.";
|
|
57856
|
+
pendingStepContext.push(`[AgentStop Continuation]
|
|
57857
|
+
${retryGuidance}`);
|
|
57858
|
+
continue;
|
|
57859
|
+
}
|
|
57860
|
+
}
|
|
57067
57861
|
break;
|
|
57068
57862
|
}
|
|
57069
57863
|
const toolResultsForStep = [];
|
|
@@ -57071,6 +57865,44 @@ async function runAgentTurn(options) {
|
|
|
57071
57865
|
if (options.abortSignal?.aborted) {
|
|
57072
57866
|
break;
|
|
57073
57867
|
}
|
|
57868
|
+
let isBlocked = false;
|
|
57869
|
+
let blockReason;
|
|
57870
|
+
if (options.callbacks?.beforeToolUse) {
|
|
57871
|
+
const beforeRes = await options.callbacks.beforeToolUse({
|
|
57872
|
+
toolCallId: call.id,
|
|
57873
|
+
toolName: call.name,
|
|
57874
|
+
toolInput: call.arguments ?? {}
|
|
57875
|
+
});
|
|
57876
|
+
if (beforeRes.blocked) {
|
|
57877
|
+
isBlocked = true;
|
|
57878
|
+
blockReason = beforeRes.reason || `Tool execution blocked by hook.`;
|
|
57879
|
+
}
|
|
57880
|
+
}
|
|
57881
|
+
if (isBlocked) {
|
|
57882
|
+
const blockedResultInfo = {
|
|
57883
|
+
id: call.id,
|
|
57884
|
+
name: call.name,
|
|
57885
|
+
args: call.arguments,
|
|
57886
|
+
result: `Tool "${call.name}" blocked: ${blockReason}`,
|
|
57887
|
+
isError: true,
|
|
57888
|
+
durationMs: 0,
|
|
57889
|
+
startedAt: new Date().toISOString(),
|
|
57890
|
+
finishedAt: new Date().toISOString()
|
|
57891
|
+
};
|
|
57892
|
+
toolResults.push(blockedResultInfo);
|
|
57893
|
+
options.onEvent?.({
|
|
57894
|
+
type: "tool-result",
|
|
57895
|
+
toolResult: blockedResultInfo
|
|
57896
|
+
});
|
|
57897
|
+
toolResultsForStep.push({
|
|
57898
|
+
type: "tool-result",
|
|
57899
|
+
toolCallId: call.id,
|
|
57900
|
+
toolName: call.name,
|
|
57901
|
+
output: blockedResultInfo.result,
|
|
57902
|
+
isError: true
|
|
57903
|
+
});
|
|
57904
|
+
continue;
|
|
57905
|
+
}
|
|
57074
57906
|
if (options.toolExecutor) {
|
|
57075
57907
|
const resultInfo = await options.toolExecutor(call);
|
|
57076
57908
|
toolResults.push(resultInfo);
|
|
@@ -57085,6 +57917,35 @@ async function runAgentTurn(options) {
|
|
|
57085
57917
|
output: resultInfo.result,
|
|
57086
57918
|
isError: resultInfo.isError
|
|
57087
57919
|
});
|
|
57920
|
+
if (resultInfo.isError) {
|
|
57921
|
+
if (options.callbacks?.toolUseFailure) {
|
|
57922
|
+
const errorText = typeof resultInfo.result === "object" && resultInfo.result !== null ? JSON.stringify(resultInfo.result) : String(resultInfo.result);
|
|
57923
|
+
const failureRes = await options.callbacks.toolUseFailure({
|
|
57924
|
+
toolCallId: call.id,
|
|
57925
|
+
toolName: call.name,
|
|
57926
|
+
toolInput: call.arguments ?? {},
|
|
57927
|
+
error: errorText,
|
|
57928
|
+
durationMs: resultInfo.durationMs ?? 0
|
|
57929
|
+
});
|
|
57930
|
+
if (failureRes?.context && failureRes.context.length > 0) {
|
|
57931
|
+
pendingStepContext.push(...failureRes.context);
|
|
57932
|
+
}
|
|
57933
|
+
}
|
|
57934
|
+
} else {
|
|
57935
|
+
if (options.callbacks?.afterToolUse) {
|
|
57936
|
+
const afterRes = await options.callbacks.afterToolUse({
|
|
57937
|
+
toolCallId: call.id,
|
|
57938
|
+
toolName: call.name,
|
|
57939
|
+
toolInput: call.arguments ?? {},
|
|
57940
|
+
toolOutput: resultInfo.result,
|
|
57941
|
+
isError: false,
|
|
57942
|
+
durationMs: resultInfo.durationMs ?? 0
|
|
57943
|
+
});
|
|
57944
|
+
if (afterRes?.context && afterRes.context.length > 0) {
|
|
57945
|
+
pendingStepContext.push(...afterRes.context);
|
|
57946
|
+
}
|
|
57947
|
+
}
|
|
57948
|
+
}
|
|
57088
57949
|
}
|
|
57089
57950
|
}
|
|
57090
57951
|
if (toolResultsForStep.length > 0) {
|
|
@@ -57196,28 +58057,28 @@ var PendingJournalSchema = union([
|
|
|
57196
58057
|
import { createHash as createHash2, randomUUID as randomUUID3 } from "node:crypto";
|
|
57197
58058
|
import {
|
|
57198
58059
|
closeSync as closeSync4,
|
|
57199
|
-
existsSync as
|
|
58060
|
+
existsSync as existsSync13,
|
|
57200
58061
|
fsyncSync as fsyncSync3,
|
|
57201
58062
|
mkdirSync as mkdirSync7,
|
|
57202
58063
|
openSync as openSync4,
|
|
57203
|
-
readFileSync as
|
|
58064
|
+
readFileSync as readFileSync10,
|
|
57204
58065
|
renameSync as renameSync3,
|
|
57205
58066
|
unlinkSync as unlinkSync3,
|
|
57206
58067
|
writeSync as writeSync3
|
|
57207
58068
|
} from "node:fs";
|
|
57208
|
-
import { homedir as
|
|
57209
|
-
import { join as
|
|
58069
|
+
import { homedir as homedir9 } from "node:os";
|
|
58070
|
+
import { join as join14 } from "node:path";
|
|
57210
58071
|
function getCheckpointsRootDir() {
|
|
57211
58072
|
if (process.env.STEWARD_CHECKPOINTS_DIR) {
|
|
57212
58073
|
return process.env.STEWARD_CHECKPOINTS_DIR;
|
|
57213
58074
|
}
|
|
57214
|
-
return
|
|
58075
|
+
return join14(homedir9(), ".steward", "checkpoints");
|
|
57215
58076
|
}
|
|
57216
58077
|
function getWorkspaceCasDir(workspaceHash) {
|
|
57217
|
-
return
|
|
58078
|
+
return join14(getCheckpointsRootDir(), workspaceHash, "cas");
|
|
57218
58079
|
}
|
|
57219
58080
|
function getBlobPath(workspaceHash, sha256) {
|
|
57220
|
-
return
|
|
58081
|
+
return join14(getWorkspaceCasDir(workspaceHash), sha256);
|
|
57221
58082
|
}
|
|
57222
58083
|
function computeSha256(data) {
|
|
57223
58084
|
const hash = createHash2("sha256");
|
|
@@ -57228,14 +58089,14 @@ function writeCasBlob(workspaceHash, content) {
|
|
|
57228
58089
|
const buffer = Buffer.isBuffer(content) ? content : typeof content === "string" ? Buffer.from(content, "utf-8") : Buffer.from(content);
|
|
57229
58090
|
const sha256 = computeSha256(buffer);
|
|
57230
58091
|
const casDir = getWorkspaceCasDir(workspaceHash);
|
|
57231
|
-
if (!
|
|
58092
|
+
if (!existsSync13(casDir)) {
|
|
57232
58093
|
mkdirSync7(casDir, { recursive: true });
|
|
57233
58094
|
}
|
|
57234
58095
|
const targetPath = getBlobPath(workspaceHash, sha256);
|
|
57235
|
-
if (
|
|
58096
|
+
if (existsSync13(targetPath)) {
|
|
57236
58097
|
return { sha256, path: targetPath, size: buffer.length };
|
|
57237
58098
|
}
|
|
57238
|
-
const tmpPath =
|
|
58099
|
+
const tmpPath = join14(casDir, `.tmp-${sha256}-${randomUUID3().slice(0, 8)}`);
|
|
57239
58100
|
let fd = null;
|
|
57240
58101
|
try {
|
|
57241
58102
|
fd = openSync4(tmpPath, "w", 384);
|
|
@@ -57251,7 +58112,7 @@ function writeCasBlob(workspaceHash, content) {
|
|
|
57251
58112
|
closeSync4(fd);
|
|
57252
58113
|
} catch {}
|
|
57253
58114
|
}
|
|
57254
|
-
if (
|
|
58115
|
+
if (existsSync13(tmpPath)) {
|
|
57255
58116
|
try {
|
|
57256
58117
|
unlinkSync3(tmpPath);
|
|
57257
58118
|
} catch {}
|
|
@@ -57261,10 +58122,10 @@ function writeCasBlob(workspaceHash, content) {
|
|
|
57261
58122
|
}
|
|
57262
58123
|
function readCasBlob(workspaceHash, sha256) {
|
|
57263
58124
|
const targetPath = getBlobPath(workspaceHash, sha256);
|
|
57264
|
-
if (!
|
|
58125
|
+
if (!existsSync13(targetPath)) {
|
|
57265
58126
|
throw new Error(`CAS blob not found: ${sha256} in workspace ${workspaceHash}`);
|
|
57266
58127
|
}
|
|
57267
|
-
const buffer =
|
|
58128
|
+
const buffer = readFileSync10(targetPath);
|
|
57268
58129
|
const actualHash = computeSha256(buffer);
|
|
57269
58130
|
if (actualHash !== sha256) {
|
|
57270
58131
|
throw new Error(`CAS blob corruption detected for ${sha256}: computed hash is ${actualHash}`);
|
|
@@ -57325,24 +58186,24 @@ var globalMutationLockManager = new MutationLockManager;
|
|
|
57325
58186
|
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
57326
58187
|
import {
|
|
57327
58188
|
closeSync as closeSync5,
|
|
57328
|
-
existsSync as
|
|
58189
|
+
existsSync as existsSync14,
|
|
57329
58190
|
fsyncSync as fsyncSync4,
|
|
57330
58191
|
mkdirSync as mkdirSync8,
|
|
57331
58192
|
openSync as openSync5,
|
|
57332
|
-
readFileSync as
|
|
58193
|
+
readFileSync as readFileSync11,
|
|
57333
58194
|
renameSync as renameSync4,
|
|
57334
58195
|
unlinkSync as unlinkSync4,
|
|
57335
58196
|
writeSync as writeSync4
|
|
57336
58197
|
} from "node:fs";
|
|
57337
|
-
import { join as
|
|
58198
|
+
import { join as join15 } from "node:path";
|
|
57338
58199
|
function getSessionCheckpointDir(workspaceHash, sessionId) {
|
|
57339
|
-
return
|
|
58200
|
+
return join15(getCheckpointsRootDir(), workspaceHash, "sessions", sessionId);
|
|
57340
58201
|
}
|
|
57341
58202
|
function getManifestPath(workspaceHash, sessionId) {
|
|
57342
|
-
return
|
|
58203
|
+
return join15(getSessionCheckpointDir(workspaceHash, sessionId), "manifest.json");
|
|
57343
58204
|
}
|
|
57344
58205
|
function getPendingJournalPath(workspaceHash, sessionId) {
|
|
57345
|
-
return
|
|
58206
|
+
return join15(getSessionCheckpointDir(workspaceHash, sessionId), "pending.json");
|
|
57346
58207
|
}
|
|
57347
58208
|
|
|
57348
58209
|
class SessionStoreSerializer {
|
|
@@ -57367,8 +58228,8 @@ class SessionStoreSerializer {
|
|
|
57367
58228
|
}
|
|
57368
58229
|
var storeSerializer = new SessionStoreSerializer;
|
|
57369
58230
|
function atomicWriteJson(targetPath, data) {
|
|
57370
|
-
const dir =
|
|
57371
|
-
if (!
|
|
58231
|
+
const dir = join15(targetPath, "..");
|
|
58232
|
+
if (!existsSync14(dir)) {
|
|
57372
58233
|
mkdirSync8(dir, { recursive: true });
|
|
57373
58234
|
}
|
|
57374
58235
|
const tmpPath = `${targetPath}.tmp-${randomUUID4().slice(0, 8)}`;
|
|
@@ -57388,7 +58249,7 @@ function atomicWriteJson(targetPath, data) {
|
|
|
57388
58249
|
closeSync5(fd);
|
|
57389
58250
|
} catch {}
|
|
57390
58251
|
}
|
|
57391
|
-
if (
|
|
58252
|
+
if (existsSync14(tmpPath)) {
|
|
57392
58253
|
try {
|
|
57393
58254
|
unlinkSync4(tmpPath);
|
|
57394
58255
|
} catch {}
|
|
@@ -57398,23 +58259,23 @@ function atomicWriteJson(targetPath, data) {
|
|
|
57398
58259
|
}
|
|
57399
58260
|
function quarantineCheckpointFile(filePath) {
|
|
57400
58261
|
try {
|
|
57401
|
-
if (!
|
|
58262
|
+
if (!existsSync14(filePath))
|
|
57402
58263
|
return;
|
|
57403
|
-
const dir =
|
|
57404
|
-
if (!
|
|
58264
|
+
const dir = join15(filePath, "..", ".quarantine");
|
|
58265
|
+
if (!existsSync14(dir)) {
|
|
57405
58266
|
mkdirSync8(dir, { recursive: true });
|
|
57406
58267
|
}
|
|
57407
58268
|
const fileName = `${filePath.split("/").pop()}-${Date.now()}-${randomUUID4().slice(0, 6)}`;
|
|
57408
|
-
renameSync4(filePath,
|
|
58269
|
+
renameSync4(filePath, join15(dir, fileName));
|
|
57409
58270
|
} catch {}
|
|
57410
58271
|
}
|
|
57411
58272
|
function loadCheckpointManifest(workspaceHash, sessionId) {
|
|
57412
58273
|
const targetPath = getManifestPath(workspaceHash, sessionId);
|
|
57413
|
-
if (!
|
|
58274
|
+
if (!existsSync14(targetPath)) {
|
|
57414
58275
|
return null;
|
|
57415
58276
|
}
|
|
57416
58277
|
try {
|
|
57417
|
-
const raw =
|
|
58278
|
+
const raw = readFileSync11(targetPath, "utf-8");
|
|
57418
58279
|
const parsed = JSON.parse(raw);
|
|
57419
58280
|
const result = SessionCheckpointManifestSchema.safeParse(parsed);
|
|
57420
58281
|
if (result.success) {
|
|
@@ -57436,11 +58297,11 @@ async function saveCheckpointManifest(workspaceHash, sessionId, manifest) {
|
|
|
57436
58297
|
}
|
|
57437
58298
|
function loadPendingJournal(workspaceHash, sessionId) {
|
|
57438
58299
|
const targetPath = getPendingJournalPath(workspaceHash, sessionId);
|
|
57439
|
-
if (!
|
|
58300
|
+
if (!existsSync14(targetPath)) {
|
|
57440
58301
|
return null;
|
|
57441
58302
|
}
|
|
57442
58303
|
try {
|
|
57443
|
-
const raw =
|
|
58304
|
+
const raw = readFileSync11(targetPath, "utf-8");
|
|
57444
58305
|
const parsed = JSON.parse(raw);
|
|
57445
58306
|
const result = PendingJournalSchema.safeParse(parsed);
|
|
57446
58307
|
if (result.success) {
|
|
@@ -57464,7 +58325,7 @@ async function deletePendingJournal(workspaceHash, sessionId) {
|
|
|
57464
58325
|
const key = `${workspaceHash}:${sessionId}`;
|
|
57465
58326
|
await storeSerializer.run(key, () => {
|
|
57466
58327
|
const targetPath = getPendingJournalPath(workspaceHash, sessionId);
|
|
57467
|
-
if (
|
|
58328
|
+
if (existsSync14(targetPath)) {
|
|
57468
58329
|
try {
|
|
57469
58330
|
unlinkSync4(targetPath);
|
|
57470
58331
|
} catch {}
|
|
@@ -57493,7 +58354,7 @@ async function commitTurnCheckpoint(workspaceHash, sessionId, workspaceRoot, tur
|
|
|
57493
58354
|
const manifestPath = getManifestPath(workspaceHash, sessionId);
|
|
57494
58355
|
atomicWriteJson(manifestPath, manifest);
|
|
57495
58356
|
const pendingPath = getPendingJournalPath(workspaceHash, sessionId);
|
|
57496
|
-
if (
|
|
58357
|
+
if (existsSync14(pendingPath)) {
|
|
57497
58358
|
try {
|
|
57498
58359
|
unlinkSync4(pendingPath);
|
|
57499
58360
|
} catch {}
|
|
@@ -57501,7 +58362,7 @@ async function commitTurnCheckpoint(workspaceHash, sessionId, workspaceRoot, tur
|
|
|
57501
58362
|
});
|
|
57502
58363
|
}
|
|
57503
58364
|
// src/packages/services/src/checkpoint/tracker.ts
|
|
57504
|
-
import { existsSync as
|
|
58365
|
+
import { existsSync as existsSync15, lstatSync as lstatSync3, readFileSync as readFileSync12, statSync as statSync5 } from "node:fs";
|
|
57505
58366
|
class MutationCheckpointTracker {
|
|
57506
58367
|
workspaceRoot;
|
|
57507
58368
|
workspaceHash;
|
|
@@ -57556,7 +58417,7 @@ class MutationCheckpointTracker {
|
|
|
57556
58417
|
};
|
|
57557
58418
|
}
|
|
57558
58419
|
let preState;
|
|
57559
|
-
if (
|
|
58420
|
+
if (existsSync15(canonicalPath)) {
|
|
57560
58421
|
const lstat = lstatSync3(canonicalPath);
|
|
57561
58422
|
if (lstat.isDirectory()) {
|
|
57562
58423
|
throw new Error(`Target path is a directory: ${canonicalPath}`);
|
|
@@ -57565,7 +58426,7 @@ class MutationCheckpointTracker {
|
|
|
57565
58426
|
throw new Error(`Target path is not a regular file: ${canonicalPath}`);
|
|
57566
58427
|
}
|
|
57567
58428
|
const stat = statSync5(canonicalPath);
|
|
57568
|
-
const buffer =
|
|
58429
|
+
const buffer = readFileSync12(canonicalPath);
|
|
57569
58430
|
const { sha256, size } = writeCasBlob(this.workspaceHash, buffer);
|
|
57570
58431
|
preState = {
|
|
57571
58432
|
kind: "file",
|
|
@@ -57606,9 +58467,9 @@ class MutationCheckpointTracker {
|
|
|
57606
58467
|
throw new Error(`Mutation completed for untracked path: ${canonicalPath}`);
|
|
57607
58468
|
}
|
|
57608
58469
|
let postState;
|
|
57609
|
-
if (
|
|
58470
|
+
if (existsSync15(canonicalPath)) {
|
|
57610
58471
|
const stat = statSync5(canonicalPath);
|
|
57611
|
-
const buffer =
|
|
58472
|
+
const buffer = readFileSync12(canonicalPath);
|
|
57612
58473
|
const { sha256, size } = writeCasBlob(this.workspaceHash, buffer);
|
|
57613
58474
|
postState = {
|
|
57614
58475
|
kind: "file",
|
|
@@ -57674,24 +58535,24 @@ class MutationCheckpointTracker {
|
|
|
57674
58535
|
import {
|
|
57675
58536
|
chmodSync as chmodSync2,
|
|
57676
58537
|
closeSync as closeSync6,
|
|
57677
|
-
existsSync as
|
|
58538
|
+
existsSync as existsSync16,
|
|
57678
58539
|
fsyncSync as fsyncSync5,
|
|
57679
58540
|
mkdirSync as mkdirSync9,
|
|
57680
58541
|
openSync as openSync6,
|
|
57681
|
-
readFileSync as
|
|
58542
|
+
readFileSync as readFileSync13,
|
|
57682
58543
|
renameSync as renameSync5,
|
|
57683
58544
|
statSync as statSync6,
|
|
57684
58545
|
unlinkSync as unlinkSync5,
|
|
57685
58546
|
writeSync as writeSync5
|
|
57686
58547
|
} from "node:fs";
|
|
57687
|
-
import { dirname as dirname6, join as
|
|
58548
|
+
import { dirname as dirname6, join as join16, resolve as resolve10 } from "node:path";
|
|
57688
58549
|
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
57689
58550
|
function getFileStateOnDisk(absPath) {
|
|
57690
|
-
if (!
|
|
58551
|
+
if (!existsSync16(absPath)) {
|
|
57691
58552
|
return { kind: "missing" };
|
|
57692
58553
|
}
|
|
57693
58554
|
const stat = statSync6(absPath);
|
|
57694
|
-
const buf =
|
|
58555
|
+
const buf = readFileSync13(absPath);
|
|
57695
58556
|
const sha256 = computeSha256(buf);
|
|
57696
58557
|
return {
|
|
57697
58558
|
kind: "file",
|
|
@@ -57702,17 +58563,17 @@ function getFileStateOnDisk(absPath) {
|
|
|
57702
58563
|
}
|
|
57703
58564
|
function restoreFileStateToDisk(workspaceHash, absPath, state) {
|
|
57704
58565
|
if (state.kind === "missing") {
|
|
57705
|
-
if (
|
|
58566
|
+
if (existsSync16(absPath)) {
|
|
57706
58567
|
unlinkSync5(absPath);
|
|
57707
58568
|
}
|
|
57708
58569
|
return;
|
|
57709
58570
|
}
|
|
57710
58571
|
const dir = dirname6(absPath);
|
|
57711
|
-
if (!
|
|
58572
|
+
if (!existsSync16(dir)) {
|
|
57712
58573
|
mkdirSync9(dir, { recursive: true });
|
|
57713
58574
|
}
|
|
57714
58575
|
const blobBytes = readCasBlob(workspaceHash, state.sha256);
|
|
57715
|
-
const tmpPath =
|
|
58576
|
+
const tmpPath = join16(dir, `.tmp-restore-${state.sha256.slice(0, 8)}-${randomUUID5().slice(0, 8)}`);
|
|
57716
58577
|
let fd = null;
|
|
57717
58578
|
try {
|
|
57718
58579
|
fd = openSync6(tmpPath, "w", state.mode || 420);
|
|
@@ -57730,7 +58591,7 @@ function restoreFileStateToDisk(workspaceHash, absPath, state) {
|
|
|
57730
58591
|
closeSync6(fd);
|
|
57731
58592
|
} catch {}
|
|
57732
58593
|
}
|
|
57733
|
-
if (
|
|
58594
|
+
if (existsSync16(tmpPath)) {
|
|
57734
58595
|
try {
|
|
57735
58596
|
unlinkSync5(tmpPath);
|
|
57736
58597
|
} catch {}
|
|
@@ -57948,46 +58809,6 @@ async function recoverPendingCheckpoint(workspaceRoot, sessionId) {
|
|
|
57948
58809
|
}
|
|
57949
58810
|
}
|
|
57950
58811
|
}
|
|
57951
|
-
// src/packages/agents/src/engine/mode.ts
|
|
57952
|
-
var MODES2 = {
|
|
57953
|
-
normal: {
|
|
57954
|
-
name: "normal",
|
|
57955
|
-
label: "Normal",
|
|
57956
|
-
description: "All tools with file and bash permission prompts",
|
|
57957
|
-
color: "text",
|
|
57958
|
-
allows: ["read", "write", "exec"],
|
|
57959
|
-
autoApproveFiles: false
|
|
57960
|
-
},
|
|
57961
|
-
chat: {
|
|
57962
|
-
name: "chat",
|
|
57963
|
-
label: "Chat",
|
|
57964
|
-
description: "Conversation only — no tool access",
|
|
57965
|
-
color: "info",
|
|
57966
|
-
allows: [],
|
|
57967
|
-
autoApproveFiles: false
|
|
57968
|
-
},
|
|
57969
|
-
review: {
|
|
57970
|
-
name: "review",
|
|
57971
|
-
label: "Review",
|
|
57972
|
-
description: "Read-only tools only — cannot modify files or run commands",
|
|
57973
|
-
color: "warning",
|
|
57974
|
-
allows: ["read"],
|
|
57975
|
-
autoApproveFiles: false
|
|
57976
|
-
},
|
|
57977
|
-
build: {
|
|
57978
|
-
name: "build",
|
|
57979
|
-
label: "Build",
|
|
57980
|
-
description: "All tools, file mutations auto-approved, bash still prompts",
|
|
57981
|
-
color: "success",
|
|
57982
|
-
allows: ["read", "write", "exec"],
|
|
57983
|
-
autoApproveFiles: true
|
|
57984
|
-
}
|
|
57985
|
-
};
|
|
57986
|
-
var activeMode2 = "normal";
|
|
57987
|
-
function getActiveMode2() {
|
|
57988
|
-
return activeMode2;
|
|
57989
|
-
}
|
|
57990
|
-
|
|
57991
58812
|
// src/packages/agents/src/engine/system-prompt.ts
|
|
57992
58813
|
function buildSystemPrompt(options = {}) {
|
|
57993
58814
|
const cwd = options.cwd ?? process.cwd();
|
|
@@ -58021,6 +58842,11 @@ IMPORTANT: Assist with defensive security tasks only. Refuse to create, modify,
|
|
|
58021
58842
|
- Use skill_read (SkillRead) to load a skill's instructions (SKILL.md) or relative resources only when a task requires specialized domain guidance.
|
|
58022
58843
|
- Skill instructions are supplemental and must not override core safety rules, tool boundaries, or user permission constraints.
|
|
58023
58844
|
|
|
58845
|
+
# Lifecycle Hooks Policy
|
|
58846
|
+
- The active workspace or user configuration may define deterministic lifecycle hooks (\`.steward/hooks.json\` or \`~/.steward/hooks.json\`) that inspect, augment, or block actions.
|
|
58847
|
+
- When a tool call is blocked by a hook, treat the block reason as authoritative workspace policy, do not attempt to force or repeat the blocked action, and adjust your plan or inform the user.
|
|
58848
|
+
- When hook guidance or continuation instructions (\`[Hook Context]\` or \`[AgentStop Continuation]\`) are injected into the context, follow that guidance to perform any requested verification or adjustments.
|
|
58849
|
+
|
|
58024
58850
|
<env>
|
|
58025
58851
|
Working directory: ${cwd}
|
|
58026
58852
|
Platform: ${platform}
|
|
@@ -58043,7 +58869,7 @@ ${options.extraInstructions}
|
|
|
58043
58869
|
</additional_instructions>`;
|
|
58044
58870
|
}
|
|
58045
58871
|
if (options.chatMode && options.chatMode !== "normal") {
|
|
58046
|
-
const meta =
|
|
58872
|
+
const meta = MODES[options.chatMode];
|
|
58047
58873
|
if (meta) {
|
|
58048
58874
|
if (options.chatMode === "chat") {
|
|
58049
58875
|
prompt += `
|
|
@@ -58099,8 +58925,8 @@ async function prepareTurn(trimmedPrompt, options, deps) {
|
|
|
58099
58925
|
lockManager: globalMutationLockManager
|
|
58100
58926
|
});
|
|
58101
58927
|
await tracker.beginTurn(turnId, deps.turnNumber);
|
|
58102
|
-
const mode =
|
|
58103
|
-
const effectiveFilePermission =
|
|
58928
|
+
const mode = getActiveMode();
|
|
58929
|
+
const effectiveFilePermission = MODES[mode]?.autoApproveFiles ? async (_req) => ({ allowed: true }) : options.requestFilePermission;
|
|
58104
58930
|
const toolContext = {
|
|
58105
58931
|
cwd,
|
|
58106
58932
|
mode,
|
|
@@ -58205,6 +59031,7 @@ class AgentSession {
|
|
|
58205
59031
|
messages = [];
|
|
58206
59032
|
sessionData;
|
|
58207
59033
|
sessionLogWriter;
|
|
59034
|
+
hookRuntime;
|
|
58208
59035
|
accumulatedUsage = {
|
|
58209
59036
|
inputTokens: 0,
|
|
58210
59037
|
outputTokens: 0,
|
|
@@ -58218,6 +59045,7 @@ class AgentSession {
|
|
|
58218
59045
|
shellTasks = new ShellTaskManager;
|
|
58219
59046
|
constructor(initialConfig, existingSession, deps) {
|
|
58220
59047
|
this.ai = deps?.ai ?? createAIEngine();
|
|
59048
|
+
this.hookRuntime = deps?.hookRuntime;
|
|
58221
59049
|
if (existingSession) {
|
|
58222
59050
|
this.sessionData = existingSession;
|
|
58223
59051
|
this.config = {
|
|
@@ -58263,6 +59091,26 @@ class AgentSession {
|
|
|
58263
59091
|
get session() {
|
|
58264
59092
|
return this.sessionData;
|
|
58265
59093
|
}
|
|
59094
|
+
getHookRuntime() {
|
|
59095
|
+
return this.hookRuntime;
|
|
59096
|
+
}
|
|
59097
|
+
async initializeHooks(options = {}) {
|
|
59098
|
+
const cwd = options.cwd ?? process.cwd();
|
|
59099
|
+
const isTrusted = options.isTrusted ?? isFolderTrusted(cwd);
|
|
59100
|
+
const source = options.source ?? "startup";
|
|
59101
|
+
if (!this.hookRuntime) {
|
|
59102
|
+
this.hookRuntime = HookRuntime.load({
|
|
59103
|
+
projectDir: cwd,
|
|
59104
|
+
isTrusted
|
|
59105
|
+
});
|
|
59106
|
+
}
|
|
59107
|
+
await this.hookRuntime.runSessionStart({
|
|
59108
|
+
sessionId: this.sessionData.id,
|
|
59109
|
+
projectDir: cwd,
|
|
59110
|
+
cwd,
|
|
59111
|
+
source
|
|
59112
|
+
});
|
|
59113
|
+
}
|
|
58266
59114
|
setModel(selection, persist = true) {
|
|
58267
59115
|
this.config.provider = selection.provider;
|
|
58268
59116
|
this.config.modelId = selection.modelId;
|
|
@@ -58324,8 +59172,39 @@ class AgentSession {
|
|
|
58324
59172
|
if (!trimmedPrompt) {
|
|
58325
59173
|
throw new Error("Prompt cannot be empty.");
|
|
58326
59174
|
}
|
|
59175
|
+
const cwd = options.cwd ?? process.cwd();
|
|
59176
|
+
if (!this.hookRuntime) {
|
|
59177
|
+
await this.initializeHooks({
|
|
59178
|
+
cwd,
|
|
59179
|
+
isTrusted: isFolderTrusted(cwd),
|
|
59180
|
+
source: "startup"
|
|
59181
|
+
});
|
|
59182
|
+
}
|
|
59183
|
+
const previewTurnId = randomUUID7();
|
|
59184
|
+
const promptHookResult = await this.hookRuntime.runUserPromptSubmit({
|
|
59185
|
+
sessionId: this.sessionData.id,
|
|
59186
|
+
turnId: previewTurnId,
|
|
59187
|
+
projectDir: cwd,
|
|
59188
|
+
cwd,
|
|
59189
|
+
prompt: trimmedPrompt
|
|
59190
|
+
});
|
|
59191
|
+
if (promptHookResult.blocked) {
|
|
59192
|
+
const blockReason = promptHookResult.firstBlockReason || "UserPromptSubmit hook blocked the prompt.";
|
|
59193
|
+
throw new Error(`Prompt blocked: ${blockReason}`);
|
|
59194
|
+
}
|
|
58327
59195
|
this.isGenerating = true;
|
|
58328
|
-
const
|
|
59196
|
+
const consumedSessionStartCtx = this.hookRuntime.consumePendingSessionContext();
|
|
59197
|
+
const combinedHookContext = [...consumedSessionStartCtx, ...promptHookResult.context];
|
|
59198
|
+
const hookInstructionText = combinedHookContext.length > 0 ? `[Hook Instructions]
|
|
59199
|
+
` + combinedHookContext.join(`
|
|
59200
|
+
`) : undefined;
|
|
59201
|
+
const effectiveExtraInstructions = [options.extraInstructions, hookInstructionText].filter(Boolean).join(`
|
|
59202
|
+
|
|
59203
|
+
`) || undefined;
|
|
59204
|
+
const prep = await prepareTurn(trimmedPrompt, {
|
|
59205
|
+
...options,
|
|
59206
|
+
extraInstructions: effectiveExtraInstructions
|
|
59207
|
+
}, {
|
|
58329
59208
|
sessionId: this.sessionData.id,
|
|
58330
59209
|
shellTasks: this.shellTasks,
|
|
58331
59210
|
sessionLogWriter: this.sessionLogWriter,
|
|
@@ -58368,6 +59247,66 @@ class AgentSession {
|
|
|
58368
59247
|
};
|
|
58369
59248
|
}
|
|
58370
59249
|
};
|
|
59250
|
+
const callbacks = {
|
|
59251
|
+
beforeToolUse: async (params) => {
|
|
59252
|
+
const res = await this.hookRuntime.runBeforeToolUse({
|
|
59253
|
+
sessionId: this.sessionData.id,
|
|
59254
|
+
turnId: prep.turnId,
|
|
59255
|
+
projectDir: prep.toolContext.cwd,
|
|
59256
|
+
cwd: prep.toolContext.cwd,
|
|
59257
|
+
toolCallId: params.toolCallId,
|
|
59258
|
+
toolName: params.toolName,
|
|
59259
|
+
toolInput: params.toolInput,
|
|
59260
|
+
abortSignal: prep.abortController.signal
|
|
59261
|
+
});
|
|
59262
|
+
return { blocked: res.blocked, reason: res.firstBlockReason };
|
|
59263
|
+
},
|
|
59264
|
+
afterToolUse: async (params) => {
|
|
59265
|
+
const res = await this.hookRuntime.runAfterToolUse({
|
|
59266
|
+
sessionId: this.sessionData.id,
|
|
59267
|
+
turnId: prep.turnId,
|
|
59268
|
+
projectDir: prep.toolContext.cwd,
|
|
59269
|
+
cwd: prep.toolContext.cwd,
|
|
59270
|
+
toolCallId: params.toolCallId,
|
|
59271
|
+
toolName: params.toolName,
|
|
59272
|
+
toolInput: params.toolInput,
|
|
59273
|
+
toolOutput: params.toolOutput,
|
|
59274
|
+
isError: params.isError,
|
|
59275
|
+
durationMs: params.durationMs,
|
|
59276
|
+
abortSignal: prep.abortController.signal
|
|
59277
|
+
});
|
|
59278
|
+
return { context: res.context };
|
|
59279
|
+
},
|
|
59280
|
+
toolUseFailure: async (params) => {
|
|
59281
|
+
const res = await this.hookRuntime.runToolUseFailure({
|
|
59282
|
+
sessionId: this.sessionData.id,
|
|
59283
|
+
turnId: prep.turnId,
|
|
59284
|
+
projectDir: prep.toolContext.cwd,
|
|
59285
|
+
cwd: prep.toolContext.cwd,
|
|
59286
|
+
toolCallId: params.toolCallId,
|
|
59287
|
+
toolName: params.toolName,
|
|
59288
|
+
toolInput: params.toolInput,
|
|
59289
|
+
error: params.error,
|
|
59290
|
+
durationMs: params.durationMs,
|
|
59291
|
+
abortSignal: prep.abortController.signal
|
|
59292
|
+
});
|
|
59293
|
+
return { context: res.context };
|
|
59294
|
+
},
|
|
59295
|
+
agentStop: async (params) => {
|
|
59296
|
+
const res = await this.hookRuntime.runAgentStop({
|
|
59297
|
+
sessionId: this.sessionData.id,
|
|
59298
|
+
turnId: prep.turnId,
|
|
59299
|
+
projectDir: prep.toolContext.cwd,
|
|
59300
|
+
cwd: prep.toolContext.cwd,
|
|
59301
|
+
stopReason: params.stopReason,
|
|
59302
|
+
finishReason: params.finishReason,
|
|
59303
|
+
assistantText: params.assistantText,
|
|
59304
|
+
stopHookActive: params.stopHookActive,
|
|
59305
|
+
abortSignal: prep.abortController.signal
|
|
59306
|
+
});
|
|
59307
|
+
return { blocked: res.blocked, reason: res.firstBlockReason };
|
|
59308
|
+
}
|
|
59309
|
+
};
|
|
58371
59310
|
try {
|
|
58372
59311
|
summary = await runAgentTurn({
|
|
58373
59312
|
ai: this.ai,
|
|
@@ -58376,6 +59315,7 @@ class AgentSession {
|
|
|
58376
59315
|
instructions: prep.instructions,
|
|
58377
59316
|
tools: prep.activeTools,
|
|
58378
59317
|
toolExecutor,
|
|
59318
|
+
callbacks,
|
|
58379
59319
|
maxSteps: this.config.maxSteps,
|
|
58380
59320
|
temperature: this.config.temperature,
|
|
58381
59321
|
reasoningEffort: this.config.reasoningEffort,
|
|
@@ -58510,6 +59450,14 @@ class AgentSession {
|
|
|
58510
59450
|
effort: this.config.reasoningEffort ?? "medium"
|
|
58511
59451
|
});
|
|
58512
59452
|
this.sessionLogWriter = new SessionLogWriter(this.sessionData.date || getCurrentDateString(), this.sessionData.id);
|
|
59453
|
+
if (this.hookRuntime) {
|
|
59454
|
+
await this.hookRuntime.runSessionStart({
|
|
59455
|
+
sessionId: this.sessionData.id,
|
|
59456
|
+
projectDir: process.cwd(),
|
|
59457
|
+
cwd: process.cwd(),
|
|
59458
|
+
source: "reset"
|
|
59459
|
+
});
|
|
59460
|
+
}
|
|
58513
59461
|
}
|
|
58514
59462
|
getUsage() {
|
|
58515
59463
|
return { ...this.accumulatedUsage };
|
|
@@ -59026,7 +59974,7 @@ for (const cmd of builtInCommands) {
|
|
|
59026
59974
|
}
|
|
59027
59975
|
|
|
59028
59976
|
// src/app/ui/components/Header.tsx
|
|
59029
|
-
import { homedir as
|
|
59977
|
+
import { homedir as homedir10 } from "node:os";
|
|
59030
59978
|
|
|
59031
59979
|
// src/packages/tui/src/engine/Component.ts
|
|
59032
59980
|
class Component {
|
|
@@ -59605,7 +60553,7 @@ class Header extends Component_default {
|
|
|
59605
60553
|
formatCwd(rawPath) {
|
|
59606
60554
|
if (!rawPath)
|
|
59607
60555
|
return "~";
|
|
59608
|
-
const home =
|
|
60556
|
+
const home = homedir10();
|
|
59609
60557
|
if (rawPath.startsWith(home)) {
|
|
59610
60558
|
return `~${rawPath.slice(home.length)}`;
|
|
59611
60559
|
}
|
|
@@ -61902,22 +62850,14 @@ class CommandPaletteController {
|
|
|
61902
62850
|
const matching = this.getMatchingCommands(value);
|
|
61903
62851
|
if (matching.length === 0)
|
|
61904
62852
|
return false;
|
|
61905
|
-
|
|
61906
|
-
this.dismiss();
|
|
61907
|
-
return false;
|
|
61908
|
-
}
|
|
61909
|
-
this.paletteIdx = this.paletteIdx - 1;
|
|
62853
|
+
this.paletteIdx = this.paletteIdx > 0 ? this.paletteIdx - 1 : matching.length - 1;
|
|
61910
62854
|
return true;
|
|
61911
62855
|
}
|
|
61912
62856
|
onDown(value) {
|
|
61913
62857
|
const matching = this.getMatchingCommands(value);
|
|
61914
62858
|
if (matching.length === 0)
|
|
61915
62859
|
return false;
|
|
61916
|
-
|
|
61917
|
-
this.dismiss();
|
|
61918
|
-
return false;
|
|
61919
|
-
}
|
|
61920
|
-
this.paletteIdx = this.paletteIdx + 1;
|
|
62860
|
+
this.paletteIdx = this.paletteIdx < matching.length - 1 ? this.paletteIdx + 1 : 0;
|
|
61921
62861
|
return true;
|
|
61922
62862
|
}
|
|
61923
62863
|
onTab(value) {
|
|
@@ -62380,25 +63320,32 @@ ${after}`, this.state.cursorPos);
|
|
|
62380
63320
|
const matchingCommands = this.commandPalette.getMatchingCommands(effectiveValue);
|
|
62381
63321
|
const paletteIdx = this.state.paletteIdx ?? this.commandPalette.getSelectedIndex();
|
|
62382
63322
|
if (this.commandPalette.isSlashMode(effectiveValue) && matchingCommands.length > 0 && effectiveValue !== `/${matchingCommands[0]?.name} `) {
|
|
62383
|
-
|
|
62384
|
-
|
|
62385
|
-
|
|
62386
|
-
|
|
62387
|
-
|
|
62388
|
-
|
|
62389
|
-
|
|
62390
|
-
|
|
62391
|
-
}
|
|
63323
|
+
const maxVisible = 5;
|
|
63324
|
+
const startIdx = Math.max(0, Math.min(paletteIdx - Math.floor(maxVisible / 2), matchingCommands.length - maxVisible));
|
|
63325
|
+
const visibleCmds = matchingCommands.slice(Math.max(0, startIdx), Math.max(0, startIdx) + maxVisible);
|
|
63326
|
+
for (let relIdx = 0;relIdx < visibleCmds.length; relIdx++) {
|
|
63327
|
+
const cmd = visibleCmds[relIdx];
|
|
63328
|
+
const actualIdx = Math.max(0, startIdx) + relIdx;
|
|
63329
|
+
const isSelected = actualIdx === paletteIdx;
|
|
63330
|
+
const p = isSelected ? c.info(`${figures.pointer} `) : " ";
|
|
63331
|
+
const namePadded = `/${cmd.name}`.padEnd(16);
|
|
63332
|
+
const nameText = isSelected ? c.info(namePadded) : c.text(namePadded);
|
|
63333
|
+
const descText = isSelected ? c.info(cmd.description) : c.muted(cmd.description);
|
|
63334
|
+
lines.push(truncateToWidth2(`${p}${nameText}${descText}`, maxCols));
|
|
62392
63335
|
}
|
|
62393
63336
|
}
|
|
62394
63337
|
const fileMatches = this.state.fileMatches ?? this.autocomplete.getMatches();
|
|
62395
63338
|
const fileSelectIdx = this.state.fileSelectIdx ?? this.autocomplete.getSelectedIndex();
|
|
62396
63339
|
if (fileMatches.length > 0) {
|
|
62397
63340
|
lines.push(c.muted("Matching files (@):"));
|
|
62398
|
-
|
|
62399
|
-
|
|
62400
|
-
|
|
62401
|
-
|
|
63341
|
+
const maxVisible = 5;
|
|
63342
|
+
const startIdx = Math.max(0, Math.min(fileSelectIdx - Math.floor(maxVisible / 2), fileMatches.length - maxVisible));
|
|
63343
|
+
const visibleFiles = fileMatches.slice(Math.max(0, startIdx), Math.max(0, startIdx) + maxVisible);
|
|
63344
|
+
for (let relIdx = 0;relIdx < visibleFiles.length; relIdx++) {
|
|
63345
|
+
const f = visibleFiles[relIdx];
|
|
63346
|
+
const actualIdx = Math.max(0, startIdx) + relIdx;
|
|
63347
|
+
const isSelected = actualIdx === fileSelectIdx;
|
|
63348
|
+
const p = isSelected ? c.info(`${figures.pointer} `) : " ";
|
|
62402
63349
|
const fileText = isSelected ? c.info(f) : c.muted(f);
|
|
62403
63350
|
lines.push(truncateToWidth2(`${p}${fileText}`, maxCols));
|
|
62404
63351
|
}
|
|
@@ -62461,7 +63408,7 @@ class TrustGate extends Component_default {
|
|
|
62461
63408
|
const { selectedIndex } = this.state;
|
|
62462
63409
|
const isYesSelected = selectedIndex === 0;
|
|
62463
63410
|
const isNoSelected = selectedIndex === 1;
|
|
62464
|
-
const pointer = figures.
|
|
63411
|
+
const pointer = figures.pointer ?? ">";
|
|
62465
63412
|
const yesOptionText = isYesSelected ? `${c.permission(pointer)} ${bold(c.permission("1. Yes, I trust this folder"))}` : ` ${c.text("1. Yes, I trust this folder")}`;
|
|
62466
63413
|
const noOptionText = isNoSelected ? `${c.permission(pointer)} ${bold(c.permission("2. No, exit"))}` : ` ${c.text("2. No, exit")}`;
|
|
62467
63414
|
const safetyCheckText = "You should only proceed if you trust this workspace. Accessing untrusted workspaces may allow malicious code in the repository to compromise security or mislead the assistant.";
|
|
@@ -63132,13 +64079,13 @@ class UpdateCheckerService {
|
|
|
63132
64079
|
}
|
|
63133
64080
|
}
|
|
63134
64081
|
// src/app/utils/open-url.ts
|
|
63135
|
-
import { spawn as
|
|
63136
|
-
import { existsSync as
|
|
64082
|
+
import { spawn as spawn3 } from "node:child_process";
|
|
64083
|
+
import { existsSync as existsSync17 } from "node:fs";
|
|
63137
64084
|
function openUrl(url) {
|
|
63138
64085
|
const platform = process.platform;
|
|
63139
64086
|
try {
|
|
63140
|
-
if (process.env.TERMUX_VERSION ||
|
|
63141
|
-
const child =
|
|
64087
|
+
if (process.env.TERMUX_VERSION || existsSync17("/data/data/com.termux/files/usr/bin/termux-open-url")) {
|
|
64088
|
+
const child = spawn3("termux-open-url", [url], {
|
|
63142
64089
|
detached: true,
|
|
63143
64090
|
stdio: "ignore"
|
|
63144
64091
|
});
|
|
@@ -63146,7 +64093,7 @@ function openUrl(url) {
|
|
|
63146
64093
|
return;
|
|
63147
64094
|
}
|
|
63148
64095
|
if (platform === "win32") {
|
|
63149
|
-
const child =
|
|
64096
|
+
const child = spawn3("cmd.exe", ["/c", "start", '""', url], {
|
|
63150
64097
|
detached: true,
|
|
63151
64098
|
stdio: "ignore"
|
|
63152
64099
|
});
|
|
@@ -63154,14 +64101,14 @@ function openUrl(url) {
|
|
|
63154
64101
|
return;
|
|
63155
64102
|
}
|
|
63156
64103
|
if (platform === "darwin") {
|
|
63157
|
-
const child =
|
|
64104
|
+
const child = spawn3("open", [url], {
|
|
63158
64105
|
detached: true,
|
|
63159
64106
|
stdio: "ignore"
|
|
63160
64107
|
});
|
|
63161
64108
|
child.unref();
|
|
63162
64109
|
return;
|
|
63163
64110
|
}
|
|
63164
|
-
const child =
|
|
64111
|
+
const child = spawn3("xdg-open", [url], {
|
|
63165
64112
|
detached: true,
|
|
63166
64113
|
stdio: "ignore"
|
|
63167
64114
|
});
|
|
@@ -63352,14 +64299,14 @@ class LoginDock extends Component_default {
|
|
|
63352
64299
|
if (step === "select-provider") {
|
|
63353
64300
|
const items = OAUTH_PROVIDERS.map((p, idx) => {
|
|
63354
64301
|
const isSelected = idx === selectedIndex;
|
|
63355
|
-
const pointer = isSelected ? c.
|
|
64302
|
+
const pointer = isSelected ? c.info(`${figures.pointer} `) : " ";
|
|
64303
|
+
const name = isSelected ? c.info(bold(p.name)) : c.text(bold(p.name));
|
|
63356
64304
|
return /* @__PURE__ */ jsxs(Box, {
|
|
63357
64305
|
direction: "column",
|
|
63358
64306
|
width: maxCols,
|
|
63359
64307
|
children: [
|
|
63360
64308
|
/* @__PURE__ */ jsx(Text, {
|
|
63361
|
-
|
|
63362
|
-
children: `${pointer}${bold(p.name)}`
|
|
64309
|
+
children: `${pointer}${name}`
|
|
63363
64310
|
}),
|
|
63364
64311
|
/* @__PURE__ */ jsx(Text, {
|
|
63365
64312
|
color: "muted",
|
|
@@ -63475,8 +64422,9 @@ class ModelPicker extends SelectList {
|
|
|
63475
64422
|
onCancel: props.onCancel,
|
|
63476
64423
|
renderItem: (m, isSelected, maxCols) => {
|
|
63477
64424
|
const isCurrent = m.provider === props.currentModel.provider && m.modelId === props.currentModel.modelId;
|
|
63478
|
-
const pointer = isSelected ? c.
|
|
64425
|
+
const pointer = isSelected ? c.info(`${figures.pointer} `) : " ";
|
|
63479
64426
|
const activeBadge = isCurrent ? bold(c.current(" (active)")) : "";
|
|
64427
|
+
const title = isSelected ? c.info(m.modelId) : isCurrent ? c.current(m.modelId) : c.text(m.modelId);
|
|
63480
64428
|
const providerName = m.provider.toUpperCase();
|
|
63481
64429
|
const caps = m.reasoning ? "reasoning" : "chat";
|
|
63482
64430
|
const meta = `${providerName} • ${caps}`;
|
|
@@ -63485,11 +64433,10 @@ class ModelPicker extends SelectList {
|
|
|
63485
64433
|
width: maxCols,
|
|
63486
64434
|
children: [
|
|
63487
64435
|
/* @__PURE__ */ jsx(Text, {
|
|
63488
|
-
color: isSelected ? "selected" : isCurrent ? "current" : "text",
|
|
63489
64436
|
wrap: false,
|
|
63490
64437
|
clip: true,
|
|
63491
64438
|
ellipsis: true,
|
|
63492
|
-
children: `${pointer}${
|
|
64439
|
+
children: `${pointer}${title}${activeBadge}`
|
|
63493
64440
|
}),
|
|
63494
64441
|
/* @__PURE__ */ jsx(Text, {
|
|
63495
64442
|
color: "muted",
|
|
@@ -63525,8 +64472,9 @@ class ThemePicker extends SelectList {
|
|
|
63525
64472
|
onCancel: props.onCancel,
|
|
63526
64473
|
renderItem: (t, isSelected, maxCols) => {
|
|
63527
64474
|
const isCurrent = t.name === props.currentTheme;
|
|
63528
|
-
const pointer = isSelected ? c.
|
|
64475
|
+
const pointer = isSelected ? c.info(`${figures.pointer} `) : " ";
|
|
63529
64476
|
const activeBadge = isCurrent ? bold(c.current(" (active)")) : "";
|
|
64477
|
+
const title = isSelected ? c.info(t.label) : isCurrent ? c.current(t.label) : c.text(t.label);
|
|
63530
64478
|
const tBrand = resolveThemeColor(t.theme.brand, false)(figures.blackCircle);
|
|
63531
64479
|
const tSuccess = resolveThemeColor(t.theme.success, false)(figures.blackCircle);
|
|
63532
64480
|
const tWarning = resolveThemeColor(t.theme.warning, false)(figures.blackCircle);
|
|
@@ -63539,11 +64487,10 @@ class ThemePicker extends SelectList {
|
|
|
63539
64487
|
width: maxCols,
|
|
63540
64488
|
children: [
|
|
63541
64489
|
/* @__PURE__ */ jsx(Text, {
|
|
63542
|
-
color: isSelected ? "selected" : isCurrent ? "current" : "text",
|
|
63543
64490
|
wrap: false,
|
|
63544
64491
|
clip: true,
|
|
63545
64492
|
ellipsis: true,
|
|
63546
|
-
children: `${pointer}${
|
|
64493
|
+
children: `${pointer}${title}${activeBadge}`
|
|
63547
64494
|
}),
|
|
63548
64495
|
/* @__PURE__ */ jsx(Text, {
|
|
63549
64496
|
color: "muted",
|
|
@@ -63583,7 +64530,8 @@ class SessionMenu extends SelectList {
|
|
|
63583
64530
|
renderItem: (s, isSelected, maxCols) => {
|
|
63584
64531
|
const rawTitle = s.name || s.turns?.[0]?.userPrompt || "Untitled Session";
|
|
63585
64532
|
const cleanTitle = rawTitle.replace(/\s+/g, " ").trim();
|
|
63586
|
-
const pointer = isSelected ? c.
|
|
64533
|
+
const pointer = isSelected ? c.info(`${figures.pointer} `) : " ";
|
|
64534
|
+
const title = isSelected ? c.info(cleanTitle) : c.text(cleanTitle);
|
|
63587
64535
|
const shortId = s.id.slice(0, 8);
|
|
63588
64536
|
const dateStr = s.date ? s.date.split("T")[0] : "";
|
|
63589
64537
|
const turnsCount = s.turns?.length ?? 0;
|
|
@@ -63598,11 +64546,10 @@ class SessionMenu extends SelectList {
|
|
|
63598
64546
|
width: maxCols,
|
|
63599
64547
|
children: [
|
|
63600
64548
|
/* @__PURE__ */ jsx(Text, {
|
|
63601
|
-
color: isSelected ? "selected" : "text",
|
|
63602
64549
|
wrap: false,
|
|
63603
64550
|
clip: true,
|
|
63604
64551
|
ellipsis: true,
|
|
63605
|
-
children: `${pointer}${
|
|
64552
|
+
children: `${pointer}${title}`
|
|
63606
64553
|
}),
|
|
63607
64554
|
/* @__PURE__ */ jsx(Text, {
|
|
63608
64555
|
color: "muted",
|
|
@@ -63955,7 +64902,8 @@ class RewindMenu extends SelectList {
|
|
|
63955
64902
|
onSelect: props.onSelect,
|
|
63956
64903
|
onCancel: props.onCancel,
|
|
63957
64904
|
renderItem: (item, isSelected, maxCols) => {
|
|
63958
|
-
const pointer = isSelected ? c.
|
|
64905
|
+
const pointer = isSelected ? c.info(`${figures.pointer} `) : " ";
|
|
64906
|
+
const title = isSelected ? c.info(item.promptText) : c.text(item.promptText);
|
|
63959
64907
|
let summaryText;
|
|
63960
64908
|
if (item.hasCodeChanges) {
|
|
63961
64909
|
const filePart = c.muted(`${item.changedFileCount} file${item.changedFileCount !== 1 ? "s" : ""} changed`);
|
|
@@ -63976,11 +64924,10 @@ class RewindMenu extends SelectList {
|
|
|
63976
64924
|
width: maxCols,
|
|
63977
64925
|
children: [
|
|
63978
64926
|
/* @__PURE__ */ jsx(Text, {
|
|
63979
|
-
color: isSelected ? "selected" : "text",
|
|
63980
64927
|
wrap: false,
|
|
63981
64928
|
clip: true,
|
|
63982
64929
|
ellipsis: true,
|
|
63983
|
-
children: `${pointer}${
|
|
64930
|
+
children: `${pointer}${title}`
|
|
63984
64931
|
}),
|
|
63985
64932
|
/* @__PURE__ */ jsx(Text, {
|
|
63986
64933
|
wrap: false,
|
|
@@ -64081,7 +65028,7 @@ class BashPermissionDock extends Component_default {
|
|
|
64081
65028
|
const maxCols = Math.max(1, termWidth);
|
|
64082
65029
|
const { mode, selectedIndex, scrollOffset } = this.state;
|
|
64083
65030
|
const { command, explanation } = this.props;
|
|
64084
|
-
const pointer = figures.
|
|
65031
|
+
const pointer = figures.pointer ?? ">";
|
|
64085
65032
|
const isYesSelected = selectedIndex === 0;
|
|
64086
65033
|
const isNoSelected = selectedIndex === 1;
|
|
64087
65034
|
const divider = c.rule(figures.horizontalLine.repeat(maxCols));
|
|
@@ -64391,7 +65338,7 @@ class FilePermissionDock extends Component_default {
|
|
|
64391
65338
|
const { mode, selectedIndex, scrollOffset } = this.state;
|
|
64392
65339
|
const { request } = this.props;
|
|
64393
65340
|
const { kind, filePath } = request;
|
|
64394
|
-
const pointer = figures.
|
|
65341
|
+
const pointer = figures.pointer ?? ">";
|
|
64395
65342
|
const isYesSelected = selectedIndex === 0;
|
|
64396
65343
|
const isNoSelected = selectedIndex === 1;
|
|
64397
65344
|
const divider = c.rule(figures.horizontalLine.repeat(maxCols));
|
|
@@ -64955,7 +65902,7 @@ async function applyCommandResult(text, cmdResult, ctx) {
|
|
|
64955
65902
|
}
|
|
64956
65903
|
|
|
64957
65904
|
// src/app/utils/bash.ts
|
|
64958
|
-
import { spawn as
|
|
65905
|
+
import { spawn as spawn4 } from "node:child_process";
|
|
64959
65906
|
async function executeDirectBash(options) {
|
|
64960
65907
|
const { command, cwd = process.cwd(), abortSignal, onChunk } = options;
|
|
64961
65908
|
const { shell, args } = getPlatformShell();
|
|
@@ -64968,7 +65915,7 @@ async function executeDirectBash(options) {
|
|
|
64968
65915
|
let output = "";
|
|
64969
65916
|
let isSettled = false;
|
|
64970
65917
|
try {
|
|
64971
|
-
child =
|
|
65918
|
+
child = spawn4(shell, childArgs, {
|
|
64972
65919
|
cwd,
|
|
64973
65920
|
env: process.env,
|
|
64974
65921
|
stdio: ["pipe", "pipe", "pipe"],
|
|
@@ -65062,6 +66009,10 @@ class TUIApp {
|
|
|
65062
66009
|
this.session = options.initialSession ?? new AgentSession;
|
|
65063
66010
|
this.onExitCallback = options.onExit;
|
|
65064
66011
|
this.engine = new TerminalEngine_default;
|
|
66012
|
+
const savedMode = getSavedMode();
|
|
66013
|
+
if (savedMode) {
|
|
66014
|
+
setActiveMode(savedMode);
|
|
66015
|
+
}
|
|
65065
66016
|
const model = this.session.getModel();
|
|
65066
66017
|
this.header = new Header({
|
|
65067
66018
|
version: options.version,
|
|
@@ -65177,8 +66128,13 @@ class TUIApp {
|
|
|
65177
66128
|
}
|
|
65178
66129
|
proceedStart() {
|
|
65179
66130
|
this.engine.ensureAlternateScreen();
|
|
66131
|
+
this.session.initializeHooks({
|
|
66132
|
+
cwd: this.cwd,
|
|
66133
|
+
isTrusted: true,
|
|
66134
|
+
source: this.session.session.turns.length > 0 ? "resume" : "startup"
|
|
66135
|
+
});
|
|
65180
66136
|
const logPath = getSessionLogPath(this.session.session.date, this.session.session.id);
|
|
65181
|
-
if (this.session.session.turns.length > 0 ||
|
|
66137
|
+
if (this.session.session.turns.length > 0 || existsSync18(logPath)) {
|
|
65182
66138
|
renderTranscript(this.engine, this.session.session, this.header);
|
|
65183
66139
|
} else {
|
|
65184
66140
|
this.engine.commit("header", this.header.render());
|