@oh-my-pi/pi-coding-agent 16.3.4 → 16.3.5
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 +18 -0
- package/dist/cli.js +3039 -3016
- package/dist/types/hindsight/content.d.ts +4 -0
- package/dist/types/internal-urls/artifact-protocol.d.ts +8 -0
- package/dist/types/internal-urls/types.d.ts +10 -0
- package/dist/types/lsp/config.d.ts +4 -0
- package/dist/types/lsp/edits.d.ts +2 -0
- package/dist/types/mcp/oauth-discovery.d.ts +30 -0
- package/dist/types/modes/components/login-dialog.d.ts +10 -2
- package/dist/types/modes/components/transcript-container.d.ts +0 -1
- package/dist/types/modes/controllers/mcp-command-controller.d.ts +20 -3
- package/dist/types/modes/rpc/rpc-client.d.ts +7 -3
- package/dist/types/modes/rpc/rpc-types.d.ts +6 -0
- package/dist/types/subprocess/worker-runtime.d.ts +11 -0
- package/dist/types/tools/bash-skill-urls.d.ts +2 -2
- package/dist/types/utils/git.d.ts +16 -0
- package/package.json +12 -12
- package/src/cli/auth-broker-cli.ts +13 -2
- package/src/cli/tiny-models-cli.ts +12 -5
- package/src/hindsight/content.ts +31 -0
- package/src/internal-urls/artifact-protocol.ts +97 -53
- package/src/internal-urls/types.ts +10 -0
- package/src/lsp/config.ts +15 -0
- package/src/lsp/edits.ts +28 -7
- package/src/lsp/index.ts +46 -4
- package/src/mcp/oauth-discovery.ts +88 -18
- package/src/mnemopi/state.ts +26 -2
- package/src/modes/components/login-dialog.ts +16 -2
- package/src/modes/components/mcp-add-wizard.ts +9 -1
- package/src/modes/components/transcript-container.ts +0 -26
- package/src/modes/controllers/mcp-command-controller.ts +106 -29
- package/src/modes/controllers/selector-controller.ts +9 -1
- package/src/modes/rpc/rpc-client.ts +8 -4
- package/src/modes/rpc/rpc-mode.ts +1 -0
- package/src/modes/rpc/rpc-types.ts +13 -1
- package/src/modes/setup-wizard/scenes/sign-in.ts +18 -0
- package/src/prompts/tools/read.md +1 -1
- package/src/subprocess/worker-runtime.ts +219 -2
- package/src/task/worktree.ts +28 -6
- package/src/tiny/worker.ts +14 -4
- package/src/tools/bash-skill-urls.ts +3 -3
- package/src/tools/grep.ts +19 -2
- package/src/tools/path-utils.ts +4 -0
- package/src/tools/read.ts +198 -1
- package/src/utils/git.ts +20 -0
- package/src/utils/open.ts +51 -6
|
@@ -64,6 +64,10 @@ export interface RetentionTranscript {
|
|
|
64
64
|
transcript: string | null;
|
|
65
65
|
messageCount: number;
|
|
66
66
|
}
|
|
67
|
+
/** Remove retention framing lines from a stored coding-agent episode transcript. */
|
|
68
|
+
export declare function stripRetentionProtocolMarkers(content: string): string;
|
|
67
69
|
export declare function prepareRetentionTranscript(messages: HindsightMessage[], retainFullWindow?: boolean): RetentionTranscript;
|
|
70
|
+
/** Format all retention messages without protocol markers for embedding, FTS, and recall display. */
|
|
71
|
+
export declare function prepareEmbeddableRetentionTranscript(messages: HindsightMessage[]): RetentionTranscript;
|
|
68
72
|
/** Format only user-authored messages for memory fact/entity extraction. */
|
|
69
73
|
export declare function prepareUserRetentionTranscript(messages: HindsightMessage[]): RetentionTranscript;
|
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
import type { InternalResource, InternalUrl, ProtocolHandler, ResolveContext, UrlCompletion } from "./types";
|
|
2
|
+
/** Filesystem location for a session artifact, resolved without materializing its content. */
|
|
3
|
+
export interface ResolvedArtifactFile {
|
|
4
|
+
id: string;
|
|
5
|
+
path: string;
|
|
6
|
+
size: number;
|
|
7
|
+
}
|
|
8
|
+
/** Resolve an `artifact://` URL to its backing file without reading artifact bytes. */
|
|
9
|
+
export declare function resolveArtifactFile(url: InternalUrl, context?: ResolveContext): Promise<ResolvedArtifactFile>;
|
|
2
10
|
export declare class ArtifactProtocolHandler implements ProtocolHandler {
|
|
3
11
|
readonly scheme = "artifact";
|
|
4
12
|
readonly immutable = true;
|
|
@@ -102,6 +102,16 @@ export interface ResolveContext {
|
|
|
102
102
|
* reject directory resources, so they never need the listing.
|
|
103
103
|
*/
|
|
104
104
|
skipDirectoryListing?: boolean;
|
|
105
|
+
/**
|
|
106
|
+
* When set, handlers that would otherwise materialize expensive content
|
|
107
|
+
* (e.g. reading a multi-MiB artifact into memory just to expose its
|
|
108
|
+
* `sourcePath`) may return the resource shape without content. Callers
|
|
109
|
+
* that only need `sourcePath` — search/grep, bash URL expansion — pass
|
|
110
|
+
* this so a large `artifact://` still resolves to its backing file
|
|
111
|
+
* without OOM risk. Handlers that cannot separate path from content
|
|
112
|
+
* ignore the flag.
|
|
113
|
+
*/
|
|
114
|
+
pathOnly?: boolean;
|
|
105
115
|
}
|
|
106
116
|
/**
|
|
107
117
|
* Caller context for write operations dispatched to host-owned URI handlers.
|
|
@@ -8,6 +8,10 @@ export interface LspConfig {
|
|
|
8
8
|
* Check if any root marker file exists in the directory
|
|
9
9
|
*/
|
|
10
10
|
export declare function hasRootMarkers(cwd: string, markers: string[]): boolean;
|
|
11
|
+
/**
|
|
12
|
+
* Check whether any ancestor directory of a file is an LSP project root.
|
|
13
|
+
*/
|
|
14
|
+
export declare function hasRootMarkerAncestor(filePath: string, markers: string[]): boolean;
|
|
11
15
|
/**
|
|
12
16
|
* Resolve a command to an executable path.
|
|
13
17
|
* Checks project-local bin directories first, then falls back to $PATH.
|
|
@@ -11,6 +11,8 @@ export declare function rangesOverlap(a: Range, b: Range): boolean;
|
|
|
11
11
|
* Equal start positions tiebreak by original array index descending so that,
|
|
12
12
|
* applied bottom-up, inserts at the same position land in array order
|
|
13
13
|
* (LSP spec: the order of edits in the array defines the order in the result).
|
|
14
|
+
* Byte-identical non-empty range edits are idempotent, so duplicate server
|
|
15
|
+
* output is collapsed before overlap validation.
|
|
14
16
|
*/
|
|
15
17
|
export declare function sortAndValidateTextEdits(edits: TextEdit[]): TextEdit[];
|
|
16
18
|
/**
|
|
@@ -12,9 +12,26 @@ export interface AuthDetectionResult {
|
|
|
12
12
|
oauth?: OAuthEndpoints;
|
|
13
13
|
authServerUrl?: string;
|
|
14
14
|
resourceMetadataUrl?: string;
|
|
15
|
+
/**
|
|
16
|
+
* OAuth scopes advertised by the challenge (RFC 6750 `scope=` on
|
|
17
|
+
* `WWW-Authenticate`) or by protected-resource metadata. Passed through
|
|
18
|
+
* `discoverOAuthEndpoints` as `protectedScopes` so the eventual
|
|
19
|
+
* authorization request carries them even when the auth-server metadata
|
|
20
|
+
* document itself omits `scopes_supported`.
|
|
21
|
+
*/
|
|
22
|
+
scopes?: string;
|
|
15
23
|
message?: string;
|
|
16
24
|
}
|
|
17
25
|
export declare function extractMcpAuthServerUrl(error: Error, serverUrl?: string): string | undefined;
|
|
26
|
+
/**
|
|
27
|
+
* Pull the `scope`/`scopes` parameter out of a `WWW-Authenticate` challenge
|
|
28
|
+
* embedded in the error message. RFC 6750 lets servers advertise the missing
|
|
29
|
+
* scopes when they reject a bearer token with `insufficient_scope`, and RFC
|
|
30
|
+
* 8414-adjacent MCP gateways sometimes list the required scopes there rather
|
|
31
|
+
* than in `scopes_supported`. Returns the raw space-separated value, or
|
|
32
|
+
* `undefined` when the challenge does not carry one.
|
|
33
|
+
*/
|
|
34
|
+
export declare function extractOAuthChallengeScopes(error: Error): string | undefined;
|
|
18
35
|
/**
|
|
19
36
|
* Extract OAuth endpoints from error response.
|
|
20
37
|
* Looks for WWW-Authenticate header format or JSON error bodies.
|
|
@@ -25,6 +42,18 @@ export declare function extractOAuthEndpoints(error: Error): OAuthEndpoints | nu
|
|
|
25
42
|
* Returns structured info about what auth is needed.
|
|
26
43
|
*/
|
|
27
44
|
export declare function analyzeAuthError(error: Error, serverUrl?: string): AuthDetectionResult;
|
|
45
|
+
/**
|
|
46
|
+
* Fetch the RFC 9728 protected-resource metadata document at
|
|
47
|
+
* {@link resourceMetadataUrl} and return any scopes it advertises. Used by
|
|
48
|
+
* `/mcp add` / `/mcp reauth` on the JSON-error-body path, where the caller
|
|
49
|
+
* already holds usable OAuth endpoints but the required scopes live only in
|
|
50
|
+
* the advertised protected-resource metadata — a case `discoverOAuthEndpoints`
|
|
51
|
+
* normally handles but that path is skipped when the body carried endpoints.
|
|
52
|
+
* Returns `undefined` on any error or when no scopes are advertised.
|
|
53
|
+
*/
|
|
54
|
+
export declare function fetchResourceMetadataScopes(resourceMetadataUrl: string, opts?: {
|
|
55
|
+
fetch?: FetchImpl;
|
|
56
|
+
}): Promise<string | undefined>;
|
|
28
57
|
/**
|
|
29
58
|
* Try to discover OAuth endpoints by querying the server's well-known endpoints.
|
|
30
59
|
* This is a fallback when error responses don't include OAuth metadata.
|
|
@@ -32,4 +61,5 @@ export declare function analyzeAuthError(error: Error, serverUrl?: string): Auth
|
|
|
32
61
|
export declare function discoverOAuthEndpoints(serverUrl: string, authServerUrl?: string, resourceMetadataUrl?: string, opts?: {
|
|
33
62
|
fetch?: FetchImpl;
|
|
34
63
|
protectedResource?: string;
|
|
64
|
+
protectedScopes?: string;
|
|
35
65
|
}): Promise<OAuthEndpoints | null>;
|
|
@@ -8,9 +8,17 @@ export declare class LoginDialogComponent extends Container {
|
|
|
8
8
|
constructor(tui: TUI, providerId: string, onComplete: (success: boolean, message?: string) => void);
|
|
9
9
|
get signal(): AbortSignal;
|
|
10
10
|
/**
|
|
11
|
-
* Called by onAuth callback
|
|
11
|
+
* Called by the OAuth `onAuth` callback. Renders the full authorization URL
|
|
12
|
+
* as the primary copy target — that works from any machine, including
|
|
13
|
+
* SSH/WSL/headless sessions where the OMP-hosted `launchUrl` would resolve
|
|
14
|
+
* against the user's local browser and fail. When `launchUrl` is present it
|
|
15
|
+
* is offered as an additional local shortcut so narrow local terminals still
|
|
16
|
+
* have a truncation-safe copy target (viewport clipping on a long authorize
|
|
17
|
+
* URL silently drops trailing OAuth query parameters — e.g.
|
|
18
|
+
* `code_challenge_method=S256`). The OSC 8 hyperlink carries the full URL
|
|
19
|
+
* for terminals that support click-through.
|
|
12
20
|
*/
|
|
13
|
-
showAuth(url: string, instructions?: string): void;
|
|
21
|
+
showAuth(url: string, instructions?: string, launchUrl?: string): void;
|
|
14
22
|
/**
|
|
15
23
|
* Show input for manual code/URL entry (for callback server providers)
|
|
16
24
|
*/
|
|
@@ -31,7 +31,6 @@ export declare class TranscriptContainer extends Container implements NativeScro
|
|
|
31
31
|
getNativeScrollbackLiveRegionStart(): number | undefined;
|
|
32
32
|
getNativeScrollbackCommitSafeEnd(): number | undefined;
|
|
33
33
|
getNativeScrollbackSnapshotSafeEnd(): number | undefined;
|
|
34
|
-
getNativeScrollbackOfferSafeEnd(): number | undefined;
|
|
35
34
|
/**
|
|
36
35
|
* Whether `component` sits below a still-mutating block — i.e. inside the
|
|
37
36
|
* live region, where its rows cannot have been committed to native
|
|
@@ -1,11 +1,28 @@
|
|
|
1
1
|
import { type Component } from "@oh-my-pi/pi-tui";
|
|
2
2
|
import type { InteractiveModeContext } from "../types";
|
|
3
|
-
/**
|
|
3
|
+
/**
|
|
4
|
+
* Renders the MCP OAuth fallback URL. Always shows the full authorization URL
|
|
5
|
+
* as the primary `Copy URL:` target — that works from any machine, including
|
|
6
|
+
* SSH/WSL/headless sessions where the OMP-hosted `/launch` loopback URL would
|
|
7
|
+
* resolve against the user's local browser and fail.
|
|
8
|
+
*
|
|
9
|
+
* The render is `width`-aware: on any viewport narrower than the composed row
|
|
10
|
+
* ({@link TUI#prepareLine} truncates anything wider with `Ellipsis.Omit`, no
|
|
11
|
+
* marker), the URL is hard-wrapped into width-fitted rows so the primary copy
|
|
12
|
+
* target can never silently lose trailing OAuth parameters — the failure mode
|
|
13
|
+
* that motivated #4418 in the first place. Browsers strip whitespace when a
|
|
14
|
+
* multi-row selection is pasted into the address bar, so the reassembled URL
|
|
15
|
+
* is byte-identical to what we rendered.
|
|
16
|
+
*
|
|
17
|
+
* When the flow's callback server hosts a short `launchUrl`, it is offered
|
|
18
|
+
* as an additional local shortcut for wide-terminal local users. The OSC 8
|
|
19
|
+
* hyperlink continues to carry the full URL for terminals that support it.
|
|
20
|
+
*/
|
|
4
21
|
export declare class MCPAuthorizationLinkPrompt implements Component {
|
|
5
22
|
#private;
|
|
6
|
-
constructor(url: string);
|
|
23
|
+
constructor(url: string, launchUrl?: string);
|
|
7
24
|
invalidate(): void;
|
|
8
|
-
render(
|
|
25
|
+
render(width: number): readonly string[];
|
|
9
26
|
}
|
|
10
27
|
/**
|
|
11
28
|
* Thrown by {@link MCPCommandController}'s OAuth handler when the user (or a
|
|
@@ -271,11 +271,15 @@ export declare class RpcClient {
|
|
|
271
271
|
* The server will emit an `open_url` extension_ui_request for the auth URL.
|
|
272
272
|
* Resolves when login completes or rejects on failure.
|
|
273
273
|
*
|
|
274
|
-
* @param onOpenUrl Called when the server emits the auth URL. The host must
|
|
275
|
-
*
|
|
274
|
+
* @param onOpenUrl Called when the server emits the auth URL. The host must
|
|
275
|
+
* open `url` in a browser for the callback-server OAuth flow to complete.
|
|
276
|
+
* When the flow's callback server hosts a `/launch` redirect, `launchUrl`
|
|
277
|
+
* is a short loopback URL that 302s to `url` — hosts SHOULD surface it as
|
|
278
|
+
* the truncation-safe copy target so terminal viewport clipping cannot
|
|
279
|
+
* corrupt trailing OAuth query parameters (e.g. `code_challenge_method=S256`).
|
|
276
280
|
*/
|
|
277
281
|
login(providerId: string, options?: {
|
|
278
|
-
onOpenUrl?: (url: string, instructions?: string) => void;
|
|
282
|
+
onOpenUrl?: (url: string, instructions?: string, launchUrl?: string) => void;
|
|
279
283
|
}): Promise<{
|
|
280
284
|
providerId: string;
|
|
281
285
|
}>;
|
|
@@ -599,6 +599,12 @@ export type RpcExtensionUIRequest = {
|
|
|
599
599
|
id: string;
|
|
600
600
|
method: "open_url";
|
|
601
601
|
url: string;
|
|
602
|
+
/**
|
|
603
|
+
* Short loopback URL that 302-redirects to {@link url}. When present,
|
|
604
|
+
* hosts SHOULD surface it as the copy target so terminal viewport
|
|
605
|
+
* truncation cannot corrupt OAuth query parameters on the full URL.
|
|
606
|
+
*/
|
|
607
|
+
launchUrl?: string;
|
|
602
608
|
instructions?: string;
|
|
603
609
|
};
|
|
604
610
|
export interface RpcHostToolDefinition {
|
|
@@ -69,6 +69,11 @@ export declare function replayCachedReady<K, M>(cache: Map<K, Promise<M>>, model
|
|
|
69
69
|
* runtime's `node_modules` directory.
|
|
70
70
|
*/
|
|
71
71
|
export declare function installSharpStubResolver(runtimeDir: string): Promise<string>;
|
|
72
|
+
/**
|
|
73
|
+
* Repairs the compiled Transformers side runtime when CUDA was requested and
|
|
74
|
+
* Bun skipped `onnxruntime-node`'s NuGet sidecar install.
|
|
75
|
+
*/
|
|
76
|
+
export declare function ensureOnnxRuntimeCudaProviders(runtimeDir: string, device?: string | undefined): Promise<void>;
|
|
72
77
|
/**
|
|
73
78
|
* Lazily resolve (and memoize) the transformers version spec. In the `catalog:`
|
|
74
79
|
* case {@link resolveTransformersVersionSpec} `require`s the installed
|
|
@@ -88,6 +93,12 @@ interface ConfigurableTransformers {
|
|
|
88
93
|
ERROR: unknown;
|
|
89
94
|
};
|
|
90
95
|
}
|
|
96
|
+
export interface TransformersRuntimeMetadata {
|
|
97
|
+
__ompRuntimeNodeModules?: string;
|
|
98
|
+
__ompTransformersEntry?: string;
|
|
99
|
+
__ompCudaRepairError?: string;
|
|
100
|
+
}
|
|
101
|
+
export declare function formatOnnxRuntimeCudaDiagnostics(metadata: TransformersRuntimeMetadata, requestedDevice: string, error: unknown): Promise<string | null>;
|
|
91
102
|
/**
|
|
92
103
|
* Memoize an async runtime load so it runs at most once per process, clearing
|
|
93
104
|
* the cache on failure so a later call can retry. Each worker holds one
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type { Skill } from "../extensibility/skills";
|
|
2
2
|
import { type LocalProtocolOptions } from "../internal-urls";
|
|
3
|
-
import type { InternalResource } from "../internal-urls/types";
|
|
3
|
+
import type { InternalResource, ResolveContext } from "../internal-urls/types";
|
|
4
4
|
interface InternalUrlResolver {
|
|
5
5
|
canHandle(input: string): boolean;
|
|
6
|
-
resolve(input: string): Promise<InternalResource>;
|
|
6
|
+
resolve(input: string, context?: ResolveContext): Promise<InternalResource>;
|
|
7
7
|
}
|
|
8
8
|
export interface InternalUrlExpansionOptions {
|
|
9
9
|
skills: readonly Skill[];
|
|
@@ -324,6 +324,22 @@ export declare const patch: {
|
|
|
324
324
|
};
|
|
325
325
|
export declare const cherryPick: ((cwd: string, revision: string, signal?: AbortSignal) => Promise<void>) & {
|
|
326
326
|
abort(cwd: string, signal?: AbortSignal): Promise<void>;
|
|
327
|
+
/**
|
|
328
|
+
* Skip the current commit of an in-progress cherry-pick sequence and
|
|
329
|
+
* continue with the rest of the range. Use after {@link isEmptyError}
|
|
330
|
+
* reports the current attempt collapsed to a no-op — the alternative,
|
|
331
|
+
* `--abort`, throws away every remaining commit in the range.
|
|
332
|
+
*/
|
|
333
|
+
skip(cwd: string, signal?: AbortSignal): Promise<void>;
|
|
334
|
+
/**
|
|
335
|
+
* True when a cherry-pick failure was caused by the current commit
|
|
336
|
+
* being empty against HEAD — either redundant with an already-applied
|
|
337
|
+
* change, or auto-resolved to HEAD by a 3-way merge. Callers should
|
|
338
|
+
* `--skip` in this case to advance the sequencer rather than aborting
|
|
339
|
+
* the whole range: an empty commit is not a merge conflict, and any
|
|
340
|
+
* later commits in the range still deserve to land.
|
|
341
|
+
*/
|
|
342
|
+
isEmptyError(err: unknown): boolean;
|
|
327
343
|
};
|
|
328
344
|
export declare const stash: {
|
|
329
345
|
/** Stash working tree + index changes. Returns true when git created a new stash entry. */
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@oh-my-pi/pi-coding-agent",
|
|
4
|
-
"version": "16.3.
|
|
4
|
+
"version": "16.3.5",
|
|
5
5
|
"description": "Coding agent CLI with read, bash, edit, write tools and session management",
|
|
6
6
|
"homepage": "https://omp.sh",
|
|
7
7
|
"author": "Can Boluk",
|
|
@@ -56,17 +56,17 @@
|
|
|
56
56
|
"@agentclientprotocol/sdk": "0.25.0",
|
|
57
57
|
"@babel/parser": "^7.29.7",
|
|
58
58
|
"@mozilla/readability": "^0.6.0",
|
|
59
|
-
"@oh-my-pi/hashline": "16.3.
|
|
60
|
-
"@oh-my-pi/omp-stats": "16.3.
|
|
61
|
-
"@oh-my-pi/pi-agent-core": "16.3.
|
|
62
|
-
"@oh-my-pi/pi-ai": "16.3.
|
|
63
|
-
"@oh-my-pi/pi-catalog": "16.3.
|
|
64
|
-
"@oh-my-pi/pi-mnemopi": "16.3.
|
|
65
|
-
"@oh-my-pi/pi-natives": "16.3.
|
|
66
|
-
"@oh-my-pi/pi-tui": "16.3.
|
|
67
|
-
"@oh-my-pi/pi-utils": "16.3.
|
|
68
|
-
"@oh-my-pi/pi-wire": "16.3.
|
|
69
|
-
"@oh-my-pi/snapcompact": "16.3.
|
|
59
|
+
"@oh-my-pi/hashline": "16.3.5",
|
|
60
|
+
"@oh-my-pi/omp-stats": "16.3.5",
|
|
61
|
+
"@oh-my-pi/pi-agent-core": "16.3.5",
|
|
62
|
+
"@oh-my-pi/pi-ai": "16.3.5",
|
|
63
|
+
"@oh-my-pi/pi-catalog": "16.3.5",
|
|
64
|
+
"@oh-my-pi/pi-mnemopi": "16.3.5",
|
|
65
|
+
"@oh-my-pi/pi-natives": "16.3.5",
|
|
66
|
+
"@oh-my-pi/pi-tui": "16.3.5",
|
|
67
|
+
"@oh-my-pi/pi-utils": "16.3.5",
|
|
68
|
+
"@oh-my-pi/pi-wire": "16.3.5",
|
|
69
|
+
"@oh-my-pi/snapcompact": "16.3.5",
|
|
70
70
|
"@opentelemetry/api": "^1.9.1",
|
|
71
71
|
"@opentelemetry/context-async-hooks": "^2.7.1",
|
|
72
72
|
"@opentelemetry/exporter-trace-otlp-proto": "^0.218.0",
|
|
@@ -222,8 +222,19 @@ async function runLocalLogin(provider: OAuthProvider): Promise<void> {
|
|
|
222
222
|
// for non-paste-code providers, so this is defense-in-depth on the same gate.
|
|
223
223
|
const usesManualInput = PASTE_CODE_LOGIN_PROVIDERS.has(provider);
|
|
224
224
|
await storage.login(provider, {
|
|
225
|
-
onAuth({ url, instructions }) {
|
|
226
|
-
process.stdout.write(
|
|
225
|
+
onAuth({ url, launchUrl, instructions }) {
|
|
226
|
+
process.stdout.write("\nOpen this URL in your browser:\n");
|
|
227
|
+
// Full URL first so the CLI works from any machine, including SSH
|
|
228
|
+
// sessions where a `launchUrl` (loopback `/launch` on the OMP
|
|
229
|
+
// host) would resolve against the caller's browser and fail.
|
|
230
|
+
// Headless capture is unaffected: it reads the first URL line.
|
|
231
|
+
process.stdout.write(`${url}\n`);
|
|
232
|
+
if (launchUrl && launchUrl !== url) {
|
|
233
|
+
// Local shortcut for the machine running OMP. Terminals or
|
|
234
|
+
// screen-scrapers narrower than the full URL still get an
|
|
235
|
+
// unbroken copy target here.
|
|
236
|
+
process.stdout.write(`Local shortcut (this machine only): ${launchUrl}\n`);
|
|
237
|
+
}
|
|
227
238
|
if (instructions) process.stdout.write(`${instructions}\n`);
|
|
228
239
|
process.stdout.write("\n");
|
|
229
240
|
},
|
|
@@ -35,12 +35,19 @@ function writeLine(text = ""): void {
|
|
|
35
35
|
process.stdout.write(`${text}\n`);
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
const ACTIONABLE_DOWNLOAD_ERROR_LINE = /PI_TINY_|CUDA|cuDNN|cudnn|libcudnn|tiny-title-runtime|onnxruntime-node/i;
|
|
39
|
+
|
|
38
40
|
function downloadErrorSummary(error: string | undefined): string | undefined {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
41
|
+
const lines =
|
|
42
|
+
error
|
|
43
|
+
?.split(/\r?\n/)
|
|
44
|
+
.map(line => line.trim().replace(/^Error:\s*/, ""))
|
|
45
|
+
.filter(line => line.length > 0) ?? [];
|
|
46
|
+
const first = lines[0];
|
|
47
|
+
if (!first) return undefined;
|
|
48
|
+
const details = lines.slice(1).filter(line => ACTIONABLE_DOWNLOAD_ERROR_LINE.test(line));
|
|
49
|
+
if (details.length === 0) return first;
|
|
50
|
+
return [first, ...details].join("\n");
|
|
44
51
|
}
|
|
45
52
|
|
|
46
53
|
export function resolveModels(model: string | undefined): TinyLocalModelKey[] {
|
package/src/hindsight/content.ts
CHANGED
|
@@ -25,6 +25,7 @@ const LEGACY_HINDSIGHT_MEMORIES_REGEX = /<hindsight_memories>[\s\S]*?<\/hindsigh
|
|
|
25
25
|
const LEGACY_RELEVANT_MEMORIES_REGEX = /<relevant_memories>[\s\S]*?<\/relevant_memories>/g;
|
|
26
26
|
const MENTAL_MODELS_REGEX = /<mental_models>[\s\S]*?<\/mental_models>/g;
|
|
27
27
|
|
|
28
|
+
const RETENTION_PROTOCOL_MARKER_REGEX = /^\[(?:role:\s*[-_a-zA-Z0-9]+|[-_a-zA-Z0-9]+:end)\]$/;
|
|
28
29
|
/**
|
|
29
30
|
* Strip `<memories>`, `<mental_models>`, and legacy memory blocks.
|
|
30
31
|
*
|
|
@@ -205,6 +206,32 @@ function formatRetentionMessages(messages: HindsightMessage[]): RetentionTranscr
|
|
|
205
206
|
return { transcript, messageCount: parts.length };
|
|
206
207
|
}
|
|
207
208
|
|
|
209
|
+
function formatEmbeddableRetentionMessages(messages: HindsightMessage[]): RetentionTranscript {
|
|
210
|
+
const parts: string[] = [];
|
|
211
|
+
for (const msg of messages) {
|
|
212
|
+
const content = stripRetentionProtocolMarkers(stripMemoryTags(msg.content)).trim();
|
|
213
|
+
if (!hasSubstantiveContent(content)) continue;
|
|
214
|
+
parts.push(content);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (parts.length === 0) return { transcript: null, messageCount: 0 };
|
|
218
|
+
|
|
219
|
+
const transcript = parts.join("\n\n");
|
|
220
|
+
if (transcript.trim().length < 10) return { transcript: null, messageCount: 0 };
|
|
221
|
+
|
|
222
|
+
return { transcript, messageCount: parts.length };
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/** Remove retention framing lines from a stored coding-agent episode transcript. */
|
|
226
|
+
export function stripRetentionProtocolMarkers(content: string): string {
|
|
227
|
+
return content
|
|
228
|
+
.split(/\r?\n/)
|
|
229
|
+
.filter(line => !RETENTION_PROTOCOL_MARKER_REGEX.test(line.trim()))
|
|
230
|
+
.join("\n")
|
|
231
|
+
.replace(/\n{3,}/g, "\n\n")
|
|
232
|
+
.trim();
|
|
233
|
+
}
|
|
234
|
+
|
|
208
235
|
export function prepareRetentionTranscript(
|
|
209
236
|
messages: HindsightMessage[],
|
|
210
237
|
retainFullWindow = false,
|
|
@@ -229,6 +256,10 @@ export function prepareRetentionTranscript(
|
|
|
229
256
|
return formatRetentionMessages(targetMessages);
|
|
230
257
|
}
|
|
231
258
|
|
|
259
|
+
/** Format all retention messages without protocol markers for embedding, FTS, and recall display. */
|
|
260
|
+
export function prepareEmbeddableRetentionTranscript(messages: HindsightMessage[]): RetentionTranscript {
|
|
261
|
+
return formatEmbeddableRetentionMessages(messages);
|
|
262
|
+
}
|
|
232
263
|
/** Format only user-authored messages for memory fact/entity extraction. */
|
|
233
264
|
export function prepareUserRetentionTranscript(messages: HindsightMessage[]): RetentionTranscript {
|
|
234
265
|
return formatRetentionMessages(messages.filter(message => message.role === "user"));
|
|
@@ -15,75 +15,119 @@ import { isEnoent } from "@oh-my-pi/pi-utils";
|
|
|
15
15
|
import { artifactsDirsFromRegistry } from "./registry-helpers";
|
|
16
16
|
import type { InternalResource, InternalUrl, ProtocolHandler, ResolveContext, UrlCompletion } from "./types";
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
readonly scheme = "artifact";
|
|
20
|
-
readonly immutable = true;
|
|
18
|
+
const MAX_INLINE_ARTIFACT_BYTES = 8 * 1024 * 1024;
|
|
21
19
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
20
|
+
/** Filesystem location for a session artifact, resolved without materializing its content. */
|
|
21
|
+
export interface ResolvedArtifactFile {
|
|
22
|
+
id: string;
|
|
23
|
+
path: string;
|
|
24
|
+
size: number;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function parseArtifactId(url: InternalUrl): string {
|
|
28
|
+
const id = url.rawHost || url.hostname;
|
|
29
|
+
if (!id) {
|
|
30
|
+
throw new Error("artifact:// URL requires a numeric ID: artifact://0");
|
|
31
|
+
}
|
|
32
|
+
if (!/^\d+$/.test(id)) {
|
|
33
|
+
throw new Error(`artifact:// ID must be numeric, got: ${id}`);
|
|
34
|
+
}
|
|
35
|
+
return id;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Resolve an `artifact://` URL to its backing file without reading artifact bytes. */
|
|
39
|
+
export async function resolveArtifactFile(url: InternalUrl, context?: ResolveContext): Promise<ResolvedArtifactFile> {
|
|
40
|
+
const id = parseArtifactId(url);
|
|
41
|
+
|
|
42
|
+
// Artifact ids are per-session counters; in multi-session hosts the same
|
|
43
|
+
// id exists in several dirs. Pin resolution to the calling session's
|
|
44
|
+
// artifacts dir first so `artifact://3` means *this* session's #3.
|
|
45
|
+
const dirs = artifactsDirsFromRegistry();
|
|
46
|
+
const pinnedDir = context?.localProtocolOptions?.getArtifactsDir?.() ?? null;
|
|
47
|
+
if (pinnedDir) {
|
|
48
|
+
const pinnedIndex = dirs.indexOf(pinnedDir);
|
|
49
|
+
if (pinnedIndex >= 0) dirs.splice(pinnedIndex, 1);
|
|
50
|
+
dirs.unshift(pinnedDir);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (dirs.length === 0) {
|
|
54
|
+
throw new Error("No session - artifacts unavailable");
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
let foundPath: string | undefined;
|
|
58
|
+
let anyDirExists = false;
|
|
59
|
+
const availableIds = new Set<string>();
|
|
60
|
+
|
|
61
|
+
for (const dir of dirs) {
|
|
62
|
+
let files: string[];
|
|
63
|
+
try {
|
|
64
|
+
files = await fs.readdir(dir);
|
|
65
|
+
anyDirExists = true;
|
|
66
|
+
} catch (err) {
|
|
67
|
+
if (isEnoent(err)) continue;
|
|
68
|
+
throw err;
|
|
26
69
|
}
|
|
27
|
-
|
|
28
|
-
|
|
70
|
+
const match = files.find(f => f.startsWith(`${id}.`));
|
|
71
|
+
if (match) {
|
|
72
|
+
foundPath = path.join(dir, match);
|
|
73
|
+
break;
|
|
29
74
|
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
// artifacts dir first so `artifact://3` means *this* session's #3.
|
|
34
|
-
const dirs = artifactsDirsFromRegistry();
|
|
35
|
-
const pinnedDir = context?.localProtocolOptions?.getArtifactsDir?.() ?? null;
|
|
36
|
-
if (pinnedDir) {
|
|
37
|
-
const pinnedIndex = dirs.indexOf(pinnedDir);
|
|
38
|
-
if (pinnedIndex >= 0) dirs.splice(pinnedIndex, 1);
|
|
39
|
-
dirs.unshift(pinnedDir);
|
|
75
|
+
for (const f of files) {
|
|
76
|
+
const m = f.match(/^(\d+)\./);
|
|
77
|
+
if (m) availableIds.add(m[1]);
|
|
40
78
|
}
|
|
79
|
+
}
|
|
41
80
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
81
|
+
if (!anyDirExists) {
|
|
82
|
+
throw new Error("No artifacts directory found");
|
|
83
|
+
}
|
|
45
84
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
const
|
|
85
|
+
if (!foundPath) {
|
|
86
|
+
const sorted = [...availableIds].sort((a, b) => Number(a) - Number(b));
|
|
87
|
+
const availableStr = sorted.length > 0 ? sorted.join(", ") : "none";
|
|
88
|
+
throw new Error(`Artifact ${id} not found. Available: ${availableStr}`);
|
|
89
|
+
}
|
|
49
90
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
}
|
|
64
|
-
for (const f of files) {
|
|
65
|
-
const m = f.match(/^(\d+)\./);
|
|
66
|
-
if (m) availableIds.add(m[1]);
|
|
67
|
-
}
|
|
68
|
-
}
|
|
91
|
+
const stat = await Bun.file(foundPath).stat();
|
|
92
|
+
if (stat.isDirectory()) {
|
|
93
|
+
throw new Error(`Artifact ${id} resolved to a directory, not a file`);
|
|
94
|
+
}
|
|
95
|
+
return { id, path: foundPath, size: stat.size };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export class ArtifactProtocolHandler implements ProtocolHandler {
|
|
99
|
+
readonly scheme = "artifact";
|
|
100
|
+
readonly immutable = true;
|
|
101
|
+
|
|
102
|
+
async resolve(url: InternalUrl, context?: ResolveContext): Promise<InternalResource> {
|
|
103
|
+
const artifact = await resolveArtifactFile(url, context);
|
|
69
104
|
|
|
70
|
-
|
|
71
|
-
|
|
105
|
+
// Path-only callers (search/grep, bash URL expansion) never touch the
|
|
106
|
+
// artifact bytes. Return the resource shape so those flows keep working
|
|
107
|
+
// on artifacts of any size — only content materialization is gated.
|
|
108
|
+
if (context?.pathOnly) {
|
|
109
|
+
return {
|
|
110
|
+
url: url.href,
|
|
111
|
+
content: "",
|
|
112
|
+
contentType: "text/plain",
|
|
113
|
+
size: artifact.size,
|
|
114
|
+
sourcePath: artifact.path,
|
|
115
|
+
};
|
|
72
116
|
}
|
|
73
117
|
|
|
74
|
-
if (
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
118
|
+
if (artifact.size > MAX_INLINE_ARTIFACT_BYTES) {
|
|
119
|
+
throw new Error(
|
|
120
|
+
`Artifact ${artifact.id} is ${artifact.size} bytes; full internal resolution is blocked. Use read selectors such as artifact://${artifact.id}:1-3000 or artifact://${artifact.id}:raw:1-3000, and use the artifact file path for search/copy workflows: ${artifact.path}`,
|
|
121
|
+
);
|
|
78
122
|
}
|
|
79
123
|
|
|
80
|
-
const content = await Bun.file(
|
|
124
|
+
const content = await Bun.file(artifact.path).text();
|
|
81
125
|
return {
|
|
82
126
|
url: url.href,
|
|
83
127
|
content,
|
|
84
128
|
contentType: "text/plain",
|
|
85
|
-
size:
|
|
86
|
-
sourcePath:
|
|
129
|
+
size: artifact.size,
|
|
130
|
+
sourcePath: artifact.path,
|
|
87
131
|
};
|
|
88
132
|
}
|
|
89
133
|
|
|
@@ -107,6 +107,16 @@ export interface ResolveContext {
|
|
|
107
107
|
* reject directory resources, so they never need the listing.
|
|
108
108
|
*/
|
|
109
109
|
skipDirectoryListing?: boolean;
|
|
110
|
+
/**
|
|
111
|
+
* When set, handlers that would otherwise materialize expensive content
|
|
112
|
+
* (e.g. reading a multi-MiB artifact into memory just to expose its
|
|
113
|
+
* `sourcePath`) may return the resource shape without content. Callers
|
|
114
|
+
* that only need `sourcePath` — search/grep, bash URL expansion — pass
|
|
115
|
+
* this so a large `artifact://` still resolves to its backing file
|
|
116
|
+
* without OOM risk. Handlers that cannot separate path from content
|
|
117
|
+
* ignore the flag.
|
|
118
|
+
*/
|
|
119
|
+
pathOnly?: boolean;
|
|
110
120
|
}
|
|
111
121
|
|
|
112
122
|
/**
|
package/src/lsp/config.ts
CHANGED
|
@@ -201,6 +201,21 @@ export function hasRootMarkers(cwd: string, markers: string[]): boolean {
|
|
|
201
201
|
return false;
|
|
202
202
|
}
|
|
203
203
|
|
|
204
|
+
/**
|
|
205
|
+
* Check whether any ancestor directory of a file is an LSP project root.
|
|
206
|
+
*/
|
|
207
|
+
export function hasRootMarkerAncestor(filePath: string, markers: string[]): boolean {
|
|
208
|
+
if (markers.length === 0) return false;
|
|
209
|
+
|
|
210
|
+
let dir = path.dirname(path.resolve(filePath));
|
|
211
|
+
while (true) {
|
|
212
|
+
if (hasRootMarkers(dir, markers)) return true;
|
|
213
|
+
const parent = path.dirname(dir);
|
|
214
|
+
if (parent === dir) return false;
|
|
215
|
+
dir = parent;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
204
219
|
// =============================================================================
|
|
205
220
|
// Local Binary Resolution
|
|
206
221
|
// =============================================================================
|