@esso0428/pi-subagents 0.15.0 → 0.15.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/README.md +2 -2
- package/dist/agent-manager.d.ts +8 -0
- package/dist/agent-manager.js +87 -19
- package/dist/agent-runner.js +85 -66
- package/dist/agent-types.js +45 -27
- package/dist/context.js +6 -2
- package/dist/cross-extension-rpc.js +9 -5
- package/dist/custom-agents.js +18 -15
- package/dist/default-agents.js +4 -1
- package/dist/enabled-models.js +16 -11
- package/dist/env.js +4 -1
- package/dist/group-join.js +5 -1
- package/dist/index.js +280 -221
- package/dist/invocation-config.js +6 -2
- package/dist/memory.js +34 -24
- package/dist/model-resolver.js +4 -1
- package/dist/nico-overrides.js +20 -14
- package/dist/output-file.js +21 -15
- package/dist/prompts.js +4 -1
- package/dist/schedule-store.js +21 -16
- package/dist/schedule.js +12 -8
- package/dist/settings.js +23 -15
- package/dist/skill-loader.js +23 -20
- package/dist/status-note.js +4 -1
- package/dist/types.d.ts +1 -0
- package/dist/types.js +4 -1
- package/dist/ui/agent-widget.js +37 -23
- package/dist/ui/conversation-viewer.js +43 -39
- package/dist/ui/fleet-list.js +30 -24
- package/dist/ui/markdown-result.d.ts +3 -0
- package/dist/ui/markdown-result.js +53 -0
- package/dist/ui/schedule-menu.js +4 -1
- package/dist/ui/viewer-keys.js +10 -7
- package/dist/usage.js +10 -4
- package/dist/worktree.js +31 -26
- package/package.json +1 -1
- package/src/agent-manager.ts +72 -0
- package/src/agent-runner.ts +11 -3
- package/src/index.ts +39 -16
- package/src/types.ts +1 -0
- package/src/ui/markdown-result.ts +56 -0
- package/test/agent-manager-history.test.ts +84 -0
- package/test/ui/markdown-result.test.ts +45 -0
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.resolveAgentInvocationConfig = resolveAgentInvocationConfig;
|
|
4
|
+
exports.resolveJoinMode = resolveJoinMode;
|
|
5
|
+
function resolveAgentInvocationConfig(agentConfig, params) {
|
|
2
6
|
return {
|
|
3
7
|
modelInput: agentConfig?.model ?? params.model,
|
|
4
8
|
modelFromParams: agentConfig?.model == null && params.model != null,
|
|
@@ -10,6 +14,6 @@ export function resolveAgentInvocationConfig(agentConfig, params) {
|
|
|
10
14
|
isolation: agentConfig?.isolation ?? params.isolation,
|
|
11
15
|
};
|
|
12
16
|
}
|
|
13
|
-
|
|
17
|
+
function resolveJoinMode(defaultJoinMode, runInBackground) {
|
|
14
18
|
return runInBackground ? defaultJoinMode : undefined;
|
|
15
19
|
}
|
package/dist/memory.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
"use strict";
|
|
1
2
|
/**
|
|
2
3
|
* memory.ts — Persistent agent memory: per-agent memory directories that persist across sessions.
|
|
3
4
|
*
|
|
@@ -10,17 +11,26 @@
|
|
|
10
11
|
* is still honored (read + write) when it exists and the new location doesn't,
|
|
11
12
|
* so existing memories aren't orphaned.
|
|
12
13
|
*/
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.isUnsafeName = isUnsafeName;
|
|
16
|
+
exports.isSymlink = isSymlink;
|
|
17
|
+
exports.safeReadFile = safeReadFile;
|
|
18
|
+
exports.resolveMemoryDir = resolveMemoryDir;
|
|
19
|
+
exports.ensureMemoryDir = ensureMemoryDir;
|
|
20
|
+
exports.readMemoryIndex = readMemoryIndex;
|
|
21
|
+
exports.buildMemoryBlock = buildMemoryBlock;
|
|
22
|
+
exports.buildReadOnlyMemoryBlock = buildReadOnlyMemoryBlock;
|
|
23
|
+
const node_fs_1 = require("node:fs");
|
|
24
|
+
const node_os_1 = require("node:os");
|
|
25
|
+
const node_path_1 = require("node:path");
|
|
26
|
+
const pi_coding_agent_1 = require("@earendil-works/pi-coding-agent");
|
|
17
27
|
/** Maximum lines to read from MEMORY.md */
|
|
18
28
|
const MAX_MEMORY_LINES = 200;
|
|
19
29
|
/**
|
|
20
30
|
* Returns true if a name contains characters not allowed in agent/skill names.
|
|
21
31
|
* Uses a whitelist: only alphanumeric, hyphens, underscores, and dots (no leading dot).
|
|
22
32
|
*/
|
|
23
|
-
|
|
33
|
+
function isUnsafeName(name) {
|
|
24
34
|
if (!name || name.length > 128)
|
|
25
35
|
return true;
|
|
26
36
|
return !/^[a-zA-Z0-9][a-zA-Z0-9._-]*$/.test(name);
|
|
@@ -28,9 +38,9 @@ export function isUnsafeName(name) {
|
|
|
28
38
|
/**
|
|
29
39
|
* Returns true if the given path is a symlink (defense against symlink attacks).
|
|
30
40
|
*/
|
|
31
|
-
|
|
41
|
+
function isSymlink(filePath) {
|
|
32
42
|
try {
|
|
33
|
-
return lstatSync(filePath).isSymbolicLink();
|
|
43
|
+
return (0, node_fs_1.lstatSync)(filePath).isSymbolicLink();
|
|
34
44
|
}
|
|
35
45
|
catch {
|
|
36
46
|
return false;
|
|
@@ -40,13 +50,13 @@ export function isSymlink(filePath) {
|
|
|
40
50
|
* Safely read a file, rejecting symlinks.
|
|
41
51
|
* Returns undefined if the file doesn't exist, is a symlink, or can't be read.
|
|
42
52
|
*/
|
|
43
|
-
|
|
44
|
-
if (!existsSync(filePath))
|
|
53
|
+
function safeReadFile(filePath) {
|
|
54
|
+
if (!(0, node_fs_1.existsSync)(filePath))
|
|
45
55
|
return undefined;
|
|
46
56
|
if (isSymlink(filePath))
|
|
47
57
|
return undefined;
|
|
48
58
|
try {
|
|
49
|
-
return readFileSync(filePath, "utf-8");
|
|
59
|
+
return (0, node_fs_1.readFileSync)(filePath, "utf-8");
|
|
50
60
|
}
|
|
51
61
|
catch {
|
|
52
62
|
return undefined;
|
|
@@ -56,26 +66,26 @@ export function safeReadFile(filePath) {
|
|
|
56
66
|
* Resolve the memory directory path for a given agent + scope + cwd.
|
|
57
67
|
* Throws if agentName contains path traversal characters.
|
|
58
68
|
*/
|
|
59
|
-
|
|
69
|
+
function resolveMemoryDir(agentName, scope, cwd) {
|
|
60
70
|
if (isUnsafeName(agentName)) {
|
|
61
71
|
throw new Error(`Unsafe agent name for memory directory: "${agentName}"`);
|
|
62
72
|
}
|
|
63
73
|
switch (scope) {
|
|
64
74
|
case "user": {
|
|
65
|
-
const current = join(getAgentDir(), "agent-memory", agentName);
|
|
75
|
+
const current = (0, node_path_1.join)((0, pi_coding_agent_1.getAgentDir)(), "agent-memory", agentName);
|
|
66
76
|
// Legacy location from when this path was hardcoded. Keep using it if it
|
|
67
77
|
// already holds this agent's memory and the new location hasn't been
|
|
68
78
|
// created yet — otherwise existing memories would be silently orphaned.
|
|
69
|
-
const legacy = join(homedir(), ".pi", "agent-memory", agentName);
|
|
70
|
-
if (!existsSync(current) && existsSync(legacy) && !isSymlink(legacy)) {
|
|
79
|
+
const legacy = (0, node_path_1.join)((0, node_os_1.homedir)(), ".pi", "agent-memory", agentName);
|
|
80
|
+
if (!(0, node_fs_1.existsSync)(current) && (0, node_fs_1.existsSync)(legacy) && !isSymlink(legacy)) {
|
|
71
81
|
return legacy;
|
|
72
82
|
}
|
|
73
83
|
return current;
|
|
74
84
|
}
|
|
75
85
|
case "project":
|
|
76
|
-
return join(cwd, ".pi", "agent-memory", agentName);
|
|
86
|
+
return (0, node_path_1.join)(cwd, ".pi", "agent-memory", agentName);
|
|
77
87
|
case "local":
|
|
78
|
-
return join(cwd, ".pi", "agent-memory-local", agentName);
|
|
88
|
+
return (0, node_path_1.join)(cwd, ".pi", "agent-memory-local", agentName);
|
|
79
89
|
}
|
|
80
90
|
}
|
|
81
91
|
/**
|
|
@@ -83,25 +93,25 @@ export function resolveMemoryDir(agentName, scope, cwd) {
|
|
|
83
93
|
* Refuses to create directories if any component in the path is a symlink
|
|
84
94
|
* to prevent symlink-based directory traversal attacks.
|
|
85
95
|
*/
|
|
86
|
-
|
|
96
|
+
function ensureMemoryDir(memoryDir) {
|
|
87
97
|
// If the directory already exists, verify it's not a symlink
|
|
88
|
-
if (existsSync(memoryDir)) {
|
|
98
|
+
if ((0, node_fs_1.existsSync)(memoryDir)) {
|
|
89
99
|
if (isSymlink(memoryDir)) {
|
|
90
100
|
throw new Error(`Refusing to use symlinked memory directory: ${memoryDir}`);
|
|
91
101
|
}
|
|
92
102
|
return;
|
|
93
103
|
}
|
|
94
|
-
mkdirSync(memoryDir, { recursive: true });
|
|
104
|
+
(0, node_fs_1.mkdirSync)(memoryDir, { recursive: true });
|
|
95
105
|
}
|
|
96
106
|
/**
|
|
97
107
|
* Read the first N lines of MEMORY.md from the memory directory, if it exists.
|
|
98
108
|
* Returns undefined if no MEMORY.md exists or if the path is a symlink.
|
|
99
109
|
*/
|
|
100
|
-
|
|
110
|
+
function readMemoryIndex(memoryDir) {
|
|
101
111
|
// Reject symlinked memory directories
|
|
102
112
|
if (isSymlink(memoryDir))
|
|
103
113
|
return undefined;
|
|
104
|
-
const memoryFile = join(memoryDir, "MEMORY.md");
|
|
114
|
+
const memoryFile = (0, node_path_1.join)(memoryDir, "MEMORY.md");
|
|
105
115
|
const content = safeReadFile(memoryFile);
|
|
106
116
|
if (content === undefined)
|
|
107
117
|
return undefined;
|
|
@@ -115,7 +125,7 @@ export function readMemoryIndex(memoryDir) {
|
|
|
115
125
|
* Build the memory block to inject into the agent's system prompt.
|
|
116
126
|
* Also ensures the memory directory exists (creates it if needed).
|
|
117
127
|
*/
|
|
118
|
-
|
|
128
|
+
function buildMemoryBlock(agentName, scope, cwd) {
|
|
119
129
|
const memoryDir = resolveMemoryDir(agentName, scope, cwd);
|
|
120
130
|
// Create the memory directory so the agent can immediately write to it
|
|
121
131
|
ensureMemoryDir(memoryDir);
|
|
@@ -128,7 +138,7 @@ Memory scope: ${scope}
|
|
|
128
138
|
This memory persists across sessions. Use it to build up knowledge over time.`;
|
|
129
139
|
const memoryContent = existingMemory
|
|
130
140
|
? `\n\n## Current MEMORY.md\n${existingMemory}`
|
|
131
|
-
: `\n\nNo MEMORY.md exists yet. Create one at ${join(memoryDir, "MEMORY.md")} to start building persistent memory.`;
|
|
141
|
+
: `\n\nNo MEMORY.md exists yet. Create one at ${(0, node_path_1.join)(memoryDir, "MEMORY.md")} to start building persistent memory.`;
|
|
132
142
|
const instructions = `
|
|
133
143
|
|
|
134
144
|
## Memory Instructions
|
|
@@ -151,7 +161,7 @@ This memory persists across sessions. Use it to build up knowledge over time.`;
|
|
|
151
161
|
* Build a read-only memory block for agents that lack write/edit tools.
|
|
152
162
|
* Does NOT create the memory directory — agents can only consume existing memory.
|
|
153
163
|
*/
|
|
154
|
-
|
|
164
|
+
function buildReadOnlyMemoryBlock(agentName, scope, cwd) {
|
|
155
165
|
const memoryDir = resolveMemoryDir(agentName, scope, cwd);
|
|
156
166
|
const existingMemory = readMemoryIndex(memoryDir);
|
|
157
167
|
const header = `# Agent Memory (read-only)
|
package/dist/model-resolver.js
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
1
2
|
/**
|
|
2
3
|
* Model resolution: exact match ("provider/modelId") with fuzzy fallback.
|
|
3
4
|
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.resolveModel = resolveModel;
|
|
4
7
|
/**
|
|
5
8
|
* Resolve a model string to a Model instance.
|
|
6
9
|
* Tries exact match first ("provider/modelId"), then fuzzy match against all available models.
|
|
7
10
|
* Returns the Model on success, or an error message string on failure.
|
|
8
11
|
*/
|
|
9
|
-
|
|
12
|
+
function resolveModel(input, registry) {
|
|
10
13
|
// Available models (those with auth configured)
|
|
11
14
|
const all = (registry.getAvailable?.() ?? registry.getAll());
|
|
12
15
|
const availableSet = new Set(all.map(m => `${m.provider}/${m.id}`.toLowerCase()));
|
package/dist/nico-overrides.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
"use strict";
|
|
1
2
|
/**
|
|
2
3
|
* nico-overrides.ts — Read and apply agent overrides from `npm:pi-subagents`-style
|
|
3
4
|
* `settings.json` (`subagents.agentOverrides`).
|
|
@@ -16,10 +17,15 @@
|
|
|
16
17
|
* [] | false → false (none)
|
|
17
18
|
* omitted → true (all, fallback)
|
|
18
19
|
*/
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
|
+
exports.readNicoAgentOverrides = readNicoAgentOverrides;
|
|
22
|
+
exports.resolveNicoSkills = resolveNicoSkills;
|
|
23
|
+
exports.applyNicoOverride = applyNicoOverride;
|
|
24
|
+
exports.applyNicoOverridesToMap = applyNicoOverridesToMap;
|
|
25
|
+
const node_fs_1 = require("node:fs");
|
|
26
|
+
const node_path_1 = require("node:path");
|
|
27
|
+
const pi_coding_agent_1 = require("@earendil-works/pi-coding-agent");
|
|
28
|
+
const agent_types_js_1 = require("./agent-types.js");
|
|
23
29
|
// ============================================================================
|
|
24
30
|
// Reader
|
|
25
31
|
// ============================================================================
|
|
@@ -27,11 +33,11 @@ import { BUILTIN_TOOL_NAMES } from "./agent-types.js";
|
|
|
27
33
|
* Read subagents.agentOverrides and subagents.defaultModel from both local
|
|
28
34
|
* and global Nico-style settings.json. Local overrides global on key collision.
|
|
29
35
|
*/
|
|
30
|
-
|
|
36
|
+
function readNicoAgentOverrides(cwd) {
|
|
31
37
|
const merged = {};
|
|
32
38
|
let defaultModel;
|
|
33
39
|
// Global: ~/.pi/agent/settings.json
|
|
34
|
-
const globalPath = join(getAgentDir(), "settings.json");
|
|
40
|
+
const globalPath = (0, node_path_1.join)((0, pi_coding_agent_1.getAgentDir)(), "settings.json");
|
|
35
41
|
const globalSettings = readNicoSettingsFile(globalPath);
|
|
36
42
|
if (globalSettings) {
|
|
37
43
|
mergeOverrides(merged, globalSettings.agentOverrides);
|
|
@@ -39,8 +45,8 @@ export function readNicoAgentOverrides(cwd) {
|
|
|
39
45
|
defaultModel = globalSettings.defaultModel;
|
|
40
46
|
}
|
|
41
47
|
// Local: .pi/settings.json
|
|
42
|
-
const localPath = join(cwd, ".pi", "settings.json");
|
|
43
|
-
if (existsSync(localPath)) {
|
|
48
|
+
const localPath = (0, node_path_1.join)(cwd, ".pi", "settings.json");
|
|
49
|
+
if ((0, node_fs_1.existsSync)(localPath)) {
|
|
44
50
|
const localSettings = readNicoSettingsFile(localPath);
|
|
45
51
|
if (localSettings) {
|
|
46
52
|
mergeOverrides(merged, localSettings.agentOverrides);
|
|
@@ -51,10 +57,10 @@ export function readNicoAgentOverrides(cwd) {
|
|
|
51
57
|
return { overrides: merged, defaultModel };
|
|
52
58
|
}
|
|
53
59
|
function readNicoSettingsFile(filePath) {
|
|
54
|
-
if (!existsSync(filePath))
|
|
60
|
+
if (!(0, node_fs_1.existsSync)(filePath))
|
|
55
61
|
return undefined;
|
|
56
62
|
try {
|
|
57
|
-
const raw = JSON.parse(readFileSync(filePath, "utf-8"));
|
|
63
|
+
const raw = JSON.parse((0, node_fs_1.readFileSync)(filePath, "utf-8"));
|
|
58
64
|
const sub = raw?.subagents;
|
|
59
65
|
if (!sub || typeof sub !== "object")
|
|
60
66
|
return undefined;
|
|
@@ -75,7 +81,7 @@ function mergeOverrides(target, source) {
|
|
|
75
81
|
// ============================================================================
|
|
76
82
|
// Skill converter: Nico string[] → tintinweb true | string[] | false
|
|
77
83
|
// ============================================================================
|
|
78
|
-
|
|
84
|
+
function resolveNicoSkills(skills) {
|
|
79
85
|
// Not set → inherit all (tintinweb default)
|
|
80
86
|
if (skills === undefined)
|
|
81
87
|
return true;
|
|
@@ -95,7 +101,7 @@ export function resolveNicoSkills(skills) {
|
|
|
95
101
|
* Apply a Nico-style override to an existing tintinweb AgentConfig.
|
|
96
102
|
* JSON values directly overwrite the config (highest priority).
|
|
97
103
|
*/
|
|
98
|
-
|
|
104
|
+
function applyNicoOverride(agent, override, nicoDefaultModel) {
|
|
99
105
|
let modified = false;
|
|
100
106
|
let next = agent;
|
|
101
107
|
// model: explicit override wins; else use defaultModel when agent has none
|
|
@@ -135,7 +141,7 @@ function createAgentFromOverride(name, override, defaultModel) {
|
|
|
135
141
|
description: `Auto-registered from npm:pi-subagents JSON settings`,
|
|
136
142
|
builtinToolNames: override.tools !== undefined
|
|
137
143
|
? (override.tools === false ? [] : [...override.tools])
|
|
138
|
-
: [...BUILTIN_TOOL_NAMES],
|
|
144
|
+
: [...agent_types_js_1.BUILTIN_TOOL_NAMES],
|
|
139
145
|
extensions: true,
|
|
140
146
|
skills: resolveNicoSkills(override.skills),
|
|
141
147
|
model: override.model !== undefined ? (override.model === false ? undefined : override.model) : defaultModel,
|
|
@@ -156,7 +162,7 @@ function createAgentFromOverride(name, override, defaultModel) {
|
|
|
156
162
|
* Apply all Nico overrides to a map of agents (mutating in-place).
|
|
157
163
|
* Agents that don't exist yet are auto-registered from the override.
|
|
158
164
|
*/
|
|
159
|
-
|
|
165
|
+
function applyNicoOverridesToMap(agents, overrides, defaultModel) {
|
|
160
166
|
for (const [name, override] of Object.entries(overrides)) {
|
|
161
167
|
const existing = agents.get(name);
|
|
162
168
|
if (existing) {
|
package/dist/output-file.js
CHANGED
|
@@ -1,19 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
1
2
|
/**
|
|
2
3
|
* output-file.ts — Streaming JSONL output file for agent transcripts.
|
|
3
4
|
*
|
|
4
5
|
* Creates a per-agent output file that streams conversation turns as JSONL,
|
|
5
6
|
* matching Claude Code's task output file format.
|
|
6
7
|
*/
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.encodeCwd = encodeCwd;
|
|
10
|
+
exports.createOutputFilePath = createOutputFilePath;
|
|
11
|
+
exports.writeInitialEntry = writeInitialEntry;
|
|
12
|
+
exports.streamToOutputFile = streamToOutputFile;
|
|
13
|
+
const node_fs_1 = require("node:fs");
|
|
14
|
+
const node_os_1 = require("node:os");
|
|
15
|
+
const node_path_1 = require("node:path");
|
|
10
16
|
/**
|
|
11
17
|
* Encode a cwd path as a filesystem-safe directory name. Handles:
|
|
12
18
|
* - POSIX: "/home/user/project" → "home-user-project"
|
|
13
19
|
* - Windows: "C:\Users\foo\project" → "Users-foo-project"
|
|
14
20
|
* - UNC: "\\\\server\\share\\project" → "server-share-project"
|
|
15
21
|
*/
|
|
16
|
-
|
|
22
|
+
function encodeCwd(cwd) {
|
|
17
23
|
return cwd
|
|
18
24
|
.replace(/[/\\]/g, "-") // both separators → dash
|
|
19
25
|
.replace(/^[A-Za-z]:-/, "") // strip Windows drive prefix ("C:-")
|
|
@@ -21,25 +27,25 @@ export function encodeCwd(cwd) {
|
|
|
21
27
|
}
|
|
22
28
|
/** Create the output file path, ensuring the directory exists.
|
|
23
29
|
* Mirrors Claude Code's layout: /tmp/{prefix}-{uid}/{encoded-cwd}/{sessionId}/tasks/{agentId}.output */
|
|
24
|
-
|
|
30
|
+
function createOutputFilePath(cwd, agentId, sessionId) {
|
|
25
31
|
const encoded = encodeCwd(cwd);
|
|
26
|
-
const root = join(tmpdir(), `pi-subagents-${process.getuid?.() ?? 0}`);
|
|
27
|
-
mkdirSync(root, { recursive: true, mode: 0o700 });
|
|
32
|
+
const root = (0, node_path_1.join)((0, node_os_1.tmpdir)(), `pi-subagents-${process.getuid?.() ?? 0}`);
|
|
33
|
+
(0, node_fs_1.mkdirSync)(root, { recursive: true, mode: 0o700 });
|
|
28
34
|
// chmod is a no-op on Windows and throws on some Windows filesystems.
|
|
29
35
|
// On Unix we still want to enforce 0o700 past umask, so only swallow on Windows.
|
|
30
36
|
try {
|
|
31
|
-
chmodSync(root, 0o700);
|
|
37
|
+
(0, node_fs_1.chmodSync)(root, 0o700);
|
|
32
38
|
}
|
|
33
39
|
catch (err) {
|
|
34
40
|
if (process.platform !== "win32")
|
|
35
41
|
throw err;
|
|
36
42
|
}
|
|
37
|
-
const dir = join(root, encoded, sessionId, "tasks");
|
|
38
|
-
mkdirSync(dir, { recursive: true });
|
|
39
|
-
return join(dir, `${agentId}.output`);
|
|
43
|
+
const dir = (0, node_path_1.join)(root, encoded, sessionId, "tasks");
|
|
44
|
+
(0, node_fs_1.mkdirSync)(dir, { recursive: true });
|
|
45
|
+
return (0, node_path_1.join)(dir, `${agentId}.output`);
|
|
40
46
|
}
|
|
41
47
|
/** Write the initial user prompt entry. */
|
|
42
|
-
|
|
48
|
+
function writeInitialEntry(path, agentId, prompt, cwd) {
|
|
43
49
|
const entry = {
|
|
44
50
|
isSidechain: true,
|
|
45
51
|
agentId,
|
|
@@ -48,13 +54,13 @@ export function writeInitialEntry(path, agentId, prompt, cwd) {
|
|
|
48
54
|
timestamp: new Date().toISOString(),
|
|
49
55
|
cwd,
|
|
50
56
|
};
|
|
51
|
-
writeFileSync(path, JSON.stringify(entry) + "\n", "utf-8");
|
|
57
|
+
(0, node_fs_1.writeFileSync)(path, JSON.stringify(entry) + "\n", "utf-8");
|
|
52
58
|
}
|
|
53
59
|
/**
|
|
54
60
|
* Subscribe to session events and flush new messages to the output file on each turn_end.
|
|
55
61
|
* Returns a cleanup function that does a final flush and unsubscribes.
|
|
56
62
|
*/
|
|
57
|
-
|
|
63
|
+
function streamToOutputFile(session, path, agentId, cwd) {
|
|
58
64
|
let writtenCount = 1; // initial user prompt already written
|
|
59
65
|
const flush = () => {
|
|
60
66
|
const messages = session.messages;
|
|
@@ -69,7 +75,7 @@ export function streamToOutputFile(session, path, agentId, cwd) {
|
|
|
69
75
|
cwd,
|
|
70
76
|
};
|
|
71
77
|
try {
|
|
72
|
-
appendFileSync(path, JSON.stringify(entry) + "\n", "utf-8");
|
|
78
|
+
(0, node_fs_1.appendFileSync)(path, JSON.stringify(entry) + "\n", "utf-8");
|
|
73
79
|
}
|
|
74
80
|
catch { /* ignore write errors */ }
|
|
75
81
|
writtenCount++;
|
package/dist/prompts.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
1
2
|
/**
|
|
2
3
|
* prompts.ts — System prompt builder for agents.
|
|
3
4
|
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.buildAgentPrompt = buildAgentPrompt;
|
|
4
7
|
/**
|
|
5
8
|
* Build the system prompt for an agent from its config.
|
|
6
9
|
*
|
|
@@ -18,7 +21,7 @@
|
|
|
18
21
|
* @param parentSystemPrompt The parent agent's effective system prompt (for append mode).
|
|
19
22
|
* @param extras Optional extra sections to inject (memory, preloaded skills).
|
|
20
23
|
*/
|
|
21
|
-
|
|
24
|
+
function buildAgentPrompt(config, cwd, env, parentSystemPrompt, extras) {
|
|
22
25
|
const activeAgentTag = `<active_agent name="${config.name}"/>\n\n`;
|
|
23
26
|
const envBlock = `# Environment
|
|
24
27
|
Working directory: ${cwd}
|
package/dist/schedule-store.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
"use strict";
|
|
1
2
|
/**
|
|
2
3
|
* schedule-store.ts — File-backed store for scheduled subagents.
|
|
3
4
|
*
|
|
@@ -9,8 +10,11 @@
|
|
|
9
10
|
* mutation acquires a PID-based exclusion lock, re-reads the latest state
|
|
10
11
|
* from disk, applies the change, atomic-writes via temp+rename, releases.
|
|
11
12
|
*/
|
|
12
|
-
|
|
13
|
-
|
|
13
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
+
exports.ScheduleStore = void 0;
|
|
15
|
+
exports.resolveStorePath = resolveStorePath;
|
|
16
|
+
const node_fs_1 = require("node:fs");
|
|
17
|
+
const node_path_1 = require("node:path");
|
|
14
18
|
const LOCK_RETRY_MS = 50;
|
|
15
19
|
const LOCK_MAX_RETRIES = 100;
|
|
16
20
|
function isProcessRunning(pid) {
|
|
@@ -25,15 +29,15 @@ function isProcessRunning(pid) {
|
|
|
25
29
|
function acquireLock(lockPath) {
|
|
26
30
|
for (let i = 0; i < LOCK_MAX_RETRIES; i++) {
|
|
27
31
|
try {
|
|
28
|
-
writeFileSync(lockPath, `${process.pid}`, { flag: "wx" });
|
|
32
|
+
(0, node_fs_1.writeFileSync)(lockPath, `${process.pid}`, { flag: "wx" });
|
|
29
33
|
return;
|
|
30
34
|
}
|
|
31
35
|
catch (e) {
|
|
32
36
|
if (e.code === "EEXIST") {
|
|
33
37
|
try {
|
|
34
|
-
const pid = parseInt(readFileSync(lockPath, "utf-8"), 10);
|
|
38
|
+
const pid = parseInt((0, node_fs_1.readFileSync)(lockPath, "utf-8"), 10);
|
|
35
39
|
if (pid && !isProcessRunning(pid)) {
|
|
36
|
-
unlinkSync(lockPath);
|
|
40
|
+
(0, node_fs_1.unlinkSync)(lockPath);
|
|
37
41
|
continue;
|
|
38
42
|
}
|
|
39
43
|
}
|
|
@@ -49,15 +53,15 @@ function acquireLock(lockPath) {
|
|
|
49
53
|
}
|
|
50
54
|
function releaseLock(lockPath) {
|
|
51
55
|
try {
|
|
52
|
-
unlinkSync(lockPath);
|
|
56
|
+
(0, node_fs_1.unlinkSync)(lockPath);
|
|
53
57
|
}
|
|
54
58
|
catch { /* ignore */ }
|
|
55
59
|
}
|
|
56
60
|
/** Resolve the storage path for a session-scoped store. */
|
|
57
|
-
|
|
58
|
-
return join(cwd, ".pi", "subagent-schedules", `${sessionId}.json`);
|
|
61
|
+
function resolveStorePath(cwd, sessionId) {
|
|
62
|
+
return (0, node_path_1.join)(cwd, ".pi", "subagent-schedules", `${sessionId}.json`);
|
|
59
63
|
}
|
|
60
|
-
|
|
64
|
+
class ScheduleStore {
|
|
61
65
|
filePath;
|
|
62
66
|
lockPath;
|
|
63
67
|
jobs = new Map();
|
|
@@ -68,14 +72,14 @@ export class ScheduleStore {
|
|
|
68
72
|
}
|
|
69
73
|
/** Create the backing directory lazily — only when we're about to persist. */
|
|
70
74
|
ensureDir() {
|
|
71
|
-
mkdirSync(dirname(this.filePath), { recursive: true });
|
|
75
|
+
(0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(this.filePath), { recursive: true });
|
|
72
76
|
}
|
|
73
77
|
/** Load from disk into the in-memory cache. Silent on parse errors. */
|
|
74
78
|
load() {
|
|
75
|
-
if (!existsSync(this.filePath))
|
|
79
|
+
if (!(0, node_fs_1.existsSync)(this.filePath))
|
|
76
80
|
return;
|
|
77
81
|
try {
|
|
78
|
-
const data = JSON.parse(readFileSync(this.filePath, "utf-8"));
|
|
82
|
+
const data = JSON.parse((0, node_fs_1.readFileSync)(this.filePath, "utf-8"));
|
|
79
83
|
this.jobs.clear();
|
|
80
84
|
for (const j of data.jobs ?? [])
|
|
81
85
|
this.jobs.set(j.id, j);
|
|
@@ -86,8 +90,8 @@ export class ScheduleStore {
|
|
|
86
90
|
save() {
|
|
87
91
|
const data = { version: 1, jobs: [...this.jobs.values()] };
|
|
88
92
|
const tmp = this.filePath + ".tmp";
|
|
89
|
-
writeFileSync(tmp, JSON.stringify(data, null, 2));
|
|
90
|
-
renameSync(tmp, this.filePath);
|
|
93
|
+
(0, node_fs_1.writeFileSync)(tmp, JSON.stringify(data, null, 2));
|
|
94
|
+
(0, node_fs_1.renameSync)(tmp, this.filePath);
|
|
91
95
|
}
|
|
92
96
|
/** Acquire lock → reload → mutate → save → release. */
|
|
93
97
|
withLock(fn) {
|
|
@@ -145,11 +149,12 @@ export class ScheduleStore {
|
|
|
145
149
|
}
|
|
146
150
|
/** Delete the backing file (used when no jobs remain, optional cleanup). */
|
|
147
151
|
deleteFileIfEmpty() {
|
|
148
|
-
if (this.jobs.size === 0 && existsSync(this.filePath)) {
|
|
152
|
+
if (this.jobs.size === 0 && (0, node_fs_1.existsSync)(this.filePath)) {
|
|
149
153
|
try {
|
|
150
|
-
unlinkSync(this.filePath);
|
|
154
|
+
(0, node_fs_1.unlinkSync)(this.filePath);
|
|
151
155
|
}
|
|
152
156
|
catch { /* ignore */ }
|
|
153
157
|
}
|
|
154
158
|
}
|
|
155
159
|
}
|
|
160
|
+
exports.ScheduleStore = ScheduleStore;
|
package/dist/schedule.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
"use strict";
|
|
1
2
|
/**
|
|
2
3
|
* schedule.ts — `SubagentScheduler`: timer-driven dispatcher of scheduled subagents.
|
|
3
4
|
*
|
|
@@ -14,10 +15,12 @@
|
|
|
14
15
|
* - Result delivery is implicit: spawn → background completion → existing
|
|
15
16
|
* `subagent-notification` followUp path. No new delivery code.
|
|
16
17
|
*/
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
|
+
exports.SubagentScheduler = void 0;
|
|
20
|
+
const croner_1 = require("croner");
|
|
21
|
+
const nanoid_1 = require("nanoid");
|
|
22
|
+
const model_resolver_js_1 = require("./model-resolver.js");
|
|
23
|
+
class SubagentScheduler {
|
|
21
24
|
jobs = new Map();
|
|
22
25
|
intervals = new Map();
|
|
23
26
|
store;
|
|
@@ -62,7 +65,7 @@ export class SubagentScheduler {
|
|
|
62
65
|
buildJob(input) {
|
|
63
66
|
const detected = SubagentScheduler.detectSchedule(input.schedule);
|
|
64
67
|
return {
|
|
65
|
-
id: nanoid(10),
|
|
68
|
+
id: (0, nanoid_1.nanoid)(10),
|
|
66
69
|
name: input.name,
|
|
67
70
|
description: input.description,
|
|
68
71
|
schedule: detected.normalized,
|
|
@@ -165,7 +168,7 @@ export class SubagentScheduler {
|
|
|
165
168
|
}
|
|
166
169
|
}
|
|
167
170
|
else {
|
|
168
|
-
const cron = new Cron(job.schedule, () => this.executeJob(job.id));
|
|
171
|
+
const cron = new croner_1.Cron(job.schedule, () => this.executeJob(job.id));
|
|
169
172
|
this.jobs.set(job.id, cron);
|
|
170
173
|
}
|
|
171
174
|
}
|
|
@@ -207,7 +210,7 @@ export class SubagentScheduler {
|
|
|
207
210
|
// if resolution fails; the spawn path handles undefined model gracefully.
|
|
208
211
|
let resolvedModel;
|
|
209
212
|
if (job.model) {
|
|
210
|
-
const r = resolveModel(job.model, ctx.modelRegistry);
|
|
213
|
+
const r = (0, model_resolver_js_1.resolveModel)(job.model, ctx.modelRegistry);
|
|
211
214
|
if (typeof r !== "string")
|
|
212
215
|
resolvedModel = r;
|
|
213
216
|
}
|
|
@@ -313,7 +316,7 @@ export class SubagentScheduler {
|
|
|
313
316
|
}
|
|
314
317
|
try {
|
|
315
318
|
// Croner validates by construction.
|
|
316
|
-
new Cron(expr, () => { });
|
|
319
|
+
new croner_1.Cron(expr, () => { });
|
|
317
320
|
return { valid: true };
|
|
318
321
|
}
|
|
319
322
|
catch (e) {
|
|
@@ -336,3 +339,4 @@ export class SubagentScheduler {
|
|
|
336
339
|
return parseInt(m[1], 10) * { s: 1000, m: 60_000, h: 3_600_000, d: 86_400_000 }[m[2]];
|
|
337
340
|
}
|
|
338
341
|
}
|
|
342
|
+
exports.SubagentScheduler = SubagentScheduler;
|
package/dist/settings.js
CHANGED
|
@@ -1,9 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
1
2
|
// Persistence for pi-subagents operational settings.
|
|
2
3
|
// - Global: ~/.pi/agent/subagents.json (via getAgentDir()) — manual defaults, never written here
|
|
3
4
|
// - Project: <cwd>/.pi/subagents.json — written by /agents → Settings; overrides global on load
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.loadSettings = loadSettings;
|
|
7
|
+
exports.saveSettings = saveSettings;
|
|
8
|
+
exports.applySettings = applySettings;
|
|
9
|
+
exports.persistToastFor = persistToastFor;
|
|
10
|
+
exports.applyAndEmitLoaded = applyAndEmitLoaded;
|
|
11
|
+
exports.saveAndEmitChanged = saveAndEmitChanged;
|
|
12
|
+
const node_fs_1 = require("node:fs");
|
|
13
|
+
const node_path_1 = require("node:path");
|
|
14
|
+
const pi_coding_agent_1 = require("@earendil-works/pi-coding-agent");
|
|
7
15
|
const VALID_JOIN_MODES = new Set(["async", "group", "smart"]);
|
|
8
16
|
const VALID_TOOL_DESCRIPTION_MODES = new Set(["full", "compact", "custom"]);
|
|
9
17
|
const VALID_WIDGET_MODES = new Set(["all", "background", "off"]);
|
|
@@ -61,10 +69,10 @@ function sanitize(raw) {
|
|
|
61
69
|
return out;
|
|
62
70
|
}
|
|
63
71
|
function globalPath() {
|
|
64
|
-
return join(getAgentDir(), "subagents.json");
|
|
72
|
+
return (0, node_path_1.join)((0, pi_coding_agent_1.getAgentDir)(), "subagents.json");
|
|
65
73
|
}
|
|
66
74
|
function projectPath(cwd) {
|
|
67
|
-
return join(cwd, ".pi", "subagents.json");
|
|
75
|
+
return (0, node_path_1.join)(cwd, ".pi", "subagents.json");
|
|
68
76
|
}
|
|
69
77
|
/**
|
|
70
78
|
* Read a settings file. Missing file is silent (returns `{}`). A file that
|
|
@@ -72,10 +80,10 @@ function projectPath(cwd) {
|
|
|
72
80
|
* silently reverted to defaults — and still returns `{}` so startup proceeds.
|
|
73
81
|
*/
|
|
74
82
|
function readSettingsFile(path) {
|
|
75
|
-
if (!existsSync(path))
|
|
83
|
+
if (!(0, node_fs_1.existsSync)(path))
|
|
76
84
|
return {};
|
|
77
85
|
try {
|
|
78
|
-
return sanitize(JSON.parse(readFileSync(path, "utf-8")));
|
|
86
|
+
return sanitize(JSON.parse((0, node_fs_1.readFileSync)(path, "utf-8")));
|
|
79
87
|
}
|
|
80
88
|
catch (err) {
|
|
81
89
|
const reason = err instanceof Error ? err.message : String(err);
|
|
@@ -84,7 +92,7 @@ function readSettingsFile(path) {
|
|
|
84
92
|
}
|
|
85
93
|
}
|
|
86
94
|
/** Load merged settings: global provides defaults, project overrides. */
|
|
87
|
-
|
|
95
|
+
function loadSettings(cwd = process.cwd()) {
|
|
88
96
|
return { ...readSettingsFile(globalPath()), ...readSettingsFile(projectPath(cwd)) };
|
|
89
97
|
}
|
|
90
98
|
/**
|
|
@@ -92,11 +100,11 @@ export function loadSettings(cwd = process.cwd()) {
|
|
|
92
100
|
* Returns `true` on success, `false` if the write (or mkdir) failed so the
|
|
93
101
|
* caller can surface a warning — persistence isn't fatal but isn't silent.
|
|
94
102
|
*/
|
|
95
|
-
|
|
103
|
+
function saveSettings(s, cwd = process.cwd()) {
|
|
96
104
|
const path = projectPath(cwd);
|
|
97
105
|
try {
|
|
98
|
-
mkdirSync(dirname(path), { recursive: true });
|
|
99
|
-
writeFileSync(path, JSON.stringify(s, null, 2), "utf-8");
|
|
106
|
+
(0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(path), { recursive: true });
|
|
107
|
+
(0, node_fs_1.writeFileSync)(path, JSON.stringify(s, null, 2), "utf-8");
|
|
100
108
|
return true;
|
|
101
109
|
}
|
|
102
110
|
catch {
|
|
@@ -104,7 +112,7 @@ export function saveSettings(s, cwd = process.cwd()) {
|
|
|
104
112
|
}
|
|
105
113
|
}
|
|
106
114
|
/** Apply persisted settings to the in-memory state via caller-supplied setters. */
|
|
107
|
-
|
|
115
|
+
function applySettings(s, appliers) {
|
|
108
116
|
if (typeof s.maxConcurrent === "number")
|
|
109
117
|
appliers.setMaxConcurrent(s.maxConcurrent);
|
|
110
118
|
if (typeof s.defaultMaxTurns === "number")
|
|
@@ -133,7 +141,7 @@ export function applySettings(s, appliers) {
|
|
|
133
141
|
* routes the success/failure of `saveSettings` into the right message + level
|
|
134
142
|
* so the UI layer (index.ts) stays a thin wire between input and notification.
|
|
135
143
|
*/
|
|
136
|
-
|
|
144
|
+
function persistToastFor(successMsg, persisted) {
|
|
137
145
|
return persisted
|
|
138
146
|
? { message: successMsg, level: "info" }
|
|
139
147
|
: { message: `${successMsg} (session only; failed to persist)`, level: "warning" };
|
|
@@ -143,7 +151,7 @@ export function persistToastFor(successMsg, persisted) {
|
|
|
143
151
|
* `subagents:settings_loaded` lifecycle event. Returns the loaded settings so
|
|
144
152
|
* callers can log/inspect. Extension init wires this once.
|
|
145
153
|
*/
|
|
146
|
-
|
|
154
|
+
function applyAndEmitLoaded(appliers, emit, cwd = process.cwd()) {
|
|
147
155
|
const settings = loadSettings(cwd);
|
|
148
156
|
applySettings(settings, appliers);
|
|
149
157
|
emit("subagents:settings_loaded", { settings });
|
|
@@ -155,7 +163,7 @@ export function applyAndEmitLoaded(appliers, emit, cwd = process.cwd()) {
|
|
|
155
163
|
* return the toast the UI should display. Event payload carries the `persisted`
|
|
156
164
|
* flag so listeners can react to write failures.
|
|
157
165
|
*/
|
|
158
|
-
|
|
166
|
+
function saveAndEmitChanged(snapshot, successMsg, emit, cwd = process.cwd()) {
|
|
159
167
|
const persisted = saveSettings(snapshot, cwd);
|
|
160
168
|
emit("subagents:settings_changed", { settings: snapshot, persisted });
|
|
161
169
|
return persistToastFor(successMsg, persisted);
|