@fro.bot/systematic 3.4.1 → 3.5.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.
@@ -16206,6 +16206,58 @@ var CategoryOverlaySchema = exports_external.object({
16206
16206
  description: "Per-category configuration overlay (same fields as agent minus disable)",
16207
16207
  examples: [{ model: "anthropic/claude-opus-4-7", temperature: 0.1 }]
16208
16208
  });
16209
+ var piSubagentsThinkingSchema = exports_external.enum(["off", "minimal", "low", "medium", "high", "xhigh", "max"]).meta({
16210
+ description: "pi-subagents reasoning effort level for exported persona frontmatter",
16211
+ examples: ["off", "medium", "high"]
16212
+ });
16213
+ var piSubagentsMaxTurnsSchema = exports_external.number().int().nonnegative().meta({
16214
+ description: "pi-subagents maximum turns for a delegated persona (0 = unlimited)",
16215
+ examples: [0, 10, 25]
16216
+ });
16217
+ var piSubagentsToolsSchema = exports_external.string().min(1).meta({
16218
+ description: "pi-subagents comma-selector tool string (built-ins, */all/none, or extension selectors)",
16219
+ examples: ["*", "read,grep,glob", "none"]
16220
+ });
16221
+ var piSubagentsSkillsSchema = exports_external.union([exports_external.literal(true), exports_external.string().min(1)]).meta({
16222
+ description: "pi-subagents skills selector: true (all) or a comma-separated list of skill names",
16223
+ examples: [true, "ce:plan,ce:review"]
16224
+ });
16225
+ var PiSubagentsAgentOverlaySchema = exports_external.object({
16226
+ thinking: trustProtected(piSubagentsThinkingSchema).optional(),
16227
+ max_turns: trustAny(piSubagentsMaxTurnsSchema).optional(),
16228
+ tools: trustProtected(piSubagentsToolsSchema).optional(),
16229
+ skills: trustProtected(piSubagentsSkillsSchema).optional()
16230
+ }).strict().meta({
16231
+ description: "Per-agent pi-subagents export overlay (Pi-native fields only; no model)",
16232
+ examples: [{ thinking: "high", max_turns: 10 }]
16233
+ });
16234
+ var PiSubagentsCategoryOverlaySchema = PiSubagentsAgentOverlaySchema.meta({
16235
+ description: "Per-category pi-subagents export overlay (Pi-native fields only; no model)",
16236
+ examples: [{ thinking: "medium" }]
16237
+ });
16238
+ var PiSubagentsSchema = exports_external.object({
16239
+ categories: exports_external.record(exports_external.string(), PiSubagentsCategoryOverlaySchema).default({}).meta({
16240
+ description: "Per-category pi-subagents export overlays keyed by category name",
16241
+ examples: [{ research: { thinking: "high" } }, {}]
16242
+ }),
16243
+ agents: exports_external.record(exports_external.string(), PiSubagentsAgentOverlaySchema).default({}).meta({
16244
+ description: "Per-agent pi-subagents export overlays keyed by bundled agent name",
16245
+ examples: [{ "repo-research-analyst": { max_turns: 10 } }, {}]
16246
+ })
16247
+ }).strict().default({ categories: {}, agents: {} }).meta({
16248
+ description: "Pi-native pi-subagents export field overlays (thinking, max_turns, tools, skills). Category values apply first; per-agent values override. No model field \u2014 model stays in the categories/agents overlay.",
16249
+ examples: [
16250
+ {
16251
+ categories: { research: { thinking: "high" } },
16252
+ agents: { "repo-research-analyst": { max_turns: 10 } }
16253
+ }
16254
+ ]
16255
+ });
16256
+ var PI_SUBAGENTS_PROTECTED_FIELDS = [
16257
+ "thinking",
16258
+ "tools",
16259
+ "skills"
16260
+ ];
16209
16261
  var BootstrapSchema = exports_external.object({
16210
16262
  enabled: exports_external.boolean().default(true).meta({
16211
16263
  description: "Enable bootstrap prompt injection into every conversation",
@@ -16276,6 +16328,7 @@ function createSystematicConfigSchema(opts) {
16276
16328
  ]
16277
16329
  }),
16278
16330
  workflow_guard: WorkflowGuardSchema,
16331
+ pi_subagents: PiSubagentsSchema,
16279
16332
  skills_as_commands: exports_external.boolean().default(true).meta({
16280
16333
  description: "Register skills discovered from user/project skill directories (OpenCode config and other agent-harness-standard locations) as slash commands. Default true.",
16281
16334
  examples: [true, false]
@@ -16315,6 +16368,7 @@ var DEFAULT_CONFIG = {
16315
16368
  },
16316
16369
  agents: {},
16317
16370
  categories: {},
16371
+ pi_subagents: { categories: {}, agents: {} },
16318
16372
  skills_as_commands: true
16319
16373
  };
16320
16374
  var SECURITY_OVERLAY_FIELDS2 = new Set(SECURITY_OVERLAY_FIELDS);
@@ -16473,16 +16527,18 @@ function mergeArraysUnique(arr1, arr2) {
16473
16527
  set2.add(item);
16474
16528
  return Array.from(set2);
16475
16529
  }
16476
- function loadConfig(projectDir) {
16477
- return loadConfigWithSources(projectDir).config;
16530
+ function loadConfig(projectDir, options) {
16531
+ return loadConfigWithSources(projectDir, options).config;
16478
16532
  }
16479
- function loadConfigWithSources(projectDir) {
16533
+ function loadConfigWithSources(projectDir, options) {
16534
+ const includeProject = options?.includeProject ?? true;
16480
16535
  const paths = getConfigPaths(projectDir);
16481
16536
  const userSource = loadConfigSource(paths.userConfig, "user");
16482
- const projectSource = loadConfigSource(paths.projectConfig, "project");
16537
+ const projectSource = includeProject ? loadConfigSource(paths.projectConfig, "project") : null;
16483
16538
  const customSource = paths.customConfig ? loadConfigSource(paths.customConfig, "custom") : null;
16484
16539
  const sources = [userSource, projectSource, customSource].filter((source) => source !== null);
16485
16540
  const mergedOverlays = mergeOverlaySources(sources);
16541
+ const mergedPiSubagentsOverlays = mergePiSubagentsOverlaySources(sources);
16486
16542
  const droppedCategories = Object.keys(mergedOverlays.categories).filter((name) => REMOVED_AGENT_CATEGORIES_SET.has(name));
16487
16543
  const warned = new Set;
16488
16544
  warnDroppedNames(droppedCategories, "categories", warned, "v3.0.0");
@@ -16511,6 +16567,10 @@ function loadConfigWithSources(projectDir) {
16511
16567
  },
16512
16568
  agents: overlayValues(overlays.agents),
16513
16569
  categories: overlayValues(overlays.categories),
16570
+ pi_subagents: {
16571
+ categories: overlayValues(mergedPiSubagentsOverlays.categories),
16572
+ agents: overlayValues(mergedPiSubagentsOverlays.agents)
16573
+ },
16514
16574
  skills_as_commands: customConfig?.skills_as_commands ?? projectConfig?.skills_as_commands ?? userConfig?.skills_as_commands ?? DEFAULT_CONFIG.skills_as_commands
16515
16575
  };
16516
16576
  const droppedSkills = computeDroppedNames(result.disabled_skills, CURRENT_SKILL_NAMES_SET);
@@ -16577,6 +16637,57 @@ function preserveSecurityFields(previous, next) {
16577
16637
  }
16578
16638
  return result;
16579
16639
  }
16640
+ var PI_SUBAGENTS_PROTECTED_FIELD_SET = new Set(PI_SUBAGENTS_PROTECTED_FIELDS);
16641
+ function mergePiSubagentsOverlaySources(sources) {
16642
+ const result = {
16643
+ agents: {},
16644
+ categories: {}
16645
+ };
16646
+ for (const source of sources) {
16647
+ mergePiSubagentsOverlayMap(result.agents, source, "agents");
16648
+ mergePiSubagentsOverlayMap(result.categories, source, "categories");
16649
+ }
16650
+ return result;
16651
+ }
16652
+ function stripPiSubagentsProtectedFields(value) {
16653
+ const result = {};
16654
+ for (const [field, fieldValue] of Object.entries(value)) {
16655
+ if (PI_SUBAGENTS_PROTECTED_FIELD_SET.has(field))
16656
+ continue;
16657
+ result[field] = fieldValue;
16658
+ }
16659
+ return result;
16660
+ }
16661
+ function preservePiSubagentsProtectedFields(previous, next) {
16662
+ const result = { ...next };
16663
+ for (const field of PI_SUBAGENTS_PROTECTED_FIELD_SET) {
16664
+ if (Object.hasOwn(previous, field)) {
16665
+ result[field] = previous[field];
16666
+ }
16667
+ }
16668
+ return result;
16669
+ }
16670
+ function mergePiSubagentsOverlayMap(target, source, mapKey) {
16671
+ const overlayMap = source.config.pi_subagents?.[mapKey];
16672
+ if (overlayMap === undefined)
16673
+ return;
16674
+ if (!isRecord2(overlayMap)) {
16675
+ throwInvalidOverlay(source.path, `pi_subagents.${mapKey}`);
16676
+ }
16677
+ for (const [key, rawValue] of Object.entries(overlayMap)) {
16678
+ const keyPath = `pi_subagents.${mapKey}.${key}`;
16679
+ if (!isRecord2(rawValue)) {
16680
+ throwInvalidOverlay(source.path, keyPath);
16681
+ }
16682
+ const previous = target[key];
16683
+ const value = source.trust === "project" ? preservePiSubagentsProtectedFields(previous?.value ?? {}, stripPiSubagentsProtectedFields(rawValue)) : rawValue;
16684
+ target[key] = {
16685
+ value,
16686
+ sourcePath: source.path,
16687
+ keyPath
16688
+ };
16689
+ }
16690
+ }
16580
16691
  function overlayValues(overlays) {
16581
16692
  const result = {};
16582
16693
  for (const [key, overlay] of Object.entries(overlays)) {
package/dist/index.js CHANGED
@@ -15,7 +15,7 @@ import {
15
15
  loadConfigWithSources,
16
16
  parseFrontmatter,
17
17
  walkDir
18
- } from "./index-0vm17gwv.js";
18
+ } from "./index-1mb4baxr.js";
19
19
 
20
20
  // src/index.ts
21
21
  import { createHash as createHash5 } from "crypto";
@@ -3320,9 +3320,20 @@ function applyUnitStart(marker, epoch, context) {
3320
3320
  return;
3321
3321
  }
3322
3322
  if (current.unitId === marker.unitId) {
3323
- if (!sameUnitDeclaration(current, marker))
3323
+ if (current.state === "completed")
3324
+ return "out-of-order";
3325
+ if (!sameUnitDeclaration(current, marker) && unitHasMintedEvidence(current, context)) {
3326
+ return "out-of-order";
3327
+ }
3328
+ if (sameUnitDeclaration(current, marker) && current.transitionDigest !== marker.transitionDigest) {
3324
3329
  return "conflicting-marker";
3325
- return current.state === "completed" ? "out-of-order" : "conflicting-marker";
3330
+ }
3331
+ if (!unitDeclarationExtends(current, marker))
3332
+ return "conflicting-marker";
3333
+ if (sameUnitDeclaration(current, marker))
3334
+ return;
3335
+ context.progression = { epoch, unit: snapshot };
3336
+ return;
3326
3337
  }
3327
3338
  if (current.state !== "completed")
3328
3339
  return "out-of-order";
@@ -3346,9 +3357,26 @@ function applyUnitComplete(marker, epoch, context) {
3346
3357
  };
3347
3358
  return;
3348
3359
  }
3360
+ function unitHasMintedEvidence(current, context) {
3361
+ return [...context.mintByReceipt.values()].some((envelope) => envelope.canonical.epochDigest === current.epochDigest && envelope.canonical.unitDigest === current.unitDigest);
3362
+ }
3349
3363
  function sameUnitDeclaration(current, marker) {
3350
3364
  return current.epochDigest === marker.epochDigest && current.unitDigest === marker.unitDigest && current.family === marker.family && JSON.stringify(current.requiredOperations) === JSON.stringify(marker.requiredOperations) && JSON.stringify(current.resourceScopes) === JSON.stringify(marker.resourceScopes);
3351
3365
  }
3366
+ function unitDeclarationExtends(current, marker) {
3367
+ if (current.epochDigest !== marker.epochDigest || current.unitDigest !== marker.unitDigest || current.family !== marker.family) {
3368
+ return false;
3369
+ }
3370
+ const nextOperations = new Set(marker.requiredOperations);
3371
+ if (current.requiredOperations.some((operation) => !nextOperations.has(operation))) {
3372
+ return false;
3373
+ }
3374
+ const nextScopes = new Map(marker.resourceScopes.map((scope) => [
3375
+ scope.operation,
3376
+ scope.resourceIdentity
3377
+ ]));
3378
+ return [...current.resourceScopes].every((scope) => nextScopes.get(scope.operation) === scope.resourceIdentity);
3379
+ }
3352
3380
  function foldProgressionMarker(marker, context) {
3353
3381
  return marker.target === "epoch" ? applyEpochProgression(marker, context) : applyUnitProgression(marker, context);
3354
3382
  }
@@ -8473,7 +8501,11 @@ function cloneUnit(unit) {
8473
8501
  requiredOperations: unit.requiredOperations,
8474
8502
  requiredResourceOperations: Object.freeze([
8475
8503
  ...unit.declaredResourceOperations
8476
- ])
8504
+ ]),
8505
+ resourceScopes: Object.freeze([...unit.resourceScopes].map(([operation, resourceIdentity]) => ({
8506
+ operation,
8507
+ resourceIdentity
8508
+ })))
8477
8509
  });
8478
8510
  }
8479
8511
  function familyForSkill(skill) {
@@ -8900,6 +8932,13 @@ function createWorkflowGuard(options) {
8900
8932
  return "receipt-mismatch";
8901
8933
  return context.worktreeIdentity !== currentWorktreeIdentity ? "receipt-mismatch" : undefined;
8902
8934
  }
8935
+ function currentOperationContext() {
8936
+ return Object.freeze({
8937
+ workspaceIdentity: currentWorkspaceIdentity,
8938
+ ...currentRepositoryIdentity === undefined ? {} : { repositoryIdentity: currentRepositoryIdentity },
8939
+ ...currentWorktreeIdentity === undefined ? {} : { worktreeIdentity: currentWorktreeIdentity }
8940
+ });
8941
+ }
8903
8942
  function resourceBeforeReason(input, unit) {
8904
8943
  if (!operationUsesResource(input.operation))
8905
8944
  return;
@@ -9591,9 +9630,10 @@ function createWorkflowGuard(options) {
9591
9630
  }
9592
9631
  return;
9593
9632
  }
9594
- function mergeResourceScopes(trustedPolicy, modelScopes) {
9595
- const result = new Map(runtimeScopes);
9596
- for (const scopes of [trustedPolicy.resourceScopes, modelScopes]) {
9633
+ function mergeResourceScopes(trustedPolicy, modelScopes, existingScopes) {
9634
+ const result = new Map(existingScopes ?? runtimeScopes);
9635
+ const scopesToMerge = existingScopes ? [runtimeScopes, trustedPolicy.resourceScopes, modelScopes] : [trustedPolicy.resourceScopes, modelScopes];
9636
+ for (const scopes of scopesToMerge) {
9597
9637
  for (const [operation, resource] of scopes) {
9598
9638
  const existing = result.get(operation);
9599
9639
  if (existing && existing !== resource)
@@ -9603,22 +9643,71 @@ function createWorkflowGuard(options) {
9603
9643
  }
9604
9644
  return result;
9605
9645
  }
9606
- function requiredOperationsFor(trustedPolicy, model, resourceScopes) {
9646
+ function requiredOperationsFor(trustedPolicy, model, resourceScopes, existingOperations = []) {
9607
9647
  return Object.freeze([
9608
9648
  ...new Set([
9609
9649
  ...MANDATORY_OPERATIONS,
9610
9650
  ...runtimeRequired,
9651
+ ...existingOperations,
9611
9652
  ...trustedPolicy.expectedOperations,
9612
9653
  ...model.expectedOperations,
9613
9654
  ...resourceScopes.keys()
9614
9655
  ])
9615
9656
  ]);
9616
9657
  }
9658
+ function resourceIdentitiesMatchUnit(unit) {
9659
+ const expected = new Map(runtimeScopes);
9660
+ for (const [operation, resource] of unit.resourceScopes) {
9661
+ expected.set(operation, resource);
9662
+ }
9663
+ if (currentResourceIdentities.size !== expected.size)
9664
+ return false;
9665
+ for (const [operation, resource] of expected) {
9666
+ if (currentResourceIdentities.get(operation) !== resource)
9667
+ return false;
9668
+ }
9669
+ return true;
9670
+ }
9671
+ function pristineActiveUnit(unit) {
9672
+ return unit.evidence.size === 0 && unit.issues.size === 0 && unit.staleReceiptIds.size === 0 && unit.recoveredReceiptIds.size === 0 && unit.operationStates.size === 0 && unit.ledgerContexts.size === 0 && globalIssue === undefined && transitionsByCall.size === 0 && terminalOperationCalls.size === 0 && currentResourceRevisionIdentities.size === 0 && currentPullRequestFingerprint === undefined && currentWorkspaceIdentity === initialWorkspaceIdentity && currentRepositoryIdentity === initialRepositoryIdentity && currentWorktreeIdentity === initialWorktreeIdentity && resourceIdentitiesMatchUnit(unit);
9673
+ }
9674
+ function declarationChanged(unit, requiredOperations, resourceScopes) {
9675
+ if (JSON.stringify(unit.requiredOperations) !== JSON.stringify(requiredOperations)) {
9676
+ return true;
9677
+ }
9678
+ if (unit.resourceScopes.size !== resourceScopes.size)
9679
+ return true;
9680
+ for (const [operation, resource] of resourceScopes) {
9681
+ if (unit.resourceScopes.get(operation) !== resource)
9682
+ return true;
9683
+ }
9684
+ return false;
9685
+ }
9686
+ function startActiveUnit(parsed, trustedPolicy, unit) {
9687
+ if (!pristineActiveUnit(unit)) {
9688
+ return { status: "rejected", reasonCode: "unit-active" };
9689
+ }
9690
+ const resourceScopes = mergeResourceScopes(trustedPolicy, parsed.resourceScopes, unit.resourceScopes);
9691
+ if (!resourceScopes) {
9692
+ return { status: "rejected", reasonCode: "runtime-scope-conflict" };
9693
+ }
9694
+ const requiredOperations = requiredOperationsFor(trustedPolicy, parsed, resourceScopes, unit.requiredOperations);
9695
+ if (!declarationChanged(unit, requiredOperations, resourceScopes)) {
9696
+ return { status: "rejected", reasonCode: "unit-active" };
9697
+ }
9698
+ unit.requiredOperations = requiredOperations;
9699
+ unit.declaredResourceOperations = Object.freeze([...resourceScopes.keys()]);
9700
+ unit.resourceScopes = resourceScopes;
9701
+ for (const [operation, resource] of resourceScopes) {
9702
+ currentResourceIdentities.set(operation, resource);
9703
+ }
9704
+ return { status: "started", unit: cloneUnit(unit) };
9705
+ }
9617
9706
  function startParsedUnit(parsed, trustedPolicy) {
9618
9707
  if (!epoch)
9619
9708
  return { status: "rejected", reasonCode: "no-active-epoch" };
9620
9709
  if (epoch.unit?.status === "active") {
9621
- return { status: "rejected", reasonCode: "unit-active" };
9710
+ return startActiveUnit(parsed, trustedPolicy, epoch.unit);
9622
9711
  }
9623
9712
  const resourceScopes = mergeResourceScopes(trustedPolicy, parsed.resourceScopes);
9624
9713
  if (!resourceScopes) {
@@ -9706,7 +9795,7 @@ function createWorkflowGuard(options) {
9706
9795
  reasonCode: existing.state === "abandoned" ? "abandoned-transition" : "transition-terminal"
9707
9796
  };
9708
9797
  }
9709
- async function processOperation(input, parsed, unit) {
9798
+ async function processOperation(input, parsed, unit, trustedClassification) {
9710
9799
  if (!unit.requiredOperations.includes(parsed.operation)) {
9711
9800
  markTerminalOperation(parsed, input);
9712
9801
  return { status: "rejected", reasonCode: "operation-not-required" };
@@ -9725,7 +9814,7 @@ function createWorkflowGuard(options) {
9725
9814
  if (prepared.status !== "prepared") {
9726
9815
  return prepared.reasonCode === "call-context-conflict" ? { status: "rejected", reasonCode: "call-context-conflict" } : { status: "rejected", reasonCode: "rejected-operation" };
9727
9816
  }
9728
- const classification = await classifyPreparedOperation(parsed, input, unit);
9817
+ const classification = trustedClassification ?? await classifyPreparedOperation(parsed, input, unit);
9729
9818
  return classification ? finalizeOperation(parsed, input, unit, classification) : { status: "rejected", reasonCode: "guard-unavailable" };
9730
9819
  }
9731
9820
  function rejectParsedOperation(parsed, input, unit, reasonCode) {
@@ -10231,12 +10320,44 @@ function createWorkflowGuard(options) {
10231
10320
  }
10232
10321
  return processOperation(input, parsed, epoch.unit);
10233
10322
  },
10323
+ async observeTrustedRecoveredOperation(input) {
10324
+ const modeResult = evidenceModeResult();
10325
+ if (modeResult)
10326
+ return modeResult;
10327
+ if (!epoch)
10328
+ return { status: "rejected", reasonCode: "no-active-epoch" };
10329
+ if (!epoch.unit)
10330
+ return { status: "rejected", reasonCode: "no-active-unit" };
10331
+ if (epoch.unit.status === "completed") {
10332
+ return { status: "rejected", reasonCode: "unit-completed" };
10333
+ }
10334
+ const terminalResult = terminalOperationResult(input);
10335
+ if (terminalResult)
10336
+ return terminalResult;
10337
+ const parsed = parseReceiptOperationObservation(input);
10338
+ if (!parsed) {
10339
+ markTerminalOperation(undefined, input);
10340
+ return recordGlobalEvidenceIssue("invalid-receipt");
10341
+ }
10342
+ const classification = {
10343
+ outcome: "accepted",
10344
+ category: parsed.operation,
10345
+ attribution: "runtime-verified",
10346
+ result: "success",
10347
+ sideEffect: parsed.operation === "verification" || parsed.operation === "check-readback" || parsed.operation === "review-readback" ? "not-required" : "required",
10348
+ reasonCode: "recognized-command"
10349
+ };
10350
+ return processOperation(input, parsed, epoch.unit, classification);
10351
+ },
10234
10352
  observeReadback(input) {
10235
10353
  const parsed = parseReadback(input);
10236
10354
  if (!parsed)
10237
10355
  return { status: "rejected", reasonCode: "invalid-receipt" };
10238
10356
  return observeRevision(parsed, epoch?.unit);
10239
10357
  },
10358
+ currentOperationContext() {
10359
+ return currentOperationContext();
10360
+ },
10240
10361
  status() {
10241
10362
  return projection();
10242
10363
  },
@@ -11156,6 +11277,7 @@ function createSessionRuntime(options) {
11156
11277
  const rollupKey = `${host.sessionID}:${host.callID}:${childSessionID}`;
11157
11278
  if (rolledUpChildren.has(rollupKey))
11158
11279
  return;
11280
+ const parentBefore = guard.currentOperationContext();
11159
11281
  const children = await options.hostReadback.listChildren(host.sessionID);
11160
11282
  if (!children.some((child) => child.sessionId === childSessionID && child.parentID === host.sessionID)) {
11161
11283
  markUnavailable();
@@ -11203,18 +11325,27 @@ function createSessionRuntime(options) {
11203
11325
  const currentStatus = guard.status();
11204
11326
  if (!currentStatus.epoch || !currentStatus.unit)
11205
11327
  return;
11206
- const expectedWorkspace = ledger.digestIdentity("workspace", options.workspaceIdentity);
11207
- const expectedRepository = ledger.digestIdentity("repository", options.repositoryIdentity ?? current.snapshot.repositoryRevisionDigest);
11208
- const expectedWorktree = ledger.digestIdentity("worktree", options.worktreeIdentity ?? current.snapshot.worktreeRevisionDigest);
11209
- let minted = false;
11328
+ const expectedWorkspace = childLedger.digestIdentity("workspace", parentBefore.workspaceIdentity);
11329
+ const expectedRepository = childLedger.digestIdentity("repository", current.snapshot.repositoryRevisionDigest);
11330
+ const expectedWorktree = childLedger.digestIdentity("worktree", current.snapshot.worktreeRevisionDigest);
11331
+ const candidates = [];
11210
11332
  for (const childReceipt of recovered.receipts) {
11211
11333
  const operation = childReceipt.canonical.operation;
11212
11334
  if (!localOperation(operation))
11213
11335
  continue;
11214
- if (childReceipt.canonical.workspaceDigest !== expectedWorkspace || childReceipt.canonical.repositoryDigest !== expectedRepository || childReceipt.canonical.worktreeDigest !== expectedWorktree) {
11336
+ if (childReceipt.canonical.workspaceDigest !== expectedWorkspace) {
11215
11337
  markUnavailable();
11216
11338
  return;
11217
11339
  }
11340
+ if (childReceipt.canonical.repositoryDigest !== expectedRepository || childReceipt.canonical.worktreeDigest !== expectedWorktree) {
11341
+ continue;
11342
+ }
11343
+ candidates.push(childReceipt);
11344
+ }
11345
+ let minted = false;
11346
+ for (const childReceipt of candidates) {
11347
+ const operation = childReceipt.canonical.operation;
11348
+ const parentContext = guard.currentOperationContext();
11218
11349
  const callID = `task-${host.callID}-${childReceipt.canonical.receiptId}`;
11219
11350
  const observation = {
11220
11351
  callId: callID,
@@ -11223,12 +11354,12 @@ function createSessionRuntime(options) {
11223
11354
  context: {
11224
11355
  epochId: currentStatus.epoch.epochId,
11225
11356
  unitId: currentStatus.unit.unitId,
11226
- workspaceIdentity: options.workspaceIdentity,
11227
- repositoryIdentity: operation === "commit" ? options.repositoryIdentity ?? current.snapshot.repositoryRevisionDigest : current.snapshot.repositoryRevisionDigest,
11228
- worktreeIdentity: operation === "implementation" ? options.worktreeIdentity ?? current.snapshot.worktreeRevisionDigest : current.snapshot.worktreeRevisionDigest
11357
+ workspaceIdentity: parentContext.workspaceIdentity,
11358
+ ...parentContext.repositoryIdentity ? { repositoryIdentity: parentContext.repositoryIdentity } : {},
11359
+ ...parentContext.worktreeIdentity ? { worktreeIdentity: parentContext.worktreeIdentity } : {}
11229
11360
  },
11230
11361
  after: {
11231
- workspaceIdentity: options.workspaceIdentity,
11362
+ workspaceIdentity: parentContext.workspaceIdentity,
11232
11363
  repositoryIdentity: current.snapshot.repositoryRevisionDigest,
11233
11364
  worktreeIdentity: current.snapshot.worktreeRevisionDigest
11234
11365
  },
@@ -11238,7 +11369,7 @@ function createSessionRuntime(options) {
11238
11369
  noOp: false
11239
11370
  }
11240
11371
  };
11241
- const result = await guard.observeOperation(observation);
11372
+ const result = await guard.observeTrustedRecoveredOperation(observation);
11242
11373
  if (result.status === "accepted") {
11243
11374
  const receipt = receiptForOperation(callID, operation);
11244
11375
  if (receipt)
@@ -11433,13 +11564,21 @@ function createSessionRuntime(options) {
11433
11564
  }
11434
11565
  return result;
11435
11566
  }
11567
+ function progressionResourceScopes(unit) {
11568
+ return [...unit.resourceScopes].sort((first, second) => first.operation.localeCompare(second.operation)).map((scope) => ({
11569
+ operation: scope.operation,
11570
+ resourceIdentity: ledger.digestIdentity("resource", scope.resourceIdentity)
11571
+ }));
11572
+ }
11436
11573
  function writeStartResult(output, result) {
11437
11574
  if (!isRecord7(output))
11438
11575
  return;
11576
+ const existingMetadata = isRecord7(output.metadata) ? output.metadata : {};
11439
11577
  if (result.status === "started") {
11440
11578
  output.title = "Workflow unit started";
11441
11579
  output.output = JSON.stringify({ status: "started" });
11442
11580
  output.metadata = {
11581
+ ...existingMetadata,
11443
11582
  ...metadata2(),
11444
11583
  workflowGuard: { status: "started" }
11445
11584
  };
@@ -11452,6 +11591,7 @@ function createSessionRuntime(options) {
11452
11591
  reasonCode
11453
11592
  });
11454
11593
  output.metadata = {
11594
+ ...existingMetadata,
11455
11595
  ...metadata2(),
11456
11596
  workflowGuard: {
11457
11597
  status: "rejected",
@@ -11783,7 +11923,7 @@ function createSessionRuntime(options) {
11783
11923
  unitId: status.unit.unitId,
11784
11924
  family: status.epoch.family,
11785
11925
  requiredOperations: status.unit.requiredOperations,
11786
- resourceScopes: [],
11926
+ resourceScopes: progressionResourceScopes(status.unit),
11787
11927
  state: "started",
11788
11928
  transitionDigest: ledger.digestIdentity("call", host.callID)
11789
11929
  }));
@@ -11840,7 +11980,7 @@ function createSessionRuntime(options) {
11840
11980
  unitId: unit.unitId,
11841
11981
  family: epoch.family,
11842
11982
  requiredOperations: unit.requiredOperations,
11843
- resourceScopes: [],
11983
+ resourceScopes: progressionResourceScopes(unit),
11844
11984
  state: "started",
11845
11985
  transitionDigest: ledger.digestIdentity("call", host.callID)
11846
11986
  }));
@@ -12205,9 +12345,10 @@ function createSessionRuntime(options) {
12205
12345
  const marker = projectReceiptMintMarker(receipt, ledger.getSessionSalt());
12206
12346
  if (!marker)
12207
12347
  return;
12348
+ const existing = output.metadata[SYSTEMATIC_WORKFLOW_RECEIPT_METADATA_KEY];
12208
12349
  output.metadata = {
12209
12350
  ...output.metadata,
12210
- [SYSTEMATIC_WORKFLOW_RECEIPT_METADATA_KEY]: marker
12351
+ [SYSTEMATIC_WORKFLOW_RECEIPT_METADATA_KEY]: existing ? Array.isArray(existing) ? [...existing, marker] : [existing, marker] : marker
12211
12352
  };
12212
12353
  }
12213
12354
  function mergeProgressionMarker(output, marker) {
@@ -87,6 +87,72 @@ export declare const CategoryOverlaySchema: z.ZodObject<{
87
87
  deny: "deny";
88
88
  }>>]>>>;
89
89
  }, z.core.$strict>;
90
+ export declare const PiSubagentsAgentOverlaySchema: z.ZodObject<{
91
+ thinking: z.ZodOptional<z.ZodEnum<{
92
+ off: "off";
93
+ minimal: "minimal";
94
+ low: "low";
95
+ medium: "medium";
96
+ high: "high";
97
+ xhigh: "xhigh";
98
+ max: "max";
99
+ }>>;
100
+ max_turns: z.ZodOptional<z.ZodNumber>;
101
+ tools: z.ZodOptional<z.ZodString>;
102
+ skills: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<true>, z.ZodString]>>;
103
+ }, z.core.$strict>;
104
+ export declare const PiSubagentsCategoryOverlaySchema: z.ZodObject<{
105
+ thinking: z.ZodOptional<z.ZodEnum<{
106
+ off: "off";
107
+ minimal: "minimal";
108
+ low: "low";
109
+ medium: "medium";
110
+ high: "high";
111
+ xhigh: "xhigh";
112
+ max: "max";
113
+ }>>;
114
+ max_turns: z.ZodOptional<z.ZodNumber>;
115
+ tools: z.ZodOptional<z.ZodString>;
116
+ skills: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<true>, z.ZodString]>>;
117
+ }, z.core.$strict>;
118
+ export declare const PiSubagentsSchema: z.ZodDefault<z.ZodObject<{
119
+ categories: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
120
+ thinking: z.ZodOptional<z.ZodEnum<{
121
+ off: "off";
122
+ minimal: "minimal";
123
+ low: "low";
124
+ medium: "medium";
125
+ high: "high";
126
+ xhigh: "xhigh";
127
+ max: "max";
128
+ }>>;
129
+ max_turns: z.ZodOptional<z.ZodNumber>;
130
+ tools: z.ZodOptional<z.ZodString>;
131
+ skills: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<true>, z.ZodString]>>;
132
+ }, z.core.$strict>>>;
133
+ agents: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
134
+ thinking: z.ZodOptional<z.ZodEnum<{
135
+ off: "off";
136
+ minimal: "minimal";
137
+ low: "low";
138
+ medium: "medium";
139
+ high: "high";
140
+ xhigh: "xhigh";
141
+ max: "max";
142
+ }>>;
143
+ max_turns: z.ZodOptional<z.ZodNumber>;
144
+ tools: z.ZodOptional<z.ZodString>;
145
+ skills: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<true>, z.ZodString]>>;
146
+ }, z.core.$strict>>>;
147
+ }, z.core.$strict>>;
148
+ /**
149
+ * Fields in PiSubagentsAgentOverlaySchema/PiSubagentsCategoryOverlaySchema that
150
+ * require a project-or-higher trust source. Project config cannot grant
151
+ * `thinking`, `tools`, or `skills` to an exported persona; `max_turns` is
152
+ * trust-any. Mirrors the hand-coded `PI_SUBAGENTS_PROTECTED_FIELDS` set in
153
+ * `src/lib/config.ts`.
154
+ */
155
+ export declare const PI_SUBAGENTS_PROTECTED_FIELDS: readonly string[];
90
156
  export declare const BootstrapSchema: z.ZodObject<{
91
157
  enabled: z.ZodDefault<z.ZodBoolean>;
92
158
  file: z.ZodOptional<z.ZodString>;
@@ -22,6 +22,10 @@ export interface SourceAwareConfigResult {
22
22
  config: SystematicConfig;
23
23
  overlays: SourcedOverlayConfigMap;
24
24
  }
25
+ export interface PiSubagentsOverlayMap {
26
+ categories?: OverlayConfigMap;
27
+ agents?: OverlayConfigMap;
28
+ }
25
29
  export interface SystematicConfig {
26
30
  disabled_skills: string[];
27
31
  disabled_agents: string[];
@@ -30,6 +34,7 @@ export interface SystematicConfig {
30
34
  workflow_guard: WorkflowGuardConfig;
31
35
  agents?: OverlayConfigMap;
32
36
  categories?: OverlayConfigMap;
37
+ pi_subagents?: PiSubagentsOverlayMap;
33
38
  skills_as_commands: boolean;
34
39
  }
35
40
  export declare const DEFAULT_CONFIG: SystematicConfig;
@@ -47,8 +52,17 @@ export declare function computeDroppedNames(names: readonly string[], allowedSet
47
52
  * Passing a fresh set per load ensures no cross-load suppression.
48
53
  */
49
54
  export declare function warnDroppedNames(dropped: string[], field: string, warned: Set<string>, removalVersion?: string): void;
50
- export declare function loadConfig(projectDir: string): SystematicConfig;
51
- export declare function loadConfigWithSources(projectDir: string): SourceAwareConfigResult;
55
+ export interface LoadConfigOptions {
56
+ /**
57
+ * When false, the project-level config source (`<cwd>/.opencode/systematic.json`)
58
+ * is not loaded at all — not merged, not trust-stripped, entirely absent from
59
+ * the source chain. Used by global-scoped pi-subagents export so it never
60
+ * absorbs cwd project overlays (plan R7/R19). Defaults to true.
61
+ */
62
+ includeProject?: boolean;
63
+ }
64
+ export declare function loadConfig(projectDir: string, options?: LoadConfigOptions): SystematicConfig;
65
+ export declare function loadConfigWithSources(projectDir: string, options?: LoadConfigOptions): SourceAwareConfigResult;
52
66
  export declare function getConfigPaths(projectDir: string): {
53
67
  customConfig?: string | undefined;
54
68
  customDir?: string | undefined;