@arhen/pi-core-subagent 1.3.65 → 1.3.66
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/README.md +6 -3
- package/package.json +1 -1
- package/src/config.ts +60 -0
- package/src/index.ts +1 -1
- package/src/manager.ts +3 -4
- package/src/presentation.ts +30 -28
package/README.md
CHANGED
|
@@ -327,9 +327,12 @@ still applies Pi\'s global script-only policy; this extension does not override
|
|
|
327
327
|
- `/subagents mode codemode` — explicitly select script-based routing (direct fallback when unavailable).
|
|
328
328
|
- `/subagents mode direct` — return to legacy direct behavior.
|
|
329
329
|
|
|
330
|
-
Choice is stored
|
|
331
|
-
|
|
332
|
-
|
|
330
|
+
Choice is stored globally in `~/.pi/agent/subagents-config.json` (the same file as `auto-limit`,
|
|
331
|
+
written with a merge, never a clobber) and applies to every session — reload, resume, fork and tree
|
|
332
|
+
navigation included. Sessions that chose a mode before the preference moved into that file keep their
|
|
333
|
+
branch entry only while the config has no `mode`; the built-in default is still `direct`. Published
|
|
334
|
+
1.3.63 defaulted to `auto`: this compatibility correction requires the corrected source/package, not
|
|
335
|
+
a retroactive change to that npm release.
|
|
333
336
|
|
|
334
337
|
## Commands
|
|
335
338
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arhen/pi-core-subagent",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.66",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "pi extension: fast in-process subagents with a dependency-graph scheduler (needs edges gate tasks and carry upstream output into dependent prompts), plus background runs, intercom and agent-to-agent mailbox. Leader defines agents inline.",
|
|
6
6
|
"license": "MIT",
|
package/src/config.ts
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { writeFile } from "node:fs/promises";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Shared user config for the extension: exposure mode plus the auto-limit ceiling. One file, so a
|
|
8
|
+
* write of one key must never drop the other — every writer merges through `writeSubagentConfig`.
|
|
9
|
+
*/
|
|
10
|
+
export const SUBAGENT_CONFIG_FILENAME = "subagents-config.json";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Presentation preference for the subagent toolset, stored globally in `subagents-config.json`.
|
|
14
|
+
* - auto: native codemode presentation when the codemode tool is active, direct otherwise.
|
|
15
|
+
* - direct (default): legacy native declarations and active-tool script callability.
|
|
16
|
+
* - codemode: subagent tools are callable from codemode scripts but not declared or listed.
|
|
17
|
+
*/
|
|
18
|
+
export type SubagentMode = "auto" | "direct" | "codemode";
|
|
19
|
+
|
|
20
|
+
export const DEFAULT_SUBAGENT_MODE: SubagentMode = "direct";
|
|
21
|
+
|
|
22
|
+
export function isSubagentMode(value: unknown): value is SubagentMode {
|
|
23
|
+
return value === "auto" || value === "direct" || value === "codemode";
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Resolved per call, so a test or `PI_CODING_AGENT_DIR` override applies to the running process. */
|
|
27
|
+
export function subagentConfigPath(): string {
|
|
28
|
+
return join(getAgentDir(), SUBAGENT_CONFIG_FILENAME);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Tolerant read: a missing, unreadable or non-object file degrades to an empty object, so a config
|
|
33
|
+
* typo can neither break delegation nor hide the built-in defaults.
|
|
34
|
+
*/
|
|
35
|
+
export function readSubagentConfig(path = subagentConfigPath()): Record<string, unknown> {
|
|
36
|
+
try {
|
|
37
|
+
const raw: unknown = JSON.parse(readFileSync(path, "utf8"));
|
|
38
|
+
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) return {};
|
|
39
|
+
return raw as Record<string, unknown>;
|
|
40
|
+
} catch {
|
|
41
|
+
return {};
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** The stored exposure mode, or undefined when the file has no usable one. */
|
|
46
|
+
export function readStoredMode(path = subagentConfigPath()): SubagentMode | undefined {
|
|
47
|
+
const mode = readSubagentConfig(path).mode;
|
|
48
|
+
return isSubagentMode(mode) ? mode : undefined;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
let writeQueue: Promise<unknown> = Promise.resolve();
|
|
52
|
+
|
|
53
|
+
/** Read-modify-write on a single queue, so concurrent writers of different keys cannot clobber each other. */
|
|
54
|
+
export function writeSubagentConfig(patch: Record<string, unknown>, path = subagentConfigPath()): Promise<void> {
|
|
55
|
+
const write = writeQueue.then(async () => {
|
|
56
|
+
await writeFile(path, `${JSON.stringify({ ...readSubagentConfig(path), ...patch }, null, 2)}\n`);
|
|
57
|
+
});
|
|
58
|
+
writeQueue = write.catch(() => {});
|
|
59
|
+
return write;
|
|
60
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -92,7 +92,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
92
92
|
};
|
|
93
93
|
pi.registerCommand("subagents", {
|
|
94
94
|
description:
|
|
95
|
-
"List subagent runs. `/subagents peek` opens the browsable pane; `/subagents mode [auto|direct|codemode]` reports or switches the tool exposure profile; `/subagents auto-limit on|off` toggles the 1 h default runtime ceiling (default off = 6 h).",
|
|
95
|
+
"List subagent runs. `/subagents peek` opens the browsable pane; `/subagents mode [auto|direct|codemode]` reports or switches the globally stored tool exposure profile; `/subagents auto-limit on|off` toggles the 1 h default runtime ceiling (default off = 6 h).",
|
|
96
96
|
handler: async (args, ctx) => {
|
|
97
97
|
const arg = String(args ?? "")
|
|
98
98
|
.trim()
|
package/src/manager.ts
CHANGED
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
import type { TUI } from "@earendil-works/pi-tui";
|
|
24
24
|
import { resolveAgentFile } from "./agentfile.ts";
|
|
25
25
|
import { CHILD_TALK_TOOLS, type ChildHandlers, createChildTools } from "./child.ts";
|
|
26
|
+
import { readSubagentConfig, writeSubagentConfig } from "./config.ts";
|
|
26
27
|
import {
|
|
27
28
|
activitySnippet,
|
|
28
29
|
describeCall,
|
|
@@ -368,16 +369,14 @@ export class SubagentManager {
|
|
|
368
369
|
|
|
369
370
|
constructor(private readonly pi: ExtensionAPI) {
|
|
370
371
|
try {
|
|
371
|
-
const cfg =
|
|
372
|
+
const cfg = readSubagentConfig();
|
|
372
373
|
if (typeof cfg.autoLimit === "boolean") this.autoLimit = cfg.autoLimit;
|
|
373
374
|
} catch {}
|
|
374
375
|
}
|
|
375
376
|
|
|
376
377
|
setAutoLimit(on: boolean): boolean {
|
|
377
378
|
this.autoLimit = on;
|
|
378
|
-
void
|
|
379
|
-
() => {},
|
|
380
|
-
);
|
|
379
|
+
void writeSubagentConfig({ autoLimit: on }).catch(() => {});
|
|
381
380
|
return on;
|
|
382
381
|
}
|
|
383
382
|
|
package/src/presentation.ts
CHANGED
|
@@ -7,17 +7,18 @@ import type {
|
|
|
7
7
|
ToolLoadoutChanges,
|
|
8
8
|
ToolNamespace,
|
|
9
9
|
} from "@earendil-works/pi-coding-agent";
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
10
|
+
import {
|
|
11
|
+
DEFAULT_SUBAGENT_MODE,
|
|
12
|
+
isSubagentMode,
|
|
13
|
+
readStoredMode,
|
|
14
|
+
type SubagentMode,
|
|
15
|
+
writeSubagentConfig,
|
|
16
|
+
} from "./config.ts";
|
|
17
|
+
|
|
18
|
+
export { isSubagentMode, type SubagentMode } from "./config.ts";
|
|
18
19
|
export type EffectiveMode = "direct" | "codemode";
|
|
19
20
|
|
|
20
|
-
/** Branch-scoped custom entry holding the preference
|
|
21
|
+
/** Branch-scoped custom entry holding the legacy preference; the global config file now wins. */
|
|
21
22
|
export const MODE_ENTRY_TYPE = "subagent-mode";
|
|
22
23
|
export const CODEMODE_TOOL_NAME = "codemode";
|
|
23
24
|
|
|
@@ -30,7 +31,7 @@ export const SUBAGENT_NAMESPACE: ToolNamespace = {
|
|
|
30
31
|
- direct (default): legacy native tool declarations and active-tool script callability; activating codemode alone does not switch profiles.
|
|
31
32
|
- auto (opt-in): codemode presentation while the \`codemode\` tool is active, direct otherwise.
|
|
32
33
|
- codemode: active tools are callable from scripts but not declared; inactive tools stay model-only and are not callable at all.
|
|
33
|
-
Switch with \`/subagents mode auto|direct|codemode\`. The preference is stored
|
|
34
|
+
Switch with \`/subagents mode auto|direct|codemode\`. The preference is stored in the global \`subagents-config.json\` and applies to every session — reload, resume and tree navigation included.
|
|
34
35
|
|
|
35
36
|
## Operations
|
|
36
37
|
Object arguments, exactly as validated when issued by the model:
|
|
@@ -67,10 +68,6 @@ export const CODEMODE_DISCOVERY_GUIDELINE =
|
|
|
67
68
|
/** The full-reference pointer only resolves while codemode scripts can reach `describeNamespace`. */
|
|
68
69
|
const NAMESPACE_REFERENCE = / Full reference: `describeNamespace\('subagents'\)`\./;
|
|
69
70
|
|
|
70
|
-
export function isSubagentMode(value: unknown): value is SubagentMode {
|
|
71
|
-
return value === "auto" || value === "direct" || value === "codemode";
|
|
72
|
-
}
|
|
73
|
-
|
|
74
71
|
export type AnyToolDefinition = ToolDefinition<any, any, any>;
|
|
75
72
|
|
|
76
73
|
export interface Presentation {
|
|
@@ -82,7 +79,7 @@ export interface Presentation {
|
|
|
82
79
|
/** Report preference, effective mode and codemode availability. */
|
|
83
80
|
describe(): string;
|
|
84
81
|
setPreference(mode: SubagentMode): void;
|
|
85
|
-
/** Restore the
|
|
82
|
+
/** Restore the globally stored preference, with the session branch as the legacy fallback. */
|
|
86
83
|
restore(ctx: ExtensionContext): void;
|
|
87
84
|
/** Register the tools for the current effective mode; no-op when already applied. */
|
|
88
85
|
sync(): boolean;
|
|
@@ -90,6 +87,17 @@ export interface Presentation {
|
|
|
90
87
|
registerInitial(): void;
|
|
91
88
|
}
|
|
92
89
|
|
|
90
|
+
/** Latest mode stored by an older package version on this session branch, if any. */
|
|
91
|
+
function branchPreference(ctx: ExtensionContext): SubagentMode | undefined {
|
|
92
|
+
let restored: SubagentMode | undefined;
|
|
93
|
+
for (const entry of ctx.sessionManager.getBranch()) {
|
|
94
|
+
const candidate = entry as { type?: string; customType?: string; data?: { mode?: unknown } };
|
|
95
|
+
if (candidate.type === "custom" && candidate.customType === MODE_ENTRY_TYPE && isSubagentMode(candidate.data?.mode))
|
|
96
|
+
restored = candidate.data.mode;
|
|
97
|
+
}
|
|
98
|
+
return restored;
|
|
99
|
+
}
|
|
100
|
+
|
|
93
101
|
/**
|
|
94
102
|
* Owns the subagent exposure profile. Tools stay registered while only their exposure changes, so
|
|
95
103
|
* the active selection (and the running manager) is preserved across mode switches.
|
|
@@ -169,25 +177,19 @@ export function createPresentation(pi: ExtensionAPI, definitions: readonly AnyTo
|
|
|
169
177
|
const availability = available ? "codemode is active" : "codemode is not active";
|
|
170
178
|
const fallback = !available && preference === "codemode" ? "; using direct fallback" : "";
|
|
171
179
|
const pending = target !== shown ? `; pending ${target} at the next request boundary` : "";
|
|
172
|
-
return `Subagent exposure: mode ${preference} → effective ${shown} (${availability}${fallback}${pending}). Use /subagents mode auto|direct|codemode.`;
|
|
180
|
+
return `Subagent exposure: mode ${preference} → effective ${shown} (${availability}${fallback}${pending}). Saved for all sessions. Use /subagents mode auto|direct|codemode.`;
|
|
173
181
|
},
|
|
174
182
|
setPreference(mode) {
|
|
175
183
|
preference = mode;
|
|
184
|
+
// The global config file is authoritative for every session; the branch entry stays as a
|
|
185
|
+
// fallback for sessions that predate it and for readers of older package versions.
|
|
186
|
+
void writeSubagentConfig({ mode }).catch(() => {});
|
|
176
187
|
pi.appendEntry(MODE_ENTRY_TYPE, { mode });
|
|
177
188
|
},
|
|
178
189
|
restore(ctx) {
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
if (
|
|
183
|
-
candidate.type === "custom" &&
|
|
184
|
-
candidate.customType === MODE_ENTRY_TYPE &&
|
|
185
|
-
isSubagentMode(candidate.data?.mode)
|
|
186
|
-
)
|
|
187
|
-
restored = candidate.data.mode;
|
|
188
|
-
}
|
|
189
|
-
// Branch-sensitive state: a branch without an entry uses the package default again.
|
|
190
|
-
preference = restored ?? "direct";
|
|
190
|
+
// Global preference wins over the branch; the branch entry is the legacy fallback for
|
|
191
|
+
// sessions that chose a mode before the preference moved into the config file.
|
|
192
|
+
preference = readStoredMode() ?? branchPreference(ctx) ?? DEFAULT_SUBAGENT_MODE;
|
|
191
193
|
},
|
|
192
194
|
sync() {
|
|
193
195
|
const own = new Set(ownNames());
|