@cryer/star-cli 0.3.3 → 0.3.4

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.
@@ -2,10 +2,10 @@
2
2
  import {
3
3
  AgentLoop,
4
4
  PLAN_MODE_PROMPT
5
- } from "./chunk-3L6QKJM4.js";
5
+ } from "./chunk-K3R42LOX.js";
6
6
  import "./chunk-B6P4VUBE.js";
7
7
  export {
8
8
  AgentLoop,
9
9
  PLAN_MODE_PROMPT
10
10
  };
11
- //# sourceMappingURL=loop-TQ7GFTTE.js.map
11
+ //# sourceMappingURL=loop-SDZVEQND.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-3L6QKJM4.js";
47
+ } from "./chunk-K3R42LOX.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";
@@ -1688,10 +1584,10 @@ async function compactSession(opts) {
1688
1584
  };
1689
1585
  }
1690
1586
  const beforeTokens = estimateTokens(messages);
1691
- const compacted = compactMessages(messages, config.contextMaxTokens);
1587
+ const compacted = compactMessages(messages, config.contextMaxTokens, { force: true });
1692
1588
  if (!compacted.compacted) {
1693
1589
  return {
1694
- message: `Nothing to compact: ~${beforeTokens} estimated tokens, below the limit of ${config.contextMaxTokens}.`,
1590
+ message: `Nothing to compact: history is too short to drop a whole turn (~${beforeTokens} estimated tokens).`,
1695
1591
  compacted: false
1696
1592
  };
1697
1593
  }
@@ -5003,11 +4899,7 @@ function Repl({
5003
4899
  };
5004
4900
  const onAgentUpdate = (task) => {
5005
4901
  setBgLabels(runningTaskLabels());
5006
- const label = task.description ? `: ${toTerminalSafe(task.description)}` : "";
5007
- pushMessage(
5008
- "system",
5009
- task.status === "running" ? `Background subagent ${task.id} started${label}` : `Background subagent ${task.id} ${task.status}${label}`
5010
- );
4902
+ pushMessage("system", toTerminalSafe(formatAgentTaskUpdate(task)));
5011
4903
  if (task.status !== "running") {
5012
4904
  notifyBell({
5013
4905
  enabled: configRef.current.notifyBell,
@@ -5638,18 +5530,26 @@ ${estimateCost(usageRef.current, modelNameRef.current, modelConfig)}`;
5638
5530
  } catch {
5639
5531
  summaryModel = null;
5640
5532
  }
5641
- const result = await compactSession({
5642
- backend: backendRef.current,
5643
- sessionStore: sessionStoreRef.current,
5644
- config,
5645
- model: summaryModel
5646
- });
5647
- if (result.compacted && result.messages) {
5648
- const display = buildDisplayMessages(result.messages);
5649
- nextIdRef.current = display.length;
5650
- applyMessages(display);
5533
+ tickerStopRef.current = startTicker((tick) => setSpinnerTick(tick));
5534
+ setActivity("compacting context\u2026");
5535
+ try {
5536
+ const result = await compactSession({
5537
+ backend: backendRef.current,
5538
+ sessionStore: sessionStoreRef.current,
5539
+ config,
5540
+ model: summaryModel
5541
+ });
5542
+ if (result.compacted && result.messages) {
5543
+ const display = buildDisplayMessages(result.messages);
5544
+ nextIdRef.current = display.length;
5545
+ applyMessages(display);
5546
+ }
5547
+ return result.message;
5548
+ } finally {
5549
+ tickerStopRef.current?.();
5550
+ tickerStopRef.current = null;
5551
+ setActivity(null);
5651
5552
  }
5652
- return result.message;
5653
5553
  },
5654
5554
  exportSession: (arg) => exportSession({
5655
5555
  backend: backendRef.current,
@@ -5821,6 +5721,7 @@ ${plan.affected.length} file change(s) will be reverted, ${messageCount} message
5821
5721
  `models (${config.models.length}): ${config.models.map((m) => m.name).join(", ") || "(none)"}`,
5822
5722
  `maxSteps: ${config.maxSteps}`,
5823
5723
  `contextMaxTokens: ${config.contextMaxTokens}`,
5724
+ `compactThresholdTokens: ${config.compactThresholdTokens ?? "(context window)"}`,
5824
5725
  `streamIdleTimeoutSec: ${config.streamIdleTimeoutSec}`,
5825
5726
  `streamFirstChunkTimeoutSec: ${config.streamFirstChunkTimeoutSec}`,
5826
5727
  `streamMaxRetries: ${config.streamMaxRetries}`,
@@ -6099,6 +6000,7 @@ var PROJECT_ALLOWED_KEYS = /* @__PURE__ */ new Set([
6099
6000
  "models",
6100
6001
  "maxSteps",
6101
6002
  "contextMaxTokens",
6003
+ "compactThresholdTokens",
6102
6004
  "streamIdleTimeoutSec",
6103
6005
  "streamFirstChunkTimeoutSec",
6104
6006
  "streamMaxRetries",