@gajae-code/coding-agent 0.11.2 → 0.11.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.
- package/CHANGELOG.md +34 -0
- package/dist/types/cli/launch-disposition.d.ts +23 -0
- package/dist/types/cli/worktree-cli.d.ts +8 -25
- package/dist/types/commands/gc.d.ts +4 -0
- package/dist/types/commands/worktree.d.ts +7 -32
- package/dist/types/config/settings-schema.d.ts +85 -10
- package/dist/types/gjc-runtime/gc-runtime.d.ts +9 -0
- package/dist/types/gjc-runtime/state-argv.d.ts +38 -0
- package/dist/types/main.d.ts +1 -0
- package/dist/types/modes/components/custom-editor.d.ts +8 -3
- package/dist/types/modes/components/tool-transcript-format.d.ts +19 -0
- package/dist/types/modes/controllers/input-controller.d.ts +8 -2
- package/dist/types/modes/prompt-action-autocomplete.d.ts +4 -0
- package/dist/types/sdk/broker/broker.d.ts +1 -0
- package/dist/types/sdk/broker/discovery.d.ts +10 -0
- package/dist/types/sdk/broker/ensure.d.ts +24 -1
- package/dist/types/sdk/broker/internal.d.ts +3 -0
- package/dist/types/sdk/broker/session-index.d.ts +12 -0
- package/dist/types/sdk/broker/state-version.d.ts +2 -1
- package/dist/types/sdk/bus/index.d.ts +2 -0
- package/dist/types/sdk/session.d.ts +2 -0
- package/dist/types/tools/ask.d.ts +39 -12
- package/dist/types/tools/browser/screenshot-format.d.ts +8 -0
- package/dist/types/tools/browser/tab-protocol.d.ts +11 -0
- package/dist/types/tools/browser/tab-supervisor.d.ts +1 -1
- package/dist/types/tools/browser/tab-worker-path-resolver.d.ts +2 -0
- package/dist/types/tools/browser/tab-worker-smoke.d.ts +16 -0
- package/dist/types/tools/path-utils.d.ts +9 -14
- package/dist/types/tools/read.d.ts +2 -0
- package/dist/types/tools/render-utils.d.ts +1 -8
- package/dist/types/tools/resource-gc.d.ts +14 -0
- package/dist/types/tools/subagent.d.ts +1 -0
- package/dist/types/utils/file-mentions.d.ts +3 -3
- package/dist/types/utils/image-loading.d.ts +23 -0
- package/dist/types/utils/image-resize.d.ts +2 -0
- package/dist/types/utils/pasted-image-loading.d.ts +39 -0
- package/dist/types/utils/pasted-image-path.d.ts +21 -19
- package/dist/types/web/scrapers/html-to-markdown.d.ts +5 -0
- package/dist/types/web/scrapers/types.d.ts +1 -5
- package/package.json +24 -8
- package/scripts/run-threat-model-check.ts +60 -0
- package/src/cli/launch-disposition.ts +42 -0
- package/src/cli/worktree-cli.ts +9 -287
- package/src/cli.ts +10 -9
- package/src/commands/gc.ts +11 -1
- package/src/commands/sdk.ts +3 -2
- package/src/commands/worktree.ts +10 -53
- package/src/config/model-profiles.ts +15 -15
- package/src/config/settings-schema.ts +55 -10
- package/src/eval/js/shared/runtime.ts +1 -1
- package/src/gjc-runtime/gc-render.ts +19 -1
- package/src/gjc-runtime/gc-runtime.ts +68 -3
- package/src/gjc-runtime/state-argv.ts +292 -0
- package/src/gjc-runtime/state-runtime.ts +51 -212
- package/src/main.ts +99 -22
- package/src/modes/components/custom-editor.ts +103 -32
- package/src/modes/components/custom-provider-wizard.ts +26 -1
- package/src/modes/components/session-observer-overlay.ts +10 -23
- package/src/modes/components/tool-transcript-format.ts +72 -0
- package/src/modes/components/transcript-viewer-overlay.ts +42 -2
- package/src/modes/controllers/input-controller.ts +98 -37
- package/src/modes/controllers/selector-controller.ts +1 -1
- package/src/modes/interactive-mode.ts +13 -2
- package/src/modes/prompt-action-autocomplete.ts +7 -0
- package/src/sdk/broker/broker.ts +139 -32
- package/src/sdk/broker/discovery.ts +125 -0
- package/src/sdk/broker/ensure.ts +103 -19
- package/src/sdk/broker/internal.ts +10 -0
- package/src/sdk/broker/session-index.ts +228 -15
- package/src/sdk/broker/state-version.ts +5 -2
- package/src/sdk/bus/index.ts +4 -1
- package/src/sdk/bus/telegram-daemon.ts +50 -10
- package/src/sdk/host/host.ts +13 -1
- package/src/sdk/session.ts +4 -1
- package/src/session/internal/managed-session-scope.ts +15 -1
- package/src/tools/ask.ts +216 -41
- package/src/tools/bash-allowed-prefixes.ts +79 -29
- package/src/tools/browser/launch.ts +3 -1
- package/src/tools/browser/readable.ts +1 -1
- package/src/tools/browser/screenshot-format.ts +34 -0
- package/src/tools/browser/tab-protocol.ts +3 -0
- package/src/tools/browser/tab-supervisor.ts +125 -122
- package/src/tools/browser/tab-worker-entry.ts +33 -14
- package/src/tools/browser/tab-worker-path-resolver.ts +67 -0
- package/src/tools/browser/tab-worker-smoke.ts +87 -0
- package/src/tools/browser/tab-worker.ts +7 -5
- package/src/tools/output-meta.ts +6 -2
- package/src/tools/path-utils.ts +42 -35
- package/src/tools/read.ts +189 -64
- package/src/tools/render-utils.ts +1 -27
- package/src/tools/resource-gc.ts +123 -34
- package/src/tools/subagent.ts +43 -2
- package/src/utils/file-mentions.ts +4 -4
- package/src/utils/image-loading.ts +85 -34
- package/src/utils/image-resize.ts +16 -9
- package/src/utils/pasted-image-loading.ts +514 -0
- package/src/utils/pasted-image-path.ts +223 -98
- package/src/web/scrapers/html-to-markdown.ts +68 -0
- package/src/web/scrapers/readthedocs.ts +1 -1
- package/src/web/scrapers/types.ts +1 -68
- package/src/web/search/providers/openai-compatible.ts +76 -23
package/src/cli/worktree-cli.ts
CHANGED
|
@@ -1,291 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Tombstone for the removed legacy worktree cleanup API.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* `<parent-repo>/.git/worktrees/<name>/`.
|
|
9
|
-
* - **Task-isolation dirs** (`task/worktree.ts`): a wrapper dir with a
|
|
10
|
-
* `merged` subdir mounted/cloned by `natives.isoStart`. These are ephemeral
|
|
11
|
-
* — `ensureIsolation` always `rm -rf`s the base before re-creating it, so
|
|
12
|
-
* any leftover on disk is a leak from a crashed run.
|
|
13
|
-
*
|
|
14
|
-
* Legacy entries from before the encoding change keep working because git still
|
|
15
|
-
* tracks them by branch name. This command exists to GC them on demand.
|
|
4
|
+
* The recursive-deletion implementation that previously lived here
|
|
5
|
+
* (`listWorktrees`/`clearWorktrees`) was deliberately removed and does not
|
|
6
|
+
* come back through this subpath. This module exists only to fail imports
|
|
7
|
+
* with actionable migration guidance.
|
|
16
8
|
*/
|
|
17
|
-
|
|
18
|
-
import * as path from "node:path";
|
|
19
|
-
import { getWorktreesDir, isEnoent } from "@gajae-code/utils";
|
|
20
|
-
import chalk from "chalk";
|
|
21
|
-
import * as git from "../utils/git";
|
|
22
|
-
|
|
23
|
-
type WorktreeKind = "pr-checkout" | "task-isolation" | "empty" | "stray";
|
|
24
|
-
|
|
25
|
-
export interface WorktreeEntry {
|
|
26
|
-
/** Absolute path to the worktree dir (or stray container) under `~/.gjc/wt/`. */
|
|
27
|
-
path: string;
|
|
28
|
-
/** Classification of what we found on disk. */
|
|
29
|
-
kind: WorktreeKind;
|
|
30
|
-
/** Parent repo root, when this is a registered git worktree. */
|
|
31
|
-
parentRepo?: string;
|
|
32
|
-
/** Branch name extracted from the parent's tracking file, when available. */
|
|
33
|
-
branch?: string;
|
|
34
|
-
/** When set, the entry is unhealthy and `gjc worktree clear` will remove it. */
|
|
35
|
-
orphanReason?: string;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export interface ListWorktreesOptions {
|
|
39
|
-
json: boolean;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export interface ClearWorktreesOptions {
|
|
43
|
-
/** Remove every entry, including live PR-checkout worktrees. */
|
|
44
|
-
all: boolean;
|
|
45
|
-
/** Print what would be removed without touching the filesystem. */
|
|
46
|
-
dryRun: boolean;
|
|
47
|
-
json: boolean;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export async function listWorktrees(options: ListWorktreesOptions): Promise<void> {
|
|
51
|
-
const entries = await scanWorktrees();
|
|
52
|
-
if (options.json) {
|
|
53
|
-
console.log(JSON.stringify(entries, null, 2));
|
|
54
|
-
return;
|
|
55
|
-
}
|
|
56
|
-
if (entries.length === 0) {
|
|
57
|
-
console.log(chalk.dim(`No agent-managed worktrees found under ${getWorktreesDir()}.`));
|
|
58
|
-
return;
|
|
59
|
-
}
|
|
60
|
-
let live = 0;
|
|
61
|
-
let orphaned = 0;
|
|
62
|
-
for (const entry of entries) {
|
|
63
|
-
const tag = entry.orphanReason ? chalk.yellow("orphaned") : chalk.green("live ");
|
|
64
|
-
const detail = formatEntryDetail(entry);
|
|
65
|
-
console.log(`${tag} ${entry.path}`);
|
|
66
|
-
if (detail) console.log(` ${chalk.dim(detail)}`);
|
|
67
|
-
if (entry.orphanReason) orphaned += 1;
|
|
68
|
-
else live += 1;
|
|
69
|
-
}
|
|
70
|
-
console.log(chalk.dim(`\n${live} live · ${orphaned} orphaned · ${entries.length} total`));
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
export async function clearWorktrees(options: ClearWorktreesOptions): Promise<void> {
|
|
74
|
-
const entries = await scanWorktrees();
|
|
75
|
-
const targets = options.all ? entries : entries.filter(entry => entry.orphanReason !== undefined);
|
|
76
|
-
|
|
77
|
-
if (targets.length === 0) {
|
|
78
|
-
if (options.json) {
|
|
79
|
-
console.log(JSON.stringify({ removed: 0, kept: entries.length }));
|
|
80
|
-
} else {
|
|
81
|
-
console.log(chalk.dim(options.all ? "No worktrees to remove." : "No orphaned worktrees to remove."));
|
|
82
|
-
}
|
|
83
|
-
return;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
if (options.dryRun) {
|
|
87
|
-
if (options.json) {
|
|
88
|
-
console.log(JSON.stringify({ wouldRemove: targets.map(t => t.path) }, null, 2));
|
|
89
|
-
} else {
|
|
90
|
-
for (const target of targets) {
|
|
91
|
-
console.log(`${chalk.yellow("would remove")} ${target.path}`);
|
|
92
|
-
}
|
|
93
|
-
console.log(chalk.dim(`\n${targets.length} dir${targets.length === 1 ? "" : "s"} would be removed.`));
|
|
94
|
-
}
|
|
95
|
-
return;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
const results: { path: string; ok: boolean; error?: string }[] = [];
|
|
99
|
-
const parentsToPrune = new Set<string>();
|
|
100
|
-
for (const target of targets) {
|
|
101
|
-
try {
|
|
102
|
-
if (target.kind === "pr-checkout" && target.parentRepo && !target.orphanReason) {
|
|
103
|
-
// Live worktree: ask git to remove it cleanly. If git refuses (locked,
|
|
104
|
-
// dirty, etc.), fall back to fs.rm and rely on `worktree prune` to
|
|
105
|
-
// clean the bookkeeping on the parent side.
|
|
106
|
-
const removed = await git.worktree.tryRemove(target.parentRepo, target.path, { force: true });
|
|
107
|
-
if (!removed) {
|
|
108
|
-
await fs.rm(target.path, { recursive: true, force: true });
|
|
109
|
-
parentsToPrune.add(target.parentRepo);
|
|
110
|
-
}
|
|
111
|
-
} else {
|
|
112
|
-
await fs.rm(target.path, { recursive: true, force: true });
|
|
113
|
-
if (target.parentRepo) parentsToPrune.add(target.parentRepo);
|
|
114
|
-
}
|
|
115
|
-
results.push({ path: target.path, ok: true });
|
|
116
|
-
} catch (err) {
|
|
117
|
-
results.push({ path: target.path, ok: false, error: err instanceof Error ? err.message : String(err) });
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// Best-effort: drop stale entries from each affected parent's `.git/worktrees/`.
|
|
122
|
-
for (const parent of parentsToPrune) {
|
|
123
|
-
try {
|
|
124
|
-
await git.worktree.prune(parent);
|
|
125
|
-
} catch {
|
|
126
|
-
/* parent repo may already be gone or pruned — ignore */
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
const succeeded = results.filter(r => r.ok).length;
|
|
131
|
-
const failed = results.length - succeeded;
|
|
132
|
-
|
|
133
|
-
if (options.json) {
|
|
134
|
-
console.log(JSON.stringify({ removed: succeeded, failed, results }, null, 2));
|
|
135
|
-
if (failed > 0) process.exitCode = 1;
|
|
136
|
-
return;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
for (const result of results) {
|
|
140
|
-
if (result.ok) {
|
|
141
|
-
console.log(`${chalk.green("removed")} ${result.path}`);
|
|
142
|
-
} else {
|
|
143
|
-
console.log(`${chalk.red("failed ")} ${result.path}`);
|
|
144
|
-
if (result.error) console.log(` ${chalk.dim(result.error)}`);
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
console.log(chalk.dim(`\n${succeeded} removed${failed > 0 ? ` · ${chalk.red(`${failed} failed`)}` : ""}`));
|
|
148
|
-
if (failed > 0) process.exitCode = 1;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
// ───────────────────────────────────────────────────────────────────────────
|
|
152
|
-
// Scanner
|
|
153
|
-
// ───────────────────────────────────────────────────────────────────────────
|
|
154
|
-
|
|
155
|
-
async function scanWorktrees(): Promise<WorktreeEntry[]> {
|
|
156
|
-
const root = getWorktreesDir();
|
|
157
|
-
let topLevel: string[];
|
|
158
|
-
try {
|
|
159
|
-
topLevel = await fs.readdir(root);
|
|
160
|
-
} catch (err) {
|
|
161
|
-
if (isEnoent(err)) return [];
|
|
162
|
-
throw err;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
const entries: WorktreeEntry[] = [];
|
|
166
|
-
for (const name of topLevel) {
|
|
167
|
-
const dir = path.join(root, name);
|
|
168
|
-
const stat = await fs.stat(dir).catch(() => null);
|
|
169
|
-
if (!stat?.isDirectory()) continue;
|
|
170
|
-
|
|
171
|
-
const direct = await classifyDir(dir);
|
|
172
|
-
if (direct) {
|
|
173
|
-
entries.push(direct);
|
|
174
|
-
continue;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
// Legacy nesting: ~/.gjc/wt/<encoded-project>/<branch-or-id>
|
|
178
|
-
let children: string[];
|
|
179
|
-
try {
|
|
180
|
-
children = await fs.readdir(dir);
|
|
181
|
-
} catch {
|
|
182
|
-
continue;
|
|
183
|
-
}
|
|
184
|
-
let nested = 0;
|
|
185
|
-
for (const child of children) {
|
|
186
|
-
const childDir = path.join(dir, child);
|
|
187
|
-
const childStat = await fs.stat(childDir).catch(() => null);
|
|
188
|
-
if (!childStat?.isDirectory()) continue;
|
|
189
|
-
const childClassified = await classifyDir(childDir);
|
|
190
|
-
if (childClassified) {
|
|
191
|
-
entries.push(childClassified);
|
|
192
|
-
nested += 1;
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
if (nested === 0) {
|
|
196
|
-
entries.push({
|
|
197
|
-
path: dir,
|
|
198
|
-
kind: children.length === 0 ? "empty" : "stray",
|
|
199
|
-
orphanReason: children.length === 0 ? "empty directory" : "no recognizable worktree contents",
|
|
200
|
-
});
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
return entries;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
async function classifyDir(dir: string): Promise<WorktreeEntry | null> {
|
|
207
|
-
const gitEntry = path.join(dir, ".git");
|
|
208
|
-
const gitStat = await fs.stat(gitEntry).catch(() => null);
|
|
209
|
-
if (gitStat?.isFile()) {
|
|
210
|
-
return classifyPrCheckout(dir, gitEntry);
|
|
211
|
-
}
|
|
212
|
-
const mergedStat = await fs.stat(path.join(dir, "merged")).catch(() => null);
|
|
213
|
-
if (mergedStat?.isDirectory()) {
|
|
214
|
-
return {
|
|
215
|
-
path: dir,
|
|
216
|
-
kind: "task-isolation",
|
|
217
|
-
orphanReason: "task-isolation leftover (no live task owns it)",
|
|
218
|
-
};
|
|
219
|
-
}
|
|
220
|
-
return null;
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
async function classifyPrCheckout(dir: string, gitEntry: string): Promise<WorktreeEntry> {
|
|
224
|
-
let contents: string;
|
|
225
|
-
try {
|
|
226
|
-
contents = await fs.readFile(gitEntry, "utf8");
|
|
227
|
-
} catch (err) {
|
|
228
|
-
return {
|
|
229
|
-
path: dir,
|
|
230
|
-
kind: "pr-checkout",
|
|
231
|
-
orphanReason: `cannot read .git file: ${err instanceof Error ? err.message : String(err)}`,
|
|
232
|
-
};
|
|
233
|
-
}
|
|
234
|
-
const match = /^gitdir:\s*(.+?)\s*$/m.exec(contents);
|
|
235
|
-
const parentGitDir = match?.[1];
|
|
236
|
-
if (!parentGitDir) {
|
|
237
|
-
return { path: dir, kind: "pr-checkout", orphanReason: "malformed .git file (no gitdir line)" };
|
|
238
|
-
}
|
|
239
|
-
// parentGitDir is `<parent-repo>/.git/worktrees/<name>`; back out the repo root.
|
|
240
|
-
const parentRepo = path.dirname(path.dirname(path.dirname(parentGitDir)));
|
|
241
|
-
const branch = await readWorktreeBranch(path.join(parentGitDir, "HEAD"));
|
|
242
|
-
|
|
243
|
-
const parentDirStat = await fs.stat(parentGitDir).catch(() => null);
|
|
244
|
-
if (!parentDirStat?.isDirectory()) {
|
|
245
|
-
return {
|
|
246
|
-
path: dir,
|
|
247
|
-
kind: "pr-checkout",
|
|
248
|
-
parentRepo,
|
|
249
|
-
branch,
|
|
250
|
-
orphanReason: "parent repo no longer tracks this worktree",
|
|
251
|
-
};
|
|
252
|
-
}
|
|
253
|
-
const parentRepoStat = await fs.stat(parentRepo).catch(() => null);
|
|
254
|
-
if (!parentRepoStat?.isDirectory()) {
|
|
255
|
-
return {
|
|
256
|
-
path: dir,
|
|
257
|
-
kind: "pr-checkout",
|
|
258
|
-
parentRepo,
|
|
259
|
-
branch,
|
|
260
|
-
orphanReason: "parent repo missing",
|
|
261
|
-
};
|
|
262
|
-
}
|
|
263
|
-
return { path: dir, kind: "pr-checkout", parentRepo, branch };
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
async function readWorktreeBranch(headFile: string): Promise<string | undefined> {
|
|
267
|
-
try {
|
|
268
|
-
const head = (await fs.readFile(headFile, "utf8")).trim();
|
|
269
|
-
const refMatch = /^ref:\s*refs\/heads\/(.+)$/.exec(head);
|
|
270
|
-
return refMatch?.[1];
|
|
271
|
-
} catch {
|
|
272
|
-
return undefined;
|
|
273
|
-
}
|
|
274
|
-
}
|
|
9
|
+
export {};
|
|
275
10
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
const repo = entry.parentRepo ? path.basename(entry.parentRepo) : "unknown repo";
|
|
280
|
-
const branch = entry.branch ?? "unknown branch";
|
|
281
|
-
parts.push(`${repo} · ${branch}`);
|
|
282
|
-
} else if (entry.kind === "task-isolation") {
|
|
283
|
-
parts.push("task-isolation sandbox");
|
|
284
|
-
} else if (entry.kind === "empty") {
|
|
285
|
-
parts.push("legacy project shell");
|
|
286
|
-
} else {
|
|
287
|
-
parts.push("unrecognized contents");
|
|
288
|
-
}
|
|
289
|
-
if (entry.orphanReason) parts.push(entry.orphanReason);
|
|
290
|
-
return parts.join(" — ");
|
|
291
|
-
}
|
|
11
|
+
throw new Error(
|
|
12
|
+
"@gajae-code/coding-agent/cli/worktree-cli was deliberately removed: the legacy worktree cleanup API (listWorktrees/clearWorktrees) is gone. Inspect leftover managed worktrees under ~/.gjc/wt manually and use `git worktree remove` or `git worktree prune` instead.",
|
|
13
|
+
);
|
package/src/cli.ts
CHANGED
|
@@ -9,6 +9,7 @@ import { Args, type CliConfig, Command, type CommandEntry, Flags, run } from "@g
|
|
|
9
9
|
import { APP_NAME, formatBunRuntimeError, MIN_BUN_VERSION, VERSION } from "@gajae-code/utils/dirs";
|
|
10
10
|
import { runFixtureReport } from "./cli/fixture-report";
|
|
11
11
|
import { isTmuxOwnerIsolationCliArgv, runTmuxOwnerIsolationCliFromStdin } from "./gjc-runtime/tmux-owner-isolation-cli";
|
|
12
|
+
import { smokeTestTabWorker } from "./tools/browser/tab-worker-smoke";
|
|
12
13
|
|
|
13
14
|
if (Bun.semver.order(Bun.version, MIN_BUN_VERSION) < 0) {
|
|
14
15
|
process.stderr.write(
|
|
@@ -252,16 +253,15 @@ function isSubcommand(first: string | undefined): boolean {
|
|
|
252
253
|
}
|
|
253
254
|
|
|
254
255
|
/**
|
|
255
|
-
* Smoke-test entry. Spawns the stats sync worker
|
|
256
|
+
* Smoke-test entry. Spawns the stats sync worker and the browser tab worker, then verifies their protocol.
|
|
256
257
|
*
|
|
257
|
-
* Purpose: catch
|
|
258
|
-
*
|
|
259
|
-
*
|
|
260
|
-
*
|
|
261
|
-
*
|
|
262
|
-
*
|
|
263
|
-
*
|
|
264
|
-
* installs all exercise it on every CI run.
|
|
258
|
+
* Purpose: catch silent compiled-worker load regressions (issues #1011,
|
|
259
|
+
* #1027, and #2598). Neither `--version` nor `stats --summary` spawns both
|
|
260
|
+
* worker entries on a fresh install. This probe proves each bundled worker
|
|
261
|
+
* module resolves and evaluates; the tab-worker probe also completes its
|
|
262
|
+
* bootstrap/closed protocol without launching a browser. Wired into
|
|
263
|
+
* `scripts/install-tests/run-ci.sh` so binary / source-link / tarball installs
|
|
264
|
+
* exercise it on every CI run.
|
|
265
265
|
*/
|
|
266
266
|
async function runSmokeTest(): Promise<void> {
|
|
267
267
|
const { smokeTestSyncWorker } = await import("@gajae-code/stats");
|
|
@@ -278,6 +278,7 @@ async function runSmokeTest(): Promise<void> {
|
|
|
278
278
|
if (typeof h02ScoreSequenceFuzzy !== "function" || typeof h01FindBestFuzzyMatch !== "function") {
|
|
279
279
|
throw new Error("smoke-test: native fuzzy exports missing from embedded addon");
|
|
280
280
|
}
|
|
281
|
+
await smokeTestTabWorker();
|
|
281
282
|
process.stdout.write("smoke-test: ok\n");
|
|
282
283
|
}
|
|
283
284
|
|
package/src/commands/gc.ts
CHANGED
|
@@ -9,9 +9,19 @@ export default class Gc extends Command {
|
|
|
9
9
|
prune: Flags.boolean({ description: "Remove stale records (default: report only)", default: false }),
|
|
10
10
|
force: Flags.boolean({ description: "Alias for --prune (eligible records only)", default: false }),
|
|
11
11
|
"dry-run": Flags.boolean({ description: "Force report-only mode", default: false }),
|
|
12
|
+
"repair-session-index": Flags.boolean({
|
|
13
|
+
description: "Quarantine a corrupt session-index suffix and retain its valid prefix",
|
|
14
|
+
default: false,
|
|
15
|
+
}),
|
|
12
16
|
};
|
|
13
17
|
|
|
14
|
-
static examples = [
|
|
18
|
+
static examples = [
|
|
19
|
+
"gjc gc",
|
|
20
|
+
"gjc gc --json",
|
|
21
|
+
"gjc gc --prune",
|
|
22
|
+
"gjc gc --prune --json",
|
|
23
|
+
"gjc gc --repair-session-index --json",
|
|
24
|
+
];
|
|
15
25
|
|
|
16
26
|
async run(): Promise<void> {
|
|
17
27
|
const result = await runGjcGcCommand(this.argv, process.cwd(), process.env);
|
package/src/commands/sdk.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { Settings } from "../config/settings";
|
|
|
7
7
|
import { applyStartupModelProfiles, createSessionManager } from "../main";
|
|
8
8
|
import { initializeExtensions } from "../modes/runtime-init";
|
|
9
9
|
import { Broker } from "../sdk/broker/broker";
|
|
10
|
+
import { completeBrokerProcess } from "../sdk/broker/internal";
|
|
10
11
|
import {
|
|
11
12
|
type LifecycleTranscriptEvidence,
|
|
12
13
|
readSessionLifecycleLaunchRequest,
|
|
@@ -439,9 +440,9 @@ export default class Sdk extends Command {
|
|
|
439
440
|
});
|
|
440
441
|
await broker.start();
|
|
441
442
|
if (!broker.ownsDiscovery) return;
|
|
442
|
-
const stop = () => void broker.stop()
|
|
443
|
+
const stop = () => void broker.stop();
|
|
443
444
|
process.once("SIGTERM", stop);
|
|
444
445
|
process.once("SIGINT", stop);
|
|
445
|
-
await
|
|
446
|
+
await completeBrokerProcess(broker);
|
|
446
447
|
}
|
|
447
448
|
}
|
package/src/commands/worktree.ts
CHANGED
|
@@ -1,56 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Tombstone for the removed legacy `worktree` CLI command module.
|
|
3
|
+
*
|
|
4
|
+
* The `gjc worktree`/`wt` command was unregistered during the
|
|
5
|
+
* workflow-surface narrowing and its implementation was deliberately
|
|
6
|
+
* removed. This module exists only to fail imports with actionable
|
|
7
|
+
* migration guidance.
|
|
3
8
|
*/
|
|
4
|
-
|
|
5
|
-
import { clearWorktrees, listWorktrees } from "../cli/worktree-cli";
|
|
9
|
+
export {};
|
|
6
10
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
static aliases = ["wt"];
|
|
11
|
-
|
|
12
|
-
static args = {
|
|
13
|
-
// `list` (default) inspects the worktree dir; `clear` removes entries.
|
|
14
|
-
// A positional action keeps `gjc worktree` (the no-arg form) useful.
|
|
15
|
-
action: Args.string({
|
|
16
|
-
description: "list (default) or clear",
|
|
17
|
-
required: false,
|
|
18
|
-
options: ["list", "clear"],
|
|
19
|
-
default: "list",
|
|
20
|
-
}),
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
static flags = {
|
|
24
|
-
all: Flags.boolean({
|
|
25
|
-
description: "Clear every entry, including live PR-checkout worktrees (clear)",
|
|
26
|
-
default: false,
|
|
27
|
-
}),
|
|
28
|
-
"dry-run": Flags.boolean({
|
|
29
|
-
char: "n",
|
|
30
|
-
description: "Print what would be removed without touching the filesystem (clear)",
|
|
31
|
-
default: false,
|
|
32
|
-
}),
|
|
33
|
-
json: Flags.boolean({ char: "j", description: "Emit machine-readable JSON", default: false }),
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
static examples = [
|
|
37
|
-
"gjc worktree",
|
|
38
|
-
"gjc worktree list --json",
|
|
39
|
-
"gjc worktree clear",
|
|
40
|
-
"gjc worktree clear --dry-run",
|
|
41
|
-
"gjc worktree clear --all",
|
|
42
|
-
];
|
|
43
|
-
|
|
44
|
-
async run(): Promise<void> {
|
|
45
|
-
const { args, flags } = await this.parse(Worktree);
|
|
46
|
-
if (args.action === "clear") {
|
|
47
|
-
await clearWorktrees({
|
|
48
|
-
all: flags.all ?? false,
|
|
49
|
-
dryRun: flags["dry-run"] ?? false,
|
|
50
|
-
json: flags.json ?? false,
|
|
51
|
-
});
|
|
52
|
-
return;
|
|
53
|
-
}
|
|
54
|
-
await listWorktrees({ json: flags.json ?? false });
|
|
55
|
-
}
|
|
56
|
-
}
|
|
11
|
+
throw new Error(
|
|
12
|
+
"@gajae-code/coding-agent/commands/worktree was deliberately removed: the `gjc worktree` command and its cleanup implementation are gone. Inspect leftover managed worktrees under ~/.gjc/wt manually and use `git worktree remove` or `git worktree prune` instead.",
|
|
13
|
+
);
|
|
@@ -136,25 +136,25 @@ export const BUILTIN_MODEL_PROFILES: readonly ModelProfileDefinition[] = [
|
|
|
136
136
|
architect: "zai/glm-5.2:xhigh",
|
|
137
137
|
}),
|
|
138
138
|
profile("kimi-coding-plan-eco", ["kimi-code"], {
|
|
139
|
-
default: "kimi-code/
|
|
140
|
-
executor: "kimi-code/
|
|
141
|
-
planner: "kimi-code/
|
|
142
|
-
critic: "kimi-code/
|
|
143
|
-
architect: "kimi-code/
|
|
139
|
+
default: "kimi-code/k3:low",
|
|
140
|
+
executor: "kimi-code/k3:low",
|
|
141
|
+
planner: "kimi-code/k3:low",
|
|
142
|
+
critic: "kimi-code/k3:high",
|
|
143
|
+
architect: "kimi-code/k3:high",
|
|
144
144
|
}),
|
|
145
145
|
profile("kimi-coding-plan-medium", ["kimi-code"], {
|
|
146
|
-
default: "kimi-code/
|
|
147
|
-
executor: "kimi-code/
|
|
148
|
-
planner: "kimi-code/
|
|
149
|
-
critic: "kimi-code/
|
|
150
|
-
architect: "kimi-code/
|
|
146
|
+
default: "kimi-code/k3:high",
|
|
147
|
+
executor: "kimi-code/k3:low",
|
|
148
|
+
planner: "kimi-code/k3:high",
|
|
149
|
+
critic: "kimi-code/k3:high",
|
|
150
|
+
architect: "kimi-code/k3:max",
|
|
151
151
|
}),
|
|
152
152
|
profile("kimi-coding-plan-pro", ["kimi-code"], {
|
|
153
|
-
default: "kimi-code/
|
|
154
|
-
executor: "kimi-code/
|
|
155
|
-
planner: "kimi-code/
|
|
156
|
-
critic: "kimi-code/
|
|
157
|
-
architect: "kimi-code/
|
|
153
|
+
default: "kimi-code/k3:max",
|
|
154
|
+
executor: "kimi-code/k3:high",
|
|
155
|
+
planner: "kimi-code/k3:high",
|
|
156
|
+
critic: "kimi-code/k3:max",
|
|
157
|
+
architect: "kimi-code/k3:max",
|
|
158
158
|
}),
|
|
159
159
|
profile("mimo-eco", ["xiaomi"], {
|
|
160
160
|
default: "xiaomi/mimo-v2.5-pro:low",
|
|
@@ -669,17 +669,17 @@ export const SETTINGS_SCHEMA = {
|
|
|
669
669
|
|
|
670
670
|
"tools.readArtifactSpillThreshold": {
|
|
671
671
|
type: "number",
|
|
672
|
-
default:
|
|
672
|
+
default: 256,
|
|
673
673
|
ui: {
|
|
674
674
|
tab: "tools",
|
|
675
|
-
label: "
|
|
675
|
+
label: "Read artifact spill threshold (KB)",
|
|
676
676
|
description:
|
|
677
|
-
"
|
|
677
|
+
"Explicit large reads above this combined size are saved as an artifact with a bounded head-and-tail snippet inline. Bare reads, directories, and converted-document receipts remain inline.",
|
|
678
678
|
options: [
|
|
679
|
-
{ value: "0", label: "Off", description: "
|
|
679
|
+
{ value: "0", label: "Off", description: "No read-specific spill (backstop only)" },
|
|
680
680
|
{ value: "50", label: "50 KB", description: "~12.5K tokens" },
|
|
681
681
|
{ value: "100", label: "100 KB", description: "~25K tokens" },
|
|
682
|
-
{ value: "256", label: "256 KB", description: "~64K tokens" },
|
|
682
|
+
{ value: "256", label: "256 KB", description: "Default; ~64K tokens" },
|
|
683
683
|
{ value: "512", label: "512 KB", description: "~128K tokens" },
|
|
684
684
|
{ value: "1000", label: "1 MB", description: "~250K tokens" },
|
|
685
685
|
],
|
|
@@ -688,17 +688,17 @@ export const SETTINGS_SCHEMA = {
|
|
|
688
688
|
|
|
689
689
|
"tools.fileMentionInlineBytes": {
|
|
690
690
|
type: "number",
|
|
691
|
-
default:
|
|
691
|
+
default: 10,
|
|
692
692
|
ui: {
|
|
693
693
|
tab: "tools",
|
|
694
694
|
label: "File-mention inline cap (KB)",
|
|
695
695
|
description:
|
|
696
|
-
"Inline byte cap for auto-read `@path` file mentions,
|
|
696
|
+
"Inline byte cap for auto-read `@path` file mentions, aligned with the 10 KiB bare-read receipt so incidental mentions stay within the same bounded context budget. The full file is still available via the read tool.",
|
|
697
697
|
options: [
|
|
698
698
|
{ value: "5", label: "5 KB", description: "~1.25K tokens" },
|
|
699
|
-
{ value: "10", label: "10 KB", description: "~2.5K tokens" },
|
|
700
|
-
{ value: "20", label: "20 KB", description: "
|
|
701
|
-
{ value: "50", label: "50 KB", description: "~12.5K tokens (matches read
|
|
699
|
+
{ value: "10", label: "10 KB", description: "Default; ~2.5K tokens" },
|
|
700
|
+
{ value: "20", label: "20 KB", description: "~5K tokens" },
|
|
701
|
+
{ value: "50", label: "50 KB", description: "~12.5K tokens (matches bare-read receipt)" },
|
|
702
702
|
],
|
|
703
703
|
},
|
|
704
704
|
},
|
|
@@ -2016,6 +2016,51 @@ export const SETTINGS_SCHEMA = {
|
|
|
2016
2016
|
],
|
|
2017
2017
|
},
|
|
2018
2018
|
},
|
|
2019
|
+
"read.receiptBudgetLines": {
|
|
2020
|
+
type: "number",
|
|
2021
|
+
default: 50,
|
|
2022
|
+
ui: {
|
|
2023
|
+
tab: "editing",
|
|
2024
|
+
label: "Read Receipt Line Budget",
|
|
2025
|
+
description: "Maximum lines included in a bare read receipt before a selector footer is shown",
|
|
2026
|
+
options: [
|
|
2027
|
+
{ value: "25", label: "25 lines" },
|
|
2028
|
+
{ value: "50", label: "50 lines", description: "Default" },
|
|
2029
|
+
{ value: "100", label: "100 lines" },
|
|
2030
|
+
{ value: "200", label: "200 lines" },
|
|
2031
|
+
],
|
|
2032
|
+
},
|
|
2033
|
+
},
|
|
2034
|
+
"read.receiptBudgetBytes": {
|
|
2035
|
+
type: "number",
|
|
2036
|
+
default: 10,
|
|
2037
|
+
ui: {
|
|
2038
|
+
tab: "editing",
|
|
2039
|
+
label: "Read Receipt Byte Budget (KB)",
|
|
2040
|
+
description: "Maximum UTF-8 body size for a bare read receipt before a selector footer is shown",
|
|
2041
|
+
options: [
|
|
2042
|
+
{ value: "5", label: "5 KB", description: "~1.25K tokens" },
|
|
2043
|
+
{ value: "10", label: "10 KB", description: "Default; ~2.5K tokens" },
|
|
2044
|
+
{ value: "20", label: "20 KB", description: "~5K tokens" },
|
|
2045
|
+
{ value: "50", label: "50 KB", description: "~12.5K tokens" },
|
|
2046
|
+
],
|
|
2047
|
+
},
|
|
2048
|
+
},
|
|
2049
|
+
"read.summaryMaxBytes": {
|
|
2050
|
+
type: "number",
|
|
2051
|
+
default: 20,
|
|
2052
|
+
ui: {
|
|
2053
|
+
tab: "editing",
|
|
2054
|
+
label: "Read Summary Size Budget (KB)",
|
|
2055
|
+
description: "Maximum UTF-8 size for a structural read summary before additional units are elided",
|
|
2056
|
+
options: [
|
|
2057
|
+
{ value: "10", label: "10 KB", description: "~2.5K tokens" },
|
|
2058
|
+
{ value: "20", label: "20 KB", description: "Default; ~5K tokens" },
|
|
2059
|
+
{ value: "50", label: "50 KB", description: "~12.5K tokens" },
|
|
2060
|
+
{ value: "100", label: "100 KB", description: "~25K tokens" },
|
|
2061
|
+
],
|
|
2062
|
+
},
|
|
2063
|
+
},
|
|
2019
2064
|
|
|
2020
2065
|
"read.summarize.enabled": {
|
|
2021
2066
|
type: "boolean",
|
|
@@ -4,7 +4,7 @@ import * as path from "node:path";
|
|
|
4
4
|
import { pathToFileURL } from "node:url";
|
|
5
5
|
import * as util from "node:util";
|
|
6
6
|
|
|
7
|
-
import
|
|
7
|
+
import * as logger from "@gajae-code/utils/logger";
|
|
8
8
|
|
|
9
9
|
import { ToolError } from "../../../tools/tool-errors";
|
|
10
10
|
import { createHelpers, type HelperBundle } from "./helpers";
|
|
@@ -39,7 +39,11 @@ function renderRecord(record: GcRecord): string {
|
|
|
39
39
|
|
|
40
40
|
export function buildGcReportText(report: GcReport): string {
|
|
41
41
|
const lines: string[] = [];
|
|
42
|
-
|
|
42
|
+
if (report.operation === "repair_session_index") {
|
|
43
|
+
lines.push("gjc gc — session-index repair (other stores are report-only)");
|
|
44
|
+
} else {
|
|
45
|
+
lines.push(report.dry_run ? "gjc gc — dry run (no changes made; pass --prune to remove)" : "gjc gc — prune");
|
|
46
|
+
}
|
|
43
47
|
lines.push("");
|
|
44
48
|
|
|
45
49
|
for (const store of GC_STORES) {
|
|
@@ -53,6 +57,20 @@ export function buildGcReportText(report: GcReport): string {
|
|
|
53
57
|
lines.push("");
|
|
54
58
|
}
|
|
55
59
|
|
|
60
|
+
if (report.session_index) {
|
|
61
|
+
const index = report.session_index;
|
|
62
|
+
lines.push(`Session index: ${index.status}; valid prefix sequence=${index.valid_prefix_seq}`);
|
|
63
|
+
if (index.quarantine_path) lines.push(` Quarantined suffix: ${index.quarantine_path}`);
|
|
64
|
+
if (index.reason) lines.push(` ${index.reason}`);
|
|
65
|
+
if (index.status === "corrupt")
|
|
66
|
+
lines.push(" Run `gjc gc --repair-session-index` to quarantine the corrupt suffix.");
|
|
67
|
+
if (index.status === "unsupported")
|
|
68
|
+
lines.push(" Upgrade GJC before attempting a repair; no index data was changed.");
|
|
69
|
+
if (index.status === "repaired")
|
|
70
|
+
lines.push(" Restart or re-register hosts whose only registration was in the quarantined suffix.");
|
|
71
|
+
lines.push("");
|
|
72
|
+
}
|
|
73
|
+
|
|
56
74
|
if (report.errors.length > 0) {
|
|
57
75
|
lines.push(`Errors (${report.errors.length})`);
|
|
58
76
|
for (const err of report.errors) lines.push(` [${err.store}/${err.scope}] ${err.message}`);
|