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