@oh-my-pi/pi-coding-agent 15.11.7 → 15.12.0

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 (107) hide show
  1. package/CHANGELOG.md +63 -1
  2. package/dist/cli.js +8106 -7708
  3. package/dist/types/cli/args.d.ts +2 -0
  4. package/dist/types/collab/crypto.d.ts +7 -0
  5. package/dist/types/collab/guest.d.ts +23 -0
  6. package/dist/types/collab/host.d.ts +29 -0
  7. package/dist/types/collab/protocol.d.ts +113 -0
  8. package/dist/types/collab/relay-client.d.ts +22 -0
  9. package/dist/types/commands/join.d.ts +12 -0
  10. package/dist/types/config/settings-schema.d.ts +60 -5
  11. package/dist/types/export/custom-share.d.ts +1 -2
  12. package/dist/types/export/html/index.d.ts +39 -1
  13. package/dist/types/export/share.d.ts +43 -0
  14. package/dist/types/extensibility/slash-commands.d.ts +1 -11
  15. package/dist/types/main.d.ts +2 -0
  16. package/dist/types/modes/components/agent-hub.d.ts +32 -1
  17. package/dist/types/modes/components/collab-prompt-message.d.ts +10 -0
  18. package/dist/types/modes/components/hook-selector.d.ts +4 -6
  19. package/dist/types/modes/components/segment-track.d.ts +11 -6
  20. package/dist/types/modes/components/status-line/component.d.ts +10 -2
  21. package/dist/types/modes/components/status-line/types.d.ts +11 -0
  22. package/dist/types/modes/controllers/event-controller.d.ts +7 -0
  23. package/dist/types/modes/controllers/input-controller.d.ts +1 -1
  24. package/dist/types/modes/controllers/session-focus-controller.d.ts +31 -0
  25. package/dist/types/modes/interactive-mode.d.ts +16 -0
  26. package/dist/types/modes/session-observer-registry.d.ts +7 -0
  27. package/dist/types/modes/theme/theme.d.ts +2 -1
  28. package/dist/types/modes/types.d.ts +20 -0
  29. package/dist/types/session/agent-session.d.ts +13 -0
  30. package/dist/types/session/codex-auto-reset.d.ts +8 -4
  31. package/dist/types/session/session-manager.d.ts +21 -0
  32. package/dist/types/session/snapcompact-inline.d.ts +6 -3
  33. package/dist/types/slash-commands/builtin-registry.d.ts +9 -0
  34. package/dist/types/task/executor.d.ts +7 -0
  35. package/dist/types/task/types.d.ts +9 -0
  36. package/package.json +14 -13
  37. package/scripts/bench-guard.ts +71 -0
  38. package/scripts/build-binary.ts +4 -0
  39. package/scripts/bundle-dist.ts +4 -0
  40. package/scripts/generate-share-viewer.ts +34 -0
  41. package/src/cli/args.ts +2 -0
  42. package/src/cli-commands.ts +1 -0
  43. package/src/collab/crypto.ts +63 -0
  44. package/src/collab/guest.ts +450 -0
  45. package/src/collab/host.ts +556 -0
  46. package/src/collab/protocol.ts +232 -0
  47. package/src/collab/relay-client.ts +216 -0
  48. package/src/commands/join.ts +39 -0
  49. package/src/config/model-registry.ts +22 -14
  50. package/src/config/settings-schema.ts +67 -5
  51. package/src/config/settings.ts +12 -0
  52. package/src/export/custom-share.ts +1 -1
  53. package/src/export/html/index.ts +122 -17
  54. package/src/export/html/share-loader.js +102 -0
  55. package/src/export/html/template.css +745 -459
  56. package/src/export/html/template.html +6 -3
  57. package/src/export/html/template.js +240 -915
  58. package/src/export/html/tool-views.generated.js +38 -0
  59. package/src/export/share.ts +268 -0
  60. package/src/extensibility/slash-commands.ts +1 -97
  61. package/src/internal-urls/docs-index.generated.ts +74 -73
  62. package/src/main.ts +33 -11
  63. package/src/modes/components/agent-hub.ts +659 -431
  64. package/src/modes/components/assistant-message.ts +126 -6
  65. package/src/modes/components/collab-prompt-message.ts +30 -0
  66. package/src/modes/components/hook-selector.ts +4 -5
  67. package/src/modes/components/segment-track.ts +44 -7
  68. package/src/modes/components/status-line/component.ts +59 -6
  69. package/src/modes/components/status-line/presets.ts +1 -1
  70. package/src/modes/components/status-line/segments.ts +18 -1
  71. package/src/modes/components/status-line/types.ts +12 -0
  72. package/src/modes/components/tips.txt +4 -1
  73. package/src/modes/controllers/command-controller.ts +55 -96
  74. package/src/modes/controllers/event-controller.ts +45 -16
  75. package/src/modes/controllers/input-controller.ts +175 -9
  76. package/src/modes/controllers/selector-controller.ts +13 -15
  77. package/src/modes/controllers/session-focus-controller.ts +112 -0
  78. package/src/modes/controllers/streaming-reveal.ts +7 -0
  79. package/src/modes/interactive-mode.ts +56 -6
  80. package/src/modes/session-observer-registry.ts +11 -0
  81. package/src/modes/theme/theme.ts +6 -0
  82. package/src/modes/types.ts +20 -0
  83. package/src/modes/utils/ui-helpers.ts +23 -13
  84. package/src/prompts/tools/job.md +1 -1
  85. package/src/sdk.ts +239 -36
  86. package/src/session/agent-session.ts +82 -7
  87. package/src/session/codex-auto-reset.ts +23 -11
  88. package/src/session/session-manager.ts +44 -0
  89. package/src/session/snapcompact-inline.ts +9 -3
  90. package/src/slash-commands/builtin-registry.ts +261 -24
  91. package/src/task/executor.ts +14 -0
  92. package/src/task/index.ts +5 -1
  93. package/src/task/render.ts +76 -5
  94. package/src/task/types.ts +9 -0
  95. package/src/tiny/worker.ts +17 -95
  96. package/src/tools/job.ts +6 -9
  97. package/src/tools/read.ts +38 -5
  98. package/src/tools/write.ts +13 -42
  99. package/dist/tokenizers.linux-x64-gnu-xcjh3jwk.node +0 -0
  100. package/dist/types/export/html/template.generated.d.ts +0 -1
  101. package/dist/types/export/html/template.macro.d.ts +0 -5
  102. package/dist/types/tiny/compiled-runtime.d.ts +0 -35
  103. package/scripts/generate-template.ts +0 -33
  104. package/src/bun-imports.d.ts +0 -28
  105. package/src/export/html/template.generated.ts +0 -2
  106. package/src/export/html/template.macro.ts +0 -25
  107. package/src/tiny/compiled-runtime.ts +0 -179
@@ -834,6 +834,18 @@ export class Settings {
834
834
  }
835
835
  delete raw["providers.parallelFetch"];
836
836
 
837
+ // codexResets.autoRedeem: boolean -> tri-state enum.
838
+ // Existing explicit false keeps the old "do not run" behavior; missing
839
+ // config now falls through to the new "unset" default, which asks before
840
+ // the first eligible spend.
841
+ const codexResetsObj = raw.codexResets as Record<string, unknown> | undefined;
842
+ if (codexResetsObj && typeof codexResetsObj.autoRedeem === "boolean") {
843
+ codexResetsObj.autoRedeem = codexResetsObj.autoRedeem ? "yes" : "no";
844
+ }
845
+ if (typeof raw["codexResets.autoRedeem"] === "boolean") {
846
+ raw["codexResets.autoRedeem"] = raw["codexResets.autoRedeem"] ? "yes" : "no";
847
+ }
848
+
837
849
  // Map legacy `memories.enabled` boolean to the explicit `memory.backend`
838
850
  // enum if the latter hasn't been set yet. Idempotent: subsequent
839
851
  // migrations are no-ops once memory.backend is materialised.
@@ -17,7 +17,7 @@ export interface CustomShareResult {
17
17
 
18
18
  export type CustomShareFn = (htmlPath: string) => Promise<CustomShareResult | string | undefined>;
19
19
 
20
- interface LoadedCustomShare {
20
+ export interface LoadedCustomShare {
21
21
  path: string;
22
22
  fn: CustomShareFn;
23
23
  }
@@ -1,14 +1,47 @@
1
+ import * as fs from "node:fs/promises";
1
2
  import * as path from "node:path";
2
3
  import type { AgentState } from "@oh-my-pi/pi-agent-core";
3
4
  import { APP_NAME, isEnoent } from "@oh-my-pi/pi-utils";
4
5
  import { getResolvedThemeColors, getThemeExportColors } from "../../modes/theme/theme";
5
- import { type SessionEntry, type SessionHeader, SessionManager } from "../../session/session-manager";
6
- // Pre-generated template (created by scripts/generate-template.ts at publish time)
7
- import { TEMPLATE } from "./template.generated";
6
+ import {
7
+ loadEntriesFromFile,
8
+ type SessionEntry,
9
+ type SessionHeader,
10
+ SessionManager,
11
+ } from "../../session/session-manager";
12
+ import templateCss from "./template.css" with { type: "text" };
13
+ import templateHtml from "./template.html" with { type: "text" };
14
+ import templateJs from "./template.js" with { type: "text" };
15
+ // Pre-built React tool renderers: built by `bun --cwd=packages/collab-web run build:tool-views`,
16
+ // run automatically by root `prepare` on install and by `prepack` at publish.
17
+ import toolViewsJs from "./tool-views.generated.js" with { type: "text" };
18
+
19
+ let cachedTemplate: string | undefined;
20
+
21
+ /** Compose the standalone export template: minified CSS, tool renderers, and viewer JS inlined. */
22
+ export function getTemplate(): string {
23
+ if (cachedTemplate) return cachedTemplate;
24
+ const minifiedCss = templateCss
25
+ .replace(/\/\*[\s\S]*?\*\//g, "")
26
+ .replace(/\s+/g, " ")
27
+ .replace(/\s*([{}:;,])\s*/g, "$1")
28
+ .trim();
29
+ // Function replacements so `$'`, `$&`, `$$`, etc. inside the embedded
30
+ // CSS/JS are not interpreted as substitution patterns. The cast is safe:
31
+ // `with { type: "text" }` yields a string at runtime; bun-types just types
32
+ // every *.html import as HTMLBundle (TS can't vary types by import attribute).
33
+ cachedTemplate = (templateHtml as unknown as string)
34
+ .replace("<template-css/>", () => `<style>${minifiedCss}</style>`)
35
+ .replace("<template-tool-views/>", () => `<script>${toolViewsJs}</script>`)
36
+ .replace("<template-js/>", () => `<script>${templateJs}</script>`);
37
+ return cachedTemplate;
38
+ }
8
39
 
9
40
  export interface ExportOptions {
10
41
  outputPath?: string;
11
42
  themeName?: string;
43
+ /** Embed subagent session transcripts found next to the session file (default true). */
44
+ includeSubSessions?: boolean;
12
45
  }
13
46
 
14
47
  /** Parse a color string to RGB values. */
@@ -71,8 +104,8 @@ function deriveExportColors(baseColor: string): { pageBg: string; cardBg: string
71
104
  };
72
105
  }
73
106
 
74
- /** Generate CSS custom properties for theme. */
75
- async function generateThemeVars(themeName?: string): Promise<string> {
107
+ /** Generate CSS custom properties for theme. Exported for the share-viewer build script. */
108
+ export async function generateThemeVars(themeName?: string): Promise<string> {
76
109
  const colors = await getResolvedThemeColors(themeName);
77
110
  const lines: string[] = [];
78
111
  for (const [key, value] of Object.entries(colors)) {
@@ -90,12 +123,83 @@ async function generateThemeVars(themeName?: string): Promise<string> {
90
123
  return lines.join(" ");
91
124
  }
92
125
 
93
- interface SessionData {
126
+ /** Embedded subagent session transcript, keyed by slash-joined agent path in `SessionData.subSessions`. */
127
+ export interface SubSession {
128
+ /** Bare agent id (session file stem), e.g. "ToolAsk". */
129
+ agentId: string;
130
+ /** Key of the parent sub-session, or null when spawned by the main session. */
131
+ parent: string | null;
132
+ header: SessionHeader | null;
133
+ entries: SessionEntry[];
134
+ leafId: string | null;
135
+ }
136
+
137
+ export interface SessionData {
94
138
  header: SessionHeader | null;
95
139
  entries: SessionEntry[];
96
140
  leafId: string | null;
97
141
  systemPrompt?: string;
98
142
  tools?: { name: string; description: string }[];
143
+ subSessions?: Record<string, SubSession>;
144
+ }
145
+
146
+ /** Snapshot the session (plus optional agent state) into the JSON shape the viewer renders. */
147
+ export function buildSessionData(sm: SessionManager, state?: AgentState): SessionData {
148
+ return {
149
+ header: sm.getHeader(),
150
+ entries: sm.getEntries(),
151
+ leafId: sm.getLeafId(),
152
+ systemPrompt: state?.systemPrompt.join("\n\n"),
153
+ tools: state?.tools?.map(t => ({ name: t.name, description: t.description })),
154
+ };
155
+ }
156
+
157
+ /**
158
+ * Collect subagent session transcripts stored next to a session file.
159
+ *
160
+ * A session at `<dir>/<name>.jsonl` keeps its subagent sessions at `<dir>/<name>/<AgentId>.jsonl`;
161
+ * each subagent's own children nest the same way under `<dir>/<name>/<AgentId>/`. Keys in the
162
+ * returned record are slash-joined ids relative to the main session ("ToolAsk", "ToolAsk/Helper").
163
+ * Corrupt or empty files are skipped silently.
164
+ */
165
+ export async function collectSubSessions(sessionFile: string): Promise<Record<string, SubSession>> {
166
+ const result: Record<string, SubSession> = {};
167
+ if (!sessionFile.endsWith(".jsonl")) return result;
168
+ await collectSubSessionsFromDir(sessionFile.slice(0, -6), null, result);
169
+ return result;
170
+ }
171
+
172
+ async function collectSubSessionsFromDir(
173
+ dir: string,
174
+ parentKey: string | null,
175
+ out: Record<string, SubSession>,
176
+ ): Promise<void> {
177
+ let names: string[];
178
+ try {
179
+ names = await fs.readdir(dir);
180
+ } catch (err) {
181
+ if (isEnoent(err)) return;
182
+ throw err;
183
+ }
184
+ for (const name of names) {
185
+ if (!name.endsWith(".jsonl") || name.includes(".bak")) continue;
186
+ const agentId = name.slice(0, -6);
187
+ const key = parentKey ? `${parentKey}/${agentId}` : agentId;
188
+ const fileEntries = await loadEntriesFromFile(path.join(dir, name));
189
+ // Empty/corrupt files (no valid session header) load as [] — skip silently.
190
+ if (fileEntries.length > 0) {
191
+ const header = (fileEntries.find(e => e.type === "session") as SessionHeader | undefined) ?? null;
192
+ const entries = fileEntries.filter((e): e is SessionEntry => e.type !== "session");
193
+ out[key] = {
194
+ agentId,
195
+ parent: parentKey,
196
+ header,
197
+ entries,
198
+ leafId: entries.length > 0 ? entries[entries.length - 1].id : null,
199
+ };
200
+ }
201
+ await collectSubSessionsFromDir(path.join(dir, agentId), key, out);
202
+ }
99
203
  }
100
204
 
101
205
  /** Generate HTML from bundled template with runtime substitutions. */
@@ -106,10 +210,9 @@ async function generateHtml(sessionData: SessionData, themeName?: string): Promi
106
210
  // Use function replacements so `$'`, `$&`, `$$`, `$n`, etc. in the
107
211
  // substituted CSS/base64 are not interpreted as substitution patterns
108
212
  // (see https://mdn.io/String.replace).
109
- return TEMPLATE.replace("<theme-vars/>", () => `<style>:root { ${themeVars} }</style>`).replace(
110
- "{{SESSION_DATA}}",
111
- () => sessionDataBase64,
112
- );
213
+ return getTemplate()
214
+ .replace("<theme-vars/>", () => `<style>:root { ${themeVars} }</style>`)
215
+ .replace("{{SESSION_DATA}}", () => sessionDataBase64);
113
216
  }
114
217
 
115
218
  /** Export session to HTML using SessionManager and AgentState. */
@@ -123,13 +226,11 @@ export async function exportSessionToHtml(
123
226
  const sessionFile = sm.getSessionFile();
124
227
  if (!sessionFile) throw new Error("Cannot export in-memory session to HTML");
125
228
 
126
- const sessionData: SessionData = {
127
- header: sm.getHeader(),
128
- entries: sm.getEntries(),
129
- leafId: sm.getLeafId(),
130
- systemPrompt: state?.systemPrompt.join("\n\n"),
131
- tools: state?.tools?.map(t => ({ name: t.name, description: t.description })),
132
- };
229
+ const sessionData = buildSessionData(sm, state);
230
+ if (opts.includeSubSessions !== false) {
231
+ const subSessions = await collectSubSessions(sessionFile);
232
+ if (Object.keys(subSessions).length > 0) sessionData.subSessions = subSessions;
233
+ }
133
234
 
134
235
  const html = await generateHtml(sessionData, opts.themeName);
135
236
  const outputPath = opts.outputPath || `${APP_NAME}-session-${path.basename(sessionFile, ".jsonl")}.html`;
@@ -155,6 +256,10 @@ export async function exportFromFile(inputPath: string, options?: ExportOptions
155
256
  entries: sm.getEntries(),
156
257
  leafId: sm.getLeafId(),
157
258
  };
259
+ if (opts.includeSubSessions !== false) {
260
+ const subSessions = await collectSubSessions(inputPath);
261
+ if (Object.keys(subSessions).length > 0) sessionData.subSessions = subSessions;
262
+ }
158
263
 
159
264
  const html = await generateHtml(sessionData, opts.themeName);
160
265
  const outputPath = opts.outputPath || `${APP_NAME}-session-${path.basename(inputPath, ".jsonl")}.html`;
@@ -0,0 +1,102 @@
1
+ (function() {
2
+ 'use strict';
3
+
4
+ // ============================================================
5
+ // SHARE VIEWER BOOTSTRAP
6
+ // ============================================================
7
+ //
8
+ // Served by the omp relay at /s/<id>; the AES-256-GCM key rides in the
9
+ // URL fragment and never leaves the browser. Resolves the session JSON
10
+ // and hands it to template.js via `window.__OMP_SESSION_DATA__`:
11
+ // 1. hex ids -> secret GitHub gist holding base64(sealed blob)
12
+ // 2. anything else -> relay blob store at /s/<id>/raw
13
+ // Sealed layout: [12B IV][AES-256-GCM(gzip(session JSON))].
14
+
15
+ var GIST_ID_RE = /^[0-9a-f]{20,64}$/;
16
+ var SHARE_PATH_RE = /\/s\/([A-Za-z0-9_-]{10,64})\/?$/;
17
+
18
+ function decodeBase64(text) {
19
+ var binary = atob(text);
20
+ var bytes = new Uint8Array(binary.length);
21
+ for (var i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
22
+ return bytes;
23
+ }
24
+
25
+ function decodeBase64Url(text) {
26
+ var b64 = text.replace(/-/g, '+').replace(/_/g, '/');
27
+ while (b64.length % 4) b64 += '=';
28
+ return decodeBase64(b64);
29
+ }
30
+
31
+ async function fetchGistBlob(id) {
32
+ var res = await fetch('https://api.github.com/gists/' + id, {
33
+ headers: { Accept: 'application/vnd.github+json' },
34
+ });
35
+ if (res.status === 404) throw new Error('This share no longer exists (gist deleted?).');
36
+ if (!res.ok) throw new Error('Gist fetch failed: HTTP ' + res.status);
37
+ var gist = await res.json();
38
+ var files = Object.values(gist.files || {});
39
+ var file = files.find(function(f) { return /\.ompshare\.txt$/.test(f.filename); }) || files[0];
40
+ if (!file) throw new Error('Gist has no files.');
41
+ var text = file.content;
42
+ if (!text || file.truncated) {
43
+ var raw = await fetch(file.raw_url);
44
+ if (!raw.ok) throw new Error('Gist raw fetch failed: HTTP ' + raw.status);
45
+ text = await raw.text();
46
+ }
47
+ return decodeBase64(text.replace(/\s+/g, ''));
48
+ }
49
+
50
+ async function fetchServerBlob(id) {
51
+ var res = await fetch('/s/' + id + '/raw');
52
+ if (res.status === 404 || res.status === 410) {
53
+ throw new Error('This share no longer exists (expired or deleted).');
54
+ }
55
+ if (!res.ok) throw new Error('Share fetch failed: HTTP ' + res.status);
56
+ return new Uint8Array(await res.arrayBuffer());
57
+ }
58
+
59
+ async function load() {
60
+ var match = SHARE_PATH_RE.exec(location.pathname);
61
+ if (!match) throw new Error('Bad share URL; expected /s/<id>.');
62
+ var keyText = location.hash.replace(/^#/, '');
63
+ if (!keyText) throw new Error('Share link is missing its #key fragment; paste the full link.');
64
+ var keyBytes;
65
+ try {
66
+ keyBytes = decodeBase64Url(keyText);
67
+ } catch (_err) {
68
+ throw new Error('Share key is not valid base64url.');
69
+ }
70
+ if (keyBytes.length !== 32) throw new Error('Share key must decode to 32 bytes.');
71
+
72
+ var id = match[1];
73
+ var sealed = await (GIST_ID_RE.test(id) ? fetchGistBlob(id) : fetchServerBlob(id));
74
+ if (sealed.length <= 12) throw new Error('Sealed session blob is truncated.');
75
+
76
+ var key = await crypto.subtle.importKey('raw', keyBytes, 'AES-GCM', false, ['decrypt']);
77
+ var plain;
78
+ try {
79
+ plain = await crypto.subtle.decrypt(
80
+ { name: 'AES-GCM', iv: sealed.subarray(0, 12) },
81
+ key,
82
+ sealed.subarray(12)
83
+ );
84
+ } catch (_err) {
85
+ throw new Error('Decryption failed: wrong or corrupted #key.');
86
+ }
87
+
88
+ var data = await new Response(
89
+ new Blob([plain]).stream().pipeThrough(new DecompressionStream('gzip'))
90
+ ).json();
91
+ if (data && data.header && data.header.title) {
92
+ document.title = data.header.title + ' — omp session';
93
+ }
94
+ return data;
95
+ }
96
+
97
+ var pending = load();
98
+ // template.js surfaces the failure in-page; swallow the duplicate here
99
+ // so the console does not report an unhandled rejection.
100
+ pending.catch(function() {});
101
+ window.__OMP_SESSION_DATA__ = pending;
102
+ })();