@oh-my-pi/pi-coding-agent 16.0.7 → 16.0.8
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 +31 -0
- package/dist/cli.js +4752 -12462
- package/dist/types/cli/update-cli.d.ts +11 -0
- package/dist/types/debug/remote-debugger.d.ts +45 -0
- package/dist/types/internal-urls/docs-index.d.ts +19 -0
- package/dist/types/markit/converters/docx.d.ts +6 -0
- package/dist/types/markit/converters/epub.d.ts +15 -0
- package/dist/types/markit/converters/pdf/columns.d.ts +35 -0
- package/dist/types/markit/converters/pdf/extract.d.ts +10 -0
- package/dist/types/markit/converters/pdf/grid.d.ts +25 -0
- package/dist/types/markit/converters/pdf/headers.d.ts +24 -0
- package/dist/types/markit/converters/pdf/index.d.ts +6 -0
- package/dist/types/markit/converters/pdf/render.d.ts +24 -0
- package/dist/types/markit/converters/pdf/types.d.ts +75 -0
- package/dist/types/markit/converters/pptx.d.ts +57 -0
- package/dist/types/markit/converters/xlsx.d.ts +25 -0
- package/dist/types/markit/index.d.ts +2 -0
- package/dist/types/markit/registry.d.ts +16 -0
- package/dist/types/markit/types.d.ts +30 -0
- package/dist/types/session/agent-session.d.ts +7 -8
- package/dist/types/session/auth-storage.d.ts +3 -2
- package/dist/types/session/yield-queue.d.ts +3 -1
- package/dist/types/tools/browser/attach.d.ts +1 -1
- package/dist/types/utils/markit.d.ts +0 -8
- package/dist/types/utils/mupdf-wasm-embed.d.ts +1 -0
- package/dist/types/utils/turndown.d.ts +15 -0
- package/dist/types/utils/zip.d.ts +119 -0
- package/package.json +20 -18
- package/scripts/build-binary.ts +7 -3
- package/scripts/bundle-dist.ts +28 -12
- package/scripts/embed-mupdf-wasm.ts +67 -0
- package/scripts/generate-docs-index.ts +48 -32
- package/scripts/omp +1 -1
- package/src/advisor/__tests__/advisor.test.ts +83 -0
- package/src/advisor/runtime.ts +16 -1
- package/src/cli/auth-broker-cli.ts +1 -3
- package/src/cli/auth-gateway-cli.ts +2 -5
- package/src/cli/update-cli.ts +63 -3
- package/src/config/model-discovery.ts +20 -8
- package/src/config/models-config-schema.ts +8 -1
- package/src/debug/index.ts +44 -0
- package/src/debug/remote-debugger.ts +151 -0
- package/src/debug/report-bundle.ts +2 -1
- package/src/internal-urls/docs-index.generated.txt +2 -0
- package/src/internal-urls/docs-index.ts +102 -0
- package/src/internal-urls/omp-protocol.ts +10 -9
- package/src/markit/NOTICE +32 -0
- package/src/markit/converters/docx.ts +56 -0
- package/src/markit/converters/epub.ts +136 -0
- package/src/markit/converters/mammoth.d.ts +24 -0
- package/src/markit/converters/pdf/columns.ts +103 -0
- package/src/markit/converters/pdf/extract.ts +574 -0
- package/src/markit/converters/pdf/grid.ts +780 -0
- package/src/markit/converters/pdf/headers.ts +106 -0
- package/src/markit/converters/pdf/index.ts +146 -0
- package/src/markit/converters/pdf/render.ts +501 -0
- package/src/markit/converters/pdf/types.ts +84 -0
- package/src/markit/converters/pptx.ts +325 -0
- package/src/markit/converters/xlsx.ts +173 -0
- package/src/markit/index.ts +2 -0
- package/src/markit/registry.ts +59 -0
- package/src/markit/types.ts +35 -0
- package/src/modes/components/snapcompact-shape-preview-doc.md +14 -7
- package/src/modes/components/snapcompact-shape-preview.ts +2 -2
- package/src/modes/controllers/input-controller.ts +29 -8
- package/src/modes/interactive-mode.ts +26 -9
- package/src/prompts/advisor/system.md +1 -0
- package/src/sdk.ts +5 -9
- package/src/session/agent-session.ts +62 -40
- package/src/session/auth-storage.ts +2 -11
- package/src/session/yield-queue.ts +7 -1
- package/src/tools/browser/attach.ts +2 -2
- package/src/tools/fetch.ts +25 -60
- package/src/tools/read.ts +1 -1
- package/src/tools/search.ts +1 -6
- package/src/tools/write.ts +25 -65
- package/src/utils/markit.ts +25 -9
- package/src/utils/mupdf-wasm-embed.ts +12 -0
- package/src/utils/tools-manager.ts +2 -11
- package/src/utils/turndown.ts +83 -0
- package/src/{tools/archive-reader.ts → utils/zip.ts} +453 -83
- package/src/web/scrapers/types.ts +3 -46
- package/dist/types/internal-urls/docs-index.generated.d.ts +0 -2
- package/dist/types/tools/archive-reader.d.ts +0 -49
- package/src/internal-urls/docs-index.generated.ts +0 -120
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/** A ZIP archive decoded to a `path → bytes` map of its file members. */
|
|
2
|
+
export type Unzipped = Record<string, Uint8Array>;
|
|
3
|
+
/** Read a single ZIP entry as UTF-8 text, or `undefined` when the entry is absent. */
|
|
4
|
+
export declare function unzipText(entries: Unzipped, entryPath: string): string | undefined;
|
|
5
|
+
/**
|
|
6
|
+
* Decode an in-memory ZIP archive into a `path → bytes` map of its file members
|
|
7
|
+
* (directory entries and `..`-escaping names are dropped). Shares the
|
|
8
|
+
* central-directory record parser with the lazy, file-backed reader.
|
|
9
|
+
*/
|
|
10
|
+
export declare function unzip(bytes: Uint8Array): Unzipped;
|
|
11
|
+
export type ArchiveFormat = "zip" | "tar" | "tar.gz";
|
|
12
|
+
/**
|
|
13
|
+
* Where to read an archive from: a filesystem path (format inferred from the
|
|
14
|
+
* extension; ZIP is read lazily via ranged central-directory access) or
|
|
15
|
+
* in-memory bytes with an explicit format.
|
|
16
|
+
*/
|
|
17
|
+
export type ArchiveSource = string | {
|
|
18
|
+
bytes: Uint8Array;
|
|
19
|
+
format: ArchiveFormat;
|
|
20
|
+
};
|
|
21
|
+
/** Content for a member when packing or extracting an archive. */
|
|
22
|
+
export type ArchiveMemberContent = string | Uint8Array | Blob;
|
|
23
|
+
export interface ArchivePathCandidate {
|
|
24
|
+
archivePath: string;
|
|
25
|
+
subPath: string;
|
|
26
|
+
}
|
|
27
|
+
export interface ArchiveNode {
|
|
28
|
+
path: string;
|
|
29
|
+
isDirectory: boolean;
|
|
30
|
+
size: number;
|
|
31
|
+
mtimeMs?: number;
|
|
32
|
+
}
|
|
33
|
+
export interface ArchiveDirectoryEntry extends ArchiveNode {
|
|
34
|
+
name: string;
|
|
35
|
+
}
|
|
36
|
+
export interface ExtractedArchiveFile extends ArchiveNode {
|
|
37
|
+
bytes: Uint8Array;
|
|
38
|
+
}
|
|
39
|
+
/** A byte window into an archive — file-backed (lazy) or in-memory. */
|
|
40
|
+
interface ByteSource {
|
|
41
|
+
readonly size: number;
|
|
42
|
+
read(start: number, end: number): Promise<Uint8Array>;
|
|
43
|
+
}
|
|
44
|
+
interface TarStorage {
|
|
45
|
+
type: "tar";
|
|
46
|
+
file: File;
|
|
47
|
+
}
|
|
48
|
+
interface ZipStorage {
|
|
49
|
+
type: "zip";
|
|
50
|
+
source: ByteSource;
|
|
51
|
+
compressedSize: number;
|
|
52
|
+
compression: number;
|
|
53
|
+
flags: number;
|
|
54
|
+
localHeaderOffset: number;
|
|
55
|
+
}
|
|
56
|
+
type EntryStorage = TarStorage | ZipStorage;
|
|
57
|
+
interface ArchiveIndexEntry extends ArchiveNode {
|
|
58
|
+
storage?: EntryStorage;
|
|
59
|
+
}
|
|
60
|
+
/** Infer an archive format from a filesystem path's extension. */
|
|
61
|
+
export declare function archiveFormatFromPath(filePath: string): ArchiveFormat | undefined;
|
|
62
|
+
export declare function formatArchiveEntryLines(entries: readonly ArchiveDirectoryEntry[]): string[];
|
|
63
|
+
export declare function sniffArchiveFormat(bytes: Uint8Array): ArchiveFormat | undefined;
|
|
64
|
+
/**
|
|
65
|
+
* Split an `archive.ext:inner/path` reference into every plausible
|
|
66
|
+
* `{ archivePath, subPath }` pair, longest archive prefix first. A path may
|
|
67
|
+
* contain more than one archive extension, so each candidate is a guess at
|
|
68
|
+
* where the archive ends and the member portion begins.
|
|
69
|
+
*/
|
|
70
|
+
export declare function parseArchivePathCandidates(filePath: string): ArchivePathCandidate[];
|
|
71
|
+
/**
|
|
72
|
+
* An indexed, read-only view over a single archive. ZIP archives are indexed
|
|
73
|
+
* from the central directory and members are inflated on demand; tar archives
|
|
74
|
+
* are fully materialized by `Bun.Archive` up front.
|
|
75
|
+
*/
|
|
76
|
+
export declare class ArchiveReader {
|
|
77
|
+
#private;
|
|
78
|
+
readonly format: ArchiveFormat;
|
|
79
|
+
constructor(format: ArchiveFormat, entries: ArchiveIndexEntry[]);
|
|
80
|
+
getNode(subPath?: string): ArchiveNode | undefined;
|
|
81
|
+
listDirectory(subPath?: string): ArchiveDirectoryEntry[];
|
|
82
|
+
readFile(subPath: string): Promise<ExtractedArchiveFile>;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Open an archive for reading. ZIP archives opened from a path are indexed
|
|
86
|
+
* lazily via ranged central-directory reads (members inflate on demand); tar
|
|
87
|
+
* archives and in-memory ZIPs are read from a single buffer.
|
|
88
|
+
*/
|
|
89
|
+
export declare function openArchive(source: ArchiveSource): Promise<ArchiveReader>;
|
|
90
|
+
/** Render the top-level entries of an in-memory archive as one line each. */
|
|
91
|
+
export declare function listArchiveRoot(bytes: Uint8Array, format: ArchiveFormat, opts?: {
|
|
92
|
+
limit?: number;
|
|
93
|
+
}): Promise<string>;
|
|
94
|
+
/**
|
|
95
|
+
* Fully materialize every file member into a `path → content` map: ZIP members
|
|
96
|
+
* are inflated in memory, tar members are returned as lazy `File`s. Use this
|
|
97
|
+
* when you need every entry (rewrite, extract); for browsing or single-member
|
|
98
|
+
* reads prefer `openArchive`, which is lazy for ZIP.
|
|
99
|
+
*/
|
|
100
|
+
export declare function readArchiveEntries(source: ArchiveSource): Promise<Map<string, ArchiveMemberContent>>;
|
|
101
|
+
/**
|
|
102
|
+
* Serialize `entries` into an archive of `format` and write it to `destPath`.
|
|
103
|
+
* ZIP is framed in memory, tar / tar.gz via `Bun.Archive` (gzip for tar.gz).
|
|
104
|
+
* String members are encoded as UTF-8.
|
|
105
|
+
*/
|
|
106
|
+
export declare function writeArchive(destPath: string, format: ArchiveFormat, entries: Iterable<readonly [string, ArchiveMemberContent]>): Promise<void>;
|
|
107
|
+
/**
|
|
108
|
+
* Extract every file member to `destDir`, creating parent directories as
|
|
109
|
+
* needed. Entries that would escape `destDir` (via `..` or an absolute path)
|
|
110
|
+
* are rejected. Returns the number of files written.
|
|
111
|
+
*/
|
|
112
|
+
export declare function extractArchive(source: ArchiveSource, destDir: string): Promise<number>;
|
|
113
|
+
/**
|
|
114
|
+
* Frame a `path → bytes` map into a ZIP archive in memory. Each member is raw
|
|
115
|
+
* DEFLATE unless that would not shrink it, in which case it is stored. ZIP64 is
|
|
116
|
+
* not emitted; archives beyond the 32-bit limits throw rather than corrupt.
|
|
117
|
+
*/
|
|
118
|
+
export declare function zip(entries: Unzipped): Uint8Array;
|
|
119
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@oh-my-pi/pi-coding-agent",
|
|
4
|
-
"version": "16.0.
|
|
4
|
+
"version": "16.0.8",
|
|
5
5
|
"description": "Coding agent CLI with read, bash, edit, write tools and session management",
|
|
6
6
|
"homepage": "https://omp.sh",
|
|
7
7
|
"author": "Can Boluk",
|
|
@@ -35,29 +35,30 @@
|
|
|
35
35
|
"check": "biome check . && bun run check:types",
|
|
36
36
|
"check:types": "tsgo -p tsconfig.json --noEmit",
|
|
37
37
|
"lint": "biome lint .",
|
|
38
|
-
"test": "bun test --parallel=4",
|
|
39
|
-
"fix": "biome check --write --unsafe . && bun run format-prompts
|
|
38
|
+
"test": "bun test --parallel=4 test src",
|
|
39
|
+
"fix": "biome check --write --unsafe . && bun run format-prompts",
|
|
40
40
|
"fmt": "biome format --write . && bun run format-prompts",
|
|
41
41
|
"format-prompts": "bun scripts/format-prompts.ts",
|
|
42
|
-
"generate-docs-index": "bun scripts/generate-docs-index.ts",
|
|
43
|
-
"prepack": "bun scripts/generate-docs-index.ts && bun --cwd=../collab-web run build:tool-views && bun scripts/bundle-dist.ts",
|
|
42
|
+
"generate-docs-index": "bun scripts/generate-docs-index.ts --generate",
|
|
43
|
+
"prepack": "bun scripts/generate-docs-index.ts --generate && bun --cwd=../collab-web run build:tool-views && bun scripts/bundle-dist.ts || ( bun scripts/generate-docs-index.ts --reset; exit 1 )",
|
|
44
|
+
"postpack": "bun scripts/generate-docs-index.ts --reset",
|
|
44
45
|
"bench:guard": "bun scripts/bench-guard.ts"
|
|
45
46
|
},
|
|
46
47
|
"dependencies": {
|
|
47
48
|
"@agentclientprotocol/sdk": "0.25.0",
|
|
48
49
|
"@babel/parser": "^7.29.7",
|
|
49
50
|
"@mozilla/readability": "^0.6.0",
|
|
50
|
-
"@oh-my-pi/hashline": "16.0.
|
|
51
|
-
"@oh-my-pi/omp-stats": "16.0.
|
|
52
|
-
"@oh-my-pi/pi-agent-core": "16.0.
|
|
53
|
-
"@oh-my-pi/pi-ai": "16.0.
|
|
54
|
-
"@oh-my-pi/pi-catalog": "16.0.
|
|
55
|
-
"@oh-my-pi/pi-mnemopi": "16.0.
|
|
56
|
-
"@oh-my-pi/pi-natives": "16.0.
|
|
57
|
-
"@oh-my-pi/pi-tui": "16.0.
|
|
58
|
-
"@oh-my-pi/pi-utils": "16.0.
|
|
59
|
-
"@oh-my-pi/pi-wire": "16.0.
|
|
60
|
-
"@oh-my-pi/snapcompact": "16.0.
|
|
51
|
+
"@oh-my-pi/hashline": "16.0.8",
|
|
52
|
+
"@oh-my-pi/omp-stats": "16.0.8",
|
|
53
|
+
"@oh-my-pi/pi-agent-core": "16.0.8",
|
|
54
|
+
"@oh-my-pi/pi-ai": "16.0.8",
|
|
55
|
+
"@oh-my-pi/pi-catalog": "16.0.8",
|
|
56
|
+
"@oh-my-pi/pi-mnemopi": "16.0.8",
|
|
57
|
+
"@oh-my-pi/pi-natives": "16.0.8",
|
|
58
|
+
"@oh-my-pi/pi-tui": "16.0.8",
|
|
59
|
+
"@oh-my-pi/pi-utils": "16.0.8",
|
|
60
|
+
"@oh-my-pi/pi-wire": "16.0.8",
|
|
61
|
+
"@oh-my-pi/snapcompact": "16.0.8",
|
|
61
62
|
"@opentelemetry/api": "^1.9.1",
|
|
62
63
|
"@opentelemetry/context-async-hooks": "^2.7.1",
|
|
63
64
|
"@opentelemetry/exporter-trace-otlp-proto": "^0.218.0",
|
|
@@ -70,11 +71,12 @@
|
|
|
70
71
|
"arktype": "^2.2.0",
|
|
71
72
|
"chalk": "^5.6.2",
|
|
72
73
|
"diff": "^9.0.0",
|
|
73
|
-
"
|
|
74
|
+
"fast-xml-parser": "^5.9.0",
|
|
74
75
|
"handlebars": "^4.7.9",
|
|
75
76
|
"linkedom": "^0.18.12",
|
|
76
77
|
"lru-cache": "11.5.1",
|
|
77
|
-
"
|
|
78
|
+
"mammoth": "^1.12.0",
|
|
79
|
+
"mupdf": "^1.27.0",
|
|
78
80
|
"puppeteer-core": "^25.1.0",
|
|
79
81
|
"turndown": "7.2.4",
|
|
80
82
|
"turndown-plugin-gfm": "1.0.2",
|
package/scripts/build-binary.ts
CHANGED
|
@@ -38,9 +38,13 @@ async function runCommand(
|
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
async function main(): Promise<void> {
|
|
41
|
-
|
|
41
|
+
// Generate inside the try so the finally always restores the empty checked-in
|
|
42
|
+
// placeholders (stats client archive, docs index) even on failure.
|
|
42
43
|
try {
|
|
44
|
+
await runCommand(["bun", "--cwd=../stats", "scripts/generate-client-bundle.ts", "--generate"]);
|
|
45
|
+
await runCommand(["bun", "scripts/generate-docs-index.ts", "--generate"]);
|
|
43
46
|
await runCommand(["bun", "--cwd=../natives", "run", "embed:native"]);
|
|
47
|
+
await runCommand(["bun", "scripts/embed-mupdf-wasm.ts", "--generate"]);
|
|
44
48
|
try {
|
|
45
49
|
const buildEnv = shouldAdhocSignDarwinBinary() ? { ...Bun.env, BUN_NO_CODESIGN_MACHO_BINARY: "1" } : Bun.env;
|
|
46
50
|
await runCommand(
|
|
@@ -58,8 +62,6 @@ async function main(): Promise<void> {
|
|
|
58
62
|
"--define",
|
|
59
63
|
`process.env.PI_TINY_TRANSFORMERS_VERSION=${JSON.stringify(transformersVersion)}`,
|
|
60
64
|
"--external",
|
|
61
|
-
"mupdf",
|
|
62
|
-
"--external",
|
|
63
65
|
"fastembed",
|
|
64
66
|
"--external",
|
|
65
67
|
"onnxruntime-node",
|
|
@@ -94,10 +96,12 @@ async function main(): Promise<void> {
|
|
|
94
96
|
await runCommand(["codesign", "--force", "--sign", "-", outputPath]);
|
|
95
97
|
}
|
|
96
98
|
} finally {
|
|
99
|
+
await runCommand(["bun", "scripts/embed-mupdf-wasm.ts", "--reset"]);
|
|
97
100
|
await runCommand(["bun", "--cwd=../natives", "run", "embed:native", "--reset"]);
|
|
98
101
|
}
|
|
99
102
|
} finally {
|
|
100
103
|
await runCommand(["bun", "--cwd=../stats", "scripts/generate-client-bundle.ts", "--reset"]);
|
|
104
|
+
await runCommand(["bun", "scripts/generate-docs-index.ts", "--reset"]);
|
|
101
105
|
}
|
|
102
106
|
}
|
|
103
107
|
|
package/scripts/bundle-dist.ts
CHANGED
|
@@ -9,6 +9,30 @@ const outDir = path.join(packageDir, "dist");
|
|
|
9
9
|
const cliPath = path.join(outDir, "cli.js");
|
|
10
10
|
const shebang = "#!/usr/bin/env bun\n";
|
|
11
11
|
|
|
12
|
+
// Native / optional / platform-specific deps that are never bundled — installed on
|
|
13
|
+
// demand (transformers/fastembed/onnxruntime) or shipped as their own artifact
|
|
14
|
+
// (native addon, mupdf).
|
|
15
|
+
const ALWAYS_EXTERNAL = ["mupdf", "@oh-my-pi/pi-natives", "@huggingface/transformers", "fastembed", "onnxruntime-node"];
|
|
16
|
+
|
|
17
|
+
// Heavy, lazily-used third-party leaf deps. Each is a declared `dependency`, so the
|
|
18
|
+
// published package resolves it from node_modules at runtime; bundling only embeds a
|
|
19
|
+
// redundant copy that bloats dist/cli.js. NEVER add a patched dependency here — the
|
|
20
|
+
// bundle is where a root `patchedDependencies` patch is baked in, so an externalized
|
|
21
|
+
// import would load the unpatched npm package in users' installs (currently
|
|
22
|
+
// @ark/schema is patched, so it — and arktype, which pulls @ark/schema — stay
|
|
23
|
+
// bundled).
|
|
24
|
+
const RUNTIME_EXTERNAL = [
|
|
25
|
+
"puppeteer-core",
|
|
26
|
+
"@puppeteer/browsers",
|
|
27
|
+
"@babel/parser",
|
|
28
|
+
"@xterm/headless",
|
|
29
|
+
"turndown",
|
|
30
|
+
"turndown-plugin-gfm",
|
|
31
|
+
"@mozilla/readability",
|
|
32
|
+
"linkedom",
|
|
33
|
+
"@agentclientprotocol/sdk",
|
|
34
|
+
];
|
|
35
|
+
|
|
12
36
|
async function runCommand(command: string[]): Promise<void> {
|
|
13
37
|
const proc = Bun.spawn(command, {
|
|
14
38
|
cwd: packageDir,
|
|
@@ -63,19 +87,11 @@ async function main(): Promise<void> {
|
|
|
63
87
|
"--target=bun",
|
|
64
88
|
"--outdir",
|
|
65
89
|
"dist",
|
|
66
|
-
|
|
67
|
-
|
|
90
|
+
// Full minify (whitespace + syntax + identifiers); --keep-names retains
|
|
91
|
+
// fn/class .name where code depends on it.
|
|
92
|
+
"--minify",
|
|
68
93
|
"--keep-names",
|
|
69
|
-
"--external",
|
|
70
|
-
"mupdf",
|
|
71
|
-
"--external",
|
|
72
|
-
"@oh-my-pi/pi-natives",
|
|
73
|
-
"--external",
|
|
74
|
-
"@huggingface/transformers",
|
|
75
|
-
"--external",
|
|
76
|
-
"fastembed",
|
|
77
|
-
"--external",
|
|
78
|
-
"onnxruntime-node",
|
|
94
|
+
...[...ALWAYS_EXTERNAL, ...RUNTIME_EXTERNAL].flatMap(dep => ["--external", dep]),
|
|
79
95
|
"--define",
|
|
80
96
|
'process.env.PI_BUNDLED="true"',
|
|
81
97
|
"./src/cli.ts",
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
|
|
3
|
+
// Embeds mupdf's `mupdf-wasm.wasm` into the compiled single-file binary.
|
|
4
|
+
//
|
|
5
|
+
// mupdf loads its wasm by reading the `mupdf-wasm.wasm` sibling of its own
|
|
6
|
+
// module via `new URL(..., import.meta.url)` + `readFileSync`. A `bun --compile`
|
|
7
|
+
// binary has no node_modules, so that read fails (`ENOENT .../mupdf-wasm.wasm`),
|
|
8
|
+
// and marking mupdf `--external` instead makes `bun --compile` eagerly fail to
|
|
9
|
+
// resolve the package at startup (the static `import * as mupdf` lives in a lazy
|
|
10
|
+
// chunk but is hoisted). So the binary build bundles mupdf and embeds the wasm
|
|
11
|
+
// bytes here, handing them to the WASM module as `$libmupdf_wasm_Module.wasmBinary`
|
|
12
|
+
// (see src/utils/markit.ts).
|
|
13
|
+
//
|
|
14
|
+
// `--generate` copies the wasm next to src/utils/mupdf-wasm-embed.ts and rewrites
|
|
15
|
+
// that module to import it via `with { type: "file" }`; `--reset` restores the
|
|
16
|
+
// checked-in placeholder and removes the copy. The npm `dist/cli.js` bundle never
|
|
17
|
+
// runs this — it keeps mupdf external and loads the wasm from node_modules.
|
|
18
|
+
|
|
19
|
+
import * as fs from "node:fs/promises";
|
|
20
|
+
import { createRequire } from "node:module";
|
|
21
|
+
import * as path from "node:path";
|
|
22
|
+
|
|
23
|
+
const utilsDir = path.join(import.meta.dir, "..", "src", "utils");
|
|
24
|
+
const helperPath = path.join(utilsDir, "mupdf-wasm-embed.ts");
|
|
25
|
+
const wasmCopyPath = path.join(utilsDir, "mupdf-wasm.wasm");
|
|
26
|
+
|
|
27
|
+
const placeholder = `// AUTOGENERATED -- managed by scripts/embed-mupdf-wasm.ts. Do not edit by hand.
|
|
28
|
+
//
|
|
29
|
+
// Compiled single-file binaries cannot let mupdf resolve its \`mupdf-wasm.wasm\`
|
|
30
|
+
// sibling from the read-only bunfs, so the binary build (scripts/build-binary.ts
|
|
31
|
+
// and scripts/ci-release-build-binaries.ts) regenerates this module to embed the
|
|
32
|
+
// wasm bytes via \`with { type: "file" }\` and copies the wasm next to it. Source
|
|
33
|
+
// checkouts, \`bun test\`, and the npm \`dist/cli.js\` bundle keep mupdf external and
|
|
34
|
+
// load the wasm from node_modules, so this placeholder returns undefined and the
|
|
35
|
+
// build resets back to it afterward.
|
|
36
|
+
export function loadEmbeddedMupdfWasm(): Uint8Array | undefined {
|
|
37
|
+
\treturn undefined;
|
|
38
|
+
}
|
|
39
|
+
`;
|
|
40
|
+
|
|
41
|
+
const generated = `// AUTOGENERATED -- managed by scripts/embed-mupdf-wasm.ts. Do not edit or commit.
|
|
42
|
+
import { readFileSync } from "node:fs";
|
|
43
|
+
import wasmPath from "./mupdf-wasm.wasm" with { type: "file" };
|
|
44
|
+
|
|
45
|
+
export function loadEmbeddedMupdfWasm(): Uint8Array | undefined {
|
|
46
|
+
\treturn readFileSync(wasmPath);
|
|
47
|
+
}
|
|
48
|
+
`;
|
|
49
|
+
|
|
50
|
+
if (process.argv.includes("--reset")) {
|
|
51
|
+
await Bun.write(helperPath, placeholder);
|
|
52
|
+
try {
|
|
53
|
+
await fs.unlink(wasmCopyPath);
|
|
54
|
+
} catch (err) {
|
|
55
|
+
if ((err as NodeJS.ErrnoException).code !== "ENOENT") throw err;
|
|
56
|
+
}
|
|
57
|
+
process.exit(0);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const wasmSource = path.join(path.dirname(createRequire(import.meta.url).resolve("mupdf")), "mupdf-wasm.wasm");
|
|
61
|
+
const wasmFile = Bun.file(wasmSource);
|
|
62
|
+
if (!(await wasmFile.exists())) {
|
|
63
|
+
throw new Error(`mupdf wasm not found at ${wasmSource}; run \`bun install\` first.`);
|
|
64
|
+
}
|
|
65
|
+
await Bun.write(wasmCopyPath, wasmFile);
|
|
66
|
+
await Bun.write(helperPath, generated);
|
|
67
|
+
console.log(`Embedded mupdf wasm (${wasmFile.size} bytes) into ${path.relative(process.cwd(), wasmCopyPath)}`);
|
|
@@ -1,40 +1,56 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Populate (or reset) the embedded harness documentation index for `omp://`.
|
|
5
|
+
*
|
|
6
|
+
* `--generate` writes `src/internal-urls/docs-index.generated.txt` as two lines:
|
|
7
|
+
* a plain JSON array of the sorted `docs/**\/*.md` file names, then a base64
|
|
8
|
+
* gzip blob of the index-aligned doc bodies (`string[]`). Keeping the filename
|
|
9
|
+
* list out of the blob lets the loader list docs without inflating it.
|
|
10
|
+
* Compiled binaries and the prepacked npm bundle inline this (~0.5MB) instead of
|
|
11
|
+
* the ~1.6MB raw map; `--reset` restores the checked-in empty placeholder so the
|
|
12
|
+
* dev tree reads `docs/` from disk. Mirrors the stats / model-catalog embeds.
|
|
13
|
+
*/
|
|
14
|
+
|
|
3
15
|
import * as path from "node:path";
|
|
16
|
+
import { gzipSync } from "node:zlib";
|
|
4
17
|
import { Glob } from "bun";
|
|
5
18
|
|
|
6
19
|
const docsDir = path.resolve(import.meta.dir, "../../../docs");
|
|
7
|
-
const outputPath = path.resolve(import.meta.dir, "../src/internal-urls/docs-index.generated.
|
|
20
|
+
const outputPath = path.resolve(import.meta.dir, "../src/internal-urls/docs-index.generated.txt");
|
|
21
|
+
const GENERATE_FLAG = "--generate";
|
|
22
|
+
const RESET_FLAG = "--reset";
|
|
23
|
+
|
|
24
|
+
async function main(): Promise<void> {
|
|
25
|
+
const rel = path.relative(process.cwd(), outputPath);
|
|
26
|
+
|
|
27
|
+
if (process.argv.includes(RESET_FLAG)) {
|
|
28
|
+
await Bun.write(outputPath, "");
|
|
29
|
+
console.log(`Reset ${rel}`);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (!process.argv.includes(GENERATE_FLAG)) {
|
|
34
|
+
console.log(`Skipping ${rel}; pass ${GENERATE_FLAG} to embed docs (the dev tree reads docs/ from disk)`);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
8
37
|
|
|
9
|
-
const glob = new Glob("**/*.md");
|
|
10
|
-
const
|
|
11
|
-
for await (const relativePath of glob.scan(docsDir)) {
|
|
12
|
-
|
|
38
|
+
const glob = new Glob("**/*.md");
|
|
39
|
+
const files: string[] = [];
|
|
40
|
+
for await (const relativePath of glob.scan(docsDir)) {
|
|
41
|
+
files.push(relativePath.split(path.sep).join("/"));
|
|
42
|
+
}
|
|
43
|
+
files.sort();
|
|
44
|
+
|
|
45
|
+
// Index-aligned bodies (Promise.all preserves order), kept separate from the
|
|
46
|
+
// filename list so the loader can list docs without inflating the blob.
|
|
47
|
+
const bodies = await Promise.all(files.map(file => Bun.file(path.join(docsDir, file)).text()));
|
|
48
|
+
|
|
49
|
+
const bodiesB64 = Buffer.from(gzipSync(Buffer.from(JSON.stringify(bodies)), { level: 9 })).toString("base64");
|
|
50
|
+
// Two lines: plain filename array, then the base64 gzip blob.
|
|
51
|
+
const payload = `${JSON.stringify(files)}\n${bodiesB64}`;
|
|
52
|
+
await Bun.write(outputPath, payload);
|
|
53
|
+
console.log(`Generated ${rel} (${files.length} docs, ${payload.length} bytes)`);
|
|
13
54
|
}
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
const docsWithContent = await Promise.all(
|
|
17
|
-
entries.map(async relativePath => ({
|
|
18
|
-
relativePath,
|
|
19
|
-
content: await Bun.file(path.join(docsDir, relativePath)).text(),
|
|
20
|
-
})),
|
|
21
|
-
);
|
|
22
|
-
|
|
23
|
-
const filenamesLiteral = JSON.stringify(entries);
|
|
24
|
-
|
|
25
|
-
const mapEntries = docsWithContent
|
|
26
|
-
.map(({ relativePath, content }) => `\t${JSON.stringify(relativePath)}: ${JSON.stringify(content)},`)
|
|
27
|
-
.join("\n");
|
|
28
|
-
const output = [
|
|
29
|
-
"// Auto-generated by scripts/generate-docs-index.ts - DO NOT EDIT",
|
|
30
|
-
"",
|
|
31
|
-
`export const EMBEDDED_DOC_FILENAMES: readonly string[] = ${filenamesLiteral};`,
|
|
32
|
-
"",
|
|
33
|
-
`export const EMBEDDED_DOCS: Readonly<Record<string, string>> = {`,
|
|
34
|
-
`${mapEntries}`,
|
|
35
|
-
`};`,
|
|
36
|
-
"",
|
|
37
|
-
].join("\n");
|
|
38
|
-
|
|
39
|
-
await Bun.write(outputPath, output);
|
|
40
|
-
console.log(`Generated ${path.relative(process.cwd(), outputPath)} (${entries.length} docs)`);
|
|
55
|
+
|
|
56
|
+
await main();
|
package/scripts/omp
CHANGED
|
@@ -106,6 +106,32 @@ describe("advisor", () => {
|
|
|
106
106
|
yq.enqueue("normal", { note: "b" });
|
|
107
107
|
expect(scheduled).toBe(1);
|
|
108
108
|
});
|
|
109
|
+
|
|
110
|
+
it("clear(kind) drops only that kind's queued entries", () => {
|
|
111
|
+
const yq = new YieldQueue({
|
|
112
|
+
isStreaming: () => false,
|
|
113
|
+
injectIdle: async () => {},
|
|
114
|
+
scheduleIdleFlush: () => {},
|
|
115
|
+
});
|
|
116
|
+
yq.register<{ note: string }>("advisor", {
|
|
117
|
+
build: entries => (entries.length === 0 ? null : ({ role: "custom", content: "x" } as AgentMessage)),
|
|
118
|
+
skipIdleFlush: true,
|
|
119
|
+
});
|
|
120
|
+
yq.register<{ note: string }>("normal", {
|
|
121
|
+
build: entries => (entries.length === 0 ? null : ({ role: "custom", content: "y" } as AgentMessage)),
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
yq.enqueue("advisor", { note: "stale advice" });
|
|
125
|
+
yq.enqueue("normal", { note: "keep me" });
|
|
126
|
+
expect(yq.has("advisor")).toBe(true);
|
|
127
|
+
expect(yq.has("normal")).toBe(true);
|
|
128
|
+
|
|
129
|
+
// Conversation-boundary cleanup must drop advisor deliveries without
|
|
130
|
+
// touching other kinds (IRC asides, async-job/diagnostic deliveries).
|
|
131
|
+
yq.clear("advisor");
|
|
132
|
+
expect(yq.has("advisor")).toBe(false);
|
|
133
|
+
expect(yq.has("normal")).toBe(true);
|
|
134
|
+
});
|
|
109
135
|
});
|
|
110
136
|
|
|
111
137
|
describe("AdviseTool", () => {
|
|
@@ -659,6 +685,63 @@ describe("advisor", () => {
|
|
|
659
685
|
expect(promptInputs).toHaveLength(3);
|
|
660
686
|
expect(runtime.backlog).toBe(0);
|
|
661
687
|
});
|
|
688
|
+
|
|
689
|
+
it("drops the in-flight batch when a reset aborts the advisor prompt", async () => {
|
|
690
|
+
const promptInputs: string[] = [];
|
|
691
|
+
const { promise: firstPromptStarted, resolve: startFirstPrompt } = Promise.withResolvers<void>();
|
|
692
|
+
let rejectInFlight: ((err: unknown) => void) | undefined;
|
|
693
|
+
let promptCalls = 0;
|
|
694
|
+
const agent: AdvisorAgent = {
|
|
695
|
+
prompt: input => {
|
|
696
|
+
promptInputs.push(input);
|
|
697
|
+
promptCalls++;
|
|
698
|
+
if (promptCalls === 1) {
|
|
699
|
+
const { promise, reject } = Promise.withResolvers<void>();
|
|
700
|
+
rejectInFlight = reject;
|
|
701
|
+
startFirstPrompt();
|
|
702
|
+
return promise;
|
|
703
|
+
}
|
|
704
|
+
return Promise.resolve();
|
|
705
|
+
},
|
|
706
|
+
// AdvisorRuntime.reset() calls agent.reset() then agent.abort(); the real
|
|
707
|
+
// Agent.abort rejects the awaited prompt, so model that rejection here.
|
|
708
|
+
abort: () => rejectInFlight?.(new Error("advisor reset")),
|
|
709
|
+
reset: () => {},
|
|
710
|
+
state: { messages: [] },
|
|
711
|
+
};
|
|
712
|
+
const messages: AgentMessage[] = [{ role: "user", content: "old-conversation", timestamp: 1 } as AgentMessage];
|
|
713
|
+
const host: AdvisorRuntimeHost = {
|
|
714
|
+
snapshotMessages: () => messages,
|
|
715
|
+
enqueueAdvice: () => {},
|
|
716
|
+
};
|
|
717
|
+
const runtime = new AdvisorRuntime(agent, host, 0);
|
|
718
|
+
|
|
719
|
+
runtime.onTurnEnd(messages);
|
|
720
|
+
await firstPromptStarted;
|
|
721
|
+
expect(promptInputs).toHaveLength(1);
|
|
722
|
+
expect(promptInputs[0]).toContain("old-conversation");
|
|
723
|
+
|
|
724
|
+
// Conversation boundary (/new): transcript replaced and the runtime reset
|
|
725
|
+
// while the advisor prompt is still in flight. The abort that rejects the
|
|
726
|
+
// prompt is the reset itself — it must NOT be treated as a transient
|
|
727
|
+
// failure that requeues and re-sends the stale pre-reset batch.
|
|
728
|
+
messages.length = 0;
|
|
729
|
+
messages.push({ role: "user", content: "new-conversation", timestamp: 2 } as AgentMessage);
|
|
730
|
+
runtime.reset();
|
|
731
|
+
await Bun.sleep(0);
|
|
732
|
+
await Bun.sleep(0);
|
|
733
|
+
|
|
734
|
+
expect(promptInputs).toHaveLength(1);
|
|
735
|
+
expect(runtime.backlog).toBe(0);
|
|
736
|
+
|
|
737
|
+
// The runtime still works afterward: the next turn replays the new
|
|
738
|
+
// transcript only, never the dropped pre-reset content.
|
|
739
|
+
runtime.onTurnEnd(messages);
|
|
740
|
+
await Bun.sleep(0);
|
|
741
|
+
expect(promptInputs).toHaveLength(2);
|
|
742
|
+
expect(promptInputs[1]).toContain("new-conversation");
|
|
743
|
+
expect(promptInputs[1]).not.toContain("old-conversation");
|
|
744
|
+
});
|
|
662
745
|
});
|
|
663
746
|
|
|
664
747
|
describe("read-only tool allowlist", () => {
|
package/src/advisor/runtime.ts
CHANGED
|
@@ -48,6 +48,11 @@ export class AdvisorRuntime {
|
|
|
48
48
|
#consecutiveFailures = 0;
|
|
49
49
|
#latestMessages?: AgentMessage[];
|
|
50
50
|
#waiters: CatchupWaiter[] = [];
|
|
51
|
+
/** Bumped by every external {@link reset}/{@link dispose}. A drain iteration
|
|
52
|
+
* captures it before its awaits; a mismatch on resume means a reset aborted
|
|
53
|
+
* the in-flight advisor prompt, so the stale batch is dropped instead of
|
|
54
|
+
* being retried/requeued into the post-reset conversation. */
|
|
55
|
+
#epoch = 0;
|
|
51
56
|
disposed = false;
|
|
52
57
|
|
|
53
58
|
constructor(
|
|
@@ -95,6 +100,7 @@ export class AdvisorRuntime {
|
|
|
95
100
|
|
|
96
101
|
dispose(): void {
|
|
97
102
|
this.disposed = true;
|
|
103
|
+
this.#epoch++;
|
|
98
104
|
this.#pending = [];
|
|
99
105
|
this.#backlog = 0;
|
|
100
106
|
this.#consecutiveFailures = 0;
|
|
@@ -130,6 +136,7 @@ export class AdvisorRuntime {
|
|
|
130
136
|
* leaving it blind to everything before the rewrite.
|
|
131
137
|
*/
|
|
132
138
|
reset(): void {
|
|
139
|
+
this.#epoch++;
|
|
133
140
|
this.#resetAdvisorContext(true, true);
|
|
134
141
|
}
|
|
135
142
|
|
|
@@ -187,6 +194,7 @@ export class AdvisorRuntime {
|
|
|
187
194
|
try {
|
|
188
195
|
while (!this.disposed && this.#pending.length) {
|
|
189
196
|
const popped = this.#pending.splice(0);
|
|
197
|
+
const epoch = this.#epoch;
|
|
190
198
|
// Each delta already opens with a `### Session update` heading, so
|
|
191
199
|
// join with a blank line rather than a `---` rule.
|
|
192
200
|
const candidateBatch = popped.map(b => b.text).join("\n\n");
|
|
@@ -205,6 +213,8 @@ export class AdvisorRuntime {
|
|
|
205
213
|
logger.debug("advisor context maintenance failed", { err: String(err) });
|
|
206
214
|
}
|
|
207
215
|
}
|
|
216
|
+
// A reset/dispose during context maintenance invalidates this batch.
|
|
217
|
+
if (this.#epoch !== epoch) continue;
|
|
208
218
|
|
|
209
219
|
let batch: string | null;
|
|
210
220
|
let finalTurns: number;
|
|
@@ -231,6 +241,11 @@ export class AdvisorRuntime {
|
|
|
231
241
|
success = true;
|
|
232
242
|
this.#consecutiveFailures = 0;
|
|
233
243
|
} catch (err) {
|
|
244
|
+
// reset()/dispose() aborts the in-flight prompt; the rejection is the
|
|
245
|
+
// reset itself, not a transient advisor failure. Drop the stale batch
|
|
246
|
+
// (reset already cleared #pending and rewound the cursor) instead of
|
|
247
|
+
// requeuing it into the post-reset conversation.
|
|
248
|
+
if (this.#epoch !== epoch) continue;
|
|
234
249
|
logger.debug("advisor turn failed", { err: String(err) });
|
|
235
250
|
this.#consecutiveFailures++;
|
|
236
251
|
if (this.#consecutiveFailures >= 3) {
|
|
@@ -243,7 +258,7 @@ export class AdvisorRuntime {
|
|
|
243
258
|
}
|
|
244
259
|
}
|
|
245
260
|
|
|
246
|
-
if (success) {
|
|
261
|
+
if (success && this.#epoch === epoch) {
|
|
247
262
|
this.#backlog = Math.max(0, this.#backlog - finalTurns);
|
|
248
263
|
this.#notifyWaiters();
|
|
249
264
|
}
|
|
@@ -19,11 +19,9 @@ import * as os from "node:os";
|
|
|
19
19
|
import * as path from "node:path";
|
|
20
20
|
import * as readline from "node:readline";
|
|
21
21
|
import {
|
|
22
|
-
AuthBrokerClient,
|
|
23
22
|
type AuthCredential,
|
|
24
23
|
AuthStorage,
|
|
25
24
|
type CredentialDisabledEvent,
|
|
26
|
-
DEFAULT_AUTH_BROKER_BIND,
|
|
27
25
|
getEnvApiKey,
|
|
28
26
|
getOAuthProviders,
|
|
29
27
|
listProvidersWithEnvKey,
|
|
@@ -32,8 +30,8 @@ import {
|
|
|
32
30
|
type OAuthProviderInfo,
|
|
33
31
|
PROVIDER_REGISTRY,
|
|
34
32
|
SqliteAuthCredentialStore,
|
|
35
|
-
startAuthBroker,
|
|
36
33
|
} from "@oh-my-pi/pi-ai";
|
|
34
|
+
import { AuthBrokerClient, DEFAULT_AUTH_BROKER_BIND, startAuthBroker } from "@oh-my-pi/pi-ai/auth-broker";
|
|
37
35
|
import { $which, APP_NAME, getAgentDbPath, getConfigRootDir, isEnoent, logger, VERSION } from "@oh-my-pi/pi-utils";
|
|
38
36
|
import { setTransports as setLoggerTransports } from "@oh-my-pi/pi-utils/logger";
|
|
39
37
|
import { $ } from "bun";
|
|
@@ -17,18 +17,15 @@ import * as fs from "node:fs/promises";
|
|
|
17
17
|
import * as path from "node:path";
|
|
18
18
|
import {
|
|
19
19
|
type Api,
|
|
20
|
-
AuthBrokerClient,
|
|
21
20
|
AuthStorage,
|
|
22
21
|
type CompletionProbe,
|
|
23
22
|
type CompletionProbeInput,
|
|
24
23
|
type CredentialCompletionResult,
|
|
25
24
|
completeSimple,
|
|
26
|
-
DEFAULT_AUTH_GATEWAY_BIND,
|
|
27
25
|
type Model,
|
|
28
|
-
RemoteAuthCredentialStore,
|
|
29
|
-
type SnapshotResponse,
|
|
30
|
-
startAuthGateway,
|
|
31
26
|
} from "@oh-my-pi/pi-ai";
|
|
27
|
+
import { AuthBrokerClient, RemoteAuthCredentialStore, type SnapshotResponse } from "@oh-my-pi/pi-ai/auth-broker";
|
|
28
|
+
import { DEFAULT_AUTH_GATEWAY_BIND, startAuthGateway } from "@oh-my-pi/pi-ai/auth-gateway";
|
|
32
29
|
import { type GeneratedProvider, getBundledModels, getBundledProviders } from "@oh-my-pi/pi-catalog/models";
|
|
33
30
|
import { getConfigRootDir, isEnoent, VERSION } from "@oh-my-pi/pi-utils";
|
|
34
31
|
import chalk from "chalk";
|