@hicaru/pi-rlm 0.2.1 → 0.2.2

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.
Files changed (61) hide show
  1. package/README.md +12 -35
  2. package/README.ru.md +18 -23
  3. package/README.zh-CN.md +17 -28
  4. package/package.json +1 -1
  5. package/src/bridge/library.ts +61 -26
  6. package/src/bridge/subcall-handlers.ts +63 -17
  7. package/src/commands/rlm-config.ts +47 -18
  8. package/src/commands/rlm.ts +3 -152
  9. package/src/config/defaults.ts +6 -17
  10. package/src/config/settings.ts +8 -32
  11. package/src/context/library-context.ts +90 -17
  12. package/src/core/engine.ts +55 -335
  13. package/src/core/history.ts +1 -1
  14. package/src/core/limits.ts +5 -12
  15. package/src/core/resource-limits.ts +0 -2
  16. package/src/core/types.ts +3 -36
  17. package/src/index.ts +23 -12
  18. package/src/mode/llm-model.ts +54 -0
  19. package/src/mode/rlm-mode.ts +26 -57
  20. package/src/prompts/glossary.ts +287 -0
  21. package/src/prompts/native.ts +127 -0
  22. package/src/prompts/system.ts +14 -407
  23. package/src/sandbox/context-file.ts +154 -0
  24. package/src/sandbox/interrupts.ts +145 -0
  25. package/src/sandbox/protocol.ts +8 -69
  26. package/src/sandbox/py/guards.py +150 -0
  27. package/src/sandbox/py/retrieval.py +265 -0
  28. package/src/sandbox/py/tasks.py +116 -0
  29. package/src/sandbox/{worker.py → py/worker.py} +76 -696
  30. package/src/sandbox/sandbox-manager.ts +13 -0
  31. package/src/sandbox/sandbox.ts +99 -193
  32. package/src/text/tokens.ts +29 -3
  33. package/src/tool/repl-details.ts +2 -2
  34. package/src/tool/repl-render.ts +58 -0
  35. package/src/tool/repl-result.ts +70 -0
  36. package/src/tool/repl-tool.ts +37 -159
  37. package/src/tool/rlm-aggregator.ts +2 -10
  38. package/src/tool/rlm-details.ts +0 -2
  39. package/src/tool/rlm-events.ts +0 -14
  40. package/src/tool/rlm-tool.ts +1 -12
  41. package/src/ui/config-panel.ts +4 -16
  42. package/src/ui/intro.ts +1 -2
  43. package/src/ui/model-picker.ts +34 -10
  44. package/src/ui/status.ts +3 -7
  45. package/src/util/concurrency.ts +9 -5
  46. package/src/bridge/fallback-todo.ts +0 -148
  47. package/src/bridge/interactive.ts +0 -65
  48. package/src/bridge/pi-interactive.ts +0 -41
  49. package/src/core/artifacts.ts +0 -89
  50. package/src/core/critique.ts +0 -92
  51. package/src/core/gates.ts +0 -301
  52. package/src/core/pipeline-handlers.ts +0 -319
  53. package/src/core/pipeline.ts +0 -268
  54. package/src/prompts/phases.ts +0 -104
  55. package/src/state/index.ts +0 -24
  56. package/src/state/internal.ts +0 -46
  57. package/src/state/paths.ts +0 -44
  58. package/src/state/reads.ts +0 -133
  59. package/src/state/resume.ts +0 -173
  60. package/src/state/rows.ts +0 -123
  61. package/src/state/writes.ts +0 -58
@@ -31,7 +31,7 @@ const execFileP = promisify(execFile);
31
31
  /** Single-file sources above this must use open() + llm_query_chunked in the REPL. */
32
32
  export const MAX_LIBRARY_FILE_BYTES = 8 * 1024 * 1024;
33
33
 
34
- /** Legacy catch-all prefix for pre-namespace string sidecars — never an identity key. */
34
+ /** Catch-all prefix for a raw string payload with no namespace — never an identity key. */
35
35
  const LEGACY_UNKNOWN_PREFIX = "lib/unknown/";
36
36
 
37
37
  export interface LibrarySource {
@@ -150,23 +150,100 @@ export function namespaceLibraryFiles(
150
150
  return namespaceLibraryFilesWithChars(payload, sourceId).files;
151
151
  }
152
152
 
153
+ /** The one `lib/<id>/` matcher. Never re-declare this regex; use the helpers below. */
154
+ const LIB_PREFIX_RE = /^(lib\/[^/]+\/)/;
155
+
156
+ /** Narrow an unknown context entry to a ContextFile. Type guard, never a cast. */
157
+ export function isContextFile(entry: unknown): entry is ContextFile {
158
+ if (entry === null || typeof entry !== "object") return false;
159
+ return "path" in entry && typeof entry.path === "string"
160
+ && "content" in entry && typeof entry.content === "string";
161
+ }
162
+
163
+ /** `path` of a context entry, or undefined when the entry is not file-shaped. */
164
+ export function contextEntryPath(entry: unknown): string | undefined {
165
+ return isContextFile(entry) ? entry.path : undefined;
166
+ }
167
+
168
+ /** The `lib/<id>/` prefix owning this path, or undefined. Skips the legacy catch-all. */
169
+ function libPrefixOf(path: string): string | undefined {
170
+ const prefix = LIB_PREFIX_RE.exec(path)?.[1];
171
+ // `lib/unknown/` is the legacy catch-all: never treat it as an identity.
172
+ return prefix === undefined || prefix === LEGACY_UNKNOWN_PREFIX ? undefined : prefix;
173
+ }
174
+
153
175
  /** First `lib/<id>/` prefix found in the payload, or undefined. Skips `lib/unknown/`. */
154
176
  export function payloadPrefix(payload: readonly unknown[]): string | undefined {
155
177
  for (let i = 0; i < payload.length; i++) {
156
- const item = payload[i];
157
- if (item === null || typeof item !== "object") continue;
158
- const path = (item as { path?: unknown }).path;
159
- if (typeof path !== "string") continue;
160
- const m = /^(lib\/[^/]+\/)/.exec(path);
161
- // `lib/unknown/` is the legacy catch-all: never treat it as an identity.
162
- if (m?.[1] !== undefined && m[1] !== LEGACY_UNKNOWN_PREFIX) return m[1];
178
+ const path = contextEntryPath(payload[i]);
179
+ if (path === undefined) continue;
180
+ const prefix = libPrefixOf(path);
181
+ if (prefix !== undefined) return prefix;
163
182
  }
164
183
  return undefined;
165
184
  }
166
185
 
167
186
  /**
168
- * Append a library payload into an existing list context (host-side resume merge).
169
- * Skips a payload whose path prefix is already present (resume-safe dedup).
187
+ * Every distinct `lib/<id>/` prefix present in a context payload.
188
+ *
189
+ * The loaded-prefix set in bridge/library.ts is a CACHE of this — derived state, never
190
+ * independent state, so it may only be cleared by re-deriving it from the live payload.
191
+ */
192
+ export function libraryPrefixesIn(context: unknown): readonly string[] {
193
+ if (!Array.isArray(context)) return Object.freeze([]);
194
+ const seen = new Set<string>();
195
+ for (let i = 0; i < context.length; i++) {
196
+ const path = contextEntryPath(context[i]);
197
+ if (path === undefined) continue;
198
+ const prefix = libPrefixOf(path);
199
+ if (prefix !== undefined) seen.add(prefix);
200
+ }
201
+ return Object.freeze(Array.from(seen));
202
+ }
203
+
204
+ export interface FilteredContext {
205
+ readonly files: readonly ContextFile[];
206
+ /** Prefixes that selected zero files — the caller decides whether that is fatal. */
207
+ readonly unmatched: readonly string[];
208
+ }
209
+
210
+ /**
211
+ * Narrow a context payload to entries under any of `prefixes` (plain prefix match, no globs).
212
+ *
213
+ * Backs `rlm_query(prompt, paths=[…])`. Prefix-only is deliberate: the sandbox's own filters use
214
+ * Python `fnmatch`, which has no host-side equivalent here, and a subtree prefix is what callers
215
+ * actually want — the child can still `search()` inside the slice.
216
+ */
217
+ export function filterContextByPaths(context: unknown, prefixes: readonly string[]): FilteredContext {
218
+ if (!Array.isArray(context) || prefixes.length === 0) {
219
+ return Object.freeze({ files: Object.freeze([]), unmatched: Object.freeze(Array.from(prefixes)) });
220
+ }
221
+ const hit = new Array<boolean>(prefixes.length).fill(false);
222
+ const out = new Array<ContextFile>(context.length); // pre-allocated, trimmed once below
223
+ let n = 0;
224
+ for (let i = 0; i < context.length; i++) {
225
+ const entry: unknown = context[i];
226
+ if (!isContextFile(entry)) continue;
227
+ for (let p = 0; p < prefixes.length; p++) {
228
+ if (!entry.path.startsWith(prefixes[p])) continue;
229
+ hit[p] = true;
230
+ out[n++] = entry;
231
+ break;
232
+ }
233
+ }
234
+ out.length = n;
235
+ const unmatched = new Array<string>(prefixes.length); // pre-allocated, no .push()
236
+ let u = 0;
237
+ for (let p = 0; p < prefixes.length; p++) {
238
+ if (!hit[p]) unmatched[u++] = prefixes[p];
239
+ }
240
+ unmatched.length = u;
241
+ return Object.freeze({ files: Object.freeze(out), unmatched: Object.freeze(unmatched) });
242
+ }
243
+
244
+ /**
245
+ * Append a library payload into an existing list context.
246
+ * Skips a payload whose path prefix is already present, so a repeat load is a no-op.
170
247
  */
171
248
  export function mergeLibraryIntoContext(base: unknown, libraryPayload: unknown): unknown {
172
249
  if (!Array.isArray(base)) return base;
@@ -175,12 +252,8 @@ export function mergeLibraryIntoContext(base: unknown, libraryPayload: unknown):
175
252
  const prefix = payloadPrefix(libraryPayload);
176
253
  if (prefix !== undefined) {
177
254
  for (let i = 0; i < base.length; i++) {
178
- const item = base[i];
179
- if (item !== null && typeof item === "object"
180
- && typeof (item as { path?: unknown }).path === "string"
181
- && (item as { path: string }).path.startsWith(prefix)) {
182
- return base; // already present
183
- }
255
+ const path = contextEntryPath(base[i]);
256
+ if (path !== undefined && path.startsWith(prefix)) return base; // already present
184
257
  }
185
258
  }
186
259
  const merged = new Array<unknown>(base.length + libraryPayload.length);
@@ -189,7 +262,7 @@ export function mergeLibraryIntoContext(base: unknown, libraryPayload: unknown):
189
262
  return merged;
190
263
  }
191
264
  if (typeof libraryPayload === "string") {
192
- // Legacy string sidecars: wrap once under an unknown prefix.
265
+ // Raw string payload: wrap once under the unknown prefix.
193
266
  return mergeLibraryIntoContext(base, namespaceLibraryFiles(libraryPayload, "unknown"));
194
267
  }
195
268
  return base;