@oh-my-pi/pi-coding-agent 15.0.1 → 15.0.2
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 +38 -0
- package/package.json +8 -8
- package/src/commands/commit.ts +10 -0
- package/src/config/model-registry.ts +31 -1
- package/src/config/settings-schema.ts +11 -0
- package/src/discovery/claude-plugins.ts +19 -7
- package/src/eval/py/runner.py +42 -11
- package/src/eval/py/runtime.ts +1 -0
- package/src/extensibility/extensions/get-commands-handler.ts +77 -0
- package/src/extensibility/plugins/legacy-pi-compat.ts +48 -31
- package/src/hashline/input.ts +2 -1
- package/src/hashline/parser.ts +27 -3
- package/src/internal-urls/docs-index.generated.ts +8 -8
- package/src/internal-urls/router.ts +8 -0
- package/src/internal-urls/types.ts +21 -0
- package/src/lsp/config.ts +15 -6
- package/src/lsp/defaults.json +6 -2
- package/src/modes/acp/acp-agent.ts +248 -50
- package/src/modes/components/status-line/segments.ts +38 -4
- package/src/modes/controllers/extension-ui-controller.ts +3 -2
- package/src/modes/rpc/host-uris.ts +235 -0
- package/src/modes/rpc/rpc-mode.ts +27 -1
- package/src/modes/rpc/rpc-types.ts +57 -0
- package/src/modes/runtime-init.ts +2 -1
- package/src/modes/theme/defaults/dark-poimandres.json +1 -0
- package/src/modes/theme/defaults/light-poimandres.json +1 -0
- package/src/modes/theme/theme.ts +6 -0
- package/src/prompts/tools/github.md +4 -4
- package/src/prompts/tools/hashline.md +22 -26
- package/src/prompts/tools/read.md +55 -37
- package/src/task/discovery.ts +5 -2
- package/src/task/executor.ts +2 -1
- package/src/tools/bash-command-fixup.ts +47 -0
- package/src/tools/bash.ts +39 -15
- package/src/tools/browser/render.ts +2 -2
- package/src/tools/eval.ts +10 -2
- package/src/tools/gh.ts +37 -4
- package/src/tools/job.ts +16 -7
- package/src/tools/output-meta.ts +26 -0
- package/src/tools/read.ts +32 -4
- package/src/tools/ssh.ts +3 -2
- package/src/tools/write.ts +20 -0
- package/src/web/search/providers/anthropic.ts +5 -0
- package/src/web/search/providers/exa.ts +3 -0
- package/src/web/search/providers/gemini.ts +5 -0
- package/src/web/search/providers/jina.ts +5 -2
- package/src/web/search/providers/zai.ts +5 -2
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import { Snowflake } from "@oh-my-pi/pi-utils";
|
|
2
|
+
import { InternalUrlRouter } from "../../internal-urls";
|
|
3
|
+
import type {
|
|
4
|
+
InternalResource,
|
|
5
|
+
InternalUrl,
|
|
6
|
+
ProtocolHandler,
|
|
7
|
+
ResolveContext,
|
|
8
|
+
WriteContext,
|
|
9
|
+
} from "../../internal-urls/types";
|
|
10
|
+
import type {
|
|
11
|
+
RpcHostUriCancelRequest,
|
|
12
|
+
RpcHostUriRequest,
|
|
13
|
+
RpcHostUriResult,
|
|
14
|
+
RpcHostUriSchemeDefinition,
|
|
15
|
+
} from "./rpc-types";
|
|
16
|
+
|
|
17
|
+
type RpcHostUriOutput = (frame: RpcHostUriRequest | RpcHostUriCancelRequest) => void;
|
|
18
|
+
|
|
19
|
+
type PendingUriRequest = {
|
|
20
|
+
operation: "read" | "write";
|
|
21
|
+
url: string;
|
|
22
|
+
resolve: (frame: RpcHostUriResult) => void;
|
|
23
|
+
reject: (error: Error) => void;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/** Type guard for inbound `host_uri_result` frames coming from the host. */
|
|
27
|
+
export function isRpcHostUriResult(value: unknown): value is RpcHostUriResult {
|
|
28
|
+
if (!value || typeof value !== "object") return false;
|
|
29
|
+
const frame = value as { type?: unknown; id?: unknown };
|
|
30
|
+
return frame.type === "host_uri_result" && typeof frame.id === "string";
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* One handler instance per host-registered scheme. Delegates reads and (when
|
|
35
|
+
* the scheme was registered as writable) writes to the bridge, which serializes
|
|
36
|
+
* them over the RPC transport.
|
|
37
|
+
*/
|
|
38
|
+
class RpcHostUriProtocolHandler implements ProtocolHandler {
|
|
39
|
+
readonly scheme: string;
|
|
40
|
+
readonly immutable: boolean;
|
|
41
|
+
readonly write?: (url: InternalUrl, content: string, context?: WriteContext) => Promise<void>;
|
|
42
|
+
readonly #bridge: RpcHostUriBridge;
|
|
43
|
+
|
|
44
|
+
constructor(definition: RpcHostUriSchemeDefinition, bridge: RpcHostUriBridge) {
|
|
45
|
+
this.scheme = definition.scheme;
|
|
46
|
+
this.immutable = definition.immutable === true;
|
|
47
|
+
this.#bridge = bridge;
|
|
48
|
+
if (definition.writable === true) {
|
|
49
|
+
this.write = (url, content, context) => this.#bridge.requestWrite(this.scheme, url, content, context);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
resolve(url: InternalUrl, context?: ResolveContext): Promise<InternalResource> {
|
|
54
|
+
return this.#bridge.requestRead(this.scheme, url, context);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Bidirectional bridge that lets the RPC host own a set of URI schemes.
|
|
60
|
+
*
|
|
61
|
+
* The host registers schemes via `set_host_uri_schemes`; the bridge installs
|
|
62
|
+
* a `RpcHostUriProtocolHandler` per scheme into the process-global
|
|
63
|
+
* {@link InternalUrlRouter}. Reads land on the read tool through the existing
|
|
64
|
+
* router; writes are intercepted by the write tool and dispatched through
|
|
65
|
+
* `requestWrite`.
|
|
66
|
+
*/
|
|
67
|
+
export class RpcHostUriBridge {
|
|
68
|
+
#output: RpcHostUriOutput;
|
|
69
|
+
#router: InternalUrlRouter;
|
|
70
|
+
#definitions = new Map<string, RpcHostUriSchemeDefinition>();
|
|
71
|
+
#pending = new Map<string, PendingUriRequest>();
|
|
72
|
+
|
|
73
|
+
constructor(output: RpcHostUriOutput, router: InternalUrlRouter = InternalUrlRouter.instance()) {
|
|
74
|
+
this.#output = output;
|
|
75
|
+
this.#router = router;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
getSchemes(): string[] {
|
|
79
|
+
return Array.from(this.#definitions.keys());
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Replace the registered set of host URI schemes. Previously registered
|
|
84
|
+
* schemes that no longer appear in the new set are unregistered from the
|
|
85
|
+
* router; surviving and new schemes get fresh handler instances.
|
|
86
|
+
*/
|
|
87
|
+
setSchemes(schemes: RpcHostUriSchemeDefinition[]): string[] {
|
|
88
|
+
const normalized = new Map<string, RpcHostUriSchemeDefinition>();
|
|
89
|
+
for (const raw of schemes) {
|
|
90
|
+
const scheme = typeof raw?.scheme === "string" ? raw.scheme.trim().toLowerCase() : "";
|
|
91
|
+
if (!scheme) {
|
|
92
|
+
throw new Error("Host URI scheme must be a non-empty string");
|
|
93
|
+
}
|
|
94
|
+
if (!/^[a-z][a-z0-9+.-]*$/.test(scheme)) {
|
|
95
|
+
throw new Error(`Host URI scheme contains invalid characters: ${raw.scheme}`);
|
|
96
|
+
}
|
|
97
|
+
normalized.set(scheme, {
|
|
98
|
+
scheme,
|
|
99
|
+
description: typeof raw.description === "string" ? raw.description : undefined,
|
|
100
|
+
writable: raw.writable === true,
|
|
101
|
+
immutable: raw.immutable === true,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
for (const previous of this.#definitions.keys()) {
|
|
106
|
+
if (!normalized.has(previous)) {
|
|
107
|
+
this.#router.unregister(previous);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
for (const definition of normalized.values()) {
|
|
111
|
+
this.#router.register(new RpcHostUriProtocolHandler(definition, this));
|
|
112
|
+
}
|
|
113
|
+
this.#definitions = normalized;
|
|
114
|
+
return Array.from(normalized.keys());
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Unregister every host scheme from the router and reject any in-flight
|
|
119
|
+
* requests. Called on RPC shutdown to keep the global router clean for
|
|
120
|
+
* subsequent sessions in the same process (used by tests).
|
|
121
|
+
*/
|
|
122
|
+
clear(message: string = "Host URI bridge shut down"): void {
|
|
123
|
+
for (const scheme of this.#definitions.keys()) {
|
|
124
|
+
this.#router.unregister(scheme);
|
|
125
|
+
}
|
|
126
|
+
this.#definitions.clear();
|
|
127
|
+
this.rejectAllPending(message);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** Resolve a pending request by id; called by `rpc-mode` on inbound results. */
|
|
131
|
+
handleResult(frame: RpcHostUriResult): boolean {
|
|
132
|
+
const pending = this.#pending.get(frame.id);
|
|
133
|
+
if (!pending) return false;
|
|
134
|
+
this.#pending.delete(frame.id);
|
|
135
|
+
pending.resolve(frame);
|
|
136
|
+
return true;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
rejectAllPending(message: string): void {
|
|
140
|
+
const error = new Error(message);
|
|
141
|
+
const pending = Array.from(this.#pending.values());
|
|
142
|
+
this.#pending.clear();
|
|
143
|
+
for (const entry of pending) {
|
|
144
|
+
entry.reject(error);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
async requestRead(scheme: string, url: InternalUrl, context?: ResolveContext): Promise<InternalResource> {
|
|
149
|
+
const result = await this.#dispatch("read", url.href, undefined, context?.signal);
|
|
150
|
+
if (result.isError) {
|
|
151
|
+
throw new Error(result.error || result.content || `Host URI read failed for ${url.href}`);
|
|
152
|
+
}
|
|
153
|
+
const content = result.content ?? "";
|
|
154
|
+
const contentType = result.contentType ?? "text/plain";
|
|
155
|
+
const definition = this.#definitions.get(scheme);
|
|
156
|
+
return {
|
|
157
|
+
url: url.href,
|
|
158
|
+
content,
|
|
159
|
+
contentType,
|
|
160
|
+
size: Buffer.byteLength(content, "utf-8"),
|
|
161
|
+
notes: result.notes && result.notes.length > 0 ? [...result.notes] : undefined,
|
|
162
|
+
immutable: result.immutable ?? definition?.immutable === true,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
async requestWrite(_scheme: string, url: InternalUrl, content: string, context?: WriteContext): Promise<void> {
|
|
167
|
+
const result = await this.#dispatch("write", url.href, content, context?.signal);
|
|
168
|
+
if (result.isError) {
|
|
169
|
+
throw new Error(result.error || result.content || `Host URI write failed for ${url.href}`);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
#dispatch(
|
|
174
|
+
operation: "read" | "write",
|
|
175
|
+
url: string,
|
|
176
|
+
content: string | undefined,
|
|
177
|
+
signal: AbortSignal | undefined,
|
|
178
|
+
): Promise<RpcHostUriResult> {
|
|
179
|
+
if (signal?.aborted) {
|
|
180
|
+
return Promise.reject(new Error(`Host URI ${operation} for ${url} was aborted`));
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const id = Snowflake.next() as string;
|
|
184
|
+
const { promise, resolve, reject } = Promise.withResolvers<RpcHostUriResult>();
|
|
185
|
+
let settled = false;
|
|
186
|
+
|
|
187
|
+
const cleanup = () => {
|
|
188
|
+
signal?.removeEventListener("abort", onAbort);
|
|
189
|
+
this.#pending.delete(id);
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
const onAbort = () => {
|
|
193
|
+
if (settled) return;
|
|
194
|
+
settled = true;
|
|
195
|
+
cleanup();
|
|
196
|
+
this.#output({
|
|
197
|
+
type: "host_uri_cancel",
|
|
198
|
+
id: Snowflake.next() as string,
|
|
199
|
+
targetId: id,
|
|
200
|
+
});
|
|
201
|
+
reject(new Error(`Host URI ${operation} for ${url} was aborted`));
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
205
|
+
this.#pending.set(id, {
|
|
206
|
+
operation,
|
|
207
|
+
url,
|
|
208
|
+
resolve: frame => {
|
|
209
|
+
if (settled) return;
|
|
210
|
+
settled = true;
|
|
211
|
+
cleanup();
|
|
212
|
+
resolve(frame);
|
|
213
|
+
},
|
|
214
|
+
reject: err => {
|
|
215
|
+
if (settled) return;
|
|
216
|
+
settled = true;
|
|
217
|
+
cleanup();
|
|
218
|
+
reject(err);
|
|
219
|
+
},
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
const frame: RpcHostUriRequest = {
|
|
223
|
+
type: "host_uri_request",
|
|
224
|
+
id,
|
|
225
|
+
operation,
|
|
226
|
+
url,
|
|
227
|
+
};
|
|
228
|
+
if (operation === "write") {
|
|
229
|
+
frame.content = content ?? "";
|
|
230
|
+
}
|
|
231
|
+
this.#output(frame);
|
|
232
|
+
|
|
233
|
+
return promise;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
@@ -21,6 +21,7 @@ import { type Theme, theme } from "../../modes/theme/theme";
|
|
|
21
21
|
import type { AgentSession } from "../../session/agent-session";
|
|
22
22
|
import { initializeExtensions } from "../runtime-init";
|
|
23
23
|
import { isRpcHostToolResult, isRpcHostToolUpdate, RpcHostToolBridge } from "./host-tools";
|
|
24
|
+
import { isRpcHostUriResult, RpcHostUriBridge } from "./host-uris";
|
|
24
25
|
import type {
|
|
25
26
|
RpcCommand,
|
|
26
27
|
RpcExtensionUIRequest,
|
|
@@ -28,6 +29,8 @@ import type {
|
|
|
28
29
|
RpcHostToolCallRequest,
|
|
29
30
|
RpcHostToolCancelRequest,
|
|
30
31
|
RpcHostToolDefinition,
|
|
32
|
+
RpcHostUriCancelRequest,
|
|
33
|
+
RpcHostUriRequest,
|
|
31
34
|
RpcResponse,
|
|
32
35
|
RpcSessionState,
|
|
33
36
|
} from "./rpc-types";
|
|
@@ -41,7 +44,14 @@ export type PendingExtensionRequest = {
|
|
|
41
44
|
};
|
|
42
45
|
|
|
43
46
|
type RpcOutput = (
|
|
44
|
-
obj:
|
|
47
|
+
obj:
|
|
48
|
+
| RpcResponse
|
|
49
|
+
| RpcExtensionUIRequest
|
|
50
|
+
| RpcHostToolCallRequest
|
|
51
|
+
| RpcHostToolCancelRequest
|
|
52
|
+
| RpcHostUriRequest
|
|
53
|
+
| RpcHostUriCancelRequest
|
|
54
|
+
| object,
|
|
45
55
|
) => void;
|
|
46
56
|
|
|
47
57
|
function normalizeHostToolDefinitions(tools: RpcHostToolDefinition[]): RpcHostToolDefinition[] {
|
|
@@ -188,6 +198,7 @@ export async function runRpcMode(
|
|
|
188
198
|
|
|
189
199
|
const pendingExtensionRequests = new Map<string, PendingExtensionRequest>();
|
|
190
200
|
const hostToolBridge = new RpcHostToolBridge(output);
|
|
201
|
+
const hostUriBridge = new RpcHostUriBridge(output);
|
|
191
202
|
|
|
192
203
|
// Shutdown request flag (wrapped in object to allow mutation with const)
|
|
193
204
|
const shutdownState = { requested: false };
|
|
@@ -533,6 +544,15 @@ export async function runRpcMode(
|
|
|
533
544
|
return success(id, "set_host_tools", { toolNames: tools.map(tool => tool.name) });
|
|
534
545
|
}
|
|
535
546
|
|
|
547
|
+
case "set_host_uri_schemes": {
|
|
548
|
+
try {
|
|
549
|
+
const schemes = hostUriBridge.setSchemes(command.schemes);
|
|
550
|
+
return success(id, "set_host_uri_schemes", { schemes });
|
|
551
|
+
} catch (err) {
|
|
552
|
+
return error(id, "set_host_uri_schemes", err instanceof Error ? err.message : String(err));
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
|
|
536
556
|
// =================================================================
|
|
537
557
|
// Model
|
|
538
558
|
// =================================================================
|
|
@@ -807,6 +827,11 @@ export async function runRpcMode(
|
|
|
807
827
|
continue;
|
|
808
828
|
}
|
|
809
829
|
|
|
830
|
+
if (isRpcHostUriResult(parsed)) {
|
|
831
|
+
hostUriBridge.handleResult(parsed);
|
|
832
|
+
continue;
|
|
833
|
+
}
|
|
834
|
+
|
|
810
835
|
// Handle regular commands
|
|
811
836
|
const command = parsed as RpcCommand;
|
|
812
837
|
const response = await handleCommand(command);
|
|
@@ -821,5 +846,6 @@ export async function runRpcMode(
|
|
|
821
846
|
|
|
822
847
|
// stdin closed — RPC client is gone, exit cleanly
|
|
823
848
|
hostToolBridge.rejectAllPending("RPC client disconnected before host tool execution completed");
|
|
849
|
+
hostUriBridge.clear("RPC client disconnected before host URI request completed");
|
|
824
850
|
process.exit(0);
|
|
825
851
|
}
|
|
@@ -29,6 +29,7 @@ export type RpcCommand =
|
|
|
29
29
|
| { id?: string; type: "get_state" }
|
|
30
30
|
| { id?: string; type: "set_todos"; phases: TodoPhase[] }
|
|
31
31
|
| { id?: string; type: "set_host_tools"; tools: RpcHostToolDefinition[] }
|
|
32
|
+
| { id?: string; type: "set_host_uri_schemes"; schemes: RpcHostUriSchemeDefinition[] }
|
|
32
33
|
|
|
33
34
|
// Model
|
|
34
35
|
| { id?: string; type: "set_model"; provider: string; modelId: string }
|
|
@@ -121,6 +122,7 @@ export type RpcResponse =
|
|
|
121
122
|
| { id?: string; type: "response"; command: "get_state"; success: true; data: RpcSessionState }
|
|
122
123
|
| { id?: string; type: "response"; command: "set_todos"; success: true; data: { todoPhases: TodoPhase[] } }
|
|
123
124
|
| { id?: string; type: "response"; command: "set_host_tools"; success: true; data: { toolNames: string[] } }
|
|
125
|
+
| { id?: string; type: "response"; command: "set_host_uri_schemes"; success: true; data: { schemes: string[] } }
|
|
124
126
|
|
|
125
127
|
// Model
|
|
126
128
|
| {
|
|
@@ -304,6 +306,61 @@ export interface RpcHostToolResult {
|
|
|
304
306
|
isError?: boolean;
|
|
305
307
|
}
|
|
306
308
|
|
|
309
|
+
// ============================================================================
|
|
310
|
+
// Host URI Frames (bidirectional)
|
|
311
|
+
// ============================================================================
|
|
312
|
+
|
|
313
|
+
export interface RpcHostUriSchemeDefinition {
|
|
314
|
+
/** URL scheme without trailing `://` (e.g. `db`, `notion`). */
|
|
315
|
+
scheme: string;
|
|
316
|
+
/** Optional human-readable description for logs/diagnostics. */
|
|
317
|
+
description?: string;
|
|
318
|
+
/** When true, the write tool is allowed to dispatch writes to this scheme. */
|
|
319
|
+
writable?: boolean;
|
|
320
|
+
/** When true, downstream callers suppress hashline anchors for resolved content. */
|
|
321
|
+
immutable?: boolean;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
export type RpcHostUriOperation = "read" | "write";
|
|
325
|
+
|
|
326
|
+
/** Emitted by the RPC server when it needs the host to satisfy a URI operation. */
|
|
327
|
+
export interface RpcHostUriRequest {
|
|
328
|
+
type: "host_uri_request";
|
|
329
|
+
id: string;
|
|
330
|
+
operation: RpcHostUriOperation;
|
|
331
|
+
url: string;
|
|
332
|
+
/** Present for write operations. */
|
|
333
|
+
content?: string;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/** Emitted by the RPC server when a pending URI request should be aborted. */
|
|
337
|
+
export interface RpcHostUriCancelRequest {
|
|
338
|
+
type: "host_uri_cancel";
|
|
339
|
+
id: string;
|
|
340
|
+
targetId: string;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/** Sent by the host to complete a pending URI request. */
|
|
344
|
+
export interface RpcHostUriResult {
|
|
345
|
+
type: "host_uri_result";
|
|
346
|
+
id: string;
|
|
347
|
+
/**
|
|
348
|
+
* Required for successful `read` results. Ignored for `write` success.
|
|
349
|
+
* Set on errors when a textual explanation accompanies `isError`.
|
|
350
|
+
*/
|
|
351
|
+
content?: string;
|
|
352
|
+
/** Defaults to `text/plain` when omitted. */
|
|
353
|
+
contentType?: "text/markdown" | "application/json" | "text/plain";
|
|
354
|
+
/** Optional resolution notes propagated to the read tool. */
|
|
355
|
+
notes?: string[];
|
|
356
|
+
/** Overrides the scheme-level `immutable` flag for this single resolution. */
|
|
357
|
+
immutable?: boolean;
|
|
358
|
+
/** When true, surface the result content as an error to the caller. */
|
|
359
|
+
isError?: boolean;
|
|
360
|
+
/** Optional error message; preferred over `content` for error surfacing. */
|
|
361
|
+
error?: string;
|
|
362
|
+
}
|
|
363
|
+
|
|
307
364
|
// ============================================================================
|
|
308
365
|
// Extension UI Commands (stdin)
|
|
309
366
|
// ============================================================================
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* caller-supplied hooks.
|
|
8
8
|
*/
|
|
9
9
|
import { runExtensionCompact, runExtensionSetModel } from "../extensibility/extensions/compact-handler";
|
|
10
|
+
import { getSessionSlashCommands } from "../extensibility/extensions/get-commands-handler";
|
|
10
11
|
import type { ExtensionError, ExtensionUIContext } from "../extensibility/extensions/types";
|
|
11
12
|
import type { AgentSession } from "../session/agent-session";
|
|
12
13
|
|
|
@@ -59,7 +60,7 @@ export async function initializeExtensions(session: AgentSession, options: Initi
|
|
|
59
60
|
getActiveTools: () => session.getActiveToolNames(),
|
|
60
61
|
getAllTools: () => session.getAllToolNames(),
|
|
61
62
|
setActiveTools: (toolNames: string[]) => session.setActiveToolsByName(toolNames),
|
|
62
|
-
getCommands: () =>
|
|
63
|
+
getCommands: () => getSessionSlashCommands(session),
|
|
63
64
|
setModel: model => runExtensionSetModel(session, model),
|
|
64
65
|
getThinkingLevel: () => session.thinkingLevel,
|
|
65
66
|
setThinkingLevel: level => session.setThinkingLevel(level),
|
package/src/modes/theme/theme.ts
CHANGED
|
@@ -95,6 +95,7 @@ export type SymbolKey =
|
|
|
95
95
|
| "icon.pause"
|
|
96
96
|
| "icon.loop"
|
|
97
97
|
| "icon.folder"
|
|
98
|
+
| "icon.scratchFolder"
|
|
98
99
|
| "icon.file"
|
|
99
100
|
| "icon.git"
|
|
100
101
|
| "icon.branch"
|
|
@@ -258,6 +259,7 @@ const UNICODE_SYMBOLS: SymbolMap = {
|
|
|
258
259
|
"icon.pause": "⏸",
|
|
259
260
|
"icon.loop": "↻",
|
|
260
261
|
"icon.folder": "📁",
|
|
262
|
+
"icon.scratchFolder": "🗑",
|
|
261
263
|
"icon.file": "📄",
|
|
262
264
|
"icon.git": "⎇",
|
|
263
265
|
"icon.branch": "⑂",
|
|
@@ -476,6 +478,8 @@ const NERD_SYMBOLS: SymbolMap = {
|
|
|
476
478
|
"icon.loop": "\uf021",
|
|
477
479
|
// pick: | alt:
|
|
478
480
|
"icon.folder": "\uf115",
|
|
481
|
+
// pick: | alt:
|
|
482
|
+
"icon.scratchFolder": "\uf014",
|
|
479
483
|
// pick: | alt:
|
|
480
484
|
"icon.file": "\uf15b",
|
|
481
485
|
// pick: | alt: ⎇
|
|
@@ -678,6 +682,7 @@ const ASCII_SYMBOLS: SymbolMap = {
|
|
|
678
682
|
"icon.pause": "||",
|
|
679
683
|
"icon.loop": "loop",
|
|
680
684
|
"icon.folder": "[D]",
|
|
685
|
+
"icon.scratchFolder": "[T]",
|
|
681
686
|
"icon.file": "[F]",
|
|
682
687
|
"icon.git": "git:",
|
|
683
688
|
"icon.branch": "@",
|
|
@@ -1457,6 +1462,7 @@ export class Theme {
|
|
|
1457
1462
|
pause: this.#symbols["icon.pause"],
|
|
1458
1463
|
loop: this.#symbols["icon.loop"],
|
|
1459
1464
|
folder: this.#symbols["icon.folder"],
|
|
1465
|
+
scratchFolder: this.#symbols["icon.scratchFolder"],
|
|
1460
1466
|
file: this.#symbols["icon.file"],
|
|
1461
1467
|
git: this.#symbols["icon.git"],
|
|
1462
1468
|
branch: this.#symbols["icon.branch"],
|
|
@@ -6,10 +6,10 @@ Pick the operation via `op`. Each op uses a subset of the parameters:
|
|
|
6
6
|
- `pr_create` — Create a pull request. Either provide `title` (and optional `body`) or set `fill: true` to auto-fill from commits. Optional `base` (target, defaults to repo default), `head` (source, defaults to current branch), `draft`, `repo`, `reviewer[]`, `assignee[]`, `label[]`. Returns the new PR URL plus a summary.
|
|
7
7
|
- `pr_checkout` — Check one or more pull requests out into dedicated git worktrees. Optional `pr` (number, URL, branch, or array of any of those — pass an array to batch-check-out multiple PRs in one call), `repo`, `force` (reset existing local branch).
|
|
8
8
|
- `pr_push` — Push a checked-out PR branch back to its source branch. Requires the branch to have been checked out via `op: pr_checkout` (carries push metadata). Optional `branch`; defaults to the current checked-out git branch. Optional `forceWithLease`.
|
|
9
|
-
- `search_issues` — Search issues using normal GitHub issue search syntax. Optional `query` (required unless `since`/`until` is set), `repo`, `limit`, `since`, `until`, `dateField`.
|
|
10
|
-
- `search_prs` — Search pull requests using normal GitHub PR search syntax. Optional `query` (required unless `since`/`until` is set), `repo`, `limit`, `since`, `until`, `dateField`.
|
|
11
|
-
- `search_code` — Search code with GitHub code search syntax. Required `query`. Optional `repo`, `limit`. Returns matching paths with surrounding fragments. Date filtering (`since`/`until`) is **not** supported by GitHub code search.
|
|
12
|
-
- `search_commits` — Search commits across GitHub. Optional `query` (required unless `since`/`until` is set), `repo`, `limit`, `since`, `until`. `dateField` is ignored — always uses `committer-date`.
|
|
9
|
+
- `search_issues` — Search issues using normal GitHub issue search syntax. Optional `query` (required unless `since`/`until` is set), `repo`, `limit`, `since`, `until`, `dateField`. Defaults `repo` to the current checkout's `owner/repo` when omitted; pass an explicit `repo:`/`org:`/`user:` qualifier in `query` to search outside it.
|
|
10
|
+
- `search_prs` — Search pull requests using normal GitHub PR search syntax. Optional `query` (required unless `since`/`until` is set), `repo`, `limit`, `since`, `until`, `dateField`. Defaults `repo` to the current checkout's `owner/repo` when omitted; pass an explicit `repo:`/`org:`/`user:` qualifier in `query` to search outside it.
|
|
11
|
+
- `search_code` — Search code with GitHub code search syntax. Required `query`. Optional `repo`, `limit`. Returns matching paths with surrounding fragments. Defaults `repo` to the current checkout's `owner/repo` when omitted; pass an explicit `repo:`/`org:`/`user:` qualifier in `query` to search outside it. Date filtering (`since`/`until`) is **not** supported by GitHub code search.
|
|
12
|
+
- `search_commits` — Search commits across GitHub. Optional `query` (required unless `since`/`until` is set), `repo`, `limit`, `since`, `until`. `dateField` is ignored — always uses `committer-date`. Defaults `repo` to the current checkout's `owner/repo` when omitted; pass an explicit `repo:`/`org:`/`user:` qualifier in `query` to search outside it.
|
|
13
13
|
- `search_repos` — Search repositories across GitHub. Optional `query` (required unless `since`/`until` is set), `limit`, `since`, `until`, `dateField` (use query qualifiers like `org:`, `language:` instead of `repo`).
|
|
14
14
|
- Date filter format for `since` / `until`: relative duration `<n><unit>` (`m`/`h`/`d`/`w`/`mo`/`y`, e.g. `3d`, `12h`, `2w`), an ISO date `YYYY-MM-DD`, or an ISO datetime. Translated to a single GitHub-search qualifier (`created:≥…`, `created:≤…`, or `created:since..until`). `dateField: "updated"` maps to `updated:` for issues/prs and `pushed:` for repos. When you only want a date filter and no keywords, omit `query` entirely.
|
|
15
15
|
- `run_watch` — Watch a GitHub Actions workflow run. Optional `run` (id or URL). Omitting `run` watches all workflow runs for the current HEAD commit; `branch` falls back to the current branch. Optional `tail` (log lines per failed job). Streams snapshots, fast-fails on the first detected job failure (with a brief grace period to capture concurrent failures), then fetches tailed logs for the failed jobs. The full failed-job logs are saved as a session artifact for on-demand reads.
|
|
@@ -66,36 +66,35 @@ When braces bound your edit, you SHOULD prefer these shapes:
|
|
|
66
66
|
</common-failures>
|
|
67
67
|
|
|
68
68
|
<case file="mod.ts">
|
|
69
|
-
{{hline 1 "const
|
|
70
|
-
{{hline 2 "export function
|
|
69
|
+
{{hline 1 "const TITLE = \"Mr\";"}}
|
|
70
|
+
{{hline 2 "export function greet(name) {"}}
|
|
71
71
|
{{hline 3 "\treturn ["}}
|
|
72
|
-
{{hline 4 "\t\
|
|
73
|
-
{{hline 5 "\t\
|
|
74
|
-
{{hline 6 "\t].join(\"\");"}}
|
|
72
|
+
{{hline 4 "\t\tTITLE,"}}
|
|
73
|
+
{{hline 5 "\t\tname?.trim() || \"guest\","}}
|
|
74
|
+
{{hline 6 "\t].join(\" \");"}}
|
|
75
75
|
{{hline 7 "}"}}
|
|
76
76
|
</case>
|
|
77
77
|
|
|
78
78
|
<examples>
|
|
79
79
|
# Replace one line (the payload must re-emit the original indentation)
|
|
80
80
|
@@ mod.ts
|
|
81
|
-
= {{hrefr
|
|
82
|
-
{{hsep}}
|
|
81
|
+
= {{hrefr 1}}..{{hrefr 1}}
|
|
82
|
+
{{hsep}}const TITLE = "Mrs";
|
|
83
83
|
|
|
84
84
|
# Replace a full multiline statement (widen to a self-contained boundary)
|
|
85
85
|
@@ mod.ts
|
|
86
86
|
= {{hrefr 3}}..{{hrefr 6}}
|
|
87
87
|
{{hsep}} return [
|
|
88
|
-
{{hsep}}
|
|
89
|
-
{{hsep}} "
|
|
90
|
-
{{hsep}}
|
|
91
|
-
{{hsep}} ].join("");
|
|
88
|
+
{{hsep}} "Mrs",
|
|
89
|
+
{{hsep}} name?.trim() || "guest",
|
|
90
|
+
{{hsep}} ].join(" ");
|
|
92
91
|
|
|
93
92
|
# Insert AFTER/BEFORE a line
|
|
94
93
|
@@ mod.ts
|
|
95
|
-
+ {{hrefr
|
|
96
|
-
{{hsep}} "
|
|
94
|
+
+ {{hrefr 4}}
|
|
95
|
+
{{hsep}} "Dr",
|
|
97
96
|
< {{hrefr 5}}
|
|
98
|
-
{{hsep}} "
|
|
97
|
+
{{hsep}} "Dr",
|
|
99
98
|
|
|
100
99
|
# Append to file
|
|
101
100
|
@@ mod.ts
|
|
@@ -112,13 +111,12 @@ When braces bound your edit, you SHOULD prefer these shapes:
|
|
|
112
111
|
</examples>
|
|
113
112
|
|
|
114
113
|
<anti-pattern>
|
|
115
|
-
# WRONG — replaces
|
|
114
|
+
# WRONG — replaces 2 lines just to add one.
|
|
116
115
|
@@ mod.ts
|
|
117
|
-
= {{hrefr 1}}..{{hrefr
|
|
118
|
-
{{hsep}}const
|
|
116
|
+
= {{hrefr 1}}..{{hrefr 2}}
|
|
117
|
+
{{hsep}}const TITLE = "Mr";
|
|
119
118
|
{{hsep}}const DEBUG = false;
|
|
120
|
-
{{hsep}}export function
|
|
121
|
-
{{hsep}} return [
|
|
119
|
+
{{hsep}}export function greet(name) {
|
|
122
120
|
# RIGHT — same effect, one-line insert
|
|
123
121
|
@@ mod.ts
|
|
124
122
|
+ {{hrefr 1}}
|
|
@@ -127,17 +125,15 @@ When braces bound your edit, you SHOULD prefer these shapes:
|
|
|
127
125
|
# WRONG — replace from the middle of a larger statement (error-prone)
|
|
128
126
|
@@ mod.ts
|
|
129
127
|
= {{hrefr 4}}..{{hrefr 5}}
|
|
130
|
-
{{hsep}}
|
|
131
|
-
{{hsep}} "
|
|
132
|
-
{{hsep}} " • ",
|
|
128
|
+
{{hsep}} "Dr",
|
|
129
|
+
{{hsep}} name?.trim() || "guest",
|
|
133
130
|
# RIGHT — widen to the full statement
|
|
134
131
|
@@ mod.ts
|
|
135
132
|
= {{hrefr 3}}..{{hrefr 6}}
|
|
136
133
|
{{hsep}} return [
|
|
137
|
-
{{hsep}}
|
|
138
|
-
{{hsep}} "
|
|
139
|
-
{{hsep}}
|
|
140
|
-
{{hsep}} ].join("");
|
|
134
|
+
{{hsep}} "Dr",
|
|
135
|
+
{{hsep}} name?.trim() || "guest",
|
|
136
|
+
{{hsep}} ].join(" ");
|
|
141
137
|
</anti-pattern>
|
|
142
138
|
|
|
143
139
|
<critical>
|