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.
- package/dist/agent/agent.js +43 -15
- package/dist/agent/agent.js.map +1 -1
- package/dist/agent/config.js +61 -20
- package/dist/agent/config.js.map +1 -1
- package/dist/agent/events.bench.js +84 -0
- package/dist/agent/events.bench.js.map +1 -0
- package/dist/agent/prompt-suggestion.js +141 -0
- package/dist/agent/prompt-suggestion.js.map +1 -0
- package/dist/agent/system-prompt.js +15 -0
- package/dist/agent/system-prompt.js.map +1 -1
- package/dist/app-server/protocol.js +7 -0
- package/dist/app-server/protocol.js.map +1 -0
- package/dist/app-server/server.js +241 -0
- package/dist/app-server/server.js.map +1 -0
- package/dist/auth/cli.js +1 -1
- package/dist/auth/cli.js.map +1 -1
- package/dist/auth/credentials.js +33 -2
- package/dist/auth/credentials.js.map +1 -1
- package/dist/auth/flow.js +145 -24
- package/dist/auth/flow.js.map +1 -1
- package/dist/auth/token-manager.js +73 -0
- package/dist/auth/token-manager.js.map +1 -0
- package/dist/cli.js +56 -1
- package/dist/cli.js.map +1 -1
- package/dist/commands/builtins.js +160 -14
- package/dist/commands/builtins.js.map +1 -1
- package/dist/commands/registry.js +46 -1
- package/dist/commands/registry.js.map +1 -1
- package/dist/compaction/monitor.js +38 -0
- package/dist/compaction/monitor.js.map +1 -0
- package/dist/config/types.js.map +1 -1
- package/dist/glue/client.js +12 -2
- package/dist/glue/client.js.map +1 -1
- package/dist/hooks/manager.js +8 -2
- package/dist/hooks/manager.js.map +1 -1
- package/dist/memory/store.js +2 -3
- package/dist/memory/store.js.map +1 -1
- package/dist/plan/run-flow.js +89 -0
- package/dist/plan/run-flow.js.map +1 -0
- package/dist/projects/cli.js +92 -0
- package/dist/projects/cli.js.map +1 -0
- package/dist/projects/client.js +120 -0
- package/dist/projects/client.js.map +1 -0
- package/dist/projects/types.js +2 -0
- package/dist/projects/types.js.map +1 -0
- package/dist/skills/platform-loader.js +133 -38
- package/dist/skills/platform-loader.js.map +1 -1
- package/dist/tools/__test__/mock-tool-context.js +31 -0
- package/dist/tools/__test__/mock-tool-context.js.map +1 -0
- package/dist/tools/file-state-cache.js.map +1 -1
- package/dist/tools/read-file.js +7 -2
- package/dist/tools/read-file.js.map +1 -1
- package/dist/ui/App.js +109 -110
- package/dist/ui/App.js.map +1 -1
- package/dist/ui/CompactionBanner.js +23 -0
- package/dist/ui/CompactionBanner.js.map +1 -0
- package/dist/ui/FirstRunSetup.js +66 -14
- package/dist/ui/FirstRunSetup.js.map +1 -1
- package/dist/ui/Input.js +370 -20
- package/dist/ui/Input.js.map +1 -1
- package/dist/ui/Markdown.js +286 -0
- package/dist/ui/Markdown.js.map +1 -0
- package/dist/ui/Message.js +110 -48
- package/dist/ui/Message.js.map +1 -1
- package/dist/ui/MessageList.js +100 -3
- package/dist/ui/MessageList.js.map +1 -1
- package/dist/ui/Permission.js +43 -20
- package/dist/ui/Permission.js.map +1 -1
- package/dist/ui/PixelC.js +25 -0
- package/dist/ui/PixelC.js.map +1 -0
- package/dist/ui/Status.js +267 -7
- package/dist/ui/Status.js.map +1 -1
- package/dist/ui/Throbber.js +11 -7
- package/dist/ui/Throbber.js.map +1 -1
- package/dist/ui/Welcome.js +59 -0
- package/dist/ui/Welcome.js.map +1 -0
- package/dist/ui/attachments.js +68 -0
- package/dist/ui/attachments.js.map +1 -0
- package/dist/ui/debug-input.js +44 -0
- package/dist/ui/debug-input.js.map +1 -0
- package/dist/ui/diff-summary.js +171 -0
- package/dist/ui/diff-summary.js.map +1 -0
- package/dist/ui/highlight.js +324 -0
- package/dist/ui/highlight.js.map +1 -0
- package/dist/ui/history-store.js +60 -0
- package/dist/ui/history-store.js.map +1 -0
- package/dist/ui/input-state.js +125 -1
- package/dist/ui/input-state.js.map +1 -1
- package/dist/ui/path-complete.js +102 -0
- package/dist/ui/path-complete.js.map +1 -0
- package/dist/ui/paths.js +41 -0
- package/dist/ui/paths.js.map +1 -0
- package/dist/ui/shell-escape.js +42 -0
- package/dist/ui/shell-escape.js.map +1 -0
- package/dist/ui/terminal-restore.js +83 -0
- package/dist/ui/terminal-restore.js.map +1 -0
- package/dist/ui/terminal-title.js +21 -0
- package/dist/ui/terminal-title.js.map +1 -0
- package/dist/ui/tool-call-line.js +83 -0
- package/dist/ui/tool-call-line.js.map +1 -0
- package/dist/ui/tool-labels.js +194 -0
- package/dist/ui/tool-labels.js.map +1 -0
- package/dist/ui/truncated-output.js +42 -0
- package/dist/ui/truncated-output.js.map +1 -0
- package/dist/ui/use-coalesced-agent-events.js +70 -0
- package/dist/ui/use-coalesced-agent-events.js.map +1 -0
- package/dist/ui/use-prompt-suggestion.js +52 -0
- package/dist/ui/use-prompt-suggestion.js.map +1 -0
- package/dist/ui/wrapped-lines.js +18 -0
- package/dist/ui/wrapped-lines.js.map +1 -0
- 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
|
-
*
|
|
3
|
-
*
|
|
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
|
-
*
|
|
6
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
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
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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":"
|
|
1
|
+
{"version":3,"file":"platform-loader.js","sourceRoot":"","sources":["../../src/skills/platform-loader.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC7E,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAI1D,MAAM,gBAAgB,GAAG,iCAAiC,CAAC;AAC3D,MAAM,kBAAkB,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,WAAW,EAAE,OAAO,EAAE,sBAAsB,CAAC,CAAC;AACzF,MAAM,cAAc,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,SAAS;AA4BhD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,MAAM,OAAO,cAAc;IACjB,MAAM,GAAG,UAAmB,CAAC;IAErB,WAAW,CAAmB;IAC9B,OAAO,CAAS;IAChB,SAAS,CAAS;IAClB,KAAK,CAAS;IACd,OAAO,CAAe;IACtB,GAAG,CAAe;IAE3B,QAAQ,GAAmC,IAAI,CAAC;IAExD,YAAY,UAAiC,EAAE;QAC9C,IAAI,CAAC,WAAW,GAAG,OAAO,CAAC,WAAW,IAAI,IAAI,gBAAgB,EAAE,CAAC;QACjE,IAAI,CAAC,OAAO,GAAG,CAAC,OAAO,CAAC,OAAO,IAAI,gBAAgB,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;QACzE,IAAI,CAAC,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,kBAAkB,CAAC;QACzD,IAAI,CAAC,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,cAAc,CAAC;QAC7C,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,UAAU,CAAC,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QACpE,IAAI,CAAC,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC;IACpC,CAAC;IAED,KAAK,CAAC,UAAU;QACf,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,MAAM,EAAE,CAAC;QACnC,OAAO,CAAC,MAAM,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,IAAI,EAAE,OAAgB,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;IAClG,CAAC;IAED,KAAK,CAAC,aAAa;QAClB,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,MAAM,EAAE,CAAC;QACnC,OAAO,CAAC,MAAM,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,IAAI,EAAE,UAAmB,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;IACxG,CAAC;IAED,KAAK,CAAC,WAAW;QAChB,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,MAAM,EAAE,CAAC;QACnC,OAAO,CAAC,MAAM,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,IAAI,EAAE,QAAiB,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;IACpG,CAAC;IAED,uEAAuE;IACvE,UAAU;QACT,IAAI,CAAC;YACJ,IAAI,UAAU,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC;gBAChC,aAAa,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC;YAC3E,CAAC;QACF,CAAC;QAAC,MAAM,CAAC;YACR,YAAY;QACb,CAAC;IACF,CAAC;IAEO,KAAK,CAAC,MAAM;QACnB,uEAAuE;QACvE,sDAAsD;QACtD,IAAI,IAAI,CAAC,QAAQ;YAAE,OAAO,IAAI,CAAC,QAAQ,CAAC;QACxC,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,UAAU,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE;YAC9C,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;QACtB,CAAC,CAAC,CAAC;QACH,OAAO,IAAI,CAAC,QAAQ,CAAC;IACtB,CAAC;IAEO,KAAK,CAAC,UAAU;QACvB,wDAAwD;QACxD,MAAM,KAAK,GAAG,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,CAAC;QACtC,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,WAAW,CAAC,SAAS,CAAC,KAAK,CAAC;YAAE,OAAO,EAAE,CAAC;QAE3D,MAAM,MAAM,GAAG,SAAS,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QACzC,MAAM,KAAK,GAAG,MAAM,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,MAAM,CAAC,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC;QACnE,IAAI,KAAK;YAAE,OAAO,MAAM,CAAC,IAAI,CAAC;QAE9B,IAAI,CAAC;YACJ,MAAM,OAAO,GAA2B;gBACvC,aAAa,EAAE,UAAU,KAAK,CAAC,WAAW,EAAE;gBAC5C,MAAM,EAAE,kBAAkB;aAC1B,CAAC;YACF,IAAI,MAAM,EAAE,IAAI;gBAAE,OAAO,CAAC,eAAe,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC;YAEzD,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,GAAG,IAAI,CAAC,OAAO,SAAS,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC;YAEtE,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG,IAAI,MAAM,EAAE,CAAC;gBAClC,6DAA6D;gBAC7D,UAAU,CAAC,IAAI,CAAC,SAAS,EAAE,EAAE,GAAG,MAAM,EAAE,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;gBACjE,OAAO,MAAM,CAAC,IAAI,CAAC;YACpB,CAAC;YAED,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;gBACxB,6DAA6D;gBAC7D,oCAAoC;gBACpC,MAAM,KAAK,GAAiB,EAAE,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC;gBAChE,UAAU,CAAC,IAAI,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;gBAClC,OAAO,KAAK,CAAC,IAAI,CAAC;YACnB,CAAC;YAED,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;gBACnC,+DAA+D;gBAC/D,OAAO,MAAM,EAAE,IAAI,IAAI,EAAE,CAAC;YAC3B,CAAC;YAED,MAAM,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAmB,CAAC;YAClD,MAAM,IAAI,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,SAAS,CAAC;YAClD,UAAU,CAAC,IAAI,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;YAClE,OAAO,IAAI,CAAC;QACb,CAAC;QAAC,MAAM,CAAC;YACR,4DAA4D;YAC5D,0CAA0C;YAC1C,OAAO,MAAM,EAAE,IAAI,IAAI,EAAE,CAAC;QAC3B,CAAC;IACF,CAAC;CACD;AAED,SAAS,SAAS,CAAC,IAAY;IAC9B,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IACnC,IAAI,CAAC;QACJ,MAAM,GAAG,GAAG,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QACvC,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAiB,CAAC;QAC/C,IAAI,OAAO,MAAM,CAAC,SAAS,KAAK,QAAQ;YAAE,OAAO,IAAI,CAAC;QACtD,IAAI,CAAC,MAAM,CAAC,IAAI,IAAI,OAAO,MAAM,CAAC,IAAI,KAAK,QAAQ;YAAE,OAAO,IAAI,CAAC;QACjE,OAAO,MAAM,CAAC;IACf,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,IAAI,CAAC;IACb,CAAC;AACF,CAAC;AAED,SAAS,UAAU,CAAC,IAAY,EAAE,MAAoB;IACrD,IAAI,CAAC;QACJ,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAC9C,aAAa,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC;IAC7C,CAAC;IAAC,MAAM,CAAC;QACR,oEAAoE;IACrE,CAAC;AACF,CAAC"}
|
|
@@ -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":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACnC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"file-state-cache.js","sourceRoot":"","sources":["../../src/tools/file-state-cache.ts"],"names":[],"mappings":"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"}
|
package/dist/tools/read-file.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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":"AAAA,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACjD,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,OAAO,EAAe,IAAI,EAAE,MAAM,SAAS,CAAC;AAC5C,OAAO,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AACjE,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAGtF,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;IAC1B,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC;QACjB,WAAW,EAAE,uDAAuD;KACpE,CAAC;IACF,MAAM,EAAE,IAAI,CAAC,QAAQ,CACpB,IAAI,CAAC,OAAO,CAAC;QACZ,OAAO,EAAE,CAAC;QACV,WAAW,EAAE,uCAAuC;KACpD,CAAC,CACF;IACD,KAAK,EAAE,IAAI,CAAC,QAAQ,CACnB,IAAI,CAAC,OAAO,CAAC;QACZ,OAAO,EAAE,CAAC;QACV,OAAO,EAAE,IAAI;QACb,WAAW,EAAE,wCAAwC;KACrD,CAAC,CACF;CACD,CAAC,CAAC;AAeH,MAAM,aAAa,GAAG,IAAI,CAAC;AAC3B,MAAM,cAAc,GAAG,CAAC,GAAG,IAAI,GAAG,IAAI,CAAC;AACvC,MAAM,UAAU,GAA2B;IAC1C,MAAM,EAAE,WAAW;IACnB,MAAM,EAAE,YAAY;IACpB,OAAO,EAAE,YAAY;IACrB,MAAM,EAAE,WAAW;IACnB,OAAO,EAAE,YAAY;IACrB,MAAM,EAAE,WAAW;CACnB,CAAC;AAEF,MAAM,WAAW,GAAG;;;;;;;;;;;;uJAYmI,CAAC;AAExJ,MAAM,UAAU,cAAc,CAAC,GAAgB;IAC9C,OAAO;QACN,IAAI,EAAE,WAAW;QACjB,KAAK,EAAE,MAAM;QACb,WAAW,EAAE,WAAW;QACxB,UAAU,EAAE,MAAM;QAClB,aAAa,EAAE,UAAU;QACzB,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,EAAE;YACtC,MAAM,OAAO,GAAG,gBAAgB,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC;YAEvD,IAAI,IAAiC,CAAC;YACtC,IAAI,CAAC;gBACJ,IAAI,GAAG,QAAQ,CAAC,OAAO,CAAC,CAAC;YAC1B,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACd,MAAM,MAAM,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;gBAChE,MAAM,IAAI,KAAK,CAAC,eAAe,MAAM,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC,CAAC;YAC1D,CAAC;YACD,IAAI,IAAI,CAAC,WAAW,EAAE,EAAE,CAAC;gBACxB,MAAM,IAAI,KAAK,CAAC,GAAG,MAAM,CAAC,IAAI,4DAA4D,CAAC,CAAC;YAC7F,CAAC;YAED,eAAe;YACf,MAAM,GAAG,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC,WAAW,EAAE,CAAC;YAC3C,MAAM,IAAI,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC;YAC7B,IAAI,IAAI,EAAE,CAAC;gBACV,IAAI,IAAI,CAAC,IAAI,GAAG,cAAc,EAAE,CAAC;oBAChC,MAAM,IAAI,iBAAiB,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,cAAc,CAAC,CAAC;gBACrE,CAAC;gBACD,MAAM,IAAI,GAAG,YAAY,CAAC,OAAO,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;gBACtD,OAAO;oBACN,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;oBAClD,OAAO,EAAE;wBACR,IAAI,EAAE,OAAO;wBACb,KAAK,EAAE,IAAI,CAAC,IAAI;wBAChB,UAAU,EAAE,CAAC;wBACb,aAAa,EAAE,CAAC;wBAChB,MAAM,EAAE,KAAK;wBACb,GAAG,EAAE,EAAE;wBACP,aAAa,EAAE,KAAK;wBACpB,OAAO,EAAE,IAAI;qBACb;iBACD,CAAC;YACH,CAAC;YAED,IAAI,IAAI,CAAC,IAAI,GAAG,cAAc,EAAE,CAAC;gBAChC,MAAM,IAAI,iBAAiB,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,cAAc,CAAC,CAAC;YACrE,CAAC;YAED,MAAM,GAAG,GAAG,YAAY,CAAC,OAAO,CAAC,CAAC;YAClC,IAAI,cAAc,CAAC,GAAG,CAAC,EAAE,CAAC;gBACzB,MAAM,IAAI,eAAe,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;YACxC,CAAC;YAED,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC;YAC1C,MAAM,GAAG,GAAG,SAAS,CAAC,OAAO,CAAC,CAAC;YAC/B,MAAM,QAAQ,GAAG,OAAO,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;YAC1C,8FAA8F;YAC9F,MAAM,UAAU,GACf,QAAQ,CAAC,MAAM,GAAG,CAAC,IAAI,QAAQ,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC;YAErG,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,IAAI,CAAC,CAAC;YAClC,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,IAAI,aAAa,CAAC;YAC5C,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,GAAG,CAAC,CAAC,CAAC;YACzC,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,QAAQ,GAAG,KAAK,EAAE,UAAU,CAAC,CAAC;YACtD,MAAM,aAAa,GAAG,QAAQ,GAAG,CAAC,IAAI,MAAM,GAAG,UAAU,CAAC;YAE1D,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;YAC/C,MAAM,SAAS,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,IAAI,CAAC,QAAQ,GAAG,CAAC,GAAG,CAAC,CAAC,
|
|
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 {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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:
|
|
82
|
-
clearDisplay: () =>
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
const
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
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(() => {
|