@oh-my-pi/pi-coding-agent 15.11.1 → 15.11.3
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 +36 -1
- package/dist/cli.js +643 -627
- package/dist/types/config/settings-schema.d.ts +36 -0
- package/dist/types/extensibility/custom-commands/types.d.ts +6 -3
- package/dist/types/extensibility/custom-tools/loader.d.ts +2 -1
- package/dist/types/extensibility/custom-tools/types.d.ts +8 -4
- package/dist/types/extensibility/extensions/types.d.ts +2 -2
- package/dist/types/extensibility/hooks/types.d.ts +8 -4
- package/dist/types/irc/bus.d.ts +15 -2
- package/dist/types/lsp/format-options.d.ts +32 -0
- package/dist/types/mnemopi/state.d.ts +29 -1
- package/dist/types/modes/components/plan-review-overlay.d.ts +2 -0
- package/dist/types/modes/rpc/rpc-client.d.ts +10 -1
- package/dist/types/modes/rpc/rpc-mode.d.ts +2 -0
- package/dist/types/modes/rpc/rpc-types.d.ts +30 -0
- package/dist/types/modes/theme/theme.d.ts +1 -1
- package/dist/types/session/agent-session.d.ts +17 -3
- package/dist/types/slash-commands/available-commands.d.ts +34 -0
- package/dist/types/tools/bash.d.ts +1 -1
- package/dist/types/tools/browser/attach.d.ts +4 -4
- package/dist/types/tools/browser/registry.d.ts +1 -0
- package/dist/types/tools/irc.d.ts +3 -2
- package/dist/types/tools/path-utils.d.ts +5 -5
- package/dist/types/utils/git.d.ts +1 -1
- package/package.json +11 -11
- package/src/config/settings-schema.ts +40 -0
- package/src/exec/bash-executor.ts +21 -6
- package/src/extensibility/custom-commands/loader.ts +3 -1
- package/src/extensibility/custom-commands/types.ts +6 -3
- package/src/extensibility/custom-tools/loader.ts +4 -7
- package/src/extensibility/custom-tools/types.ts +8 -4
- package/src/extensibility/extensions/loader.ts +2 -1
- package/src/extensibility/extensions/types.ts +2 -2
- package/src/extensibility/hooks/loader.ts +3 -1
- package/src/extensibility/hooks/types.ts +8 -4
- package/src/internal-urls/docs-index.generated.ts +4 -4
- package/src/irc/bus.ts +14 -3
- package/src/lsp/clients/lsp-linter-client.ts +2 -10
- package/src/lsp/defaults.json +6 -0
- package/src/lsp/format-options.ts +119 -0
- package/src/lsp/index.ts +2 -10
- package/src/lsp/render.ts +2 -28
- package/src/memories/index.ts +2 -0
- package/src/mnemopi/backend.ts +4 -8
- package/src/mnemopi/state.ts +42 -3
- package/src/modes/acp/acp-agent.ts +4 -67
- package/src/modes/components/plan-review-overlay.ts +32 -3
- package/src/modes/controllers/streaming-reveal.ts +16 -8
- package/src/modes/interactive-mode.ts +54 -2
- package/src/modes/rpc/rpc-client.ts +32 -0
- package/src/modes/rpc/rpc-mode.ts +82 -7
- package/src/modes/rpc/rpc-types.ts +23 -0
- package/src/modes/theme/theme.ts +7 -7
- package/src/modes/utils/ui-helpers.ts +13 -4
- package/src/prompts/memories/consolidation_system.md +4 -0
- package/src/prompts/system/irc-autoreply.md +6 -0
- package/src/prompts/system/irc-incoming.md +1 -1
- package/src/prompts/tools/bash.md +1 -0
- package/src/prompts/tools/irc.md +1 -1
- package/src/session/agent-session.ts +96 -7
- package/src/slash-commands/available-commands.ts +105 -0
- package/src/tools/bash.ts +5 -1
- package/src/tools/browser/attach.ts +26 -7
- package/src/tools/browser/registry.ts +11 -1
- package/src/tools/irc.ts +16 -4
- package/src/tools/job.ts +7 -3
- package/src/tools/path-utils.ts +56 -25
- package/src/tools/search.ts +11 -0
- package/src/utils/git.ts +7 -2
|
@@ -14,7 +14,14 @@ import {
|
|
|
14
14
|
import type { CompactionOutcome } from "@oh-my-pi/pi-agent-core/compaction";
|
|
15
15
|
import type { AssistantMessage, ImageContent, Message, Model, UsageReport } from "@oh-my-pi/pi-ai";
|
|
16
16
|
import { modelsAreEqual } from "@oh-my-pi/pi-catalog/models";
|
|
17
|
-
import type {
|
|
17
|
+
import type {
|
|
18
|
+
Component,
|
|
19
|
+
EditorTheme,
|
|
20
|
+
LoaderMessageColorFn,
|
|
21
|
+
NativeScrollbackLiveRegion,
|
|
22
|
+
OverlayHandle,
|
|
23
|
+
SlashCommand,
|
|
24
|
+
} from "@oh-my-pi/pi-tui";
|
|
18
25
|
import {
|
|
19
26
|
Container,
|
|
20
27
|
clearRenderCache,
|
|
@@ -257,6 +264,19 @@ export interface InteractiveModeOptions {
|
|
|
257
264
|
initialMessages?: string[];
|
|
258
265
|
}
|
|
259
266
|
|
|
267
|
+
/**
|
|
268
|
+
* Hosts the working loader and transient status rows. While anything is
|
|
269
|
+
* mounted, every row is live: report a seam at 0 so the engine never commits
|
|
270
|
+
* a still-animating loader to native scrollback (stale `Working…` rows would
|
|
271
|
+
* otherwise pile up above the live one). The transcript's own seam, when
|
|
272
|
+
* present, sits higher and wins (topmost-seam merge in TUI.render).
|
|
273
|
+
*/
|
|
274
|
+
class StatusContainer extends Container implements NativeScrollbackLiveRegion {
|
|
275
|
+
getNativeScrollbackLiveRegionStart(): number | undefined {
|
|
276
|
+
return this.children.length > 0 ? 0 : undefined;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
260
280
|
export class InteractiveMode implements InteractiveModeContext {
|
|
261
281
|
session: AgentSession;
|
|
262
282
|
sessionManager: SessionManager;
|
|
@@ -418,7 +438,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
418
438
|
setTerminalTextSizing(settings.get("tui.textSizing") && TERMINAL.textSizing);
|
|
419
439
|
this.chatContainer = new TranscriptContainer();
|
|
420
440
|
this.pendingMessagesContainer = new Container();
|
|
421
|
-
this.statusContainer = new
|
|
441
|
+
this.statusContainer = new StatusContainer();
|
|
422
442
|
this.todoContainer = new Container();
|
|
423
443
|
this.btwContainer = new Container();
|
|
424
444
|
this.omfgContainer = new Container();
|
|
@@ -1785,6 +1805,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1785
1805
|
onPick: choice => finish(choice),
|
|
1786
1806
|
onCancel: () => finish(undefined),
|
|
1787
1807
|
onExternalEditor: dialogOptions?.onExternalEditor,
|
|
1808
|
+
onAnnotationExternalEditor: (draft, commit) => void this.#openPlanAnnotationInExternalEditor(draft, commit),
|
|
1788
1809
|
onPlanEdited: dialogOptions?.onPlanEdited,
|
|
1789
1810
|
onFeedbackChange: dialogOptions?.onFeedbackChange,
|
|
1790
1811
|
},
|
|
@@ -1899,6 +1920,37 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1899
1920
|
}
|
|
1900
1921
|
}
|
|
1901
1922
|
|
|
1923
|
+
async #openPlanAnnotationInExternalEditor(draft: string, commit: (text: string | null) => void): Promise<void> {
|
|
1924
|
+
const editorCmd = getEditorCommand();
|
|
1925
|
+
if (!editorCmd) {
|
|
1926
|
+
this.showWarning("No editor configured. Set $VISUAL or $EDITOR environment variable.");
|
|
1927
|
+
return;
|
|
1928
|
+
}
|
|
1929
|
+
|
|
1930
|
+
let ttyHandle: fs.FileHandle | null = null;
|
|
1931
|
+
try {
|
|
1932
|
+
ttyHandle = await this.#openEditorTerminalHandle();
|
|
1933
|
+
this.ui.stop();
|
|
1934
|
+
|
|
1935
|
+
const stdio: [number | "inherit", number | "inherit", number | "inherit"] = ttyHandle
|
|
1936
|
+
? [ttyHandle.fd, ttyHandle.fd, ttyHandle.fd]
|
|
1937
|
+
: ["inherit", "inherit", "inherit"];
|
|
1938
|
+
|
|
1939
|
+
const result = await openInEditor(editorCmd, draft, { extension: ".md", stdio });
|
|
1940
|
+
if (result !== null) {
|
|
1941
|
+
commit(result);
|
|
1942
|
+
}
|
|
1943
|
+
} catch (error) {
|
|
1944
|
+
this.showWarning(`Failed to open external editor: ${error instanceof Error ? error.message : String(error)}`);
|
|
1945
|
+
} finally {
|
|
1946
|
+
if (ttyHandle) {
|
|
1947
|
+
await ttyHandle.close();
|
|
1948
|
+
}
|
|
1949
|
+
this.ui.start();
|
|
1950
|
+
this.ui.requestRender(true);
|
|
1951
|
+
}
|
|
1952
|
+
}
|
|
1953
|
+
|
|
1902
1954
|
async #applyPlanExecutionModel(entry: ResolvedRoleModel | undefined): Promise<void> {
|
|
1903
1955
|
if (!entry) return;
|
|
1904
1956
|
try {
|
|
@@ -13,6 +13,8 @@ import type { FileSink } from "bun";
|
|
|
13
13
|
import type { BashResult } from "../../exec/bash-executor";
|
|
14
14
|
import type { AgentSessionEvent, SessionStats } from "../../session/agent-session";
|
|
15
15
|
import type {
|
|
16
|
+
RpcAvailableCommandsUpdateFrame,
|
|
17
|
+
RpcAvailableSlashCommand,
|
|
16
18
|
RpcCommand,
|
|
17
19
|
RpcExtensionUIRequest,
|
|
18
20
|
RpcHandoffResult,
|
|
@@ -63,6 +65,7 @@ export type RpcSessionEventListener = (event: AgentSessionEvent) => void;
|
|
|
63
65
|
export type RpcSubagentLifecycleListener = (payload: RpcSubagentLifecycleFrame["payload"]) => void;
|
|
64
66
|
export type RpcSubagentProgressListener = (payload: RpcSubagentProgressFrame["payload"]) => void;
|
|
65
67
|
export type RpcSubagentEventListener = (payload: RpcSubagentEventFrame["payload"]) => void;
|
|
68
|
+
export type RpcAvailableCommandsUpdateListener = (commands: RpcAvailableSlashCommand[]) => void;
|
|
66
69
|
|
|
67
70
|
export interface RpcClientToolContext<TDetails = unknown> {
|
|
68
71
|
toolCallId: string;
|
|
@@ -161,6 +164,11 @@ function isRpcSubagentEventFrame(value: unknown): value is RpcSubagentEventFrame
|
|
|
161
164
|
return value.type === "subagent_event" && isRecord(value.payload);
|
|
162
165
|
}
|
|
163
166
|
|
|
167
|
+
function isRpcAvailableCommandsUpdateFrame(value: unknown): value is RpcAvailableCommandsUpdateFrame {
|
|
168
|
+
if (!isRecord(value)) return false;
|
|
169
|
+
return value.type === "available_commands_update" && Array.isArray(value.commands);
|
|
170
|
+
}
|
|
171
|
+
|
|
164
172
|
function isRpcHostToolCallRequest(value: unknown): value is RpcHostToolCallRequest {
|
|
165
173
|
if (!isRecord(value)) return false;
|
|
166
174
|
return (
|
|
@@ -202,6 +210,7 @@ export class RpcClient {
|
|
|
202
210
|
#subagentLifecycleListeners = new Set<RpcSubagentLifecycleListener>();
|
|
203
211
|
#subagentProgressListeners = new Set<RpcSubagentProgressListener>();
|
|
204
212
|
#subagentEventListeners = new Set<RpcSubagentEventListener>();
|
|
213
|
+
#availableCommandsUpdateListeners = new Set<RpcAvailableCommandsUpdateListener>();
|
|
205
214
|
#pendingRequests: Map<string, { resolve: (response: RpcResponse) => void; reject: (error: Error) => void }> =
|
|
206
215
|
new Map();
|
|
207
216
|
#customTools: RpcClientCustomTool[] = [];
|
|
@@ -377,6 +386,14 @@ export class RpcClient {
|
|
|
377
386
|
return () => this.#subagentEventListeners.delete(listener);
|
|
378
387
|
}
|
|
379
388
|
|
|
389
|
+
/**
|
|
390
|
+
* Subscribe to slash-command availability updates emitted by the RPC server.
|
|
391
|
+
*/
|
|
392
|
+
onAvailableCommandsUpdate(listener: RpcAvailableCommandsUpdateListener): () => void {
|
|
393
|
+
this.#availableCommandsUpdateListeners.add(listener);
|
|
394
|
+
return () => this.#availableCommandsUpdateListeners.delete(listener);
|
|
395
|
+
}
|
|
396
|
+
|
|
380
397
|
/**
|
|
381
398
|
* Get collected stderr output (useful for debugging).
|
|
382
399
|
*/
|
|
@@ -511,6 +528,14 @@ export class RpcClient {
|
|
|
511
528
|
return this.#getData<{ models: ModelInfo[] }>(response).models;
|
|
512
529
|
}
|
|
513
530
|
|
|
531
|
+
/**
|
|
532
|
+
* Get list of available slash commands.
|
|
533
|
+
*/
|
|
534
|
+
async getAvailableCommands(): Promise<RpcAvailableSlashCommand[]> {
|
|
535
|
+
const response = await this.#send({ type: "get_available_commands" });
|
|
536
|
+
return this.#getData<{ commands: RpcAvailableSlashCommand[] }>(response).commands;
|
|
537
|
+
}
|
|
538
|
+
|
|
514
539
|
/**
|
|
515
540
|
* Set thinking level.
|
|
516
541
|
*/
|
|
@@ -825,6 +850,13 @@ export class RpcClient {
|
|
|
825
850
|
return;
|
|
826
851
|
}
|
|
827
852
|
|
|
853
|
+
if (isRpcAvailableCommandsUpdateFrame(data)) {
|
|
854
|
+
for (const listener of this.#availableCommandsUpdateListeners) {
|
|
855
|
+
listener(data.commands);
|
|
856
|
+
}
|
|
857
|
+
return;
|
|
858
|
+
}
|
|
859
|
+
|
|
828
860
|
if (!isAgentSessionEvent(data)) return;
|
|
829
861
|
|
|
830
862
|
for (const listener of this.#sessionEventListeners) {
|
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
*/
|
|
13
13
|
import { getOAuthProviders } from "@oh-my-pi/pi-ai/oauth";
|
|
14
14
|
import { $env, readJsonl, Snowflake } from "@oh-my-pi/pi-utils";
|
|
15
|
+
import { reset as resetCapabilities } from "../../capability";
|
|
16
|
+
import { clearPluginRootsAndCaches, resolveActiveProjectRegistryPath } from "../../discovery/helpers";
|
|
15
17
|
import {
|
|
16
18
|
type ExtensionUIContext,
|
|
17
19
|
type ExtensionUIDialogOptions,
|
|
@@ -19,8 +21,13 @@ import {
|
|
|
19
21
|
type ExtensionWidgetOptions,
|
|
20
22
|
getExtensionUISelectOptionLabel,
|
|
21
23
|
} from "../../extensibility/extensions";
|
|
24
|
+
import { buildSkillPromptMessage } from "../../extensibility/skills";
|
|
25
|
+
import { loadSlashCommands } from "../../extensibility/slash-commands";
|
|
22
26
|
import { type Theme, theme } from "../../modes/theme/theme";
|
|
23
27
|
import type { AgentSession } from "../../session/agent-session";
|
|
28
|
+
import { SKILL_PROMPT_MESSAGE_TYPE } from "../../session/messages";
|
|
29
|
+
import { executeAcpBuiltinSlashCommand } from "../../slash-commands/acp-builtins";
|
|
30
|
+
import { buildAvailableSlashCommands } from "../../slash-commands/available-commands";
|
|
24
31
|
import type { EventBus } from "../../utils/event-bus";
|
|
25
32
|
import { initializeExtensions } from "../runtime-init";
|
|
26
33
|
import { isRpcHostToolResult, isRpcHostToolUpdate, RpcHostToolBridge } from "./host-tools";
|
|
@@ -70,6 +77,28 @@ export type RpcSessionChangeResult =
|
|
|
70
77
|
| { type: "branch"; data: { text: string; cancelled: boolean } };
|
|
71
78
|
|
|
72
79
|
export type RpcSessionChangeSession = Pick<AgentSession, "newSession" | "switchSession" | "branch">;
|
|
80
|
+
|
|
81
|
+
export type RpcSkillCommandSession = Pick<AgentSession, "promptCustomMessage" | "skills" | "skillsSettings">;
|
|
82
|
+
|
|
83
|
+
export async function tryRunRpcSkillCommand(session: RpcSkillCommandSession, text: string): Promise<boolean> {
|
|
84
|
+
if (!text.startsWith("/skill:")) return false;
|
|
85
|
+
if (!session.skillsSettings?.enableSkillCommands) return false;
|
|
86
|
+
const spaceIndex = text.indexOf(" ");
|
|
87
|
+
const commandName = spaceIndex === -1 ? text.slice(1) : text.slice(1, spaceIndex);
|
|
88
|
+
const args = spaceIndex === -1 ? "" : text.slice(spaceIndex + 1).trim();
|
|
89
|
+
const skillName = commandName.slice("skill:".length);
|
|
90
|
+
const skill = session.skills.find(candidate => candidate.name === skillName);
|
|
91
|
+
if (!skill) return false;
|
|
92
|
+
const built = await buildSkillPromptMessage(skill, args);
|
|
93
|
+
await session.promptCustomMessage({
|
|
94
|
+
customType: SKILL_PROMPT_MESSAGE_TYPE,
|
|
95
|
+
content: built.message,
|
|
96
|
+
display: true,
|
|
97
|
+
details: built.details,
|
|
98
|
+
attribution: "user",
|
|
99
|
+
});
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
73
102
|
export type RpcSubagentResetRegistry = Pick<RpcSubagentRegistry, "clear">;
|
|
74
103
|
|
|
75
104
|
export async function handleRpcSessionChange(
|
|
@@ -511,6 +540,24 @@ export async function runRpcMode(
|
|
|
511
540
|
output(event);
|
|
512
541
|
});
|
|
513
542
|
|
|
543
|
+
const getAvailableCommands = async () => buildAvailableSlashCommands(session);
|
|
544
|
+
const reloadPluginState = async () => {
|
|
545
|
+
const cwd = session.sessionManager.getCwd();
|
|
546
|
+
const projectPath = await resolveActiveProjectRegistryPath(cwd);
|
|
547
|
+
clearPluginRootsAndCaches(projectPath ? [projectPath] : undefined);
|
|
548
|
+
resetCapabilities();
|
|
549
|
+
session.setSlashCommands(await loadSlashCommands({ cwd }));
|
|
550
|
+
await session.refreshSshTool({ activateIfAvailable: true });
|
|
551
|
+
await emitAvailableCommandsUpdate();
|
|
552
|
+
};
|
|
553
|
+
const emitAvailableCommandsUpdate = async () => {
|
|
554
|
+
output({ type: "available_commands_update", commands: await getAvailableCommands() });
|
|
555
|
+
};
|
|
556
|
+
session.subscribeCommandMetadataChanged(() => {
|
|
557
|
+
void emitAvailableCommandsUpdate();
|
|
558
|
+
});
|
|
559
|
+
await emitAvailableCommandsUpdate();
|
|
560
|
+
|
|
514
561
|
// Handle a single command
|
|
515
562
|
const handleCommand = async (command: RpcCommand): Promise<RpcResponse> => {
|
|
516
563
|
const id = command.id;
|
|
@@ -521,6 +568,33 @@ export async function runRpcMode(
|
|
|
521
568
|
// =================================================================
|
|
522
569
|
|
|
523
570
|
case "prompt": {
|
|
571
|
+
if (await tryRunRpcSkillCommand(session, command.message)) {
|
|
572
|
+
return success(id, "prompt");
|
|
573
|
+
}
|
|
574
|
+
const builtinResult = await executeAcpBuiltinSlashCommand(command.message, {
|
|
575
|
+
session,
|
|
576
|
+
sessionManager: session.sessionManager,
|
|
577
|
+
settings: session.settings,
|
|
578
|
+
cwd: session.sessionManager.getCwd(),
|
|
579
|
+
output: text => output({ type: "command_output", text }),
|
|
580
|
+
refreshCommands: emitAvailableCommandsUpdate,
|
|
581
|
+
reloadPlugins: reloadPluginState,
|
|
582
|
+
notifyTitleChanged: async () => {
|
|
583
|
+
output({ type: "session_info_update", title: session.sessionName, sessionId: session.sessionId });
|
|
584
|
+
},
|
|
585
|
+
notifyConfigChanged: async () => {
|
|
586
|
+
output({ type: "config_update", model: session.model, thinkingLevel: session.thinkingLevel });
|
|
587
|
+
},
|
|
588
|
+
});
|
|
589
|
+
if (builtinResult !== false) {
|
|
590
|
+
if ("prompt" in builtinResult) {
|
|
591
|
+
session
|
|
592
|
+
.prompt(builtinResult.prompt, { images: command.images })
|
|
593
|
+
.catch(e => output(error(id, "prompt", e.message)));
|
|
594
|
+
}
|
|
595
|
+
return success(id, "prompt");
|
|
596
|
+
}
|
|
597
|
+
|
|
524
598
|
// Don't await - events will stream
|
|
525
599
|
// Extension commands are executed immediately, file prompt templates are expanded
|
|
526
600
|
// If streaming and streamingBehavior specified, queues via steer/followUp
|
|
@@ -556,8 +630,11 @@ export async function runRpcMode(
|
|
|
556
630
|
return success(id, "abort_and_prompt");
|
|
557
631
|
}
|
|
558
632
|
|
|
559
|
-
case "new_session":
|
|
633
|
+
case "new_session":
|
|
634
|
+
case "switch_session":
|
|
635
|
+
case "branch": {
|
|
560
636
|
const result = await handleRpcSessionChange(session, command, subagentRegistry);
|
|
637
|
+
if (!result.data.cancelled) await emitAvailableCommandsUpdate();
|
|
561
638
|
return success(id, result.type, result.data);
|
|
562
639
|
}
|
|
563
640
|
|
|
@@ -592,6 +669,10 @@ export async function runRpcMode(
|
|
|
592
669
|
return success(id, "get_state", state);
|
|
593
670
|
}
|
|
594
671
|
|
|
672
|
+
case "get_available_commands": {
|
|
673
|
+
return success(id, "get_available_commands", { commands: await getAvailableCommands() });
|
|
674
|
+
}
|
|
675
|
+
|
|
595
676
|
case "set_todos": {
|
|
596
677
|
session.setTodoPhases(command.phases);
|
|
597
678
|
return success(id, "set_todos", { todoPhases: session.getTodoPhases() });
|
|
@@ -770,12 +851,6 @@ export async function runRpcMode(
|
|
|
770
851
|
return success(id, "export_html", { path });
|
|
771
852
|
}
|
|
772
853
|
|
|
773
|
-
case "switch_session":
|
|
774
|
-
case "branch": {
|
|
775
|
-
const result = await handleRpcSessionChange(session, command, subagentRegistry);
|
|
776
|
-
return success(id, result.type, result.data);
|
|
777
|
-
}
|
|
778
|
-
|
|
779
854
|
case "get_branch_messages": {
|
|
780
855
|
const messages = session.getUserMessagesForBranching();
|
|
781
856
|
return success(id, "get_branch_messages", { messages });
|
|
@@ -11,6 +11,7 @@ import type { BashResult } from "../../exec/bash-executor";
|
|
|
11
11
|
import type { ContextUsage } from "../../extensibility/extensions/types";
|
|
12
12
|
import type { AgentSessionEvent, SessionStats } from "../../session/agent-session";
|
|
13
13
|
import type { FileEntry } from "../../session/session-manager";
|
|
14
|
+
import type { AvailableSlashCommandSource } from "../../slash-commands/available-commands";
|
|
14
15
|
import type {
|
|
15
16
|
AgentProgress,
|
|
16
17
|
SubagentEventPayload,
|
|
@@ -34,6 +35,7 @@ export type RpcCommand =
|
|
|
34
35
|
|
|
35
36
|
// State
|
|
36
37
|
| { id?: string; type: "get_state" }
|
|
38
|
+
| { id?: string; type: "get_available_commands" }
|
|
37
39
|
| { id?: string; type: "set_todos"; phases: TodoPhase[] }
|
|
38
40
|
| { id?: string; type: "set_host_tools"; tools: RpcHostToolDefinition[] }
|
|
39
41
|
| { id?: string; type: "set_host_uri_schemes"; schemes: RpcHostUriSchemeDefinition[] }
|
|
@@ -110,6 +112,20 @@ export interface RpcSessionState {
|
|
|
110
112
|
contextUsage?: ContextUsage;
|
|
111
113
|
}
|
|
112
114
|
|
|
115
|
+
export interface RpcAvailableSlashCommand {
|
|
116
|
+
name: string;
|
|
117
|
+
aliases?: string[];
|
|
118
|
+
description?: string;
|
|
119
|
+
input?: { hint?: string };
|
|
120
|
+
subcommands?: Array<{ name: string; description?: string; usage?: string }>;
|
|
121
|
+
source: AvailableSlashCommandSource;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export interface RpcAvailableCommandsUpdateFrame {
|
|
125
|
+
type: "available_commands_update";
|
|
126
|
+
commands: RpcAvailableSlashCommand[];
|
|
127
|
+
}
|
|
128
|
+
|
|
113
129
|
export interface RpcHandoffResult {
|
|
114
130
|
savedPath?: string;
|
|
115
131
|
}
|
|
@@ -156,6 +172,13 @@ export type RpcResponse =
|
|
|
156
172
|
|
|
157
173
|
// State
|
|
158
174
|
| { id?: string; type: "response"; command: "get_state"; success: true; data: RpcSessionState }
|
|
175
|
+
| {
|
|
176
|
+
id?: string;
|
|
177
|
+
type: "response";
|
|
178
|
+
command: "get_available_commands";
|
|
179
|
+
success: true;
|
|
180
|
+
data: { commands: RpcAvailableSlashCommand[] };
|
|
181
|
+
}
|
|
159
182
|
| { id?: string; type: "response"; command: "set_todos"; success: true; data: { todoPhases: TodoPhase[] } }
|
|
160
183
|
| { id?: string; type: "response"; command: "set_host_tools"; success: true; data: { toolNames: string[] } }
|
|
161
184
|
| { id?: string; type: "response"; command: "set_host_uri_schemes"; success: true; data: { schemes: string[] } }
|
package/src/modes/theme/theme.ts
CHANGED
|
@@ -2565,10 +2565,10 @@ const HIGHLIGHT_CACHE_MAX = 256;
|
|
|
2565
2565
|
const highlightCache = new LRUCache<string, string>({ max: HIGHLIGHT_CACHE_MAX });
|
|
2566
2566
|
let highlightCacheTheme: Theme | undefined;
|
|
2567
2567
|
|
|
2568
|
-
function highlightCached(code: string, validLang: string | undefined): string | null {
|
|
2569
|
-
if (highlightCacheTheme !==
|
|
2568
|
+
function highlightCached(code: string, validLang: string | undefined, highlightTheme: Theme): string | null {
|
|
2569
|
+
if (highlightCacheTheme !== highlightTheme) {
|
|
2570
2570
|
highlightCache.clear();
|
|
2571
|
-
highlightCacheTheme =
|
|
2571
|
+
highlightCacheTheme = highlightTheme;
|
|
2572
2572
|
}
|
|
2573
2573
|
const key = `${validLang ?? ""}\x00${code}`;
|
|
2574
2574
|
const hit = highlightCache.get(key);
|
|
@@ -2577,7 +2577,7 @@ function highlightCached(code: string, validLang: string | undefined): string |
|
|
|
2577
2577
|
}
|
|
2578
2578
|
let highlighted: string;
|
|
2579
2579
|
try {
|
|
2580
|
-
highlighted = nativeHighlightCode(code, validLang, getHighlightColors(
|
|
2580
|
+
highlighted = nativeHighlightCode(code, validLang, getHighlightColors(highlightTheme));
|
|
2581
2581
|
} catch {
|
|
2582
2582
|
return null;
|
|
2583
2583
|
}
|
|
@@ -2589,9 +2589,9 @@ function highlightCached(code: string, validLang: string | undefined): string |
|
|
|
2589
2589
|
* Highlight code with syntax coloring based on file extension or language.
|
|
2590
2590
|
* Returns array of highlighted lines.
|
|
2591
2591
|
*/
|
|
2592
|
-
export function highlightCode(code: string, lang?: string): string[] {
|
|
2592
|
+
export function highlightCode(code: string, lang?: string, highlightTheme: Theme = theme): string[] {
|
|
2593
2593
|
const validLang = lang && nativeSupportsLanguage(lang) ? lang : undefined;
|
|
2594
|
-
const highlighted = highlightCached(code, validLang);
|
|
2594
|
+
const highlighted = highlightCached(code, validLang, highlightTheme);
|
|
2595
2595
|
// Always return a fresh array: callers (e.g. renderCodeCell) push extra lines
|
|
2596
2596
|
// onto the result, which would corrupt the cached string otherwise.
|
|
2597
2597
|
return (highlighted ?? code).split("\n");
|
|
@@ -2639,7 +2639,7 @@ export function getMarkdownTheme(): MarkdownTheme {
|
|
|
2639
2639
|
resolveMermaidAscii,
|
|
2640
2640
|
highlightCode: (code: string, lang?: string): string[] => {
|
|
2641
2641
|
const validLang = lang && nativeSupportsLanguage(lang) ? lang : undefined;
|
|
2642
|
-
const highlighted = highlightCached(code, validLang);
|
|
2642
|
+
const highlighted = highlightCached(code, validLang, theme);
|
|
2643
2643
|
if (highlighted !== null) return highlighted.split("\n");
|
|
2644
2644
|
return code.split("\n").map(line => theme.fg("mdCodeBlock", line));
|
|
2645
2645
|
},
|
|
@@ -191,7 +191,11 @@ export class UiHelpers {
|
|
|
191
191
|
this.ctx.chatContainer.addChild(component);
|
|
192
192
|
break;
|
|
193
193
|
}
|
|
194
|
-
if (
|
|
194
|
+
if (
|
|
195
|
+
message.customType === "irc:incoming" ||
|
|
196
|
+
message.customType === "irc:autoreply" ||
|
|
197
|
+
message.customType === "irc:relay"
|
|
198
|
+
) {
|
|
195
199
|
const details = (
|
|
196
200
|
message as CustomMessage<{
|
|
197
201
|
from?: string;
|
|
@@ -201,13 +205,18 @@ export class UiHelpers {
|
|
|
201
205
|
replyTo?: string;
|
|
202
206
|
}>
|
|
203
207
|
).details;
|
|
204
|
-
const
|
|
208
|
+
const kind =
|
|
209
|
+
message.customType === "irc:incoming"
|
|
210
|
+
? ("incoming" as const)
|
|
211
|
+
: message.customType === "irc:autoreply"
|
|
212
|
+
? ("autoreply" as const)
|
|
213
|
+
: ("relay" as const);
|
|
205
214
|
const card = createIrcMessageCard(
|
|
206
215
|
{
|
|
207
|
-
kind
|
|
216
|
+
kind,
|
|
208
217
|
from: details?.from,
|
|
209
218
|
to: details?.to,
|
|
210
|
-
body: incoming ? details?.message : details?.body,
|
|
219
|
+
body: kind === "incoming" ? details?.message : details?.body,
|
|
211
220
|
replyTo: details?.replyTo,
|
|
212
221
|
timestamp: message.timestamp,
|
|
213
222
|
},
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
<irc>
|
|
2
|
+
You received an IRC message from agent `{{from}}`{{#if replyTo}} (replying to {{replyTo}}){{/if}} while you are busy mid-task. This is a side-channel turn: reply briefly and directly using the conversation context already available to you. NEVER call tools. The text you write is delivered back to `{{from}}` as your answer.
|
|
3
|
+
|
|
4
|
+
Message:
|
|
5
|
+
{{message}}
|
|
6
|
+
</irc>
|
|
@@ -3,5 +3,5 @@ Incoming IRC message from agent `{{from}}`{{#if replyTo}} (replying to {{replyTo
|
|
|
3
3
|
|
|
4
4
|
{{message}}
|
|
5
5
|
|
|
6
|
-
If a response is expected, reply with the `irc` tool (`op: "send"`, `to: "{{from}}"`) — you may finish your current step first. Nobody replies on your behalf.
|
|
6
|
+
{{#if autoReplied}}You are mid-task, so a side-channel auto-reply was generated from your context and delivered to `{{from}}` on your behalf (recorded after this message). Follow up with the `irc` tool (`op: "send"`, `to: "{{from}}"`) only if that auto-reply needs correcting.{{else}}If a response is expected, reply with the `irc` tool (`op: "send"`, `to: "{{from}}"`) — you may finish your current step first. Nobody replies on your behalf.{{/if}}
|
|
7
7
|
</irc>
|
|
@@ -6,6 +6,7 @@ Executes bash command in shell session for terminal operations like git, bun, ca
|
|
|
6
6
|
- Quote variable expansions like `"$NAME"` to preserve exact content
|
|
7
7
|
- PTY mode is opt-in: set `pty: true` only when the command needs a real terminal (e.g. `sudo`, `ssh` requiring user input); default is `false`
|
|
8
8
|
- Use `;` only when later commands should run regardless of earlier failures
|
|
9
|
+
- Multiple bash calls in one message run concurrently. NEVER split order-dependent commands across parallel calls — chain them with `&&` in a single call.
|
|
9
10
|
- Internal URIs (`skill://`, `agent://`, etc.) are auto-resolved to filesystem paths
|
|
10
11
|
{{#if asyncEnabled}}
|
|
11
12
|
- Use `async: true` for long-running commands when you don't need immediate output; the call returns a background job ID and the result is delivered automatically as a follow-up.
|
package/src/prompts/tools/irc.md
CHANGED
|
@@ -9,7 +9,7 @@ Sends short text messages to other agents in this process and receives theirs.
|
|
|
9
9
|
- `op: "wait"` — block until a message arrives (optionally only `from` a specific peer); consumes and returns it. A timeout is a clean "no message" result, not an error.
|
|
10
10
|
- `op: "inbox"` — drain pending messages without blocking (`peek: true` to leave them unread).
|
|
11
11
|
- `replyTo` — set it to the id of the message you are answering so the sender can correlate.
|
|
12
|
-
- Nobody answers on a peer's behalf
|
|
12
|
+
- Nobody answers on a peer's behalf — a reply normally arrives only when the recipient sends one — with one exception: `send` with `await: true` to a peer that is mid-turn and cannot reach a step boundary (async execution disabled, e.g. blocked in a synchronous task spawn) gets a side-channel auto-reply generated from that peer's context. For background on what a peer has been doing, `read` `history://<id>` instead of interrogating them.
|
|
13
13
|
</instruction>
|
|
14
14
|
|
|
15
15
|
<when_to_use>
|