@bastani/atomic 0.8.1-1 → 0.8.2-0

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 (149) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/dist/builtin/intercom/config.ts +3 -4
  3. package/dist/builtin/intercom/index.ts +6 -6
  4. package/dist/builtin/intercom/package.json +1 -1
  5. package/dist/builtin/mcp/agent-dir.ts +11 -2
  6. package/dist/builtin/mcp/cli.js +12 -6
  7. package/dist/builtin/mcp/config.ts +31 -22
  8. package/dist/builtin/mcp/package.json +1 -1
  9. package/dist/builtin/subagents/package.json +1 -1
  10. package/dist/builtin/subagents/src/agents/agents.ts +63 -23
  11. package/dist/builtin/subagents/src/agents/skills.ts +21 -21
  12. package/dist/builtin/subagents/src/extension/index.ts +9 -8
  13. package/dist/builtin/subagents/src/runs/shared/run-history.ts +13 -10
  14. package/dist/builtin/subagents/src/runs/shared/subagent-prompt-runtime.ts +3 -3
  15. package/dist/builtin/subagents/src/shared/artifacts.ts +18 -17
  16. package/dist/builtin/subagents/src/shared/types.ts +4 -4
  17. package/dist/builtin/web-access/config-paths.ts +11 -0
  18. package/dist/builtin/web-access/exa.ts +3 -2
  19. package/dist/builtin/web-access/gemini-api.ts +2 -1
  20. package/dist/builtin/web-access/gemini-search.ts +2 -1
  21. package/dist/builtin/web-access/gemini-web-config.ts +2 -1
  22. package/dist/builtin/web-access/github-extract.ts +2 -1
  23. package/dist/builtin/web-access/index.ts +11 -8
  24. package/dist/builtin/web-access/package.json +1 -1
  25. package/dist/builtin/web-access/perplexity.ts +2 -1
  26. package/dist/builtin/web-access/video-extract.ts +2 -1
  27. package/dist/builtin/web-access/youtube-extract.ts +2 -1
  28. package/dist/builtin/workflows/builtin/deep-research-codebase.ts +4 -0
  29. package/dist/builtin/workflows/builtin/open-claude-design.ts +39 -22
  30. package/dist/builtin/workflows/builtin/ralph.ts +7 -0
  31. package/dist/builtin/workflows/package.json +1 -1
  32. package/dist/builtin/workflows/skills/workflow/SKILL.md +28 -20
  33. package/dist/builtin/workflows/skills/workflow/references/design-checklist.md +8 -4
  34. package/dist/builtin/workflows/skills/workflow/references/running-workflows.md +52 -23
  35. package/dist/builtin/workflows/skills/workflow/references/sdk-authoring.md +41 -12
  36. package/dist/builtin/workflows/src/extension/config-loader.ts +13 -14
  37. package/dist/builtin/workflows/src/extension/discovery.ts +4 -6
  38. package/dist/builtin/workflows/src/extension/index.ts +675 -524
  39. package/dist/builtin/workflows/src/extension/runtime.ts +40 -16
  40. package/dist/builtin/workflows/src/extension/wiring.ts +3 -0
  41. package/dist/builtin/workflows/src/extension/workflow-schema.ts +43 -33
  42. package/dist/builtin/workflows/src/runs/foreground/executor.ts +34 -10
  43. package/dist/builtin/workflows/src/shared/types.ts +1 -5
  44. package/dist/builtin/workflows/src/tui/graph-view.ts +245 -75
  45. package/dist/builtin/workflows/src/tui/overlay-adapter.ts +23 -0
  46. package/dist/builtin/workflows/src/tui/stage-chat-view.ts +259 -149
  47. package/dist/builtin/workflows/src/tui/status-helpers.ts +3 -3
  48. package/dist/builtin/workflows/src/tui/store-widget-installer.ts +99 -10
  49. package/dist/builtin/workflows/src/tui/switcher.ts +4 -5
  50. package/dist/builtin/workflows/src/tui/workflow-attach-pane.ts +29 -0
  51. package/dist/cli/args.d.ts.map +1 -1
  52. package/dist/cli/args.js +11 -8
  53. package/dist/cli/args.js.map +1 -1
  54. package/dist/config.d.ts +21 -0
  55. package/dist/config.d.ts.map +1 -1
  56. package/dist/config.js +59 -4
  57. package/dist/config.js.map +1 -1
  58. package/dist/core/agent-session.d.ts +1 -1
  59. package/dist/core/agent-session.d.ts.map +1 -1
  60. package/dist/core/agent-session.js +2 -2
  61. package/dist/core/agent-session.js.map +1 -1
  62. package/dist/core/auth-storage.d.ts +3 -1
  63. package/dist/core/auth-storage.d.ts.map +1 -1
  64. package/dist/core/auth-storage.js +31 -8
  65. package/dist/core/auth-storage.js.map +1 -1
  66. package/dist/core/extensions/runner.d.ts.map +1 -1
  67. package/dist/core/extensions/runner.js +9 -0
  68. package/dist/core/extensions/runner.js.map +1 -1
  69. package/dist/core/extensions/types.d.ts +11 -0
  70. package/dist/core/extensions/types.d.ts.map +1 -1
  71. package/dist/core/extensions/types.js.map +1 -1
  72. package/dist/core/model-registry.d.ts +3 -2
  73. package/dist/core/model-registry.d.ts.map +1 -1
  74. package/dist/core/model-registry.js +25 -8
  75. package/dist/core/model-registry.js.map +1 -1
  76. package/dist/core/package-manager.d.ts +3 -0
  77. package/dist/core/package-manager.d.ts.map +1 -1
  78. package/dist/core/package-manager.js +97 -58
  79. package/dist/core/package-manager.js.map +1 -1
  80. package/dist/core/resource-loader.d.ts +1 -0
  81. package/dist/core/resource-loader.d.ts.map +1 -1
  82. package/dist/core/resource-loader.js +37 -36
  83. package/dist/core/resource-loader.js.map +1 -1
  84. package/dist/core/sdk.d.ts +5 -4
  85. package/dist/core/sdk.d.ts.map +1 -1
  86. package/dist/core/sdk.js +2 -2
  87. package/dist/core/sdk.js.map +1 -1
  88. package/dist/core/settings-manager.d.ts +7 -1
  89. package/dist/core/settings-manager.d.ts.map +1 -1
  90. package/dist/core/settings-manager.js +29 -8
  91. package/dist/core/settings-manager.js.map +1 -1
  92. package/dist/core/system-prompt.d.ts +1 -1
  93. package/dist/core/system-prompt.d.ts.map +1 -1
  94. package/dist/core/system-prompt.js.map +1 -1
  95. package/dist/core/telemetry.d.ts.map +1 -1
  96. package/dist/core/telemetry.js +2 -2
  97. package/dist/core/telemetry.js.map +1 -1
  98. package/dist/core/timings.d.ts.map +1 -1
  99. package/dist/core/timings.js +2 -2
  100. package/dist/core/timings.js.map +1 -1
  101. package/dist/core/tools/index.d.ts +1 -0
  102. package/dist/core/tools/index.d.ts.map +1 -1
  103. package/dist/core/tools/index.js +8 -0
  104. package/dist/core/tools/index.js.map +1 -1
  105. package/dist/core/tools/todos.d.ts.map +1 -1
  106. package/dist/core/tools/todos.js +3 -3
  107. package/dist/core/tools/todos.js.map +1 -1
  108. package/dist/index.d.ts +2 -2
  109. package/dist/index.d.ts.map +1 -1
  110. package/dist/index.js +2 -2
  111. package/dist/index.js.map +1 -1
  112. package/dist/main.d.ts.map +1 -1
  113. package/dist/main.js +6 -6
  114. package/dist/main.js.map +1 -1
  115. package/dist/modes/interactive/components/atomic-banner.d.ts +4 -0
  116. package/dist/modes/interactive/components/atomic-banner.d.ts.map +1 -0
  117. package/dist/modes/interactive/components/atomic-banner.js +34 -0
  118. package/dist/modes/interactive/components/atomic-banner.js.map +1 -0
  119. package/dist/modes/interactive/components/chat-message-renderer.d.ts +99 -0
  120. package/dist/modes/interactive/components/chat-message-renderer.d.ts.map +1 -0
  121. package/dist/modes/interactive/components/chat-message-renderer.js +450 -0
  122. package/dist/modes/interactive/components/chat-message-renderer.js.map +1 -0
  123. package/dist/modes/interactive/components/chat-transcript.d.ts +69 -0
  124. package/dist/modes/interactive/components/chat-transcript.d.ts.map +1 -0
  125. package/dist/modes/interactive/components/chat-transcript.js +183 -0
  126. package/dist/modes/interactive/components/chat-transcript.js.map +1 -0
  127. package/dist/modes/interactive/components/footer.d.ts +16 -4
  128. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  129. package/dist/modes/interactive/components/footer.js +110 -137
  130. package/dist/modes/interactive/components/footer.js.map +1 -1
  131. package/dist/modes/interactive/components/index.d.ts +2 -0
  132. package/dist/modes/interactive/components/index.d.ts.map +1 -1
  133. package/dist/modes/interactive/components/index.js +2 -0
  134. package/dist/modes/interactive/components/index.js.map +1 -1
  135. package/dist/modes/interactive/interactive-mode.d.ts +9 -0
  136. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  137. package/dist/modes/interactive/interactive-mode.js +192 -137
  138. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  139. package/dist/modes/interactive/theme/catppuccin-mocha.json +5 -5
  140. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  141. package/dist/modes/rpc/rpc-mode.js +11 -0
  142. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  143. package/dist/utils/tools-manager.d.ts.map +1 -1
  144. package/dist/utils/tools-manager.js +2 -2
  145. package/dist/utils/tools-manager.js.map +1 -1
  146. package/dist/utils/version-check.d.ts.map +1 -1
  147. package/dist/utils/version-check.js +2 -2
  148. package/dist/utils/version-check.js.map +1 -1
  149. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -2,6 +2,19 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [0.8.2-0] - 2026-05-16
6
+
7
+ ### Changed
8
+
9
+ - Reduced the Atomic startup banner to a compact three-line mark.
10
+
11
+ ## [0.8.1] - 2026-05-15
12
+
13
+ ### Fixed
14
+
15
+ - Fixed the Atomic changelog viewer to show only the current release notes instead of including older sections.
16
+ - Fixed the published `@bastani/atomic` package manifest so Bun can install it outside the monorepo without resolving private workspace-only bundled packages.
17
+
5
18
  ## [0.8.1-1] - 2026-05-15
6
19
 
7
20
  ### Fixed
@@ -1,7 +1,5 @@
1
1
  import { existsSync, readFileSync } from "fs";
2
- import { join } from "path";
3
- import { homedir } from "os";
4
- import { CONFIG_DIR_NAME } from "@bastani/atomic";
2
+ import { getAgentConfigPaths } from "@bastani/atomic";
5
3
 
6
4
  export interface IntercomConfig {
7
5
  /** Broker command used to spawn the broker process (e.g. "npx" or "bun") */
@@ -23,7 +21,8 @@ export interface IntercomConfig {
23
21
  replyHint: boolean;
24
22
  }
25
23
 
26
- const CONFIG_PATH = join(homedir(), CONFIG_DIR_NAME, "agent", "intercom", "config.json");
24
+ const CONFIG_PATHS = getAgentConfigPaths("intercom", "config.json");
25
+ const CONFIG_PATH = CONFIG_PATHS.find((path) => existsSync(path)) ?? CONFIG_PATHS[0]!;
27
26
 
28
27
  const defaults: IntercomConfig = {
29
28
  brokerCommand: "npx",
@@ -2,7 +2,7 @@ import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-age
2
2
  import { randomUUID } from "crypto";
3
3
  import { Type } from "typebox";
4
4
  import { Text } from "@mariozechner/pi-tui";
5
- import { APP_NAME } from "@bastani/atomic";
5
+ import { APP_NAME, getEnvValue } from "@bastani/atomic";
6
6
  import { IntercomClient } from "./broker/client.ts";
7
7
  import { spawnBrokerIfNeeded } from "./broker/spawn.ts";
8
8
  import { SessionListOverlay } from "./ui/session-list.ts";
@@ -79,14 +79,14 @@ function formatAttachments(attachments: Attachment[]): string {
79
79
  return text;
80
80
  }
81
81
  function readChildOrchestratorMetadata(): ChildOrchestratorMetadata | null {
82
- const orchestratorTarget = process.env[SUBAGENT_ORCHESTRATOR_TARGET_ENV]?.trim();
83
- const runId = process.env[SUBAGENT_RUN_ID_ENV]?.trim();
84
- const agent = process.env[SUBAGENT_CHILD_AGENT_ENV]?.trim();
85
- const index = process.env[SUBAGENT_CHILD_INDEX_ENV]?.trim();
82
+ const orchestratorTarget = getEnvValue(SUBAGENT_ORCHESTRATOR_TARGET_ENV)?.trim();
83
+ const runId = getEnvValue(SUBAGENT_RUN_ID_ENV)?.trim();
84
+ const agent = getEnvValue(SUBAGENT_CHILD_AGENT_ENV)?.trim();
85
+ const index = getEnvValue(SUBAGENT_CHILD_INDEX_ENV)?.trim();
86
86
  if (!orchestratorTarget || !runId || !agent || !index) {
87
87
  return null;
88
88
  }
89
- const sessionName = process.env[SUBAGENT_INTERCOM_SESSION_NAME_ENV]?.trim();
89
+ const sessionName = getEnvValue(SUBAGENT_INTERCOM_SESSION_NAME_ENV)?.trim();
90
90
  return {
91
91
  orchestratorTarget,
92
92
  runId,
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bastani/intercom",
3
- "version": "0.8.1-1",
3
+ "version": "0.8.2-0",
4
4
  "private": true,
5
5
  "description": "Atomic extension providing a private coordination channel between parent and child agent sessions.",
6
6
  "contributors": [
@@ -1,9 +1,9 @@
1
1
  import { homedir } from "node:os";
2
2
  import { join, resolve } from "node:path";
3
- import { APP_NAME, CONFIG_DIR_NAME } from "@bastani/atomic";
3
+ import { APP_NAME, CONFIG_DIR_NAME, getAgentDirs as getAtomicAgentDirs, getEnvValue } from "@bastani/atomic";
4
4
 
5
5
  export function getAgentDir(): string {
6
- const configured = process.env[`${APP_NAME.toUpperCase()}_CODING_AGENT_DIR`]?.trim();
6
+ const configured = getEnvValue(`${APP_NAME.toUpperCase()}_CODING_AGENT_DIR`)?.trim();
7
7
  if (!configured) {
8
8
  return join(homedir(), CONFIG_DIR_NAME, "agent");
9
9
  }
@@ -16,6 +16,15 @@ export function getAgentDir(): string {
16
16
  return resolve(configured);
17
17
  }
18
18
 
19
+ export function getAgentDirs(): string[] {
20
+ const configured = getEnvValue(`${APP_NAME.toUpperCase()}_CODING_AGENT_DIR`)?.trim();
21
+ return configured ? [getAgentDir()] : getAtomicAgentDirs();
22
+ }
23
+
19
24
  export function getAgentPath(...segments: string[]): string {
20
25
  return join(getAgentDir(), ...segments);
21
26
  }
27
+
28
+ export function getAgentPaths(...segments: string[]): string[] {
29
+ return getAgentDirs().map((dir) => join(dir, ...segments));
30
+ }
@@ -4,7 +4,7 @@ import fs from "node:fs";
4
4
  import path from "node:path";
5
5
  import os from "node:os";
6
6
  import { pathToFileURL } from "node:url";
7
- import { APP_NAME, CONFIG_DIR_NAME } from "@bastani/atomic";
7
+ import { APP_NAME, CONFIG_DIR_NAME, getAgentDirs, getEnvValue, getProjectConfigPaths } from "@bastani/atomic";
8
8
 
9
9
  const HOME = os.homedir();
10
10
  const AGENT_DIR_ENV = `${APP_NAME.toUpperCase()}_CODING_AGENT_DIR`;
@@ -15,13 +15,18 @@ function expandHome(input) {
15
15
  return path.resolve(input);
16
16
  }
17
17
 
18
- const AGENT_DIR = process.env[AGENT_DIR_ENV]?.trim()
19
- ? expandHome(process.env[AGENT_DIR_ENV].trim())
18
+ const AGENT_DIR_ENV_VALUE = getEnvValue(AGENT_DIR_ENV)?.trim();
19
+ const AGENT_DIR = AGENT_DIR_ENV_VALUE
20
+ ? expandHome(AGENT_DIR_ENV_VALUE)
20
21
  : path.join(HOME, CONFIG_DIR_NAME, "agent");
21
22
  const PI_CONFIG_PATH = path.join(AGENT_DIR, "mcp.json");
23
+ const PI_CONFIG_READ_PATHS = AGENT_DIR_ENV_VALUE
24
+ ? [PI_CONFIG_PATH]
25
+ : getAgentDirs().map((dir) => path.join(dir, "mcp.json"));
22
26
  const GENERIC_GLOBAL_CONFIG_PATH = path.join(HOME, ".config", "mcp", "mcp.json");
23
27
  const PROJECT_CONFIG_PATH = path.resolve(process.cwd(), ".mcp.json");
24
28
  const PROJECT_PI_CONFIG_PATH = path.resolve(process.cwd(), CONFIG_DIR_NAME, "mcp.json");
29
+ const PROJECT_PI_CONFIG_READ_PATHS = getProjectConfigPaths(process.cwd(), "mcp.json");
25
30
 
26
31
  const IMPORT_PATHS = {
27
32
  cursor: [path.join(HOME, ".cursor", "mcp.json")],
@@ -50,14 +55,15 @@ function readJsonFile(filePath) {
50
55
  }
51
56
 
52
57
  function loadPiConfig() {
53
- if (!fs.existsSync(PI_CONFIG_PATH)) {
58
+ const configPath = PI_CONFIG_READ_PATHS.find((candidate) => fs.existsSync(candidate));
59
+ if (!configPath) {
54
60
  return { mcpServers: {} };
55
61
  }
56
62
 
57
- const raw = readJsonFile(PI_CONFIG_PATH);
63
+ const raw = readJsonFile(configPath);
58
64
  const mcpServers = raw.mcpServers ?? raw["mcp-servers"] ?? {};
59
65
  if (!mcpServers || typeof mcpServers !== "object" || Array.isArray(mcpServers)) {
60
- throw new Error(`Invalid MCP config at ${PI_CONFIG_PATH}: expected \"mcpServers\" to be an object`);
66
+ throw new Error(`Invalid MCP config at ${configPath}: expected \"mcpServers\" to be an object`);
61
67
  }
62
68
 
63
69
  const normalized = { ...raw };
@@ -2,8 +2,8 @@
2
2
  import { existsSync, readFileSync, writeFileSync, mkdirSync, renameSync } from "node:fs";
3
3
  import { homedir } from "node:os";
4
4
  import { dirname, join, resolve } from "node:path";
5
- import { CONFIG_DIR_NAME } from "@bastani/atomic";
6
- import { getAgentPath } from "./agent-dir.ts";
5
+ import { CONFIG_DIR_NAME, getProjectConfigPaths } from "@bastani/atomic";
6
+ import { getAgentPath, getAgentPaths } from "./agent-dir.ts";
7
7
  import type { McpConfig, ServerEntry, McpSettings, ImportKind, ServerProvenance } from "./types.ts";
8
8
 
9
9
  const GENERIC_GLOBAL_CONFIG_PATH = join(homedir(), ".config", "mcp", "mcp.json");
@@ -106,6 +106,10 @@ export function getProjectPiConfigPath(cwd = process.cwd()): string {
106
106
  return resolve(cwd, PROJECT_PI_CONFIG_NAME);
107
107
  }
108
108
 
109
+ function getProjectPiConfigPaths(cwd = process.cwd()): string[] {
110
+ return getProjectConfigPaths(cwd, "mcp.json").map((p) => resolve(p));
111
+ }
112
+
109
113
  export function getConfigDiscoveryPaths(overridePath?: string, cwd = process.cwd()): ConfigDiscoveryPath[] {
110
114
  return getConfigSources(overridePath, cwd).map((source) => ({
111
115
  label: source.label,
@@ -195,8 +199,9 @@ export function loadMcpConfig(overridePath?: string, cwd = process.cwd()): McpCo
195
199
 
196
200
  function getConfigSources(overridePath?: string, cwd = process.cwd()): ConfigSourceSpec[] {
197
201
  const userPath = getPiGlobalConfigPath(overridePath);
202
+ const userPaths = overridePath ? [userPath] : getAgentPaths("mcp.json");
198
203
  const projectPath = getProjectConfigPath(cwd);
199
- const projectPiPath = getProjectPiConfigPath(cwd);
204
+ const projectPiPaths = getProjectPiConfigPaths(cwd);
200
205
  const sources: ConfigSourceSpec[] = [];
201
206
 
202
207
  if (GENERIC_GLOBAL_CONFIG_PATH !== userPath) {
@@ -212,15 +217,17 @@ function getConfigSources(overridePath?: string, cwd = process.cwd()): ConfigSou
212
217
  });
213
218
  }
214
219
 
215
- sources.push({
216
- id: "pi-global",
217
- label: "Pi global override",
218
- readPath: userPath,
219
- writePath: userPath,
220
- kind: "user",
221
- shared: false,
222
- scope: "global",
223
- });
220
+ for (const readPath of userPaths.reverse()) {
221
+ sources.push({
222
+ id: "pi-global",
223
+ label: readPath === userPath ? "Pi global override" : "Pi legacy global override",
224
+ readPath,
225
+ writePath: userPath,
226
+ kind: "user",
227
+ shared: false,
228
+ scope: "global",
229
+ });
230
+ }
224
231
 
225
232
  if (projectPath !== userPath) {
226
233
  sources.push({
@@ -234,16 +241,18 @@ function getConfigSources(overridePath?: string, cwd = process.cwd()): ConfigSou
234
241
  });
235
242
  }
236
243
 
237
- if (projectPiPath !== userPath && projectPiPath !== projectPath) {
238
- sources.push({
239
- id: "pi-project",
240
- label: "project Pi override",
241
- readPath: projectPiPath,
242
- writePath: projectPiPath,
243
- kind: "project",
244
- shared: false,
245
- scope: "project",
246
- });
244
+ for (const projectPiPath of projectPiPaths.reverse()) {
245
+ if (projectPiPath !== userPath && projectPiPath !== projectPath) {
246
+ sources.push({
247
+ id: "pi-project",
248
+ label: projectPiPath === getProjectPiConfigPath(cwd) ? "project Pi override" : "project Pi legacy override",
249
+ readPath: projectPiPath,
250
+ writePath: getProjectPiConfigPath(cwd),
251
+ kind: "project",
252
+ shared: false,
253
+ scope: "project",
254
+ });
255
+ }
247
256
  }
248
257
 
249
258
  return sources;
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bastani/mcp",
3
- "version": "0.8.1-1",
3
+ "version": "0.8.2-0",
4
4
  "private": true,
5
5
  "description": "Atomic extension that adapts MCP (Model Context Protocol) servers into the coding agent.",
6
6
  "contributors": [
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bastani/subagents",
3
- "version": "0.8.1-1",
3
+ "version": "0.8.2-0",
4
4
  "private": true,
5
5
  "description": "Atomic extension for delegating tasks to subagents with chains, parallel execution, and TUI clarification.",
6
6
  "contributors": [
@@ -6,7 +6,7 @@ import * as fs from "node:fs";
6
6
  import * as os from "node:os";
7
7
  import * as path from "node:path";
8
8
  import { fileURLToPath } from "node:url";
9
- import { CONFIG_DIR_NAME } from "@bastani/atomic";
9
+ import { CONFIG_DIR_NAME, getAgentConfigPaths, getProjectConfigDirs } from "@bastani/atomic";
10
10
  import type { OutputMode } from "../shared/types.ts";
11
11
  import { KNOWN_FIELDS } from "./agent-serializer.ts";
12
12
  import { parseChain } from "./chain-serializer.ts";
@@ -132,7 +132,15 @@ interface AgentDiscoveryResult {
132
132
  }
133
133
 
134
134
  function getUserChainDir(): string {
135
- return path.join(os.homedir(), CONFIG_DIR_NAME, "agent", "chains");
135
+ return getAgentConfigPaths("chains")[0] ?? path.join(os.homedir(), CONFIG_DIR_NAME, "agent", "chains");
136
+ }
137
+
138
+ function getUserChainDirs(): string[] {
139
+ return getAgentConfigPaths("chains");
140
+ }
141
+
142
+ function getUserAgentDirs(): string[] {
143
+ return getAgentConfigPaths("agents");
136
144
  }
137
145
 
138
146
  function splitToolList(rawTools: string[] | undefined): { tools?: string[]; mcpDirectTools?: string[] } {
@@ -207,7 +215,7 @@ function cloneOverrideValue(override: BuiltinAgentOverrideConfig): BuiltinAgentO
207
215
  function findNearestProjectRoot(cwd: string): string | null {
208
216
  let currentDir = cwd;
209
217
  while (true) {
210
- if (isDirectory(path.join(currentDir, CONFIG_DIR_NAME)) || isDirectory(path.join(currentDir, ".agents"))) {
218
+ if (getProjectConfigDirs(currentDir).some(isDirectory) || isDirectory(path.join(currentDir, ".agents"))) {
211
219
  return currentDir;
212
220
  }
213
221
 
@@ -218,12 +226,21 @@ function findNearestProjectRoot(cwd: string): string | null {
218
226
  }
219
227
 
220
228
  function getUserAgentSettingsPath(): string {
221
- return path.join(os.homedir(), CONFIG_DIR_NAME, "agent", "settings.json");
229
+ return getAgentConfigPaths("settings.json")[0] ?? path.join(os.homedir(), CONFIG_DIR_NAME, "agent", "settings.json");
230
+ }
231
+
232
+ function getUserAgentSettingsPaths(): string[] {
233
+ return getAgentConfigPaths("settings.json");
222
234
  }
223
235
 
224
236
  function getProjectAgentSettingsPath(cwd: string): string | null {
225
237
  const projectRoot = findNearestProjectRoot(cwd);
226
- return projectRoot ? path.join(projectRoot, CONFIG_DIR_NAME, "settings.json") : null;
238
+ return projectRoot ? path.join(getProjectConfigDirs(projectRoot)[0]!, "settings.json") : null;
239
+ }
240
+
241
+ function getProjectAgentSettingsPaths(cwd: string): string[] {
242
+ const projectRoot = findNearestProjectRoot(cwd);
243
+ return projectRoot ? getProjectConfigDirs(projectRoot).map((dir) => path.join(dir, "settings.json")) : [];
227
244
  }
228
245
 
229
246
  function readSettingsFileStrict(filePath: string): Record<string, unknown> {
@@ -413,6 +430,22 @@ function applyBuiltinOverride(
413
430
  return next;
414
431
  }
415
432
 
433
+ function readMergedSubagentSettings(filePaths: string[]): { settings: SubagentSettings; path: string | null } {
434
+ let settings = EMPTY_SUBAGENT_SETTINGS;
435
+ let path: string | null = null;
436
+ for (let i = filePaths.length - 1; i >= 0; i--) {
437
+ const filePath = filePaths[i]!;
438
+ if (!fs.existsSync(filePath)) continue;
439
+ const next = readSubagentSettings(filePath);
440
+ settings = {
441
+ disableBuiltins: next.disableBuiltins ?? settings.disableBuiltins,
442
+ overrides: { ...settings.overrides, ...next.overrides },
443
+ };
444
+ path = filePath;
445
+ }
446
+ return { settings, path };
447
+ }
448
+
416
449
  function applyBuiltinOverrides(
417
450
  builtinAgents: AgentConfig[],
418
451
  userSettings: SubagentSettings,
@@ -700,10 +733,13 @@ function resolveNearestProjectAgentDirs(cwd: string): { readDirs: string[]; pref
700
733
  if (!projectRoot) return { readDirs: [], preferredDir: null };
701
734
 
702
735
  const legacyDir = path.join(projectRoot, ".agents");
703
- const preferredDir = path.join(projectRoot, CONFIG_DIR_NAME, "agents");
736
+ const preferredDir = path.join(getProjectConfigDirs(projectRoot)[0]!, "agents");
704
737
  const readDirs: string[] = [];
705
738
  if (isDirectory(legacyDir)) readDirs.push(legacyDir);
706
- if (isDirectory(preferredDir)) readDirs.push(preferredDir);
739
+ for (const configDir of getProjectConfigDirs(projectRoot).reverse()) {
740
+ const agentsDir = path.join(configDir, "agents");
741
+ if (isDirectory(agentsDir)) readDirs.push(agentsDir);
742
+ }
707
743
 
708
744
  return {
709
745
  readDirs,
@@ -715,22 +751,24 @@ function resolveNearestProjectChainDirs(cwd: string): { readDirs: string[]; pref
715
751
  const projectRoot = findNearestProjectRoot(cwd);
716
752
  if (!projectRoot) return { readDirs: [], preferredDir: null };
717
753
 
718
- const preferredDir = path.join(projectRoot, CONFIG_DIR_NAME, "chains");
754
+ const preferredDir = path.join(getProjectConfigDirs(projectRoot)[0]!, "chains");
719
755
  return {
720
- readDirs: isDirectory(preferredDir) ? [preferredDir] : [],
756
+ readDirs: getProjectConfigDirs(projectRoot).reverse().map((configDir) => path.join(configDir, "chains")).filter(isDirectory),
721
757
  preferredDir,
722
758
  };
723
759
  }
724
760
  const BUILTIN_AGENTS_DIR = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "..", "agents");
725
761
 
726
762
  export function discoverAgents(cwd: string, scope: AgentScope): AgentDiscoveryResult {
727
- const userDirOld = path.join(os.homedir(), CONFIG_DIR_NAME, "agent", "agents");
763
+ const userDirOld = getUserAgentDirs();
728
764
  const userDirNew = path.join(os.homedir(), ".agents");
729
765
  const { readDirs: projectAgentDirs, preferredDir: projectAgentsDir } = resolveNearestProjectAgentDirs(cwd);
730
- const userSettingsPath = getUserAgentSettingsPath();
731
- const projectSettingsPath = getProjectAgentSettingsPath(cwd);
732
- const userSettings = scope === "project" ? EMPTY_SUBAGENT_SETTINGS : readSubagentSettings(userSettingsPath);
733
- const projectSettings = scope === "user" ? EMPTY_SUBAGENT_SETTINGS : readSubagentSettings(projectSettingsPath);
766
+ const userSettingsLoad = readMergedSubagentSettings(getUserAgentSettingsPaths());
767
+ const projectSettingsLoad = readMergedSubagentSettings(getProjectAgentSettingsPaths(cwd));
768
+ const userSettingsPath = userSettingsLoad.path ?? getUserAgentSettingsPath();
769
+ const projectSettingsPath = projectSettingsLoad.path ?? getProjectAgentSettingsPath(cwd);
770
+ const userSettings = scope === "project" ? EMPTY_SUBAGENT_SETTINGS : userSettingsLoad.settings;
771
+ const projectSettings = scope === "user" ? EMPTY_SUBAGENT_SETTINGS : projectSettingsLoad.settings;
734
772
 
735
773
  const builtinAgents = applyBuiltinOverrides(
736
774
  loadAgentsFromDir(BUILTIN_AGENTS_DIR, "builtin"),
@@ -740,7 +778,7 @@ export function discoverAgents(cwd: string, scope: AgentScope): AgentDiscoveryRe
740
778
  projectSettingsPath,
741
779
  );
742
780
 
743
- const userAgentsOld = scope === "project" ? [] : loadAgentsFromDir(userDirOld, "user");
781
+ const userAgentsOld = scope === "project" ? [] : userDirOld.flatMap((dir) => loadAgentsFromDir(dir, "user"));
744
782
  const userAgentsNew = scope === "project" ? [] : loadAgentsFromDir(userDirNew, "user");
745
783
  const userAgents = [...userAgentsOld, ...userAgentsNew];
746
784
 
@@ -763,15 +801,17 @@ export function discoverAgentsAll(cwd: string): {
763
801
  userSettingsPath: string;
764
802
  projectSettingsPath: string | null;
765
803
  } {
766
- const userDirOld = path.join(os.homedir(), CONFIG_DIR_NAME, "agent", "agents");
804
+ const userDirOld = getUserAgentDirs();
767
805
  const userDirNew = path.join(os.homedir(), ".agents");
768
806
  const userChainDir = getUserChainDir();
769
807
  const { readDirs: projectDirs, preferredDir: projectDir } = resolveNearestProjectAgentDirs(cwd);
770
808
  const { readDirs: projectChainDirs, preferredDir: projectChainDir } = resolveNearestProjectChainDirs(cwd);
771
- const userSettingsPath = getUserAgentSettingsPath();
772
- const projectSettingsPath = getProjectAgentSettingsPath(cwd);
773
- const userSettings = readSubagentSettings(userSettingsPath);
774
- const projectSettings = readSubagentSettings(projectSettingsPath);
809
+ const userSettingsLoad = readMergedSubagentSettings(getUserAgentSettingsPaths());
810
+ const projectSettingsLoad = readMergedSubagentSettings(getProjectAgentSettingsPaths(cwd));
811
+ const userSettingsPath = userSettingsLoad.path ?? getUserAgentSettingsPath();
812
+ const projectSettingsPath = projectSettingsLoad.path ?? getProjectAgentSettingsPath(cwd);
813
+ const userSettings = userSettingsLoad.settings;
814
+ const projectSettings = projectSettingsLoad.settings;
775
815
 
776
816
  const builtin = applyBuiltinOverrides(
777
817
  loadAgentsFromDir(BUILTIN_AGENTS_DIR, "builtin"),
@@ -781,7 +821,7 @@ export function discoverAgentsAll(cwd: string): {
781
821
  projectSettingsPath,
782
822
  );
783
823
  const user = [
784
- ...loadAgentsFromDir(userDirOld, "user"),
824
+ ...userDirOld.flatMap((dir) => loadAgentsFromDir(dir, "user")),
785
825
  ...loadAgentsFromDir(userDirNew, "user"),
786
826
  ];
787
827
  const projectMap = new Map<string, AgentConfig>();
@@ -799,11 +839,11 @@ export function discoverAgentsAll(cwd: string): {
799
839
  }
800
840
  }
801
841
  const chains = [
802
- ...loadChainsFromDir(userChainDir, "user"),
842
+ ...getUserChainDirs().flatMap((dir) => loadChainsFromDir(dir, "user")),
803
843
  ...Array.from(chainMap.values()),
804
844
  ];
805
845
 
806
- const userDir = fs.existsSync(userDirNew) ? userDirNew : userDirOld;
846
+ const userDir = fs.existsSync(userDirNew) ? userDirNew : userDirOld[0]!;
807
847
 
808
848
  return { builtin, user, project, chains, userDir, projectDir, userChainDir, projectChainDir, userSettingsPath, projectSettingsPath };
809
849
  }
@@ -6,7 +6,7 @@ import { execSync } from "node:child_process";
6
6
  import * as fs from "node:fs";
7
7
  import * as os from "node:os";
8
8
  import * as path from "node:path";
9
- import { CONFIG_DIR_NAME as CONFIG_DIR } from "@bastani/atomic";
9
+ import { getAgentConfigPaths, getAgentDirs, getProjectConfigDirs } from "@bastani/atomic";
10
10
 
11
11
  export type SkillSource =
12
12
  | "project"
@@ -50,7 +50,6 @@ const MAX_CACHE_SIZE = 50;
50
50
  let loadSkillsCache: { cwd: string; skills: CachedSkillEntry[]; timestamp: number } | null = null;
51
51
  const LOAD_SKILLS_CACHE_TTL_MS = 5000;
52
52
 
53
- const AGENT_DIR = path.join(os.homedir(), CONFIG_DIR, "agent");
54
53
  const SUBAGENT_ORCHESTRATION_SKILL = "subagent";
55
54
 
56
55
  const SOURCE_PRIORITY: Record<SkillSource, number> = {
@@ -135,8 +134,8 @@ function getGlobalNpmRoot(): string | null {
135
134
 
136
135
  function collectInstalledPackageSkillPaths(cwd: string): SkillSearchPath[] {
137
136
  const dirs: SkillSearchPath[] = [
138
- { path: path.join(cwd, CONFIG_DIR, "npm", "node_modules"), source: "project-package" },
139
- { path: path.join(AGENT_DIR, "npm", "node_modules"), source: "user-package" },
137
+ ...getProjectConfigDirs(cwd).map((configDir) => ({ path: path.join(configDir, "npm", "node_modules"), source: "project-package" as const })),
138
+ ...getAgentConfigPaths("npm", "node_modules").map((dir) => ({ path: dir, source: "user-package" as const })),
140
139
  ];
141
140
 
142
141
  const globalRoot = getGlobalNpmRoot();
@@ -187,8 +186,8 @@ function collectInstalledPackageSkillPaths(cwd: string): SkillSearchPath[] {
187
186
  function collectSettingsSkillPaths(cwd: string): SkillSearchPath[] {
188
187
  const results: SkillSearchPath[] = [];
189
188
  const settingsFiles = [
190
- { file: path.join(cwd, CONFIG_DIR, "settings.json"), base: path.join(cwd, CONFIG_DIR), source: "project-settings" as const },
191
- { file: path.join(AGENT_DIR, "settings.json"), base: AGENT_DIR, source: "user-settings" as const },
189
+ ...getProjectConfigDirs(cwd).map((configDir) => ({ file: path.join(configDir, "settings.json"), base: configDir, source: "project-settings" as const })),
190
+ ...getAgentConfigPaths("settings.json").map((file) => ({ file, base: path.dirname(file), source: "user-settings" as const })),
192
191
  ];
193
192
 
194
193
  for (const { file, base, source } of settingsFiles) {
@@ -287,8 +286,8 @@ function resolveSettingsPackageRoot(source: string, baseDir: string): string | u
287
286
 
288
287
  function collectSettingsPackageSkillPaths(cwd: string): SkillSearchPath[] {
289
288
  const settingsFiles = [
290
- { file: path.join(cwd, CONFIG_DIR, "settings.json"), base: path.join(cwd, CONFIG_DIR), source: "project-package" as const },
291
- { file: path.join(AGENT_DIR, "settings.json"), base: AGENT_DIR, source: "user-package" as const },
289
+ ...getProjectConfigDirs(cwd).map((configDir) => ({ file: path.join(configDir, "settings.json"), base: configDir, source: "project-package" as const })),
290
+ ...getAgentConfigPaths("settings.json").map((file) => ({ file, base: path.dirname(file), source: "user-package" as const })),
292
291
  ];
293
292
  const results: SkillSearchPath[] = [];
294
293
 
@@ -317,9 +316,9 @@ function collectSettingsPackageSkillPaths(cwd: string): SkillSearchPath[] {
317
316
 
318
317
  function buildSkillPaths(cwd: string): SkillSearchPath[] {
319
318
  const skillPaths: SkillSearchPath[] = [
320
- { path: path.join(cwd, CONFIG_DIR, "skills"), source: "project" },
319
+ ...getProjectConfigDirs(cwd).map((configDir) => ({ path: path.join(configDir, "skills"), source: "project" as const })),
321
320
  { path: path.join(cwd, ".agents", "skills"), source: "project" },
322
- { path: path.join(AGENT_DIR, "skills"), source: "user" },
321
+ ...getAgentConfigPaths("skills").map((dir) => ({ path: dir, source: "user" as const })),
323
322
  { path: path.join(os.homedir(), ".agents", "skills"), source: "user" },
324
323
  ...collectInstalledPackageSkillPaths(cwd),
325
324
  ...collectSettingsPackageSkillPaths(cwd),
@@ -340,21 +339,22 @@ function buildSkillPaths(cwd: string): SkillSearchPath[] {
340
339
  function inferSkillSource(filePath: string, cwd: string, sourceHint?: SkillSource): SkillSource {
341
340
  if (sourceHint) return sourceHint;
342
341
 
343
- const projectConfigRoot = path.resolve(cwd, CONFIG_DIR);
344
- const projectSkillsRoot = path.resolve(cwd, CONFIG_DIR, "skills");
345
- const projectPackagesRoot = path.resolve(cwd, CONFIG_DIR, "npm", "node_modules");
342
+ const projectConfigRoots = getProjectConfigDirs(cwd).map((dir) => path.resolve(dir));
343
+ const projectSkillsRoots = projectConfigRoots.map((dir) => path.join(dir, "skills"));
344
+ const projectPackagesRoots = projectConfigRoots.map((dir) => path.join(dir, "npm", "node_modules"));
346
345
  const projectAgentsRoot = path.resolve(cwd, ".agents");
347
- const userSkillsRoot = path.resolve(AGENT_DIR, "skills");
348
- const userPackagesRoot = path.resolve(AGENT_DIR, "npm", "node_modules");
346
+ const userAgentRoots = getAgentDirs().map((dir) => path.resolve(dir));
347
+ const userSkillsRoots = userAgentRoots.map((dir) => path.join(dir, "skills"));
348
+ const userPackagesRoots = userAgentRoots.map((dir) => path.join(dir, "npm", "node_modules"));
349
349
  const userAgentsRoot = path.resolve(os.homedir(), ".agents");
350
350
 
351
- if (isWithinPath(filePath, projectPackagesRoot)) return "project-package";
352
- if (isWithinPath(filePath, projectSkillsRoot) || isWithinPath(filePath, projectAgentsRoot)) return "project";
353
- if (isWithinPath(filePath, projectConfigRoot)) return "project-settings";
351
+ if (projectPackagesRoots.some((root) => isWithinPath(filePath, root))) return "project-package";
352
+ if (projectSkillsRoots.some((root) => isWithinPath(filePath, root)) || isWithinPath(filePath, projectAgentsRoot)) return "project";
353
+ if (projectConfigRoots.some((root) => isWithinPath(filePath, root))) return "project-settings";
354
354
 
355
- if (isWithinPath(filePath, userPackagesRoot)) return "user-package";
356
- if (isWithinPath(filePath, userSkillsRoot) || isWithinPath(filePath, userAgentsRoot)) return "user";
357
- if (isWithinPath(filePath, AGENT_DIR)) return "user-settings";
355
+ if (userPackagesRoots.some((root) => isWithinPath(filePath, root))) return "user-package";
356
+ if (userSkillsRoots.some((root) => isWithinPath(filePath, root)) || isWithinPath(filePath, userAgentsRoot)) return "user";
357
+ if (userAgentRoots.some((root) => isWithinPath(filePath, root))) return "user-settings";
358
358
 
359
359
  const globalRoot = getGlobalNpmRoot();
360
360
  if (globalRoot && isWithinPath(filePath, globalRoot)) return "user-package";
@@ -15,7 +15,7 @@
15
15
  import * as fs from "node:fs";
16
16
  import * as os from "node:os";
17
17
  import * as path from "node:path";
18
- import { CONFIG_DIR_NAME } from "@bastani/atomic";
18
+ import { getAgentConfigPaths, getEnvValue } from "@bastani/atomic";
19
19
  import type { AgentToolResult } from "@earendil-works/pi-agent-core";
20
20
  import { type ExtensionAPI, type ExtensionContext, type ToolDefinition } from "@earendil-works/pi-coding-agent";
21
21
  import { Box, Container, Spacer, Text, truncateToWidth, visibleWidth, wrapTextWithAnsi, type Component } from "@earendil-works/pi-tui";
@@ -74,13 +74,14 @@ function getSubagentSessionRoot(parentSessionFile: string | null): string {
74
74
  }
75
75
 
76
76
  function loadConfig(): ExtensionConfig {
77
- const configPath = path.join(os.homedir(), CONFIG_DIR_NAME, "agent", "extensions", "subagent", "config.json");
78
- try {
79
- if (fs.existsSync(configPath)) {
80
- return JSON.parse(fs.readFileSync(configPath, "utf-8")) as ExtensionConfig;
77
+ for (const configPath of getAgentConfigPaths("extensions", "subagent", "config.json")) {
78
+ try {
79
+ if (fs.existsSync(configPath)) {
80
+ return JSON.parse(fs.readFileSync(configPath, "utf-8")) as ExtensionConfig;
81
+ }
82
+ } catch (error) {
83
+ console.error(`Failed to load subagent config from '${configPath}':`, error);
81
84
  }
82
- } catch (error) {
83
- console.error(`Failed to load subagent config from '${configPath}':`, error);
84
85
  }
85
86
  return {};
86
87
  }
@@ -221,7 +222,7 @@ class SubagentControlNoticeComponent implements Component {
221
222
  }
222
223
 
223
224
  export default function registerSubagentExtension(pi: ExtensionAPI): void {
224
- if (process.env[SUBAGENT_CHILD_ENV] === "1") return;
225
+ if (getEnvValue(SUBAGENT_CHILD_ENV) === "1") return;
225
226
  const globalStore = globalThis as Record<string, unknown>;
226
227
  const runtimeCleanupStoreKey = "__piSubagentRuntimeCleanup";
227
228
  const previousRuntimeCleanup = globalStore[runtimeCleanupStoreKey];
@@ -1,7 +1,7 @@
1
1
  import * as fs from "node:fs";
2
2
  import * as os from "node:os";
3
3
  import * as path from "node:path";
4
- import { CONFIG_DIR_NAME } from "@bastani/atomic";
4
+ import { getAgentConfigPaths } from "@bastani/atomic";
5
5
 
6
6
  export interface RunEntry {
7
7
  agent: string;
@@ -12,7 +12,8 @@ export interface RunEntry {
12
12
  exit?: number;
13
13
  }
14
14
 
15
- const HISTORY_PATH = path.join(os.homedir(), CONFIG_DIR_NAME, "agent", "run-history.jsonl");
15
+ const HISTORY_PATH = getAgentConfigPaths("run-history.jsonl")[0] ?? path.join(os.homedir(), ".atomic", "agent", "run-history.jsonl");
16
+ const HISTORY_READ_PATHS = getAgentConfigPaths("run-history.jsonl");
16
17
  const ROTATE_READ_THRESHOLD = 1200;
17
18
  const ROTATE_KEEP = 1000;
18
19
 
@@ -34,15 +35,17 @@ export function recordRun(agent: string, task: string, exitCode: number, duratio
34
35
  }
35
36
 
36
37
  export function loadRunsForAgent(agent: string): RunEntry[] {
37
- if (!fs.existsSync(HISTORY_PATH)) return [];
38
- let raw: string;
39
- try {
40
- raw = fs.readFileSync(HISTORY_PATH, "utf-8");
41
- } catch {
42
- return [];
38
+ let lines: string[] = [];
39
+ for (const historyPath of HISTORY_READ_PATHS) {
40
+ if (!fs.existsSync(historyPath)) continue;
41
+ try {
42
+ lines.push(...fs.readFileSync(historyPath, "utf-8").split("\n"));
43
+ } catch {
44
+ continue;
45
+ }
43
46
  }
44
-
45
- let lines = raw.split("\n").map((line) => line.trim()).filter((line) => line.length > 0);
47
+ lines = lines.map((line) => line.trim()).filter((line) => line.length > 0);
48
+ if (lines.length === 0) return [];
46
49
 
47
50
  if (lines.length > ROTATE_READ_THRESHOLD) {
48
51
  lines = lines.slice(-ROTATE_KEEP);