@pencil-agent/nano-pencil 1.11.36 → 1.11.37

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 (42) hide show
  1. package/dist/builtin-extensions.js +10 -0
  2. package/dist/core/i18n/slash-commands.d.ts +1 -0
  3. package/dist/core/i18n/slash-commands.js +1 -0
  4. package/dist/core/i18n/slash-commands.zh.d.ts +1 -0
  5. package/dist/core/i18n/slash-commands.zh.js +1 -0
  6. package/dist/core/runtime/agent-session.d.ts +2 -0
  7. package/dist/core/runtime/agent-session.js +6 -0
  8. package/dist/core/runtime/sdk.d.ts +2 -0
  9. package/dist/core/runtime/sdk.js +1 -0
  10. package/dist/core/skills.js +6 -2
  11. package/dist/core/slash-commands.js +1 -0
  12. package/dist/core/sub-agent/index.d.ts +7 -0
  13. package/dist/core/sub-agent/index.js +6 -0
  14. package/dist/core/sub-agent/sub-agent-backend.d.ts +14 -0
  15. package/dist/core/sub-agent/sub-agent-backend.js +119 -0
  16. package/dist/core/sub-agent/sub-agent-runtime.d.ts +38 -0
  17. package/dist/core/sub-agent/sub-agent-runtime.js +54 -0
  18. package/dist/core/sub-agent/sub-agent-types.d.ts +75 -0
  19. package/dist/core/sub-agent/sub-agent-types.js +8 -0
  20. package/dist/core/tools/bash.d.ts +11 -0
  21. package/dist/core/tools/bash.js +51 -0
  22. package/dist/core/tools/index.d.ts +1 -1
  23. package/dist/core/tools/index.js +1 -1
  24. package/dist/core/workspace/index.d.ts +6 -0
  25. package/dist/core/workspace/index.js +5 -0
  26. package/dist/core/workspace/worktree-manager.d.ts +51 -0
  27. package/dist/core/workspace/worktree-manager.js +124 -0
  28. package/dist/extensions/defaults/loop/scheduler-parser.js +6 -9
  29. package/dist/extensions/defaults/subagent/index.d.ts +8 -0
  30. package/dist/extensions/defaults/subagent/index.js +118 -0
  31. package/dist/extensions/defaults/subagent/subagent-parser.d.ts +25 -0
  32. package/dist/extensions/defaults/subagent/subagent-parser.js +66 -0
  33. package/dist/extensions/defaults/subagent/subagent-runner.d.ts +41 -0
  34. package/dist/extensions/defaults/subagent/subagent-runner.js +171 -0
  35. package/dist/extensions/defaults/subagent/subagent-types.d.ts +52 -0
  36. package/dist/extensions/defaults/subagent/subagent-types.js +7 -0
  37. package/dist/extensions/defaults/team/index.js +1 -1
  38. package/dist/migrations.js +2 -2
  39. package/dist/modes/interactive/interactive-mode.d.ts +4 -0
  40. package/dist/modes/interactive/interactive-mode.js +71 -1
  41. package/docs/AgentTeam/351/207/215/346/236/204/346/226/271/346/241/210.md +43 -7
  42. package/package.json +1 -1
@@ -20,6 +20,7 @@ const BUNDLED_PRESENCE_EXTENSION = join(__dirname, "extensions", "defaults", "pr
20
20
  const BUNDLED_INTERVIEW_EXTENSION = join(__dirname, "extensions", "defaults", "interview", "index.js");
21
21
  const BUNDLED_LOOP_EXTENSION = join(__dirname, "extensions", "defaults", "loop", "index.js");
22
22
  const BUNDLED_TEAM_EXTENSION = join(__dirname, "extensions", "defaults", "team", "index.js");
23
+ const BUNDLED_SUBAGENT_EXTENSION = join(__dirname, "extensions", "defaults", "subagent", "index.js");
23
24
  const BUNDLED_MCP_EXTENSION = join(__dirname, "extensions", "defaults", "mcp", "index.js");
24
25
  const BUNDLED_EXPORT_HTML_EXTENSION = join(__dirname, "extensions", "optional", "export-html", "index.js");
25
26
  /** Find package root from current module location (containing package.json with nano-pencil related name) */
@@ -157,6 +158,15 @@ export function getBuiltinExtensionPaths() {
157
158
  if (existsSync(teamTs))
158
159
  paths.push(teamTs);
159
160
  }
161
+ // Built-in SubAgent extension
162
+ if (existsSync(BUNDLED_SUBAGENT_EXTENSION)) {
163
+ paths.push(BUNDLED_SUBAGENT_EXTENSION);
164
+ }
165
+ else {
166
+ const subagentTs = join(__dirname, "extensions", "defaults", "subagent", "index.ts");
167
+ if (existsSync(subagentTs))
168
+ paths.push(subagentTs);
169
+ }
160
170
  // Built-in MCP extension
161
171
  if (existsSync(BUNDLED_MCP_EXTENSION)) {
162
172
  paths.push(BUNDLED_MCP_EXTENSION);
@@ -28,6 +28,7 @@ export declare const slashCommands: {
28
28
  logout: string;
29
29
  new: string;
30
30
  update: string;
31
+ reinstall: string;
31
32
  compact: string;
32
33
  resume: string;
33
34
  reload: string;
@@ -28,6 +28,7 @@ export const slashCommands = {
28
28
  logout: "Logout from OAuth provider",
29
29
  new: "Start a new session",
30
30
  update: "Check for NanoPencil updates",
31
+ reinstall: "Force reinstall NanoPencil (clean install)",
31
32
  compact: "Manually compact the session context",
32
33
  resume: "Resume a different session",
33
34
  reload: "Reload extensions, skills, prompts, and themes",
@@ -28,6 +28,7 @@ export declare const slashCommands: {
28
28
  logout: string;
29
29
  new: string;
30
30
  update: string;
31
+ reinstall: string;
31
32
  compact: string;
32
33
  resume: string;
33
34
  reload: string;
@@ -28,6 +28,7 @@ export const slashCommands = {
28
28
  logout: "从 OAuth 提供商登出",
29
29
  new: "开始新会话",
30
30
  update: "检查 NanoPencil 更新",
31
+ reinstall: "强制重新安装 NanoPencil(干净安装)",
31
32
  compact: "手动压缩会话上下文",
32
33
  resume: "恢复其他会话",
33
34
  reload: "重新加载扩展、技能、提示和主题",
@@ -90,6 +90,8 @@ export interface AgentSessionConfig {
90
90
  extensionRunnerRef?: {
91
91
  current?: ExtensionRunner;
92
92
  };
93
+ /** External abort signal for stopping the session (e.g., from SubAgent runtime) */
94
+ signal?: AbortSignal;
93
95
  }
94
96
  export interface ExtensionBindings {
95
97
  uiContext?: ExtensionUIContext;
@@ -154,6 +154,12 @@ export class AgentSession {
154
154
  // Always subscribe to agent events for internal handling
155
155
  // (session persistence, extensions, auto-compaction, retry logic)
156
156
  this._unsubscribeAgent = this.agent.subscribe(this._handleAgentEvent);
157
+ // Listen to external abort signal (e.g., from SubAgent runtime)
158
+ if (config.signal) {
159
+ config.signal.addEventListener("abort", () => {
160
+ this.abort();
161
+ });
162
+ }
157
163
  this._buildRuntime({
158
164
  activeToolNames: this._initialActiveToolNames,
159
165
  includeAllExtensionTools: true,
@@ -40,6 +40,8 @@ export interface CreateAgentSessionOptions {
40
40
  sessionManager?: SessionManager;
41
41
  /** Settings manager. Default: SettingsManager.create(cwd, agentDir) */
42
42
  settingsManager?: SettingsManager;
43
+ /** External abort signal for stopping the session (e.g., from SubAgent runtime) */
44
+ signal?: AbortSignal;
43
45
  }
44
46
  /** Result from createAgentSession */
45
47
  export interface CreateAgentSessionResult {
@@ -350,6 +350,7 @@ export async function createAgentSession(options = {}) {
350
350
  extensionRunnerRef,
351
351
  soulManager,
352
352
  soulManagerFactory,
353
+ signal: options.signal,
353
354
  });
354
355
  const extensionsResult = resourceLoader.getExtensions();
355
356
  return {
@@ -56,7 +56,9 @@ function addIgnoreRules(ig, dir, rootDir) {
56
56
  ig.add(patterns);
57
57
  }
58
58
  }
59
- catch { }
59
+ catch (e) {
60
+ console.warn("Warning: Failed to read ignore file:", e instanceof Error ? e.message : e);
61
+ }
60
62
  }
61
63
  }
62
64
  /**
@@ -166,7 +168,9 @@ function loadSkillsFromDirInternal(dir, source, includeRootFiles, ignoreMatcher,
166
168
  diagnostics.push(...result.diagnostics);
167
169
  }
168
170
  }
169
- catch { }
171
+ catch (e) {
172
+ console.warn("Warning: Failed to read skills directory:", e instanceof Error ? e.message : e);
173
+ }
170
174
  return { skills, diagnostics };
171
175
  }
172
176
  function loadSkillFromFile(filePath, source) {
@@ -22,6 +22,7 @@ export const BUILTIN_SLASH_COMMANDS = [
22
22
  { name: "logout", descriptionKey: "slash.logout" },
23
23
  { name: "new", descriptionKey: "slash.new" },
24
24
  { name: "update", descriptionKey: "slash.update" },
25
+ { name: "reinstall", descriptionKey: "slash.reinstall" },
25
26
  { name: "compact", descriptionKey: "slash.compact" },
26
27
  { name: "resume", descriptionKey: "slash.resume" },
27
28
  { name: "reload", descriptionKey: "slash.reload" },
@@ -0,0 +1,7 @@
1
+ /**
2
+ * SubAgent runtime exports.
3
+ */
4
+ export { SubAgentRuntime, subAgentRuntime } from "./sub-agent-runtime.js";
5
+ export { InProcessSubAgentBackend } from "./sub-agent-backend.js";
6
+ export type { SubAgentSpec, SubAgentResult, SubAgentHandle, SubAgentBackend, } from "./sub-agent-types.js";
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,6 @@
1
+ /**
2
+ * SubAgent runtime exports.
3
+ */
4
+ export { SubAgentRuntime, subAgentRuntime } from "./sub-agent-runtime.js";
5
+ export { InProcessSubAgentBackend } from "./sub-agent-backend.js";
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,14 @@
1
+ /**
2
+ * [UPSTREAM]: Depends on sub-agent-types.ts, core/runtime/sdk.ts
3
+ * [SURFACE]: InProcessSubAgentBackend
4
+ * [LOCUS]: core/sub-agent/sub-agent-backend.ts
5
+ */
6
+ import type { SubAgentBackend, SubAgentHandle, SubAgentSpec } from "./sub-agent-types.js";
7
+ /**
8
+ * In-process SubAgent backend.
9
+ * Wraps createAgentSession() to run SubAgent in the same process.
10
+ */
11
+ export declare class InProcessSubAgentBackend implements SubAgentBackend {
12
+ spawn(spec: SubAgentSpec): Promise<SubAgentHandle>;
13
+ }
14
+ //# sourceMappingURL=sub-agent-backend.d.ts.map
@@ -0,0 +1,119 @@
1
+ /**
2
+ * [UPSTREAM]: Depends on sub-agent-types.ts, core/runtime/sdk.ts
3
+ * [SURFACE]: InProcessSubAgentBackend
4
+ * [LOCUS]: core/sub-agent/sub-agent-backend.ts
5
+ */
6
+ import { createAgentSession } from "../runtime/sdk.js";
7
+ /**
8
+ * In-process SubAgent backend.
9
+ * Wraps createAgentSession() to run SubAgent in the same process.
10
+ */
11
+ export class InProcessSubAgentBackend {
12
+ async spawn(spec) {
13
+ const id = crypto.randomUUID();
14
+ // Create an internal AbortController that can be triggered by external signal or timeout
15
+ const internalAbortController = new AbortController();
16
+ // Forward external signal abort to internal controller
17
+ const signalHandler = () => {
18
+ if (!internalAbortController.signal.aborted) {
19
+ internalAbortController.abort();
20
+ }
21
+ };
22
+ spec.signal.addEventListener("abort", signalHandler);
23
+ // Create agent session with our internal signal
24
+ const options = {
25
+ cwd: spec.cwd,
26
+ tools: spec.tools,
27
+ signal: internalAbortController.signal,
28
+ model: spec.model,
29
+ };
30
+ const { session } = await createAgentSession(options);
31
+ const timeoutMs = spec.timeoutMs;
32
+ let status = "running";
33
+ let result;
34
+ // Set up timeout if specified
35
+ let timeoutId;
36
+ if (timeoutMs !== undefined) {
37
+ timeoutId = setTimeout(() => {
38
+ if (status === "running") {
39
+ internalAbortController.abort();
40
+ }
41
+ }, timeoutMs);
42
+ }
43
+ // Extract text from assistant message content
44
+ const extractTextFromContent = (content) => {
45
+ if (typeof content === "string")
46
+ return content;
47
+ if (Array.isArray(content)) {
48
+ return content
49
+ .filter((part) => typeof part === "object" && part !== null && "type" in part && part.type === "text" && typeof part.text === "string")
50
+ .map((part) => part.text)
51
+ .join("\n");
52
+ }
53
+ return "";
54
+ };
55
+ // Start the prompt
56
+ const promptPromise = (async () => {
57
+ try {
58
+ await session.prompt(spec.prompt, {
59
+ images: spec.images,
60
+ });
61
+ status = "done";
62
+ // Extract the last assistant message as the result
63
+ const messages = session.messages ?? [];
64
+ const assistantMessages = messages.filter((m) => m.role === "assistant");
65
+ const lastAssistant = assistantMessages[assistantMessages.length - 1];
66
+ const responseText = lastAssistant ? extractTextFromContent(lastAssistant.content) : "";
67
+ result = {
68
+ success: true,
69
+ response: responseText,
70
+ };
71
+ }
72
+ catch (error) {
73
+ if (error instanceof Error && error.name === "AbortError") {
74
+ status = "aborted";
75
+ result = {
76
+ success: false,
77
+ error: "Aborted",
78
+ };
79
+ }
80
+ else {
81
+ status = "error";
82
+ result = {
83
+ success: false,
84
+ error: error instanceof Error ? error.message : String(error),
85
+ };
86
+ }
87
+ }
88
+ finally {
89
+ if (timeoutId !== undefined) {
90
+ clearTimeout(timeoutId);
91
+ }
92
+ // Clean up signal handler
93
+ spec.signal.removeEventListener("abort", signalHandler);
94
+ }
95
+ })();
96
+ return {
97
+ id,
98
+ get status() {
99
+ return status;
100
+ },
101
+ async result() {
102
+ await promptPromise;
103
+ return (result ?? {
104
+ success: false,
105
+ error: "No result available",
106
+ });
107
+ },
108
+ async abort() {
109
+ internalAbortController.abort();
110
+ await session.abort();
111
+ },
112
+ async terminate() {
113
+ internalAbortController.abort();
114
+ await session.abort();
115
+ },
116
+ };
117
+ }
118
+ }
119
+ //# sourceMappingURL=sub-agent-backend.js.map
@@ -0,0 +1,38 @@
1
+ /**
2
+ * [UPSTREAM]: Depends on sub-agent-types.ts, sub-agent-backend.ts
3
+ * [SURFACE]: SubAgentRuntime - spawn, abort, lifecycle management
4
+ * [LOCUS]: core/sub-agent/sub-agent-runtime.ts
5
+ */
6
+ import type { SubAgentBackend, SubAgentHandle, SubAgentSpec } from "./sub-agent-types.js";
7
+ /**
8
+ * Runtime for managing SubAgents.
9
+ * Provides spawn/abort/lifecycle operations.
10
+ */
11
+ export declare class SubAgentRuntime {
12
+ private backend;
13
+ private activeAgents;
14
+ constructor(backend?: SubAgentBackend);
15
+ /**
16
+ * Spawn a new SubAgent with the given specification.
17
+ * @param spec The SubAgent specification
18
+ * @returns A handle to the spawned SubAgent
19
+ */
20
+ spawn(spec: SubAgentSpec): Promise<SubAgentHandle>;
21
+ /**
22
+ * Get all active SubAgent handles.
23
+ */
24
+ getActiveAgents(): SubAgentHandle[];
25
+ /**
26
+ * Abort all active SubAgents.
27
+ */
28
+ abortAll(): Promise<void>;
29
+ /**
30
+ * Terminate all active SubAgents.
31
+ */
32
+ terminateAll(): Promise<void>;
33
+ }
34
+ /**
35
+ * Default global SubAgent runtime instance.
36
+ */
37
+ export declare const subAgentRuntime: SubAgentRuntime;
38
+ //# sourceMappingURL=sub-agent-runtime.d.ts.map
@@ -0,0 +1,54 @@
1
+ /**
2
+ * [UPSTREAM]: Depends on sub-agent-types.ts, sub-agent-backend.ts
3
+ * [SURFACE]: SubAgentRuntime - spawn, abort, lifecycle management
4
+ * [LOCUS]: core/sub-agent/sub-agent-runtime.ts
5
+ */
6
+ import { InProcessSubAgentBackend } from "./sub-agent-backend.js";
7
+ /**
8
+ * Runtime for managing SubAgents.
9
+ * Provides spawn/abort/lifecycle operations.
10
+ */
11
+ export class SubAgentRuntime {
12
+ backend;
13
+ activeAgents = new Map();
14
+ constructor(backend = new InProcessSubAgentBackend()) {
15
+ this.backend = backend;
16
+ }
17
+ /**
18
+ * Spawn a new SubAgent with the given specification.
19
+ * @param spec The SubAgent specification
20
+ * @returns A handle to the spawned SubAgent
21
+ */
22
+ async spawn(spec) {
23
+ const handle = await this.backend.spawn(spec);
24
+ this.activeAgents.set(handle.id, handle);
25
+ // Clean up when the agent finishes
26
+ handle.result().finally(() => {
27
+ this.activeAgents.delete(handle.id);
28
+ });
29
+ return handle;
30
+ }
31
+ /**
32
+ * Get all active SubAgent handles.
33
+ */
34
+ getActiveAgents() {
35
+ return Array.from(this.activeAgents.values());
36
+ }
37
+ /**
38
+ * Abort all active SubAgents.
39
+ */
40
+ async abortAll() {
41
+ await Promise.all(Array.from(this.activeAgents.values()).map((agent) => agent.abort()));
42
+ }
43
+ /**
44
+ * Terminate all active SubAgents.
45
+ */
46
+ async terminateAll() {
47
+ await Promise.all(Array.from(this.activeAgents.values()).map((agent) => agent.terminate()));
48
+ }
49
+ }
50
+ /**
51
+ * Default global SubAgent runtime instance.
52
+ */
53
+ export const subAgentRuntime = new SubAgentRuntime();
54
+ //# sourceMappingURL=sub-agent-runtime.js.map
@@ -0,0 +1,75 @@
1
+ /**
2
+ * [UPSTREAM]: Depends on core/runtime/sdk.ts, core/tools/*
3
+ * [SURFACE]: SubAgentSpec, SubAgentHandle, SubAgentBackend
4
+ * [LOCUS]: core/sub-agent/sub-agent-types.ts
5
+ * [COVENANT]: Change these types → update P1 architecture diagram
6
+ */
7
+ import type { ImageContent, Model } from "@pencil-agent/ai";
8
+ import type { Tool } from "../tools/index.js";
9
+ /**
10
+ * Specification for spawning a SubAgent.
11
+ */
12
+ export interface SubAgentSpec {
13
+ /** Task prompt for the SubAgent */
14
+ prompt: string;
15
+ /** Tools available to the SubAgent (determined by caller, not guessed by core) */
16
+ tools: Tool[];
17
+ /** Working directory for the SubAgent (can be a worktree) */
18
+ cwd: string;
19
+ /** Abort signal for stopping the SubAgent (required) */
20
+ signal: AbortSignal;
21
+ /** Optional timeout in milliseconds */
22
+ timeoutMs?: number;
23
+ /** Images to include in the prompt */
24
+ images?: ImageContent[];
25
+ /** Model to use (reuses main session's model and auth) */
26
+ model?: Model<any>;
27
+ }
28
+ /**
29
+ * Result from a completed SubAgent run.
30
+ */
31
+ export interface SubAgentResult {
32
+ /** Whether the run was successful */
33
+ success: boolean;
34
+ /** The response text from the SubAgent */
35
+ response?: string;
36
+ /** Error message if unsuccessful */
37
+ error?: string;
38
+ }
39
+ /**
40
+ * Handle to a running SubAgent.
41
+ */
42
+ export interface SubAgentHandle {
43
+ /** Unique identifier for this SubAgent */
44
+ readonly id: string;
45
+ /** Current status of the SubAgent */
46
+ readonly status: "running" | "done" | "aborted" | "error";
47
+ /**
48
+ * Wait for the SubAgent to complete and get its result.
49
+ * Resolves when the SubAgent finishes (successfully or with error).
50
+ */
51
+ result(): Promise<SubAgentResult>;
52
+ /**
53
+ * Abort the current turn (stops after current LLM response).
54
+ * The SubAgent may still be in "running" state if it can accept more turns.
55
+ */
56
+ abort(): Promise<void>;
57
+ /**
58
+ * Terminate the SubAgent completely.
59
+ * This stops all work and disposes of resources.
60
+ */
61
+ terminate(): Promise<void>;
62
+ }
63
+ /**
64
+ * Backend for spawning SubAgents.
65
+ * Core provides in-process backend; subprocess backend can be added in phase B.
66
+ */
67
+ export interface SubAgentBackend {
68
+ /**
69
+ * Spawn a new SubAgent with the given specification.
70
+ * @param spec The SubAgent specification
71
+ * @returns A handle to the spawned SubAgent
72
+ */
73
+ spawn(spec: SubAgentSpec): Promise<SubAgentHandle>;
74
+ }
75
+ //# sourceMappingURL=sub-agent-types.d.ts.map
@@ -0,0 +1,8 @@
1
+ /**
2
+ * [UPSTREAM]: Depends on core/runtime/sdk.ts, core/tools/*
3
+ * [SURFACE]: SubAgentSpec, SubAgentHandle, SubAgentBackend
4
+ * [LOCUS]: core/sub-agent/sub-agent-types.ts
5
+ * [COVENANT]: Change these types → update P1 architecture diagram
6
+ */
7
+ export {};
8
+ //# sourceMappingURL=sub-agent-types.js.map
@@ -51,5 +51,16 @@ export declare const bashTool: AgentTool<import("@sinclair/typebox").TObject<{
51
51
  command: import("@sinclair/typebox").TString;
52
52
  timeout: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TNumber>;
53
53
  }>, any>;
54
+ export interface BashSandboxOptions {
55
+ /** Additional patterns to block (in addition to default blocked patterns) */
56
+ additionalBlockedPatterns?: RegExp[];
57
+ /** Custom error message for blocked commands */
58
+ blockedMessage?: string;
59
+ }
60
+ /**
61
+ * Create a sandboxed bash hook that blocks dangerous write operations.
62
+ * This is a defense-in-depth measure for read-only SubAgents.
63
+ */
64
+ export declare function createSandboxHook(options?: BashSandboxOptions): BashSpawnHook;
54
65
  export {};
55
66
  //# sourceMappingURL=bash.d.ts.map
@@ -245,4 +245,55 @@ export function createBashTool(cwd, options) {
245
245
  }
246
246
  /** Default bash tool using process.cwd() - for backwards compatibility */
247
247
  export const bashTool = createBashTool(process.cwd());
248
+ /**
249
+ * Patterns that indicate write operations in bash commands.
250
+ * These are blocked in sandbox mode.
251
+ */
252
+ const SANDBOX_BLOCKED_PATTERNS = [
253
+ // Output redirection
254
+ /^\s*>|\s>|\s>>|\s&\d>/,
255
+ // File deletion
256
+ /\brm\s+/,
257
+ // Move/copy
258
+ /\bmv\s+/,
259
+ /\bcp\s+.*\s+\//,
260
+ // Git write operations
261
+ /\bgit\s+(commit|push|pull|add|checkout\s+[^-|]|reset|rebase\s+[^--]|merge)/,
262
+ // Create directories
263
+ /\bmkdir\s+[^-]/,
264
+ // chmod/chown
265
+ /\bchmod\s+/,
266
+ /\bchown\s+/,
267
+ //ln -s
268
+ /\bln\s+.*-s/,
269
+ // tee
270
+ /\btee\s+/,
271
+ // wget/curl with output
272
+ /\bwget\s+.*-O/,
273
+ /\bcurl\s+.*-o/,
274
+ ];
275
+ /**
276
+ * Create a sandboxed bash hook that blocks dangerous write operations.
277
+ * This is a defense-in-depth measure for read-only SubAgents.
278
+ */
279
+ export function createSandboxHook(options) {
280
+ const blockedPatterns = [
281
+ ...SANDBOX_BLOCKED_PATTERNS,
282
+ ...(options?.additionalBlockedPatterns ?? []),
283
+ ];
284
+ const blockedMessage = options?.blockedMessage ?? "Write operations are not allowed in sandbox mode";
285
+ return (context) => {
286
+ const command = context.command.trim();
287
+ // Check if command contains any blocked patterns
288
+ for (const pattern of blockedPatterns) {
289
+ if (pattern.test(command)) {
290
+ return {
291
+ ...context,
292
+ command: `echo "${blockedMessage}" >&2; exit 1`,
293
+ };
294
+ }
295
+ }
296
+ return context;
297
+ };
298
+ }
248
299
  //# sourceMappingURL=bash.js.map
@@ -4,7 +4,7 @@
4
4
  * [TO]: Consumed by index.ts, main.ts, cli/args.ts, modes/interactive/components/tool-execution.ts, extensions/defaults/team/index.ts, and test files
5
5
  * [HERE]: Tool system public API; consumed by SDK and orchestrator
6
6
  */
7
- export { type BashOperations, type BashSpawnContext, type BashSpawnHook, type BashToolDetails, type BashToolInput, type BashToolOptions, bashTool, createBashTool, } from "./bash.js";
7
+ export { type BashOperations, type BashSandboxOptions, type BashSpawnContext, type BashSpawnHook, type BashToolDetails, type BashToolInput, type BashToolOptions, bashTool, createBashTool, createSandboxHook, } from "./bash.js";
8
8
  export { createEditTool, type EditOperations, type EditToolDetails, type EditToolInput, type EditToolOptions, editTool, } from "./edit.js";
9
9
  export { createFindTool, type FindOperations, type FindToolDetails, type FindToolInput, type FindToolOptions, findTool, } from "./find.js";
10
10
  export { createGrepTool, type GrepOperations, type GrepToolDetails, type GrepToolInput, type GrepToolOptions, grepTool, } from "./grep.js";
@@ -4,7 +4,7 @@
4
4
  * [TO]: Consumed by index.ts, main.ts, cli/args.ts, modes/interactive/components/tool-execution.ts, extensions/defaults/team/index.ts, and test files
5
5
  * [HERE]: Tool system public API; consumed by SDK and orchestrator
6
6
  */
7
- export { bashTool, createBashTool, } from "./bash.js";
7
+ export { bashTool, createBashTool, createSandboxHook, } from "./bash.js";
8
8
  export { createEditTool, editTool, } from "./edit.js";
9
9
  export { createFindTool, findTool, } from "./find.js";
10
10
  export { createGrepTool, grepTool, } from "./grep.js";
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Workspace management exports.
3
+ */
4
+ export { WorktreeManager, worktreeManager } from "./worktree-manager.js";
5
+ export type { WorkspacePath } from "./worktree-manager.js";
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Workspace management exports.
3
+ */
4
+ export { WorktreeManager, worktreeManager } from "./worktree-manager.js";
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,51 @@
1
+ /**
2
+ * [UPSTREAM]: Depends on node:fs/promises, node:path
3
+ * [SURFACE]: WorktreeManager - temporary workspace management
4
+ * [LOCUS]: core/workspace/worktree-manager.ts
5
+ */
6
+ export interface WorkspacePath {
7
+ /** Absolute path to the workspace */
8
+ readonly path: string;
9
+ /** Type of workspace */
10
+ readonly type: "temp" | "worktree";
11
+ }
12
+ /**
13
+ * Manager for creating and disposing workspaces for SubAgents.
14
+ * Provides temporary directories and git worktrees.
15
+ */
16
+ export declare class WorktreeManager {
17
+ private tempDirs;
18
+ private worktrees;
19
+ /**
20
+ * Create a temporary workspace.
21
+ * Copies seed files to a new temp directory.
22
+ * @param seedFiles Files to copy to the temp workspace
23
+ * @param prefix Prefix for the temp directory name
24
+ */
25
+ createTempWorkspace(seedFiles?: string[], prefix?: string): Promise<WorkspacePath>;
26
+ /**
27
+ * Create a git worktree for the given branch.
28
+ * @param branch Branch name for the worktree
29
+ * @param cwd Working directory for git operations
30
+ */
31
+ createGitWorktree(branch?: string, cwd?: string): Promise<WorkspacePath>;
32
+ /**
33
+ * Dispose of a workspace.
34
+ * Removes temp directories and worktrees.
35
+ * @param workspace The workspace to dispose
36
+ */
37
+ dispose(workspace: WorkspacePath): Promise<void>;
38
+ /**
39
+ * Get all active temp directories.
40
+ */
41
+ getActiveTempDirs(): string[];
42
+ /**
43
+ * Clean up all temp directories.
44
+ */
45
+ disposeAll(): Promise<void>;
46
+ }
47
+ /**
48
+ * Default global worktree manager instance.
49
+ */
50
+ export declare const worktreeManager: WorktreeManager;
51
+ //# sourceMappingURL=worktree-manager.d.ts.map