steward-cli 0.12.0 → 0.14.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 +1 -1
- package/dist/cli.js +1151 -659
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -52395,6 +52395,8 @@ function formatToolStatus(options) {
|
|
|
52395
52395
|
let mainLine = `${bullet} ${dispName}`;
|
|
52396
52396
|
if (truncatedArg) {
|
|
52397
52397
|
mainLine += `${source_default.dim("(")}${source_default.dim(truncatedArg)}${source_default.dim(")")}`;
|
|
52398
|
+
} else {
|
|
52399
|
+
mainLine += `${source_default.dim("()")}`;
|
|
52398
52400
|
}
|
|
52399
52401
|
const lines = [mainLine];
|
|
52400
52402
|
if (status === "completed" && options.toolOutput) {
|
|
@@ -70954,7 +70956,6 @@ class ToolCatalog {
|
|
|
70954
70956
|
aiTools[name] = tool({
|
|
70955
70957
|
description: def.description,
|
|
70956
70958
|
inputSchema: def.parameters,
|
|
70957
|
-
parameters: def.parameters,
|
|
70958
70959
|
execute: async (args) => {
|
|
70959
70960
|
return def.execute(args, context);
|
|
70960
70961
|
}
|
|
@@ -70966,13 +70967,52 @@ class ToolCatalog {
|
|
|
70966
70967
|
var defaultToolCatalog = new ToolCatalog;
|
|
70967
70968
|
|
|
70968
70969
|
// src/tools/read-file/index.ts
|
|
70969
|
-
import { existsSync as existsSync9, lstatSync as lstatSync3, readFileSync as readFileSync8 } from "node:fs";
|
|
70970
|
-
import { isAbsolute as isAbsolute2, resolve as resolve5 } from "node:path";
|
|
70970
|
+
import { existsSync as existsSync9, lstatSync as lstatSync3, readFileSync as readFileSync8, realpathSync as realpathSync3 } from "node:fs";
|
|
70971
|
+
import { isAbsolute as isAbsolute2, resolve as resolve5, sep as sep3 } from "node:path";
|
|
70971
70972
|
var readFileInputSchema = object({
|
|
70972
70973
|
path: string2().describe("The path of the file to read (relative to current working directory or absolute)."),
|
|
70973
70974
|
offset: number2().int().min(1).optional().describe("The line number to start reading from (1-indexed). Defaults to 1."),
|
|
70974
70975
|
limit: number2().int().min(1).max(2000).optional().describe("The maximum number of lines to read. Defaults to 200.")
|
|
70975
70976
|
});
|
|
70977
|
+
function isPathInside2(targetPath, basePath) {
|
|
70978
|
+
const normTarget = resolve5(targetPath);
|
|
70979
|
+
const normBase = resolve5(basePath);
|
|
70980
|
+
if (normTarget === normBase) {
|
|
70981
|
+
return true;
|
|
70982
|
+
}
|
|
70983
|
+
const prefix = normBase.endsWith(sep3) ? normBase : `${normBase}${sep3}`;
|
|
70984
|
+
return normTarget.startsWith(prefix);
|
|
70985
|
+
}
|
|
70986
|
+
function resolveSafeReadPath(cwd, filePath) {
|
|
70987
|
+
if (!filePath || !filePath.trim()) {
|
|
70988
|
+
throw new Error("File path cannot be empty.");
|
|
70989
|
+
}
|
|
70990
|
+
const trimmed = filePath.trim();
|
|
70991
|
+
const resolved = isAbsolute2(trimmed) ? resolve5(trimmed) : resolve5(cwd, trimmed);
|
|
70992
|
+
if (!isPathInside2(resolved, cwd)) {
|
|
70993
|
+
throw new Error(`Access denied: path "${filePath}" resolves outside the trusted workspace root ("${cwd}").`);
|
|
70994
|
+
}
|
|
70995
|
+
if (!existsSync9(resolved)) {
|
|
70996
|
+
throw new Error(`File not found: ${filePath}`);
|
|
70997
|
+
}
|
|
70998
|
+
let realTarget;
|
|
70999
|
+
try {
|
|
71000
|
+
realTarget = realpathSync3(resolved);
|
|
71001
|
+
} catch {
|
|
71002
|
+
realTarget = resolved;
|
|
71003
|
+
}
|
|
71004
|
+
if (!isPathInside2(realTarget, cwd)) {
|
|
71005
|
+
throw new Error(`Access denied: symlink "${filePath}" points outside the trusted workspace root.`);
|
|
71006
|
+
}
|
|
71007
|
+
const stat = lstatSync3(resolved);
|
|
71008
|
+
if (stat.isDirectory()) {
|
|
71009
|
+
throw new Error(`Cannot read path because it is a directory: ${filePath}`);
|
|
71010
|
+
}
|
|
71011
|
+
if (!stat.isFile() && !stat.isSymbolicLink()) {
|
|
71012
|
+
throw new Error(`Cannot read special or non-regular file: ${filePath}`);
|
|
71013
|
+
}
|
|
71014
|
+
return realTarget;
|
|
71015
|
+
}
|
|
70976
71016
|
function isBinaryBuffer(buffer) {
|
|
70977
71017
|
const checkLength = Math.min(buffer.length, 1024);
|
|
70978
71018
|
for (let i = 0;i < checkLength; i++) {
|
|
@@ -70985,7 +71025,7 @@ function isBinaryBuffer(buffer) {
|
|
|
70985
71025
|
var readFileTool = {
|
|
70986
71026
|
name: "read_file",
|
|
70987
71027
|
displayName: "Read",
|
|
70988
|
-
description: "Reads the contents of a file
|
|
71028
|
+
description: "Reads the contents of a file. Supports reading specific line ranges via offset and limit. Read is read-only and does not mutate or checkpoint.",
|
|
70989
71029
|
parameters: readFileInputSchema,
|
|
70990
71030
|
confirmationPolicy: "never",
|
|
70991
71031
|
summarize: (_args, result) => {
|
|
@@ -70993,10 +71033,7 @@ var readFileTool = {
|
|
|
70993
71033
|
return `Read ${lines} line${lines === 1 ? "" : "s"}`;
|
|
70994
71034
|
},
|
|
70995
71035
|
execute: async (args, context) => {
|
|
70996
|
-
const targetPath =
|
|
70997
|
-
if (!existsSync9(targetPath)) {
|
|
70998
|
-
throw new Error(`File not found: ${args.path}`);
|
|
70999
|
-
}
|
|
71036
|
+
const targetPath = resolveSafeReadPath(context.cwd, args.path);
|
|
71000
71037
|
const stat = lstatSync3(targetPath);
|
|
71001
71038
|
if (stat.isDirectory()) {
|
|
71002
71039
|
throw new Error(`Cannot read path because it is a directory: ${args.path}`);
|
|
@@ -71032,18 +71069,13 @@ var readFileTool = {
|
|
|
71032
71069
|
const startIndex = Math.max(0, startLine - 1);
|
|
71033
71070
|
const endIndex = Math.min(totalLines, startIndex + limit);
|
|
71034
71071
|
const slice = allLines.slice(startIndex, endIndex);
|
|
71035
|
-
const padWidth = String(endIndex).length;
|
|
71036
|
-
const formattedContent = slice.map((line, idx) => {
|
|
71037
|
-
const lineNum = String(startIndex + idx + 1).padStart(padWidth, " ");
|
|
71038
|
-
return `${lineNum} | ${line}`;
|
|
71039
|
-
}).join(`
|
|
71040
|
-
`);
|
|
71041
71072
|
return {
|
|
71042
71073
|
path: args.path,
|
|
71043
71074
|
totalLines,
|
|
71044
71075
|
startLine,
|
|
71045
71076
|
endLine: endIndex,
|
|
71046
|
-
content:
|
|
71077
|
+
content: slice.join(`
|
|
71078
|
+
`),
|
|
71047
71079
|
isTruncated: endIndex < totalLines,
|
|
71048
71080
|
linesRead: slice.length
|
|
71049
71081
|
};
|
|
@@ -71051,6 +71083,9 @@ var readFileTool = {
|
|
|
71051
71083
|
};
|
|
71052
71084
|
|
|
71053
71085
|
// src/tools/write-file/index.ts
|
|
71086
|
+
import { existsSync as existsSync11, readFileSync as readFileSync9, statSync as statSync3 } from "node:fs";
|
|
71087
|
+
|
|
71088
|
+
// src/utils/atomic-write.ts
|
|
71054
71089
|
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
71055
71090
|
import {
|
|
71056
71091
|
chmodSync as chmodSync2,
|
|
@@ -71059,13 +71094,46 @@ import {
|
|
|
71059
71094
|
fsyncSync as fsyncSync5,
|
|
71060
71095
|
mkdirSync as mkdirSync7,
|
|
71061
71096
|
openSync as openSync6,
|
|
71062
|
-
readFileSync as readFileSync9,
|
|
71063
71097
|
renameSync as renameSync5,
|
|
71064
|
-
statSync as statSync3,
|
|
71065
71098
|
unlinkSync as unlinkSync5,
|
|
71066
71099
|
writeSync as writeSync5
|
|
71067
71100
|
} from "node:fs";
|
|
71068
71101
|
import { dirname as dirname5, join as join7 } from "node:path";
|
|
71102
|
+
function atomicWriteFileSync(targetPath, content, options = {}) {
|
|
71103
|
+
const buffer = typeof content === "string" ? Buffer.from(content, options.encoding ?? "utf-8") : content;
|
|
71104
|
+
const dir = dirname5(targetPath);
|
|
71105
|
+
if (!existsSync10(dir)) {
|
|
71106
|
+
mkdirSync7(dir, { recursive: true });
|
|
71107
|
+
}
|
|
71108
|
+
const mode = options.mode ?? 420;
|
|
71109
|
+
const tmpPath = join7(dir, `.tmp-write-${randomUUID5().slice(0, 8)}`);
|
|
71110
|
+
let fd = null;
|
|
71111
|
+
try {
|
|
71112
|
+
fd = openSync6(tmpPath, "w", mode);
|
|
71113
|
+
writeSync5(fd, buffer, 0, buffer.length);
|
|
71114
|
+
fsyncSync5(fd);
|
|
71115
|
+
closeSync6(fd);
|
|
71116
|
+
fd = null;
|
|
71117
|
+
try {
|
|
71118
|
+
chmodSync2(tmpPath, mode);
|
|
71119
|
+
} catch {}
|
|
71120
|
+
renameSync5(tmpPath, targetPath);
|
|
71121
|
+
} catch (err) {
|
|
71122
|
+
if (fd !== null) {
|
|
71123
|
+
try {
|
|
71124
|
+
closeSync6(fd);
|
|
71125
|
+
} catch {}
|
|
71126
|
+
}
|
|
71127
|
+
if (existsSync10(tmpPath)) {
|
|
71128
|
+
try {
|
|
71129
|
+
unlinkSync5(tmpPath);
|
|
71130
|
+
} catch {}
|
|
71131
|
+
}
|
|
71132
|
+
throw err;
|
|
71133
|
+
}
|
|
71134
|
+
}
|
|
71135
|
+
|
|
71136
|
+
// src/tools/write-file/index.ts
|
|
71069
71137
|
var writeFileInputSchema = object({
|
|
71070
71138
|
file_path: string2().describe("The path of the file to write (relative to current working directory or absolute inside trusted workspace)."),
|
|
71071
71139
|
content: string2().describe("The full text content to write to the file.")
|
|
@@ -71097,7 +71165,7 @@ ${detail}${overflow}`;
|
|
|
71097
71165
|
},
|
|
71098
71166
|
execute: async (args, context) => {
|
|
71099
71167
|
const { absolutePath: targetPath, relativePath } = resolveDirectMutationPath(context.cwd, args.file_path);
|
|
71100
|
-
const isNew = !
|
|
71168
|
+
const isNew = !existsSync11(targetPath);
|
|
71101
71169
|
const beforeContent = isNew ? null : readFileSync9(targetPath, "utf-8");
|
|
71102
71170
|
const afterContent = args.content;
|
|
71103
71171
|
const linesWritten = afterContent.length === 0 ? 0 : afterContent.split(/\r?\n/).length;
|
|
@@ -71133,11 +71201,11 @@ ${detail}${overflow}`;
|
|
|
71133
71201
|
throw new Error("File write permission denied by user.");
|
|
71134
71202
|
}
|
|
71135
71203
|
if (isNew) {
|
|
71136
|
-
if (
|
|
71204
|
+
if (existsSync11(targetPath)) {
|
|
71137
71205
|
throw new Error(`File was created externally during approval review: "${relativePath}". Mutation aborted to prevent data loss.`);
|
|
71138
71206
|
}
|
|
71139
71207
|
} else {
|
|
71140
|
-
if (!
|
|
71208
|
+
if (!existsSync11(targetPath)) {
|
|
71141
71209
|
throw new Error(`File was deleted externally during approval review: "${relativePath}". Mutation aborted.`);
|
|
71142
71210
|
}
|
|
71143
71211
|
const freshContent = readFileSync9(targetPath, "utf-8");
|
|
@@ -71145,42 +71213,14 @@ ${detail}${overflow}`;
|
|
|
71145
71213
|
throw new Error(`File was modified externally during approval review: "${relativePath}". Mutation aborted to prevent data loss.`);
|
|
71146
71214
|
}
|
|
71147
71215
|
}
|
|
71148
|
-
const newBuffer = Buffer.from(afterContent, "utf-8");
|
|
71149
|
-
const dir = dirname5(targetPath);
|
|
71150
|
-
if (!existsSync10(dir)) {
|
|
71151
|
-
mkdirSync7(dir, { recursive: true });
|
|
71152
|
-
}
|
|
71153
71216
|
let existingMode = 420;
|
|
71154
71217
|
if (!isNew) {
|
|
71155
71218
|
try {
|
|
71156
71219
|
existingMode = statSync3(targetPath).mode;
|
|
71157
71220
|
} catch {}
|
|
71158
71221
|
}
|
|
71159
|
-
const
|
|
71160
|
-
|
|
71161
|
-
try {
|
|
71162
|
-
fd = openSync6(tmpPath, "w", existingMode);
|
|
71163
|
-
writeSync5(fd, newBuffer, 0, newBuffer.length);
|
|
71164
|
-
fsyncSync5(fd);
|
|
71165
|
-
closeSync6(fd);
|
|
71166
|
-
fd = null;
|
|
71167
|
-
try {
|
|
71168
|
-
chmodSync2(tmpPath, existingMode);
|
|
71169
|
-
} catch {}
|
|
71170
|
-
renameSync5(tmpPath, targetPath);
|
|
71171
|
-
} catch (err) {
|
|
71172
|
-
if (fd !== null) {
|
|
71173
|
-
try {
|
|
71174
|
-
closeSync6(fd);
|
|
71175
|
-
} catch {}
|
|
71176
|
-
}
|
|
71177
|
-
if (existsSync10(tmpPath)) {
|
|
71178
|
-
try {
|
|
71179
|
-
unlinkSync5(tmpPath);
|
|
71180
|
-
} catch {}
|
|
71181
|
-
}
|
|
71182
|
-
throw err;
|
|
71183
|
-
}
|
|
71222
|
+
const newBuffer = Buffer.from(afterContent, "utf-8");
|
|
71223
|
+
atomicWriteFileSync(targetPath, newBuffer, { mode: existingMode });
|
|
71184
71224
|
if (context.checkpointTracker) {
|
|
71185
71225
|
await context.checkpointTracker.completeMutation(targetPath);
|
|
71186
71226
|
}
|
|
@@ -71200,20 +71240,7 @@ ${detail}${overflow}`;
|
|
|
71200
71240
|
};
|
|
71201
71241
|
|
|
71202
71242
|
// src/tools/edit-file/index.ts
|
|
71203
|
-
import {
|
|
71204
|
-
import {
|
|
71205
|
-
chmodSync as chmodSync3,
|
|
71206
|
-
closeSync as closeSync7,
|
|
71207
|
-
existsSync as existsSync11,
|
|
71208
|
-
fsyncSync as fsyncSync6,
|
|
71209
|
-
openSync as openSync7,
|
|
71210
|
-
readFileSync as readFileSync10,
|
|
71211
|
-
renameSync as renameSync6,
|
|
71212
|
-
statSync as statSync4,
|
|
71213
|
-
unlinkSync as unlinkSync6,
|
|
71214
|
-
writeSync as writeSync6
|
|
71215
|
-
} from "node:fs";
|
|
71216
|
-
import { dirname as dirname6, join as join8 } from "node:path";
|
|
71243
|
+
import { existsSync as existsSync12, readFileSync as readFileSync10, statSync as statSync4 } from "node:fs";
|
|
71217
71244
|
|
|
71218
71245
|
// src/utils/diff.ts
|
|
71219
71246
|
function tokenizeLines(content) {
|
|
@@ -71523,7 +71550,7 @@ ${diffLines.join(`
|
|
|
71523
71550
|
},
|
|
71524
71551
|
execute: async (args, context) => {
|
|
71525
71552
|
const { absolutePath: targetPath, relativePath } = resolveDirectMutationPath(context.cwd, args.file_path);
|
|
71526
|
-
if (!
|
|
71553
|
+
if (!existsSync12(targetPath)) {
|
|
71527
71554
|
throw new Error(`Cannot edit file because it does not exist: "${args.file_path}". Use write_file to create new files.`);
|
|
71528
71555
|
}
|
|
71529
71556
|
const currentContent = readFileSync10(targetPath, "utf-8");
|
|
@@ -71574,65 +71601,30 @@ ${diffLines.join(`
|
|
|
71574
71601
|
if (!perm.allowed) {
|
|
71575
71602
|
throw new Error("File edit permission denied by user.");
|
|
71576
71603
|
}
|
|
71577
|
-
if (!
|
|
71604
|
+
if (!existsSync12(targetPath)) {
|
|
71578
71605
|
throw new Error(`File was deleted externally during approval review: "${relativePath}". Mutation aborted.`);
|
|
71579
71606
|
}
|
|
71580
71607
|
const freshContent = readFileSync10(targetPath, "utf-8");
|
|
71581
71608
|
if (freshContent !== currentContent) {
|
|
71582
71609
|
throw new Error(`File was modified externally during approval review: "${relativePath}". Mutation aborted to prevent data loss.`);
|
|
71583
71610
|
}
|
|
71584
|
-
const
|
|
71585
|
-
const afterLines = updatedContent.split(/\r?\n/);
|
|
71586
|
-
const lineDelta = afterLines.length - beforeLines.length;
|
|
71611
|
+
const diff = buildUnifiedDiff(currentContent, updatedContent, 0);
|
|
71587
71612
|
let addedLines = 0;
|
|
71588
71613
|
let removedLines = 0;
|
|
71589
|
-
const
|
|
71590
|
-
|
|
71591
|
-
|
|
71592
|
-
|
|
71593
|
-
|
|
71594
|
-
|
|
71595
|
-
addedLines = totalReplacedOccurrences;
|
|
71596
|
-
removedLines = totalReplacedOccurrences;
|
|
71597
|
-
} else {
|
|
71598
|
-
removedLines = (oldLinesInMatch - 1) * totalReplacedOccurrences + (lineDelta < 0 ? Math.abs(lineDelta) : 0);
|
|
71599
|
-
addedLines = (newLinesInMatch - 1) * totalReplacedOccurrences + (lineDelta > 0 ? lineDelta : 0);
|
|
71600
|
-
if (addedLines === 0 && removedLines === 0 && args.old_string !== args.new_string) {
|
|
71601
|
-
addedLines = totalReplacedOccurrences;
|
|
71602
|
-
removedLines = totalReplacedOccurrences;
|
|
71614
|
+
for (const hunk of diff.hunks) {
|
|
71615
|
+
for (const line of hunk.lines) {
|
|
71616
|
+
if (line.kind === "addition")
|
|
71617
|
+
addedLines++;
|
|
71618
|
+
if (line.kind === "deletion")
|
|
71619
|
+
removedLines++;
|
|
71603
71620
|
}
|
|
71604
71621
|
}
|
|
71605
|
-
const newBuffer = Buffer.from(updatedContent, "utf-8");
|
|
71606
|
-
const dir = dirname6(targetPath);
|
|
71607
71622
|
let existingMode = 420;
|
|
71608
71623
|
try {
|
|
71609
71624
|
existingMode = statSync4(targetPath).mode;
|
|
71610
71625
|
} catch {}
|
|
71611
|
-
const
|
|
71612
|
-
|
|
71613
|
-
try {
|
|
71614
|
-
fd = openSync7(tmpPath, "w", existingMode);
|
|
71615
|
-
writeSync6(fd, newBuffer, 0, newBuffer.length);
|
|
71616
|
-
fsyncSync6(fd);
|
|
71617
|
-
closeSync7(fd);
|
|
71618
|
-
fd = null;
|
|
71619
|
-
try {
|
|
71620
|
-
chmodSync3(tmpPath, existingMode);
|
|
71621
|
-
} catch {}
|
|
71622
|
-
renameSync6(tmpPath, targetPath);
|
|
71623
|
-
} catch (err) {
|
|
71624
|
-
if (fd !== null) {
|
|
71625
|
-
try {
|
|
71626
|
-
closeSync7(fd);
|
|
71627
|
-
} catch {}
|
|
71628
|
-
}
|
|
71629
|
-
if (existsSync11(tmpPath)) {
|
|
71630
|
-
try {
|
|
71631
|
-
unlinkSync6(tmpPath);
|
|
71632
|
-
} catch {}
|
|
71633
|
-
}
|
|
71634
|
-
throw err;
|
|
71635
|
-
}
|
|
71626
|
+
const newBuffer = Buffer.from(updatedContent, "utf-8");
|
|
71627
|
+
atomicWriteFileSync(targetPath, newBuffer, { mode: existingMode });
|
|
71636
71628
|
if (context.checkpointTracker) {
|
|
71637
71629
|
await context.checkpointTracker.completeMutation(targetPath);
|
|
71638
71630
|
}
|
|
@@ -71653,7 +71645,7 @@ ${diffLines.join(`
|
|
|
71653
71645
|
|
|
71654
71646
|
// src/tools/glob/index.ts
|
|
71655
71647
|
import { readdirSync as readdirSync2 } from "node:fs";
|
|
71656
|
-
import { isAbsolute as isAbsolute3, join as
|
|
71648
|
+
import { isAbsolute as isAbsolute3, join as join8, relative as relative2, resolve as resolve6 } from "node:path";
|
|
71657
71649
|
var globInputSchema = object({
|
|
71658
71650
|
pattern: string2().describe('Glob pattern to match files against (e.g. "**/*.ts", "src/*.json").'),
|
|
71659
71651
|
path: string2().optional().describe("Starting directory path. Defaults to workspace root."),
|
|
@@ -71682,10 +71674,10 @@ function walkDir(dir, baseDir, matcher, results, limit) {
|
|
|
71682
71674
|
break;
|
|
71683
71675
|
if (entry.isDirectory()) {
|
|
71684
71676
|
if (!IGNORED_DIRS.has(entry.name)) {
|
|
71685
|
-
walkDir(
|
|
71677
|
+
walkDir(join8(dir, entry.name), baseDir, matcher, results, limit);
|
|
71686
71678
|
}
|
|
71687
71679
|
} else if (entry.isFile()) {
|
|
71688
|
-
const rel = relative2(baseDir,
|
|
71680
|
+
const rel = relative2(baseDir, join8(dir, entry.name));
|
|
71689
71681
|
if (matcher(rel, entry.name)) {
|
|
71690
71682
|
results.push(rel);
|
|
71691
71683
|
}
|
|
@@ -71705,7 +71697,13 @@ var globTool = {
|
|
|
71705
71697
|
},
|
|
71706
71698
|
execute: async (args, context) => {
|
|
71707
71699
|
const startTime = Date.now();
|
|
71708
|
-
const searchRoot = args.path ? isAbsolute3(args.path) ? args.path : resolve6(context.cwd, args.path) : context.cwd;
|
|
71700
|
+
const searchRoot = args.path ? isAbsolute3(args.path) ? resolve6(args.path) : resolve6(context.cwd, args.path) : context.cwd;
|
|
71701
|
+
const normCwd = resolve6(context.cwd);
|
|
71702
|
+
const normTarget = resolve6(searchRoot);
|
|
71703
|
+
const prefix = normCwd.endsWith("/") ? normCwd : `${normCwd}/`;
|
|
71704
|
+
if (normTarget !== normCwd && !normTarget.startsWith(prefix)) {
|
|
71705
|
+
throw new Error(`Access denied: path "${args.path}" resolves outside the trusted workspace root ("${context.cwd}").`);
|
|
71706
|
+
}
|
|
71709
71707
|
const limit = args.limit ?? 100;
|
|
71710
71708
|
const pattern = args.pattern.trim();
|
|
71711
71709
|
const regex = globToRegex(pattern);
|
|
@@ -71728,7 +71726,7 @@ var globTool = {
|
|
|
71728
71726
|
|
|
71729
71727
|
// src/tools/grep/index.ts
|
|
71730
71728
|
import { readFileSync as readFileSync11, readdirSync as readdirSync3 } from "node:fs";
|
|
71731
|
-
import { isAbsolute as isAbsolute4, join as
|
|
71729
|
+
import { isAbsolute as isAbsolute4, join as join9, relative as relative3, resolve as resolve7 } from "node:path";
|
|
71732
71730
|
var grepInputSchema = object({
|
|
71733
71731
|
pattern: string2().describe("The regex or text pattern to search for across files."),
|
|
71734
71732
|
path: string2().optional().describe("Target directory or file path to search. Defaults to current directory."),
|
|
@@ -71757,7 +71755,7 @@ function searchInDir(dir, baseDir, regex, fileFilter, results, filesWithMatches,
|
|
|
71757
71755
|
for (const entry of entries) {
|
|
71758
71756
|
if (results.length >= limit)
|
|
71759
71757
|
break;
|
|
71760
|
-
const fullPath =
|
|
71758
|
+
const fullPath = join9(dir, entry.name);
|
|
71761
71759
|
if (entry.isDirectory()) {
|
|
71762
71760
|
if (!IGNORED_DIRS2.has(entry.name)) {
|
|
71763
71761
|
searchInDir(fullPath, baseDir, regex, fileFilter, results, filesWithMatches, limit);
|
|
@@ -71803,7 +71801,13 @@ var grepTool = {
|
|
|
71803
71801
|
},
|
|
71804
71802
|
execute: async (args, context) => {
|
|
71805
71803
|
const startTime = Date.now();
|
|
71806
|
-
const searchRoot = args.path ? isAbsolute4(args.path) ? args.path : resolve7(context.cwd, args.path) : context.cwd;
|
|
71804
|
+
const searchRoot = args.path ? isAbsolute4(args.path) ? resolve7(args.path) : resolve7(context.cwd, args.path) : context.cwd;
|
|
71805
|
+
const normCwd = resolve7(context.cwd);
|
|
71806
|
+
const normTarget = resolve7(searchRoot);
|
|
71807
|
+
const prefix = normCwd.endsWith("/") ? normCwd : `${normCwd}/`;
|
|
71808
|
+
if (normTarget !== normCwd && !normTarget.startsWith(prefix)) {
|
|
71809
|
+
throw new Error(`Access denied: path "${args.path}" resolves outside the trusted workspace root ("${context.cwd}").`);
|
|
71810
|
+
}
|
|
71807
71811
|
const limit = args.limit ?? 100;
|
|
71808
71812
|
const isCaseSensitive = args.case_sensitive ?? false;
|
|
71809
71813
|
const flags = isCaseSensitive ? "" : "i";
|
|
@@ -71858,7 +71862,7 @@ var grepTool = {
|
|
|
71858
71862
|
|
|
71859
71863
|
// src/tools/list-dir/index.ts
|
|
71860
71864
|
import { readdirSync as readdirSync4, statSync as statSync5 } from "node:fs";
|
|
71861
|
-
import { isAbsolute as isAbsolute5, join as
|
|
71865
|
+
import { isAbsolute as isAbsolute5, join as join10, resolve as resolve8 } from "node:path";
|
|
71862
71866
|
var listDirInputSchema = object({
|
|
71863
71867
|
path: string2().optional().describe("Directory path to list. Defaults to current working directory."),
|
|
71864
71868
|
recursive: boolean2().optional().describe("If true, lists files recursively (depth 2). Defaults to false.")
|
|
@@ -71875,7 +71879,13 @@ var listDirTool = {
|
|
|
71875
71879
|
return `Listed ${files} file${files === 1 ? "" : "s"}, ${dirs} director${dirs === 1 ? "y" : "ies"}`;
|
|
71876
71880
|
},
|
|
71877
71881
|
execute: async (args, context) => {
|
|
71878
|
-
const targetPath = args.path ? isAbsolute5(args.path) ? args.path : resolve8(context.cwd, args.path) : context.cwd;
|
|
71882
|
+
const targetPath = args.path ? isAbsolute5(args.path) ? resolve8(args.path) : resolve8(context.cwd, args.path) : context.cwd;
|
|
71883
|
+
const normCwd = resolve8(context.cwd);
|
|
71884
|
+
const normTarget = resolve8(targetPath);
|
|
71885
|
+
const prefix = normCwd.endsWith("/") ? normCwd : `${normCwd}/`;
|
|
71886
|
+
if (normTarget !== normCwd && !normTarget.startsWith(prefix)) {
|
|
71887
|
+
throw new Error(`Access denied: path "${args.path}" resolves outside the trusted workspace root ("${context.cwd}").`);
|
|
71888
|
+
}
|
|
71879
71889
|
const rawEntries = readdirSync4(targetPath, { withFileTypes: true });
|
|
71880
71890
|
const entries = [];
|
|
71881
71891
|
let fileCount = 0;
|
|
@@ -71892,7 +71902,7 @@ var listDirTool = {
|
|
|
71892
71902
|
type = "file";
|
|
71893
71903
|
fileCount++;
|
|
71894
71904
|
try {
|
|
71895
|
-
sizeBytes = statSync5(
|
|
71905
|
+
sizeBytes = statSync5(join10(targetPath, entry.name)).size;
|
|
71896
71906
|
} catch {}
|
|
71897
71907
|
} else {
|
|
71898
71908
|
fileCount++;
|
|
@@ -71960,7 +71970,7 @@ import { rmSync as rmSync3 } from "node:fs";
|
|
|
71960
71970
|
// src/services/tasks/process.ts
|
|
71961
71971
|
import { spawn } from "node:child_process";
|
|
71962
71972
|
import { createWriteStream, mkdirSync as mkdirSync8 } from "node:fs";
|
|
71963
|
-
import { join as
|
|
71973
|
+
import { join as join11 } from "node:path";
|
|
71964
71974
|
import { tmpdir, homedir as homedir4 } from "node:os";
|
|
71965
71975
|
|
|
71966
71976
|
// src/services/tasks/shell.ts
|
|
@@ -72008,16 +72018,16 @@ class ShellExecution {
|
|
|
72008
72018
|
this.command = options.command;
|
|
72009
72019
|
this.cwd = options.cwd;
|
|
72010
72020
|
this.startedAt = new Date().toISOString();
|
|
72011
|
-
const taskDir =
|
|
72021
|
+
const taskDir = join11(homedir4(), ".steward", "tasks", options.sessionId || "default");
|
|
72012
72022
|
try {
|
|
72013
72023
|
mkdirSync8(taskDir, { recursive: true });
|
|
72014
72024
|
} catch {
|
|
72015
|
-
const fallbackDir =
|
|
72025
|
+
const fallbackDir = join11(tmpdir(), "steward-tasks");
|
|
72016
72026
|
mkdirSync8(fallbackDir, { recursive: true });
|
|
72017
|
-
this.outputPath =
|
|
72027
|
+
this.outputPath = join11(fallbackDir, `${this.taskId}.log`);
|
|
72018
72028
|
return;
|
|
72019
72029
|
}
|
|
72020
|
-
this.outputPath =
|
|
72030
|
+
this.outputPath = join11(taskDir, `${this.taskId}.log`);
|
|
72021
72031
|
}
|
|
72022
72032
|
get status() {
|
|
72023
72033
|
switch (this.state) {
|
|
@@ -72534,27 +72544,38 @@ async function evaluateBashPermission(req, context) {
|
|
|
72534
72544
|
|
|
72535
72545
|
// src/tools/bash/index.ts
|
|
72536
72546
|
var bashInputSchema = object({
|
|
72537
|
-
command: string2(
|
|
72538
|
-
|
|
72547
|
+
command: string2({
|
|
72548
|
+
required_error: "command is required"
|
|
72549
|
+
}).min(1, "command cannot be empty").describe("The command to execute in the system shell."),
|
|
72550
|
+
explanation: string2({
|
|
72551
|
+
required_error: "An explanation is mandatory for running a bash command.",
|
|
72552
|
+
invalid_type_error: "explanation must be a string."
|
|
72553
|
+
}).min(1, "An explanation is mandatory for running a bash command.").max(240).describe("Mandatory brief sentence explaining why this command is needed and what it does (displayed in permission prompt).")
|
|
72539
72554
|
});
|
|
72540
72555
|
var bashTool = {
|
|
72541
72556
|
name: "bash",
|
|
72542
72557
|
displayName: "Bash",
|
|
72543
|
-
description: "Executes a command in the platform shell. Commands that exceed the foreground limit (~12s) automatically continue as background shell tasks. Use task_read, task_send_input, and task_kill to manage background tasks. Note: Bash commands are NOT tracked by the file checkpoint system (/rewind).",
|
|
72558
|
+
description: "Executes a command in the platform shell. An explanation of what the command does and why it is needed is MANDATORY. Commands that exceed the foreground limit (~12s) automatically continue as background shell tasks. Use task_read, task_send_input, and task_kill to manage background tasks. Note: Bash commands are NOT tracked by the file checkpoint system (/rewind).",
|
|
72544
72559
|
parameters: bashInputSchema,
|
|
72545
72560
|
confirmationPolicy: "never",
|
|
72561
|
+
summarizeArgs: (args) => {
|
|
72562
|
+
return args.command ?? "";
|
|
72563
|
+
},
|
|
72546
72564
|
summarize: (_args, result) => {
|
|
72547
72565
|
if (result && "status" in result && result.status === "backgrounded") {
|
|
72548
72566
|
return `Moved to background · task id: ${result.taskId}`;
|
|
72549
72567
|
}
|
|
72550
|
-
const code = result
|
|
72568
|
+
const code = result && "exitCode" in result ? result.exitCode : 0;
|
|
72551
72569
|
return `Ran successfully · exit code: ${code}`;
|
|
72552
72570
|
},
|
|
72553
72571
|
execute: async (args, context) => {
|
|
72554
|
-
const command = args.command.trim();
|
|
72555
|
-
|
|
72572
|
+
const command = typeof args?.command === "string" ? args.command.trim() : "";
|
|
72573
|
+
if (!command) {
|
|
72574
|
+
throw new Error("Command is required and cannot be empty.");
|
|
72575
|
+
}
|
|
72576
|
+
const explanation = typeof args?.explanation === "string" ? args.explanation.trim() : "";
|
|
72556
72577
|
if (!explanation) {
|
|
72557
|
-
throw new Error("An explanation is
|
|
72578
|
+
throw new Error("An explanation is mandatory for running a bash command.");
|
|
72558
72579
|
}
|
|
72559
72580
|
const perm = await evaluateBashPermission({ command, explanation }, context);
|
|
72560
72581
|
if (!perm.allowed) {
|
|
@@ -72676,10 +72697,34 @@ var taskKillTool = {
|
|
|
72676
72697
|
return context.shellTasks.kill(args.task_id);
|
|
72677
72698
|
}
|
|
72678
72699
|
};
|
|
72700
|
+
|
|
72701
|
+
// src/tools/task-list/index.ts
|
|
72702
|
+
var taskListInputSchema = object({});
|
|
72703
|
+
var taskListTool = {
|
|
72704
|
+
name: "task_list",
|
|
72705
|
+
displayName: "Task List",
|
|
72706
|
+
description: "Lists all currently tracked background shell tasks along with their IDs, statuses, commands, and exit codes.",
|
|
72707
|
+
parameters: taskListInputSchema,
|
|
72708
|
+
confirmationPolicy: "never",
|
|
72709
|
+
summarize: (_args, result) => {
|
|
72710
|
+
const count = result?.total ?? 0;
|
|
72711
|
+
return `Listed ${count} background task${count === 1 ? "" : "s"}`;
|
|
72712
|
+
},
|
|
72713
|
+
execute: async (_args, context) => {
|
|
72714
|
+
if (!context.shellTasks) {
|
|
72715
|
+
throw new Error("No shell tasks manager available in execution context.");
|
|
72716
|
+
}
|
|
72717
|
+
const tasks = context.shellTasks.list();
|
|
72718
|
+
return {
|
|
72719
|
+
tasks,
|
|
72720
|
+
total: tasks.length
|
|
72721
|
+
};
|
|
72722
|
+
}
|
|
72723
|
+
};
|
|
72679
72724
|
// package.json
|
|
72680
72725
|
var package_default = {
|
|
72681
72726
|
name: "steward-cli",
|
|
72682
|
-
version: "0.
|
|
72727
|
+
version: "0.14.0",
|
|
72683
72728
|
description: "Interactive AI engineering assistant for the terminal with human-in-the-loop control across local and frontier models.",
|
|
72684
72729
|
author: "sapirrior",
|
|
72685
72730
|
license: "MIT",
|
|
@@ -72896,6 +72941,727 @@ var webSearchTool = {
|
|
|
72896
72941
|
};
|
|
72897
72942
|
}
|
|
72898
72943
|
};
|
|
72944
|
+
// src/services/todos/store.ts
|
|
72945
|
+
import {
|
|
72946
|
+
existsSync as existsSync13,
|
|
72947
|
+
mkdirSync as mkdirSync9,
|
|
72948
|
+
readFileSync as readFileSync12,
|
|
72949
|
+
renameSync as renameSync6,
|
|
72950
|
+
unlinkSync as unlinkSync6,
|
|
72951
|
+
writeFileSync as writeFileSync3,
|
|
72952
|
+
openSync as openSync7,
|
|
72953
|
+
fsyncSync as fsyncSync6,
|
|
72954
|
+
closeSync as closeSync7
|
|
72955
|
+
} from "node:fs";
|
|
72956
|
+
import { join as join12 } from "node:path";
|
|
72957
|
+
import { randomUUID as randomUUID6 } from "node:crypto";
|
|
72958
|
+
|
|
72959
|
+
// src/config/env.ts
|
|
72960
|
+
var ALL_PROVIDER_NAMES = [
|
|
72961
|
+
"gemini",
|
|
72962
|
+
"anthropic",
|
|
72963
|
+
"openai",
|
|
72964
|
+
"xai",
|
|
72965
|
+
"mistral",
|
|
72966
|
+
"deepseek",
|
|
72967
|
+
"openrouter",
|
|
72968
|
+
"custom"
|
|
72969
|
+
];
|
|
72970
|
+
function getEnvConfig() {
|
|
72971
|
+
const geminiApiKey = process.env["GEMINI_API_KEY"]?.trim() || process.env["GOOGLE_GENERATIVE_AI_API_KEY"]?.trim() || undefined;
|
|
72972
|
+
const anthropicApiKey = process.env["ANTHROPIC_API_KEY"]?.trim() || undefined;
|
|
72973
|
+
const openaiApiKey = process.env["OPENAI_API_KEY"]?.trim() || undefined;
|
|
72974
|
+
const xaiApiKey = process.env["XAI_API_KEY"]?.trim() || undefined;
|
|
72975
|
+
const mistralApiKey = process.env["MISTRAL_API_KEY"]?.trim() || undefined;
|
|
72976
|
+
const deepseekApiKey = process.env["DEEPSEEK_API_KEY"]?.trim() || undefined;
|
|
72977
|
+
const openrouterApiKey = process.env["OPENROUTER_API_KEY"]?.trim() || undefined;
|
|
72978
|
+
const customApiKey = process.env["CUSTOM_API_KEY"]?.trim() || undefined;
|
|
72979
|
+
const customApiModelName = process.env["CUSTOM_API_MODEL_NAME"]?.trim() || undefined;
|
|
72980
|
+
const customApiUrl = process.env["CUSTOM_API_URL"]?.trim() || undefined;
|
|
72981
|
+
return {
|
|
72982
|
+
geminiApiKey,
|
|
72983
|
+
anthropicApiKey,
|
|
72984
|
+
openaiApiKey,
|
|
72985
|
+
xaiApiKey,
|
|
72986
|
+
mistralApiKey,
|
|
72987
|
+
deepseekApiKey,
|
|
72988
|
+
openrouterApiKey,
|
|
72989
|
+
custom: {
|
|
72990
|
+
apiKey: customApiKey,
|
|
72991
|
+
modelName: customApiModelName,
|
|
72992
|
+
baseURL: customApiUrl
|
|
72993
|
+
}
|
|
72994
|
+
};
|
|
72995
|
+
}
|
|
72996
|
+
function hasProviderConfig(provider, config = getEnvConfig()) {
|
|
72997
|
+
switch (provider) {
|
|
72998
|
+
case "gemini":
|
|
72999
|
+
return Boolean(config.geminiApiKey);
|
|
73000
|
+
case "anthropic":
|
|
73001
|
+
return Boolean(config.anthropicApiKey);
|
|
73002
|
+
case "openai":
|
|
73003
|
+
return Boolean(config.openaiApiKey);
|
|
73004
|
+
case "xai":
|
|
73005
|
+
return Boolean(config.xaiApiKey);
|
|
73006
|
+
case "mistral":
|
|
73007
|
+
return Boolean(config.mistralApiKey);
|
|
73008
|
+
case "deepseek":
|
|
73009
|
+
return Boolean(config.deepseekApiKey);
|
|
73010
|
+
case "openrouter":
|
|
73011
|
+
return Boolean(config.openrouterApiKey);
|
|
73012
|
+
case "custom":
|
|
73013
|
+
return Boolean(config.custom.baseURL && config.custom.modelName);
|
|
73014
|
+
}
|
|
73015
|
+
}
|
|
73016
|
+
function getAvailableProviders(config = getEnvConfig()) {
|
|
73017
|
+
const providers = [];
|
|
73018
|
+
if (hasProviderConfig("gemini", config))
|
|
73019
|
+
providers.push("gemini");
|
|
73020
|
+
if (hasProviderConfig("anthropic", config))
|
|
73021
|
+
providers.push("anthropic");
|
|
73022
|
+
if (hasProviderConfig("openai", config))
|
|
73023
|
+
providers.push("openai");
|
|
73024
|
+
if (hasProviderConfig("xai", config))
|
|
73025
|
+
providers.push("xai");
|
|
73026
|
+
if (hasProviderConfig("mistral", config))
|
|
73027
|
+
providers.push("mistral");
|
|
73028
|
+
if (hasProviderConfig("deepseek", config))
|
|
73029
|
+
providers.push("deepseek");
|
|
73030
|
+
if (hasProviderConfig("openrouter", config))
|
|
73031
|
+
providers.push("openrouter");
|
|
73032
|
+
if (hasProviderConfig("custom", config))
|
|
73033
|
+
providers.push("custom");
|
|
73034
|
+
return providers;
|
|
73035
|
+
}
|
|
73036
|
+
// src/services/todos/store.ts
|
|
73037
|
+
var VALID_STATUSES = [
|
|
73038
|
+
"pending",
|
|
73039
|
+
"in_progress",
|
|
73040
|
+
"completed",
|
|
73041
|
+
"cancelled",
|
|
73042
|
+
"blocked"
|
|
73043
|
+
];
|
|
73044
|
+
var MIN_ITEMS = 2;
|
|
73045
|
+
var MAX_ITEMS = 10;
|
|
73046
|
+
|
|
73047
|
+
class SessionLockQueue {
|
|
73048
|
+
queues = new Map;
|
|
73049
|
+
async runExclusive(sessionId, task) {
|
|
73050
|
+
const prev = this.queues.get(sessionId) ?? Promise.resolve();
|
|
73051
|
+
let release;
|
|
73052
|
+
const current = new Promise((resolve) => {
|
|
73053
|
+
release = resolve;
|
|
73054
|
+
});
|
|
73055
|
+
this.queues.set(sessionId, prev.then(() => current, () => current));
|
|
73056
|
+
try {
|
|
73057
|
+
await prev;
|
|
73058
|
+
return await task();
|
|
73059
|
+
} finally {
|
|
73060
|
+
release();
|
|
73061
|
+
if (this.queues.get(sessionId) === current) {
|
|
73062
|
+
this.queues.delete(sessionId);
|
|
73063
|
+
}
|
|
73064
|
+
}
|
|
73065
|
+
}
|
|
73066
|
+
}
|
|
73067
|
+
var sessionLocks = new SessionLockQueue;
|
|
73068
|
+
function sanitizeSessionId(sessionId) {
|
|
73069
|
+
if (!sessionId || typeof sessionId !== "string") {
|
|
73070
|
+
throw new Error("Invalid sessionId: must be a non-empty string");
|
|
73071
|
+
}
|
|
73072
|
+
const clean = sessionId.trim();
|
|
73073
|
+
if (!clean || clean.includes("..") || clean.includes("/") || clean.includes("\\")) {
|
|
73074
|
+
throw new Error(`Invalid sessionId for path resolution: "${sessionId}"`);
|
|
73075
|
+
}
|
|
73076
|
+
return clean;
|
|
73077
|
+
}
|
|
73078
|
+
function getSessionTodosDir(sessionId) {
|
|
73079
|
+
const safeId = sanitizeSessionId(sessionId);
|
|
73080
|
+
return join12(getSettingsDir(), "todos", safeId);
|
|
73081
|
+
}
|
|
73082
|
+
function getSessionTodosFilePath(sessionId) {
|
|
73083
|
+
return join12(getSessionTodosDir(sessionId), "todos.json");
|
|
73084
|
+
}
|
|
73085
|
+
function validateTodoList(todos) {
|
|
73086
|
+
if (!Array.isArray(todos)) {
|
|
73087
|
+
throw new Error("Invalid todos format: expected an array of todo items");
|
|
73088
|
+
}
|
|
73089
|
+
if (todos.length < MIN_ITEMS || todos.length > MAX_ITEMS) {
|
|
73090
|
+
throw new Error(`Todo list must contain between ${MIN_ITEMS} and ${MAX_ITEMS} items (received ${todos.length})`);
|
|
73091
|
+
}
|
|
73092
|
+
const seenIds = new Set;
|
|
73093
|
+
let inProgressCount = 0;
|
|
73094
|
+
const validated = [];
|
|
73095
|
+
for (let i = 0;i < todos.length; i++) {
|
|
73096
|
+
const item = todos[i];
|
|
73097
|
+
if (!item || typeof item !== "object") {
|
|
73098
|
+
throw new Error(`Invalid todo at index ${i}: item must be an object`);
|
|
73099
|
+
}
|
|
73100
|
+
const { id, description, status } = item;
|
|
73101
|
+
if (typeof id !== "string" || !id.trim()) {
|
|
73102
|
+
throw new Error(`Invalid todo at index ${i}: id must be a non-empty string`);
|
|
73103
|
+
}
|
|
73104
|
+
const trimmedId = id.trim();
|
|
73105
|
+
if (seenIds.has(trimmedId)) {
|
|
73106
|
+
throw new Error(`Duplicate todo id "${trimmedId}" at index ${i}`);
|
|
73107
|
+
}
|
|
73108
|
+
seenIds.add(trimmedId);
|
|
73109
|
+
if (typeof description !== "string" || !description.trim()) {
|
|
73110
|
+
throw new Error(`Invalid todo at index ${i} (${trimmedId}): description must be a non-empty string`);
|
|
73111
|
+
}
|
|
73112
|
+
if (typeof status !== "string" || !VALID_STATUSES.includes(status)) {
|
|
73113
|
+
throw new Error(`Invalid todo at index ${i} (${trimmedId}): status must be one of ${VALID_STATUSES.join(", ")} (got "${status}")`);
|
|
73114
|
+
}
|
|
73115
|
+
if (status === "in_progress") {
|
|
73116
|
+
inProgressCount++;
|
|
73117
|
+
}
|
|
73118
|
+
validated.push({
|
|
73119
|
+
id: trimmedId,
|
|
73120
|
+
description: description.trim(),
|
|
73121
|
+
status
|
|
73122
|
+
});
|
|
73123
|
+
}
|
|
73124
|
+
if (inProgressCount > 1) {
|
|
73125
|
+
throw new Error(`At most 1 todo item can be 'in_progress' at any time (found ${inProgressCount} in_progress items)`);
|
|
73126
|
+
}
|
|
73127
|
+
return validated;
|
|
73128
|
+
}
|
|
73129
|
+
async function readTodoList(sessionId) {
|
|
73130
|
+
const filePath = getSessionTodosFilePath(sessionId);
|
|
73131
|
+
if (!existsSync13(filePath)) {
|
|
73132
|
+
return null;
|
|
73133
|
+
}
|
|
73134
|
+
const raw = readFileSync12(filePath, "utf-8");
|
|
73135
|
+
let parsed;
|
|
73136
|
+
try {
|
|
73137
|
+
parsed = JSON.parse(raw);
|
|
73138
|
+
} catch (err) {
|
|
73139
|
+
throw new Error(`Corrupted todo state for session "${sessionId}": failed to parse JSON (${err instanceof Error ? err.message : String(err)})`);
|
|
73140
|
+
}
|
|
73141
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
73142
|
+
throw new Error(`Corrupted todo state for session "${sessionId}": root must be an object`);
|
|
73143
|
+
}
|
|
73144
|
+
const record = parsed;
|
|
73145
|
+
if (record.schemaVersion !== 1) {
|
|
73146
|
+
throw new Error(`Unsupported todo schemaVersion "${record.schemaVersion}" (expected 1) for session "${sessionId}"`);
|
|
73147
|
+
}
|
|
73148
|
+
if (typeof record.sessionId !== "string") {
|
|
73149
|
+
throw new Error(`Invalid todo state for session "${sessionId}": missing or invalid sessionId`);
|
|
73150
|
+
}
|
|
73151
|
+
const validatedTodos = validateTodoList(record.todos);
|
|
73152
|
+
return {
|
|
73153
|
+
schemaVersion: 1,
|
|
73154
|
+
sessionId: record.sessionId,
|
|
73155
|
+
updatedAt: typeof record.updatedAt === "string" ? record.updatedAt : new Date().toISOString(),
|
|
73156
|
+
todos: validatedTodos
|
|
73157
|
+
};
|
|
73158
|
+
}
|
|
73159
|
+
async function writeTodoList(sessionId, todos) {
|
|
73160
|
+
return sessionLocks.runExclusive(sessionId, async () => {
|
|
73161
|
+
const validated = validateTodoList(todos);
|
|
73162
|
+
const safeSessionId = sanitizeSessionId(sessionId);
|
|
73163
|
+
const dir = getSessionTodosDir(safeSessionId);
|
|
73164
|
+
if (!existsSync13(dir)) {
|
|
73165
|
+
mkdirSync9(dir, { recursive: true, mode: 448 });
|
|
73166
|
+
}
|
|
73167
|
+
const payload = {
|
|
73168
|
+
schemaVersion: 1,
|
|
73169
|
+
sessionId: safeSessionId,
|
|
73170
|
+
updatedAt: new Date().toISOString(),
|
|
73171
|
+
todos: validated
|
|
73172
|
+
};
|
|
73173
|
+
const targetPath = getSessionTodosFilePath(safeSessionId);
|
|
73174
|
+
const tempPath = join12(dir, `.todos.${randomUUID6()}.tmp`);
|
|
73175
|
+
const jsonStr = JSON.stringify(payload, null, 2) + `
|
|
73176
|
+
`;
|
|
73177
|
+
try {
|
|
73178
|
+
writeFileSync3(tempPath, jsonStr, { encoding: "utf-8", mode: 384 });
|
|
73179
|
+
const fd = openSync7(tempPath, "r");
|
|
73180
|
+
try {
|
|
73181
|
+
fsyncSync6(fd);
|
|
73182
|
+
} finally {
|
|
73183
|
+
closeSync7(fd);
|
|
73184
|
+
}
|
|
73185
|
+
renameSync6(tempPath, targetPath);
|
|
73186
|
+
} catch (err) {
|
|
73187
|
+
try {
|
|
73188
|
+
if (existsSync13(tempPath)) {
|
|
73189
|
+
unlinkSync6(tempPath);
|
|
73190
|
+
}
|
|
73191
|
+
} catch {}
|
|
73192
|
+
throw err;
|
|
73193
|
+
}
|
|
73194
|
+
return payload;
|
|
73195
|
+
});
|
|
73196
|
+
}
|
|
73197
|
+
async function updateTodoItem(sessionId, todoId, updates) {
|
|
73198
|
+
return sessionLocks.runExclusive(sessionId, async () => {
|
|
73199
|
+
if (!todoId || typeof todoId !== "string" || !todoId.trim()) {
|
|
73200
|
+
throw new Error("todoId must be a non-empty string");
|
|
73201
|
+
}
|
|
73202
|
+
const targetId = todoId.trim();
|
|
73203
|
+
const current = await readTodoList(sessionId);
|
|
73204
|
+
if (!current) {
|
|
73205
|
+
throw new Error(`No todo list found for session "${sessionId}". Create one first with TodoWrite.`);
|
|
73206
|
+
}
|
|
73207
|
+
const itemIndex = current.todos.findIndex((t) => t.id === targetId);
|
|
73208
|
+
if (itemIndex === -1) {
|
|
73209
|
+
throw new Error(`Todo item with id "${targetId}" not found in current session todo list`);
|
|
73210
|
+
}
|
|
73211
|
+
if (updates.status === undefined && updates.description === undefined) {
|
|
73212
|
+
throw new Error("At least one of status or description must be provided for update");
|
|
73213
|
+
}
|
|
73214
|
+
const currentItem = current.todos[itemIndex];
|
|
73215
|
+
const newStatus = updates.status !== undefined ? updates.status : currentItem.status;
|
|
73216
|
+
const newDescription = updates.description !== undefined ? updates.description.trim() : currentItem.description;
|
|
73217
|
+
if (!newDescription) {
|
|
73218
|
+
throw new Error("Updated description cannot be empty");
|
|
73219
|
+
}
|
|
73220
|
+
const updatedTodos = current.todos.map((item, idx) => {
|
|
73221
|
+
if (idx !== itemIndex) {
|
|
73222
|
+
return item;
|
|
73223
|
+
}
|
|
73224
|
+
return {
|
|
73225
|
+
...item,
|
|
73226
|
+
status: newStatus,
|
|
73227
|
+
description: newDescription
|
|
73228
|
+
};
|
|
73229
|
+
});
|
|
73230
|
+
const validated = validateTodoList(updatedTodos);
|
|
73231
|
+
const safeSessionId = sanitizeSessionId(sessionId);
|
|
73232
|
+
const dir = getSessionTodosDir(safeSessionId);
|
|
73233
|
+
if (!existsSync13(dir)) {
|
|
73234
|
+
mkdirSync9(dir, { recursive: true, mode: 448 });
|
|
73235
|
+
}
|
|
73236
|
+
const payload = {
|
|
73237
|
+
schemaVersion: 1,
|
|
73238
|
+
sessionId: safeSessionId,
|
|
73239
|
+
updatedAt: new Date().toISOString(),
|
|
73240
|
+
todos: validated
|
|
73241
|
+
};
|
|
73242
|
+
const targetPath = getSessionTodosFilePath(safeSessionId);
|
|
73243
|
+
const tempPath = join12(dir, `.todos.${randomUUID6()}.tmp`);
|
|
73244
|
+
const jsonStr = JSON.stringify(payload, null, 2) + `
|
|
73245
|
+
`;
|
|
73246
|
+
try {
|
|
73247
|
+
writeFileSync3(tempPath, jsonStr, { encoding: "utf-8", mode: 384 });
|
|
73248
|
+
const fd = openSync7(tempPath, "r");
|
|
73249
|
+
try {
|
|
73250
|
+
fsyncSync6(fd);
|
|
73251
|
+
} finally {
|
|
73252
|
+
closeSync7(fd);
|
|
73253
|
+
}
|
|
73254
|
+
renameSync6(tempPath, targetPath);
|
|
73255
|
+
} catch (err) {
|
|
73256
|
+
try {
|
|
73257
|
+
if (existsSync13(tempPath)) {
|
|
73258
|
+
unlinkSync6(tempPath);
|
|
73259
|
+
}
|
|
73260
|
+
} catch {}
|
|
73261
|
+
throw err;
|
|
73262
|
+
}
|
|
73263
|
+
return payload;
|
|
73264
|
+
});
|
|
73265
|
+
}
|
|
73266
|
+
// src/tools/todo-write/index.ts
|
|
73267
|
+
var todoItemSchema = object({
|
|
73268
|
+
id: string2().min(1, "id is required").describe("Unique identifier for the todo item"),
|
|
73269
|
+
description: string2().min(1, "description is required").describe("Clear description of the task"),
|
|
73270
|
+
status: _enum(["pending", "in_progress", "completed", "cancelled", "blocked"]).describe("Current status of the todo item")
|
|
73271
|
+
});
|
|
73272
|
+
var todoWriteParamsSchema = object({
|
|
73273
|
+
todos: array(todoItemSchema).min(2, "Todo list must contain at least 2 items").max(10, "Todo list must contain at most 10 items").describe("Complete list of 2 to 10 todo items establishing the execution plan")
|
|
73274
|
+
});
|
|
73275
|
+
function formatTodoCheckbox(status) {
|
|
73276
|
+
switch (status) {
|
|
73277
|
+
case "completed":
|
|
73278
|
+
return "[x]";
|
|
73279
|
+
case "in_progress":
|
|
73280
|
+
return `[${figures.sliderPointer}]`;
|
|
73281
|
+
case "cancelled":
|
|
73282
|
+
return `[${figures.cross}]`;
|
|
73283
|
+
case "blocked":
|
|
73284
|
+
return `[${figures.warning}]`;
|
|
73285
|
+
case "pending":
|
|
73286
|
+
default:
|
|
73287
|
+
return "[ ]";
|
|
73288
|
+
}
|
|
73289
|
+
}
|
|
73290
|
+
function formatTodoListSummary(todos, maxItems = 10) {
|
|
73291
|
+
const visible = todos.slice(0, maxItems);
|
|
73292
|
+
const hiddenCount = todos.length - visible.length;
|
|
73293
|
+
const lines = visible.map((t, i) => `${i + 1}. ${formatTodoCheckbox(t.status)} ${t.description}`);
|
|
73294
|
+
if (hiddenCount > 0) {
|
|
73295
|
+
lines.push(`… and ${hiddenCount} more`);
|
|
73296
|
+
}
|
|
73297
|
+
return lines.join(`
|
|
73298
|
+
`);
|
|
73299
|
+
}
|
|
73300
|
+
var todoWriteTool = {
|
|
73301
|
+
name: "todo_write",
|
|
73302
|
+
displayName: "TodoWrite",
|
|
73303
|
+
icon: "\uD83D\uDCDD",
|
|
73304
|
+
description: "Initializes or replaces the current session todo list with 2 to 10 items. Use this to establish or replace the multi-step execution plan for the session.",
|
|
73305
|
+
parameters: todoWriteParamsSchema,
|
|
73306
|
+
summarizeArgs(args) {
|
|
73307
|
+
if (!args.todos || args.todos.length === 0)
|
|
73308
|
+
return "";
|
|
73309
|
+
if (args.todos.length <= 2) {
|
|
73310
|
+
return args.todos.map((t) => `"${t.description}"`).join(", ");
|
|
73311
|
+
}
|
|
73312
|
+
return `"${args.todos[0]?.description}", "${args.todos[1]?.description}", +${args.todos.length - 2} more`;
|
|
73313
|
+
},
|
|
73314
|
+
async execute(args, context) {
|
|
73315
|
+
if (!context.sessionId) {
|
|
73316
|
+
throw new Error("TodoWrite requires an active session ID in ToolContext");
|
|
73317
|
+
}
|
|
73318
|
+
return writeTodoList(context.sessionId, args.todos);
|
|
73319
|
+
},
|
|
73320
|
+
summarize(args, result) {
|
|
73321
|
+
const list = result?.todos ?? args.todos;
|
|
73322
|
+
const total = list.length;
|
|
73323
|
+
const header = `Added ${total} todos`;
|
|
73324
|
+
const details = formatTodoListSummary(list);
|
|
73325
|
+
return `${header}
|
|
73326
|
+
${details}`;
|
|
73327
|
+
}
|
|
73328
|
+
};
|
|
73329
|
+
|
|
73330
|
+
// src/tools/todo-update/index.ts
|
|
73331
|
+
var todoUpdateParamsSchema = object({
|
|
73332
|
+
id: string2().min(1, "id is required").describe("ID of the existing todo item to update"),
|
|
73333
|
+
status: _enum(["pending", "in_progress", "completed", "cancelled", "blocked"]).optional().describe("New status for the todo item"),
|
|
73334
|
+
description: string2().min(1).optional().describe("Updated description for the todo item")
|
|
73335
|
+
});
|
|
73336
|
+
function formatTodoCheckbox2(status) {
|
|
73337
|
+
switch (status) {
|
|
73338
|
+
case "completed":
|
|
73339
|
+
return "[x]";
|
|
73340
|
+
case "in_progress":
|
|
73341
|
+
return `[${figures.sliderPointer}]`;
|
|
73342
|
+
case "cancelled":
|
|
73343
|
+
return `[${figures.cross}]`;
|
|
73344
|
+
case "blocked":
|
|
73345
|
+
return `[${figures.warning}]`;
|
|
73346
|
+
case "pending":
|
|
73347
|
+
default:
|
|
73348
|
+
return "[ ]";
|
|
73349
|
+
}
|
|
73350
|
+
}
|
|
73351
|
+
function formatTodoListSummary2(todos, maxItems = 10) {
|
|
73352
|
+
const visible = todos.slice(0, maxItems);
|
|
73353
|
+
const hiddenCount = todos.length - visible.length;
|
|
73354
|
+
const lines = visible.map((t, i) => `${i + 1}. ${formatTodoCheckbox2(t.status)} ${t.description}`);
|
|
73355
|
+
if (hiddenCount > 0) {
|
|
73356
|
+
lines.push(`… and ${hiddenCount} more`);
|
|
73357
|
+
}
|
|
73358
|
+
return lines.join(`
|
|
73359
|
+
`);
|
|
73360
|
+
}
|
|
73361
|
+
var todoUpdateTool = {
|
|
73362
|
+
name: "todo_update",
|
|
73363
|
+
displayName: "TodoUpdate",
|
|
73364
|
+
icon: "✓",
|
|
73365
|
+
description: "Updates a single todo item status or description in the current session. Cannot create new items or update multiple items at once.",
|
|
73366
|
+
parameters: todoUpdateParamsSchema,
|
|
73367
|
+
summarizeArgs(args) {
|
|
73368
|
+
const parts = [`id: "${args.id}"`];
|
|
73369
|
+
if (args.status)
|
|
73370
|
+
parts.push(`status: "${args.status}"`);
|
|
73371
|
+
if (args.description)
|
|
73372
|
+
parts.push(`desc: "${args.description}"`);
|
|
73373
|
+
return parts.join(", ");
|
|
73374
|
+
},
|
|
73375
|
+
async execute(args, context) {
|
|
73376
|
+
if (!context.sessionId) {
|
|
73377
|
+
throw new Error("TodoUpdate requires an active session ID in ToolContext");
|
|
73378
|
+
}
|
|
73379
|
+
return updateTodoItem(context.sessionId, args.id, {
|
|
73380
|
+
status: args.status,
|
|
73381
|
+
description: args.description
|
|
73382
|
+
});
|
|
73383
|
+
},
|
|
73384
|
+
summarize(args, result) {
|
|
73385
|
+
if (!result || !result.todos) {
|
|
73386
|
+
return `Todo ${args.id} updated`;
|
|
73387
|
+
}
|
|
73388
|
+
const total = result.todos.length;
|
|
73389
|
+
const completed = result.todos.filter((t) => t.status === "completed").length;
|
|
73390
|
+
const updatedItem = result.todos.find((t) => t.id === args.id);
|
|
73391
|
+
const statusText = updatedItem?.status ?? args.status ?? "updated";
|
|
73392
|
+
const header = `Todo ${args.id} ${statusText} · ${completed}/${total} done`;
|
|
73393
|
+
const details = formatTodoListSummary2(result.todos);
|
|
73394
|
+
return `${header}
|
|
73395
|
+
${details}`;
|
|
73396
|
+
}
|
|
73397
|
+
};
|
|
73398
|
+
|
|
73399
|
+
// src/tools/todo-read/index.ts
|
|
73400
|
+
var todoReadParamsSchema = object({});
|
|
73401
|
+
function formatTodoCheckbox3(status) {
|
|
73402
|
+
switch (status) {
|
|
73403
|
+
case "completed":
|
|
73404
|
+
return "[x]";
|
|
73405
|
+
case "in_progress":
|
|
73406
|
+
return `[${figures.sliderPointer}]`;
|
|
73407
|
+
case "cancelled":
|
|
73408
|
+
return `[${figures.cross}]`;
|
|
73409
|
+
case "blocked":
|
|
73410
|
+
return `[${figures.warning}]`;
|
|
73411
|
+
case "pending":
|
|
73412
|
+
default:
|
|
73413
|
+
return "[ ]";
|
|
73414
|
+
}
|
|
73415
|
+
}
|
|
73416
|
+
function formatTodoListSummary3(todos, maxItems = 10) {
|
|
73417
|
+
const visible = todos.slice(0, maxItems);
|
|
73418
|
+
const hiddenCount = todos.length - visible.length;
|
|
73419
|
+
const lines = visible.map((t, i) => `${i + 1}. ${formatTodoCheckbox3(t.status)} ${t.description}`);
|
|
73420
|
+
if (hiddenCount > 0) {
|
|
73421
|
+
lines.push(`… and ${hiddenCount} more`);
|
|
73422
|
+
}
|
|
73423
|
+
return lines.join(`
|
|
73424
|
+
`);
|
|
73425
|
+
}
|
|
73426
|
+
var todoReadTool = {
|
|
73427
|
+
name: "todo_read",
|
|
73428
|
+
displayName: "TodoRead",
|
|
73429
|
+
icon: "\uD83D\uDCCB",
|
|
73430
|
+
description: "Reads the current session todo list without modifying it. Returns an empty result if no todos have been established yet.",
|
|
73431
|
+
parameters: todoReadParamsSchema,
|
|
73432
|
+
summarizeArgs() {
|
|
73433
|
+
return "";
|
|
73434
|
+
},
|
|
73435
|
+
async execute(_args, context) {
|
|
73436
|
+
if (!context.sessionId) {
|
|
73437
|
+
throw new Error("TodoRead requires an active session ID in ToolContext");
|
|
73438
|
+
}
|
|
73439
|
+
const list = await readTodoList(context.sessionId);
|
|
73440
|
+
if (!list) {
|
|
73441
|
+
return {
|
|
73442
|
+
hasTodos: false,
|
|
73443
|
+
todos: [],
|
|
73444
|
+
message: "No todos currently established for this session."
|
|
73445
|
+
};
|
|
73446
|
+
}
|
|
73447
|
+
return {
|
|
73448
|
+
hasTodos: true,
|
|
73449
|
+
todos: list.todos,
|
|
73450
|
+
updatedAt: list.updatedAt
|
|
73451
|
+
};
|
|
73452
|
+
},
|
|
73453
|
+
summarize(_args, result) {
|
|
73454
|
+
if (!result || !result.hasTodos || result.todos.length === 0) {
|
|
73455
|
+
return "No active todos";
|
|
73456
|
+
}
|
|
73457
|
+
const total = result.todos.length;
|
|
73458
|
+
const completed = result.todos.filter((t) => t.status === "completed").length;
|
|
73459
|
+
const active = result.todos.find((t) => t.status === "in_progress");
|
|
73460
|
+
const header = active ? `Todos ${completed}/${total} done · ▶ ${active.description}` : `Todos ${completed}/${total} done`;
|
|
73461
|
+
const details = formatTodoListSummary3(result.todos);
|
|
73462
|
+
return `${header}
|
|
73463
|
+
${details}`;
|
|
73464
|
+
}
|
|
73465
|
+
};
|
|
73466
|
+
// src/skills/discovery.ts
|
|
73467
|
+
import { existsSync as existsSync14, readdirSync as readdirSync5, readFileSync as readFileSync13, statSync as statSync6 } from "node:fs";
|
|
73468
|
+
import { homedir as homedir5 } from "node:os";
|
|
73469
|
+
import { isAbsolute as isAbsolute6, join as join13, normalize, resolve as resolve9, sep as sep4 } from "node:path";
|
|
73470
|
+
var MAX_SKILL_FILE_SIZE = 64 * 1024;
|
|
73471
|
+
function parseSkillFrontmatter(content) {
|
|
73472
|
+
const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
73473
|
+
if (!match || !match[1]) {
|
|
73474
|
+
return null;
|
|
73475
|
+
}
|
|
73476
|
+
const rawYaml = match[1];
|
|
73477
|
+
const lines = rawYaml.split(/\r?\n/);
|
|
73478
|
+
const result = {};
|
|
73479
|
+
let currentKey = null;
|
|
73480
|
+
let descriptionLines = [];
|
|
73481
|
+
for (const line of lines) {
|
|
73482
|
+
const trimmed = line.trim();
|
|
73483
|
+
if (!trimmed || trimmed.startsWith("#"))
|
|
73484
|
+
continue;
|
|
73485
|
+
const keyMatch = line.match(/^([a-zA-Z0-9_-]+):\s*(.*)$/);
|
|
73486
|
+
if (keyMatch && keyMatch[1]) {
|
|
73487
|
+
const key = keyMatch[1].toLowerCase();
|
|
73488
|
+
const val = (keyMatch[2] ?? "").trim();
|
|
73489
|
+
if (key === "name") {
|
|
73490
|
+
currentKey = "name";
|
|
73491
|
+
result.name = unquote(val);
|
|
73492
|
+
} else if (key === "description") {
|
|
73493
|
+
currentKey = "description";
|
|
73494
|
+
if (val === ">" || val === ">-" || val === "|" || val === "|-") {
|
|
73495
|
+
descriptionLines = [];
|
|
73496
|
+
} else if (val) {
|
|
73497
|
+
descriptionLines = [unquote(val)];
|
|
73498
|
+
} else {
|
|
73499
|
+
descriptionLines = [];
|
|
73500
|
+
}
|
|
73501
|
+
} else {
|
|
73502
|
+
currentKey = null;
|
|
73503
|
+
}
|
|
73504
|
+
} else if (currentKey === "description" && (line.startsWith(" ") || line.startsWith("\t"))) {
|
|
73505
|
+
descriptionLines.push(trimmed);
|
|
73506
|
+
}
|
|
73507
|
+
}
|
|
73508
|
+
if (descriptionLines.length > 0) {
|
|
73509
|
+
result.description = descriptionLines.join(" ").trim();
|
|
73510
|
+
}
|
|
73511
|
+
return result;
|
|
73512
|
+
}
|
|
73513
|
+
function unquote(str) {
|
|
73514
|
+
const trimmed = str.trim();
|
|
73515
|
+
if (trimmed.startsWith('"') && trimmed.endsWith('"') || trimmed.startsWith("'") && trimmed.endsWith("'")) {
|
|
73516
|
+
return trimmed.slice(1, -1);
|
|
73517
|
+
}
|
|
73518
|
+
return trimmed;
|
|
73519
|
+
}
|
|
73520
|
+
function discoverSkills(cwd = process.cwd()) {
|
|
73521
|
+
const targets = [
|
|
73522
|
+
{ dir: join13(cwd, ".agents", "skills"), source: "workspace" },
|
|
73523
|
+
{ dir: join13(homedir5(), ".agents", "skills"), source: "user-agents" },
|
|
73524
|
+
{ dir: join13(homedir5(), ".steward", "skills"), source: "user-steward" }
|
|
73525
|
+
];
|
|
73526
|
+
const skillMap = new Map;
|
|
73527
|
+
for (const target of targets) {
|
|
73528
|
+
if (!existsSync14(target.dir)) {
|
|
73529
|
+
continue;
|
|
73530
|
+
}
|
|
73531
|
+
try {
|
|
73532
|
+
const entries = readdirSync5(target.dir, { withFileTypes: true });
|
|
73533
|
+
for (const entry of entries) {
|
|
73534
|
+
if (!entry.isDirectory())
|
|
73535
|
+
continue;
|
|
73536
|
+
const skillDir = join13(target.dir, entry.name);
|
|
73537
|
+
const skillMdPath = join13(skillDir, "SKILL.md");
|
|
73538
|
+
const fallbackMdPath = join13(skillDir, "skill.md");
|
|
73539
|
+
const resolvedFile = existsSync14(skillMdPath) ? skillMdPath : existsSync14(fallbackMdPath) ? fallbackMdPath : null;
|
|
73540
|
+
if (!resolvedFile)
|
|
73541
|
+
continue;
|
|
73542
|
+
try {
|
|
73543
|
+
const raw = readFileSync13(resolvedFile, "utf-8");
|
|
73544
|
+
const frontmatter = parseSkillFrontmatter(raw);
|
|
73545
|
+
const skillName = frontmatter?.name || entry.name;
|
|
73546
|
+
const description = frontmatter?.description || "No description provided.";
|
|
73547
|
+
if (!skillMap.has(skillName)) {
|
|
73548
|
+
skillMap.set(skillName, {
|
|
73549
|
+
name: skillName,
|
|
73550
|
+
description,
|
|
73551
|
+
filePath: resolvedFile,
|
|
73552
|
+
dirPath: skillDir,
|
|
73553
|
+
source: target.source
|
|
73554
|
+
});
|
|
73555
|
+
}
|
|
73556
|
+
} catch {}
|
|
73557
|
+
}
|
|
73558
|
+
} catch {}
|
|
73559
|
+
}
|
|
73560
|
+
return Array.from(skillMap.values());
|
|
73561
|
+
}
|
|
73562
|
+
function getSkill(name, cwd = process.cwd()) {
|
|
73563
|
+
const cleanName = name.trim();
|
|
73564
|
+
const all = discoverSkills(cwd);
|
|
73565
|
+
return all.find((s) => s.name.toLowerCase() === cleanName.toLowerCase()) ?? null;
|
|
73566
|
+
}
|
|
73567
|
+
function readSkillResource(name, relativePath, cwd = process.cwd(), maxBytes = MAX_SKILL_FILE_SIZE) {
|
|
73568
|
+
const skill = getSkill(name, cwd);
|
|
73569
|
+
if (!skill) {
|
|
73570
|
+
throw new Error(`Skill "${name}" not found`);
|
|
73571
|
+
}
|
|
73572
|
+
let targetFilePath;
|
|
73573
|
+
if (!relativePath || !relativePath.trim()) {
|
|
73574
|
+
targetFilePath = skill.filePath;
|
|
73575
|
+
} else {
|
|
73576
|
+
const trimmedPath = relativePath.trim();
|
|
73577
|
+
if (isAbsolute6(trimmedPath)) {
|
|
73578
|
+
throw new Error(`Invalid skill resource path: absolute paths are not permitted (${trimmedPath})`);
|
|
73579
|
+
}
|
|
73580
|
+
const normalizedRelative = normalize(trimmedPath);
|
|
73581
|
+
if (normalizedRelative.startsWith("..") || normalizedRelative.includes(`${sep4}..`)) {
|
|
73582
|
+
throw new Error(`Invalid skill resource path: directory traversal ("..") is forbidden (${trimmedPath})`);
|
|
73583
|
+
}
|
|
73584
|
+
targetFilePath = resolve9(skill.dirPath, normalizedRelative);
|
|
73585
|
+
const resolvedDir = resolve9(skill.dirPath);
|
|
73586
|
+
if (!targetFilePath.startsWith(resolvedDir + sep4) && targetFilePath !== resolvedDir) {
|
|
73587
|
+
throw new Error(`Resource "${trimmedPath}" resolves outside skill directory`);
|
|
73588
|
+
}
|
|
73589
|
+
}
|
|
73590
|
+
if (!existsSync14(targetFilePath)) {
|
|
73591
|
+
throw new Error(`Skill resource file "${targetFilePath}" does not exist`);
|
|
73592
|
+
}
|
|
73593
|
+
const stat = statSync6(targetFilePath);
|
|
73594
|
+
if (!stat.isFile()) {
|
|
73595
|
+
throw new Error(`Skill resource path "${targetFilePath}" is not a regular file`);
|
|
73596
|
+
}
|
|
73597
|
+
if (stat.size > maxBytes) {
|
|
73598
|
+
throw new Error(`Skill resource file exceeds size limit of ${maxBytes} bytes (file is ${stat.size} bytes)`);
|
|
73599
|
+
}
|
|
73600
|
+
const content = readFileSync13(targetFilePath, "utf-8");
|
|
73601
|
+
return {
|
|
73602
|
+
name: skill.name,
|
|
73603
|
+
description: skill.description,
|
|
73604
|
+
source: skill.source,
|
|
73605
|
+
resourcePath: targetFilePath,
|
|
73606
|
+
content
|
|
73607
|
+
};
|
|
73608
|
+
}
|
|
73609
|
+
// src/tools/skill-list/index.ts
|
|
73610
|
+
var skillListParamsSchema = object({});
|
|
73611
|
+
var skillListTool = {
|
|
73612
|
+
name: "skill_list",
|
|
73613
|
+
displayName: "SkillList",
|
|
73614
|
+
icon: "\uD83D\uDCA1",
|
|
73615
|
+
description: "Discovers and lists all available specialized skills and their descriptions. Use this when you need specialized domain knowledge or workflows.",
|
|
73616
|
+
parameters: skillListParamsSchema,
|
|
73617
|
+
summarizeArgs() {
|
|
73618
|
+
return "";
|
|
73619
|
+
},
|
|
73620
|
+
async execute(_args, context) {
|
|
73621
|
+
const cwd = context?.cwd ?? process.cwd();
|
|
73622
|
+
const skills = discoverSkills(cwd);
|
|
73623
|
+
return skills.map((s) => ({
|
|
73624
|
+
name: s.name,
|
|
73625
|
+
description: s.description,
|
|
73626
|
+
source: s.source
|
|
73627
|
+
})).sort((a, b) => a.name.localeCompare(b.name));
|
|
73628
|
+
},
|
|
73629
|
+
summarize(_args, result) {
|
|
73630
|
+
const count = result?.length ?? 0;
|
|
73631
|
+
if (count === 0) {
|
|
73632
|
+
return "Discovered 0 skills";
|
|
73633
|
+
}
|
|
73634
|
+
return `Discovered ${count} skill${count === 1 ? "" : "s"}`;
|
|
73635
|
+
}
|
|
73636
|
+
};
|
|
73637
|
+
|
|
73638
|
+
// src/tools/skill-read/index.ts
|
|
73639
|
+
var skillReadParamsSchema = object({
|
|
73640
|
+
name: string2().min(1, "Skill name is required"),
|
|
73641
|
+
path: string2().optional()
|
|
73642
|
+
});
|
|
73643
|
+
var skillReadTool = {
|
|
73644
|
+
name: "skill_read",
|
|
73645
|
+
displayName: "SkillRead",
|
|
73646
|
+
icon: "\uD83D\uDCD6",
|
|
73647
|
+
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.",
|
|
73648
|
+
parameters: skillReadParamsSchema,
|
|
73649
|
+
summarizeArgs(args) {
|
|
73650
|
+
if (args.path) {
|
|
73651
|
+
return `name="${args.name}" path="${args.path}"`;
|
|
73652
|
+
}
|
|
73653
|
+
return `name="${args.name}"`;
|
|
73654
|
+
},
|
|
73655
|
+
async execute(args, context) {
|
|
73656
|
+
const cwd = context?.cwd ?? process.cwd();
|
|
73657
|
+
return readSkillResource(args.name, args.path, cwd);
|
|
73658
|
+
},
|
|
73659
|
+
summarize(args, result) {
|
|
73660
|
+
const target = args.path ? `${args.name}/${args.path}` : args.name;
|
|
73661
|
+
const len = result?.content ? ` (${result.content.length} bytes)` : "";
|
|
73662
|
+
return `SkillRead: ${target}${len}`;
|
|
73663
|
+
}
|
|
73664
|
+
};
|
|
72899
73665
|
|
|
72900
73666
|
// src/tools/index.ts
|
|
72901
73667
|
var builtInTools = [
|
|
@@ -72907,11 +73673,17 @@ var builtInTools = [
|
|
|
72907
73673
|
listDirTool,
|
|
72908
73674
|
sleepTool,
|
|
72909
73675
|
bashTool,
|
|
73676
|
+
taskListTool,
|
|
72910
73677
|
taskReadTool,
|
|
72911
73678
|
taskSendInputTool,
|
|
72912
73679
|
taskKillTool,
|
|
72913
73680
|
webFetchTool,
|
|
72914
|
-
webSearchTool
|
|
73681
|
+
webSearchTool,
|
|
73682
|
+
todoWriteTool,
|
|
73683
|
+
todoUpdateTool,
|
|
73684
|
+
todoReadTool,
|
|
73685
|
+
skillListTool,
|
|
73686
|
+
skillReadTool
|
|
72915
73687
|
];
|
|
72916
73688
|
for (const tool of builtInTools) {
|
|
72917
73689
|
defaultToolCatalog.register(tool);
|
|
@@ -72953,6 +73725,7 @@ var MAX_RETAINED_TERMINAL_TASKS = 50;
|
|
|
72953
73725
|
class ShellTaskManager {
|
|
72954
73726
|
tasks = new Map;
|
|
72955
73727
|
terminalTimestamps = new Map;
|
|
73728
|
+
evictedTaskReasons = new Map;
|
|
72956
73729
|
isShuttingDown = false;
|
|
72957
73730
|
generateTaskId() {
|
|
72958
73731
|
return `task-${randomBytes(4).toString("hex")}`;
|
|
@@ -72986,7 +73759,7 @@ class ShellTaskManager {
|
|
|
72986
73759
|
this.evictExpiredTasks();
|
|
72987
73760
|
const exec = this.tasks.get(taskId);
|
|
72988
73761
|
if (!exec) {
|
|
72989
|
-
throw
|
|
73762
|
+
throw this.getTaskNotFoundError(taskId);
|
|
72990
73763
|
}
|
|
72991
73764
|
const rawOutput = exec.getRecentOutput();
|
|
72992
73765
|
const { preview, truncated } = boundResultText(rawOutput);
|
|
@@ -73007,7 +73780,7 @@ class ShellTaskManager {
|
|
|
73007
73780
|
this.evictExpiredTasks();
|
|
73008
73781
|
const exec = this.tasks.get(taskId);
|
|
73009
73782
|
if (!exec) {
|
|
73010
|
-
throw
|
|
73783
|
+
throw this.getTaskNotFoundError(taskId);
|
|
73011
73784
|
}
|
|
73012
73785
|
if (!exec.isRunning) {
|
|
73013
73786
|
throw new Error(`Task "${taskId}" is not running (status: ${exec.status}).`);
|
|
@@ -73028,7 +73801,7 @@ class ShellTaskManager {
|
|
|
73028
73801
|
this.evictExpiredTasks();
|
|
73029
73802
|
const exec = this.tasks.get(taskId);
|
|
73030
73803
|
if (!exec) {
|
|
73031
|
-
throw
|
|
73804
|
+
throw this.getTaskNotFoundError(taskId);
|
|
73032
73805
|
}
|
|
73033
73806
|
if (exec.isRunning) {
|
|
73034
73807
|
await exec.kill();
|
|
@@ -73102,6 +73875,7 @@ class ShellTaskManager {
|
|
|
73102
73875
|
}
|
|
73103
73876
|
this.tasks.delete(id);
|
|
73104
73877
|
this.terminalTimestamps.delete(id);
|
|
73878
|
+
this.recordEviction(id, "expired due to 10-minute retention TTL");
|
|
73105
73879
|
}
|
|
73106
73880
|
}
|
|
73107
73881
|
const remainingTerminal = Array.from(this.terminalTimestamps.entries()).sort((a, b) => a[1] - b[1]);
|
|
@@ -73115,86 +73889,25 @@ class ShellTaskManager {
|
|
|
73115
73889
|
}
|
|
73116
73890
|
this.tasks.delete(oldestId);
|
|
73117
73891
|
this.terminalTimestamps.delete(oldestId);
|
|
73892
|
+
this.recordEviction(oldestId, "evicted due to task capacity limit (50 tasks max)");
|
|
73118
73893
|
}
|
|
73119
73894
|
}
|
|
73120
|
-
|
|
73121
|
-
|
|
73122
|
-
|
|
73123
|
-
|
|
73124
|
-
|
|
73125
|
-
|
|
73126
|
-
"openai",
|
|
73127
|
-
"xai",
|
|
73128
|
-
"mistral",
|
|
73129
|
-
"deepseek",
|
|
73130
|
-
"openrouter",
|
|
73131
|
-
"custom"
|
|
73132
|
-
];
|
|
73133
|
-
function getEnvConfig() {
|
|
73134
|
-
const geminiApiKey = process.env["GEMINI_API_KEY"]?.trim() || process.env["GOOGLE_GENERATIVE_AI_API_KEY"]?.trim() || undefined;
|
|
73135
|
-
const anthropicApiKey = process.env["ANTHROPIC_API_KEY"]?.trim() || undefined;
|
|
73136
|
-
const openaiApiKey = process.env["OPENAI_API_KEY"]?.trim() || undefined;
|
|
73137
|
-
const xaiApiKey = process.env["XAI_API_KEY"]?.trim() || undefined;
|
|
73138
|
-
const mistralApiKey = process.env["MISTRAL_API_KEY"]?.trim() || undefined;
|
|
73139
|
-
const deepseekApiKey = process.env["DEEPSEEK_API_KEY"]?.trim() || undefined;
|
|
73140
|
-
const openrouterApiKey = process.env["OPENROUTER_API_KEY"]?.trim() || undefined;
|
|
73141
|
-
const customApiKey = process.env["CUSTOM_API_KEY"]?.trim() || undefined;
|
|
73142
|
-
const customApiModelName = process.env["CUSTOM_API_MODEL_NAME"]?.trim() || undefined;
|
|
73143
|
-
const customApiUrl = process.env["CUSTOM_API_URL"]?.trim() || undefined;
|
|
73144
|
-
return {
|
|
73145
|
-
geminiApiKey,
|
|
73146
|
-
anthropicApiKey,
|
|
73147
|
-
openaiApiKey,
|
|
73148
|
-
xaiApiKey,
|
|
73149
|
-
mistralApiKey,
|
|
73150
|
-
deepseekApiKey,
|
|
73151
|
-
openrouterApiKey,
|
|
73152
|
-
custom: {
|
|
73153
|
-
apiKey: customApiKey,
|
|
73154
|
-
modelName: customApiModelName,
|
|
73155
|
-
baseURL: customApiUrl
|
|
73895
|
+
recordEviction(taskId, reason) {
|
|
73896
|
+
if (this.evictedTaskReasons.size >= 100) {
|
|
73897
|
+
const oldest = this.evictedTaskReasons.keys().next().value;
|
|
73898
|
+
if (oldest) {
|
|
73899
|
+
this.evictedTaskReasons.delete(oldest);
|
|
73900
|
+
}
|
|
73156
73901
|
}
|
|
73157
|
-
|
|
73158
|
-
}
|
|
73159
|
-
|
|
73160
|
-
|
|
73161
|
-
|
|
73162
|
-
return
|
|
73163
|
-
|
|
73164
|
-
|
|
73165
|
-
case "openai":
|
|
73166
|
-
return Boolean(config.openaiApiKey);
|
|
73167
|
-
case "xai":
|
|
73168
|
-
return Boolean(config.xaiApiKey);
|
|
73169
|
-
case "mistral":
|
|
73170
|
-
return Boolean(config.mistralApiKey);
|
|
73171
|
-
case "deepseek":
|
|
73172
|
-
return Boolean(config.deepseekApiKey);
|
|
73173
|
-
case "openrouter":
|
|
73174
|
-
return Boolean(config.openrouterApiKey);
|
|
73175
|
-
case "custom":
|
|
73176
|
-
return Boolean(config.custom.baseURL && config.custom.modelName);
|
|
73902
|
+
this.evictedTaskReasons.set(taskId, reason);
|
|
73903
|
+
}
|
|
73904
|
+
getTaskNotFoundError(taskId) {
|
|
73905
|
+
const reason = this.evictedTaskReasons.get(taskId);
|
|
73906
|
+
if (reason) {
|
|
73907
|
+
return new Error(`Task "${taskId}" was ${reason}.`);
|
|
73908
|
+
}
|
|
73909
|
+
return new Error(`Task "${taskId}" not found (task ID does not exist).`);
|
|
73177
73910
|
}
|
|
73178
|
-
}
|
|
73179
|
-
function getAvailableProviders(config = getEnvConfig()) {
|
|
73180
|
-
const providers = [];
|
|
73181
|
-
if (hasProviderConfig("gemini", config))
|
|
73182
|
-
providers.push("gemini");
|
|
73183
|
-
if (hasProviderConfig("anthropic", config))
|
|
73184
|
-
providers.push("anthropic");
|
|
73185
|
-
if (hasProviderConfig("openai", config))
|
|
73186
|
-
providers.push("openai");
|
|
73187
|
-
if (hasProviderConfig("xai", config))
|
|
73188
|
-
providers.push("xai");
|
|
73189
|
-
if (hasProviderConfig("mistral", config))
|
|
73190
|
-
providers.push("mistral");
|
|
73191
|
-
if (hasProviderConfig("deepseek", config))
|
|
73192
|
-
providers.push("deepseek");
|
|
73193
|
-
if (hasProviderConfig("openrouter", config))
|
|
73194
|
-
providers.push("openrouter");
|
|
73195
|
-
if (hasProviderConfig("custom", config))
|
|
73196
|
-
providers.push("custom");
|
|
73197
|
-
return providers;
|
|
73198
73911
|
}
|
|
73199
73912
|
// src/errors/classifier.ts
|
|
73200
73913
|
function classifyError(error) {
|
|
@@ -73340,15 +74053,15 @@ function classifyError(error) {
|
|
|
73340
74053
|
};
|
|
73341
74054
|
}
|
|
73342
74055
|
// src/errors/logger.ts
|
|
73343
|
-
import { appendFileSync as appendFileSync3, existsSync as
|
|
73344
|
-
import { homedir as
|
|
73345
|
-
import { join as
|
|
74056
|
+
import { appendFileSync as appendFileSync3, existsSync as existsSync15, mkdirSync as mkdirSync10, writeFileSync as writeFileSync4 } from "node:fs";
|
|
74057
|
+
import { homedir as homedir6 } from "node:os";
|
|
74058
|
+
import { join as join14 } from "node:path";
|
|
73346
74059
|
var SENSITIVE_KEY_REGEX = /api[_-]?key|token|secret|password|auth|credential|bearer/i;
|
|
73347
74060
|
function getLogsRootDir() {
|
|
73348
74061
|
if (process.env.STEWARD_LOGS_DIR) {
|
|
73349
74062
|
return process.env.STEWARD_LOGS_DIR;
|
|
73350
74063
|
}
|
|
73351
|
-
return
|
|
74064
|
+
return join14(homedir6(), ".steward", "logs");
|
|
73352
74065
|
}
|
|
73353
74066
|
function getLogDateTime(d = new Date) {
|
|
73354
74067
|
const year = d.getFullYear();
|
|
@@ -73432,11 +74145,11 @@ function logError(error, contextInfo) {
|
|
|
73432
74145
|
try {
|
|
73433
74146
|
const rootDir = getLogsRootDir();
|
|
73434
74147
|
const { date, time } = getLogDateTime();
|
|
73435
|
-
const dateDir =
|
|
73436
|
-
if (!
|
|
73437
|
-
|
|
74148
|
+
const dateDir = join14(rootDir, date);
|
|
74149
|
+
if (!existsSync15(dateDir)) {
|
|
74150
|
+
mkdirSync10(dateDir, { recursive: true });
|
|
73438
74151
|
}
|
|
73439
|
-
const logPath =
|
|
74152
|
+
const logPath = join14(dateDir, `${time}.log`);
|
|
73440
74153
|
const structured = classifyError(error);
|
|
73441
74154
|
const mem = process.memoryUsage ? process.memoryUsage() : undefined;
|
|
73442
74155
|
const logEntry = {
|
|
@@ -73462,10 +74175,10 @@ function logError(error, contextInfo) {
|
|
|
73462
74175
|
` + JSON.stringify(logEntry, null, 2) + `
|
|
73463
74176
|
|
|
73464
74177
|
`;
|
|
73465
|
-
if (
|
|
74178
|
+
if (existsSync15(logPath)) {
|
|
73466
74179
|
appendFileSync3(logPath, formatted, "utf-8");
|
|
73467
74180
|
} else {
|
|
73468
|
-
|
|
74181
|
+
writeFileSync4(logPath, formatted, "utf-8");
|
|
73469
74182
|
}
|
|
73470
74183
|
return logPath;
|
|
73471
74184
|
} catch {
|
|
@@ -111123,7 +111836,7 @@ function createProviderExecutedToolFactory2({
|
|
|
111123
111836
|
supportsDeferredResults
|
|
111124
111837
|
});
|
|
111125
111838
|
}
|
|
111126
|
-
async function
|
|
111839
|
+
async function resolve10(value) {
|
|
111127
111840
|
if (typeof value === "function") {
|
|
111128
111841
|
value = value();
|
|
111129
111842
|
}
|
|
@@ -116349,7 +117062,7 @@ var XaiSpeechModel = class _XaiSpeechModel {
|
|
|
116349
117062
|
const { requestBody, warnings, withTimestamps } = await this.getArgs(options);
|
|
116350
117063
|
const { value, responseHeaders, rawValue } = await postJsonToApi4({
|
|
116351
117064
|
url: `${this.config.baseURL}/tts`,
|
|
116352
|
-
headers: combineHeaders4(this.config.headers ? await
|
|
117065
|
+
headers: combineHeaders4(this.config.headers ? await resolve10(this.config.headers) : undefined, options.headers),
|
|
116353
117066
|
body: requestBody,
|
|
116354
117067
|
failedResponseHandler: xaiFailedResponseHandler,
|
|
116355
117068
|
successfulResponseHandler: withTimestamps ? createJsonResponseHandler4(xaiSpeechTimestampsResponseSchema) : createBinaryResponseHandler2(),
|
|
@@ -117156,288 +117869,59 @@ function inferProviderFromModelId(modelId) {
|
|
|
117156
117869
|
}
|
|
117157
117870
|
return null;
|
|
117158
117871
|
}
|
|
117159
|
-
// src/skills/discovery.ts
|
|
117160
|
-
import { existsSync as existsSync13, readdirSync as readdirSync5, readFileSync as readFileSync12 } from "node:fs";
|
|
117161
|
-
import { homedir as homedir6 } from "node:os";
|
|
117162
|
-
import { join as join14 } from "node:path";
|
|
117163
|
-
function parseSkillFrontmatter(content) {
|
|
117164
|
-
const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
117165
|
-
if (!match || !match[1]) {
|
|
117166
|
-
return null;
|
|
117167
|
-
}
|
|
117168
|
-
const rawYaml = match[1];
|
|
117169
|
-
const lines = rawYaml.split(/\r?\n/);
|
|
117170
|
-
const result = {};
|
|
117171
|
-
let currentKey = null;
|
|
117172
|
-
let descriptionLines = [];
|
|
117173
|
-
for (const line of lines) {
|
|
117174
|
-
const trimmed = line.trim();
|
|
117175
|
-
if (!trimmed || trimmed.startsWith("#"))
|
|
117176
|
-
continue;
|
|
117177
|
-
const keyMatch = line.match(/^([a-zA-Z0-9_-]+):\s*(.*)$/);
|
|
117178
|
-
if (keyMatch && keyMatch[1]) {
|
|
117179
|
-
const key = keyMatch[1].toLowerCase();
|
|
117180
|
-
const val = (keyMatch[2] ?? "").trim();
|
|
117181
|
-
if (key === "name") {
|
|
117182
|
-
currentKey = "name";
|
|
117183
|
-
result.name = unquote(val);
|
|
117184
|
-
} else if (key === "description") {
|
|
117185
|
-
currentKey = "description";
|
|
117186
|
-
if (val === ">" || val === ">-" || val === "|" || val === "|-") {
|
|
117187
|
-
descriptionLines = [];
|
|
117188
|
-
} else if (val) {
|
|
117189
|
-
descriptionLines = [unquote(val)];
|
|
117190
|
-
} else {
|
|
117191
|
-
descriptionLines = [];
|
|
117192
|
-
}
|
|
117193
|
-
} else {
|
|
117194
|
-
currentKey = null;
|
|
117195
|
-
}
|
|
117196
|
-
} else if (currentKey === "description" && (line.startsWith(" ") || line.startsWith("\t"))) {
|
|
117197
|
-
descriptionLines.push(trimmed);
|
|
117198
|
-
}
|
|
117199
|
-
}
|
|
117200
|
-
if (descriptionLines.length > 0) {
|
|
117201
|
-
result.description = descriptionLines.join(" ").trim();
|
|
117202
|
-
}
|
|
117203
|
-
return result;
|
|
117204
|
-
}
|
|
117205
|
-
function unquote(str) {
|
|
117206
|
-
const trimmed = str.trim();
|
|
117207
|
-
if (trimmed.startsWith('"') && trimmed.endsWith('"') || trimmed.startsWith("'") && trimmed.endsWith("'")) {
|
|
117208
|
-
return trimmed.slice(1, -1);
|
|
117209
|
-
}
|
|
117210
|
-
return trimmed;
|
|
117211
|
-
}
|
|
117212
|
-
function discoverSkills(cwd = process.cwd()) {
|
|
117213
|
-
const targets = [
|
|
117214
|
-
{ dir: join14(cwd, ".agents", "skills"), source: "workspace" },
|
|
117215
|
-
{ dir: join14(homedir6(), ".agents", "skills"), source: "user-agents" },
|
|
117216
|
-
{ dir: join14(homedir6(), ".steward", "skills"), source: "user-steward" }
|
|
117217
|
-
];
|
|
117218
|
-
const skillMap = new Map;
|
|
117219
|
-
for (const target of targets) {
|
|
117220
|
-
if (!existsSync13(target.dir)) {
|
|
117221
|
-
continue;
|
|
117222
|
-
}
|
|
117223
|
-
try {
|
|
117224
|
-
const entries = readdirSync5(target.dir, { withFileTypes: true });
|
|
117225
|
-
for (const entry of entries) {
|
|
117226
|
-
if (!entry.isDirectory())
|
|
117227
|
-
continue;
|
|
117228
|
-
const skillDir = join14(target.dir, entry.name);
|
|
117229
|
-
const skillMdPath = join14(skillDir, "SKILL.md");
|
|
117230
|
-
const fallbackMdPath = join14(skillDir, "skill.md");
|
|
117231
|
-
const resolvedFile = existsSync13(skillMdPath) ? skillMdPath : existsSync13(fallbackMdPath) ? fallbackMdPath : null;
|
|
117232
|
-
if (!resolvedFile)
|
|
117233
|
-
continue;
|
|
117234
|
-
try {
|
|
117235
|
-
const raw = readFileSync12(resolvedFile, "utf-8");
|
|
117236
|
-
const frontmatter = parseSkillFrontmatter(raw);
|
|
117237
|
-
const skillName = frontmatter?.name || entry.name;
|
|
117238
|
-
const description = frontmatter?.description || "No description provided.";
|
|
117239
|
-
if (!skillMap.has(skillName)) {
|
|
117240
|
-
skillMap.set(skillName, {
|
|
117241
|
-
name: skillName,
|
|
117242
|
-
description,
|
|
117243
|
-
filePath: resolvedFile,
|
|
117244
|
-
dirPath: skillDir,
|
|
117245
|
-
source: target.source
|
|
117246
|
-
});
|
|
117247
|
-
}
|
|
117248
|
-
} catch {}
|
|
117249
|
-
}
|
|
117250
|
-
} catch {}
|
|
117251
|
-
}
|
|
117252
|
-
return Array.from(skillMap.values());
|
|
117253
|
-
}
|
|
117254
|
-
function formatSkillsForSystemPrompt(skills) {
|
|
117255
|
-
if (skills.length === 0)
|
|
117256
|
-
return "";
|
|
117257
|
-
const list = skills.map((s) => `- ${s.name} (${s.filePath}): ${s.description}`).join(`
|
|
117258
|
-
`);
|
|
117259
|
-
return `<skills>
|
|
117260
|
-
You can use specialized 'skills' to help you with complex tasks.
|
|
117261
|
-
|
|
117262
|
-
Skills are folders of instructions, scripts, and resources that extend your capabilities for specialized tasks. Each skill folder contains a SKILL.md file with YAML frontmatter and detailed markdown instructions.
|
|
117263
|
-
|
|
117264
|
-
Available skills:
|
|
117265
|
-
${list}
|
|
117266
117872
|
|
|
117267
|
-
If a skill seems relevant to your current task, you MUST read its SKILL.md instructions using read_file before proceeding.
|
|
117268
|
-
</skills>`;
|
|
117269
|
-
}
|
|
117270
117873
|
// src/engine/system-prompt.ts
|
|
117271
117874
|
function buildSystemPrompt(options = {}) {
|
|
117272
117875
|
const cwd = options.cwd ?? process.cwd();
|
|
117273
117876
|
const platform = process.platform;
|
|
117274
|
-
const skills = options.skills ?? discoverSkills(cwd);
|
|
117275
117877
|
const dateUTC = new Date().toUTCString();
|
|
117276
|
-
let
|
|
117277
|
-
try {
|
|
117278
|
-
isAGitREPO = Bun.spawnSync(["git", "status"], { cwd, stdio: ["ignore", "ignore", "ignore"] }).exitCode === 0 ? "yes" : "no";
|
|
117279
|
-
} catch {
|
|
117280
|
-
isAGitREPO = "no";
|
|
117281
|
-
}
|
|
117282
|
-
let prompt = `
|
|
117283
|
-
You are an interactive CLI tool that helps users with software engineering tasks. Use the instructions below and the tools available to you to assist the user.
|
|
117284
|
-
|
|
117285
|
-
IMPORTANT: Assist with defensive security tasks only. Refuse to create, modify, or improve code that may be used maliciously. Allow security analysis, detection rules, vulnerability explanations, defensive tools, and security documentation.
|
|
117286
|
-
NOTE: - To give feedback, users should report the issue at https://github.com/sapirrior/steward/issues
|
|
117287
|
-
|
|
117288
|
-
NOTE: When the user directly asks about Steward (eg 'can Steward do...', 'does Steward have...') or asks in second person (eg 'are you able...', 'can you do...', 'will you be abel to do...'), response the USER to head over to the Open-Source repo of Steward (https://github.com/sapirrior/steward/) for information related to steward and its architecture
|
|
117289
|
-
|
|
117290
|
-
# Tone and style
|
|
117291
|
-
You should be concise, direct, and to the point.
|
|
117292
|
-
You MUST answer concisely with fewer than 4 lines (not including the tool use or code generation), unless the USER asks for detail.
|
|
117293
|
-
Do not add additional code explanation summary unless requested by the USER. After working on a file, just stop, rather than providing an explanation of what you did.
|
|
117294
|
-
IMPORTANT: You should minimize the output tokens as much as possible while maintaining appropriate helpfulness, quality, and accuracy. Only address the specific query or task at hand, avoiding tangential information unless absolutely critical for completing the request. If you can answer in 1-4 sentences or a short paragraph, please do.
|
|
117295
|
-
IMPORTANT: You should NOT answer with unnecessary preamble or postamble (such as explaining your code or summarizing your action), unless the user asks you explicitly.
|
|
117296
|
-
IMPORTANT: Keep your responses short, since they will be displayed on a command line interface.
|
|
117297
|
-
Answer the user's question directly, without elaboration, explanation, or details. One word answers are best. Avoid introductions, conclusions, and explanations. You MUST avoid text before/after your response, such as "The answer is <answer>.", "Here is the content of the file..." or "Based on the information provided, the answer is..." or "Here is what I will do next...". Here are some examples to demonstrate appropriate verbosity:
|
|
117298
|
-
|
|
117299
|
-
<example>
|
|
117300
|
-
user: what is 2 + 2
|
|
117301
|
-
assistant: 4
|
|
117302
|
-
</example>
|
|
117303
|
-
|
|
117304
|
-
<example>
|
|
117305
|
-
user: 4 + 4
|
|
117306
|
-
assistant: 8
|
|
117307
|
-
</example>
|
|
117308
|
-
|
|
117309
|
-
<example>
|
|
117310
|
-
user: what command should I run to read the content of a specific file?
|
|
117311
|
-
assistant: cat
|
|
117312
|
-
</example>
|
|
117313
|
-
|
|
117314
|
-
<example>
|
|
117315
|
-
user: what command should I run to watch files in the current directory?
|
|
117316
|
-
[assistant runs the ls command through tool call to list the files in the current directory, then read docs/commands in the relevant file to find out how to watch files]
|
|
117317
|
-
assistant: npm run dev
|
|
117318
|
-
</example>
|
|
117319
|
-
|
|
117320
|
-
<example>
|
|
117321
|
-
user: can you find all the TODO comments in src/
|
|
117322
|
-
[assistant runs search text tool call and sees TODO comment occurance in src/foo.c at line 11 src/boo.c at 180]
|
|
117323
|
-
assistant: src/foo:11 and src/boo.c:180
|
|
117324
|
-
</example>
|
|
117325
|
-
|
|
117326
|
-
When you run a non-trivial bash command, you should explain what the command does and why you are running it, to make sure the USER understands what you are doing (this is especially very important when you are running a command that will make changes to the USER's system including file mutation).
|
|
117327
|
-
Output text to communicate with the USER; all text you output outside of tool use is displayed to the USER. Only use tools to complete tasks. Never use tools like Bash or code comments as means to communicate with the USER during the session.
|
|
117328
|
-
If you cannot or will not help the user with something, please do not say why or what it could lead to, since this comes across as preachy and annoying. Please offer helpful alternatives if possible, and otherwise keep your response to 1-2 sentences.
|
|
117329
|
-
IMPORTANT: Only use emojis if the USER explicitly requests it. Avoid using emojis in all communication unless asked.
|
|
117330
|
-
IMPORTANT: Keep your responses short, since they will be displayed on a command line interface.
|
|
117331
|
-
|
|
117332
|
-
# Proactiveness
|
|
117333
|
-
You are allowed to be proactive, but only when the USER asks you to do something. You should strive to strike a balance between:
|
|
117334
|
-
- Doing the right thing when asked, including taking actions and follow-up actions
|
|
117335
|
-
- Not surprising the USER with actions you take without asking
|
|
117336
|
-
For example, if the USER asks you how to approach something, you should do your best to answer their question first, and not immediately jump into taking actions.
|
|
117337
|
-
|
|
117338
|
-
# Following conventions
|
|
117339
|
-
When making changes to files, first understand the file's code conventions. Mimic code style, use existing libraries and utilities, and follow existing patterns.
|
|
117340
|
-
- NEVER assume that a given library is available, even if it is well known or. Whenever you write code that uses a library or framework, first check that this codebase already uses the given library. For example, you might look at neighboring files, or check the package.json (or cargo.toml or pyproject.toml, and so on depending on the language).
|
|
117341
|
-
- When you create a new component, first look at existing components to see how they're written; then consider framework choice, naming conventions, typing, and other conventions.
|
|
117342
|
-
- When you edit a piece of code, first look at the code's surrounding context (especially its imports) to understand the code's choice of frameworks and libraries. Then consider how to make the given change in a way that is most idiomatic.
|
|
117343
|
-
- Always follow security best practices. Never introduce code that exposes or logs secrets and keys. Never commit secrets or keys to the repository.
|
|
117344
|
-
- Always read the AI Context File / Memory File / Contributing Guidelines / readme for better understanding of the codebase, code writing styles or conventions. For example (not limited to): AGENTS.md, IMPORTANT.md, CONTRIBUTING.md, readme.txt / README.md.
|
|
117345
|
-
|
|
117346
|
-
# Code style
|
|
117347
|
-
- IMPORTANT: DO NOT ADD **ANY** COMMENTS unless explicitly asked.
|
|
117878
|
+
let prompt = `You are Steward, an interactive software-engineering agent operating in a terminal workspace. Assist the user with building, refactoring, debugging, inspecting, and verifying software.
|
|
117348
117879
|
|
|
117349
|
-
|
|
117350
|
-
- Use the edit file and write file tools for both normal code/file changes as well as making plans and todos.
|
|
117351
|
-
- When WebFetch returns a message about a redirect to a different host, immediately make a new WebFetch request with the redirect URL provided in the response.
|
|
117352
|
-
- When web search returns but does not contain enough data, use the URLs from the results and WebFetch them for more detailed information.
|
|
117353
|
-
- When a command moves to the background as a shell task, use task_read to inspect its status/output, task_send_input to send standard input (always include a trailing '
|
|
117354
|
-
' to submit a line / press Enter), and task_kill to terminate it.
|
|
117880
|
+
IMPORTANT: Assist with defensive security tasks only. Refuse to create, modify, or improve code intended for malicious use.
|
|
117355
117881
|
|
|
117356
|
-
|
|
117357
|
-
|
|
117358
|
-
|
|
117359
|
-
|
|
117360
|
-
|
|
117361
|
-
|
|
117362
|
-
|
|
117882
|
+
# Operating Principles
|
|
117883
|
+
- Inspect before editing: investigate existing files, directories, and patterns before making changes.
|
|
117884
|
+
- Minimal and idiomatic: prefer the smallest correct change that aligns with existing repository architecture and conventions.
|
|
117885
|
+
- Never assume external dependencies or libraries exist; check project configuration files (e.g. package.json, Cargo.toml, pyproject.toml) first.
|
|
117886
|
+
- Security and secrets: never expose, commit, or log API keys, credentials, or secrets.
|
|
117887
|
+
- Verify your changes: run relevant project verification or tests after making non-trivial modifications.
|
|
117888
|
+
- Never commit git changes unless the user explicitly requests you to commit.
|
|
117889
|
+
- Provide direct, concise responses suitable for a terminal interface. Explain non-obvious failures or decisions clearly without unnecessary filler or commentary.
|
|
117890
|
+
- When referencing code locations, use the format: \`file_path:line_number\`.
|
|
117891
|
+
- Avoid adding redundant comments unless explaining non-obvious invariants or explicitly requested.
|
|
117363
117892
|
|
|
117364
|
-
|
|
117365
|
-
|
|
117366
|
-
|
|
117367
|
-
|
|
117368
|
-
|
|
117369
|
-
|
|
117370
|
-
assistant: I'm now going to run the build using Bash.
|
|
117371
|
-
[assistant runs the build command and gets 10 type errors]
|
|
117372
|
-
assistant: Looks like I found 10 type errors. I'm going to use the edit file tool to write 10 items to the todo list.
|
|
117373
|
-
[assistant runs edit tool and puts 10 child todos under the 'figure out type error files' todo in [project dir]/.steward/plans/fixing_type_errors_plan.md]
|
|
117374
|
-
assistant: Marking the first todo as in_progress.
|
|
117375
|
-
[assistant uses the edit file to mark 'run build command to find type errors' todo]
|
|
117376
|
-
Let me start working on the first item...
|
|
117377
|
-
[assistant reads the first file, uses edit tool and fixes it]
|
|
117378
|
-
The first item has been fixed, let me mark the first todo as completed, and move on to the second item...
|
|
117379
|
-
[same as first one, assistant fixes all files]
|
|
117380
|
-
</example>
|
|
117381
|
-
In the above example, the assistant completes all the tasks, including the 10 error fixes and running the build to double-check the fixes.
|
|
117893
|
+
# Tool Usage Policy
|
|
117894
|
+
- Investigation: use read_file, grep, list_dir, and glob to explore code before modifying it.
|
|
117895
|
+
- Mutations: ALWAYS use write_file and edit_file to create or modify files rather than bash redirection (e.g. cat > file). write_file and edit_file participate in the file checkpoint and rewind system (/rewind), whereas bash commands are not tracked by checkpoints.
|
|
117896
|
+
- Commands: when using bash, you MUST always provide a concise, non-empty 'explanation' parameter describing what the command does and why it is needed.
|
|
117897
|
+
- Background tasks: use task_list to inspect active and recent tasks, task_read to check status/output, task_send_input to send standard input (with a trailing newline), and task_kill to terminate.
|
|
117898
|
+
- Web: use web_fetch and web_search when external documentation or live data is needed. If web_fetch indicates a redirect, follow up with the target URL.
|
|
117382
117899
|
|
|
117383
|
-
|
|
117384
|
-
|
|
117900
|
+
# Task and Plan Management (Todos)
|
|
117901
|
+
- For multi-step, complex, or verification-heavy tasks, maintain operational state using session todos:
|
|
117902
|
+
- todo_write (TodoWrite): initialize or replace the session todo list (must contain 2 to 10 items).
|
|
117903
|
+
- todo_update (TodoUpdate): update exactly one todo item status or description as progress occurs.
|
|
117904
|
+
- todo_read (TodoRead): inspect current session todos when recovering or verifying progress.
|
|
117905
|
+
- Keep at most one item in 'in_progress' status at any time.
|
|
117906
|
+
- Mark todos completed immediately after completing each step; do not batch progress updates.
|
|
117907
|
+
- For simple, one-step queries or edits, do not create a todo list.
|
|
117385
117908
|
|
|
117386
|
-
|
|
117387
|
-
|
|
117388
|
-
|
|
117389
|
-
|
|
117390
|
-
|
|
117391
|
-
4. Create export functionality for different formats
|
|
117392
|
-
[assistant uses the write file to write the plan and its todos in [project dir]/.steward/plans/]
|
|
117393
|
-
Let me start by researching the existing codebase to understand what metrics we might already be tracking and how we can build on that.
|
|
117394
|
-
[assistant lists all files and reads all the relevant files step by step]
|
|
117395
|
-
I'm going to search for any existing metrics or telemetry code in the project.
|
|
117396
|
-
[assistant runs search text to find any existing telemetry code in the codebase]
|
|
117397
|
-
I've found some existing telemetry code. Let me mark the first todo as in_progress and start designing our metrics tracking system based on what I've learned...
|
|
117398
|
-
[assistant continues implementing the feature step by step, marking todos as completed as they go]
|
|
117399
|
-
</example>
|
|
117909
|
+
# Specialized Skills Policy
|
|
117910
|
+
- Skills are discoverable on-demand packages of specialized instructions located in workspace or user configuration.
|
|
117911
|
+
- Use skill_list (SkillList) to discover available skills and their descriptions.
|
|
117912
|
+
- Use skill_read (SkillRead) to load a skill's instructions (SKILL.md) or relative resources only when a task requires specialized domain guidance.
|
|
117913
|
+
- Skill instructions are supplemental and must not override core safety rules, tool boundaries, or user permission constraints.
|
|
117400
117914
|
|
|
117401
|
-
|
|
117402
|
-
|
|
117403
|
-
|
|
117404
|
-
|
|
117405
|
-
|
|
117406
|
-
- Use the available search tools to understand the codebase and the user's query. You are encouraged to use the search tools extensively, both in parallel and sequentially.
|
|
117407
|
-
- Implement the solution using all tools available to you.
|
|
117408
|
-
- Verify the solution if possible with tests. NEVER assume a specific test framework or test script. Check the README or search the codebase to determine the testing approach.
|
|
117409
|
-
- VERY IMPORTANT: When you have completed a task, you MUST run the lint and typecheck commands (e.g. npm run lint, npm run typecheck, ruff, etc.) with Bash if they were provided to you to ensure your code is correct. If you are unable to find the correct command, ask the user for the command to run and if they supply it, proactively suggest writing it to AGENTS.md so that you will know to run it next time.
|
|
117410
|
-
NEVER commit changes unless the user explicitly asks you to. It is VERY IMPORTANT to only commit when explicitly asked, otherwise the user will feel that you are being too proactive.
|
|
117411
|
-
|
|
117412
|
-
Here is useful information about the environment you are running in:
|
|
117413
|
-
<env>
|
|
117414
|
-
Working directory: ${cwd}
|
|
117415
|
-
Is directory a git repo: ${isAGitREPO}
|
|
117416
|
-
Platform: ${platform}
|
|
117417
|
-
Today's date: ${dateUTC}
|
|
117418
|
-
</env>
|
|
117419
|
-
|
|
117420
|
-
# Code References
|
|
117421
|
-
When referencing specific functions or pieces of code, include the pattern \`file_path:line_number\` to allow the user to easily navigate to the source code location.
|
|
117422
|
-
|
|
117423
|
-
<example>
|
|
117424
|
-
user: Where are errors from the client handled?
|
|
117425
|
-
assistant: Clients are marked as failed in the \`connectToServer\` function in src/services/process.ts:712.
|
|
117426
|
-
</example>`;
|
|
117427
|
-
const skillsPrompt = formatSkillsForSystemPrompt(skills);
|
|
117428
|
-
if (skillsPrompt) {
|
|
117429
|
-
prompt += `
|
|
117430
|
-
|
|
117431
|
-
${skillsPrompt}`;
|
|
117432
|
-
}
|
|
117915
|
+
<env>
|
|
117916
|
+
Working directory: ${cwd}
|
|
117917
|
+
Platform: ${platform}
|
|
117918
|
+
Today's date: ${dateUTC}
|
|
117919
|
+
</env>`;
|
|
117433
117920
|
if (options.userRules && options.userRules.length > 0) {
|
|
117434
117921
|
prompt += `
|
|
117435
117922
|
|
|
117436
117923
|
<user_defined_rules>
|
|
117437
|
-
The following rules are
|
|
117438
|
-
They take precedence over all other style and behaviour guidelines above,
|
|
117439
|
-
except for the operating_principles and security sections.
|
|
117440
|
-
|
|
117924
|
+
The following rules are configured by the user and must be followed:
|
|
117441
117925
|
${options.userRules.map((rule) => `- ${rule}`).join(`
|
|
117442
117926
|
`)}
|
|
117443
117927
|
</user_defined_rules>`;
|
|
@@ -117601,6 +118085,7 @@ class AgentSession {
|
|
|
117601
118085
|
await tracker.beginTurn(turnId, this.sessionData.turns.length + 1);
|
|
117602
118086
|
const toolContext = {
|
|
117603
118087
|
cwd,
|
|
118088
|
+
sessionId: this.sessionData.id,
|
|
117604
118089
|
abortSignal: this.activeAbortController.signal,
|
|
117605
118090
|
checkpointTracker: tracker,
|
|
117606
118091
|
mutationLocks: globalMutationLockManager,
|
|
@@ -117617,6 +118102,7 @@ class AgentSession {
|
|
|
117617
118102
|
activeTools = defaultToolCatalog.toAISDKTools(toolContext);
|
|
117618
118103
|
}
|
|
117619
118104
|
const instructions = buildSystemPrompt({
|
|
118105
|
+
cwd,
|
|
117620
118106
|
extraInstructions: options.extraInstructions
|
|
117621
118107
|
});
|
|
117622
118108
|
const wrappedOnEvent = (event) => {
|
|
@@ -119068,6 +119554,7 @@ class StatusBar extends Component {
|
|
|
119068
119554
|
clip = true;
|
|
119069
119555
|
warningTimer = null;
|
|
119070
119556
|
voiceTimer = null;
|
|
119557
|
+
updateTimer = null;
|
|
119071
119558
|
constructor(props) {
|
|
119072
119559
|
super(props);
|
|
119073
119560
|
this.state = {
|
|
@@ -119077,7 +119564,8 @@ class StatusBar extends Component {
|
|
|
119077
119564
|
exitPending: props.exitPending,
|
|
119078
119565
|
warning: props.warning,
|
|
119079
119566
|
voiceState: props.voiceState ?? "idle",
|
|
119080
|
-
voiceDurationSec: props.voiceDurationSec ?? 0
|
|
119567
|
+
voiceDurationSec: props.voiceDurationSec ?? 0,
|
|
119568
|
+
updateStatus: props.updateStatus
|
|
119081
119569
|
};
|
|
119082
119570
|
}
|
|
119083
119571
|
update(partial) {
|
|
@@ -119100,8 +119588,20 @@ class StatusBar extends Component {
|
|
|
119100
119588
|
});
|
|
119101
119589
|
}
|
|
119102
119590
|
}
|
|
119103
|
-
setUpdateStatus(updateStatus) {
|
|
119591
|
+
setUpdateStatus(updateStatus, durationMs) {
|
|
119592
|
+
if (this.updateTimer) {
|
|
119593
|
+
clearTimeout(this.updateTimer);
|
|
119594
|
+
this.updateTimer = null;
|
|
119595
|
+
}
|
|
119104
119596
|
this.setState({ updateStatus });
|
|
119597
|
+
if (updateStatus && durationMs && durationMs > 0) {
|
|
119598
|
+
this.updateTimer = setTimeout(() => {
|
|
119599
|
+
this.updateTimer = null;
|
|
119600
|
+
if (this.state.updateStatus === updateStatus) {
|
|
119601
|
+
this.setState({ updateStatus: undefined });
|
|
119602
|
+
}
|
|
119603
|
+
}, durationMs);
|
|
119604
|
+
}
|
|
119105
119605
|
}
|
|
119106
119606
|
showWarning(warningText, durationMs = 4000) {
|
|
119107
119607
|
if (this.warningTimer) {
|
|
@@ -119125,6 +119625,10 @@ class StatusBar extends Component {
|
|
|
119125
119625
|
clearInterval(this.voiceTimer);
|
|
119126
119626
|
this.voiceTimer = null;
|
|
119127
119627
|
}
|
|
119628
|
+
if (this.updateTimer) {
|
|
119629
|
+
clearTimeout(this.updateTimer);
|
|
119630
|
+
this.updateTimer = null;
|
|
119631
|
+
}
|
|
119128
119632
|
}
|
|
119129
119633
|
render(width) {
|
|
119130
119634
|
const theme = getTheme();
|
|
@@ -119158,14 +119662,10 @@ class StatusBar extends Component {
|
|
|
119158
119662
|
} else if (updateStatus && updateStatus.state !== "idle") {
|
|
119159
119663
|
if (updateStatus.state === "checking") {
|
|
119160
119664
|
left = source_default.white(updateStatus.message ?? "Checking for updates...");
|
|
119161
|
-
} else if (updateStatus.state === "no-updates") {
|
|
119162
|
-
left = source_default.white(updateStatus.message ?? "No updates found");
|
|
119163
119665
|
} else if (updateStatus.state === "available") {
|
|
119164
|
-
left = source_default.white(updateStatus.message ?? `
|
|
119165
|
-
} else if (updateStatus.state === "
|
|
119166
|
-
left = source_default.white(updateStatus.message ??
|
|
119167
|
-
} else if (updateStatus.state === "ready") {
|
|
119168
|
-
left = source_default.white.bold(updateStatus.message ?? "Update complete · Restart to apply");
|
|
119666
|
+
left = source_default.white(updateStatus.message ?? `Update v${updateStatus.version ?? ""} is available`);
|
|
119667
|
+
} else if (updateStatus.state === "no-updates") {
|
|
119668
|
+
left = source_default.white(updateStatus.message ?? "Steward is up to date");
|
|
119169
119669
|
} else if (updateStatus.state === "error" && updateStatus.message) {
|
|
119170
119670
|
left = themeColor2(theme.error)(updateStatus.message);
|
|
119171
119671
|
} else if (isBusy) {
|
|
@@ -119306,6 +119806,8 @@ class StreamingView extends Component {
|
|
|
119306
119806
|
const maxArgLen = Math.max(10, maxCols - toolName.length - 8);
|
|
119307
119807
|
const truncatedArg = targetArg.length > maxArgLen ? truncateMiddle(targetArg, maxArgLen) : targetArg;
|
|
119308
119808
|
line += `${source_default.dim("(")}${source_default.dim(truncatedArg)}${source_default.dim(")")}`;
|
|
119809
|
+
} else {
|
|
119810
|
+
line += `${source_default.dim("()")}`;
|
|
119309
119811
|
}
|
|
119310
119812
|
lines.push(line);
|
|
119311
119813
|
if (activeTool.recentLines && activeTool.recentLines.length > 0) {
|
|
@@ -120088,6 +120590,9 @@ class TrustGate extends Component {
|
|
|
120088
120590
|
|
|
120089
120591
|
// src/tui/components/docks/ModelPicker.tsx
|
|
120090
120592
|
class ModelPicker extends SelectList {
|
|
120593
|
+
wrap = false;
|
|
120594
|
+
clip = true;
|
|
120595
|
+
ellipsis = false;
|
|
120091
120596
|
constructor(props) {
|
|
120092
120597
|
super({
|
|
120093
120598
|
items: props.models,
|
|
@@ -120136,6 +120641,9 @@ class ModelPicker extends SelectList {
|
|
|
120136
120641
|
|
|
120137
120642
|
// src/tui/components/docks/SessionMenu.tsx
|
|
120138
120643
|
class SessionMenu extends SelectList {
|
|
120644
|
+
wrap = false;
|
|
120645
|
+
clip = true;
|
|
120646
|
+
ellipsis = false;
|
|
120139
120647
|
constructor(props) {
|
|
120140
120648
|
super({
|
|
120141
120649
|
items: props.sessions,
|
|
@@ -120518,6 +121026,9 @@ function buildRewindItems(session, cwd) {
|
|
|
120518
121026
|
}
|
|
120519
121027
|
|
|
120520
121028
|
class RewindMenu extends SelectList {
|
|
121029
|
+
wrap = false;
|
|
121030
|
+
clip = true;
|
|
121031
|
+
ellipsis = false;
|
|
120521
121032
|
constructor(props) {
|
|
120522
121033
|
const items = buildRewindItems(props.session, props.cwd);
|
|
120523
121034
|
super({
|
|
@@ -121139,6 +121650,15 @@ class FilePermissionDock extends Component {
|
|
|
121139
121650
|
}
|
|
121140
121651
|
|
|
121141
121652
|
// src/tui/utils/permission-queue.ts
|
|
121653
|
+
function settleItem(item, allowed) {
|
|
121654
|
+
item.settled = true;
|
|
121655
|
+
if (item.kind === "bash") {
|
|
121656
|
+
item.resolve({ allowed });
|
|
121657
|
+
} else {
|
|
121658
|
+
item.resolve({ allowed });
|
|
121659
|
+
}
|
|
121660
|
+
}
|
|
121661
|
+
|
|
121142
121662
|
class PermissionQueue {
|
|
121143
121663
|
queue = [];
|
|
121144
121664
|
activeItem = null;
|
|
@@ -121209,28 +121729,25 @@ class PermissionQueue {
|
|
|
121209
121729
|
if (!current || current.settled) {
|
|
121210
121730
|
return;
|
|
121211
121731
|
}
|
|
121212
|
-
current.settled = true;
|
|
121213
121732
|
this.activeItem = null;
|
|
121214
121733
|
this.onHide();
|
|
121215
|
-
current
|
|
121734
|
+
settleItem(current, allowed);
|
|
121216
121735
|
this.processNext();
|
|
121217
121736
|
}
|
|
121218
121737
|
cancelItem(item) {
|
|
121219
121738
|
if (item.settled)
|
|
121220
121739
|
return;
|
|
121221
121740
|
if (this.activeItem === item) {
|
|
121222
|
-
item.settled = true;
|
|
121223
121741
|
this.activeItem = null;
|
|
121224
121742
|
this.onHide();
|
|
121225
|
-
item
|
|
121743
|
+
settleItem(item, false);
|
|
121226
121744
|
this.processNext();
|
|
121227
121745
|
} else {
|
|
121228
121746
|
const idx = this.queue.indexOf(item);
|
|
121229
121747
|
if (idx !== -1) {
|
|
121230
121748
|
this.queue.splice(idx, 1);
|
|
121231
121749
|
}
|
|
121232
|
-
item
|
|
121233
|
-
item.resolve({ allowed: false });
|
|
121750
|
+
settleItem(item, false);
|
|
121234
121751
|
}
|
|
121235
121752
|
}
|
|
121236
121753
|
processNext() {
|
|
@@ -121238,8 +121755,7 @@ class PermissionQueue {
|
|
|
121238
121755
|
const next = this.queue.shift();
|
|
121239
121756
|
if (next.settled || next.abortSignal?.aborted) {
|
|
121240
121757
|
if (!next.settled) {
|
|
121241
|
-
next
|
|
121242
|
-
next.resolve({ allowed: false });
|
|
121758
|
+
settleItem(next, false);
|
|
121243
121759
|
}
|
|
121244
121760
|
continue;
|
|
121245
121761
|
}
|
|
@@ -121251,16 +121767,15 @@ class PermissionQueue {
|
|
|
121251
121767
|
}
|
|
121252
121768
|
clear() {
|
|
121253
121769
|
if (this.activeItem && !this.activeItem.settled) {
|
|
121254
|
-
this.activeItem
|
|
121255
|
-
this.activeItem.resolve({ allowed: false });
|
|
121770
|
+
const active = this.activeItem;
|
|
121256
121771
|
this.activeItem = null;
|
|
121257
121772
|
this.onHide();
|
|
121773
|
+
settleItem(active, false);
|
|
121258
121774
|
}
|
|
121259
121775
|
while (this.queue.length > 0) {
|
|
121260
121776
|
const item = this.queue.shift();
|
|
121261
121777
|
if (!item.settled) {
|
|
121262
|
-
item
|
|
121263
|
-
item.resolve({ allowed: false });
|
|
121778
|
+
settleItem(item, false);
|
|
121264
121779
|
}
|
|
121265
121780
|
}
|
|
121266
121781
|
}
|
|
@@ -121347,17 +121862,19 @@ function classifyVoiceError(err) {
|
|
|
121347
121862
|
};
|
|
121348
121863
|
}
|
|
121349
121864
|
// src/voice/prerequisites.ts
|
|
121865
|
+
import { spawn as spawn2 } from "node:child_process";
|
|
121350
121866
|
async function defaultCheckParec() {
|
|
121351
|
-
|
|
121352
|
-
|
|
121353
|
-
|
|
121354
|
-
|
|
121355
|
-
|
|
121356
|
-
|
|
121357
|
-
|
|
121358
|
-
|
|
121359
|
-
|
|
121360
|
-
|
|
121867
|
+
return new Promise((resolve) => {
|
|
121868
|
+
try {
|
|
121869
|
+
const proc = spawn2("which", ["parec"], {
|
|
121870
|
+
stdio: "ignore"
|
|
121871
|
+
});
|
|
121872
|
+
proc.on("error", () => resolve(false));
|
|
121873
|
+
proc.on("close", (code) => resolve(code === 0));
|
|
121874
|
+
} catch {
|
|
121875
|
+
resolve(false);
|
|
121876
|
+
}
|
|
121877
|
+
});
|
|
121361
121878
|
}
|
|
121362
121879
|
async function checkVoicePrerequisites(options = {}) {
|
|
121363
121880
|
const config = options.config ?? getEnvConfig();
|
|
@@ -121425,6 +121942,8 @@ class TranscriptAccumulator {
|
|
|
121425
121942
|
}
|
|
121426
121943
|
}
|
|
121427
121944
|
// src/voice/recorder.ts
|
|
121945
|
+
import { spawn as spawn3 } from "node:child_process";
|
|
121946
|
+
import { Readable } from "node:stream";
|
|
121428
121947
|
var DEFAULT_PAREC_ARGS = ["--raw", "--rate=16000", "--channels=1", "--format=s16le"];
|
|
121429
121948
|
|
|
121430
121949
|
class ParecAudioRecorder {
|
|
@@ -121436,12 +121955,40 @@ class ParecAudioRecorder {
|
|
|
121436
121955
|
checkAvailableFn;
|
|
121437
121956
|
constructor(options) {
|
|
121438
121957
|
this.spawnFn = options?.spawnFn ?? ((cmd, opts) => {
|
|
121439
|
-
const
|
|
121958
|
+
const [executable, ...args] = cmd;
|
|
121959
|
+
const child = spawn3(executable, args, {
|
|
121960
|
+
stdio: [
|
|
121961
|
+
"ignore",
|
|
121962
|
+
opts.stdout === "pipe" ? "pipe" : "ignore",
|
|
121963
|
+
opts.stderr === "pipe" ? "pipe" : "ignore"
|
|
121964
|
+
]
|
|
121965
|
+
});
|
|
121966
|
+
const exited = new Promise((resolve) => {
|
|
121967
|
+
child.on("close", (code) => resolve(code ?? 0));
|
|
121968
|
+
child.on("error", () => resolve(1));
|
|
121969
|
+
});
|
|
121970
|
+
const stdout = child.stdout ? Readable.toWeb(child.stdout) : null;
|
|
121971
|
+
const stderr = child.stderr ? Readable.toWeb(child.stderr) : null;
|
|
121440
121972
|
return {
|
|
121441
|
-
stdout
|
|
121442
|
-
stderr
|
|
121443
|
-
exited
|
|
121444
|
-
kill: (sig) =>
|
|
121973
|
+
stdout,
|
|
121974
|
+
stderr,
|
|
121975
|
+
exited,
|
|
121976
|
+
kill: (sig) => {
|
|
121977
|
+
try {
|
|
121978
|
+
if (typeof sig === "number") {
|
|
121979
|
+
const signalMap = {
|
|
121980
|
+
2: "SIGINT",
|
|
121981
|
+
9: "SIGKILL",
|
|
121982
|
+
15: "SIGTERM"
|
|
121983
|
+
};
|
|
121984
|
+
child.kill(signalMap[sig] || sig);
|
|
121985
|
+
} else if (sig) {
|
|
121986
|
+
child.kill(sig);
|
|
121987
|
+
} else {
|
|
121988
|
+
child.kill();
|
|
121989
|
+
}
|
|
121990
|
+
} catch {}
|
|
121991
|
+
}
|
|
121445
121992
|
};
|
|
121446
121993
|
});
|
|
121447
121994
|
this.checkAvailableFn = options?.checkAvailableFn ?? defaultCheckParec;
|
|
@@ -122088,9 +122635,11 @@ class VoiceController {
|
|
|
122088
122635
|
}
|
|
122089
122636
|
}
|
|
122090
122637
|
// src/services/updater/service.ts
|
|
122091
|
-
import { spawn as spawn2 } from "node:child_process";
|
|
122092
122638
|
var DEFAULT_REPO = "sapirrior/steward";
|
|
122093
122639
|
function parseSemver(v) {
|
|
122640
|
+
if (!v || typeof v !== "string") {
|
|
122641
|
+
return [0, 0, 0];
|
|
122642
|
+
}
|
|
122094
122643
|
const clean = v.replace(/^v/i, "").trim().split("-")[0] ?? "";
|
|
122095
122644
|
const parts = clean.split(".").map((p) => parseInt(p, 10) || 0);
|
|
122096
122645
|
return [parts[0] ?? 0, parts[1] ?? 0, parts[2] ?? 0];
|
|
@@ -122109,14 +122658,13 @@ function isNewerVersion(current, latest) {
|
|
|
122109
122658
|
return lPat > cPat;
|
|
122110
122659
|
}
|
|
122111
122660
|
|
|
122112
|
-
class
|
|
122661
|
+
class UpdateCheckerService {
|
|
122113
122662
|
currentVersion;
|
|
122114
122663
|
repo;
|
|
122115
122664
|
state = "idle";
|
|
122116
122665
|
options;
|
|
122117
122666
|
checkTimer = null;
|
|
122118
|
-
|
|
122119
|
-
isUpdating = false;
|
|
122667
|
+
resetTimer = null;
|
|
122120
122668
|
constructor(options = {}) {
|
|
122121
122669
|
this.options = options;
|
|
122122
122670
|
this.currentVersion = options.currentVersion || package_default.version || "0.0.0";
|
|
@@ -122131,14 +122679,8 @@ class AutoUpdaterService {
|
|
|
122131
122679
|
getCurrentVersion() {
|
|
122132
122680
|
return this.currentVersion;
|
|
122133
122681
|
}
|
|
122134
|
-
get
|
|
122135
|
-
return `https://
|
|
122136
|
-
}
|
|
122137
|
-
get installShUrl() {
|
|
122138
|
-
return `https://raw.githubusercontent.com/${this.repo}/main/installer/install.sh`;
|
|
122139
|
-
}
|
|
122140
|
-
get installPs1Url() {
|
|
122141
|
-
return `https://raw.githubusercontent.com/${this.repo}/main/installer/install.ps1`;
|
|
122682
|
+
get rawPackageJsonUrl() {
|
|
122683
|
+
return `https://raw.githubusercontent.com/${this.repo}/main/package.json`;
|
|
122142
122684
|
}
|
|
122143
122685
|
setState(state, info) {
|
|
122144
122686
|
this.state = state;
|
|
@@ -122148,156 +122690,94 @@ class AutoUpdaterService {
|
|
|
122148
122690
|
if (process.env.STEWARD_NO_UPDATE_CHECK === "1" || process.env.STEWARD_NO_UPDATE_CHECK === "true") {
|
|
122149
122691
|
return null;
|
|
122150
122692
|
}
|
|
122693
|
+
if (this.resetTimer) {
|
|
122694
|
+
clearTimeout(this.resetTimer);
|
|
122695
|
+
this.resetTimer = null;
|
|
122696
|
+
}
|
|
122151
122697
|
try {
|
|
122152
122698
|
this.setState("checking", { message: "Checking for updates..." });
|
|
122699
|
+
const checkStartTime = Date.now();
|
|
122153
122700
|
const controller = new AbortController;
|
|
122154
122701
|
const timeout = setTimeout(() => controller.abort(), this.options.timeoutMs ?? 1e4);
|
|
122155
|
-
const response = await fetch(this.
|
|
122702
|
+
const response = await fetch(this.rawPackageJsonUrl, {
|
|
122156
122703
|
headers: {
|
|
122157
122704
|
"User-Agent": `steward-cli/${this.currentVersion}`,
|
|
122158
|
-
Accept: "application/
|
|
122705
|
+
Accept: "application/json"
|
|
122159
122706
|
},
|
|
122160
122707
|
signal: controller.signal
|
|
122161
122708
|
});
|
|
122162
122709
|
clearTimeout(timeout);
|
|
122710
|
+
const elapsed = Date.now() - checkStartTime;
|
|
122711
|
+
const targetMinDisplay = this.options.minDisplayMs !== undefined ? this.options.minDisplayMs : 1000;
|
|
122712
|
+
const minDisplayDelay = Math.max(0, targetMinDisplay - elapsed);
|
|
122713
|
+
if (minDisplayDelay > 0) {
|
|
122714
|
+
await new Promise((resolve) => setTimeout(resolve, minDisplayDelay));
|
|
122715
|
+
}
|
|
122163
122716
|
if (!response.ok) {
|
|
122164
|
-
this.setState("
|
|
122717
|
+
this.setState("error", { message: "Failed to check for updates" });
|
|
122718
|
+
this.resetTimer = setTimeout(() => {
|
|
122719
|
+
if (this.state === "error")
|
|
122720
|
+
this.setState("idle");
|
|
122721
|
+
}, 3000);
|
|
122165
122722
|
return null;
|
|
122166
122723
|
}
|
|
122167
122724
|
const data = await response.json();
|
|
122168
|
-
|
|
122169
|
-
|
|
122725
|
+
if (!data || typeof data !== "object" || typeof data.version !== "string") {
|
|
122726
|
+
this.setState("error", { message: "Invalid upstream version response" });
|
|
122727
|
+
this.resetTimer = setTimeout(() => {
|
|
122728
|
+
if (this.state === "error")
|
|
122729
|
+
this.setState("idle");
|
|
122730
|
+
}, 3000);
|
|
122731
|
+
return null;
|
|
122732
|
+
}
|
|
122733
|
+
const latestVersion = data.version.replace(/^v/i, "").trim();
|
|
122170
122734
|
if (!latestVersion) {
|
|
122171
|
-
this.setState("
|
|
122735
|
+
this.setState("error", { message: "Empty upstream version response" });
|
|
122736
|
+
this.resetTimer = setTimeout(() => {
|
|
122737
|
+
if (this.state === "error")
|
|
122738
|
+
this.setState("idle");
|
|
122739
|
+
}, 3000);
|
|
122172
122740
|
return null;
|
|
122173
122741
|
}
|
|
122174
122742
|
const hasUpdate = isNewerVersion(this.currentVersion, latestVersion);
|
|
122175
122743
|
const updateInfo = {
|
|
122176
122744
|
currentVersion: this.currentVersion,
|
|
122177
122745
|
latestVersion,
|
|
122178
|
-
hasUpdate
|
|
122179
|
-
tag: rawTag,
|
|
122180
|
-
releaseUrl: data.html_url
|
|
122746
|
+
hasUpdate
|
|
122181
122747
|
};
|
|
122182
122748
|
if (hasUpdate) {
|
|
122183
122749
|
this.setState("available", {
|
|
122184
122750
|
version: latestVersion,
|
|
122185
|
-
message: `
|
|
122186
|
-
});
|
|
122187
|
-
} else {
|
|
122188
|
-
this.setState("no-updates", { message: "No updates found" });
|
|
122189
|
-
setTimeout(() => {
|
|
122190
|
-
if (this.state === "no-updates") {
|
|
122191
|
-
this.setState("idle");
|
|
122192
|
-
}
|
|
122193
|
-
}, 3000);
|
|
122194
|
-
}
|
|
122195
|
-
return updateInfo;
|
|
122196
|
-
} catch (err) {
|
|
122197
|
-
logError(err, { component: "AutoUpdaterService", method: "checkForUpdates" });
|
|
122198
|
-
this.setState("idle");
|
|
122199
|
-
return null;
|
|
122200
|
-
}
|
|
122201
|
-
}
|
|
122202
|
-
async installUpdate(version) {
|
|
122203
|
-
if (this.isUpdating)
|
|
122204
|
-
return false;
|
|
122205
|
-
this.isUpdating = true;
|
|
122206
|
-
this.setState("downloading", {
|
|
122207
|
-
version,
|
|
122208
|
-
message: `Downloading v${version}...`
|
|
122209
|
-
});
|
|
122210
|
-
try {
|
|
122211
|
-
const isWindows = process.platform === "win32";
|
|
122212
|
-
let childProcess;
|
|
122213
|
-
if (isWindows) {
|
|
122214
|
-
childProcess = spawn2("powershell", [
|
|
122215
|
-
"-NoProfile",
|
|
122216
|
-
"-ExecutionPolicy",
|
|
122217
|
-
"Bypass",
|
|
122218
|
-
"-Command",
|
|
122219
|
-
`irm ${this.installPs1Url} | iex`
|
|
122220
|
-
], {
|
|
122221
|
-
stdio: "ignore",
|
|
122222
|
-
windowsHide: true
|
|
122751
|
+
message: `Update v${latestVersion} is available`
|
|
122223
122752
|
});
|
|
122224
122753
|
} else {
|
|
122225
|
-
|
|
122226
|
-
|
|
122227
|
-
|
|
122228
|
-
...process.env,
|
|
122229
|
-
HOME: process.env.HOME || "",
|
|
122230
|
-
PATH: process.env.PATH || ""
|
|
122231
|
-
}
|
|
122754
|
+
this.setState("no-updates", {
|
|
122755
|
+
version: this.currentVersion,
|
|
122756
|
+
message: `Steward is up to date (v${this.currentVersion})`
|
|
122232
122757
|
});
|
|
122233
|
-
|
|
122234
|
-
|
|
122235
|
-
const timeout = setTimeout(() => {
|
|
122236
|
-
try {
|
|
122237
|
-
childProcess.kill();
|
|
122238
|
-
} catch {}
|
|
122239
|
-
resolve(false);
|
|
122240
|
-
}, 300000);
|
|
122241
|
-
childProcess.on("exit", (code) => {
|
|
122242
|
-
clearTimeout(timeout);
|
|
122243
|
-
resolve(code === 0);
|
|
122244
|
-
});
|
|
122245
|
-
childProcess.on("error", () => {
|
|
122246
|
-
clearTimeout(timeout);
|
|
122247
|
-
resolve(false);
|
|
122248
|
-
});
|
|
122249
|
-
});
|
|
122250
|
-
if (success) {
|
|
122251
|
-
this.setState("ready", {
|
|
122252
|
-
version,
|
|
122253
|
-
message: `Update complete · Restart to apply`
|
|
122254
|
-
});
|
|
122255
|
-
return true;
|
|
122256
|
-
} else {
|
|
122257
|
-
this.setState("error", {
|
|
122258
|
-
version,
|
|
122259
|
-
message: `Update to v${version} failed`
|
|
122260
|
-
});
|
|
122261
|
-
setTimeout(() => {
|
|
122262
|
-
if (this.state === "error") {
|
|
122758
|
+
this.resetTimer = setTimeout(() => {
|
|
122759
|
+
if (this.state === "no-updates")
|
|
122263
122760
|
this.setState("idle");
|
|
122264
|
-
|
|
122265
|
-
}, 4000);
|
|
122266
|
-
return false;
|
|
122761
|
+
}, 3500);
|
|
122267
122762
|
}
|
|
122763
|
+
return updateInfo;
|
|
122268
122764
|
} catch (err) {
|
|
122269
|
-
logError(err, { component: "
|
|
122270
|
-
this.setState("error", {
|
|
122271
|
-
setTimeout(() => {
|
|
122272
|
-
if (this.state === "error")
|
|
122765
|
+
logError(err, { component: "UpdateCheckerService", method: "checkForUpdates" });
|
|
122766
|
+
this.setState("error", { message: "Failed to check for updates" });
|
|
122767
|
+
this.resetTimer = setTimeout(() => {
|
|
122768
|
+
if (this.state === "error")
|
|
122273
122769
|
this.setState("idle");
|
|
122274
|
-
|
|
122275
|
-
|
|
122276
|
-
return false;
|
|
122277
|
-
} finally {
|
|
122278
|
-
this.isUpdating = false;
|
|
122770
|
+
}, 3000);
|
|
122771
|
+
return null;
|
|
122279
122772
|
}
|
|
122280
122773
|
}
|
|
122281
|
-
startBackgroundCheck(initialDelayMs = 2000
|
|
122282
|
-
if (this.checkTimer)
|
|
122774
|
+
startBackgroundCheck(initialDelayMs = 2000) {
|
|
122775
|
+
if (this.checkTimer) {
|
|
122283
122776
|
clearTimeout(this.checkTimer);
|
|
122284
|
-
|
|
122285
|
-
clearInterval(this.intervalTimer);
|
|
122286
|
-
const performCheck = async () => {
|
|
122287
|
-
const update = await this.checkForUpdates();
|
|
122288
|
-
if (update && update.hasUpdate) {
|
|
122289
|
-
setTimeout(async () => {
|
|
122290
|
-
await this.installUpdate(update.latestVersion);
|
|
122291
|
-
}, 1200);
|
|
122292
|
-
}
|
|
122293
|
-
};
|
|
122777
|
+
}
|
|
122294
122778
|
this.checkTimer = setTimeout(async () => {
|
|
122295
122779
|
this.checkTimer = null;
|
|
122296
|
-
await
|
|
122297
|
-
const recurringInterval = intervalMs ?? this.options.checkIntervalMs;
|
|
122298
|
-
if (recurringInterval && recurringInterval > 0) {
|
|
122299
|
-
this.intervalTimer = setInterval(performCheck, recurringInterval);
|
|
122300
|
-
}
|
|
122780
|
+
await this.checkForUpdates();
|
|
122301
122781
|
}, initialDelayMs);
|
|
122302
122782
|
}
|
|
122303
122783
|
dispose() {
|
|
@@ -122305,9 +122785,9 @@ class AutoUpdaterService {
|
|
|
122305
122785
|
clearTimeout(this.checkTimer);
|
|
122306
122786
|
this.checkTimer = null;
|
|
122307
122787
|
}
|
|
122308
|
-
if (this.
|
|
122309
|
-
|
|
122310
|
-
this.
|
|
122788
|
+
if (this.resetTimer) {
|
|
122789
|
+
clearTimeout(this.resetTimer);
|
|
122790
|
+
this.resetTimer = null;
|
|
122311
122791
|
}
|
|
122312
122792
|
}
|
|
122313
122793
|
}
|
|
@@ -122326,7 +122806,7 @@ class TUIApp {
|
|
|
122326
122806
|
ctrlCTimer = null;
|
|
122327
122807
|
isBusy = false;
|
|
122328
122808
|
voiceController;
|
|
122329
|
-
|
|
122809
|
+
updateChecker;
|
|
122330
122810
|
permissionQueue;
|
|
122331
122811
|
constructor(options = {}) {
|
|
122332
122812
|
this.cwd = options.cwd ?? process.cwd();
|
|
@@ -122389,11 +122869,23 @@ class TUIApp {
|
|
|
122389
122869
|
this.statusBar.showWarning(warning);
|
|
122390
122870
|
}
|
|
122391
122871
|
});
|
|
122392
|
-
this.
|
|
122872
|
+
this.updateChecker = new UpdateCheckerService({
|
|
122393
122873
|
currentVersion: options.version,
|
|
122394
122874
|
onStatusChange: (state, info) => {
|
|
122395
122875
|
if (state === "idle") {
|
|
122396
122876
|
this.statusBar.setUpdateStatus(undefined);
|
|
122877
|
+
} else if (state === "available") {
|
|
122878
|
+
this.statusBar.setUpdateStatus({
|
|
122879
|
+
state,
|
|
122880
|
+
version: info?.version,
|
|
122881
|
+
message: info?.message
|
|
122882
|
+
}, 8000);
|
|
122883
|
+
} else if (state === "error") {
|
|
122884
|
+
this.statusBar.setUpdateStatus({
|
|
122885
|
+
state,
|
|
122886
|
+
version: info?.version,
|
|
122887
|
+
message: info?.message
|
|
122888
|
+
}, 4000);
|
|
122397
122889
|
} else {
|
|
122398
122890
|
this.statusBar.setUpdateStatus({
|
|
122399
122891
|
state,
|
|
@@ -122499,7 +122991,7 @@ class TUIApp {
|
|
|
122499
122991
|
this.engine.mount(this.streamingView, { kind: "custom" });
|
|
122500
122992
|
this.engine.mount(this.promptInput, { keepCursorVisible: true, kind: "input" });
|
|
122501
122993
|
this.engine.mount(this.statusBar, { kind: "custom" });
|
|
122502
|
-
this.
|
|
122994
|
+
this.updateChecker.startBackgroundCheck(3000);
|
|
122503
122995
|
this.engine.addInputListener((chunk) => {
|
|
122504
122996
|
const action = parseKeyInput(chunk);
|
|
122505
122997
|
if (this.voiceController.isActive) {
|
|
@@ -122935,7 +123427,7 @@ class TUIApp {
|
|
|
122935
123427
|
this.permissionQueue.clear();
|
|
122936
123428
|
this.session.shutdown().catch(() => {});
|
|
122937
123429
|
this.voiceController.dispose();
|
|
122938
|
-
this.
|
|
123430
|
+
this.updateChecker.dispose();
|
|
122939
123431
|
this.engine.cleanupSync();
|
|
122940
123432
|
if (this.onExitCallback) {
|
|
122941
123433
|
this.onExitCallback();
|