@pugi/cli 0.1.0-beta.25 → 0.1.0-beta.26
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/dist/core/checkpoint/resumer.js +149 -0
- package/dist/core/checkpoint/rewinder.js +291 -0
- package/dist/core/compact/summarizer.js +12 -0
- package/dist/core/dispatch/cache-cleanup.js +197 -0
- package/dist/core/dispatch/cache-handoff.js +295 -0
- package/dist/core/memory-sync/queue.js +158 -0
- package/dist/core/memory-sync/queue.spec.js +105 -0
- package/dist/core/repl/session.js +38 -1
- package/dist/core/repl/slash-commands.js +10 -0
- package/dist/core/repl/store/session-store.js +31 -2
- package/dist/core/telemetry/emitter.js +229 -0
- package/dist/core/telemetry/queue.js +251 -0
- package/dist/runtime/cli.js +126 -0
- package/dist/runtime/commands/dispatch.js +126 -0
- package/dist/runtime/commands/memory.js +508 -0
- package/dist/runtime/commands/memory.spec.js +174 -0
- package/dist/runtime/commands/resume.js +118 -0
- package/dist/runtime/commands/rewind.js +333 -0
- package/dist/runtime/commands/sessions.js +163 -0
- package/dist/runtime/version.js +1 -1
- package/dist/tools/agent-tool.js +23 -0
- package/dist/tui/repl-splash-mascot.js +7 -19
- package/package.json +3 -3
package/dist/runtime/cli.js
CHANGED
|
@@ -22,6 +22,7 @@ import { buildRuntimeConfig, fetchPersonaRoster, loadRuntimeConfig, openPugiSess
|
|
|
22
22
|
import { PUGI_TAGLINE } from '@pugi/personas';
|
|
23
23
|
import { resolveRoster, renderRosterTable } from './commands/roster.js';
|
|
24
24
|
import { runDelegateCommand } from './commands/delegate.js';
|
|
25
|
+
import { runDispatchCommand } from './commands/dispatch.js';
|
|
25
26
|
import { clearApiKey, DEFAULT_API_URL, listStoredCredentials, maskApiKey, normalizeApiUrl, purgeAllCredentials, readCredentialsFile, resolveActiveCredential, storeApiKey, switchActiveAccount, } from '../core/credentials.js';
|
|
26
27
|
import { resolveAndValidateEnvLogin, } from '../core/auth/env-provider.js';
|
|
27
28
|
import { runDeployCommand } from '../commands/deploy.js';
|
|
@@ -41,6 +42,12 @@ import { runRepoMapCommand } from './commands/repo-map.js';
|
|
|
41
42
|
import { runReleaseNotesCommand, defaultReleaseNotesHome, } from './commands/release-notes.js';
|
|
42
43
|
import { runUndoCommand } from './commands/undo.js';
|
|
43
44
|
import { runCompactCommand } from './commands/compact.js';
|
|
45
|
+
import { runRewindCommand } from './commands/rewind.js';
|
|
46
|
+
import { runSessionsCommand } from './commands/sessions.js';
|
|
47
|
+
// Day 4 ADR-0063: persona-memory operator surface (list / recall / write /
|
|
48
|
+
// forget / sync). The runner is shared by `pugi memory` top-level and the
|
|
49
|
+
// in-REPL `/memory` slash so the two surfaces stay single-sourced.
|
|
50
|
+
import { runMemoryCommand } from './commands/memory.js';
|
|
44
51
|
import { runBudgetCommand } from './commands/budget.js';
|
|
45
52
|
import { BARE_MODE_BANNER, isBareMode, setBareMode, } from '../core/bare-mode/index.js';
|
|
46
53
|
import { runCostCommand } from './commands/cost.js';
|
|
@@ -92,6 +99,11 @@ const handlers = {
|
|
|
92
99
|
config: dispatchConfig,
|
|
93
100
|
cost: dispatchCost,
|
|
94
101
|
delegate: dispatchDelegate,
|
|
102
|
+
// Leak L10 (2026-05-27): `pugi dispatch list-cache-refs` /
|
|
103
|
+
// `clear-cache-refs` operate on `.pugi/cache-refs/` — the persisted
|
|
104
|
+
// prompt-cache inheritance handles for fork-subagent dispatches. The
|
|
105
|
+
// handler module lives in commands/dispatch.ts so the table stays narrow.
|
|
106
|
+
dispatch: dispatchSubagentCacheRefs,
|
|
95
107
|
deploy: dispatchDeploy,
|
|
96
108
|
doctor,
|
|
97
109
|
explain: runEngineTask('explain'),
|
|
@@ -106,6 +118,10 @@ const handlers = {
|
|
|
106
118
|
logout,
|
|
107
119
|
lsp: dispatchLsp,
|
|
108
120
|
mcp: dispatchMcp,
|
|
121
|
+
// ADR-0063 Day 4: `pugi memory list|recall|write|forget|sync`. Routes
|
|
122
|
+
// to `runMemoryCommand` (admin-api `/api/persona-memory` + offline
|
|
123
|
+
// queue at `~/.pugi/memory-queue.jsonl`).
|
|
124
|
+
memory: dispatchMemory,
|
|
109
125
|
patch: dispatchPatch,
|
|
110
126
|
permissions: dispatchPermissions,
|
|
111
127
|
perms: dispatchPermissions,
|
|
@@ -159,6 +175,11 @@ const handlers = {
|
|
|
159
175
|
vim: dispatchVim,
|
|
160
176
|
undo: dispatchUndo,
|
|
161
177
|
compact: dispatchCompact,
|
|
178
|
+
// Leak L9 (2026-05-27): `pugi rewind [N | --to <id>]` rolls the
|
|
179
|
+
// conversation back to a checkpoint by appending a tombstone marker
|
|
180
|
+
// to the NDJSON event log. The slash counterpart `/rewind` forwards
|
|
181
|
+
// to the same runner via session.ts.
|
|
182
|
+
rewind: dispatchRewind,
|
|
162
183
|
// L19 (2026-05-27): `pugi usage` is an alias of `pugi cost` — same
|
|
163
184
|
// handler, same flags. Operators trained on Claude Code expect either
|
|
164
185
|
// verb to surface the per-model token + USD table.
|
|
@@ -343,6 +364,41 @@ async function dispatchPrivacy(args, flags, _session) {
|
|
|
343
364
|
writeOutput: (payload, text) => writeOutput(flags, payload, text),
|
|
344
365
|
});
|
|
345
366
|
}
|
|
367
|
+
/**
|
|
368
|
+
* ADR-0063 Day 4 — `pugi memory <sub>` top-level dispatcher.
|
|
369
|
+
*
|
|
370
|
+
* Forwards to the shared `runMemoryCommand` runner. Exit codes:
|
|
371
|
+
*
|
|
372
|
+
* - 0 — happy paths (listed / recalled / written / forgot / synced /
|
|
373
|
+
* queued_offline / sync_noop / sync_partial)
|
|
374
|
+
* - 1 — unauthenticated / feature_disabled / unknown_sub
|
|
375
|
+
* - 2 — invalid_args
|
|
376
|
+
*
|
|
377
|
+
* `forget_not_found` exits 0 because the operator-visible behaviour
|
|
378
|
+
* (the memory is gone) matches their intent; the JSON envelope still
|
|
379
|
+
* carries the `forget_not_found` status flag for scripted callers.
|
|
380
|
+
*/
|
|
381
|
+
async function dispatchMemory(args, flags, _session) {
|
|
382
|
+
const result = await runMemoryCommand(args, {
|
|
383
|
+
workspaceRoot: process.cwd(),
|
|
384
|
+
json: flags.json,
|
|
385
|
+
writeOutput: (payload, text) => writeOutput(flags, payload, text),
|
|
386
|
+
});
|
|
387
|
+
switch (result.status) {
|
|
388
|
+
case 'unauthenticated':
|
|
389
|
+
case 'feature_disabled':
|
|
390
|
+
case 'unknown_sub':
|
|
391
|
+
process.exitCode = 1;
|
|
392
|
+
return;
|
|
393
|
+
case 'invalid_args':
|
|
394
|
+
process.exitCode = 2;
|
|
395
|
+
return;
|
|
396
|
+
default:
|
|
397
|
+
// 'listed' | 'recalled' | 'written' | 'queued_offline' | 'forgot' |
|
|
398
|
+
// 'forget_not_found' | 'synced' | 'sync_partial' | 'sync_noop' — exit 0.
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
346
402
|
/**
|
|
347
403
|
* Leak L18 (2026-05-27) — `pugi style` top-level dispatcher.
|
|
348
404
|
*
|
|
@@ -570,6 +626,31 @@ async function dispatchBudget(args, flags, _session) {
|
|
|
570
626
|
writeOutput: (payload, text) => writeOutput(flags, payload, text),
|
|
571
627
|
});
|
|
572
628
|
}
|
|
629
|
+
/**
|
|
630
|
+
* Leak L9 (2026-05-27) — `pugi rewind [N | --to <id>]` rolls the
|
|
631
|
+
* conversation back to a checkpoint by appending a tombstone marker to
|
|
632
|
+
* the NDJSON event log. Append-only: events stay durable; `pugi
|
|
633
|
+
* sessions undo-rewind` reverses the operation. The slash `/rewind`
|
|
634
|
+
* forwards through this same runner via session.ts.
|
|
635
|
+
*/
|
|
636
|
+
async function dispatchRewind(args, flags, _session) {
|
|
637
|
+
const result = await runRewindCommand(args, {
|
|
638
|
+
workspaceRoot: process.cwd(),
|
|
639
|
+
writeOutput: (payload, text) => writeOutput(flags, payload, text),
|
|
640
|
+
});
|
|
641
|
+
if (result.status === 'failed_no_session'
|
|
642
|
+
|| result.status === 'failed_store') {
|
|
643
|
+
process.exitCode = 1;
|
|
644
|
+
return;
|
|
645
|
+
}
|
|
646
|
+
if (result.status === 'failed_parse') {
|
|
647
|
+
process.exitCode = 2;
|
|
648
|
+
return;
|
|
649
|
+
}
|
|
650
|
+
if (result.status === 'noop_zero' || result.status === 'noop_empty') {
|
|
651
|
+
process.exitCode = 2;
|
|
652
|
+
}
|
|
653
|
+
}
|
|
573
654
|
/**
|
|
574
655
|
* Leak L6 — `pugi permissions [mode] [--persist] [--confirm]`.
|
|
575
656
|
*
|
|
@@ -745,6 +826,19 @@ async function dispatchAgents(args, flags, _session) {
|
|
|
745
826
|
writeOutput: (payload, text) => writeOutput(flags, payload, text),
|
|
746
827
|
});
|
|
747
828
|
}
|
|
829
|
+
/**
|
|
830
|
+
* Leak L10 (2026-05-27): `pugi dispatch <sub>` — operator-facing
|
|
831
|
+
* inspection + GC for fork-subagent prompt-cache inherit refs
|
|
832
|
+
* (.pugi/cache-refs/). Delegates to the standalone runner in
|
|
833
|
+
* commands/dispatch.ts so the cli.ts table stays under control.
|
|
834
|
+
*/
|
|
835
|
+
async function dispatchSubagentCacheRefs(args, flags, _session) {
|
|
836
|
+
await runDispatchCommand(args, {
|
|
837
|
+
workspaceRoot: process.cwd(),
|
|
838
|
+
json: flags.json,
|
|
839
|
+
writeOutput: (payload, text) => writeOutput(flags, payload, text),
|
|
840
|
+
});
|
|
841
|
+
}
|
|
748
842
|
/**
|
|
749
843
|
* `pugi web <url>` — Sprint α6.15 Phase 1 quick-win subcommand.
|
|
750
844
|
*
|
|
@@ -1526,6 +1620,17 @@ const COMMAND_HELP_BODIES = {
|
|
|
1526
1620
|
'Slugs (Tier 1 alpha 7.5): dev qa pm devops researcher analyst designer',
|
|
1527
1621
|
'frontend architect. `pugi roster` lists the live set.',
|
|
1528
1622
|
],
|
|
1623
|
+
dispatch: [
|
|
1624
|
+
'pugi dispatch <sub> — inspect + GC fork-subagent prompt-cache inherit refs.',
|
|
1625
|
+
'',
|
|
1626
|
+
' list-cache-refs Table of every active ref under .pugi/cache-refs/.',
|
|
1627
|
+
' clear-cache-refs [--older-than 1h] Evict refs older than the window (default 24h).',
|
|
1628
|
+
'',
|
|
1629
|
+
'Leak L10 (2026-05-27): when Mira spawns a child via the `agent` tool,',
|
|
1630
|
+
'a prompt-cache handle is persisted so the child loop can request',
|
|
1631
|
+
'parent-context reuse on the wire. These commands surface + clean up',
|
|
1632
|
+
'the persisted refs.',
|
|
1633
|
+
],
|
|
1529
1634
|
roster: [
|
|
1530
1635
|
'pugi roster — list the live Tier 1 personas + roles.',
|
|
1531
1636
|
],
|
|
@@ -1694,6 +1799,8 @@ async function help(args, flags, _session) {
|
|
|
1694
1799
|
'Persona dispatch (α7.5):',
|
|
1695
1800
|
' pugi roster List the live Tier 1 personas + roles.',
|
|
1696
1801
|
' pugi delegate <slug> "<brief>" Dispatch a brief to one specialist.',
|
|
1802
|
+
' pugi dispatch list-cache-refs Inspect fork-subagent prompt-cache inherit refs.',
|
|
1803
|
+
' pugi dispatch clear-cache-refs GC stale cache refs (--older-than 1h).',
|
|
1697
1804
|
'',
|
|
1698
1805
|
'Plan decomposition (α6.8):',
|
|
1699
1806
|
' pugi plan --decompose <idea> Split a high-level idea into 3-7 components.',
|
|
@@ -3339,6 +3446,25 @@ async function handoff(args, flags, session) {
|
|
|
3339
3446
|
writeOutput(flags, bundle, ['Pugi handoff bundle created', `Bundle: ${bundle.path}`].join('\n'));
|
|
3340
3447
|
}
|
|
3341
3448
|
async function sessions(args, flags, _session) {
|
|
3449
|
+
// L9 (2026-05-27): `pugi sessions undo-rewind [<session-id>]` rolls
|
|
3450
|
+
// back the latest /rewind by appending an inverse marker. Append-only,
|
|
3451
|
+
// reversible. Falls through to the legacy artifact-based handler when
|
|
3452
|
+
// the sub-command is not recognised.
|
|
3453
|
+
if (args[0] === 'undo-rewind') {
|
|
3454
|
+
const result = await runSessionsCommand(args, {
|
|
3455
|
+
workspaceRoot: process.cwd(),
|
|
3456
|
+
writeOutput: (payload, text) => writeOutput(flags, payload, text),
|
|
3457
|
+
});
|
|
3458
|
+
if (result) {
|
|
3459
|
+
if (result.status === 'failed_no_session' || result.status === 'failed_store') {
|
|
3460
|
+
process.exitCode = 1;
|
|
3461
|
+
}
|
|
3462
|
+
else if (result.status === 'noop_no_rewind') {
|
|
3463
|
+
process.exitCode = 2;
|
|
3464
|
+
}
|
|
3465
|
+
return;
|
|
3466
|
+
}
|
|
3467
|
+
}
|
|
3342
3468
|
// α6.4: `pugi sessions --local` / `--search "query"` route to the
|
|
3343
3469
|
// local SessionStore. The default surface stays artifact-based for
|
|
3344
3470
|
// backward compat — operators who relied on the index.json view get
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `pugi dispatch <sub>` — fork-subagent cache-handoff surface (Leak L10 — 2026-05-27).
|
|
3
|
+
*
|
|
4
|
+
* Sub-commands:
|
|
5
|
+
*
|
|
6
|
+
* - `pugi dispatch list-cache-refs` — show active cache-inherit refs
|
|
7
|
+
* persisted under `.pugi/cache-refs/`. Renders an aligned table or
|
|
8
|
+
* JSON depending on `--json`.
|
|
9
|
+
*
|
|
10
|
+
* - `pugi dispatch clear-cache-refs [--older-than 1h] [-v]` — GC stale
|
|
11
|
+
* refs. Without `--older-than`, defaults to 24h (the same cutoff
|
|
12
|
+
* the REPL boot-time auto-sweep uses). The flag accepts duration
|
|
13
|
+
* strings the `parseDuration` helper recognises (`1h`, `30m`,
|
|
14
|
+
* `7d`, `500ms`, ...).
|
|
15
|
+
*
|
|
16
|
+
* The command lives in its own module (not inline in cli.ts) so the
|
|
17
|
+
* dispatch table stays narrow and tests can drive it without spinning
|
|
18
|
+
* up the full CLI.
|
|
19
|
+
*/
|
|
20
|
+
import { cleanupStaleCacheRefs, parseDuration, } from '../../core/dispatch/cache-cleanup.js';
|
|
21
|
+
import { listCacheRefs } from '../../core/dispatch/cache-handoff.js';
|
|
22
|
+
const USAGE = [
|
|
23
|
+
'Usage:',
|
|
24
|
+
' pugi dispatch list-cache-refs',
|
|
25
|
+
' Show every active subagent cache-inherit',
|
|
26
|
+
' reference persisted under .pugi/cache-refs/.',
|
|
27
|
+
'',
|
|
28
|
+
' pugi dispatch clear-cache-refs [--older-than <duration>] [-v]',
|
|
29
|
+
' Evict stale refs. Accepts 500ms / 30s / 5m /',
|
|
30
|
+
' 1h / 7d. Default --older-than 24h.',
|
|
31
|
+
].join('\n');
|
|
32
|
+
export async function runDispatchCommand(args, ctx) {
|
|
33
|
+
const sub = args[0];
|
|
34
|
+
if (!sub || sub === '--help' || sub === '-h') {
|
|
35
|
+
ctx.writeOutput({ command: 'dispatch', usage: USAGE.split('\n') }, USAGE);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
switch (sub) {
|
|
39
|
+
case 'list-cache-refs':
|
|
40
|
+
return runListCacheRefs(ctx);
|
|
41
|
+
case 'clear-cache-refs':
|
|
42
|
+
return runClearCacheRefs(args.slice(1), ctx);
|
|
43
|
+
default:
|
|
44
|
+
ctx.writeOutput({
|
|
45
|
+
ok: false,
|
|
46
|
+
error: `unknown subcommand: ${sub}`,
|
|
47
|
+
usage: USAGE.split('\n'),
|
|
48
|
+
}, `pugi dispatch: unknown subcommand '${sub}'\n\n${USAGE}`);
|
|
49
|
+
process.exitCode = 2;
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function runListCacheRefs(ctx) {
|
|
54
|
+
const refs = listCacheRefs(ctx.workspaceRoot);
|
|
55
|
+
if (ctx.json) {
|
|
56
|
+
ctx.writeOutput({ ok: true, count: refs.length, refs }, '');
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
if (refs.length === 0) {
|
|
60
|
+
ctx.writeOutput({ ok: true, count: 0, refs: [] }, 'No active cache-inherit refs under .pugi/cache-refs/.');
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
// Aligned column table. Widths derived from the data — no hardcoded
|
|
64
|
+
// sizes so a long childAgentId does not overflow the layout.
|
|
65
|
+
const header = ['CHILD AGENT', 'CACHE ID', 'PARENT SESSION', 'CREATED'];
|
|
66
|
+
const rows = refs.map((r) => [
|
|
67
|
+
r.childAgentId,
|
|
68
|
+
r.cacheId,
|
|
69
|
+
r.parentSessionId,
|
|
70
|
+
r.createdAt,
|
|
71
|
+
]);
|
|
72
|
+
const widths = header.map((h, i) => Math.max(h.length, ...rows.map((row) => (row[i] ?? '').length)));
|
|
73
|
+
const lines = [];
|
|
74
|
+
lines.push(header.map((h, i) => h.padEnd(widths[i] ?? 0)).join(' '));
|
|
75
|
+
lines.push(widths.map((w) => '-'.repeat(w)).join(' '));
|
|
76
|
+
for (const row of rows) {
|
|
77
|
+
lines.push(row.map((cell, i) => (cell ?? '').padEnd(widths[i] ?? 0)).join(' '));
|
|
78
|
+
}
|
|
79
|
+
ctx.writeOutput({ ok: true, count: refs.length, refs }, lines.join('\n'));
|
|
80
|
+
}
|
|
81
|
+
function runClearCacheRefs(args, ctx) {
|
|
82
|
+
let olderThanArg;
|
|
83
|
+
let verbose = false;
|
|
84
|
+
for (let i = 0; i < args.length; i++) {
|
|
85
|
+
const arg = args[i];
|
|
86
|
+
if (arg === '--older-than') {
|
|
87
|
+
olderThanArg = args[i + 1];
|
|
88
|
+
i++;
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
if (arg && arg.startsWith('--older-than=')) {
|
|
92
|
+
olderThanArg = arg.slice('--older-than='.length);
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
if (arg === '-v' || arg === '--verbose') {
|
|
96
|
+
verbose = true;
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
// Default 24h matches the REPL boot-time auto-sweep cutoff.
|
|
101
|
+
const olderThanMs = olderThanArg ? parseDuration(olderThanArg) : 24 * 60 * 60 * 1000;
|
|
102
|
+
if (olderThanMs === null) {
|
|
103
|
+
ctx.writeOutput({
|
|
104
|
+
ok: false,
|
|
105
|
+
error: `invalid --older-than value: ${olderThanArg ?? '(missing)'}`,
|
|
106
|
+
usage: USAGE.split('\n'),
|
|
107
|
+
}, `pugi dispatch clear-cache-refs: invalid --older-than '${olderThanArg ?? '(missing)'}'\n\n${USAGE}`);
|
|
108
|
+
process.exitCode = 2;
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
const result = cleanupStaleCacheRefs(ctx.workspaceRoot, {
|
|
112
|
+
olderThanMs,
|
|
113
|
+
verbose,
|
|
114
|
+
});
|
|
115
|
+
const summary = `Removed ${result.removedCount} ref(s) (${result.corruptCount} corrupt), kept ${result.keptCount}.`;
|
|
116
|
+
ctx.writeOutput({
|
|
117
|
+
ok: true,
|
|
118
|
+
olderThanMs,
|
|
119
|
+
removedCount: result.removedCount,
|
|
120
|
+
keptCount: result.keptCount,
|
|
121
|
+
corruptCount: result.corruptCount,
|
|
122
|
+
removed: result.removed,
|
|
123
|
+
corrupt: result.corrupt,
|
|
124
|
+
}, summary);
|
|
125
|
+
}
|
|
126
|
+
//# sourceMappingURL=dispatch.js.map
|