@cryer/star-cli 0.3.3 → 0.3.5
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 +8 -1
- package/README.zh-CN.md +7 -1
- package/dist/{chunk-3L6QKJM4.js → chunk-OIDB2E5J.js} +237 -74
- package/dist/chunk-OIDB2E5J.js.map +1 -0
- package/dist/{loop-TQ7GFTTE.js → loop-H5RYTO2O.js} +2 -2
- package/dist/main.js +58 -134
- package/dist/main.js.map +1 -1
- package/package.json +25 -16
- package/dist/chunk-3L6QKJM4.js.map +0 -1
- /package/dist/{loop-TQ7GFTTE.js.map → loop-H5RYTO2O.js.map} +0 -0
package/README.md
CHANGED
|
@@ -52,6 +52,13 @@ Config file: `~/.star-cli/config.toml` (project-level override: `.star/config.to
|
|
|
52
52
|
defaultModel = "gpt"
|
|
53
53
|
permissionMode = "ask" # ask | auto | readonly | yolo | plan
|
|
54
54
|
contextMaxTokens = 100000
|
|
55
|
+
# Max model round-trips ("steps") per turn between progress checkpoints — one
|
|
56
|
+
# step is a reply plus the tool calls it asked for; retries and auto-continue
|
|
57
|
+
# nudges don't count. Reaching the cap while tools still execute resets it
|
|
58
|
+
# with a notice instead of stopping the turn; 0 disables the cap entirely.
|
|
59
|
+
maxSteps = 100
|
|
60
|
+
# compactThresholdTokens = 80000 # optional auto-compaction trigger in tokens; defaults to the
|
|
61
|
+
# context window. /compact always compacts, regardless of this.
|
|
55
62
|
contextCompaction = "summary" # summary | truncate — how over-budget history is compacted
|
|
56
63
|
# Seconds with no stream output before a stalled response is ended gracefully
|
|
57
64
|
# (some relays never close the stream). The first token gets a fixed 120s allowance.
|
|
@@ -148,7 +155,7 @@ star --clear-sessions all delete every stored session
|
|
|
148
155
|
| `/connect` | interactive provider onboarding wizard: pick a preset or custom endpoint, paste the API key (masked), name a model — appends `[[providers]]`/`[[models]]` to the config, stores the key in `~/.star-cli/.env` (never in `config.toml`), and can set the new model as default + switch to it immediately |
|
|
149
156
|
| `/plan` | toggle plan mode: read-only research, then approve the generated plan before it executes (session-only) |
|
|
150
157
|
| `/memory [add <text>]` | show the long-term memory file (`~/.star-cli/MEMORY.md`, injected into every session's system prompt), or append a line to it — see [Long-term memory](#long-term-memory) |
|
|
151
|
-
| `/compact` | compact conversation history
|
|
158
|
+
| `/compact` | compact conversation history now — always runs, even below the auto-compact threshold |
|
|
152
159
|
| `/export [path]` | export the current session to a Markdown file |
|
|
153
160
|
| `/undo` | undo the last conversation turn: revert its file changes (write_file/edit_file) and retract its messages — earlier turns are never touched (previews the message count and per-file revert diffs, then asks for confirmation) |
|
|
154
161
|
| `/rewind [n]` | list file-change checkpoints, or rewind to just before checkpoint `n`: restore every file changed since then and retract the matching conversation messages (asks for confirmation first) |
|
package/README.zh-CN.md
CHANGED
|
@@ -52,6 +52,12 @@ star -p "hi" # non-interactive print mode
|
|
|
52
52
|
defaultModel = "gpt"
|
|
53
53
|
permissionMode = "ask" # ask | auto | readonly | yolo | plan
|
|
54
54
|
contextMaxTokens = 100000
|
|
55
|
+
# 每回合在进度检查点之间允许的最大模型往返("step")数——一个 step 是
|
|
56
|
+
# 一次回复加上它请求的所有工具调用;重试和 auto-continue nudge 不占 step。
|
|
57
|
+
# 到达上限时若工具仍在执行,会发提示并重置额度而不是终止回合;0 表示完全不限。
|
|
58
|
+
maxSteps = 100
|
|
59
|
+
# compactThresholdTokens = 80000 # optional auto-compaction trigger in tokens; defaults to the
|
|
60
|
+
# context window. /compact always compacts, regardless of this.
|
|
55
61
|
contextCompaction = "summary" # summary | truncate — how over-budget history is compacted
|
|
56
62
|
# Seconds with no stream output before a stalled response is ended gracefully
|
|
57
63
|
# (some relays never close the stream). The first token gets a fixed 120s allowance.
|
|
@@ -148,7 +154,7 @@ star --clear-sessions all delete every stored session
|
|
|
148
154
|
| `/connect` | 交互式服务商接入向导:选择预设或自定义端点,粘贴 API 密钥(掩码显示),命名模型——把 `[[providers]]`/`[[models]]` 追加到配置,密钥存入 `~/.star-cli/.env`(绝不写入 `config.toml`),还可将新模型设为默认并立即切换 |
|
|
149
155
|
| `/plan` | 切换计划模式:先只读调研,再批准生成的计划后才执行(仅会话内有效) |
|
|
150
156
|
| `/memory [add <text>]` | 查看长期记忆文件(`~/.star-cli/MEMORY.md`,注入每个会话的系统提示词),或向其中追加一行——见[长期记忆](#长期记忆) |
|
|
151
|
-
| `/compact` |
|
|
157
|
+
| `/compact` | 立即压缩对话历史——低于自动压缩阈值时也会执行 |
|
|
152
158
|
| `/export [path]` | 将当前会话导出为 Markdown 文件 |
|
|
153
159
|
| `/undo` | 撤销上一轮对话:还原其文件改动(write_file/edit_file)并撤回其消息——更早的回合不受影响(先预览消息数量与逐文件还原 diff,再要求确认) |
|
|
154
160
|
| `/rewind [n]` | 列出文件改动检查点,或回滚到检查点 `n` 之前:还原此后改动的所有文件并撤回对应的对话消息(先要求确认) |
|
|
@@ -12,6 +12,129 @@ import {
|
|
|
12
12
|
// src/agent/loop.ts
|
|
13
13
|
import { tool as aiTool, generateText as generateText3 } from "ai";
|
|
14
14
|
|
|
15
|
+
// src/config/schema.ts
|
|
16
|
+
import { z } from "zod";
|
|
17
|
+
var ProviderConfigSchema = z.object({
|
|
18
|
+
name: z.string(),
|
|
19
|
+
// openai-responses talks to the Responses API (/responses) instead of chat
|
|
20
|
+
// completions — the wire format codex uses, and the native API of
|
|
21
|
+
// reasoning-model relays that otherwise hide reasoning behind empty chat
|
|
22
|
+
// completions. streamChat sends strictSchemas:false + store:false with it.
|
|
23
|
+
protocol: z.enum(["openai-compatible", "anthropic", "openai-responses"]).default("openai-compatible"),
|
|
24
|
+
baseURL: z.string(),
|
|
25
|
+
apiKeyEnv: z.string().optional(),
|
|
26
|
+
apiKey: z.string().optional(),
|
|
27
|
+
headers: z.record(z.string()).optional()
|
|
28
|
+
});
|
|
29
|
+
var ModelConfigSchema = z.object({
|
|
30
|
+
name: z.string(),
|
|
31
|
+
provider: z.string(),
|
|
32
|
+
model: z.string(),
|
|
33
|
+
maxTokens: z.number().int().positive().optional(),
|
|
34
|
+
// Per-model context window; overrides the top-level contextMaxTokens for
|
|
35
|
+
// compaction and the status bar when set.
|
|
36
|
+
contextMaxTokens: z.number().int().positive().optional(),
|
|
37
|
+
// Cost estimation needs BOTH prices (USD per 1M tokens); with only one set
|
|
38
|
+
// the model is treated as unpriced.
|
|
39
|
+
promptPrice: z.number().nonnegative().optional(),
|
|
40
|
+
completionPrice: z.number().nonnegative().optional(),
|
|
41
|
+
// Optional price (USD per 1M tokens) for cache-read prompt tokens. When
|
|
42
|
+
// unset, OpenAI-style cached tokens bill at promptPrice and Anthropic-style
|
|
43
|
+
// cache reads are not billed — set this for accurate cache pricing.
|
|
44
|
+
cacheReadPrice: z.number().nonnegative().optional()
|
|
45
|
+
});
|
|
46
|
+
var PermissionsConfigSchema = z.object({
|
|
47
|
+
allow: z.array(z.string()).default([]),
|
|
48
|
+
deny: z.array(z.string()).default([])
|
|
49
|
+
});
|
|
50
|
+
var HookConfigSchema = z.object({
|
|
51
|
+
event: z.enum(["PreToolUse", "PostToolUse", "Stop"]),
|
|
52
|
+
matcher: z.string().refine(
|
|
53
|
+
(pattern) => {
|
|
54
|
+
try {
|
|
55
|
+
new RegExp(pattern);
|
|
56
|
+
return true;
|
|
57
|
+
} catch {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
{ message: "invalid regular expression" }
|
|
62
|
+
).optional(),
|
|
63
|
+
command: z.string().min(1),
|
|
64
|
+
timeoutSec: z.number().positive().default(30)
|
|
65
|
+
});
|
|
66
|
+
var ConfigSchema = z.object({
|
|
67
|
+
defaultModel: z.string().default(""),
|
|
68
|
+
permissionMode: z.enum(["auto", "ask", "readonly", "yolo", "plan"]).default("ask"),
|
|
69
|
+
providers: z.array(ProviderConfigSchema).default([]),
|
|
70
|
+
models: z.array(ModelConfigSchema).default([]),
|
|
71
|
+
// Cap on model round-trips ("steps") per turn between progress checkpoints.
|
|
72
|
+
// One step is a streamed reply plus every tool call it asked for — stream
|
|
73
|
+
// retries and auto-continue nudges do not consume steps. Reaching the cap
|
|
74
|
+
// while tools still execute resets the budget with a notice; the turn stops
|
|
75
|
+
// only when no tool ran since the last checkpoint (the doom-loop
|
|
76
|
+
// signature). 0 disables the cap entirely (opencode/codex have none).
|
|
77
|
+
maxSteps: z.number().int().min(0).default(100),
|
|
78
|
+
contextMaxTokens: z.number().int().positive().default(1e5),
|
|
79
|
+
// Token threshold at which the agent loop auto-compacts the history.
|
|
80
|
+
// Defaults to the effective context window (compact only when full); set a
|
|
81
|
+
// lower value to compact earlier and keep headroom. Manual /compact ignores
|
|
82
|
+
// this — it always compacts.
|
|
83
|
+
compactThresholdTokens: z.number().int().positive().optional(),
|
|
84
|
+
// Seconds without any stream part before a stalled response is ended
|
|
85
|
+
// gracefully (some relays never send the terminal chunks). Applies once
|
|
86
|
+
// streaming has started; the first part gets a longer, fixed allowance.
|
|
87
|
+
streamIdleTimeoutSec: z.number().positive().default(20),
|
|
88
|
+
// Seconds to wait for the very first stream part before giving up; slow
|
|
89
|
+
// thinking-model relays can buffer for minutes before sending anything.
|
|
90
|
+
streamFirstChunkTimeoutSec: z.number().positive().default(300),
|
|
91
|
+
// Extra attempts per model request when a stream fails transiently
|
|
92
|
+
// (network error, 429/5xx, idle watchdog cutoff) or comes back empty, so a
|
|
93
|
+
// relay hiccup does not silently end a half-finished turn. 0 disables.
|
|
94
|
+
// The wait between attempts honors Retry-After headers and otherwise backs
|
|
95
|
+
// off exponentially with jitter (llm/retry.ts); retries after a
|
|
96
|
+
// timeout/empty failure also scale both stream timeouts up (attempt number
|
|
97
|
+
// ×, capped at 3x) since the relay is likely overloaded. Default 5 matches
|
|
98
|
+
// opencode/Codex retry budgets and rides out flaky relays (中转站) better
|
|
99
|
+
// than a minimal budget.
|
|
100
|
+
streamMaxRetries: z.number().int().min(0).default(5),
|
|
101
|
+
// How many consecutive unproductive replies may be nudged before the turn
|
|
102
|
+
// is handed back: a text-only reply that announces pending work ("我将…",
|
|
103
|
+
// "I will…") or answers a nudge with more words, or an empty reply that
|
|
104
|
+
// survived the stream retry budget (steered with a nudge — a changed
|
|
105
|
+
// request — since identical resends already failed). Tool calls reset the
|
|
106
|
+
// count, so a productive task keeps going; exhaustion surfaces a notice
|
|
107
|
+
// instead of stopping silently. Never fires in plan mode. 0 disables.
|
|
108
|
+
maxAutoContinues: z.number().int().min(0).default(2),
|
|
109
|
+
contextCompaction: z.enum(["summary", "truncate"]).default("summary"),
|
|
110
|
+
// Optional per-session dollar budget; when set, the REPL stops the session
|
|
111
|
+
// once the estimated cost for the session reaches this amount.
|
|
112
|
+
sessionBudgetUsd: z.number().positive().optional(),
|
|
113
|
+
// Terminal bell (REPL only): ring when a turn takes longer than
|
|
114
|
+
// notifyBellThresholdSec, and when a background task finishes.
|
|
115
|
+
// STAR_NO_NOTIFY=1 disables without touching the config.
|
|
116
|
+
notifyBell: z.boolean().default(true),
|
|
117
|
+
notifyBellThresholdSec: z.number().positive().default(10),
|
|
118
|
+
permissions: PermissionsConfigSchema.default({ allow: [], deny: [] }),
|
|
119
|
+
hooks: z.array(HookConfigSchema).default([]),
|
|
120
|
+
// Consecutive identical tool calls (same tool + same arguments) allowed
|
|
121
|
+
// before the loop refuses to execute the repeat and tells the model to
|
|
122
|
+
// change approach — a guard against agents stuck retrying a failing call
|
|
123
|
+
// (opencode's doom_loop). 0 disables.
|
|
124
|
+
doomLoopThreshold: z.number().int().min(0).default(3),
|
|
125
|
+
// Track the whole working tree with an internal git repo (outside the
|
|
126
|
+
// user's .git) at every turn boundary, so /undo and /redo can restore
|
|
127
|
+
// changes made by ANY tool (bash included), not just write_file/edit_file.
|
|
128
|
+
// Falls back silently to per-file snapshots when git is unavailable.
|
|
129
|
+
gitSnapshots: z.boolean().default(true)
|
|
130
|
+
});
|
|
131
|
+
function contextWindowTokens(config, modelName) {
|
|
132
|
+
return config.models.find((m) => m.name === modelName)?.contextMaxTokens ?? config.contextMaxTokens;
|
|
133
|
+
}
|
|
134
|
+
function resolveCompactThreshold(config, windowTokens) {
|
|
135
|
+
return Math.min(config.compactThresholdTokens ?? windowTokens, windowTokens);
|
|
136
|
+
}
|
|
137
|
+
|
|
15
138
|
// src/context/compaction.ts
|
|
16
139
|
import { generateText } from "ai";
|
|
17
140
|
|
|
@@ -106,8 +229,9 @@ ${transcript}`,
|
|
|
106
229
|
});
|
|
107
230
|
return text.trim();
|
|
108
231
|
}
|
|
109
|
-
function compactMessages(messages, maxTokens) {
|
|
110
|
-
|
|
232
|
+
function compactMessages(messages, maxTokens, opts = {}) {
|
|
233
|
+
const limit = opts.force ? 0 : maxTokens;
|
|
234
|
+
if (estimateTokens(messages) <= limit) {
|
|
111
235
|
return { messages, compacted: false, droppedCount: 0 };
|
|
112
236
|
}
|
|
113
237
|
const first = messages[0];
|
|
@@ -133,7 +257,7 @@ function compactMessages(messages, maxTokens) {
|
|
|
133
257
|
droppedCount += turn.length;
|
|
134
258
|
turnIndex += 1;
|
|
135
259
|
const candidate = [...head, placeholderMessage(droppedCount), ...rest.slice(droppedCount)];
|
|
136
|
-
if (estimateTokens(candidate) <=
|
|
260
|
+
if (estimateTokens(candidate) <= limit) {
|
|
137
261
|
break;
|
|
138
262
|
}
|
|
139
263
|
}
|
|
@@ -1091,7 +1215,7 @@ async function rewindToSnapshot(id, includeSubagent = false) {
|
|
|
1091
1215
|
import { spawn as spawn3 } from "child_process";
|
|
1092
1216
|
import { existsSync as existsSync2 } from "fs";
|
|
1093
1217
|
import path4 from "path";
|
|
1094
|
-
import { z } from "zod";
|
|
1218
|
+
import { z as z2 } from "zod";
|
|
1095
1219
|
|
|
1096
1220
|
// src/tasks/manager.ts
|
|
1097
1221
|
import { spawn as spawn2 } from "child_process";
|
|
@@ -1270,11 +1394,11 @@ function resolveShell() {
|
|
|
1270
1394
|
}
|
|
1271
1395
|
return cachedSpec;
|
|
1272
1396
|
}
|
|
1273
|
-
var schema =
|
|
1274
|
-
command:
|
|
1275
|
-
timeout:
|
|
1276
|
-
description:
|
|
1277
|
-
run_in_background:
|
|
1397
|
+
var schema = z2.object({
|
|
1398
|
+
command: z2.string().describe("Shell command to execute"),
|
|
1399
|
+
timeout: z2.number().positive().max(MAX_TIMEOUT).optional().describe("Timeout in seconds (default 120, max 600)"),
|
|
1400
|
+
description: z2.string().optional().describe("Short description of what the command does"),
|
|
1401
|
+
run_in_background: z2.boolean().optional().describe(
|
|
1278
1402
|
"Run the command in the background and return a task id immediately (default false). Use task_output/task_kill to inspect or stop it."
|
|
1279
1403
|
)
|
|
1280
1404
|
});
|
|
@@ -1408,12 +1532,12 @@ Command timed out after ${timeoutSeconds}s`,
|
|
|
1408
1532
|
// src/tools/fs/edit.ts
|
|
1409
1533
|
import { readFile as readFile2, stat as stat2, writeFile as writeFile2 } from "fs/promises";
|
|
1410
1534
|
import path5 from "path";
|
|
1411
|
-
import { z as
|
|
1412
|
-
var schema2 =
|
|
1413
|
-
path:
|
|
1414
|
-
old_string:
|
|
1415
|
-
new_string:
|
|
1416
|
-
replace_all:
|
|
1535
|
+
import { z as z3 } from "zod";
|
|
1536
|
+
var schema2 = z3.object({
|
|
1537
|
+
path: z3.string().describe("File path, absolute or relative to the working directory"),
|
|
1538
|
+
old_string: z3.string().min(1).describe("Exact text to replace"),
|
|
1539
|
+
new_string: z3.string().describe("Replacement text"),
|
|
1540
|
+
replace_all: z3.boolean().optional().describe("Replace every occurrence instead of requiring a unique match")
|
|
1417
1541
|
});
|
|
1418
1542
|
var editFileTool = {
|
|
1419
1543
|
name: "edit_file",
|
|
@@ -1477,7 +1601,7 @@ var editFileTool = {
|
|
|
1477
1601
|
|
|
1478
1602
|
// src/tools/fs/glob.ts
|
|
1479
1603
|
import path7 from "path";
|
|
1480
|
-
import { z as
|
|
1604
|
+
import { z as z4 } from "zod";
|
|
1481
1605
|
|
|
1482
1606
|
// src/tools/fs/util.ts
|
|
1483
1607
|
import { readFileSync, statSync } from "fs";
|
|
@@ -1708,11 +1832,11 @@ function literalDirPrefix(pattern) {
|
|
|
1708
1832
|
}
|
|
1709
1833
|
return out;
|
|
1710
1834
|
}
|
|
1711
|
-
var schema3 =
|
|
1712
|
-
pattern:
|
|
1835
|
+
var schema3 = z4.object({
|
|
1836
|
+
pattern: z4.string().describe(
|
|
1713
1837
|
"Glob pattern, e.g. 'src/**/*.ts'. Supports **, *, ?, {a,b} braces and [abc] character classes. A bare pattern like '*.ts' matches basenames recursively."
|
|
1714
1838
|
),
|
|
1715
|
-
path:
|
|
1839
|
+
path: z4.string().optional().describe("Directory to search, defaults to the working directory")
|
|
1716
1840
|
});
|
|
1717
1841
|
var globTool = {
|
|
1718
1842
|
name: "glob",
|
|
@@ -1736,7 +1860,7 @@ var globTool = {
|
|
|
1736
1860
|
// src/tools/fs/grep.ts
|
|
1737
1861
|
import { readFile as readFile3, stat as stat4 } from "fs/promises";
|
|
1738
1862
|
import path8 from "path";
|
|
1739
|
-
import { z as
|
|
1863
|
+
import { z as z5 } from "zod";
|
|
1740
1864
|
var MAX_MATCHES = 250;
|
|
1741
1865
|
var MAX_LINE_CHARS = 500;
|
|
1742
1866
|
var MAX_PATTERN_CHARS = 200;
|
|
@@ -1744,11 +1868,11 @@ var MAX_FILE_BYTES = 5 * 1024 * 1024;
|
|
|
1744
1868
|
function isTooComplexPattern(pattern) {
|
|
1745
1869
|
return pattern.length > MAX_PATTERN_CHARS || /\)[+*]/.test(pattern) || /\)\{\d+(,\d*)?\}/.test(pattern);
|
|
1746
1870
|
}
|
|
1747
|
-
var schema4 =
|
|
1748
|
-
pattern:
|
|
1749
|
-
path:
|
|
1750
|
-
glob:
|
|
1751
|
-
ignoreCase:
|
|
1871
|
+
var schema4 = z5.object({
|
|
1872
|
+
pattern: z5.string().describe("Regular expression to search for"),
|
|
1873
|
+
path: z5.string().optional().describe("File or directory to search, defaults to the working directory"),
|
|
1874
|
+
glob: z5.string().optional().describe("Glob pattern to filter which files are searched, e.g. '*.ts'"),
|
|
1875
|
+
ignoreCase: z5.boolean().optional().describe("Case-insensitive matching")
|
|
1752
1876
|
});
|
|
1753
1877
|
var grepTool = {
|
|
1754
1878
|
name: "grep",
|
|
@@ -1847,7 +1971,7 @@ var grepTool = {
|
|
|
1847
1971
|
// src/tools/fs/read.ts
|
|
1848
1972
|
import { open, stat as stat5 } from "fs/promises";
|
|
1849
1973
|
import path9 from "path";
|
|
1850
|
-
import { z as
|
|
1974
|
+
import { z as z6 } from "zod";
|
|
1851
1975
|
var DEFAULT_LIMIT = 2e3;
|
|
1852
1976
|
var MAX_CHARS = 100 * 1024;
|
|
1853
1977
|
var MAX_READ_BYTES = 1024 * 1024;
|
|
@@ -1873,10 +1997,10 @@ function decodeTextBuffer(buf) {
|
|
|
1873
1997
|
}
|
|
1874
1998
|
return { text: buf.toString("utf8") };
|
|
1875
1999
|
}
|
|
1876
|
-
var schema5 =
|
|
1877
|
-
path:
|
|
1878
|
-
offset:
|
|
1879
|
-
limit:
|
|
2000
|
+
var schema5 = z6.object({
|
|
2001
|
+
path: z6.string().describe("File path, absolute or relative to the working directory"),
|
|
2002
|
+
offset: z6.number().int().positive().optional().describe("1-based line number to start reading from"),
|
|
2003
|
+
limit: z6.number().int().positive().optional().describe("Maximum number of lines to read (default 2000)")
|
|
1880
2004
|
});
|
|
1881
2005
|
var readFileTool = {
|
|
1882
2006
|
name: "read_file",
|
|
@@ -1949,10 +2073,10 @@ var readFileTool = {
|
|
|
1949
2073
|
// src/tools/fs/write.ts
|
|
1950
2074
|
import { mkdir as mkdir3, writeFile as writeFile3 } from "fs/promises";
|
|
1951
2075
|
import path10 from "path";
|
|
1952
|
-
import { z as
|
|
1953
|
-
var schema6 =
|
|
1954
|
-
path:
|
|
1955
|
-
content:
|
|
2076
|
+
import { z as z7 } from "zod";
|
|
2077
|
+
var schema6 = z7.object({
|
|
2078
|
+
path: z7.string().describe("File path, absolute or relative to the working directory"),
|
|
2079
|
+
content: z7.string().describe("Full content to write to the file")
|
|
1956
2080
|
});
|
|
1957
2081
|
var writeFileTool = {
|
|
1958
2082
|
name: "write_file",
|
|
@@ -1974,7 +2098,7 @@ var writeFileTool = {
|
|
|
1974
2098
|
};
|
|
1975
2099
|
|
|
1976
2100
|
// src/tools/tasks.ts
|
|
1977
|
-
import { z as
|
|
2101
|
+
import { z as z8 } from "zod";
|
|
1978
2102
|
|
|
1979
2103
|
// src/agent/agent-tasks.ts
|
|
1980
2104
|
import { EventEmitter as EventEmitter2 } from "events";
|
|
@@ -2086,6 +2210,20 @@ var AgentTaskManager = class extends EventEmitter2 {
|
|
|
2086
2210
|
}
|
|
2087
2211
|
};
|
|
2088
2212
|
var defaultAgentTasks = new AgentTaskManager();
|
|
2213
|
+
function formatAgentTaskUpdate(task) {
|
|
2214
|
+
const label = task.description ? `: ${task.description}` : "";
|
|
2215
|
+
if (task.status === "running") {
|
|
2216
|
+
return `Background subagent ${task.id} started${label}`;
|
|
2217
|
+
}
|
|
2218
|
+
let text = `Background subagent ${task.id} ${task.status}${label}`;
|
|
2219
|
+
if (task.status === "failed") {
|
|
2220
|
+
const reason = (task.result.split("\n")[0] ?? "").replace(/^Subagent failed:\s*/, "");
|
|
2221
|
+
if (reason) {
|
|
2222
|
+
text += ` \u2014 ${reason.length > 140 ? `${reason.slice(0, 140)}\u2026` : reason}`;
|
|
2223
|
+
}
|
|
2224
|
+
}
|
|
2225
|
+
return text;
|
|
2226
|
+
}
|
|
2089
2227
|
|
|
2090
2228
|
// src/tasks/format.ts
|
|
2091
2229
|
function formatDuration(startedAt, endedAt) {
|
|
@@ -2131,8 +2269,8 @@ function formatTaskFinished(task) {
|
|
|
2131
2269
|
}
|
|
2132
2270
|
|
|
2133
2271
|
// src/tools/tasks.ts
|
|
2134
|
-
var idSchema =
|
|
2135
|
-
id:
|
|
2272
|
+
var idSchema = z8.object({
|
|
2273
|
+
id: z8.string().describe("Task id, e.g. task-1 or agent-1")
|
|
2136
2274
|
});
|
|
2137
2275
|
function truncateOutput(s) {
|
|
2138
2276
|
if (s.length <= MAX_OUTPUT) return s;
|
|
@@ -2149,7 +2287,7 @@ var taskListTool = {
|
|
|
2149
2287
|
name: "task_list",
|
|
2150
2288
|
description: "List background tasks (shell commands and subagents) with status and runtime.",
|
|
2151
2289
|
permission: "read",
|
|
2152
|
-
parameters:
|
|
2290
|
+
parameters: z8.object({}),
|
|
2153
2291
|
execute() {
|
|
2154
2292
|
const shell = formatTaskList(defaultTaskManager.list());
|
|
2155
2293
|
const agents = defaultAgentTasks.list();
|
|
@@ -2210,16 +2348,16 @@ var taskKillTool = {
|
|
|
2210
2348
|
// src/tools/todo.ts
|
|
2211
2349
|
import { mkdir as mkdir4, readFile as readFile4, rename, writeFile as writeFile4 } from "fs/promises";
|
|
2212
2350
|
import path11 from "path";
|
|
2213
|
-
import { z as
|
|
2214
|
-
var todoItemSchema =
|
|
2215
|
-
id:
|
|
2216
|
-
title:
|
|
2217
|
-
status:
|
|
2351
|
+
import { z as z9 } from "zod";
|
|
2352
|
+
var todoItemSchema = z9.object({
|
|
2353
|
+
id: z9.number().int(),
|
|
2354
|
+
title: z9.string(),
|
|
2355
|
+
status: z9.enum(["pending", "in_progress", "done"])
|
|
2218
2356
|
});
|
|
2219
|
-
var writeSchema =
|
|
2220
|
-
todos:
|
|
2357
|
+
var writeSchema = z9.object({
|
|
2358
|
+
todos: z9.array(todoItemSchema).describe("The full todo list, replacing the current one")
|
|
2221
2359
|
});
|
|
2222
|
-
var readSchema =
|
|
2360
|
+
var readSchema = z9.object({});
|
|
2223
2361
|
function fileFor(cwd) {
|
|
2224
2362
|
return path11.join(cwd, ".star", "todos.json");
|
|
2225
2363
|
}
|
|
@@ -2335,13 +2473,13 @@ function createTodoTools(store = defaultStore) {
|
|
|
2335
2473
|
}
|
|
2336
2474
|
|
|
2337
2475
|
// src/tools/web/fetch.ts
|
|
2338
|
-
import { z as
|
|
2476
|
+
import { z as z10 } from "zod";
|
|
2339
2477
|
var DEFAULT_MAX_CHARS = 2e4;
|
|
2340
2478
|
var MAX_BYTES = 2 * 1024 * 1024;
|
|
2341
2479
|
var TIMEOUT_MS = 3e4;
|
|
2342
|
-
var schema7 =
|
|
2343
|
-
url:
|
|
2344
|
-
maxChars:
|
|
2480
|
+
var schema7 = z10.object({
|
|
2481
|
+
url: z10.string().describe("The http(s) URL to fetch"),
|
|
2482
|
+
maxChars: z10.number().int().positive().optional().describe("Maximum characters of text to return (default 20000)")
|
|
2345
2483
|
});
|
|
2346
2484
|
function safeCodePoint(n, raw) {
|
|
2347
2485
|
if (!Number.isInteger(n) || n < 0 || n > 1114111) {
|
|
@@ -2475,15 +2613,15 @@ var webFetchTool = {
|
|
|
2475
2613
|
};
|
|
2476
2614
|
|
|
2477
2615
|
// src/tools/web/search.ts
|
|
2478
|
-
import { z as
|
|
2616
|
+
import { z as z11 } from "zod";
|
|
2479
2617
|
var TIMEOUT_MS2 = 15e3;
|
|
2480
2618
|
var DEFAULT_MAX_RESULTS = 5;
|
|
2481
2619
|
var MAX_RESULTS_LIMIT = 10;
|
|
2482
2620
|
var SEARCH_URL = "https://html.duckduckgo.com/html/?q=";
|
|
2483
2621
|
var USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36";
|
|
2484
|
-
var schema8 =
|
|
2485
|
-
query:
|
|
2486
|
-
maxResults:
|
|
2622
|
+
var schema8 = z11.object({
|
|
2623
|
+
query: z11.string().min(1).describe("The search query"),
|
|
2624
|
+
maxResults: z11.number().int().min(1).max(MAX_RESULTS_LIMIT).optional().describe(
|
|
2487
2625
|
`Maximum number of results to return (default ${DEFAULT_MAX_RESULTS}, max ${MAX_RESULTS_LIMIT})`
|
|
2488
2626
|
)
|
|
2489
2627
|
});
|
|
@@ -2664,7 +2802,7 @@ ${body}`;
|
|
|
2664
2802
|
// src/agent/skills.ts
|
|
2665
2803
|
import { readFileSync as readFileSync3, readdirSync, statSync as statSync3 } from "fs";
|
|
2666
2804
|
import path13 from "path";
|
|
2667
|
-
import { z as
|
|
2805
|
+
import { z as z12 } from "zod";
|
|
2668
2806
|
var SKILL_FILE = "SKILL.md";
|
|
2669
2807
|
var SKILL_MAX_CHARS = 32 * 1024;
|
|
2670
2808
|
var NAME_PATTERN = /^[a-z0-9-]+$/;
|
|
@@ -2749,8 +2887,8 @@ function createSkillTool(deps) {
|
|
|
2749
2887
|
name: "skill",
|
|
2750
2888
|
description: "Load the full instructions of a skill by name. Available skills are listed in the system prompt when present; call this before starting work that matches one of them.",
|
|
2751
2889
|
permission: "read",
|
|
2752
|
-
parameters:
|
|
2753
|
-
name:
|
|
2890
|
+
parameters: z12.object({
|
|
2891
|
+
name: z12.string().describe("Name of the skill to load, as listed in the system prompt.")
|
|
2754
2892
|
}),
|
|
2755
2893
|
async execute(args) {
|
|
2756
2894
|
const skills = discoverSkills(deps.cwd, deps.home);
|
|
@@ -2780,7 +2918,7 @@ function createSkillTool(deps) {
|
|
|
2780
2918
|
}
|
|
2781
2919
|
|
|
2782
2920
|
// src/agent/subagent.ts
|
|
2783
|
-
import { z as
|
|
2921
|
+
import { z as z13 } from "zod";
|
|
2784
2922
|
|
|
2785
2923
|
// src/tools/index.ts
|
|
2786
2924
|
function createDefaultRegistry(todoStore) {
|
|
@@ -2792,7 +2930,7 @@ var MAX_SUBAGENT_DEPTH = 1;
|
|
|
2792
2930
|
var MAX_REPORT_CHARS = 2e4;
|
|
2793
2931
|
var SUBAGENT_PROMPT = "You are a subagent spawned to handle a focused subtask on behalf of the main agent. Work autonomously with the tools available to you, then finish with a concise report of what you found or did. Your final text is returned to the main agent as the tool result \u2014 make it self-contained.";
|
|
2794
2932
|
async function runSubagent(deps, args, cwd, signal) {
|
|
2795
|
-
const { AgentLoop: AgentLoop2 } = await import("./loop-
|
|
2933
|
+
const { AgentLoop: AgentLoop2 } = await import("./loop-H5RYTO2O.js");
|
|
2796
2934
|
const child = new AgentLoop2({
|
|
2797
2935
|
model: deps.model,
|
|
2798
2936
|
registry: createDefaultRegistry(),
|
|
@@ -2850,10 +2988,10 @@ function createSubagentTool(deps) {
|
|
|
2850
2988
|
name: "subagent",
|
|
2851
2989
|
description: "Spawn a subagent with its own agent loop to handle a focused subtask (research, exploration, or an isolated change). The subagent has the same tools but cannot spawn further subagents. Returns the subagent's final report. Prefer this for self-contained work that would otherwise clutter the main conversation. Set run_in_background to start it without blocking: independent subtasks then run concurrently and their reports are delivered to you automatically when they finish (also inspectable via task_list/task_output/task_kill).",
|
|
2852
2990
|
permission: "exec",
|
|
2853
|
-
parameters:
|
|
2854
|
-
prompt:
|
|
2855
|
-
description:
|
|
2856
|
-
run_in_background:
|
|
2991
|
+
parameters: z13.object({
|
|
2992
|
+
prompt: z13.string().describe("Complete, self-contained instructions for the subagent."),
|
|
2993
|
+
description: z13.string().optional().describe("Short label for the subtask, shown in the result header."),
|
|
2994
|
+
run_in_background: z13.boolean().optional().describe(
|
|
2857
2995
|
"Run concurrently in the background and return a task id immediately (default false). Use for independent subtasks that can run in parallel."
|
|
2858
2996
|
)
|
|
2859
2997
|
}),
|
|
@@ -2881,7 +3019,7 @@ function createSubagentTool(deps) {
|
|
|
2881
3019
|
// src/agent/user-memory.ts
|
|
2882
3020
|
import { appendFileSync, mkdirSync, readFileSync as readFileSync4, statSync as statSync4 } from "fs";
|
|
2883
3021
|
import path14 from "path";
|
|
2884
|
-
import { z as
|
|
3022
|
+
import { z as z14 } from "zod";
|
|
2885
3023
|
var USER_MEMORY_MAX_CHARS = 32 * 1024;
|
|
2886
3024
|
var REMEMBER_TEXT_MAX_CHARS = 500;
|
|
2887
3025
|
var cache3 = /* @__PURE__ */ new Map();
|
|
@@ -2926,8 +3064,8 @@ function createRememberTool(deps = {}) {
|
|
|
2926
3064
|
name: "remember",
|
|
2927
3065
|
description: `Save a note to the user's long-term memory (${userMemoryPath()}), injected into every future session. ONLY use when the user explicitly asks to remember or save something. Never use autonomously.`,
|
|
2928
3066
|
permission: "write",
|
|
2929
|
-
parameters:
|
|
2930
|
-
text:
|
|
3067
|
+
parameters: z14.object({
|
|
3068
|
+
text: z14.string().max(REMEMBER_TEXT_MAX_CHARS).describe("The note to save, as a single concise sentence.")
|
|
2931
3069
|
}),
|
|
2932
3070
|
async execute(args) {
|
|
2933
3071
|
appendUserMemory(args.text, deps.home);
|
|
@@ -3343,7 +3481,27 @@ var AgentLoop = class {
|
|
|
3343
3481
|
let lastWasNudge = false;
|
|
3344
3482
|
let usedToolsThisTurn = false;
|
|
3345
3483
|
let openTodos = [];
|
|
3346
|
-
|
|
3484
|
+
const maxSteps = config.maxSteps;
|
|
3485
|
+
let step = 0;
|
|
3486
|
+
let toolExecutedSinceCheckpoint = false;
|
|
3487
|
+
for (; ; ) {
|
|
3488
|
+
if (maxSteps > 0 && step >= maxSteps) {
|
|
3489
|
+
if (!toolExecutedSinceCheckpoint) {
|
|
3490
|
+
yield {
|
|
3491
|
+
type: "error",
|
|
3492
|
+
error: new Error(
|
|
3493
|
+
`Max steps (${maxSteps}) reached with no tool execution since the last checkpoint, stopping.`
|
|
3494
|
+
)
|
|
3495
|
+
};
|
|
3496
|
+
return;
|
|
3497
|
+
}
|
|
3498
|
+
toolExecutedSinceCheckpoint = false;
|
|
3499
|
+
step = 0;
|
|
3500
|
+
yield {
|
|
3501
|
+
type: "notice",
|
|
3502
|
+
message: `maxSteps (${maxSteps}) reached and the turn is still making progress; continuing. Press Esc to interrupt.`
|
|
3503
|
+
};
|
|
3504
|
+
}
|
|
3347
3505
|
if ((this.opts.subagentDepth ?? 0) === 0) {
|
|
3348
3506
|
for (const finished of defaultAgentTasks.drainNotifications()) {
|
|
3349
3507
|
const label = finished.description ? ` "${finished.description}"` : "";
|
|
@@ -3357,7 +3515,10 @@ ${finished.result}`
|
|
|
3357
3515
|
}
|
|
3358
3516
|
}
|
|
3359
3517
|
const maxTokens = this.opts.contextMaxTokens ?? config.contextMaxTokens;
|
|
3360
|
-
const compacted = compactMessages(
|
|
3518
|
+
const compacted = compactMessages(
|
|
3519
|
+
[...this.messages],
|
|
3520
|
+
resolveCompactThreshold(config, maxTokens)
|
|
3521
|
+
);
|
|
3361
3522
|
if (compacted.compacted) {
|
|
3362
3523
|
this.messages = await this.applyCompactionSummary(compacted, signal);
|
|
3363
3524
|
this.turnMarkers = [];
|
|
@@ -3425,7 +3586,7 @@ ${finished.result}`
|
|
|
3425
3586
|
if (attempt >= maxRetries) break;
|
|
3426
3587
|
if (!failure && text.length === 0 && toolCalls.length === 0 && attempt >= 1 && (isFilteredEmptyFinish(lastFinishReason) || sawReasoning || lastUsage !== void 0 && lastUsage.completionTokens > 0))
|
|
3427
3588
|
break;
|
|
3428
|
-
const delayMs = computeRetryDelayMs(attempt, baseDelay, failure);
|
|
3589
|
+
const delayMs = computeRetryDelayMs(attempt, failure ? baseDelay : baseDelay * 2, failure);
|
|
3429
3590
|
yield {
|
|
3430
3591
|
type: "retry",
|
|
3431
3592
|
attempt: attempt + 2,
|
|
@@ -3528,6 +3689,8 @@ ${finished.result}`
|
|
|
3528
3689
|
const refusal = this.doomLoopRefusal(call);
|
|
3529
3690
|
if (refusal !== null) {
|
|
3530
3691
|
yield { type: "notice", message: refusal };
|
|
3692
|
+
} else {
|
|
3693
|
+
toolExecutedSinceCheckpoint = true;
|
|
3531
3694
|
}
|
|
3532
3695
|
const result = refusal !== null ? { content: refusal, isError: true } : await this.executeTool(call, signal);
|
|
3533
3696
|
const toolMessage = {
|
|
@@ -3577,11 +3740,8 @@ ${finished.result}`
|
|
|
3577
3740
|
}
|
|
3578
3741
|
}
|
|
3579
3742
|
if (signal.aborted) return;
|
|
3743
|
+
step++;
|
|
3580
3744
|
}
|
|
3581
|
-
yield {
|
|
3582
|
-
type: "error",
|
|
3583
|
-
error: new Error(`Max steps (${config.maxSteps}) reached, stopping.`)
|
|
3584
|
-
};
|
|
3585
3745
|
}
|
|
3586
3746
|
// The permission mode can change at runtime, so the system message is
|
|
3587
3747
|
// recomputed at the start of every turn instead of being frozen by the
|
|
@@ -3772,6 +3932,9 @@ ${summary}`
|
|
|
3772
3932
|
|
|
3773
3933
|
export {
|
|
3774
3934
|
defaultAgentTasks,
|
|
3935
|
+
formatAgentTaskUpdate,
|
|
3936
|
+
ConfigSchema,
|
|
3937
|
+
contextWindowTokens,
|
|
3775
3938
|
estimateTokens,
|
|
3776
3939
|
summarizeMessages,
|
|
3777
3940
|
compactMessages,
|
|
@@ -3814,4 +3977,4 @@ export {
|
|
|
3814
3977
|
PLAN_MODE_PROMPT,
|
|
3815
3978
|
AgentLoop
|
|
3816
3979
|
};
|
|
3817
|
-
//# sourceMappingURL=chunk-
|
|
3980
|
+
//# sourceMappingURL=chunk-OIDB2E5J.js.map
|