steward-cli 0.12.0 → 0.13.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 +902 -508
- 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) {
|
|
@@ -52971,7 +52973,7 @@ class TerminalEngine {
|
|
|
52971
52973
|
}
|
|
52972
52974
|
|
|
52973
52975
|
// src/engine/agent-session.ts
|
|
52974
|
-
import { randomUUID as
|
|
52976
|
+
import { randomUUID as randomUUID8 } from "node:crypto";
|
|
52975
52977
|
// src/session/helpers.ts
|
|
52976
52978
|
function formatToolOutputSummary(res, isError = false) {
|
|
52977
52979
|
if (isError || res === undefined || res === null) {
|
|
@@ -70953,7 +70955,6 @@ class ToolCatalog {
|
|
|
70953
70955
|
for (const [name, def] of this.tools.entries()) {
|
|
70954
70956
|
aiTools[name] = tool({
|
|
70955
70957
|
description: def.description,
|
|
70956
|
-
inputSchema: def.parameters,
|
|
70957
70958
|
parameters: def.parameters,
|
|
70958
70959
|
execute: async (args) => {
|
|
70959
70960
|
return def.execute(args, context);
|
|
@@ -72534,15 +72535,23 @@ async function evaluateBashPermission(req, context) {
|
|
|
72534
72535
|
|
|
72535
72536
|
// src/tools/bash/index.ts
|
|
72536
72537
|
var bashInputSchema = object({
|
|
72537
|
-
command: string2(
|
|
72538
|
-
|
|
72538
|
+
command: string2({
|
|
72539
|
+
required_error: "command is required"
|
|
72540
|
+
}).min(1, "command cannot be empty").describe("The command to execute in the system shell."),
|
|
72541
|
+
explanation: string2({
|
|
72542
|
+
required_error: "An explanation is mandatory for running a bash command.",
|
|
72543
|
+
invalid_type_error: "explanation must be a string."
|
|
72544
|
+
}).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
72545
|
});
|
|
72540
72546
|
var bashTool = {
|
|
72541
72547
|
name: "bash",
|
|
72542
72548
|
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).",
|
|
72549
|
+
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
72550
|
parameters: bashInputSchema,
|
|
72545
72551
|
confirmationPolicy: "never",
|
|
72552
|
+
summarizeArgs: (args) => {
|
|
72553
|
+
return args.command ?? "";
|
|
72554
|
+
},
|
|
72546
72555
|
summarize: (_args, result) => {
|
|
72547
72556
|
if (result && "status" in result && result.status === "backgrounded") {
|
|
72548
72557
|
return `Moved to background · task id: ${result.taskId}`;
|
|
@@ -72551,10 +72560,13 @@ var bashTool = {
|
|
|
72551
72560
|
return `Ran successfully · exit code: ${code}`;
|
|
72552
72561
|
},
|
|
72553
72562
|
execute: async (args, context) => {
|
|
72554
|
-
const command = args.command.trim();
|
|
72555
|
-
|
|
72563
|
+
const command = typeof args?.command === "string" ? args.command.trim() : "";
|
|
72564
|
+
if (!command) {
|
|
72565
|
+
throw new Error("Command is required and cannot be empty.");
|
|
72566
|
+
}
|
|
72567
|
+
const explanation = typeof args?.explanation === "string" ? args.explanation.trim() : "";
|
|
72556
72568
|
if (!explanation) {
|
|
72557
|
-
throw new Error("An explanation is
|
|
72569
|
+
throw new Error("An explanation is mandatory for running a bash command.");
|
|
72558
72570
|
}
|
|
72559
72571
|
const perm = await evaluateBashPermission({ command, explanation }, context);
|
|
72560
72572
|
if (!perm.allowed) {
|
|
@@ -72679,7 +72691,7 @@ var taskKillTool = {
|
|
|
72679
72691
|
// package.json
|
|
72680
72692
|
var package_default = {
|
|
72681
72693
|
name: "steward-cli",
|
|
72682
|
-
version: "0.
|
|
72694
|
+
version: "0.13.0",
|
|
72683
72695
|
description: "Interactive AI engineering assistant for the terminal with human-in-the-loop control across local and frontier models.",
|
|
72684
72696
|
author: "sapirrior",
|
|
72685
72697
|
license: "MIT",
|
|
@@ -72896,6 +72908,727 @@ var webSearchTool = {
|
|
|
72896
72908
|
};
|
|
72897
72909
|
}
|
|
72898
72910
|
};
|
|
72911
|
+
// src/services/todos/store.ts
|
|
72912
|
+
import {
|
|
72913
|
+
existsSync as existsSync12,
|
|
72914
|
+
mkdirSync as mkdirSync9,
|
|
72915
|
+
readFileSync as readFileSync12,
|
|
72916
|
+
renameSync as renameSync7,
|
|
72917
|
+
unlinkSync as unlinkSync7,
|
|
72918
|
+
writeFileSync as writeFileSync3,
|
|
72919
|
+
openSync as openSync8,
|
|
72920
|
+
fsyncSync as fsyncSync7,
|
|
72921
|
+
closeSync as closeSync8
|
|
72922
|
+
} from "node:fs";
|
|
72923
|
+
import { join as join13 } from "node:path";
|
|
72924
|
+
import { randomUUID as randomUUID7 } from "node:crypto";
|
|
72925
|
+
|
|
72926
|
+
// src/config/env.ts
|
|
72927
|
+
var ALL_PROVIDER_NAMES = [
|
|
72928
|
+
"gemini",
|
|
72929
|
+
"anthropic",
|
|
72930
|
+
"openai",
|
|
72931
|
+
"xai",
|
|
72932
|
+
"mistral",
|
|
72933
|
+
"deepseek",
|
|
72934
|
+
"openrouter",
|
|
72935
|
+
"custom"
|
|
72936
|
+
];
|
|
72937
|
+
function getEnvConfig() {
|
|
72938
|
+
const geminiApiKey = process.env["GEMINI_API_KEY"]?.trim() || process.env["GOOGLE_GENERATIVE_AI_API_KEY"]?.trim() || undefined;
|
|
72939
|
+
const anthropicApiKey = process.env["ANTHROPIC_API_KEY"]?.trim() || undefined;
|
|
72940
|
+
const openaiApiKey = process.env["OPENAI_API_KEY"]?.trim() || undefined;
|
|
72941
|
+
const xaiApiKey = process.env["XAI_API_KEY"]?.trim() || undefined;
|
|
72942
|
+
const mistralApiKey = process.env["MISTRAL_API_KEY"]?.trim() || undefined;
|
|
72943
|
+
const deepseekApiKey = process.env["DEEPSEEK_API_KEY"]?.trim() || undefined;
|
|
72944
|
+
const openrouterApiKey = process.env["OPENROUTER_API_KEY"]?.trim() || undefined;
|
|
72945
|
+
const customApiKey = process.env["CUSTOM_API_KEY"]?.trim() || undefined;
|
|
72946
|
+
const customApiModelName = process.env["CUSTOM_API_MODEL_NAME"]?.trim() || undefined;
|
|
72947
|
+
const customApiUrl = process.env["CUSTOM_API_URL"]?.trim() || undefined;
|
|
72948
|
+
return {
|
|
72949
|
+
geminiApiKey,
|
|
72950
|
+
anthropicApiKey,
|
|
72951
|
+
openaiApiKey,
|
|
72952
|
+
xaiApiKey,
|
|
72953
|
+
mistralApiKey,
|
|
72954
|
+
deepseekApiKey,
|
|
72955
|
+
openrouterApiKey,
|
|
72956
|
+
custom: {
|
|
72957
|
+
apiKey: customApiKey,
|
|
72958
|
+
modelName: customApiModelName,
|
|
72959
|
+
baseURL: customApiUrl
|
|
72960
|
+
}
|
|
72961
|
+
};
|
|
72962
|
+
}
|
|
72963
|
+
function hasProviderConfig(provider, config = getEnvConfig()) {
|
|
72964
|
+
switch (provider) {
|
|
72965
|
+
case "gemini":
|
|
72966
|
+
return Boolean(config.geminiApiKey);
|
|
72967
|
+
case "anthropic":
|
|
72968
|
+
return Boolean(config.anthropicApiKey);
|
|
72969
|
+
case "openai":
|
|
72970
|
+
return Boolean(config.openaiApiKey);
|
|
72971
|
+
case "xai":
|
|
72972
|
+
return Boolean(config.xaiApiKey);
|
|
72973
|
+
case "mistral":
|
|
72974
|
+
return Boolean(config.mistralApiKey);
|
|
72975
|
+
case "deepseek":
|
|
72976
|
+
return Boolean(config.deepseekApiKey);
|
|
72977
|
+
case "openrouter":
|
|
72978
|
+
return Boolean(config.openrouterApiKey);
|
|
72979
|
+
case "custom":
|
|
72980
|
+
return Boolean(config.custom.baseURL && config.custom.modelName);
|
|
72981
|
+
}
|
|
72982
|
+
}
|
|
72983
|
+
function getAvailableProviders(config = getEnvConfig()) {
|
|
72984
|
+
const providers = [];
|
|
72985
|
+
if (hasProviderConfig("gemini", config))
|
|
72986
|
+
providers.push("gemini");
|
|
72987
|
+
if (hasProviderConfig("anthropic", config))
|
|
72988
|
+
providers.push("anthropic");
|
|
72989
|
+
if (hasProviderConfig("openai", config))
|
|
72990
|
+
providers.push("openai");
|
|
72991
|
+
if (hasProviderConfig("xai", config))
|
|
72992
|
+
providers.push("xai");
|
|
72993
|
+
if (hasProviderConfig("mistral", config))
|
|
72994
|
+
providers.push("mistral");
|
|
72995
|
+
if (hasProviderConfig("deepseek", config))
|
|
72996
|
+
providers.push("deepseek");
|
|
72997
|
+
if (hasProviderConfig("openrouter", config))
|
|
72998
|
+
providers.push("openrouter");
|
|
72999
|
+
if (hasProviderConfig("custom", config))
|
|
73000
|
+
providers.push("custom");
|
|
73001
|
+
return providers;
|
|
73002
|
+
}
|
|
73003
|
+
// src/services/todos/store.ts
|
|
73004
|
+
var VALID_STATUSES = [
|
|
73005
|
+
"pending",
|
|
73006
|
+
"in_progress",
|
|
73007
|
+
"completed",
|
|
73008
|
+
"cancelled",
|
|
73009
|
+
"blocked"
|
|
73010
|
+
];
|
|
73011
|
+
var MIN_ITEMS = 2;
|
|
73012
|
+
var MAX_ITEMS = 10;
|
|
73013
|
+
|
|
73014
|
+
class SessionLockQueue {
|
|
73015
|
+
queues = new Map;
|
|
73016
|
+
async runExclusive(sessionId, task) {
|
|
73017
|
+
const prev = this.queues.get(sessionId) ?? Promise.resolve();
|
|
73018
|
+
let release;
|
|
73019
|
+
const current = new Promise((resolve) => {
|
|
73020
|
+
release = resolve;
|
|
73021
|
+
});
|
|
73022
|
+
this.queues.set(sessionId, prev.then(() => current, () => current));
|
|
73023
|
+
try {
|
|
73024
|
+
await prev;
|
|
73025
|
+
return await task();
|
|
73026
|
+
} finally {
|
|
73027
|
+
release();
|
|
73028
|
+
if (this.queues.get(sessionId) === current) {
|
|
73029
|
+
this.queues.delete(sessionId);
|
|
73030
|
+
}
|
|
73031
|
+
}
|
|
73032
|
+
}
|
|
73033
|
+
}
|
|
73034
|
+
var sessionLocks = new SessionLockQueue;
|
|
73035
|
+
function sanitizeSessionId(sessionId) {
|
|
73036
|
+
if (!sessionId || typeof sessionId !== "string") {
|
|
73037
|
+
throw new Error("Invalid sessionId: must be a non-empty string");
|
|
73038
|
+
}
|
|
73039
|
+
const clean = sessionId.trim();
|
|
73040
|
+
if (!clean || clean.includes("..") || clean.includes("/") || clean.includes("\\")) {
|
|
73041
|
+
throw new Error(`Invalid sessionId for path resolution: "${sessionId}"`);
|
|
73042
|
+
}
|
|
73043
|
+
return clean;
|
|
73044
|
+
}
|
|
73045
|
+
function getSessionTodosDir(sessionId) {
|
|
73046
|
+
const safeId = sanitizeSessionId(sessionId);
|
|
73047
|
+
return join13(getSettingsDir(), "todos", safeId);
|
|
73048
|
+
}
|
|
73049
|
+
function getSessionTodosFilePath(sessionId) {
|
|
73050
|
+
return join13(getSessionTodosDir(sessionId), "todos.json");
|
|
73051
|
+
}
|
|
73052
|
+
function validateTodoList(todos) {
|
|
73053
|
+
if (!Array.isArray(todos)) {
|
|
73054
|
+
throw new Error("Invalid todos format: expected an array of todo items");
|
|
73055
|
+
}
|
|
73056
|
+
if (todos.length < MIN_ITEMS || todos.length > MAX_ITEMS) {
|
|
73057
|
+
throw new Error(`Todo list must contain between ${MIN_ITEMS} and ${MAX_ITEMS} items (received ${todos.length})`);
|
|
73058
|
+
}
|
|
73059
|
+
const seenIds = new Set;
|
|
73060
|
+
let inProgressCount = 0;
|
|
73061
|
+
const validated = [];
|
|
73062
|
+
for (let i = 0;i < todos.length; i++) {
|
|
73063
|
+
const item = todos[i];
|
|
73064
|
+
if (!item || typeof item !== "object") {
|
|
73065
|
+
throw new Error(`Invalid todo at index ${i}: item must be an object`);
|
|
73066
|
+
}
|
|
73067
|
+
const { id, description, status } = item;
|
|
73068
|
+
if (typeof id !== "string" || !id.trim()) {
|
|
73069
|
+
throw new Error(`Invalid todo at index ${i}: id must be a non-empty string`);
|
|
73070
|
+
}
|
|
73071
|
+
const trimmedId = id.trim();
|
|
73072
|
+
if (seenIds.has(trimmedId)) {
|
|
73073
|
+
throw new Error(`Duplicate todo id "${trimmedId}" at index ${i}`);
|
|
73074
|
+
}
|
|
73075
|
+
seenIds.add(trimmedId);
|
|
73076
|
+
if (typeof description !== "string" || !description.trim()) {
|
|
73077
|
+
throw new Error(`Invalid todo at index ${i} (${trimmedId}): description must be a non-empty string`);
|
|
73078
|
+
}
|
|
73079
|
+
if (typeof status !== "string" || !VALID_STATUSES.includes(status)) {
|
|
73080
|
+
throw new Error(`Invalid todo at index ${i} (${trimmedId}): status must be one of ${VALID_STATUSES.join(", ")} (got "${status}")`);
|
|
73081
|
+
}
|
|
73082
|
+
if (status === "in_progress") {
|
|
73083
|
+
inProgressCount++;
|
|
73084
|
+
}
|
|
73085
|
+
validated.push({
|
|
73086
|
+
id: trimmedId,
|
|
73087
|
+
description: description.trim(),
|
|
73088
|
+
status
|
|
73089
|
+
});
|
|
73090
|
+
}
|
|
73091
|
+
if (inProgressCount > 1) {
|
|
73092
|
+
throw new Error(`At most 1 todo item can be 'in_progress' at any time (found ${inProgressCount} in_progress items)`);
|
|
73093
|
+
}
|
|
73094
|
+
return validated;
|
|
73095
|
+
}
|
|
73096
|
+
async function readTodoList(sessionId) {
|
|
73097
|
+
const filePath = getSessionTodosFilePath(sessionId);
|
|
73098
|
+
if (!existsSync12(filePath)) {
|
|
73099
|
+
return null;
|
|
73100
|
+
}
|
|
73101
|
+
const raw = readFileSync12(filePath, "utf-8");
|
|
73102
|
+
let parsed;
|
|
73103
|
+
try {
|
|
73104
|
+
parsed = JSON.parse(raw);
|
|
73105
|
+
} catch (err) {
|
|
73106
|
+
throw new Error(`Corrupted todo state for session "${sessionId}": failed to parse JSON (${err instanceof Error ? err.message : String(err)})`);
|
|
73107
|
+
}
|
|
73108
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
73109
|
+
throw new Error(`Corrupted todo state for session "${sessionId}": root must be an object`);
|
|
73110
|
+
}
|
|
73111
|
+
const record = parsed;
|
|
73112
|
+
if (record.schemaVersion !== 1) {
|
|
73113
|
+
throw new Error(`Unsupported todo schemaVersion "${record.schemaVersion}" (expected 1) for session "${sessionId}"`);
|
|
73114
|
+
}
|
|
73115
|
+
if (typeof record.sessionId !== "string") {
|
|
73116
|
+
throw new Error(`Invalid todo state for session "${sessionId}": missing or invalid sessionId`);
|
|
73117
|
+
}
|
|
73118
|
+
const validatedTodos = validateTodoList(record.todos);
|
|
73119
|
+
return {
|
|
73120
|
+
schemaVersion: 1,
|
|
73121
|
+
sessionId: record.sessionId,
|
|
73122
|
+
updatedAt: typeof record.updatedAt === "string" ? record.updatedAt : new Date().toISOString(),
|
|
73123
|
+
todos: validatedTodos
|
|
73124
|
+
};
|
|
73125
|
+
}
|
|
73126
|
+
async function writeTodoList(sessionId, todos) {
|
|
73127
|
+
return sessionLocks.runExclusive(sessionId, async () => {
|
|
73128
|
+
const validated = validateTodoList(todos);
|
|
73129
|
+
const safeSessionId = sanitizeSessionId(sessionId);
|
|
73130
|
+
const dir = getSessionTodosDir(safeSessionId);
|
|
73131
|
+
if (!existsSync12(dir)) {
|
|
73132
|
+
mkdirSync9(dir, { recursive: true, mode: 448 });
|
|
73133
|
+
}
|
|
73134
|
+
const payload = {
|
|
73135
|
+
schemaVersion: 1,
|
|
73136
|
+
sessionId: safeSessionId,
|
|
73137
|
+
updatedAt: new Date().toISOString(),
|
|
73138
|
+
todos: validated
|
|
73139
|
+
};
|
|
73140
|
+
const targetPath = getSessionTodosFilePath(safeSessionId);
|
|
73141
|
+
const tempPath = join13(dir, `.todos.${randomUUID7()}.tmp`);
|
|
73142
|
+
const jsonStr = JSON.stringify(payload, null, 2) + `
|
|
73143
|
+
`;
|
|
73144
|
+
try {
|
|
73145
|
+
writeFileSync3(tempPath, jsonStr, { encoding: "utf-8", mode: 384 });
|
|
73146
|
+
const fd = openSync8(tempPath, "r");
|
|
73147
|
+
try {
|
|
73148
|
+
fsyncSync7(fd);
|
|
73149
|
+
} finally {
|
|
73150
|
+
closeSync8(fd);
|
|
73151
|
+
}
|
|
73152
|
+
renameSync7(tempPath, targetPath);
|
|
73153
|
+
} catch (err) {
|
|
73154
|
+
try {
|
|
73155
|
+
if (existsSync12(tempPath)) {
|
|
73156
|
+
unlinkSync7(tempPath);
|
|
73157
|
+
}
|
|
73158
|
+
} catch {}
|
|
73159
|
+
throw err;
|
|
73160
|
+
}
|
|
73161
|
+
return payload;
|
|
73162
|
+
});
|
|
73163
|
+
}
|
|
73164
|
+
async function updateTodoItem(sessionId, todoId, updates) {
|
|
73165
|
+
return sessionLocks.runExclusive(sessionId, async () => {
|
|
73166
|
+
if (!todoId || typeof todoId !== "string" || !todoId.trim()) {
|
|
73167
|
+
throw new Error("todoId must be a non-empty string");
|
|
73168
|
+
}
|
|
73169
|
+
const targetId = todoId.trim();
|
|
73170
|
+
const current = await readTodoList(sessionId);
|
|
73171
|
+
if (!current) {
|
|
73172
|
+
throw new Error(`No todo list found for session "${sessionId}". Create one first with TodoWrite.`);
|
|
73173
|
+
}
|
|
73174
|
+
const itemIndex = current.todos.findIndex((t) => t.id === targetId);
|
|
73175
|
+
if (itemIndex === -1) {
|
|
73176
|
+
throw new Error(`Todo item with id "${targetId}" not found in current session todo list`);
|
|
73177
|
+
}
|
|
73178
|
+
if (updates.status === undefined && updates.description === undefined) {
|
|
73179
|
+
throw new Error("At least one of status or description must be provided for update");
|
|
73180
|
+
}
|
|
73181
|
+
const currentItem = current.todos[itemIndex];
|
|
73182
|
+
const newStatus = updates.status !== undefined ? updates.status : currentItem.status;
|
|
73183
|
+
const newDescription = updates.description !== undefined ? updates.description.trim() : currentItem.description;
|
|
73184
|
+
if (!newDescription) {
|
|
73185
|
+
throw new Error("Updated description cannot be empty");
|
|
73186
|
+
}
|
|
73187
|
+
const updatedTodos = current.todos.map((item, idx) => {
|
|
73188
|
+
if (idx !== itemIndex) {
|
|
73189
|
+
return item;
|
|
73190
|
+
}
|
|
73191
|
+
return {
|
|
73192
|
+
...item,
|
|
73193
|
+
status: newStatus,
|
|
73194
|
+
description: newDescription
|
|
73195
|
+
};
|
|
73196
|
+
});
|
|
73197
|
+
const validated = validateTodoList(updatedTodos);
|
|
73198
|
+
const safeSessionId = sanitizeSessionId(sessionId);
|
|
73199
|
+
const dir = getSessionTodosDir(safeSessionId);
|
|
73200
|
+
if (!existsSync12(dir)) {
|
|
73201
|
+
mkdirSync9(dir, { recursive: true, mode: 448 });
|
|
73202
|
+
}
|
|
73203
|
+
const payload = {
|
|
73204
|
+
schemaVersion: 1,
|
|
73205
|
+
sessionId: safeSessionId,
|
|
73206
|
+
updatedAt: new Date().toISOString(),
|
|
73207
|
+
todos: validated
|
|
73208
|
+
};
|
|
73209
|
+
const targetPath = getSessionTodosFilePath(safeSessionId);
|
|
73210
|
+
const tempPath = join13(dir, `.todos.${randomUUID7()}.tmp`);
|
|
73211
|
+
const jsonStr = JSON.stringify(payload, null, 2) + `
|
|
73212
|
+
`;
|
|
73213
|
+
try {
|
|
73214
|
+
writeFileSync3(tempPath, jsonStr, { encoding: "utf-8", mode: 384 });
|
|
73215
|
+
const fd = openSync8(tempPath, "r");
|
|
73216
|
+
try {
|
|
73217
|
+
fsyncSync7(fd);
|
|
73218
|
+
} finally {
|
|
73219
|
+
closeSync8(fd);
|
|
73220
|
+
}
|
|
73221
|
+
renameSync7(tempPath, targetPath);
|
|
73222
|
+
} catch (err) {
|
|
73223
|
+
try {
|
|
73224
|
+
if (existsSync12(tempPath)) {
|
|
73225
|
+
unlinkSync7(tempPath);
|
|
73226
|
+
}
|
|
73227
|
+
} catch {}
|
|
73228
|
+
throw err;
|
|
73229
|
+
}
|
|
73230
|
+
return payload;
|
|
73231
|
+
});
|
|
73232
|
+
}
|
|
73233
|
+
// src/tools/todo-write/index.ts
|
|
73234
|
+
var todoItemSchema = object({
|
|
73235
|
+
id: string2().min(1, "id is required").describe("Unique identifier for the todo item"),
|
|
73236
|
+
description: string2().min(1, "description is required").describe("Clear description of the task"),
|
|
73237
|
+
status: _enum(["pending", "in_progress", "completed", "cancelled", "blocked"]).describe("Current status of the todo item")
|
|
73238
|
+
});
|
|
73239
|
+
var todoWriteParamsSchema = object({
|
|
73240
|
+
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")
|
|
73241
|
+
});
|
|
73242
|
+
function formatTodoCheckbox(status) {
|
|
73243
|
+
switch (status) {
|
|
73244
|
+
case "completed":
|
|
73245
|
+
return "[x]";
|
|
73246
|
+
case "in_progress":
|
|
73247
|
+
return `[${figures.sliderPointer}]`;
|
|
73248
|
+
case "cancelled":
|
|
73249
|
+
return `[${figures.cross}]`;
|
|
73250
|
+
case "blocked":
|
|
73251
|
+
return `[${figures.warning}]`;
|
|
73252
|
+
case "pending":
|
|
73253
|
+
default:
|
|
73254
|
+
return "[ ]";
|
|
73255
|
+
}
|
|
73256
|
+
}
|
|
73257
|
+
function formatTodoListSummary(todos, maxItems = 10) {
|
|
73258
|
+
const visible = todos.slice(0, maxItems);
|
|
73259
|
+
const hiddenCount = todos.length - visible.length;
|
|
73260
|
+
const lines = visible.map((t, i) => `${i + 1}. ${formatTodoCheckbox(t.status)} ${t.description}`);
|
|
73261
|
+
if (hiddenCount > 0) {
|
|
73262
|
+
lines.push(`… and ${hiddenCount} more`);
|
|
73263
|
+
}
|
|
73264
|
+
return lines.join(`
|
|
73265
|
+
`);
|
|
73266
|
+
}
|
|
73267
|
+
var todoWriteTool = {
|
|
73268
|
+
name: "TodoWrite",
|
|
73269
|
+
displayName: "TodoWrite",
|
|
73270
|
+
icon: "\uD83D\uDCDD",
|
|
73271
|
+
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.",
|
|
73272
|
+
parameters: todoWriteParamsSchema,
|
|
73273
|
+
summarizeArgs(args) {
|
|
73274
|
+
if (!args.todos || args.todos.length === 0)
|
|
73275
|
+
return "";
|
|
73276
|
+
if (args.todos.length <= 2) {
|
|
73277
|
+
return args.todos.map((t) => `"${t.description}"`).join(", ");
|
|
73278
|
+
}
|
|
73279
|
+
return `"${args.todos[0]?.description}", "${args.todos[1]?.description}", +${args.todos.length - 2} more`;
|
|
73280
|
+
},
|
|
73281
|
+
async execute(args, context) {
|
|
73282
|
+
if (!context.sessionId) {
|
|
73283
|
+
throw new Error("TodoWrite requires an active session ID in ToolContext");
|
|
73284
|
+
}
|
|
73285
|
+
return writeTodoList(context.sessionId, args.todos);
|
|
73286
|
+
},
|
|
73287
|
+
summarize(args, result) {
|
|
73288
|
+
const list = result?.todos ?? args.todos;
|
|
73289
|
+
const total = list.length;
|
|
73290
|
+
const header = `Added ${total} todos`;
|
|
73291
|
+
const details = formatTodoListSummary(list);
|
|
73292
|
+
return `${header}
|
|
73293
|
+
${details}`;
|
|
73294
|
+
}
|
|
73295
|
+
};
|
|
73296
|
+
|
|
73297
|
+
// src/tools/todo-update/index.ts
|
|
73298
|
+
var todoUpdateParamsSchema = object({
|
|
73299
|
+
id: string2().min(1, "id is required").describe("ID of the existing todo item to update"),
|
|
73300
|
+
status: _enum(["pending", "in_progress", "completed", "cancelled", "blocked"]).optional().describe("New status for the todo item"),
|
|
73301
|
+
description: string2().min(1).optional().describe("Updated description for the todo item")
|
|
73302
|
+
});
|
|
73303
|
+
function formatTodoCheckbox2(status) {
|
|
73304
|
+
switch (status) {
|
|
73305
|
+
case "completed":
|
|
73306
|
+
return "[x]";
|
|
73307
|
+
case "in_progress":
|
|
73308
|
+
return `[${figures.sliderPointer}]`;
|
|
73309
|
+
case "cancelled":
|
|
73310
|
+
return `[${figures.cross}]`;
|
|
73311
|
+
case "blocked":
|
|
73312
|
+
return `[${figures.warning}]`;
|
|
73313
|
+
case "pending":
|
|
73314
|
+
default:
|
|
73315
|
+
return "[ ]";
|
|
73316
|
+
}
|
|
73317
|
+
}
|
|
73318
|
+
function formatTodoListSummary2(todos, maxItems = 10) {
|
|
73319
|
+
const visible = todos.slice(0, maxItems);
|
|
73320
|
+
const hiddenCount = todos.length - visible.length;
|
|
73321
|
+
const lines = visible.map((t, i) => `${i + 1}. ${formatTodoCheckbox2(t.status)} ${t.description}`);
|
|
73322
|
+
if (hiddenCount > 0) {
|
|
73323
|
+
lines.push(`… and ${hiddenCount} more`);
|
|
73324
|
+
}
|
|
73325
|
+
return lines.join(`
|
|
73326
|
+
`);
|
|
73327
|
+
}
|
|
73328
|
+
var todoUpdateTool = {
|
|
73329
|
+
name: "TodoUpdate",
|
|
73330
|
+
displayName: "TodoUpdate",
|
|
73331
|
+
icon: "✓",
|
|
73332
|
+
description: "Updates a single todo item status or description in the current session. Cannot create new items or update multiple items at once.",
|
|
73333
|
+
parameters: todoUpdateParamsSchema,
|
|
73334
|
+
summarizeArgs(args) {
|
|
73335
|
+
const parts = [`id: "${args.id}"`];
|
|
73336
|
+
if (args.status)
|
|
73337
|
+
parts.push(`status: "${args.status}"`);
|
|
73338
|
+
if (args.description)
|
|
73339
|
+
parts.push(`desc: "${args.description}"`);
|
|
73340
|
+
return parts.join(", ");
|
|
73341
|
+
},
|
|
73342
|
+
async execute(args, context) {
|
|
73343
|
+
if (!context.sessionId) {
|
|
73344
|
+
throw new Error("TodoUpdate requires an active session ID in ToolContext");
|
|
73345
|
+
}
|
|
73346
|
+
return updateTodoItem(context.sessionId, args.id, {
|
|
73347
|
+
status: args.status,
|
|
73348
|
+
description: args.description
|
|
73349
|
+
});
|
|
73350
|
+
},
|
|
73351
|
+
summarize(args, result) {
|
|
73352
|
+
if (!result || !result.todos) {
|
|
73353
|
+
return `Todo ${args.id} updated`;
|
|
73354
|
+
}
|
|
73355
|
+
const total = result.todos.length;
|
|
73356
|
+
const completed = result.todos.filter((t) => t.status === "completed").length;
|
|
73357
|
+
const updatedItem = result.todos.find((t) => t.id === args.id);
|
|
73358
|
+
const statusText = updatedItem?.status ?? args.status ?? "updated";
|
|
73359
|
+
const header = `Todo ${args.id} ${statusText} · ${completed}/${total} done`;
|
|
73360
|
+
const details = formatTodoListSummary2(result.todos);
|
|
73361
|
+
return `${header}
|
|
73362
|
+
${details}`;
|
|
73363
|
+
}
|
|
73364
|
+
};
|
|
73365
|
+
|
|
73366
|
+
// src/tools/todo-read/index.ts
|
|
73367
|
+
var todoReadParamsSchema = object({});
|
|
73368
|
+
function formatTodoCheckbox3(status) {
|
|
73369
|
+
switch (status) {
|
|
73370
|
+
case "completed":
|
|
73371
|
+
return "[x]";
|
|
73372
|
+
case "in_progress":
|
|
73373
|
+
return `[${figures.sliderPointer}]`;
|
|
73374
|
+
case "cancelled":
|
|
73375
|
+
return `[${figures.cross}]`;
|
|
73376
|
+
case "blocked":
|
|
73377
|
+
return `[${figures.warning}]`;
|
|
73378
|
+
case "pending":
|
|
73379
|
+
default:
|
|
73380
|
+
return "[ ]";
|
|
73381
|
+
}
|
|
73382
|
+
}
|
|
73383
|
+
function formatTodoListSummary3(todos, maxItems = 10) {
|
|
73384
|
+
const visible = todos.slice(0, maxItems);
|
|
73385
|
+
const hiddenCount = todos.length - visible.length;
|
|
73386
|
+
const lines = visible.map((t, i) => `${i + 1}. ${formatTodoCheckbox3(t.status)} ${t.description}`);
|
|
73387
|
+
if (hiddenCount > 0) {
|
|
73388
|
+
lines.push(`… and ${hiddenCount} more`);
|
|
73389
|
+
}
|
|
73390
|
+
return lines.join(`
|
|
73391
|
+
`);
|
|
73392
|
+
}
|
|
73393
|
+
var todoReadTool = {
|
|
73394
|
+
name: "TodoRead",
|
|
73395
|
+
displayName: "TodoRead",
|
|
73396
|
+
icon: "\uD83D\uDCCB",
|
|
73397
|
+
description: "Reads the current session todo list without modifying it. Returns an empty result if no todos have been established yet.",
|
|
73398
|
+
parameters: todoReadParamsSchema,
|
|
73399
|
+
summarizeArgs() {
|
|
73400
|
+
return "";
|
|
73401
|
+
},
|
|
73402
|
+
async execute(_args, context) {
|
|
73403
|
+
if (!context.sessionId) {
|
|
73404
|
+
throw new Error("TodoRead requires an active session ID in ToolContext");
|
|
73405
|
+
}
|
|
73406
|
+
const list = await readTodoList(context.sessionId);
|
|
73407
|
+
if (!list) {
|
|
73408
|
+
return {
|
|
73409
|
+
hasTodos: false,
|
|
73410
|
+
todos: [],
|
|
73411
|
+
message: "No todos currently established for this session."
|
|
73412
|
+
};
|
|
73413
|
+
}
|
|
73414
|
+
return {
|
|
73415
|
+
hasTodos: true,
|
|
73416
|
+
todos: list.todos,
|
|
73417
|
+
updatedAt: list.updatedAt
|
|
73418
|
+
};
|
|
73419
|
+
},
|
|
73420
|
+
summarize(_args, result) {
|
|
73421
|
+
if (!result || !result.hasTodos || result.todos.length === 0) {
|
|
73422
|
+
return "No active todos";
|
|
73423
|
+
}
|
|
73424
|
+
const total = result.todos.length;
|
|
73425
|
+
const completed = result.todos.filter((t) => t.status === "completed").length;
|
|
73426
|
+
const active = result.todos.find((t) => t.status === "in_progress");
|
|
73427
|
+
const header = active ? `Todos ${completed}/${total} done · ▶ ${active.description}` : `Todos ${completed}/${total} done`;
|
|
73428
|
+
const details = formatTodoListSummary3(result.todos);
|
|
73429
|
+
return `${header}
|
|
73430
|
+
${details}`;
|
|
73431
|
+
}
|
|
73432
|
+
};
|
|
73433
|
+
// src/skills/discovery.ts
|
|
73434
|
+
import { existsSync as existsSync13, readdirSync as readdirSync5, readFileSync as readFileSync13, statSync as statSync6 } from "node:fs";
|
|
73435
|
+
import { homedir as homedir5 } from "node:os";
|
|
73436
|
+
import { isAbsolute as isAbsolute6, join as join14, normalize, resolve as resolve9, sep as sep3 } from "node:path";
|
|
73437
|
+
var MAX_SKILL_FILE_SIZE = 64 * 1024;
|
|
73438
|
+
function parseSkillFrontmatter(content) {
|
|
73439
|
+
const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
73440
|
+
if (!match || !match[1]) {
|
|
73441
|
+
return null;
|
|
73442
|
+
}
|
|
73443
|
+
const rawYaml = match[1];
|
|
73444
|
+
const lines = rawYaml.split(/\r?\n/);
|
|
73445
|
+
const result = {};
|
|
73446
|
+
let currentKey = null;
|
|
73447
|
+
let descriptionLines = [];
|
|
73448
|
+
for (const line of lines) {
|
|
73449
|
+
const trimmed = line.trim();
|
|
73450
|
+
if (!trimmed || trimmed.startsWith("#"))
|
|
73451
|
+
continue;
|
|
73452
|
+
const keyMatch = line.match(/^([a-zA-Z0-9_-]+):\s*(.*)$/);
|
|
73453
|
+
if (keyMatch && keyMatch[1]) {
|
|
73454
|
+
const key = keyMatch[1].toLowerCase();
|
|
73455
|
+
const val = (keyMatch[2] ?? "").trim();
|
|
73456
|
+
if (key === "name") {
|
|
73457
|
+
currentKey = "name";
|
|
73458
|
+
result.name = unquote(val);
|
|
73459
|
+
} else if (key === "description") {
|
|
73460
|
+
currentKey = "description";
|
|
73461
|
+
if (val === ">" || val === ">-" || val === "|" || val === "|-") {
|
|
73462
|
+
descriptionLines = [];
|
|
73463
|
+
} else if (val) {
|
|
73464
|
+
descriptionLines = [unquote(val)];
|
|
73465
|
+
} else {
|
|
73466
|
+
descriptionLines = [];
|
|
73467
|
+
}
|
|
73468
|
+
} else {
|
|
73469
|
+
currentKey = null;
|
|
73470
|
+
}
|
|
73471
|
+
} else if (currentKey === "description" && (line.startsWith(" ") || line.startsWith("\t"))) {
|
|
73472
|
+
descriptionLines.push(trimmed);
|
|
73473
|
+
}
|
|
73474
|
+
}
|
|
73475
|
+
if (descriptionLines.length > 0) {
|
|
73476
|
+
result.description = descriptionLines.join(" ").trim();
|
|
73477
|
+
}
|
|
73478
|
+
return result;
|
|
73479
|
+
}
|
|
73480
|
+
function unquote(str) {
|
|
73481
|
+
const trimmed = str.trim();
|
|
73482
|
+
if (trimmed.startsWith('"') && trimmed.endsWith('"') || trimmed.startsWith("'") && trimmed.endsWith("'")) {
|
|
73483
|
+
return trimmed.slice(1, -1);
|
|
73484
|
+
}
|
|
73485
|
+
return trimmed;
|
|
73486
|
+
}
|
|
73487
|
+
function discoverSkills(cwd = process.cwd()) {
|
|
73488
|
+
const targets = [
|
|
73489
|
+
{ dir: join14(cwd, ".agents", "skills"), source: "workspace" },
|
|
73490
|
+
{ dir: join14(homedir5(), ".agents", "skills"), source: "user-agents" },
|
|
73491
|
+
{ dir: join14(homedir5(), ".steward", "skills"), source: "user-steward" }
|
|
73492
|
+
];
|
|
73493
|
+
const skillMap = new Map;
|
|
73494
|
+
for (const target of targets) {
|
|
73495
|
+
if (!existsSync13(target.dir)) {
|
|
73496
|
+
continue;
|
|
73497
|
+
}
|
|
73498
|
+
try {
|
|
73499
|
+
const entries = readdirSync5(target.dir, { withFileTypes: true });
|
|
73500
|
+
for (const entry of entries) {
|
|
73501
|
+
if (!entry.isDirectory())
|
|
73502
|
+
continue;
|
|
73503
|
+
const skillDir = join14(target.dir, entry.name);
|
|
73504
|
+
const skillMdPath = join14(skillDir, "SKILL.md");
|
|
73505
|
+
const fallbackMdPath = join14(skillDir, "skill.md");
|
|
73506
|
+
const resolvedFile = existsSync13(skillMdPath) ? skillMdPath : existsSync13(fallbackMdPath) ? fallbackMdPath : null;
|
|
73507
|
+
if (!resolvedFile)
|
|
73508
|
+
continue;
|
|
73509
|
+
try {
|
|
73510
|
+
const raw = readFileSync13(resolvedFile, "utf-8");
|
|
73511
|
+
const frontmatter = parseSkillFrontmatter(raw);
|
|
73512
|
+
const skillName = frontmatter?.name || entry.name;
|
|
73513
|
+
const description = frontmatter?.description || "No description provided.";
|
|
73514
|
+
if (!skillMap.has(skillName)) {
|
|
73515
|
+
skillMap.set(skillName, {
|
|
73516
|
+
name: skillName,
|
|
73517
|
+
description,
|
|
73518
|
+
filePath: resolvedFile,
|
|
73519
|
+
dirPath: skillDir,
|
|
73520
|
+
source: target.source
|
|
73521
|
+
});
|
|
73522
|
+
}
|
|
73523
|
+
} catch {}
|
|
73524
|
+
}
|
|
73525
|
+
} catch {}
|
|
73526
|
+
}
|
|
73527
|
+
return Array.from(skillMap.values());
|
|
73528
|
+
}
|
|
73529
|
+
function getSkill(name, cwd = process.cwd()) {
|
|
73530
|
+
const cleanName = name.trim();
|
|
73531
|
+
const all = discoverSkills(cwd);
|
|
73532
|
+
return all.find((s) => s.name.toLowerCase() === cleanName.toLowerCase()) ?? null;
|
|
73533
|
+
}
|
|
73534
|
+
function readSkillResource(name, relativePath, cwd = process.cwd(), maxBytes = MAX_SKILL_FILE_SIZE) {
|
|
73535
|
+
const skill = getSkill(name, cwd);
|
|
73536
|
+
if (!skill) {
|
|
73537
|
+
throw new Error(`Skill "${name}" not found`);
|
|
73538
|
+
}
|
|
73539
|
+
let targetFilePath;
|
|
73540
|
+
if (!relativePath || !relativePath.trim()) {
|
|
73541
|
+
targetFilePath = skill.filePath;
|
|
73542
|
+
} else {
|
|
73543
|
+
const trimmedPath = relativePath.trim();
|
|
73544
|
+
if (isAbsolute6(trimmedPath)) {
|
|
73545
|
+
throw new Error(`Invalid skill resource path: absolute paths are not permitted (${trimmedPath})`);
|
|
73546
|
+
}
|
|
73547
|
+
const normalizedRelative = normalize(trimmedPath);
|
|
73548
|
+
if (normalizedRelative.startsWith("..") || normalizedRelative.includes(`${sep3}..`)) {
|
|
73549
|
+
throw new Error(`Invalid skill resource path: directory traversal ("..") is forbidden (${trimmedPath})`);
|
|
73550
|
+
}
|
|
73551
|
+
targetFilePath = resolve9(skill.dirPath, normalizedRelative);
|
|
73552
|
+
const resolvedDir = resolve9(skill.dirPath);
|
|
73553
|
+
if (!targetFilePath.startsWith(resolvedDir + sep3) && targetFilePath !== resolvedDir) {
|
|
73554
|
+
throw new Error(`Resource "${trimmedPath}" resolves outside skill directory`);
|
|
73555
|
+
}
|
|
73556
|
+
}
|
|
73557
|
+
if (!existsSync13(targetFilePath)) {
|
|
73558
|
+
throw new Error(`Skill resource file "${targetFilePath}" does not exist`);
|
|
73559
|
+
}
|
|
73560
|
+
const stat = statSync6(targetFilePath);
|
|
73561
|
+
if (!stat.isFile()) {
|
|
73562
|
+
throw new Error(`Skill resource path "${targetFilePath}" is not a regular file`);
|
|
73563
|
+
}
|
|
73564
|
+
if (stat.size > maxBytes) {
|
|
73565
|
+
throw new Error(`Skill resource file exceeds size limit of ${maxBytes} bytes (file is ${stat.size} bytes)`);
|
|
73566
|
+
}
|
|
73567
|
+
const content = readFileSync13(targetFilePath, "utf-8");
|
|
73568
|
+
return {
|
|
73569
|
+
name: skill.name,
|
|
73570
|
+
description: skill.description,
|
|
73571
|
+
source: skill.source,
|
|
73572
|
+
resourcePath: targetFilePath,
|
|
73573
|
+
content
|
|
73574
|
+
};
|
|
73575
|
+
}
|
|
73576
|
+
// src/tools/skill-list/index.ts
|
|
73577
|
+
var skillListParamsSchema = object({});
|
|
73578
|
+
var skillListTool = {
|
|
73579
|
+
name: "SkillList",
|
|
73580
|
+
displayName: "SkillList",
|
|
73581
|
+
icon: "\uD83D\uDCA1",
|
|
73582
|
+
description: "Discovers and lists all available specialized skills and their descriptions. Use this when you need specialized domain knowledge or workflows.",
|
|
73583
|
+
parameters: skillListParamsSchema,
|
|
73584
|
+
summarizeArgs() {
|
|
73585
|
+
return "";
|
|
73586
|
+
},
|
|
73587
|
+
async execute(_args, context) {
|
|
73588
|
+
const cwd = context?.cwd ?? process.cwd();
|
|
73589
|
+
const skills = discoverSkills(cwd);
|
|
73590
|
+
return skills.map((s) => ({
|
|
73591
|
+
name: s.name,
|
|
73592
|
+
description: s.description,
|
|
73593
|
+
source: s.source
|
|
73594
|
+
})).sort((a, b) => a.name.localeCompare(b.name));
|
|
73595
|
+
},
|
|
73596
|
+
summarize(_args, result) {
|
|
73597
|
+
const count = result?.length ?? 0;
|
|
73598
|
+
if (count === 0) {
|
|
73599
|
+
return "Discovered 0 skills";
|
|
73600
|
+
}
|
|
73601
|
+
return `Discovered ${count} skill${count === 1 ? "" : "s"}`;
|
|
73602
|
+
}
|
|
73603
|
+
};
|
|
73604
|
+
|
|
73605
|
+
// src/tools/skill-read/index.ts
|
|
73606
|
+
var skillReadParamsSchema = object({
|
|
73607
|
+
name: string2().min(1, "Skill name is required"),
|
|
73608
|
+
path: string2().optional()
|
|
73609
|
+
});
|
|
73610
|
+
var skillReadTool = {
|
|
73611
|
+
name: "SkillRead",
|
|
73612
|
+
displayName: "SkillRead",
|
|
73613
|
+
icon: "\uD83D\uDCD6",
|
|
73614
|
+
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.",
|
|
73615
|
+
parameters: skillReadParamsSchema,
|
|
73616
|
+
summarizeArgs(args) {
|
|
73617
|
+
if (args.path) {
|
|
73618
|
+
return `name="${args.name}" path="${args.path}"`;
|
|
73619
|
+
}
|
|
73620
|
+
return `name="${args.name}"`;
|
|
73621
|
+
},
|
|
73622
|
+
async execute(args, context) {
|
|
73623
|
+
const cwd = context?.cwd ?? process.cwd();
|
|
73624
|
+
return readSkillResource(args.name, args.path, cwd);
|
|
73625
|
+
},
|
|
73626
|
+
summarize(args, result) {
|
|
73627
|
+
const target = args.path ? `${args.name}/${args.path}` : args.name;
|
|
73628
|
+
const len = result?.content ? ` (${result.content.length} bytes)` : "";
|
|
73629
|
+
return `SkillRead: ${target}${len}`;
|
|
73630
|
+
}
|
|
73631
|
+
};
|
|
72899
73632
|
|
|
72900
73633
|
// src/tools/index.ts
|
|
72901
73634
|
var builtInTools = [
|
|
@@ -72911,7 +73644,12 @@ var builtInTools = [
|
|
|
72911
73644
|
taskSendInputTool,
|
|
72912
73645
|
taskKillTool,
|
|
72913
73646
|
webFetchTool,
|
|
72914
|
-
webSearchTool
|
|
73647
|
+
webSearchTool,
|
|
73648
|
+
todoWriteTool,
|
|
73649
|
+
todoUpdateTool,
|
|
73650
|
+
todoReadTool,
|
|
73651
|
+
skillListTool,
|
|
73652
|
+
skillReadTool
|
|
72915
73653
|
];
|
|
72916
73654
|
for (const tool of builtInTools) {
|
|
72917
73655
|
defaultToolCatalog.register(tool);
|
|
@@ -73118,84 +73856,6 @@ class ShellTaskManager {
|
|
|
73118
73856
|
}
|
|
73119
73857
|
}
|
|
73120
73858
|
}
|
|
73121
|
-
|
|
73122
|
-
// src/config/env.ts
|
|
73123
|
-
var ALL_PROVIDER_NAMES = [
|
|
73124
|
-
"gemini",
|
|
73125
|
-
"anthropic",
|
|
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
|
|
73156
|
-
}
|
|
73157
|
-
};
|
|
73158
|
-
}
|
|
73159
|
-
function hasProviderConfig(provider, config = getEnvConfig()) {
|
|
73160
|
-
switch (provider) {
|
|
73161
|
-
case "gemini":
|
|
73162
|
-
return Boolean(config.geminiApiKey);
|
|
73163
|
-
case "anthropic":
|
|
73164
|
-
return Boolean(config.anthropicApiKey);
|
|
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);
|
|
73177
|
-
}
|
|
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
|
-
}
|
|
73199
73859
|
// src/errors/classifier.ts
|
|
73200
73860
|
function classifyError(error) {
|
|
73201
73861
|
if (!error) {
|
|
@@ -73340,15 +74000,15 @@ function classifyError(error) {
|
|
|
73340
74000
|
};
|
|
73341
74001
|
}
|
|
73342
74002
|
// src/errors/logger.ts
|
|
73343
|
-
import { appendFileSync as appendFileSync3, existsSync as
|
|
73344
|
-
import { homedir as
|
|
73345
|
-
import { join as
|
|
74003
|
+
import { appendFileSync as appendFileSync3, existsSync as existsSync14, mkdirSync as mkdirSync10, writeFileSync as writeFileSync4 } from "node:fs";
|
|
74004
|
+
import { homedir as homedir6 } from "node:os";
|
|
74005
|
+
import { join as join15 } from "node:path";
|
|
73346
74006
|
var SENSITIVE_KEY_REGEX = /api[_-]?key|token|secret|password|auth|credential|bearer/i;
|
|
73347
74007
|
function getLogsRootDir() {
|
|
73348
74008
|
if (process.env.STEWARD_LOGS_DIR) {
|
|
73349
74009
|
return process.env.STEWARD_LOGS_DIR;
|
|
73350
74010
|
}
|
|
73351
|
-
return
|
|
74011
|
+
return join15(homedir6(), ".steward", "logs");
|
|
73352
74012
|
}
|
|
73353
74013
|
function getLogDateTime(d = new Date) {
|
|
73354
74014
|
const year = d.getFullYear();
|
|
@@ -73432,11 +74092,11 @@ function logError(error, contextInfo) {
|
|
|
73432
74092
|
try {
|
|
73433
74093
|
const rootDir = getLogsRootDir();
|
|
73434
74094
|
const { date, time } = getLogDateTime();
|
|
73435
|
-
const dateDir =
|
|
73436
|
-
if (!
|
|
73437
|
-
|
|
74095
|
+
const dateDir = join15(rootDir, date);
|
|
74096
|
+
if (!existsSync14(dateDir)) {
|
|
74097
|
+
mkdirSync10(dateDir, { recursive: true });
|
|
73438
74098
|
}
|
|
73439
|
-
const logPath =
|
|
74099
|
+
const logPath = join15(dateDir, `${time}.log`);
|
|
73440
74100
|
const structured = classifyError(error);
|
|
73441
74101
|
const mem = process.memoryUsage ? process.memoryUsage() : undefined;
|
|
73442
74102
|
const logEntry = {
|
|
@@ -73462,10 +74122,10 @@ function logError(error, contextInfo) {
|
|
|
73462
74122
|
` + JSON.stringify(logEntry, null, 2) + `
|
|
73463
74123
|
|
|
73464
74124
|
`;
|
|
73465
|
-
if (
|
|
74125
|
+
if (existsSync14(logPath)) {
|
|
73466
74126
|
appendFileSync3(logPath, formatted, "utf-8");
|
|
73467
74127
|
} else {
|
|
73468
|
-
|
|
74128
|
+
writeFileSync4(logPath, formatted, "utf-8");
|
|
73469
74129
|
}
|
|
73470
74130
|
return logPath;
|
|
73471
74131
|
} catch {
|
|
@@ -111123,7 +111783,7 @@ function createProviderExecutedToolFactory2({
|
|
|
111123
111783
|
supportsDeferredResults
|
|
111124
111784
|
});
|
|
111125
111785
|
}
|
|
111126
|
-
async function
|
|
111786
|
+
async function resolve10(value) {
|
|
111127
111787
|
if (typeof value === "function") {
|
|
111128
111788
|
value = value();
|
|
111129
111789
|
}
|
|
@@ -116349,7 +117009,7 @@ var XaiSpeechModel = class _XaiSpeechModel {
|
|
|
116349
117009
|
const { requestBody, warnings, withTimestamps } = await this.getArgs(options);
|
|
116350
117010
|
const { value, responseHeaders, rawValue } = await postJsonToApi4({
|
|
116351
117011
|
url: `${this.config.baseURL}/tts`,
|
|
116352
|
-
headers: combineHeaders4(this.config.headers ? await
|
|
117012
|
+
headers: combineHeaders4(this.config.headers ? await resolve10(this.config.headers) : undefined, options.headers),
|
|
116353
117013
|
body: requestBody,
|
|
116354
117014
|
failedResponseHandler: xaiFailedResponseHandler,
|
|
116355
117015
|
successfulResponseHandler: withTimestamps ? createJsonResponseHandler4(xaiSpeechTimestampsResponseSchema) : createBinaryResponseHandler2(),
|
|
@@ -117156,288 +117816,59 @@ function inferProviderFromModelId(modelId) {
|
|
|
117156
117816
|
}
|
|
117157
117817
|
return null;
|
|
117158
117818
|
}
|
|
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
117819
|
|
|
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
|
-
|
|
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
117820
|
// src/engine/system-prompt.ts
|
|
117271
117821
|
function buildSystemPrompt(options = {}) {
|
|
117272
117822
|
const cwd = options.cwd ?? process.cwd();
|
|
117273
117823
|
const platform = process.platform;
|
|
117274
|
-
const skills = options.skills ?? discoverSkills(cwd);
|
|
117275
117824
|
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>
|
|
117825
|
+
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.
|
|
117313
117826
|
|
|
117314
|
-
|
|
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>
|
|
117827
|
+
IMPORTANT: Assist with defensive security tasks only. Refuse to create, modify, or improve code intended for malicious use.
|
|
117319
117828
|
|
|
117320
|
-
|
|
117321
|
-
|
|
117322
|
-
|
|
117323
|
-
|
|
117324
|
-
|
|
117829
|
+
# Operating Principles
|
|
117830
|
+
- Inspect before editing: investigate existing files, directories, and patterns before making changes.
|
|
117831
|
+
- Minimal and idiomatic: prefer the smallest correct change that aligns with existing repository architecture and conventions.
|
|
117832
|
+
- Never assume external dependencies or libraries exist; check project configuration files (e.g. package.json, Cargo.toml, pyproject.toml) first.
|
|
117833
|
+
- Security and secrets: never expose, commit, or log API keys, credentials, or secrets.
|
|
117834
|
+
- Verify your changes: run relevant project verification or tests after making non-trivial modifications.
|
|
117835
|
+
- Never commit git changes unless the user explicitly requests you to commit.
|
|
117836
|
+
- Provide direct, concise responses suitable for a terminal interface. Explain non-obvious failures or decisions clearly without unnecessary filler or commentary.
|
|
117837
|
+
- When referencing code locations, use the format: \`file_path:line_number\`.
|
|
117838
|
+
- Avoid adding redundant comments unless explaining non-obvious invariants or explicitly requested.
|
|
117325
117839
|
|
|
117326
|
-
|
|
117327
|
-
|
|
117328
|
-
|
|
117329
|
-
|
|
117330
|
-
|
|
117840
|
+
# Tool Usage Policy
|
|
117841
|
+
- Investigation: use read_file, grep, list_dir, and glob to explore code before modifying it.
|
|
117842
|
+
- Mutations: use write_file and edit_file to apply changes. Respect user permission prompts.
|
|
117843
|
+
- Commands: when using bash, you MUST always provide a concise, non-empty 'explanation' parameter describing what the command does and why it is needed.
|
|
117844
|
+
- Background tasks: use task_read to check status/output, task_send_input to send standard input (with a trailing newline), and task_kill to terminate.
|
|
117845
|
+
- 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.
|
|
117331
117846
|
|
|
117332
|
-
|
|
117333
|
-
|
|
117334
|
-
|
|
117335
|
-
|
|
117336
|
-
|
|
117847
|
+
# Task and Plan Management (Todos)
|
|
117848
|
+
- For multi-step, complex, or verification-heavy tasks, maintain operational state using session todos:
|
|
117849
|
+
- TodoWrite: initialize or replace the session todo list (must contain 2 to 10 items).
|
|
117850
|
+
- TodoUpdate: update exactly one todo item status or description as progress occurs.
|
|
117851
|
+
- TodoRead: inspect current session todos when recovering or verifying progress.
|
|
117852
|
+
- Keep at most one item in 'in_progress' status at any time.
|
|
117853
|
+
- Mark todos completed immediately after completing each step; do not batch progress updates.
|
|
117854
|
+
- For simple, one-step queries or edits, do not create a todo list.
|
|
117337
117855
|
|
|
117338
|
-
|
|
117339
|
-
|
|
117340
|
-
|
|
117341
|
-
|
|
117342
|
-
|
|
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.
|
|
117856
|
+
# Specialized Skills Policy
|
|
117857
|
+
- Skills are discoverable on-demand packages of specialized instructions located in workspace or user configuration.
|
|
117858
|
+
- Use SkillList to discover available skills and their descriptions.
|
|
117859
|
+
- Use SkillRead to load a skill's instructions (SKILL.md) or relative resources only when a task requires specialized domain guidance.
|
|
117860
|
+
- Skill instructions are supplemental and must not override core safety rules, tool boundaries, or user permission constraints.
|
|
117345
117861
|
|
|
117346
|
-
|
|
117347
|
-
|
|
117348
|
-
|
|
117349
|
-
|
|
117350
|
-
|
|
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.
|
|
117355
|
-
|
|
117356
|
-
# Task Management (Planning)
|
|
117357
|
-
You have access to the write file and edit file tools to help you manage and plan tasks alongside normal file/code creation. Use these tools VERY frequently to ensure that you are tracking your tasks and giving the user visibility into your progress.
|
|
117358
|
-
These tools are also EXTREMELY helpful for planning tasks and for breaking down larger complex tasks into smaller steps. If you do not use this tool when planning, you may forget to do important tasks — and that is unacceptable.
|
|
117359
|
-
IMPORTANT: Write plans/tasks in the codebase/repository's .steward/plans/ directory and write them in standard Markdown format. For example, a terminal TUI library plan: [project dir]/.steward/plans/tui_library_plan.md, an HTTP API server plan: [project dir]/.steward/plans/api_server_implementation_plan.md.
|
|
117360
|
-
IMPORTANT: If the user asks to create the plan in a different directory than the standard [project dir]/.steward/plans/, proceed normally but warn the USER that it is not recommended to make plans outside the .steward/plans/ directory.
|
|
117361
|
-
IMPORTANT: Always use proper names for the plan/task files (wrong: plan.md, plan_12.md, implementation.md; right: code_refactor_plan.md, codebase_migration_plan.md).
|
|
117362
|
-
IMPORTANT: It is **VERY VERY IMPORTANT** to always put a todo list between the main title header and the content of the plan. It is critical that you mark todos as completed as soon as you are done with a task. **NEVER** batch up multiple tasks before marking them as completed.
|
|
117363
|
-
|
|
117364
|
-
<example>
|
|
117365
|
-
user: Run the build and fix any type errors.
|
|
117366
|
-
assistant: I'm going to use the TodoWrite tool to write the following items to the todo list:
|
|
117367
|
-
- Run the build
|
|
117368
|
-
- Fix any type errors
|
|
117369
|
-
[assistant uses write_file to write the task todo/plan in [project dir]/.steward/plans/fixing_type_errors_plan.md]
|
|
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.
|
|
117382
|
-
|
|
117383
|
-
<example>
|
|
117384
|
-
user: Help me write a new feature that allows users to track their usage metrics and export them to various formats
|
|
117385
|
-
|
|
117386
|
-
assistant: I'll help you implement a usage metrics tracking and export feature. Let me first use the write file tool to plan this task.
|
|
117387
|
-
Adding the following todos to the todo list:
|
|
117388
|
-
1. Research existing metrics tracking in the codebase
|
|
117389
|
-
2. Design the metrics collection system
|
|
117390
|
-
3. Implement core metrics tracking functionality
|
|
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>
|
|
117400
|
-
|
|
117401
|
-
IMPORTANT: Completed todo: marked with ◉ beside the todo text and * beside the current working todo.
|
|
117402
|
-
|
|
117403
|
-
# Doing tasks
|
|
117404
|
-
The user will primarily request you perform software engineering tasks. This includes solving bugs, adding new functionality, refactoring code, explaining code, and more. For these tasks the following steps are recommended:
|
|
117405
|
-
- Use the write file tool to plan the task if required.
|
|
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
|
-
}
|
|
117862
|
+
<env>
|
|
117863
|
+
Working directory: ${cwd}
|
|
117864
|
+
Platform: ${platform}
|
|
117865
|
+
Today's date: ${dateUTC}
|
|
117866
|
+
</env>`;
|
|
117433
117867
|
if (options.userRules && options.userRules.length > 0) {
|
|
117434
117868
|
prompt += `
|
|
117435
117869
|
|
|
117436
117870
|
<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
|
-
|
|
117871
|
+
The following rules are configured by the user and must be followed:
|
|
117441
117872
|
${options.userRules.map((rule) => `- ${rule}`).join(`
|
|
117442
117873
|
`)}
|
|
117443
117874
|
</user_defined_rules>`;
|
|
@@ -117575,7 +118006,7 @@ class AgentSession {
|
|
|
117575
118006
|
this.messages.push(userMessage);
|
|
117576
118007
|
this.isGenerating = true;
|
|
117577
118008
|
this.activeAbortController = new AbortController;
|
|
117578
|
-
const turnId =
|
|
118009
|
+
const turnId = randomUUID8();
|
|
117579
118010
|
const turnStartedAt = new Date().toISOString();
|
|
117580
118011
|
const turnStartMonotonic = performance.now();
|
|
117581
118012
|
const turnModel = this.getModel();
|
|
@@ -117601,6 +118032,7 @@ class AgentSession {
|
|
|
117601
118032
|
await tracker.beginTurn(turnId, this.sessionData.turns.length + 1);
|
|
117602
118033
|
const toolContext = {
|
|
117603
118034
|
cwd,
|
|
118035
|
+
sessionId: this.sessionData.id,
|
|
117604
118036
|
abortSignal: this.activeAbortController.signal,
|
|
117605
118037
|
checkpointTracker: tracker,
|
|
117606
118038
|
mutationLocks: globalMutationLockManager,
|
|
@@ -117617,6 +118049,7 @@ class AgentSession {
|
|
|
117617
118049
|
activeTools = defaultToolCatalog.toAISDKTools(toolContext);
|
|
117618
118050
|
}
|
|
117619
118051
|
const instructions = buildSystemPrompt({
|
|
118052
|
+
cwd,
|
|
117620
118053
|
extraInstructions: options.extraInstructions
|
|
117621
118054
|
});
|
|
117622
118055
|
const wrappedOnEvent = (event) => {
|
|
@@ -119068,6 +119501,7 @@ class StatusBar extends Component {
|
|
|
119068
119501
|
clip = true;
|
|
119069
119502
|
warningTimer = null;
|
|
119070
119503
|
voiceTimer = null;
|
|
119504
|
+
updateTimer = null;
|
|
119071
119505
|
constructor(props) {
|
|
119072
119506
|
super(props);
|
|
119073
119507
|
this.state = {
|
|
@@ -119077,7 +119511,8 @@ class StatusBar extends Component {
|
|
|
119077
119511
|
exitPending: props.exitPending,
|
|
119078
119512
|
warning: props.warning,
|
|
119079
119513
|
voiceState: props.voiceState ?? "idle",
|
|
119080
|
-
voiceDurationSec: props.voiceDurationSec ?? 0
|
|
119514
|
+
voiceDurationSec: props.voiceDurationSec ?? 0,
|
|
119515
|
+
updateStatus: props.updateStatus
|
|
119081
119516
|
};
|
|
119082
119517
|
}
|
|
119083
119518
|
update(partial) {
|
|
@@ -119100,8 +119535,20 @@ class StatusBar extends Component {
|
|
|
119100
119535
|
});
|
|
119101
119536
|
}
|
|
119102
119537
|
}
|
|
119103
|
-
setUpdateStatus(updateStatus) {
|
|
119538
|
+
setUpdateStatus(updateStatus, durationMs) {
|
|
119539
|
+
if (this.updateTimer) {
|
|
119540
|
+
clearTimeout(this.updateTimer);
|
|
119541
|
+
this.updateTimer = null;
|
|
119542
|
+
}
|
|
119104
119543
|
this.setState({ updateStatus });
|
|
119544
|
+
if (updateStatus && durationMs && durationMs > 0) {
|
|
119545
|
+
this.updateTimer = setTimeout(() => {
|
|
119546
|
+
this.updateTimer = null;
|
|
119547
|
+
if (this.state.updateStatus === updateStatus) {
|
|
119548
|
+
this.setState({ updateStatus: undefined });
|
|
119549
|
+
}
|
|
119550
|
+
}, durationMs);
|
|
119551
|
+
}
|
|
119105
119552
|
}
|
|
119106
119553
|
showWarning(warningText, durationMs = 4000) {
|
|
119107
119554
|
if (this.warningTimer) {
|
|
@@ -119125,6 +119572,10 @@ class StatusBar extends Component {
|
|
|
119125
119572
|
clearInterval(this.voiceTimer);
|
|
119126
119573
|
this.voiceTimer = null;
|
|
119127
119574
|
}
|
|
119575
|
+
if (this.updateTimer) {
|
|
119576
|
+
clearTimeout(this.updateTimer);
|
|
119577
|
+
this.updateTimer = null;
|
|
119578
|
+
}
|
|
119128
119579
|
}
|
|
119129
119580
|
render(width) {
|
|
119130
119581
|
const theme = getTheme();
|
|
@@ -119158,14 +119609,10 @@ class StatusBar extends Component {
|
|
|
119158
119609
|
} else if (updateStatus && updateStatus.state !== "idle") {
|
|
119159
119610
|
if (updateStatus.state === "checking") {
|
|
119160
119611
|
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
119612
|
} 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");
|
|
119613
|
+
left = source_default.white(updateStatus.message ?? `Update v${updateStatus.version ?? ""} is available`);
|
|
119614
|
+
} else if (updateStatus.state === "no-updates") {
|
|
119615
|
+
left = source_default.white(updateStatus.message ?? "Steward is up to date");
|
|
119169
119616
|
} else if (updateStatus.state === "error" && updateStatus.message) {
|
|
119170
119617
|
left = themeColor2(theme.error)(updateStatus.message);
|
|
119171
119618
|
} else if (isBusy) {
|
|
@@ -119306,6 +119753,8 @@ class StreamingView extends Component {
|
|
|
119306
119753
|
const maxArgLen = Math.max(10, maxCols - toolName.length - 8);
|
|
119307
119754
|
const truncatedArg = targetArg.length > maxArgLen ? truncateMiddle(targetArg, maxArgLen) : targetArg;
|
|
119308
119755
|
line += `${source_default.dim("(")}${source_default.dim(truncatedArg)}${source_default.dim(")")}`;
|
|
119756
|
+
} else {
|
|
119757
|
+
line += `${source_default.dim("()")}`;
|
|
119309
119758
|
}
|
|
119310
119759
|
lines.push(line);
|
|
119311
119760
|
if (activeTool.recentLines && activeTool.recentLines.length > 0) {
|
|
@@ -122088,9 +122537,11 @@ class VoiceController {
|
|
|
122088
122537
|
}
|
|
122089
122538
|
}
|
|
122090
122539
|
// src/services/updater/service.ts
|
|
122091
|
-
import { spawn as spawn2 } from "node:child_process";
|
|
122092
122540
|
var DEFAULT_REPO = "sapirrior/steward";
|
|
122093
122541
|
function parseSemver(v) {
|
|
122542
|
+
if (!v || typeof v !== "string") {
|
|
122543
|
+
return [0, 0, 0];
|
|
122544
|
+
}
|
|
122094
122545
|
const clean = v.replace(/^v/i, "").trim().split("-")[0] ?? "";
|
|
122095
122546
|
const parts = clean.split(".").map((p) => parseInt(p, 10) || 0);
|
|
122096
122547
|
return [parts[0] ?? 0, parts[1] ?? 0, parts[2] ?? 0];
|
|
@@ -122109,14 +122560,13 @@ function isNewerVersion(current, latest) {
|
|
|
122109
122560
|
return lPat > cPat;
|
|
122110
122561
|
}
|
|
122111
122562
|
|
|
122112
|
-
class
|
|
122563
|
+
class UpdateCheckerService {
|
|
122113
122564
|
currentVersion;
|
|
122114
122565
|
repo;
|
|
122115
122566
|
state = "idle";
|
|
122116
122567
|
options;
|
|
122117
122568
|
checkTimer = null;
|
|
122118
|
-
|
|
122119
|
-
isUpdating = false;
|
|
122569
|
+
resetTimer = null;
|
|
122120
122570
|
constructor(options = {}) {
|
|
122121
122571
|
this.options = options;
|
|
122122
122572
|
this.currentVersion = options.currentVersion || package_default.version || "0.0.0";
|
|
@@ -122131,14 +122581,8 @@ class AutoUpdaterService {
|
|
|
122131
122581
|
getCurrentVersion() {
|
|
122132
122582
|
return this.currentVersion;
|
|
122133
122583
|
}
|
|
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`;
|
|
122584
|
+
get rawPackageJsonUrl() {
|
|
122585
|
+
return `https://raw.githubusercontent.com/${this.repo}/main/package.json`;
|
|
122142
122586
|
}
|
|
122143
122587
|
setState(state, info) {
|
|
122144
122588
|
this.state = state;
|
|
@@ -122148,156 +122592,94 @@ class AutoUpdaterService {
|
|
|
122148
122592
|
if (process.env.STEWARD_NO_UPDATE_CHECK === "1" || process.env.STEWARD_NO_UPDATE_CHECK === "true") {
|
|
122149
122593
|
return null;
|
|
122150
122594
|
}
|
|
122595
|
+
if (this.resetTimer) {
|
|
122596
|
+
clearTimeout(this.resetTimer);
|
|
122597
|
+
this.resetTimer = null;
|
|
122598
|
+
}
|
|
122151
122599
|
try {
|
|
122152
122600
|
this.setState("checking", { message: "Checking for updates..." });
|
|
122601
|
+
const checkStartTime = Date.now();
|
|
122153
122602
|
const controller = new AbortController;
|
|
122154
122603
|
const timeout = setTimeout(() => controller.abort(), this.options.timeoutMs ?? 1e4);
|
|
122155
|
-
const response = await fetch(this.
|
|
122604
|
+
const response = await fetch(this.rawPackageJsonUrl, {
|
|
122156
122605
|
headers: {
|
|
122157
122606
|
"User-Agent": `steward-cli/${this.currentVersion}`,
|
|
122158
|
-
Accept: "application/
|
|
122607
|
+
Accept: "application/json"
|
|
122159
122608
|
},
|
|
122160
122609
|
signal: controller.signal
|
|
122161
122610
|
});
|
|
122162
122611
|
clearTimeout(timeout);
|
|
122612
|
+
const elapsed = Date.now() - checkStartTime;
|
|
122613
|
+
const targetMinDisplay = this.options.minDisplayMs !== undefined ? this.options.minDisplayMs : 1000;
|
|
122614
|
+
const minDisplayDelay = Math.max(0, targetMinDisplay - elapsed);
|
|
122615
|
+
if (minDisplayDelay > 0) {
|
|
122616
|
+
await new Promise((resolve) => setTimeout(resolve, minDisplayDelay));
|
|
122617
|
+
}
|
|
122163
122618
|
if (!response.ok) {
|
|
122164
|
-
this.setState("
|
|
122619
|
+
this.setState("error", { message: "Failed to check for updates" });
|
|
122620
|
+
this.resetTimer = setTimeout(() => {
|
|
122621
|
+
if (this.state === "error")
|
|
122622
|
+
this.setState("idle");
|
|
122623
|
+
}, 3000);
|
|
122165
122624
|
return null;
|
|
122166
122625
|
}
|
|
122167
122626
|
const data = await response.json();
|
|
122168
|
-
|
|
122169
|
-
|
|
122627
|
+
if (!data || typeof data !== "object" || typeof data.version !== "string") {
|
|
122628
|
+
this.setState("error", { message: "Invalid upstream version response" });
|
|
122629
|
+
this.resetTimer = setTimeout(() => {
|
|
122630
|
+
if (this.state === "error")
|
|
122631
|
+
this.setState("idle");
|
|
122632
|
+
}, 3000);
|
|
122633
|
+
return null;
|
|
122634
|
+
}
|
|
122635
|
+
const latestVersion = data.version.replace(/^v/i, "").trim();
|
|
122170
122636
|
if (!latestVersion) {
|
|
122171
|
-
this.setState("
|
|
122637
|
+
this.setState("error", { message: "Empty upstream version response" });
|
|
122638
|
+
this.resetTimer = setTimeout(() => {
|
|
122639
|
+
if (this.state === "error")
|
|
122640
|
+
this.setState("idle");
|
|
122641
|
+
}, 3000);
|
|
122172
122642
|
return null;
|
|
122173
122643
|
}
|
|
122174
122644
|
const hasUpdate = isNewerVersion(this.currentVersion, latestVersion);
|
|
122175
122645
|
const updateInfo = {
|
|
122176
122646
|
currentVersion: this.currentVersion,
|
|
122177
122647
|
latestVersion,
|
|
122178
|
-
hasUpdate
|
|
122179
|
-
tag: rawTag,
|
|
122180
|
-
releaseUrl: data.html_url
|
|
122648
|
+
hasUpdate
|
|
122181
122649
|
};
|
|
122182
122650
|
if (hasUpdate) {
|
|
122183
122651
|
this.setState("available", {
|
|
122184
122652
|
version: latestVersion,
|
|
122185
|
-
message: `
|
|
122653
|
+
message: `Update v${latestVersion} is available`
|
|
122186
122654
|
});
|
|
122187
122655
|
} else {
|
|
122188
|
-
this.setState("no-updates", {
|
|
122189
|
-
|
|
122190
|
-
|
|
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
|
|
122656
|
+
this.setState("no-updates", {
|
|
122657
|
+
version: this.currentVersion,
|
|
122658
|
+
message: `Steward is up to date (v${this.currentVersion})`
|
|
122223
122659
|
});
|
|
122224
|
-
|
|
122225
|
-
|
|
122226
|
-
stdio: "ignore",
|
|
122227
|
-
env: {
|
|
122228
|
-
...process.env,
|
|
122229
|
-
HOME: process.env.HOME || "",
|
|
122230
|
-
PATH: process.env.PATH || ""
|
|
122231
|
-
}
|
|
122232
|
-
});
|
|
122233
|
-
}
|
|
122234
|
-
const success = await new Promise((resolve) => {
|
|
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") {
|
|
122660
|
+
this.resetTimer = setTimeout(() => {
|
|
122661
|
+
if (this.state === "no-updates")
|
|
122263
122662
|
this.setState("idle");
|
|
122264
|
-
|
|
122265
|
-
}, 4000);
|
|
122266
|
-
return false;
|
|
122663
|
+
}, 3500);
|
|
122267
122664
|
}
|
|
122665
|
+
return updateInfo;
|
|
122268
122666
|
} catch (err) {
|
|
122269
|
-
logError(err, { component: "
|
|
122270
|
-
this.setState("error", {
|
|
122271
|
-
setTimeout(() => {
|
|
122272
|
-
if (this.state === "error")
|
|
122667
|
+
logError(err, { component: "UpdateCheckerService", method: "checkForUpdates" });
|
|
122668
|
+
this.setState("error", { message: "Failed to check for updates" });
|
|
122669
|
+
this.resetTimer = setTimeout(() => {
|
|
122670
|
+
if (this.state === "error")
|
|
122273
122671
|
this.setState("idle");
|
|
122274
|
-
|
|
122275
|
-
|
|
122276
|
-
return false;
|
|
122277
|
-
} finally {
|
|
122278
|
-
this.isUpdating = false;
|
|
122672
|
+
}, 3000);
|
|
122673
|
+
return null;
|
|
122279
122674
|
}
|
|
122280
122675
|
}
|
|
122281
|
-
startBackgroundCheck(initialDelayMs = 2000
|
|
122282
|
-
if (this.checkTimer)
|
|
122676
|
+
startBackgroundCheck(initialDelayMs = 2000) {
|
|
122677
|
+
if (this.checkTimer) {
|
|
122283
122678
|
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
|
-
};
|
|
122679
|
+
}
|
|
122294
122680
|
this.checkTimer = setTimeout(async () => {
|
|
122295
122681
|
this.checkTimer = null;
|
|
122296
|
-
await
|
|
122297
|
-
const recurringInterval = intervalMs ?? this.options.checkIntervalMs;
|
|
122298
|
-
if (recurringInterval && recurringInterval > 0) {
|
|
122299
|
-
this.intervalTimer = setInterval(performCheck, recurringInterval);
|
|
122300
|
-
}
|
|
122682
|
+
await this.checkForUpdates();
|
|
122301
122683
|
}, initialDelayMs);
|
|
122302
122684
|
}
|
|
122303
122685
|
dispose() {
|
|
@@ -122305,9 +122687,9 @@ class AutoUpdaterService {
|
|
|
122305
122687
|
clearTimeout(this.checkTimer);
|
|
122306
122688
|
this.checkTimer = null;
|
|
122307
122689
|
}
|
|
122308
|
-
if (this.
|
|
122309
|
-
|
|
122310
|
-
this.
|
|
122690
|
+
if (this.resetTimer) {
|
|
122691
|
+
clearTimeout(this.resetTimer);
|
|
122692
|
+
this.resetTimer = null;
|
|
122311
122693
|
}
|
|
122312
122694
|
}
|
|
122313
122695
|
}
|
|
@@ -122326,7 +122708,7 @@ class TUIApp {
|
|
|
122326
122708
|
ctrlCTimer = null;
|
|
122327
122709
|
isBusy = false;
|
|
122328
122710
|
voiceController;
|
|
122329
|
-
|
|
122711
|
+
updateChecker;
|
|
122330
122712
|
permissionQueue;
|
|
122331
122713
|
constructor(options = {}) {
|
|
122332
122714
|
this.cwd = options.cwd ?? process.cwd();
|
|
@@ -122389,11 +122771,23 @@ class TUIApp {
|
|
|
122389
122771
|
this.statusBar.showWarning(warning);
|
|
122390
122772
|
}
|
|
122391
122773
|
});
|
|
122392
|
-
this.
|
|
122774
|
+
this.updateChecker = new UpdateCheckerService({
|
|
122393
122775
|
currentVersion: options.version,
|
|
122394
122776
|
onStatusChange: (state, info) => {
|
|
122395
122777
|
if (state === "idle") {
|
|
122396
122778
|
this.statusBar.setUpdateStatus(undefined);
|
|
122779
|
+
} else if (state === "available") {
|
|
122780
|
+
this.statusBar.setUpdateStatus({
|
|
122781
|
+
state,
|
|
122782
|
+
version: info?.version,
|
|
122783
|
+
message: info?.message
|
|
122784
|
+
}, 8000);
|
|
122785
|
+
} else if (state === "error") {
|
|
122786
|
+
this.statusBar.setUpdateStatus({
|
|
122787
|
+
state,
|
|
122788
|
+
version: info?.version,
|
|
122789
|
+
message: info?.message
|
|
122790
|
+
}, 4000);
|
|
122397
122791
|
} else {
|
|
122398
122792
|
this.statusBar.setUpdateStatus({
|
|
122399
122793
|
state,
|
|
@@ -122499,7 +122893,7 @@ class TUIApp {
|
|
|
122499
122893
|
this.engine.mount(this.streamingView, { kind: "custom" });
|
|
122500
122894
|
this.engine.mount(this.promptInput, { keepCursorVisible: true, kind: "input" });
|
|
122501
122895
|
this.engine.mount(this.statusBar, { kind: "custom" });
|
|
122502
|
-
this.
|
|
122896
|
+
this.updateChecker.startBackgroundCheck(3000);
|
|
122503
122897
|
this.engine.addInputListener((chunk) => {
|
|
122504
122898
|
const action = parseKeyInput(chunk);
|
|
122505
122899
|
if (this.voiceController.isActive) {
|
|
@@ -122935,7 +123329,7 @@ class TUIApp {
|
|
|
122935
123329
|
this.permissionQueue.clear();
|
|
122936
123330
|
this.session.shutdown().catch(() => {});
|
|
122937
123331
|
this.voiceController.dispose();
|
|
122938
|
-
this.
|
|
123332
|
+
this.updateChecker.dispose();
|
|
122939
123333
|
this.engine.cleanupSync();
|
|
122940
123334
|
if (this.onExitCallback) {
|
|
122941
123335
|
this.onExitCallback();
|