pi-subagents 0.61.0 → 0.63.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 (63) hide show
  1. package/CHANGELOG.md +52 -1
  2. package/docs/agents.md +11 -6
  3. package/docs/configuration.md +31 -5
  4. package/docs/extension-api.md +2 -2
  5. package/docs/models.md +5 -5
  6. package/docs/observability.md +5 -2
  7. package/docs/tool-reference.md +3 -3
  8. package/install.mjs +0 -1
  9. package/package.json +1 -1
  10. package/skills/pi-subagents/references/execution-controls.md +3 -4
  11. package/skills/pi-subagents/references/management-authoring-rpc.md +1 -1
  12. package/skills/pi-subagents/references/prompting-and-roles.md +3 -3
  13. package/src/agents/agent-management.ts +41 -4
  14. package/src/agents/agent-serializer.ts +3 -0
  15. package/src/agents/agents.ts +120 -124
  16. package/src/agents/runtime-agent-registry.ts +5 -1
  17. package/src/api/preflight.ts +4 -0
  18. package/src/api/shared-types.ts +3 -0
  19. package/src/extension/config.ts +20 -0
  20. package/src/extension/public-execution.ts +1 -0
  21. package/src/extension/schemas.ts +6 -2
  22. package/src/extension/tool-description.ts +1 -1
  23. package/src/inspectors/herdr/inspector-runner.ts +19 -13
  24. package/src/runs/background/active-async-capacity.ts +26 -8
  25. package/src/runs/background/async-execution.ts +100 -23
  26. package/src/runs/background/async-resume.ts +6 -2
  27. package/src/runs/background/async-status.ts +18 -2
  28. package/src/runs/background/notify.ts +13 -1
  29. package/src/runs/background/process-terminal.ts +16 -0
  30. package/src/runs/background/run-status.ts +22 -2
  31. package/src/runs/background/scheduled-runs.ts +63 -6
  32. package/src/runs/background/steering.ts +4 -1
  33. package/src/runs/background/subagent-runner.ts +44 -9
  34. package/src/runs/background/wait-completions.ts +13 -0
  35. package/src/runs/background/wait-tool.ts +1 -7
  36. package/src/runs/foreground/execution.ts +14 -7
  37. package/src/runs/foreground/subagent-executor.ts +38 -5
  38. package/src/runs/shared/acceptance.ts +85 -18
  39. package/src/runs/shared/capability-ceiling.ts +1 -0
  40. package/src/runs/shared/dynamic-fanout.ts +1 -1
  41. package/src/runs/shared/lane-metadata.ts +24 -3
  42. package/src/runs/shared/parallel-handoff.ts +4 -0
  43. package/src/runs/shared/parallel-utils.ts +2 -6
  44. package/src/runs/shared/permissions.ts +1 -1
  45. package/src/runs/shared/pi-args.ts +32 -14
  46. package/src/runs/shared/pi-spawn.ts +69 -35
  47. package/src/runs/shared/structured-output.ts +33 -6
  48. package/src/runs/shared/subagent-prompt-runtime.ts +20 -3
  49. package/src/runs/shared/task-intent.ts +21 -7
  50. package/src/runs/shared/tool-timeout.ts +1 -1
  51. package/src/runs/shared/worktree.ts +467 -63
  52. package/src/shared/atomic-json.ts +3 -1
  53. package/src/shared/fork-context.ts +0 -12
  54. package/src/shared/fork-session-cwd.ts +27 -0
  55. package/src/shared/launch-contract.ts +3 -0
  56. package/src/shared/types.ts +32 -1
  57. package/src/shared/utils.ts +18 -7
  58. package/src/slash/slash-commands.ts +1 -1
  59. package/src/slash/subagents-admin.ts +26 -12
  60. package/src/tui/fleet-status.ts +61 -2
  61. package/src/tui/fleet.ts +12 -7
  62. package/src/tui/render.ts +222 -14
  63. package/src/workflows/workflow-checklist.ts +441 -0
@@ -11,6 +11,7 @@ import { fileURLToPath } from "node:url";
11
11
  import type { AcceptanceInput, AcceptanceRole, AgentRunnerConfig, OutputMode, ToolBudgetConfig } from "../shared/types.ts";
12
12
  import { CODE_OWNED_EXTERNAL_CLI_ADAPTER_LABEL, isCodeOwnedExternalCliAdapterId, parseExternalCliCapabilityNarrowing, validateCodeOwnedProfileRunner } from "../runs/shared/external-cli-contract.ts";
13
13
  import { getAgentDir, getProjectConfigDir } from "../shared/utils.ts";
14
+ import { expandHomePath } from "../shared/settings.ts";
14
15
  import { KNOWN_FIELDS } from "./agent-serializer.ts";
15
16
  import { parseChain, parseJsonChain } from "./chain-serializer.ts";
16
17
  import { mergeAgentsForScope } from "./agent-selection.ts";
@@ -70,6 +71,7 @@ export interface BuiltinAgentOverrideBase {
70
71
  skills?: string[];
71
72
  skillPath?: string[];
72
73
  tools?: string[];
74
+ excludeTools?: string[];
73
75
  allowNestedSubagents?: boolean;
74
76
  mcpDirectTools?: string[];
75
77
  extensions?: string[];
@@ -99,6 +101,7 @@ interface BuiltinAgentOverrideConfig {
99
101
  systemPrompt?: string;
100
102
  skills?: string[] | false;
101
103
  tools?: string[] | false | "inherit";
104
+ excludeTools?: string[] | false;
102
105
  allowNestedSubagents?: boolean;
103
106
  extensions?: string[] | false;
104
107
  subagentOnlyExtensions?: string[] | false;
@@ -111,6 +114,8 @@ interface BuiltinAgentOverrideInfo {
111
114
  scope: "user" | "project";
112
115
  path: string;
113
116
  base: BuiltinAgentOverrideBase;
117
+ fields?: string[];
118
+ fieldScopes?: Record<string, Array<"user" | "project">>;
114
119
  }
115
120
 
116
121
  export interface AgentModelSourceInfo {
@@ -132,6 +137,7 @@ export interface AgentConfig {
132
137
  description: string;
133
138
  aliases?: string[];
134
139
  tools?: string[];
140
+ excludeTools?: string[];
135
141
  allowNestedSubagents?: boolean;
136
142
  mcpDirectTools?: string[];
137
143
  model?: string;
@@ -181,6 +187,7 @@ type ProjectRootResolution = "nearest" | "git-root";
181
187
  interface SubagentSettings {
182
188
  overrides: Record<string, BuiltinAgentOverrideConfig>;
183
189
  providerOverrides: Record<string, Record<string, BuiltinAgentOverrideConfig>>;
190
+ agentScanDirs?: string[];
184
191
  defaultModel?: string;
185
192
  defaultProvider?: string;
186
193
  defaultThinking?: string;
@@ -760,6 +767,7 @@ function cloneOverrideBase(agent: AgentConfig): BuiltinAgentOverrideBase {
760
767
  ...(agent.skills ? { skills: [...agent.skills] } : {}),
761
768
  ...(agent.skillPath ? { skillPath: [...agent.skillPath] } : {}),
762
769
  ...(agent.tools ? { tools: [...agent.tools] } : {}),
770
+ ...(agent.excludeTools ? { excludeTools: [...agent.excludeTools] } : {}),
763
771
  ...(agent.allowNestedSubagents !== undefined ? { allowNestedSubagents: agent.allowNestedSubagents } : {}),
764
772
  ...(agent.mcpDirectTools ? { mcpDirectTools: [...agent.mcpDirectTools] } : {}),
765
773
  ...(!agent.extensionsFromDefault && agent.extensions ? { extensions: [...agent.extensions] } : {}),
@@ -793,6 +801,7 @@ function cloneOverrideValue(override: BuiltinAgentOverrideConfig): BuiltinAgentO
793
801
  ...(override.systemPrompt !== undefined ? { systemPrompt: override.systemPrompt } : {}),
794
802
  ...(override.skills !== undefined ? { skills: override.skills === false ? false : [...override.skills] } : {}),
795
803
  ...(override.tools !== undefined ? { tools: Array.isArray(override.tools) ? [...override.tools] : override.tools } : {}),
804
+ ...(override.excludeTools !== undefined ? { excludeTools: override.excludeTools === false ? false : [...override.excludeTools] } : {}),
796
805
  ...(override.allowNestedSubagents !== undefined ? { allowNestedSubagents: override.allowNestedSubagents } : {}),
797
806
  ...(override.extensions !== undefined ? { extensions: override.extensions === false ? false : [...override.extensions] } : {}),
798
807
  ...(override.subagentOnlyExtensions !== undefined ? { subagentOnlyExtensions: override.subagentOnlyExtensions === false ? false : [...override.subagentOnlyExtensions] } : {}),
@@ -1085,6 +1094,8 @@ function parseBuiltinOverrideEntry(
1085
1094
 
1086
1095
  const tools = parseToolsOverride(input.tools, { filePath, name });
1087
1096
  if (tools !== undefined) override.tools = tools;
1097
+ const excludeTools = parseOverrideStringArrayOrFalse(input.excludeTools, { filePath, name, field: "excludeTools" });
1098
+ if (excludeTools !== undefined) override.excludeTools = excludeTools;
1088
1099
  if ("allowNestedSubagents" in input) {
1089
1100
  if (typeof input.allowNestedSubagents === "boolean") override.allowNestedSubagents = input.allowNestedSubagents;
1090
1101
  else throw new Error(`Builtin override '${name}' in '${filePath}' has invalid 'allowNestedSubagents'; expected a boolean.`);
@@ -1165,6 +1176,14 @@ function readSubagentSettings(filePath: string | null): SubagentSettings {
1165
1176
  }
1166
1177
  defaultExtensions = subagentsObject.defaultExtensions.map((item) => item.trim());
1167
1178
  }
1179
+ let agentScanDirs: string[] | undefined;
1180
+ if ("agentScanDirs" in subagentsObject) {
1181
+ if (!Array.isArray(subagentsObject.agentScanDirs)
1182
+ || subagentsObject.agentScanDirs.some((item) => typeof item !== "string" || !item.trim())) {
1183
+ throw new Error(`Subagent settings in '${filePath}' have invalid 'agentScanDirs'; expected an array of non-empty strings.`);
1184
+ }
1185
+ agentScanDirs = subagentsObject.agentScanDirs.map((item) => item.trim());
1186
+ }
1168
1187
  const modelScope = parseModelScopeConfig(subagentsObject.modelScope, { filePath });
1169
1188
 
1170
1189
  const parsed: Record<string, BuiltinAgentOverrideConfig> = {};
@@ -1179,6 +1198,7 @@ function readSubagentSettings(filePath: string | null): SubagentSettings {
1179
1198
  ...(defaultThinking !== undefined ? { defaultThinking } : {}),
1180
1199
  ...(maxThinking !== undefined ? { maxThinking } : {}),
1181
1200
  ...(defaultExtensions !== undefined ? { defaultExtensions } : {}),
1201
+ ...(agentScanDirs !== undefined ? { agentScanDirs } : {}),
1182
1202
  ...(disableBuiltins !== undefined ? { disableBuiltins } : {}),
1183
1203
  ...(disableThinking !== undefined ? { disableThinking } : {}),
1184
1204
  ...(modelScope !== undefined ? { modelScope } : {}),
@@ -1341,9 +1361,19 @@ function applyBuiltinOverride(
1341
1361
  override: BuiltinAgentOverrideConfig,
1342
1362
  meta: { scope: "user" | "project"; path: string },
1343
1363
  ): AgentConfig {
1364
+ const overrideInfo: BuiltinAgentOverrideInfo = {
1365
+ ...meta,
1366
+ base: agent.override?.base ?? cloneOverrideBase(agent),
1367
+ fields: [...new Set([...(agent.override?.fields ?? []), ...Object.keys(override)])].sort(),
1368
+ fieldScopes: Object.fromEntries(Object.entries({ ...(agent.override?.fieldScopes ?? {}) }).map(([field, scopes]) => [field, [...scopes]])),
1369
+ };
1370
+ for (const field of Object.keys(override)) {
1371
+ const scopes = overrideInfo.fieldScopes![field] ?? [];
1372
+ overrideInfo.fieldScopes![field] = [...new Set([...scopes, meta.scope])].sort();
1373
+ }
1344
1374
  const next: AgentConfig = {
1345
1375
  ...agent,
1346
- override: { ...meta, base: cloneOverrideBase(agent) },
1376
+ override: overrideInfo,
1347
1377
  };
1348
1378
 
1349
1379
  if (override.description !== undefined) next.description = override.description;
@@ -1371,6 +1401,7 @@ function applyBuiltinOverride(
1371
1401
  if (override.systemPrompt !== undefined) next.systemPrompt = override.systemPrompt;
1372
1402
  if (override.skills !== undefined) { if (override.skills === false) delete next.skills; else next.skills = [...override.skills]; }
1373
1403
  if (override.tools !== undefined) applyToolsOverride(next, override.tools);
1404
+ if (override.excludeTools !== undefined) { if (override.excludeTools === false) delete next.excludeTools; else next.excludeTools = [...override.excludeTools]; }
1374
1405
  if (override.allowNestedSubagents !== undefined) next.allowNestedSubagents = override.allowNestedSubagents;
1375
1406
  if (override.extensions !== undefined) { if (override.extensions === false) delete next.extensions; else next.extensions = [...override.extensions]; }
1376
1407
  if (override.subagentOnlyExtensions !== undefined) { if (override.subagentOnlyExtensions === false) delete next.subagentOnlyExtensions; else next.subagentOnlyExtensions = [...override.subagentOnlyExtensions]; }
@@ -1442,125 +1473,12 @@ function applyBuiltinOverrides(
1442
1473
  });
1443
1474
  }
1444
1475
 
1445
- export function agentHasFrontmatterField(agent: AgentConfig, ...fields: string[]): boolean {
1446
- const frontmatterFields = agentFrontmatterFields.get(agent);
1447
- return frontmatterFields ? fields.some((field) => frontmatterFields.has(field)) : false;
1448
- }
1449
-
1450
1476
  function applyCustomAgentOverride(
1451
1477
  agent: AgentConfig,
1452
1478
  override: BuiltinAgentOverrideConfig,
1453
1479
  meta: { scope: "user" | "project"; path: string },
1454
1480
  ): AgentConfig {
1455
- let next: AgentConfig | undefined;
1456
- let anyFilled = false;
1457
-
1458
- const mutable = (): AgentConfig => {
1459
- next ??= { ...agent };
1460
- return next;
1461
- };
1462
-
1463
- const fill = <K extends keyof AgentConfig>(
1464
- field: K,
1465
- frontmatterFields: string[],
1466
- value: AgentConfig[K],
1467
- ): void => {
1468
- if (agentHasFrontmatterField(agent, ...frontmatterFields)) return;
1469
- const target = mutable();
1470
- if (value === undefined) delete target[field]; else target[field] = value;
1471
- anyFilled = true;
1472
- };
1473
-
1474
- if (override.description !== undefined) {
1475
- mutable().description = override.description;
1476
- anyFilled = true;
1477
- }
1478
- if (override.output !== undefined) {
1479
- fill("output", ["output"], override.output === false ? undefined : override.output);
1480
- }
1481
- if (override.outputMode !== undefined) {
1482
- fill("outputMode", ["outputMode"], override.outputMode);
1483
- }
1484
- if (override.defaultReads !== undefined) {
1485
- fill("defaultReads", ["defaultReads"], override.defaultReads === false ? undefined : [...override.defaultReads]);
1486
- }
1487
- if (override.model !== undefined && !agentHasFrontmatterField(agent, "model")) {
1488
- const target = mutable();
1489
- if (override.model === false) delete target.model; else target.model = override.model;
1490
- delete target.modelSource;
1491
- anyFilled = true;
1492
- }
1493
- if (override.defaultProvider !== undefined) {
1494
- fill("modelProvider", ["modelProvider", "defaultProvider"], override.defaultProvider === false ? undefined : override.defaultProvider);
1495
- }
1496
- if (override.fallbackModels !== undefined) {
1497
- fill(
1498
- "fallbackModels",
1499
- ["fallbackModels"],
1500
- override.fallbackModels === false ? undefined : [...override.fallbackModels],
1501
- );
1502
- }
1503
- if (override.fast !== undefined) {
1504
- fill("fast", ["fast"], override.fast);
1505
- }
1506
- if (override.thinking !== undefined) {
1507
- fill("thinking", ["thinking"], override.thinking === false ? undefined : override.thinking);
1508
- }
1509
- if (override.systemPromptMode !== undefined) {
1510
- fill("systemPromptMode", ["systemPromptMode"], override.systemPromptMode);
1511
- }
1512
- if (override.inheritProjectContext !== undefined) {
1513
- fill("inheritProjectContext", ["inheritProjectContext"], override.inheritProjectContext);
1514
- }
1515
- if (override.inheritGlobalContext !== undefined) {
1516
- fill("inheritGlobalContext", ["inheritGlobalContext"], override.inheritGlobalContext);
1517
- }
1518
- if (override.inheritSkills !== undefined) {
1519
- fill("inheritSkills", ["inheritSkills"], override.inheritSkills);
1520
- }
1521
- if (override.defaultContext !== undefined) {
1522
- fill("defaultContext", ["defaultContext"], override.defaultContext === false ? undefined : override.defaultContext);
1523
- }
1524
- if (override.acceptanceRole !== undefined) {
1525
- fill("acceptanceRole", ["acceptanceRole"], override.acceptanceRole === false ? undefined : override.acceptanceRole);
1526
- }
1527
- if (override.disabled !== undefined) {
1528
- // Custom agent files cannot set `disabled`, so project overrides replace user overrides.
1529
- mutable().disabled = override.disabled;
1530
- anyFilled = true;
1531
- }
1532
- if (override.skills !== undefined) {
1533
- fill("skills", ["skill", "skills"], override.skills === false ? undefined : [...override.skills]);
1534
- }
1535
- if (override.tools !== undefined && !agentHasFrontmatterField(agent, "tools")) {
1536
- applyToolsOverride(mutable(), override.tools);
1537
- anyFilled = true;
1538
- }
1539
- if (override.allowNestedSubagents !== undefined) {
1540
- fill("allowNestedSubagents", ["allowNestedSubagents"], override.allowNestedSubagents);
1541
- }
1542
- if (override.extensions !== undefined) {
1543
- fill("extensions", ["extensions"], override.extensions === false ? undefined : [...override.extensions]);
1544
- }
1545
- if (override.subagentOnlyExtensions !== undefined) {
1546
- fill(
1547
- "subagentOnlyExtensions",
1548
- ["subagentOnlyExtensions"],
1549
- override.subagentOnlyExtensions === false ? undefined : [...override.subagentOnlyExtensions],
1550
- );
1551
- }
1552
- if (override.mutationTools !== undefined) {
1553
- fill("mutationTools", ["mutationTools"], override.mutationTools === false ? undefined : [...override.mutationTools]);
1554
- }
1555
- if (override.completionGuard !== undefined) {
1556
- fill("completionGuard", ["completionGuard"], override.completionGuard);
1557
- }
1558
- if (override.toolBudget !== undefined) {
1559
- fill("toolBudget", ["toolBudget"], override.toolBudget === false ? undefined : override.toolBudget);
1560
- }
1561
-
1562
- if (!anyFilled || !next) return agent;
1563
- next.override = { ...meta, base: agent.override?.base ?? cloneOverrideBase(agent) };
1481
+ const next = applyBuiltinOverride(agent, override, meta);
1564
1482
  const frontmatterFields = agentFrontmatterFields.get(agent);
1565
1483
  if (frontmatterFields) agentFrontmatterFields.set(next, frontmatterFields);
1566
1484
  return next;
@@ -1591,7 +1509,7 @@ function applyCustomAgentOverrides(
1591
1509
 
1592
1510
  export function buildBuiltinOverrideConfig(
1593
1511
  base: BuiltinAgentOverrideBase,
1594
- draft: Pick<AgentConfig, "model" | "modelProvider" | "fallbackModels" | "fast" | "thinking" | "systemPromptMode" | "inheritProjectContext" | "inheritGlobalContext" | "inheritSkills" | "defaultContext" | "acceptanceRole" | "disabled" | "systemPrompt" | "skills" | "tools" | "allowNestedSubagents" | "mcpDirectTools" | "extensions" | "subagentOnlyExtensions" | "mutationTools" | "completionGuard" | "toolBudget"> & Partial<Pick<AgentConfig, "description" | "output" | "outputMode" | "defaultReads">>,
1512
+ draft: Pick<AgentConfig, "model" | "modelProvider" | "fallbackModels" | "fast" | "thinking" | "systemPromptMode" | "inheritProjectContext" | "inheritGlobalContext" | "inheritSkills" | "defaultContext" | "acceptanceRole" | "disabled" | "systemPrompt" | "skills" | "tools" | "allowNestedSubagents" | "mcpDirectTools" | "extensions" | "subagentOnlyExtensions" | "mutationTools" | "completionGuard" | "toolBudget"> & Partial<Pick<AgentConfig, "description" | "output" | "outputMode" | "defaultReads" | "excludeTools">>,
1595
1513
  ): BuiltinAgentOverrideConfig | undefined {
1596
1514
  const override: BuiltinAgentOverrideConfig = {};
1597
1515
 
@@ -1620,6 +1538,7 @@ export function buildBuiltinOverrideConfig(
1620
1538
  const baseTools = joinToolList(base);
1621
1539
  const draftTools = joinToolList(draft);
1622
1540
  if (!arraysEqual(draftTools, baseTools)) override.tools = draftTools ? [...draftTools] : false;
1541
+ if (!arraysEqual(draft.excludeTools, base.excludeTools)) override.excludeTools = draft.excludeTools ? [...draft.excludeTools] : false;
1623
1542
  if (draft.allowNestedSubagents !== base.allowNestedSubagents) override.allowNestedSubagents = draft.allowNestedSubagents === true;
1624
1543
  if (!arraysEqual(draft.extensions, base.extensions)) override.extensions = draft.extensions ? [...draft.extensions] : false;
1625
1544
  if (!arraysEqual(draft.subagentOnlyExtensions, base.subagentOnlyExtensions)) {
@@ -1984,7 +1903,7 @@ function parseAgentRunnerFrontmatter(raw: string | undefined, agentName: string)
1984
1903
 
1985
1904
  function validateExternalRunnerProfile(frontmatter: Record<string, string>, agentName: string, runner: AgentRunnerConfig | undefined): void {
1986
1905
  if (runner?.type !== "external-cli" && runner?.type !== "external-job") return;
1987
- const unsupported = ["tools", "allowNestedSubagents", "model", "fallbackModels", "thinking", "extensions", "subagentOnlyExtensions", "mutationTools", "maxSubagentDepth", "completionGuard", "skills", "skill", "skillPath", "toolBudget", "permission", "permissions"]
1906
+ const unsupported = ["tools", "excludeTools", "allowNestedSubagents", "model", "fallbackModels", "thinking", "extensions", "subagentOnlyExtensions", "mutationTools", "maxSubagentDepth", "completionGuard", "skills", "skill", "skillPath", "toolBudget", "permission", "permissions"]
1988
1907
  .filter((field) => frontmatter[field] !== undefined);
1989
1908
  if (unsupported.length > 0) {
1990
1909
  throw new Error(`Agent '${agentName}' uses runner.type='${runner.type}' and declares unsupported Pi-only fields: ${unsupported.join(", ")}.`);
@@ -2066,6 +1985,7 @@ function loadAgentsFromDefinitionFiles(files: AgentDefinitionFile[], source: Age
2066
1985
  const parsedTools = splitToolList(rawTools);
2067
1986
  const tools = parsedTools.tools ?? [];
2068
1987
  const mcpDirectTools = parsedTools.mcpDirectTools ?? [];
1988
+ const excludeTools = parseFrontmatterList(frontmatter.excludeTools);
2069
1989
  const defaultReads = parseFrontmatterList(frontmatter.defaultReads);
2070
1990
  const aliases = normalizeAgentAliases(parseFrontmatterList(frontmatter.aliases ?? frontmatter.alias), runtimeName);
2071
1991
  const profileError = validateCodeOwnedProfileRunner({ name: runtimeName, localName, aliases, runner });
@@ -2187,6 +2107,7 @@ function loadAgentsFromDefinitionFiles(files: AgentDefinitionFile[], source: Age
2187
2107
  description: frontmatter.description,
2188
2108
  ...(aliases !== undefined ? { aliases } : {}),
2189
2109
  ...(rawTools !== undefined ? { tools } : {}),
2110
+ ...(excludeTools !== undefined ? { excludeTools } : {}),
2190
2111
  ...(allowNestedSubagents !== undefined ? { allowNestedSubagents } : {}),
2191
2112
  ...(mcpDirectTools.length > 0 ? { mcpDirectTools } : {}),
2192
2113
  ...(frontmatter.model !== undefined ? { model: frontmatter.model } : {}),
@@ -2328,6 +2249,61 @@ function extraUserAgentDirs(): string[] {
2328
2249
  .filter((dir) => dir.length > 0);
2329
2250
  }
2330
2251
 
2252
+ interface AgentScanDirs {
2253
+ dirs: string[];
2254
+ watchPaths: string[];
2255
+ }
2256
+
2257
+ function readConfiguredAgentScanDirs(filePath: string | null): string[] {
2258
+ if (!filePath) return [];
2259
+ try {
2260
+ const settings = readSettingsFileStrict(filePath);
2261
+ const subagents = settings.subagents;
2262
+ if (!subagents || typeof subagents !== "object" || Array.isArray(subagents)) return [];
2263
+ const dirs = (subagents as { agentScanDirs?: unknown }).agentScanDirs;
2264
+ return Array.isArray(dirs) ? dirs.filter((dir): dir is string => typeof dir === "string" && dir.trim().length > 0) : [];
2265
+ } catch {
2266
+ return [];
2267
+ }
2268
+ }
2269
+
2270
+ function expandAgentScanDirPattern(pattern: string): AgentScanDirs {
2271
+ const expanded = expandHomePath(pattern.trim()).replace(/[\\/]+/g, path.sep);
2272
+ if (!expanded) return { dirs: [], watchPaths: [] };
2273
+ const wildcardMatches = [...expanded.matchAll(/\*/g)];
2274
+ if (wildcardMatches.length === 0) {
2275
+ const dir = path.resolve(expanded);
2276
+ return { dirs: fs.existsSync(dir) ? [dir] : [], watchPaths: [dir] };
2277
+ }
2278
+ const parts = expanded.split(path.sep);
2279
+ const wildcardIndex = parts.findIndex((part) => part.includes("*"));
2280
+ if (wildcardMatches.length !== 1 || wildcardIndex === -1 || parts[wildcardIndex] !== "*") return { dirs: [], watchPaths: [] };
2281
+ const base = path.resolve(parts.slice(0, wildcardIndex).join(path.sep) || path.sep);
2282
+ const rest = parts.slice(wildcardIndex + 1);
2283
+ let entries: fs.Dirent[];
2284
+ try {
2285
+ entries = fs.readdirSync(base, { withFileTypes: true });
2286
+ } catch {
2287
+ return { dirs: [], watchPaths: [base] };
2288
+ }
2289
+ const candidateDirs = entries.filter((entry) => entry.isDirectory()).map((entry) => path.join(base, entry.name, ...rest));
2290
+ return {
2291
+ dirs: candidateDirs.filter((dir) => fs.existsSync(dir)),
2292
+ watchPaths: [base, ...candidateDirs],
2293
+ };
2294
+ }
2295
+
2296
+ function settingsAgentScanDirs(entries: string[]): AgentScanDirs {
2297
+ const dirs = new Set<string>();
2298
+ const watchPaths = new Set<string>();
2299
+ for (const entry of entries) {
2300
+ const expanded = expandAgentScanDirPattern(entry);
2301
+ for (const dir of expanded.dirs) dirs.add(dir);
2302
+ for (const watchPath of expanded.watchPaths) watchPaths.add(watchPath);
2303
+ }
2304
+ return { dirs: [...dirs], watchPaths: [...watchPaths] };
2305
+ }
2306
+
2331
2307
  export interface AgentDiscoveryAllResult {
2332
2308
  builtin: AgentConfig[];
2333
2309
  package: AgentConfig[];
@@ -2488,16 +2464,18 @@ function buildAgentDiscoverySources(cwd: string, preferredModelProvider?: string
2488
2464
  const userSettingsPath = getUserAgentSettingsPath();
2489
2465
  const projectSettingsPath = getProjectAgentSettingsPath(effectiveCwd);
2490
2466
  const packageSubagentPaths = collectPackageSubagentPaths(effectiveCwd);
2467
+ const userScanDirs = settingsAgentScanDirs(readConfiguredAgentScanDirs(userSettingsPath));
2468
+ const projectScanDirs = settingsAgentScanDirs(readConfiguredAgentScanDirs(projectSettingsPath));
2491
2469
 
2492
2470
  const builtinLoaded = loadAgentsFromDefinitionFiles(BUILTIN_AGENT_DEFINITION_FILES, "builtin");
2493
- const userLoaded = [...extraUserAgentDirs(), userDirOld, userDirNew].map((dir, discoveryPriority): LoadedAgentDirectory => {
2471
+ const userLoaded = [...extraUserAgentDirs(), ...userScanDirs.dirs, userDirOld, userDirNew].map((dir, discoveryPriority): LoadedAgentDirectory => {
2494
2472
  const inspection = inspectAgentDefinitionDirectory(dir);
2495
2473
  return { dir, inspection, loaded: loadAgentsFromDir(dir, "user", discoveryPriority, undefined, inspection) };
2496
2474
  });
2497
2475
  const projectInspections = new Map(projectCandidateDirs.map((dir) => [dir, inspectAgentDefinitionDirectory(dir)]));
2498
- const projectLoaded = projectAgentDirs.map((dir): LoadedAgentDirectory => {
2499
- const inspection = projectInspections.get(dir)!;
2500
- return { dir, inspection, loaded: loadAgentsFromDir(dir, "project", dir === projectAgentsDir ? 1 : 0, undefined, inspection) };
2476
+ const projectLoaded = [...projectScanDirs.dirs, ...projectAgentDirs].map((dir, discoveryPriority): LoadedAgentDirectory => {
2477
+ const inspection = projectInspections.get(dir) ?? inspectAgentDefinitionDirectory(dir);
2478
+ return { dir, inspection, loaded: loadAgentsFromDir(dir, "project", dir === projectAgentsDir ? 1 : discoveryPriority, undefined, inspection) };
2501
2479
  });
2502
2480
  const packageLoaded = packageSubagentPaths.agents.map((entry, index): LoadedAgentDirectory => {
2503
2481
  const inspection = inspectAgentDefinitionDirectory(entry.dir);
@@ -2509,6 +2487,8 @@ function buildAgentDiscoverySources(cwd: string, preferredModelProvider?: string
2509
2487
  ...(projectSettingsPath ? [projectSettingsPath] : []),
2510
2488
  ...projectDiscoveryWatchPaths(effectiveCwd),
2511
2489
  ...findProjectRootCandidates(effectiveCwd).map((root) => path.join(getProjectConfigDir(root), "settings.json")),
2490
+ ...userScanDirs.watchPaths,
2491
+ ...projectScanDirs.watchPaths,
2512
2492
  ...packageSubagentPaths.watchPaths,
2513
2493
  ]);
2514
2494
  for (const directory of userLoaded) addDirectoryWatchPaths(watchPaths, directory.dir, directory.inspection.files, inspectedAgentDefinitionDirectories(directory.inspection, directory.dir));
@@ -2516,6 +2496,7 @@ function buildAgentDiscoverySources(cwd: string, preferredModelProvider?: string
2516
2496
  const inspection = projectInspections.get(dir);
2517
2497
  addDirectoryWatchPaths(watchPaths, dir, inspection?.files ?? [], inspection ? inspectedAgentDefinitionDirectories(inspection, dir) : []);
2518
2498
  }
2499
+ for (const directory of projectLoaded) addDirectoryWatchPaths(watchPaths, directory.dir, directory.inspection.files, inspectedAgentDefinitionDirectories(directory.inspection, directory.dir));
2519
2500
  for (const directory of packageLoaded) addDirectoryWatchPaths(watchPaths, directory.dir, directory.inspection.files, inspectedAgentDefinitionDirectories(directory.inspection, directory.dir));
2520
2501
 
2521
2502
  const userDir = process.env.PI_CODING_AGENT_DIR ? userDirOld : fs.existsSync(userDirNew) ? userDirNew : userDirOld;
@@ -2651,7 +2632,16 @@ function configuredAgentsForScope(sources: AgentDiscoverySources, scope: AgentSc
2651
2632
  function discoveryDirectories(sources: AgentDiscoverySources, scope: AgentScope): AgentDefinitionDirectoryReport[] {
2652
2633
  const directories: AgentDefinitionDirectoryReport[] = [reportAgentDefinitionDirectory("builtin", BUILTIN_AGENTS_DIR, BUILTIN_AGENT_DEFINITION_INSPECTION)];
2653
2634
  if (scope !== "project") for (const directory of sources.userLoaded) directories.push(reportAgentDefinitionDirectory("user", directory.dir, directory.inspection));
2654
- if (scope !== "user") for (const dir of sources.projectCandidateDirs) directories.push(reportAgentDefinitionDirectory("project", dir, sources.projectInspections.get(dir)!));
2635
+ if (scope !== "user") {
2636
+ const reported = new Set<string>();
2637
+ for (const dir of sources.projectCandidateDirs) {
2638
+ reported.add(path.resolve(dir));
2639
+ directories.push(reportAgentDefinitionDirectory("project", dir, sources.projectInspections.get(dir)!));
2640
+ }
2641
+ for (const directory of sources.projectLoaded) {
2642
+ if (!reported.has(path.resolve(directory.dir))) directories.push(reportAgentDefinitionDirectory("project", directory.dir, directory.inspection));
2643
+ }
2644
+ }
2655
2645
  for (const directory of sources.packageLoaded) {
2656
2646
  if (directory.packageEntry && packageEntryIncluded(scope, directory.packageEntry.scope)) directories.push(reportAgentDefinitionDirectory("package", directory.dir, directory.inspection));
2657
2647
  }
@@ -2760,7 +2750,9 @@ function discoverAgentsUncached(cwd: string, scope: AgentScope, preferredModelPr
2760
2750
  const directories: AgentDefinitionDirectoryReport[] = [reportAgentDefinitionDirectory("builtin", BUILTIN_AGENTS_DIR, BUILTIN_AGENT_DEFINITION_INSPECTION)];
2761
2751
  const builtinLoaded = loadAgentsFromDefinitionFiles(BUILTIN_AGENT_DEFINITION_FILES, "builtin");
2762
2752
  const builtinAgents = applyBuiltinOverrides(applySubagentDefaults(builtinLoaded.agents, defaultModel, defaultProvider, defaultThinking, defaultExtensions), userSettings, projectSettings, userSettingsPath, projectSettingsPath);
2763
- const userLoaded = scope === "project" ? [] : [...extraUserAgentDirs(), userDirOld, userDirNew].map((dir, discoveryPriority) => {
2753
+ const userScanDirs = settingsAgentScanDirs(userSettings.agentScanDirs ?? []);
2754
+ const projectScanDirs = settingsAgentScanDirs(projectSettings.agentScanDirs ?? []);
2755
+ const userLoaded = scope === "project" ? [] : [...extraUserAgentDirs(), ...userScanDirs.dirs, userDirOld, userDirNew].map((dir, discoveryPriority) => {
2764
2756
  const inspection = inspectAgentDefinitionDirectory(dir);
2765
2757
  directories.push(reportAgentDefinitionDirectory("user", dir, inspection));
2766
2758
  return loadAgentsFromDir(dir, "user", discoveryPriority, undefined, inspection);
@@ -2768,7 +2760,11 @@ function discoverAgentsUncached(cwd: string, scope: AgentScope, preferredModelPr
2768
2760
  const userAgents = applyCustomAgentOverrides(applySubagentDefaults(userLoaded.flatMap((loaded) => loaded.agents), defaultModel, defaultProvider, defaultThinking, defaultExtensions), userSettings, projectSettings, userSettingsPath, projectSettingsPath);
2769
2761
  const projectInspections = scope === "user" ? new Map<string, AgentDefinitionInspection>() : new Map(projectCandidateDirs.map((dir) => [dir, inspectAgentDefinitionDirectory(dir)]));
2770
2762
  if (scope !== "user") for (const dir of projectCandidateDirs) directories.push(reportAgentDefinitionDirectory("project", dir, projectInspections.get(dir)!));
2771
- const projectLoaded = scope === "user" ? [] : projectAgentDirs.map((dir) => loadAgentsFromDir(dir, "project", dir === projectAgentsDir ? 1 : 0, undefined, projectInspections.get(dir)!));
2763
+ const projectLoaded = scope === "user" ? [] : [...projectScanDirs.dirs, ...projectAgentDirs].map((dir, discoveryPriority) => {
2764
+ const inspection = projectInspections.get(dir) ?? inspectAgentDefinitionDirectory(dir);
2765
+ if (!projectInspections.has(dir)) directories.push(reportAgentDefinitionDirectory("project", dir, inspection));
2766
+ return loadAgentsFromDir(dir, "project", dir === projectAgentsDir ? 1 : discoveryPriority, undefined, inspection);
2767
+ });
2772
2768
  const projectAgents = applyCustomAgentOverrides(applySubagentDefaults(projectLoaded.flatMap((loaded) => loaded.agents), defaultModel, defaultProvider, defaultThinking, defaultExtensions), userSettings, projectSettings, userSettingsPath, projectSettingsPath);
2773
2769
  const packageLoaded = packageSubagentPaths.agents.map((entry, index) => {
2774
2770
  const inspection = inspectAgentDefinitionDirectory(entry.dir);
@@ -20,6 +20,7 @@ export interface RuntimeAgentDefinition {
20
20
  systemPrompt: string;
21
21
  aliases?: readonly string[];
22
22
  tools?: readonly string[];
23
+ excludeTools?: readonly string[];
23
24
  allowNestedSubagents?: boolean;
24
25
  mcpDirectTools?: readonly string[];
25
26
  model?: string;
@@ -198,7 +199,7 @@ function validateDefinition(value: unknown): RuntimeAgentDefinition {
198
199
  if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("Runtime agent definition must be an object.");
199
200
  const definition = value as Record<string, unknown>;
200
201
  const supported = new Set([
201
- "description", "systemPrompt", "aliases", "tools", "allowNestedSubagents", "mcpDirectTools", "model", "fallbackModels", "thinking",
202
+ "description", "systemPrompt", "aliases", "tools", "excludeTools", "allowNestedSubagents", "mcpDirectTools", "model", "fallbackModels", "thinking",
202
203
  "systemPromptMode", "inheritProjectContext", "inheritGlobalContext", "inheritSkills", "defaultContext", "defaultAsync", "defaultTimeoutMs",
203
204
  "defaultToolTimeoutMs", "defaultAcceptance", "acceptanceRole", "runner", "skills", "skillPath",
204
205
  "extensions", "subagentOnlyExtensions", "mutationTools", "output", "outputMode", "defaultReads", "defaultProgress", "interactive",
@@ -218,6 +219,7 @@ function validateDefinition(value: unknown): RuntimeAgentDefinition {
218
219
  if (outputMode !== undefined && outputMode !== "inline" && outputMode !== "file-only") throw new Error("Runtime agent definition outputMode must be 'inline' or 'file-only'.");
219
220
  const aliases = validateStringList(definition.aliases, "Runtime agent definition aliases");
220
221
  const tools = validateStringList(definition.tools, "Runtime agent definition tools");
222
+ const excludeTools = validateStringList(definition.excludeTools, "Runtime agent definition excludeTools");
221
223
  const allowNestedSubagents = validateBoolean(definition.allowNestedSubagents, "Runtime agent definition allowNestedSubagents");
222
224
  const mcpDirectTools = validateStringList(definition.mcpDirectTools, "Runtime agent definition mcpDirectTools");
223
225
  const model = validateOptionalString(definition.model, "Runtime agent definition model");
@@ -248,6 +250,7 @@ function validateDefinition(value: unknown): RuntimeAgentDefinition {
248
250
  systemPrompt: validateString(definition.systemPrompt, "Runtime agent definition systemPrompt", MAX_SYSTEM_PROMPT_LENGTH),
249
251
  ...(aliases ? { aliases } : {}),
250
252
  ...(tools ? { tools } : {}),
253
+ ...(excludeTools ? { excludeTools } : {}),
251
254
  ...(allowNestedSubagents !== undefined ? { allowNestedSubagents } : {}),
252
255
  ...(mcpDirectTools ? { mcpDirectTools } : {}),
253
256
  ...(model ? { model } : {}),
@@ -327,6 +330,7 @@ function toAgentConfig(name: string, definition: RuntimeAgentDefinition): AgentC
327
330
  ...(aliases ? { aliases } : {}),
328
331
  ...(definition.runner !== undefined ? { runner: definition.runner } : {}),
329
332
  ...(definition.tools !== undefined ? { tools: [...definition.tools] } : {}),
333
+ ...(definition.excludeTools !== undefined ? { excludeTools: [...definition.excludeTools] } : {}),
330
334
  ...(definition.allowNestedSubagents !== undefined ? { allowNestedSubagents: definition.allowNestedSubagents } : {}),
331
335
  ...(definition.mcpDirectTools !== undefined ? { mcpDirectTools: [...definition.mcpDirectTools] } : {}),
332
336
  ...(definition.model !== undefined ? { model: definition.model } : {}),
@@ -111,6 +111,7 @@ export interface SubagentLaunchContractSkills {
111
111
  export interface SubagentLaunchContractTools {
112
112
  requestedBuiltin: string[];
113
113
  declaredBuiltin: string[];
114
+ excludeTools?: string[];
114
115
  effectiveAllowlist: string[];
115
116
  explicitAllowlist: boolean;
116
117
  requiredChildTools: string[];
@@ -356,6 +357,7 @@ export async function resolveSubagentLaunchContract(input: SubagentLaunchContrac
356
357
  try {
357
358
  toolPlan = resolvePiLaunchToolPlan({
358
359
  tools: agent.tools,
360
+ excludeTools: agent.excludeTools,
359
361
  allowNestedSubagents: agent.allowNestedSubagents,
360
362
  extensions: agent.extensions,
361
363
  subagentOnlyExtensions: agent.subagentOnlyExtensions,
@@ -435,6 +437,7 @@ export async function resolveSubagentLaunchContract(input: SubagentLaunchContrac
435
437
  tools: {
436
438
  requestedBuiltin: toolPlan.requestedBuiltinTools,
437
439
  declaredBuiltin: toolPlan.declaredBuiltinTools,
440
+ ...(toolPlan.excludeTools.length > 0 ? { excludeTools: toolPlan.excludeTools } : {}),
438
441
  effectiveAllowlist: toolPlan.effectiveToolAllowlist,
439
442
  explicitAllowlist: toolPlan.explicitToolAllowlist,
440
443
  requiredChildTools: toolPlan.requiredChildTools,
@@ -485,6 +488,7 @@ export async function resolveSubagentLaunchContract(input: SubagentLaunchContrac
485
488
  inheritSkills: agent.inheritSkills,
486
489
  skills: requestedSkills,
487
490
  tools: toolPlan.effectiveToolAllowlist,
491
+ ...(toolPlan.excludeTools.length > 0 ? { excludeTools: toolPlan.excludeTools } : {}),
488
492
  extensions: toolPlan.extensionArgs,
489
493
  mcpDirectTools: toolPlan.effectiveMcpTools,
490
494
  ...(outputPath ? { outputPath } : {}),
@@ -20,5 +20,8 @@ export {
20
20
  type SubagentResultStatus,
21
21
  type SubagentRunMode,
22
22
  type Usage,
23
+ type ManagedWorktreeProvider,
24
+ type WorktreeNaming,
25
+ type WorktreeProvider,
23
26
  type WorkflowResourceProvenanceV1,
24
27
  } from "../shared/types.ts";
@@ -9,6 +9,7 @@ import { getAgentDir } from "../shared/utils.ts";
9
9
  import { DEFAULT_MODEL_EXCLUSION_TTL_MS, MAX_MODEL_EXCLUSION_TTL_MS, setDefaultTTL } from "../runs/shared/model-exclusions.ts";
10
10
  import { validatePermissionConfig } from "../runs/shared/permissions.ts";
11
11
  import { MAX_ABANDONED_SLOT_RELEASE_AFTER_MS, MIN_ABANDONED_SLOT_RELEASE_AFTER_MS } from "../runs/background/active-async-capacity.ts";
12
+ import { normalizeWorktreeBranchPrefix } from "../runs/shared/worktree.ts";
12
13
 
13
14
  const ARTIFACT_DIR_PREFERENCES = new Set<ArtifactDirPreference>(["project", "session", "temp"]);
14
15
  const FLEET_KEYBINDING_ACTION_SET = new Set<string>(FLEET_KEYBINDING_ACTIONS);
@@ -138,6 +139,16 @@ function validateMainWindowRendererConfig(value: unknown): void {
138
139
  }
139
140
 
140
141
  function validateConfig(config: Record<string, unknown>): void {
142
+ if (config.worktree !== undefined && typeof config.worktree !== "boolean") {
143
+ throw new Error("config.worktree must be a boolean");
144
+ }
145
+ if (config.worktreeProvider !== undefined && config.worktreeProvider !== "auto" && config.worktreeProvider !== "native" && config.worktreeProvider !== "worktrunk") {
146
+ throw new Error('config.worktreeProvider must be "auto", "native", or "worktrunk"');
147
+ }
148
+ if (config.worktreeBranchPrefix !== undefined) {
149
+ if (typeof config.worktreeBranchPrefix !== "string") throw new Error("config.worktreeBranchPrefix must be a string");
150
+ normalizeWorktreeBranchPrefix(config.worktreeBranchPrefix);
151
+ }
141
152
  if (config.defaultSubagentContext !== undefined && config.defaultSubagentContext !== "fresh" && config.defaultSubagentContext !== "fork") {
142
153
  throw new Error('config.defaultSubagentContext must be "fresh" or "fork"');
143
154
  }
@@ -229,6 +240,15 @@ export function loadConfig(): ExtensionConfig {
229
240
  return readConfigForUpdate(configPath);
230
241
  } catch (error) {
231
242
  if (error instanceof PrunedForkConfigError) throw error;
243
+ // An explicitly requested worktree provider/prefix must not be silently
244
+ // discarded and replaced by the built-in defaults after validation fails.
245
+ try {
246
+ const raw = JSON.parse(fs.readFileSync(configPath, "utf-8")) as unknown;
247
+ if (raw && typeof raw === "object" && !Array.isArray(raw)
248
+ && (Object.hasOwn(raw, "worktreeProvider") || Object.hasOwn(raw, "worktreeBranchPrefix"))) throw error;
249
+ } catch (readError) {
250
+ if (readError === error) throw error;
251
+ }
232
252
  console.error(`Failed to load subagent config from '${configPath}':`, error);
233
253
  }
234
254
  return {};
@@ -22,6 +22,7 @@ export interface PublicSubagentExecutionParams {
22
22
  args?: unknown;
23
23
  workflowScript?: unknown;
24
24
  workflowScriptPath?: unknown;
25
+ sessionOnly?: unknown;
25
26
  globalConcurrencyLimit?: unknown;
26
27
  maxSubagentSpawnsPerRun?: unknown;
27
28
  preflight?: unknown;
@@ -86,10 +86,13 @@ const AcceptanceOverride = Type.Unsafe({
86
86
  deprecated: true,
87
87
  description: "Invalid as an explicit policy. Recognized only so preflight can explain that reviewed is an achieved status.",
88
88
  },
89
+ {
90
+ type: "string",
91
+ },
89
92
  { type: "boolean", enum: [false] },
90
93
  { type: "object", additionalProperties: true },
91
94
  ],
92
- description: `Optional acceptance policy. For reviewer/read-only calls, omit acceptance. Example: { level: "checked", evidence: ["commands-run", "changed-files"] }. Supported evidence kinds: ${AcceptanceEvidenceKinds.join(", ")}. Evidence levels end at verified; use acceptance.review.required for review. Omitted means auto-inferred unless agentContract compatibility behavior is enabled.`,
95
+ description: `Optional acceptance policy. Prefer an inline JSON object. JSON-encoded object strings are tolerated only during input normalization; invalid strings fail closed. Reviewer/read-only calls, omit acceptance. { level: "checked", evidence: ["commands-run", "changed-files"] }. Supported evidence kinds: ${AcceptanceEvidenceKinds.join(",")}. acceptance.review.required.`,
93
96
  });
94
97
 
95
98
  const AgentContractOverride = Type.Object({
@@ -317,8 +320,9 @@ const SubagentParamProperties = {
317
320
  thinking: Type.Optional(Type.Unsafe({ anyOf: [{ type: "string" }, { type: "boolean", enum: [false] }], description: "Thinking level for action='watchdog.configure' only (off/minimal/low/medium/high/xhigh/max, inherit, or false for off). Ignored on dispatch; set per-run child thinking with a suffix on the model string, e.g. model: 'provider/id:high'." })),
318
321
  at: Type.Optional(Type.String({ description: "One-shot trigger for action='schedule.create': a relative delay such as '+10m' or an ISO timestamp with timezone." })),
319
322
  every: Type.Optional(Type.String({ description: "Fixed recurring interval for action='schedule.create', such as '30m', '6h', '2d', or '2w'." })),
323
+ sessionOnly: Type.Optional(Type.Boolean()),
320
324
  on: Type.Optional(Type.Unsafe({ anyOf: [{ type: "string" }, { type: "integer" }], description: "Calendar selector reserved for a later schedule slice." })),
321
- timezone: Type.Optional(Type.String({ description: "IANA timezone reserved for a later calendar schedule slice." })),
325
+ timezone: Type.Optional(Type.String()),
322
326
  overlap: Type.Optional(Type.String({ enum: ["skip"], description: "Overlap policy. This slice supports skip only." })),
323
327
  catchUp: Type.Optional(Type.String({ enum: ["none", "latest"], description: "Missed occurrence policy for recurring schedules. Defaults to latest." })),
324
328
  missionId: Type.Optional(Type.String({ description: "Mission id." })),
@@ -57,7 +57,7 @@ EXECUTION:
57
57
  MANAGEMENT / CONTROL (use action; omit execution fields):
58
58
  • validate checks workflowScript or workflowScriptPath syntax and statically decidable structure without launching children. list, get, models, guide, children.list, create, update, delete, eject, disable, enable, reset, status, debug.run, doctor, grant-spawn-budget, worktree.discard, worktree.cleanup (plan-only), lane.status, lane.recordMerge, lane.recordSupersession, refine/refine.show/refine.rollback, mission.create/list/show/update/resolve-decision/attach-run/close, inspector.open/status/close, project.open/status/close, and watchdog actions remain available. Use {action:"guide", topic:"overview"} for packaged current-version help; topics are overview, workflows, agents, missions, observability, tool-reference, configuration, models, watchdog, and extension-api.
59
59
  • status, interrupt, stop, resume, and steer manage live or persisted runs. Use status view:"fleet" for an overview or view:"transcript" with id and optional index to tail output.
60
- • Create durable project schedules with { action:"schedule.create", id?, name?, at:"+10m" | ISO, workflowScript:"return runs.run('main', {agent:'worker', task:'...'})" }, or use workflowScriptPath instead. Manage them with schedule.list/show/history/pause/resume/run/run-due/delete. This first slice supports fixed intervals; calendar schedules and schedule mission attachment are deferred.
60
+ • Create durable project schedules with { action:"schedule.create", id?, name?, sessionOnly?:true, at:"+10m" | ISO, workflowScript:"return runs.run('main', {agent:'worker', task:'...'})" }, or use workflowScriptPath instead. With sessionOnly:true, the schedule records the creating session file and only that session can restore or execute it; omitted/false preserves project-wide behavior. Manage them with schedule.list/show/history/pause/resume/run/run-due/delete. This first slice supports fixed intervals; calendar schedules and schedule mission attachment are deferred.
61
61
 
62
62
  ${SUBAGENT_SAFETY_GUIDANCE}`;
63
63