@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
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
import {
|
|
3
3
|
AgentLoop,
|
|
4
4
|
PLAN_MODE_PROMPT
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-OIDB2E5J.js";
|
|
6
6
|
import "./chunk-B6P4VUBE.js";
|
|
7
7
|
export {
|
|
8
8
|
AgentLoop,
|
|
9
9
|
PLAN_MODE_PROMPT
|
|
10
10
|
};
|
|
11
|
-
//# sourceMappingURL=loop-
|
|
11
|
+
//# sourceMappingURL=loop-H5RYTO2O.js.map
|
package/dist/main.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
AgentLoop,
|
|
4
|
+
ConfigSchema,
|
|
4
5
|
MAX_IMAGE_DIMENSION,
|
|
5
6
|
SKIP_DIRS,
|
|
6
7
|
TodoStore,
|
|
@@ -13,6 +14,7 @@ import {
|
|
|
13
14
|
collectCommitContext,
|
|
14
15
|
collectWorkingDiff,
|
|
15
16
|
compactMessages,
|
|
17
|
+
contextWindowTokens,
|
|
16
18
|
createDefaultRegistry,
|
|
17
19
|
createIgnorePredicate,
|
|
18
20
|
defaultAgentTasks,
|
|
@@ -20,6 +22,7 @@ import {
|
|
|
20
22
|
diffTreeNames,
|
|
21
23
|
discoverSkills,
|
|
22
24
|
estimateTokens,
|
|
25
|
+
formatAgentTaskUpdate,
|
|
23
26
|
formatTaskFinished,
|
|
24
27
|
formatTaskList,
|
|
25
28
|
formatTaskStarted,
|
|
@@ -41,7 +44,7 @@ import {
|
|
|
41
44
|
resolveSnapshotContent,
|
|
42
45
|
rewindToSnapshot,
|
|
43
46
|
summarizeMessages
|
|
44
|
-
} from "./chunk-
|
|
47
|
+
} from "./chunk-OIDB2E5J.js";
|
|
45
48
|
import {
|
|
46
49
|
envFilePath,
|
|
47
50
|
globalConfigPath,
|
|
@@ -598,113 +601,6 @@ async function savePermissionMode(mode) {
|
|
|
598
601
|
await writeFileAtomic(filePath, upsertTopLevelKeyText(content, "permissionMode", mode));
|
|
599
602
|
}
|
|
600
603
|
|
|
601
|
-
// src/config/schema.ts
|
|
602
|
-
import { z } from "zod";
|
|
603
|
-
var ProviderConfigSchema = z.object({
|
|
604
|
-
name: z.string(),
|
|
605
|
-
// openai-responses talks to the Responses API (/responses) instead of chat
|
|
606
|
-
// completions — the wire format codex uses, and the native API of
|
|
607
|
-
// reasoning-model relays that otherwise hide reasoning behind empty chat
|
|
608
|
-
// completions. streamChat sends strictSchemas:false + store:false with it.
|
|
609
|
-
protocol: z.enum(["openai-compatible", "anthropic", "openai-responses"]).default("openai-compatible"),
|
|
610
|
-
baseURL: z.string(),
|
|
611
|
-
apiKeyEnv: z.string().optional(),
|
|
612
|
-
apiKey: z.string().optional(),
|
|
613
|
-
headers: z.record(z.string()).optional()
|
|
614
|
-
});
|
|
615
|
-
var ModelConfigSchema = z.object({
|
|
616
|
-
name: z.string(),
|
|
617
|
-
provider: z.string(),
|
|
618
|
-
model: z.string(),
|
|
619
|
-
maxTokens: z.number().int().positive().optional(),
|
|
620
|
-
// Per-model context window; overrides the top-level contextMaxTokens for
|
|
621
|
-
// compaction and the status bar when set.
|
|
622
|
-
contextMaxTokens: z.number().int().positive().optional(),
|
|
623
|
-
// Cost estimation needs BOTH prices (USD per 1M tokens); with only one set
|
|
624
|
-
// the model is treated as unpriced.
|
|
625
|
-
promptPrice: z.number().nonnegative().optional(),
|
|
626
|
-
completionPrice: z.number().nonnegative().optional(),
|
|
627
|
-
// Optional price (USD per 1M tokens) for cache-read prompt tokens. When
|
|
628
|
-
// unset, OpenAI-style cached tokens bill at promptPrice and Anthropic-style
|
|
629
|
-
// cache reads are not billed — set this for accurate cache pricing.
|
|
630
|
-
cacheReadPrice: z.number().nonnegative().optional()
|
|
631
|
-
});
|
|
632
|
-
var PermissionsConfigSchema = z.object({
|
|
633
|
-
allow: z.array(z.string()).default([]),
|
|
634
|
-
deny: z.array(z.string()).default([])
|
|
635
|
-
});
|
|
636
|
-
var HookConfigSchema = z.object({
|
|
637
|
-
event: z.enum(["PreToolUse", "PostToolUse", "Stop"]),
|
|
638
|
-
matcher: z.string().refine(
|
|
639
|
-
(pattern) => {
|
|
640
|
-
try {
|
|
641
|
-
new RegExp(pattern);
|
|
642
|
-
return true;
|
|
643
|
-
} catch {
|
|
644
|
-
return false;
|
|
645
|
-
}
|
|
646
|
-
},
|
|
647
|
-
{ message: "invalid regular expression" }
|
|
648
|
-
).optional(),
|
|
649
|
-
command: z.string().min(1),
|
|
650
|
-
timeoutSec: z.number().positive().default(30)
|
|
651
|
-
});
|
|
652
|
-
var ConfigSchema = z.object({
|
|
653
|
-
defaultModel: z.string().default(""),
|
|
654
|
-
permissionMode: z.enum(["auto", "ask", "readonly", "yolo", "plan"]).default("ask"),
|
|
655
|
-
providers: z.array(ProviderConfigSchema).default([]),
|
|
656
|
-
models: z.array(ModelConfigSchema).default([]),
|
|
657
|
-
maxSteps: z.number().int().positive().default(50),
|
|
658
|
-
contextMaxTokens: z.number().int().positive().default(1e5),
|
|
659
|
-
// Seconds without any stream part before a stalled response is ended
|
|
660
|
-
// gracefully (some relays never send the terminal chunks). Applies once
|
|
661
|
-
// streaming has started; the first part gets a longer, fixed allowance.
|
|
662
|
-
streamIdleTimeoutSec: z.number().positive().default(20),
|
|
663
|
-
// Seconds to wait for the very first stream part before giving up; slow
|
|
664
|
-
// thinking-model relays can buffer for minutes before sending anything.
|
|
665
|
-
streamFirstChunkTimeoutSec: z.number().positive().default(300),
|
|
666
|
-
// Extra attempts per model request when a stream fails transiently
|
|
667
|
-
// (network error, 429/5xx, idle watchdog cutoff) or comes back empty, so a
|
|
668
|
-
// relay hiccup does not silently end a half-finished turn. 0 disables.
|
|
669
|
-
// The wait between attempts honors Retry-After headers and otherwise backs
|
|
670
|
-
// off exponentially with jitter (llm/retry.ts); retries after a
|
|
671
|
-
// timeout/empty failure also scale both stream timeouts up (attempt number
|
|
672
|
-
// ×, capped at 3x) since the relay is likely overloaded.
|
|
673
|
-
streamMaxRetries: z.number().int().min(0).default(3),
|
|
674
|
-
// How many consecutive unproductive replies may be nudged before the turn
|
|
675
|
-
// is handed back: a text-only reply that announces pending work ("我将…",
|
|
676
|
-
// "I will…") or answers a nudge with more words, or an empty reply that
|
|
677
|
-
// survived the stream retry budget (steered with a nudge — a changed
|
|
678
|
-
// request — since identical resends already failed). Tool calls reset the
|
|
679
|
-
// count, so a productive task keeps going; exhaustion surfaces a notice
|
|
680
|
-
// instead of stopping silently. Never fires in plan mode. 0 disables.
|
|
681
|
-
maxAutoContinues: z.number().int().min(0).default(2),
|
|
682
|
-
contextCompaction: z.enum(["summary", "truncate"]).default("summary"),
|
|
683
|
-
// Optional per-session dollar budget; when set, the REPL stops the session
|
|
684
|
-
// once the estimated cost for the session reaches this amount.
|
|
685
|
-
sessionBudgetUsd: z.number().positive().optional(),
|
|
686
|
-
// Terminal bell (REPL only): ring when a turn takes longer than
|
|
687
|
-
// notifyBellThresholdSec, and when a background task finishes.
|
|
688
|
-
// STAR_NO_NOTIFY=1 disables without touching the config.
|
|
689
|
-
notifyBell: z.boolean().default(true),
|
|
690
|
-
notifyBellThresholdSec: z.number().positive().default(10),
|
|
691
|
-
permissions: PermissionsConfigSchema.default({ allow: [], deny: [] }),
|
|
692
|
-
hooks: z.array(HookConfigSchema).default([]),
|
|
693
|
-
// Consecutive identical tool calls (same tool + same arguments) allowed
|
|
694
|
-
// before the loop refuses to execute the repeat and tells the model to
|
|
695
|
-
// change approach — a guard against agents stuck retrying a failing call
|
|
696
|
-
// (opencode's doom_loop). 0 disables.
|
|
697
|
-
doomLoopThreshold: z.number().int().min(0).default(3),
|
|
698
|
-
// Track the whole working tree with an internal git repo (outside the
|
|
699
|
-
// user's .git) at every turn boundary, so /undo and /redo can restore
|
|
700
|
-
// changes made by ANY tool (bash included), not just write_file/edit_file.
|
|
701
|
-
// Falls back silently to per-file snapshots when git is unavailable.
|
|
702
|
-
gitSnapshots: z.boolean().default(true)
|
|
703
|
-
});
|
|
704
|
-
function contextWindowTokens(config, modelName) {
|
|
705
|
-
return config.models.find((m) => m.name === modelName)?.contextMaxTokens ?? config.contextMaxTokens;
|
|
706
|
-
}
|
|
707
|
-
|
|
708
604
|
// src/llm/provider.ts
|
|
709
605
|
import { createAnthropic } from "@ai-sdk/anthropic";
|
|
710
606
|
import { createOpenAI } from "@ai-sdk/openai";
|
|
@@ -931,6 +827,12 @@ function debugWarn(message) {
|
|
|
931
827
|
if (process.env.STAR_DEBUG === "1") process.stderr.write(`[star-cli] ${message}
|
|
932
828
|
`);
|
|
933
829
|
}
|
|
830
|
+
var META_WRITE_MAX_ATTEMPTS = 5;
|
|
831
|
+
var META_WRITE_RETRY_DELAY_MS = 25;
|
|
832
|
+
function isTransientFsError(error) {
|
|
833
|
+
const code = error.code;
|
|
834
|
+
return code === "EPERM" || code === "EACCES" || code === "EBUSY";
|
|
835
|
+
}
|
|
934
836
|
var FALLBACK_META = /* @__PURE__ */ Symbol("star.fallbackMeta");
|
|
935
837
|
function isFallbackMeta(meta) {
|
|
936
838
|
return meta[FALLBACK_META] === true;
|
|
@@ -1005,17 +907,33 @@ var SessionStore = class _SessionStore {
|
|
|
1005
907
|
// writers race the loop's own appends, and a truncated read once turned
|
|
1006
908
|
// fallbackMeta's empty defaults into a persisted wipe of model/cwd/usage.
|
|
1007
909
|
// A branded fallback meta is never written back for the same reason.
|
|
910
|
+
// A write that still fails after retries is dropped rather than thrown:
|
|
911
|
+
// meta.json is bookkeeping the cached meta rebuilds on the next write,
|
|
912
|
+
// while the conversation itself lives in messages.jsonl — killing the
|
|
913
|
+
// agent turn over a stale updatedAt would be the wrong trade.
|
|
1008
914
|
writeMeta(meta) {
|
|
1009
915
|
if (isFallbackMeta(meta)) return Promise.resolve();
|
|
1010
916
|
const tmp = `${this.metaPath()}.tmp-${process.pid}-${this.metaTmpSeq++}`;
|
|
1011
917
|
const run = this.metaWriteQueue.then(async () => {
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
})
|
|
1018
|
-
|
|
918
|
+
for (let attempt = 0; attempt < META_WRITE_MAX_ATTEMPTS; attempt++) {
|
|
919
|
+
try {
|
|
920
|
+
await fs3.writeFile(tmp, JSON.stringify(meta, null, 2));
|
|
921
|
+
await fs3.rename(tmp, this.metaPath());
|
|
922
|
+
return;
|
|
923
|
+
} catch (error) {
|
|
924
|
+
if (isTransientFsError(error) && attempt + 1 < META_WRITE_MAX_ATTEMPTS) {
|
|
925
|
+
await new Promise(
|
|
926
|
+
(resolve) => setTimeout(resolve, META_WRITE_RETRY_DELAY_MS * (attempt + 1))
|
|
927
|
+
);
|
|
928
|
+
continue;
|
|
929
|
+
}
|
|
930
|
+
await fs3.unlink(tmp).catch(() => {
|
|
931
|
+
});
|
|
932
|
+
debugWarn(
|
|
933
|
+
`session ${this.id}: meta write failed (${error.code ?? String(error)}), skipping`
|
|
934
|
+
);
|
|
935
|
+
return;
|
|
936
|
+
}
|
|
1019
937
|
}
|
|
1020
938
|
});
|
|
1021
939
|
this.metaWriteQueue = run.catch(() => {
|
|
@@ -1688,10 +1606,10 @@ async function compactSession(opts) {
|
|
|
1688
1606
|
};
|
|
1689
1607
|
}
|
|
1690
1608
|
const beforeTokens = estimateTokens(messages);
|
|
1691
|
-
const compacted = compactMessages(messages, config.contextMaxTokens);
|
|
1609
|
+
const compacted = compactMessages(messages, config.contextMaxTokens, { force: true });
|
|
1692
1610
|
if (!compacted.compacted) {
|
|
1693
1611
|
return {
|
|
1694
|
-
message: `Nothing to compact:
|
|
1612
|
+
message: `Nothing to compact: history is too short to drop a whole turn (~${beforeTokens} estimated tokens).`,
|
|
1695
1613
|
compacted: false
|
|
1696
1614
|
};
|
|
1697
1615
|
}
|
|
@@ -5003,11 +4921,7 @@ function Repl({
|
|
|
5003
4921
|
};
|
|
5004
4922
|
const onAgentUpdate = (task) => {
|
|
5005
4923
|
setBgLabels(runningTaskLabels());
|
|
5006
|
-
|
|
5007
|
-
pushMessage(
|
|
5008
|
-
"system",
|
|
5009
|
-
task.status === "running" ? `Background subagent ${task.id} started${label}` : `Background subagent ${task.id} ${task.status}${label}`
|
|
5010
|
-
);
|
|
4924
|
+
pushMessage("system", toTerminalSafe(formatAgentTaskUpdate(task)));
|
|
5011
4925
|
if (task.status !== "running") {
|
|
5012
4926
|
notifyBell({
|
|
5013
4927
|
enabled: configRef.current.notifyBell,
|
|
@@ -5638,18 +5552,26 @@ ${estimateCost(usageRef.current, modelNameRef.current, modelConfig)}`;
|
|
|
5638
5552
|
} catch {
|
|
5639
5553
|
summaryModel = null;
|
|
5640
5554
|
}
|
|
5641
|
-
|
|
5642
|
-
|
|
5643
|
-
|
|
5644
|
-
|
|
5645
|
-
|
|
5646
|
-
|
|
5647
|
-
|
|
5648
|
-
|
|
5649
|
-
|
|
5650
|
-
|
|
5555
|
+
tickerStopRef.current = startTicker((tick) => setSpinnerTick(tick));
|
|
5556
|
+
setActivity("compacting context\u2026");
|
|
5557
|
+
try {
|
|
5558
|
+
const result = await compactSession({
|
|
5559
|
+
backend: backendRef.current,
|
|
5560
|
+
sessionStore: sessionStoreRef.current,
|
|
5561
|
+
config,
|
|
5562
|
+
model: summaryModel
|
|
5563
|
+
});
|
|
5564
|
+
if (result.compacted && result.messages) {
|
|
5565
|
+
const display = buildDisplayMessages(result.messages);
|
|
5566
|
+
nextIdRef.current = display.length;
|
|
5567
|
+
applyMessages(display);
|
|
5568
|
+
}
|
|
5569
|
+
return result.message;
|
|
5570
|
+
} finally {
|
|
5571
|
+
tickerStopRef.current?.();
|
|
5572
|
+
tickerStopRef.current = null;
|
|
5573
|
+
setActivity(null);
|
|
5651
5574
|
}
|
|
5652
|
-
return result.message;
|
|
5653
5575
|
},
|
|
5654
5576
|
exportSession: (arg) => exportSession({
|
|
5655
5577
|
backend: backendRef.current,
|
|
@@ -5819,8 +5741,9 @@ ${plan.affected.length} file change(s) will be reverted, ${messageCount} message
|
|
|
5819
5741
|
`permissionMode: ${config.permissionMode}`,
|
|
5820
5742
|
`providers (${config.providers.length}): ${config.providers.map((p) => p.name).join(", ") || "(none)"}`,
|
|
5821
5743
|
`models (${config.models.length}): ${config.models.map((m) => m.name).join(", ") || "(none)"}`,
|
|
5822
|
-
`maxSteps: ${config.maxSteps}`,
|
|
5744
|
+
`maxSteps: ${config.maxSteps === 0 ? "unlimited" : config.maxSteps}`,
|
|
5823
5745
|
`contextMaxTokens: ${config.contextMaxTokens}`,
|
|
5746
|
+
`compactThresholdTokens: ${config.compactThresholdTokens ?? "(context window)"}`,
|
|
5824
5747
|
`streamIdleTimeoutSec: ${config.streamIdleTimeoutSec}`,
|
|
5825
5748
|
`streamFirstChunkTimeoutSec: ${config.streamFirstChunkTimeoutSec}`,
|
|
5826
5749
|
`streamMaxRetries: ${config.streamMaxRetries}`,
|
|
@@ -6099,6 +6022,7 @@ var PROJECT_ALLOWED_KEYS = /* @__PURE__ */ new Set([
|
|
|
6099
6022
|
"models",
|
|
6100
6023
|
"maxSteps",
|
|
6101
6024
|
"contextMaxTokens",
|
|
6025
|
+
"compactThresholdTokens",
|
|
6102
6026
|
"streamIdleTimeoutSec",
|
|
6103
6027
|
"streamFirstChunkTimeoutSec",
|
|
6104
6028
|
"streamMaxRetries",
|