@code-yeongyu/senpi 2026.10.5 → 2026.10.7
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 +46 -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-SO5RJ2N7.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-ECDLITEY.js → chunk-6EH7QXOG.js} +2 -2
- package/dist/bundle/chunks/{chunk-JVSOW4QU.js → chunk-7A3WRAKP.js} +1 -1
- package/dist/bundle/chunks/chunk-7SMFF2QJ.js +14 -0
- package/dist/bundle/chunks/{chunk-WTJU5B2C.js → chunk-BE66QJ3Z.js} +1 -1
- package/dist/bundle/chunks/{chunk-6JXPZACL.js → chunk-CM5QTRC2.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-EEOKVSNF.js → chunk-EUU6W5B3.js} +180 -176
- package/dist/bundle/chunks/{chunk-KLBKUBDV.js → chunk-GQ3YLXVZ.js} +1 -1
- package/dist/bundle/chunks/{chunk-HKG7XL4H.js → chunk-HY4AOLON.js} +1 -1
- package/dist/bundle/chunks/{chunk-2WV426OJ.js → chunk-JJNIU2A2.js} +8 -4
- package/dist/bundle/chunks/{chunk-22DAHAHO.js → chunk-LUWWM7GA.js} +2 -2
- 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-BBK6SSYT.js → chunk-TAV6QZFM.js} +1 -1
- package/dist/bundle/chunks/{chunk-QUROHJLP.js → chunk-TFO5JGLR.js} +1 -1
- package/dist/bundle/chunks/chunk-VJJ53ORU.js +2 -0
- package/dist/bundle/chunks/{chunk-XYDZ3LIE.js → chunk-YI7SPFDN.js} +2 -2
- package/dist/bundle/chunks/chunk-ZJHRDQCH.js +2 -0
- package/dist/bundle/chunks/cli-main-XBQN73O2.js +3 -0
- 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-YXQAXNKK.js +2 -0
- package/dist/bundle/chunks/{host-lifecycle-7OVWGVIZ.js → host-lifecycle-L4XQIATP.js} +1 -1
- package/dist/bundle/chunks/host-lifecycle.js +1 -1
- package/dist/bundle/chunks/interactive-mode-ROO5W56P.js +2 -0
- 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-5WTKAQJM.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-NCD5FQGV.js} +1 -1
- package/dist/bundle/chunks/{rotation-stream-JUCEXHWU.js → rotation-stream-7E37KOCX.js} +1 -1
- package/dist/bundle/chunks/rpc-mode-MWURKCP2.js +2 -0
- package/dist/bundle/chunks/{schedule-command-QTYSUYO2.js → schedule-command-UTXB465F.js} +1 -1
- package/dist/bundle/chunks/session-control-endpoint-J6RHZXWB.js +3 -0
- package/dist/bundle/chunks/{session-picker-HX4LOJZ7.js → session-picker-VTTRFGE7.js} +1 -1
- package/dist/bundle/chunks/session-worker.js +376 -368
- 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/extensions/eval-handle-host.d.ts +82 -0
- package/dist/core/extensions/eval-handle-host.js +28 -0
- package/dist/core/extensions/loader.js +5 -0
- package/dist/core/extensions/runner.js +4 -0
- package/dist/core/extensions/types.d.ts +15 -0
- 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/index.d.ts +1 -0
- package/dist/index.js +1 -0
- 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/session-control-registry.js +2 -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/host-daemon-registration.d.ts +9 -0
- package/dist/modes/rpc/host-daemon-registration.js +2 -4
- package/dist/modes/rpc/host-ensure-start.js +2 -0
- package/dist/modes/rpc/host-handoff-unregistered.d.ts +24 -0
- package/dist/modes/rpc/host-handoff-unregistered.js +50 -0
- package/dist/modes/rpc/host-handoff.d.ts +7 -2
- package/dist/modes/rpc/host-handoff.js +7 -3
- package/dist/modes/rpc/host-legacy.d.ts +3 -0
- package/dist/modes/rpc/host-legacy.js +10 -5
- package/dist/modes/rpc/host-probe.d.ts +12 -0
- package/dist/modes/rpc/host-probe.js +53 -5
- package/dist/modes/rpc/host-runner.js +1 -1
- package/dist/modes/rpc/host-successor.d.ts +5 -4
- package/dist/modes/rpc/host-successor.js +11 -4
- 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 +49 -7
- package/docs/settings.md +3 -1
- package/package.json +6 -6
- package/dist/bundle/chunks/chunk-K3BXFPWZ.js +0 -2
- package/dist/bundle/chunks/chunk-UUAKEDVS.js +0 -14
- package/dist/bundle/chunks/cli-main-VZWSUHFB.js +0 -3
- package/dist/bundle/chunks/host-command-7JIGGOWR.js +0 -2
- package/dist/bundle/chunks/interactive-mode-JA7AHYYL.js +0 -2
- package/dist/bundle/chunks/rpc-mode-QEBSN2OH.js +0 -2
- package/dist/bundle/chunks/session-control-endpoint-UGQIP6ZN.js +0 -3
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Host capability that lets an eval cell wait on, inspect and control work the host owns (agent runs,
|
|
3
|
+
* workpools). The task owner implements it and registers it; codemode only consumes it. Every operation is
|
|
4
|
+
* fenced by the owning session, the handle id and its run epoch: a ref whose epoch is no longer live is
|
|
5
|
+
* refused with `eval_handle_stale`, never silently redirected to a successor run.
|
|
6
|
+
*/
|
|
7
|
+
export type HandleKind = "agent" | "completion" | "workpool";
|
|
8
|
+
export interface HandleRef {
|
|
9
|
+
readonly kind: HandleKind;
|
|
10
|
+
readonly id: string;
|
|
11
|
+
readonly run_epoch: number;
|
|
12
|
+
}
|
|
13
|
+
export type HandlePhase = "pending" | "succeeded" | "failed" | "cancelled" | "lost";
|
|
14
|
+
export interface HandleSnapshot {
|
|
15
|
+
readonly ref: HandleRef;
|
|
16
|
+
readonly phase: HandlePhase;
|
|
17
|
+
readonly host_status: string;
|
|
18
|
+
/** Strictly increases with every change of this ref; a consumer drops an update whose revision is not newer. */
|
|
19
|
+
readonly revision: number;
|
|
20
|
+
}
|
|
21
|
+
export interface HandleError {
|
|
22
|
+
readonly code: string;
|
|
23
|
+
readonly message: string;
|
|
24
|
+
readonly details?: unknown;
|
|
25
|
+
}
|
|
26
|
+
export type HandleOutcome = {
|
|
27
|
+
readonly status: "fulfilled";
|
|
28
|
+
readonly ref: HandleRef;
|
|
29
|
+
readonly value: unknown;
|
|
30
|
+
} | {
|
|
31
|
+
readonly status: "rejected";
|
|
32
|
+
readonly ref: HandleRef;
|
|
33
|
+
readonly error: HandleError;
|
|
34
|
+
};
|
|
35
|
+
export interface HandleCallContext {
|
|
36
|
+
readonly ownerSessionId: string;
|
|
37
|
+
readonly signal?: AbortSignal;
|
|
38
|
+
}
|
|
39
|
+
export interface HandleWatch {
|
|
40
|
+
/** One snapshot per watched ref, taken atomically with the subscription. */
|
|
41
|
+
readonly initial: readonly HandleSnapshot[];
|
|
42
|
+
/** Every change after `initial`, in order; a change during `watch()` setup arrives here exactly once. */
|
|
43
|
+
readonly updates: AsyncIterable<HandleSnapshot>;
|
|
44
|
+
close(): void;
|
|
45
|
+
}
|
|
46
|
+
export interface CancelReceipt {
|
|
47
|
+
readonly ref: HandleRef;
|
|
48
|
+
/** False when the run had already ended; cancelling twice is not an error. */
|
|
49
|
+
readonly cancelled: boolean;
|
|
50
|
+
readonly phase: HandlePhase;
|
|
51
|
+
}
|
|
52
|
+
export interface OutputRequest {
|
|
53
|
+
readonly format?: "raw" | "tail";
|
|
54
|
+
readonly offset?: number;
|
|
55
|
+
readonly limit?: number;
|
|
56
|
+
}
|
|
57
|
+
export interface OutputSnapshot {
|
|
58
|
+
readonly ref: HandleRef;
|
|
59
|
+
readonly text: string;
|
|
60
|
+
readonly offset: number;
|
|
61
|
+
readonly total: number;
|
|
62
|
+
readonly truncated: boolean;
|
|
63
|
+
}
|
|
64
|
+
export interface EvalHandleHost {
|
|
65
|
+
readonly version: 1;
|
|
66
|
+
watch(refs: readonly HandleRef[], ctx: HandleCallContext): Promise<HandleWatch>;
|
|
67
|
+
/** The terminal outcome; rejects with `eval_handle_pending` while the run is still pending. */
|
|
68
|
+
result(ref: HandleRef, ctx: HandleCallContext): Promise<HandleOutcome>;
|
|
69
|
+
/** Agent refs only; others refuse with `eval_handle_operation_unsupported`. */
|
|
70
|
+
send(ref: HandleRef, message: string, ctx: HandleCallContext): Promise<HandleSnapshot>;
|
|
71
|
+
cancel(ref: HandleRef, ctx: HandleCallContext): Promise<CancelReceipt>;
|
|
72
|
+
output(ref: HandleRef, request: OutputRequest, ctx: HandleCallContext): Promise<OutputSnapshot>;
|
|
73
|
+
}
|
|
74
|
+
export declare const EVAL_HANDLE_ERROR_CODES: readonly ["eval_handle_stale", "eval_handle_forbidden", "eval_handle_not_found", "eval_handle_pending", "eval_handle_operation_unsupported", "eval_wait_unavailable", "eval_wait_timeout", "eval_wait_empty", "eval_pool_open", "eval_workpool_failed"];
|
|
75
|
+
export type EvalHandleErrorCode = (typeof EVAL_HANDLE_ERROR_CODES)[number];
|
|
76
|
+
export declare class EvalHandleError extends Error {
|
|
77
|
+
readonly name = "EvalHandleError";
|
|
78
|
+
readonly code: EvalHandleErrorCode;
|
|
79
|
+
readonly details?: unknown;
|
|
80
|
+
constructor(code: EvalHandleErrorCode, message: string, details?: unknown);
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=eval-handle-host.d.ts.map
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Host capability that lets an eval cell wait on, inspect and control work the host owns (agent runs,
|
|
3
|
+
* workpools). The task owner implements it and registers it; codemode only consumes it. Every operation is
|
|
4
|
+
* fenced by the owning session, the handle id and its run epoch: a ref whose epoch is no longer live is
|
|
5
|
+
* refused with `eval_handle_stale`, never silently redirected to a successor run.
|
|
6
|
+
*/
|
|
7
|
+
export const EVAL_HANDLE_ERROR_CODES = [
|
|
8
|
+
"eval_handle_stale",
|
|
9
|
+
"eval_handle_forbidden",
|
|
10
|
+
"eval_handle_not_found",
|
|
11
|
+
"eval_handle_pending",
|
|
12
|
+
"eval_handle_operation_unsupported",
|
|
13
|
+
"eval_wait_unavailable",
|
|
14
|
+
"eval_wait_timeout",
|
|
15
|
+
"eval_wait_empty",
|
|
16
|
+
"eval_pool_open",
|
|
17
|
+
"eval_workpool_failed",
|
|
18
|
+
];
|
|
19
|
+
export class EvalHandleError extends Error {
|
|
20
|
+
constructor(code, message, details) {
|
|
21
|
+
super(`${code}: ${message}`);
|
|
22
|
+
this.name = "EvalHandleError";
|
|
23
|
+
this.code = code;
|
|
24
|
+
if (details !== undefined)
|
|
25
|
+
this.details = details;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=eval-handle-host.js.map
|
|
@@ -209,6 +209,7 @@ export function createExtensionRuntime() {
|
|
|
209
209
|
for (const unsubscribe of eventBusUnsubscribers)
|
|
210
210
|
unsubscribe();
|
|
211
211
|
eventBusUnsubscribers.clear();
|
|
212
|
+
runtime.evalHandleHost = undefined;
|
|
212
213
|
runtimeFactories.delete(runtime);
|
|
213
214
|
},
|
|
214
215
|
trackEventBusSubscription: (unsubscribe) => {
|
|
@@ -350,6 +351,10 @@ function createExtensionAPI(extension, runtime, cwd, eventBus, session) {
|
|
|
350
351
|
extension.lazyToolActivators = activators;
|
|
351
352
|
runtime.registerLazyToolActivator(activator);
|
|
352
353
|
},
|
|
354
|
+
provideEvalHandleHost(host) {
|
|
355
|
+
runtime.assertActive();
|
|
356
|
+
runtime.evalHandleHost = host;
|
|
357
|
+
},
|
|
353
358
|
registerRemovedToolHint(name, hint) {
|
|
354
359
|
runtime.assertActive();
|
|
355
360
|
let hints = extension.removedToolHints;
|
|
@@ -955,6 +955,10 @@ export class ExtensionRunner {
|
|
|
955
955
|
runner.assertActive();
|
|
956
956
|
return kernelToolsStorage.getStore();
|
|
957
957
|
},
|
|
958
|
+
get evalHandleHost() {
|
|
959
|
+
runner.assertActive();
|
|
960
|
+
return runner.runtime.evalHandleHost;
|
|
961
|
+
},
|
|
958
962
|
abort: (source) => {
|
|
959
963
|
runner.assertActive();
|
|
960
964
|
runner.abortFn(source);
|
|
@@ -34,6 +34,7 @@ import type { BashToolDetails, BashToolInput, EditToolInput, FindToolDetails, Fi
|
|
|
34
34
|
import type { ReadClassifier } from "../tools/read-classifiers.ts";
|
|
35
35
|
import type { ModelRoute, ModelRouteRequest, VirtualModelDefinition } from "../virtual-models.ts";
|
|
36
36
|
import type { McpServerDeclaration } from "./builtin/mcp/config-schema.ts";
|
|
37
|
+
import type { EvalHandleHost } from "./eval-handle-host.ts";
|
|
37
38
|
import type { ExtensionKernelTools } from "./kernel-tools-context.ts";
|
|
38
39
|
import type { SessionControlActions, SessionControlWakeEvent } from "./session-control-types.ts";
|
|
39
40
|
export type { ExecOptions, ExecResult } from "../exec.ts";
|
|
@@ -391,6 +392,12 @@ export interface ExtensionContext {
|
|
|
391
392
|
* JavaScript eval owns the host-tool context; absent on older runtimes.
|
|
392
393
|
*/
|
|
393
394
|
readonly kernelTools?: ExtensionKernelTools;
|
|
395
|
+
/**
|
|
396
|
+
* Session-scoped host capability behind the in-cell `wait()` / `handle()` helpers: watch, send to,
|
|
397
|
+
* cancel and read host-owned work (agent runs, workpools) fenced by owner, id and run epoch.
|
|
398
|
+
* Provided by the task owner through `pi.provideEvalHandleHost`; absent on runtimes without a provider.
|
|
399
|
+
*/
|
|
400
|
+
readonly evalHandleHost?: EvalHandleHost;
|
|
394
401
|
/** Abort the current agent operation */
|
|
395
402
|
abort(source?: "user" | "system"): void;
|
|
396
403
|
/** Whether there are queued messages waiting */
|
|
@@ -1797,6 +1804,12 @@ export interface ExtensionAPI {
|
|
|
1797
1804
|
registerTool<TParams extends TSchema = TSchema, TDetails = unknown, TState = any>(tool: ToolDefinition<TParams, TDetails, TState>): void;
|
|
1798
1805
|
/** Register migration guidance returned when an intentionally removed tool is called. */
|
|
1799
1806
|
registerRemovedToolHint(name: string, hint: string): void;
|
|
1807
|
+
/**
|
|
1808
|
+
* Provide this session's `EvalHandleHost` (the task/workpool owner implements it). Session-scoped:
|
|
1809
|
+
* every extension loaded in the same session reads it back as `ctx.evalHandleHost`; the last
|
|
1810
|
+
* provider wins, and a replaced or reloaded session starts without one.
|
|
1811
|
+
*/
|
|
1812
|
+
provideEvalHandleHost(host: EvalHandleHost): void;
|
|
1800
1813
|
/**
|
|
1801
1814
|
* Register a callback that may activate a registered-but-inactive tool on demand.
|
|
1802
1815
|
* Called only when executeTool would otherwise fail with `inactive_tool`. Return
|
|
@@ -2302,6 +2315,8 @@ export interface ExtensionRuntimeState {
|
|
|
2302
2315
|
registerRemovedToolHint: RegisterRemovedToolHintHandler;
|
|
2303
2316
|
registerVirtualModel: (definition: VirtualModelDefinition, extensionPath?: string) => void;
|
|
2304
2317
|
unregisterVirtualModel: (provider: string, id: string) => void;
|
|
2318
|
+
/** The session's eval handle host, set by `pi.provideEvalHandleHost`; cleared when the runtime is invalidated. */
|
|
2319
|
+
evalHandleHost?: EvalHandleHost;
|
|
2305
2320
|
}
|
|
2306
2321
|
/**
|
|
2307
2322
|
* Action implementations for pi.* API methods.
|
|
@@ -4,6 +4,9 @@ import { isBillingErrorMessage } from "./billing.js";
|
|
|
4
4
|
// Subscription limit prose that carries no quota/billing keyword, e.g. Claude's
|
|
5
5
|
// "You've hit your session limit · resets 3pm" and the SDK terminal reasons.
|
|
6
6
|
const SUBSCRIPTION_LIMIT_PATTERN = /\bhit\s+your\b[^.]*\blimit\b|\b(?:session|weekly|monthly|daily|hourly|\d+[- ]hour|usage)\s+limit\b|\bblocking_limit\b|\brapid_refill_breaker\b/i;
|
|
7
|
+
// A plan cap that tells the user to switch models, e.g. Devin's "Reached free model rate limit. ...
|
|
8
|
+
// switch to a different model" (senpi#2660). An ordinary "rate limit exceeded" stays transient.
|
|
9
|
+
const PLAN_MODEL_LIMIT_PATTERN = /\breached\b[^.]*\b(?:free|plan|tier|model)\b[^.]*\blimit\b[\s\S]*\bswitch to (?:a |an )?(?:different|another) model\b/i;
|
|
7
10
|
const MODEL_SCOPE_PATTERN = /\bmodels?\b|\bpremium\b|\b(?:opus|sonnet|haiku|fable|mythos)\b/i;
|
|
8
11
|
export function usageLimitScope(errorMessage) {
|
|
9
12
|
if (errorMessage === undefined)
|
|
@@ -14,7 +17,8 @@ export function usageLimitScope(errorMessage) {
|
|
|
14
17
|
return "model";
|
|
15
18
|
const limited = isQuotaExhaustionMessage(errorMessage) ||
|
|
16
19
|
isBillingErrorMessage(errorMessage) ||
|
|
17
|
-
SUBSCRIPTION_LIMIT_PATTERN.test(errorMessage)
|
|
20
|
+
SUBSCRIPTION_LIMIT_PATTERN.test(errorMessage) ||
|
|
21
|
+
PLAN_MODEL_LIMIT_PATTERN.test(errorMessage);
|
|
18
22
|
if (!limited)
|
|
19
23
|
return undefined;
|
|
20
24
|
return MODEL_SCOPE_PATTERN.test(errorMessage) ? "model" : "account";
|
|
@@ -9,5 +9,9 @@ export type RenderToolDiffOptions = {
|
|
|
9
9
|
filePath?: string;
|
|
10
10
|
theme: ToolDiffTheme;
|
|
11
11
|
};
|
|
12
|
+
export declare function countDiffChanges(diffText: string): {
|
|
13
|
+
added: number;
|
|
14
|
+
removed: number;
|
|
15
|
+
};
|
|
12
16
|
export declare function renderToolDiff(diffText: string, options: RenderToolDiffOptions): string;
|
|
13
17
|
//# sourceMappingURL=diff-render.d.ts.map
|
|
@@ -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/index.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ export { type OAuthCredential, readStoredCredential } from "./core/auth-storage.
|
|
|
7
7
|
export { type BranchPreparation, type BranchSummaryResult, type CacheFriendlySummaryOptions, type CollectEntriesResult, type CompactionResult, type CutPointResult, calculateContextTokens, collectEntriesForBranchSummary, compact, DEFAULT_COMPACTION_SETTINGS, estimateTokens, type FileOperations, findCutPoint, findTurnStartIndex, type GenerateBranchSummaryOptions, generateBranchSummary, generateSummary, generateSummaryWithUsage, getLastAssistantUsage, prepareBranchEntries, serializeConversation, shouldCompact, } from "./core/compaction/index.ts";
|
|
8
8
|
export { compareEngineOrdinal, type EngineBuildIdentity, type EngineBuildInput, type EngineOrdinal, type EngineOrdinalScheme, engineBuildIdentity, engineBuildIdentityFrom, } from "./core/engine-build-identity.ts";
|
|
9
9
|
export { createEventBus, type EventBus, type EventBusController } from "./core/event-bus.ts";
|
|
10
|
+
export { type CancelReceipt, EVAL_HANDLE_ERROR_CODES, EvalHandleError, type EvalHandleErrorCode, type EvalHandleHost, type HandleCallContext, type HandleError, type HandleKind, type HandleOutcome, type HandlePhase, type HandleRef, type HandleSnapshot, type HandleWatch, type OutputRequest, type OutputSnapshot, } from "./core/extensions/eval-handle-host.ts";
|
|
10
11
|
export type { AfterProviderResponseEvent, AgentActivityOutcome, AgentBeforeSettleEvent, AgentBeforeSettleEventResult, AgentEndEvent, AgentSettledEvent, AgentStartEvent, AgentToolResult, AgentToolUpdateCallback, AppKeybinding, AutocompleteProviderFactory, BashToolCallEvent, BeforeAgentStartEvent, BeforeAgentStartEventResult, BeforeProviderHeadersEvent, BeforeProviderRequestEvent, BeforeProviderRequestEventResult, BoundaryContextPreview, BoundaryResult, BoundaryState, BuildSystemPromptOptions, CompactionEntryDraft, CompactOptions, ContextEditEntryDraft, ContextEvent, ContextEventResult, ContextUsage, ContextWithSystemEvent, CustomEntryDraft, CustomMessageEntryDraft, CustomToolCallEvent, EditToolCallEvent, EntryRenderer, EntryRendererOptions, EntryRenderOptions, ExecOptions, ExecResult, ExecuteToolOptions, Extension, ExtensionActions, ExtensionAPI, ExtensionCommandContext, ExtensionCommandContextActions, ExtensionContext, ExtensionContextActions, ExtensionError, ExtensionEvent, ExtensionFactory, ExtensionFlag, ExtensionHandler, ExtensionRpcRequestHandler, ExtensionRuntime, ExtensionShortcut, ExtensionToolContext, ExtensionUIContext, ExtensionUIDialogOptions, ExtensionVirtualModel, ExtensionWidgetOptions, FilesystemOperation, FilesystemPolicy, FilesystemPolicyChecker, FilesystemPolicyDecision, FilesystemPolicyRequest, FindToolCallEvent, GrepToolCallEvent, InlineExtension, InputDispositionEvent, InputEvent, InputEventResult, InputSource, KernelPreludeContribution, KeybindingsManager, LoadExtensionsResult, LsToolCallEvent, MarkdownTransformContext, MarkdownTransformer, McpServerDeclaration, MessageEndEvent, MessageEndEventResult, MessageRenderer, MessageRenderOptions, MessageStartEvent, MessageUpdateEvent, ModelSelectEvent, ModelSelectSource, NormalizedBuildSystemPromptOptions, PowerShellToolCallEvent, ProjectTrustContext, ProjectTrustEvent, ProjectTrustEventDecision, ProjectTrustEventResult, ProjectTrustHandler, ProviderConfig, ProviderModelConfig, ProviderStreamEvent, ReadToolCallEvent, RegisteredCommand, RegisteredTool, ResolvedCommand, ResourcesDiscoverEvent, ResourcesDiscoverResult, SessionBeforeCompactEvent, SessionBeforeCompactResult, SessionBeforeForkEvent, SessionBeforeForkResult, SessionBeforeSwitchEvent, SessionBeforeSwitchResult, SessionBeforeTreeEvent, SessionBeforeTreeResult, SessionBoundaryDraft, SessionCompactEvent, SessionCompactFailedEvent, SessionInfoChangedEvent, SessionParkedEvent, SessionResumedEvent, SessionShutdownEvent, SessionStartEvent, SessionTreeEvent, SlashCommandInfo, SlashCommandSource, SourceInfo, TerminalInputHandler, ThinkingLevelSelectEvent, ToolAnnotations, ToolCallEvent, ToolCallEventResult, ToolDefinition, ToolExecutionEndEvent, ToolExecutionMode, ToolExecutionStartEvent, ToolExecutionUpdateEvent, ToolExposure, ToolInfo, ToolLoadout, ToolLoadoutChanges, ToolNamespace, ToolPermissionRequest, ToolRenderResultOptions, ToolResultEvent, ToolResultEventResult, TurnEndEvent, TurnEndEventResult, TurnStartEvent, UIPromptEndEvent, UIPromptKind, UIPromptStartEvent, UserBashEvent, UserBashEventResult, WidgetPlacement, WorkingIndicatorOptions, WriteToolCallEvent, } from "./core/extensions/index.ts";
|
|
11
12
|
export { createExtensionRuntime, defineTool, discoverAndLoadExtensions, ExtensionRunner, isBashToolResult, isEditToolResult, isFindToolResult, isGrepToolResult, isLsToolResult, isPowerShellToolResult, isReadToolResult, isToolCallEventType, isWriteToolResult, wrapRegisteredTool, wrapRegisteredTools, } from "./core/extensions/index.ts";
|
|
12
13
|
export { type ExtensionKernelTools, type KernelToolInvokeOptions, type KernelToolInvokeScope, kernelToolsStorage, } from "./core/extensions/kernel-tools-context.ts";
|
package/dist/index.js
CHANGED
|
@@ -12,6 +12,7 @@ export { calculateContextTokens, collectEntriesForBranchSummary, compact, DEFAUL
|
|
|
12
12
|
// Engine build identity: the ordinal clients compare instead of version strings
|
|
13
13
|
export { compareEngineOrdinal, engineBuildIdentity, engineBuildIdentityFrom, } from "./core/engine-build-identity.js";
|
|
14
14
|
export { createEventBus } from "./core/event-bus.js";
|
|
15
|
+
export { EVAL_HANDLE_ERROR_CODES, EvalHandleError, } from "./core/extensions/eval-handle-host.js";
|
|
15
16
|
export { createExtensionRuntime, defineTool, discoverAndLoadExtensions, ExtensionRunner, isBashToolResult, isEditToolResult, isFindToolResult, isGrepToolResult, isLsToolResult, isPowerShellToolResult, isReadToolResult, isToolCallEventType, isWriteToolResult, wrapRegisteredTool, wrapRegisteredTools, } from "./core/extensions/index.js";
|
|
16
17
|
export { kernelToolsStorage, } from "./core/extensions/kernel-tools-context.js";
|
|
17
18
|
// Notice primitives
|
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
|
}
|
|
@@ -11,6 +11,7 @@ import { createHash } from "node:crypto";
|
|
|
11
11
|
import { rmSync } from "node:fs";
|
|
12
12
|
import { chmod, lstat, mkdir, realpath, rm } from "node:fs/promises";
|
|
13
13
|
import { dirname, join } from "node:path";
|
|
14
|
+
import { engineBuildIdentity } from "../../core/engine-build-identity.js";
|
|
14
15
|
import { createDaemonDirectories, createHostDaemonPaths } from "../rpc/host-daemon-paths.js";
|
|
15
16
|
import { thisProcessStartTime, writeHostRegistration } from "../rpc/host-daemon-registration.js";
|
|
16
17
|
import { acquireHostEnsureLock } from "../rpc/host-ensure-lock.js";
|
|
@@ -64,6 +65,7 @@ export async function registerTuiEndpoint(options) {
|
|
|
64
65
|
instanceId: options.instanceId,
|
|
65
66
|
generation: 0,
|
|
66
67
|
launchProfileId: "tui",
|
|
68
|
+
build: engineBuildIdentity(),
|
|
67
69
|
});
|
|
68
70
|
await createDaemonDirectories(paths, { kind: "tui" });
|
|
69
71
|
}
|