@oh-my-pi/pi-coding-agent 17.0.2 → 17.0.4
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 +80 -20
- package/dist/cli.js +4087 -4108
- package/dist/types/config/settings-schema.d.ts +7 -3
- package/dist/types/eval/agent-bridge.d.ts +7 -19
- package/dist/types/extensibility/legacy-pi-coding-agent-shim.d.ts +15 -1
- package/dist/types/lsp/client.d.ts +2 -0
- package/dist/types/lsp/types.d.ts +3 -0
- package/dist/types/mcp/transports/stdio.d.ts +29 -0
- package/dist/types/mnemopi/state.d.ts +32 -9
- package/dist/types/modes/components/agent-hub.d.ts +15 -0
- package/dist/types/registry/persisted-agents.d.ts +3 -0
- package/dist/types/sdk.d.ts +14 -3
- package/dist/types/session/session-entries.d.ts +6 -1
- package/dist/types/session/session-manager.d.ts +5 -0
- package/dist/types/task/executor.d.ts +26 -1
- package/dist/types/task/index.d.ts +1 -1
- package/dist/types/task/parallel.d.ts +14 -0
- package/dist/types/task/structured-subagent.d.ts +111 -0
- package/dist/types/task/types.d.ts +51 -0
- package/dist/types/tools/fetch.d.ts +4 -5
- package/dist/types/tools/hub/messaging.d.ts +5 -1
- package/dist/types/tools/index.d.ts +16 -1
- package/dist/types/tools/todo.d.ts +31 -0
- package/dist/types/tui/tree-list.d.ts +7 -0
- package/dist/types/utils/markit.d.ts +11 -0
- package/package.json +12 -12
- package/src/cli/file-processor.ts +1 -2
- package/src/config/settings-schema.ts +4 -3
- package/src/eval/__tests__/agent-bridge.test.ts +133 -47
- package/src/eval/__tests__/prelude-agent.test.ts +29 -0
- package/src/eval/agent-bridge.ts +104 -477
- package/src/eval/jl/prelude.jl +7 -6
- package/src/eval/js/shared/prelude.txt +5 -4
- package/src/eval/py/prelude.py +11 -39
- package/src/eval/rb/prelude.rb +5 -6
- package/src/extensibility/legacy-pi-coding-agent-shim.ts +53 -7
- package/src/lsp/client.ts +57 -0
- package/src/lsp/index.ts +62 -6
- package/src/lsp/types.ts +3 -0
- package/src/mcp/oauth-flow.ts +20 -0
- package/src/mcp/transports/stdio.test.ts +269 -1
- package/src/mcp/transports/stdio.ts +152 -1
- package/src/mnemopi/backend.ts +1 -1
- package/src/mnemopi/embed-worker.ts +8 -7
- package/src/mnemopi/state.ts +45 -18
- package/src/modes/components/agent-hub.ts +1 -72
- package/src/modes/components/bash-execution.ts +7 -3
- package/src/modes/components/eval-execution.ts +3 -1
- package/src/modes/components/transcript-container.ts +13 -7
- package/src/modes/interactive-mode.ts +30 -8
- package/src/prompts/system/system-prompt.md +1 -1
- package/src/prompts/tools/eval.md +5 -2
- package/src/prompts/tools/read.md +1 -1
- package/src/prompts/tools/task.md +12 -8
- package/src/prompts/tools/write.md +1 -1
- package/src/registry/persisted-agents.ts +74 -0
- package/src/sdk.ts +129 -87
- package/src/session/agent-session.ts +75 -21
- package/src/session/session-entries.ts +6 -1
- package/src/session/session-loader.ts +31 -3
- package/src/session/session-manager.ts +9 -0
- package/src/session/streaming-output.ts +41 -1
- package/src/stt/recorder.ts +68 -55
- package/src/system-prompt.ts +7 -2
- package/src/task/executor.ts +99 -21
- package/src/task/index.ts +258 -429
- package/src/task/parallel.ts +43 -0
- package/src/task/persisted-revive.ts +19 -7
- package/src/task/structured-subagent.ts +642 -0
- package/src/task/types.ts +61 -0
- package/src/tools/__tests__/eval-description.test.ts +1 -1
- package/src/tools/fetch.ts +28 -105
- package/src/tools/hub/messaging.ts +16 -3
- package/src/tools/index.ts +47 -14
- package/src/tools/path-utils.ts +1 -0
- package/src/tools/read.ts +14 -26
- package/src/tools/todo.ts +126 -13
- package/src/tui/tree-list.ts +39 -0
- package/src/utils/markit.ts +12 -0
- package/src/utils/zip.ts +16 -1
|
@@ -13,17 +13,15 @@
|
|
|
13
13
|
*
|
|
14
14
|
* Replaces the old SessionObserverOverlayComponent (ctrl+s observer).
|
|
15
15
|
*/
|
|
16
|
-
import * as fs from "node:fs";
|
|
17
|
-
import * as path from "node:path";
|
|
18
16
|
import { type AgentTool, ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
19
17
|
import { Container, Ellipsis, matchesKey, type OverlayHandle, padding, type TUI, visibleWidth } from "@oh-my-pi/pi-tui";
|
|
20
18
|
import { formatAge, getProjectDir, logger } from "@oh-my-pi/pi-utils";
|
|
21
|
-
import { ADVISOR_TRANSCRIPT_FILENAME, isAdvisorTranscriptName } from "../../advisor";
|
|
22
19
|
import type { KeyId } from "../../config/keybindings";
|
|
23
20
|
import type { MessageRenderer } from "../../extensibility/extensions/types";
|
|
24
21
|
import { IrcBus } from "../../irc/bus";
|
|
25
22
|
import { AgentLifecycleManager } from "../../registry/agent-lifecycle";
|
|
26
23
|
import { type AgentRef, AgentRegistry, type AgentStatus, MAIN_AGENT_ID } from "../../registry/agent-registry";
|
|
24
|
+
import { registerPersistedSubagents } from "../../registry/persisted-agents";
|
|
27
25
|
import { USER_INTERRUPT_LABEL } from "../../session/messages";
|
|
28
26
|
import { parseThinkingLevel } from "../../thinking";
|
|
29
27
|
import { replaceTabs, TRUNCATE_LENGTHS, truncateToWidth } from "../../tools/render-utils";
|
|
@@ -100,75 +98,6 @@ function modelBadge(ref: AgentRef, observed: ObservableSession | undefined): str
|
|
|
100
98
|
return formatModelBadge(selector.slice(selector.indexOf("/") + 1), level);
|
|
101
99
|
}
|
|
102
100
|
|
|
103
|
-
async function registerPersistedSubagents(
|
|
104
|
-
registry: AgentRegistry,
|
|
105
|
-
sessionFile: string | null | undefined,
|
|
106
|
-
): Promise<void> {
|
|
107
|
-
if (!sessionFile?.endsWith(".jsonl")) return;
|
|
108
|
-
const root = sessionFile.slice(0, -6);
|
|
109
|
-
await registerPersistedSubagentsFromDir(registry, root, undefined);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
async function registerPersistedSubagentsFromDir(
|
|
113
|
-
registry: AgentRegistry,
|
|
114
|
-
dir: string,
|
|
115
|
-
parentId: string | undefined,
|
|
116
|
-
): Promise<void> {
|
|
117
|
-
let entries: fs.Dirent[];
|
|
118
|
-
try {
|
|
119
|
-
entries = await fs.promises.readdir(dir, { withFileTypes: true });
|
|
120
|
-
} catch {
|
|
121
|
-
return;
|
|
122
|
-
}
|
|
123
|
-
for (const entry of entries) {
|
|
124
|
-
if (!entry.isFile() || !entry.name.endsWith(".jsonl") || entry.name.includes(".bak")) continue;
|
|
125
|
-
const sessionFile = path.join(dir, entry.name);
|
|
126
|
-
// The advisor transcript is observability-only: register it as a non-peer
|
|
127
|
-
// `advisor` kind under its owning session so the Hub can show its read-only
|
|
128
|
-
// transcript, but it never joins agent-facing rosters and is not revivable.
|
|
129
|
-
if (isAdvisorTranscriptName(entry.name)) {
|
|
130
|
-
const owner = parentId ?? MAIN_AGENT_ID;
|
|
131
|
-
// `__advisor.jsonl` → the default advisor (no slug); `__advisor.<slug>.jsonl`
|
|
132
|
-
// → a named advisor, keyed and labeled by its slug.
|
|
133
|
-
const slug =
|
|
134
|
-
entry.name === ADVISOR_TRANSCRIPT_FILENAME ? "" : entry.name.slice("__advisor.".length, -".jsonl".length);
|
|
135
|
-
const advisorId = slug ? `${owner}/advisor:${slug}` : `${owner}/advisor`;
|
|
136
|
-
const displayName = slug ? `advisor:${slug}` : "advisor";
|
|
137
|
-
const existing = registry.get(advisorId);
|
|
138
|
-
// Never clobber a non-advisor ref that happens to share this id (a freak
|
|
139
|
-
// user task literally named `<owner>/advisor`): leave it, skip the advisor.
|
|
140
|
-
if (existing && existing.kind !== "advisor") continue;
|
|
141
|
-
if (existing?.sessionFile !== sessionFile) {
|
|
142
|
-
// The id is reused across `/new`; refresh it to the current session's file.
|
|
143
|
-
if (existing) registry.unregister(advisorId);
|
|
144
|
-
registry.register({
|
|
145
|
-
id: advisorId,
|
|
146
|
-
displayName,
|
|
147
|
-
kind: "advisor",
|
|
148
|
-
parentId: owner,
|
|
149
|
-
session: null,
|
|
150
|
-
sessionFile,
|
|
151
|
-
status: "parked",
|
|
152
|
-
});
|
|
153
|
-
}
|
|
154
|
-
continue;
|
|
155
|
-
}
|
|
156
|
-
const id = entry.name.slice(0, -6);
|
|
157
|
-
if (!registry.get(id)) {
|
|
158
|
-
registry.register({
|
|
159
|
-
id,
|
|
160
|
-
displayName: id,
|
|
161
|
-
kind: "sub",
|
|
162
|
-
parentId: parentId ?? MAIN_AGENT_ID,
|
|
163
|
-
session: null,
|
|
164
|
-
sessionFile,
|
|
165
|
-
status: "parked",
|
|
166
|
-
});
|
|
167
|
-
}
|
|
168
|
-
await registerPersistedSubagentsFromDir(registry, path.join(dir, id), id);
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
|
|
172
101
|
/** Result of one host-backed transcript read for the Agent Hub viewer. */
|
|
173
102
|
export interface AgentHubRemoteTranscript {
|
|
174
103
|
text: string;
|
|
@@ -146,14 +146,18 @@ export class BashExecutionComponent extends Container {
|
|
|
146
146
|
#updateDisplay(): void {
|
|
147
147
|
const availableLines = this.#outputLines;
|
|
148
148
|
|
|
149
|
-
//
|
|
149
|
+
// Full output is shown when expanded or when sixel passthrough renders
|
|
150
|
+
// the raw payload; the collapsed preview shows only the tail window.
|
|
150
151
|
const previewLogicalLines = availableLines.slice(-PREVIEW_LINES);
|
|
151
|
-
const hiddenLineCount = availableLines.length - previewLogicalLines.length;
|
|
152
152
|
const sixelLineMask =
|
|
153
153
|
TERMINAL.imageProtocol === ImageProtocol.Sixel && isSixelPassthroughEnabled()
|
|
154
154
|
? getSixelLineMask(availableLines)
|
|
155
155
|
: undefined;
|
|
156
156
|
const hasSixelOutput = sixelLineMask?.some(Boolean) ?? false;
|
|
157
|
+
const showingAllLines = this.#expanded || hasSixelOutput;
|
|
158
|
+
// Only the collapsed preview hides lines; when the full output is shown
|
|
159
|
+
// the footer must not keep advertising hidden lines / ctrl+o.
|
|
160
|
+
const hiddenLineCount = showingAllLines ? 0 : availableLines.length - previewLogicalLines.length;
|
|
157
161
|
|
|
158
162
|
// Rebuild content container
|
|
159
163
|
this.#contentContainer.clear();
|
|
@@ -163,7 +167,7 @@ export class BashExecutionComponent extends Container {
|
|
|
163
167
|
|
|
164
168
|
// Output
|
|
165
169
|
if (availableLines.length > 0) {
|
|
166
|
-
if (
|
|
170
|
+
if (showingAllLines) {
|
|
167
171
|
const displayText = availableLines
|
|
168
172
|
.map((line, index) => (sixelLineMask?.[index] ? line : theme.fg("muted", line)))
|
|
169
173
|
.join("\n");
|
|
@@ -114,7 +114,9 @@ export class EvalExecutionComponent extends Container {
|
|
|
114
114
|
#updateDisplay(): void {
|
|
115
115
|
const availableLines = this.#outputLines;
|
|
116
116
|
const previewLogicalLines = availableLines.slice(-PREVIEW_LINES);
|
|
117
|
-
|
|
117
|
+
// Only the collapsed preview hides lines; when expanded the footer must
|
|
118
|
+
// not keep advertising hidden lines / ctrl+o.
|
|
119
|
+
const hiddenLineCount = this.#expanded ? 0 : availableLines.length - previewLogicalLines.length;
|
|
118
120
|
|
|
119
121
|
this.#contentContainer.clear();
|
|
120
122
|
|
|
@@ -19,11 +19,17 @@ interface FinalizableBlock {
|
|
|
19
19
|
/**
|
|
20
20
|
* Monotonic content version for blocks that can still mutate *after*
|
|
21
21
|
* reporting finalized (e.g. `AssistantMessageComponent`: the inline error
|
|
22
|
-
* restored at the next turn's `agent_start`, late tool-result images).
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
22
|
+
* restored at the next turn's `agent_start`, late tool-result images). While
|
|
23
|
+
* the block's rows are still on screen (not yet fully committed), the
|
|
24
|
+
* committed-scrollback render bypass replays its previous rows only when the
|
|
25
|
+
* version is unchanged; a bump forces a real render so the TUI's
|
|
26
|
+
* committed-prefix audit can observe and re-anchor the change. Once the rows
|
|
27
|
+
* fully commit to native scrollback they are dropped from the local frame
|
|
28
|
+
* (compacted) — a later mutation no longer recommits on an ordinary frame
|
|
29
|
+
* (immutable history the terminal owns; recommitting would duplicate it) and
|
|
30
|
+
* instead rehydrates on the next destructive full replay
|
|
31
|
+
* ({@link "@oh-my-pi/pi-tui".NativeScrollbackReplay}). Blocks that never
|
|
32
|
+
* mutate post-finalize simply omit the method.
|
|
27
33
|
*/
|
|
28
34
|
getTranscriptBlockVersion?(): number;
|
|
29
35
|
/**
|
|
@@ -124,7 +130,7 @@ interface BlockSegment {
|
|
|
124
130
|
sep: number;
|
|
125
131
|
/** Whether the block reported finalized when this segment was rendered. */
|
|
126
132
|
finalized: boolean;
|
|
127
|
-
/** Safe to drop
|
|
133
|
+
/** Safe to drop from the local frame once its rows fully commit to native scrollback: produced while finalized. */
|
|
128
134
|
compactable: boolean;
|
|
129
135
|
/** Block version observed when this segment was rendered (see {@link FinalizableBlock}). */
|
|
130
136
|
version: number | undefined;
|
|
@@ -453,7 +459,7 @@ export class TranscriptContainer
|
|
|
453
459
|
previous.width === width &&
|
|
454
460
|
previous.generation === this.#generation);
|
|
455
461
|
const contribution = reusable ? previous.contribution : stripPlainBlankEdges(raw);
|
|
456
|
-
const compactable = finalized &&
|
|
462
|
+
const compactable = finalized && previous?.finalized !== false;
|
|
457
463
|
|
|
458
464
|
// Empty (or stripped-to-nothing) children contribute nothing and never
|
|
459
465
|
// affect spacing. An empty still-live child still gates the commit
|
|
@@ -123,7 +123,12 @@ import type { LspStartupServerInfo } from "../tools";
|
|
|
123
123
|
import { normalizeLocalScheme } from "../tools/path-utils";
|
|
124
124
|
import { replaceTabs, TRUNCATE_LENGTHS, truncateToWidth } from "../tools/render-utils";
|
|
125
125
|
import { setAutoQaConsentHandler } from "../tools/report-tool-issue";
|
|
126
|
-
import {
|
|
126
|
+
import {
|
|
127
|
+
formatPhaseDisplayName,
|
|
128
|
+
selectCollapsedTodos,
|
|
129
|
+
setActiveTodoDescriptionsProvider,
|
|
130
|
+
todoMatchesAnyDescription,
|
|
131
|
+
} from "../tools/todo";
|
|
127
132
|
import { ToolError } from "../tools/tool-errors";
|
|
128
133
|
import { vocalizer } from "../tts/vocalizer";
|
|
129
134
|
import { renderTreeList } from "../tui/tree-list";
|
|
@@ -951,6 +956,9 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
951
956
|
this.#observerRegistry.onChange(kind => {
|
|
952
957
|
this.#scheduleObserverUiSync(kind);
|
|
953
958
|
});
|
|
959
|
+
// Let the transient todo tool result light up pending todos executed by a
|
|
960
|
+
// live subagent, matching the sticky HUD's active set (#5873).
|
|
961
|
+
setActiveTodoDescriptionsProvider(() => this.#getActiveSubagentDescriptions());
|
|
954
962
|
|
|
955
963
|
// Load initial todos
|
|
956
964
|
await this.#loadTodoList();
|
|
@@ -1895,15 +1903,29 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1895
1903
|
const isMatched = (todo: TodoItem): boolean =>
|
|
1896
1904
|
activeDescs.length > 0 && todoMatchesAnyDescription(todo.content, activeDescs);
|
|
1897
1905
|
|
|
1898
|
-
// Task subtree for a phase. Collapsed
|
|
1899
|
-
//
|
|
1900
|
-
//
|
|
1906
|
+
// Task subtree for a phase. Collapsed runs the shared walking-viewport
|
|
1907
|
+
// policy (completed/abandoned omitted, active work pulled to the head,
|
|
1908
|
+
// then following pending tasks) so the HUD and the transient tool result
|
|
1909
|
+
// can never disagree about the current work (#5873). Expanded lists all.
|
|
1901
1910
|
const renderTasks = (phase: TodoPhase): string[] => {
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1911
|
+
if (expanded) {
|
|
1912
|
+
return renderTreeList(
|
|
1913
|
+
{
|
|
1914
|
+
items: phase.tasks,
|
|
1915
|
+
expanded: true,
|
|
1916
|
+
renderItem: todo => this.#formatTodoLine(todo, "", isMatched(todo)),
|
|
1917
|
+
},
|
|
1918
|
+
theme,
|
|
1919
|
+
);
|
|
1920
|
+
}
|
|
1921
|
+
const selection = selectCollapsedTodos(phase.tasks, isMatched, activeTaskCap);
|
|
1905
1922
|
return renderTreeList(
|
|
1906
|
-
{
|
|
1923
|
+
{
|
|
1924
|
+
items: selection.items,
|
|
1925
|
+
itemType: "task",
|
|
1926
|
+
trailingSummary: selection.summary,
|
|
1927
|
+
renderItem: todo => this.#formatTodoLine(todo, "", isMatched(todo)),
|
|
1928
|
+
},
|
|
1907
1929
|
theme,
|
|
1908
1930
|
);
|
|
1909
1931
|
};
|
|
@@ -57,7 +57,7 @@ Special URLs for internal resources; with most FS/bash tools they auto-resolve t
|
|
|
57
57
|
- `memory://root`: project memory summary
|
|
58
58
|
{{/if}}
|
|
59
59
|
- `agent://<id>`: agent output artifact; `/<child>` reads a nested subagent's output, else `/<path>` extracts a JSON field
|
|
60
|
-
- `history://<id>`: read-only markdown transcript of an agent (live, parked, or released); bare `history://` lists all agents. Serves
|
|
60
|
+
- `history://<id>`: read-only markdown transcript of an agent (live, parked, or released); bare `history://` lists all agents. Serves registered agents process-wide plus persisted subagents discoverable from their artifact trees; does not discover unregistered top-level sessions solely from their persisted session files.
|
|
61
61
|
- `artifact://<id>`: artifact content
|
|
62
62
|
- `local://<name>.md`: plan artifacts or shared content for subagents
|
|
63
63
|
{{#if hasObsidian}}
|
|
@@ -17,9 +17,12 @@ write(path, content) → str
|
|
|
17
17
|
env(key?=None, value?=None) → str | None | dict
|
|
18
18
|
output(*ids, format?="raw", query?=None, offset?=None, limit?=None) → str | dict | list[dict]
|
|
19
19
|
tool.<name>(args) → unknown
|
|
20
|
+
Invoke any session tool; `args` = its parameter object.
|
|
20
21
|
completion(prompt, model?="default"|"smol"|"slow", system?=None, schema?=None) → str | dict
|
|
21
|
-
|
|
22
|
-
{{#if
|
|
22
|
+
Oneshot, stateless (no history/tools). `model`: "smol" fast | "default" session | "slow" most capable. `schema` (JSON-Schema) → parsed object.
|
|
23
|
+
{{#if spawns}}agent(prompt, agent?="{{spawnDefaultAgent}}", model?=None, label?=None, schema?=None, schema{{#if js}}Mode{{else}}_mode{{/if}}?="permissive", isolated?=None, apply?=None, merge?=None, handle?=False) → str | dict
|
|
24
|
+
Run a subagent → final output. `agent` selects a discovered agent; omit it to use `{{spawnDefaultAgent}}`.{{#if spawnAllowedAgentsText}} Allowed agents: {{spawnAllowedAgentsText}}.{{/if}} `schema` overrides agent/session schemas; `schemaMode`/`schema_mode`: "permissive" | "strict". Effective schemas return parsed data. `isolated` requests a worktree; `apply`/`merge` control its changes. Background via `local://` files named in the prompt. `handle` → { text, output, handle: "agent://<id>", id, agent }, parsed `data` when structured.
|
|
25
|
+
{{#if js}} JS: ONE trailing object — agent(prompt, { agent, model, label, schema, schemaMode, isolated, apply, merge, handle }).{{/if}}
|
|
23
26
|
{{/if}}
|
|
24
27
|
parallel(thunks) → list pipeline(items, ...stages) → list
|
|
25
28
|
log(message) → None phase(title) → None
|
|
@@ -15,7 +15,7 @@ Read files, directories, archives, SQLite, images, documents, internal resources
|
|
|
15
15
|
- {{#if IS_HL_MODE}}File + selector → `[foo.ts#1A2B]` snapshot header + numbered lines. Copy `[FILENAME#TAG]` for anchored edits; NEVER fabricate the tag.{{/if}}
|
|
16
16
|
- Directory → depth-limited dirent listing.
|
|
17
17
|
- SQLite (`.sqlite`, `.sqlite3`, `.db`, `.db3`): `file.db` (tables), `file.db:table` (schema+rows), `file.db:table:key` (by PK), `?limit=`/`?where=`/`?q=SELECT`.
|
|
18
|
-
- Archives (`.tar`, `.tar.gz`, `.tgz`, `.zip`): `archive.ext:path/inside/archive` reads a member.
|
|
18
|
+
- Archives (`.tar`, `.tar.gz`, `.tgz`, `.zip`, plus ZIP-based `.jar`/`.war`/`.ear`/`.apk`): `archive.ext:path/inside/archive` reads a member.
|
|
19
19
|
- Documents → extracted text. Notebooks → editable cells. Images → {{#if INSPECT_IMAGE_ENABLED}}metadata; call `inspect_image`{{else}}decoded inline{{/if}}. `:raw` bypasses converters.
|
|
20
20
|
- URLs → reader-mode clean text/markdown; `:raw` → untouched HTML. Bare `host:port` needs trailing slash.
|
|
21
21
|
- Internal URIs — all schemes take selectors. `artifact://<id>` recovers spilled output; page with `:N-M`/`:raw:N-M`.
|
|
@@ -10,18 +10,22 @@ Agents marked BLOCKING run inline — results return in this call; non-blocking
|
|
|
10
10
|
|
|
11
11
|
# Inputs
|
|
12
12
|
{{#if batchEnabled}}
|
|
13
|
-
- `context`: Shared project state
|
|
14
|
-
- `tasks[]`:
|
|
15
|
-
- `name`: CamelCase ≤32 chars (
|
|
16
|
-
- `agent`:
|
|
17
|
-
- `task`: Complete, self-contained instructions
|
|
13
|
+
- `context`: Shared project state, constraints, and contracts. Applies to the entire batch; do not duplicate this background into individual tasks.
|
|
14
|
+
- `tasks[]`: Array of subagents to spawn.
|
|
15
|
+
- `name`: A stable CamelCase identifier (≤32 chars), used to address the agent (IRC, job ids). Generated automatically if omitted.
|
|
16
|
+
- `agent`: The agent type running this item (e.g. `scout`, `reviewer`). Omitting it gives you the general-purpose worker (`{{defaultAgent}}`) — NEVER pass that name explicitly. Only omit it after checking the agent list below and finding no specialist that fits.{{#if allowedAgentsText}} Current spawn policy allows: {{allowedAgentsText}}.{{/if}}
|
|
17
|
+
- `task`: Complete, self-contained instructions. One-liners or missing acceptance criteria are PROHIBITED.
|
|
18
|
+
- `outputSchema`: Invocation-specific JSON Schema. Overrides the selected agent and parent-session schemas.
|
|
19
|
+
- `schemaMode`: `"permissive"` (default) accepts a retry-exhausted invalid result with a warning; `"strict"` fails it.
|
|
18
20
|
{{#if isolationEnabled}}
|
|
19
21
|
- `isolated`: Run in dedicated worktree, return patches. Destroyed on completion, cannot be addressed afterward.
|
|
20
22
|
{{/if}}
|
|
21
23
|
{{else}}
|
|
22
|
-
- `name`: CamelCase ≤32 chars (
|
|
23
|
-
- `agent`:
|
|
24
|
-
- `task`: Complete, self-contained instructions
|
|
24
|
+
- `name`: A stable CamelCase identifier (≤32 chars), used to address the agent (IRC, job ids). Generated automatically if omitted.
|
|
25
|
+
- `agent`: The agent type to spawn (e.g. `scout`, `reviewer`). Omitting it gives you the general-purpose worker (`{{defaultAgent}}`) — NEVER pass that name explicitly. Only omit it after checking the agent list below and finding no specialist that fits.{{#if allowedAgentsText}} Current spawn policy allows: {{allowedAgentsText}}.{{/if}}
|
|
26
|
+
- `task`: Complete, self-contained instructions. One-liners or missing acceptance criteria are PROHIBITED.
|
|
27
|
+
- `outputSchema`: Invocation-specific JSON Schema. Overrides the selected agent and parent-session schemas.
|
|
28
|
+
- `schemaMode`: `"permissive"` (default) accepts a retry-exhausted invalid result with a warning; `"strict"` fails it.
|
|
25
29
|
{{#if isolationEnabled}}
|
|
26
30
|
- `isolated`: Run in dedicated worktree, return patches.
|
|
27
31
|
{{/if}}
|
|
@@ -3,7 +3,7 @@ Creates or overwrites file at specified path.
|
|
|
3
3
|
<conditions>
|
|
4
4
|
- Creating new files explicitly required by task
|
|
5
5
|
- Replacing entire file contents when editing would be more complex
|
|
6
|
-
- Supports `.tar`, `.tar.gz`, `.tgz`, and `.
|
|
6
|
+
- Supports `.tar`, `.tar.gz`, `.tgz`, `.zip`, and ZIP-based `.jar`/`.war`/`.ear`/`.apk` archive entries via `archive.ext:path/inside/archive`
|
|
7
7
|
- Supports SQLite row operations via `db.sqlite:table` (insert), `db.sqlite:table:key` (update with JSON content, delete with empty content)
|
|
8
8
|
</conditions>
|
|
9
9
|
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { ADVISOR_TRANSCRIPT_FILENAME, isAdvisorTranscriptName } from "../advisor/transcript-recorder";
|
|
4
|
+
import { type AgentRegistry, MAIN_AGENT_ID } from "./agent-registry";
|
|
5
|
+
|
|
6
|
+
/** Register persisted subagent and advisor transcripts as parked registry refs. */
|
|
7
|
+
export async function registerPersistedSubagents(
|
|
8
|
+
registry: AgentRegistry,
|
|
9
|
+
sessionFile: string | null | undefined,
|
|
10
|
+
): Promise<void> {
|
|
11
|
+
if (!sessionFile?.endsWith(".jsonl")) return;
|
|
12
|
+
const root = sessionFile.slice(0, -6);
|
|
13
|
+
await registerPersistedSubagentsFromDir(registry, root, undefined);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async function registerPersistedSubagentsFromDir(
|
|
17
|
+
registry: AgentRegistry,
|
|
18
|
+
dir: string,
|
|
19
|
+
parentId: string | undefined,
|
|
20
|
+
): Promise<void> {
|
|
21
|
+
let entries: fs.Dirent[];
|
|
22
|
+
try {
|
|
23
|
+
entries = await fs.promises.readdir(dir, { withFileTypes: true });
|
|
24
|
+
} catch {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
for (const entry of entries) {
|
|
28
|
+
if (!entry.isFile() || !entry.name.endsWith(".jsonl") || entry.name.includes(".bak")) continue;
|
|
29
|
+
const sessionFile = path.join(dir, entry.name);
|
|
30
|
+
// The advisor transcript is observability-only: register it as a non-peer
|
|
31
|
+
// `advisor` kind under its owning session so the Hub can show its read-only
|
|
32
|
+
// transcript, but it never joins agent-facing rosters and is not revivable.
|
|
33
|
+
if (isAdvisorTranscriptName(entry.name)) {
|
|
34
|
+
const owner = parentId ?? MAIN_AGENT_ID;
|
|
35
|
+
// `__advisor.jsonl` → the default advisor (no slug); `__advisor.<slug>.jsonl`
|
|
36
|
+
// → a named advisor, keyed and labeled by its slug.
|
|
37
|
+
const slug =
|
|
38
|
+
entry.name === ADVISOR_TRANSCRIPT_FILENAME ? "" : entry.name.slice("__advisor.".length, -".jsonl".length);
|
|
39
|
+
const advisorId = slug ? `${owner}/advisor:${slug}` : `${owner}/advisor`;
|
|
40
|
+
const displayName = slug ? `advisor:${slug}` : "advisor";
|
|
41
|
+
const existing = registry.get(advisorId);
|
|
42
|
+
// Never clobber a non-advisor ref that happens to share this id (a freak
|
|
43
|
+
// user task literally named `<owner>/advisor`): leave it, skip the advisor.
|
|
44
|
+
if (existing && existing.kind !== "advisor") continue;
|
|
45
|
+
if (existing?.sessionFile !== sessionFile) {
|
|
46
|
+
// The id is reused across `/new`; refresh it to the current session's file.
|
|
47
|
+
if (existing) registry.unregister(advisorId);
|
|
48
|
+
registry.register({
|
|
49
|
+
id: advisorId,
|
|
50
|
+
displayName,
|
|
51
|
+
kind: "advisor",
|
|
52
|
+
parentId: owner,
|
|
53
|
+
session: null,
|
|
54
|
+
sessionFile,
|
|
55
|
+
status: "parked",
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
const id = entry.name.slice(0, -6);
|
|
61
|
+
if (!registry.get(id)) {
|
|
62
|
+
registry.register({
|
|
63
|
+
id,
|
|
64
|
+
displayName: id,
|
|
65
|
+
kind: "sub",
|
|
66
|
+
parentId: parentId ?? MAIN_AGENT_ID,
|
|
67
|
+
session: null,
|
|
68
|
+
sessionFile,
|
|
69
|
+
status: "parked",
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
await registerPersistedSubagentsFromDir(registry, path.join(dir, id), id);
|
|
73
|
+
}
|
|
74
|
+
}
|