@oh-my-pi/pi-coding-agent 16.3.9 → 16.3.10
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 +19 -0
- package/dist/cli.js +2902 -2902
- package/dist/types/modes/interactive-mode.d.ts +5 -3
- package/dist/types/modes/session-observer-registry.d.ts +3 -1
- package/dist/types/session/agent-session.d.ts +11 -7
- package/dist/types/task/renderer.d.ts +0 -1
- package/dist/types/tools/browser/run-cancellation.d.ts +11 -0
- package/dist/types/tools/browser/tab-protocol.d.ts +1 -0
- package/dist/types/tools/tool-errors.d.ts +1 -1
- package/package.json +12 -12
- package/src/modes/components/__tests__/skill-message.test.ts +2 -0
- package/src/modes/components/agent-hub.ts +17 -2
- package/src/modes/components/skill-message.ts +1 -1
- package/src/modes/index.ts +0 -7
- package/src/modes/interactive-mode.ts +54 -18
- package/src/modes/session-observer-registry.ts +11 -8
- package/src/sdk.ts +18 -0
- package/src/session/agent-session.ts +25 -13
- package/src/task/render.test.ts +134 -1
- package/src/task/render.ts +24 -6
- package/src/task/renderer.ts +0 -1
- package/src/tools/browser/cmux/cmux-tab.ts +7 -3
- package/src/tools/browser/run-cancellation.ts +28 -8
- package/src/tools/browser/tab-protocol.ts +1 -1
- package/src/tools/browser/tab-supervisor.ts +4 -4
- package/src/tools/browser/tab-worker.ts +19 -7
- package/src/tools/irc.ts +18 -2
- package/src/tools/tool-errors.ts +3 -3
|
@@ -33,7 +33,7 @@ import { TranscriptContainer } from "./components/transcript-container.js";
|
|
|
33
33
|
import { EventController } from "./controllers/event-controller.js";
|
|
34
34
|
import { type LoopLimitRuntime } from "./loop-limit.js";
|
|
35
35
|
import { OAuthManualInputManager } from "./oauth-manual-input.js";
|
|
36
|
-
import type
|
|
36
|
+
import { type ObservableSession } from "./session-observer-registry.js";
|
|
37
37
|
import type { Theme } from "./theme/theme.js";
|
|
38
38
|
import type { CompactionQueuedMessage, InteractiveModeContext, InteractiveModeInitOptions, InteractiveSelectorDialogOptions, SubmittedUserInput, TodoItem, TodoPhase } from "./types.js";
|
|
39
39
|
/**
|
|
@@ -63,7 +63,7 @@ export interface InteractiveModeOptions {
|
|
|
63
63
|
}
|
|
64
64
|
/**
|
|
65
65
|
* Build the anchored subagent HUD block: a bold accent "Subagents" header plus
|
|
66
|
-
*
|
|
66
|
+
* a bounded set of running-agent rows in the same `Id: description` shape the
|
|
67
67
|
* inline task rows use (muted task preview when no description was given).
|
|
68
68
|
* Layout mirrors the Todos HUD exactly: unindented header, then
|
|
69
69
|
* `renderTreeList` rows (dim connectors) shifted right by one space.
|
|
@@ -217,7 +217,9 @@ export declare class InteractiveMode implements InteractiveModeContext {
|
|
|
217
217
|
finishPendingSubmission(input: SubmittedUserInput): void;
|
|
218
218
|
updateEditorBorderColor(): void;
|
|
219
219
|
/** Refresh the running-subagents status badge from the active local or collab registry. */
|
|
220
|
-
syncRunningSubagentBadge(
|
|
220
|
+
syncRunningSubagentBadge(options?: {
|
|
221
|
+
requestRender?: boolean;
|
|
222
|
+
}): void;
|
|
221
223
|
rebuildChatFromMessages(): void;
|
|
222
224
|
/**
|
|
223
225
|
* Render the ctrl+p model-role cycle chip track into its own anchored
|
|
@@ -21,10 +21,12 @@ export interface ObservableSession {
|
|
|
21
21
|
/** Latest progress snapshot from the subagent executor */
|
|
22
22
|
progress?: AgentProgress;
|
|
23
23
|
}
|
|
24
|
+
/** Coarse source of an observer change; callers use it to separate lifecycle work from high-frequency progress. */
|
|
25
|
+
export type SessionObserverChangeKind = "main" | "reset" | "lifecycle" | "progress";
|
|
24
26
|
export declare class SessionObserverRegistry {
|
|
25
27
|
#private;
|
|
26
28
|
/** Add a change listener. Returns unsubscribe function. */
|
|
27
|
-
onChange(cb: () => void): () => void;
|
|
29
|
+
onChange(cb: (kind: SessionObserverChangeKind) => void): () => void;
|
|
28
30
|
setMainSession(sessionFile?: string): void;
|
|
29
31
|
getSessions(): ObservableSession[];
|
|
30
32
|
getActiveSubagentCount(): number;
|
|
@@ -423,6 +423,7 @@ export interface SessionStats {
|
|
|
423
423
|
};
|
|
424
424
|
premiumRequests: number;
|
|
425
425
|
cost: number;
|
|
426
|
+
contextUsage?: ContextUsage;
|
|
426
427
|
}
|
|
427
428
|
/** Advisor statistics for /advisor status command. */
|
|
428
429
|
export interface AdvisorStats {
|
|
@@ -1160,15 +1161,18 @@ export declare class AgentSession {
|
|
|
1160
1161
|
/** Whether there are pending Python messages waiting to be flushed */
|
|
1161
1162
|
get hasPendingPythonMessages(): boolean;
|
|
1162
1163
|
/**
|
|
1163
|
-
* Surfaces
|
|
1164
|
+
* Surfaces and consumes pending IRC incoming records before the next model
|
|
1164
1165
|
* step can inject them automatically.
|
|
1165
1166
|
*
|
|
1166
|
-
*
|
|
1167
|
-
*
|
|
1168
|
-
*
|
|
1169
|
-
*
|
|
1170
|
-
*/
|
|
1171
|
-
drainPendingIrcInboxMessages(agentId: string
|
|
1167
|
+
* Tool results already expose the formatted body to the model. Leaving the
|
|
1168
|
+
* same record in either pending IRC queue would deliver it a second time at
|
|
1169
|
+
* the next step boundary — including on `peek`, which is why inbox peeks
|
|
1170
|
+
* also drain here.
|
|
1171
|
+
*/
|
|
1172
|
+
drainPendingIrcInboxMessages(agentId: string, opts?: {
|
|
1173
|
+
from?: string;
|
|
1174
|
+
limit?: number;
|
|
1175
|
+
}): IrcMessage[];
|
|
1172
1176
|
/**
|
|
1173
1177
|
* Deliver an IRC message into this session (recipient side; called by the
|
|
1174
1178
|
* IrcBus). Emits the `irc_message` session event for UI cards and injects
|
|
@@ -1,3 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Marks a run-scoped promise as observed without changing its behavior for awaited callers.
|
|
3
|
+
*
|
|
4
|
+
* Browser run teardown aborts can reject promises created for evaluated code after user code
|
|
5
|
+
* has stopped observing them (for example fire-and-forget `wait()`/facade calls). In 16.3.0
|
|
6
|
+
* those zero-consumer rejections reached the process-level `unhandledRejection` handler and
|
|
7
|
+
* killed every subagent sharing the process (issues #4499/#4672). Attaching a no-op rejection
|
|
8
|
+
* handler at creation makes the promise observed while returning the original promise so callers
|
|
9
|
+
* that do await it still receive the rejection.
|
|
10
|
+
*/
|
|
11
|
+
export declare function markHandled<T>(promise: Promise<T>): Promise<T>;
|
|
1
12
|
/** Sleeps inside evaluated browser code while honoring the owning run's cancellation signal. */
|
|
2
13
|
export declare function waitForBrowserRun(ms: number, signal: AbortSignal): Promise<void>;
|
|
3
14
|
/** Binds a long-lived browser facade to one evaluated run's abort signal. */
|
|
@@ -19,7 +19,7 @@ export declare class ToolError extends Error {
|
|
|
19
19
|
*/
|
|
20
20
|
export declare class ToolAbortError extends Error {
|
|
21
21
|
static readonly MESSAGE = "Operation aborted";
|
|
22
|
-
constructor(message?: string);
|
|
22
|
+
constructor(message?: string, options?: ErrorOptions);
|
|
23
23
|
}
|
|
24
24
|
/**
|
|
25
25
|
* Throw ToolAbortError if the signal is aborted.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@oh-my-pi/pi-coding-agent",
|
|
4
|
-
"version": "16.3.
|
|
4
|
+
"version": "16.3.10",
|
|
5
5
|
"description": "Coding agent CLI with read, bash, edit, write tools and session management",
|
|
6
6
|
"homepage": "https://omp.sh",
|
|
7
7
|
"author": "Can Boluk",
|
|
@@ -56,17 +56,17 @@
|
|
|
56
56
|
"@agentclientprotocol/sdk": "0.25.0",
|
|
57
57
|
"@babel/parser": "^7.29.7",
|
|
58
58
|
"@mozilla/readability": "^0.6.0",
|
|
59
|
-
"@oh-my-pi/hashline": "16.3.
|
|
60
|
-
"@oh-my-pi/omp-stats": "16.3.
|
|
61
|
-
"@oh-my-pi/pi-agent-core": "16.3.
|
|
62
|
-
"@oh-my-pi/pi-ai": "16.3.
|
|
63
|
-
"@oh-my-pi/pi-catalog": "16.3.
|
|
64
|
-
"@oh-my-pi/pi-mnemopi": "16.3.
|
|
65
|
-
"@oh-my-pi/pi-natives": "16.3.
|
|
66
|
-
"@oh-my-pi/pi-tui": "16.3.
|
|
67
|
-
"@oh-my-pi/pi-utils": "16.3.
|
|
68
|
-
"@oh-my-pi/pi-wire": "16.3.
|
|
69
|
-
"@oh-my-pi/snapcompact": "16.3.
|
|
59
|
+
"@oh-my-pi/hashline": "16.3.10",
|
|
60
|
+
"@oh-my-pi/omp-stats": "16.3.10",
|
|
61
|
+
"@oh-my-pi/pi-agent-core": "16.3.10",
|
|
62
|
+
"@oh-my-pi/pi-ai": "16.3.10",
|
|
63
|
+
"@oh-my-pi/pi-catalog": "16.3.10",
|
|
64
|
+
"@oh-my-pi/pi-mnemopi": "16.3.10",
|
|
65
|
+
"@oh-my-pi/pi-natives": "16.3.10",
|
|
66
|
+
"@oh-my-pi/pi-tui": "16.3.10",
|
|
67
|
+
"@oh-my-pi/pi-utils": "16.3.10",
|
|
68
|
+
"@oh-my-pi/pi-wire": "16.3.10",
|
|
69
|
+
"@oh-my-pi/snapcompact": "16.3.10",
|
|
70
70
|
"@opentelemetry/api": "^1.9.1",
|
|
71
71
|
"@opentelemetry/context-async-hooks": "^2.7.1",
|
|
72
72
|
"@opentelemetry/exporter-trace-otlp-proto": "^0.218.0",
|
|
@@ -42,6 +42,8 @@ describe("SkillMessageComponent", () => {
|
|
|
42
42
|
// New look: an icon-tagged "skill" header with the name and a single meta line.
|
|
43
43
|
expect(text).toContain("skill");
|
|
44
44
|
expect(text).toContain("atomic-commit");
|
|
45
|
+
expect(text).toContain("skill atomic-commit");
|
|
46
|
+
expect(text).not.toContain("skill atomic-commit");
|
|
45
47
|
expect(text).toContain("88 lines");
|
|
46
48
|
|
|
47
49
|
// The card is drawn with an outline.
|
|
@@ -34,6 +34,7 @@ import { DynamicBorder } from "./dynamic-border";
|
|
|
34
34
|
|
|
35
35
|
/** Refresh cadence for the relative-time column */
|
|
36
36
|
const AGE_TICK_MS = 5_000;
|
|
37
|
+
const DATA_CHANGE_RENDER_COALESCE_MS = 100;
|
|
37
38
|
/** Double-tap window for the table's left-left "close hub" gesture. */
|
|
38
39
|
const LEFT_TAP_WINDOW_MS = 500;
|
|
39
40
|
|
|
@@ -198,6 +199,7 @@ export class AgentHubOverlayComponent extends Container {
|
|
|
198
199
|
#hubKeys: KeyId[];
|
|
199
200
|
#unsubscribers: Array<() => void> = [];
|
|
200
201
|
#ageTimer: NodeJS.Timeout | undefined;
|
|
202
|
+
#dataChangeTimer?: NodeJS.Timeout;
|
|
201
203
|
#remote: AgentHubRemote | undefined;
|
|
202
204
|
/** Resolves after persisted historical subagents have been registered and rows refreshed. */
|
|
203
205
|
readonly persistedSubagentsReady: Promise<void>;
|
|
@@ -251,8 +253,8 @@ export class AgentHubOverlayComponent extends Container {
|
|
|
251
253
|
this.#expandKeys = deps.expandKeys ?? ["ctrl+o"];
|
|
252
254
|
this.#focusAgent = deps.focusAgent;
|
|
253
255
|
|
|
254
|
-
this.#unsubscribers.push(this.#registry.onChange(() => this.#
|
|
255
|
-
this.#unsubscribers.push(this.#observers.onChange(() => this.#
|
|
256
|
+
this.#unsubscribers.push(this.#registry.onChange(() => this.#scheduleDataChange()));
|
|
257
|
+
this.#unsubscribers.push(this.#observers.onChange(() => this.#scheduleDataChange()));
|
|
256
258
|
this.#ageTimer = setInterval(() => this.#requestRender(), AGE_TICK_MS);
|
|
257
259
|
this.#ageTimer.unref?.();
|
|
258
260
|
|
|
@@ -285,6 +287,10 @@ export class AgentHubOverlayComponent extends Container {
|
|
|
285
287
|
clearInterval(this.#ageTimer);
|
|
286
288
|
this.#ageTimer = undefined;
|
|
287
289
|
}
|
|
290
|
+
if (this.#dataChangeTimer) {
|
|
291
|
+
clearTimeout(this.#dataChangeTimer);
|
|
292
|
+
this.#dataChangeTimer = undefined;
|
|
293
|
+
}
|
|
288
294
|
this.#closeTranscriptOverlay();
|
|
289
295
|
}
|
|
290
296
|
|
|
@@ -355,6 +361,15 @@ export class AgentHubOverlayComponent extends Container {
|
|
|
355
361
|
// Live data plumbing
|
|
356
362
|
// ========================================================================
|
|
357
363
|
|
|
364
|
+
#scheduleDataChange(): void {
|
|
365
|
+
if (this.#dataChangeTimer) return;
|
|
366
|
+
this.#dataChangeTimer = setTimeout(() => {
|
|
367
|
+
this.#dataChangeTimer = undefined;
|
|
368
|
+
this.#onDataChange();
|
|
369
|
+
}, DATA_CHANGE_RENDER_COALESCE_MS);
|
|
370
|
+
this.#dataChangeTimer.unref?.();
|
|
371
|
+
}
|
|
372
|
+
|
|
358
373
|
#onDataChange(): void {
|
|
359
374
|
this.#refreshRows();
|
|
360
375
|
this.#requestRender();
|
|
@@ -50,7 +50,7 @@ export class SkillMessageComponent extends Container {
|
|
|
50
50
|
|
|
51
51
|
// Header: icon-tag + skill name, with the invocation args trailing dimmed.
|
|
52
52
|
const tag = theme.fg("customMessageLabel", theme.bold(`${theme.icon.extensionSkill} skill`));
|
|
53
|
-
let header = `${tag}
|
|
53
|
+
let header = `${tag} ${theme.fg("customMessageText", theme.bold(name))}`;
|
|
54
54
|
if (args) {
|
|
55
55
|
header += ` ${theme.fg("dim", args)}`;
|
|
56
56
|
}
|
package/src/modes/index.ts
CHANGED
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
import { emergencyTerminalRestore } from "@oh-my-pi/pi-tui";
|
|
2
|
-
import { postmortem } from "@oh-my-pi/pi-utils";
|
|
3
|
-
|
|
4
1
|
/**
|
|
5
2
|
* Interactive mode and embeddable RPC client exports for the coding agent.
|
|
6
3
|
*
|
|
@@ -11,7 +8,3 @@ import { postmortem } from "@oh-my-pi/pi-utils";
|
|
|
11
8
|
export * from "./interactive-mode";
|
|
12
9
|
export * from "./rpc/rpc-client";
|
|
13
10
|
export * from "./rpc/rpc-types";
|
|
14
|
-
|
|
15
|
-
postmortem.register("terminal-restore", () => {
|
|
16
|
-
emergencyTerminalRestore();
|
|
17
|
-
});
|
|
@@ -161,8 +161,11 @@ import {
|
|
|
161
161
|
} from "./loop-limit";
|
|
162
162
|
import { OAuthManualInputManager } from "./oauth-manual-input";
|
|
163
163
|
import { countRunningSubagentBadgeAgents, getRunningSubagentBadgeRegistry } from "./running-subagent-badge";
|
|
164
|
-
import
|
|
165
|
-
|
|
164
|
+
import {
|
|
165
|
+
type ObservableSession,
|
|
166
|
+
type SessionObserverChangeKind,
|
|
167
|
+
SessionObserverRegistry,
|
|
168
|
+
} from "./session-observer-registry";
|
|
166
169
|
import { createSessionTeardown, type SessionTeardown } from "./session-teardown";
|
|
167
170
|
import { runProviderSetupWizard } from "./setup-wizard/lazy";
|
|
168
171
|
import { interruptHint } from "./shared";
|
|
@@ -343,9 +346,12 @@ class AnchoredLiveContainer extends Container implements NativeScrollbackLiveReg
|
|
|
343
346
|
* before it auto-clears, mirroring the todo HUD's auto-clear timer. */
|
|
344
347
|
const MODEL_CYCLE_TRACK_CLEAR_MS = 4000;
|
|
345
348
|
|
|
349
|
+
const SUBAGENT_HUD_VISIBLE_LIMIT = 8;
|
|
350
|
+
const SUBAGENT_OBSERVER_UI_COALESCE_MS = 100;
|
|
351
|
+
|
|
346
352
|
/**
|
|
347
353
|
* Build the anchored subagent HUD block: a bold accent "Subagents" header plus
|
|
348
|
-
*
|
|
354
|
+
* a bounded set of running-agent rows in the same `Id: description` shape the
|
|
349
355
|
* inline task rows use (muted task preview when no description was given).
|
|
350
356
|
* Layout mirrors the Todos HUD exactly: unindented header, then
|
|
351
357
|
* `renderTreeList` rows (dim connectors) shifted right by one space.
|
|
@@ -361,9 +367,11 @@ export function renderSubagentHudLines(sessions: ObservableSession[], columns: n
|
|
|
361
367
|
if (running.length === 0) return [];
|
|
362
368
|
|
|
363
369
|
const dot = theme.styledSymbol("status.done", "accent");
|
|
370
|
+
const visible = running.slice(0, SUBAGENT_HUD_VISIBLE_LIMIT);
|
|
371
|
+
const hiddenCount = running.length - visible.length;
|
|
364
372
|
const rows = renderTreeList(
|
|
365
373
|
{
|
|
366
|
-
items:
|
|
374
|
+
items: visible,
|
|
367
375
|
expanded: true,
|
|
368
376
|
renderItem: session => {
|
|
369
377
|
const displayId = formatTaskId(session.id);
|
|
@@ -385,6 +393,9 @@ export function renderSubagentHudLines(sessions: ObservableSession[], columns: n
|
|
|
385
393
|
},
|
|
386
394
|
theme,
|
|
387
395
|
);
|
|
396
|
+
if (hiddenCount > 0) {
|
|
397
|
+
rows.push(theme.fg("dim", `… ${hiddenCount} more running — open Agent Hub for full list`));
|
|
398
|
+
}
|
|
388
399
|
return ["", theme.bold(theme.fg("accent", "Subagents")), ...rows.map(line => ` ${line}`)];
|
|
389
400
|
}
|
|
390
401
|
|
|
@@ -588,6 +599,8 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
588
599
|
#observerRegistry: SessionObserverRegistry;
|
|
589
600
|
#eventBus?: EventBus;
|
|
590
601
|
#eventBusUnsubscribers: Array<() => void> = [];
|
|
602
|
+
#observerUiSyncTimer?: NodeJS.Timeout;
|
|
603
|
+
#observerUiSyncNeedsTodoReconcile = false;
|
|
591
604
|
#agentRegistryUnsubscribe?: () => void;
|
|
592
605
|
#agentRegistrySubscriptionTarget?: AgentRegistry;
|
|
593
606
|
#mcpStatusOrder: string[] = [];
|
|
@@ -917,17 +930,8 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
917
930
|
}
|
|
918
931
|
this.#observerRegistry.setMainSession(this.sessionManager.getSessionFile() ?? undefined);
|
|
919
932
|
this.syncRunningSubagentBadge();
|
|
920
|
-
this.#observerRegistry.onChange(
|
|
921
|
-
this
|
|
922
|
-
// Auto-checkmark todos whose matching subagent just succeeded, then
|
|
923
|
-
// re-render so the running override (the static "live" glyph when a
|
|
924
|
-
// subagent is doing the work for a still-pending todo) updates as
|
|
925
|
-
// subagents start, finish, or fail.
|
|
926
|
-
this.#reconcileTodosWithSubagents();
|
|
927
|
-
this.#syncTodoAutoClearTimer();
|
|
928
|
-
this.#renderTodoList();
|
|
929
|
-
this.#renderSubagentList();
|
|
930
|
-
this.ui.requestRender();
|
|
933
|
+
this.#observerRegistry.onChange(kind => {
|
|
934
|
+
this.#scheduleObserverUiSync(kind);
|
|
931
935
|
});
|
|
932
936
|
|
|
933
937
|
// Load initial todos
|
|
@@ -1521,19 +1525,18 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1521
1525
|
}
|
|
1522
1526
|
|
|
1523
1527
|
/** Refresh the running-subagents status badge from the active local or collab registry. */
|
|
1524
|
-
syncRunningSubagentBadge(): void {
|
|
1528
|
+
syncRunningSubagentBadge(options: { requestRender?: boolean } = {}): void {
|
|
1525
1529
|
const registry = getRunningSubagentBadgeRegistry(this.collabGuest);
|
|
1526
1530
|
if (this.#agentRegistrySubscriptionTarget !== registry) {
|
|
1527
1531
|
this.#agentRegistryUnsubscribe?.();
|
|
1528
1532
|
this.#agentRegistrySubscriptionTarget = registry;
|
|
1529
1533
|
this.#agentRegistryUnsubscribe = registry.onChange(() => {
|
|
1530
1534
|
this.syncRunningSubagentBadge();
|
|
1531
|
-
this.ui.requestRender();
|
|
1532
1535
|
});
|
|
1533
1536
|
}
|
|
1534
1537
|
const count = countRunningSubagentBadgeAgents(registry);
|
|
1535
1538
|
this.statusLine.setSubagentCount(count);
|
|
1536
|
-
this.ui.requestRender();
|
|
1539
|
+
if (options.requestRender !== false) this.ui.requestRender();
|
|
1537
1540
|
}
|
|
1538
1541
|
|
|
1539
1542
|
rebuildChatFromMessages(): void {
|
|
@@ -1754,6 +1757,38 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1754
1757
|
return active ?? nonEmpty[nonEmpty.length - 1];
|
|
1755
1758
|
}
|
|
1756
1759
|
|
|
1760
|
+
#scheduleObserverUiSync(kind: SessionObserverChangeKind): void {
|
|
1761
|
+
if (kind !== "progress") {
|
|
1762
|
+
this.#observerUiSyncNeedsTodoReconcile = true;
|
|
1763
|
+
}
|
|
1764
|
+
if (this.#observerUiSyncTimer) return;
|
|
1765
|
+
this.#observerUiSyncTimer = setTimeout(() => {
|
|
1766
|
+
this.#observerUiSyncTimer = undefined;
|
|
1767
|
+
this.#flushObserverUiSync();
|
|
1768
|
+
}, SUBAGENT_OBSERVER_UI_COALESCE_MS);
|
|
1769
|
+
this.#observerUiSyncTimer.unref?.();
|
|
1770
|
+
}
|
|
1771
|
+
|
|
1772
|
+
#flushObserverUiSync(): void {
|
|
1773
|
+
this.syncRunningSubagentBadge({ requestRender: false });
|
|
1774
|
+
if (this.#observerUiSyncNeedsTodoReconcile) {
|
|
1775
|
+
this.#observerUiSyncNeedsTodoReconcile = false;
|
|
1776
|
+
this.#reconcileTodosWithSubagents();
|
|
1777
|
+
}
|
|
1778
|
+
this.#syncTodoAutoClearTimer();
|
|
1779
|
+
this.#renderTodoList();
|
|
1780
|
+
this.#renderSubagentList();
|
|
1781
|
+
this.ui.requestRender();
|
|
1782
|
+
}
|
|
1783
|
+
|
|
1784
|
+
#cancelObserverUiSyncTimer(): void {
|
|
1785
|
+
if (this.#observerUiSyncTimer) {
|
|
1786
|
+
clearTimeout(this.#observerUiSyncTimer);
|
|
1787
|
+
this.#observerUiSyncTimer = undefined;
|
|
1788
|
+
}
|
|
1789
|
+
this.#observerUiSyncNeedsTodoReconcile = false;
|
|
1790
|
+
}
|
|
1791
|
+
|
|
1757
1792
|
#renderTodoList(): void {
|
|
1758
1793
|
this.todoContainer.clear();
|
|
1759
1794
|
const phases = this.todoPhases.filter(phase => phase.tasks.length > 0);
|
|
@@ -3306,6 +3341,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
3306
3341
|
}
|
|
3307
3342
|
this.#cleanupMicAnimation();
|
|
3308
3343
|
this.#cancelTodoAutoClearTimer();
|
|
3344
|
+
this.#cancelObserverUiSyncTimer();
|
|
3309
3345
|
this.#cancelGoalContinuation();
|
|
3310
3346
|
if (this.#sttController) {
|
|
3311
3347
|
this.#sttController.dispose();
|
|
@@ -24,6 +24,9 @@ export interface ObservableSession {
|
|
|
24
24
|
progress?: AgentProgress;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
/** Coarse source of an observer change; callers use it to separate lifecycle work from high-frequency progress. */
|
|
28
|
+
export type SessionObserverChangeKind = "main" | "reset" | "lifecycle" | "progress";
|
|
29
|
+
|
|
27
30
|
const STATUS_MAP: Record<string, ObservableSession["status"]> = {
|
|
28
31
|
started: "active",
|
|
29
32
|
completed: "completed",
|
|
@@ -33,20 +36,20 @@ const STATUS_MAP: Record<string, ObservableSession["status"]> = {
|
|
|
33
36
|
|
|
34
37
|
export class SessionObserverRegistry {
|
|
35
38
|
#sessions = new Map<string, ObservableSession>();
|
|
36
|
-
#listeners = new Set<() => void>();
|
|
39
|
+
#listeners = new Set<(kind: SessionObserverChangeKind) => void>();
|
|
37
40
|
#eventBusUnsubscribers: Array<() => void> = [];
|
|
38
41
|
#sortOrderById = new Map<string, number>();
|
|
39
42
|
#parentSortOrderById = new Map<string, number>();
|
|
40
43
|
#nextSortOrder = 0;
|
|
41
44
|
|
|
42
45
|
/** Add a change listener. Returns unsubscribe function. */
|
|
43
|
-
onChange(cb: () => void): () => void {
|
|
46
|
+
onChange(cb: (kind: SessionObserverChangeKind) => void): () => void {
|
|
44
47
|
this.#listeners.add(cb);
|
|
45
48
|
return () => this.#listeners.delete(cb);
|
|
46
49
|
}
|
|
47
50
|
|
|
48
|
-
#notifyListeners(): void {
|
|
49
|
-
for (const cb of this.#listeners) cb();
|
|
51
|
+
#notifyListeners(kind: SessionObserverChangeKind): void {
|
|
52
|
+
for (const cb of this.#listeners) cb(kind);
|
|
50
53
|
}
|
|
51
54
|
|
|
52
55
|
#ensureSortOrder(id: string): number {
|
|
@@ -85,7 +88,7 @@ export class SessionObserverRegistry {
|
|
|
85
88
|
sessionFile: sessionFile ?? existing?.sessionFile,
|
|
86
89
|
lastUpdate: Date.now(),
|
|
87
90
|
});
|
|
88
|
-
this.#notifyListeners();
|
|
91
|
+
this.#notifyListeners("main");
|
|
89
92
|
}
|
|
90
93
|
|
|
91
94
|
getSessions(): ObservableSession[] {
|
|
@@ -121,7 +124,7 @@ export class SessionObserverRegistry {
|
|
|
121
124
|
this.#sortOrderById.clear();
|
|
122
125
|
this.#parentSortOrderById.clear();
|
|
123
126
|
this.#nextSortOrder = 0;
|
|
124
|
-
this.#notifyListeners();
|
|
127
|
+
this.#notifyListeners("reset");
|
|
125
128
|
}
|
|
126
129
|
|
|
127
130
|
dispose(): void {
|
|
@@ -171,7 +174,7 @@ export class SessionObserverRegistry {
|
|
|
171
174
|
lastUpdate: Date.now(),
|
|
172
175
|
});
|
|
173
176
|
}
|
|
174
|
-
this.#notifyListeners();
|
|
177
|
+
this.#notifyListeners("lifecycle");
|
|
175
178
|
}),
|
|
176
179
|
);
|
|
177
180
|
|
|
@@ -208,7 +211,7 @@ export class SessionObserverRegistry {
|
|
|
208
211
|
progress,
|
|
209
212
|
});
|
|
210
213
|
}
|
|
211
|
-
this.#notifyListeners();
|
|
214
|
+
this.#notifyListeners("progress");
|
|
212
215
|
}),
|
|
213
216
|
);
|
|
214
217
|
}
|
package/src/sdk.ts
CHANGED
|
@@ -2132,6 +2132,24 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
2132
2132
|
}
|
|
2133
2133
|
}
|
|
2134
2134
|
|
|
2135
|
+
if (model) {
|
|
2136
|
+
const selectedModel = model;
|
|
2137
|
+
const refreshedModel = await logger.time("refreshInitialModelMetadata", () =>
|
|
2138
|
+
modelRegistry.refreshSelectedModelMetadata(selectedModel),
|
|
2139
|
+
);
|
|
2140
|
+
if (refreshedModel !== selectedModel) {
|
|
2141
|
+
model = refreshedModel;
|
|
2142
|
+
thinkingLevel = pickInitialThinkingLevel(refreshedModel);
|
|
2143
|
+
autoThinking = thinkingLevel === AUTO_THINKING;
|
|
2144
|
+
effectiveThinkingLevel = concreteThinkingLevel(thinkingLevel);
|
|
2145
|
+
effectiveThinkingLevel = logger.time("resolveThinkingLevelForModel", () =>
|
|
2146
|
+
autoThinking
|
|
2147
|
+
? resolveProvisionalAutoLevel(refreshedModel)
|
|
2148
|
+
: resolveThinkingLevelForModel(refreshedModel, effectiveThinkingLevel),
|
|
2149
|
+
);
|
|
2150
|
+
}
|
|
2151
|
+
}
|
|
2152
|
+
|
|
2135
2153
|
// Discover custom commands (TypeScript slash commands)
|
|
2136
2154
|
const customCommandsResult: CustomCommandsLoadResult = options.disableExtensionDiscovery
|
|
2137
2155
|
? { commands: [], errors: [] }
|
|
@@ -933,6 +933,7 @@ export interface SessionStats {
|
|
|
933
933
|
};
|
|
934
934
|
premiumRequests: number;
|
|
935
935
|
cost: number;
|
|
936
|
+
contextUsage?: ContextUsage;
|
|
936
937
|
}
|
|
937
938
|
|
|
938
939
|
/** Advisor statistics for /advisor status command. */
|
|
@@ -1824,13 +1825,19 @@ export class AgentSession {
|
|
|
1824
1825
|
* Sticky across an in-flight prompt run: a successful `yield` makes the run
|
|
1825
1826
|
* terminal for execution purposes, so any trailing empty/aborted assistant
|
|
1826
1827
|
* stop must NOT trigger empty-stop/unexpected-stop/compaction continuations.
|
|
1827
|
-
* Cleared
|
|
1828
|
+
* Cleared before every new prompt turn so the next turn evaluates cleanly.
|
|
1828
1829
|
*/
|
|
1829
1830
|
#yieldTerminationPending = false;
|
|
1830
1831
|
#providerSessionState = new Map<string, ProviderSessionState>();
|
|
1831
1832
|
#hindsightSessionState: HindsightSessionState | undefined = undefined;
|
|
1832
1833
|
readonly rawSseDebugBuffer: RawSseDebugBuffer;
|
|
1833
1834
|
|
|
1835
|
+
#resetPromptMaintenanceState(): void {
|
|
1836
|
+
this.#emptyStopRetryCount = 0;
|
|
1837
|
+
this.#unexpectedStopRetryCount = 0;
|
|
1838
|
+
this.#yieldTerminationPending = false;
|
|
1839
|
+
}
|
|
1840
|
+
|
|
1834
1841
|
#acquirePowerAssertion(): void {
|
|
1835
1842
|
if (process.platform !== "darwin") return;
|
|
1836
1843
|
if (isBunTestRuntime()) return;
|
|
@@ -1949,6 +1956,7 @@ export class AgentSession {
|
|
|
1949
1956
|
if (parkedFollowUps.length > 0) {
|
|
1950
1957
|
this.agent.replaceQueues([...this.agent.peekSteeringQueue()], []);
|
|
1951
1958
|
}
|
|
1959
|
+
this.#resetPromptMaintenanceState();
|
|
1952
1960
|
this.#beginInFlight();
|
|
1953
1961
|
void this.agent
|
|
1954
1962
|
.prompt(records)
|
|
@@ -7566,12 +7574,7 @@ export class AgentSession {
|
|
|
7566
7574
|
this.#todoReminderAwaitingProgress = false;
|
|
7567
7575
|
this.#mutationsSinceLastTodoTouch = 0;
|
|
7568
7576
|
this.#midRunNudgeCount = 0;
|
|
7569
|
-
this.#
|
|
7570
|
-
this.#unexpectedStopRetryCount = 0;
|
|
7571
|
-
// A new prompt cycle starts: drop any sticky yield-termination from the
|
|
7572
|
-
// previous run so empty-stop / unexpected-stop / compaction maintenance
|
|
7573
|
-
// can evaluate this turn normally.
|
|
7574
|
-
this.#yieldTerminationPending = false;
|
|
7577
|
+
this.#resetPromptMaintenanceState();
|
|
7575
7578
|
|
|
7576
7579
|
await this.#maybeRestoreRetryFallbackPrimary();
|
|
7577
7580
|
|
|
@@ -14120,15 +14123,15 @@ export class AgentSession {
|
|
|
14120
14123
|
// =========================================================================
|
|
14121
14124
|
|
|
14122
14125
|
/**
|
|
14123
|
-
* Surfaces
|
|
14126
|
+
* Surfaces and consumes pending IRC incoming records before the next model
|
|
14124
14127
|
* step can inject them automatically.
|
|
14125
14128
|
*
|
|
14126
|
-
*
|
|
14127
|
-
*
|
|
14128
|
-
*
|
|
14129
|
-
*
|
|
14129
|
+
* Tool results already expose the formatted body to the model. Leaving the
|
|
14130
|
+
* same record in either pending IRC queue would deliver it a second time at
|
|
14131
|
+
* the next step boundary — including on `peek`, which is why inbox peeks
|
|
14132
|
+
* also drain here.
|
|
14130
14133
|
*/
|
|
14131
|
-
drainPendingIrcInboxMessages(agentId: string): IrcMessage[] {
|
|
14134
|
+
drainPendingIrcInboxMessages(agentId: string, opts?: { from?: string; limit?: number }): IrcMessage[] {
|
|
14132
14135
|
const messages: IrcMessage[] = [];
|
|
14133
14136
|
const remainingInterrupts: CustomMessage[] = [];
|
|
14134
14137
|
const remainingAsides: CustomMessage[] = [];
|
|
@@ -14155,6 +14158,14 @@ export class AgentSession {
|
|
|
14155
14158
|
queue.remaining.push(record);
|
|
14156
14159
|
continue;
|
|
14157
14160
|
}
|
|
14161
|
+
if (opts?.from !== undefined && from !== opts.from) {
|
|
14162
|
+
queue.remaining.push(record);
|
|
14163
|
+
continue;
|
|
14164
|
+
}
|
|
14165
|
+
if (opts?.limit !== undefined && messages.length >= opts.limit) {
|
|
14166
|
+
queue.remaining.push(record);
|
|
14167
|
+
continue;
|
|
14168
|
+
}
|
|
14158
14169
|
messages.push({
|
|
14159
14170
|
id,
|
|
14160
14171
|
from,
|
|
@@ -15212,6 +15223,7 @@ export class AgentSession {
|
|
|
15212
15223
|
},
|
|
15213
15224
|
cost: totalCost,
|
|
15214
15225
|
premiumRequests: totalPremiumRequests,
|
|
15226
|
+
contextUsage: this.getContextUsage(),
|
|
15215
15227
|
};
|
|
15216
15228
|
}
|
|
15217
15229
|
|