@oh-my-pi/pi-coding-agent 17.2.9 → 17.2.11
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 +83 -0
- package/dist/{CHANGELOG-8yy8h3bc.md → CHANGELOG-d9xenpn9.md} +83 -0
- package/dist/cli.js +10208 -12596
- package/dist/types/capability/mcp.d.ts +11 -0
- package/dist/types/capability/skill.d.ts +6 -0
- package/dist/types/cli/args.d.ts +1 -0
- package/dist/types/cli/command-help.d.ts +3 -0
- package/dist/types/commands/share.d.ts +25 -0
- package/dist/types/commit/agentic/index.d.ts +3 -1
- package/dist/types/commit/execute.d.ts +32 -0
- package/dist/types/commit/index.d.ts +2 -1
- package/dist/types/commit/pipeline.d.ts +7 -1
- package/dist/types/config/model-registry.d.ts +4 -0
- package/dist/types/config/model-resolver.d.ts +13 -0
- package/dist/types/config/settings-schema.d.ts +1 -34
- package/dist/types/discovery/agent-plugin-format.d.ts +143 -0
- package/dist/types/discovery/agent-plugins.d.ts +1 -0
- package/dist/types/discovery/contained-path.d.ts +34 -0
- package/dist/types/discovery/index.d.ts +1 -0
- package/dist/types/extensibility/custom-commands/types.d.ts +5 -5
- package/dist/types/extensibility/custom-tools/types.d.ts +3 -3
- package/dist/types/extensibility/extensions/loader.d.ts +9 -2
- package/dist/types/extensibility/extensions/types.d.ts +45 -8
- package/dist/types/extensibility/hooks/types.d.ts +5 -5
- package/dist/types/extensibility/shared-events.d.ts +3 -2
- package/dist/types/extensibility/skills.d.ts +5 -0
- package/dist/types/index.d.ts +2 -2
- package/dist/types/main.d.ts +1 -1
- package/dist/types/mcp/transports/header-policy.d.ts +45 -0
- package/dist/types/mcp/types.d.ts +15 -0
- package/dist/types/modes/acp/acp-agent.d.ts +1 -1
- package/dist/types/modes/acp/acp-client-bridge.d.ts +1 -1
- package/dist/types/modes/acp/acp-event-mapper.d.ts +1 -1
- package/dist/types/modes/acp/acp-mode.d.ts +1 -1
- package/dist/types/modes/components/agent-hub-projection.d.ts +38 -0
- package/dist/types/modes/components/agent-hub-renderer.d.ts +41 -0
- package/dist/types/modes/components/agent-hub.d.ts +14 -5
- package/dist/types/modes/components/agent-transcript-viewer.d.ts +2 -0
- package/dist/types/modes/components/chat-transcript-builder.d.ts +2 -0
- package/dist/types/modes/components/custom-editor.d.ts +1 -2
- package/dist/types/modes/components/model-browser.d.ts +9 -1
- package/dist/types/modes/components/status-line/types.d.ts +2 -0
- package/dist/types/modes/components/tool-execution.d.ts +2 -0
- package/dist/types/modes/interactive-mode.d.ts +8 -1
- package/dist/types/modes/utils/transcript-render-helpers.d.ts +3 -2
- package/dist/types/registry/agent-registry.d.ts +47 -0
- package/dist/types/registry/persisted-agents.d.ts +3 -1
- package/dist/types/secrets/index.d.ts +23 -1
- package/dist/types/session/agent-session-events.d.ts +2 -2
- package/dist/types/session/agent-session.d.ts +4 -2
- package/dist/types/session/messages.d.ts +1 -0
- package/dist/types/session/prewalk.d.ts +3 -1
- package/dist/types/session/session-entries.d.ts +10 -0
- package/dist/types/session/session-loader.d.ts +11 -1
- package/dist/types/session/session-manager.d.ts +9 -1
- package/dist/types/session/session-tools.d.ts +18 -1
- package/dist/types/session/turn-recovery.d.ts +7 -2
- package/dist/types/slash-commands/acp-builtins.d.ts +1 -1
- package/dist/types/slash-commands/available-commands.d.ts +1 -1
- package/dist/types/task/executor.d.ts +6 -0
- package/dist/types/task/index.d.ts +4 -3
- package/dist/types/task/read-only-policy.d.ts +3 -0
- package/dist/types/task/structured-subagent.d.ts +2 -0
- package/dist/types/task/types.d.ts +4 -0
- package/dist/types/tools/bash.d.ts +3 -3
- package/dist/types/tools/browser/launch.d.ts +2 -1
- package/dist/types/tools/browser/tab-worker.d.ts +2 -2
- package/dist/types/tools/path-utils.d.ts +1 -0
- package/dist/types/tools/read.d.ts +1 -5
- package/dist/types/tools/run-scope.d.ts +27 -1
- package/dist/types/tools/shell-tokenize.d.ts +16 -0
- package/dist/types/tools/terminal-output.d.ts +1 -1
- package/dist/types/utils/turndown.d.ts +1 -1
- package/dist/types/vibe/runtime.d.ts +2 -3
- package/dist/types/web/scrapers/readthedocs.d.ts +0 -3
- package/dist/types/web/search/providers/browser-headers.d.ts +2 -3
- package/examples/custom-tools/README.md +1 -1
- package/examples/extensions/README.md +1 -1
- package/examples/extensions/api-demo.ts +5 -6
- package/examples/extensions/chalk-logger.ts +1 -1
- package/examples/extensions/hello.ts +2 -2
- package/examples/extensions/reload-runtime.ts +1 -1
- package/examples/extensions/tools.ts +2 -2
- package/examples/extensions/with-deps/index.ts +1 -1
- package/examples/sdk/06-extensions.ts +1 -1
- package/package.json +15 -31
- package/scripts/bundle-dist.ts +1 -11
- package/scripts/legacy-pi-virtual-module.ts +4 -1
- package/src/advisor/runtime.ts +54 -0
- package/src/capability/mcp.ts +11 -0
- package/src/capability/skill.ts +6 -0
- package/src/cli/agents-cli.ts +1 -1
- package/src/cli/args.ts +24 -2
- package/src/cli/auth-broker-cli.ts +1 -1
- package/src/cli/auth-gateway-cli.ts +1 -1
- package/src/cli/bench-cli.ts +1 -1
- package/src/cli/claude-trace-cli.ts +1 -1
- package/src/cli/command-help.ts +4 -0
- package/src/cli/config-cli.ts +1 -1
- package/src/cli/dry-balance-cli.ts +1 -1
- package/src/cli/file-processor.ts +1 -1
- package/src/cli/flag-tables.ts +4 -0
- package/src/cli/gallery-cli.ts +1 -1
- package/src/cli/grep-cli.ts +1 -1
- package/src/cli/grievances-cli.ts +1 -1
- package/src/cli/help-extra.ts +1 -1
- package/src/cli/models-cli.ts +1 -1
- package/src/cli/plugin-cli.ts +1 -1
- package/src/cli/read-cli.ts +1 -1
- package/src/cli/setup-cli.ts +1 -1
- package/src/cli/shell-cli.ts +1 -1
- package/src/cli/ssh-cli.ts +1 -1
- package/src/cli/stats-cli.ts +1 -1
- package/src/cli/tiny-models-cli.ts +1 -1
- package/src/cli/ttsr-cli.ts +1 -1
- package/src/cli/update-cli.ts +1 -1
- package/src/cli/usage-cli.ts +1 -1
- package/src/cli/web-search-cli.ts +1 -1
- package/src/cli/worktree-cli.ts +1 -1
- package/src/cli-commands.ts +5 -0
- package/src/commands/commit.ts +16 -3
- package/src/commands/say.ts +1 -1
- package/src/commands/share.ts +71 -0
- package/src/commands/token.ts +1 -1
- package/src/commit/agentic/agent.ts +1 -1
- package/src/commit/agentic/index.ts +39 -21
- package/src/commit/cli.ts +1 -1
- package/src/commit/execute.ts +56 -0
- package/src/commit/index.ts +2 -1
- package/src/commit/pipeline.ts +20 -7
- package/src/config/model-discovery.ts +1 -1
- package/src/config/model-registry.ts +21 -1
- package/src/config/model-resolver.ts +54 -7
- package/src/config/settings-schema.ts +2 -33
- package/src/config/settings.ts +46 -0
- package/src/discovery/agent-plugin-format.ts +551 -0
- package/src/discovery/agent-plugins.ts +341 -0
- package/src/discovery/agents.ts +4 -2
- package/src/discovery/builtin-rules/ts-no-inline-cast-access.md +5 -4
- package/src/discovery/builtin-rules/ts-no-tiny-functions.md +1 -1
- package/src/discovery/claude-plugins.ts +21 -5
- package/src/discovery/contained-path.ts +78 -0
- package/src/discovery/helpers.ts +23 -9
- package/src/discovery/index.ts +1 -0
- package/src/discovery/omp-plugins.ts +20 -7
- package/src/eval/py/runner.py +38 -1
- package/src/exec/non-interactive-env.ts +1 -0
- package/src/extensibility/custom-commands/loader.ts +4 -4
- package/src/extensibility/custom-commands/types.ts +5 -5
- package/src/extensibility/custom-tools/loader.ts +4 -4
- package/src/extensibility/custom-tools/types.ts +3 -3
- package/src/extensibility/extensions/loader.ts +85 -20
- package/src/extensibility/extensions/runner.ts +6 -0
- package/src/extensibility/extensions/types.ts +51 -8
- package/src/extensibility/hooks/loader.ts +4 -5
- package/src/extensibility/hooks/types.ts +5 -5
- package/src/extensibility/plugins/legacy-pi-compat.ts +53 -0
- package/src/extensibility/plugins/marketplace/manager.ts +2 -1
- package/src/extensibility/shared-events.ts +3 -2
- package/src/extensibility/skills.ts +9 -0
- package/src/index.ts +2 -2
- package/src/internal-urls/memory-protocol.ts +5 -1
- package/src/internal-urls/skill-protocol.ts +14 -0
- package/src/internal-urls/vault-protocol.ts +2 -2
- package/src/launch/client.ts +11 -1
- package/src/launch/protocol.ts +7 -2
- package/src/launch/terminal-output.ts +1 -1
- package/src/main.ts +88 -23
- package/src/markit/converters/docx.ts +1 -1
- package/src/markit/converters/epub.ts +1 -1
- package/src/markit/converters/pptx.ts +1 -1
- package/src/markit/converters/xlsx.ts +1 -1
- package/src/mcp/config.ts +3 -0
- package/src/mcp/manager.ts +12 -9
- package/src/mcp/transports/header-policy.ts +95 -0
- package/src/mcp/transports/http.ts +35 -52
- package/src/mcp/transports/sse.ts +20 -27
- package/src/mcp/types.ts +15 -0
- package/src/modes/acp/acp-agent.ts +20 -9
- package/src/modes/acp/acp-client-bridge.ts +1 -1
- package/src/modes/acp/acp-event-mapper.ts +1 -1
- package/src/modes/acp/acp-mode.ts +1 -1
- package/src/modes/components/agent-dashboard.ts +2 -0
- package/src/modes/components/agent-hub-projection.ts +248 -0
- package/src/modes/components/agent-hub-renderer.ts +194 -0
- package/src/modes/components/agent-hub.ts +674 -207
- package/src/modes/components/agent-transcript-viewer.ts +3 -0
- package/src/modes/components/assistant-message.ts +1 -1
- package/src/modes/components/chat-transcript-builder.ts +3 -0
- package/src/modes/components/custom-editor.ts +0 -9
- package/src/modes/components/extensions/inspector-panel.ts +11 -6
- package/src/modes/components/footer.ts +1 -3
- package/src/modes/components/model-browser.ts +11 -3
- package/src/modes/components/model-hub.ts +4 -1
- package/src/modes/components/status-line/component.ts +1 -0
- package/src/modes/components/status-line/segments.ts +10 -7
- package/src/modes/components/status-line/types.ts +2 -0
- package/src/modes/components/tool-execution.ts +13 -16
- package/src/modes/components/tree-selector.ts +62 -3
- package/src/modes/controllers/command-controller.ts +4 -1
- package/src/modes/controllers/event-controller.ts +44 -20
- package/src/modes/controllers/extension-ui-controller.ts +2 -2
- package/src/modes/controllers/input-controller.ts +20 -2
- package/src/modes/controllers/mcp-command-controller.ts +81 -20
- package/src/modes/controllers/selector-controller.ts +41 -49
- package/src/modes/controllers/streaming-reveal.ts +1 -1
- package/src/modes/interactive-mode.ts +13 -2
- package/src/modes/rpc/rpc-mode.ts +2 -2
- package/src/modes/runtime-init.ts +1 -1
- package/src/modes/theme/theme.ts +2 -2
- package/src/modes/utils/transcript-render-helpers.ts +4 -2
- package/src/modes/utils/ui-helpers.ts +1 -0
- package/src/prompts/tools/computer.md +1 -1
- package/src/registry/agent-lifecycle.ts +14 -7
- package/src/registry/agent-registry.ts +65 -2
- package/src/registry/persisted-agents.ts +341 -16
- package/src/sdk.ts +6 -49
- package/src/secrets/index.ts +52 -1
- package/src/session/agent-session-events.ts +2 -2
- package/src/session/agent-session.ts +54 -16
- package/src/session/messages.ts +1 -0
- package/src/session/prewalk.ts +57 -17
- package/src/session/session-context.ts +3 -5
- package/src/session/session-entries.ts +10 -0
- package/src/session/session-handoff.ts +5 -1
- package/src/session/session-listing.ts +1 -1
- package/src/session/session-loader.ts +74 -8
- package/src/session/session-manager.ts +22 -2
- package/src/session/session-paths.ts +57 -9
- package/src/session/session-tools.ts +65 -4
- package/src/session/todo-tracker.ts +11 -1
- package/src/session/turn-recovery.ts +96 -74
- package/src/slash-commands/acp-builtins.ts +1 -1
- package/src/slash-commands/available-commands.ts +1 -1
- package/src/slash-commands/builtin-registry.ts +12 -8
- package/src/task/executor.ts +70 -20
- package/src/task/index.ts +30 -34
- package/src/task/isolation-runner.ts +24 -11
- package/src/task/persisted-revive.ts +12 -5
- package/src/task/read-only-policy.ts +27 -0
- package/src/task/structured-subagent.ts +14 -4
- package/src/task/types.ts +4 -0
- package/src/tools/auto-generated-guard.ts +1 -1
- package/src/tools/bash-interactive.ts +4 -4
- package/src/tools/bash-skill-urls.ts +15 -0
- package/src/tools/bash.ts +16 -17
- package/src/tools/browser/cmux/cmux-tab.ts +66 -18
- package/src/tools/browser/launch.ts +41 -12
- package/src/tools/browser/readable.ts +9 -9
- package/src/tools/browser/tab-supervisor.ts +12 -3
- package/src/tools/browser/tab-worker-entry.ts +1 -1
- package/src/tools/browser/tab-worker.ts +101 -12
- package/src/tools/debug.ts +1 -1
- package/src/tools/fetch.ts +1 -1
- package/src/tools/jtd-to-json-schema.ts +123 -21
- package/src/tools/path-utils.ts +9 -2
- package/src/tools/read.ts +21 -9
- package/src/tools/run-scope.ts +290 -4
- package/src/tools/shell-tokenize.ts +98 -0
- package/src/tools/terminal-output.ts +1 -1
- package/src/tools/todo.ts +1 -1
- package/src/utils/clipboard.ts +10 -2
- package/src/utils/external-editor.ts +4 -2
- package/src/utils/jj.ts +1 -1
- package/src/utils/turndown.ts +1 -2
- package/src/vibe/runtime.ts +5 -5
- package/src/web/scrapers/arxiv.ts +1 -1
- package/src/web/scrapers/go-pkg.ts +1 -1
- package/src/web/scrapers/iacr.ts +1 -1
- package/src/web/scrapers/readthedocs.ts +2 -1
- package/src/web/scrapers/twitter.ts +2 -2
- package/src/web/scrapers/types.ts +1 -1
- package/src/web/scrapers/wikipedia.ts +1 -1
- package/src/web/search/providers/browser-headers.ts +12 -39
- package/src/web/search/providers/codex.ts +3 -26
- package/src/web/search/providers/ecosia.ts +1 -1
- package/src/web/search/providers/exa.ts +1 -1
- package/src/web/search/providers/google.ts +1 -1
- package/src/web/search/providers/mojeek.ts +1 -1
- package/src/web/search/providers/startpage.ts +1 -1
- package/src/markit/converters/mammoth.d.ts +0 -24
|
@@ -8,7 +8,7 @@ import * as fs from "node:fs/promises";
|
|
|
8
8
|
import * as os from "node:os";
|
|
9
9
|
import path from "node:path";
|
|
10
10
|
import { $env, prompt, Snowflake } from "@oh-my-pi/pi-utils";
|
|
11
|
-
import { resolveAgentModelPatterns } from "../config/model-resolver";
|
|
11
|
+
import { resolveAgentModelPatterns, resolveAgentModelSource, resolveExplicitModelRole } from "../config/model-resolver";
|
|
12
12
|
import type { LocalProtocolOptions } from "../internal-urls";
|
|
13
13
|
import { registerArtifactsDir } from "../internal-urls/registry-helpers";
|
|
14
14
|
import { MCPManager } from "../mcp/manager";
|
|
@@ -123,6 +123,8 @@ export interface EffectiveSubagentPolicy {
|
|
|
123
123
|
agent: AgentDefinition;
|
|
124
124
|
effectiveAgent: AgentDefinition;
|
|
125
125
|
modelOverride?: string | string[];
|
|
126
|
+
/** Explicit pre-expansion model role alias selected for this run. */
|
|
127
|
+
modelRole?: string;
|
|
126
128
|
parentActiveModelPattern?: string;
|
|
127
129
|
schema: StructuredSubagentSchemaResolution;
|
|
128
130
|
planMode: boolean;
|
|
@@ -278,13 +280,18 @@ export async function resolveEffectiveSubagentPolicy(
|
|
|
278
280
|
}
|
|
279
281
|
const agentModelOverrides = request.session.settings.get("task.agentModelOverrides");
|
|
280
282
|
const parentActiveModelPattern = request.session.getActiveModelString?.();
|
|
281
|
-
const
|
|
282
|
-
|
|
283
|
+
const modelResolution = {
|
|
284
|
+
requestModel: request.model,
|
|
285
|
+
settingsOverride: agentModelOverrides[agentName],
|
|
283
286
|
agentModel: effectiveAgent.model,
|
|
284
287
|
settings: request.session.settings,
|
|
285
288
|
activeModelPattern: parentActiveModelPattern,
|
|
286
289
|
fallbackModelPattern: request.session.getModelString?.(),
|
|
287
|
-
}
|
|
290
|
+
};
|
|
291
|
+
// Keep role identity from the same effective non-empty source that supplies
|
|
292
|
+
// model selection: caller request, settings override, then agent definition.
|
|
293
|
+
const modelRole = resolveExplicitModelRole(resolveAgentModelSource(modelResolution), request.session.settings);
|
|
294
|
+
const modelOverride = resolveAgentModelPatterns(modelResolution);
|
|
288
295
|
const isolationMode = request.session.settings.get("task.isolation.mode");
|
|
289
296
|
const isIsolated = request.isolation?.requested === true;
|
|
290
297
|
if (isIsolated && isolationMode === "none") {
|
|
@@ -299,6 +306,7 @@ export async function resolveEffectiveSubagentPolicy(
|
|
|
299
306
|
agent,
|
|
300
307
|
effectiveAgent,
|
|
301
308
|
modelOverride,
|
|
309
|
+
modelRole,
|
|
302
310
|
parentActiveModelPattern,
|
|
303
311
|
schema,
|
|
304
312
|
planMode,
|
|
@@ -394,6 +402,7 @@ function buildExecutorOptions(
|
|
|
394
402
|
invokedAt: request.invokedAt,
|
|
395
403
|
acquiredAt: request.acquiredAt,
|
|
396
404
|
modelOverride: policy.modelOverride,
|
|
405
|
+
modelRole: policy.modelRole,
|
|
397
406
|
parentActiveModelPattern: policy.parentActiveModelPattern,
|
|
398
407
|
thinkingLevel: policy.effectiveAgent.thinkingLevel,
|
|
399
408
|
effort: request.effort,
|
|
@@ -476,6 +485,7 @@ function buildFailureResult(
|
|
|
476
485
|
tokens: 0,
|
|
477
486
|
requests: 0,
|
|
478
487
|
modelOverride: policy.modelOverride,
|
|
488
|
+
modelRole: policy.modelRole,
|
|
479
489
|
error: message,
|
|
480
490
|
};
|
|
481
491
|
};
|
package/src/task/types.ts
CHANGED
|
@@ -426,6 +426,8 @@ export interface AgentProgress {
|
|
|
426
426
|
cost: number;
|
|
427
427
|
durationMs: number;
|
|
428
428
|
modelOverride?: string | string[];
|
|
429
|
+
/** Explicit pre-expansion model role alias selected for this run. */
|
|
430
|
+
modelRole?: string;
|
|
429
431
|
/** Resolved model display string in the form `<provider>/<id>`, optionally suffixed with `:<thinkingLevel>` when the level was set explicitly. Undefined when the model could not be resolved. */
|
|
430
432
|
resolvedModel?: string;
|
|
431
433
|
/** True when {@link resolvedModel} is the target of an active retry fallback (not the originally configured model). Lets observer-only UIs (collab guests, Agent Hub rows with no live session) flag the fallback and keep the provider. */
|
|
@@ -495,6 +497,8 @@ export interface SingleResult {
|
|
|
495
497
|
/** Model's context window in tokens, when known. */
|
|
496
498
|
contextWindow?: number;
|
|
497
499
|
modelOverride?: string | string[];
|
|
500
|
+
/** Explicit pre-expansion model role alias selected for this run. */
|
|
501
|
+
modelRole?: string;
|
|
498
502
|
/** Resolved model display string in the form `<provider>/<id>`, optionally suffixed with `:<thinkingLevel>` when the level was set explicitly. Omitted from tool-result JSON when undefined to keep wire payloads small. */
|
|
499
503
|
resolvedModel?: string;
|
|
500
504
|
/** True when {@link resolvedModel} is the target of an active retry fallback. Mirrors {@link AgentProgress.resolvedModelIsFallback} onto the settled result. */
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import * as path from "node:path";
|
|
8
8
|
import { isEnoent, peekFile } from "@oh-my-pi/pi-utils";
|
|
9
|
-
import { LRUCache } from "
|
|
9
|
+
import { LRUCache } from "@oh-my-pi/pi-utils/lru";
|
|
10
10
|
import { settings as globalSettings, isSettingsInitialized, type Settings } from "../config/settings";
|
|
11
11
|
import { getDefault } from "../config/settings-schema";
|
|
12
12
|
import { ToolError } from "./tool-errors";
|
|
@@ -11,8 +11,8 @@ import {
|
|
|
11
11
|
visibleWidth,
|
|
12
12
|
} from "@oh-my-pi/pi-tui";
|
|
13
13
|
import { sanitizeText } from "@oh-my-pi/pi-utils";
|
|
14
|
-
import type * as XtermModule from "@
|
|
15
|
-
import type { Terminal as XtermTerminalType } from "@
|
|
14
|
+
import type * as XtermModule from "@oh-my-pi/pi-utils/vterm";
|
|
15
|
+
import type { Terminal as XtermTerminalType } from "@oh-my-pi/pi-utils/vterm";
|
|
16
16
|
import { Settings } from "../config/settings";
|
|
17
17
|
import type { Theme } from "../modes/theme/theme";
|
|
18
18
|
import { OutputSink, type OutputSummary } from "../session/streaming-output";
|
|
@@ -36,13 +36,13 @@ function normalizeCaptureChunk(chunk: string): string {
|
|
|
36
36
|
// source of truth for the final captured output.
|
|
37
37
|
const MAX_LIVE_WRITE_QUEUE_CHUNKS = 512;
|
|
38
38
|
|
|
39
|
-
//
|
|
39
|
+
// The virtual terminal is only needed once an interactive PTY session actually starts,
|
|
40
40
|
// so it is loaded lazily (and memoized) instead of weighing down CLI startup.
|
|
41
41
|
let xtermTerminalCtor: typeof XtermModule.Terminal | undefined;
|
|
42
42
|
|
|
43
43
|
async function loadXtermTerminal(): Promise<typeof XtermModule.Terminal> {
|
|
44
44
|
if (!xtermTerminalCtor) {
|
|
45
|
-
const mod = (await import("@
|
|
45
|
+
const mod = (await import("@oh-my-pi/pi-utils/vterm")) as typeof XtermModule & { default?: typeof XtermModule };
|
|
46
46
|
xtermTerminalCtor = (mod.default ?? mod).Terminal;
|
|
47
47
|
}
|
|
48
48
|
return xtermTerminalCtor;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as fs from "node:fs/promises";
|
|
2
2
|
import * as path from "node:path";
|
|
3
|
+
import { resolveContainedPathSync } from "../discovery/contained-path";
|
|
3
4
|
import type { Skill } from "../extensibility/skills";
|
|
4
5
|
import { type LocalProtocolOptions, resolveLocalUrlToPath } from "../internal-urls";
|
|
5
6
|
import { validateRelativePath } from "../internal-urls/skill-protocol";
|
|
@@ -89,6 +90,20 @@ export function resolveSkillUrlToPath(url: string, skills: readonly Skill[]): st
|
|
|
89
90
|
if (!resolvedPath.startsWith(resolvedBaseDir + path.sep) && resolvedPath !== resolvedBaseDir) {
|
|
90
91
|
throw new ToolError("Path traversal is not allowed in skill:// URLs");
|
|
91
92
|
}
|
|
93
|
+
// Agent Plugin skills (§4.1): the resource must canonically resolve within
|
|
94
|
+
// the plugin root. Fail closed: a dangling or unresolvable path is rejected
|
|
95
|
+
// rather than handed to bash, where writing through it could create the
|
|
96
|
+
// outside target. Symlinks may target other files inside the same package.
|
|
97
|
+
if (skill.containRoot) {
|
|
98
|
+
const contained = resolveContainedPathSync(skill.containRoot, resolvedPath);
|
|
99
|
+
if (contained.status === "outside") {
|
|
100
|
+
throw new ToolError(`skill:// path resolves outside the plugin root: ${url}`);
|
|
101
|
+
}
|
|
102
|
+
if (contained.status === "missing") {
|
|
103
|
+
throw new ToolError(`skill:// path does not exist: ${url}`);
|
|
104
|
+
}
|
|
105
|
+
return contained.realPath;
|
|
106
|
+
}
|
|
92
107
|
|
|
93
108
|
return resolvedPath;
|
|
94
109
|
}
|
package/src/tools/bash.ts
CHANGED
|
@@ -49,7 +49,7 @@ import {
|
|
|
49
49
|
previewWindowRows,
|
|
50
50
|
replaceTabs,
|
|
51
51
|
} from "./render-utils";
|
|
52
|
-
import { tokenizeShellSegments } from "./shell-tokenize";
|
|
52
|
+
import { extractLeadingCdTarget, tokenizeShellSegments } from "./shell-tokenize";
|
|
53
53
|
import { ToolAbortError, ToolError } from "./tool-errors";
|
|
54
54
|
import { toolResult } from "./tool-result";
|
|
55
55
|
import { clampTimeout, TOOL_TIMEOUTS } from "./tool-timeouts";
|
|
@@ -384,8 +384,8 @@ function normalizeBashEnv(env: Record<string, string> | undefined): Record<strin
|
|
|
384
384
|
return normalized;
|
|
385
385
|
}
|
|
386
386
|
|
|
387
|
-
function escapeBashEnvValueForDisplay(value:
|
|
388
|
-
return value
|
|
387
|
+
function escapeBashEnvValueForDisplay(value: unknown): string {
|
|
388
|
+
return String(value)
|
|
389
389
|
.replaceAll("\\", "\\\\")
|
|
390
390
|
.replaceAll("\n", "\\n")
|
|
391
391
|
.replaceAll("\r", "\\r")
|
|
@@ -395,7 +395,7 @@ function escapeBashEnvValueForDisplay(value: string): string {
|
|
|
395
395
|
.replaceAll("`", "\\`");
|
|
396
396
|
}
|
|
397
397
|
|
|
398
|
-
function formatBashEnvAssignments(env: Record<string,
|
|
398
|
+
function formatBashEnvAssignments(env: Record<string, unknown> | undefined): string {
|
|
399
399
|
if (!env || Object.keys(env).length === 0) return "";
|
|
400
400
|
return Object.entries(env)
|
|
401
401
|
.sort(([a], [b]) => a.localeCompare(b))
|
|
@@ -960,17 +960,16 @@ export class BashTool implements AgentTool<typeof bashSchemaBase | typeof bashSc
|
|
|
960
960
|
let command = rawCommand;
|
|
961
961
|
const env = normalizeBashEnv(rawEnv);
|
|
962
962
|
|
|
963
|
-
// Extract leading `cd <path> && ...` into cwd when the model ignores the
|
|
964
|
-
//
|
|
965
|
-
//
|
|
963
|
+
// Extract a leading `cd <path> && ...` into cwd when the model ignores the
|
|
964
|
+
// cwd parameter. The scanner captures only a single path token and defers
|
|
965
|
+
// to the shell for anything else (redirects, extra args, shell expansion),
|
|
966
|
+
// so it never absorbs shell syntax like `cd /tmp 2>/dev/null && ...` into
|
|
967
|
+
// the structured cwd. Constrained to a top-level `&&` on the first line.
|
|
966
968
|
if (!cwd) {
|
|
967
|
-
const
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
if (cdMatch && !/[$`(]/.test(cdMatch[1])) {
|
|
972
|
-
cwd = cdMatch[1].trim().replace(/^["']|["']$/g, "");
|
|
973
|
-
command = command.slice(cdMatch[0].length);
|
|
969
|
+
const cd = extractLeadingCdTarget(command);
|
|
970
|
+
if (cd) {
|
|
971
|
+
cwd = cd.path;
|
|
972
|
+
command = cd.rest;
|
|
974
973
|
}
|
|
975
974
|
}
|
|
976
975
|
if (asyncRequested && !this.#asyncEnabled) {
|
|
@@ -1498,7 +1497,7 @@ export class BashTool implements AgentTool<typeof bashSchemaBase | typeof bashSc
|
|
|
1498
1497
|
// =============================================================================
|
|
1499
1498
|
export interface BashRenderArgs {
|
|
1500
1499
|
command?: string;
|
|
1501
|
-
env?: Record<string,
|
|
1500
|
+
env?: Record<string, unknown>;
|
|
1502
1501
|
timeout?: number;
|
|
1503
1502
|
cwd?: string;
|
|
1504
1503
|
__partialJson?: string;
|
|
@@ -1522,7 +1521,7 @@ export interface ShellRendererConfig<TArgs> {
|
|
|
1522
1521
|
resolveTitle: (args: TArgs | undefined, options: RenderResultOptions) => string;
|
|
1523
1522
|
resolveCommand?: (args: TArgs | undefined) => string | undefined;
|
|
1524
1523
|
resolveCwd?: (args: TArgs | undefined) => string | undefined;
|
|
1525
|
-
resolveEnv?: (args: TArgs | undefined) => Record<string,
|
|
1524
|
+
resolveEnv?: (args: TArgs | undefined) => Record<string, unknown> | undefined;
|
|
1526
1525
|
showHeader?: boolean;
|
|
1527
1526
|
}
|
|
1528
1527
|
|
|
@@ -1532,7 +1531,7 @@ function getPartialJson<TArgs>(args: TArgs | undefined): string | undefined {
|
|
|
1532
1531
|
return typeof value === "string" ? value : undefined;
|
|
1533
1532
|
}
|
|
1534
1533
|
|
|
1535
|
-
export function getBashEnvForDisplay(args: BashRenderArgs): Record<string,
|
|
1534
|
+
export function getBashEnvForDisplay(args: BashRenderArgs): Record<string, unknown> | undefined {
|
|
1536
1535
|
// The parsed args don't always mirror the exact current stream prefix, so recover
|
|
1537
1536
|
// env from the raw JSON buffer to surface `NAME="..." cmd` in the preview as it
|
|
1538
1537
|
// streams rather than only once the args object finishes.
|
|
@@ -8,7 +8,16 @@ import { resizeImage } from "../../../utils/image-resize";
|
|
|
8
8
|
import type { ToolSession } from "../../index";
|
|
9
9
|
import { resolveToCwd } from "../../path-utils";
|
|
10
10
|
import { formatScreenshot } from "../../render-utils";
|
|
11
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
bindRunFacade,
|
|
13
|
+
isBrowserRunOwnedRejection,
|
|
14
|
+
markBrowserRunRejection,
|
|
15
|
+
observeBrowserRunPromise,
|
|
16
|
+
resolvePredicateTimeout,
|
|
17
|
+
type WaitPredicateOptions,
|
|
18
|
+
waitForRun,
|
|
19
|
+
withBrowserPromiseCombinatorTracking,
|
|
20
|
+
} from "../../run-scope";
|
|
12
21
|
import { ToolAbortError, ToolError, throwIfAborted } from "../../tool-errors";
|
|
13
22
|
import { type AriaSnapshotOptions, assertSelectorString, buildAriaSnapshotScript } from "../aria/aria-snapshot";
|
|
14
23
|
import { DEFAULT_VIEWPORT } from "../launch";
|
|
@@ -1368,6 +1377,7 @@ export async function runCmuxCode(tab: CmuxTab, opts: RunCmuxCodeOptions): Promi
|
|
|
1368
1377
|
const signal = AbortSignal.any(
|
|
1369
1378
|
opts.signal ? [timeoutSignal, opts.signal, runAc.signal] : [timeoutSignal, runAc.signal],
|
|
1370
1379
|
);
|
|
1380
|
+
const runEndedError = postmortem.markExpectedCleanupError(new ToolAbortError("Browser run ended"));
|
|
1371
1381
|
const output = new RunOutput();
|
|
1372
1382
|
const screenshots: ScreenshotResult[] = [];
|
|
1373
1383
|
const runId = crypto.randomUUID();
|
|
@@ -1382,6 +1392,28 @@ export async function runCmuxCode(tab: CmuxTab, opts: RunCmuxCodeOptions): Promi
|
|
|
1382
1392
|
// handler to this promise; keep its armed rejection from surfacing as an
|
|
1383
1393
|
// unhandled rejection — the postmortem-fatal path this run guards against.
|
|
1384
1394
|
cancelRejection.catch(() => {});
|
|
1395
|
+
const rejectionOwner = {};
|
|
1396
|
+
const { promise: floatingFailure, reject: rejectFloatingFailure } = Promise.withResolvers<never>();
|
|
1397
|
+
floatingFailure.catch(() => {});
|
|
1398
|
+
let runActive = true;
|
|
1399
|
+
let hasFloatingFailure = false;
|
|
1400
|
+
const recordFloatingFailure = (reason: unknown): void => {
|
|
1401
|
+
if (hasFloatingFailure || postmortem.isExpectedCleanupError(reason)) return;
|
|
1402
|
+
const message = reason instanceof Error ? reason.message : String(reason);
|
|
1403
|
+
if (!runActive) {
|
|
1404
|
+
logger.warn("Unhandled rejection after browser run ended", { runId, error: message });
|
|
1405
|
+
return;
|
|
1406
|
+
}
|
|
1407
|
+
hasFloatingFailure = true;
|
|
1408
|
+
const error = new Error(`Unhandled rejection (missing await?): ${message}`, { cause: reason });
|
|
1409
|
+
if (reason instanceof Error) error.name = reason.name;
|
|
1410
|
+
rejectFloatingFailure(error);
|
|
1411
|
+
};
|
|
1412
|
+
const uninstallRejectionInterceptor = postmortem.interceptUnhandledRejections(reason => {
|
|
1413
|
+
if (!isBrowserRunOwnedRejection(reason, rejectionOwner, `cmux-run-${runId}.js`)) return false;
|
|
1414
|
+
recordFloatingFailure(reason);
|
|
1415
|
+
return true;
|
|
1416
|
+
});
|
|
1385
1417
|
const onAbort = (): void => {
|
|
1386
1418
|
if (timeoutSignal.aborted) {
|
|
1387
1419
|
reject(new ToolError(`Browser code execution timed out after ${opts.timeoutMs}ms`));
|
|
@@ -1402,24 +1434,30 @@ export async function runCmuxCode(tab: CmuxTab, opts: RunCmuxCodeOptions): Promi
|
|
|
1402
1434
|
// Keep both inside try so a concurrent in-process eval/browser run surfaces as
|
|
1403
1435
|
// a rejected promise the supervisor can report, never an unhandled rejection.
|
|
1404
1436
|
runtime.setCwd(opts.snapshot.cwd);
|
|
1405
|
-
const runTab = bindRunFacade(tab, signal);
|
|
1437
|
+
const runTab = bindRunFacade(tab, signal, rejectionOwner, recordFloatingFailure);
|
|
1406
1438
|
runtime.setRunScope({
|
|
1407
|
-
page: bindRunFacade(tab.page, signal),
|
|
1408
|
-
browser: bindRunFacade(tab.browser, signal),
|
|
1439
|
+
page: bindRunFacade(tab.page, signal, rejectionOwner, recordFloatingFailure),
|
|
1440
|
+
browser: bindRunFacade(tab.browser, signal, rejectionOwner, recordFloatingFailure),
|
|
1409
1441
|
tab: runTab,
|
|
1410
1442
|
assert: (cond: unknown, text?: string): void => {
|
|
1411
1443
|
if (!cond) throw new ToolError(text ?? "Assertion failed");
|
|
1412
1444
|
},
|
|
1413
1445
|
wait: (msOrPredicate: number | (() => unknown), waitOpts?: WaitPredicateOptions): Promise<unknown> =>
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1446
|
+
observeBrowserRunPromise(
|
|
1447
|
+
waitForRun(
|
|
1448
|
+
msOrPredicate,
|
|
1449
|
+
signal,
|
|
1450
|
+
typeof msOrPredicate === "number"
|
|
1451
|
+
? waitOpts
|
|
1452
|
+
: {
|
|
1453
|
+
timeout: resolvePredicateTimeout(opts.timeoutMs, waitOpts?.timeout),
|
|
1454
|
+
interval: waitOpts?.interval,
|
|
1455
|
+
},
|
|
1456
|
+
).catch(error => {
|
|
1457
|
+
throw markBrowserRunRejection(error, rejectionOwner);
|
|
1458
|
+
}),
|
|
1459
|
+
rejectionOwner,
|
|
1460
|
+
recordFloatingFailure,
|
|
1423
1461
|
),
|
|
1424
1462
|
});
|
|
1425
1463
|
|
|
@@ -1444,16 +1482,24 @@ export async function runCmuxCode(tab: CmuxTab, opts: RunCmuxCodeOptions): Promi
|
|
|
1444
1482
|
let runError: unknown;
|
|
1445
1483
|
let runFailed = false;
|
|
1446
1484
|
try {
|
|
1447
|
-
returnValue = await
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1485
|
+
returnValue = await withBrowserPromiseCombinatorTracking(
|
|
1486
|
+
rejectionOwner,
|
|
1487
|
+
recordFloatingFailure,
|
|
1488
|
+
async () =>
|
|
1489
|
+
await Promise.race([
|
|
1490
|
+
runtime.run(opts.code, filename, hooks, { runId, cwd: opts.snapshot.cwd }),
|
|
1491
|
+
cancelRejection,
|
|
1492
|
+
floatingFailure,
|
|
1493
|
+
]),
|
|
1494
|
+
);
|
|
1451
1495
|
} catch (error) {
|
|
1452
1496
|
runFailed = true;
|
|
1453
1497
|
runError = error;
|
|
1454
1498
|
}
|
|
1499
|
+
runAc.abort(runEndedError);
|
|
1455
1500
|
// Let rejection callbacks run while this run can still own guest-created promises.
|
|
1456
1501
|
await Bun.sleep(0);
|
|
1502
|
+
if (hasFloatingFailure && !runFailed) await floatingFailure;
|
|
1457
1503
|
if (runFailed) {
|
|
1458
1504
|
for (const reason of activeRun.floatingRejections) {
|
|
1459
1505
|
logger.warn("Unhandled rejection accompanied a failed cmux browser run", { filename, error: reason });
|
|
@@ -1470,8 +1516,10 @@ export async function runCmuxCode(tab: CmuxTab, opts: RunCmuxCodeOptions): Promi
|
|
|
1470
1516
|
}
|
|
1471
1517
|
return { displays: output.finish(), returnValue: cloneSafe(returnValue), screenshots };
|
|
1472
1518
|
} finally {
|
|
1519
|
+
runActive = false;
|
|
1520
|
+
uninstallRejectionInterceptor();
|
|
1473
1521
|
signal.removeEventListener("abort", onAbort);
|
|
1474
|
-
runAc.abort(
|
|
1522
|
+
runAc.abort(runEndedError);
|
|
1475
1523
|
activeCmuxRuns.delete(filename);
|
|
1476
1524
|
rememberCmuxRunFile(filename);
|
|
1477
1525
|
tab.clearRunContext();
|
|
@@ -2,7 +2,7 @@ import * as fs from "node:fs";
|
|
|
2
2
|
import * as os from "node:os";
|
|
3
3
|
import * as path from "node:path";
|
|
4
4
|
import { $which, getPuppeteerDir, logger, removeWithRetries } from "@oh-my-pi/pi-utils";
|
|
5
|
-
import type * as BrowsersNs from "@
|
|
5
|
+
import type * as BrowsersNs from "@oh-my-pi/pi-utils/browsers";
|
|
6
6
|
import type { Browser, CDPSession, Page, default as Puppeteer, Target } from "puppeteer-core";
|
|
7
7
|
import stealthTamperingScript from "../puppeteer/00_stealth_tampering.txt" with { type: "text" };
|
|
8
8
|
import stealthActivityScript from "../puppeteer/01_stealth_activity.txt" with { type: "text" };
|
|
@@ -115,7 +115,7 @@ export async function loadPuppeteerInWorker(safeDir: string): Promise<typeof Pup
|
|
|
115
115
|
let browsersModule: typeof BrowsersNs | undefined;
|
|
116
116
|
async function loadBrowsers(): Promise<typeof BrowsersNs> {
|
|
117
117
|
if (!browsersModule) {
|
|
118
|
-
browsersModule = await import("@
|
|
118
|
+
browsersModule = await import("@oh-my-pi/pi-utils/browsers");
|
|
119
119
|
}
|
|
120
120
|
return browsersModule;
|
|
121
121
|
}
|
|
@@ -133,7 +133,7 @@ let chromiumExecutablePromise: Promise<string | undefined> | undefined;
|
|
|
133
133
|
export async function ensureChromiumExecutable(): Promise<string | undefined> {
|
|
134
134
|
const envPath = process.env.PUPPETEER_EXECUTABLE_PATH;
|
|
135
135
|
if (envPath) return envPath;
|
|
136
|
-
const sysChrome = resolveSystemChromium();
|
|
136
|
+
const sysChrome = await resolveSystemChromium();
|
|
137
137
|
if (sysChrome) return sysChrome;
|
|
138
138
|
if (chromiumExecutablePromise) return chromiumExecutablePromise;
|
|
139
139
|
|
|
@@ -166,13 +166,13 @@ export async function ensureChromiumExecutable(): Promise<string | undefined> {
|
|
|
166
166
|
buildId,
|
|
167
167
|
cacheDir,
|
|
168
168
|
platform,
|
|
169
|
-
downloadProgressCallback: (
|
|
170
|
-
if (
|
|
171
|
-
const pct = Math.floor((
|
|
172
|
-
if (pct >= lastReportedPercent + 10 ||
|
|
169
|
+
downloadProgressCallback: ({ downloadedBytes, totalBytes }) => {
|
|
170
|
+
if (totalBytes <= 0) return;
|
|
171
|
+
const pct = Math.floor((downloadedBytes / totalBytes) * 100);
|
|
172
|
+
if (pct >= lastReportedPercent + 10 || downloadedBytes === totalBytes) {
|
|
173
173
|
lastReportedPercent = pct;
|
|
174
174
|
logger.debug(
|
|
175
|
-
`Chromium download: ${pct}% (${Math.round(
|
|
175
|
+
`Chromium download: ${pct}% (${Math.round(downloadedBytes / 1_000_000)} / ${Math.round(totalBytes / 1_000_000)} MB)`,
|
|
176
176
|
);
|
|
177
177
|
}
|
|
178
178
|
},
|
|
@@ -193,7 +193,32 @@ let resolvedChromium: string | null | undefined; // undefined = unchecked; null
|
|
|
193
193
|
function isExecutableFile(p: string): boolean {
|
|
194
194
|
try {
|
|
195
195
|
const st = fs.statSync(p);
|
|
196
|
-
|
|
196
|
+
if (!st.isFile()) return false;
|
|
197
|
+
if (process.platform === "win32") return true;
|
|
198
|
+
fs.accessSync(p, fs.constants.X_OK);
|
|
199
|
+
return true;
|
|
200
|
+
} catch {
|
|
201
|
+
return false;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
async function isChromiumExecutable(p: string): Promise<boolean> {
|
|
206
|
+
if (!isExecutableFile(p)) return false;
|
|
207
|
+
try {
|
|
208
|
+
const probeTimeoutMs = 3000;
|
|
209
|
+
const proc = Bun.spawn([p, "--version"], {
|
|
210
|
+
stdout: "pipe",
|
|
211
|
+
stderr: "ignore",
|
|
212
|
+
signal: AbortSignal.timeout(probeTimeoutMs),
|
|
213
|
+
killSignal: "SIGKILL",
|
|
214
|
+
});
|
|
215
|
+
const stdout = await Promise.race([
|
|
216
|
+
new Response(proc.stdout).text(),
|
|
217
|
+
Bun.sleep(probeTimeoutMs + 500).then(() => null),
|
|
218
|
+
]);
|
|
219
|
+
if (stdout === null) return false;
|
|
220
|
+
await proc.exited;
|
|
221
|
+
return proc.exitCode === 0 && /Chrom|Edg/i.test(stdout);
|
|
197
222
|
} catch {
|
|
198
223
|
return false;
|
|
199
224
|
}
|
|
@@ -278,13 +303,13 @@ function systemChromiumCandidates(
|
|
|
278
303
|
return candidates;
|
|
279
304
|
}
|
|
280
305
|
|
|
281
|
-
function resolveSystemChromium(): string | undefined {
|
|
306
|
+
async function resolveSystemChromium(): Promise<string | undefined> {
|
|
282
307
|
if (resolvedChromium !== undefined) return resolvedChromium ?? undefined;
|
|
283
308
|
const seen = new Set<string>();
|
|
284
309
|
for (const candidate of systemChromiumCandidates()) {
|
|
285
310
|
if (!candidate || seen.has(candidate)) continue;
|
|
286
311
|
seen.add(candidate);
|
|
287
|
-
if (
|
|
312
|
+
if (await isChromiumExecutable(candidate)) {
|
|
288
313
|
resolvedChromium = candidate;
|
|
289
314
|
logger.debug("Using system Chrome/Chromium", { path: candidate });
|
|
290
315
|
return candidate;
|
|
@@ -889,11 +914,15 @@ export async function applyStealthPatches(
|
|
|
889
914
|
export function systemChromiumCandidatesForTest(
|
|
890
915
|
platform: NodeJS.Platform = process.platform,
|
|
891
916
|
home?: string,
|
|
892
|
-
which?: (name: string) => string | undefined,
|
|
917
|
+
which?: (name: string) => string | null | undefined,
|
|
893
918
|
): string[] {
|
|
894
919
|
return systemChromiumCandidates(platform, home, which);
|
|
895
920
|
}
|
|
896
921
|
|
|
922
|
+
export async function chromiumExecutableProbeForTest(executablePath: string): Promise<boolean> {
|
|
923
|
+
return isChromiumExecutable(executablePath);
|
|
924
|
+
}
|
|
925
|
+
|
|
897
926
|
export function stealthIgnoreDefaultArgsForTest(executablePath: string | undefined): string[] {
|
|
898
927
|
return stealthIgnoreDefaultArgs(executablePath);
|
|
899
928
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type * as
|
|
2
|
-
import type * as
|
|
1
|
+
import type * as DomNs from "@oh-my-pi/pi-utils/dom";
|
|
2
|
+
import type * as ReadabilityNs from "@oh-my-pi/pi-utils/readability";
|
|
3
3
|
import { htmlToBasicMarkdown } from "../../web/scrapers/types";
|
|
4
4
|
|
|
5
5
|
export type ReadableFormat = "text" | "markdown";
|
|
@@ -23,17 +23,17 @@ function normalize(text: string | null | undefined): string | undefined {
|
|
|
23
23
|
let readabilityModule: typeof ReadabilityNs | undefined;
|
|
24
24
|
async function loadReadability(): Promise<typeof ReadabilityNs> {
|
|
25
25
|
if (!readabilityModule) {
|
|
26
|
-
readabilityModule = await import("@
|
|
26
|
+
readabilityModule = await import("@oh-my-pi/pi-utils/readability");
|
|
27
27
|
}
|
|
28
28
|
return readabilityModule;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
let
|
|
32
|
-
async function
|
|
33
|
-
if (!
|
|
34
|
-
|
|
31
|
+
let domModule: typeof DomNs | undefined;
|
|
32
|
+
async function loadDom(): Promise<typeof DomNs> {
|
|
33
|
+
if (!domModule) {
|
|
34
|
+
domModule = await import("@oh-my-pi/pi-utils/dom");
|
|
35
35
|
}
|
|
36
|
-
return
|
|
36
|
+
return domModule;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
/**
|
|
@@ -47,7 +47,7 @@ export async function extractReadableFromHtml(
|
|
|
47
47
|
url: string,
|
|
48
48
|
format: ReadableFormat,
|
|
49
49
|
): Promise<ReadableResult | null> {
|
|
50
|
-
const [{ parseHTML }, { Readability }] = await Promise.all([
|
|
50
|
+
const [{ parseHTML }, { Readability }] = await Promise.all([loadDom(), loadReadability()]);
|
|
51
51
|
const { document } = parseHTML(html);
|
|
52
52
|
|
|
53
53
|
// --- Primary: Readability article extraction ---
|
|
@@ -134,6 +134,7 @@ const tabs = new Map<string, TabSession>();
|
|
|
134
134
|
// awaits) cannot interleave and leak a worker + browser refCount.
|
|
135
135
|
const acquireChains = new Map<string, Promise<void>>();
|
|
136
136
|
const GRACE_MS = 750;
|
|
137
|
+
const WORKER_INIT_TIMEOUT_MS = 15_000;
|
|
137
138
|
// Names of tabs the supervisor force-killed (timeout past grace, failed recycle),
|
|
138
139
|
// mapped to the kill reason. Lets the next `run` on that name explain WHY the tab
|
|
139
140
|
// vanished instead of a bare "not alive". Cleared when the name is opened again.
|
|
@@ -272,7 +273,11 @@ async function acquireTabImpl(
|
|
|
272
273
|
}
|
|
273
274
|
let info: ReadyInfo;
|
|
274
275
|
try {
|
|
275
|
-
info = await initializeTabWorker(
|
|
276
|
+
info = await initializeTabWorker(
|
|
277
|
+
worker,
|
|
278
|
+
initPayload,
|
|
279
|
+
Math.max(WORKER_INIT_TIMEOUT_MS, opts.timeoutMs + GRACE_MS),
|
|
280
|
+
);
|
|
276
281
|
} catch (error) {
|
|
277
282
|
// `BuildMessage`-class failures arrive asynchronously via the worker's `error` event,
|
|
278
283
|
// after `spawnTabWorker`'s synchronous try/catch has already returned. Fall back to
|
|
@@ -287,7 +292,11 @@ async function acquireTabImpl(
|
|
|
287
292
|
});
|
|
288
293
|
worker = await spawnInlineWorker();
|
|
289
294
|
try {
|
|
290
|
-
info = await initializeTabWorker(
|
|
295
|
+
info = await initializeTabWorker(
|
|
296
|
+
worker,
|
|
297
|
+
initPayload,
|
|
298
|
+
Math.max(WORKER_INIT_TIMEOUT_MS, opts.timeoutMs + GRACE_MS),
|
|
299
|
+
);
|
|
291
300
|
} catch (inlineError) {
|
|
292
301
|
await worker.terminate().catch(() => undefined);
|
|
293
302
|
if (tempHold || browser.refCount === 0) await releaseBrowser(browser, { kill: false });
|
|
@@ -1014,7 +1023,7 @@ async function spawnInlineWorker(): Promise<WorkerHandle> {
|
|
|
1014
1023
|
close: () => {},
|
|
1015
1024
|
};
|
|
1016
1025
|
const { WorkerCore } = await import("./tab-worker");
|
|
1017
|
-
new WorkerCore(workerTransport);
|
|
1026
|
+
new WorkerCore(workerTransport, false);
|
|
1018
1027
|
return {
|
|
1019
1028
|
mode: "inline",
|
|
1020
1029
|
send: msg =>
|