@oh-my-pi/pi-coding-agent 15.9.5 → 15.10.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.
- package/CHANGELOG.md +98 -1
- package/dist/types/cli/args.d.ts +1 -1
- package/dist/types/cli/gallery-cli.d.ts +43 -0
- package/dist/types/cli/gallery-fixtures/agentic.d.ts +2 -0
- package/dist/types/cli/gallery-fixtures/codeintel.d.ts +3 -0
- package/dist/types/cli/gallery-fixtures/edit.d.ts +3 -0
- package/dist/types/cli/gallery-fixtures/fs.d.ts +2 -0
- package/dist/types/cli/gallery-fixtures/index.d.ts +4 -0
- package/dist/types/cli/gallery-fixtures/interaction.d.ts +3 -0
- package/dist/types/cli/gallery-fixtures/memory.d.ts +2 -0
- package/dist/types/cli/gallery-fixtures/misc.d.ts +3 -0
- package/dist/types/cli/gallery-fixtures/search.d.ts +3 -0
- package/dist/types/cli/gallery-fixtures/shell.d.ts +3 -0
- package/dist/types/cli/gallery-fixtures/types.d.ts +44 -0
- package/dist/types/cli/gallery-fixtures/web.d.ts +2 -0
- package/dist/types/cli/gallery-screenshot.d.ts +35 -0
- package/dist/types/commands/gallery.d.ts +47 -0
- package/dist/types/config/keybindings.d.ts +10 -2
- package/dist/types/config/model-id-affixes.d.ts +2 -0
- package/dist/types/config/model-registry.d.ts +8 -1
- package/dist/types/config/settings-schema.d.ts +43 -7
- package/dist/types/edit/file-snapshot-store.d.ts +1 -1
- package/dist/types/eval/backend.d.ts +6 -6
- package/dist/types/eval/bridge-timeout.d.ts +27 -0
- package/dist/types/eval/idle-timeout.d.ts +16 -14
- package/dist/types/eval/js/executor.d.ts +3 -3
- package/dist/types/eval/py/executor.d.ts +2 -2
- package/dist/types/eval/py/spawn-options.d.ts +58 -0
- package/dist/types/extensibility/plugins/marketplace-auto-update.d.ts +8 -0
- package/dist/types/lsp/types.d.ts +10 -0
- package/dist/types/main.d.ts +3 -2
- package/dist/types/memory-backend/index.d.ts +2 -1
- package/dist/types/memory-backend/resolve.d.ts +1 -1
- package/dist/types/memory-backend/types.d.ts +1 -1
- package/dist/types/modes/components/assistant-message.d.ts +5 -0
- package/dist/types/modes/components/copy-selector.d.ts +22 -0
- package/dist/types/modes/components/custom-editor.d.ts +2 -1
- package/dist/types/modes/components/model-selector.d.ts +1 -0
- package/dist/types/modes/components/tool-execution.d.ts +18 -0
- package/dist/types/modes/controllers/command-controller.d.ts +0 -1
- package/dist/types/modes/controllers/selector-controller.d.ts +2 -1
- package/dist/types/modes/index.d.ts +5 -4
- package/dist/types/modes/interactive-mode.d.ts +2 -2
- package/dist/types/modes/setup-version.d.ts +11 -0
- package/dist/types/modes/setup-wizard/index.d.ts +2 -1
- package/dist/types/modes/setup-wizard/scenes/web-search.d.ts +2 -1
- package/dist/types/modes/types.d.ts +2 -2
- package/dist/types/modes/utils/copy-targets.d.ts +53 -0
- package/dist/types/sdk.d.ts +1 -1
- package/dist/types/task/executor.d.ts +7 -0
- package/dist/types/telemetry-export.d.ts +1 -1
- package/dist/types/tools/eval-render.d.ts +1 -0
- package/dist/types/tools/fetch.d.ts +15 -7
- package/dist/types/tools/render-utils.d.ts +33 -0
- package/dist/types/tools/renderers.d.ts +16 -2
- package/dist/types/tools/search.d.ts +1 -1
- package/dist/types/tools/write.d.ts +2 -0
- package/dist/types/tui/code-cell.d.ts +6 -0
- package/dist/types/tui/output-block.d.ts +11 -0
- package/dist/types/web/scrapers/github.d.ts +22 -0
- package/dist/types/web/search/providers/perplexity.d.ts +8 -1
- package/dist/types/web/search/types.d.ts +1 -1
- package/package.json +9 -9
- package/scripts/dev-launch +42 -0
- package/scripts/dev-launch-preload.ts +19 -0
- package/src/autoresearch/dashboard.ts +11 -21
- package/src/cli/args.ts +2 -2
- package/src/cli/claude-trace-cli.ts +13 -1
- package/src/cli/gallery-cli.ts +223 -0
- package/src/cli/gallery-fixtures/agentic.ts +292 -0
- package/src/cli/gallery-fixtures/codeintel.ts +188 -0
- package/src/cli/gallery-fixtures/edit.ts +194 -0
- package/src/cli/gallery-fixtures/fs.ts +153 -0
- package/src/cli/gallery-fixtures/index.ts +40 -0
- package/src/cli/gallery-fixtures/interaction.ts +49 -0
- package/src/cli/gallery-fixtures/memory.ts +81 -0
- package/src/cli/gallery-fixtures/misc.ts +221 -0
- package/src/cli/gallery-fixtures/search.ts +213 -0
- package/src/cli/gallery-fixtures/shell.ts +167 -0
- package/src/cli/gallery-fixtures/types.ts +41 -0
- package/src/cli/gallery-fixtures/web.ts +158 -0
- package/src/cli/gallery-screenshot.ts +279 -0
- package/src/cli-commands.ts +1 -0
- package/src/commands/gallery.ts +52 -0
- package/src/commands/launch.ts +1 -1
- package/src/config/keybindings.ts +68 -2
- package/src/config/model-equivalence.ts +35 -12
- package/src/config/model-id-affixes.ts +39 -22
- package/src/config/model-registry.ts +16 -16
- package/src/config/settings-schema.ts +29 -6
- package/src/config/settings.ts +11 -0
- package/src/dap/client.ts +14 -16
- package/src/debug/raw-sse.ts +18 -4
- package/src/edit/file-snapshot-store.ts +1 -1
- package/src/edit/index.ts +1 -1
- package/src/edit/renderer.ts +43 -55
- package/src/edit/streaming.ts +1 -1
- package/src/eval/__tests__/agent-bridge.test.ts +102 -58
- package/src/eval/__tests__/bridge-timeout.test.ts +64 -0
- package/src/eval/__tests__/idle-timeout.test.ts +26 -12
- package/src/eval/__tests__/kernel-spawn.test.ts +103 -0
- package/src/eval/__tests__/llm-bridge.test.ts +10 -10
- package/src/eval/agent-bridge.ts +38 -12
- package/src/eval/backend.ts +6 -6
- package/src/eval/bridge-timeout.ts +44 -0
- package/src/eval/idle-timeout.ts +33 -15
- package/src/eval/js/executor.ts +10 -10
- package/src/eval/llm-bridge.ts +4 -5
- package/src/eval/py/executor.ts +6 -6
- package/src/eval/py/kernel.ts +11 -1
- package/src/eval/py/spawn-options.ts +126 -0
- package/src/export/ttsr.ts +9 -0
- package/src/extensibility/extensions/runner.ts +3 -0
- package/src/extensibility/plugins/doctor.ts +0 -1
- package/src/extensibility/plugins/marketplace-auto-update.ts +49 -0
- package/src/goals/tools/goal-tool.ts +2 -2
- package/src/internal-urls/docs-index.generated.ts +7 -6
- package/src/lsp/client.ts +179 -52
- package/src/lsp/index.ts +38 -4
- package/src/lsp/render.ts +3 -3
- package/src/lsp/types.ts +10 -0
- package/src/main.ts +47 -52
- package/src/memory-backend/index.ts +13 -1
- package/src/memory-backend/resolve.ts +3 -5
- package/src/memory-backend/types.ts +1 -1
- package/src/modes/components/agent-dashboard.ts +13 -4
- package/src/modes/components/assistant-message.ts +22 -1
- package/src/modes/components/copy-selector.ts +249 -0
- package/src/modes/components/custom-editor.ts +10 -1
- package/src/modes/components/extensions/extension-list.ts +17 -8
- package/src/modes/components/history-search.ts +19 -11
- package/src/modes/components/model-selector.ts +125 -29
- package/src/modes/components/oauth-selector.ts +28 -12
- package/src/modes/components/session-observer-overlay.ts +13 -15
- package/src/modes/components/session-selector.ts +24 -13
- package/src/modes/components/status-line.ts +3 -5
- package/src/modes/components/tool-execution.ts +83 -24
- package/src/modes/components/tree-selector.ts +19 -7
- package/src/modes/components/user-message-selector.ts +25 -14
- package/src/modes/controllers/command-controller.ts +13 -118
- package/src/modes/controllers/event-controller.ts +26 -10
- package/src/modes/controllers/input-controller.ts +11 -3
- package/src/modes/controllers/selector-controller.ts +40 -3
- package/src/modes/index.ts +5 -4
- package/src/modes/interactive-mode.ts +21 -7
- package/src/modes/setup-version.ts +11 -0
- package/src/modes/setup-wizard/index.ts +3 -2
- package/src/modes/setup-wizard/scenes/web-search.ts +3 -2
- package/src/modes/theme/theme.ts +46 -10
- package/src/modes/types.ts +2 -2
- package/src/modes/utils/context-usage.ts +10 -6
- package/src/modes/utils/copy-targets.ts +254 -0
- package/src/modes/utils/hotkeys-markdown.ts +1 -0
- package/src/prompts/tools/ast-edit.md +1 -1
- package/src/prompts/tools/ast-grep.md +1 -1
- package/src/prompts/tools/read.md +1 -1
- package/src/prompts/tools/search.md +1 -1
- package/src/sdk.ts +21 -23
- package/src/session/agent-session.ts +13 -9
- package/src/slash-commands/builtin-registry.ts +4 -12
- package/src/slash-commands/helpers/usage-report.ts +2 -0
- package/src/task/executor.ts +20 -2
- package/src/task/render.ts +37 -11
- package/src/telemetry-export.ts +25 -7
- package/src/tools/bash.ts +18 -8
- package/src/tools/browser/render.ts +5 -4
- package/src/tools/debug.ts +3 -3
- package/src/tools/eval-backends.ts +6 -17
- package/src/tools/eval-render.ts +28 -10
- package/src/tools/eval.ts +19 -23
- package/src/tools/fetch.ts +99 -89
- package/src/tools/read.ts +7 -7
- package/src/tools/render-utils.ts +63 -3
- package/src/tools/renderers.ts +16 -1
- package/src/tools/report-tool-issue.ts +1 -1
- package/src/tools/search.ts +173 -81
- package/src/tools/ssh.ts +21 -8
- package/src/tools/todo.ts +20 -7
- package/src/tools/write.ts +39 -9
- package/src/tui/code-cell.ts +19 -4
- package/src/tui/output-block.ts +14 -0
- package/src/web/scrapers/github.ts +255 -3
- package/src/web/scrapers/youtube.ts +3 -2
- package/src/web/search/providers/perplexity.ts +199 -51
- package/src/web/search/render.ts +42 -57
- package/src/web/search/types.ts +5 -1
- package/dist/types/eval/heartbeat.d.ts +0 -45
- package/src/eval/__tests__/heartbeat.test.ts +0 -84
- package/src/eval/__tests__/shared-executors.test.ts +0 -609
- package/src/eval/heartbeat.ts +0 -74
- /package/dist/types/eval/__tests__/{heartbeat.test.d.ts → bridge-timeout.test.d.ts} +0 -0
- /package/dist/types/eval/__tests__/{shared-executors.test.d.ts → kernel-spawn.test.d.ts} +0 -0
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Timeout suspension for in-flight host-side eval bridge calls.
|
|
3
|
+
*
|
|
4
|
+
* The eval watchdog caps a cell's `timeout` as a budget on the cell runtime's
|
|
5
|
+
* own work. Host-side `agent()` / `parallel()` / `llm()` bridge calls hand
|
|
6
|
+
* control to the outer TypeScript process, where the Python kernel or JS VM is
|
|
7
|
+
* only waiting for a result. While that delegated work is in flight, the cell
|
|
8
|
+
* timeout must be ignored completely; once the bridge returns and the runtime is
|
|
9
|
+
* back in control, the watchdog starts a fresh timeout window.
|
|
10
|
+
*
|
|
11
|
+
* Bridge helpers express that handoff with synthetic pause/resume status events
|
|
12
|
+
* on the existing `emitStatus → onStatus` path. Consumers MUST treat these as
|
|
13
|
+
* timeout-control events only: update the watchdog and drop them from rendered
|
|
14
|
+
* or persisted cell output.
|
|
15
|
+
*/
|
|
16
|
+
import type { JsStatusEvent } from "./js/shared/types";
|
|
17
|
+
|
|
18
|
+
/** Synthetic status op emitted when a bridge call leaves the cell runtime. */
|
|
19
|
+
export const EVAL_TIMEOUT_PAUSE_OP = "timeout-pause";
|
|
20
|
+
|
|
21
|
+
/** Synthetic status op emitted when a bridge call returns control to the runtime. */
|
|
22
|
+
export const EVAL_TIMEOUT_RESUME_OP = "timeout-resume";
|
|
23
|
+
|
|
24
|
+
/** Whether a status event is pure eval-timeout control and should not render. */
|
|
25
|
+
export function isEvalTimeoutControlEvent(event: JsStatusEvent): boolean {
|
|
26
|
+
return event.op === EVAL_TIMEOUT_PAUSE_OP || event.op === EVAL_TIMEOUT_RESUME_OP;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Run {@link operation} while suspending the eval watchdog through
|
|
31
|
+
* {@link emitStatus}. A no-op wrapper when no status sink is wired.
|
|
32
|
+
*/
|
|
33
|
+
export async function withBridgeTimeoutPause<T>(
|
|
34
|
+
emitStatus: ((event: JsStatusEvent) => void) | undefined,
|
|
35
|
+
operation: () => Promise<T>,
|
|
36
|
+
): Promise<T> {
|
|
37
|
+
if (!emitStatus) return operation();
|
|
38
|
+
emitStatus({ op: EVAL_TIMEOUT_PAUSE_OP });
|
|
39
|
+
try {
|
|
40
|
+
return await operation();
|
|
41
|
+
} finally {
|
|
42
|
+
emitStatus({ op: EVAL_TIMEOUT_RESUME_OP });
|
|
43
|
+
}
|
|
44
|
+
}
|
package/src/eval/idle-timeout.ts
CHANGED
|
@@ -1,17 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Watchdog for eval cell work.
|
|
3
3
|
*
|
|
4
|
-
* A cell's `timeout`
|
|
5
|
-
*
|
|
6
|
-
* `
|
|
7
|
-
*
|
|
8
|
-
* long-running fanout that keeps reporting progress (e.g. `agent()` status
|
|
9
|
-
* updates, `log()`/`phase()`) never trips the timeout, while a genuinely
|
|
10
|
-
* stalled cell still gets interrupted.
|
|
4
|
+
* A cell's `timeout` bounds time while the Python kernel or JS VM is in control.
|
|
5
|
+
* Host-side bridge calls can {@link pause} the watchdog so delegated
|
|
6
|
+
* `agent()`/`parallel()`/`llm()` work is ignored completely, then {@link resume}
|
|
7
|
+
* starts a fresh timeout window once the runtime gets control back.
|
|
11
8
|
*
|
|
12
|
-
* The timer self-reschedules instead of being torn down
|
|
13
|
-
*
|
|
14
|
-
*
|
|
9
|
+
* The active timer self-reschedules instead of being torn down on every
|
|
10
|
+
* activity event, so frequent activity costs one timestamp write per event.
|
|
11
|
+
* Pause is reference-counted because `parallel()` can have multiple bridge calls
|
|
12
|
+
* in flight at once.
|
|
15
13
|
*/
|
|
16
14
|
export class IdleTimeout {
|
|
17
15
|
readonly #controller = new AbortController();
|
|
@@ -20,6 +18,7 @@ export class IdleTimeout {
|
|
|
20
18
|
#deadlineMs: number;
|
|
21
19
|
#timer: NodeJS.Timeout | undefined;
|
|
22
20
|
#settled = false;
|
|
21
|
+
#pauseDepth = 0;
|
|
23
22
|
|
|
24
23
|
constructor(idleMs: number) {
|
|
25
24
|
this.#idleMs = Math.max(1, Math.floor(idleMs));
|
|
@@ -27,20 +26,39 @@ export class IdleTimeout {
|
|
|
27
26
|
this.#arm(this.#idleMs);
|
|
28
27
|
}
|
|
29
28
|
|
|
30
|
-
/** Aborts with a `TimeoutError` reason once the
|
|
29
|
+
/** Aborts with a `TimeoutError` reason once the active timeout window is exhausted. */
|
|
31
30
|
get signal(): AbortSignal {
|
|
32
31
|
return this.#controller.signal;
|
|
33
32
|
}
|
|
34
33
|
|
|
35
|
-
/** Configured
|
|
34
|
+
/** Configured active timeout window in milliseconds. */
|
|
36
35
|
get idleMs(): number {
|
|
37
36
|
return this.#idleMs;
|
|
38
37
|
}
|
|
39
38
|
|
|
40
|
-
/** Record activity, pushing the
|
|
39
|
+
/** Record runtime activity, pushing the active deadline forward by `idleMs`. */
|
|
41
40
|
bump(): void {
|
|
41
|
+
if (this.#settled || this.#pauseDepth > 0) return;
|
|
42
|
+
this.#deadlineMs = Date.now() + this.#idleMs;
|
|
43
|
+
}
|
|
44
|
+
/** Suspend timeout accounting while control is delegated to host-side work. */
|
|
45
|
+
pause(): void {
|
|
42
46
|
if (this.#settled) return;
|
|
47
|
+
this.#pauseDepth++;
|
|
48
|
+
if (this.#pauseDepth !== 1) return;
|
|
49
|
+
if (this.#timer) {
|
|
50
|
+
clearTimeout(this.#timer);
|
|
51
|
+
this.#timer = undefined;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Resume timeout accounting with a fresh timeout window. */
|
|
56
|
+
resume(): void {
|
|
57
|
+
if (this.#settled || this.#pauseDepth === 0) return;
|
|
58
|
+
this.#pauseDepth--;
|
|
59
|
+
if (this.#pauseDepth > 0) return;
|
|
43
60
|
this.#deadlineMs = Date.now() + this.#idleMs;
|
|
61
|
+
this.#arm(this.#idleMs);
|
|
44
62
|
}
|
|
45
63
|
|
|
46
64
|
/** Stop the watchdog. Safe to call multiple times. */
|
|
@@ -65,7 +83,7 @@ export class IdleTimeout {
|
|
|
65
83
|
}
|
|
66
84
|
|
|
67
85
|
#onExpire(): void {
|
|
68
|
-
if (this.#settled) return;
|
|
86
|
+
if (this.#settled || this.#pauseDepth > 0) return;
|
|
69
87
|
const remainingMs = this.#deadlineMs - Date.now();
|
|
70
88
|
if (remainingMs > 0) {
|
|
71
89
|
// A bump moved the deadline forward after this timer was armed; wait
|
package/src/eval/js/executor.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { DEFAULT_MAX_BYTES, OutputSink } from "../../session/streaming-output";
|
|
2
2
|
import type { ToolSession } from "../../tools";
|
|
3
3
|
import { resolveOutputMaxColumns, resolveOutputSinkHeadBytes } from "../../tools/output-meta";
|
|
4
|
-
import {
|
|
4
|
+
import { isEvalTimeoutControlEvent } from "../bridge-timeout";
|
|
5
5
|
import { executeInVmContext, type JsDisplayOutput } from "./context-manager";
|
|
6
6
|
import type { JsStatusEvent } from "./shared/types";
|
|
7
7
|
|
|
@@ -10,9 +10,9 @@ export interface JsExecutorOptions {
|
|
|
10
10
|
timeoutMs?: number;
|
|
11
11
|
deadlineMs?: number;
|
|
12
12
|
/**
|
|
13
|
-
*
|
|
14
|
-
* timeout-annotation text when the caller drives cancellation via
|
|
15
|
-
*
|
|
13
|
+
* Runtime-work budget (ms). Used for worker cold-start headroom and
|
|
14
|
+
* timeout-annotation text when the caller drives cancellation via the eval
|
|
15
|
+
* watchdog `signal` instead of `deadlineMs`/`timeoutMs`. Never arms a timer.
|
|
16
16
|
*/
|
|
17
17
|
idleTimeoutMs?: number;
|
|
18
18
|
onChunk?: (chunk: string) => Promise<void> | void;
|
|
@@ -85,9 +85,9 @@ export async function executeJs(code: string, options: JsExecutorOptions): Promi
|
|
|
85
85
|
options.signal && timeoutSignal
|
|
86
86
|
? AbortSignal.any([options.signal, timeoutSignal])
|
|
87
87
|
: (options.signal ?? timeoutSignal);
|
|
88
|
-
// The eval tool drives cancellation via
|
|
89
|
-
//
|
|
90
|
-
// derive a competing fixed timer from it.
|
|
88
|
+
// The eval tool drives cancellation via its own watchdog `signal` and passes
|
|
89
|
+
// only the runtime-work budget; use it solely as worker cold-start headroom
|
|
90
|
+
// and never derive a competing fixed timer from it.
|
|
91
91
|
const acquireBudgetMs = legacyTimeoutMs ?? options.idleTimeoutMs;
|
|
92
92
|
|
|
93
93
|
try {
|
|
@@ -105,10 +105,10 @@ export async function executeJs(code: string, options: JsExecutorOptions): Promi
|
|
|
105
105
|
onText: chunk => outputSink.push(chunk),
|
|
106
106
|
onDisplay: output => {
|
|
107
107
|
if (output.type === "status") {
|
|
108
|
-
//
|
|
109
|
-
//
|
|
108
|
+
// Timeout-control events drive the eval watchdog only; never
|
|
109
|
+
// store or render them as cell output.
|
|
110
110
|
options.onStatus?.(output.event);
|
|
111
|
-
if (output.event
|
|
111
|
+
if (isEvalTimeoutControlEvent(output.event)) return;
|
|
112
112
|
}
|
|
113
113
|
displayOutputs.push(output);
|
|
114
114
|
},
|
package/src/eval/llm-bridge.ts
CHANGED
|
@@ -18,7 +18,7 @@ import { extractTextContent, extractToolCall, parseJsonPayload } from "../commit
|
|
|
18
18
|
import { expandRoleAlias, formatModelString, resolveModelFromString } from "../config/model-resolver";
|
|
19
19
|
import type { ToolSession } from "../tools";
|
|
20
20
|
import { ToolError } from "../tools/tool-errors";
|
|
21
|
-
import {
|
|
21
|
+
import { withBridgeTimeoutPause } from "./bridge-timeout";
|
|
22
22
|
import type { JsStatusEvent } from "./js/shared/types";
|
|
23
23
|
|
|
24
24
|
/** Synthetic bridge name reserved for the `llm()` helper across both runtimes. */
|
|
@@ -132,10 +132,9 @@ export async function runEvalLlm(args: unknown, options: EvalLlmBridgeOptions):
|
|
|
132
132
|
|
|
133
133
|
const telemetry = resolveTelemetry(options.session.getTelemetry?.(), options.session.getSessionId?.() ?? undefined);
|
|
134
134
|
|
|
135
|
-
//
|
|
136
|
-
//
|
|
137
|
-
|
|
138
|
-
const response = await withBridgeHeartbeat(options.emitStatus, () =>
|
|
135
|
+
// Suspend eval timeout accounting while the model request owns control. The
|
|
136
|
+
// timeout clock restarts once the bridge returns to the cell runtime.
|
|
137
|
+
const response = await withBridgeTimeoutPause(options.emitStatus, () =>
|
|
139
138
|
instrumentedCompleteSimple(
|
|
140
139
|
model,
|
|
141
140
|
{
|
package/src/eval/py/executor.ts
CHANGED
|
@@ -5,7 +5,7 @@ import { Settings } from "../../config/settings";
|
|
|
5
5
|
import { OutputSink } from "../../session/streaming-output";
|
|
6
6
|
import type { ToolSession } from "../../tools";
|
|
7
7
|
import { resolveOutputMaxColumns, resolveOutputSinkHeadBytes } from "../../tools/output-meta";
|
|
8
|
-
import {
|
|
8
|
+
import { isEvalTimeoutControlEvent } from "../bridge-timeout";
|
|
9
9
|
import type { JsStatusEvent } from "../js/shared/types";
|
|
10
10
|
import {
|
|
11
11
|
checkPythonKernelAvailability,
|
|
@@ -27,8 +27,8 @@ export interface PythonExecutorOptions {
|
|
|
27
27
|
/** Absolute wall-clock deadline in milliseconds since epoch */
|
|
28
28
|
deadlineMs?: number;
|
|
29
29
|
/**
|
|
30
|
-
*
|
|
31
|
-
* caller drives cancellation via
|
|
30
|
+
* Runtime-work budget (ms). Used only for timeout-annotation text when the
|
|
31
|
+
* caller drives cancellation via the eval watchdog `signal` instead of a
|
|
32
32
|
* wall-clock `deadlineMs`/`timeoutMs`. Does not arm a timer.
|
|
33
33
|
*/
|
|
34
34
|
idleTimeoutMs?: number;
|
|
@@ -492,10 +492,10 @@ async function executeWithKernel(
|
|
|
492
492
|
// long-running bridge helpers (e.g. `agent()`) surface progress mid-cell.
|
|
493
493
|
const collectDisplay = (output: KernelDisplayOutput) => {
|
|
494
494
|
if (output.type === "status") {
|
|
495
|
-
//
|
|
496
|
-
//
|
|
495
|
+
// Timeout-control events drive the eval watchdog only; never store or
|
|
496
|
+
// render them as cell output.
|
|
497
497
|
options?.onStatus?.(output.event);
|
|
498
|
-
if (output.event
|
|
498
|
+
if (isEvalTimeoutControlEvent(output.event)) return;
|
|
499
499
|
}
|
|
500
500
|
displayOutputs.push(output);
|
|
501
501
|
};
|
package/src/eval/py/kernel.ts
CHANGED
|
@@ -18,6 +18,7 @@ import { type KernelDisplayOutput, renderKernelDisplay } from "./display";
|
|
|
18
18
|
import { PYTHON_PRELUDE } from "./prelude";
|
|
19
19
|
import RUNNER_SCRIPT from "./runner.py" with { type: "text" };
|
|
20
20
|
import { enumeratePythonRuntimes, filterEnv, type PythonRuntime, resolvePythonRuntime } from "./runtime";
|
|
21
|
+
import { hostHasInheritableConsole, shouldHideKernelWindow } from "./spawn-options";
|
|
21
22
|
|
|
22
23
|
export type { KernelDisplayOutput, PythonStatusEvent } from "./display";
|
|
23
24
|
export { renderKernelDisplay } from "./display";
|
|
@@ -253,7 +254,16 @@ export class PythonKernel {
|
|
|
253
254
|
stdin: "pipe",
|
|
254
255
|
stdout: "pipe",
|
|
255
256
|
stderr: "pipe",
|
|
256
|
-
|
|
257
|
+
// Detached from any inherited console only when the host itself
|
|
258
|
+
// has no console — kernel32!GetConsoleWindow() is authoritative
|
|
259
|
+
// (works even when every stdio stream is redirected), with a
|
|
260
|
+
// TTY-OR fallback when the FFI probe is unavailable. See #1960
|
|
261
|
+
// for the numpy/pandas LoadLibraryExW hang + SIGINT-recovery
|
|
262
|
+
// failure that motivates the predicate.
|
|
263
|
+
windowsHide: shouldHideKernelWindow({
|
|
264
|
+
platform: process.platform,
|
|
265
|
+
hostHasInheritableConsole: hostHasInheritableConsole(),
|
|
266
|
+
}),
|
|
257
267
|
});
|
|
258
268
|
kernel.#proc = proc;
|
|
259
269
|
kernel.#stdin = proc.stdin;
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Subprocess spawn-option helpers for the Python kernel.
|
|
3
|
+
*
|
|
4
|
+
* Pure helpers (`shouldHideKernelWindow`, `consoleAttachedViaTTY`) live here
|
|
5
|
+
* so they can be unit-tested without dragging in the kernel's runtime
|
|
6
|
+
* dependencies. The effectful `hostHasInheritableConsole` wraps a Win32 FFI
|
|
7
|
+
* probe with a TTY fallback and is the function `kernel.ts` actually calls.
|
|
8
|
+
*/
|
|
9
|
+
import { dlopen, FFIType } from "bun:ffi";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Decide whether the long-lived Python kernel subprocess should be spawned
|
|
13
|
+
* with `windowsHide: true`.
|
|
14
|
+
*
|
|
15
|
+
* On Windows, Bun maps `windowsHide: true` to the `CREATE_NO_WINDOW` flag,
|
|
16
|
+
* which detaches the child from any inherited console. The Python kernel
|
|
17
|
+
* runs user code that imports NumPy/pandas; those native extensions
|
|
18
|
+
* (`numpy/_core/_multiarray_umath.pyd` + bundled OpenBLAS/SLEEF thread-pool
|
|
19
|
+
* init) can deadlock inside `LoadLibraryExW` when no console is attached,
|
|
20
|
+
* and a console-less child cannot receive SIGINT via
|
|
21
|
+
* `GenerateConsoleCtrlEvent` (the recovery path the host relies on). See
|
|
22
|
+
* issue #1960.
|
|
23
|
+
*
|
|
24
|
+
* So on Windows we hide only when the host itself has no console to share.
|
|
25
|
+
* In any launch where a console is attached — even one with every stdio
|
|
26
|
+
* stream redirected — the kernel inherits the parent's console, matching
|
|
27
|
+
* `python.exe` invoked from `cmd.exe`, which keeps native imports and
|
|
28
|
+
* SIGINT recovery working.
|
|
29
|
+
*
|
|
30
|
+
* Short-lived helper subprocesses elsewhere in the codebase (LSP probes,
|
|
31
|
+
* git, plugin installs) keep `windowsHide: true` because they don't load
|
|
32
|
+
* complex native modules and the brief console flash would be user-visible
|
|
33
|
+
* noise.
|
|
34
|
+
*/
|
|
35
|
+
export function shouldHideKernelWindow(opts: {
|
|
36
|
+
platform: NodeJS.Platform;
|
|
37
|
+
hostHasInheritableConsole: boolean;
|
|
38
|
+
}): boolean {
|
|
39
|
+
if (opts.platform !== "win32") return false;
|
|
40
|
+
return !opts.hostHasInheritableConsole;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* TTY-based fallback used when the Win32 console probe is unavailable.
|
|
45
|
+
*
|
|
46
|
+
* Returns `true` if any of stdin/stdout/stderr is currently a TTY. This
|
|
47
|
+
* correctly detects the common interactive launches and the partial-
|
|
48
|
+
* redirection cases (`omp -p > out.txt`, `< in.txt`, `2> err.log`) where at
|
|
49
|
+
* least one stream stays bound to the terminal. The all-stdio-redirected
|
|
50
|
+
* case (`< in > out 2> err` from a console) is the reason we prefer the
|
|
51
|
+
* Win32 probe over this fallback whenever possible.
|
|
52
|
+
*/
|
|
53
|
+
export function consoleAttachedViaTTY(opts: {
|
|
54
|
+
stdinIsTTY: boolean;
|
|
55
|
+
stdoutIsTTY: boolean;
|
|
56
|
+
stderrIsTTY: boolean;
|
|
57
|
+
}): boolean {
|
|
58
|
+
return opts.stdinIsTTY || opts.stdoutIsTTY || opts.stderrIsTTY;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Probe `kernel32.dll!GetConsoleWindow()` to detect whether the current
|
|
63
|
+
* Windows process owns a console window.
|
|
64
|
+
*
|
|
65
|
+
* Returns `true` for a non-NULL HWND, `false` when NULL (no console — true
|
|
66
|
+
* service / `DETACHED_PROCESS` / GUI parent), and `null` when the probe
|
|
67
|
+
* itself fails (off-Windows, FFI disabled, or unexpected kernel32 layout).
|
|
68
|
+
* A `null` return means "don't trust me, use the TTY fallback".
|
|
69
|
+
*
|
|
70
|
+
* Cached on first call because in practice the console attachment of a
|
|
71
|
+
* long-lived OMP host never changes for the lifetime of the process, and
|
|
72
|
+
* we don't want to re-dlopen kernel32 on every kernel spawn.
|
|
73
|
+
*/
|
|
74
|
+
type ConsoleProbeResult = boolean | null;
|
|
75
|
+
let cachedWindowsConsoleProbe: { value: ConsoleProbeResult } | undefined;
|
|
76
|
+
|
|
77
|
+
function probeWindowsConsoleWindow(): ConsoleProbeResult {
|
|
78
|
+
if (cachedWindowsConsoleProbe) return cachedWindowsConsoleProbe.value;
|
|
79
|
+
let value: ConsoleProbeResult = null;
|
|
80
|
+
try {
|
|
81
|
+
const lib = dlopen("kernel32.dll", {
|
|
82
|
+
GetConsoleWindow: { args: [], returns: FFIType.ptr },
|
|
83
|
+
});
|
|
84
|
+
try {
|
|
85
|
+
const hwnd = lib.symbols.GetConsoleWindow();
|
|
86
|
+
// FFIType.ptr returns `Pointer | null`; a 0 pointer should also be
|
|
87
|
+
// treated as NULL defensively in case Bun ever returns 0n / 0.
|
|
88
|
+
value = hwnd !== null && hwnd !== 0;
|
|
89
|
+
} finally {
|
|
90
|
+
lib.close();
|
|
91
|
+
}
|
|
92
|
+
} catch {
|
|
93
|
+
value = null;
|
|
94
|
+
}
|
|
95
|
+
cachedWindowsConsoleProbe = { value };
|
|
96
|
+
return value;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** Reset the cached Win32 probe result. Test-only; not part of the public surface. */
|
|
100
|
+
export function __resetWindowsConsoleProbeCache(): void {
|
|
101
|
+
cachedWindowsConsoleProbe = undefined;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Whether the host process owns a console its children can inherit.
|
|
106
|
+
*
|
|
107
|
+
* - On Windows, the authoritative signal is `GetConsoleWindow()`. It returns
|
|
108
|
+
* a non-NULL HWND whenever the process has a console attached, regardless
|
|
109
|
+
* of how the standard streams are redirected — so an `omp -p ... < in.txt
|
|
110
|
+
* > out.txt 2> err.log` launched from a real Windows Terminal session is
|
|
111
|
+
* correctly classified as console-attached and the kernel keeps its
|
|
112
|
+
* inheritable console.
|
|
113
|
+
* - On any other platform, or if the FFI probe fails, fall back to the
|
|
114
|
+
* TTY-OR heuristic. That still catches the common interactive cases.
|
|
115
|
+
*/
|
|
116
|
+
export function hostHasInheritableConsole(): boolean {
|
|
117
|
+
if (process.platform === "win32") {
|
|
118
|
+
const native = probeWindowsConsoleWindow();
|
|
119
|
+
if (native !== null) return native;
|
|
120
|
+
}
|
|
121
|
+
return consoleAttachedViaTTY({
|
|
122
|
+
stdinIsTTY: !!process.stdin.isTTY,
|
|
123
|
+
stdoutIsTTY: !!process.stdout.isTTY,
|
|
124
|
+
stderrIsTTY: !!process.stderr.isTTY,
|
|
125
|
+
});
|
|
126
|
+
}
|
package/src/export/ttsr.ts
CHANGED
|
@@ -294,6 +294,9 @@ export class TtsrManager {
|
|
|
294
294
|
|
|
295
295
|
/** Add a TTSR rule to be monitored. */
|
|
296
296
|
addRule(rule: Rule): boolean {
|
|
297
|
+
if (!this.#settings.enabled) {
|
|
298
|
+
return false;
|
|
299
|
+
}
|
|
297
300
|
if (this.#rules.has(rule.name)) {
|
|
298
301
|
return false;
|
|
299
302
|
}
|
|
@@ -357,6 +360,9 @@ export class TtsrManager {
|
|
|
357
360
|
}
|
|
358
361
|
|
|
359
362
|
#matchBuffer(buffer: string, context: TtsrMatchContext): Rule[] {
|
|
363
|
+
if (!this.#settings.enabled) {
|
|
364
|
+
return [];
|
|
365
|
+
}
|
|
360
366
|
const matches: Rule[] = [];
|
|
361
367
|
for (const [name, entry] of this.#rules) {
|
|
362
368
|
if (!this.#canTrigger(name)) {
|
|
@@ -433,6 +439,9 @@ export class TtsrManager {
|
|
|
433
439
|
|
|
434
440
|
/** Check if any TTSR rules are registered. */
|
|
435
441
|
hasRules(): boolean {
|
|
442
|
+
if (!this.#settings.enabled) {
|
|
443
|
+
return false;
|
|
444
|
+
}
|
|
436
445
|
return this.#rules.size > 0;
|
|
437
446
|
}
|
|
438
447
|
|
|
@@ -354,6 +354,9 @@ export class ExtensionRunner {
|
|
|
354
354
|
"ctrl+o": true,
|
|
355
355
|
"ctrl+t": true,
|
|
356
356
|
"ctrl+g": true,
|
|
357
|
+
"alt+m": true,
|
|
358
|
+
// Default chord for `app.message.followUp` (Windows Terminal can't deliver Ctrl+Enter; #1903).
|
|
359
|
+
"ctrl+q": true,
|
|
357
360
|
"shift+tab": true,
|
|
358
361
|
"shift+ctrl+p": true,
|
|
359
362
|
"alt+enter": true,
|
|
@@ -25,7 +25,6 @@ export async function runDoctorChecks(): Promise<DoctorCheck[]> {
|
|
|
25
25
|
const apiKeys = [
|
|
26
26
|
{ name: "ANTHROPIC_API_KEY", description: "Anthropic API" },
|
|
27
27
|
{ name: "OPENAI_API_KEY", description: "OpenAI API" },
|
|
28
|
-
{ name: "PERPLEXITY_API_KEY", description: "Perplexity search" },
|
|
29
28
|
{ name: "EXA_API_KEY", description: "Exa search" },
|
|
30
29
|
];
|
|
31
30
|
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { getProjectDir, logger } from "@oh-my-pi/pi-utils";
|
|
2
|
+
|
|
3
|
+
type MarketplaceAutoUpdateMode = "off" | "notify" | "auto";
|
|
4
|
+
|
|
5
|
+
interface MarketplaceAutoUpdateOptions {
|
|
6
|
+
autoUpdate: MarketplaceAutoUpdateMode;
|
|
7
|
+
resolveActiveProjectRegistryPath: (cwd: string) => Promise<string | null>;
|
|
8
|
+
clearPluginRootsCache: () => void;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function scheduleMarketplaceAutoUpdate(options: MarketplaceAutoUpdateOptions): void {
|
|
12
|
+
if (options.autoUpdate === "off") {
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
void runMarketplaceAutoUpdate(options);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async function runMarketplaceAutoUpdate(options: MarketplaceAutoUpdateOptions): Promise<void> {
|
|
20
|
+
try {
|
|
21
|
+
// Startup perf: marketplace manager pulls scraper/fetch/cache code; keep it out of the initial TUI graph.
|
|
22
|
+
const {
|
|
23
|
+
MarketplaceManager,
|
|
24
|
+
getInstalledPluginsRegistryPath,
|
|
25
|
+
getMarketplacesCacheDir,
|
|
26
|
+
getMarketplacesRegistryPath,
|
|
27
|
+
getPluginsCacheDir,
|
|
28
|
+
} = await import("./marketplace");
|
|
29
|
+
const mgr = new MarketplaceManager({
|
|
30
|
+
marketplacesRegistryPath: getMarketplacesRegistryPath(),
|
|
31
|
+
installedRegistryPath: getInstalledPluginsRegistryPath(),
|
|
32
|
+
projectInstalledRegistryPath: (await options.resolveActiveProjectRegistryPath(getProjectDir())) ?? undefined,
|
|
33
|
+
marketplacesCacheDir: getMarketplacesCacheDir(),
|
|
34
|
+
pluginsCacheDir: getPluginsCacheDir(),
|
|
35
|
+
clearPluginRootsCache: options.clearPluginRootsCache,
|
|
36
|
+
});
|
|
37
|
+
await mgr.refreshStaleMarketplaces();
|
|
38
|
+
const updates = await mgr.checkForUpdates();
|
|
39
|
+
if (updates.length === 0) return;
|
|
40
|
+
if (options.autoUpdate === "auto") {
|
|
41
|
+
await mgr.upgradeAllPlugins();
|
|
42
|
+
logger.debug(`Auto-upgraded ${updates.length} marketplace plugin(s)`);
|
|
43
|
+
} else {
|
|
44
|
+
logger.debug(`${updates.length} marketplace plugin update(s) available — /marketplace upgrade`);
|
|
45
|
+
}
|
|
46
|
+
} catch {
|
|
47
|
+
// Silently ignore — network failure, corrupt data, offline.
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -8,7 +8,7 @@ import type { Theme, ThemeColor } from "../../modes/theme/theme";
|
|
|
8
8
|
import goalDescription from "../../prompts/tools/goal.md" with { type: "text" };
|
|
9
9
|
import { formatDuration } from "../../slash-commands/helpers/format";
|
|
10
10
|
import type { ToolSession } from "../../tools";
|
|
11
|
-
import {
|
|
11
|
+
import { formatErrorDetail, TRUNCATE_LENGTHS } from "../../tools/render-utils";
|
|
12
12
|
import { ToolError } from "../../tools/tool-errors";
|
|
13
13
|
import { renderStatusLine, truncateToWidth } from "../../tui";
|
|
14
14
|
import { completionBudgetReport, remainingTokens } from "../runtime";
|
|
@@ -190,7 +190,7 @@ export const goalToolRenderer = {
|
|
|
190
190
|
|
|
191
191
|
if (result.isError) {
|
|
192
192
|
const header = renderStatusLine({ icon: "error", title: "Goal", description }, uiTheme);
|
|
193
|
-
const body =
|
|
193
|
+
const body = formatErrorDetail(fallbackText || "Goal tool failed", uiTheme);
|
|
194
194
|
return new Text([header, body].join("\n"), 0, 0);
|
|
195
195
|
}
|
|
196
196
|
|