@oh-my-pi/pi-coding-agent 16.2.2 → 16.2.3
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 +49 -0
- package/dist/cli.js +3624 -3568
- package/dist/types/advisor/__tests__/config.test.d.ts +1 -0
- package/dist/types/advisor/advise-tool.d.ts +8 -4
- package/dist/types/advisor/config.d.ts +88 -0
- package/dist/types/advisor/index.d.ts +1 -0
- package/dist/types/advisor/transcript-recorder.d.ts +13 -2
- package/dist/types/advisor/watchdog.d.ts +20 -0
- package/dist/types/collab/guest.d.ts +29 -0
- package/dist/types/config/settings-schema.d.ts +81 -0
- package/dist/types/debug/log-viewer.d.ts +1 -0
- package/dist/types/debug/raw-sse.d.ts +1 -0
- package/dist/types/edit/hashline/diff.d.ts +0 -11
- package/dist/types/extensibility/tool-event-input.d.ts +7 -0
- package/dist/types/extensibility/utils.d.ts +12 -0
- package/dist/types/mcp/transports/index.d.ts +1 -0
- package/dist/types/mcp/transports/sse.d.ts +20 -0
- package/dist/types/modes/components/advisor-config.d.ts +59 -0
- package/dist/types/modes/components/index.d.ts +1 -0
- package/dist/types/modes/components/model-selector.d.ts +9 -1
- package/dist/types/modes/components/settings-selector.d.ts +1 -0
- package/dist/types/modes/components/status-line/component.d.ts +30 -1
- package/dist/types/modes/components/status-line/types.d.ts +13 -1
- package/dist/types/modes/controllers/selector-controller.d.ts +1 -0
- package/dist/types/modes/interactive-mode.d.ts +10 -4
- package/dist/types/modes/skill-command.d.ts +32 -0
- package/dist/types/modes/types.d.ts +7 -2
- package/dist/types/session/agent-session.d.ts +58 -10
- package/dist/types/session/indexed-session-storage.d.ts +7 -1
- package/dist/types/session/messages.d.ts +26 -0
- package/dist/types/session/messages.test.d.ts +1 -0
- package/dist/types/session/session-entries.d.ts +31 -3
- package/dist/types/session/session-history-format.d.ts +6 -0
- package/dist/types/session/session-loader.d.ts +9 -1
- package/dist/types/session/session-manager.d.ts +6 -4
- package/dist/types/session/session-storage.d.ts +11 -0
- package/dist/types/session/session-title-slot.d.ts +19 -0
- package/dist/types/ssh/connection-manager.d.ts +47 -0
- package/dist/types/ssh/utils.d.ts +16 -0
- package/dist/types/task/executor.d.ts +3 -16
- package/dist/types/task/render.d.ts +0 -5
- package/dist/types/task/renderer.d.ts +13 -0
- package/dist/types/task/types.d.ts +16 -0
- package/dist/types/task/yield-assembly.d.ts +28 -0
- package/dist/types/tiny/text.d.ts +8 -0
- package/dist/types/tools/render-utils.d.ts +2 -0
- package/dist/types/tools/review.d.ts +6 -4
- package/dist/types/tools/ssh.d.ts +1 -1
- package/dist/types/tools/todo.d.ts +6 -0
- package/dist/types/tools/yield.d.ts +8 -3
- package/dist/types/utils/thinking-display.d.ts +4 -0
- package/package.json +12 -12
- package/src/advisor/__tests__/advisor.test.ts +242 -10
- package/src/advisor/__tests__/config.test.ts +173 -0
- package/src/advisor/advise-tool.ts +11 -6
- package/src/advisor/config.ts +256 -0
- package/src/advisor/index.ts +1 -0
- package/src/advisor/runtime.ts +12 -2
- package/src/advisor/transcript-recorder.ts +25 -2
- package/src/advisor/watchdog.ts +57 -31
- package/src/autoresearch/index.ts +7 -2
- package/src/cli/gc-cli.ts +17 -10
- package/src/collab/guest.ts +43 -7
- package/src/config/model-registry.ts +80 -18
- package/src/config/settings-schema.ts +77 -0
- package/src/debug/index.ts +32 -7
- package/src/debug/log-viewer.ts +111 -53
- package/src/debug/raw-sse.ts +68 -48
- package/src/discovery/codex.ts +13 -5
- package/src/edit/hashline/diff.ts +57 -4
- package/src/eval/js/shared/local-module-loader.ts +23 -1
- package/src/export/html/template.js +13 -7
- package/src/extensibility/extensions/loader.ts +5 -3
- package/src/extensibility/extensions/wrapper.ts +9 -3
- package/src/extensibility/hooks/loader.ts +3 -3
- package/src/extensibility/hooks/tool-wrapper.ts +13 -4
- package/src/extensibility/plugins/manager.ts +2 -1
- package/src/extensibility/tool-event-input.ts +23 -0
- package/src/extensibility/utils.ts +74 -0
- package/src/internal-urls/docs-index.generated.txt +1 -1
- package/src/mcp/client.ts +3 -1
- package/src/mcp/manager.ts +12 -5
- package/src/mcp/transports/index.ts +1 -0
- package/src/mcp/transports/sse.ts +377 -0
- package/src/memories/index.ts +15 -6
- package/src/modes/components/advisor-config.ts +555 -0
- package/src/modes/components/advisor-message.ts +9 -2
- package/src/modes/components/agent-hub.ts +9 -4
- package/src/modes/components/index.ts +2 -0
- package/src/modes/components/model-selector.ts +79 -48
- package/src/modes/components/settings-selector.ts +1 -0
- package/src/modes/components/status-line/component.ts +144 -5
- package/src/modes/components/status-line/segments.ts +46 -21
- package/src/modes/components/status-line/types.ts +13 -1
- package/src/modes/components/tool-execution.ts +47 -6
- package/src/modes/controllers/command-controller.ts +23 -2
- package/src/modes/controllers/event-controller.ts +106 -0
- package/src/modes/controllers/extension-ui-controller.ts +1 -1
- package/src/modes/controllers/input-controller.ts +61 -61
- package/src/modes/controllers/selector-controller.ts +100 -9
- package/src/modes/interactive-mode.ts +65 -5
- package/src/modes/skill-command.ts +116 -0
- package/src/modes/types.ts +7 -2
- package/src/modes/utils/ui-helpers.ts +41 -23
- package/src/prompts/agents/reviewer.md +11 -10
- package/src/prompts/review-custom-request.md +1 -2
- package/src/prompts/review-request.md +1 -2
- package/src/prompts/system/interrupted-thinking.md +7 -0
- package/src/prompts/system/recap-user.md +9 -0
- package/src/prompts/system/subagent-system-prompt.md +8 -5
- package/src/prompts/system/subagent-yield-reminder.md +6 -5
- package/src/prompts/system/system-prompt.md +0 -1
- package/src/prompts/tools/irc.md +2 -2
- package/src/prompts/tools/read.md +2 -2
- package/src/sdk.ts +28 -24
- package/src/session/agent-session.ts +867 -428
- package/src/session/indexed-session-storage.ts +86 -13
- package/src/session/messages.test.ts +125 -0
- package/src/session/messages.ts +172 -9
- package/src/session/redis-session-storage.ts +49 -2
- package/src/session/session-entries.ts +39 -2
- package/src/session/session-history-format.ts +29 -2
- package/src/session/session-listing.ts +54 -24
- package/src/session/session-loader.ts +66 -3
- package/src/session/session-manager.ts +113 -19
- package/src/session/session-persistence.ts +95 -1
- package/src/session/session-storage.ts +36 -0
- package/src/session/session-title-slot.ts +141 -0
- package/src/session/sql-session-storage.ts +71 -11
- package/src/slash-commands/builtin-registry.ts +16 -3
- package/src/ssh/__tests__/connection-manager-args.test.ts +123 -1
- package/src/ssh/__tests__/file-transfer-posix-guard.test.ts +55 -18
- package/src/ssh/connection-manager.ts +139 -12
- package/src/ssh/file-transfer.ts +23 -18
- package/src/ssh/ssh-executor.ts +2 -13
- package/src/ssh/utils.ts +19 -0
- package/src/task/executor.ts +21 -23
- package/src/task/render.ts +162 -20
- package/src/task/renderer.ts +14 -0
- package/src/task/types.ts +17 -0
- package/src/task/yield-assembly.ts +207 -0
- package/src/tiny/text.ts +23 -0
- package/src/tools/ask.ts +55 -4
- package/src/tools/render-utils.ts +2 -0
- package/src/tools/renderers.ts +8 -2
- package/src/tools/review.ts +17 -7
- package/src/tools/ssh.ts +8 -4
- package/src/tools/todo.ts +17 -1
- package/src/tools/yield.ts +140 -31
- package/src/utils/thinking-display.ts +15 -0
package/src/advisor/watchdog.ts
CHANGED
|
@@ -31,79 +31,105 @@ export function formatAdvisorContextPrompt(
|
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
/**
|
|
34
|
-
*
|
|
35
|
-
*
|
|
34
|
+
* A readable config candidate discovered on the watchdog/advisor search path,
|
|
35
|
+
* with raw (un-expanded) content and its position metadata.
|
|
36
36
|
*/
|
|
37
|
-
export
|
|
37
|
+
export interface ConfigCandidate {
|
|
38
|
+
path: string;
|
|
39
|
+
content: string;
|
|
40
|
+
level: "user" | "project";
|
|
41
|
+
depth: number;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Walk the watchdog/advisor config search path — the user agent dir plus every
|
|
46
|
+
* directory from `cwd` up to the repo root (or home), probing both `<F>` and
|
|
47
|
+
* `.omp/<F>` for each given filename — and return the readable candidates with
|
|
48
|
+
* their raw content, sorted user-first then project ancestor→leaf (depth
|
|
49
|
+
* descending, so the leaf directory is most specific/last). Shared by
|
|
50
|
+
* {@link discoverWatchdogFiles} and `discoverAdvisorConfigs`. Content is returned
|
|
51
|
+
* verbatim (no `@import` expansion); callers expand what they need.
|
|
52
|
+
*/
|
|
53
|
+
export async function collectConfigCandidates(
|
|
54
|
+
cwd: string,
|
|
55
|
+
agentDir: string | undefined,
|
|
56
|
+
filenames: string[],
|
|
57
|
+
): Promise<ConfigCandidate[]> {
|
|
38
58
|
const home = os.homedir();
|
|
39
59
|
const resolvedAgentDir = agentDir ?? getAgentDir();
|
|
40
|
-
const
|
|
60
|
+
const userPaths = new Set<string>();
|
|
41
61
|
let repoRoot: string | null = null;
|
|
42
62
|
try {
|
|
43
63
|
repoRoot = await repo.root(cwd);
|
|
44
64
|
} catch (err) {
|
|
45
|
-
logger.debug("Failed to resolve git root for
|
|
65
|
+
logger.debug("Failed to resolve git root for config discovery", { err: String(err) });
|
|
46
66
|
}
|
|
47
67
|
|
|
48
68
|
const candidates = new Set<string>();
|
|
49
69
|
|
|
50
|
-
// 1. User level: ~/.omp
|
|
70
|
+
// 1. User level: ~/.omp/<F> (or active profile agent dir)
|
|
51
71
|
if (resolvedAgentDir) {
|
|
52
|
-
|
|
72
|
+
for (const filename of filenames) {
|
|
73
|
+
const userPath = path.resolve(resolvedAgentDir, filename);
|
|
74
|
+
candidates.add(userPath);
|
|
75
|
+
userPaths.add(userPath);
|
|
76
|
+
}
|
|
53
77
|
}
|
|
54
78
|
|
|
55
79
|
// 2. Project levels (both standalone and native config .omp/): walk up from cwd to repoRoot / home
|
|
56
80
|
let current = cwd;
|
|
57
81
|
while (true) {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
82
|
+
for (const filename of filenames) {
|
|
83
|
+
candidates.add(path.resolve(current, ".omp", filename));
|
|
84
|
+
candidates.add(path.resolve(current, filename));
|
|
85
|
+
}
|
|
61
86
|
if (current === (repoRoot ?? home)) break;
|
|
62
87
|
const parent = path.dirname(current);
|
|
63
88
|
if (parent === current) break;
|
|
64
89
|
current = parent;
|
|
65
90
|
}
|
|
66
91
|
|
|
67
|
-
const items:
|
|
68
|
-
|
|
92
|
+
const items: ConfigCandidate[] = [];
|
|
69
93
|
for (const candidate of candidates) {
|
|
70
94
|
try {
|
|
71
95
|
const content = await Bun.file(candidate).text();
|
|
72
|
-
const expanded = await expandAtImports(content, candidate);
|
|
73
96
|
const parent = path.dirname(candidate);
|
|
74
97
|
const baseName = parent.split(path.sep).pop() ?? "";
|
|
75
|
-
|
|
76
|
-
const isUser = userPath !== null && candidate === userPath;
|
|
98
|
+
const isUser = userPaths.has(candidate);
|
|
77
99
|
const ownerDir = baseName === ".omp" ? path.dirname(parent) : parent;
|
|
78
100
|
const ownerBaseName = ownerDir.split(path.sep).pop() ?? "";
|
|
79
|
-
|
|
80
101
|
if (isUser || !ownerBaseName.startsWith(".") || baseName === ".omp") {
|
|
81
102
|
const relative = path.relative(cwd, ownerDir);
|
|
82
103
|
const depth = relative === "" ? 0 : relative.split(path.sep).filter(Boolean).length;
|
|
83
|
-
items.push({
|
|
84
|
-
path: candidate,
|
|
85
|
-
content: expanded,
|
|
86
|
-
level: isUser ? "user" : "project",
|
|
87
|
-
depth,
|
|
88
|
-
});
|
|
104
|
+
items.push({ path: candidate, content, level: isUser ? "user" : "project", depth });
|
|
89
105
|
}
|
|
90
106
|
} catch (err) {
|
|
91
107
|
if (!isEnoent(err)) {
|
|
92
|
-
logger.warn("Failed to read
|
|
108
|
+
logger.warn("Failed to read config candidate", { path: candidate, error: String(err) });
|
|
93
109
|
}
|
|
94
110
|
}
|
|
95
111
|
}
|
|
96
112
|
|
|
97
|
-
//
|
|
98
|
-
//
|
|
113
|
+
// User level first, then project levels sorted by depth descending — ancestor
|
|
114
|
+
// directories first, the leaf (depth 0) last/most prominent.
|
|
99
115
|
items.sort((a, b) => {
|
|
100
|
-
if (a.level !== b.level)
|
|
101
|
-
return a.level === "user" ? -1 : 1;
|
|
102
|
-
}
|
|
116
|
+
if (a.level !== b.level) return a.level === "user" ? -1 : 1;
|
|
103
117
|
return b.depth - a.depth;
|
|
104
118
|
});
|
|
105
119
|
|
|
106
|
-
return items
|
|
107
|
-
|
|
108
|
-
|
|
120
|
+
return items;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Discover and load WATCHDOG.md files walking up from cwd, project .omp folder, and user agent dir.
|
|
125
|
+
* Returns formatted watchdog file blocks ready to be appended to the advisor system prompt.
|
|
126
|
+
*/
|
|
127
|
+
export async function discoverWatchdogFiles(cwd: string, agentDir?: string): Promise<string[]> {
|
|
128
|
+
const items = await collectConfigCandidates(cwd, agentDir, ["WATCHDOG.md"]);
|
|
129
|
+
const blocks: string[] = [];
|
|
130
|
+
for (const item of items) {
|
|
131
|
+
const expanded = await expandAtImports(item.content, item.path);
|
|
132
|
+
blocks.push(`Especially pay attention to:\n<attention>\n${expanded}\n</attention>`);
|
|
133
|
+
}
|
|
134
|
+
return blocks;
|
|
109
135
|
}
|
|
@@ -320,6 +320,11 @@ export const createAutoresearchExtension: ExtensionFactory = api => {
|
|
|
320
320
|
runtime.lastRunDuration = pendingRun?.durationSeconds ?? runtime.lastRunDuration;
|
|
321
321
|
runtime.lastRunAsi = pendingRun?.parsedAsi ?? runtime.lastRunAsi;
|
|
322
322
|
const state = runtime.state;
|
|
323
|
+
// `event.systemPrompt` is typed `string[]`, but upstream code paths can leave
|
|
324
|
+
// it unset (issue #3665). Coerce defensively so the autoresearch block still
|
|
325
|
+
// renders — the model just loses the upstream prefix for this turn, which is
|
|
326
|
+
// strictly better than crashing the handler.
|
|
327
|
+
const basePrompt = Array.isArray(event.systemPrompt) ? event.systemPrompt.join("\n\n") : "";
|
|
323
328
|
const currentSegmentResults = currentResults(state.results, state.currentSegment);
|
|
324
329
|
const baselineMetric = findBaselineMetric(state.results, state.currentSegment);
|
|
325
330
|
const baselineRunNumber = findBaselineRunNumber(state.results, state.currentSegment);
|
|
@@ -358,7 +363,7 @@ export const createAutoresearchExtension: ExtensionFactory = api => {
|
|
|
358
363
|
return {
|
|
359
364
|
systemPrompt: [
|
|
360
365
|
prompt.render(setupPromptTemplate, {
|
|
361
|
-
base_system_prompt:
|
|
366
|
+
base_system_prompt: basePrompt,
|
|
362
367
|
has_goal: goal.trim().length > 0,
|
|
363
368
|
goal,
|
|
364
369
|
working_dir: ctx.cwd,
|
|
@@ -373,7 +378,7 @@ export const createAutoresearchExtension: ExtensionFactory = api => {
|
|
|
373
378
|
return {
|
|
374
379
|
systemPrompt: [
|
|
375
380
|
prompt.render(promptTemplate, {
|
|
376
|
-
base_system_prompt:
|
|
381
|
+
base_system_prompt: basePrompt,
|
|
377
382
|
has_goal: goal.trim().length > 0,
|
|
378
383
|
goal,
|
|
379
384
|
working_dir: ctx.cwd,
|
package/src/cli/gc-cli.ts
CHANGED
|
@@ -434,17 +434,24 @@ function sessionIdFromSessionPath(sessionPath: string): string | undefined {
|
|
|
434
434
|
}
|
|
435
435
|
|
|
436
436
|
function sessionIdFromSessionText(text: string): string | undefined {
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
437
|
+
let sawTitleSlot = false;
|
|
438
|
+
for (const rawLine of text.split(/\r?\n/)) {
|
|
439
|
+
const line = rawLine.trim();
|
|
440
|
+
if (!line) continue;
|
|
441
|
+
try {
|
|
442
|
+
const record = JSON.parse(line) as { type?: unknown; id?: unknown };
|
|
443
|
+
if (!sawTitleSlot && record.type === "title") {
|
|
444
|
+
sawTitleSlot = true;
|
|
445
|
+
continue;
|
|
446
|
+
}
|
|
447
|
+
return record.type === "session" && typeof record.id === "string" && record.id.length > 0
|
|
448
|
+
? record.id
|
|
449
|
+
: undefined;
|
|
450
|
+
} catch {
|
|
451
|
+
return undefined;
|
|
452
|
+
}
|
|
447
453
|
}
|
|
454
|
+
return undefined;
|
|
448
455
|
}
|
|
449
456
|
|
|
450
457
|
async function archivedSessionIdFromFile(file: string): Promise<string | undefined> {
|
package/src/collab/guest.ts
CHANGED
|
@@ -78,6 +78,46 @@ interface PendingSnapshot {
|
|
|
78
78
|
isResync: boolean;
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
+
/** Minimal context surface the idle-state reconciler mutates. */
|
|
82
|
+
export interface GuestIdleReconcilerCtx {
|
|
83
|
+
statusLine: { markActivityEnd: () => void };
|
|
84
|
+
loadingAnimation: { stop: () => void } | undefined;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Close the guest UI state held open by an earlier `agent_start` whose
|
|
89
|
+
* matching `agent_end` never reached us — most often because a reconnect
|
|
90
|
+
* dropped the event mid-stream. Triggered from {@link CollabGuestLink}'s
|
|
91
|
+
* `state` reconciler when the host reports `isStreaming === false`:
|
|
92
|
+
* folds the in-flight active-time window into the per-session meter (so
|
|
93
|
+
* `time_spent` stops ticking) and stops the `Working…` loader if one is
|
|
94
|
+
* still animating. No-op when the host is still streaming.
|
|
95
|
+
*
|
|
96
|
+
* Exported for direct unit testing; mutates the loader field on `ctx` so
|
|
97
|
+
* the same loader is not stopped twice on subsequent reconciliations.
|
|
98
|
+
*/
|
|
99
|
+
export function reconcileGuestIdleHostState(ctx: GuestIdleReconcilerCtx, isStreaming: boolean): void {
|
|
100
|
+
if (isStreaming) return;
|
|
101
|
+
ctx.statusLine.markActivityEnd();
|
|
102
|
+
if (ctx.loadingAnimation) {
|
|
103
|
+
ctx.loadingAnimation.stop();
|
|
104
|
+
ctx.loadingAnimation = undefined;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** Reconcile a welcome/resync snapshot's host activity state into the guest meter. */
|
|
109
|
+
export interface GuestSnapshotActivityReconcilerCtx extends GuestIdleReconcilerCtx {
|
|
110
|
+
statusLine: GuestIdleReconcilerCtx["statusLine"] & { markActivityStart: () => void };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function reconcileGuestSnapshotHostState(ctx: GuestSnapshotActivityReconcilerCtx, isStreaming: boolean): void {
|
|
114
|
+
if (isStreaming) {
|
|
115
|
+
ctx.statusLine.markActivityStart();
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
reconcileGuestIdleHostState(ctx, false);
|
|
119
|
+
}
|
|
120
|
+
|
|
81
121
|
export class CollabGuestLink {
|
|
82
122
|
#ctx: InteractiveModeContext;
|
|
83
123
|
#socket: CollabSocket | null = null;
|
|
@@ -355,6 +395,7 @@ export class CollabGuestLink {
|
|
|
355
395
|
this.#clearAgentMirror();
|
|
356
396
|
await this.#ctx.session.switchSession(replicaPath);
|
|
357
397
|
this.state = pending.state;
|
|
398
|
+
reconcileGuestSnapshotHostState(this.#ctx, pending.state.isStreaming);
|
|
358
399
|
this.#applyHostState(pending.state);
|
|
359
400
|
this.#ctx.resetObserverRegistry();
|
|
360
401
|
this.#applyAgentSnapshots(pending.agents);
|
|
@@ -425,12 +466,7 @@ export class CollabGuestLink {
|
|
|
425
466
|
this.#applyHostState(frame.state);
|
|
426
467
|
setSessionTerminalTitle(frame.state.sessionName, frame.state.cwd);
|
|
427
468
|
this.#updateStatusSegment();
|
|
428
|
-
|
|
429
|
-
// the host reports idle (e.g. events lost across a reconnect).
|
|
430
|
-
if (!frame.state.isStreaming && this.#ctx.loadingAnimation) {
|
|
431
|
-
this.#ctx.loadingAnimation.stop();
|
|
432
|
-
this.#ctx.loadingAnimation = undefined;
|
|
433
|
-
}
|
|
469
|
+
reconcileGuestIdleHostState(this.#ctx, frame.state.isStreaming);
|
|
434
470
|
this.#ctx.statusLine.invalidate();
|
|
435
471
|
this.#ctx.ui.requestRender();
|
|
436
472
|
break;
|
|
@@ -588,7 +624,7 @@ export class CollabGuestLink {
|
|
|
588
624
|
await this.#ctx.session.newSession();
|
|
589
625
|
setSessionTerminalTitle(this.#ctx.sessionManager.getSessionName(), this.#ctx.sessionManager.getCwd());
|
|
590
626
|
this.#ctx.statusLine.invalidate();
|
|
591
|
-
this.#ctx.statusLine.
|
|
627
|
+
this.#ctx.statusLine.resetActiveTime();
|
|
592
628
|
this.#ctx.updateEditorTopBorder();
|
|
593
629
|
this.#ctx.updateEditorBorderColor();
|
|
594
630
|
this.#ctx.renderInitialMessages({ clearTerminalHistory: true });
|
|
@@ -303,16 +303,82 @@ function resolveConfigValue(valueConfig: string): string | undefined {
|
|
|
303
303
|
return valueConfig;
|
|
304
304
|
}
|
|
305
305
|
|
|
306
|
-
|
|
307
|
-
|
|
306
|
+
type HeaderSource = Record<string, string> | undefined;
|
|
307
|
+
|
|
308
|
+
interface HeaderResolutionOptions {
|
|
309
|
+
authHeader?: boolean;
|
|
310
|
+
apiKeyConfig?: string;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function materializeConfigHeaderSources(
|
|
314
|
+
sources: readonly HeaderSource[],
|
|
315
|
+
options?: HeaderResolutionOptions,
|
|
316
|
+
): Record<string, string> | undefined {
|
|
308
317
|
const resolved: Record<string, string> = {};
|
|
309
|
-
for (const
|
|
310
|
-
|
|
311
|
-
|
|
318
|
+
for (const source of sources) {
|
|
319
|
+
if (!source) continue;
|
|
320
|
+
for (const [key, value] of Object.entries(source)) {
|
|
321
|
+
const next = resolveConfigValue(value);
|
|
322
|
+
if (next) resolved[key] = next;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
if (options?.authHeader && options.apiKeyConfig) {
|
|
326
|
+
const resolvedKey = resolveConfigValue(options.apiKeyConfig);
|
|
327
|
+
if (resolvedKey) resolved.Authorization = `Bearer ${resolvedKey}`;
|
|
312
328
|
}
|
|
313
329
|
return Object.keys(resolved).length > 0 ? resolved : undefined;
|
|
314
330
|
}
|
|
315
331
|
|
|
332
|
+
function createLiveConfigHeaders(
|
|
333
|
+
sources: readonly HeaderSource[],
|
|
334
|
+
options?: HeaderResolutionOptions,
|
|
335
|
+
): Record<string, string> | undefined {
|
|
336
|
+
const liveSources = sources.filter((source): source is Record<string, string> => source !== undefined);
|
|
337
|
+
if (liveSources.length === 0 && (!options?.authHeader || !options.apiKeyConfig)) return undefined;
|
|
338
|
+
|
|
339
|
+
const localHeaders: Record<string, string> = {};
|
|
340
|
+
const allSources = [...liveSources, localHeaders];
|
|
341
|
+
const current = () => materializeConfigHeaderSources(allSources, options) ?? {};
|
|
342
|
+
return new Proxy(localHeaders, {
|
|
343
|
+
get(target, property, receiver) {
|
|
344
|
+
if (typeof property !== "string") return Reflect.get(target, property, receiver);
|
|
345
|
+
return current()[property];
|
|
346
|
+
},
|
|
347
|
+
set(target, property, value) {
|
|
348
|
+
if (typeof property !== "string" || typeof value !== "string") return false;
|
|
349
|
+
target[property] = value;
|
|
350
|
+
return true;
|
|
351
|
+
},
|
|
352
|
+
deleteProperty(target, property) {
|
|
353
|
+
if (typeof property !== "string") return false;
|
|
354
|
+
delete target[property];
|
|
355
|
+
return true;
|
|
356
|
+
},
|
|
357
|
+
has(_target, property) {
|
|
358
|
+
if (typeof property !== "string") return false;
|
|
359
|
+
return Object.hasOwn(current(), property);
|
|
360
|
+
},
|
|
361
|
+
ownKeys() {
|
|
362
|
+
return Reflect.ownKeys(current());
|
|
363
|
+
},
|
|
364
|
+
getOwnPropertyDescriptor(_target, property) {
|
|
365
|
+
if (typeof property !== "string") return undefined;
|
|
366
|
+
const headers = current();
|
|
367
|
+
if (!Object.hasOwn(headers, property)) return undefined;
|
|
368
|
+
return {
|
|
369
|
+
configurable: true,
|
|
370
|
+
enumerable: true,
|
|
371
|
+
value: headers[property],
|
|
372
|
+
writable: true,
|
|
373
|
+
};
|
|
374
|
+
},
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
function resolveConfigHeaders(headers: Record<string, string> | undefined): Record<string, string> | undefined {
|
|
379
|
+
return materializeConfigHeaderSources([headers]);
|
|
380
|
+
}
|
|
381
|
+
|
|
316
382
|
function extractGoogleOAuthToken(value: string | undefined): string | undefined {
|
|
317
383
|
if (!isAuthenticated(value)) return undefined;
|
|
318
384
|
try {
|
|
@@ -495,21 +561,15 @@ function mergeCustomModelHeaders(
|
|
|
495
561
|
authHeader: boolean | undefined,
|
|
496
562
|
apiKeyConfig: string | undefined,
|
|
497
563
|
): Record<string, string> | undefined {
|
|
498
|
-
|
|
499
|
-
return mergeAuthHeader({ ...providerHeaders, ...resolvedModelHeaders }, authHeader, apiKeyConfig);
|
|
564
|
+
return createLiveConfigHeaders([providerHeaders, modelHeaders], { authHeader, apiKeyConfig });
|
|
500
565
|
}
|
|
501
566
|
|
|
502
|
-
function
|
|
503
|
-
|
|
567
|
+
function mergeAuthHeaderSources(
|
|
568
|
+
sources: readonly HeaderSource[],
|
|
504
569
|
authHeader: boolean | undefined,
|
|
505
570
|
apiKeyConfig: string | undefined,
|
|
506
571
|
): Record<string, string> | undefined {
|
|
507
|
-
|
|
508
|
-
if (!authHeader || !apiKeyConfig) {
|
|
509
|
-
return nextHeaders;
|
|
510
|
-
}
|
|
511
|
-
const resolvedKey = resolveConfigValue(apiKeyConfig);
|
|
512
|
-
return resolvedKey ? { ...nextHeaders, Authorization: `Bearer ${resolvedKey}` } : nextHeaders;
|
|
572
|
+
return createLiveConfigHeaders(sources, { authHeader, apiKeyConfig });
|
|
513
573
|
}
|
|
514
574
|
|
|
515
575
|
/**
|
|
@@ -1661,7 +1721,9 @@ export class ModelRegistry {
|
|
|
1661
1721
|
baseUrl: override.baseUrl ?? baseOverride?.baseUrl,
|
|
1662
1722
|
apiKey: override.apiKey ?? baseOverride?.apiKey,
|
|
1663
1723
|
authHeader: override.authHeader ?? baseOverride?.authHeader,
|
|
1664
|
-
headers: override.headers
|
|
1724
|
+
headers: override.headers
|
|
1725
|
+
? createLiveConfigHeaders([baseOverride?.headers, override.headers])
|
|
1726
|
+
: baseOverride?.headers,
|
|
1665
1727
|
compat: override.compat ? mergeCompat(baseOverride?.compat, override.compat) : baseOverride?.compat,
|
|
1666
1728
|
remoteCompaction: mergeRemoteCompactionConfig(baseOverride?.remoteCompaction, override.remoteCompaction),
|
|
1667
1729
|
transport: override.transport ?? baseOverride?.transport,
|
|
@@ -1676,8 +1738,8 @@ export class ModelRegistry {
|
|
|
1676
1738
|
"baseUrl" | "headers" | "authHeader" | "apiKey" | "remoteCompaction" | "transport"
|
|
1677
1739
|
>,
|
|
1678
1740
|
): T {
|
|
1679
|
-
const headers =
|
|
1680
|
-
override.headers ?
|
|
1741
|
+
const headers = mergeAuthHeaderSources(
|
|
1742
|
+
override.headers ? [entry.headers, override.headers] : [entry.headers],
|
|
1681
1743
|
override.authHeader,
|
|
1682
1744
|
override.apiKey,
|
|
1683
1745
|
);
|
|
@@ -600,6 +600,17 @@ export const SETTINGS_SCHEMA = {
|
|
|
600
600
|
"Use the terminal's default background for the status line instead of the theme's `statusLineBg`. Powerline end caps are dropped because they need a contrasting fill to bridge into the surrounding terminal.",
|
|
601
601
|
},
|
|
602
602
|
},
|
|
603
|
+
"statusLine.compactThinkingLevel": {
|
|
604
|
+
type: "boolean",
|
|
605
|
+
default: false,
|
|
606
|
+
ui: {
|
|
607
|
+
tab: "appearance",
|
|
608
|
+
group: "Status Line",
|
|
609
|
+
label: "Compact Thinking Level",
|
|
610
|
+
description:
|
|
611
|
+
"Show the thinking level as a single icon on the model name instead of a separate ` · <level>` suffix.",
|
|
612
|
+
},
|
|
613
|
+
},
|
|
603
614
|
"tools.artifactSpillThreshold": {
|
|
604
615
|
type: "number",
|
|
605
616
|
default: 50,
|
|
@@ -1633,6 +1644,35 @@ export const SETTINGS_SCHEMA = {
|
|
|
1633
1644
|
},
|
|
1634
1645
|
},
|
|
1635
1646
|
|
|
1647
|
+
"recap.enabled": {
|
|
1648
|
+
type: "boolean",
|
|
1649
|
+
default: true,
|
|
1650
|
+
ui: {
|
|
1651
|
+
tab: "interaction",
|
|
1652
|
+
group: "Notifications",
|
|
1653
|
+
label: "Idle Recap",
|
|
1654
|
+
description: "Generate a brief LLM recap of where things stand after the terminal has been idle",
|
|
1655
|
+
},
|
|
1656
|
+
},
|
|
1657
|
+
|
|
1658
|
+
"recap.idleSeconds": {
|
|
1659
|
+
type: "number",
|
|
1660
|
+
default: 240,
|
|
1661
|
+
ui: {
|
|
1662
|
+
tab: "interaction",
|
|
1663
|
+
group: "Notifications",
|
|
1664
|
+
label: "Idle Recap Delay",
|
|
1665
|
+
description: "Seconds to wait while idle before showing the recap",
|
|
1666
|
+
options: [
|
|
1667
|
+
{ value: "60", label: "1 minute" },
|
|
1668
|
+
{ value: "120", label: "2 minutes" },
|
|
1669
|
+
{ value: "240", label: "4 minutes" },
|
|
1670
|
+
{ value: "300", label: "5 minutes" },
|
|
1671
|
+
{ value: "600", label: "10 minutes" },
|
|
1672
|
+
],
|
|
1673
|
+
},
|
|
1674
|
+
},
|
|
1675
|
+
|
|
1636
1676
|
// Collab
|
|
1637
1677
|
"collab.relayUrl": {
|
|
1638
1678
|
type: "string",
|
|
@@ -1889,6 +1929,17 @@ export const SETTINGS_SCHEMA = {
|
|
|
1889
1929
|
},
|
|
1890
1930
|
},
|
|
1891
1931
|
|
|
1932
|
+
"compaction.remoteStreamingV2Enabled": {
|
|
1933
|
+
type: "boolean",
|
|
1934
|
+
default: true,
|
|
1935
|
+
ui: {
|
|
1936
|
+
tab: "context",
|
|
1937
|
+
group: "Compaction",
|
|
1938
|
+
label: "Remote Compaction V2",
|
|
1939
|
+
description: "Use Responses streaming compaction for compatible remote compaction models",
|
|
1940
|
+
},
|
|
1941
|
+
},
|
|
1942
|
+
|
|
1892
1943
|
"compaction.reserveTokens": { type: "number", default: 16384 },
|
|
1893
1944
|
|
|
1894
1945
|
"compaction.keepRecentTokens": { type: "number", default: 20000 },
|
|
@@ -1897,6 +1948,8 @@ export const SETTINGS_SCHEMA = {
|
|
|
1897
1948
|
|
|
1898
1949
|
"compaction.remoteEndpoint": { type: "string", default: undefined },
|
|
1899
1950
|
|
|
1951
|
+
"compaction.v2RetainedMessageBudget": { type: "number", default: 64000 },
|
|
1952
|
+
|
|
1900
1953
|
// Idle compaction
|
|
1901
1954
|
"compaction.idleEnabled": {
|
|
1902
1955
|
type: "boolean",
|
|
@@ -3796,6 +3849,17 @@ export const SETTINGS_SCHEMA = {
|
|
|
3796
3849
|
},
|
|
3797
3850
|
},
|
|
3798
3851
|
|
|
3852
|
+
"title.refreshOnReplan": {
|
|
3853
|
+
type: "boolean",
|
|
3854
|
+
default: true,
|
|
3855
|
+
ui: {
|
|
3856
|
+
tab: "tasks",
|
|
3857
|
+
group: "Modes",
|
|
3858
|
+
label: "Refresh Title on Replan",
|
|
3859
|
+
description: "Refresh generated session titles after todo init replans unless the title was set by the user",
|
|
3860
|
+
},
|
|
3861
|
+
},
|
|
3862
|
+
|
|
3799
3863
|
// Delegation
|
|
3800
3864
|
"task.isolation.mode": {
|
|
3801
3865
|
type: "enum",
|
|
@@ -4850,6 +4914,8 @@ export interface CompactionSettings {
|
|
|
4850
4914
|
autoContinue: boolean;
|
|
4851
4915
|
remoteEnabled: boolean;
|
|
4852
4916
|
remoteEndpoint: string | undefined;
|
|
4917
|
+
remoteStreamingV2Enabled: boolean;
|
|
4918
|
+
v2RetainedMessageBudget: number;
|
|
4853
4919
|
idleEnabled: boolean;
|
|
4854
4920
|
idleThresholdTokens: number;
|
|
4855
4921
|
idleTimeoutSeconds: number;
|
|
@@ -4857,6 +4923,15 @@ export interface CompactionSettings {
|
|
|
4857
4923
|
dropUseless: boolean;
|
|
4858
4924
|
}
|
|
4859
4925
|
|
|
4926
|
+
export interface RecapSettings {
|
|
4927
|
+
enabled: boolean;
|
|
4928
|
+
idleSeconds: number;
|
|
4929
|
+
}
|
|
4930
|
+
|
|
4931
|
+
export interface TitleSettings {
|
|
4932
|
+
refreshOnReplan: boolean;
|
|
4933
|
+
}
|
|
4934
|
+
|
|
4860
4935
|
export interface ContextPromotionSettings {
|
|
4861
4936
|
enabled: boolean;
|
|
4862
4937
|
}
|
|
@@ -5002,6 +5077,8 @@ export interface GcSettings {
|
|
|
5002
5077
|
/** Map group prefix -> typed settings interface */
|
|
5003
5078
|
export interface GroupTypeMap {
|
|
5004
5079
|
compaction: CompactionSettings;
|
|
5080
|
+
recap: RecapSettings;
|
|
5081
|
+
title: TitleSettings;
|
|
5005
5082
|
contextPromotion: ContextPromotionSettings;
|
|
5006
5083
|
retry: RetrySettings;
|
|
5007
5084
|
memories: MemoriesSettings;
|
package/src/debug/index.ts
CHANGED
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
Container,
|
|
11
11
|
isNotificationSuppressed,
|
|
12
12
|
Loader,
|
|
13
|
+
type OverlayHandle,
|
|
13
14
|
type SelectItem,
|
|
14
15
|
SelectList,
|
|
15
16
|
Spacer,
|
|
@@ -327,18 +328,29 @@ export class DebugSelectorComponent extends Container {
|
|
|
327
328
|
return;
|
|
328
329
|
}
|
|
329
330
|
|
|
331
|
+
let overlay: OverlayHandle | undefined;
|
|
332
|
+
const close = (): void => {
|
|
333
|
+
overlay?.hide();
|
|
334
|
+
overlay = undefined;
|
|
335
|
+
void this.ctx.showDebugSelector();
|
|
336
|
+
};
|
|
330
337
|
const viewer = new DebugLogViewerComponent({
|
|
331
338
|
logs,
|
|
332
339
|
terminalRows: this.ctx.ui.terminal.rows,
|
|
333
|
-
onExit:
|
|
340
|
+
onExit: close,
|
|
334
341
|
onStatus: message => this.ctx.showStatus(message, { dim: true }),
|
|
335
342
|
onError: message => this.ctx.showError(message),
|
|
336
343
|
onUpdate: () => this.ctx.ui.requestRender(),
|
|
337
344
|
logSource,
|
|
338
345
|
});
|
|
339
346
|
|
|
340
|
-
this.ctx.
|
|
341
|
-
|
|
347
|
+
overlay = this.ctx.ui.showOverlay(viewer, {
|
|
348
|
+
anchor: "top-left",
|
|
349
|
+
width: "100%",
|
|
350
|
+
maxHeight: "100%",
|
|
351
|
+
margin: 0,
|
|
352
|
+
fullscreen: true,
|
|
353
|
+
});
|
|
342
354
|
this.ctx.ui.setFocus(viewer);
|
|
343
355
|
} catch (err) {
|
|
344
356
|
this.ctx.showError(`Failed to read logs: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -348,16 +360,29 @@ export class DebugSelectorComponent extends Container {
|
|
|
348
360
|
}
|
|
349
361
|
|
|
350
362
|
async #handleViewRawSse(): Promise<void> {
|
|
351
|
-
|
|
363
|
+
let overlay: OverlayHandle | undefined;
|
|
364
|
+
let viewer: RawSseViewerComponent | undefined;
|
|
365
|
+
const close = (): void => {
|
|
366
|
+
viewer?.dispose();
|
|
367
|
+
overlay?.hide();
|
|
368
|
+
overlay = undefined;
|
|
369
|
+
void this.ctx.showDebugSelector();
|
|
370
|
+
};
|
|
371
|
+
viewer = new RawSseViewerComponent({
|
|
352
372
|
buffer: resolveRawSseDebugBuffer(this.ctx.session),
|
|
353
373
|
terminalRows: this.ctx.ui.terminal.rows,
|
|
354
|
-
onExit:
|
|
374
|
+
onExit: close,
|
|
355
375
|
onStatus: message => this.ctx.showStatus(message, { dim: true }),
|
|
356
376
|
onUpdate: () => this.ctx.ui.requestRender(),
|
|
357
377
|
});
|
|
358
378
|
|
|
359
|
-
this.ctx.
|
|
360
|
-
|
|
379
|
+
overlay = this.ctx.ui.showOverlay(viewer, {
|
|
380
|
+
anchor: "top-left",
|
|
381
|
+
width: "100%",
|
|
382
|
+
maxHeight: "100%",
|
|
383
|
+
margin: 0,
|
|
384
|
+
fullscreen: true,
|
|
385
|
+
});
|
|
361
386
|
this.ctx.ui.setFocus(viewer);
|
|
362
387
|
this.ctx.ui.requestRender();
|
|
363
388
|
}
|