@oh-my-pi/pi-coding-agent 15.7.2 → 15.7.3
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 +75 -6
- package/dist/types/cli/args.d.ts +1 -1
- package/dist/types/cli/extension-flags.d.ts +36 -0
- package/dist/types/config/config-file.d.ts +4 -0
- package/dist/types/config/file-lock.d.ts +23 -0
- package/dist/types/config/keybindings.d.ts +2 -1
- package/dist/types/config/model-registry.d.ts +6 -0
- package/dist/types/config/settings-schema.d.ts +88 -65
- package/dist/types/edit/hashline/diff.d.ts +3 -3
- package/dist/types/eval/__tests__/agent-bridge.test.d.ts +1 -0
- package/dist/types/eval/__tests__/budget-bridge.test.d.ts +1 -0
- package/dist/types/eval/__tests__/idle-timeout.test.d.ts +1 -0
- package/dist/types/eval/agent-bridge.d.ts +25 -0
- package/dist/types/eval/backend.d.ts +17 -2
- package/dist/types/eval/budget-bridge.d.ts +29 -0
- package/dist/types/eval/idle-timeout.d.ts +28 -0
- package/dist/types/eval/js/executor.d.ts +8 -0
- package/dist/types/eval/js/tool-bridge.d.ts +2 -1
- package/dist/types/eval/py/executor.d.ts +13 -0
- package/dist/types/exec/bash-executor.d.ts +1 -0
- package/dist/types/extensibility/custom-tools/types.d.ts +2 -2
- package/dist/types/extensibility/extensions/runner.d.ts +7 -0
- package/dist/types/extensibility/plugins/git-url.d.ts +11 -1
- package/dist/types/extensibility/plugins/manager.d.ts +12 -1
- package/dist/types/extensibility/shared-events.d.ts +2 -2
- package/dist/types/memory-backend/index.d.ts +1 -1
- package/dist/types/memory-backend/resolve.d.ts +1 -1
- package/dist/types/memory-backend/types.d.ts +3 -3
- package/dist/types/mnemopi/backend.d.ts +4 -0
- package/dist/types/mnemopi/config.d.ts +29 -0
- package/dist/types/mnemopi/state.d.ts +72 -0
- package/dist/types/modes/components/custom-editor.d.ts +2 -2
- package/dist/types/modes/components/omfg-panel.d.ts +19 -0
- package/dist/types/modes/controllers/command-controller.d.ts +7 -0
- package/dist/types/modes/controllers/input-controller.d.ts +1 -3
- package/dist/types/modes/controllers/omfg-controller.d.ts +10 -0
- package/dist/types/modes/controllers/omfg-rule.d.ts +26 -0
- package/dist/types/modes/gradient-highlight.d.ts +5 -1
- package/dist/types/modes/interactive-mode.d.ts +7 -3
- package/dist/types/modes/magic-keywords.d.ts +14 -0
- package/dist/types/modes/markdown-prose.d.ts +27 -0
- package/dist/types/modes/orchestrate.d.ts +7 -2
- package/dist/types/modes/shared.d.ts +1 -1
- package/dist/types/modes/turn-budget.d.ts +18 -0
- package/dist/types/modes/types.d.ts +7 -3
- package/dist/types/modes/ultrathink.d.ts +7 -2
- package/dist/types/modes/workflow.d.ts +15 -0
- package/dist/types/sdk.d.ts +13 -3
- package/dist/types/session/agent-session.d.ts +40 -17
- package/dist/types/session/session-manager.d.ts +18 -0
- package/dist/types/session/session-storage.d.ts +6 -0
- package/dist/types/session/shake-types.d.ts +24 -0
- package/dist/types/task/executor.d.ts +2 -2
- package/dist/types/tiny/models.d.ts +15 -1
- package/dist/types/tiny/title-protocol.d.ts +4 -0
- package/dist/types/tools/index.d.ts +19 -3
- package/dist/types/tools/memory-edit.d.ts +1 -1
- package/examples/sdk/09-api-keys-and-oauth.ts +2 -2
- package/package.json +10 -10
- package/src/autoresearch/tools/run-experiment.ts +45 -113
- package/src/cli/args.ts +39 -16
- package/src/cli/extension-flags.ts +48 -0
- package/src/cli/plugin-cli.ts +11 -2
- package/src/config/config-file.ts +98 -13
- package/src/config/file-lock.ts +60 -17
- package/src/config/keybindings.ts +78 -27
- package/src/config/model-registry.ts +7 -1
- package/src/config/settings-schema.ts +94 -67
- package/src/config/settings.ts +12 -0
- package/src/edit/hashline/diff.ts +81 -24
- package/src/edit/renderer.ts +16 -12
- package/src/eval/__tests__/agent-bridge.test.ts +433 -0
- package/src/eval/__tests__/budget-bridge.test.ts +69 -0
- package/src/eval/__tests__/idle-timeout.test.ts +66 -0
- package/src/eval/__tests__/shared-executors.test.ts +21 -0
- package/src/eval/agent-bridge.ts +295 -0
- package/src/eval/backend.ts +17 -2
- package/src/eval/budget-bridge.ts +48 -0
- package/src/eval/idle-timeout.ts +80 -0
- package/src/eval/js/executor.ts +35 -7
- package/src/eval/js/index.ts +2 -1
- package/src/eval/js/shared/prelude.txt +85 -1
- package/src/eval/js/tool-bridge.ts +9 -0
- package/src/eval/py/executor.ts +41 -14
- package/src/eval/py/index.ts +2 -1
- package/src/eval/py/prelude.py +132 -1
- package/src/exec/bash-executor.ts +2 -3
- package/src/extensibility/custom-tools/types.ts +2 -2
- package/src/extensibility/extensions/runner.ts +12 -2
- package/src/extensibility/plugins/git-url.ts +90 -4
- package/src/extensibility/plugins/manager.ts +103 -7
- package/src/extensibility/shared-events.ts +2 -2
- package/src/internal-urls/docs-index.generated.ts +88 -88
- package/src/main.ts +44 -55
- package/src/memory-backend/index.ts +1 -1
- package/src/memory-backend/resolve.ts +3 -3
- package/src/memory-backend/types.ts +3 -3
- package/src/{mnemosyne → mnemopi}/backend.ts +70 -70
- package/src/{mnemosyne → mnemopi}/config.ts +36 -36
- package/src/{mnemosyne → mnemopi}/state.ts +67 -67
- package/src/modes/components/agent-dashboard.ts +6 -6
- package/src/modes/components/custom-editor.ts +4 -11
- package/src/modes/components/extensions/state-manager.ts +3 -4
- package/src/modes/components/footer.ts +8 -9
- package/src/modes/components/hook-selector.ts +86 -20
- package/src/modes/components/oauth-selector.ts +93 -21
- package/src/modes/components/omfg-panel.ts +141 -0
- package/src/modes/components/settings-defs.ts +2 -2
- package/src/modes/components/tips.txt +2 -1
- package/src/modes/components/tool-execution.ts +38 -19
- package/src/modes/components/tree-selector.ts +4 -3
- package/src/modes/components/user-message-selector.ts +94 -19
- package/src/modes/components/user-message.ts +8 -1
- package/src/modes/controllers/command-controller.ts +57 -0
- package/src/modes/controllers/event-controller.ts +60 -2
- package/src/modes/controllers/input-controller.ts +14 -11
- package/src/modes/controllers/omfg-controller.ts +283 -0
- package/src/modes/controllers/omfg-rule.ts +647 -0
- package/src/modes/controllers/selector-controller.ts +1 -0
- package/src/modes/gradient-highlight.ts +23 -6
- package/src/modes/interactive-mode.ts +41 -7
- package/src/modes/magic-keywords.ts +20 -0
- package/src/modes/markdown-prose.ts +247 -0
- package/src/modes/orchestrate.ts +17 -11
- package/src/modes/shared.ts +3 -11
- package/src/modes/turn-budget.ts +31 -0
- package/src/modes/types.ts +7 -1
- package/src/modes/ultrathink.ts +16 -10
- package/src/modes/utils/hotkeys-markdown.ts +1 -1
- package/src/modes/workflow.ts +42 -0
- package/src/prompts/system/omfg-user.md +51 -0
- package/src/prompts/system/system-prompt.md +1 -0
- package/src/prompts/system/workflow-notice.md +70 -0
- package/src/prompts/tools/eval.md +13 -1
- package/src/prompts/tools/memory-edit.md +1 -1
- package/src/sdk.ts +63 -33
- package/src/session/agent-session.ts +373 -56
- package/src/session/session-manager.ts +32 -0
- package/src/session/session-storage.ts +68 -8
- package/src/session/shake-types.ts +44 -0
- package/src/slash-commands/builtin-registry.ts +41 -16
- package/src/task/executor.ts +3 -3
- package/src/task/index.ts +6 -6
- package/src/tiny/models.ts +30 -2
- package/src/tiny/title-protocol.ts +11 -1
- package/src/tiny/worker.ts +19 -7
- package/src/tools/eval.ts +202 -26
- package/src/tools/grouped-file-output.ts +9 -2
- package/src/tools/index.ts +17 -5
- package/src/tools/memory-edit.ts +4 -4
- package/src/tools/memory-recall.ts +5 -5
- package/src/tools/memory-reflect.ts +5 -5
- package/src/tools/memory-retain.ts +4 -4
- package/src/tools/render-utils.ts +2 -1
- package/src/tools/search.ts +480 -76
- package/dist/types/mnemosyne/backend.d.ts +0 -4
- package/dist/types/mnemosyne/config.d.ts +0 -29
- package/dist/types/mnemosyne/state.d.ts +0 -72
- /package/dist/types/{mnemosyne → mnemopi}/index.d.ts +0 -0
- /package/src/{mnemosyne → mnemopi}/index.ts +0 -0
|
@@ -1837,6 +1837,12 @@ export class SessionManager {
|
|
|
1837
1837
|
premiumRequests: 0,
|
|
1838
1838
|
cost: 0,
|
|
1839
1839
|
} satisfies UsageStatistics;
|
|
1840
|
+
/** Per-turn output-token budget set by a `+Nk` directive (total null when none this turn). */
|
|
1841
|
+
#turnBudget: { total: number | null; hard: boolean } = { total: null, hard: false };
|
|
1842
|
+
/** Cumulative `output` snapshot captured when the current turn budget window opened. */
|
|
1843
|
+
#turnBaselineOutput = 0;
|
|
1844
|
+
/** Output tokens consumed by eval-spawned subagents in the current turn window. */
|
|
1845
|
+
#turnEvalOutput = 0;
|
|
1840
1846
|
#persistWriter: NdjsonFileWriter | undefined;
|
|
1841
1847
|
#persistWriterPath: string | undefined;
|
|
1842
1848
|
#persistChain: Promise<void> = Promise.resolve();
|
|
@@ -2397,6 +2403,32 @@ export class SessionManager {
|
|
|
2397
2403
|
return this.#usageStatistics;
|
|
2398
2404
|
}
|
|
2399
2405
|
|
|
2406
|
+
/**
|
|
2407
|
+
* Open a new per-turn budget window: snapshot the cumulative output baseline,
|
|
2408
|
+
* reset the eval-subagent counter, and set the (optional) ceiling. Called once
|
|
2409
|
+
* per real user message; `total` is null when no `+Nk` directive was present.
|
|
2410
|
+
*/
|
|
2411
|
+
beginTurnBudget(total: number | null, hard: boolean): void {
|
|
2412
|
+
this.#turnBudget = { total, hard };
|
|
2413
|
+
this.#turnBaselineOutput = this.#usageStatistics.output;
|
|
2414
|
+
this.#turnEvalOutput = 0;
|
|
2415
|
+
}
|
|
2416
|
+
|
|
2417
|
+
/** Record output tokens consumed by an eval-spawned subagent in the current turn. */
|
|
2418
|
+
recordEvalSubagentOutput(output: number): void {
|
|
2419
|
+
if (Number.isFinite(output) && output > 0) this.#turnEvalOutput += output;
|
|
2420
|
+
}
|
|
2421
|
+
|
|
2422
|
+
/**
|
|
2423
|
+
* Current turn budget for the eval `budget` helper: the ceiling (null = none),
|
|
2424
|
+
* output tokens spent this turn (main loop + eval-spawned subagents, no
|
|
2425
|
+
* double-count), and whether the ceiling is hard.
|
|
2426
|
+
*/
|
|
2427
|
+
getTurnBudget(): { total: number | null; spent: number; hard: boolean } {
|
|
2428
|
+
const mainDelta = Math.max(0, this.#usageStatistics.output - this.#turnBaselineOutput);
|
|
2429
|
+
return { total: this.#turnBudget.total, spent: mainDelta + this.#turnEvalOutput, hard: this.#turnBudget.hard };
|
|
2430
|
+
}
|
|
2431
|
+
|
|
2400
2432
|
getSessionDir(): string {
|
|
2401
2433
|
return this.sessionDir;
|
|
2402
2434
|
}
|
|
@@ -272,8 +272,8 @@ class MemorySessionStorageWriter implements SessionStorageWriter {
|
|
|
272
272
|
if (this.#closed) throw new Error("Writer closed");
|
|
273
273
|
if (this.#error) throw this.#error;
|
|
274
274
|
try {
|
|
275
|
-
|
|
276
|
-
this.#storage.
|
|
275
|
+
// O(1) chunked append — see MemorySessionStorage.appendChunkSync.
|
|
276
|
+
this.#storage.appendChunkSync(this.#path, line);
|
|
277
277
|
} catch (err) {
|
|
278
278
|
throw this.#recordError(err);
|
|
279
279
|
}
|
|
@@ -302,8 +302,26 @@ class MemorySessionStorageWriter implements SessionStorageWriter {
|
|
|
302
302
|
}
|
|
303
303
|
}
|
|
304
304
|
|
|
305
|
+
/**
|
|
306
|
+
* Mirror entry stored per path. Chunks accumulate via O(1) `push` on
|
|
307
|
+
* `appendChunkSync`; readers materialise into a single string lazily.
|
|
308
|
+
* `byteLen` is kept in sync so `statSync` is O(1) (and returns true UTF-8
|
|
309
|
+
* bytes, not character count).
|
|
310
|
+
*/
|
|
311
|
+
interface MirrorEntry {
|
|
312
|
+
chunks: string[];
|
|
313
|
+
byteLen: number;
|
|
314
|
+
mtimeMs: number;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function materialiseMirror(entry: MirrorEntry): string {
|
|
318
|
+
if (entry.chunks.length === 0) return "";
|
|
319
|
+
if (entry.chunks.length === 1) return entry.chunks[0];
|
|
320
|
+
return entry.chunks.join("");
|
|
321
|
+
}
|
|
322
|
+
|
|
305
323
|
export class MemorySessionStorage implements SessionStorage {
|
|
306
|
-
#files = new Map<string,
|
|
324
|
+
#files = new Map<string, MirrorEntry>();
|
|
307
325
|
|
|
308
326
|
ensureDirSync(_dir: string): void {
|
|
309
327
|
// No-op for in-memory storage.
|
|
@@ -314,20 +332,40 @@ export class MemorySessionStorage implements SessionStorage {
|
|
|
314
332
|
}
|
|
315
333
|
|
|
316
334
|
writeTextSync(path: string, content: string): void {
|
|
317
|
-
this.#files.set(path, {
|
|
335
|
+
this.#files.set(path, {
|
|
336
|
+
chunks: content.length === 0 ? [] : [content],
|
|
337
|
+
byteLen: Buffer.byteLength(content, "utf-8"),
|
|
338
|
+
mtimeMs: Date.now(),
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Internal O(1) append used by {@link MemorySessionStorageWriter}. Lazily
|
|
344
|
+
* creates the entry. External callers should go through `openWriter()`
|
|
345
|
+
* rather than touching the mirror directly.
|
|
346
|
+
*/
|
|
347
|
+
appendChunkSync(path: string, chunk: string): void {
|
|
348
|
+
let entry = this.#files.get(path);
|
|
349
|
+
if (!entry) {
|
|
350
|
+
entry = { chunks: [], byteLen: 0, mtimeMs: Date.now() };
|
|
351
|
+
this.#files.set(path, entry);
|
|
352
|
+
}
|
|
353
|
+
entry.chunks.push(chunk);
|
|
354
|
+
entry.byteLen += Buffer.byteLength(chunk, "utf-8");
|
|
355
|
+
entry.mtimeMs = Date.now();
|
|
318
356
|
}
|
|
319
357
|
|
|
320
358
|
readTextSync(path: string): string {
|
|
321
359
|
const entry = this.#files.get(path);
|
|
322
360
|
if (!entry) throw new Error(`File not found: ${path}`);
|
|
323
|
-
return entry
|
|
361
|
+
return materialiseMirror(entry);
|
|
324
362
|
}
|
|
325
363
|
|
|
326
364
|
statSync(path: string): SessionStorageStat {
|
|
327
365
|
const entry = this.#files.get(path);
|
|
328
366
|
if (!entry) throw new Error(`File not found: ${path}`);
|
|
329
367
|
return {
|
|
330
|
-
size: entry.
|
|
368
|
+
size: entry.byteLen,
|
|
331
369
|
mtimeMs: entry.mtimeMs,
|
|
332
370
|
mtime: new Date(entry.mtimeMs),
|
|
333
371
|
};
|
|
@@ -353,13 +391,35 @@ export class MemorySessionStorage implements SessionStorage {
|
|
|
353
391
|
readText(path: string): Promise<string> {
|
|
354
392
|
const entry = this.#files.get(path);
|
|
355
393
|
if (!entry) return Promise.reject(new Error(`File not found: ${path}`));
|
|
356
|
-
return Promise.resolve(entry
|
|
394
|
+
return Promise.resolve(materialiseMirror(entry));
|
|
357
395
|
}
|
|
358
396
|
|
|
359
397
|
readTextPrefix(path: string, maxBytes: number): Promise<string> {
|
|
360
398
|
const entry = this.#files.get(path);
|
|
361
399
|
if (!entry) return Promise.reject(new Error(`File not found: ${path}`));
|
|
362
|
-
|
|
400
|
+
if (entry.chunks.length === 0 || maxBytes <= 0) return Promise.resolve("");
|
|
401
|
+
|
|
402
|
+
// Walk chunks until the byte budget is exhausted. Avoids materialising
|
|
403
|
+
// the full mirror just to slice a prefix — bounded work for big files.
|
|
404
|
+
let accumulatedBytes = 0;
|
|
405
|
+
const out: string[] = [];
|
|
406
|
+
for (const chunk of entry.chunks) {
|
|
407
|
+
const chunkBytes = Buffer.byteLength(chunk, "utf-8");
|
|
408
|
+
if (accumulatedBytes + chunkBytes <= maxBytes) {
|
|
409
|
+
out.push(chunk);
|
|
410
|
+
accumulatedBytes += chunkBytes;
|
|
411
|
+
if (accumulatedBytes === maxBytes) break;
|
|
412
|
+
continue;
|
|
413
|
+
}
|
|
414
|
+
// Boundary chunk: slice in byte space and decode. Result MAY be
|
|
415
|
+
// shorter than the budget if a multi-byte codepoint straddles the
|
|
416
|
+
// boundary — matches `peekFile` semantics (partial decode at cap).
|
|
417
|
+
const remainingBytes = maxBytes - accumulatedBytes;
|
|
418
|
+
const utf8 = Buffer.from(chunk, "utf-8");
|
|
419
|
+
out.push(utf8Decoder.decode(utf8.subarray(0, remainingBytes)));
|
|
420
|
+
break;
|
|
421
|
+
}
|
|
422
|
+
return Promise.resolve(out.join(""));
|
|
363
423
|
}
|
|
364
424
|
|
|
365
425
|
writeText(path: string, content: string): Promise<void> {
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public shape of the `shake` operation, kept in a dependency-free leaf module
|
|
3
|
+
* so slash-command registries and controllers can import `formatShakeSummary`
|
|
4
|
+
* without pulling in the heavy `agent-session` module graph (which would form
|
|
5
|
+
* an import cycle through the slash-command registry).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/** Mode selector for `AgentSession.shake`. */
|
|
9
|
+
export type ShakeMode = "elide" | "summary" | "images";
|
|
10
|
+
|
|
11
|
+
/** Outcome of an `AgentSession.shake` run. */
|
|
12
|
+
export interface ShakeResult {
|
|
13
|
+
mode: ShakeMode;
|
|
14
|
+
/** Whole tool-call results dropped/compressed. */
|
|
15
|
+
toolResultsDropped: number;
|
|
16
|
+
/** Large fenced/XML blocks dropped/compressed. */
|
|
17
|
+
blocksDropped: number;
|
|
18
|
+
/** Image blocks removed (images mode only). */
|
|
19
|
+
imagesDropped?: number;
|
|
20
|
+
/** Estimated context tokens reclaimed. */
|
|
21
|
+
tokensFreed: number;
|
|
22
|
+
/** Session artifact holding the dropped originals, when persisted. */
|
|
23
|
+
artifactId?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** One-line operator summary of a {@link ShakeResult} (shared by TUI + ACP). */
|
|
27
|
+
export function formatShakeSummary(result: ShakeResult): string {
|
|
28
|
+
if (result.mode === "images") {
|
|
29
|
+
const n = result.imagesDropped ?? 0;
|
|
30
|
+
return n === 0
|
|
31
|
+
? "No images found in this session."
|
|
32
|
+
: `Dropped ${n} image${n === 1 ? "" : "s"} from this session.`;
|
|
33
|
+
}
|
|
34
|
+
const parts: string[] = [];
|
|
35
|
+
if (result.toolResultsDropped > 0) {
|
|
36
|
+
parts.push(`${result.toolResultsDropped} tool result${result.toolResultsDropped === 1 ? "" : "s"}`);
|
|
37
|
+
}
|
|
38
|
+
if (result.blocksDropped > 0) {
|
|
39
|
+
parts.push(`${result.blocksDropped} block${result.blocksDropped === 1 ? "" : "s"}`);
|
|
40
|
+
}
|
|
41
|
+
if (parts.length === 0) return "Nothing to shake.";
|
|
42
|
+
const verb = result.mode === "summary" ? "Compressed" : "Shook";
|
|
43
|
+
return `${verb} ${parts.join(" + ")} (~${result.tokensFreed} tokens freed).`;
|
|
44
|
+
}
|
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
} from "../extensibility/plugins/marketplace";
|
|
22
22
|
import { resolveMemoryBackend } from "../memory-backend";
|
|
23
23
|
import type { InteractiveModeContext } from "../modes/types";
|
|
24
|
+
import { formatShakeSummary, type ShakeMode } from "../session/shake-types";
|
|
24
25
|
import { getChangelogPath, parseChangelog } from "../utils/changelog";
|
|
25
26
|
import { buildContextReportText } from "./helpers/context-report";
|
|
26
27
|
import { formatDuration } from "./helpers/format";
|
|
@@ -57,6 +58,15 @@ const shutdownHandlerTui = (_command: ParsedSlashCommand, runtime: TuiSlashComma
|
|
|
57
58
|
return commandConsumed();
|
|
58
59
|
};
|
|
59
60
|
|
|
61
|
+
/** Parse the `/shake` subcommand into a {@link ShakeMode}; empty defaults to elide. */
|
|
62
|
+
function parseShakeMode(args: string): ShakeMode | { error: string } {
|
|
63
|
+
const verb = args.trim().toLowerCase();
|
|
64
|
+
if (verb === "" || verb === "elide") return "elide";
|
|
65
|
+
if (verb === "summary") return "summary";
|
|
66
|
+
if (verb === "images") return "images";
|
|
67
|
+
return { error: `Unknown /shake mode "${verb}". Use elide, summary, or images.` };
|
|
68
|
+
}
|
|
69
|
+
|
|
60
70
|
const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<SlashCommandSpec> = [
|
|
61
71
|
{
|
|
62
72
|
name: "settings",
|
|
@@ -811,27 +821,31 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<SlashCommandSpec> = [
|
|
|
811
821
|
},
|
|
812
822
|
},
|
|
813
823
|
{
|
|
814
|
-
name: "
|
|
815
|
-
description: "
|
|
816
|
-
acpDescription: "
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
+
name: "shake",
|
|
825
|
+
description: "Drop heavy content from context (tool results, large blocks)",
|
|
826
|
+
acpDescription: "Shake heavy content out of the conversation context",
|
|
827
|
+
subcommands: [
|
|
828
|
+
{ name: "elide", description: "Strip tool results + large blocks (default)" },
|
|
829
|
+
{ name: "summary", description: "Compress heavy regions with a local on-device model" },
|
|
830
|
+
{ name: "images", description: "Strip image blocks" },
|
|
831
|
+
],
|
|
832
|
+
acpInputHint: "[elide|summary|images]",
|
|
833
|
+
allowArgs: true,
|
|
834
|
+
handle: async (command, runtime) => {
|
|
835
|
+
const mode = parseShakeMode(command.args);
|
|
836
|
+
if (typeof mode !== "string") return usage(mode.error, runtime);
|
|
837
|
+
const result = await runtime.session.shake(mode);
|
|
838
|
+
await runtime.output(formatShakeSummary(result));
|
|
824
839
|
return commandConsumed();
|
|
825
840
|
},
|
|
826
|
-
handleTui: async (
|
|
841
|
+
handleTui: async (command, runtime) => {
|
|
827
842
|
runtime.ctx.editor.setText("");
|
|
828
|
-
const
|
|
829
|
-
if (
|
|
830
|
-
runtime.ctx.
|
|
843
|
+
const mode = parseShakeMode(command.args);
|
|
844
|
+
if (typeof mode !== "string") {
|
|
845
|
+
runtime.ctx.showWarning(mode.error);
|
|
831
846
|
return;
|
|
832
847
|
}
|
|
833
|
-
runtime.ctx.
|
|
834
|
-
runtime.ctx.showStatus(`Dropped ${removed} image${removed === 1 ? "" : "s"} from this session.`);
|
|
848
|
+
await runtime.ctx.handleShakeCommand(mode);
|
|
835
849
|
},
|
|
836
850
|
},
|
|
837
851
|
{
|
|
@@ -864,6 +878,17 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<SlashCommandSpec> = [
|
|
|
864
878
|
await runtime.ctx.handleBtwCommand(question);
|
|
865
879
|
},
|
|
866
880
|
},
|
|
881
|
+
{
|
|
882
|
+
name: "omfg",
|
|
883
|
+
description: "Forge a TTSR rule from a complaint to stop a recurring behavior",
|
|
884
|
+
inlineHint: "<complaint>",
|
|
885
|
+
allowArgs: true,
|
|
886
|
+
handleTui: async (command, runtime) => {
|
|
887
|
+
const complaint = command.text.slice(`/${command.name}`.length).trim();
|
|
888
|
+
runtime.ctx.editor.setText("");
|
|
889
|
+
await runtime.ctx.handleOmfgCommand(complaint);
|
|
890
|
+
},
|
|
891
|
+
},
|
|
867
892
|
{
|
|
868
893
|
name: "retry",
|
|
869
894
|
description: "Retry the last failed agent turn",
|
package/src/task/executor.ts
CHANGED
|
@@ -21,7 +21,7 @@ import type { HindsightSessionState } from "../hindsight/state";
|
|
|
21
21
|
import type { LocalProtocolOptions } from "../internal-urls";
|
|
22
22
|
import { callTool } from "../mcp/client";
|
|
23
23
|
import type { MCPManager } from "../mcp/manager";
|
|
24
|
-
import type {
|
|
24
|
+
import type { MnemopiSessionState } from "../mnemopi/state";
|
|
25
25
|
import subagentSystemPromptTemplate from "../prompts/system/subagent-system-prompt.md" with { type: "text" };
|
|
26
26
|
import submitReminderTemplate from "../prompts/system/subagent-yield-reminder.md" with { type: "text" };
|
|
27
27
|
import { AgentRegistry } from "../registry/agent-registry";
|
|
@@ -186,7 +186,7 @@ export interface ExecutorOptions {
|
|
|
186
186
|
*/
|
|
187
187
|
parentArtifactManager?: ArtifactManager;
|
|
188
188
|
parentHindsightSessionState?: HindsightSessionState;
|
|
189
|
-
|
|
189
|
+
parentMnemopiSessionState?: MnemopiSessionState;
|
|
190
190
|
/** Parent agent's eval executor session id. Subagents reuse it so eval state is shared. */
|
|
191
191
|
parentEvalSessionId?: string;
|
|
192
192
|
/**
|
|
@@ -1245,7 +1245,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
1245
1245
|
spawns: spawnsEnv,
|
|
1246
1246
|
taskDepth: childDepth,
|
|
1247
1247
|
parentHindsightSessionState: options.parentHindsightSessionState,
|
|
1248
|
-
|
|
1248
|
+
parentMnemopiSessionState: options.parentMnemopiSessionState,
|
|
1249
1249
|
parentTaskPrefix: id,
|
|
1250
1250
|
agentId: id,
|
|
1251
1251
|
agentDisplayName: agent.name,
|
package/src/task/index.ts
CHANGED
|
@@ -762,8 +762,7 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
762
762
|
const tempArtifactsDir = artifactsDir ? null : path.join(os.tmpdir(), `omp-task-${Snowflake.next()}`);
|
|
763
763
|
const effectiveArtifactsDir = artifactsDir || tempArtifactsDir!;
|
|
764
764
|
|
|
765
|
-
|
|
766
|
-
const localProtocolOptions: LocalProtocolOptions = {
|
|
765
|
+
const localProtocolOptions: LocalProtocolOptions = this.session.localProtocolOptions ?? {
|
|
767
766
|
getArtifactsDir: this.session.getArtifactsDir ?? (() => null),
|
|
768
767
|
getSessionId: this.session.getSessionId ?? (() => null),
|
|
769
768
|
};
|
|
@@ -864,6 +863,7 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
864
863
|
);
|
|
865
864
|
const promptTemplates = this.session.promptTemplates;
|
|
866
865
|
const parentEvalSessionId = this.session.getEvalSessionId?.() ?? undefined;
|
|
866
|
+
const mcpManager = this.session.mcpManager ?? MCPManager.instance();
|
|
867
867
|
|
|
868
868
|
// Initialize progress for all tasks
|
|
869
869
|
for (let i = 0; i < tasksWithUniqueIds.length; i++) {
|
|
@@ -921,7 +921,7 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
921
921
|
authStorage: this.session.authStorage,
|
|
922
922
|
modelRegistry: this.session.modelRegistry,
|
|
923
923
|
settings: this.session.settings,
|
|
924
|
-
mcpManager
|
|
924
|
+
mcpManager,
|
|
925
925
|
contextFiles,
|
|
926
926
|
skills: availableSkills,
|
|
927
927
|
autoloadSkills: resolvedAutoloadSkills,
|
|
@@ -930,7 +930,7 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
930
930
|
localProtocolOptions,
|
|
931
931
|
parentArtifactManager,
|
|
932
932
|
parentHindsightSessionState: this.session.getHindsightSessionState?.(),
|
|
933
|
-
|
|
933
|
+
parentMnemopiSessionState: this.session.getMnemopiSessionState?.(),
|
|
934
934
|
parentTelemetry: this.session.getTelemetry?.(),
|
|
935
935
|
parentEvalSessionId,
|
|
936
936
|
});
|
|
@@ -978,7 +978,7 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
978
978
|
authStorage: this.session.authStorage,
|
|
979
979
|
modelRegistry: this.session.modelRegistry,
|
|
980
980
|
settings: this.session.settings,
|
|
981
|
-
mcpManager
|
|
981
|
+
mcpManager,
|
|
982
982
|
contextFiles,
|
|
983
983
|
skills: availableSkills,
|
|
984
984
|
autoloadSkills: resolvedAutoloadSkills,
|
|
@@ -987,7 +987,7 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
987
987
|
localProtocolOptions,
|
|
988
988
|
parentArtifactManager,
|
|
989
989
|
parentHindsightSessionState: this.session.getHindsightSessionState?.(),
|
|
990
|
-
|
|
990
|
+
parentMnemopiSessionState: this.session.getMnemopiSessionState?.(),
|
|
991
991
|
parentTelemetry: this.session.getTelemetry?.(),
|
|
992
992
|
parentEvalSessionId,
|
|
993
993
|
});
|
package/src/tiny/models.ts
CHANGED
|
@@ -108,7 +108,7 @@ export const ONLINE_MEMORY_MODEL_KEY = "online";
|
|
|
108
108
|
export const DEFAULT_MEMORY_LOCAL_MODEL_KEY = "qwen3-1.7b";
|
|
109
109
|
|
|
110
110
|
/**
|
|
111
|
-
* Local models for
|
|
111
|
+
* Local models for Mnemopi memory tasks (fact extraction + consolidation).
|
|
112
112
|
* These are larger (1B-1.7B) than the title models: structured extraction and
|
|
113
113
|
* faithful summarization need more capacity than 3-6 word titles. All q4.
|
|
114
114
|
* Ranking/recipe rationale lives in docs/local-models.md.
|
|
@@ -177,7 +177,7 @@ export const TINY_MEMORY_MODEL_OPTIONS = [
|
|
|
177
177
|
value: ONLINE_MEMORY_MODEL_KEY,
|
|
178
178
|
label: "Online (smol/remote)",
|
|
179
179
|
description:
|
|
180
|
-
"Use the configured
|
|
180
|
+
"Use the configured Mnemopi LLM mode (smol or remote); no local model download or on-device inference.",
|
|
181
181
|
},
|
|
182
182
|
...TINY_MEMORY_LOCAL_MODELS.map(model => ({
|
|
183
183
|
value: model.key,
|
|
@@ -196,6 +196,34 @@ export function getTinyMemoryModelSpec(key: TinyMemoryLocalModelKey): (typeof TI
|
|
|
196
196
|
return spec;
|
|
197
197
|
}
|
|
198
198
|
|
|
199
|
+
/**
|
|
200
|
+
* Shake-summary models. Shake's `summary` mode (and the `shake-summary`
|
|
201
|
+
* compaction strategy) compress heavy regions strictly on-device — there is no
|
|
202
|
+
* online/remote option, so this registry reuses the local memory models only.
|
|
203
|
+
*/
|
|
204
|
+
export const SHAKE_SUMMARY_MODEL_VALUES = [
|
|
205
|
+
"qwen3-1.7b",
|
|
206
|
+
"gemma-3-1b",
|
|
207
|
+
"qwen2.5-1.5b",
|
|
208
|
+
"lfm2-1.2b",
|
|
209
|
+
] as const satisfies readonly TinyMemoryLocalModelKey[];
|
|
210
|
+
|
|
211
|
+
export type ShakeSummaryModelKey = (typeof SHAKE_SUMMARY_MODEL_VALUES)[number];
|
|
212
|
+
|
|
213
|
+
// Guard: every local memory model is offered for shake summary (catches drift).
|
|
214
|
+
type MissingShakeSummaryValue = Exclude<TinyMemoryLocalModelKey, ShakeSummaryModelKey>;
|
|
215
|
+
const SHAKE_SUMMARY_MODEL_VALUES_MATCH_REGISTRY: MissingShakeSummaryValue extends never ? true : never = true;
|
|
216
|
+
void SHAKE_SUMMARY_MODEL_VALUES_MATCH_REGISTRY;
|
|
217
|
+
|
|
218
|
+
export const SHAKE_SUMMARY_MODEL_OPTIONS = TINY_MEMORY_LOCAL_MODELS.map(model => ({
|
|
219
|
+
value: model.key,
|
|
220
|
+
label: model.label,
|
|
221
|
+
description: model.description,
|
|
222
|
+
})) satisfies ReadonlyArray<{ value: ShakeSummaryModelKey; label: string; description: string }>;
|
|
223
|
+
|
|
224
|
+
/** Default shake-summary local model when none is named. */
|
|
225
|
+
export const DEFAULT_SHAKE_SUMMARY_MODEL_KEY: ShakeSummaryModelKey = DEFAULT_MEMORY_LOCAL_MODEL_KEY;
|
|
226
|
+
|
|
199
227
|
/** Any local model key (title or memory), used by the shared inference worker. */
|
|
200
228
|
export type TinyLocalModelKey = TinyTitleLocalModelKey | TinyMemoryLocalModelKey;
|
|
201
229
|
|
|
@@ -30,7 +30,17 @@ export interface TinyTitleProgressEvent {
|
|
|
30
30
|
export type TinyTitleWorkerInbound =
|
|
31
31
|
| { type: "ping"; id: string }
|
|
32
32
|
| { type: "generate"; id: string; modelKey: TinyTitleLocalModelKey; message: string }
|
|
33
|
-
| {
|
|
33
|
+
| {
|
|
34
|
+
type: "complete";
|
|
35
|
+
id: string;
|
|
36
|
+
modelKey: TinyLocalModelKey;
|
|
37
|
+
prompt: string;
|
|
38
|
+
maxTokens?: number;
|
|
39
|
+
/** Optional assistant-turn prefix appended after the generation prompt to pin output format. */
|
|
40
|
+
prefill?: string;
|
|
41
|
+
/** Optional literal stop string; generation halts once it appears in the decoded tail. */
|
|
42
|
+
stop?: string;
|
|
43
|
+
}
|
|
34
44
|
| { type: "download"; id: string; modelKey: TinyLocalModelKey }
|
|
35
45
|
| { type: "close" };
|
|
36
46
|
|
package/src/tiny/worker.ts
CHANGED
|
@@ -461,19 +461,20 @@ async function generateTitle(
|
|
|
461
461
|
return extractTinyTitle(output[0]?.generated_text ?? "");
|
|
462
462
|
}
|
|
463
463
|
|
|
464
|
-
function buildCompletionPrompt(generator: TextGenerationPipeline, promptText: string): string {
|
|
464
|
+
function buildCompletionPrompt(generator: TextGenerationPipeline, promptText: string, prefill?: string): string {
|
|
465
465
|
const chat = [{ role: "user", content: promptText }];
|
|
466
466
|
const chatTemplateOptions = {
|
|
467
467
|
add_generation_prompt: true,
|
|
468
468
|
tokenize: false,
|
|
469
469
|
enable_thinking: false,
|
|
470
470
|
};
|
|
471
|
-
|
|
471
|
+
const base = generator.tokenizer.apply_chat_template(chat, chatTemplateOptions) as string;
|
|
472
|
+
return prefill ? `${base}${prefill}` : base;
|
|
472
473
|
}
|
|
473
474
|
|
|
474
475
|
/**
|
|
475
|
-
* Generic single-turn completion used by
|
|
476
|
-
* and consolidation). The caller (
|
|
476
|
+
* Generic single-turn completion used by Mnemopi memory tasks (fact extraction
|
|
477
|
+
* and consolidation). The caller (Mnemopi) supplies the full task prompt; we
|
|
477
478
|
* wrap it as the user turn, decode greedily, and return the raw text for the
|
|
478
479
|
* caller's own parser. Output is capped to keep local inference latency bounded.
|
|
479
480
|
*/
|
|
@@ -483,18 +484,27 @@ async function generateCompletion(
|
|
|
483
484
|
modelKey: TinyLocalModelKey,
|
|
484
485
|
promptText: string,
|
|
485
486
|
maxTokens: number | undefined,
|
|
487
|
+
prefill?: string,
|
|
488
|
+
stop?: string,
|
|
486
489
|
): Promise<string | null> {
|
|
487
490
|
const generator = await loadPipeline(modelKey, transport, requestId);
|
|
488
|
-
const text = buildCompletionPrompt(generator, promptText);
|
|
491
|
+
const text = buildCompletionPrompt(generator, promptText, prefill);
|
|
489
492
|
const requested = maxTokens ?? MEMORY_COMPLETION_MAX_NEW_TOKENS;
|
|
490
493
|
const maxNewTokens = Math.min(Math.max(1, requested), MEMORY_COMPLETION_MAX_NEW_TOKENS);
|
|
494
|
+
const transformers = stop ? await loadTransformers(transport, requestId, modelKey) : undefined;
|
|
491
495
|
const output = (await generator(text, {
|
|
492
496
|
max_new_tokens: maxNewTokens,
|
|
493
497
|
do_sample: false,
|
|
494
498
|
return_full_text: false,
|
|
499
|
+
...(transformers && stop
|
|
500
|
+
? { stopping_criteria: createStopOnTextCriteria(transformers, generator.tokenizer, stop) }
|
|
501
|
+
: {}),
|
|
495
502
|
})) as TextGenerationStringOutput;
|
|
496
|
-
const generated =
|
|
497
|
-
|
|
503
|
+
const generated = output[0]?.generated_text ?? "";
|
|
504
|
+
// Re-attach the forced prefix so the caller's parser sees the full assistant turn,
|
|
505
|
+
// including the opening tag it pinned via `prefill`.
|
|
506
|
+
const full = `${prefill ?? ""}${generated}`.trim();
|
|
507
|
+
return full === "" ? null : full;
|
|
498
508
|
}
|
|
499
509
|
|
|
500
510
|
function releasePipelines(): void {
|
|
@@ -538,6 +548,8 @@ async function handleQueuedRequest(
|
|
|
538
548
|
request.modelKey,
|
|
539
549
|
request.prompt,
|
|
540
550
|
request.maxTokens,
|
|
551
|
+
request.prefill,
|
|
552
|
+
request.stop,
|
|
541
553
|
);
|
|
542
554
|
transport.send({ type: "completion", id: request.id, text });
|
|
543
555
|
return;
|