@pencil-agent/nano-pencil 1.13.16 → 1.14.1

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 (171) hide show
  1. package/README.md +1 -1
  2. package/dist/build-meta.json +4 -4
  3. package/dist/builtin-extensions.js +20 -0
  4. package/dist/cli/args.d.ts +2 -0
  5. package/dist/cli/args.js +6 -1
  6. package/dist/config.d.ts +26 -3
  7. package/dist/config.js +92 -16
  8. package/dist/core/agent-dir/agent-dir-context.d.ts +60 -0
  9. package/dist/core/agent-dir/agent-dir-context.js +84 -0
  10. package/dist/core/agent-dir/agent-metadata.d.ts +48 -0
  11. package/dist/core/agent-dir/agent-metadata.js +68 -0
  12. package/dist/core/agent-dir/migration-tool.d.ts +39 -0
  13. package/dist/core/agent-dir/migration-tool.js +179 -0
  14. package/dist/core/bash-executor.d.ts +2 -0
  15. package/dist/core/bash-executor.js +1 -0
  16. package/dist/core/config/auth-storage.d.ts +2 -1
  17. package/dist/core/config/auth-storage.js +4 -2
  18. package/dist/core/config/resource-loader.d.ts +3 -0
  19. package/dist/core/config/resource-loader.js +7 -4
  20. package/dist/core/config/settings-manager.d.ts +4 -3
  21. package/dist/core/config/settings-manager.js +10 -7
  22. package/dist/core/extensions/loader.d.ts +2 -2
  23. package/dist/core/extensions/loader.js +9 -8
  24. package/dist/core/extensions/runner.d.ts +3 -1
  25. package/dist/core/extensions/runner.js +7 -1
  26. package/dist/core/extensions/types.d.ts +45 -7
  27. package/dist/core/extensions/wrapper.js +21 -5
  28. package/dist/core/i18n/slash-commands.d.ts +1 -0
  29. package/dist/core/i18n/slash-commands.js +1 -0
  30. package/dist/core/i18n/slash-commands.zh.d.ts +1 -0
  31. package/dist/core/i18n/slash-commands.zh.js +1 -0
  32. package/dist/core/keybindings.d.ts +2 -1
  33. package/dist/core/keybindings.js +3 -3
  34. package/dist/core/mcp/mcp-client.d.ts +6 -1
  35. package/dist/core/mcp/mcp-client.js +10 -3
  36. package/dist/core/mcp/mcp-config.d.ts +21 -10
  37. package/dist/core/mcp/mcp-config.js +41 -28
  38. package/dist/core/messages.js +1 -1
  39. package/dist/core/model-registry.d.ts +4 -0
  40. package/dist/core/model-registry.js +20 -0
  41. package/dist/core/persona/persona-manager.d.ts +19 -0
  42. package/dist/core/persona/persona-manager.js +104 -60
  43. package/dist/core/runtime/agent-session.d.ts +17 -8
  44. package/dist/core/runtime/agent-session.js +53 -229
  45. package/dist/core/runtime/default-tools.d.ts +9 -0
  46. package/dist/core/runtime/default-tools.js +10 -0
  47. package/dist/core/runtime/extension-core-bindings.d.ts +45 -0
  48. package/dist/core/runtime/extension-core-bindings.js +177 -0
  49. package/dist/core/runtime/sdk.d.ts +4 -1
  50. package/dist/core/runtime/sdk.js +14 -12
  51. package/dist/core/runtime/slash-command-catalog.d.ts +26 -0
  52. package/dist/core/runtime/slash-command-catalog.js +75 -0
  53. package/dist/core/session/session-manager.d.ts +22 -11
  54. package/dist/core/session/session-manager.js +48 -30
  55. package/dist/core/slash-commands.js +1 -0
  56. package/dist/core/soul-integration.d.ts +5 -2
  57. package/dist/core/soul-integration.js +10 -8
  58. package/dist/core/sub-agent/sub-agent-backend.js +4 -2
  59. package/dist/core/tools/bash.js +3 -1
  60. package/dist/core/tools/find.d.ts +2 -2
  61. package/dist/core/tools/find.js +4 -1
  62. package/dist/core/tools/grep.d.ts +4 -4
  63. package/dist/core/tools/grep.js +8 -4
  64. package/dist/core/tools/index.d.ts +7 -6
  65. package/dist/core/tools/index.js +1 -0
  66. package/dist/core/tools/input-validation.d.ts +13 -0
  67. package/dist/core/tools/input-validation.js +14 -0
  68. package/dist/core/tools/ls.d.ts +2 -2
  69. package/dist/core/tools/ls.js +10 -2
  70. package/dist/core/tools/read.d.ts +4 -4
  71. package/dist/core/tools/read.js +6 -2
  72. package/dist/core/tools/time.js +1 -0
  73. package/dist/core/tools/write-guard.d.ts +2 -0
  74. package/dist/core/tools/write-guard.js +26 -0
  75. package/dist/core/utils/logger.js +2 -2
  76. package/dist/core/workspace/worktree-manager.js +8 -1
  77. package/dist/extensions/defaults/CLAUDE.md +6 -0
  78. package/dist/extensions/defaults/loop/cron/cron-scheduler.d.ts +2 -0
  79. package/dist/extensions/defaults/loop/cron/cron-scheduler.js +2 -2
  80. package/dist/extensions/defaults/loop/cron/cron-tasks.d.ts +5 -2
  81. package/dist/extensions/defaults/loop/cron/cron-tasks.js +48 -2
  82. package/dist/extensions/defaults/loop/cron-tools/cron-create-tool.js +1 -1
  83. package/dist/extensions/defaults/loop/cron-tools/cron-delete-tool.js +1 -1
  84. package/dist/extensions/defaults/loop/cron-tools/cron-list-tool.js +1 -1
  85. package/dist/extensions/defaults/loop/index.js +23 -22
  86. package/dist/extensions/defaults/recap/CLAUDE.md +15 -0
  87. package/dist/extensions/defaults/recap/index.d.ts +8 -0
  88. package/dist/extensions/defaults/recap/index.js +99 -0
  89. package/dist/extensions/defaults/recap/recap-budget.d.ts +24 -0
  90. package/dist/extensions/defaults/recap/recap-budget.js +21 -0
  91. package/dist/extensions/defaults/recap/recap-extractor.d.ts +34 -0
  92. package/dist/extensions/defaults/recap/recap-extractor.js +128 -0
  93. package/dist/extensions/defaults/recap/recap-renderer.d.ts +19 -0
  94. package/dist/extensions/defaults/recap/recap-renderer.js +55 -0
  95. package/dist/extensions/defaults/recap/recap-synthesizer.d.ts +51 -0
  96. package/dist/extensions/defaults/recap/recap-synthesizer.js +113 -0
  97. package/dist/extensions/defaults/recap/recap-types.d.ts +40 -0
  98. package/dist/extensions/defaults/recap/recap-types.js +16 -0
  99. package/dist/extensions/defaults/sal/eval/types.d.ts +1 -1
  100. package/dist/extensions/defaults/sal/index.js +4 -1
  101. package/dist/extensions/defaults/security-audit/engine/logger.d.ts +19 -1
  102. package/dist/extensions/defaults/security-audit/engine/logger.js +44 -46
  103. package/dist/extensions/defaults/security-audit/index.js +84 -200
  104. package/dist/extensions/defaults/subagent/subagent-runner.d.ts +2 -1
  105. package/dist/extensions/defaults/token-save/README.md +56 -0
  106. package/dist/extensions/defaults/token-save/config.d.ts +8 -0
  107. package/dist/extensions/defaults/token-save/config.js +66 -0
  108. package/dist/extensions/defaults/token-save/filters.d.ts +14 -0
  109. package/dist/extensions/defaults/token-save/filters.js +279 -0
  110. package/dist/extensions/defaults/token-save/index.d.ts +8 -0
  111. package/dist/extensions/defaults/token-save/index.js +169 -0
  112. package/dist/extensions/defaults/token-save/lexer.d.ts +11 -0
  113. package/dist/extensions/defaults/token-save/lexer.js +51 -0
  114. package/dist/extensions/defaults/token-save/recovery.d.ts +1 -0
  115. package/dist/extensions/defaults/token-save/recovery.js +21 -0
  116. package/dist/extensions/defaults/token-save/rewrite.d.ts +19 -0
  117. package/dist/extensions/defaults/token-save/rewrite.js +73 -0
  118. package/dist/extensions/defaults/token-save/runner.d.ts +14 -0
  119. package/dist/extensions/defaults/token-save/runner.js +55 -0
  120. package/dist/extensions/defaults/token-save/stream.d.ts +20 -0
  121. package/dist/extensions/defaults/token-save/stream.js +33 -0
  122. package/dist/extensions/defaults/token-save/toml-dsl.d.ts +25 -0
  123. package/dist/extensions/defaults/token-save/toml-dsl.js +34 -0
  124. package/dist/extensions/defaults/token-save/tracking.d.ts +24 -0
  125. package/dist/extensions/defaults/token-save/tracking.js +74 -0
  126. package/dist/main.js +94 -45
  127. package/dist/migrations.d.ts +4 -3
  128. package/dist/migrations.js +13 -15
  129. package/dist/modes/acp/acp-mode.js +21 -0
  130. package/dist/modes/interactive/components/config-selector.js +5 -5
  131. package/dist/modes/interactive/interactive-mode.d.ts +2 -1
  132. package/dist/modes/interactive/interactive-mode.js +41 -64
  133. package/dist/modes/rpc/rpc-client.d.ts +7 -1
  134. package/dist/modes/rpc/rpc-client.js +6 -0
  135. package/dist/modes/rpc/rpc-mode.js +8 -0
  136. package/dist/modes/rpc/rpc-types.d.ts +13 -1
  137. package/dist/nanopencil-defaults.d.ts +2 -2
  138. package/dist/nanopencil-defaults.js +4 -6
  139. package/dist/node_modules/@pencil-agent/agent-core/agent.d.ts +15 -2
  140. package/dist/node_modules/@pencil-agent/agent-core/agent.js +23 -4
  141. package/dist/node_modules/@pencil-agent/agent-core/index.d.ts +3 -1
  142. package/dist/node_modules/@pencil-agent/agent-core/index.js +3 -1
  143. package/dist/node_modules/@pencil-agent/agent-core/proxy.js +13 -6
  144. package/dist/node_modules/@pencil-agent/agent-core/structured-adaptive-agent-loop.d.ts +15 -0
  145. package/dist/node_modules/@pencil-agent/agent-core/structured-adaptive-agent-loop.js +409 -0
  146. package/dist/node_modules/@pencil-agent/agent-core/structured-adaptive-tool-orchestration.d.ts +25 -0
  147. package/dist/node_modules/@pencil-agent/agent-core/structured-adaptive-tool-orchestration.js +294 -0
  148. package/dist/node_modules/@pencil-agent/agent-core/types.d.ts +100 -2
  149. package/dist/node_modules/@pencil-agent/agent-core/types.js +8 -2
  150. package/dist/node_modules/@pencil-agent/ai/models.d.ts +5 -3
  151. package/dist/node_modules/@pencil-agent/ai/models.generated.d.ts +1534 -2556
  152. package/dist/node_modules/@pencil-agent/ai/models.generated.js +552 -1544
  153. package/dist/node_modules/@pencil-agent/ai/stream.js +17 -4
  154. package/dist/node_modules/@pencil-agent/ai/types.d.ts +8 -1
  155. package/dist/node_modules/@pencil-agent/ai/types.js +1 -1
  156. package/dist/node_modules/@pencil-agent/ai/utils/typebox-helpers.js +1 -1
  157. package/dist/node_modules/@pencil-agent/ai/utils/validation.js +2 -1
  158. package/dist/packages/mem-core/engine-scoring-v2.js +3 -3
  159. package/dist/packages/soul-core/evolution.js +12 -3
  160. package/dist/packages/soul-core/src/evolution.js +12 -3
  161. package/docs/Recap/346/211/251/345/261/225.md +410 -0
  162. package/docs/agent-loop-frameworks.md +96 -0
  163. package/docs/multi-agent-fs-design.md +247 -36
  164. package/docs/pencil-platform-charter.md +457 -0
  165. package/docs/remote-tool-register-design.md +436 -0
  166. package/docs//346/226/207/346/241/243/344/270/255/345/277/203.md +7 -1
  167. package/package.json +8 -3
  168. package/docs/Insight /346/264/236/345/257/237/346/212/245/345/221/212.md" +0 -352
  169. package/docs/SAL/345/256/236/351/252/214/350/257/204/344/274/260/346/226/271/345/274/217/357/274/210/344/273/243/347/240/201/345/257/271/346/257/224/344/270/216/345/244/232worktree/357/274/211.md +0 -158
  170. package/docs/SAL/346/200/273/344/275/223/350/267/257/347/272/277/344/270/216/345/256/236/351/252/214/345/244/247/347/272/262.md +0 -213
  171. package/docs//350/256/244/347/237/245/345/234/260/345/233/276/346/236/266/346/236/204/350/215/211/346/241/210.md +0 -600
package/README.md CHANGED
@@ -127,7 +127,7 @@ nanopencil
127
127
  ```
128
128
 
129
129
  1. **Select your model** — Choose from available providers
130
- 2. **Enter API key** — Securely stored in `~/.nanopencil/agent/auth.json`
130
+ 2. **Enter API key** — Securely stored in `~/.pencils/agents/default/auth.json`
131
131
  3. **Start coding** — Just type what you want to build
132
132
 
133
133
  ### Example Session
@@ -1,6 +1,6 @@
1
1
  {
2
- "version": "1.13.16",
3
- "commitHash": "2949449",
4
- "branch": "release-1.13.16",
5
- "builtAt": "2026-05-09T13:16:34.096Z"
2
+ "version": "1.14.1",
3
+ "commitHash": "b4f6f24",
4
+ "branch": "main",
5
+ "builtAt": "2026-05-23T15:05:06.541Z"
6
6
  }
@@ -23,11 +23,13 @@ const BUNDLED_LOOP_EXTENSION = join(__dirname, "extensions", "defaults", "loop",
23
23
  const BUNDLED_PLAN_EXTENSION = join(__dirname, "extensions", "defaults", "plan", "index.js");
24
24
  const BUNDLED_DIAGNOSTICS_EXTENSION = join(__dirname, "extensions", "defaults", "diagnostics", "index.js");
25
25
  const BUNDLED_SAL_EXTENSION = join(__dirname, "extensions", "defaults", "sal", "index.js");
26
+ const BUNDLED_TOKEN_SAVE_EXTENSION = join(__dirname, "extensions", "defaults", "token-save", "index.js");
26
27
  const BUNDLED_GRUB_EXTENSION = join(__dirname, "extensions", "defaults", "grub", "index.js");
27
28
  const BUNDLED_SUBAGENT_EXTENSION = join(__dirname, "extensions", "defaults", "subagent", "index.js");
28
29
  const BUNDLED_TEAM_EXTENSION = join(__dirname, "extensions", "defaults", "team", "index.js");
29
30
  const BUNDLED_IDLE_THINK_EXTENSION = join(__dirname, "extensions", "defaults", "idle-think", "index.js");
30
31
  const BUNDLED_BTW_EXTENSION = join(__dirname, "extensions", "defaults", "btw", "index.js");
32
+ const BUNDLED_RECAP_EXTENSION = join(__dirname, "extensions", "defaults", "recap", "index.js");
31
33
  const BUNDLED_DEBUG_EXTENSION = join(__dirname, "extensions", "defaults", "debug", "index.js");
32
34
  const BUNDLED_MCP_EXTENSION = join(__dirname, "extensions", "defaults", "mcp", "index.js");
33
35
  const BUNDLED_EXPORT_HTML_EXTENSION = join(__dirname, "extensions", "optional", "export-html", "index.js");
@@ -95,6 +97,15 @@ export function getBuiltinExtensionPaths() {
95
97
  if (existsSync(salTs))
96
98
  paths.push(salTs);
97
99
  }
100
+ // === TokenSave extension (default-on command output filtering and savings analytics) ===
101
+ if (existsSync(BUNDLED_TOKEN_SAVE_EXTENSION)) {
102
+ paths.push(BUNDLED_TOKEN_SAVE_EXTENSION);
103
+ }
104
+ else {
105
+ const tokenSaveTs = join(__dirname, "extensions", "defaults", "token-save", "index.ts");
106
+ if (existsSync(tokenSaveTs))
107
+ paths.push(tokenSaveTs);
108
+ }
98
109
  // === NanoMem extension ===
99
110
  // 1) Prefer extension bundled to dist/packages during build
100
111
  if (existsSync(BUNDLED_NANOMEM_EXTENSION_PACKAGES)) {
@@ -252,6 +263,15 @@ export function getBuiltinExtensionPaths() {
252
263
  if (existsSync(btwTs))
253
264
  paths.push(btwTs);
254
265
  }
266
+ // === Recap extension (on-demand ※ recap situational summaries) ===
267
+ if (existsSync(BUNDLED_RECAP_EXTENSION)) {
268
+ paths.push(BUNDLED_RECAP_EXTENSION);
269
+ }
270
+ else {
271
+ const recapTs = join(__dirname, "extensions", "defaults", "recap", "index.ts");
272
+ if (existsSync(recapTs))
273
+ paths.push(recapTs);
274
+ }
255
275
  // === Debug extension (system diagnostics with three-layer analysis) ===
256
276
  if (existsSync(BUNDLED_DEBUG_EXTENSION)) {
257
277
  paths.push(BUNDLED_DEBUG_EXTENSION);
@@ -45,6 +45,8 @@ export interface Args {
45
45
  disableSoul?: boolean;
46
46
  /** Enable ACP (Agent Client Protocol) mode for editor integration */
47
47
  acp?: boolean;
48
+ /** Multi-Agent: ID of the agent to use. Default: "default" */
49
+ agent?: string;
48
50
  messages: string[];
49
51
  fileArgs: string[];
50
52
  /** Unknown flags (potentially extension flags) - map of flag name to value */
package/dist/cli/args.js CHANGED
@@ -144,6 +144,9 @@ export function parseArgs(args, extensionFlags) {
144
144
  else if (arg === "--acp") {
145
145
  result.acp = true;
146
146
  }
147
+ else if (arg === "--agent" && i + 1 < args.length) {
148
+ result.agent = args[++i];
149
+ }
147
150
  else if (arg.startsWith("@")) {
148
151
  result.fileArgs.push(arg.slice(1)); // Remove @ prefix
149
152
  }
@@ -179,7 +182,8 @@ ${chalk.bold("Commands:")}
179
182
  ${APP_NAME} update [source] Update installed extensions (skips pinned sources)
180
183
  ${APP_NAME} list List installed extensions from settings
181
184
  ${APP_NAME} config Open TUI to enable/disable package resources
182
- ${APP_NAME} <command> --help Show help for install/remove/update/list
185
+ ${APP_NAME} migrate Migrate data from legacy ~/.nanopencil to ~/.pencils
186
+ ${APP_NAME} <command> --help Show help for commands
183
187
 
184
188
  ${chalk.bold("Options:")}
185
189
  --provider <name> Provider name (default: google)
@@ -216,6 +220,7 @@ ${chalk.bold("Options:")}
216
220
  --disable-soul Disable Soul (AI personality evolution)
217
221
  --no-mcp Disable MCP (Model Context Protocol) tools
218
222
  --acp Run as ACP Agent (for editor integration)
223
+ --agent <id> Multi-Agent: Select a specific agent by ID (default: "default")
219
224
  --help, -h Show this help
220
225
  --version, -v Show version number
221
226
 
package/dist/config.d.ts CHANGED
@@ -50,7 +50,7 @@ export declare const PACKAGE_NAME: string;
50
50
  export declare const ENV_AGENT_DIR: string;
51
51
  /** Get the share viewer URL for a gist ID */
52
52
  export declare function getShareViewerUrl(gistId: string): string;
53
- /** Get the agent config directory (e.g., ~/.nanopencil/agent/) */
53
+ /** Get the agent config directory (e.g., ~/.pencils/agents/default/) */
54
54
  export declare function getAgentDir(): string;
55
55
  /** Get path to user's custom themes directory */
56
56
  export declare function getCustomThemesDir(): string;
@@ -72,7 +72,30 @@ export declare function getSessionsDir(): string;
72
72
  export declare function getDebugLogPath(): string;
73
73
  /** Get the root nanopencil config directory (e.g., ~/.nanopencil/) */
74
74
  export declare function getConfigRoot(): string;
75
- /** Get path to global browser-workspace directory (e.g., ~/.nanopencil/browser-workspace) */
75
+ /**
76
+ * Resolve the Pencils ecosystem root directory.
77
+ * Priority: PENCILS_HOME > NANOPENCIL_HOME > ~/.pencils
78
+ *
79
+ * Design doc §3: only PENCILS_HOME is the canonical name;
80
+ * NANOPENCIL_HOME is a compat alias for existing users.
81
+ */
82
+ export declare function getPencilsHome(): string;
83
+ /** New Pencils agents root (e.g., ~/.pencils/agents/) */
84
+ export declare function getPencilsAgentsDir(): string;
85
+ /**
86
+ * Resolve an agent directory context for a given agent id.
87
+ * If no id is provided, returns the legacy single-agent context.
88
+ *
89
+ * Resolution order for per-agent path:
90
+ * 1. NANOPENCIL_CODING_AGENT_DIR env (legacy single-agent override)
91
+ * 2. PENCILS_AGENTS_DIR/<id> (new multi-agent path)
92
+ * 3. ~/.pencils/agents/<id> (default)
93
+ */
94
+ export declare function resolveAgentDirContext(agentId?: string): {
95
+ id: string;
96
+ path: string;
97
+ };
98
+ /** Get path to global browser-workspace directory (e.g., ~/.pencils/workspaces/browser-workspace) */
76
99
  export declare function getBrowserWorkspaceDir(): string;
77
- /** Get path to global link-world-workspace directory (e.g., ~/.nanopencil/link-world-workspace) */
100
+ /** Get path to global link-world-workspace directory (e.g., ~/.pencils/workspaces/link-world-workspace) */
78
101
  export declare function getLinkWorldWorkspaceDir(): string;
package/dist/config.js CHANGED
@@ -168,20 +168,11 @@ export function getShareViewerUrl(gistId) {
168
168
  return `${baseUrl}#${gistId}`;
169
169
  }
170
170
  // =============================================================================
171
- // User Config Paths (~/.nanopencil/agent/*)
171
+ // User Config Paths (~/.pencils/agents/default/*)
172
172
  // =============================================================================
173
- /** Get the agent config directory (e.g., ~/.nanopencil/agent/) */
173
+ /** Get the agent config directory (e.g., ~/.pencils/agents/default/) */
174
174
  export function getAgentDir() {
175
- const envDir = process.env[ENV_AGENT_DIR];
176
- if (envDir) {
177
- // Expand tilde to home directory
178
- if (envDir === "~")
179
- return homedir();
180
- if (envDir.startsWith("~/"))
181
- return homedir() + envDir.slice(1);
182
- return envDir;
183
- }
184
- return join(homedir(), CONFIG_DIR_NAME, "agent");
175
+ return resolveAgentDirContext().path;
185
176
  }
186
177
  /** Get path to user's custom themes directory */
187
178
  export function getCustomThemesDir() {
@@ -240,11 +231,96 @@ export function getConfigRoot() {
240
231
  }
241
232
  return join(homedir(), CONFIG_DIR_NAME);
242
233
  }
243
- /** Get path to global browser-workspace directory (e.g., ~/.nanopencil/browser-workspace) */
234
+ // =============================================================================
235
+ // Multi-Agent: PENCILS_HOME & AgentDirContext support (N2)
236
+ // =============================================================================
237
+ /**
238
+ * Regex for a valid agent <id>.
239
+ * ASCII slug: lowercase alphanumeric start, then [a-z0-9._-], max 64 chars.
240
+ * Design doc §4.1.
241
+ */
242
+ const AGENT_ID_RE = /^[a-z0-9][a-z0-9._-]{0,63}$/;
243
+ /**
244
+ * Validate an agent id. Returns the id if valid, throws otherwise.
245
+ */
246
+ function validateAgentId(id) {
247
+ if (!AGENT_ID_RE.test(id)) {
248
+ throw new Error(`Invalid agent id "${id}". Must match ${AGENT_ID_RE.source} (lowercase ASCII slug, max 64 chars).`);
249
+ }
250
+ return id;
251
+ }
252
+ /**
253
+ * Resolve the Pencils ecosystem root directory.
254
+ * Priority: PENCILS_HOME > NANOPENCIL_HOME > ~/.pencils
255
+ *
256
+ * Design doc §3: only PENCILS_HOME is the canonical name;
257
+ * NANOPENCIL_HOME is a compat alias for existing users.
258
+ */
259
+ export function getPencilsHome() {
260
+ const envPencils = process.env.PENCILS_HOME;
261
+ if (envPencils) {
262
+ if (envPencils === "~")
263
+ return homedir();
264
+ if (envPencils.startsWith("~/"))
265
+ return homedir() + envPencils.slice(1);
266
+ return envPencils;
267
+ }
268
+ // Compat alias
269
+ const envNano = process.env.NANOPENCIL_HOME;
270
+ if (envNano) {
271
+ if (envNano === "~")
272
+ return homedir();
273
+ if (envNano.startsWith("~/"))
274
+ return homedir() + envNano.slice(1);
275
+ return envNano;
276
+ }
277
+ // Default: ~/.pencils (future target; for now fallback to legacy behavior)
278
+ return join(homedir(), ".pencils");
279
+ }
280
+ /** New Pencils agents root (e.g., ~/.pencils/agents/) */
281
+ export function getPencilsAgentsDir() {
282
+ const envAgents = process.env.PENCILS_AGENTS_DIR;
283
+ if (envAgents) {
284
+ if (envAgents === "~")
285
+ return homedir();
286
+ if (envAgents.startsWith("~/"))
287
+ return homedir() + envAgents.slice(1);
288
+ return envAgents;
289
+ }
290
+ return join(getPencilsHome(), "agents");
291
+ }
292
+ /**
293
+ * Resolve an agent directory context for a given agent id.
294
+ * If no id is provided, returns the legacy single-agent context.
295
+ *
296
+ * Resolution order for per-agent path:
297
+ * 1. NANOPENCIL_CODING_AGENT_DIR env (legacy single-agent override)
298
+ * 2. PENCILS_AGENTS_DIR/<id> (new multi-agent path)
299
+ * 3. ~/.pencils/agents/<id> (default)
300
+ */
301
+ export function resolveAgentDirContext(agentId) {
302
+ const id = agentId || "default";
303
+ validateAgentId(id);
304
+ // 1. Check legacy env override first (single-agent mode)
305
+ const envDir = process.env[ENV_AGENT_DIR];
306
+ if (envDir && id === "default") {
307
+ const resolvedEnv = envDir.startsWith("~/") ? homedir() + envDir.slice(1) : envDir;
308
+ return { id, path: resolvedEnv };
309
+ }
310
+ // 2. Check explicit per-agent env override
311
+ const envAgentsDir = process.env.PENCILS_AGENTS_DIR;
312
+ if (envAgentsDir) {
313
+ const base = envAgentsDir.startsWith("~/") ? homedir() + envAgentsDir.slice(1) : envAgentsDir;
314
+ return { id, path: join(base, id) };
315
+ }
316
+ // 3. Default multi-agent path under PENCILS_HOME
317
+ return { id, path: join(getPencilsHome(), "agents", id) };
318
+ }
319
+ /** Get path to global browser-workspace directory (e.g., ~/.pencils/workspaces/browser-workspace) */
244
320
  export function getBrowserWorkspaceDir() {
245
- return join(getConfigRoot(), "browser-workspace");
321
+ return join(getConfigRoot(), "workspaces", "browser-workspace");
246
322
  }
247
- /** Get path to global link-world-workspace directory (e.g., ~/.nanopencil/link-world-workspace) */
323
+ /** Get path to global link-world-workspace directory (e.g., ~/.pencils/workspaces/link-world-workspace) */
248
324
  export function getLinkWorldWorkspaceDir() {
249
- return join(getConfigRoot(), "link-world-workspace");
325
+ return join(getConfigRoot(), "workspaces", "link-world-workspace");
250
326
  }
@@ -0,0 +1,60 @@
1
+ /**
2
+ * [WHO]: AgentDirContext interface, defaultAgentDirContext(), agentDirContextOf(), validateAgentId()
3
+ * [FROM]: Depends on config.ts (getAgentDir)
4
+ * [TO]: Consumed by core/persona, core/session, core/soul-integration, core/mcp, extensions, future --agent flag
5
+ * [HERE]: core/agent-dir/agent-dir-context.ts - multi-agent directory abstraction
6
+ */
7
+ /**
8
+ * Regex for a valid agent <id>.
9
+ * ASCII slug: lowercase alphanumeric start, then [a-z0-9._-], max 64 chars.
10
+ * Design doc §4.1.
11
+ */
12
+ export declare const AGENT_ID_RE: RegExp;
13
+ /**
14
+ * Validate an agent id. Returns the id if valid, throws otherwise.
15
+ */
16
+ export declare function validateAgentId(id: string): string;
17
+ export interface AgentOriginMetadata {
18
+ type: "local" | "cloud-adopted" | "imported";
19
+ asgard?: {
20
+ templateId: string;
21
+ templateVersion: string;
22
+ originUrl: string;
23
+ externalId: string;
24
+ lastSyncedAt: string;
25
+ };
26
+ }
27
+ /**
28
+ * Represents the resolved filesystem context for one agent.
29
+ *
30
+ * - `id` : machine-readable slug (directory name, route key, Asgard externalId)
31
+ * - `path`: absolute path to the agent's data directory
32
+ * - `origin`: optional metadata if adopted from cloud (future, §4.2)
33
+ */
34
+ export interface AgentDirContext {
35
+ /** Slug id, [a-z0-9._-]{1,64}; matches the directory name. Immutable once created. */
36
+ readonly id: string;
37
+ /** Absolute path; trusted to exist or be creatable. */
38
+ readonly path: string;
39
+ /** Optional — if the agent was adopted from cloud, the origin metadata. */
40
+ readonly origin?: AgentOriginMetadata;
41
+ }
42
+ /**
43
+ * Build the default context for the legacy single-agent path.
44
+ * This is the fallback when no `--agent` flag is provided.
45
+ * Resolves to whatever `getAgentDir()` returns today (~/.pencils/agents/default).
46
+ */
47
+ export declare function defaultAgentDirContext(): AgentDirContext;
48
+ /**
49
+ * Build an AgentDirContext for a specific agent id + resolved path.
50
+ * Throws if the id fails validation.
51
+ */
52
+ export declare function agentDirContextOf(id: string, path: string, origin?: AgentOriginMetadata): AgentDirContext;
53
+ /**
54
+ * Load AgentDirContext from an agent directory (loads agent.json if it exists).
55
+ */
56
+ export declare function loadAgentDirContext(id: string): AgentDirContext;
57
+ /**
58
+ * Save AgentDirContext to agent.json.
59
+ */
60
+ export declare function saveAgentDirContext(ctx: AgentDirContext): void;
@@ -0,0 +1,84 @@
1
+ /**
2
+ * [WHO]: AgentDirContext interface, defaultAgentDirContext(), agentDirContextOf(), validateAgentId()
3
+ * [FROM]: Depends on config.ts (getAgentDir)
4
+ * [TO]: Consumed by core/persona, core/session, core/soul-integration, core/mcp, extensions, future --agent flag
5
+ * [HERE]: core/agent-dir/agent-dir-context.ts - multi-agent directory abstraction
6
+ */
7
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from "node:fs";
8
+ import { join } from "node:path";
9
+ import { getAgentDir, getPencilsAgentsDir } from "../../config.js";
10
+ // ---------------------------------------------------------------------------
11
+ // ID validation
12
+ // ---------------------------------------------------------------------------
13
+ /**
14
+ * Regex for a valid agent <id>.
15
+ * ASCII slug: lowercase alphanumeric start, then [a-z0-9._-], max 64 chars.
16
+ * Design doc §4.1.
17
+ */
18
+ export const AGENT_ID_RE = /^[a-z0-9][a-z0-9._-]{0,63}$/;
19
+ /**
20
+ * Validate an agent id. Returns the id if valid, throws otherwise.
21
+ */
22
+ export function validateAgentId(id) {
23
+ if (!AGENT_ID_RE.test(id)) {
24
+ throw new Error(`Invalid agent id "${id}". Must match ${AGENT_ID_RE.source} (lowercase ASCII slug, max 64 chars).`);
25
+ }
26
+ return id;
27
+ }
28
+ /**
29
+ * Build the default context for the legacy single-agent path.
30
+ * This is the fallback when no `--agent` flag is provided.
31
+ * Resolves to whatever `getAgentDir()` returns today (~/.pencils/agents/default).
32
+ */
33
+ export function defaultAgentDirContext() {
34
+ return { id: "default", path: getAgentDir() };
35
+ }
36
+ /**
37
+ * Build an AgentDirContext for a specific agent id + resolved path.
38
+ * Throws if the id fails validation.
39
+ */
40
+ export function agentDirContextOf(id, path, origin) {
41
+ validateAgentId(id);
42
+ return { id, path, origin };
43
+ }
44
+ /**
45
+ * Load AgentDirContext from an agent directory (loads agent.json if it exists).
46
+ */
47
+ export function loadAgentDirContext(id) {
48
+ const path = join(getPencilsAgentsDir(), id);
49
+ const agentJsonPath = join(path, "agent.json");
50
+ if (existsSync(agentJsonPath)) {
51
+ try {
52
+ const content = readFileSync(agentJsonPath, "utf-8");
53
+ const metadata = JSON.parse(content);
54
+ return {
55
+ id: metadata.id || id,
56
+ path,
57
+ origin: metadata.origin,
58
+ };
59
+ }
60
+ catch {
61
+ // Fallback on parse error
62
+ }
63
+ }
64
+ // Default for agents under PENCILS_AGENTS_DIR
65
+ if (id !== "default" || existsSync(path)) {
66
+ return { id, path };
67
+ }
68
+ // Ultimate fallback to legacy default
69
+ return defaultAgentDirContext();
70
+ }
71
+ /**
72
+ * Save AgentDirContext to agent.json.
73
+ */
74
+ export function saveAgentDirContext(ctx) {
75
+ if (!existsSync(ctx.path)) {
76
+ mkdirSync(ctx.path, { recursive: true });
77
+ }
78
+ const agentJsonPath = join(ctx.path, "agent.json");
79
+ const metadata = {
80
+ id: ctx.id,
81
+ origin: ctx.origin,
82
+ };
83
+ writeFileSync(agentJsonPath, JSON.stringify(metadata, null, 2), "utf-8");
84
+ }
@@ -0,0 +1,48 @@
1
+ /**
2
+ * [WHO]: AgentMetadata interface, loadAgentMetadata(), saveAgentMetadata(), ensureAgentMetadata()
3
+ * [FROM]: Depends on agent-dir-context.ts, node:fs, node:path
4
+ * [TO]: Consumed by main.ts, future Gateway integration
5
+ * [HERE]: core/agent-dir/agent-metadata.ts - agent.json (machine-readable metadata)
6
+ *
7
+ * Design doc: docs/multi-agent-fs-design.md §4.2
8
+ */
9
+ import type { AgentDirContext } from "./agent-dir-context.js";
10
+ export interface AgentAsgardMetadata {
11
+ templateId: string;
12
+ templateVersion: string;
13
+ originUrl: string;
14
+ externalId: string;
15
+ lastSyncedAt: string;
16
+ }
17
+ export interface AgentMetadata {
18
+ version: string;
19
+ /** Slug id, [a-z0-9._-]{1,64}; matches the directory name. Immutable. */
20
+ id: string;
21
+ /** Human-readable name; can contain Chinese/emoji. */
22
+ displayName: string;
23
+ /** Human-readable description. */
24
+ description?: string;
25
+ createdAt: string;
26
+ updatedAt: string;
27
+ origin: {
28
+ type: "local" | "cloud-adopted" | "imported";
29
+ asgard?: AgentAsgardMetadata;
30
+ };
31
+ tags: string[];
32
+ engine: string;
33
+ extensions: Record<string, unknown>;
34
+ }
35
+ export declare const CURRENT_METADATA_VERSION = "1.0.0";
36
+ /**
37
+ * Load agent.json from the agent directory.
38
+ * Returns undefined if the file does not exist or is invalid.
39
+ */
40
+ export declare function loadAgentMetadata(agentDir: string): AgentMetadata | undefined;
41
+ /**
42
+ * Save agent.json to the agent directory.
43
+ */
44
+ export declare function saveAgentMetadata(agentDir: string, metadata: AgentMetadata): void;
45
+ /**
46
+ * Ensure agent.json exists. If not, creates a default one based on context.
47
+ */
48
+ export declare function ensureAgentMetadata(ctx: AgentDirContext): AgentMetadata;
@@ -0,0 +1,68 @@
1
+ /**
2
+ * [WHO]: AgentMetadata interface, loadAgentMetadata(), saveAgentMetadata(), ensureAgentMetadata()
3
+ * [FROM]: Depends on agent-dir-context.ts, node:fs, node:path
4
+ * [TO]: Consumed by main.ts, future Gateway integration
5
+ * [HERE]: core/agent-dir/agent-metadata.ts - agent.json (machine-readable metadata)
6
+ *
7
+ * Design doc: docs/multi-agent-fs-design.md §4.2
8
+ */
9
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from "node:fs";
10
+ import { join } from "node:path";
11
+ export const CURRENT_METADATA_VERSION = "1.0.0";
12
+ /**
13
+ * Load agent.json from the agent directory.
14
+ * Returns undefined if the file does not exist or is invalid.
15
+ */
16
+ export function loadAgentMetadata(agentDir) {
17
+ const path = join(agentDir, "agent.json");
18
+ if (!existsSync(path))
19
+ return undefined;
20
+ try {
21
+ const raw = readFileSync(path, "utf-8");
22
+ return JSON.parse(raw);
23
+ }
24
+ catch {
25
+ return undefined;
26
+ }
27
+ }
28
+ /**
29
+ * Save agent.json to the agent directory.
30
+ */
31
+ export function saveAgentMetadata(agentDir, metadata) {
32
+ const path = join(agentDir, "agent.json");
33
+ if (!existsSync(agentDir)) {
34
+ mkdirSync(agentDir, { recursive: true });
35
+ }
36
+ metadata.updatedAt = new Date().toISOString();
37
+ writeFileSync(path, JSON.stringify(metadata, null, 2), "utf-8");
38
+ }
39
+ /**
40
+ * Ensure agent.json exists. If not, creates a default one based on context.
41
+ */
42
+ export function ensureAgentMetadata(ctx) {
43
+ const existing = loadAgentMetadata(ctx.path);
44
+ if (existing) {
45
+ // Basic migration: ensure ID matches context (id is immutable)
46
+ if (existing.id !== ctx.id) {
47
+ existing.id = ctx.id;
48
+ saveAgentMetadata(ctx.path, existing);
49
+ }
50
+ return existing;
51
+ }
52
+ const now = new Date().toISOString();
53
+ const metadata = {
54
+ version: CURRENT_METADATA_VERSION,
55
+ id: ctx.id,
56
+ displayName: ctx.id === "default" ? "Default Agent" : ctx.id,
57
+ createdAt: now,
58
+ updatedAt: now,
59
+ origin: {
60
+ type: "local",
61
+ },
62
+ tags: [],
63
+ engine: "nano-pencil",
64
+ extensions: {},
65
+ };
66
+ saveAgentMetadata(ctx.path, metadata);
67
+ return metadata;
68
+ }
@@ -0,0 +1,39 @@
1
+ /**
2
+ * [WHO]: MigrationManager class, handleMigration()
3
+ * [FROM]: Depends on node:fs, node:path, node:os, chalk, config.ts
4
+ * [TO]: Consumed by main.ts (migrate command)
5
+ * [HERE]: core/agent-dir/migration-tool.ts - Safe copy-first migration from ~/.nanopencil to ~/.pencils
6
+ *
7
+ * Design doc: docs/multi-agent-fs-design.md §12.2
8
+ */
9
+ export interface MigrationOptions {
10
+ dryRun: boolean;
11
+ apply: boolean;
12
+ copy: boolean;
13
+ }
14
+ export interface MigrationTask {
15
+ id: string;
16
+ source: string;
17
+ target: string;
18
+ label: string;
19
+ description: string;
20
+ }
21
+ export declare class MigrationManager {
22
+ private readonly legacyRoot;
23
+ private readonly newRoot;
24
+ private readonly migrationLogPath;
25
+ constructor();
26
+ run(options: MigrationOptions): Promise<void>;
27
+ /**
28
+ * Check if any migration is currently needed (legacy data exists and hasn't been migrated).
29
+ */
30
+ isMigrationNeeded(): boolean;
31
+ /**
32
+ * Run migration silently (no preview, direct apply) and return the list of migrated labels.
33
+ */
34
+ runSilent(): string[];
35
+ private plan;
36
+ private execute;
37
+ private isAlreadyApplied;
38
+ private logMigration;
39
+ }