@oh-my-pi/pi-coding-agent 15.10.12 → 15.11.1
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 +90 -4
- package/dist/cli.js +869 -825
- package/dist/types/async/index.d.ts +0 -1
- package/dist/types/capability/mcp.d.ts +1 -0
- package/dist/types/cli/gallery-fixtures/types.d.ts +5 -0
- package/dist/types/config/keybindings.d.ts +6 -1
- package/dist/types/config/settings-schema.d.ts +66 -34
- package/dist/types/export/html/template.generated.d.ts +1 -1
- package/dist/types/extensibility/custom-tools/types.d.ts +2 -2
- package/dist/types/extensibility/shared-events.d.ts +2 -2
- package/dist/types/internal-urls/history-protocol.d.ts +14 -0
- package/dist/types/internal-urls/index.d.ts +1 -0
- package/dist/types/internal-urls/types.d.ts +1 -1
- package/dist/types/irc/bus.d.ts +66 -0
- package/dist/types/mcp/oauth-discovery.d.ts +2 -0
- package/dist/types/mcp/oauth-flow.d.ts +6 -1
- package/dist/types/mcp/transports/stdio.d.ts +1 -0
- package/dist/types/mcp/types.d.ts +2 -0
- package/dist/types/modes/components/agent-hub.d.ts +30 -0
- package/dist/types/modes/components/assistant-message.d.ts +1 -0
- package/dist/types/modes/components/compaction-summary-message.d.ts +10 -4
- package/dist/types/modes/components/custom-editor.d.ts +2 -0
- package/dist/types/modes/components/mcp-add-wizard.d.ts +2 -1
- package/dist/types/modes/components/settings-selector.d.ts +1 -0
- package/dist/types/modes/components/status-line/types.d.ts +3 -0
- package/dist/types/modes/components/tool-execution.d.ts +8 -0
- package/dist/types/modes/components/transcript-container.d.ts +3 -2
- package/dist/types/modes/components/ttsr-notification.d.ts +5 -1
- package/dist/types/modes/components/welcome.d.ts +3 -9
- package/dist/types/modes/controllers/selector-controller.d.ts +1 -1
- package/dist/types/modes/controllers/tool-args-reveal.d.ts +43 -0
- package/dist/types/modes/interactive-mode.d.ts +3 -2
- package/dist/types/modes/theme/theme.d.ts +3 -1
- package/dist/types/modes/types.d.ts +3 -2
- package/dist/types/modes/utils/ui-helpers.d.ts +1 -1
- package/dist/types/registry/agent-lifecycle.d.ts +51 -0
- package/dist/types/registry/agent-registry.d.ts +16 -5
- package/dist/types/session/agent-session.d.ts +35 -30
- package/dist/types/session/messages.d.ts +2 -4
- package/dist/types/session/session-history-format.d.ts +12 -0
- package/dist/types/session/session-manager.d.ts +21 -3
- package/dist/types/session/streaming-output.d.ts +23 -0
- package/dist/types/task/executor.d.ts +11 -2
- package/dist/types/task/index.d.ts +11 -4
- package/dist/types/task/output-manager.d.ts +0 -7
- package/dist/types/task/repair-args.d.ts +8 -7
- package/dist/types/task/types.d.ts +55 -51
- package/dist/types/tools/browser/tab-worker.d.ts +3 -1
- package/dist/types/tools/find.d.ts +0 -11
- package/dist/types/tools/grouped-file-output.d.ts +0 -49
- package/dist/types/tools/index.d.ts +1 -3
- package/dist/types/tools/irc.d.ts +76 -38
- package/dist/types/tools/job.d.ts +7 -1
- package/dist/types/tools/render-utils.d.ts +22 -0
- package/examples/extensions/with-deps/package.json +1 -0
- package/package.json +11 -10
- package/scripts/bundle-dist.ts +28 -19
- package/src/async/index.ts +0 -1
- package/src/capability/mcp.ts +1 -0
- package/src/cli/gallery-cli.ts +6 -5
- package/src/cli/gallery-fixtures/agentic.ts +230 -115
- package/src/cli/gallery-fixtures/types.ts +5 -0
- package/src/cli.ts +20 -6
- package/src/commit/agentic/tools/analyze-file.ts +38 -19
- package/src/config/keybindings.ts +6 -1
- package/src/config/mcp-schema.json +4 -0
- package/src/config/settings-schema.ts +68 -41
- package/src/config/settings.ts +7 -0
- package/src/edit/renderer.ts +96 -46
- package/src/eval/__tests__/agent-bridge.test.ts +5 -3
- package/src/eval/agent-bridge.ts +3 -16
- package/src/eval/js/shared/prelude.txt +1 -1
- package/src/eval/py/prelude.py +5 -6
- package/src/export/html/template.generated.ts +1 -1
- package/src/export/html/template.js +44 -14
- package/src/extensibility/custom-tools/types.ts +2 -2
- package/src/extensibility/shared-events.ts +2 -2
- package/src/internal-urls/docs-index.generated.ts +9 -9
- package/src/internal-urls/history-protocol.ts +113 -0
- package/src/internal-urls/index.ts +1 -0
- package/src/internal-urls/router.ts +3 -1
- package/src/internal-urls/types.ts +1 -1
- package/src/irc/bus.ts +292 -0
- package/src/main.ts +8 -60
- package/src/mcp/manager.ts +3 -0
- package/src/mcp/oauth-discovery.ts +27 -2
- package/src/mcp/oauth-flow.ts +47 -1
- package/src/mcp/transports/stdio.ts +3 -0
- package/src/mcp/types.ts +2 -0
- package/src/modes/components/{session-observer-overlay.ts → agent-hub.ts} +586 -367
- package/src/modes/components/assistant-message.ts +15 -0
- package/src/modes/components/btw-panel.ts +5 -1
- package/src/modes/components/compaction-summary-message.ts +68 -32
- package/src/modes/components/custom-editor.ts +10 -0
- package/src/modes/components/mcp-add-wizard.ts +13 -0
- package/src/modes/components/settings-selector.ts +2 -0
- package/src/modes/components/status-line/component.ts +22 -12
- package/src/modes/components/status-line/types.ts +3 -0
- package/src/modes/components/tool-execution.ts +31 -1
- package/src/modes/components/transcript-container.ts +99 -18
- package/src/modes/components/tree-selector.ts +6 -1
- package/src/modes/components/ttsr-notification.ts +72 -30
- package/src/modes/components/welcome.ts +9 -33
- package/src/modes/controllers/event-controller.ts +93 -4
- package/src/modes/controllers/extension-ui-controller.ts +8 -8
- package/src/modes/controllers/input-controller.ts +18 -2
- package/src/modes/controllers/mcp-command-controller.ts +34 -2
- package/src/modes/controllers/selector-controller.ts +25 -17
- package/src/modes/controllers/tool-args-reveal.ts +174 -0
- package/src/modes/interactive-mode.ts +17 -15
- package/src/modes/theme/theme.ts +24 -5
- package/src/modes/types.ts +3 -5
- package/src/modes/utils/hotkeys-markdown.ts +1 -0
- package/src/modes/utils/ui-helpers.ts +51 -49
- package/src/prompts/system/irc-incoming.md +3 -4
- package/src/prompts/system/orchestrate-notice.md +2 -2
- package/src/prompts/system/subagent-system-prompt.md +0 -5
- package/src/prompts/system/system-prompt.md +1 -0
- package/src/prompts/system/workflow-notice.md +2 -2
- package/src/prompts/tools/eval.md +3 -3
- package/src/prompts/tools/irc.md +29 -19
- package/src/prompts/tools/read.md +2 -2
- package/src/prompts/tools/task-summary.md +5 -16
- package/src/prompts/tools/task.md +43 -29
- package/src/registry/agent-lifecycle.ts +218 -0
- package/src/registry/agent-registry.ts +16 -5
- package/src/sdk.ts +29 -9
- package/src/session/agent-session.ts +268 -241
- package/src/session/messages.ts +11 -78
- package/src/session/session-history-format.ts +246 -0
- package/src/session/session-manager.ts +59 -5
- package/src/session/streaming-output.ts +60 -0
- package/src/task/executor.ts +855 -466
- package/src/task/index.ts +723 -794
- package/src/task/output-manager.ts +0 -11
- package/src/task/render.ts +142 -66
- package/src/task/repair-args.ts +21 -9
- package/src/task/types.ts +73 -66
- package/src/tools/ask.ts +4 -2
- package/src/tools/bash.ts +15 -5
- package/src/tools/browser/tab-worker.ts +26 -7
- package/src/tools/browser.ts +28 -1
- package/src/tools/find.ts +2 -27
- package/src/tools/grouped-file-output.ts +1 -118
- package/src/tools/index.ts +4 -12
- package/src/tools/irc.ts +596 -171
- package/src/tools/job.ts +41 -7
- package/src/tools/read.ts +57 -1
- package/src/tools/render-utils.ts +56 -0
- package/src/tools/renderers.ts +2 -0
- package/src/tools/resolve.ts +4 -1
- package/src/tools/write.ts +65 -47
- package/src/web/search/providers/anthropic.ts +29 -4
- package/dist/types/async/support.d.ts +0 -2
- package/dist/types/modes/components/session-observer-overlay.d.ts +0 -11
- package/dist/types/task/simple-mode.d.ts +0 -8
- package/src/async/support.ts +0 -5
- package/src/task/simple-mode.ts +0 -27
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { parseStreamingJson } from "@oh-my-pi/pi-ai/utils/json-parse";
|
|
2
|
+
import { nextStep, STREAMING_REVEAL_FRAME_MS } from "./streaming-reveal";
|
|
3
|
+
|
|
4
|
+
/** Minimal component surface the reveal pushes frames into. */
|
|
5
|
+
type ToolArgsRevealComponent = {
|
|
6
|
+
updateArgs(args: unknown, toolCallId?: string): void;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
type ToolArgsRevealControllerOptions = {
|
|
10
|
+
getSmoothStreaming(): boolean;
|
|
11
|
+
requestRender(): void;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
type RevealEntry = {
|
|
15
|
+
component: ToolArgsRevealComponent | undefined;
|
|
16
|
+
/** Latest raw streamed argument text (JSON for function tools, raw text for custom tools). */
|
|
17
|
+
target: string;
|
|
18
|
+
/** Revealed UTF-16 code units of `target`. */
|
|
19
|
+
revealed: number;
|
|
20
|
+
/** Custom-tool raw input: display args are `{ input: prefix }`, never parsed as JSON. */
|
|
21
|
+
rawInput: boolean;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/** Clamp a slice end into `text`, never splitting a surrogate pair: a prefix
|
|
25
|
+
* ending on a high surrogate would feed a lone surrogate into the parsed
|
|
26
|
+
* preview args (providers decode UTF-8 incrementally, so the raw stream
|
|
27
|
+
* itself never contains one). */
|
|
28
|
+
function clampSliceEnd(text: string, end: number): number {
|
|
29
|
+
if (end <= 0) return 0;
|
|
30
|
+
if (end >= text.length) return text.length;
|
|
31
|
+
const code = text.charCodeAt(end - 1);
|
|
32
|
+
return code >= 0xd800 && code <= 0xdbff ? end + 1 : end;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Display args for a revealed raw-stream prefix. Function-tool prefixes are
|
|
36
|
+
* re-parsed with the same streaming-tolerant parser providers use, so every
|
|
37
|
+
* frame is a state the provider itself could have produced; custom tools
|
|
38
|
+
* mirror the provider's `{ input }` shape. `__partialJson` carries the
|
|
39
|
+
* matching raw prefix for renderers that read it directly (bash env preview,
|
|
40
|
+
* edit strategies). */
|
|
41
|
+
function buildDisplayArgs(prefix: string, rawInput: boolean): Record<string, unknown> {
|
|
42
|
+
const base: Record<string, unknown> = rawInput ? { input: prefix } : parseStreamingJson(prefix);
|
|
43
|
+
return { ...base, __partialJson: prefix };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Paces streamed tool-call arguments the same way StreamingRevealController
|
|
48
|
+
* paces assistant text: providers that deliver `partialJson` in large batches
|
|
49
|
+
* (or throttle their partial parses) would otherwise make write/edit/bash
|
|
50
|
+
* streaming previews jump in chunks. Each pending tool call reveals its raw
|
|
51
|
+
* argument stream at the shared 30fps cadence with the same adaptive
|
|
52
|
+
* catch-up step, re-parsing the revealed prefix per frame.
|
|
53
|
+
*
|
|
54
|
+
* Reveal units are UTF-16 code units of the raw stream, not graphemes —
|
|
55
|
+
* the prefix goes through a JSON parser rather than straight to the screen,
|
|
56
|
+
* so only surrogate-pair integrity matters (see {@link clampSliceEnd}).
|
|
57
|
+
*/
|
|
58
|
+
export class ToolArgsRevealController {
|
|
59
|
+
readonly #getSmoothStreaming: () => boolean;
|
|
60
|
+
readonly #requestRender: () => void;
|
|
61
|
+
readonly #entries = new Map<string, RevealEntry>();
|
|
62
|
+
#timer: NodeJS.Timeout | undefined;
|
|
63
|
+
|
|
64
|
+
constructor(options: ToolArgsRevealControllerOptions) {
|
|
65
|
+
this.#getSmoothStreaming = options.getSmoothStreaming;
|
|
66
|
+
this.#requestRender = options.requestRender;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Record the latest streamed argument text for a tool call and return the
|
|
71
|
+
* args to render right now. With smoothing disabled the full target passes
|
|
72
|
+
* through in the caller's legacy shape (`{ ...args, __partialJson }`).
|
|
73
|
+
*/
|
|
74
|
+
setTarget(
|
|
75
|
+
id: string,
|
|
76
|
+
partialJson: string,
|
|
77
|
+
rawInput: boolean,
|
|
78
|
+
fullArgs: Record<string, unknown>,
|
|
79
|
+
): Record<string, unknown> {
|
|
80
|
+
if (!this.#getSmoothStreaming()) {
|
|
81
|
+
// Toggle may flip mid-call: drop any live entry so ticks stop.
|
|
82
|
+
this.#entries.delete(id);
|
|
83
|
+
return { ...fullArgs, __partialJson: partialJson };
|
|
84
|
+
}
|
|
85
|
+
let entry = this.#entries.get(id);
|
|
86
|
+
if (!entry) {
|
|
87
|
+
entry = { component: undefined, target: partialJson, revealed: 0, rawInput };
|
|
88
|
+
this.#entries.set(id, entry);
|
|
89
|
+
} else {
|
|
90
|
+
// Streams only append; a non-prefix target means a rewind — snap into range.
|
|
91
|
+
if (!partialJson.startsWith(entry.target)) {
|
|
92
|
+
entry.revealed = Math.min(entry.revealed, partialJson.length);
|
|
93
|
+
}
|
|
94
|
+
entry.target = partialJson;
|
|
95
|
+
}
|
|
96
|
+
entry.revealed = clampSliceEnd(entry.target, entry.revealed);
|
|
97
|
+
this.#syncTimer();
|
|
98
|
+
return buildDisplayArgs(entry.target.slice(0, entry.revealed), entry.rawInput);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** Attach the component future ticks push frames into. */
|
|
102
|
+
bind(id: string, component: ToolArgsRevealComponent): void {
|
|
103
|
+
const entry = this.#entries.get(id);
|
|
104
|
+
if (entry) entry.component = component;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** Final arguments arrived (the JSON closed): drop the reveal so the
|
|
108
|
+
* caller's final-args render wins immediately, mirroring how assistant
|
|
109
|
+
* text snaps to the full message at message_end. */
|
|
110
|
+
finish(id: string): void {
|
|
111
|
+
this.#entries.delete(id);
|
|
112
|
+
if (this.#entries.size === 0) this.#stopTimer();
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** Snap every live entry to its full received stream and clear. Used at
|
|
116
|
+
* message_end (abort/error mid-stream) so sealed components freeze showing
|
|
117
|
+
* everything that arrived rather than a mid-reveal prefix. */
|
|
118
|
+
flushAll(): void {
|
|
119
|
+
for (const [id, entry] of this.#entries) {
|
|
120
|
+
if (entry.component && entry.revealed < entry.target.length) {
|
|
121
|
+
entry.component.updateArgs(buildDisplayArgs(entry.target, entry.rawInput), id);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
this.#entries.clear();
|
|
125
|
+
this.#stopTimer();
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** Clear without pushing (teardown). */
|
|
129
|
+
stop(): void {
|
|
130
|
+
this.#entries.clear();
|
|
131
|
+
this.#stopTimer();
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
#syncTimer(): void {
|
|
135
|
+
for (const entry of this.#entries.values()) {
|
|
136
|
+
if (entry.revealed < entry.target.length) {
|
|
137
|
+
this.#startTimer();
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
this.#stopTimer();
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
#startTimer(): void {
|
|
145
|
+
if (this.#timer) return;
|
|
146
|
+
this.#timer = setInterval(() => {
|
|
147
|
+
this.#tick();
|
|
148
|
+
}, STREAMING_REVEAL_FRAME_MS);
|
|
149
|
+
this.#timer.unref?.();
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
#stopTimer(): void {
|
|
153
|
+
if (!this.#timer) return;
|
|
154
|
+
clearInterval(this.#timer);
|
|
155
|
+
this.#timer = undefined;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
#tick(): void {
|
|
159
|
+
let advanced = false;
|
|
160
|
+
for (const [id, entry] of this.#entries) {
|
|
161
|
+
const backlog = entry.target.length - entry.revealed;
|
|
162
|
+
if (backlog <= 0 || !entry.component) continue;
|
|
163
|
+
entry.revealed = clampSliceEnd(entry.target, entry.revealed + nextStep(backlog));
|
|
164
|
+
entry.component.updateArgs(buildDisplayArgs(entry.target.slice(0, entry.revealed), entry.rawInput), id);
|
|
165
|
+
advanced = true;
|
|
166
|
+
}
|
|
167
|
+
if (advanced) {
|
|
168
|
+
this.#requestRender();
|
|
169
|
+
} else {
|
|
170
|
+
// Every entry caught up (or unbound); setTarget restarts on growth.
|
|
171
|
+
this.#stopTimer();
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
@@ -327,6 +327,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
327
327
|
#pendingSubmissionDispose: (() => void) | undefined;
|
|
328
328
|
lastSigintTime = 0;
|
|
329
329
|
lastEscapeTime = 0;
|
|
330
|
+
lastLeftTapTime = 0;
|
|
330
331
|
shutdownRequested = false;
|
|
331
332
|
#isShuttingDown = false;
|
|
332
333
|
hookSelector: HookSelectorComponent | undefined = undefined;
|
|
@@ -495,8 +496,12 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
495
496
|
}
|
|
496
497
|
|
|
497
498
|
playWelcomeIntro(): void {
|
|
498
|
-
this.#welcomeComponent
|
|
499
|
+
const welcome = this.#welcomeComponent;
|
|
500
|
+
// Component-scoped: the intro only mutates the welcome box's own rows,
|
|
501
|
+
// so a resumed long transcript is not re-walked per animation frame.
|
|
502
|
+
welcome?.playIntro(() => this.ui.requestComponentRender(welcome));
|
|
499
503
|
}
|
|
504
|
+
|
|
500
505
|
async init(options: InteractiveModeInitOptions = {}): Promise<void> {
|
|
501
506
|
if (this.isInitialized) return;
|
|
502
507
|
|
|
@@ -1049,6 +1054,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1049
1054
|
separator: settings.get("statusLine.separator"),
|
|
1050
1055
|
showHookStatus: settings.get("statusLine.showHookStatus"),
|
|
1051
1056
|
sessionAccent: settings.get("statusLine.sessionAccent"),
|
|
1057
|
+
transparent: settings.get("statusLine.transparent"),
|
|
1052
1058
|
segmentOptions: settings.get("statusLine.segmentOptions"),
|
|
1053
1059
|
});
|
|
1054
1060
|
}
|
|
@@ -1088,7 +1094,9 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1088
1094
|
|
|
1089
1095
|
rebuildChatFromMessages(): void {
|
|
1090
1096
|
this.chatContainer.clear();
|
|
1091
|
-
|
|
1097
|
+
// Full-history transcript: compactions render as inline dividers instead
|
|
1098
|
+
// of restarting the visible conversation (the LLM context still resets).
|
|
1099
|
+
const context = this.session.buildTranscriptSessionContext();
|
|
1092
1100
|
this.renderSessionContext(context);
|
|
1093
1101
|
}
|
|
1094
1102
|
|
|
@@ -2880,11 +2888,8 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
2880
2888
|
this.#uiHelpers.renderSessionContext(sessionContext, options);
|
|
2881
2889
|
}
|
|
2882
2890
|
|
|
2883
|
-
renderInitialMessages(
|
|
2884
|
-
|
|
2885
|
-
options?: { preserveExistingChat?: boolean; clearTerminalHistory?: boolean },
|
|
2886
|
-
): void {
|
|
2887
|
-
this.#uiHelpers.renderInitialMessages(prebuiltContext, options);
|
|
2891
|
+
renderInitialMessages(options?: { preserveExistingChat?: boolean; clearTerminalHistory?: boolean }): void {
|
|
2892
|
+
this.#uiHelpers.renderInitialMessages(options);
|
|
2888
2893
|
}
|
|
2889
2894
|
|
|
2890
2895
|
getUserMessageText(message: Message): string {
|
|
@@ -3036,7 +3041,9 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
3036
3041
|
this.#voiceAnimationInterval = setInterval(() => {
|
|
3037
3042
|
this.#voiceHue = (this.#voiceHue + 8) % 360;
|
|
3038
3043
|
this.#updateMicIcon();
|
|
3039
|
-
|
|
3044
|
+
// Component-scoped: the hue sweep only recolors the editor's cursor
|
|
3045
|
+
// glyph, so the transcript subtree is reused per animation frame.
|
|
3046
|
+
this.ui.requestComponentRender(this.editor);
|
|
3040
3047
|
}, 60);
|
|
3041
3048
|
}
|
|
3042
3049
|
|
|
@@ -3068,13 +3075,8 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
3068
3075
|
await this.#selectorController.showDebugSelector();
|
|
3069
3076
|
}
|
|
3070
3077
|
|
|
3071
|
-
|
|
3072
|
-
|
|
3073
|
-
if (sessions.length <= 1) {
|
|
3074
|
-
this.showStatus("No active subagent sessions");
|
|
3075
|
-
return;
|
|
3076
|
-
}
|
|
3077
|
-
this.#selectorController.showSessionObserver(this.#observerRegistry);
|
|
3078
|
+
showAgentHub(): void {
|
|
3079
|
+
this.#selectorController.showAgentHub(this.#observerRegistry);
|
|
3078
3080
|
}
|
|
3079
3081
|
|
|
3080
3082
|
resetObserverRegistry(): void {
|
package/src/modes/theme/theme.ts
CHANGED
|
@@ -108,6 +108,7 @@ export type SymbolKey =
|
|
|
108
108
|
| "icon.time"
|
|
109
109
|
| "icon.pi"
|
|
110
110
|
| "icon.agents"
|
|
111
|
+
| "icon.job"
|
|
111
112
|
| "icon.cache"
|
|
112
113
|
| "icon.input"
|
|
113
114
|
| "icon.output"
|
|
@@ -129,6 +130,8 @@ export type SymbolKey =
|
|
|
129
130
|
| "icon.extensionInstruction"
|
|
130
131
|
// STT
|
|
131
132
|
| "icon.mic"
|
|
133
|
+
// Compaction divider
|
|
134
|
+
| "icon.camera"
|
|
132
135
|
// Thinking Levels
|
|
133
136
|
| "thinking.minimal"
|
|
134
137
|
| "thinking.low"
|
|
@@ -220,7 +223,8 @@ export type SymbolKey =
|
|
|
220
223
|
| "tool.resolve"
|
|
221
224
|
| "tool.review"
|
|
222
225
|
| "tool.inspectImage"
|
|
223
|
-
| "tool.goal"
|
|
226
|
+
| "tool.goal"
|
|
227
|
+
| "tool.irc";
|
|
224
228
|
|
|
225
229
|
type SymbolMap = Record<SymbolKey, string>;
|
|
226
230
|
|
|
@@ -301,6 +305,7 @@ const UNICODE_SYMBOLS: SymbolMap = {
|
|
|
301
305
|
"icon.time": "⏱",
|
|
302
306
|
"icon.pi": "π",
|
|
303
307
|
"icon.agents": "👥",
|
|
308
|
+
"icon.job": "⚙",
|
|
304
309
|
"icon.cache": "💾",
|
|
305
310
|
"icon.input": "⤵",
|
|
306
311
|
"icon.output": "⤴",
|
|
@@ -322,13 +327,15 @@ const UNICODE_SYMBOLS: SymbolMap = {
|
|
|
322
327
|
"icon.extensionInstruction": "📘",
|
|
323
328
|
// STT
|
|
324
329
|
"icon.mic": "🎤",
|
|
330
|
+
// Compaction divider
|
|
331
|
+
"icon.camera": "📷",
|
|
325
332
|
// Thinking levels
|
|
326
333
|
"thinking.minimal": "◔ min",
|
|
327
334
|
"thinking.low": "◑ low",
|
|
328
335
|
"thinking.medium": "◒ med",
|
|
329
336
|
"thinking.high": "◕ high",
|
|
330
337
|
"thinking.xhigh": "◉ xhigh",
|
|
331
|
-
"thinking.autoPending": "
|
|
338
|
+
"thinking.autoPending": "⟳",
|
|
332
339
|
// Checkboxes
|
|
333
340
|
"checkbox.checked": "☑",
|
|
334
341
|
"checkbox.unchecked": "☐",
|
|
@@ -414,6 +421,7 @@ const UNICODE_SYMBOLS: SymbolMap = {
|
|
|
414
421
|
"tool.review": "◉",
|
|
415
422
|
"tool.inspectImage": "🖼",
|
|
416
423
|
"tool.goal": "◎",
|
|
424
|
+
"tool.irc": "✉",
|
|
417
425
|
};
|
|
418
426
|
|
|
419
427
|
const NERD_SYMBOLS: SymbolMap = {
|
|
@@ -561,6 +569,8 @@ const NERD_SYMBOLS: SymbolMap = {
|
|
|
561
569
|
"icon.pi": "\ue22c",
|
|
562
570
|
// pick: | alt:
|
|
563
571
|
"icon.agents": "\uf0c0",
|
|
572
|
+
// pick: (nf-fa-gear) | alt: ⚙
|
|
573
|
+
"icon.job": "\uf013",
|
|
564
574
|
// pick: | alt:
|
|
565
575
|
"icon.cache": "\uf1c0",
|
|
566
576
|
// pick: | alt: →
|
|
@@ -599,6 +609,8 @@ const NERD_SYMBOLS: SymbolMap = {
|
|
|
599
609
|
"icon.extensionInstruction": "\uf02d",
|
|
600
610
|
// STT - fa-microphone
|
|
601
611
|
"icon.mic": "\uf130",
|
|
612
|
+
// Compaction divider - fa-camera-retro
|
|
613
|
+
"icon.camera": "\uf083",
|
|
602
614
|
// Thinking Levels - emoji labels
|
|
603
615
|
// pick: 🤨 min | alt: min min
|
|
604
616
|
"thinking.minimal": "\u{F0E7} min",
|
|
@@ -610,8 +622,8 @@ const NERD_SYMBOLS: SymbolMap = {
|
|
|
610
622
|
"thinking.high": "\u{F111} high",
|
|
611
623
|
// pick: 🧠 xhi | alt: xhi xhi
|
|
612
624
|
"thinking.xhigh": "\u{F06D} xhi",
|
|
613
|
-
// pick:
|
|
614
|
-
"thinking.autoPending": "\
|
|
625
|
+
// pick: (fa-circle-o-notch) | alt: (nf-md-cached) ⟳
|
|
626
|
+
"thinking.autoPending": "\uf1ce",
|
|
615
627
|
// Checkboxes
|
|
616
628
|
// pick: | alt:
|
|
617
629
|
"checkbox.checked": "\uf14a",
|
|
@@ -708,6 +720,7 @@ const NERD_SYMBOLS: SymbolMap = {
|
|
|
708
720
|
"tool.review": "\uEA70",
|
|
709
721
|
"tool.inspectImage": "\uEAEA",
|
|
710
722
|
"tool.goal": "\uEBF8",
|
|
723
|
+
"tool.irc": "\uF086",
|
|
711
724
|
};
|
|
712
725
|
|
|
713
726
|
const ASCII_SYMBOLS: SymbolMap = {
|
|
@@ -787,6 +800,7 @@ const ASCII_SYMBOLS: SymbolMap = {
|
|
|
787
800
|
"icon.time": "t:",
|
|
788
801
|
"icon.pi": "pi",
|
|
789
802
|
"icon.agents": "AG",
|
|
803
|
+
"icon.job": "bg",
|
|
790
804
|
"icon.cache": "cache",
|
|
791
805
|
"icon.input": "in:",
|
|
792
806
|
"icon.output": "out:",
|
|
@@ -808,13 +822,15 @@ const ASCII_SYMBOLS: SymbolMap = {
|
|
|
808
822
|
"icon.extensionInstruction": "IN",
|
|
809
823
|
// STT
|
|
810
824
|
"icon.mic": "MIC",
|
|
825
|
+
// Compaction divider
|
|
826
|
+
"icon.camera": "[o]",
|
|
811
827
|
// Thinking Levels
|
|
812
828
|
"thinking.minimal": "[min]",
|
|
813
829
|
"thinking.low": "[low]",
|
|
814
830
|
"thinking.medium": "[med]",
|
|
815
831
|
"thinking.high": "[high]",
|
|
816
832
|
"thinking.xhigh": "[xhi]",
|
|
817
|
-
"thinking.autoPending": "[
|
|
833
|
+
"thinking.autoPending": "[~]",
|
|
818
834
|
// Checkboxes
|
|
819
835
|
"checkbox.checked": "[x]",
|
|
820
836
|
"checkbox.unchecked": "[ ]",
|
|
@@ -898,6 +914,7 @@ const ASCII_SYMBOLS: SymbolMap = {
|
|
|
898
914
|
"tool.review": "rev",
|
|
899
915
|
"tool.inspectImage": "[i]",
|
|
900
916
|
"tool.goal": "(o)",
|
|
917
|
+
"tool.irc": "irc",
|
|
901
918
|
};
|
|
902
919
|
|
|
903
920
|
const SYMBOL_PRESETS: Record<SymbolPreset, SymbolMap> = {
|
|
@@ -1666,6 +1683,7 @@ export class Theme {
|
|
|
1666
1683
|
time: this.#symbols["icon.time"],
|
|
1667
1684
|
pi: this.#symbols["icon.pi"],
|
|
1668
1685
|
agents: this.#symbols["icon.agents"],
|
|
1686
|
+
job: this.#symbols["icon.job"],
|
|
1669
1687
|
cache: this.#symbols["icon.cache"],
|
|
1670
1688
|
input: this.#symbols["icon.input"],
|
|
1671
1689
|
output: this.#symbols["icon.output"],
|
|
@@ -1686,6 +1704,7 @@ export class Theme {
|
|
|
1686
1704
|
extensionContextFile: this.#symbols["icon.extensionContextFile"],
|
|
1687
1705
|
extensionInstruction: this.#symbols["icon.extensionInstruction"],
|
|
1688
1706
|
mic: this.#symbols["icon.mic"],
|
|
1707
|
+
camera: this.#symbols["icon.camera"],
|
|
1689
1708
|
};
|
|
1690
1709
|
}
|
|
1691
1710
|
|
package/src/modes/types.ts
CHANGED
|
@@ -136,6 +136,7 @@ export interface InteractiveModeContext {
|
|
|
136
136
|
locallySubmittedUserSignatures: Set<string>;
|
|
137
137
|
lastSigintTime: number;
|
|
138
138
|
lastEscapeTime: number;
|
|
139
|
+
lastLeftTapTime: number;
|
|
139
140
|
shutdownRequested: boolean;
|
|
140
141
|
hookSelector: HookSelectorComponent | undefined;
|
|
141
142
|
hookInput: HookInputComponent | undefined;
|
|
@@ -225,10 +226,7 @@ export interface InteractiveModeContext {
|
|
|
225
226
|
sessionContext: SessionContext,
|
|
226
227
|
options?: { updateFooter?: boolean; populateHistory?: boolean },
|
|
227
228
|
): void;
|
|
228
|
-
renderInitialMessages(
|
|
229
|
-
prebuiltContext?: SessionContext,
|
|
230
|
-
options?: { preserveExistingChat?: boolean; clearTerminalHistory?: boolean },
|
|
231
|
-
): void;
|
|
229
|
+
renderInitialMessages(options?: { preserveExistingChat?: boolean; clearTerminalHistory?: boolean }): void;
|
|
232
230
|
getUserMessageText(message: Message): string;
|
|
233
231
|
findLastAssistantMessage(): AssistantMessage | undefined;
|
|
234
232
|
extractAssistantText(message: AssistantMessage): string;
|
|
@@ -292,7 +290,7 @@ export interface InteractiveModeContext {
|
|
|
292
290
|
showProviderSetup(): Promise<void>;
|
|
293
291
|
showHookConfirm(title: string, message: string): Promise<boolean>;
|
|
294
292
|
showDebugSelector(): Promise<void>;
|
|
295
|
-
|
|
293
|
+
showAgentHub(): void;
|
|
296
294
|
resetObserverRegistry(): void;
|
|
297
295
|
|
|
298
296
|
// Input handling
|
|
@@ -50,6 +50,7 @@ export function buildHotkeysMarkdown(bindings: HotkeysMarkdownBindings): string
|
|
|
50
50
|
`| \`${appKey(bindings, "app.editor.external")}\` | Edit message in external editor |`,
|
|
51
51
|
`| \`${appKey(bindings, "app.clipboard.pasteImage")}\` | Paste image from clipboard |`,
|
|
52
52
|
`| \`${appKey(bindings, "app.stt.toggle")}\` | Toggle speech-to-text recording |`,
|
|
53
|
+
`| \`${appKey(bindings, "app.agents.hub")}\` / \`${appKey(bindings, "app.session.observe")}\` / double-tap \`←\` (empty editor) | Open the agent hub |`,
|
|
53
54
|
"| `#` | Open prompt actions |",
|
|
54
55
|
"| `/` | Slash commands |",
|
|
55
56
|
"| `!` | Run bash command |",
|
|
@@ -35,6 +35,7 @@ import {
|
|
|
35
35
|
type SkillPromptDetails,
|
|
36
36
|
} from "../../session/messages";
|
|
37
37
|
import type { SessionContext } from "../../session/session-manager";
|
|
38
|
+
import { createIrcMessageCard } from "../../tools/irc";
|
|
38
39
|
import { formatBytes, formatDuration } from "../../tools/render-utils";
|
|
39
40
|
|
|
40
41
|
type TextBlock = { type: "text"; text: string };
|
|
@@ -190,49 +191,31 @@ export class UiHelpers {
|
|
|
190
191
|
this.ctx.chatContainer.addChild(component);
|
|
191
192
|
break;
|
|
192
193
|
}
|
|
193
|
-
if (
|
|
194
|
-
message.customType === "irc:incoming" ||
|
|
195
|
-
message.customType === "irc:autoreply" ||
|
|
196
|
-
message.customType === "irc:relay"
|
|
197
|
-
) {
|
|
194
|
+
if (message.customType === "irc:incoming" || message.customType === "irc:relay") {
|
|
198
195
|
const details = (
|
|
199
196
|
message as CustomMessage<{
|
|
200
197
|
from?: string;
|
|
201
198
|
to?: string;
|
|
202
199
|
message?: string;
|
|
203
|
-
reply?: string;
|
|
204
200
|
body?: string;
|
|
205
|
-
|
|
201
|
+
replyTo?: string;
|
|
206
202
|
}>
|
|
207
203
|
).details;
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
}
|
|
224
|
-
const block = new TranscriptBlock();
|
|
225
|
-
const header = `${theme.fg("accent", `[IRC] ${arrow}`)}`;
|
|
226
|
-
const headerComponent = new Text(header, 1, 0);
|
|
227
|
-
block.addChild(headerComponent);
|
|
228
|
-
if (body) {
|
|
229
|
-
for (const line of body.split("\n")) {
|
|
230
|
-
const lineComponent = new Text(theme.fg("muted", ` ${line}`), 0, 0);
|
|
231
|
-
block.addChild(lineComponent);
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
this.ctx.chatContainer.addChild(block);
|
|
235
|
-
return [block];
|
|
204
|
+
const incoming = message.customType === "irc:incoming";
|
|
205
|
+
const card = createIrcMessageCard(
|
|
206
|
+
{
|
|
207
|
+
kind: incoming ? "incoming" : "relay",
|
|
208
|
+
from: details?.from,
|
|
209
|
+
to: details?.to,
|
|
210
|
+
body: incoming ? details?.message : details?.body,
|
|
211
|
+
replyTo: details?.replyTo,
|
|
212
|
+
timestamp: message.timestamp,
|
|
213
|
+
},
|
|
214
|
+
() => this.ctx.toolOutputExpanded,
|
|
215
|
+
theme,
|
|
216
|
+
);
|
|
217
|
+
this.ctx.chatContainer.addChild(card);
|
|
218
|
+
return [card];
|
|
236
219
|
}
|
|
237
220
|
const renderer = this.ctx.session.extensionRunner?.getMessageRenderer(message.customType);
|
|
238
221
|
// Both HookMessage and CustomMessage have the same structure, cast for compatibility
|
|
@@ -337,13 +320,23 @@ export class UiHelpers {
|
|
|
337
320
|
let readGroup: ReadToolGroupComponent | null = null;
|
|
338
321
|
const readToolCallArgs = new Map<string, Record<string, unknown>>();
|
|
339
322
|
const readToolCallAssistantComponents = new Map<string, AssistantMessageComponent>();
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
323
|
+
// Rebuild-time mirror of the event controller's displaceable-poll
|
|
324
|
+
// bookkeeping: a `job` poll that found every watched job still running is
|
|
325
|
+
// superseded by the next `job` call, so a rebuilt transcript collapses a
|
|
326
|
+
// repeated-poll run to its final snapshot instead of replaying the spam.
|
|
327
|
+
let waitingPoll: ToolExecutionComponent | null = null;
|
|
328
|
+
const resolveWaitingPoll = (nextToolName?: string) => {
|
|
329
|
+
const previous = waitingPoll;
|
|
330
|
+
if (!previous) return;
|
|
331
|
+
waitingPoll = null;
|
|
332
|
+
if (nextToolName === "job" && previous.isDisplaceableBlock()) {
|
|
333
|
+
this.ctx.chatContainer.removeChild(previous);
|
|
346
334
|
}
|
|
335
|
+
// Sealing freezes the block and stops the waiting-poll spinner that
|
|
336
|
+
// updateResult armed.
|
|
337
|
+
previous.seal();
|
|
338
|
+
};
|
|
339
|
+
for (const message of sessionContext.messages) {
|
|
347
340
|
// Assistant messages need special handling for tool calls
|
|
348
341
|
if (message.role === "assistant") {
|
|
349
342
|
this.ctx.addMessageToChat(message);
|
|
@@ -379,6 +372,7 @@ export class UiHelpers {
|
|
|
379
372
|
if (content.type !== "toolCall") {
|
|
380
373
|
continue;
|
|
381
374
|
}
|
|
375
|
+
resolveWaitingPoll(content.name);
|
|
382
376
|
|
|
383
377
|
if (
|
|
384
378
|
content.name === "read" &&
|
|
@@ -493,8 +487,17 @@ export class UiHelpers {
|
|
|
493
487
|
if (component) {
|
|
494
488
|
component.updateResult(message, false, message.toolCallId);
|
|
495
489
|
this.ctx.pendingTools.delete(message.toolCallId);
|
|
490
|
+
if (
|
|
491
|
+
message.toolName === "job" &&
|
|
492
|
+
component instanceof ToolExecutionComponent &&
|
|
493
|
+
component.isDisplaceableBlock()
|
|
494
|
+
) {
|
|
495
|
+
waitingPoll = component;
|
|
496
|
+
}
|
|
496
497
|
}
|
|
497
498
|
} else {
|
|
499
|
+
// A user prompt closes the displacement window, same as the live path.
|
|
500
|
+
if (message.role === "user") resolveWaitingPoll();
|
|
498
501
|
// All other messages use standard rendering
|
|
499
502
|
this.ctx.addMessageToChat(message, options);
|
|
500
503
|
}
|
|
@@ -504,17 +507,15 @@ export class UiHelpers {
|
|
|
504
507
|
// rebuilt group freezes (even with a never-persisted result) and commits to
|
|
505
508
|
// native scrollback like every other historical block.
|
|
506
509
|
readGroup?.seal();
|
|
507
|
-
|
|
508
|
-
//
|
|
509
|
-
|
|
510
|
-
this.ctx.addMessageToChat(message, options);
|
|
511
|
-
}
|
|
510
|
+
// A trailing waiting poll is final history on rebuild; seal it so it
|
|
511
|
+
// freezes (and its spinner timer stops) like every other block.
|
|
512
|
+
resolveWaitingPoll();
|
|
512
513
|
|
|
513
514
|
this.ctx.pendingTools.clear();
|
|
514
515
|
this.ctx.ui.requestRender();
|
|
515
516
|
}
|
|
516
517
|
|
|
517
|
-
renderInitialMessages(
|
|
518
|
+
renderInitialMessages(options: RenderInitialMessagesOptions = {}): void {
|
|
518
519
|
// This path is used to rebuild the visible chat transcript (e.g. after custom/debug UI).
|
|
519
520
|
// Clear existing rendered chat first to avoid duplicating the full session in the container.
|
|
520
521
|
// On a non-preserving rebuild the existing blocks are discarded for good, so
|
|
@@ -530,8 +531,9 @@ export class UiHelpers {
|
|
|
530
531
|
this.ctx.pendingBashComponents = [];
|
|
531
532
|
this.ctx.pendingPythonComponents = [];
|
|
532
533
|
|
|
533
|
-
//
|
|
534
|
-
|
|
534
|
+
// Display always uses the full-history transcript: compactions show as
|
|
535
|
+
// inline dividers instead of restarting the visible conversation.
|
|
536
|
+
const context = this.ctx.session.buildTranscriptSessionContext();
|
|
535
537
|
this.ctx.renderSessionContext(context, {
|
|
536
538
|
updateFooter: true,
|
|
537
539
|
populateHistory: true,
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
<irc>
|
|
2
|
-
|
|
2
|
+
Incoming IRC message from agent `{{from}}`{{#if replyTo}} (replying to {{replyTo}}){{/if}}:
|
|
3
3
|
|
|
4
|
-
Reply briefly and directly using the conversation context already available to you. NEVER call tools. The reply you write is delivered back to `{{from}}` as your answer.
|
|
5
|
-
|
|
6
|
-
Message:
|
|
7
4
|
{{message}}
|
|
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.
|
|
8
7
|
</irc>
|
|
@@ -8,7 +8,7 @@ You decompose, dispatch, verify, and iterate. Substantial and parallelizable wor
|
|
|
8
8
|
<rules>
|
|
9
9
|
1. **NEVER yield until everything is closed.** A phase finishing is *not* a yield point — launch the next phase in the same turn. Stop only when every requested item is verifiably done, or you hit a concrete [blocked] state that genuinely requires the user.
|
|
10
10
|
2. **Enumerate the full surface before dispatching.** If the request references audits, plans, checklists, phase lists, or file lists, expand them into a flat set of items in `todo`. "Most of them" or "the important ones" is failure. Re-read the source documents — NEVER work from memory.
|
|
11
|
-
3. **Parallelize maximally; NEVER launch a one-off task.** Every set of edits with disjoint file scope MUST ship as
|
|
11
|
+
3. **Parallelize maximally; NEVER launch a one-off task.** Every set of edits with disjoint file scope MUST ship as parallel `task` calls in one message — fan the work as wide as it decomposes. Dispatching divisible work one call at a time, serially, is a failure: split it and dispatch together. If you are about to dispatch exactly one subagent, stop — either there is more to run alongside it (find it and dispatch them together) or the change is small enough to make inline yourself (do it). Serialize only when one subagent produces a contract (types, schema, shared module) the next consumes — and state the dependency when you do.
|
|
12
12
|
4. **Each `task` assignment is self-contained.** Subagents have no shared context. Spell out: target files (≤3–5 explicit paths, no globs), the change with APIs and patterns, edge cases, and observable acceptance criteria. NEVER assume they read the same plan you did.
|
|
13
13
|
5. **Verify after every phase before launching the next.** Run the appropriate gate: `bun check` for types, package-scoped `bun test` for behavior, `lsp diagnostics` for changed files. If a phase introduced breakage, dispatch fix-up subagents *before* moving on. NEVER declare a phase done on a red tree.
|
|
14
14
|
6. **Commit policy.** If the request asks for commits or the repo workflow expects them, commit after each green phase with a focused message. NEVER commit a red tree. NEVER commit work the user did not ask to commit.
|
|
@@ -21,7 +21,7 @@ You decompose, dispatch, verify, and iterate. Substantial and parallelizable wor
|
|
|
21
21
|
<workflow>
|
|
22
22
|
1. **Ingest.** Read every referenced file (audits, plans, prior agent output, current branch state). Run `git status` to see uncommitted changes.
|
|
23
23
|
2. **Plan.** Materialize the full work surface in `todo` as ordered phases. Within each phase, list the parallelizable units.
|
|
24
|
-
3. **Dispatch phase.** Launch all parallel `task` subagents in one
|
|
24
|
+
3. **Dispatch phase.** Launch all parallel `task` subagents in one message, then collect every result (async results / `job poll`) before moving on.
|
|
25
25
|
4. **Verify phase.** Run the gates. On failure, dispatch fix-up subagents and re-verify. Do not advance with a red gate.
|
|
26
26
|
5. **Commit phase** (if applicable). Focused message naming the phase.
|
|
27
27
|
6. **Advance.** Mark the phase done in `todo`, immediately start the next phase. No summary message between phases — keep going.
|
|
@@ -32,11 +32,6 @@ You are working in an isolated working tree at `{{worktree}}` for this sub-task.
|
|
|
32
32
|
You NEVER modify files outside this tree or in the original repository.
|
|
33
33
|
{{/if}}
|
|
34
34
|
|
|
35
|
-
{{#if contextFile}}
|
|
36
|
-
# Conversation Context
|
|
37
|
-
If you need additional information, your conversation with the user is in {{contextFile}} — `read` its tail or `search` it for relevant terms.
|
|
38
|
-
{{/if}}
|
|
39
|
-
|
|
40
35
|
{{#if ircPeers}}
|
|
41
36
|
# IRC Peers
|
|
42
37
|
You can reach other live agents via the `irc` tool. Your id is `{{ircSelfId}}`. Currently visible peers:
|
|
@@ -149,6 +149,7 @@ With most FS/bash-like tools, static references to them will automatically resol
|
|
|
149
149
|
- `agent://<id>`: full agent output artifact
|
|
150
150
|
- `/<path>`: JSON field extraction
|
|
151
151
|
- `artifact://<id>`: Artifact content
|
|
152
|
+
- `history://<agentId>`: agent transcript as concise markdown; bare `history://` lists agents
|
|
152
153
|
- `local://<name>.md`: Plan artifacts and shared content with subagents
|
|
153
154
|
{{#if hasObsidian}}
|
|
154
155
|
- `vault://<vault>/<path>`: Obsidian vault content (read/edit). `vault://` lists vaults; `vault://_/…` targets the active vault. File-scoped `?op=outline|backlinks|links|tags|properties|tasks|base|…`; vault-scoped `?op=search&q=…|daily|tasks|orphans|unresolved|bases|…`.
|