@fitchmultz/pi-subagents 0.0.0-stage → 0.44.5
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 +1654 -0
- package/LICENSE +21 -0
- package/README.md +1533 -2
- package/agents/context-builder.md +97 -0
- package/agents/debugger.md +51 -0
- package/agents/delegate.md +13 -0
- package/agents/fixer.md +37 -0
- package/agents/oracle.md +71 -0
- package/agents/planner.md +72 -0
- package/agents/researcher.md +51 -0
- package/agents/reviewer-claude.md +69 -0
- package/agents/reviewer-gpt.md +68 -0
- package/agents/reviewer-ponytail.md +82 -0
- package/agents/reviewer-security.md +81 -0
- package/agents/reviewer.md +69 -0
- package/agents/scout.md +66 -0
- package/agents/ui-designer.md +51 -0
- package/agents/watcher.md +26 -0
- package/agents/worker.md +46 -0
- package/agents/writer.md +36 -0
- package/dist/agents/agent-management.js +923 -0
- package/dist/agents/agent-scope.js +7 -0
- package/dist/agents/agent-selection.js +20 -0
- package/dist/agents/agent-serializer.js +100 -0
- package/dist/agents/agents.js +752 -0
- package/dist/agents/chain-serializer.js +367 -0
- package/dist/agents/frontmatter.js +21 -0
- package/dist/agents/identity.js +29 -0
- package/dist/agents/skills.js +570 -0
- package/dist/extension/build-info.js +1 -0
- package/dist/extension/compact-tools.js +112 -0
- package/dist/extension/config.js +22 -0
- package/dist/extension/control-notices.js +29 -0
- package/dist/extension/doctor.js +143 -0
- package/dist/extension/fanout-child.js +326 -0
- package/dist/extension/index.js +577 -0
- package/dist/extension/schemas.js +416 -0
- package/dist/extension/tool-input.js +20 -0
- package/dist/extension/tool-result.js +14 -0
- package/dist/history/canonical-result.js +81 -0
- package/dist/history/database.js +72 -0
- package/dist/history/index.js +199 -0
- package/dist/history/ingest.js +468 -0
- package/dist/history/queries.js +350 -0
- package/dist/history/store.js +216 -0
- package/dist/history/types.js +4 -0
- package/dist/history/worker.js +466 -0
- package/dist/intercom/intercom-bridge.js +112 -0
- package/dist/intercom/live-intercom.js +94 -0
- package/dist/intercom/result-intercom.js +373 -0
- package/dist/pi-intercom/agent-dir.js +10 -0
- package/dist/pi-intercom/bridge.js +750 -0
- package/dist/pi-intercom/broker/broker.js +426 -0
- package/dist/pi-intercom/broker/client.js +492 -0
- package/dist/pi-intercom/broker/framing.js +65 -0
- package/dist/pi-intercom/broker/paths.js +42 -0
- package/dist/pi-intercom/broker/pid.js +66 -0
- package/dist/pi-intercom/broker/spawn.js +221 -0
- package/dist/pi-intercom/config.js +102 -0
- package/dist/pi-intercom/index.js +2942 -0
- package/dist/pi-intercom/reply-tracker.js +182 -0
- package/dist/pi-intercom/session-targets.js +128 -0
- package/dist/pi-intercom/subagent-live-events.js +132 -0
- package/dist/pi-intercom/topics.js +157 -0
- package/dist/pi-intercom/types.js +163 -0
- package/dist/pi-intercom/ui/compose.js +269 -0
- package/dist/pi-intercom/ui/inline-message.js +80 -0
- package/dist/pi-intercom/ui/session-list.js +124 -0
- package/dist/runs/background/async-control.js +95 -0
- package/dist/runs/background/async-execution.js +676 -0
- package/dist/runs/background/async-job-tracker.js +383 -0
- package/dist/runs/background/async-result-file.js +58 -0
- package/dist/runs/background/async-resume.js +305 -0
- package/dist/runs/background/async-status.js +442 -0
- package/dist/runs/background/completion-dedupe.js +69 -0
- package/dist/runs/background/completion-delivery.js +245 -0
- package/dist/runs/background/notify.js +71 -0
- package/dist/runs/background/parallel-groups.js +55 -0
- package/dist/runs/background/result-watcher.js +426 -0
- package/dist/runs/background/run-id-resolver.js +64 -0
- package/dist/runs/background/run-status.js +382 -0
- package/dist/runs/background/stale-run-reconciler.js +335 -0
- package/dist/runs/background/subagent-runner-launcher.js +55 -0
- package/dist/runs/background/subagent-runner.js +1872 -0
- package/dist/runs/background/top-level-async.js +5 -0
- package/dist/runs/foreground/chain-clarify.js +1152 -0
- package/dist/runs/foreground/clarify-invocation.js +72 -0
- package/dist/runs/foreground/execution-input.js +423 -0
- package/dist/runs/foreground/foreground-control.js +817 -0
- package/dist/runs/foreground/question-control.js +88 -0
- package/dist/runs/foreground/run-async-path.js +205 -0
- package/dist/runs/foreground/subagent-executor.js +442 -0
- package/dist/runs/foreground/subagent-params.js +165 -0
- package/dist/runs/foreground/wait-run.js +141 -0
- package/dist/runs/shared/acceptance-contract.js +253 -0
- package/dist/runs/shared/acceptance-evaluation.js +256 -0
- package/dist/runs/shared/acceptance-finalization.js +280 -0
- package/dist/runs/shared/acceptance-reports.js +147 -0
- package/dist/runs/shared/acceptance.js +4 -0
- package/dist/runs/shared/chain-outputs.js +110 -0
- package/dist/runs/shared/child-attempt.js +693 -0
- package/dist/runs/shared/child-execution-cwd.js +96 -0
- package/dist/runs/shared/child-observations.js +114 -0
- package/dist/runs/shared/claude-code.js +199 -0
- package/dist/runs/shared/completion-guard.js +31 -0
- package/dist/runs/shared/dynamic-fanout.js +274 -0
- package/dist/runs/shared/history-index.js +72 -0
- package/dist/runs/shared/mcp-direct-tool-allowlist.js +334 -0
- package/dist/runs/shared/model-fallback.js +185 -0
- package/dist/runs/shared/mutating-tool-guard.js +170 -0
- package/dist/runs/shared/native-finalization.js +101 -0
- package/dist/runs/shared/native-usage.js +153 -0
- package/dist/runs/shared/nested-events.js +700 -0
- package/dist/runs/shared/nested-path.js +48 -0
- package/dist/runs/shared/nested-render.js +108 -0
- package/dist/runs/shared/parallel-utils.js +61 -0
- package/dist/runs/shared/parent-receipts.js +68 -0
- package/dist/runs/shared/parent-usage.js +121 -0
- package/dist/runs/shared/pi-args.js +310 -0
- package/dist/runs/shared/pi-spawn.js +52 -0
- package/dist/runs/shared/run-history-queries.js +45 -0
- package/dist/runs/shared/run-history.js +110 -0
- package/dist/runs/shared/run-id-ambiguity.js +4 -0
- package/dist/runs/shared/run-records.js +679 -0
- package/dist/runs/shared/session-cwd-preload.js +33 -0
- package/dist/runs/shared/single-output.js +222 -0
- package/dist/runs/shared/streaming-text.js +15 -0
- package/dist/runs/shared/structured-output.js +55 -0
- package/dist/runs/shared/subagent-control.js +179 -0
- package/dist/runs/shared/subagent-prompt-runtime.js +197 -0
- package/dist/runs/shared/subagent-tool-loop-guard.js +66 -0
- package/dist/runs/shared/supervisor-questions.js +350 -0
- package/dist/runs/shared/workflow-graph.js +214 -0
- package/dist/runs/shared/workflow-policy.js +84 -0
- package/dist/runs/shared/worktree.js +525 -0
- package/dist/shared/agent-context-policy.js +126 -0
- package/dist/shared/artifacts.js +96 -0
- package/dist/shared/atomic-json.js +13 -0
- package/dist/shared/cooperative.js +20 -0
- package/dist/shared/execution-cwd.js +14 -0
- package/dist/shared/file-coalescer.js +21 -0
- package/dist/shared/fork-context.js +49 -0
- package/dist/shared/formatters.js +85 -0
- package/dist/shared/journal-reader.js +407 -0
- package/dist/shared/lazy-tools.js +32 -0
- package/dist/shared/model-info.js +68 -0
- package/dist/shared/native-session.js +15 -0
- package/dist/shared/native-tui.js +18 -0
- package/dist/shared/native-typebox.js +20 -0
- package/dist/shared/post-exit-stdio-guard.js +197 -0
- package/dist/shared/prompt-sections.js +8 -0
- package/dist/shared/session-entries.js +52 -0
- package/dist/shared/session-identity.js +14 -0
- package/dist/shared/settings.js +254 -0
- package/dist/shared/status-format.js +111 -0
- package/dist/shared/temp-root.js +114 -0
- package/dist/shared/types.js +207 -0
- package/dist/shared/ui-mode.js +3 -0
- package/dist/shared/utils.js +334 -0
- package/dist/slash/prompt-template-bridge.js +298 -0
- package/dist/slash/slash-bridge.js +116 -0
- package/dist/slash/slash-commands.js +607 -0
- package/dist/slash/slash-live-state.js +246 -0
- package/dist/tui/action-hints.js +75 -0
- package/dist/tui/agent-history.js +197 -0
- package/dist/tui/agent-view.js +1656 -0
- package/dist/tui/render.js +1515 -0
- package/docs/intercom-bridge.md +212 -0
- package/docs/intercom.md +581 -0
- package/docs/performance.md +121 -0
- package/package.json +102 -4
- package/prompts/gather-context-and-clarify.md +14 -0
- package/prompts/parallel-cleanup.md +62 -0
- package/prompts/parallel-context-build.md +56 -0
- package/prompts/parallel-handoff-plan.md +62 -0
- package/prompts/parallel-research.md +51 -0
- package/prompts/parallel-review.md +59 -0
- package/prompts/review-loop.md +42 -0
- package/scripts/build.mjs +134 -0
- package/scripts/check-compat.mjs +48 -0
- package/scripts/compat-host.mjs +19 -0
- package/scripts/intercom-bridge-credentials.mjs +143 -0
- package/scripts/intercom-remote.mjs +103 -0
- package/scripts/linux-smoke.sh +51 -0
- package/scripts/local-install-smoke.mjs +66 -0
- package/scripts/native-package-smoke.mjs +82 -0
- package/scripts/package-smoke.mjs +209 -0
- package/scripts/prepare.mjs +67 -0
- package/scripts/real-pi-smoke.mjs +415 -0
- package/scripts/run-tests.mjs +149 -0
- package/skills/pi-intercom/SKILL.md +113 -0
- package/skills/pi-intercom/evals/evals.json +29 -0
- package/skills/pi-intercom/evals/trigger-evals.json +47 -0
- package/skills/pi-intercom/references/peer-sessions.md +69 -0
- package/skills/pi-subagents/SKILL.md +76 -0
- package/skills/pi-subagents/evals/evals.json +29 -0
- package/skills/pi-subagents/evals/trigger-evals.json +42 -0
- package/skills/pi-subagents/references/full-orchestration-guide.md +863 -0
- package/src/agents/agent-management.ts +813 -0
- package/src/agents/agent-scope.ts +7 -0
- package/src/agents/agent-selection.ts +23 -0
- package/src/agents/agent-serializer.ts +99 -0
- package/src/agents/agents.ts +916 -0
- package/src/agents/chain-serializer.ts +329 -0
- package/src/agents/frontmatter.ts +21 -0
- package/src/agents/identity.ts +30 -0
- package/src/agents/skills.ts +637 -0
- package/src/extension/build-info.ts +2 -0
- package/src/extension/compact-tools.ts +114 -0
- package/src/extension/config.ts +23 -0
- package/src/extension/control-notices.ts +47 -0
- package/src/extension/doctor.ts +171 -0
- package/src/extension/fanout-child.ts +312 -0
- package/src/extension/index.ts +649 -0
- package/src/extension/schemas.ts +444 -0
- package/src/extension/tool-input.ts +19 -0
- package/src/extension/tool-result.ts +16 -0
- package/src/history/canonical-result.ts +62 -0
- package/src/history/database.ts +59 -0
- package/src/history/index.ts +156 -0
- package/src/history/ingest.ts +349 -0
- package/src/history/queries.ts +241 -0
- package/src/history/store.ts +203 -0
- package/src/history/types.ts +166 -0
- package/src/history/worker.ts +321 -0
- package/src/intercom/intercom-bridge.ts +123 -0
- package/src/intercom/live-intercom.ts +104 -0
- package/src/intercom/result-intercom.ts +429 -0
- package/src/pi-intercom/agent-dir.ts +9 -0
- package/src/pi-intercom/bridge.ts +668 -0
- package/src/pi-intercom/broker/broker.ts +463 -0
- package/src/pi-intercom/broker/client.ts +558 -0
- package/src/pi-intercom/broker/framing.ts +77 -0
- package/src/pi-intercom/broker/paths.ts +47 -0
- package/src/pi-intercom/broker/pid.ts +59 -0
- package/src/pi-intercom/broker/spawn.ts +248 -0
- package/src/pi-intercom/config.ts +142 -0
- package/src/pi-intercom/index.ts +2929 -0
- package/src/pi-intercom/reply-tracker.ts +211 -0
- package/src/pi-intercom/session-targets.ts +172 -0
- package/src/pi-intercom/subagent-live-events.ts +133 -0
- package/src/pi-intercom/topics.ts +125 -0
- package/src/pi-intercom/types.ts +271 -0
- package/src/pi-intercom/ui/compose.ts +289 -0
- package/src/pi-intercom/ui/inline-message.ts +92 -0
- package/src/pi-intercom/ui/session-list.ts +139 -0
- package/src/runs/background/async-control.ts +89 -0
- package/src/runs/background/async-execution.ts +850 -0
- package/src/runs/background/async-job-tracker.ts +385 -0
- package/src/runs/background/async-result-file.ts +62 -0
- package/src/runs/background/async-resume.ts +333 -0
- package/src/runs/background/async-status.ts +481 -0
- package/src/runs/background/completion-dedupe.ts +79 -0
- package/src/runs/background/completion-delivery.ts +195 -0
- package/src/runs/background/notify.ts +123 -0
- package/src/runs/background/parallel-groups.ts +53 -0
- package/src/runs/background/result-watcher.ts +423 -0
- package/src/runs/background/run-id-resolver.ts +69 -0
- package/src/runs/background/run-status.ts +384 -0
- package/src/runs/background/stale-run-reconciler.ts +369 -0
- package/src/runs/background/subagent-runner-launcher.ts +56 -0
- package/src/runs/background/subagent-runner.ts +2053 -0
- package/src/runs/background/top-level-async.ts +13 -0
- package/src/runs/foreground/chain-clarify.ts +1340 -0
- package/src/runs/foreground/clarify-invocation.ts +77 -0
- package/src/runs/foreground/execution-input.ts +467 -0
- package/src/runs/foreground/foreground-control.ts +821 -0
- package/src/runs/foreground/question-control.ts +82 -0
- package/src/runs/foreground/run-async-path.ts +243 -0
- package/src/runs/foreground/subagent-executor.ts +501 -0
- package/src/runs/foreground/subagent-params.ts +320 -0
- package/src/runs/foreground/wait-run.ts +124 -0
- package/src/runs/shared/acceptance-contract.ts +257 -0
- package/src/runs/shared/acceptance-evaluation.ts +275 -0
- package/src/runs/shared/acceptance-finalization.ts +353 -0
- package/src/runs/shared/acceptance-reports.ts +152 -0
- package/src/runs/shared/acceptance.ts +33 -0
- package/src/runs/shared/chain-outputs.ts +114 -0
- package/src/runs/shared/child-attempt.ts +623 -0
- package/src/runs/shared/child-execution-cwd.ts +91 -0
- package/src/runs/shared/child-observations.ts +99 -0
- package/src/runs/shared/claude-code.ts +243 -0
- package/src/runs/shared/completion-guard.ts +35 -0
- package/src/runs/shared/dynamic-fanout.ts +295 -0
- package/src/runs/shared/history-index.ts +69 -0
- package/src/runs/shared/mcp-direct-tool-allowlist.ts +376 -0
- package/src/runs/shared/model-fallback.ts +219 -0
- package/src/runs/shared/mutating-tool-guard.ts +194 -0
- package/src/runs/shared/native-finalization.ts +128 -0
- package/src/runs/shared/native-usage.ts +113 -0
- package/src/runs/shared/nested-events.ts +752 -0
- package/src/runs/shared/nested-path.ts +52 -0
- package/src/runs/shared/nested-render.ts +119 -0
- package/src/runs/shared/parallel-utils.ts +153 -0
- package/src/runs/shared/parent-receipts.ts +59 -0
- package/src/runs/shared/parent-usage.ts +121 -0
- package/src/runs/shared/pi-args.ts +350 -0
- package/src/runs/shared/pi-spawn.ts +52 -0
- package/src/runs/shared/run-history-queries.ts +44 -0
- package/src/runs/shared/run-history.ts +100 -0
- package/src/runs/shared/run-id-ambiguity.ts +4 -0
- package/src/runs/shared/run-records.ts +607 -0
- package/src/runs/shared/session-cwd-preload.ts +27 -0
- package/src/runs/shared/single-output.ts +267 -0
- package/src/runs/shared/streaming-text.ts +18 -0
- package/src/runs/shared/structured-output.ts +68 -0
- package/src/runs/shared/subagent-control.ts +219 -0
- package/src/runs/shared/subagent-prompt-runtime.ts +201 -0
- package/src/runs/shared/subagent-tool-loop-guard.ts +89 -0
- package/src/runs/shared/supervisor-questions.ts +415 -0
- package/src/runs/shared/workflow-graph.ts +220 -0
- package/src/runs/shared/workflow-policy.ts +125 -0
- package/src/runs/shared/worktree.ts +635 -0
- package/src/shared/agent-context-policy.ts +184 -0
- package/src/shared/artifacts.ts +95 -0
- package/src/shared/atomic-json.ts +16 -0
- package/src/shared/cooperative.ts +21 -0
- package/src/shared/execution-cwd.ts +14 -0
- package/src/shared/file-coalescer.ts +29 -0
- package/src/shared/fork-context.ts +68 -0
- package/src/shared/formatters.ts +84 -0
- package/src/shared/journal-reader.ts +333 -0
- package/src/shared/lazy-tools.ts +36 -0
- package/src/shared/model-info.ts +81 -0
- package/src/shared/native-session.ts +8 -0
- package/src/shared/native-tui.ts +10 -0
- package/src/shared/native-typebox.ts +14 -0
- package/src/shared/post-exit-stdio-guard.ts +204 -0
- package/src/shared/prompt-sections.ts +10 -0
- package/src/shared/session-entries.ts +49 -0
- package/src/shared/session-identity.ts +18 -0
- package/src/shared/settings.ts +384 -0
- package/src/shared/status-format.ts +123 -0
- package/src/shared/temp-root.ts +107 -0
- package/src/shared/types.ts +1260 -0
- package/src/shared/ui-mode.ts +5 -0
- package/src/shared/utils.ts +342 -0
- package/src/slash/prompt-template-bridge.ts +408 -0
- package/src/slash/slash-bridge.ts +178 -0
- package/src/slash/slash-commands.ts +617 -0
- package/src/slash/slash-live-state.ts +292 -0
- package/src/tui/action-hints.ts +69 -0
- package/src/tui/agent-history.ts +210 -0
- package/src/tui/agent-view.ts +1323 -0
- package/src/tui/render.ts +1550 -0
- package/tsconfig.build.json +12 -0
- package/tsconfig.json +19 -0
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as parserModule from "stream-json/core/parser.js";
|
|
3
|
+
import type { Token } from "stream-json/core/parser.js";
|
|
4
|
+
|
|
5
|
+
type JsonObject = Record<string, any>;
|
|
6
|
+
export type Projection = (path: readonly (string | number)[], root?: JsonObject) => boolean | number;
|
|
7
|
+
type KeyLimit = (parentPath: readonly (string | number)[], root?: JsonObject) => number;
|
|
8
|
+
export interface ProjectionLimits { depth: number; nodes: number; arrayLength: number; keyLength: number }
|
|
9
|
+
export interface JournalRecord { value: JsonObject; start: number; end: number }
|
|
10
|
+
export type JournalPolicy = "strict" | "live" | "inspect";
|
|
11
|
+
|
|
12
|
+
// The public synchronous tokenizer is exported by 3.7.0 but omitted from its declarations.
|
|
13
|
+
const jsonParser = (parserModule as unknown as {
|
|
14
|
+
jsonParser(options: object): (chunk: string | symbol) => { values: Token[] } | symbol;
|
|
15
|
+
}).jsonParser;
|
|
16
|
+
const endInput = Symbol.for("object-stream.none");
|
|
17
|
+
|
|
18
|
+
/** Validate everything, assembling only selected fields. Discarded strings are never packed. */
|
|
19
|
+
export class JsonProjection {
|
|
20
|
+
private parse = jsonParser({ packValues: false });
|
|
21
|
+
private stack: Array<{ path: (string | number)[]; value?: any; key: string; index: number }> = [];
|
|
22
|
+
private scalar?: { path: (string | number)[]; text: string; limit: number; number: boolean };
|
|
23
|
+
private key = false;
|
|
24
|
+
private keyLimit = 4096;
|
|
25
|
+
private keys: KeyLimit;
|
|
26
|
+
private limits?: ProjectionLimits;
|
|
27
|
+
private nodes = 0;
|
|
28
|
+
value?: JsonObject;
|
|
29
|
+
private select: Projection;
|
|
30
|
+
private stringChunk?: (path: readonly (string | number)[], text: string, root?: JsonObject) => void;
|
|
31
|
+
constructor(select: Projection, stringChunk?: (path: readonly (string | number)[], text: string, root?: JsonObject) => void,
|
|
32
|
+
keys: KeyLimit = () => 4096, limits?: ProjectionLimits) { this.select = select; this.stringChunk = stringChunk; this.keys = keys; this.limits = limits; }
|
|
33
|
+
|
|
34
|
+
private selected(path: (string | number)[]): boolean | number {
|
|
35
|
+
const keep = this.stack.length && this.stack.at(-1)!.value === undefined ? false : this.select(path, this.value);
|
|
36
|
+
if (keep && this.limits && ++this.nodes > this.limits.nodes) throw new RangeError("JSON projection exceeds its assembled-node budget.");
|
|
37
|
+
return keep;
|
|
38
|
+
}
|
|
39
|
+
private nextPath(): (string | number)[] {
|
|
40
|
+
const parent = this.stack.at(-1);
|
|
41
|
+
if (this.limits && (this.stack.length > this.limits.depth || parent && parent.index >= this.limits.arrayLength)) throw new RangeError("JSON projection exceeds its depth or array-position budget.");
|
|
42
|
+
return parent ? [...parent.path, Array.isArray(parent.value) || parent.index >= 0 ? parent.index++ : parent.key] : [];
|
|
43
|
+
}
|
|
44
|
+
private put(path: (string | number)[], value: any): void {
|
|
45
|
+
const parent = this.stack.at(-1);
|
|
46
|
+
if (!parent) this.value = value;
|
|
47
|
+
else if (parent.value !== undefined) Object.defineProperty(parent.value, path.at(-1)!, { value, enumerable: true, writable: true, configurable: true });
|
|
48
|
+
}
|
|
49
|
+
write(chunk: string | symbol): void {
|
|
50
|
+
const output = this.parse(chunk);
|
|
51
|
+
if (typeof output === "symbol") return;
|
|
52
|
+
for (const token of output.values) {
|
|
53
|
+
switch (token.name) {
|
|
54
|
+
case "startKey": {
|
|
55
|
+
const parent = this.stack.at(-1)!;
|
|
56
|
+
this.key = true; parent.key = "";
|
|
57
|
+
this.keyLimit = parent.value === undefined ? 0 : this.keys(parent.path, this.value);
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
case "endKey": this.key = false; break;
|
|
61
|
+
case "startObject": case "startArray": {
|
|
62
|
+
const path = this.nextPath(), keep = this.selected(path);
|
|
63
|
+
const value = keep ? token.name === "startArray" ? [] : {} : undefined;
|
|
64
|
+
if (keep) this.put(path, value);
|
|
65
|
+
this.stack.push({ path, value, key: "", index: token.name === "startArray" ? 0 : -1 });
|
|
66
|
+
break;
|
|
67
|
+
}
|
|
68
|
+
case "endObject": case "endArray": this.stack.pop(); break;
|
|
69
|
+
case "startString": case "startNumber": {
|
|
70
|
+
const path = this.nextPath(), keep = this.selected(path);
|
|
71
|
+
this.scalar = { path, text: "", limit: keep === true ? Infinity : Number(keep) || 0, number: token.name === "startNumber" };
|
|
72
|
+
break;
|
|
73
|
+
}
|
|
74
|
+
case "stringChunk": case "numberChunk":
|
|
75
|
+
if (this.key) {
|
|
76
|
+
const parent = this.stack.at(-1)!;
|
|
77
|
+
if (parent.value !== undefined && this.limits && parent.key.length + token.value.length > this.limits.keyLength) throw new RangeError("JSON projection exceeds its property-key budget.");
|
|
78
|
+
if (parent.key.length < this.keyLimit) parent.key += token.value.slice(0, this.keyLimit - parent.key.length);
|
|
79
|
+
} else if (this.scalar) {
|
|
80
|
+
if (token.name === "stringChunk") this.stringChunk?.(this.scalar.path, token.value, this.value);
|
|
81
|
+
if (this.scalar.text.length < this.scalar.limit) this.scalar.text += token.value.slice(0, this.scalar.limit - this.scalar.text.length);
|
|
82
|
+
}
|
|
83
|
+
break;
|
|
84
|
+
case "endString": case "endNumber": {
|
|
85
|
+
const scalar = this.scalar!;
|
|
86
|
+
if (scalar.limit) this.put(scalar.path, scalar.number ? Number(scalar.text) : scalar.text);
|
|
87
|
+
this.scalar = undefined;
|
|
88
|
+
break;
|
|
89
|
+
}
|
|
90
|
+
case "trueValue": case "falseValue": case "nullValue": {
|
|
91
|
+
const path = this.nextPath();
|
|
92
|
+
if (this.selected(path)) this.put(path, token.value);
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
finish(): JsonObject | undefined { this.write(endInput); return this.value; }
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** Byte-framed JSONL. Publication framing is independent of malformed-record tolerance. */
|
|
102
|
+
export class JournalFrames {
|
|
103
|
+
private projection: JsonProjection;
|
|
104
|
+
private decoder = new TextDecoder("utf8", { fatal: true });
|
|
105
|
+
private error?: unknown;
|
|
106
|
+
private nonblank = false;
|
|
107
|
+
private start: number;
|
|
108
|
+
offset: number;
|
|
109
|
+
private select: Projection;
|
|
110
|
+
private record: (record: JournalRecord) => void;
|
|
111
|
+
private policy: JournalPolicy;
|
|
112
|
+
private malformed?: (start: number, end: number, error: unknown) => void;
|
|
113
|
+
private stringChunk?: ConstructorParameters<typeof JsonProjection>[1];
|
|
114
|
+
private keys?: KeyLimit;
|
|
115
|
+
private requireNewline: boolean;
|
|
116
|
+
private limits?: ProjectionLimits;
|
|
117
|
+
constructor(select: Projection, record: (record: JournalRecord) => void,
|
|
118
|
+
policy: JournalPolicy = "strict", offset = 0,
|
|
119
|
+
malformed?: (start: number, end: number, error: unknown) => void,
|
|
120
|
+
stringChunk?: ConstructorParameters<typeof JsonProjection>[1], keys?: KeyLimit, requireNewline = policy === "live", limits?: ProjectionLimits) {
|
|
121
|
+
this.select = select; this.record = record; this.policy = policy; this.malformed = malformed;
|
|
122
|
+
this.stringChunk = stringChunk; this.keys = keys;
|
|
123
|
+
this.requireNewline = requireNewline; this.limits = limits;
|
|
124
|
+
this.start = this.offset = offset;
|
|
125
|
+
this.projection = new JsonProjection(select, stringChunk, keys, limits);
|
|
126
|
+
}
|
|
127
|
+
private part(bytes: Buffer): void {
|
|
128
|
+
if (!this.nonblank && bytes.some((byte) => ![9, 10, 13, 32].includes(byte))) this.nonblank = true;
|
|
129
|
+
if (!this.error) try { this.projection.write(this.decoder.decode(bytes, { stream: true })); } catch (error) { this.error = error; }
|
|
130
|
+
}
|
|
131
|
+
private commit(end: number): void {
|
|
132
|
+
let record: JournalRecord | undefined, malformed: unknown;
|
|
133
|
+
const start = this.start;
|
|
134
|
+
if (this.nonblank) {
|
|
135
|
+
let value: JsonObject | undefined;
|
|
136
|
+
if (!this.error) try {
|
|
137
|
+
this.projection.write(this.decoder.decode());
|
|
138
|
+
value = this.projection.finish();
|
|
139
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw new SyntaxError("JSONL records must be objects");
|
|
140
|
+
} catch (error) { this.error = error; }
|
|
141
|
+
if (this.error) {
|
|
142
|
+
if (this.policy !== "inspect") throw new SyntaxError(`Invalid JSONL record at byte ${this.start}: ${String(this.error)}`, { cause: this.error });
|
|
143
|
+
malformed = this.error;
|
|
144
|
+
} else record = { value: value!, start, end };
|
|
145
|
+
}
|
|
146
|
+
this.start = end;
|
|
147
|
+
this.projection = new JsonProjection(this.select, this.stringChunk, this.keys, this.limits);
|
|
148
|
+
this.decoder = new TextDecoder("utf8", { fatal: true });
|
|
149
|
+
this.error = undefined;
|
|
150
|
+
this.nonblank = false;
|
|
151
|
+
if (malformed) this.malformed?.(start, end, malformed);
|
|
152
|
+
else if (record) this.record(record);
|
|
153
|
+
}
|
|
154
|
+
write(bytes: Buffer): void {
|
|
155
|
+
const offset = this.offset;
|
|
156
|
+
this.offset += bytes.length;
|
|
157
|
+
let from = 0;
|
|
158
|
+
for (let newline = bytes.indexOf(10); newline >= 0; newline = bytes.indexOf(10, from)) {
|
|
159
|
+
this.part(bytes.subarray(from, newline));
|
|
160
|
+
this.commit(offset + newline + 1);
|
|
161
|
+
from = newline + 1;
|
|
162
|
+
}
|
|
163
|
+
this.part(bytes.subarray(from));
|
|
164
|
+
}
|
|
165
|
+
finish(): number {
|
|
166
|
+
if (this.requireNewline) {
|
|
167
|
+
if (this.policy === "strict" && this.nonblank) throw new SyntaxError(`Unpublished JSONL record at byte ${this.start}: newline required`);
|
|
168
|
+
} else if (this.offset > this.start) this.commit(this.offset);
|
|
169
|
+
return this.start;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export function scanJournal(file: string | number, select: Projection, record: (record: JournalRecord) => void,
|
|
174
|
+
options: { policy?: JournalPolicy; requireNewline?: boolean; start?: number; end?: number; malformed?: (start: number, end: number, error: unknown) => void; stringChunk?: ConstructorParameters<typeof JsonProjection>[1]; keys?: KeyLimit } = {}): number {
|
|
175
|
+
const fd = typeof file === "number" ? file : fs.openSync(file, "r");
|
|
176
|
+
try {
|
|
177
|
+
const end = Math.min(options.end ?? Infinity, fs.fstatSync(fd).size);
|
|
178
|
+
const frames = new JournalFrames(select, record, options.policy, options.start ?? 0, options.malformed, options.stringChunk, options.keys, options.requireNewline);
|
|
179
|
+
const buffer = Buffer.allocUnsafe(64 * 1024);
|
|
180
|
+
for (let offset = options.start ?? 0; offset < end;) {
|
|
181
|
+
const count = fs.readSync(fd, buffer, 0, Math.min(buffer.length, end - offset), offset);
|
|
182
|
+
if (!count) throw new Error("Journal truncated while reading");
|
|
183
|
+
frames.write(buffer.subarray(0, count)); offset += count;
|
|
184
|
+
}
|
|
185
|
+
return frames.finish();
|
|
186
|
+
} finally { if (typeof file !== "number") fs.closeSync(fd); }
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export function readJsonProjection(file: string, select: Projection): any {
|
|
190
|
+
// ponytail: selected frozen schemas and acceptance values fit their consumer's
|
|
191
|
+
// heap, including arbitrary property names; discarded owner messages do not.
|
|
192
|
+
const fd = fs.openSync(file, "r"), decoder = new TextDecoder("utf8", { fatal: true }), projection = new JsonProjection(select, undefined, () => Infinity);
|
|
193
|
+
try {
|
|
194
|
+
const end = fs.fstatSync(fd).size, bytes = Buffer.allocUnsafe(64 * 1024);
|
|
195
|
+
for (let offset = 0; offset < end;) {
|
|
196
|
+
const count = fs.readSync(fd, bytes, 0, Math.min(bytes.length, end - offset), offset);
|
|
197
|
+
if (!count) throw new Error("JSON file truncated while reading");
|
|
198
|
+
projection.write(decoder.decode(bytes.subarray(0, count), { stream: true })); offset += count;
|
|
199
|
+
}
|
|
200
|
+
projection.write(decoder.decode());
|
|
201
|
+
const value = projection.finish();
|
|
202
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw new SyntaxError("Owner records must be objects");
|
|
203
|
+
return value;
|
|
204
|
+
} catch (error) { throw new SyntaxError(`Invalid JSON file ${file}: ${String(error)}`, { cause: error }); }
|
|
205
|
+
finally { fs.closeSync(fd); }
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/** Compact owner reads never hydrate transcript arrays or historical output strings. */
|
|
209
|
+
export const ownerProjection: Projection = (path) => {
|
|
210
|
+
const resultField = path.length === 2 && path[0] === "result"
|
|
211
|
+
|| path.length === 3 && path[0] === "results" && typeof path[1] === "number"
|
|
212
|
+
|| path.length === 4 && path[0] === "children" && typeof path[1] === "number" && path[2] === "result";
|
|
213
|
+
if (resultField && path.at(-1) === "messages") return false;
|
|
214
|
+
const preview = resultField && ["output", "finalOutput", "initialOutput", "rawOutput"].includes(String(path.at(-1)))
|
|
215
|
+
|| path.length === 1 && path[0] === "summary"
|
|
216
|
+
|| path.length === 3 && path[0] === "children" && typeof path[1] === "number" && path[2] === "summary";
|
|
217
|
+
return preview ? 8192 : true;
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
const entryFields = new Set(["type", "id", "parentId", "timestamp", "version", "cwd", "parentSession", "checkpoint", "provider", "model", "modelId", "thinkingLevel", "customType", "usage", "firstKeptEntryId", "fromId"]);
|
|
221
|
+
const messageFields = new Set(["role", "provider", "model", "responseModel", "thinkingLevel", "usage", "stopReason", "errorMessage", "timestamp", "toolCallId", "toolName", "isError", "content", "command", "output", "exitCode", "cancelled"]);
|
|
222
|
+
/** Metadata/previews, never unrelated custom data, image bytes or raw tool details. */
|
|
223
|
+
export const nativeProjection: Projection = (path, root) => {
|
|
224
|
+
if (!path.length) return true;
|
|
225
|
+
if (path[0] === "message") {
|
|
226
|
+
if (path.length === 1) return true;
|
|
227
|
+
if (!messageFields.has(String(path[1]))) return false;
|
|
228
|
+
if (path[1] === "content") {
|
|
229
|
+
if (path.length <= 3) return 2048;
|
|
230
|
+
if (path[3] === "arguments") {
|
|
231
|
+
if (path.length === 4) return true;
|
|
232
|
+
if (path.length === 5) return Object.keys(root?.message?.content?.[Number(path[2])]?.arguments ?? {}).length < 32 ? 2048 : false;
|
|
233
|
+
return path.length === 6 && path[5] === 0 ? 2048 : false;
|
|
234
|
+
}
|
|
235
|
+
return ["type", "id", "name", "text", "thinking", "mimeType", "arguments"].includes(String(path[3])) ? 2048 : false;
|
|
236
|
+
}
|
|
237
|
+
return path[1] === "usage" ? true : 4096;
|
|
238
|
+
}
|
|
239
|
+
if (path[0] === "summary" || path[0] === "content") return 2048;
|
|
240
|
+
if (path[0] === "details") return path.length === 1 || ["bodyText", "message", "from"].includes(String(path[1])) ? 2048 : false;
|
|
241
|
+
return entryFields.has(String(path[0])) ? 4096 : false;
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
export interface NativeRecord extends JournalRecord { value: JsonObject & { id: string; type: string } }
|
|
245
|
+
export class NativeJournal {
|
|
246
|
+
readonly records: NativeRecord[] = [];
|
|
247
|
+
readonly byId = new Map<string, NativeRecord>();
|
|
248
|
+
readonly stamp: string;
|
|
249
|
+
readonly end: number;
|
|
250
|
+
readonly file: string;
|
|
251
|
+
constructor(file: string, policy: JournalPolicy = "inspect", requireNewline = policy === "live") {
|
|
252
|
+
this.file = file;
|
|
253
|
+
const fd = fs.openSync(file, "r");
|
|
254
|
+
try {
|
|
255
|
+
const stat = fs.fstatSync(fd, { bigint: true });
|
|
256
|
+
this.stamp = journalStamp(stat); this.end = Number(stat.size);
|
|
257
|
+
let parent: string | null = null;
|
|
258
|
+
scanJournal(fd, nativeProjection, (record) => {
|
|
259
|
+
const entry = record.value;
|
|
260
|
+
if (entry.type !== "session") {
|
|
261
|
+
// Read-only legacy v1 presentation uses stable offset IDs, without rewriting the source.
|
|
262
|
+
entry.id ??= `legacy-${record.start}`;
|
|
263
|
+
if (this.records[0]?.value.version === 1) entry.parentId = parent;
|
|
264
|
+
parent = entry.id;
|
|
265
|
+
}
|
|
266
|
+
this.records.push(record as NativeRecord); this.byId.set(entry.id, record as NativeRecord);
|
|
267
|
+
}, { policy, requireNewline, end: this.end });
|
|
268
|
+
if (journalStamp(fs.fstatSync(fd, { bigint: true })) !== this.stamp) throw new Error("Journal changed while indexing; refresh history.");
|
|
269
|
+
} finally { fs.closeSync(fd); }
|
|
270
|
+
if (this.records[0]?.value.type !== "session") throw new Error("Not a readable native Pi session.");
|
|
271
|
+
}
|
|
272
|
+
branch(leaf?: string | null, endedAt?: number): NativeRecord[] {
|
|
273
|
+
if (leaf === null) return [];
|
|
274
|
+
const eligible = endedAt === undefined ? this.records : this.records.filter((record) => record.value.type === "session" || Date.parse(record.value.timestamp) <= endedAt);
|
|
275
|
+
let current = leaf ? this.byId.get(leaf) : eligible.findLast((record) => record.value.type !== "session");
|
|
276
|
+
const path: NativeRecord[] = [], seen = new Set<string>(), allowed = new Set(eligible.map((record) => record.value.id));
|
|
277
|
+
while (current && current.value.type !== "session" && allowed.has(current.value.id)) {
|
|
278
|
+
if (seen.has(current.value.id)) throw new Error("Cyclic journal ancestry");
|
|
279
|
+
seen.add(current.value.id); path.push(current);
|
|
280
|
+
current = this.byId.get(current.value.parentId);
|
|
281
|
+
}
|
|
282
|
+
return path.reverse();
|
|
283
|
+
}
|
|
284
|
+
body(record: NativeRecord): any {
|
|
285
|
+
const fd = fs.openSync(this.file, "r");
|
|
286
|
+
try {
|
|
287
|
+
if (journalStamp(fs.fstatSync(fd, { bigint: true })) !== this.stamp) throw new Error("Journal changed; refresh before reading history.");
|
|
288
|
+
// ponytail: explicitly requested individual bodies must fit the consumer's heap;
|
|
289
|
+
// metadata and previews stay bounded. Use pages for large output.
|
|
290
|
+
const bytes = Buffer.alloc(record.end - record.start);
|
|
291
|
+
if (fs.readSync(fd, bytes, 0, bytes.length, record.start) !== bytes.length) throw new Error("Journal truncated");
|
|
292
|
+
const body = JSON.parse(bytes.toString("utf8"));
|
|
293
|
+
if (body.type !== "session") { body.id ??= record.value.id; body.parentId ??= record.value.parentId; }
|
|
294
|
+
return body;
|
|
295
|
+
} finally { fs.closeSync(fd); }
|
|
296
|
+
}
|
|
297
|
+
configuration(endedAt?: number, leaf?: string | null): { model?: string; thinking?: string; modelRecordedAt?: number } {
|
|
298
|
+
const result: ReturnType<NativeJournal["configuration"]> = {};
|
|
299
|
+
let model: NativeRecord["value"] | undefined;
|
|
300
|
+
for (const { value } of this.branch(leaf, endedAt)) {
|
|
301
|
+
if (value.type === "thinking_level_change") result.thinking = value.thinkingLevel;
|
|
302
|
+
if (value.type === "model_change" || value.type === "message" && value.message?.role === "assistant") model = value;
|
|
303
|
+
}
|
|
304
|
+
if (model) {
|
|
305
|
+
result.model = model.type === "model_change" ? `${model.provider}/${model.modelId}` : `${model.message.provider}/${model.message.model}`;
|
|
306
|
+
result.modelRecordedAt = Date.parse(model.timestamp);
|
|
307
|
+
}
|
|
308
|
+
return result;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
export function journalStamp(stat: fs.BigIntStats): string { return `${stat.dev}:${stat.ino}:${stat.size}:${stat.mtimeNs}:${stat.ctimeNs}`; }
|
|
313
|
+
|
|
314
|
+
/** Read a UTF-8 page or tail without reading its preceding output. */
|
|
315
|
+
export function readOutputPage(file: string, options: { offset?: number; length?: number } = {}): { text: string; offset: number; nextOffset?: number; size: number } {
|
|
316
|
+
const fd = fs.openSync(file, "r");
|
|
317
|
+
try {
|
|
318
|
+
const size = fs.fstatSync(fd).size, length = options.length ?? 64 * 1024;
|
|
319
|
+
if (!Number.isSafeInteger(length) || length < 1 || length > 1024 * 1024 || options.offset !== undefined && (!Number.isSafeInteger(options.offset) || options.offset < 0)) throw new Error("Invalid output page");
|
|
320
|
+
const offset = Math.min(size, options.offset ?? Math.max(0, size - length));
|
|
321
|
+
const bytes = Buffer.alloc(Math.min(length, size - offset));
|
|
322
|
+
const count = fs.readSync(fd, bytes, 0, bytes.length, offset);
|
|
323
|
+
let start = 0, end = count;
|
|
324
|
+
while (start < end && (bytes[start]! & 0xc0) === 0x80) start++;
|
|
325
|
+
if (offset + end < size) {
|
|
326
|
+
let last = end - 1;
|
|
327
|
+
while (last >= start && (bytes[last]! & 0xc0) === 0x80) last--;
|
|
328
|
+
const lead = bytes[last]!, width = lead < 0x80 ? 1 : lead < 0xe0 ? 2 : lead < 0xf0 ? 3 : 4;
|
|
329
|
+
if (last + width > end) end = last;
|
|
330
|
+
}
|
|
331
|
+
return { text: bytes.subarray(start, end).toString("utf8"), offset: offset + start, size, ...(offset + end < size ? { nextOffset: offset + end } : {}) };
|
|
332
|
+
} finally { fs.closeSync(fd); }
|
|
333
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { getCurrentSystemMessage } from "@earendil-works/pi-ai";
|
|
2
|
+
import type { ExtensionAPI, ExtensionContext, SessionManager } from "@earendil-works/pi-coding-agent";
|
|
3
|
+
|
|
4
|
+
const declarations = new WeakMap<ExtensionContext["sessionManager"], {
|
|
5
|
+
leaf: string | null; count: number | undefined; current: ReturnType<typeof getCurrentSystemMessage>;
|
|
6
|
+
}>();
|
|
7
|
+
|
|
8
|
+
/** Only our unnamespaced tools are owned here; foreign public IDs pass through untouched. */
|
|
9
|
+
export function activateTools(pi: ExtensionAPI, names: readonly string[]): void {
|
|
10
|
+
const available = pi.getAllTools();
|
|
11
|
+
const active = pi.getActiveTools();
|
|
12
|
+
const added = names.filter((name) => !active.includes(name) && available.some((tool) => tool.name === name && !("namespace" in tool && tool.namespace)));
|
|
13
|
+
if (added.length) pi.setActiveTools([...active, ...new Set(added)]);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Official 1.0 SDK creation omits initial resume restoration; tree/reload are native. */
|
|
17
|
+
export function restoreLazyTools(pi: ExtensionAPI, ctx: ExtensionContext, loader: string, names: readonly string[]): void {
|
|
18
|
+
const available = pi.getAllTools();
|
|
19
|
+
// A tool-only allowlist must remain usable without its discovery entry.
|
|
20
|
+
if (!available.some((tool) => tool.name === loader && !("namespace" in tool && tool.namespace))) {
|
|
21
|
+
activateTools(pi, names);
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
const manager = ctx.sessionManager, leaf = manager.getLeafId(), count = (manager as Partial<SessionManager>).getEntryCount?.();
|
|
25
|
+
let cached = declarations.get(manager);
|
|
26
|
+
if (count === undefined || !cached || cached.leaf !== leaf || cached.count !== count) {
|
|
27
|
+
cached = { leaf, count, current: getCurrentSystemMessage(manager.buildSessionProjection().messages) };
|
|
28
|
+
declarations.set(manager, cached);
|
|
29
|
+
}
|
|
30
|
+
const declared = cached.current;
|
|
31
|
+
if (!declared) return;
|
|
32
|
+
const selected = declared?.toolsAdded ?? [];
|
|
33
|
+
const restored = names.filter((name) => selected.some((tool) => tool.name === name && !("namespace" in tool && tool.namespace))
|
|
34
|
+
&& available.some((tool) => tool.name === name && !("namespace" in tool && tool.namespace)));
|
|
35
|
+
activateTools(pi, restored);
|
|
36
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
export const THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh", "max"] as const;
|
|
2
|
+
export type ThinkingLevel = typeof THINKING_LEVELS[number];
|
|
3
|
+
export type ThinkingLevelMap = Partial<Record<ThinkingLevel, string | null>>;
|
|
4
|
+
|
|
5
|
+
export interface ModelInfo {
|
|
6
|
+
provider: string;
|
|
7
|
+
id: string;
|
|
8
|
+
fullId: string;
|
|
9
|
+
reasoning?: boolean;
|
|
10
|
+
thinkingLevelMap?: ThinkingLevelMap;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface RegistryModelLike {
|
|
14
|
+
provider: string;
|
|
15
|
+
id: string;
|
|
16
|
+
reasoning?: boolean;
|
|
17
|
+
thinkingLevelMap?: ThinkingLevelMap;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function toModelInfo(model: RegistryModelLike): ModelInfo {
|
|
21
|
+
return {
|
|
22
|
+
provider: model.provider,
|
|
23
|
+
id: model.id,
|
|
24
|
+
fullId: `${model.provider}/${model.id}`,
|
|
25
|
+
reasoning: model.reasoning,
|
|
26
|
+
thinkingLevelMap: model.thinkingLevelMap,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function providerQualifiedModelId(provider: string | undefined, model: string | undefined): string | undefined {
|
|
31
|
+
if (!model) return undefined;
|
|
32
|
+
return provider && !model.startsWith(`${provider}/`) ? `${provider}/${model}` : model;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Resolve the effective thinking level from a model string (which may contain a known suffix like `:high`)
|
|
36
|
+
* and an explicit thinking config value. Returns `undefined` when no thinking is applicable
|
|
37
|
+
* (e.g. no model was specified, or the model has no suffix and no config was provided). */
|
|
38
|
+
export function resolveEffectiveThinking(model: string | undefined, configThinking: string | undefined): string | undefined {
|
|
39
|
+
if (!model) return undefined;
|
|
40
|
+
const { thinkingSuffix } = splitKnownThinkingSuffix(model);
|
|
41
|
+
if (thinkingSuffix) return thinkingSuffix.slice(1);
|
|
42
|
+
return THINKING_LEVELS.find((level) => level === configThinking);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function splitKnownThinkingSuffix(model: string): { baseModel: string; thinkingSuffix: string } {
|
|
46
|
+
const colonIdx = model.lastIndexOf(":");
|
|
47
|
+
if (colonIdx === -1) return { baseModel: model, thinkingSuffix: "" };
|
|
48
|
+
const suffix = THINKING_LEVELS.find((level) => level === model.substring(colonIdx + 1));
|
|
49
|
+
if (!suffix) return { baseModel: model, thinkingSuffix: "" };
|
|
50
|
+
return {
|
|
51
|
+
baseModel: model.substring(0, colonIdx),
|
|
52
|
+
thinkingSuffix: `:${suffix}`,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function findModelInfo(model: string | undefined, availableModels: ModelInfo[] | undefined, preferredProvider?: string): ModelInfo | undefined {
|
|
57
|
+
if (!model || !availableModels || availableModels.length === 0) return undefined;
|
|
58
|
+
const { baseModel } = splitKnownThinkingSuffix(model);
|
|
59
|
+
const exact = availableModels.find((entry) => entry.fullId === baseModel);
|
|
60
|
+
if (exact) return exact;
|
|
61
|
+
|
|
62
|
+
const matches = availableModels.filter((entry) => entry.id === baseModel);
|
|
63
|
+
if (preferredProvider) {
|
|
64
|
+
const preferred = matches.find((entry) => entry.provider === preferredProvider);
|
|
65
|
+
if (preferred) return preferred;
|
|
66
|
+
}
|
|
67
|
+
return matches.length === 1 ? matches[0] : undefined;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function getSupportedThinkingLevels(model: ModelInfo | undefined): ThinkingLevel[] {
|
|
71
|
+
if (!model) return [...THINKING_LEVELS];
|
|
72
|
+
if (model.reasoning === false) return ["off"];
|
|
73
|
+
|
|
74
|
+
const levels = THINKING_LEVELS.filter((level) => {
|
|
75
|
+
const mapped = model.thinkingLevelMap?.[level];
|
|
76
|
+
if (mapped === null) return false;
|
|
77
|
+
if (level === "xhigh" || level === "max") return mapped !== undefined;
|
|
78
|
+
return true;
|
|
79
|
+
});
|
|
80
|
+
return levels;
|
|
81
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import * as path from "node:path";
|
|
2
|
+
import { pathToFileURL } from "node:url";
|
|
3
|
+
import { requirePiPackageRoot } from "../runs/shared/pi-spawn.ts";
|
|
4
|
+
|
|
5
|
+
// Detached Node runners do not get Pi's extension-loader package aliases.
|
|
6
|
+
const root = requirePiPackageRoot();
|
|
7
|
+
const sessionModule: Pick<typeof import("@earendil-works/pi-coding-agent"), "buildSessionContext" | "parseSessionEntries" | "migrateSessionEntries" | "SessionManager"> = await import(pathToFileURL(path.join(root, "dist/index.js")).href);
|
|
8
|
+
export const { buildSessionContext, parseSessionEntries, migrateSessionEntries, SessionManager } = sessionModule;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { findPackageJSON } from "node:module";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { pathToFileURL } from "node:url";
|
|
4
|
+
import { requirePiPackageRoot } from "../runs/shared/pi-spawn.ts";
|
|
5
|
+
|
|
6
|
+
// Detached Node workers use the selected host's TUI, without extension-loader aliases.
|
|
7
|
+
const manifest = findPackageJSON("@earendil-works/pi-tui", pathToFileURL(path.join(requirePiPackageRoot(), "dist/index.js")));
|
|
8
|
+
if (!manifest) throw new Error("Could not locate the selected Pi TUI package.");
|
|
9
|
+
const tui: Pick<typeof import("@earendil-works/pi-tui"), "stripTerminalSequences"> = await import(new URL("./dist/utils.js", pathToFileURL(manifest)).href);
|
|
10
|
+
export const { stripTerminalSequences } = tui;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { pathToFileURL } from "node:url";
|
|
4
|
+
import { requirePiPackageRoot } from "../runs/shared/pi-spawn.ts";
|
|
5
|
+
|
|
6
|
+
// Detached Node runners do not get Pi's extension-loader package aliases.
|
|
7
|
+
const require = createRequire(path.join(requirePiPackageRoot(), "dist/index.js"));
|
|
8
|
+
const typebox: typeof import("typebox") = await import(pathToFileURL(require.resolve("typebox")).href);
|
|
9
|
+
const compile: typeof import("typebox/compile") = await import(pathToFileURL(require.resolve("typebox/compile")).href);
|
|
10
|
+
const value: typeof import("typebox/value") = await import(pathToFileURL(require.resolve("typebox/value")).href);
|
|
11
|
+
|
|
12
|
+
export const { Type } = typebox;
|
|
13
|
+
export const { Compile } = compile;
|
|
14
|
+
export const { Check, Errors } = value;
|