@code-yeongyu/senpi 2026.10.5 → 2026.10.6
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 +29 -0
- package/dist/bundle/chunks/{anthropic-messages-G5236QNK.js → anthropic-messages-4MNFPQ66.js} +1 -1
- package/dist/bundle/chunks/{app-server-command-3Q5G4CAF.js → app-server-command-GNTX4MZT.js} +1 -1
- package/dist/bundle/chunks/{azure-openai-responses-NTA2HGG4.js → azure-openai-responses-A5OEHSJM.js} +1 -1
- package/dist/bundle/chunks/bedrock-converse-stream.js +1 -1
- package/dist/bundle/chunks/{chunk-EEOKVSNF.js → chunk-3EE6G4G7.js} +180 -176
- package/dist/bundle/chunks/{chunk-2WV426OJ.js → chunk-4MJKBXI3.js} +1 -1
- package/dist/bundle/chunks/{chunk-ECDLITEY.js → chunk-6EH7QXOG.js} +2 -2
- package/dist/bundle/chunks/chunk-6KQGOVQG.js +2 -0
- package/dist/bundle/chunks/{chunk-JVSOW4QU.js → chunk-7A3WRAKP.js} +1 -1
- package/dist/bundle/chunks/{chunk-WTJU5B2C.js → chunk-BE66QJ3Z.js} +1 -1
- package/dist/bundle/chunks/{chunk-UHRXXTFZ.js → chunk-D2W3MXRF.js} +2 -2
- package/dist/bundle/chunks/{chunk-U7CI2JGX.js → chunk-DDEAZX22.js} +1 -1
- package/dist/bundle/chunks/{chunk-UXIQK5NQ.js → chunk-ESSQUSRC.js} +1 -1
- package/dist/bundle/chunks/{chunk-KLBKUBDV.js → chunk-GQ3YLXVZ.js} +1 -1
- package/dist/bundle/chunks/{chunk-CTQU2XNX.js → chunk-MLNJNJFE.js} +1 -1
- package/dist/bundle/chunks/{chunk-BPX6QWPD.js → chunk-PELNNWGO.js} +1 -1
- package/dist/bundle/chunks/{chunk-UUAKEDVS.js → chunk-QLIW3YMI.js} +1 -1
- package/dist/bundle/chunks/{chunk-BBK6SSYT.js → chunk-TAV6QZFM.js} +1 -1
- package/dist/bundle/chunks/{chunk-QUROHJLP.js → chunk-TFO5JGLR.js} +1 -1
- package/dist/bundle/chunks/{cli-main-VZWSUHFB.js → cli-main-2C3YPHRE.js} +1 -1
- package/dist/bundle/chunks/{cloudflare-workers-ai-system-one-5ZUHUKMU.js → cloudflare-workers-ai-system-one-TE3OEJEL.js} +1 -1
- package/dist/bundle/chunks/{google-generative-ai-OTMG6ZXI.js → google-generative-ai-JBC53AMK.js} +1 -1
- package/dist/bundle/chunks/{google-vertex-2LXS3ICC.js → google-vertex-46ODNPM2.js} +1 -1
- package/dist/bundle/chunks/{help-fast-path-SHZJ3QWQ.js → help-fast-path-FUMH5NSA.js} +1 -1
- package/dist/bundle/chunks/{host-command-7JIGGOWR.js → host-command-N6XS5O67.js} +1 -1
- package/dist/bundle/chunks/{interactive-mode-JA7AHYYL.js → interactive-mode-CM4C4ZFY.js} +1 -1
- package/dist/bundle/chunks/{llama-cpp-classify-U5XAMKZE.js → llama-cpp-classify-AD6TTKNL.js} +1 -1
- package/dist/bundle/chunks/{multi-session-host-34WOINP4.js → multi-session-host-QLLTPWIM.js} +5 -5
- package/dist/bundle/chunks/{openai-codex-responses-WNTYIPFZ.js → openai-codex-responses-Q4OWIK7Y.js} +1 -1
- package/dist/bundle/chunks/{openai-completions-VSH22FCN.js → openai-completions-6HU7GIGD.js} +1 -1
- package/dist/bundle/chunks/{openai-images-XBTUJXPR.js → openai-images-M73YANTR.js} +1 -1
- package/dist/bundle/chunks/{openai-responses-WLBHCFYL.js → openai-responses-S4QYZXF3.js} +1 -1
- package/dist/bundle/chunks/{openrouter-images-3LVJZY3G.js → openrouter-images-PCJFX2KO.js} +1 -1
- package/dist/bundle/chunks/{package-manager-cli-2QLVGQSC.js → package-manager-cli-VRCAO7RV.js} +1 -1
- package/dist/bundle/chunks/{rotation-stream-JUCEXHWU.js → rotation-stream-7E37KOCX.js} +1 -1
- package/dist/bundle/chunks/{rpc-mode-QEBSN2OH.js → rpc-mode-2OPE6J7P.js} +1 -1
- package/dist/bundle/chunks/{session-control-endpoint-UGQIP6ZN.js → session-control-endpoint-SOG4XFF7.js} +1 -1
- package/dist/bundle/chunks/{session-picker-HX4LOJZ7.js → session-picker-VTTRFGE7.js} +1 -1
- package/dist/bundle/chunks/session-worker.js +370 -366
- package/dist/bundle/chunks/{typesafe-system-one-FNI3UASE.js → typesafe-system-one-TF23RUVG.js} +1 -1
- package/dist/bundle/cli.js +1 -1
- package/dist/bundle/index.js +1 -1
- package/dist/bundle/rpc-entry.js +1 -1
- package/dist/bundle/runtime-manifest.json +1 -1
- package/dist/core/agent-session-runtime.d.ts +12 -0
- package/dist/core/agent-session.d.ts +18 -0
- package/dist/core/agent-session.js +55 -19
- package/dist/core/dynamic-prompt/handoff.d.ts +4 -2
- package/dist/core/dynamic-prompt/handoff.js +1 -1
- package/dist/core/extensions/builtin/diff.js +14 -9
- package/dist/core/extensions/builtin/files.js +6 -2
- package/dist/core/extensions/builtin/gpt-apply-patch/parser.js +5 -1
- package/dist/core/extensions/builtin/gpt-apply-patch/streaming-parser.d.ts +8 -0
- package/dist/core/extensions/builtin/gpt-apply-patch/streaming-parser.js +26 -2
- package/dist/core/extensions/builtin/gpt-apply-patch/streaming-render.js +82 -11
- package/dist/core/extensions/builtin/gpt-apply-patch/types.d.ts +9 -1
- package/dist/core/extensions/builtin/permission-system/auto-credentials.d.ts +2 -0
- package/dist/core/extensions/builtin/permission-system/auto-credentials.js +17 -0
- package/dist/core/extensions/builtin/permission-system/auto-paths.d.ts +14 -0
- package/dist/core/extensions/builtin/permission-system/auto-paths.js +93 -0
- package/dist/core/extensions/builtin/permission-system/auto-policy.d.ts +20 -0
- package/dist/core/extensions/builtin/permission-system/auto-policy.js +126 -0
- package/dist/core/extensions/builtin/permission-system/auto-program-rules.d.ts +6 -0
- package/dist/core/extensions/builtin/permission-system/auto-program-rules.js +139 -0
- package/dist/core/extensions/builtin/permission-system/auto-shell-grammar.d.ts +29 -0
- package/dist/core/extensions/builtin/permission-system/auto-shell-grammar.js +74 -0
- package/dist/core/extensions/builtin/permission-system/auto-shell-segments.d.ts +13 -0
- package/dist/core/extensions/builtin/permission-system/auto-shell-segments.js +23 -0
- package/dist/core/extensions/builtin/permission-system/cli.d.ts +1 -0
- package/dist/core/extensions/builtin/permission-system/cli.js +9 -10
- package/dist/core/extensions/builtin/permission-system/config.d.ts +4 -1
- package/dist/core/extensions/builtin/permission-system/config.js +13 -1
- package/dist/core/extensions/builtin/permission-system/index.js +20 -12
- package/dist/core/extensions/builtin/permission-system/non-interactive.d.ts +11 -6
- package/dist/core/extensions/builtin/permission-system/non-interactive.js +8 -33
- package/dist/core/extensions/builtin/permission-system/service.d.ts +20 -1
- package/dist/core/extensions/builtin/permission-system/service.js +43 -7
- package/dist/core/extensions/builtin/permission-system/settings.d.ts +1 -0
- package/dist/core/extensions/builtin/permission-system/settings.js +4 -3
- package/dist/core/extensions/builtin/permission-system/types.d.ts +1 -1
- package/dist/core/extensions/builtin/prompt-preset/claude-fable-5-1.js +16 -3
- package/dist/core/retry-fallback/usage-limit.js +5 -1
- package/dist/core/tools/diff-render.d.ts +4 -0
- package/dist/core/tools/diff-render.js +13 -0
- package/dist/core/tools/renderers/edit.js +6 -4
- package/dist/main.js +12 -0
- package/dist/modes/interactive/interactive-mode.d.ts +1 -0
- package/dist/modes/interactive/interactive-mode.js +55 -11
- package/dist/modes/interactive/provider-error-presentation.d.ts +25 -0
- package/dist/modes/interactive/provider-error-presentation.js +42 -0
- package/dist/modes/interactive/theme/dark.json +5 -5
- package/dist/modes/interactive/theme/light.json +5 -5
- package/dist/modes/rpc/custom-capability.d.ts +6 -0
- package/dist/modes/rpc/custom-capability.js +6 -0
- package/dist/modes/rpc/rpc-client.d.ts +3 -0
- package/dist/modes/rpc/rpc-input-validation.d.ts +7 -0
- package/dist/modes/rpc/rpc-input-validation.js +37 -0
- package/dist/modes/rpc/rpc-types.d.ts +9 -0
- package/dist/modes/rpc/session-command-activity.d.ts +2 -0
- package/dist/modes/rpc/session-command-activity.js +25 -0
- package/dist/modes/rpc/session-command-router.js +11 -4
- package/dist/modes/rpc/session-registry-attach.js +1 -0
- package/dist/modes/rpc/session-registry-types.d.ts +7 -0
- package/dist/modes/rpc/session-registry-types.js +11 -0
- package/dist/modes/rpc/session-registry.js +6 -3
- package/dist/modes/rpc/session-sweep.d.ts +12 -2
- package/dist/modes/rpc/session-sweep.js +36 -3
- package/dist/modes/rpc/session-teardown.d.ts +2 -0
- package/dist/modes/rpc/session-teardown.js +1 -0
- package/dist/modes/rpc/worker-session-registry.js +5 -1
- package/docs/rpc.md +39 -5
- package/docs/settings.md +3 -1
- package/package.json +6 -6
- package/dist/bundle/chunks/chunk-K3BXFPWZ.js +0 -2
|
@@ -79,6 +79,19 @@ function renderToolDiffLine(line, filePath, theme, contentOverride) {
|
|
|
79
79
|
const rendered = `${theme.fg(diffColor, line.sign)}${lineNumber} ${content}`;
|
|
80
80
|
return theme.bg(background, rendered);
|
|
81
81
|
}
|
|
82
|
+
export function countDiffChanges(diffText) {
|
|
83
|
+
let added = 0;
|
|
84
|
+
let removed = 0;
|
|
85
|
+
for (const line of diffText.split("\n")) {
|
|
86
|
+
if (line.startsWith("+++") || line.startsWith("---"))
|
|
87
|
+
continue;
|
|
88
|
+
if (line.startsWith("+"))
|
|
89
|
+
added++;
|
|
90
|
+
else if (line.startsWith("-"))
|
|
91
|
+
removed++;
|
|
92
|
+
}
|
|
93
|
+
return { added, removed };
|
|
94
|
+
}
|
|
82
95
|
export function renderToolDiff(diffText, options) {
|
|
83
96
|
const parsedLines = diffText.split("\n").map(parseRenderableDiffLine);
|
|
84
97
|
const rendered = [];
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* definition, so the tool's public shape is unchanged.
|
|
7
7
|
*/
|
|
8
8
|
import { Box, Container, Spacer, Text } from "@earendil-works/pi-tui";
|
|
9
|
-
import { renderToolDiff } from "../diff-render.js";
|
|
9
|
+
import { countDiffChanges, renderToolDiff } from "../diff-render.js";
|
|
10
10
|
import { computeEditsDiff } from "../edit-diff.js";
|
|
11
11
|
import { renderToolPath, str } from "../render-utils.js";
|
|
12
12
|
function createEditCallRenderComponent() {
|
|
@@ -48,9 +48,11 @@ function getRenderablePreviewInput(args) {
|
|
|
48
48
|
}
|
|
49
49
|
return null;
|
|
50
50
|
}
|
|
51
|
-
function formatEditCall(args, theme, cwd) {
|
|
51
|
+
function formatEditCall(args, theme, cwd, diffText) {
|
|
52
52
|
const pathDisplay = renderToolPath(str(args?.file_path ?? args?.path), theme, cwd);
|
|
53
|
-
|
|
53
|
+
const counts = diffText === undefined ? undefined : countDiffChanges(diffText);
|
|
54
|
+
const countTag = counts === undefined ? "" : theme.fg("muted", ` (+${counts.added}/-${counts.removed})`);
|
|
55
|
+
return `${theme.fg("toolTitle", theme.bold("edit"))} ${pathDisplay}${countTag}`;
|
|
54
56
|
}
|
|
55
57
|
function formatEditResult(args, preview, result, theme, isError) {
|
|
56
58
|
const rawPath = str(args?.file_path ?? args?.path);
|
|
@@ -87,7 +89,7 @@ function getEditHeaderBg(preview, settledError, theme) {
|
|
|
87
89
|
function buildEditCallComponent(component, args, theme, cwd) {
|
|
88
90
|
component.setBgFn(getEditHeaderBg(component.preview, component.settledError, theme));
|
|
89
91
|
component.detachAll();
|
|
90
|
-
component.addChild(new Text(formatEditCall(args, theme, cwd), 0, 0));
|
|
92
|
+
component.addChild(new Text(formatEditCall(args, theme, cwd, component.preview && !("error" in component.preview) ? component.preview.diff : undefined), 0, 0));
|
|
91
93
|
if (!component.preview) {
|
|
92
94
|
return component;
|
|
93
95
|
}
|
package/dist/main.js
CHANGED
|
@@ -677,6 +677,18 @@ export function createCliRuntimeFactory(configuration, local = {}) {
|
|
|
677
677
|
parsed.projectTrustOverride ??
|
|
678
678
|
(!hasTrustRequiringResources || trustStore.get(cwd) === true));
|
|
679
679
|
const runtimeSettingsManager = SettingsManager.create(cwd, agentDir, { projectTrusted });
|
|
680
|
+
// The opener's per-session fallback policy is an in-memory override: never saved, never shared.
|
|
681
|
+
if (launchProfile?.retryFallback) {
|
|
682
|
+
runtimeSettingsManager.applyOverrides({
|
|
683
|
+
retry: {
|
|
684
|
+
modelFallback: launchProfile.retryFallback.modelFallback,
|
|
685
|
+
fallbackChains: Object.fromEntries(Object.entries(launchProfile.retryFallback.fallbackChains).map(([key, entries]) => [
|
|
686
|
+
key,
|
|
687
|
+
[...entries],
|
|
688
|
+
])),
|
|
689
|
+
},
|
|
690
|
+
});
|
|
691
|
+
}
|
|
680
692
|
const services = await createAgentSessionServices({
|
|
681
693
|
cwd,
|
|
682
694
|
agentDir,
|
|
@@ -108,6 +108,7 @@ export declare class InteractiveMode {
|
|
|
108
108
|
private readonly sessionShownTipIds;
|
|
109
109
|
private shortcutOverlay;
|
|
110
110
|
private lastEditorText;
|
|
111
|
+
private startupProviderGuidanceShown;
|
|
111
112
|
private lastInputWasPaste;
|
|
112
113
|
private sessionLogger;
|
|
113
114
|
private readonly continuityNotices;
|
|
@@ -13,6 +13,7 @@ import { spawn, spawnSync } from "child_process";
|
|
|
13
13
|
import { APP_COMMAND, APP_NAME, APP_TITLE, BRAND, CONFIG_DIR_NAME, DISPLAY_VERSION, expandTildePath, getAgentDir, getAuthPath, getDebugLogPath, getDocsPath, getShareViewerUrl, } from "../../config.js";
|
|
14
14
|
import { parseSkillBlock, } from "../../core/agent-session.js";
|
|
15
15
|
import { SessionImportFileNotFoundError } from "../../core/agent-session-runtime.js";
|
|
16
|
+
import { formatNoModelsAvailableMessage } from "../../core/auth-guidance.js";
|
|
16
17
|
import { isApiKeyLoginProvider } from "../../core/auth-providers.js";
|
|
17
18
|
import { envValue } from "../../core/brand.js";
|
|
18
19
|
import { CACHE_TTL_MS, collectCacheMisses, computeCacheWaste, detectCacheMiss, } from "../../core/cache-stats.js";
|
|
@@ -113,7 +114,7 @@ import { buildResourceScopeGroups, formatResourceScopeGroups, getDisplaySourceIn
|
|
|
113
114
|
import { describeLoginFailure } from "./login-outcome.js";
|
|
114
115
|
import { refreshModelCatalogs } from "./model-catalog-refresh.js";
|
|
115
116
|
import { getModelSearchText } from "./model-search.js";
|
|
116
|
-
import { isNetworkProviderError, isNetworkProviderMessage, ProviderErrorPresentation, } from "./provider-error-presentation.js";
|
|
117
|
+
import { isNetworkProviderError, isNetworkProviderMessage, isRetryableProviderError, ProviderErrorPresentation, } from "./provider-error-presentation.js";
|
|
117
118
|
import { replayAssistantTools } from "./replay-assistant-tools.js";
|
|
118
119
|
import { allScopeSessions, chooseResumePath, currentScopeSessions } from "./resume-rebind.js";
|
|
119
120
|
import { isRiskyMainModel, RISKY_MAIN_MODEL_WARNING } from "./risky-main-model-warning.js";
|
|
@@ -554,6 +555,7 @@ export class InteractiveMode {
|
|
|
554
555
|
constructor(runtimeHost, options = {}) {
|
|
555
556
|
this.sessionShownTipIds = new Set();
|
|
556
557
|
this.lastEditorText = "";
|
|
558
|
+
this.startupProviderGuidanceShown = false;
|
|
557
559
|
this.lastInputWasPaste = false;
|
|
558
560
|
this.continuityNotices = new ContinuityNoticeTracker();
|
|
559
561
|
this.turnWorkingTip = new WorkingTipCache();
|
|
@@ -1323,11 +1325,31 @@ export class InteractiveMode {
|
|
|
1323
1325
|
if (modelsJsonError) {
|
|
1324
1326
|
this.showError(`models.json error: ${modelsJsonError}`);
|
|
1325
1327
|
}
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
+
const modelRuntimeWarnings = this.session.modelRuntime.getWarnings();
|
|
1329
|
+
// Fold repeated warnings only in quiet startup; a hand-built context without getQuietStartup
|
|
1330
|
+
// is treated as not-quiet so full detail shows (the safe default, and what the old path did).
|
|
1331
|
+
const getQuietStartup = this.settingsManager?.getQuietStartup?.bind(this.settingsManager);
|
|
1332
|
+
const quietStartup = getQuietStartup ? getQuietStartup() : false;
|
|
1333
|
+
if (showsStartupDetails(this.options.verbose, quietStartup)) {
|
|
1334
|
+
for (const warning of modelRuntimeWarnings) {
|
|
1335
|
+
this.showWarning(warning);
|
|
1336
|
+
}
|
|
1337
|
+
}
|
|
1338
|
+
else if (modelRuntimeWarnings.length === 1) {
|
|
1339
|
+
this.showWarning(modelRuntimeWarnings[0]);
|
|
1340
|
+
}
|
|
1341
|
+
else if (modelRuntimeWarnings.length > 1) {
|
|
1342
|
+
this.showNoticeBox({
|
|
1343
|
+
title: `${modelRuntimeWarnings.length} model warnings`,
|
|
1344
|
+
tone: "warning",
|
|
1345
|
+
why: modelRuntimeWarnings[0],
|
|
1346
|
+
extra: modelRuntimeWarnings.slice(1).map((text) => ({ text })),
|
|
1347
|
+
});
|
|
1328
1348
|
}
|
|
1329
1349
|
if (modelFallbackMessage) {
|
|
1330
1350
|
this.showWarning(modelFallbackMessage);
|
|
1351
|
+
if (modelFallbackMessage === formatNoModelsAvailableMessage())
|
|
1352
|
+
this.startupProviderGuidanceShown = true;
|
|
1331
1353
|
}
|
|
1332
1354
|
for (const warning of this.session.fallbackValidationWarnings) {
|
|
1333
1355
|
this.showWarning(warning);
|
|
@@ -1805,7 +1827,13 @@ export class InteractiveMode {
|
|
|
1805
1827
|
if (options?.sort !== false) {
|
|
1806
1828
|
labels.sort((a, b) => a.localeCompare(b));
|
|
1807
1829
|
}
|
|
1808
|
-
|
|
1830
|
+
// A short listing fits on one line and shows in full; a long one (dozens of skills) is
|
|
1831
|
+
// what flooded the first screen on a narrow terminal, so it truncates to a few names
|
|
1832
|
+
// with a +N more hint. The full list is one Ctrl+O away.
|
|
1833
|
+
const shown = labels.length <= 8 ? labels : labels.slice(0, 3);
|
|
1834
|
+
const hidden = labels.length - shown.length;
|
|
1835
|
+
const more = hidden > 0 ? theme.fg("muted", ` +${hidden} more (${keyText("app.tools.expand")})`) : "";
|
|
1836
|
+
return theme.fg("dim", ` ${shown.join(", ")}`) + more;
|
|
1809
1837
|
};
|
|
1810
1838
|
// System resources are left out of the compact body; a section with nothing else to show stays
|
|
1811
1839
|
// hidden until the listing is expanded, where the system group lists them. Bodies are built on
|
|
@@ -4185,6 +4213,15 @@ export class InteractiveMode {
|
|
|
4185
4213
|
case "resume_context_reduced":
|
|
4186
4214
|
this.showWarning(event.notice);
|
|
4187
4215
|
break;
|
|
4216
|
+
case "provider_required":
|
|
4217
|
+
// The first notice repeats the startup "No models available" warning (same /login guidance), so it
|
|
4218
|
+
// is absorbed by it; the session emits again only after a turn was admitted in between.
|
|
4219
|
+
if (this.startupProviderGuidanceShown) {
|
|
4220
|
+
this.startupProviderGuidanceShown = false;
|
|
4221
|
+
break;
|
|
4222
|
+
}
|
|
4223
|
+
this.showWarning(event.notice);
|
|
4224
|
+
break;
|
|
4188
4225
|
case "settings_source_selected":
|
|
4189
4226
|
this.showSettingsSourceSelected(event);
|
|
4190
4227
|
break;
|
|
@@ -4475,6 +4512,9 @@ export class InteractiveMode {
|
|
|
4475
4512
|
}
|
|
4476
4513
|
}
|
|
4477
4514
|
else if (event.result) {
|
|
4515
|
+
// A successful compaction ends the retry episode: no finish is owed, so a later
|
|
4516
|
+
// never-retried terminal failure must not reopen the banner.
|
|
4517
|
+
this.providerErrors?.clear();
|
|
4478
4518
|
// Compaction event consumers in the fork are session-backed and do not
|
|
4479
4519
|
// necessarily expose InteractiveMode's SessionManager convenience getter.
|
|
4480
4520
|
// Keep the structural fallback for focused handler consumers while using
|
|
@@ -4520,7 +4560,11 @@ export class InteractiveMode {
|
|
|
4520
4560
|
}
|
|
4521
4561
|
else if (event.errorMessage) {
|
|
4522
4562
|
const errorMessage = sanitizeTerminalLabel(event.errorMessage);
|
|
4523
|
-
|
|
4563
|
+
// The quiet provider-retry banner's finish() closes out a retry recorded in the CURRENT
|
|
4564
|
+
// episode. A terminal compaction failure after an episode that already finished (or one
|
|
4565
|
+
// that never retried) must always surface as an error, even when its message looks
|
|
4566
|
+
// transient ("timeout", "truncated generator").
|
|
4567
|
+
if (isRetryableProviderError(errorMessage) && this.providerErrors?.awaitingRetryFinish === true) {
|
|
4524
4568
|
this.getProviderErrors().finish(errorMessage);
|
|
4525
4569
|
}
|
|
4526
4570
|
else if (event.reason === "manual") {
|
|
@@ -4632,7 +4676,7 @@ export class InteractiveMode {
|
|
|
4632
4676
|
this.setExtensionStatus(FALLBACK_STATUS_KEY, undefined);
|
|
4633
4677
|
break;
|
|
4634
4678
|
case "retry_fallback_exhausted":
|
|
4635
|
-
if (
|
|
4679
|
+
if (isRetryableProviderError(event.lastError)) {
|
|
4636
4680
|
this.getProviderErrors().finish(event.lastError);
|
|
4637
4681
|
this.setExtensionStatus(FALLBACK_STATUS_KEY, undefined);
|
|
4638
4682
|
break;
|
|
@@ -4654,7 +4698,7 @@ export class InteractiveMode {
|
|
|
4654
4698
|
});
|
|
4655
4699
|
break;
|
|
4656
4700
|
case "auto_retry_start": {
|
|
4657
|
-
if (
|
|
4701
|
+
if (isRetryableProviderError(event.errorMessage)) {
|
|
4658
4702
|
this.getProviderErrors().retrying(event.errorMessage, this.toolOutputExpanded);
|
|
4659
4703
|
}
|
|
4660
4704
|
// During retry waits, isStreaming flips false between attempts. The main Esc handler
|
|
@@ -4694,7 +4738,7 @@ export class InteractiveMode {
|
|
|
4694
4738
|
if (event.success || event.finalError === "Retry cancelled") {
|
|
4695
4739
|
this.providerErrors?.clear();
|
|
4696
4740
|
}
|
|
4697
|
-
else if (
|
|
4741
|
+
else if (isRetryableProviderError(event.finalError)) {
|
|
4698
4742
|
this.getProviderErrors().finish(event.finalError, event.attempt);
|
|
4699
4743
|
}
|
|
4700
4744
|
else {
|
|
@@ -4704,7 +4748,7 @@ export class InteractiveMode {
|
|
|
4704
4748
|
break;
|
|
4705
4749
|
}
|
|
4706
4750
|
case "summarization_retry_scheduled": {
|
|
4707
|
-
if (
|
|
4751
|
+
if (isRetryableProviderError(event.errorMessage)) {
|
|
4708
4752
|
this.getProviderErrors().retrying(event.errorMessage, this.toolOutputExpanded);
|
|
4709
4753
|
}
|
|
4710
4754
|
else {
|
|
@@ -4725,7 +4769,7 @@ export class InteractiveMode {
|
|
|
4725
4769
|
break;
|
|
4726
4770
|
}
|
|
4727
4771
|
case "summarization_retry_finished": {
|
|
4728
|
-
this.providerErrors?.
|
|
4772
|
+
this.providerErrors?.clearTransient();
|
|
4729
4773
|
this.clearStatusIndicator("retry");
|
|
4730
4774
|
this.ui.requestRender();
|
|
4731
4775
|
break;
|
|
@@ -4760,7 +4804,7 @@ export class InteractiveMode {
|
|
|
4760
4804
|
showRetryStatusIndicatorWithCadence(event) {
|
|
4761
4805
|
const refreshIntervalMs = largeSessionWorkingStatusInterval(this.sessionManager.getEntryCount(), DEFAULT_RETRY_STATUS_REFRESH_INTERVAL_MS, LARGE_SESSION_RETRY_STATUS_REFRESH_INTERVAL_MS);
|
|
4762
4806
|
const indicator = refreshIntervalMs === DEFAULT_RETRY_STATUS_REFRESH_INTERVAL_MS ? undefined : { intervalMs: refreshIntervalMs };
|
|
4763
|
-
this.showStatusIndicator(new RetryStatusIndicator(this.ui, event.attempt, event.maxAttempts, event.delayMs, indicator,
|
|
4807
|
+
this.showStatusIndicator(new RetryStatusIndicator(this.ui, event.attempt, event.maxAttempts, event.delayMs, indicator, isRetryableProviderError(event.errorMessage)));
|
|
4764
4808
|
this.ui.requestRender();
|
|
4765
4809
|
}
|
|
4766
4810
|
/** Extract text content from a user message */
|
|
@@ -2,6 +2,15 @@ import type { AssistantMessage } from "@earendil-works/pi-ai";
|
|
|
2
2
|
import { type Container } from "@earendil-works/pi-tui";
|
|
3
3
|
/** Presentation classification only. Retry eligibility remains owned by AgentSession. */
|
|
4
4
|
export declare function isNetworkProviderError(raw: string | undefined, envelopeOnly?: boolean): boolean;
|
|
5
|
+
/**
|
|
6
|
+
* True when a provider failure should take the quiet presentation path (one banner, a
|
|
7
|
+
* status-line countdown while retrying, raw JSON only on expand): any transient failure,
|
|
8
|
+
* which is a network drop, a 429 rate-limit, or a 5xx. Hard quota/auth/billing failures
|
|
9
|
+
* classify non-retryable here and stay verbose, because they need a credential or plan
|
|
10
|
+
* change, not a wait. Delegates to the shared classifier so the transcript decision and
|
|
11
|
+
* the retry engine never disagree about what is transient.
|
|
12
|
+
*/
|
|
13
|
+
export declare function isRetryableProviderError(raw: string | undefined): boolean;
|
|
5
14
|
export declare function isNetworkProviderMessage(message: AssistantMessage): boolean;
|
|
6
15
|
/** Owns one display episode, not session messages or retry/fallback policy. */
|
|
7
16
|
export declare class ProviderErrorPresentation {
|
|
@@ -11,7 +20,23 @@ export declare class ProviderErrorPresentation {
|
|
|
11
20
|
constructor(chat: Container);
|
|
12
21
|
record(raw: string, expanded?: boolean): void;
|
|
13
22
|
retrying(raw: string, expanded: boolean): void;
|
|
23
|
+
/** A success or cleanup ends the episode: no finish is owed afterward. */
|
|
14
24
|
clear(): void;
|
|
25
|
+
/**
|
|
26
|
+
* The retry engine finishing (summarization_retry_finished): clears the transient row but keeps
|
|
27
|
+
* the episode marker, because the terminal compaction_end that follows still closes the
|
|
28
|
+
* exhausted episode out via finish().
|
|
29
|
+
*/
|
|
30
|
+
clearTransient(): void;
|
|
31
|
+
/** True while a retry episode is active (a retry was recorded and not yet finished/cleared). */
|
|
32
|
+
get hasActiveRetry(): boolean;
|
|
33
|
+
/**
|
|
34
|
+
* True only while a retry has been recorded in the CURRENT episode and not yet closed out by
|
|
35
|
+
* finish(). A fresh terminal failure after an episode that already finished returns false, so
|
|
36
|
+
* it surfaces as a plain error instead of reopening the stale banner.
|
|
37
|
+
*/
|
|
38
|
+
get awaitingRetryFinish(): boolean;
|
|
39
|
+
private retryAwaitingFinish;
|
|
15
40
|
finish(raw?: string, attempts?: number): void;
|
|
16
41
|
newTurn(): void;
|
|
17
42
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isRetryableErrorMessage } from "@earendil-works/pi-ai";
|
|
1
2
|
import { Text } from "@earendil-works/pi-tui";
|
|
2
3
|
import { z } from "zod";
|
|
3
4
|
import { keyText } from "./components/keybinding-hints.js";
|
|
@@ -25,6 +26,21 @@ export function isNetworkProviderError(raw, envelopeOnly = false) {
|
|
|
25
26
|
throw error;
|
|
26
27
|
}
|
|
27
28
|
}
|
|
29
|
+
/**
|
|
30
|
+
* True when a provider failure should take the quiet presentation path (one banner, a
|
|
31
|
+
* status-line countdown while retrying, raw JSON only on expand): any transient failure,
|
|
32
|
+
* which is a network drop, a 429 rate-limit, or a 5xx. Hard quota/auth/billing failures
|
|
33
|
+
* classify non-retryable here and stay verbose, because they need a credential or plan
|
|
34
|
+
* change, not a wait. Delegates to the shared classifier so the transcript decision and
|
|
35
|
+
* the retry engine never disagree about what is transient.
|
|
36
|
+
*/
|
|
37
|
+
export function isRetryableProviderError(raw) {
|
|
38
|
+
if (!raw)
|
|
39
|
+
return false;
|
|
40
|
+
if (isNetworkProviderError(raw))
|
|
41
|
+
return true;
|
|
42
|
+
return isRetryableErrorMessage(raw);
|
|
43
|
+
}
|
|
28
44
|
export function isNetworkProviderMessage(message) {
|
|
29
45
|
return ((message.stopReason === "error" || message.stopReason === "aborted") &&
|
|
30
46
|
isNetworkProviderError(message.errorMessage));
|
|
@@ -62,10 +78,12 @@ class ProviderFailureNotice {
|
|
|
62
78
|
export class ProviderErrorPresentation {
|
|
63
79
|
constructor(chat) {
|
|
64
80
|
this.pending = false;
|
|
81
|
+
this.retryAwaitingFinish = false;
|
|
65
82
|
this.chat = chat;
|
|
66
83
|
}
|
|
67
84
|
record(raw, expanded = false) {
|
|
68
85
|
this.pending = true;
|
|
86
|
+
this.retryAwaitingFinish = true;
|
|
69
87
|
if (!this.notice) {
|
|
70
88
|
this.notice = new ProviderFailureNotice();
|
|
71
89
|
this.notice.setExpanded(expanded);
|
|
@@ -78,17 +96,41 @@ export class ProviderErrorPresentation {
|
|
|
78
96
|
// RetryStatusIndicator owns the single visible transient row.
|
|
79
97
|
this.notice?.setSummary(undefined);
|
|
80
98
|
}
|
|
99
|
+
/** A success or cleanup ends the episode: no finish is owed afterward. */
|
|
81
100
|
clear() {
|
|
82
101
|
this.pending = false;
|
|
102
|
+
this.retryAwaitingFinish = false;
|
|
83
103
|
// Keep diagnostics reachable through the existing expansion affordance.
|
|
84
104
|
this.notice?.setSummary(undefined);
|
|
85
105
|
}
|
|
106
|
+
/**
|
|
107
|
+
* The retry engine finishing (summarization_retry_finished): clears the transient row but keeps
|
|
108
|
+
* the episode marker, because the terminal compaction_end that follows still closes the
|
|
109
|
+
* exhausted episode out via finish().
|
|
110
|
+
*/
|
|
111
|
+
clearTransient() {
|
|
112
|
+
this.pending = false;
|
|
113
|
+
this.notice?.setSummary(undefined);
|
|
114
|
+
}
|
|
115
|
+
/** True while a retry episode is active (a retry was recorded and not yet finished/cleared). */
|
|
116
|
+
get hasActiveRetry() {
|
|
117
|
+
return this.pending;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* True only while a retry has been recorded in the CURRENT episode and not yet closed out by
|
|
121
|
+
* finish(). A fresh terminal failure after an episode that already finished returns false, so
|
|
122
|
+
* it surfaces as a plain error instead of reopening the stale banner.
|
|
123
|
+
*/
|
|
124
|
+
get awaitingRetryFinish() {
|
|
125
|
+
return this.retryAwaitingFinish;
|
|
126
|
+
}
|
|
86
127
|
finish(raw, attempts) {
|
|
87
128
|
if (raw)
|
|
88
129
|
this.record(raw);
|
|
89
130
|
if (!this.pending)
|
|
90
131
|
return;
|
|
91
132
|
this.pending = false;
|
|
133
|
+
this.retryAwaitingFinish = false;
|
|
92
134
|
const count = attempts === undefined ? "" : ` after ${attempts} retries`;
|
|
93
135
|
this.notice?.setSummary(`The model provider could not complete the request${count}. Try again or choose another model with /model.`);
|
|
94
136
|
}
|
|
@@ -34,9 +34,9 @@
|
|
|
34
34
|
"customMessageBg": "okhsl(295 42% 24%)",
|
|
35
35
|
"customMessageText": "muted",
|
|
36
36
|
"customMessageLabel": "violet",
|
|
37
|
-
"toolPendingBg": "okhsl(229 5%
|
|
38
|
-
"toolSuccessBg": "okhsl(158
|
|
39
|
-
"toolErrorBg": "okhsl(19
|
|
37
|
+
"toolPendingBg": "okhsl(229 5% 13%)",
|
|
38
|
+
"toolSuccessBg": "okhsl(158 26% 13%)",
|
|
39
|
+
"toolErrorBg": "okhsl(19 28% 14%)",
|
|
40
40
|
"toolTitle": "text",
|
|
41
41
|
"toolOutput": "muted",
|
|
42
42
|
"mdHeading": "yellow",
|
|
@@ -49,8 +49,8 @@
|
|
|
49
49
|
"mdQuoteBorder": "muted",
|
|
50
50
|
"mdHr": "muted",
|
|
51
51
|
"mdListBullet": "violet",
|
|
52
|
-
"toolDiffAdded": "
|
|
53
|
-
"toolDiffRemoved": "
|
|
52
|
+
"toolDiffAdded": "okhsl(159 58% 76%)",
|
|
53
|
+
"toolDiffRemoved": "okhsl(20 70% 77%)",
|
|
54
54
|
"toolDiffContext": "muted",
|
|
55
55
|
"syntaxComment": "muted",
|
|
56
56
|
"syntaxKeyword": "blue",
|
|
@@ -34,9 +34,9 @@
|
|
|
34
34
|
"customMessageBg": "okhsl(295 25% 91%)",
|
|
35
35
|
"customMessageText": "muted",
|
|
36
36
|
"customMessageLabel": "violet",
|
|
37
|
-
"toolPendingBg": "okhsl(248 3%
|
|
38
|
-
"toolSuccessBg": "okhsl(156
|
|
39
|
-
"toolErrorBg": "okhsl(24
|
|
37
|
+
"toolPendingBg": "okhsl(248 3% 93%)",
|
|
38
|
+
"toolSuccessBg": "okhsl(156 25% 91%)",
|
|
39
|
+
"toolErrorBg": "okhsl(24 28% 91%)",
|
|
40
40
|
"toolTitle": "text",
|
|
41
41
|
"toolOutput": "muted",
|
|
42
42
|
"mdHeading": "yellow",
|
|
@@ -49,8 +49,8 @@
|
|
|
49
49
|
"mdQuoteBorder": "muted",
|
|
50
50
|
"mdHr": "muted",
|
|
51
51
|
"mdListBullet": "violet",
|
|
52
|
-
"toolDiffAdded": "
|
|
53
|
-
"toolDiffRemoved": "
|
|
52
|
+
"toolDiffAdded": "okhsl(159 85% 28%)",
|
|
53
|
+
"toolDiffRemoved": "okhsl(20 100% 30%)",
|
|
54
54
|
"toolDiffContext": "muted",
|
|
55
55
|
"syntaxComment": "muted",
|
|
56
56
|
"syntaxKeyword": "blue",
|
|
@@ -74,6 +74,12 @@ export declare const PROMPT_SURFACE_CAPABILITY = "prompt_surface";
|
|
|
74
74
|
* value reaches every consumer, so a client may rely on it the moment it sees this name.
|
|
75
75
|
*/
|
|
76
76
|
export declare const BROWSER_ENGINE_CAPABILITY = "browser_engine";
|
|
77
|
+
/**
|
|
78
|
+
* HOST capability: this host honors `open_session.retryFallback` (`{ modelFallback, fallbackChains }`). The
|
|
79
|
+
* policy is that session's own: it is applied as an in-memory settings override for that session only,
|
|
80
|
+
* never written to a settings file and never seen by another session on the host.
|
|
81
|
+
*/
|
|
82
|
+
export declare const RETRY_FALLBACK_PROFILE_CAPABILITY = "retry_fallback_profile";
|
|
77
83
|
/**
|
|
78
84
|
* HOST capability: `open_session.promptSurface` also accepts `chat` (a chat bridge: no routing line,
|
|
79
85
|
* no handoff block, no todo cues). A host without it refuses `chat` with `invalid_launch_profile`,
|
|
@@ -74,6 +74,12 @@ export const PROMPT_SURFACE_CAPABILITY = "prompt_surface";
|
|
|
74
74
|
* value reaches every consumer, so a client may rely on it the moment it sees this name.
|
|
75
75
|
*/
|
|
76
76
|
export const BROWSER_ENGINE_CAPABILITY = "browser_engine";
|
|
77
|
+
/**
|
|
78
|
+
* HOST capability: this host honors `open_session.retryFallback` (`{ modelFallback, fallbackChains }`). The
|
|
79
|
+
* policy is that session's own: it is applied as an in-memory settings override for that session only,
|
|
80
|
+
* never written to a settings file and never seen by another session on the host.
|
|
81
|
+
*/
|
|
82
|
+
export const RETRY_FALLBACK_PROFILE_CAPABILITY = "retry_fallback_profile";
|
|
77
83
|
/**
|
|
78
84
|
* HOST capability: `open_session.promptSurface` also accepts `chat` (a chat bridge: no routing line,
|
|
79
85
|
* no handoff block, no todo cues). A host without it refuses `chat` with `invalid_launch_profile`,
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
import type { AgentMessage, ThinkingLevel } from "@earendil-works/pi-agent-core";
|
|
7
7
|
import type { ImageContent } from "@earendil-works/pi-ai";
|
|
8
8
|
import type { PromptDisposition, QueuedInputDisposition, SessionStats } from "../../core/agent-session.ts";
|
|
9
|
+
import type { SessionRetryFallbackProfile } from "../../core/agent-session-runtime.ts";
|
|
9
10
|
import type { BashResult } from "../../core/bash-executor.ts";
|
|
10
11
|
import type { BrowserEngine } from "../../core/browser-engine.ts";
|
|
11
12
|
import { type ClientMessageIdentity } from "../../core/client-message-identity.ts";
|
|
@@ -136,6 +137,8 @@ export declare class RpcClient {
|
|
|
136
137
|
promptSurface?: PromptSurface;
|
|
137
138
|
/** Which browser this session's skills drive; needs the host's `browser_engine`. */
|
|
138
139
|
browserEngine?: BrowserEngine;
|
|
140
|
+
/** This session's own fallback policy, in memory only; needs the host's `retry_fallback_profile`. */
|
|
141
|
+
retryFallback?: SessionRetryFallbackProfile;
|
|
139
142
|
}): Promise<{
|
|
140
143
|
sessionId: string;
|
|
141
144
|
state: RpcSessionState;
|
|
@@ -40,6 +40,13 @@ export declare function sessionPromptSurfaceError(value: unknown): string | unde
|
|
|
40
40
|
* own browser must not silently run without one.
|
|
41
41
|
*/
|
|
42
42
|
export declare function sessionBrowserEngineError(value: unknown): string | undefined;
|
|
43
|
+
/**
|
|
44
|
+
* Detail for an `open_session.retryFallback` the host refuses, or undefined when it is absent or well
|
|
45
|
+
* formed: `{ modelFallback: boolean, fallbackChains: Record<string, string[]> }`, at most 32 chains of at
|
|
46
|
+
* most 32 non-empty selectors. A malformed profile is refused rather than ignored, so a caller that asked
|
|
47
|
+
* for a chain never runs without one unknowingly.
|
|
48
|
+
*/
|
|
49
|
+
export declare function sessionRetryFallbackError(value: unknown): string | undefined;
|
|
43
50
|
export declare function rpcCommandShapeError(command: unknown): string | undefined;
|
|
44
51
|
export declare function rpcMessageLengthError(command: unknown): string | undefined;
|
|
45
52
|
//# sourceMappingURL=rpc-input-validation.d.ts.map
|
|
@@ -161,6 +161,43 @@ export function sessionBrowserEngineError(value) {
|
|
|
161
161
|
return undefined;
|
|
162
162
|
return `browserEngine must be "connected", "builtin" or "none".`;
|
|
163
163
|
}
|
|
164
|
+
const MAX_RETRY_FALLBACK_CHAINS = 32;
|
|
165
|
+
const MAX_RETRY_FALLBACK_ENTRIES = 32;
|
|
166
|
+
const MAX_RETRY_FALLBACK_SELECTOR_LENGTH = 512;
|
|
167
|
+
/**
|
|
168
|
+
* Detail for an `open_session.retryFallback` the host refuses, or undefined when it is absent or well
|
|
169
|
+
* formed: `{ modelFallback: boolean, fallbackChains: Record<string, string[]> }`, at most 32 chains of at
|
|
170
|
+
* most 32 non-empty selectors. A malformed profile is refused rather than ignored, so a caller that asked
|
|
171
|
+
* for a chain never runs without one unknowingly.
|
|
172
|
+
*/
|
|
173
|
+
export function sessionRetryFallbackError(value) {
|
|
174
|
+
if (value === undefined)
|
|
175
|
+
return undefined;
|
|
176
|
+
const shape = "retryFallback must be { modelFallback: boolean, fallbackChains: Record<string, string[]> }.";
|
|
177
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
178
|
+
return shape;
|
|
179
|
+
const record = value;
|
|
180
|
+
if (typeof record.modelFallback !== "boolean")
|
|
181
|
+
return shape;
|
|
182
|
+
const chains = record.fallbackChains;
|
|
183
|
+
if (typeof chains !== "object" || chains === null || Array.isArray(chains))
|
|
184
|
+
return shape;
|
|
185
|
+
const entries = Object.entries(chains);
|
|
186
|
+
if (entries.length > MAX_RETRY_FALLBACK_CHAINS)
|
|
187
|
+
return `retryFallback allows at most ${MAX_RETRY_FALLBACK_CHAINS} chains.`;
|
|
188
|
+
for (const [key, selectors] of entries) {
|
|
189
|
+
if (!isSelector(key))
|
|
190
|
+
return shape;
|
|
191
|
+
if (!Array.isArray(selectors) || !selectors.every(isSelector))
|
|
192
|
+
return shape;
|
|
193
|
+
if (selectors.length > MAX_RETRY_FALLBACK_ENTRIES)
|
|
194
|
+
return `retryFallback allows at most ${MAX_RETRY_FALLBACK_ENTRIES} entries per chain.`;
|
|
195
|
+
}
|
|
196
|
+
return undefined;
|
|
197
|
+
}
|
|
198
|
+
function isSelector(value) {
|
|
199
|
+
return typeof value === "string" && value.trim().length > 0 && value.length <= MAX_RETRY_FALLBACK_SELECTOR_LENGTH;
|
|
200
|
+
}
|
|
164
201
|
export function rpcCommandShapeError(command) {
|
|
165
202
|
if (typeof command !== "object" || command === null || Array.isArray(command)) {
|
|
166
203
|
return "RPC command must be a JSON object.";
|
|
@@ -9,6 +9,7 @@ import type { ImageContent, Model, ProviderDiagnostic, ThinkingSelection } from
|
|
|
9
9
|
import type { SessionRuntimeKind } from "../../cli/args.ts";
|
|
10
10
|
import type { AgentAbortSource } from "../../core/agent-abort-provenance.ts";
|
|
11
11
|
import type { PromptDisposition, QueuedInput, QueuedInputDisposition, SessionStats } from "../../core/agent-session.ts";
|
|
12
|
+
import type { SessionRetryFallbackProfile } from "../../core/agent-session-runtime.ts";
|
|
12
13
|
import type { BashResult } from "../../core/bash-executor.ts";
|
|
13
14
|
import type { BrowserEngine } from "../../core/browser-engine.ts";
|
|
14
15
|
import type { ClientMessageIdentity } from "../../core/client-message-identity.ts";
|
|
@@ -504,6 +505,14 @@ export type RpcCommand = (RpcSessionCommand & {
|
|
|
504
505
|
* Requires the host capability `browser_engine`. Any other value is refused with `invalid_launch_profile`.
|
|
505
506
|
*/
|
|
506
507
|
browserEngine?: BrowserEngine;
|
|
508
|
+
/**
|
|
509
|
+
* The fallback policy THIS session runs with (e.g. a task child's own chain): applied as an
|
|
510
|
+
* in-memory override of this session's `retry.modelFallback` / `retry.fallbackChains`, never written
|
|
511
|
+
* to a settings file and never seen by another session. Requires the host capability
|
|
512
|
+
* `retry_fallback_profile`; a malformed value is refused with `invalid_launch_profile`. Applied
|
|
513
|
+
* when the open creates the session; an attach keeps the session's existing policy.
|
|
514
|
+
*/
|
|
515
|
+
retryFallback?: SessionRetryFallbackProfile;
|
|
507
516
|
} | {
|
|
508
517
|
id?: string;
|
|
509
518
|
type: "close_session";
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/** Whether the command itself refreshes activity, apart from attached-client keepalives. */
|
|
2
|
+
const observations = new Set([
|
|
3
|
+
"get_state",
|
|
4
|
+
"get_messages",
|
|
5
|
+
"get_entries",
|
|
6
|
+
"get_tree",
|
|
7
|
+
"get_fork_messages",
|
|
8
|
+
"get_last_assistant_text",
|
|
9
|
+
"get_session_stats",
|
|
10
|
+
"get_steering_messages",
|
|
11
|
+
"get_follow_up_messages",
|
|
12
|
+
"get_available_models",
|
|
13
|
+
"get_available_thinking_levels",
|
|
14
|
+
"get_fast_mode",
|
|
15
|
+
"get_commands",
|
|
16
|
+
"get_loaded_surfaces",
|
|
17
|
+
"get_media",
|
|
18
|
+
"get_auth_providers",
|
|
19
|
+
"get_provider_accounts",
|
|
20
|
+
"memory_report",
|
|
21
|
+
]);
|
|
22
|
+
export function refreshesSessionActivity(command) {
|
|
23
|
+
return !observations.has(command);
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=session-command-activity.js.map
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { VERSION } from "../../config.js";
|
|
2
|
-
import { ACCEPT_EDITS_PERMISSION_PRESET_CAPABILITY } from "../../core/extensions/builtin/permission-system/config.js";
|
|
2
|
+
import { ACCEPT_EDITS_PERMISSION_PRESET_CAPABILITY, AUTO_PERMISSION_PRESET_CAPABILITY, } from "../../core/extensions/builtin/permission-system/config.js";
|
|
3
3
|
import { DURABLE_CLIENT_MESSAGE_ID_CAPABILITY } from "./client-admission-record.js";
|
|
4
4
|
import { buildRpcSessionState } from "./connection-handler.js";
|
|
5
|
-
import { AUTO_TITLE_PER_SESSION_CAPABILITY, AUTO_TITLE_SESSIONS_CAPABILITY, BROWSER_ENGINE_CAPABILITY, CONTINUE_FROM_LEAF_CAPABILITY, DURABLE_SESSION_ID_CAPABILITY, MEDIA_PLACEHOLDERS_CAPABILITY, PROMPT_SURFACE_CAPABILITY, PROMPT_SURFACE_CHAT_CAPABILITY, RETAIN_ON_DISCONNECT_CAPABILITY, SESSION_CONTEXT_CAPABILITY, SESSION_KIND_CAPABILITY, WARM_CAPABILITY, } from "./custom-capability.js";
|
|
5
|
+
import { AUTO_TITLE_PER_SESSION_CAPABILITY, AUTO_TITLE_SESSIONS_CAPABILITY, BROWSER_ENGINE_CAPABILITY, CONTINUE_FROM_LEAF_CAPABILITY, DURABLE_SESSION_ID_CAPABILITY, MEDIA_PLACEHOLDERS_CAPABILITY, PROMPT_SURFACE_CAPABILITY, PROMPT_SURFACE_CHAT_CAPABILITY, RETAIN_ON_DISCONNECT_CAPABILITY, RETRY_FALLBACK_PROFILE_CAPABILITY, SESSION_CONTEXT_CAPABILITY, SESSION_KIND_CAPABILITY, WARM_CAPABILITY, } from "./custom-capability.js";
|
|
6
6
|
import { answerWarm } from "./host-warm.js";
|
|
7
7
|
import { protocolIdentity } from "./protocol-identity.js";
|
|
8
|
-
import { sessionAutoTitleError, sessionBrowserEngineError, sessionContextError, sessionKindError, sessionPromptSurfaceError, } from "./rpc-input-validation.js";
|
|
8
|
+
import { sessionAutoTitleError, sessionBrowserEngineError, sessionContextError, sessionKindError, sessionPromptSurfaceError, sessionRetryFallbackError, } from "./rpc-input-validation.js";
|
|
9
9
|
import { RPC_ERROR_INVALID_LAUNCH_PROFILE, RPC_ERROR_INVALID_SESSION_CONTEXT, RPC_ERROR_INVALID_SESSION_KIND, RPC_ERROR_MISSING_SESSION_ID, RPC_ERROR_OPEN_FAILED, RPC_ERROR_SESSION_CLOSING, RPC_ERROR_UNKNOWN_SESSION, } from "./rpc-types.js";
|
|
10
10
|
import { runWithSessionAttribution } from "./session-attribution.js";
|
|
11
11
|
import { createRpcSessionBinding } from "./session-binding.js";
|
|
@@ -213,7 +213,10 @@ export class SessionCommandRouter {
|
|
|
213
213
|
PROMPT_SURFACE_CHAT_CAPABILITY,
|
|
214
214
|
// Each session's tool subprocesses and eval kernel get its own OMO_BROWSER_ENGINE from its launch profile.
|
|
215
215
|
BROWSER_ENGINE_CAPABILITY,
|
|
216
|
+
// Each session's fallback chain is its own in-memory settings override, never the host's file.
|
|
217
|
+
RETRY_FALLBACK_PROFILE_CAPABILITY,
|
|
216
218
|
ACCEPT_EDITS_PERMISSION_PRESET_CAPABILITY,
|
|
219
|
+
AUTO_PERMISSION_PRESET_CAPABILITY,
|
|
217
220
|
// Only an in-process runtime shares the loop a warm loads into (senpi#2314).
|
|
218
221
|
...(this.registry.warm ? [WARM_CAPABILITY] : []),
|
|
219
222
|
...(this.connectionOptions?.capabilities ?? []),
|
|
@@ -317,7 +320,7 @@ export class SessionCommandRouter {
|
|
|
317
320
|
sweepIdleSessions() {
|
|
318
321
|
const now = this.idleNow();
|
|
319
322
|
const idleEvictionMs = this.memoryPressure ? this.idleEvictionMs / 2 : this.idleEvictionMs;
|
|
320
|
-
const verdicts = selectSweepEvictions(this.registry, now, idleEvictionMs);
|
|
323
|
+
const verdicts = selectSweepEvictions(this.registry, now, idleEvictionMs, (sessionId) => this.activeRequests.has(sessionId) || (this.bindings.get(sessionId)?.pendingPrompts?.().length ?? 0) > 0);
|
|
321
324
|
for (const sessionId of verdicts.orphaned)
|
|
322
325
|
void this.evictIdleSession(sessionId, "session_dir_removed");
|
|
323
326
|
for (const sessionId of verdicts.idle)
|
|
@@ -486,6 +489,9 @@ export class SessionCommandRouter {
|
|
|
486
489
|
const browserEngineError = sessionBrowserEngineError(command.browserEngine);
|
|
487
490
|
if (browserEngineError)
|
|
488
491
|
return error(command.id, "open_session", `${RPC_ERROR_INVALID_LAUNCH_PROFILE}: ${browserEngineError}`);
|
|
492
|
+
const retryFallbackError = sessionRetryFallbackError(command.retryFallback);
|
|
493
|
+
if (retryFallbackError)
|
|
494
|
+
return error(command.id, "open_session", `${RPC_ERROR_INVALID_LAUNCH_PROFILE}: ${retryFallbackError}`);
|
|
489
495
|
let opened;
|
|
490
496
|
try {
|
|
491
497
|
opened = await this.registry.openSession({
|
|
@@ -504,6 +510,7 @@ export class SessionCommandRouter {
|
|
|
504
510
|
...(typeof command.auto_title === "boolean" ? { autoTitle: command.auto_title } : {}),
|
|
505
511
|
...(command.promptSurface !== undefined ? { promptSurface: command.promptSurface } : {}),
|
|
506
512
|
...(command.browserEngine !== undefined ? { browserEngine: command.browserEngine } : {}),
|
|
513
|
+
...(command.retryFallback !== undefined ? { retryFallback: command.retryFallback } : {}),
|
|
507
514
|
},
|
|
508
515
|
// Host lifecycle policy, deliberately outside the immutable launch profile.
|
|
509
516
|
{ retainOnDisconnect: command.retain_on_disconnect === true });
|
|
@@ -6,6 +6,7 @@ export async function attachToOpenSession(entries, sessionPath, profile, options
|
|
|
6
6
|
const [handle, entry] = existing;
|
|
7
7
|
const wasParked = entry.retainOnDisconnect === true && entry.attachments === 0;
|
|
8
8
|
entry.attachments += 1;
|
|
9
|
+
entry.detachedAt = undefined;
|
|
9
10
|
// The claim carries the attachment state another generation decides on: a path this host is
|
|
10
11
|
// actively serving a client on is never reclaimable from it.
|
|
11
12
|
pathReservations?.setAttached(sessionPath, true);
|