@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.
Files changed (44) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +2 -2
  3. package/dist/agent-manager.d.ts +8 -0
  4. package/dist/agent-manager.js +87 -19
  5. package/dist/agent-runner.js +85 -66
  6. package/dist/agent-types.js +45 -27
  7. package/dist/context.js +6 -2
  8. package/dist/cross-extension-rpc.js +9 -5
  9. package/dist/custom-agents.js +18 -15
  10. package/dist/default-agents.js +4 -1
  11. package/dist/enabled-models.js +16 -11
  12. package/dist/env.js +4 -1
  13. package/dist/group-join.js +5 -1
  14. package/dist/index.js +280 -221
  15. package/dist/invocation-config.js +6 -2
  16. package/dist/memory.js +34 -24
  17. package/dist/model-resolver.js +4 -1
  18. package/dist/nico-overrides.js +20 -14
  19. package/dist/output-file.js +21 -15
  20. package/dist/prompts.js +4 -1
  21. package/dist/schedule-store.js +21 -16
  22. package/dist/schedule.js +12 -8
  23. package/dist/settings.js +23 -15
  24. package/dist/skill-loader.js +23 -20
  25. package/dist/status-note.js +4 -1
  26. package/dist/types.d.ts +1 -0
  27. package/dist/types.js +4 -1
  28. package/dist/ui/agent-widget.js +37 -23
  29. package/dist/ui/conversation-viewer.js +43 -39
  30. package/dist/ui/fleet-list.js +30 -24
  31. package/dist/ui/markdown-result.d.ts +3 -0
  32. package/dist/ui/markdown-result.js +53 -0
  33. package/dist/ui/schedule-menu.js +4 -1
  34. package/dist/ui/viewer-keys.js +10 -7
  35. package/dist/usage.js +10 -4
  36. package/dist/worktree.js +31 -26
  37. package/package.json +1 -1
  38. package/src/agent-manager.ts +72 -0
  39. package/src/agent-runner.ts +11 -3
  40. package/src/index.ts +39 -16
  41. package/src/types.ts +1 -0
  42. package/src/ui/markdown-result.ts +56 -0
  43. package/test/agent-manager-history.test.ts +84 -0
  44. package/test/ui/markdown-result.test.ts +45 -0
@@ -1,4 +1,8 @@
1
- export function resolveAgentInvocationConfig(agentConfig, params) {
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
- export function resolveJoinMode(defaultJoinMode, runInBackground) {
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
- 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";
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
- export function isUnsafeName(name) {
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
- export function isSymlink(filePath) {
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
- export function safeReadFile(filePath) {
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
- export function resolveMemoryDir(agentName, scope, cwd) {
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
- export function ensureMemoryDir(memoryDir) {
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
- export function readMemoryIndex(memoryDir) {
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
- export function buildMemoryBlock(agentName, scope, cwd) {
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
- export function buildReadOnlyMemoryBlock(agentName, scope, cwd) {
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)
@@ -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
- export function resolveModel(input, registry) {
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()));
@@ -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
- 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";
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
- export function readNicoAgentOverrides(cwd) {
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
- export function resolveNicoSkills(skills) {
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
- export function applyNicoOverride(agent, override, nicoDefaultModel) {
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
- export function applyNicoOverridesToMap(agents, overrides, defaultModel) {
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) {
@@ -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
- import { appendFileSync, chmodSync, mkdirSync, writeFileSync } from "node:fs";
8
- import { tmpdir } from "node:os";
9
- import { join } from "node:path";
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
- export function encodeCwd(cwd) {
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
- export function createOutputFilePath(cwd, agentId, sessionId) {
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
- export function writeInitialEntry(path, agentId, prompt, cwd) {
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
- export function streamToOutputFile(session, path, agentId, cwd) {
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
- export function buildAgentPrompt(config, cwd, env, parentSystemPrompt, extras) {
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}
@@ -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
- import { existsSync, mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
13
- import { dirname, join } from "node:path";
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
- export function resolveStorePath(cwd, sessionId) {
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
- export class ScheduleStore {
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
- import { Cron } from "croner";
18
- import { nanoid } from "nanoid";
19
- import { resolveModel } from "./model-resolver.js";
20
- export class SubagentScheduler {
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
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
5
- import { dirname, join } from "node:path";
6
- import { getAgentDir } from "@earendil-works/pi-coding-agent";
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
- export function loadSettings(cwd = process.cwd()) {
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
- export function saveSettings(s, cwd = process.cwd()) {
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
- export function applySettings(s, appliers) {
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
- export function persistToastFor(successMsg, persisted) {
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
- export function applyAndEmitLoaded(appliers, emit, cwd = process.cwd()) {
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
- export function saveAndEmitChanged(snapshot, successMsg, emit, cwd = process.cwd()) {
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);