pi-crew 0.11.6 → 0.11.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 +95 -0
- package/dist/index.mjs +486 -481
- package/package.json +2 -2
- package/src/config/config-validation.ts +28 -0
- package/src/config/env-vars.ts +23 -0
- package/src/config/types.ts +47 -0
- package/src/prompt/prompt-runtime.ts +33 -4
- package/src/runtime/child-pi/child-pi-spawn.ts +7 -0
- package/src/runtime/child-pi/child-pi.ts +93 -4
- package/src/runtime/child-pi/session-recovery.ts +334 -0
- package/src/runtime/model/model-scope.ts +141 -21
- package/src/runtime/model/pi-args.ts +104 -3
- package/src/runtime/peer-dep.ts +24 -0
- package/src/runtime/rpc/README.md +100 -0
- package/src/runtime/rpc/frame-client.ts +328 -0
- package/src/runtime/rpc/rpc-worker.ts +151 -0
- package/src/runtime/rpc/ui-request-policy.ts +112 -0
- package/src/runtime/run-worker.ts +14 -0
- package/src/runtime/surface/surface-spawn.ts +8 -1
- package/src/runtime/task-output-context.ts +191 -32
- package/src/runtime/task-packet.ts +3 -0
- package/src/runtime/task-runner/child-executor.ts +40 -2
- package/src/runtime/task-runner/post-execution.ts +5 -3
- package/src/runtime/task-runner/pre-execution.ts +24 -1
- package/src/runtime/task-runner/prompt-builder.ts +10 -2
- package/src/schema/config-schema.ts +25 -0
- package/src/schema/team-tool-schema.ts +10 -0
- package/src/ui/pi-ui-compat.ts +18 -9
- package/src/ui/widget/index.ts +0 -1
|
@@ -38,6 +38,7 @@ import { type FatalFsCause, failureCauseForAttempt } from "../../utils/fs-errno.
|
|
|
38
38
|
import { logInternalError } from "../../utils/internal-error.ts";
|
|
39
39
|
import { resolveRealContainedPath } from "../../utils/safe-paths.ts";
|
|
40
40
|
import type { ChildPiLifecycleEvent, ChildPiRunResult } from "../child-pi/child-pi.ts";
|
|
41
|
+
import type { SessionRecoveryInfo } from "../child-pi/session-recovery.ts";
|
|
41
42
|
import {
|
|
42
43
|
appendCrewAgentEventBuffered,
|
|
43
44
|
appendCrewAgentOutputBuffered,
|
|
@@ -398,7 +399,11 @@ export async function runChildProcessTask(ctx: TaskExecutionContext): Promise<Ta
|
|
|
398
399
|
const manifest: TeamRunManifest = ctx.manifest;
|
|
399
400
|
let task = ctx.task;
|
|
400
401
|
let tasks = ctx.tasks;
|
|
401
|
-
|
|
402
|
+
// R3-1: the child-pi worker's USER message is the dynamic suffix — the
|
|
403
|
+
// run-static header rides --append-system-prompt (compaction-safe).
|
|
404
|
+
// Fallback to the full prompt keeps hand-built contexts (tests, custom
|
|
405
|
+
// callers that skip pre-execution) on the old single-channel behavior.
|
|
406
|
+
const userPrompt = ctx.userPrompt ?? ctx.prompt;
|
|
402
407
|
const skillPaths = ctx.skillPaths;
|
|
403
408
|
const collectedJsonEvents = ctx.collectedJsonEvents;
|
|
404
409
|
const streamBridge: StreamBridgeHandle | undefined = ctx.streamBridge;
|
|
@@ -409,6 +414,9 @@ export async function runChildProcessTask(ctx: TaskExecutionContext): Promise<Ta
|
|
|
409
414
|
let modelAttempts: ModelAttemptSummary[] | undefined;
|
|
410
415
|
let parsedOutput: ParsedPiJsonOutput | undefined;
|
|
411
416
|
let rawFinalText: string | undefined;
|
|
417
|
+
// W2 (P1-1): last attempt's session-recovery provenance (thread-level,
|
|
418
|
+
// mirrors rawFinalText — escapes the attempt loop's block scope).
|
|
419
|
+
let recoveredFromSession: SessionRecoveryInfo | undefined;
|
|
412
420
|
let intermediateFindings: string | undefined;
|
|
413
421
|
let finalStdout = "";
|
|
414
422
|
let transcriptPath: string | undefined;
|
|
@@ -652,7 +660,12 @@ export async function runChildProcessTask(ctx: TaskExecutionContext): Promise<Ta
|
|
|
652
660
|
try {
|
|
653
661
|
childResult = await runWorker({
|
|
654
662
|
cwd: task.cwd,
|
|
655
|
-
task:
|
|
663
|
+
task: userPrompt,
|
|
664
|
+
systemPromptAppend: ctx.systemPromptAppend,
|
|
665
|
+
// R3-19/D5: thread the runtime.hermeticWorkers config gate to the
|
|
666
|
+
// worker argv (--no-extensions). Env PI_CREW_HERMETIC_WORKERS still
|
|
667
|
+
// overrides either way (resolveHermeticWorkers).
|
|
668
|
+
hermeticWorkers: input.runtimeConfig?.hermeticWorkers,
|
|
656
669
|
agent: input.agent,
|
|
657
670
|
model,
|
|
658
671
|
signal: timeoutController.signal,
|
|
@@ -665,6 +678,10 @@ export async function runChildProcessTask(ctx: TaskExecutionContext): Promise<Ta
|
|
|
665
678
|
parentContext: input.parentContext,
|
|
666
679
|
excludeContextBash: input.runtimeConfig?.excludeContextBash,
|
|
667
680
|
sessionId: manifest.sessionId,
|
|
681
|
+
// W2 (P1-1): thread the runtime.sessionRecovery flag so the config
|
|
682
|
+
// gate reaches the crash-path tail-replay (env/default still work
|
|
683
|
+
// without this — resolveSessionRecoveryEnabled precedence).
|
|
684
|
+
sessionRecovery: input.runtimeConfig?.sessionRecovery,
|
|
668
685
|
role: task.role,
|
|
669
686
|
thinkingOverride: input.teamRoleThinking,
|
|
670
687
|
runId: manifest.runId,
|
|
@@ -997,6 +1014,9 @@ export async function runChildProcessTask(ctx: TaskExecutionContext): Promise<Ta
|
|
|
997
1014
|
const transcriptText = tailReadWithLineSnap(transcriptPath, MAX_TRANSCRIPT_PARSE_BYTES, childResult.stdout);
|
|
998
1015
|
parsedOutput = parsePiJsonOutput(transcriptText);
|
|
999
1016
|
rawFinalText = childResult.rawFinalText;
|
|
1017
|
+
// W2 (P1-1): keep the most recent recovery info (crash-path only —
|
|
1018
|
+
// undefined on clean exits, so this stays dormant in the happy path).
|
|
1019
|
+
recoveredFromSession = childResult.recoveredFromSession ?? recoveredFromSession;
|
|
1000
1020
|
intermediateFindings = childResult.intermediateFindings;
|
|
1001
1021
|
error = attemptErrorFor(childResult, parsedOutput, task.id);
|
|
1002
1022
|
// bug-026 sub-issue B: classify fatal fs errnos (ENOSPC/EDQUOT/EMFILE/
|
|
@@ -1121,6 +1141,12 @@ export async function runChildProcessTask(ctx: TaskExecutionContext): Promise<Ta
|
|
|
1121
1141
|
const resultCandidates: ReadonlyArray<{ source: ResultSource; text: string | undefined }> = [
|
|
1122
1142
|
{ source: "rawFinalText", text: cleanResultText(rawFinalText) },
|
|
1123
1143
|
{ source: "finalText", text: cleanResultText(parsedOutput?.finalText) },
|
|
1144
|
+
// W2 (P1-1): crash-path session tail-recovery. Ranks BELOW live captures
|
|
1145
|
+
// (raw/finalText — the stream is authoritative when it survived) but
|
|
1146
|
+
// ABOVE stdout/stderr noise: on signal death (exitCode null) the live
|
|
1147
|
+
// captures are empty and this is the recovered last complete assistant
|
|
1148
|
+
// message from the worker's session JSONL.
|
|
1149
|
+
{ source: "session", text: cleanResultText(recoveredFromSession?.text) },
|
|
1124
1150
|
{ source: "stdout", text: cleanResultText(finalStdout) },
|
|
1125
1151
|
{ source: "stderr", text: cleanResultText(finalStderr) },
|
|
1126
1152
|
{ source: "findings", text: cleanResultText(intermediateFindings) },
|
|
@@ -1142,6 +1168,18 @@ export async function runChildProcessTask(ctx: TaskExecutionContext): Promise<Ta
|
|
|
1142
1168
|
"(no output)",
|
|
1143
1169
|
producer: task.id,
|
|
1144
1170
|
});
|
|
1171
|
+
// W2 (P1-1): serialize the recovery provenance as a result sidecar — the
|
|
1172
|
+
// recovered text itself flows through the "session" candidate above; this
|
|
1173
|
+
// records WHERE it came from (session id/file, timestamp, stopReason) so
|
|
1174
|
+
// crash-path recovery is auditable after the fact.
|
|
1175
|
+
if (recoveredFromSession) {
|
|
1176
|
+
writeArtifact(manifest.artifactsRoot, {
|
|
1177
|
+
kind: "result",
|
|
1178
|
+
relativePath: `results/${task.id}.session-recovery.json`,
|
|
1179
|
+
content: JSON.stringify(recoveredFromSession, null, "\t"),
|
|
1180
|
+
producer: task.id,
|
|
1181
|
+
});
|
|
1182
|
+
}
|
|
1145
1183
|
const logArtifact = writeArtifact(manifest.artifactsRoot, {
|
|
1146
1184
|
kind: "log",
|
|
1147
1185
|
relativePath: `logs/${task.id}.log`,
|
|
@@ -86,16 +86,18 @@ export interface TaskExecutionResult {
|
|
|
86
86
|
terminalEvidence: OperationTerminalEvidence[];
|
|
87
87
|
startupEvidence: import("../heartbeat/worker-startup.ts").WorkerStartupEvidence;
|
|
88
88
|
/** Which child-executor fallback branch produced the result artifact
|
|
89
|
-
* content (rawFinalText → finalText → stdout → stderr → findings →
|
|
89
|
+
* content (rawFinalText → finalText → session → stdout → stderr → findings →
|
|
90
90
|
* "(no output)"). Finding #2 (2026-09-29 battery): "stderr"/"none" mean
|
|
91
91
|
* every authoritative worker output source was empty — finalizeTaskResult
|
|
92
92
|
* fails those DETERMINISTICALLY; the stderr-noise classifier stays a
|
|
93
|
-
* defense layer, never the verdict oracle.
|
|
93
|
+
* defense layer, never the verdict oracle. "session" (W2/P1-1) = the text
|
|
94
|
+
* was tail-recovered from the worker's session JSONL on a signal-death
|
|
95
|
+
* crash path — authoritative recovered output, not a failure source. */
|
|
94
96
|
resultSource?: ResultSource;
|
|
95
97
|
}
|
|
96
98
|
|
|
97
99
|
/** Result-artifact provenance tags (see {@link TaskExecutionResult.resultSource}). */
|
|
98
|
-
export type ResultSource = "rawFinalText" | "finalText" | "stdout" | "stderr" | "findings" | "none";
|
|
100
|
+
export type ResultSource = "rawFinalText" | "finalText" | "session" | "stdout" | "stderr" | "findings" | "none";
|
|
99
101
|
|
|
100
102
|
/** Final persisted task state returned by finalizeTaskResult. */
|
|
101
103
|
export interface FinalTaskResult {
|
|
@@ -69,6 +69,17 @@ export interface TaskExecutionContext {
|
|
|
69
69
|
skillNames: string[] | undefined;
|
|
70
70
|
skillPaths: string[] | undefined;
|
|
71
71
|
prompt: string;
|
|
72
|
+
/** R3-1: the USER-message half of the rendered prompt (dynamicSuffix) —
|
|
73
|
+
* what the worker's task.md actually carries when the run-static header
|
|
74
|
+
* moves to the --append-system-prompt channel. `prompt` stays the FULL
|
|
75
|
+
* rendered prompt (stablePrefix + dynamicSuffix) for artifacts and the
|
|
76
|
+
* live-session path. Optional so hand-built contexts (tests, custom
|
|
77
|
+
* callers) keep the old single-channel behavior via the ?? fallback. */
|
|
78
|
+
userPrompt?: string;
|
|
79
|
+
/** R3-1: run-static worker header (stablePrefix) for the append-system-prompt
|
|
80
|
+
* channel — threaded to buildPiWorkerArgs via ChildPiRunInput. Undefined on
|
|
81
|
+
* hand-built contexts → single-channel legacy behavior. */
|
|
82
|
+
systemPromptAppend?: string;
|
|
72
83
|
promptArtifact: ArtifactDescriptor;
|
|
73
84
|
/** G18: prompt-breakdown artifact descriptor (undefined when PI_CREW_PROMPT_BREAKDOWN off). */
|
|
74
85
|
breakdownArtifact?: ArtifactDescriptor;
|
|
@@ -141,7 +152,14 @@ export async function prepareTaskExecutionContext(
|
|
|
141
152
|
// caller provided one) into the dep-context collection — cache hits reuse
|
|
142
153
|
// the closeout's reads byte-identically; undefined keeps uncached behavior.
|
|
143
154
|
const dependencyContext = collectDependencyOutputContext(manifest, input.tasks, input.task, input.step, input.resultReadCache);
|
|
144
|
-
|
|
155
|
+
// Handoff budget: caps the dynamic.dependencyContext layer at an est-token
|
|
156
|
+
// (chars/4) budget (env PI_CREW_HANDOFF_BUDGET_TOKENS > runtime config >
|
|
157
|
+
// default 1800). IMPORTANT: only the RENDER path is budgeted — an explicit
|
|
158
|
+
// input.dependencyContextText override (leader-provided text) passes through
|
|
159
|
+
// UNCHANGED.
|
|
160
|
+
const dependencyContextText =
|
|
161
|
+
input.dependencyContextText ??
|
|
162
|
+
renderDependencyOutputContext(dependencyContext, { budgetTokens: input.runtimeConfig?.handoffBudgetTokens });
|
|
145
163
|
let task: TeamTaskState = {
|
|
146
164
|
...input.task,
|
|
147
165
|
cwd: workspace.cwd,
|
|
@@ -435,6 +453,11 @@ export async function prepareTaskExecutionContext(
|
|
|
435
453
|
collectedJsonEvents,
|
|
436
454
|
streamBridge,
|
|
437
455
|
startupEvidence,
|
|
456
|
+
// R3-1: channel split for child-pi workers — user message = dynamic
|
|
457
|
+
// suffix; run-static header = system-prompt append channel (compaction
|
|
458
|
+
// summarizes the user-message span, compaction.md:150-160).
|
|
459
|
+
userPrompt: promptResult.dynamicSuffix,
|
|
460
|
+
systemPromptAppend: promptResult.stablePrefix,
|
|
438
461
|
},
|
|
439
462
|
};
|
|
440
463
|
}
|
|
@@ -275,11 +275,14 @@ export interface RenderedTaskPrompt {
|
|
|
275
275
|
/** Full rendered prompt (stablePrefix + dynamicSuffix). */
|
|
276
276
|
full: string;
|
|
277
277
|
/**
|
|
278
|
-
* SR-02 phase 1 (2026-09-23): per-section char counts of the
|
|
278
|
+
* SR-02 phase 1 (2026-09-23): per-section char counts of the worker prompt,
|
|
279
279
|
* keyed by section name — the token-breakdown instrumentation. Populated
|
|
280
280
|
* only when PI_CREW_PROMPT_BREAKDOWN=1 (off by default; zero cost when off:
|
|
281
281
|
* the sections object is built lazily). Pre-execution adds the SYSTEM-side
|
|
282
282
|
* pieces (agent definition, skills) and writes the JSON artifact.
|
|
283
|
+
* R3-1 note: stable.* sections ride the --append-system-prompt channel;
|
|
284
|
+
* total.userPrompt counts the user message (dynamic suffix) only, while
|
|
285
|
+
* total.systemAppend counts the system-channel header.
|
|
283
286
|
*/
|
|
284
287
|
sections?: Record<string, number>;
|
|
285
288
|
}
|
|
@@ -424,6 +427,8 @@ export async function renderTaskPrompt(
|
|
|
424
427
|
const full = [stablePrefix, "", dynamicSuffix].join("\n");
|
|
425
428
|
const sections: Record<string, number> | undefined = promptBreakdownEnabled()
|
|
426
429
|
? {
|
|
430
|
+
// R3-1: the stable.* sections now ride the --append-system-prompt
|
|
431
|
+
// channel; the worker's user message is the dynamic suffix only.
|
|
427
432
|
"stable.runtimeHeader": headerBlock.length,
|
|
428
433
|
"stable.protocol": protocolBlock.length,
|
|
429
434
|
"stable.roleInstructions": roleInstructions.length,
|
|
@@ -440,7 +445,10 @@ export async function renderTaskPrompt(
|
|
|
440
445
|
"dynamic.memory": memoryBlock.length,
|
|
441
446
|
"dynamic.outputSchema": outputSchemaBlock.length,
|
|
442
447
|
"dynamic.taskAndHandoff": taskAndHandoff.length,
|
|
443
|
-
|
|
448
|
+
// R3-1: channel split — the user message carries ONLY the dynamic
|
|
449
|
+
// suffix; the stable prefix went to the system-prompt append channel.
|
|
450
|
+
"total.systemAppend": stablePrefix.length,
|
|
451
|
+
"total.userPrompt": dynamicSuffix.length,
|
|
444
452
|
}
|
|
445
453
|
: undefined;
|
|
446
454
|
return { stablePrefix, dynamicSuffix, full, sections };
|
|
@@ -75,6 +75,18 @@ export const PiTeamsRuntimeConfigSchema = Type.Object(
|
|
|
75
75
|
graceTurns: Type.Optional(Type.Integer({ minimum: 1 })),
|
|
76
76
|
taskTimeoutMs: Type.Optional(Type.Integer({ minimum: 1 })),
|
|
77
77
|
inheritContext: Type.Optional(Type.Boolean({ sensitive: true })),
|
|
78
|
+
// W2 (session-file recovery): when a worker dies without a final assistant
|
|
79
|
+
// event (exitCode null / killed), tail-replay its session JSONL and attach
|
|
80
|
+
// the last complete assistant record to the run result. Default ON; env
|
|
81
|
+
// PI_CREW_SESSION_RECOVERY overrides. Kill switch, not a secret — no
|
|
82
|
+
// sensitive mark (same policy as surface: picks behavior, not authority).
|
|
83
|
+
sessionRecovery: Type.Optional(Type.Boolean()),
|
|
84
|
+
// R3-19/D5 reversal: hermetic worker spawns (--no-extensions on worker
|
|
85
|
+
// argv; explicit -e prompt-runtime + --skill flags unaffected). Default
|
|
86
|
+
// TRUE — off-switch for operators who want the old ambient-stack parity.
|
|
87
|
+
// Env PI_CREW_HERMETIC_WORKERS overrides either way. Behavior pick, not
|
|
88
|
+
// authority — no sensitive mark.
|
|
89
|
+
hermeticWorkers: Type.Optional(Type.Boolean()),
|
|
78
90
|
promptMode: Type.Optional(Type.Union([Type.Literal("replace"), Type.Literal("append")])),
|
|
79
91
|
groupJoin: Type.Optional(Type.Union([Type.Literal("off"), Type.Literal("group"), Type.Literal("smart")])),
|
|
80
92
|
groupJoinAckTimeoutMs: Type.Optional(Type.Integer({ minimum: 1 })),
|
|
@@ -94,6 +106,10 @@ export const PiTeamsRuntimeConfigSchema = Type.Object(
|
|
|
94
106
|
),
|
|
95
107
|
),
|
|
96
108
|
excludeContextBash: Type.Optional(Type.Boolean()),
|
|
109
|
+
// Handoff budget: est-token (chars/4) cap on the dynamic.dependencyContext
|
|
110
|
+
// prompt layer. Matches parseHandoffBudgetTokens in config-validation.ts
|
|
111
|
+
// (out-of-range values are dropped → render default 1800).
|
|
112
|
+
handoffBudgetTokens: Type.Optional(Type.Integer({ minimum: 1, maximum: 1_000_000 })),
|
|
97
113
|
agentExtensions: Type.Optional(Type.Array(Type.String({ minLength: 1 }), { sensitive: true })),
|
|
98
114
|
// Mux-surface policy (spec v0.7 §8.2.4): no sensitive marks — surface
|
|
99
115
|
// picks where worker processes live, not what they may do.
|
|
@@ -118,6 +134,15 @@ export const PiTeamsRuntimeConfigSchema = Type.Object(
|
|
|
118
134
|
),
|
|
119
135
|
),
|
|
120
136
|
modelFallback: Type.Optional(PiTeamsModelFallbackConfigSchema),
|
|
137
|
+
// W7 (P2-3) EXPERIMENTAL: worker transport selector. Absent = "stdio"
|
|
138
|
+
// (production child-pi path, behavior unchanged). "rpc" selects the
|
|
139
|
+
// prototype RPC frame-client transport (src/runtime/rpc/) — currently
|
|
140
|
+
// the run-worker seam returns a structured not-implemented result
|
|
141
|
+
// instead of spawning. Live gate is env PI_CREW_WORKER_TRANSPORT;
|
|
142
|
+
// config→seam plumbing is the integration-phase follow-up (needs a
|
|
143
|
+
// parseRuntimeConfig line in config-validation.ts). Not sensitive —
|
|
144
|
+
// picks a transport, not authority.
|
|
145
|
+
workerTransport: Type.Optional(Type.Union([Type.Literal("stdio"), Type.Literal("rpc")])),
|
|
121
146
|
},
|
|
122
147
|
{ additionalProperties: false },
|
|
123
148
|
);
|
|
@@ -280,6 +280,14 @@ const sharedFields = {
|
|
|
280
280
|
description: "Mark certain bash commands as excludeFromContext to reduce context tokens (default: false).",
|
|
281
281
|
}),
|
|
282
282
|
),
|
|
283
|
+
handoffBudgetTokens: Type.Optional(
|
|
284
|
+
Type.Integer({
|
|
285
|
+
minimum: 1,
|
|
286
|
+
maximum: 1_000_000,
|
|
287
|
+
description:
|
|
288
|
+
"Est-token (chars/4) budget cap on the dependency-context layer of worker prompts (default 1800). When exceeded, later dependencies are trimmed to summary head + artifact pointer. Out-of-range values are ignored.",
|
|
289
|
+
}),
|
|
290
|
+
),
|
|
283
291
|
budgetTotal: Type.Optional(
|
|
284
292
|
// Empty-string unset marker accepted (Tier-9: models emit "" when unset).
|
|
285
293
|
// 0 accepted as "unset/disabled" (models emit 0 for off); still rejects 1-999
|
|
@@ -596,6 +604,8 @@ export interface TeamToolParamsValue {
|
|
|
596
604
|
b?: number;
|
|
597
605
|
/** Mark certain bash commands as excludeFromContext to reduce context tokens (default: false). */
|
|
598
606
|
excludeContextBash?: boolean;
|
|
607
|
+
/** Est-token (chars/4) budget cap on the dependency-context layer of worker prompts (default 1800). Later dependencies get trimmed to summary head + artifact pointer when exceeded. */
|
|
608
|
+
handoffBudgetTokens?: number;
|
|
599
609
|
/** Total token budget for the run. When set, enables budget tracking (minimum 1000). */
|
|
600
610
|
budgetTotal?: number;
|
|
601
611
|
/** When true, skip budget enforcement entirely (explicit opt-out). */
|
package/src/ui/pi-ui-compat.ts
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Host-compatibility shims for the `ExtensionContext["ui"]` surface.
|
|
5
|
+
*
|
|
6
|
+
* RETENTION POLICY (W4 2026-10-03): these typeof-based capability guards are
|
|
7
|
+
* deliberate no-ops, NOT version guards, and must be kept. pi-crew declares
|
|
8
|
+
* `"@earendil-works/pi-coding-agent": "*"` and `"@earendil-works/pi-tui": "*"`
|
|
9
|
+
* as optional peerDependencies with no minimum-version floor
|
|
10
|
+
* (package.json peerDependencies/peerDependenciesMeta), so host installs older
|
|
11
|
+
* than any SDK floor we verify against must keep working — retiring these
|
|
12
|
+
* shims would crash hosts whose UI predates
|
|
13
|
+
* `setWidget`/`setFooter`/`custom`/`setStatus`/`setWorkingIndicator`.
|
|
14
|
+
* Verified live: each guard is a pure feature check; there is no version
|
|
15
|
+
* constant or gate in this file. New host UI APIs should be added here
|
|
16
|
+
* (feature-detected), not called directly from src/.
|
|
17
|
+
*/
|
|
18
|
+
|
|
3
19
|
export interface WorkingIndicatorOptions {
|
|
4
20
|
frames?: string[];
|
|
5
21
|
intervalMs?: number;
|
|
@@ -9,7 +25,6 @@ type UiContext = Pick<ExtensionContext, "ui">;
|
|
|
9
25
|
type ExtensionUi = ExtensionContext["ui"];
|
|
10
26
|
type WidgetContent = string[] | ((tui: unknown, theme: unknown) => unknown);
|
|
11
27
|
type WidgetOptions = Parameters<ExtensionUi["setWidget"]>[2];
|
|
12
|
-
type WidgetOptionsWithPersist = WidgetOptions & { persist?: boolean };
|
|
13
28
|
|
|
14
29
|
type CustomOptions = Parameters<ExtensionUi["custom"]>[1];
|
|
15
30
|
|
|
@@ -38,16 +53,10 @@ export function setWorkingIndicator(ctx: UiContext, options?: WorkingIndicatorOp
|
|
|
38
53
|
|
|
39
54
|
/** Install or update an extension widget slot.
|
|
40
55
|
* No-op when the host UI predates the `setWidget` API. */
|
|
41
|
-
export function setExtensionWidget(
|
|
42
|
-
ctx: UiContext,
|
|
43
|
-
key: string,
|
|
44
|
-
content: WidgetContent | undefined,
|
|
45
|
-
options?: WidgetOptionsWithPersist,
|
|
46
|
-
): void {
|
|
47
|
-
const { persist: _persist, ...widgetOptions } = options ?? {};
|
|
56
|
+
export function setExtensionWidget(ctx: UiContext, key: string, content: WidgetContent | undefined, options?: WidgetOptions): void {
|
|
48
57
|
const record = maybeRecord(ctx.ui);
|
|
49
58
|
const fn = record?.setWidget;
|
|
50
|
-
if (typeof fn === "function") fn.call(ctx.ui, key, content as never,
|
|
59
|
+
if (typeof fn === "function") fn.call(ctx.ui, key, content as never, options);
|
|
51
60
|
}
|
|
52
61
|
|
|
53
62
|
/**
|
package/src/ui/widget/index.ts
CHANGED
|
@@ -522,7 +522,6 @@ export function updateCrewWidget(
|
|
|
522
522
|
const model = state.model;
|
|
523
523
|
setExtensionWidget(ctx, WIDGET_KEY, ((_tui: unknown, theme: unknown) => new CrewWidgetComponent(model, theme, _tui)) as never, {
|
|
524
524
|
placement: piPlacement,
|
|
525
|
-
persist: true,
|
|
526
525
|
});
|
|
527
526
|
state.lastVisibility = "visible";
|
|
528
527
|
state.lastPlacement = placement;
|