@timurproko/a1 0.1.8-dev.290 → 0.1.8-dev.322
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/README.md +21 -0
- package/bin/guardian.js +4 -3
- package/bin/ui.js +4 -2
- package/bin/update-recovery.js +3 -1
- package/dist/composition/owned-ui.js +13 -1
- package/dist/contracts/owned-ui/index.d.ts +1 -0
- package/dist/contracts/owned-ui/index.js +1 -0
- package/dist/contracts/owned-ui/prompt-history.d.ts +37 -0
- package/dist/contracts/owned-ui/prompt-history.js +23 -0
- package/dist/features/launch/profile-paths.d.ts +2 -0
- package/dist/features/launch/profile-paths.js +11 -0
- package/dist/features/prompt-history/index.d.ts +2 -0
- package/dist/features/prompt-history/index.js +2 -0
- package/dist/features/prompt-history/paths.d.ts +4 -0
- package/dist/features/prompt-history/paths.js +16 -0
- package/dist/features/prompt-history/service.d.ts +24 -0
- package/dist/features/prompt-history/service.js +260 -0
- package/dist/features/prompt-history/store.d.ts +15 -0
- package/dist/features/prompt-history/store.js +166 -0
- package/dist/features/prompt-history/worker.d.ts +9 -0
- package/dist/features/prompt-history/worker.js +42 -0
- package/dist/foundation/launch-context/index.d.ts +29 -0
- package/dist/foundation/launch-context/index.js +83 -0
- package/dist/foundation/launch-guardian/main.js +4 -3
- package/dist/foundation/release/bootstrap.d.ts +1 -1
- package/dist/foundation/release/bootstrap.js +18 -12
- package/dist/foundation/release/cohort-state.d.ts +1 -0
- package/dist/foundation/release/cohort-state.js +4 -0
- package/dist/foundation/release/release-gc.js +2 -2
- package/dist/foundation/release/release-store.js +10 -3
- package/dist/foundation/release/release.d.ts +3 -1
- package/dist/foundation/release/release.js +7 -3
- package/dist/foundation/release/restart-certification.d.ts +1 -0
- package/dist/foundation/release/restart-certification.js +5 -0
- package/dist/foundation/release/update-recovery.d.ts +2 -0
- package/dist/foundation/release/update-recovery.js +4 -0
- package/dist/foundation/release/update.js +5 -0
- package/dist/foundation/release/warmup.d.ts +1 -1
- package/dist/foundation/release/warmup.js +5 -4
- package/dist/foundation/startup/startup-runtime.js +13 -11
- package/dist/foundation/supervision/main.js +8 -7
- package/dist/integrations/pi/components/editor-interaction.d.ts +38 -0
- package/dist/integrations/pi/components/editor-interaction.js +1 -0
- package/dist/integrations/pi/components/history-editor-loader.d.ts +5 -0
- package/dist/integrations/pi/components/history-editor-loader.js +5 -0
- package/dist/integrations/pi/components/index.d.ts +1 -0
- package/dist/integrations/pi/components/index.js +1 -0
- package/dist/integrations/pi/components/owned-editor-ux.d.ts +2 -1
- package/dist/integrations/pi/components/owned-editor-ux.js +95 -22
- package/dist/integrations/pi/components/shell-editor-autocomplete.js +12 -1
- package/dist/integrations/pi/components/shell-presenters-info.d.ts +9 -2
- package/dist/integrations/pi/components/shell-presenters-info.js +35 -4
- package/dist/integrations/pi/components/shell-selectors-dialogs.d.ts +3 -2
- package/dist/integrations/pi/components/shell-selectors-dialogs.js +8 -5
- package/dist/integrations/pi/components/shell-shared-facade.d.ts +15 -2
- package/dist/integrations/pi/components/upstream/assets/earendil-image.json +12 -0
- package/dist/integrations/pi/components/upstream/components/earendil-announcement.js +25 -2
- package/dist/integrations/pi/components/upstream/components/owned-editor.d.ts +13 -9
- package/dist/integrations/pi/components/upstream/components/owned-editor.js +137 -133
- package/dist/integrations/pi/components/upstream/history/editor-core.d.ts +280 -0
- package/dist/integrations/pi/components/upstream/history/editor-core.js +2062 -0
- package/dist/integrations/pi/components/upstream/history/kill-ring.d.ts +32 -0
- package/dist/integrations/pi/components/upstream/history/kill-ring.js +48 -0
- package/dist/integrations/pi/components/upstream/history/printable-key.d.ts +1 -0
- package/dist/integrations/pi/components/upstream/history/printable-key.js +40 -0
- package/dist/integrations/pi/components/upstream/history/text-helpers.d.ts +14 -0
- package/dist/integrations/pi/components/upstream/history/text-helpers.js +28 -0
- package/dist/integrations/pi/components/upstream/history/undo-stack.d.ts +22 -0
- package/dist/integrations/pi/components/upstream/history/undo-stack.js +36 -0
- package/dist/integrations/pi/components/upstream/history/word-navigation.d.ts +24 -0
- package/dist/integrations/pi/components/upstream/history/word-navigation.js +100 -0
- package/dist/integrations/pi/engine/adapter.d.ts +13 -1
- package/dist/integrations/pi/engine/adapter.js +86 -25
- package/dist/integrations/pi/engine/changelog.d.ts +2 -0
- package/dist/integrations/pi/engine/changelog.js +63 -0
- package/dist/integrations/pi/engine/resources/changelog.json +12 -0
- package/dist/integrations/pi/engine/workflow-controllers.d.ts +1 -1
- package/dist/integrations/pi/engine/workflows.d.ts +1 -1
- package/dist/integrations/pi/session-ui/prompt-chips.d.ts +4 -1
- package/dist/integrations/pi/session-ui/prompt-chips.js +40 -13
- package/dist/integrations/pi/session-ui/prompt-history-controller.d.ts +30 -0
- package/dist/integrations/pi/session-ui/prompt-history-controller.js +147 -0
- package/dist/integrations/pi/session-ui/session-shell-root.d.ts +33 -0
- package/dist/integrations/pi/session-ui/session-shell-root.js +68 -74
- package/dist/integrations/pi/session-ui/session-shell.d.ts +1 -1
- package/dist/integrations/pi/session-ui/session-shell.js +98 -12
- package/dist/integrations/pi/session-ui/session-viewport-controller.d.ts +5 -0
- package/dist/integrations/pi/session-ui/session-viewport-controller.js +21 -15
- package/dist/integrations/pi/tui-runtime/damage-aware-terminal.d.ts +7 -0
- package/dist/integrations/pi/tui-runtime/damage-aware-terminal.js +7 -6
- package/dist/native/darwin-arm64/manifest.json +1 -1
- package/dist/native/linux-x64/manifest.json +1 -1
- package/dist/native/win32-x64/manifest.json +2 -2
- package/dist/native/win32-x64/process-guardian.exe +0 -0
- package/dist/product-identity.d.ts +2 -2
- package/dist/product-identity.js +3 -36
- package/dist/product-identity.json +1 -22
- package/dist/ui/components/transcript-viewport.d.ts +8 -0
- package/dist/ui/components/transcript-viewport.js +23 -2
- package/dist/ui/components/visible-hyperlinks.d.ts +7 -0
- package/dist/ui/components/visible-hyperlinks.js +1 -0
- package/dist/ui/settings/declarations.js +19 -0
- package/dist/ui/settings/sections.js +11 -1
- package/dist/ui/settings/session.js +13 -4
- package/docs/architecture/boundaries.md +2 -0
- package/docs/architecture/history-editor-provenance.md +96 -0
- package/docs/architecture/internal-naming.md +89 -0
- package/docs/architecture/project-structure.md +1 -0
- package/docs/architecture/resource-and-data-policy.md +27 -1
- package/docs/architecture/toolchain.md +15 -1
- package/docs/features/prompt-history.md +149 -0
- package/package.json +8 -3
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adapted from @earendil-works/pi-tui 0.84.2, packages/tui/src/kill-ring.ts (MIT).
|
|
3
|
+
* Source commit: 914cf1472e715297caa30db4b9535d534a9eb718.
|
|
4
|
+
* Modifications: public imports and owned editor-local typed seams; see docs/architecture/history-editor-provenance.md.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Ring buffer for Emacs-style kill/yank operations.
|
|
8
|
+
*
|
|
9
|
+
* Tracks killed (deleted) text entries. Consecutive kills can accumulate
|
|
10
|
+
* into a single entry. Supports yank (paste most recent) and yank-pop
|
|
11
|
+
* (cycle through older entries).
|
|
12
|
+
*/
|
|
13
|
+
export declare class KillRing {
|
|
14
|
+
private ring;
|
|
15
|
+
/**
|
|
16
|
+
* Add text to the kill ring.
|
|
17
|
+
*
|
|
18
|
+
* @param text - The killed text to add
|
|
19
|
+
* @param opts - Push options
|
|
20
|
+
* @param opts.prepend - If accumulating, prepend (backward deletion) or append (forward deletion)
|
|
21
|
+
* @param opts.accumulate - Merge with the most recent entry instead of creating a new one
|
|
22
|
+
*/
|
|
23
|
+
push(text: string, opts: {
|
|
24
|
+
prepend: boolean;
|
|
25
|
+
accumulate?: boolean;
|
|
26
|
+
}): void;
|
|
27
|
+
/** Get most recent entry without modifying the ring. */
|
|
28
|
+
peek(): string | undefined;
|
|
29
|
+
/** Move last entry to front (for yank-pop cycling). */
|
|
30
|
+
rotate(): void;
|
|
31
|
+
get length(): number;
|
|
32
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adapted from @earendil-works/pi-tui 0.84.2, packages/tui/src/kill-ring.ts (MIT).
|
|
3
|
+
* Source commit: 914cf1472e715297caa30db4b9535d534a9eb718.
|
|
4
|
+
* Modifications: public imports and owned editor-local typed seams; see docs/architecture/history-editor-provenance.md.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Ring buffer for Emacs-style kill/yank operations.
|
|
8
|
+
*
|
|
9
|
+
* Tracks killed (deleted) text entries. Consecutive kills can accumulate
|
|
10
|
+
* into a single entry. Supports yank (paste most recent) and yank-pop
|
|
11
|
+
* (cycle through older entries).
|
|
12
|
+
*/
|
|
13
|
+
export class KillRing {
|
|
14
|
+
ring = [];
|
|
15
|
+
/**
|
|
16
|
+
* Add text to the kill ring.
|
|
17
|
+
*
|
|
18
|
+
* @param text - The killed text to add
|
|
19
|
+
* @param opts - Push options
|
|
20
|
+
* @param opts.prepend - If accumulating, prepend (backward deletion) or append (forward deletion)
|
|
21
|
+
* @param opts.accumulate - Merge with the most recent entry instead of creating a new one
|
|
22
|
+
*/
|
|
23
|
+
push(text, opts) {
|
|
24
|
+
if (!text)
|
|
25
|
+
return;
|
|
26
|
+
if (opts.accumulate && this.ring.length > 0) {
|
|
27
|
+
const last = this.ring.pop();
|
|
28
|
+
this.ring.push(opts.prepend ? text + last : last + text);
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
this.ring.push(text);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/** Get most recent entry without modifying the ring. */
|
|
35
|
+
peek() {
|
|
36
|
+
return this.ring.length > 0 ? this.ring[this.ring.length - 1] : undefined;
|
|
37
|
+
}
|
|
38
|
+
/** Move last entry to front (for yank-pop cycling). */
|
|
39
|
+
rotate() {
|
|
40
|
+
if (this.ring.length > 1) {
|
|
41
|
+
const last = this.ring.pop();
|
|
42
|
+
this.ring.unshift(last);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
get length() {
|
|
46
|
+
return this.ring.length;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function decodePrintableKey(data: string): string | undefined;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adapted from @earendil-works/pi-tui 0.84.2, packages/tui/src/keys.ts (MIT).
|
|
3
|
+
* Source commit: 914cf1472e715297caa30db4b9535d534a9eb718.
|
|
4
|
+
* Modifications: public imports and owned editor-local typed seams; see docs/architecture/history-editor-provenance.md.
|
|
5
|
+
*/
|
|
6
|
+
import { decodeKittyPrintable } from "#pi-tui";
|
|
7
|
+
const MODIFIERS = {
|
|
8
|
+
shift: 1,
|
|
9
|
+
alt: 2,
|
|
10
|
+
ctrl: 4,
|
|
11
|
+
super: 8,
|
|
12
|
+
};
|
|
13
|
+
const LOCK_MASK = 64 + 128;
|
|
14
|
+
function parseModifyOtherKeysSequence(data) {
|
|
15
|
+
const match = data.match(/^\x1b\[27;(\d+);(\d+)~$/);
|
|
16
|
+
if (!match)
|
|
17
|
+
return null;
|
|
18
|
+
const modValue = parseInt(match[1], 10);
|
|
19
|
+
const codepoint = parseInt(match[2], 10);
|
|
20
|
+
return { codepoint, modifier: modValue - 1 };
|
|
21
|
+
}
|
|
22
|
+
function decodeModifyOtherKeysPrintable(data) {
|
|
23
|
+
const parsed = parseModifyOtherKeysSequence(data);
|
|
24
|
+
if (!parsed)
|
|
25
|
+
return undefined;
|
|
26
|
+
const modifier = parsed.modifier & ~LOCK_MASK;
|
|
27
|
+
if ((modifier & ~MODIFIERS.shift) !== 0)
|
|
28
|
+
return undefined;
|
|
29
|
+
if (!Number.isFinite(parsed.codepoint) || parsed.codepoint < 32)
|
|
30
|
+
return undefined;
|
|
31
|
+
try {
|
|
32
|
+
return String.fromCodePoint(parsed.codepoint);
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
export function decodePrintableKey(data) {
|
|
39
|
+
return decodeKittyPrintable(data) ?? decodeModifyOtherKeysPrintable(data);
|
|
40
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Get the shared grapheme segmenter instance.
|
|
3
|
+
*/
|
|
4
|
+
export declare function getGraphemeSegmenter(): Intl.Segmenter;
|
|
5
|
+
/**
|
|
6
|
+
* Get the shared word segmenter instance.
|
|
7
|
+
*/
|
|
8
|
+
export declare function getWordSegmenter(): Intl.Segmenter;
|
|
9
|
+
export declare const cjkBreakRegex: RegExp;
|
|
10
|
+
export declare const PUNCTUATION_REGEX: RegExp;
|
|
11
|
+
/**
|
|
12
|
+
* Check if a character is whitespace.
|
|
13
|
+
*/
|
|
14
|
+
export declare function isWhitespaceChar(char: string): boolean;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adapted from @earendil-works/pi-tui 0.84.2, packages/tui/src/utils.ts (MIT).
|
|
3
|
+
* Source commit: 914cf1472e715297caa30db4b9535d534a9eb718.
|
|
4
|
+
* Modifications: public imports and owned editor-local typed seams; see docs/architecture/history-editor-provenance.md.
|
|
5
|
+
*/
|
|
6
|
+
// segmenters (shared instance)
|
|
7
|
+
const graphemeSegmenter = new Intl.Segmenter(undefined, { granularity: "grapheme" });
|
|
8
|
+
const wordSegmenter = new Intl.Segmenter(undefined, { granularity: "word" });
|
|
9
|
+
/**
|
|
10
|
+
* Get the shared grapheme segmenter instance.
|
|
11
|
+
*/
|
|
12
|
+
export function getGraphemeSegmenter() {
|
|
13
|
+
return graphemeSegmenter;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Get the shared word segmenter instance.
|
|
17
|
+
*/
|
|
18
|
+
export function getWordSegmenter() {
|
|
19
|
+
return wordSegmenter;
|
|
20
|
+
}
|
|
21
|
+
export const cjkBreakRegex = /[\p{Script_Extensions=Han}\p{Script_Extensions=Hiragana}\p{Script_Extensions=Katakana}\p{Script_Extensions=Hangul}\p{Script_Extensions=Bopomofo}]/u;
|
|
22
|
+
export const PUNCTUATION_REGEX = /[(){}[\]<>.,;:'"!?+\-=*/\\|&%^$#@~`]/;
|
|
23
|
+
/**
|
|
24
|
+
* Check if a character is whitespace.
|
|
25
|
+
*/
|
|
26
|
+
export function isWhitespaceChar(char) {
|
|
27
|
+
return /\s/.test(char);
|
|
28
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adapted from @earendil-works/pi-tui 0.84.2, packages/tui/src/undo-stack.ts (MIT).
|
|
3
|
+
* Source commit: 914cf1472e715297caa30db4b9535d534a9eb718.
|
|
4
|
+
* Modifications: public imports and owned editor-local typed seams; see docs/architecture/history-editor-provenance.md.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Generic undo stack with clone-on-push semantics.
|
|
8
|
+
*
|
|
9
|
+
* Stores deep clones of state snapshots. Popped snapshots are returned
|
|
10
|
+
* directly (no re-cloning) since they are already detached.
|
|
11
|
+
*/
|
|
12
|
+
export declare class UndoStack<S> {
|
|
13
|
+
private stack;
|
|
14
|
+
/** Push a deep clone of the given state onto the stack. */
|
|
15
|
+
push(state: S): void;
|
|
16
|
+
/** Pop and return the most recent snapshot, or undefined if empty. */
|
|
17
|
+
pop(): S | undefined;
|
|
18
|
+
/** Remove all snapshots. */
|
|
19
|
+
clear(): void;
|
|
20
|
+
updateSnapshots(update: (snapshot: S) => void): void;
|
|
21
|
+
get length(): number;
|
|
22
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adapted from @earendil-works/pi-tui 0.84.2, packages/tui/src/undo-stack.ts (MIT).
|
|
3
|
+
* Source commit: 914cf1472e715297caa30db4b9535d534a9eb718.
|
|
4
|
+
* Modifications: public imports and owned editor-local typed seams; see docs/architecture/history-editor-provenance.md.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Generic undo stack with clone-on-push semantics.
|
|
8
|
+
*
|
|
9
|
+
* Stores deep clones of state snapshots. Popped snapshots are returned
|
|
10
|
+
* directly (no re-cloning) since they are already detached.
|
|
11
|
+
*/
|
|
12
|
+
export class UndoStack {
|
|
13
|
+
stack = [];
|
|
14
|
+
/** Push a deep clone of the given state onto the stack. */
|
|
15
|
+
push(state) {
|
|
16
|
+
this.stack.push(structuredClone(state));
|
|
17
|
+
}
|
|
18
|
+
/** Pop and return the most recent snapshot, or undefined if empty. */
|
|
19
|
+
pop() {
|
|
20
|
+
return this.stack.pop();
|
|
21
|
+
}
|
|
22
|
+
/** Remove all snapshots. */
|
|
23
|
+
clear() {
|
|
24
|
+
this.stack.length = 0;
|
|
25
|
+
}
|
|
26
|
+
updateSnapshots(update) {
|
|
27
|
+
this.stack = this.stack.map(snapshot => {
|
|
28
|
+
const copy = structuredClone(snapshot);
|
|
29
|
+
update(copy);
|
|
30
|
+
return copy;
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
get length() {
|
|
34
|
+
return this.stack.length;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Options for word navigation functions.
|
|
3
|
+
* When omitted, uses the default Intl.Segmenter word segmentation.
|
|
4
|
+
*/
|
|
5
|
+
export interface WordNavigationOptions {
|
|
6
|
+
/** Custom segmenter returning word segments for the given text. */
|
|
7
|
+
segment?: (text: string) => Iterable<Intl.SegmentData>;
|
|
8
|
+
/** Predicate identifying atomic segments that should be treated as single units (e.g. paste markers). */
|
|
9
|
+
isAtomicSegment?: (segment: string) => boolean;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Find the cursor position after moving one word backward from `cursor` in `text`.
|
|
13
|
+
* Skips trailing whitespace, then stops at the next word/punctuation boundary.
|
|
14
|
+
*
|
|
15
|
+
* Pure function - does not mutate any state.
|
|
16
|
+
*/
|
|
17
|
+
export declare function findWordBackward(text: string, cursor: number, options?: WordNavigationOptions): number;
|
|
18
|
+
/**
|
|
19
|
+
* Find the cursor position after moving one word forward from `cursor` in `text`.
|
|
20
|
+
* Skips leading whitespace, then stops at the next word/punctuation boundary.
|
|
21
|
+
*
|
|
22
|
+
* Pure function - does not mutate any state.
|
|
23
|
+
*/
|
|
24
|
+
export declare function findWordForward(text: string, cursor: number, options?: WordNavigationOptions): number;
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adapted from @earendil-works/pi-tui 0.84.2, packages/tui/src/word-navigation.ts (MIT).
|
|
3
|
+
* Source commit: 914cf1472e715297caa30db4b9535d534a9eb718.
|
|
4
|
+
* Modifications: public imports and owned editor-local typed seams; see docs/architecture/history-editor-provenance.md.
|
|
5
|
+
*/
|
|
6
|
+
import { getWordSegmenter, isWhitespaceChar, PUNCTUATION_REGEX } from "./text-helpers.js";
|
|
7
|
+
const wordSegmenter = getWordSegmenter();
|
|
8
|
+
/**
|
|
9
|
+
* Find the cursor position after moving one word backward from `cursor` in `text`.
|
|
10
|
+
* Skips trailing whitespace, then stops at the next word/punctuation boundary.
|
|
11
|
+
*
|
|
12
|
+
* Pure function - does not mutate any state.
|
|
13
|
+
*/
|
|
14
|
+
export function findWordBackward(text, cursor, options) {
|
|
15
|
+
if (cursor <= 0)
|
|
16
|
+
return 0;
|
|
17
|
+
const textBeforeCursor = text.slice(0, cursor);
|
|
18
|
+
const segmentFn = options?.segment;
|
|
19
|
+
const isAtomic = options?.isAtomicSegment;
|
|
20
|
+
const segments = segmentFn ? [...segmentFn(textBeforeCursor)] : [...wordSegmenter.segment(textBeforeCursor)];
|
|
21
|
+
let newCursor = cursor;
|
|
22
|
+
// Skip trailing whitespace
|
|
23
|
+
while (segments.length > 0 &&
|
|
24
|
+
!isAtomic?.(segments[segments.length - 1]?.segment || "") &&
|
|
25
|
+
isWhitespaceChar(segments[segments.length - 1]?.segment || "")) {
|
|
26
|
+
newCursor -= segments.pop()?.segment.length || 0;
|
|
27
|
+
}
|
|
28
|
+
if (segments.length === 0)
|
|
29
|
+
return newCursor;
|
|
30
|
+
const last = segments[segments.length - 1];
|
|
31
|
+
if (isAtomic?.(last.segment)) {
|
|
32
|
+
// Skip one atomic segment.
|
|
33
|
+
newCursor -= last.segment.length;
|
|
34
|
+
}
|
|
35
|
+
else if (last.isWordLike) {
|
|
36
|
+
// Skip inside one word-like segment, preserving ASCII punctuation boundaries.
|
|
37
|
+
const segment = last.segment;
|
|
38
|
+
const matches = [...segment.matchAll(new RegExp(PUNCTUATION_REGEX, "g"))];
|
|
39
|
+
if (matches.length <= 0) {
|
|
40
|
+
newCursor -= segment.length;
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
const lastMatch = matches[matches.length - 1];
|
|
44
|
+
newCursor -= segment.length - (lastMatch.index + lastMatch[0].length);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
// Skip non-word non-whitespace run (punctuation)
|
|
49
|
+
while (segments.length > 0 &&
|
|
50
|
+
!isAtomic?.(segments[segments.length - 1]?.segment || "") &&
|
|
51
|
+
!segments[segments.length - 1]?.isWordLike &&
|
|
52
|
+
!isWhitespaceChar(segments[segments.length - 1]?.segment || "")) {
|
|
53
|
+
newCursor -= segments.pop()?.segment.length || 0;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return newCursor;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Find the cursor position after moving one word forward from `cursor` in `text`.
|
|
60
|
+
* Skips leading whitespace, then stops at the next word/punctuation boundary.
|
|
61
|
+
*
|
|
62
|
+
* Pure function - does not mutate any state.
|
|
63
|
+
*/
|
|
64
|
+
export function findWordForward(text, cursor, options) {
|
|
65
|
+
if (cursor >= text.length)
|
|
66
|
+
return text.length;
|
|
67
|
+
const textAfterCursor = text.slice(cursor);
|
|
68
|
+
const segmentFn = options?.segment;
|
|
69
|
+
const isAtomic = options?.isAtomicSegment;
|
|
70
|
+
const segments = segmentFn ? segmentFn(textAfterCursor) : wordSegmenter.segment(textAfterCursor);
|
|
71
|
+
const iterator = segments[Symbol.iterator]();
|
|
72
|
+
let next = iterator.next();
|
|
73
|
+
let newCursor = cursor;
|
|
74
|
+
// Skip leading whitespace
|
|
75
|
+
while (!next.done && !isAtomic?.(next.value.segment) && isWhitespaceChar(next.value.segment)) {
|
|
76
|
+
newCursor += next.value.segment.length;
|
|
77
|
+
next = iterator.next();
|
|
78
|
+
}
|
|
79
|
+
if (next.done)
|
|
80
|
+
return newCursor;
|
|
81
|
+
if (isAtomic?.(next.value.segment)) {
|
|
82
|
+
// Skip one atomic segment.
|
|
83
|
+
newCursor += next.value.segment.length;
|
|
84
|
+
}
|
|
85
|
+
else if (next.value.isWordLike) {
|
|
86
|
+
// Skip inside one word-like segment, preserving ASCII punctuation boundaries.
|
|
87
|
+
newCursor += PUNCTUATION_REGEX.exec(next.value.segment)?.index ?? next.value.segment.length;
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
// Skip non-word non-whitespace run (punctuation)
|
|
91
|
+
while (!next.done &&
|
|
92
|
+
!isAtomic?.(next.value.segment) &&
|
|
93
|
+
!next.value.isWordLike &&
|
|
94
|
+
!isWhitespaceChar(next.value.segment)) {
|
|
95
|
+
newCursor += next.value.segment.length;
|
|
96
|
+
next = iterator.next();
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return newCursor;
|
|
100
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { PiSessionForkPrompt, PiSessionSelection } from "./session-selection.js";
|
|
2
|
-
import { type AgentSessionRuntime, type AgentSessionServices, type SessionInfo } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { type AgentSession, type AgentSessionRuntime, type AgentSessionServices, type SessionInfo } from "@earendil-works/pi-coding-agent";
|
|
3
3
|
import { OWNED_UI_EXTENSION_CONTRACT_VERSION, OWNED_UI_EXTENSION_RENDER_CALLBACKS, OWNED_UI_EXTENSION_UI_CALLBACKS, OWNED_UI_EXTENSION_UI_PROPERTIES, type OwnedUiCommand, type OwnedUiCommandOutcome, type OwnedUiImageAttachment, type OwnedUiPromptSuggestionGeneratorPort, type OwnedUiPromptSuggestionRequest, type OwnedUiPromptSuggestionResult, type OwnedUiEvent, type OwnedUiSessionViewModel, type OwnedUiSnapshot } from "../../../contracts/owned-ui/index.js";
|
|
4
4
|
import { type PiAuthenticationProviderOption, type PiBashWorkflowResult, type PiPinnedSettingsCallback, type PiPinnedSettingsSnapshot, type PiWorkflowAutocompleteCommand, type PiWorkflowHost, type PiWorkflowInteractionHost, type PiWorkflowOption, type PiWorkflowRequest, type PiWorkflowResult } from "./workflows.js";
|
|
5
5
|
import { PiSettingsIntegration } from "./settings-integration.js";
|
|
@@ -176,16 +176,28 @@ export declare class PiEngineAdapter implements OwnedUiPromptSuggestionGenerator
|
|
|
176
176
|
readonly title: string;
|
|
177
177
|
readonly options: readonly PiWorkflowOption[];
|
|
178
178
|
};
|
|
179
|
+
pinnedAmbientAuthentication(selection: string): {
|
|
180
|
+
readonly providerId: string;
|
|
181
|
+
readonly providerName: string;
|
|
182
|
+
readonly title: string;
|
|
183
|
+
readonly message: string;
|
|
184
|
+
} | null;
|
|
179
185
|
pinnedLogoutOptions(): Promise<readonly PiAuthenticationProviderOption[]>;
|
|
180
186
|
pinnedForkOptions(): readonly PiWorkflowOption[];
|
|
181
187
|
pinnedTreeSelectorContext(): PiTreeSelectorContext;
|
|
182
188
|
pinnedSettingsSnapshot(): PiPinnedSettingsSnapshot;
|
|
183
189
|
pinnedMessageRenderer(customType: string): unknown;
|
|
190
|
+
pinnedShortcutDescriptions(bindings: Parameters<AgentSession["extensionRunner"]["getShortcuts"]>[0]): readonly {
|
|
191
|
+
readonly key: string;
|
|
192
|
+
readonly description: string;
|
|
193
|
+
}[];
|
|
184
194
|
pinnedToolDefinition(toolName: string): unknown;
|
|
195
|
+
copyWorkflowText(text: string): Promise<void>;
|
|
185
196
|
clearQueuedWorkflows(): readonly string[];
|
|
186
197
|
abortBashWorkflow(): void;
|
|
187
198
|
executeBashWorkflow(command: string, excludeFromContext: boolean): Promise<PiBashWorkflowResult>;
|
|
188
199
|
applyPinnedSettingValue(callback: PiPinnedSettingsCallback, value: unknown): Promise<PiWorkflowResult>;
|
|
200
|
+
reloadBlockedResult(): PiWorkflowResult | null;
|
|
189
201
|
executeWorkflow(request: PiWorkflowRequest): Promise<PiWorkflowResult>;
|
|
190
202
|
view(): OwnedUiSessionViewModel;
|
|
191
203
|
snapshot(): OwnedUiSnapshot;
|
|
@@ -7,6 +7,7 @@ import { dirname, join, resolve } from "node:path";
|
|
|
7
7
|
import { promisify } from "node:util";
|
|
8
8
|
import { PRODUCT_IDENTITY } from "../../../product-identity.js";
|
|
9
9
|
import { configureOwnedHttpDispatcher } from "./http-dispatcher.js";
|
|
10
|
+
import { readPinnedCommandChangelog } from "./changelog.js";
|
|
10
11
|
import { copyToClipboard, CredentialSynchronizationError, DefaultPackageManager, getAgentDir, ProjectTrustStore, SessionManager, VERSION, } from "@earendil-works/pi-coding-agent";
|
|
11
12
|
import { OWNED_UI_EXTENSION_CONTRACT_VERSION, OWNED_UI_EXTENSION_RENDER_CALLBACKS, OWNED_UI_EXTENSION_UI_CALLBACKS, OWNED_UI_EXTENSION_UI_PROPERTIES, CONTEXTUAL_PROMPT_SUGGESTION_INSTRUCTION, assertOwnedUiCommand, assertOwnedUiExtensionUiPort, assertOwnedUiPromptSuggestionRequest, assertOwnedUiPromptSuggestionResult, assertOwnedUiSnapshot, normalizePromptSuggestionCandidate, } from "../../../contracts/owned-ui/index.js";
|
|
12
13
|
import { PINNED_PI_SETTINGS_CALLBACKS, PINNED_PI_WORKFLOW_COMMAND_NAMES, } from "./workflows.js";
|
|
@@ -308,14 +309,22 @@ export class PiEngineAdapter {
|
|
|
308
309
|
if (!settingsManager || typeof settingsManager.getLastChangelogVersion !== "function"
|
|
309
310
|
|| typeof settingsManager.setLastChangelogVersion !== "function")
|
|
310
311
|
return;
|
|
311
|
-
if (
|
|
312
|
+
if ((this.#session?.messages.length ?? 0) > 0)
|
|
312
313
|
return;
|
|
313
|
-
const
|
|
314
|
+
const lastVersion = settingsManager.getLastChangelogVersion();
|
|
315
|
+
if (lastVersion === VERSION)
|
|
316
|
+
return;
|
|
317
|
+
if (!lastVersion) {
|
|
318
|
+
settingsManager.setLastChangelogVersion(VERSION);
|
|
319
|
+
await settingsManager.flush();
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
const markdown = await this.#workflowHost.readChangelog(lastVersion).catch(() => "");
|
|
314
323
|
if (markdown.trim().length > 0) {
|
|
315
324
|
this.#addDiagnostic("info", settingsManager.getCollapseChangelog() ? "changelog-collapsed" : "changelog-expanded", markdown, true);
|
|
325
|
+
settingsManager.setLastChangelogVersion(VERSION);
|
|
326
|
+
await settingsManager.flush();
|
|
316
327
|
}
|
|
317
|
-
settingsManager.setLastChangelogVersion(VERSION);
|
|
318
|
-
await settingsManager.flush();
|
|
319
328
|
}
|
|
320
329
|
async #announcePackageUpdates(settingsManager) {
|
|
321
330
|
if (process.env.PI_OFFLINE)
|
|
@@ -877,6 +886,17 @@ export class PiEngineAdapter {
|
|
|
877
886
|
}));
|
|
878
887
|
return { title: `Select authentication method for ${providerName}:`, options };
|
|
879
888
|
}
|
|
889
|
+
pinnedAmbientAuthentication(selection) {
|
|
890
|
+
if (!selection.startsWith("api_key:"))
|
|
891
|
+
return null;
|
|
892
|
+
const providerId = selection.slice("api_key:".length);
|
|
893
|
+
const provider = this.#runtime?.services.modelRuntime.getProvider?.(providerId);
|
|
894
|
+
const method = dynamicObject(dynamicObject(provider, "auth"), "apiKey");
|
|
895
|
+
if (!method || typeof method.login === "function")
|
|
896
|
+
return null;
|
|
897
|
+
const providerName = stringProperty(provider, "name") ?? providerId;
|
|
898
|
+
return { providerId, providerName, title: `${providerName} setup`, message: `${stringProperty(method, "name") ?? "Authentication"} is configured outside pi.` };
|
|
899
|
+
}
|
|
880
900
|
pinnedLogoutOptions() {
|
|
881
901
|
return this.#logoutOptions();
|
|
882
902
|
}
|
|
@@ -957,9 +977,16 @@ export class PiEngineAdapter {
|
|
|
957
977
|
pinnedMessageRenderer(customType) {
|
|
958
978
|
return this.#requireWorkflowSession().extensionRunner?.getMessageRenderer?.(customType);
|
|
959
979
|
}
|
|
980
|
+
pinnedShortcutDescriptions(bindings) {
|
|
981
|
+
const shortcuts = this.#requireWorkflowSession().extensionRunner?.getShortcuts?.(bindings);
|
|
982
|
+
return shortcuts === undefined ? [] : [...shortcuts].map(([key, shortcut]) => ({ key, description: shortcut.description ?? shortcut.extensionPath }));
|
|
983
|
+
}
|
|
960
984
|
pinnedToolDefinition(toolName) {
|
|
961
985
|
return this.#requireWorkflowSession().extensionRunner?.getToolDefinition?.(toolName);
|
|
962
986
|
}
|
|
987
|
+
async copyWorkflowText(text) {
|
|
988
|
+
await this.#workflowHost.copyText(text);
|
|
989
|
+
}
|
|
963
990
|
clearQueuedWorkflows() {
|
|
964
991
|
const session = this.#requireWorkflowSession();
|
|
965
992
|
const result = session.clearQueue?.();
|
|
@@ -992,6 +1019,14 @@ export class PiEngineAdapter {
|
|
|
992
1019
|
return workflowResult("settings", "failed", error instanceof Error ? error.message : String(error));
|
|
993
1020
|
}
|
|
994
1021
|
}
|
|
1022
|
+
reloadBlockedResult() {
|
|
1023
|
+
const session = this.#requireWorkflowSession();
|
|
1024
|
+
if (session.isStreaming)
|
|
1025
|
+
return workflowResult("reload", "failed", "Wait for the current response to finish before reloading.", undefined, "warning");
|
|
1026
|
+
if (session.isCompacting)
|
|
1027
|
+
return workflowResult("reload", "failed", "Wait for compaction to finish before reloading.", undefined, "warning");
|
|
1028
|
+
return null;
|
|
1029
|
+
}
|
|
995
1030
|
async executeWorkflow(request) {
|
|
996
1031
|
try {
|
|
997
1032
|
return await this.#performWorkflow(request);
|
|
@@ -999,7 +1034,7 @@ export class PiEngineAdapter {
|
|
|
999
1034
|
catch (error) {
|
|
1000
1035
|
const message = error instanceof Error ? error.message : String(error);
|
|
1001
1036
|
const contextualMessage = request.command === "export"
|
|
1002
|
-
? `Failed to export session: ${
|
|
1037
|
+
? `Failed to export session: ${errorMessage(error, "Unknown error")}`
|
|
1003
1038
|
: request.command === "import"
|
|
1004
1039
|
? `Failed to import session: ${message}`
|
|
1005
1040
|
: request.command === "new"
|
|
@@ -1133,8 +1168,18 @@ export class PiEngineAdapter {
|
|
|
1133
1168
|
: [...(runtime.services.modelRuntime.getAvailableSnapshot?.() ?? [])];
|
|
1134
1169
|
let model = findExactWorkflowModel(reference, availableModels);
|
|
1135
1170
|
const messages = [];
|
|
1171
|
+
const generation = this.#sessionGeneration;
|
|
1172
|
+
const current = () => !this.#disposed && generation === this.#sessionGeneration;
|
|
1173
|
+
const publish = (message) => {
|
|
1174
|
+
if (!current())
|
|
1175
|
+
return;
|
|
1176
|
+
if (this.#workflowInteraction.publish)
|
|
1177
|
+
this.#workflowInteraction.publish(message);
|
|
1178
|
+
else
|
|
1179
|
+
messages.push(message);
|
|
1180
|
+
};
|
|
1136
1181
|
if (model === undefined && scopedModels.length === 0) {
|
|
1137
|
-
|
|
1182
|
+
publish({ kind: "status", message: "Refreshing model catalogs…" });
|
|
1138
1183
|
const controller = new AbortController();
|
|
1139
1184
|
let timedOut = false;
|
|
1140
1185
|
const timeout = setTimeout(() => {
|
|
@@ -1144,14 +1189,14 @@ export class PiEngineAdapter {
|
|
|
1144
1189
|
try {
|
|
1145
1190
|
const refreshed = await runtime.services.modelRuntime.refresh?.({ signal: controller.signal });
|
|
1146
1191
|
if (isRecord(refreshed) && refreshed.aborted === true && timedOut) {
|
|
1147
|
-
|
|
1192
|
+
publish({ kind: "warning", message: "Model refresh timed out; searching cached models." });
|
|
1148
1193
|
}
|
|
1149
1194
|
else if (isRecord(refreshed) && refreshed.errors instanceof Map && refreshed.errors.size > 0) {
|
|
1150
|
-
|
|
1195
|
+
publish({ kind: "warning", message: `Could not refresh ${[...refreshed.errors.keys()].join(", ")}; searching cached models.` });
|
|
1151
1196
|
}
|
|
1152
1197
|
}
|
|
1153
1198
|
catch (error) {
|
|
1154
|
-
|
|
1199
|
+
publish({
|
|
1155
1200
|
kind: "warning",
|
|
1156
1201
|
message: timedOut
|
|
1157
1202
|
? "Model refresh timed out; searching cached models."
|
|
@@ -1164,13 +1209,25 @@ export class PiEngineAdapter {
|
|
|
1164
1209
|
availableModels = [...(runtime.services.modelRuntime.getAvailableSnapshot?.() ?? [])];
|
|
1165
1210
|
model = findExactWorkflowModel(reference, availableModels);
|
|
1166
1211
|
}
|
|
1212
|
+
if (!current())
|
|
1213
|
+
return workflowResult(request.command, "cancelled", "Model selection cancelled", undefined, "silent");
|
|
1167
1214
|
if (model === undefined) {
|
|
1168
1215
|
return {
|
|
1169
1216
|
...workflowResult(request.command, "requires-selection", "Select a model", reference, "silent"),
|
|
1170
1217
|
...(messages.length === 0 ? {} : { messages: Object.freeze(messages) }),
|
|
1171
1218
|
};
|
|
1172
1219
|
}
|
|
1173
|
-
|
|
1220
|
+
try {
|
|
1221
|
+
await session.setModel(model);
|
|
1222
|
+
}
|
|
1223
|
+
catch (error) {
|
|
1224
|
+
if (!current())
|
|
1225
|
+
return workflowResult(request.command, "cancelled", "Model selection cancelled", undefined, "silent");
|
|
1226
|
+
const failure = { kind: "error", message: error instanceof Error ? error.message : String(error) };
|
|
1227
|
+
return workflowResult(request.command, "failed", failure.message, undefined, "error", messages.length === 0 ? undefined : [...messages, failure]);
|
|
1228
|
+
}
|
|
1229
|
+
if (!current())
|
|
1230
|
+
return workflowResult(request.command, "cancelled", "Model selection cancelled", undefined, "silent");
|
|
1174
1231
|
const providerId = stringProperty(model, "provider") ?? "unknown";
|
|
1175
1232
|
const modelId = stringProperty(model, "id") ?? reference;
|
|
1176
1233
|
this.#activeModel = { providerId, modelId, displayName: stringProperty(model, "name") ?? modelId };
|
|
@@ -1226,9 +1283,8 @@ export class PiEngineAdapter {
|
|
|
1226
1283
|
const signal = request.signal;
|
|
1227
1284
|
if (signal?.aborted)
|
|
1228
1285
|
return workflowResult(request.command, "cancelled", "Share cancelled", undefined, "status");
|
|
1229
|
-
let auth;
|
|
1230
1286
|
try {
|
|
1231
|
-
|
|
1287
|
+
await this.#workflowHost.runCommand("gh", ["auth", "status"], signal === undefined ? undefined : { signal });
|
|
1232
1288
|
}
|
|
1233
1289
|
catch (error) {
|
|
1234
1290
|
if (signal?.aborted || isAbortError(error))
|
|
@@ -1238,9 +1294,6 @@ export class PiEngineAdapter {
|
|
|
1238
1294
|
: "GitHub CLI is not logged in. Run 'gh auth login' first.";
|
|
1239
1295
|
return workflowResult(request.command, "failed", message);
|
|
1240
1296
|
}
|
|
1241
|
-
if (auth.stderr && !auth.stdout) {
|
|
1242
|
-
return workflowResult(request.command, "failed", "GitHub CLI is not logged in. Run 'gh auth login' first.");
|
|
1243
|
-
}
|
|
1244
1297
|
const temporary = join(tmpdir(), `${PRODUCT_IDENTITY.filesystem.temporaryPrefix}pi-session-${process.pid}.html`);
|
|
1245
1298
|
try {
|
|
1246
1299
|
try {
|
|
@@ -1258,13 +1311,10 @@ export class PiEngineAdapter {
|
|
|
1258
1311
|
catch (error) {
|
|
1259
1312
|
if (signal?.aborted || isAbortError(error))
|
|
1260
1313
|
return workflowResult(request.command, "cancelled", "Share cancelled", undefined, "status");
|
|
1261
|
-
return workflowResult(request.command, "failed", `Failed to create gist: ${
|
|
1314
|
+
return workflowResult(request.command, "failed", `Failed to create gist: ${shareCommandFailureDetail(error)}`);
|
|
1262
1315
|
}
|
|
1263
1316
|
if (signal?.aborted)
|
|
1264
1317
|
return workflowResult(request.command, "cancelled", "Share cancelled", undefined, "status");
|
|
1265
|
-
if (gist.stderr && !gist.stdout) {
|
|
1266
|
-
return workflowResult(request.command, "failed", `Failed to create gist: ${gist.stderr.trim() || "Unknown error"}`);
|
|
1267
|
-
}
|
|
1268
1318
|
const gistUrl = gist.stdout.trim();
|
|
1269
1319
|
const gistId = gistUrl.split("/").at(-1);
|
|
1270
1320
|
if (!gistId)
|
|
@@ -1497,13 +1547,17 @@ export class PiEngineAdapter {
|
|
|
1497
1547
|
}
|
|
1498
1548
|
}
|
|
1499
1549
|
case "reload": {
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
return workflowResult(request.command, "failed", "Wait for compaction to finish before reloading.", undefined, "warning");
|
|
1550
|
+
const blocked = this.reloadBlockedResult();
|
|
1551
|
+
if (blocked)
|
|
1552
|
+
return blocked;
|
|
1504
1553
|
await requireCapability(session.reload, "reload").call(session);
|
|
1505
1554
|
await this.#bindExtensionUiToSession();
|
|
1506
|
-
|
|
1555
|
+
const message = "Reloaded keybindings, extensions, skills, prompts, themes, and context files";
|
|
1556
|
+
const modelError = runtime.services.modelRuntime.getError?.();
|
|
1557
|
+
return workflowResult(request.command, "completed", message, undefined, "status", modelError ? [
|
|
1558
|
+
{ kind: "error", message: `models.json error: ${modelError}` },
|
|
1559
|
+
{ kind: "status", message },
|
|
1560
|
+
] : undefined);
|
|
1507
1561
|
}
|
|
1508
1562
|
case "quit": {
|
|
1509
1563
|
await this.dispose();
|
|
@@ -2698,7 +2752,7 @@ function defaultWorkflowHost() {
|
|
|
2698
2752
|
const result = await execFileAsync(command, [...arguments_], { encoding: "utf8", signal: options?.signal });
|
|
2699
2753
|
return { stdout: result.stdout, stderr: result.stderr };
|
|
2700
2754
|
},
|
|
2701
|
-
readChangelog:
|
|
2755
|
+
readChangelog: readPinnedCommandChangelog,
|
|
2702
2756
|
};
|
|
2703
2757
|
}
|
|
2704
2758
|
function workflowLoginNotification(event) {
|
|
@@ -2757,6 +2811,13 @@ function workflowConfirmation(command, message, detail) {
|
|
|
2757
2811
|
function errorMessage(error, fallback) {
|
|
2758
2812
|
return error instanceof Error ? error.message : fallback;
|
|
2759
2813
|
}
|
|
2814
|
+
function shareCommandFailureDetail(error) {
|
|
2815
|
+
// Compatibility: Pi reports a failed child's stderr, not execFile's command wrapper.
|
|
2816
|
+
if (isRecord(error) && (typeof error.code === "number" || typeof error.signal === "string") && typeof error.stderr === "string") {
|
|
2817
|
+
return error.stderr.trim() || "Unknown error";
|
|
2818
|
+
}
|
|
2819
|
+
return errorMessage(error, "Unknown error");
|
|
2820
|
+
}
|
|
2760
2821
|
function commandMissing(error) {
|
|
2761
2822
|
return isRecord(error) && error.code === "ENOENT";
|
|
2762
2823
|
}
|