codebase-cli 2.0.0-pre.4 → 2.0.0-pre.40

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.
Files changed (111) hide show
  1. package/dist/agent/agent.js +43 -15
  2. package/dist/agent/agent.js.map +1 -1
  3. package/dist/agent/config.js +61 -20
  4. package/dist/agent/config.js.map +1 -1
  5. package/dist/agent/events.bench.js +84 -0
  6. package/dist/agent/events.bench.js.map +1 -0
  7. package/dist/agent/prompt-suggestion.js +141 -0
  8. package/dist/agent/prompt-suggestion.js.map +1 -0
  9. package/dist/agent/system-prompt.js +15 -0
  10. package/dist/agent/system-prompt.js.map +1 -1
  11. package/dist/app-server/protocol.js +7 -0
  12. package/dist/app-server/protocol.js.map +1 -0
  13. package/dist/app-server/server.js +241 -0
  14. package/dist/app-server/server.js.map +1 -0
  15. package/dist/auth/cli.js +1 -1
  16. package/dist/auth/cli.js.map +1 -1
  17. package/dist/auth/credentials.js +33 -2
  18. package/dist/auth/credentials.js.map +1 -1
  19. package/dist/auth/flow.js +145 -24
  20. package/dist/auth/flow.js.map +1 -1
  21. package/dist/auth/token-manager.js +73 -0
  22. package/dist/auth/token-manager.js.map +1 -0
  23. package/dist/cli.js +56 -1
  24. package/dist/cli.js.map +1 -1
  25. package/dist/commands/builtins.js +160 -14
  26. package/dist/commands/builtins.js.map +1 -1
  27. package/dist/commands/registry.js +46 -1
  28. package/dist/commands/registry.js.map +1 -1
  29. package/dist/compaction/monitor.js +38 -0
  30. package/dist/compaction/monitor.js.map +1 -0
  31. package/dist/config/types.js.map +1 -1
  32. package/dist/glue/client.js +12 -2
  33. package/dist/glue/client.js.map +1 -1
  34. package/dist/hooks/manager.js +8 -2
  35. package/dist/hooks/manager.js.map +1 -1
  36. package/dist/memory/store.js +2 -3
  37. package/dist/memory/store.js.map +1 -1
  38. package/dist/plan/run-flow.js +89 -0
  39. package/dist/plan/run-flow.js.map +1 -0
  40. package/dist/projects/cli.js +92 -0
  41. package/dist/projects/cli.js.map +1 -0
  42. package/dist/projects/client.js +120 -0
  43. package/dist/projects/client.js.map +1 -0
  44. package/dist/projects/types.js +2 -0
  45. package/dist/projects/types.js.map +1 -0
  46. package/dist/skills/platform-loader.js +133 -38
  47. package/dist/skills/platform-loader.js.map +1 -1
  48. package/dist/tools/__test__/mock-tool-context.js +31 -0
  49. package/dist/tools/__test__/mock-tool-context.js.map +1 -0
  50. package/dist/tools/file-state-cache.js.map +1 -1
  51. package/dist/tools/read-file.js +7 -2
  52. package/dist/tools/read-file.js.map +1 -1
  53. package/dist/ui/App.js +109 -110
  54. package/dist/ui/App.js.map +1 -1
  55. package/dist/ui/CompactionBanner.js +23 -0
  56. package/dist/ui/CompactionBanner.js.map +1 -0
  57. package/dist/ui/FirstRunSetup.js +66 -14
  58. package/dist/ui/FirstRunSetup.js.map +1 -1
  59. package/dist/ui/Input.js +370 -20
  60. package/dist/ui/Input.js.map +1 -1
  61. package/dist/ui/Markdown.js +286 -0
  62. package/dist/ui/Markdown.js.map +1 -0
  63. package/dist/ui/Message.js +110 -48
  64. package/dist/ui/Message.js.map +1 -1
  65. package/dist/ui/MessageList.js +100 -3
  66. package/dist/ui/MessageList.js.map +1 -1
  67. package/dist/ui/Permission.js +43 -20
  68. package/dist/ui/Permission.js.map +1 -1
  69. package/dist/ui/PixelC.js +25 -0
  70. package/dist/ui/PixelC.js.map +1 -0
  71. package/dist/ui/Status.js +267 -7
  72. package/dist/ui/Status.js.map +1 -1
  73. package/dist/ui/Throbber.js +11 -7
  74. package/dist/ui/Throbber.js.map +1 -1
  75. package/dist/ui/Welcome.js +59 -0
  76. package/dist/ui/Welcome.js.map +1 -0
  77. package/dist/ui/attachments.js +68 -0
  78. package/dist/ui/attachments.js.map +1 -0
  79. package/dist/ui/debug-input.js +44 -0
  80. package/dist/ui/debug-input.js.map +1 -0
  81. package/dist/ui/diff-summary.js +171 -0
  82. package/dist/ui/diff-summary.js.map +1 -0
  83. package/dist/ui/highlight.js +324 -0
  84. package/dist/ui/highlight.js.map +1 -0
  85. package/dist/ui/history-store.js +60 -0
  86. package/dist/ui/history-store.js.map +1 -0
  87. package/dist/ui/input-state.js +125 -1
  88. package/dist/ui/input-state.js.map +1 -1
  89. package/dist/ui/path-complete.js +102 -0
  90. package/dist/ui/path-complete.js.map +1 -0
  91. package/dist/ui/paths.js +41 -0
  92. package/dist/ui/paths.js.map +1 -0
  93. package/dist/ui/shell-escape.js +42 -0
  94. package/dist/ui/shell-escape.js.map +1 -0
  95. package/dist/ui/terminal-restore.js +83 -0
  96. package/dist/ui/terminal-restore.js.map +1 -0
  97. package/dist/ui/terminal-title.js +21 -0
  98. package/dist/ui/terminal-title.js.map +1 -0
  99. package/dist/ui/tool-call-line.js +83 -0
  100. package/dist/ui/tool-call-line.js.map +1 -0
  101. package/dist/ui/tool-labels.js +194 -0
  102. package/dist/ui/tool-labels.js.map +1 -0
  103. package/dist/ui/truncated-output.js +42 -0
  104. package/dist/ui/truncated-output.js.map +1 -0
  105. package/dist/ui/use-coalesced-agent-events.js +70 -0
  106. package/dist/ui/use-coalesced-agent-events.js.map +1 -0
  107. package/dist/ui/use-prompt-suggestion.js +52 -0
  108. package/dist/ui/use-prompt-suggestion.js.map +1 -0
  109. package/dist/ui/wrapped-lines.js +18 -0
  110. package/dist/ui/wrapped-lines.js.map +1 -0
  111. package/package.json +4 -1
@@ -1,15 +1,20 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import { dirname, join } from "node:path";
4
+ import { CredentialsStore } from "../auth/credentials.js";
5
+ const DEFAULT_BASE_URL = "https://codebase.design/api/cli";
6
+ const DEFAULT_CACHE_PATH = join(homedir(), ".codebase", "cache", "platform-assets.json");
7
+ const DEFAULT_TTL_MS = 60 * 60 * 1000; // 1 hour
1
8
  /**
2
- * Loader that fetches the user's curated skills, templates, and
3
- * prompts from their codebase.foundation account.
9
+ * Fetches the user's curated skills/templates/prompts from
10
+ * codebase.design and merges them into the AssetRegistry. Caches
11
+ * the response under ~/.codebase/cache/platform-assets.json so
12
+ * subsequent CLI launches don't pay the round trip.
4
13
  *
5
- * NOT YET IMPLEMENTED. This file exists as a contract anchor so the
6
- * AssetRegistry already knows about the platform path: when the
7
- * Phase 7+ implementation lands, it slots in here without further
8
- * surgery to the agent or App layer.
14
+ * Wire format (server returns one bundle, the loader fans it out
15
+ * into the three asset kinds):
9
16
  *
10
- * Wire format (planned):
11
- *
12
- * GET https://codebase.foundation/api/cli/skills
17
+ * GET ${baseUrl}/assets
13
18
  * Authorization: Bearer <accessToken>
14
19
  *
15
20
  * 200 OK
@@ -17,47 +22,137 @@
17
22
  * "templates": [ { id, name, description, body, files?, tags? }, … ],
18
23
  * "prompts": [ { id, name, description, body, tags? }, … ] }
19
24
  *
20
- * Cache the response under ~/.codebase/cache/platform-assets.json with
21
- * an ETag header so subsequent CLI launches don't pay the round trip.
22
- * Refresh on `codebase auth refresh` or after 1 hour, whichever is
23
- * sooner. Refresh failures fall back to the cached response — never
24
- * leave a session stranded because the platform is briefly down.
25
+ * Behavior matrix:
25
26
  *
26
- * Not authenticated → returns empty lists, never throws. The user just
27
- * sees their bundled + local assets, which is the correct
28
- * unauthenticated UX.
27
+ * not signed in → returns [] without a network call
28
+ * network error → returns the last cached body if any,
29
+ * else [] (silent — never crashes a session)
30
+ * 404 (endpoint TBD) → caches an empty bundle, returns []
31
+ * (no warning — the backend half is
32
+ * expected to land later, see
33
+ * docs/plans/2026-05-09-codebase-cli-
34
+ * oauth-server-side.md §3-4)
35
+ * 200 → caches body + ETag, returns shaped list
36
+ * 304 (ETag match) → bumps the cache timestamp, returns cached
29
37
  */
30
38
  export class PlatformLoader {
39
+ source = "platform";
31
40
  credentials;
32
41
  baseUrl;
33
- source = "platform";
34
- constructor(credentials, baseUrl = "https://codebase.foundation/api/cli") {
35
- this.credentials = credentials;
36
- this.baseUrl = baseUrl;
42
+ cachePath;
43
+ ttlMs;
44
+ fetchFn;
45
+ now;
46
+ inFlight = null;
47
+ constructor(options = {}) {
48
+ this.credentials = options.credentials ?? new CredentialsStore();
49
+ this.baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
50
+ this.cachePath = options.cachePath ?? DEFAULT_CACHE_PATH;
51
+ this.ttlMs = options.ttlMs ?? DEFAULT_TTL_MS;
52
+ this.fetchFn = options.fetchFn ?? globalThis.fetch.bind(globalThis);
53
+ this.now = options.now ?? Date.now;
37
54
  }
38
55
  async listSkills() {
39
- void this.fetchOrEmpty;
40
- return [];
56
+ const bundle = await this.bundle();
57
+ return (bundle.skills ?? []).map((s) => ({ ...s, kind: "skill", source: this.source }));
41
58
  }
42
59
  async listTemplates() {
43
- return [];
60
+ const bundle = await this.bundle();
61
+ return (bundle.templates ?? []).map((t) => ({ ...t, kind: "template", source: this.source }));
44
62
  }
45
63
  async listPrompts() {
46
- return [];
47
- }
48
- /**
49
- * Placeholder for the real fetch path. Implementation steps:
50
- * 1. Read credentials; if absent or expired, return null.
51
- * 2. GET ${baseUrl}/skills with Authorization: Bearer …
52
- * 3. On 401, trigger refresh via OAuthFlow.refreshAccessToken.
53
- * 4. On success, cache under ~/.codebase/cache/platform-assets.json.
54
- * 5. On any failure post-401-retry, return cached response if any
55
- * and log a one-line warning.
56
- */
57
- async fetchOrEmpty() {
58
- void this.credentials;
59
- void this.baseUrl;
64
+ const bundle = await this.bundle();
65
+ return (bundle.prompts ?? []).map((p) => ({ ...p, kind: "prompt", source: this.source }));
66
+ }
67
+ /** Force the next list call to re-fetch instead of using the cache. */
68
+ invalidate() {
69
+ try {
70
+ if (existsSync(this.cachePath)) {
71
+ writeFileSync(this.cachePath, JSON.stringify({ fetchedAt: 0, body: {} }));
72
+ }
73
+ }
74
+ catch {
75
+ // non-fatal
76
+ }
77
+ }
78
+ async bundle() {
79
+ // Coalesce concurrent calls — listSkills + listTemplates + listPrompts
80
+ // from a single registry pass should share one fetch.
81
+ if (this.inFlight)
82
+ return this.inFlight;
83
+ this.inFlight = this.bundleOnce().finally(() => {
84
+ this.inFlight = null;
85
+ });
86
+ return this.inFlight;
87
+ }
88
+ async bundleOnce() {
89
+ // Not signed in → never fetch, never cache, never warn.
90
+ const creds = this.credentials.load();
91
+ if (!creds || this.credentials.isExpired(creds))
92
+ return {};
93
+ const cached = readCache(this.cachePath);
94
+ const fresh = cached && this.now() - cached.fetchedAt < this.ttlMs;
95
+ if (fresh)
96
+ return cached.body;
97
+ try {
98
+ const headers = {
99
+ Authorization: `Bearer ${creds.accessToken}`,
100
+ Accept: "application/json",
101
+ };
102
+ if (cached?.etag)
103
+ headers["If-None-Match"] = cached.etag;
104
+ const res = await this.fetchFn(`${this.baseUrl}/assets`, { headers });
105
+ if (res.status === 304 && cached) {
106
+ // ETag match — bump the cache timestamp, return cached body.
107
+ writeCache(this.cachePath, { ...cached, fetchedAt: this.now() });
108
+ return cached.body;
109
+ }
110
+ if (res.status === 404) {
111
+ // Endpoint TBD. Cache an empty bundle so we don't hammer the
112
+ // server until the next TTL window.
113
+ const empty = { fetchedAt: this.now(), body: {} };
114
+ writeCache(this.cachePath, empty);
115
+ return empty.body;
116
+ }
117
+ if (res.status === 401 || !res.ok) {
118
+ // Auth blip or transient 5xx — fall back to whatever's cached.
119
+ return cached?.body ?? {};
120
+ }
121
+ const body = (await res.json());
122
+ const etag = res.headers.get("ETag") ?? undefined;
123
+ writeCache(this.cachePath, { fetchedAt: this.now(), etag, body });
124
+ return body;
125
+ }
126
+ catch {
127
+ // Network failures degrade to cached. Never throw — a flaky
128
+ // platform shouldn't take the agent down.
129
+ return cached?.body ?? {};
130
+ }
131
+ }
132
+ }
133
+ function readCache(path) {
134
+ if (!existsSync(path))
135
+ return null;
136
+ try {
137
+ const raw = readFileSync(path, "utf8");
138
+ const parsed = JSON.parse(raw);
139
+ if (typeof parsed.fetchedAt !== "number")
140
+ return null;
141
+ if (!parsed.body || typeof parsed.body !== "object")
142
+ return null;
143
+ return parsed;
144
+ }
145
+ catch {
60
146
  return null;
61
147
  }
62
148
  }
149
+ function writeCache(path, bundle) {
150
+ try {
151
+ mkdirSync(dirname(path), { recursive: true });
152
+ writeFileSync(path, JSON.stringify(bundle));
153
+ }
154
+ catch {
155
+ // non-fatal — the in-memory result still serves the current process
156
+ }
157
+ }
63
158
  //# sourceMappingURL=platform-loader.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"platform-loader.js","sourceRoot":"","sources":["../../src/skills/platform-loader.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,MAAM,OAAO,cAAc;IAIR;IACA;IAJT,MAAM,GAAG,UAAmB,CAAC;IAEtC,YACkB,WAA6B,EAC7B,UAAkB,qCAAqC;QADvD,gBAAW,GAAX,WAAW,CAAkB;QAC7B,YAAO,GAAP,OAAO,CAAgD;IACtE,CAAC;IAEJ,KAAK,CAAC,UAAU;QACf,KAAK,IAAI,CAAC,YAAY,CAAC;QACvB,OAAO,EAAE,CAAC;IACX,CAAC;IAED,KAAK,CAAC,aAAa;QAClB,OAAO,EAAE,CAAC;IACX,CAAC;IAED,KAAK,CAAC,WAAW;QAChB,OAAO,EAAE,CAAC;IACX,CAAC;IAED;;;;;;;;OAQG;IACK,KAAK,CAAC,YAAY;QACzB,KAAK,IAAI,CAAC,WAAW,CAAC;QACtB,KAAK,IAAI,CAAC,OAAO,CAAC;QAClB,OAAO,IAAI,CAAC;IACb,CAAC;CACD"}
1
+ {"version":3,"file":"platform-loader.js","sourceRoot":"","sources":["../../src/skills/platform-loader.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Typed factory for a ToolContext suitable for unit tests. Uses real
3
+ * instances of the in-memory stores (they're cheap to construct) so
4
+ * tests catch interface-shape drift the moment a field is added to
5
+ * ToolContext — previously this was `{} as any` for every field, which
6
+ * meant adding a new required member to ToolContext compiled green
7
+ * while every test was secretly missing it.
8
+ */
9
+ import { MemoryStore } from "../../memory/store.js";
10
+ import { PlanModeStore } from "../../plan/store.js";
11
+ import { UserQueryStore } from "../../user-queries/store.js";
12
+ import { FileStateCache } from "../file-state-cache.js";
13
+ import { TaskStore } from "../task-store.js";
14
+ export function makeMockToolContext(cwd) {
15
+ return {
16
+ cwd,
17
+ fileStateCache: new FileStateCache(),
18
+ tasks: new TaskStore(),
19
+ userQueries: new UserQueryStore(),
20
+ planMode: new PlanModeStore(),
21
+ memory: new MemoryStore({ cwd }),
22
+ // spawnSubagent is the only field a test can't supply a real
23
+ // implementation for (it depends on the live agent factory).
24
+ // We throw to make the boundary explicit: any test that calls a
25
+ // subagent-spawning tool needs to provide its own stub.
26
+ spawnSubagent: () => {
27
+ throw new Error("mock-tool-context: spawnSubagent not stubbed — provide one in the test if you need it");
28
+ },
29
+ };
30
+ }
31
+ //# sourceMappingURL=mock-tool-context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mock-tool-context.js","sourceRoot":"","sources":["../../../src/tools/__test__/mock-tool-context.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACpD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAC7D,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAG7C,MAAM,UAAU,mBAAmB,CAAC,GAAW;IAC9C,OAAO;QACN,GAAG;QACH,cAAc,EAAE,IAAI,cAAc,EAAE;QACpC,KAAK,EAAE,IAAI,SAAS,EAAE;QACtB,WAAW,EAAE,IAAI,cAAc,EAAE;QACjC,QAAQ,EAAE,IAAI,aAAa,EAAE;QAC7B,MAAM,EAAE,IAAI,WAAW,CAAC,EAAE,GAAG,EAAE,CAAC;QAChC,6DAA6D;QAC7D,6DAA6D;QAC7D,gEAAgE;QAChE,wDAAwD;QACxD,aAAa,EAAE,GAAG,EAAE;YACnB,MAAM,IAAI,KAAK,CAAC,uFAAuF,CAAC,CAAC;QAC1G,CAAC;KACD,CAAC;AACH,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"file-state-cache.js","sourceRoot":"","sources":["../../src/tools/file-state-cache.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"file-state-cache.js","sourceRoot":"","sources":["../../src/tools/file-state-cache.ts"],"names":[],"mappings":"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"}
@@ -35,7 +35,7 @@ Arguments:
35
35
  - limit (optional): max lines to return. Default 2000, max 2000.
36
36
 
37
37
  Behavior:
38
- - Text files: returned line-numbered (six-column gutter, tab, content), suitable for the model to quote back into edit_file.
38
+ - Text files: returned line-numbered as \` N→content\` (six-column right-aligned line number, an arrow separator, then the line). When you quote a line into edit_file's old_string, quote only the part after the arrow.
39
39
  - Image files (.png, .jpg, .jpeg, .gif, .webp, .bmp): returned as a vision payload — works only with image-capable models.
40
40
  - Files > 5 MB are rejected; use shell head/tail/sed for huge files.
41
41
  - Binary text files are rejected.
@@ -100,7 +100,12 @@ export function createReadFile(ctx) {
100
100
  const endIdx = Math.min(startIdx + limit, totalLines);
101
101
  const isPartialView = startIdx > 0 || endIdx < totalLines;
102
102
  const slice = allLines.slice(startIdx, endIdx);
103
- const formatted = slice.map((line, i) => `${pad6(startIdx + i + 1)}\t${line}`).join("\n");
103
+ // Use a literal arrow instead of \t so the gutter renders tight in
104
+ // every terminal (TUI / pager / web echo). cat -n's tab gets
105
+ // expanded to the next tab stop by most renderers, producing an
106
+ // ugly 8-column gap. The `${num}→${content}` format also reads
107
+ // well in model context where tab-alignment is unreliable.
108
+ const formatted = slice.map((line, i) => `${pad6(startIdx + i + 1)}→${line}`).join("\n");
104
109
  const tail = isPartialView ? `\n... (showing lines ${startIdx + 1}-${endIdx} of ${totalLines})` : "";
105
110
  ctx.fileStateCache.record({
106
111
  path: absPath,
@@ -1 +1 @@
1
- {"version":3,"file":"read-file.js","sourceRoot":"","sources":["../../src/tools/read-file.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"read-file.js","sourceRoot":"","sources":["../../src/tools/read-file.ts"],"names":[],"mappings":"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"}
package/dist/ui/App.js CHANGED
@@ -7,17 +7,22 @@ import { initialState, reducer } from "../agent/events.js";
7
7
  import { routeUserInput } from "../agent/router.js";
8
8
  import { BUILTIN_COMMANDS } from "../commands/builtins.js";
9
9
  import { CommandRegistry } from "../commands/registry.js";
10
- import { buildAgentPrompt, generatePlan, generateQuestion, MAX_QUESTIONS, parseAnswer, revisePlan, } from "../plan/flow.js";
11
- import { ANSWER_START_BUILDING } from "../plan/types.js";
12
- import { UserQueryCancelled } from "../user-queries/store.js";
10
+ import { runPlanFlow } from "../plan/run-flow.js";
11
+ import { buildAttachmentPrompt, collectAttachments } from "./attachments.js";
12
+ import { CompactionBanner } from "./CompactionBanner.js";
13
13
  import { FirstRunSetup } from "./FirstRunSetup.js";
14
+ import { HistoryStore } from "./history-store.js";
14
15
  import { Input } from "./Input.js";
15
16
  import { MessageList } from "./MessageList.js";
16
17
  import { Permission } from "./Permission.js";
17
18
  import { Status } from "./Status.js";
19
+ import { runShellEscape } from "./shell-escape.js";
18
20
  import { TaskPanel } from "./TaskPanel.js";
19
21
  import { ToolPanel } from "./ToolPanel.js";
20
22
  import { UserQueryView } from "./UserQuery.js";
23
+ import { useCoalescedAgentEvents } from "./use-coalesced-agent-events.js";
24
+ import { usePromptSuggestion } from "./use-prompt-suggestion.js";
25
+ import { Welcome } from "./Welcome.js";
21
26
  export function App() {
22
27
  const { exit } = useApp();
23
28
  const [setupAttempt, setSetupAttempt] = useState(0);
@@ -25,7 +30,12 @@ export function App() {
25
30
  const { bundle, configError } = useMemo(() => {
26
31
  void setupAttempt;
27
32
  try {
28
- return { bundle: createAgent(), configError: undefined };
33
+ // Auto-resume the prior session for this cwd by default. The
34
+ // `--new` CLI flag (parsed in cli.tsx) sets CODEBASE_FRESH so
35
+ // users who explicitly want a clean slate can opt out without
36
+ // having to wipe ~/.codebase/sessions manually.
37
+ const resume = process.env.CODEBASE_FRESH !== "1";
38
+ return { bundle: createAgent({ resume }), configError: undefined };
29
39
  }
30
40
  catch (err) {
31
41
  return {
@@ -49,21 +59,46 @@ function ChatApp({ bundle, onExit }) {
49
59
  const [state, dispatch] = useReducer(reducer, initialState({ provider: bundle.model.provider, id: bundle.model.id, name: bundle.model.name }));
50
60
  const [permRequest, setPermRequest] = useState(bundle.permissions.current());
51
61
  const [userQuery, setUserQuery] = useState(bundle.userQueries.current());
62
+ const [compactionState, setCompactionState] = useState(bundle.compactionMonitor.current());
52
63
  const [statusLines, setStatusLines] = useState([]);
64
+ // Cap the buffer so noisy emits (long /help, many !cmds) don't grow
65
+ // the status pane indefinitely. 50 rows ≈ a screen on most terms.
66
+ const appendStatus = (line) => setStatusLines((prev) => {
67
+ const next = [...prev, line];
68
+ return next.length > 50 ? next.slice(next.length - 50) : next;
69
+ });
53
70
  const [tasks, setTasks] = useState(() => bundle.toolContext.tasks.list());
54
71
  const registry = useMemo(() => {
55
72
  const reg = new CommandRegistry();
56
73
  reg.registerAll(BUILTIN_COMMANDS);
57
74
  return reg;
58
75
  }, []);
76
+ const commandSuggestions = useMemo(() => registry.list().map((c) => ({ name: c.name, description: c.description })), [registry]);
77
+ const historyStore = useMemo(() => new HistoryStore({ cwd: bundle.toolContext.cwd }), [bundle.toolContext.cwd]);
78
+ const persistedHistory = useMemo(() => historyStore.load(), [historyStore]);
79
+ const inputHistory = useMemo(() => {
80
+ // Build the recall list from (persisted history) ++ (this-session prompts),
81
+ // in chronological order. Persisted gives the user prior runs to recall;
82
+ // this-session ensures their most recent prompt is at the top of ↑.
83
+ const out = [...persistedHistory];
84
+ for (const m of state.messages) {
85
+ if (m.role !== "user")
86
+ continue;
87
+ const text = typeof m.content === "string" ? m.content : extractUserText(m.content);
88
+ if (text.trim().length === 0)
89
+ continue;
90
+ if (out[out.length - 1] === text)
91
+ continue;
92
+ out.push(text);
93
+ }
94
+ return out;
95
+ }, [state.messages, persistedHistory]);
96
+ useCoalescedAgentEvents(bundle, dispatch);
59
97
  useEffect(() => {
60
- const unsubscribe = bundle.subscribe((event) => {
61
- dispatch({ type: "agent-event", event });
62
- });
63
- return unsubscribe;
98
+ return bundle.permissions.subscribe((req) => setPermRequest(req));
64
99
  }, [bundle]);
65
100
  useEffect(() => {
66
- return bundle.permissions.subscribe((req) => setPermRequest(req));
101
+ return bundle.compactionMonitor.subscribe((s) => setCompactionState(s));
67
102
  }, [bundle]);
68
103
  useEffect(() => {
69
104
  return bundle.userQueries.subscribe((q) => setUserQuery(q));
@@ -72,29 +107,51 @@ function ChatApp({ bundle, onExit }) {
72
107
  return bundle.toolContext.tasks.subscribe((snapshot) => setTasks(snapshot));
73
108
  }, [bundle]);
74
109
  const busy = state.status === "thinking" || state.status === "streaming" || state.status === "tool";
110
+ const { suggestion, dismiss: dismissSuggestion } = usePromptSuggestion(bundle, state.status, state.messages.length);
75
111
  const handleSubmit = async (text) => {
112
+ // `!cmd` runs a shell command directly without involving the LLM —
113
+ // "I just want to check something real quick." We bypass the agent
114
+ // loop entirely and inject the output as a synthetic user /
115
+ // toolResult pair so it shows up in the transcript but doesn't end
116
+ // up in the model's context.
117
+ if (text.startsWith("!") && text.length > 1) {
118
+ await runShellEscape(text.slice(1), bundle.toolContext.cwd, appendStatus);
119
+ return;
120
+ }
76
121
  // Slash commands first — they bypass the agent.
77
122
  if (text.startsWith("/")) {
78
123
  const result = await registry.dispatch(text, {
79
124
  bundle,
80
125
  state: state,
81
- emit: (line) => setStatusLines((prev) => [...prev, line]),
82
- clearDisplay: () => dispatch({ type: "reset" }),
126
+ emit: appendStatus,
127
+ clearDisplay: () => {
128
+ dispatch({ type: "reset" });
129
+ setStatusLines([]);
130
+ },
83
131
  exit: onExit,
84
- // Inject the registry so /help can list commands.
85
- // biome-ignore lint/suspicious/noExplicitAny: cross-cutting injection
86
132
  registry,
87
- // biome-ignore lint/suspicious/noExplicitAny: cross-cutting injection
88
133
  });
89
134
  if (result.handled)
90
135
  return;
91
136
  }
137
+ // `@path` tokens auto-attach file contents to the prompt so the
138
+ // user doesn't have to spend a tool turn just to put a file in
139
+ // context. Anything that doesn't resolve falls through unchanged
140
+ // — we don't want to silently transform a literal @-mention.
141
+ const attachments = collectAttachments(text, bundle.toolContext.cwd);
142
+ const augmentedText = attachments.length > 0 ? buildAttachmentPrompt(text, attachments) : text;
143
+ if (attachments.length > 0) {
144
+ appendStatus(`Attached: ${attachments.map((a) => a.relPath).join(", ")}`);
145
+ }
92
146
  // Capture history-presence BEFORE the user-prompt dispatch — React's
93
147
  // batched updates mean state.messages won't reflect the new user message
94
148
  // in the same tick, but the router needs to know whether this is a
95
149
  // continuation (greeting fast-track) or a first message.
96
150
  const hadHistory = state.messages.some((m) => m.role === "assistant");
97
151
  dispatch({ type: "user-prompt", text });
152
+ // Persist the raw user input (pre-attachment-augmentation) so ↑ in
153
+ // future sessions recalls what they actually typed.
154
+ historyStore.append(text);
98
155
  try {
99
156
  const route = await routeUserInput(bundle.glue, text, { hasHistory: hadHistory });
100
157
  if (route.kind === "chat") {
@@ -102,119 +159,61 @@ function ChatApp({ bundle, onExit }) {
102
159
  return;
103
160
  }
104
161
  if (route.kind === "plan") {
105
- await runPlanFlow(text);
162
+ await runPlanFlow(bundle, text, {
163
+ onReply: (replyText) => dispatch({ type: "chat-reply", text: replyText }),
164
+ onError: (message) => dispatch({ type: "error", message }),
165
+ });
106
166
  return;
107
167
  }
108
168
  }
109
169
  catch (err) {
110
170
  // If the router itself crashes, don't drop the request — run the agent.
111
171
  // We still log it as a non-fatal status line so users notice.
112
- setStatusLines((prev) => [
113
- ...prev,
114
- `(router fell back to agent: ${err instanceof Error ? err.message : err})`,
115
- ]);
172
+ appendStatus(`(router fell back to agent: ${err instanceof Error ? err.message : err})`);
116
173
  }
117
- bundle.agent.prompt(text).catch((err) => {
174
+ bundle.agent.prompt(augmentedText).catch((err) => {
118
175
  dispatch({ type: "error", message: err instanceof Error ? err.message : String(err) });
119
176
  });
120
177
  };
121
- /**
122
- * Plan-mode flow:
123
- * 1. Q&A loop (up to MAX_QUESTIONS, with the start-building escape).
124
- * 2. Generate plan, render as a synthetic assistant message so the
125
- * user can read it in chat.
126
- * 3. Approve / Revise / Cancel via the UserQuery primitive.
127
- * 4. On approve, hand the original prompt + plan + Q&A to the agent
128
- * with the canonical buildAgentPrompt wrapper so weaker models
129
- * stick to the plan instead of re-planning mid-execution.
130
- */
131
- const runPlanFlow = async (originalPrompt) => {
132
- const qaHistory = [];
133
- try {
134
- for (let i = 0; i < MAX_QUESTIONS; i++) {
135
- const result = await generateQuestion(bundle.glue, originalPrompt, qaHistory, i);
136
- if (result.done || !result.question)
137
- break;
138
- const q = result.question;
139
- const optionLabels = q.options?.map((o) => o.label);
140
- const answer = await bundle.userQueries.ask({
141
- question: q.question,
142
- options: optionLabels,
143
- placeholder: optionLabels ? `1-${optionLabels.length}, or type a free-form answer` : undefined,
144
- });
145
- const resolved = parseAnswer(answer, q);
146
- if (resolved === ANSWER_START_BUILDING)
147
- break;
148
- qaHistory.push({ question: q.question, answer: resolved });
149
- }
150
- let plan = await generatePlan(bundle.glue, originalPrompt, qaHistory);
151
- while (true) {
152
- dispatch({ type: "chat-reply", text: plan });
153
- const decision = await bundle.userQueries.ask({
154
- question: "Approve this plan and run it?",
155
- options: ["Yes — run it", "Revise", "Cancel"],
156
- });
157
- const choice = matchOption(decision, ["Yes — run it", "Revise", "Cancel"]);
158
- if (choice === "Yes — run it") {
159
- const finalPrompt = buildAgentPrompt(originalPrompt, plan, qaHistory);
160
- bundle.agent.prompt(finalPrompt).catch((err) => {
161
- dispatch({ type: "error", message: err instanceof Error ? err.message : String(err) });
162
- });
163
- return;
164
- }
165
- if (choice === "Cancel") {
166
- dispatch({ type: "chat-reply", text: "(plan cancelled)" });
167
- return;
168
- }
169
- const feedback = await bundle.userQueries.ask({
170
- question: "What should change about the plan?",
171
- placeholder: "describe the revision",
172
- });
173
- plan = await revisePlan(bundle.glue, plan, feedback);
174
- }
175
- }
176
- catch (err) {
177
- if (err instanceof UserQueryCancelled) {
178
- dispatch({ type: "chat-reply", text: "(plan cancelled)" });
179
- return;
180
- }
181
- dispatch({
182
- type: "error",
183
- message: `plan flow failed: ${err instanceof Error ? err.message : String(err)}`,
184
- });
185
- }
186
- };
178
+ // Ctrl-C semantics:
179
+ // • While the agent is busy: abort the turn. Stays in the app.
180
+ // A second Ctrl-C within DOUBLE_TAP_MS exits, regardless of
181
+ // whether the previous press aborted or just landed a hint.
182
+ // "Twice real fast" is universally understood as "I want out."
183
+ // While idle: first press posts a hint + arms the exit window.
184
+ //
185
+ // 1000ms is "real fast" wide enough that intentional double-taps
186
+ // register, tight enough that mashing Ctrl-C twice while flustered
187
+ // doesn't accidentally exit. Tunable here if testers want it longer.
188
+ const exitTimerRef = useMemo(() => ({ deadline: 0 }), []);
187
189
  const handleAbort = () => {
190
+ const DOUBLE_TAP_MS = 1000;
191
+ const now = Date.now();
192
+ if (now < exitTimerRef.deadline) {
193
+ onExit();
194
+ return;
195
+ }
196
+ exitTimerRef.deadline = now + DOUBLE_TAP_MS;
188
197
  if (busy) {
189
198
  bundle.agent.abort();
190
199
  dispatch({ type: "abort" });
200
+ // No hint here — the abort itself is the feedback. The
201
+ // exit window is set silently so a quick second tap still
202
+ // gets the user out without confirmation theater.
203
+ return;
191
204
  }
192
- else {
193
- onExit();
194
- }
205
+ appendStatus("Press Ctrl-C again to exit.");
195
206
  };
196
- return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { paddingX: 1, paddingY: 0, marginBottom: 1, children: [_jsx(Text, { bold: true, color: "cyan", children: "codebase v2" }), _jsxs(Text, { dimColor: true, children: [" ", "\u00B7 ", bundle.model.name, " (", bundle.source, ")"] })] }), _jsx(MessageList, { messages: state.messages, streaming: state.streaming }), _jsx(ToolPanel, { tools: state.tools }), _jsx(TaskPanel, { tasks: tasks }), statusLines.length > 0 ? (_jsx(Box, { flexDirection: "column", paddingX: 1, marginBottom: 1, children: statusLines.map((line, i) => (_jsx(Text, { dimColor: true, children: line }, `${i}-${line.slice(0, 8)}`))) })) : null, _jsx(Status, { state: state }), permRequest ? (_jsx(Permission, { request: permRequest, onRespond: (choice) => bundle.permissions.respond(permRequest.id, choice) })) : userQuery ? (_jsx(UserQueryView, { query: userQuery, onAnswer: (answer) => bundle.userQueries.respond(userQuery.id, answer), onCancel: () => bundle.userQueries.cancel(userQuery.id) })) : (_jsx(Input, { disabled: busy, onSubmit: handleSubmit, onAbort: handleAbort }))] }));
207
+ return (_jsxs(Box, { flexDirection: "column", children: [state.messages.length === 0 && !state.streaming ? (_jsx(Welcome, { modelName: bundle.model.name, source: bundle.source, cwd: bundle.toolContext.cwd, resumedFrom: bundle.resumedFrom })) : (_jsxs(Box, { paddingX: 1, paddingY: 0, marginBottom: 1, children: [_jsx(Text, { bold: true, color: "cyan", children: "codebase" }), _jsxs(Text, { dimColor: true, children: [" \u00B7 ", bundle.model.name, " Model"] })] })), _jsx(MessageList, { messages: state.messages, streaming: state.streaming, tools: state.tools }), compactionState.active ? _jsx(CompactionBanner, { state: compactionState }) : null, _jsx(ToolPanel, { tools: state.tools }), _jsx(TaskPanel, { tasks: tasks }), statusLines.length > 0 ? (_jsx(Box, { flexDirection: "column", paddingX: 1, marginBottom: 1, children: statusLines.map((line, i) => (_jsx(Text, { dimColor: true, children: line }, `${i}-${line.slice(0, 8)}`))) })) : null, _jsx(Status, { state: state, cwd: bundle.toolContext.cwd }), permRequest ? (_jsx(Permission, { request: permRequest, onRespond: (choice) => bundle.permissions.respond(permRequest.id, choice) })) : userQuery ? (_jsx(UserQueryView, { query: userQuery, onAnswer: (answer) => bundle.userQueries.respond(userQuery.id, answer), onCancel: () => bundle.userQueries.cancel(userQuery.id) })) : (_jsx(Input, { disabled: busy, onSubmit: handleSubmit, onAbort: handleAbort, commands: commandSuggestions, history: inputHistory, cwd: bundle.toolContext.cwd, suggestion: suggestion, onSuggestionDismiss: dismissSuggestion }))] }));
197
208
  }
198
- /**
199
- * Resolve a user's typed answer to one of the supplied options.
200
- * Accepts the option label (case-insensitive), a 1-based index,
201
- * or the leading word of the label. Falls back to the raw input
202
- * if nothing matches — caller decides what to do with that.
203
- */
204
- function matchOption(answer, options) {
205
- const trimmed = answer.trim();
206
- const idx = Number.parseInt(trimmed, 10);
207
- if (Number.isFinite(idx) && idx >= 1 && idx <= options.length) {
208
- return options[idx - 1];
209
- }
210
- const lower = trimmed.toLowerCase();
211
- for (const option of options) {
212
- if (option.toLowerCase() === lower)
213
- return option;
214
- if (option.toLowerCase().startsWith(lower) && lower.length >= 3)
215
- return option;
216
- }
217
- return trimmed;
209
+ /** Extract the user-visible text from a content array (image messages). */
210
+ function extractUserText(content) {
211
+ if (!Array.isArray(content))
212
+ return "";
213
+ return content
214
+ .filter((b) => b?.type === "text")
215
+ .map((b) => b.text ?? "")
216
+ .join("");
218
217
  }
219
218
  function ExitOnCtrlC({ onExit }) {
220
219
  useEffect(() => {