@oh-my-pi/pi-coding-agent 17.2.8 → 17.2.10
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 +94 -1
- package/dist/{CHANGELOG-0b0w17se.md → CHANGELOG-rhwzpexa.md} +94 -1
- package/dist/cli.js +13216 -15614
- package/dist/types/async/job-manager.d.ts +12 -0
- package/dist/types/cli/args.d.ts +1 -0
- package/dist/types/config/model-resolver.d.ts +13 -0
- package/dist/types/discovery/agents.d.ts +11 -0
- package/dist/types/extensibility/custom-commands/types.d.ts +5 -5
- package/dist/types/extensibility/custom-tools/types.d.ts +1 -1
- package/dist/types/extensibility/extensions/loader.d.ts +2 -2
- package/dist/types/extensibility/extensions/types.d.ts +34 -8
- package/dist/types/extensibility/hooks/types.d.ts +5 -5
- package/dist/types/extensibility/legacy-pi-coding-agent-shim.d.ts +10 -0
- package/dist/types/hindsight/state.d.ts +0 -1
- package/dist/types/index.d.ts +2 -2
- package/dist/types/lsp/utils.d.ts +8 -0
- package/dist/types/main.d.ts +1 -1
- 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 +12 -5
- package/dist/types/modes/components/model-browser.d.ts +9 -1
- package/dist/types/modes/interactive-mode.d.ts +8 -1
- package/dist/types/modes/session-observer-registry.d.ts +2 -0
- package/dist/types/registry/agent-lifecycle.d.ts +1 -1
- package/dist/types/registry/agent-registry.d.ts +47 -0
- package/dist/types/registry/persisted-agents.d.ts +3 -1
- package/dist/types/session/agent-session-types.d.ts +7 -0
- package/dist/types/session/agent-session.d.ts +8 -4
- 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 +10 -1
- package/dist/types/session/turn-recovery.d.ts +6 -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 +10 -0
- package/dist/types/task/index.d.ts +2 -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 +3 -0
- package/dist/types/tools/browser/tab-worker.d.ts +2 -2
- package/dist/types/tools/path-utils.d.ts +8 -0
- package/dist/types/tools/read.d.ts +3 -5
- package/dist/types/tools/run-scope.d.ts +27 -1
- package/dist/types/tools/shell-tokenize.d.ts +21 -3
- package/dist/types/tools/terminal-output.d.ts +1 -1
- package/dist/types/utils/changelog.d.ts +3 -2
- package/dist/types/utils/late-cleanup.d.ts +2 -0
- package/dist/types/utils/turndown.d.ts +1 -1
- package/dist/types/vibe/runtime.d.ts +4 -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/omp +11 -2
- package/src/advisor/runtime.ts +54 -0
- package/src/async/job-manager.ts +26 -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/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/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 +4 -4
- package/src/cli/tiny-models-cli.ts +1 -1
- package/src/cli/ttsr-cli.ts +1 -1
- package/src/cli/update-cli.ts +3 -57
- 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/commands/say.ts +1 -1
- package/src/commands/token.ts +1 -1
- package/src/commit/agentic/agent.ts +1 -1
- package/src/commit/cli.ts +1 -1
- package/src/config/config-file.ts +2 -2
- package/src/config/keybindings.ts +3 -3
- package/src/config/model-discovery.ts +36 -2
- package/src/config/model-registry.ts +4 -1
- package/src/config/model-resolver.ts +54 -7
- package/src/discovery/agents.ts +73 -3
- 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.ts +8 -5
- package/src/discovery/cursor.ts +8 -5
- package/src/discovery/gemini.ts +11 -8
- package/src/discovery/vscode.ts +1 -0
- package/src/discovery/windsurf.ts +4 -2
- package/src/edit/hashline/filesystem.ts +7 -7
- package/src/eval/agent-bridge.ts +1 -3
- package/src/eval/jl/prelude.jl +4 -4
- package/src/eval/js/shared/prelude.txt +2 -2
- package/src/eval/py/prelude.py +0 -3
- package/src/eval/py/runner.py +38 -1
- package/src/eval/rb/prelude.rb +1 -2
- 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 +1 -1
- package/src/extensibility/extensions/loader.ts +7 -6
- package/src/extensibility/extensions/types.ts +39 -8
- package/src/extensibility/hooks/loader.ts +4 -5
- package/src/extensibility/hooks/types.ts +5 -5
- package/src/extensibility/legacy-pi-coding-agent-shim.ts +23 -0
- package/src/extensibility/plugins/legacy-pi-compat.ts +53 -0
- package/src/hindsight/state.ts +2 -22
- package/src/index.ts +2 -2
- package/src/internal-urls/memory-protocol.ts +33 -0
- package/src/launch/terminal-output.ts +1 -1
- package/src/lsp/client.ts +2 -2
- package/src/lsp/utils.ts +29 -0
- package/src/main.ts +85 -21
- 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/modes/acp/acp-agent.ts +7 -6
- 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-hub-projection.ts +248 -0
- package/src/modes/components/agent-hub-renderer.ts +194 -0
- package/src/modes/components/agent-hub.ts +670 -192
- package/src/modes/components/agent-transcript-viewer.ts +1 -3
- package/src/modes/components/assistant-message.ts +1 -1
- package/src/modes/components/bordered-loader.ts +1 -1
- package/src/modes/components/custom-editor.ts +2 -2
- package/src/modes/components/extensions/inspector-panel.ts +11 -6
- package/src/modes/components/footer.ts +1 -3
- package/src/modes/components/hook-input.ts +1 -1
- package/src/modes/components/hook-selector.ts +2 -2
- package/src/modes/components/model-browser.ts +11 -3
- package/src/modes/components/model-hub.ts +4 -1
- package/src/modes/components/session-selector.ts +2 -2
- package/src/modes/components/status-line/segments.ts +4 -3
- package/src/modes/controllers/event-controller.ts +188 -7
- package/src/modes/controllers/extension-ui-controller.ts +4 -3
- package/src/modes/controllers/mcp-command-controller.ts +81 -20
- package/src/modes/controllers/selector-controller.ts +40 -49
- package/src/modes/controllers/streaming-reveal.ts +1 -1
- package/src/modes/interactive-mode.ts +13 -2
- package/src/modes/print-mode.ts +29 -12
- package/src/modes/rpc/rpc-mode.ts +2 -2
- package/src/modes/runtime-init.ts +1 -1
- package/src/modes/session-observer-registry.ts +5 -0
- package/src/modes/theme/theme.ts +2 -2
- package/src/prompts/tools/computer.md +1 -1
- package/src/registry/agent-lifecycle.ts +35 -10
- package/src/registry/agent-registry.ts +65 -2
- package/src/registry/persisted-agents.ts +341 -16
- package/src/sdk.ts +11 -5
- package/src/session/agent-session-types.ts +8 -0
- package/src/session/agent-session.ts +177 -188
- package/src/session/session-entries.ts +10 -0
- 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 +105 -110
- package/src/session/session-tools.ts +28 -1
- package/src/session/settings-stream-fn.ts +7 -3
- package/src/session/turn-recovery.ts +236 -18
- package/src/session/unexpected-stop-classifier.ts +12 -3
- package/src/slash-commands/acp-builtins.ts +1 -1
- package/src/slash-commands/available-commands.ts +1 -1
- package/src/slash-commands/helpers/stats-dashboard.ts +2 -1
- package/src/task/agents.ts +1 -1
- package/src/task/executor.ts +173 -47
- package/src/task/index.ts +9 -28
- package/src/task/isolation-runner.ts +43 -12
- package/src/task/persisted-revive.ts +12 -5
- package/src/task/read-only-policy.ts +27 -0
- package/src/task/structured-subagent.ts +31 -6
- package/src/task/types.ts +4 -0
- package/src/tiny/worker.ts +1 -1
- package/src/tools/auto-generated-guard.ts +1 -1
- package/src/tools/bash-interactive.ts +4 -4
- package/src/tools/bash-interceptor.ts +8 -4
- package/src/tools/bash.ts +6 -6
- package/src/tools/browser/cmux/cmux-tab.ts +66 -18
- package/src/tools/browser/launch.ts +76 -19
- 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/fetch.ts +1 -1
- package/src/tools/glob.ts +4 -2
- package/src/tools/path-utils.ts +22 -5
- package/src/tools/read.ts +22 -5
- package/src/tools/run-scope.ts +290 -4
- package/src/tools/shell-tokenize.ts +38 -9
- package/src/tools/terminal-output.ts +1 -1
- package/src/tools/todo.ts +1 -1
- package/src/utils/changelog.ts +5 -4
- 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/late-cleanup.ts +17 -0
- package/src/utils/turndown.ts +1 -2
- package/src/vibe/runtime.ts +202 -58
- package/src/web/scrapers/arxiv.ts +1 -1
- package/src/web/scrapers/go-pkg.ts +1 -1
- package/src/web/scrapers/hackage.ts +1 -12
- 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/duckduckgo.ts +1 -1
- package/src/web/search/providers/ecosia.ts +2 -2
- package/src/web/search/providers/exa.ts +1 -1
- package/src/web/search/providers/firecrawl.ts +1 -1
- package/src/web/search/providers/google.ts +1 -1
- package/src/web/search/providers/mojeek.ts +2 -2
- package/src/web/search/providers/perplexity.ts +1 -1
- package/src/web/search/providers/public.ts +1 -1
- package/src/web/search/providers/startpage.ts +2 -2
- package/src/markit/converters/mammoth.d.ts +0 -24
package/src/tools/read.ts
CHANGED
|
@@ -26,7 +26,7 @@ import {
|
|
|
26
26
|
readImageMetadata,
|
|
27
27
|
untilAborted,
|
|
28
28
|
} from "@oh-my-pi/pi-utils";
|
|
29
|
-
import { LRUCache } from "
|
|
29
|
+
import { LRUCache } from "@oh-my-pi/pi-utils/lru";
|
|
30
30
|
import {
|
|
31
31
|
canonicalSnapshotKey,
|
|
32
32
|
getFileSnapshotStore,
|
|
@@ -721,6 +721,10 @@ const readSchema = type({
|
|
|
721
721
|
),
|
|
722
722
|
});
|
|
723
723
|
|
|
724
|
+
const readSchemaWithoutMemory = type({
|
|
725
|
+
path: type("string").describe("Local path, internal URI (e.g. skill://), or URL. Inline selectors are supported."),
|
|
726
|
+
});
|
|
727
|
+
|
|
724
728
|
export type ReadToolInput = typeof readSchema.infer;
|
|
725
729
|
|
|
726
730
|
export interface ReadToolDetails {
|
|
@@ -737,6 +741,8 @@ export interface ReadToolDetails {
|
|
|
737
741
|
meta?: OutputMeta;
|
|
738
742
|
/** Full on-disk byte size recorded before applying a file range. */
|
|
739
743
|
fileSize?: number;
|
|
744
|
+
/** Full source line count when the read reached EOF and the count is exact. */
|
|
745
|
+
totalLines?: number;
|
|
740
746
|
/** Raw text + start line for user-visible TUI rendering, set when content is text-like.
|
|
741
747
|
* Mirrors the same lines the model receives but without hashline/line-number prefixes,
|
|
742
748
|
* so the TUI can render the file content with its own gutter without re-parsing the formatted text. */
|
|
@@ -864,7 +870,9 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
|
|
|
864
870
|
readonly label = "Read";
|
|
865
871
|
readonly loadMode = "essential";
|
|
866
872
|
description: string;
|
|
867
|
-
|
|
873
|
+
get parameters(): typeof readSchema {
|
|
874
|
+
return this.session.settings.get("memory.backend") === "off" ? readSchemaWithoutMemory : readSchema;
|
|
875
|
+
}
|
|
868
876
|
readonly strict = true;
|
|
869
877
|
|
|
870
878
|
readonly #autoResizeImages: boolean;
|
|
@@ -960,8 +968,9 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
|
|
|
960
968
|
async #tryReadDelimitedPaths(
|
|
961
969
|
readPath: string,
|
|
962
970
|
signal?: AbortSignal,
|
|
971
|
+
routedUrlPredicate?: (entry: string) => boolean,
|
|
963
972
|
): Promise<AgentToolResult<ReadToolDetails> | null> {
|
|
964
|
-
const parts = await splitDelimitedPathEntry(readPath, this.session.cwd);
|
|
973
|
+
const parts = await splitDelimitedPathEntry(readPath, this.session.cwd, { routedUrlPredicate });
|
|
965
974
|
if (!parts) return null;
|
|
966
975
|
|
|
967
976
|
const notice = `Note: interpreted as ${parts.length} paths: ${parts.join(", ")}`;
|
|
@@ -1397,6 +1406,7 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
|
|
|
1397
1406
|
const details = options.details ?? {};
|
|
1398
1407
|
const allLines = text.split("\n");
|
|
1399
1408
|
const totalLines = allLines.length;
|
|
1409
|
+
details.totalLines = totalLines;
|
|
1400
1410
|
// User-requested 0-indexed range start. Lines BEFORE this are leading
|
|
1401
1411
|
// context (added below if offset is explicit).
|
|
1402
1412
|
const requestedStart = offset ? Math.max(0, offset - 1) : 0;
|
|
@@ -1592,6 +1602,7 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
|
|
|
1592
1602
|
const details = options.details ?? {};
|
|
1593
1603
|
const allLines = text.split("\n");
|
|
1594
1604
|
const totalLines = allLines.length;
|
|
1605
|
+
details.totalLines = totalLines;
|
|
1595
1606
|
const shouldAddHashLines = displayMode.hashLines;
|
|
1596
1607
|
const shouldAddLineNumbers = shouldAddHashLines ? false : displayMode.lineNumbers;
|
|
1597
1608
|
const hashContext =
|
|
@@ -2302,9 +2313,13 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
|
|
|
2302
2313
|
}
|
|
2303
2314
|
|
|
2304
2315
|
// Handle native OMP URLs and custom-scheme resources advertised by MCP servers.
|
|
2305
|
-
// Use the internal-URL-aware splitter so malformed selectors are peeled
|
|
2306
|
-
// off the URL and surfaced via parseSel rather than confusing handlers.
|
|
2307
2316
|
const internalRouter = InternalUrlRouter.instance();
|
|
2317
|
+
const delimitedInternalResult = internalRouter.canResolve(readPath)
|
|
2318
|
+
? await this.#tryReadDelimitedPaths(readPath, signal, entry => internalRouter.canResolve(entry))
|
|
2319
|
+
: null;
|
|
2320
|
+
if (delimitedInternalResult) return delimitedInternalResult;
|
|
2321
|
+
|
|
2322
|
+
// Peel malformed selectors through the internal-URL-aware parser before routing.
|
|
2308
2323
|
let promotedSelector: string | undefined;
|
|
2309
2324
|
if (internalRouter.canResolve(readPath)) {
|
|
2310
2325
|
const internalTarget = splitInternalUrlSel(readPath);
|
|
@@ -2908,6 +2923,7 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
|
|
|
2908
2923
|
details = {};
|
|
2909
2924
|
sourcePath = absolutePath;
|
|
2910
2925
|
}
|
|
2926
|
+
if (reachedEof) details.totalLines = totalFileLines;
|
|
2911
2927
|
|
|
2912
2928
|
if (hashContext?.tag) {
|
|
2913
2929
|
recordSeenLinesFromBody(this.session, absolutePath, hashContext.tag, outputText);
|
|
@@ -3239,6 +3255,7 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
|
|
|
3239
3255
|
if (!rawSelector && artifact.size > MAX_ARTIFACT_RAW_INLINE_BYTES) {
|
|
3240
3256
|
outputText += `\n\n[${this.#formatArtifactWorkflowNotice(artifact, artifactUrl)}]`;
|
|
3241
3257
|
}
|
|
3258
|
+
if (reachedEof) details.totalLines = totalFileLines;
|
|
3242
3259
|
if (displayContent) details.displayContent = displayContent;
|
|
3243
3260
|
if (truncationInfo) details.truncation = truncationInfo.result;
|
|
3244
3261
|
const resultBuilder = toolResult<ReadToolDetails>(details)
|
package/src/tools/run-scope.ts
CHANGED
|
@@ -1,6 +1,285 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
1
2
|
import { untilAborted } from "@oh-my-pi/pi-utils/abortable";
|
|
3
|
+
import * as postmortem from "@oh-my-pi/pi-utils/postmortem";
|
|
2
4
|
import { ToolError, throwIfAborted } from "./tool-errors";
|
|
3
5
|
|
|
6
|
+
const browserRunRejections = new WeakMap<object, object>();
|
|
7
|
+
|
|
8
|
+
/** Associates a browser operation failure with its owning evaluated run. */
|
|
9
|
+
export function markBrowserRunRejection<T>(reason: T, owner: object): T {
|
|
10
|
+
if (reason !== null && (typeof reason === "object" || typeof reason === "function")) {
|
|
11
|
+
browserRunRejections.set(reason, owner);
|
|
12
|
+
}
|
|
13
|
+
return reason;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Returns whether a rejection was marked for the specified evaluated run. */
|
|
17
|
+
export function isBrowserRunRejection(reason: unknown, owner: object): boolean {
|
|
18
|
+
return (
|
|
19
|
+
reason !== null &&
|
|
20
|
+
(typeof reason === "object" || typeof reason === "function") &&
|
|
21
|
+
browserRunRejections.get(reason) === owner
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Returns whether a rejection belongs to the marked browser run or its evaluated source file. */
|
|
26
|
+
export function isBrowserRunOwnedRejection(reason: unknown, owner: object, filename: string): boolean {
|
|
27
|
+
if (isBrowserRunRejection(reason, owner)) return true;
|
|
28
|
+
return reason instanceof Error && typeof reason.stack === "string" && reason.stack.includes(filename);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
type FloatingRejectionHandler = (reason: unknown) => void;
|
|
32
|
+
|
|
33
|
+
interface ObservedPromiseState {
|
|
34
|
+
handled: boolean;
|
|
35
|
+
userContinuationFailed: boolean;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const observedBrowserPromises = new WeakMap<Promise<unknown>, ObservedPromiseState>();
|
|
39
|
+
const observedPromiseConstructor = { [Symbol.species]: Promise };
|
|
40
|
+
|
|
41
|
+
type PromiseCombinatorName = "all" | "race";
|
|
42
|
+
type PromiseCombinator = (this: PromiseConstructor, values: Iterable<unknown>) => Promise<unknown>;
|
|
43
|
+
|
|
44
|
+
interface PromiseCombinatorTrackingContext {
|
|
45
|
+
owner: object;
|
|
46
|
+
onFloatingRejection: FloatingRejectionHandler;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const PROMISE_COMBINATORS: readonly PromiseCombinatorName[] = ["all", "race"];
|
|
50
|
+
const NativePromise = Promise;
|
|
51
|
+
const nativePromiseCombinators: Record<PromiseCombinatorName, PromiseCombinator> = {
|
|
52
|
+
all: Promise.all,
|
|
53
|
+
race: Promise.race,
|
|
54
|
+
};
|
|
55
|
+
const promiseCombinatorTracking = new AsyncLocalStorage<PromiseCombinatorTrackingContext>();
|
|
56
|
+
let previousPromiseDescriptor: PropertyDescriptor | undefined;
|
|
57
|
+
let promiseCombinatorTrackingScopes = 0;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Observes native promise-combinator results derived from browser promises for
|
|
61
|
+
* the duration of one evaluated run. Native `await` remains unchanged; dropped
|
|
62
|
+
* user continuations from `Promise.all` and `Promise.race` are routed to the
|
|
63
|
+
* owning run.
|
|
64
|
+
*/
|
|
65
|
+
export async function withBrowserPromiseCombinatorTracking<T>(
|
|
66
|
+
owner: object,
|
|
67
|
+
onFloatingRejection: FloatingRejectionHandler,
|
|
68
|
+
run: () => Promise<T>,
|
|
69
|
+
): Promise<T> {
|
|
70
|
+
installPromiseCombinatorTracking();
|
|
71
|
+
try {
|
|
72
|
+
return await promiseCombinatorTracking.run({ owner, onFloatingRejection }, run);
|
|
73
|
+
} finally {
|
|
74
|
+
restorePromiseCombinatorTracking();
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function installPromiseCombinatorTracking(): void {
|
|
79
|
+
if (promiseCombinatorTrackingScopes > 0) {
|
|
80
|
+
promiseCombinatorTrackingScopes++;
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
const descriptor = Object.getOwnPropertyDescriptor(globalThis, "Promise");
|
|
84
|
+
if (!descriptor) throw new Error("Global Promise descriptor is unavailable");
|
|
85
|
+
const trackedPromise = createTrackedPromiseConstructor();
|
|
86
|
+
Object.defineProperty(globalThis, "Promise", { ...descriptor, value: trackedPromise });
|
|
87
|
+
previousPromiseDescriptor = descriptor;
|
|
88
|
+
promiseCombinatorTrackingScopes = 1;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function restorePromiseCombinatorTracking(): void {
|
|
92
|
+
if (promiseCombinatorTrackingScopes > 1) {
|
|
93
|
+
promiseCombinatorTrackingScopes--;
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
const descriptor = previousPromiseDescriptor;
|
|
97
|
+
try {
|
|
98
|
+
if (!descriptor) throw new Error("Global Promise tracking scope is not installed");
|
|
99
|
+
Object.defineProperty(globalThis, "Promise", descriptor);
|
|
100
|
+
} finally {
|
|
101
|
+
previousPromiseDescriptor = undefined;
|
|
102
|
+
promiseCombinatorTrackingScopes = 0;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function createTrackedPromiseConstructor(): PromiseConstructor {
|
|
107
|
+
class TrackedPromise<T> extends NativePromise<T> {}
|
|
108
|
+
for (const name of PROMISE_COMBINATORS) {
|
|
109
|
+
const original = nativePromiseCombinators[name];
|
|
110
|
+
Object.defineProperty(TrackedPromise, name, {
|
|
111
|
+
configurable: true,
|
|
112
|
+
writable: true,
|
|
113
|
+
value(this: PromiseConstructor, values: Iterable<unknown>): Promise<unknown> {
|
|
114
|
+
let hasObservedInput = false;
|
|
115
|
+
const result = Reflect.apply(original, this, [
|
|
116
|
+
tapObservedBrowserPromises(values, () => {
|
|
117
|
+
hasObservedInput = true;
|
|
118
|
+
}),
|
|
119
|
+
]) as Promise<unknown>;
|
|
120
|
+
const context = promiseCombinatorTracking.getStore();
|
|
121
|
+
return hasObservedInput && context
|
|
122
|
+
? observeBrowserRunPromise(result, context.owner, context.onFloatingRejection)
|
|
123
|
+
: result;
|
|
124
|
+
},
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
return TrackedPromise;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function* tapObservedBrowserPromises(
|
|
131
|
+
values: Iterable<unknown>,
|
|
132
|
+
onObserved: () => void,
|
|
133
|
+
): Generator<unknown, void, undefined> {
|
|
134
|
+
for (const value of values) {
|
|
135
|
+
if (observedBrowserPromises.has(value as Promise<unknown>)) onObserved();
|
|
136
|
+
yield value;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Observes every explicit continuation of a browser promise without replacing
|
|
142
|
+
* the native promise. Browser failures remain contained; an unhandled error
|
|
143
|
+
* created by user continuation code is reported to the owning run.
|
|
144
|
+
*/
|
|
145
|
+
export function observeBrowserRunPromise<T>(
|
|
146
|
+
promise: Promise<T>,
|
|
147
|
+
owner: object,
|
|
148
|
+
onFloatingRejection: FloatingRejectionHandler,
|
|
149
|
+
): Promise<T> {
|
|
150
|
+
return observeBrowserRunPromiseWithState(promise, owner, onFloatingRejection, {
|
|
151
|
+
handled: false,
|
|
152
|
+
userContinuationFailed: false,
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function observeBrowserRunPromiseWithState<T>(
|
|
157
|
+
promise: Promise<T>,
|
|
158
|
+
owner: object,
|
|
159
|
+
onFloatingRejection: FloatingRejectionHandler,
|
|
160
|
+
state: ObservedPromiseState,
|
|
161
|
+
): Promise<T> {
|
|
162
|
+
if (observedBrowserPromises.has(promise)) return promise;
|
|
163
|
+
observedBrowserPromises.set(promise, state);
|
|
164
|
+
const originalThen = promise.then.bind(promise);
|
|
165
|
+
const originalFinally = promise.finally.bind(promise);
|
|
166
|
+
void originalThen(undefined, reason => {
|
|
167
|
+
setTimeout(() => {
|
|
168
|
+
if (!state.handled && (state.userContinuationFailed || !isBrowserRunRejection(reason, owner))) {
|
|
169
|
+
onFloatingRejection(reason);
|
|
170
|
+
}
|
|
171
|
+
}, 0);
|
|
172
|
+
});
|
|
173
|
+
Object.defineProperties(promise, {
|
|
174
|
+
constructor: { configurable: true, value: observedPromiseConstructor },
|
|
175
|
+
// biome-ignore lint/suspicious/noThenProperty: native Promise continuations must remain thenable.
|
|
176
|
+
then: {
|
|
177
|
+
configurable: true,
|
|
178
|
+
value: <TResult1 = T, TResult2 = never>(
|
|
179
|
+
onFulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | null,
|
|
180
|
+
onRejected?: ((reason: unknown) => TResult2 | PromiseLike<TResult2>) | null,
|
|
181
|
+
): Promise<TResult1 | TResult2> => {
|
|
182
|
+
state.handled = true;
|
|
183
|
+
const childState = createContinuationState();
|
|
184
|
+
return observeBrowserRunPromiseWithState(
|
|
185
|
+
originalThen(
|
|
186
|
+
recordContinuationFailure(onFulfilled, childState),
|
|
187
|
+
recordContinuationFailure(onRejected, childState),
|
|
188
|
+
),
|
|
189
|
+
owner,
|
|
190
|
+
onFloatingRejection,
|
|
191
|
+
childState,
|
|
192
|
+
);
|
|
193
|
+
},
|
|
194
|
+
},
|
|
195
|
+
catch: {
|
|
196
|
+
configurable: true,
|
|
197
|
+
value: <TResult = never>(
|
|
198
|
+
onRejected?: ((reason: unknown) => TResult | PromiseLike<TResult>) | null,
|
|
199
|
+
): Promise<T | TResult> => {
|
|
200
|
+
state.handled = true;
|
|
201
|
+
const childState = createContinuationState();
|
|
202
|
+
return observeBrowserRunPromiseWithState(
|
|
203
|
+
originalThen(undefined, recordContinuationFailure(onRejected, childState)),
|
|
204
|
+
owner,
|
|
205
|
+
onFloatingRejection,
|
|
206
|
+
childState,
|
|
207
|
+
);
|
|
208
|
+
},
|
|
209
|
+
},
|
|
210
|
+
finally: {
|
|
211
|
+
configurable: true,
|
|
212
|
+
value: (onFinally?: (() => void) | null): Promise<T> => {
|
|
213
|
+
state.handled = true;
|
|
214
|
+
const childState = createContinuationState();
|
|
215
|
+
return observeBrowserRunPromiseWithState(
|
|
216
|
+
originalFinally(recordContinuationFailure(onFinally, childState)),
|
|
217
|
+
owner,
|
|
218
|
+
onFloatingRejection,
|
|
219
|
+
childState,
|
|
220
|
+
);
|
|
221
|
+
},
|
|
222
|
+
},
|
|
223
|
+
});
|
|
224
|
+
return promise;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function createContinuationState(): ObservedPromiseState {
|
|
228
|
+
return { handled: false, userContinuationFailed: false };
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function recordContinuationFailure<TArgs extends unknown[], TResult>(
|
|
232
|
+
continuation: ((...args: TArgs) => TResult | PromiseLike<TResult>) | null | undefined,
|
|
233
|
+
state: ObservedPromiseState,
|
|
234
|
+
): ((...args: TArgs) => TResult | PromiseLike<TResult>) | null | undefined {
|
|
235
|
+
if (!continuation) return continuation;
|
|
236
|
+
return (...args) => {
|
|
237
|
+
try {
|
|
238
|
+
const result = continuation(...args);
|
|
239
|
+
if (!isThenable(result)) return result;
|
|
240
|
+
return Promise.resolve(result).catch(reason => {
|
|
241
|
+
state.userContinuationFailed = true;
|
|
242
|
+
throw reason;
|
|
243
|
+
}) as PromiseLike<TResult>;
|
|
244
|
+
} catch (reason) {
|
|
245
|
+
state.userContinuationFailed = true;
|
|
246
|
+
throw reason;
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function isThenable(value: unknown): value is PromiseLike<unknown> {
|
|
252
|
+
if (value === null || (typeof value !== "object" && typeof value !== "function")) return false;
|
|
253
|
+
return typeof Reflect.get(value, "then") === "function";
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function trackBrowserRunPromise<T>(
|
|
257
|
+
promise: Promise<T>,
|
|
258
|
+
owner?: object,
|
|
259
|
+
onFloatingRejection?: FloatingRejectionHandler,
|
|
260
|
+
): Promise<T> {
|
|
261
|
+
if (!owner) return markHandled(promise);
|
|
262
|
+
const tracked = promise.catch(error => {
|
|
263
|
+
throw markBrowserRunRejection(error, owner);
|
|
264
|
+
});
|
|
265
|
+
return onFloatingRejection ? observeBrowserRunPromise(tracked, owner, onFloatingRejection) : tracked;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Installs worker-realm rejection routing. Consumed browser-run failures stay in
|
|
270
|
+
* the worker; unrelated failures retain the default fatal worker behavior.
|
|
271
|
+
*/
|
|
272
|
+
export function installBrowserWorkerRejectionGuard(consume: (reason: unknown) => boolean): () => void {
|
|
273
|
+
const onRejection = (reason: unknown): void => {
|
|
274
|
+
if (postmortem.isExpectedCleanupError(reason) || consume(reason)) return;
|
|
275
|
+
setTimeout(() => {
|
|
276
|
+
throw reason;
|
|
277
|
+
}, 0);
|
|
278
|
+
};
|
|
279
|
+
process.on("unhandledRejection", onRejection);
|
|
280
|
+
return () => process.off("unhandledRejection", onRejection);
|
|
281
|
+
}
|
|
282
|
+
|
|
4
283
|
/**
|
|
5
284
|
* Marks a run-scoped promise as observed without changing its behavior for awaited callers.
|
|
6
285
|
*
|
|
@@ -83,11 +362,16 @@ export function waitForRun(
|
|
|
83
362
|
await untilAborted(signal, async () => await Bun.sleep(interval));
|
|
84
363
|
}
|
|
85
364
|
})();
|
|
86
|
-
return
|
|
365
|
+
return trackBrowserRunPromise(promise);
|
|
87
366
|
}
|
|
88
367
|
|
|
89
368
|
/** Binds a long-lived scope facade (page/tab/desktop objects) to one evaluated run's abort signal. */
|
|
90
|
-
export function bindRunFacade<T extends object>(
|
|
369
|
+
export function bindRunFacade<T extends object>(
|
|
370
|
+
target: T,
|
|
371
|
+
signal: AbortSignal,
|
|
372
|
+
rejectionOwner?: object,
|
|
373
|
+
onFloatingRejection?: FloatingRejectionHandler,
|
|
374
|
+
): T {
|
|
91
375
|
const cache = new Map<PropertyKey, unknown>();
|
|
92
376
|
return new Proxy(target, {
|
|
93
377
|
get(current, prop) {
|
|
@@ -102,11 +386,13 @@ export function bindRunFacade<T extends object>(target: T, signal: AbortSignal):
|
|
|
102
386
|
if (result && typeof result === "object") {
|
|
103
387
|
const then = Reflect.get(result, "then");
|
|
104
388
|
if (typeof then === "function") {
|
|
105
|
-
return
|
|
389
|
+
return trackBrowserRunPromise(
|
|
106
390
|
Promise.resolve(result).then(resolved => {
|
|
107
391
|
throwIfAborted(signal);
|
|
108
392
|
return resolved;
|
|
109
393
|
}),
|
|
394
|
+
rejectionOwner,
|
|
395
|
+
onFloatingRejection,
|
|
110
396
|
);
|
|
111
397
|
}
|
|
112
398
|
}
|
|
@@ -121,7 +407,7 @@ export function bindRunFacade<T extends object>(target: T, signal: AbortSignal):
|
|
|
121
407
|
// brand-check internal slots that a Proxy cannot forward, and reading a
|
|
122
408
|
// signal needs no abort gating anyway.
|
|
123
409
|
if (value instanceof AbortSignal) return value;
|
|
124
|
-
const wrapped = bindRunFacade(value, signal);
|
|
410
|
+
const wrapped = bindRunFacade(value, signal, rejectionOwner, onFloatingRejection);
|
|
125
411
|
cache.set(prop, wrapped);
|
|
126
412
|
return wrapped;
|
|
127
413
|
}
|
|
@@ -83,25 +83,47 @@ export function tokenizeShellSegments(command: string): string[][] {
|
|
|
83
83
|
}
|
|
84
84
|
|
|
85
85
|
/**
|
|
86
|
-
*
|
|
86
|
+
* A flat shell command segment with the context needed to decide interception.
|
|
87
|
+
*
|
|
88
|
+
* @see extractFlatShellCommandSegments
|
|
89
|
+
*/
|
|
90
|
+
export interface FlatShellCommandSegment {
|
|
91
|
+
/** Original segment text with quoting and escaping preserved. */
|
|
92
|
+
text: string;
|
|
93
|
+
/**
|
|
94
|
+
* True when this segment consumes the previous stage's stdout via an
|
|
95
|
+
* unquoted `|` or `|&`. Blank and comment-only continuation lines preserve
|
|
96
|
+
* the pending pipe state. Such a stage reads piped stdin, so path-based
|
|
97
|
+
* dedicated tools (read/grep/glob) cannot replace it. `||`, `;`, `&`, and
|
|
98
|
+
* `&&` start an independent command and leave this false.
|
|
99
|
+
*/
|
|
100
|
+
pipedStdin: boolean;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Returns the flat shell command segments with the original text of each. Unlike
|
|
87
105
|
* `tokenizeShellSegments`, this preserves quoting and escaping so the results
|
|
88
|
-
* are safe to match against user-configured regular expressions
|
|
106
|
+
* are safe to match against user-configured regular expressions, and flags
|
|
107
|
+
* segments that receive piped stdin.
|
|
89
108
|
*
|
|
90
109
|
* The extractor deliberately declines to split syntax whose execution context
|
|
91
110
|
* cannot be determined with this small scanner (heredocs, command substitution,
|
|
92
111
|
* backticks, grouping, and malformed quoting). Callers must still check the
|
|
93
112
|
* complete input in that case.
|
|
94
113
|
*/
|
|
95
|
-
export function extractFlatShellCommandSegments(command: string):
|
|
96
|
-
const segments:
|
|
114
|
+
export function extractFlatShellCommandSegments(command: string): FlatShellCommandSegment[] {
|
|
115
|
+
const segments: FlatShellCommandSegment[] = [];
|
|
97
116
|
let segmentStart = 0;
|
|
98
117
|
let inSingle = false;
|
|
99
118
|
let inDouble = false;
|
|
100
119
|
let atWordStart = true;
|
|
120
|
+
let currentPiped = false;
|
|
101
121
|
|
|
102
|
-
const pushSegment = (end: number) => {
|
|
122
|
+
const pushSegment = (end: number): boolean => {
|
|
103
123
|
const segment = command.slice(segmentStart, end).trim();
|
|
104
|
-
if (segment.length
|
|
124
|
+
if (segment.length === 0) return false;
|
|
125
|
+
segments.push({ text: segment, pipedStdin: currentPiped });
|
|
126
|
+
return true;
|
|
105
127
|
};
|
|
106
128
|
|
|
107
129
|
for (let i = 0; i < command.length; i++) {
|
|
@@ -154,12 +176,14 @@ export function extractFlatShellCommandSegments(command: string): string[] {
|
|
|
154
176
|
return [];
|
|
155
177
|
}
|
|
156
178
|
if (ch === "#" && atWordStart) {
|
|
157
|
-
pushSegment(i);
|
|
179
|
+
const pushed = pushSegment(i);
|
|
158
180
|
const newline = command.indexOf("\n", i + 1);
|
|
159
181
|
if (newline === -1) return segments;
|
|
160
182
|
i = newline;
|
|
161
183
|
segmentStart = newline + 1;
|
|
162
184
|
atWordStart = true;
|
|
185
|
+
// Preserve a pending pipe through a comment-only continuation.
|
|
186
|
+
if (pushed) currentPiped = false;
|
|
163
187
|
continue;
|
|
164
188
|
}
|
|
165
189
|
const isRedirectionOperatorCharacter =
|
|
@@ -169,8 +193,13 @@ export function extractFlatShellCommandSegments(command: string): string[] {
|
|
|
169
193
|
? command[i - 1] === ">" || command[i - 1] === "<" || command[i + 1] === ">"
|
|
170
194
|
: false;
|
|
171
195
|
if ((ch === "\n" || ch === ";" || ch === "|" || ch === "&") && !isRedirectionOperatorCharacter) {
|
|
172
|
-
pushSegment(i);
|
|
173
|
-
|
|
196
|
+
const pushed = pushSegment(i);
|
|
197
|
+
const doubled = (ch === "|" || ch === "&") && command[i + 1] === ch;
|
|
198
|
+
const pipeStderr = ch === "|" && command[i + 1] === "&";
|
|
199
|
+
if (doubled || pipeStderr) i++;
|
|
200
|
+
// `|` and `|&` pipe into the next segment. Blank continuation
|
|
201
|
+
// lines preserve that pending state; all other operators reset it.
|
|
202
|
+
if (pushed || ch !== "\n") currentPiped = ch === "|" && !doubled;
|
|
174
203
|
segmentStart = i + 1;
|
|
175
204
|
atWordStart = true;
|
|
176
205
|
continue;
|
package/src/tools/todo.ts
CHANGED
|
@@ -4,7 +4,7 @@ import type { ToolExample } from "@oh-my-pi/pi-ai";
|
|
|
4
4
|
import type { Component } from "@oh-my-pi/pi-tui";
|
|
5
5
|
import { Text } from "@oh-my-pi/pi-tui";
|
|
6
6
|
import { isRecord, prompt, sanitizeText } from "@oh-my-pi/pi-utils";
|
|
7
|
-
import chalk from "chalk";
|
|
7
|
+
import chalk from "@oh-my-pi/pi-utils/chalk";
|
|
8
8
|
import type { RenderResultOptions } from "../extensibility/custom-tools/types";
|
|
9
9
|
import type { Theme } from "../modes/theme/theme";
|
|
10
10
|
import todoDescription from "../prompts/tools/todo.md" with { type: "text" };
|
package/src/utils/changelog.ts
CHANGED
|
@@ -198,9 +198,10 @@ function parseChangelogContent(content: string): ChangelogEntry[] {
|
|
|
198
198
|
}
|
|
199
199
|
|
|
200
200
|
/**
|
|
201
|
-
* Compare
|
|
201
|
+
* Compare changelog entries by their parsed version parts.
|
|
202
|
+
* Returns: -1 if v1 < v2, 0 if v1 === v2, 1 if v1 > v2
|
|
202
203
|
*/
|
|
203
|
-
export function
|
|
204
|
+
export function compareChangelogEntries(v1: ChangelogEntry, v2: ChangelogEntry): number {
|
|
204
205
|
if (v1.major !== v2.major) return v1.major - v2.major;
|
|
205
206
|
if (v1.minor !== v2.minor) return v1.minor - v2.minor;
|
|
206
207
|
return v1.patch - v2.patch;
|
|
@@ -232,7 +233,7 @@ export function getNewEntries(entries: ChangelogEntry[], lastVersion: string): C
|
|
|
232
233
|
return [];
|
|
233
234
|
}
|
|
234
235
|
|
|
235
|
-
return entries.filter(entry =>
|
|
236
|
+
return entries.filter(entry => compareChangelogEntries(entry, parsedLastVersion) > 0);
|
|
236
237
|
}
|
|
237
238
|
|
|
238
239
|
/**
|
|
@@ -328,7 +329,7 @@ export async function resolveStartupChangelogForDisplay(options: {
|
|
|
328
329
|
}
|
|
329
330
|
if (options.mode === "hidden") {
|
|
330
331
|
const currentVersion = parseChangelogVersion(options.currentVersion);
|
|
331
|
-
if (currentVersion &&
|
|
332
|
+
if (currentVersion && compareChangelogEntries(currentVersion, parsedLastVersion) > 0) {
|
|
332
333
|
await writeLastChangelogVersion(options.currentVersion, options.agentDir);
|
|
333
334
|
}
|
|
334
335
|
return undefined;
|
package/src/utils/clipboard.ts
CHANGED
|
@@ -259,6 +259,14 @@ async function readTextViaPowerShell(): Promise<string | null> {
|
|
|
259
259
|
}
|
|
260
260
|
}
|
|
261
261
|
|
|
262
|
+
async function readTextFromX11Clipboard(): Promise<string> {
|
|
263
|
+
try {
|
|
264
|
+
return await spawnCapture(["xclip", "-selection", "clipboard", "-o"]);
|
|
265
|
+
} catch {
|
|
266
|
+
return await spawnCapture(["xsel", "--clipboard", "--output"]);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
262
270
|
/**
|
|
263
271
|
* Read an image from the system clipboard.
|
|
264
272
|
*
|
|
@@ -340,11 +348,11 @@ export async function readTextFromClipboard(): Promise<string> {
|
|
|
340
348
|
return await spawnCapture(["wl-paste", "--type", "text/plain", "--no-newline"]);
|
|
341
349
|
} catch {
|
|
342
350
|
if (hasX11Display) {
|
|
343
|
-
return await
|
|
351
|
+
return await readTextFromX11Clipboard();
|
|
344
352
|
}
|
|
345
353
|
}
|
|
346
354
|
} else if (hasX11Display) {
|
|
347
|
-
return await
|
|
355
|
+
return await readTextFromX11Clipboard();
|
|
348
356
|
}
|
|
349
357
|
} catch (error) {
|
|
350
358
|
logger.warn("clipboard: failed to read clipboard text", { error: String(error) });
|
|
@@ -53,8 +53,10 @@ export async function openInEditor(
|
|
|
53
53
|
|
|
54
54
|
const [editor, ...editorArgs] = editorCmd.split(" ");
|
|
55
55
|
const stdio = options?.stdio ?? ["inherit", "inherit", "inherit"];
|
|
56
|
-
|
|
57
|
-
|
|
56
|
+
const child =
|
|
57
|
+
process.platform === "win32"
|
|
58
|
+
? spawn(editor, [...editorArgs, tmpFile], { stdio, shell: true })
|
|
59
|
+
: spawn("/bin/sh", ["-c", `${editorCmd} "$1"`, "sh", tmpFile], { stdio });
|
|
58
60
|
const { promise, reject, resolve } = Promise.withResolvers<number>();
|
|
59
61
|
child.once("exit", (code, signal) => resolve(code ?? (signal ? -1 : 0)));
|
|
60
62
|
child.once("error", error => reject(error));
|
package/src/utils/jj.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import { $which } from "@oh-my-pi/pi-utils";
|
|
4
|
-
import { LRUCache } from "
|
|
4
|
+
import { LRUCache } from "@oh-my-pi/pi-utils/lru";
|
|
5
5
|
import { withTimeoutSignal } from "./fetch-timeout";
|
|
6
6
|
import * as git from "./git";
|
|
7
7
|
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { logger } from "@oh-my-pi/pi-utils";
|
|
2
|
+
|
|
3
|
+
const pendingCleanups = new Set<Promise<void>>();
|
|
4
|
+
|
|
5
|
+
/** Keep timed-out cleanup reachable until its resources really settle. */
|
|
6
|
+
export function trackLateCleanup(work: Promise<void>, context: Record<string, unknown>): void {
|
|
7
|
+
let tracked: Promise<void>;
|
|
8
|
+
tracked = work
|
|
9
|
+
.catch(error => {
|
|
10
|
+
logger.warn("Deferred cleanup failed", {
|
|
11
|
+
...context,
|
|
12
|
+
error: error instanceof Error ? error.message : String(error),
|
|
13
|
+
});
|
|
14
|
+
})
|
|
15
|
+
.finally(() => pendingCleanups.delete(tracked));
|
|
16
|
+
pendingCleanups.add(tracked);
|
|
17
|
+
}
|
package/src/utils/turndown.ts
CHANGED