@hicaru/pi-rlm 0.2.2 → 0.3.1
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/README.md +38 -16
- package/README.ru.md +2 -2
- package/README.zh-CN.md +2 -2
- package/package.json +22 -19
- package/src/bridge/add-context.ts +322 -0
- package/src/bridge/subcall-handlers.ts +1 -1
- package/src/config/defaults.ts +2 -1
- package/src/config/settings.ts +5 -2
- package/src/context/anydoc.ts +67 -0
- package/src/context/listing.ts +70 -0
- package/src/context/md-cache.ts +112 -0
- package/src/context/merge.ts +97 -0
- package/src/context/namespace.ts +180 -0
- package/src/context/resolve.ts +122 -0
- package/src/context/source-dir.ts +166 -0
- package/src/context/source-doc.ts +71 -0
- package/src/context/source-git.ts +51 -0
- package/src/context/source-text.ts +45 -0
- package/src/context/types.ts +88 -0
- package/src/context/walk.ts +250 -0
- package/src/core/engine.ts +15 -19
- package/src/core/types.ts +7 -2
- package/src/index.ts +119 -47
- package/src/mode/rlm-mode.ts +5 -4
- package/src/mode/subagent.ts +68 -0
- package/src/prompts/glossary.ts +31 -28
- package/src/prompts/native.ts +4 -4
- package/src/prompts/system.ts +2 -2
- package/src/sandbox/context-file.ts +4 -4
- package/src/sandbox/interrupts.ts +25 -10
- package/src/sandbox/protocol.ts +13 -7
- package/src/sandbox/py/__pycache__/guards.cpython-314.pyc +0 -0
- package/src/sandbox/py/__pycache__/hostio.cpython-314.pyc +0 -0
- package/src/sandbox/py/__pycache__/retrieval.cpython-314.pyc +0 -0
- package/src/sandbox/py/__pycache__/tasks.cpython-314.pyc +0 -0
- package/src/sandbox/py/guards.py +8 -1
- package/src/sandbox/py/hostio.py +57 -0
- package/src/sandbox/py/retrieval.py +1 -1
- package/src/sandbox/py/tasks.py +17 -4
- package/src/sandbox/py/worker.py +71 -52
- package/src/sandbox/sandbox-manager.ts +18 -16
- package/src/sandbox/sandbox.ts +9 -2
- package/src/text/tokens.ts +3 -3
- package/src/tool/repl-details.ts +1 -1
- package/src/tool/repl-tool.ts +31 -19
- package/src/tool/rlm-tool.ts +1 -1
- package/src/ui/config-panel.ts +8 -4
- package/src/bridge/library.ts +0 -190
- package/src/context/library-context.ts +0 -339
- package/src/context/repomix-context.ts +0 -204
package/src/text/tokens.ts
CHANGED
|
@@ -25,7 +25,7 @@ export function estimateMessageTokens(messages: { content: string }[]): number {
|
|
|
25
25
|
* Character length of one context entry. `ContextFile`-shaped entries report their content
|
|
26
26
|
* length; anything else falls back to its serialized form.
|
|
27
27
|
*
|
|
28
|
-
* Deliberately does NOT import `isContextFile` from context/
|
|
28
|
+
* Deliberately does NOT import `isContextFile` from context/namespace.ts: that module
|
|
29
29
|
* imports `estimateTokens` from here, so the reverse import would be a cycle. `in`-narrowing
|
|
30
30
|
* needs no type guard and no cast.
|
|
31
31
|
*/
|
|
@@ -74,8 +74,8 @@ const isTokenizedEntry = (v: unknown): v is { readonly tokens: number } =>
|
|
|
74
74
|
typeof v === "object" && v !== null && typeof (v as { readonly tokens?: unknown }).tokens === "number";
|
|
75
75
|
|
|
76
76
|
/** Per-file token distribution for a context payload; `undefined` for plain strings or empty arrays.
|
|
77
|
-
* Handles both
|
|
78
|
-
*
|
|
77
|
+
* Handles both a flat ContextFile[] and a raw bundle object ({ files: [...] }) so callers
|
|
78
|
+
* don't need to know which form they received. */
|
|
79
79
|
export function contextSizeStats(context: unknown): ContextSizeStats | undefined {
|
|
80
80
|
// Normalise to a flat entry list: accept either a direct array or an object with a .files array.
|
|
81
81
|
const entries: readonly unknown[] = Array.isArray(context)
|
package/src/tool/repl-details.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* ReplDetails — structured payload for the repl() tool's AgentToolResult<T>.
|
|
3
3
|
*
|
|
4
4
|
* Mirrors RlmDetails but scoped to a single code execution. Sub-calls (llm_query,
|
|
5
|
-
* rlm_query,
|
|
5
|
+
* rlm_query, add_context) triggered during sandbox execution are
|
|
6
6
|
* accumulated into the subcalls array for tree rendering.
|
|
7
7
|
*/
|
|
8
8
|
|
package/src/tool/repl-tool.ts
CHANGED
|
@@ -20,8 +20,8 @@ import type { ToolDefinition } from "@earendil-works/pi-coding-agent";
|
|
|
20
20
|
import { Text } from "@earendil-works/pi-tui";
|
|
21
21
|
import type { Model, Usage, Api } from "@earendil-works/pi-ai";
|
|
22
22
|
import type { ModelRegistry } from "@earendil-works/pi-coding-agent";
|
|
23
|
-
import {
|
|
24
|
-
import {
|
|
23
|
+
import { buildAddContextHandler, type AddContextHandlerBundle } from "../bridge/add-context.ts";
|
|
24
|
+
import { contextPrefixesIn } from "../context/namespace.ts";
|
|
25
25
|
import type { SubcallGates } from "../util/concurrency.ts";
|
|
26
26
|
import { LimitGuard, limitsFromConfig } from "../core/limits.ts";
|
|
27
27
|
import type { RlmConfig, RlmInput, RlmResult } from "../core/types.ts";
|
|
@@ -115,8 +115,13 @@ export interface ReplToolDeps {
|
|
|
115
115
|
readonly signal?: AbortSignal;
|
|
116
116
|
readonly onUsage?: (usage: Usage, role: "sub") => void;
|
|
117
117
|
readonly ensureContext?: () => Promise<void>;
|
|
118
|
-
/** Register a reset hook for sandbox death/dispose (e.g.
|
|
118
|
+
/** Register a reset hook for sandbox death/dispose (e.g. add_context prefix cache). */
|
|
119
119
|
readonly registerDiscardHook?: (reset: () => void) => void;
|
|
120
|
+
/**
|
|
121
|
+
* Hands the live add_context bundle to the extension so the cwd seed can
|
|
122
|
+
* markLoaded("") / markSeededCwd(abs) — without this, add_context(".") doubles the tree.
|
|
123
|
+
*/
|
|
124
|
+
readonly registerContextBundle?: (bundle: AddContextHandlerBundle) => void;
|
|
120
125
|
}
|
|
121
126
|
|
|
122
127
|
export function createReplTool(deps: ReplToolDeps): ToolDefinition<typeof ReplToolParams, ReplDetails> {
|
|
@@ -159,15 +164,15 @@ export function createReplTool(deps: ReplToolDeps): ToolDefinition<typeof ReplTo
|
|
|
159
164
|
signal,
|
|
160
165
|
onUsage,
|
|
161
166
|
runChild,
|
|
162
|
-
// The session sandbox's context is the child's world. Read lazily so
|
|
167
|
+
// The session sandbox's context is the child's world. Read lazily so an add_context from an
|
|
163
168
|
// earlier repl() reaches a child spawned in a later one. Populated before any interrupt can
|
|
164
169
|
// fire: execute() awaits ensureContext() before getOrCreate().
|
|
165
170
|
getChildContext: () => sandboxManager.contextPayload ?? undefined,
|
|
166
171
|
trackDetached: (task) => background.track(task),
|
|
167
172
|
});
|
|
168
173
|
|
|
169
|
-
const
|
|
170
|
-
?
|
|
174
|
+
const contextBundle = getConfig().contextLoader
|
|
175
|
+
? buildAddContextHandler({
|
|
171
176
|
getCwd: () => sessionCwd,
|
|
172
177
|
getEmitter: () => bridgeState.currentEmitter,
|
|
173
178
|
// Refuse pre-flight whatever the worker would reject, so host idempotency is never
|
|
@@ -177,14 +182,20 @@ export function createReplTool(deps: ReplToolDeps): ToolDefinition<typeof ReplTo
|
|
|
177
182
|
signal,
|
|
178
183
|
// Keep the manager's replay copy in step with the worker's live `context`, and with it
|
|
179
184
|
// whatever a child spawned after this load will inherit.
|
|
180
|
-
onLoaded: (payload) => { sandboxManager.
|
|
185
|
+
onLoaded: (payload) => { sandboxManager.appendContext(payload); },
|
|
181
186
|
})
|
|
182
187
|
: undefined;
|
|
183
|
-
if (
|
|
188
|
+
if (contextBundle) {
|
|
184
189
|
// Re-derive the loaded-prefix cache from the payload that will actually be replayed —
|
|
185
|
-
// clearing it outright would make the host re-clone a
|
|
186
|
-
|
|
187
|
-
|
|
190
|
+
// clearing it outright would make the host re-clone a source the recreated worker already has.
|
|
191
|
+
// Re-plant the cwd sentinel if the seed is still in the payload (un-prefixed files).
|
|
192
|
+
const bundle = contextBundle;
|
|
193
|
+
deps.registerDiscardHook?.(() => {
|
|
194
|
+
const prefixes = contextPrefixesIn(sandboxManager.contextPayload);
|
|
195
|
+
bundle.reset(prefixes);
|
|
196
|
+
if (bundle.seededCwd() !== undefined) bundle.markLoaded("");
|
|
197
|
+
});
|
|
198
|
+
deps.registerContextBundle?.(contextBundle);
|
|
188
199
|
}
|
|
189
200
|
|
|
190
201
|
return {
|
|
@@ -192,14 +203,15 @@ export function createReplTool(deps: ReplToolDeps): ToolDefinition<typeof ReplTo
|
|
|
192
203
|
label: "REPL",
|
|
193
204
|
description:
|
|
194
205
|
"PRIMARY tool for ALL repository reading and analysis (read/grep are disabled in RLM mode). " +
|
|
195
|
-
"Persistent Python sandbox with
|
|
196
|
-
"free primitives search(query) / grep_context(pattern) /
|
|
197
|
-
"semantic reading to map_files / llm_query /
|
|
198
|
-
"(rlm_query for iterative sub-tasks) — stdout
|
|
199
|
-
"so printing file bodies is useless.
|
|
200
|
-
"persist across calls. Also supports
|
|
206
|
+
"Persistent Python sandbox with loaded files in `context` (starts empty; cwd seeds on first " +
|
|
207
|
+
"call). Locate first with the free primitives search(query) / grep_context(pattern) / " +
|
|
208
|
+
"outline(path), then delegate the semantic reading to map_files / llm_query / " +
|
|
209
|
+
"llm_query_batched / llm_query_chunked (rlm_query for iterative sub-tasks) — stdout " +
|
|
210
|
+
"returned to you is hard-capped at 4K chars, so printing file bodies is useless. " +
|
|
211
|
+
"Variables, imports, and the `answers`/`plan` memo persist across calls. Also supports " +
|
|
212
|
+
"add_context for external dirs/files/git URLs and document conversion.",
|
|
201
213
|
promptSnippet:
|
|
202
|
-
"repl: run Python in a persistent sandbox holding
|
|
214
|
+
"repl: run Python in a persistent sandbox holding loaded files in `context`; " +
|
|
203
215
|
"search/grep_context/outline to locate, map_files/llm_query* to read.",
|
|
204
216
|
promptGuidelines: [
|
|
205
217
|
"In RLM mode, read the repository through `repl` only — `read`/`grep` and bash readers are blocked.",
|
|
@@ -271,7 +283,7 @@ export function createReplTool(deps: ReplToolDeps): ToolDefinition<typeof ReplTo
|
|
|
271
283
|
await deps.ensureContext?.();
|
|
272
284
|
await sandboxManager.getOrCreate({
|
|
273
285
|
...subcallHandlers,
|
|
274
|
-
...(
|
|
286
|
+
...(contextBundle?.handlers ?? {}),
|
|
275
287
|
});
|
|
276
288
|
|
|
277
289
|
// Detect queue contention AFTER sandbox init (initPromise settled, isExecuting now accurate)
|
package/src/tool/rlm-tool.ts
CHANGED
|
@@ -31,7 +31,7 @@ const CALL_PREVIEW_CHARS = 80;
|
|
|
31
31
|
|
|
32
32
|
export const RlmToolParams = Object.freeze(Type.Object({
|
|
33
33
|
prompt: Type.String({ description: "The task or question for the RLM engine" }),
|
|
34
|
-
context: Type.Optional(Type.String({ description: "Optional context. If omitted,
|
|
34
|
+
context: Type.Optional(Type.String({ description: "Optional context. If omitted, the working directory is packed into context." })),
|
|
35
35
|
}));
|
|
36
36
|
|
|
37
37
|
// ── Rendering helpers ──
|
package/src/ui/config-panel.ts
CHANGED
|
@@ -20,7 +20,8 @@ const CHOICES = Object.freeze({
|
|
|
20
20
|
rootSamplingMaxTokens: Object.freeze(["4096", "8192", "16384", "32768"]),
|
|
21
21
|
sandboxInitTimeoutMs: Object.freeze(["10000", "30000", "60000", "120000"]),
|
|
22
22
|
requestTimeoutMs: Object.freeze(["2", "5", "10", "20"]),
|
|
23
|
-
|
|
23
|
+
contextLoader: Object.freeze(["on", "off"]),
|
|
24
|
+
autoSeedCwd: Object.freeze(["on", "off"]),
|
|
24
25
|
});
|
|
25
26
|
|
|
26
27
|
function item(id: string, label: string, currentValue: string, values: readonly string[], description: string): SettingItem {
|
|
@@ -49,8 +50,10 @@ export async function showConfigPanel(ctx: ExtensionContext, config: RlmConfig):
|
|
|
49
50
|
item("rootSamplingMaxTokens", "Root model output cap (tok)", String(config.rootSampling?.maxTokens ?? 16384), CHOICES.rootSamplingMaxTokens, "Max output tokens per root-model turn. Lower values keep each turn lean."),
|
|
50
51
|
item("sandboxInitTimeoutMs", "Sandbox init timeout", String(config.sandboxInitTimeoutMs), CHOICES.sandboxInitTimeoutMs, "How long to wait for the Python worker to start."),
|
|
51
52
|
item("requestTimeoutMs", "Sandbox request timeout (min)", String(Math.round(config.requestTimeoutMs / 60_000)), CHOICES.requestTimeoutMs, "Parent-side watchdog per sandbox request; on breach the Python worker is killed."),
|
|
52
|
-
item("
|
|
53
|
-
"Allow
|
|
53
|
+
item("contextLoader", "Context loader", config.contextLoader ? "on" : "off", CHOICES.contextLoader,
|
|
54
|
+
"Allow add_context() to pull an external dir, file, document, or git repo into context."),
|
|
55
|
+
item("autoSeedCwd", "Auto-seed cwd", config.autoSeedCwd ? "on" : "off", CHOICES.autoSeedCwd,
|
|
56
|
+
"Seed the working directory into context on the first repl() call (otherwise starts empty)."),
|
|
54
57
|
item("__save__", "Save & close", "↵", ["↵"], "Save these settings and close (Esc also saves)."),
|
|
55
58
|
];
|
|
56
59
|
|
|
@@ -104,7 +107,8 @@ export function applySetting(config: RlmConfig, id: string, value: string): RlmC
|
|
|
104
107
|
return Object.freeze({ ...config, rootSampling: Object.freeze({ ...config.rootSampling, maxTokens: Number(value) }) });
|
|
105
108
|
case "sandboxInitTimeoutMs": return Object.freeze({ ...config, sandboxInitTimeoutMs: Number(value) });
|
|
106
109
|
case "requestTimeoutMs": return Object.freeze({ ...config, requestTimeoutMs: Number(value) * 60_000 });
|
|
107
|
-
case "
|
|
110
|
+
case "contextLoader": return Object.freeze({ ...config, contextLoader: value === "on" });
|
|
111
|
+
case "autoSeedCwd": return Object.freeze({ ...config, autoSeedCwd: value === "on" });
|
|
108
112
|
default: return config;
|
|
109
113
|
}
|
|
110
114
|
}
|
package/src/bridge/library.ts
DELETED
|
@@ -1,190 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared load_library handler for headless engine and native repl() mode.
|
|
3
|
-
*
|
|
4
|
-
* Host packs the source via resolveLibrarySource (namespaced under lib/<id>/) and returns the
|
|
5
|
-
* payload for the worker to append into the single `context` list.
|
|
6
|
-
*
|
|
7
|
-
* Idempotency is host-side: re-loading a source that was already packed does not re-clone or
|
|
8
|
-
* re-pack, and the prefix set is the only state that decides it.
|
|
9
|
-
*
|
|
10
|
-
* Late-bound deps (getCwd / getEmitter) keep a single handler closure correct
|
|
11
|
-
* across native repl() calls — getOrCreate only installs handlers at spawn.
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
import type { RlmEmitter } from "../tool/rlm-events.ts";
|
|
15
|
-
import type { SubLlmHandlers } from "../sandbox/sandbox.ts";
|
|
16
|
-
import {
|
|
17
|
-
libraryNamespace,
|
|
18
|
-
resolveLibrarySource,
|
|
19
|
-
} from "../context/library-context.ts";
|
|
20
|
-
import { previewText } from "../text/preview.ts";
|
|
21
|
-
|
|
22
|
-
export interface LibraryBridgeOpts {
|
|
23
|
-
/** Fixed cwd (headless). Prefer getCwd when the sandbox outlives a single invocation. */
|
|
24
|
-
readonly cwd?: string;
|
|
25
|
-
/** Late-bound cwd (native mode — sandbox handlers outlive a single repl()). */
|
|
26
|
-
readonly getCwd?: () => string;
|
|
27
|
-
readonly emitter?: RlmEmitter;
|
|
28
|
-
/** Native mode: read the live emitter each call. */
|
|
29
|
-
readonly getEmitter?: () => RlmEmitter | null | undefined;
|
|
30
|
-
readonly parentId?: string;
|
|
31
|
-
readonly signal?: AbortSignal;
|
|
32
|
-
/** Prefixes already present in context — seeds host-side idempotency after a sandbox restart. */
|
|
33
|
-
readonly loadedPrefixes?: readonly string[];
|
|
34
|
-
/**
|
|
35
|
-
* The live context this sandbox holds. Read to refuse pre-flight exactly what the worker's
|
|
36
|
-
* `_append_library` would reject, before any prefix is committed.
|
|
37
|
-
*/
|
|
38
|
-
readonly getContext?: () => unknown;
|
|
39
|
-
/**
|
|
40
|
-
* Post-load hook. The engine grows its live context here; native mode grows
|
|
41
|
-
* SandboxManager.contextPayload.
|
|
42
|
-
*/
|
|
43
|
-
readonly onLoaded?: (payload: unknown) => void | Promise<void>;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export interface LibraryHandlerBundle {
|
|
47
|
-
readonly handlers: Pick<SubLlmHandlers, "loadLibrary">;
|
|
48
|
-
/**
|
|
49
|
-
* Reset the loaded-prefix cache (call when the sandbox is
|
|
50
|
-
* discarded and will re-spawn).
|
|
51
|
-
*
|
|
52
|
-
* `keep` re-seeds the cache from the payload that will be replayed into the fresh worker.
|
|
53
|
-
* `loaded` is a CACHE of `libraryPrefixesIn(context)`, never independent state, so it may only
|
|
54
|
-
* be cleared by re-deriving it — clearing it outright would make the host re-clone and re-pack
|
|
55
|
-
* a library the recreated worker already holds.
|
|
56
|
-
*/
|
|
57
|
-
readonly reset: (keep?: readonly string[]) => void;
|
|
58
|
-
/** Prefixes loaded in this sandbox lifetime (for tests). */
|
|
59
|
-
readonly loadedPrefixes: () => ReadonlySet<string>;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* JS runtime kind → the Python type name worker.py reports, so both sides emit exactly one
|
|
64
|
-
* message for the same refusal. Covers every shape a context payload can take after JSON
|
|
65
|
-
* transport; anything else is a plain object, which `json.load` materializes as a dict.
|
|
66
|
-
*/
|
|
67
|
-
const PY_TYPE_NAME: Readonly<Record<string, string>> = Object.freeze({
|
|
68
|
-
string: "str", boolean: "bool", number: "int", bigint: "int", undefined: "None",
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
function pythonKindOf(value: unknown): string {
|
|
72
|
-
if (value === null) return "None"; // matches worker.py's `if ctx is not None else "None"`
|
|
73
|
-
return PY_TYPE_NAME[typeof value] ?? "dict";
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Refusal messages shared with worker.py `_append_library`. The worker is the backstop; the host
|
|
78
|
-
* pre-flights the same two conditions so it never commits a prefix for an append that
|
|
79
|
-
* will be rejected. Keep the wording identical — a comment in worker.py points back here.
|
|
80
|
-
*/
|
|
81
|
-
const LIST_CONTEXT_REQUIRED = (kind: string): string =>
|
|
82
|
-
`load_library requires list context (file bundle); got ${kind}`;
|
|
83
|
-
const NO_FILES_PRODUCED = "load_library produced no files";
|
|
84
|
-
|
|
85
|
-
export function buildLibraryHandler(opts: LibraryBridgeOpts): LibraryHandlerBundle {
|
|
86
|
-
/** Prefixes already loaded in this sandbox — mirrors the worker's context state. */
|
|
87
|
-
const loaded = new Set<string>(opts.loadedPrefixes ?? []);
|
|
88
|
-
return {
|
|
89
|
-
reset: (keep) => {
|
|
90
|
-
const seed = keep ?? opts.loadedPrefixes ?? [];
|
|
91
|
-
loaded.clear();
|
|
92
|
-
for (const prefix of seed) loaded.add(prefix);
|
|
93
|
-
},
|
|
94
|
-
loadedPrefixes: () => loaded,
|
|
95
|
-
handlers: {
|
|
96
|
-
async loadLibrary(source, depth) {
|
|
97
|
-
const emitter = opts.getEmitter?.() ?? opts.emitter;
|
|
98
|
-
const cwd = opts.getCwd?.() ?? opts.cwd;
|
|
99
|
-
if (cwd === undefined || cwd === "") {
|
|
100
|
-
throw new Error("load_library: no cwd configured");
|
|
101
|
-
}
|
|
102
|
-
const id = emitter?.emitSubcallCreated({
|
|
103
|
-
kind: "tool", parentId: opts.parentId,
|
|
104
|
-
label: "load_library",
|
|
105
|
-
args: previewText(source, 80),
|
|
106
|
-
depth,
|
|
107
|
-
});
|
|
108
|
-
try {
|
|
109
|
-
// Pre-flight the worker's own refusal: a non-list context cannot be appended to, and
|
|
110
|
-
// committing a prefix for it would make the NEXT load lie with already_loaded.
|
|
111
|
-
const current = opts.getContext?.();
|
|
112
|
-
if (current !== undefined && !Array.isArray(current)) {
|
|
113
|
-
throw new Error(LIST_CONTEXT_REQUIRED(pythonKindOf(current)));
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// Cheap pre-check BEFORE cloning/packing: same namespace ⇒ nothing to do.
|
|
117
|
-
const { sourceId: preId, pathPrefix: prefix } = libraryNamespace(source, cwd);
|
|
118
|
-
if (loaded.has(prefix)) {
|
|
119
|
-
if (id) {
|
|
120
|
-
emitter?.emitSubcallUpdated({
|
|
121
|
-
id,
|
|
122
|
-
status: "done",
|
|
123
|
-
resultPreview: `already loaded (${prefix}*)`,
|
|
124
|
-
});
|
|
125
|
-
}
|
|
126
|
-
return {
|
|
127
|
-
payload: Object.freeze([]),
|
|
128
|
-
files: 0,
|
|
129
|
-
chars: 0,
|
|
130
|
-
sourceId: preId,
|
|
131
|
-
pathPrefix: prefix,
|
|
132
|
-
alreadyLoaded: true,
|
|
133
|
-
};
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
const resolved = await resolveLibrarySource(source, cwd, opts.signal);
|
|
137
|
-
if (!resolved.ok) throw new Error(resolved.error);
|
|
138
|
-
const { payload, files, chars, sourceId, pathPrefix } = resolved.value;
|
|
139
|
-
// The worker's other refusal, pre-flighted for the same reason.
|
|
140
|
-
if (payload.length === 0) throw new Error(NO_FILES_PRODUCED);
|
|
141
|
-
|
|
142
|
-
// Race: another concurrent load of the same prefix finished while we packed.
|
|
143
|
-
if (loaded.has(pathPrefix)) {
|
|
144
|
-
if (id) {
|
|
145
|
-
emitter?.emitSubcallUpdated({
|
|
146
|
-
id,
|
|
147
|
-
status: "done",
|
|
148
|
-
resultPreview: `already loaded (${pathPrefix}*)`,
|
|
149
|
-
});
|
|
150
|
-
}
|
|
151
|
-
return {
|
|
152
|
-
payload: Object.freeze([]),
|
|
153
|
-
files: 0,
|
|
154
|
-
chars: 0,
|
|
155
|
-
sourceId,
|
|
156
|
-
pathPrefix,
|
|
157
|
-
alreadyLoaded: true,
|
|
158
|
-
};
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
// Mark loaded only after the host has grown its own copy of the context.
|
|
162
|
-
if (opts.onLoaded) {
|
|
163
|
-
await opts.onLoaded(payload);
|
|
164
|
-
}
|
|
165
|
-
loaded.add(pathPrefix);
|
|
166
|
-
|
|
167
|
-
if (id) {
|
|
168
|
-
emitter?.emitSubcallUpdated({
|
|
169
|
-
id,
|
|
170
|
-
status: "done",
|
|
171
|
-
resultPreview:
|
|
172
|
-
`+${files} file(s) → context (${pathPrefix}*, ${chars.toLocaleString()} chars)`,
|
|
173
|
-
});
|
|
174
|
-
}
|
|
175
|
-
return {
|
|
176
|
-
payload,
|
|
177
|
-
files,
|
|
178
|
-
chars,
|
|
179
|
-
sourceId,
|
|
180
|
-
pathPrefix,
|
|
181
|
-
alreadyLoaded: false,
|
|
182
|
-
};
|
|
183
|
-
} catch (err) {
|
|
184
|
-
if (id) emitter?.emitSubcallUpdated({ id, status: "error", detail: String(err) });
|
|
185
|
-
throw err; // serviceInterrupt catch → {error} reply → "Error: …" in the REPL
|
|
186
|
-
}
|
|
187
|
-
},
|
|
188
|
-
},
|
|
189
|
-
};
|
|
190
|
-
}
|