@oh-my-pi/pi-coding-agent 14.9.5 → 14.9.7
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 +52 -0
- package/package.json +7 -7
- package/src/cli/setup-cli.ts +14 -161
- package/src/cli/stats-cli.ts +56 -2
- package/src/cli.ts +0 -1
- package/src/config/settings-schema.ts +0 -10
- package/src/eval/eval.lark +30 -10
- package/src/eval/js/context-manager.ts +334 -564
- package/src/eval/js/shared/helpers.ts +237 -0
- package/src/eval/js/shared/indirect-eval.ts +30 -0
- package/src/eval/js/shared/rewrite-imports.ts +211 -0
- package/src/eval/js/shared/runtime.ts +168 -0
- package/src/eval/js/shared/types.ts +18 -0
- package/src/eval/js/tool-bridge.ts +2 -4
- package/src/eval/js/worker-core.ts +146 -0
- package/src/eval/js/worker-entry.ts +24 -0
- package/src/eval/js/worker-protocol.ts +41 -0
- package/src/eval/parse.ts +218 -49
- package/src/eval/py/display.ts +71 -0
- package/src/eval/py/executor.ts +74 -89
- package/src/eval/py/index.ts +1 -2
- package/src/eval/py/kernel.ts +472 -900
- package/src/eval/py/prelude.py +95 -7
- package/src/eval/py/runner.py +879 -0
- package/src/eval/py/runtime.ts +3 -16
- package/src/eval/py/tool-bridge.ts +137 -0
- package/src/export/html/template.generated.ts +1 -1
- package/src/export/html/template.js +93 -5
- package/src/internal-urls/docs-index.generated.ts +3 -3
- package/src/modes/controllers/command-controller.ts +0 -23
- package/src/prompts/tools/eval.md +14 -27
- package/src/session/agent-session.ts +0 -1
- package/src/session/history-storage.ts +77 -19
- package/src/tools/browser/tab-protocol.ts +4 -0
- package/src/tools/browser/tab-supervisor.ts +86 -5
- package/src/tools/browser/tab-worker.ts +104 -58
- package/src/tools/eval.ts +1 -1
- package/src/web/search/index.ts +6 -4
- package/src/cli/jupyter-cli.ts +0 -106
- package/src/commands/jupyter.ts +0 -32
- package/src/eval/py/cancellation.ts +0 -28
- package/src/eval/py/gateway-coordinator.ts +0 -424
- /package/src/eval/js/{prelude.ts → shared/prelude.ts} +0 -0
- /package/src/eval/js/{prelude.txt → shared/prelude.txt} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,58 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
|
+
### Breaking Changes
|
|
5
|
+
|
|
6
|
+
- Changed the `eval` tool input format to a single-line `*** Cell <lang>:"<title>" [t:<duration>] [rst]` header per cell, replacing the `*** Begin <LANG>` / `*** End <LANG>` envelope and the standalone `*** Title:` / `*** Timeout:` / `*** Reset` directives. The lark grammar enforces a fixed attribute order; the runtime parser remains lenient (alias keys, bare positional tokens, single-quoted titles).
|
|
7
|
+
|
|
8
|
+
### Added
|
|
9
|
+
|
|
10
|
+
- Added support for explicit boolean `rst` values (`rst:true`, `rst:false`, `rst:1`, `rst:0`, `rst:yes`, `rst:no`, `rst:on`, `rst:off`) in `*** Cell` headers
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- Changed the HTML transcript renderer to parse the new `*** Cell` headers while keeping the older `*** Begin <LANG>` and `===== ... =====` formats renderable for historical sessions.
|
|
15
|
+
- Changed the `eval` tool parser so a stray non-marker line between cells no longer crashes with `null is not an object (evaluating 'BEGIN_RE.exec(lines[i])[1]')`; stray content is consumed without aborting parsing.
|
|
16
|
+
- Changed `*** End` to be an optional, undocumented per-cell terminator (kept in the lark to satisfy GPT-trained models' natural terminator habit during constrained sampling).
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
|
|
20
|
+
- Improved `*** Cell` header parsing to reject invalid `rst` values with a clear `invalid rst value` error
|
|
21
|
+
|
|
22
|
+
## [14.9.7] - 2026-05-12
|
|
23
|
+
|
|
24
|
+
### Breaking Changes
|
|
25
|
+
|
|
26
|
+
- Changed the `timeoutMs` execution option to no longer be enforced during worker-based JS runs, so callers must rely on external cancellation signals for time limits
|
|
27
|
+
- Replaced the Jupyter kernel gateway + WebSocket protocol behind the Python `eval` backend with a subprocess-backed runner that speaks NDJSON over stdin/stdout; removed the `jupyter_kernel_gateway`/`ipykernel` pip dependencies, the `python.sharedGateway` setting, the `omp jupyter` CLI command, and the `PI_PYTHON_GATEWAY_URL` / `PI_PYTHON_GATEWAY_TOKEN` environment variables
|
|
28
|
+
|
|
29
|
+
### Added
|
|
30
|
+
|
|
31
|
+
- Added Python `tool.<name>(args)` support to `executePython` sessions so evaluated Python code can invoke session tools through the prelude `tool` proxy
|
|
32
|
+
- Added per-execution Python tool bridge session registration and loopback endpoint wiring so Python tool calls resolve to host tools and return tool results
|
|
33
|
+
- Added status-event forwarding for Python tool bridge calls so `tool` invocations can emit execution status updates
|
|
34
|
+
- Added browser-tab JavaScript execution through the shared runtime so tab runs now expose the standard helper globals (`read`, `write`, `sort`, `uniq`, `counter`, `diff`, `tree`, `env`, `output`, `display`, and `tool`)
|
|
35
|
+
- Added static ESM `import` support to browser-tab JavaScript by rewriting top-level imports and resolving them against the tab session context
|
|
36
|
+
- Added substring fallback matching to `HistoryStorage.search` so infix and short-token queries that FTS5 prefix matching misses are still returned
|
|
37
|
+
- Added a live single-line sync progress display to the stats command showing current/total sessions while syncing
|
|
38
|
+
- Added automatic inline JS evaluation fallback when worker creation failed so script execution still works in environments without worker support
|
|
39
|
+
|
|
40
|
+
### Changed
|
|
41
|
+
|
|
42
|
+
- Changed `setup python` to only verify a reachable Python 3 interpreter instead of installing Jupyter dependencies
|
|
43
|
+
- Changed `info` output to remove the obsolete Python Gateway status block now that shared gateway management is no longer available
|
|
44
|
+
- Changed JavaScript execution in `executeJs` to expose the worker\u2019s real `process` object instead of a restricted, frozen subset
|
|
45
|
+
- Changed JavaScript evaluation to run per session in a worker-backed runner with explicit initialization and teardown handling
|
|
46
|
+
- Changed the Python backend to launch one `python -u runner.py` subprocess per kernel; cancellation now sends `SIGINT` which raises a real `KeyboardInterrupt` in user code, and the same subprocess is reused across cells in session mode
|
|
47
|
+
- Changed Python magic handling so `%pip`, `%cd`, `%env`, `%pwd`, `%ls`, `%time`, `%timeit`, `%who`, `%reset`, `%load`, `%run`, `%%bash`, `%%capture`, `%%timeit`, `%%writefile`, and `!shell` work without depending on IPython
|
|
48
|
+
|
|
49
|
+
### Fixed
|
|
50
|
+
|
|
51
|
+
- Fixed Python output rendering so `text/markdown` takes precedence over `text/plain` and status bundles are emitted as status updates rather than plain text
|
|
52
|
+
- Fixed query tokenization in `HistoryStorage.search` so punctuation-delimited terms like `git-commit` are aligned with indexing and matched correctly
|
|
53
|
+
- Fixed history search result merging to de-duplicate matches and return full-text matches before substring-only matches while still respecting the requested limit
|
|
54
|
+
- Fixed JS run cancellation so aborting a run now also cancels in-flight tool calls and terminates the active worker session
|
|
55
|
+
- Fixed top-level `const`, `let`, and `class` declarations in evaluated JavaScript to persist across subsequent runs by rewriting top-level declarations
|
|
4
56
|
|
|
5
57
|
## [14.9.5] - 2026-05-12
|
|
6
58
|
### Breaking Changes
|
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": "14.9.
|
|
4
|
+
"version": "14.9.7",
|
|
5
5
|
"description": "Coding agent CLI with read, bash, edit, write tools and session management",
|
|
6
6
|
"homepage": "https://github.com/can1357/oh-my-pi",
|
|
7
7
|
"author": "Can Boluk",
|
|
@@ -47,12 +47,12 @@
|
|
|
47
47
|
"@agentclientprotocol/sdk": "0.21.0",
|
|
48
48
|
"@babel/parser": "^7.29.3",
|
|
49
49
|
"@mozilla/readability": "^0.6.0",
|
|
50
|
-
"@oh-my-pi/omp-stats": "14.9.
|
|
51
|
-
"@oh-my-pi/pi-agent-core": "14.9.
|
|
52
|
-
"@oh-my-pi/pi-ai": "14.9.
|
|
53
|
-
"@oh-my-pi/pi-natives": "14.9.
|
|
54
|
-
"@oh-my-pi/pi-tui": "14.9.
|
|
55
|
-
"@oh-my-pi/pi-utils": "14.9.
|
|
50
|
+
"@oh-my-pi/omp-stats": "14.9.7",
|
|
51
|
+
"@oh-my-pi/pi-agent-core": "14.9.7",
|
|
52
|
+
"@oh-my-pi/pi-ai": "14.9.7",
|
|
53
|
+
"@oh-my-pi/pi-natives": "14.9.7",
|
|
54
|
+
"@oh-my-pi/pi-tui": "14.9.7",
|
|
55
|
+
"@oh-my-pi/pi-utils": "14.9.7",
|
|
56
56
|
"@puppeteer/browsers": "^2.13.0",
|
|
57
57
|
"@sinclair/typebox": "^0.34.49",
|
|
58
58
|
"@types/turndown": "5.0.6",
|
package/src/cli/setup-cli.ts
CHANGED
|
@@ -21,7 +21,6 @@ export interface SetupCommandArgs {
|
|
|
21
21
|
|
|
22
22
|
const VALID_COMPONENTS: SetupComponent[] = ["python", "stt"];
|
|
23
23
|
|
|
24
|
-
const PYTHON_PACKAGES = ["jupyter_kernel_gateway", "ipykernel"];
|
|
25
24
|
const MANAGED_PYTHON_ENV = getPythonEnvDir();
|
|
26
25
|
|
|
27
26
|
/**
|
|
@@ -65,10 +64,6 @@ export function parseSetupArgs(args: string[]): SetupCommandArgs | undefined {
|
|
|
65
64
|
interface PythonCheckResult {
|
|
66
65
|
available: boolean;
|
|
67
66
|
pythonPath?: string;
|
|
68
|
-
uvPath?: string;
|
|
69
|
-
pipPath?: string;
|
|
70
|
-
missingPackages: string[];
|
|
71
|
-
installedPackages: string[];
|
|
72
67
|
usingManagedEnv?: boolean;
|
|
73
68
|
managedEnvPath?: string;
|
|
74
69
|
}
|
|
@@ -85,8 +80,6 @@ function managedPythonPath(): string {
|
|
|
85
80
|
async function checkPythonSetup(): Promise<PythonCheckResult> {
|
|
86
81
|
const result: PythonCheckResult = {
|
|
87
82
|
available: false,
|
|
88
|
-
missingPackages: [],
|
|
89
|
-
installedPackages: [],
|
|
90
83
|
managedEnvPath: MANAGED_PYTHON_ENV,
|
|
91
84
|
};
|
|
92
85
|
|
|
@@ -94,109 +87,24 @@ async function checkPythonSetup(): Promise<PythonCheckResult> {
|
|
|
94
87
|
const managedPath = managedPythonPath();
|
|
95
88
|
const hasManagedEnv = await Bun.file(managedPath).exists();
|
|
96
89
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
const candidates = [systemPythonPath, hasManagedEnv ? managedPath : undefined].filter(
|
|
101
|
-
(candidate): candidate is string => !!candidate,
|
|
102
|
-
);
|
|
103
|
-
if (candidates.length === 0) {
|
|
90
|
+
const pythonPath = systemPythonPath ?? (hasManagedEnv ? managedPath : undefined);
|
|
91
|
+
if (!pythonPath) {
|
|
104
92
|
return result;
|
|
105
93
|
}
|
|
106
|
-
|
|
107
|
-
result.pythonPath =
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
missingPackages: [...PYTHON_PACKAGES],
|
|
111
|
-
installedPackages: [] as string[],
|
|
112
|
-
usingManagedEnv: candidates[0] === managedPath,
|
|
113
|
-
};
|
|
114
|
-
|
|
115
|
-
for (const pythonPath of candidates) {
|
|
116
|
-
const installedPackages: string[] = [];
|
|
117
|
-
const missingPackages: string[] = [];
|
|
118
|
-
for (const pkg of PYTHON_PACKAGES) {
|
|
119
|
-
const moduleName = pkg === "jupyter_kernel_gateway" ? "kernel_gateway" : pkg;
|
|
120
|
-
const script = `import importlib.util; raise SystemExit(0 if importlib.util.find_spec('${moduleName}') else 1)`;
|
|
121
|
-
const check = await $`${pythonPath} -c ${script}`.quiet().nothrow();
|
|
122
|
-
if (check.exitCode === 0) {
|
|
123
|
-
installedPackages.push(pkg);
|
|
124
|
-
} else {
|
|
125
|
-
missingPackages.push(pkg);
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
if (missingPackages.length < bestMatch.missingPackages.length) {
|
|
130
|
-
bestMatch = {
|
|
131
|
-
pythonPath,
|
|
132
|
-
missingPackages,
|
|
133
|
-
installedPackages,
|
|
134
|
-
usingManagedEnv: pythonPath === managedPath,
|
|
135
|
-
};
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
if (missingPackages.length === 0) {
|
|
139
|
-
result.available = true;
|
|
140
|
-
result.pythonPath = pythonPath;
|
|
141
|
-
result.missingPackages = missingPackages;
|
|
142
|
-
result.installedPackages = installedPackages;
|
|
143
|
-
result.usingManagedEnv = pythonPath === managedPath;
|
|
144
|
-
return result;
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
result.pythonPath = bestMatch.pythonPath;
|
|
149
|
-
result.missingPackages = bestMatch.missingPackages;
|
|
150
|
-
result.installedPackages = bestMatch.installedPackages;
|
|
151
|
-
result.usingManagedEnv = bestMatch.usingManagedEnv;
|
|
94
|
+
const probe = await $`${pythonPath} -c "import sys;sys.exit(0)"`.quiet().nothrow();
|
|
95
|
+
result.pythonPath = pythonPath;
|
|
96
|
+
result.available = probe.exitCode === 0;
|
|
97
|
+
result.usingManagedEnv = pythonPath === managedPath;
|
|
152
98
|
return result;
|
|
153
99
|
}
|
|
154
100
|
|
|
155
101
|
/**
|
|
156
102
|
* Install Python packages using uv (preferred) or pip.
|
|
157
103
|
*/
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
pipPath?: string,
|
|
163
|
-
): Promise<{ success: boolean; usedManagedEnv: boolean }> {
|
|
164
|
-
if (uvPath) {
|
|
165
|
-
console.log(chalk.dim(`Installing via uv: ${packages.join(" ")}`));
|
|
166
|
-
const result = await $`${uvPath} pip install ${packages}`.nothrow();
|
|
167
|
-
if (result.exitCode === 0) {
|
|
168
|
-
return { success: true, usedManagedEnv: false };
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
if (pipPath) {
|
|
173
|
-
console.log(chalk.dim(`Installing via pip: ${packages.join(" ")}`));
|
|
174
|
-
const result = await $`${pipPath} install ${packages}`.nothrow();
|
|
175
|
-
if (result.exitCode === 0) {
|
|
176
|
-
return { success: true, usedManagedEnv: false };
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
console.log(chalk.dim(`Falling back to managed virtual environment: ${MANAGED_PYTHON_ENV}`));
|
|
181
|
-
|
|
182
|
-
if (uvPath) {
|
|
183
|
-
const createEnv = await $`${uvPath} venv ${MANAGED_PYTHON_ENV}`.quiet().nothrow();
|
|
184
|
-
if (createEnv.exitCode !== 0) {
|
|
185
|
-
return { success: false, usedManagedEnv: true };
|
|
186
|
-
}
|
|
187
|
-
const installInManagedEnv = await $`${uvPath} pip install --python ${MANAGED_PYTHON_ENV} ${packages}`.nothrow();
|
|
188
|
-
return { success: installInManagedEnv.exitCode === 0, usedManagedEnv: true };
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
const createEnv = await $`${pythonPath} -m venv ${MANAGED_PYTHON_ENV}`.quiet().nothrow();
|
|
192
|
-
if (createEnv.exitCode !== 0) {
|
|
193
|
-
return { success: false, usedManagedEnv: true };
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
const managedPython = managedPythonPath();
|
|
197
|
-
const installInManagedEnv = await $`${managedPython} -m pip install ${packages}`.nothrow();
|
|
198
|
-
return { success: installInManagedEnv.exitCode === 0, usedManagedEnv: true };
|
|
199
|
-
}
|
|
104
|
+
// Python installation helper removed: the subprocess runner has no Python
|
|
105
|
+
// package dependencies beyond a working interpreter. `omp setup python --check`
|
|
106
|
+
// remains as a probe; users install optional libs (pandas, matplotlib, ...)
|
|
107
|
+
// directly via pip or the in-process `%pip` magic.
|
|
200
108
|
|
|
201
109
|
/**
|
|
202
110
|
* Run the setup command.
|
|
@@ -232,67 +140,13 @@ async function handlePythonSetup(flags: { json?: boolean; check?: boolean }): Pr
|
|
|
232
140
|
console.log(chalk.dim(`Using managed environment: ${check.managedEnvPath}`));
|
|
233
141
|
}
|
|
234
142
|
|
|
235
|
-
if (check.
|
|
236
|
-
console.log(chalk.dim(`uv: ${check.uvPath}`));
|
|
237
|
-
} else if (check.pipPath) {
|
|
238
|
-
console.log(chalk.dim(`pip: ${check.pipPath}`));
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
if (check.installedPackages.length > 0) {
|
|
242
|
-
console.log(chalk.green(`${theme.status.success} Installed: ${check.installedPackages.join(", ")}`));
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
if (check.missingPackages.length === 0) {
|
|
143
|
+
if (check.available) {
|
|
246
144
|
console.log(chalk.green(`\n${theme.status.success} Python execution is ready`));
|
|
247
145
|
return;
|
|
248
146
|
}
|
|
249
147
|
|
|
250
|
-
console.
|
|
251
|
-
|
|
252
|
-
if (flags.check) {
|
|
253
|
-
process.exit(1);
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
if (!check.uvPath && !check.pipPath) {
|
|
257
|
-
console.error(chalk.red(`\n${theme.status.error} No package manager found`));
|
|
258
|
-
console.error(chalk.dim("Install uv (recommended) or pip:"));
|
|
259
|
-
console.error(chalk.dim(" curl -LsSf https://astral.sh/uv/install.sh | sh"));
|
|
260
|
-
process.exit(1);
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
console.log("");
|
|
264
|
-
const install = await installPythonPackages(check.missingPackages, check.pythonPath, check.uvPath, check.pipPath);
|
|
265
|
-
|
|
266
|
-
if (!install.success) {
|
|
267
|
-
console.error(chalk.red(`\n${theme.status.error} Installation failed`));
|
|
268
|
-
console.error(chalk.dim("Try installing manually:"));
|
|
269
|
-
if (install.usedManagedEnv) {
|
|
270
|
-
if (check.uvPath) {
|
|
271
|
-
console.error(chalk.dim(` uv venv ${MANAGED_PYTHON_ENV}`));
|
|
272
|
-
console.error(
|
|
273
|
-
chalk.dim(` uv pip install --python ${MANAGED_PYTHON_ENV} ${check.missingPackages.join(" ")}`),
|
|
274
|
-
);
|
|
275
|
-
} else {
|
|
276
|
-
console.error(chalk.dim(` ${check.pythonPath} -m venv ${MANAGED_PYTHON_ENV}`));
|
|
277
|
-
console.error(chalk.dim(` ${managedPythonPath()} -m pip install ${check.missingPackages.join(" ")}`));
|
|
278
|
-
}
|
|
279
|
-
} else {
|
|
280
|
-
console.error(chalk.dim(` ${check.uvPath ? "uv pip" : "pip"} install ${check.missingPackages.join(" ")}`));
|
|
281
|
-
}
|
|
282
|
-
process.exit(1);
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
const recheck = await checkPythonSetup();
|
|
286
|
-
if (recheck.available) {
|
|
287
|
-
console.log(chalk.green(`\n${theme.status.success} Python execution is ready`));
|
|
288
|
-
if (recheck.usingManagedEnv) {
|
|
289
|
-
console.log(chalk.dim(`Managed Python environment: ${recheck.managedEnvPath}`));
|
|
290
|
-
}
|
|
291
|
-
} else {
|
|
292
|
-
console.error(chalk.red(`\n${theme.status.error} Setup incomplete`));
|
|
293
|
-
console.error(chalk.dim(`Still missing: ${recheck.missingPackages.join(", ")}`));
|
|
294
|
-
process.exit(1);
|
|
295
|
-
}
|
|
148
|
+
console.error(chalk.red(`\n${theme.status.error} Python interpreter reported failure`));
|
|
149
|
+
process.exit(1);
|
|
296
150
|
}
|
|
297
151
|
|
|
298
152
|
async function handleSttSetup(flags: { json?: boolean; check?: boolean }): Promise<void> {
|
|
@@ -359,9 +213,8 @@ ${chalk.bold("Usage:")}
|
|
|
359
213
|
${APP_NAME} setup <component> [options]
|
|
360
214
|
|
|
361
215
|
${chalk.bold("Components:")}
|
|
362
|
-
python
|
|
216
|
+
python Verify a Python 3 interpreter is reachable for code execution
|
|
363
217
|
stt Install speech-to-text dependencies (openai-whisper, recording tools)
|
|
364
|
-
Packages: ${PYTHON_PACKAGES.join(", ")}
|
|
365
218
|
|
|
366
219
|
${chalk.bold("Options:")}
|
|
367
220
|
-c, --check Check if dependencies are installed without installing
|
package/src/cli/stats-cli.ts
CHANGED
|
@@ -8,6 +8,58 @@ import { APP_NAME, formatDuration, formatNumber, formatPercent } from "@oh-my-pi
|
|
|
8
8
|
import chalk from "chalk";
|
|
9
9
|
import { openPath } from "../utils/open";
|
|
10
10
|
|
|
11
|
+
/**
|
|
12
|
+
* Single-line TTY progress bar. On a non-TTY stream we just stay quiet -
|
|
13
|
+
* the final "Synced ..." summary still prints either way.
|
|
14
|
+
*/
|
|
15
|
+
function createSyncProgressReporter(): {
|
|
16
|
+
onProgress: (event: { current: number; total: number; sessionFile: string }) => void;
|
|
17
|
+
finish: () => void;
|
|
18
|
+
} {
|
|
19
|
+
const stream = process.stderr;
|
|
20
|
+
const isTty = stream.isTTY === true;
|
|
21
|
+
let lastWidth = 0;
|
|
22
|
+
let lastRender = 0;
|
|
23
|
+
return {
|
|
24
|
+
onProgress(event) {
|
|
25
|
+
if (!isTty) return;
|
|
26
|
+
const now = Date.now();
|
|
27
|
+
// Throttle to ~30 fps and always force a render for the last file.
|
|
28
|
+
if (event.current < event.total && now - lastRender < 33) return;
|
|
29
|
+
lastRender = now;
|
|
30
|
+
const label = chalk.dim(shortenSessionFile(event.sessionFile));
|
|
31
|
+
const pct = ((event.current / event.total) * 100).toFixed(0).padStart(3, " ");
|
|
32
|
+
const counter = chalk.cyan(`[${event.current}/${event.total}]`);
|
|
33
|
+
const line = `${counter} ${pct}% ${label}`;
|
|
34
|
+
const columns = stream.columns ?? 120;
|
|
35
|
+
const trimmed = truncateToColumns(line, columns - 1);
|
|
36
|
+
stream.write(`\r${trimmed.padEnd(lastWidth)}`);
|
|
37
|
+
lastWidth = trimmed.length;
|
|
38
|
+
},
|
|
39
|
+
finish() {
|
|
40
|
+
if (!isTty || lastWidth === 0) return;
|
|
41
|
+
stream.write(`\r${" ".repeat(lastWidth)}\r`);
|
|
42
|
+
lastWidth = 0;
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function shortenSessionFile(p: string): string {
|
|
48
|
+
const marker = "/sessions/";
|
|
49
|
+
const idx = p.indexOf(marker);
|
|
50
|
+
return idx >= 0 ? p.slice(idx + marker.length) : p;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function truncateToColumns(s: string, max: number): string {
|
|
54
|
+
if (max <= 0) return "";
|
|
55
|
+
const width = Bun.stringWidth(s, { countAnsiEscapeCodes: false });
|
|
56
|
+
if (width <= max) return s;
|
|
57
|
+
// Cheap right-trim with an ellipsis - we don't need ANSI-aware slicing
|
|
58
|
+
// because the colored prefix is short and the truncated tail is the
|
|
59
|
+
// dim filename, where dropping bytes is fine.
|
|
60
|
+
return `${s.slice(0, Math.max(0, max - 1))}\u2026`;
|
|
61
|
+
}
|
|
62
|
+
|
|
11
63
|
// =============================================================================
|
|
12
64
|
// Types
|
|
13
65
|
// =============================================================================
|
|
@@ -74,8 +126,10 @@ export async function runStatsCommand(cmd: StatsCommandArgs): Promise<void> {
|
|
|
74
126
|
);
|
|
75
127
|
|
|
76
128
|
// Sync session files first
|
|
77
|
-
|
|
78
|
-
|
|
129
|
+
const progress = createSyncProgressReporter();
|
|
130
|
+
process.stderr.write("Syncing session files...\n");
|
|
131
|
+
const { processed, files } = await syncAllSessions({ onProgress: progress.onProgress });
|
|
132
|
+
progress.finish();
|
|
79
133
|
const total = await getTotalMessageCount();
|
|
80
134
|
console.log(`Synced ${processed} new entries from ${files} files (${total} total)\n`);
|
|
81
135
|
|
package/src/cli.ts
CHANGED
|
@@ -55,7 +55,6 @@ const commands: CommandEntry[] = [
|
|
|
55
55
|
{ name: "config", load: () => import("./commands/config").then(m => m.default) },
|
|
56
56
|
{ name: "grep", load: () => import("./commands/grep").then(m => m.default) },
|
|
57
57
|
{ name: "grievances", load: () => import("./commands/grievances").then(m => m.default) },
|
|
58
|
-
{ name: "jupyter", load: () => import("./commands/jupyter").then(m => m.default) },
|
|
59
58
|
{ name: "plugin", load: () => import("./commands/plugin").then(m => m.default) },
|
|
60
59
|
{ name: "setup", load: () => import("./commands/setup").then(m => m.default) },
|
|
61
60
|
{ name: "shell", load: () => import("./commands/shell").then(m => m.default) },
|
|
@@ -1662,16 +1662,6 @@ export const SETTINGS_SCHEMA = {
|
|
|
1662
1662
|
},
|
|
1663
1663
|
},
|
|
1664
1664
|
|
|
1665
|
-
"python.sharedGateway": {
|
|
1666
|
-
type: "boolean",
|
|
1667
|
-
default: true,
|
|
1668
|
-
ui: {
|
|
1669
|
-
tab: "editing",
|
|
1670
|
-
label: "Shared Python Gateway",
|
|
1671
|
-
description: "Share IPython kernel gateway across pi instances",
|
|
1672
|
-
},
|
|
1673
|
-
},
|
|
1674
|
-
|
|
1675
1665
|
// ────────────────────────────────────────────────────────────────────────
|
|
1676
1666
|
// Tools
|
|
1677
1667
|
// ────────────────────────────────────────────────────────────────────────
|
package/src/eval/eval.lark
CHANGED
|
@@ -1,16 +1,36 @@
|
|
|
1
|
-
|
|
1
|
+
// Canonical Eval input. Each cell is introduced by a single header line:
|
|
2
|
+
//
|
|
3
|
+
// *** Cell <LANG>:"<title>" [t:<duration>] [rst]
|
|
4
|
+
//
|
|
5
|
+
// Attribute order is fixed: language+title, then optional timeout, then
|
|
6
|
+
// optional reset flag. Title may be empty (`py:""`).
|
|
7
|
+
//
|
|
8
|
+
// Tokens:
|
|
9
|
+
//
|
|
10
|
+
// py:"..." | js:"..." language plus title (required)
|
|
11
|
+
// t:<digits>(ms|s|m)? per-cell timeout (default 30s)
|
|
12
|
+
// rst reset this language's kernel before running
|
|
13
|
+
//
|
|
14
|
+
// Everything between one header line and the next (or the optional trailing
|
|
15
|
+
// `*** End`, or end of input) is the cell's code, verbatim. The runtime
|
|
16
|
+
// parser additionally accepts content before the first header as an implicit
|
|
17
|
+
// default-language cell, but that is lenient fallback and MUST NOT be relied
|
|
18
|
+
// on.
|
|
2
19
|
|
|
3
|
-
|
|
4
|
-
begin_cell: "*** Begin " LANG LF
|
|
5
|
-
end_cell: "*** End Cell" LF?
|
|
20
|
+
start: cell+ end_marker
|
|
6
21
|
|
|
7
|
-
|
|
8
|
-
title: "*** Title: " /(.+)/ LF
|
|
9
|
-
timeout: "*** Timeout: " /\d+(ms|s|m)?/ LF
|
|
10
|
-
reset: "*** Reset" LF
|
|
22
|
+
cell: cell_header code_line*
|
|
11
23
|
|
|
12
|
-
|
|
24
|
+
cell_header: "*** Cell" WS_INLINE LANG_TITLE (WS_INLINE T_ATTR)? (WS_INLINE RST_FLAG)? LF
|
|
13
25
|
|
|
14
|
-
|
|
26
|
+
end_marker: "*** End" LF?
|
|
27
|
+
|
|
28
|
+
code_line: CODE_TEXT LF | LF
|
|
29
|
+
CODE_TEXT: /([^*\r\n]|\*\*?[^*\r\n])+\*{0,2}|\*{1,2}/
|
|
30
|
+
|
|
31
|
+
LANG_TITLE: ("py" | "js") ":\"" /[^"\r\n]*/ "\""
|
|
32
|
+
T_ATTR: "t:" /\d+(ms|s|m)?/
|
|
33
|
+
RST_FLAG: "rst"
|
|
15
34
|
|
|
16
35
|
%import common.LF
|
|
36
|
+
%import common.WS_INLINE
|