@fro.bot/systematic 3.16.3 → 3.16.5

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.
package/dist/cli.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { type CapabilityClock, type CapabilityOutputSink, type ConfigObservationMetadata } from './lib/capability-snapshot.js';
3
- interface CapabilityCliRoots {
3
+ export interface CapabilityCliRoots {
4
4
  readonly agentsRoot: string;
5
5
  readonly cwd: string;
6
6
  readonly homeDir: string;
package/dist/index.js CHANGED
@@ -11776,6 +11776,15 @@ function createSessionRuntime(options, operationObservers, recoveredOperationCon
11776
11776
  }
11777
11777
  return source;
11778
11778
  }
11779
+ function withoutStaleConditionalMetadata(existingMetadata) {
11780
+ if (!("questionAttestation" in existingMetadata))
11781
+ return existingMetadata;
11782
+ if (existingMetadata.sourceDigest !== currentMarkerSource().source) {
11783
+ return existingMetadata;
11784
+ }
11785
+ const { questionAttestation: _stale, ...rest } = existingMetadata;
11786
+ return rest;
11787
+ }
11779
11788
  function metadata2() {
11780
11789
  const source = currentMarkerSource();
11781
11790
  const result = {
@@ -11803,7 +11812,7 @@ function createSessionRuntime(options, operationObservers, recoveredOperationCon
11803
11812
  result.satisfiedCount = source.debug.satisfiedCount;
11804
11813
  result.missingCount = source.debug.missingCount;
11805
11814
  result.family = source.debug.family;
11806
- result.status = source.debug.status;
11815
+ result.debugEpochStatus = source.debug.status;
11807
11816
  }
11808
11817
  return result;
11809
11818
  }
@@ -11821,7 +11830,7 @@ function createSessionRuntime(options, operationObservers, recoveredOperationCon
11821
11830
  output.title = "Workflow unit started";
11822
11831
  output.output = JSON.stringify({ status: "started" });
11823
11832
  output.metadata = {
11824
- ...existingMetadata,
11833
+ ...withoutStaleConditionalMetadata(existingMetadata),
11825
11834
  ...metadata2(),
11826
11835
  workflowGuard: { status: "started" }
11827
11836
  };
@@ -11834,7 +11843,7 @@ function createSessionRuntime(options, operationObservers, recoveredOperationCon
11834
11843
  reasonCode
11835
11844
  });
11836
11845
  output.metadata = {
11837
- ...existingMetadata,
11846
+ ...withoutStaleConditionalMetadata(existingMetadata),
11838
11847
  ...metadata2(),
11839
11848
  workflowGuard: {
11840
11849
  status: "rejected",
@@ -11842,34 +11851,53 @@ function createSessionRuntime(options, operationObservers, recoveredOperationCon
11842
11851
  }
11843
11852
  };
11844
11853
  }
11845
- function writeCompletionResult(output, result, target) {
11854
+ function targetMetadataField(target) {
11855
+ return target ? { target } : {};
11856
+ }
11857
+ function writeSuccessfulCompletionResult(output, target) {
11846
11858
  if (!isRecord7(output))
11847
11859
  return;
11848
- if (result.status === "completed" || result.status === "duplicate") {
11849
- output.title = "Workflow transition completed";
11850
- output.output = `workflow guard completed ${target}`;
11851
- output.metadata = {
11852
- ...metadata2(),
11853
- workflowGuard: { status: "completed", target }
11854
- };
11860
+ const existingMetadata = isRecord7(output.metadata) ? output.metadata : {};
11861
+ output.title = "Workflow transition completed";
11862
+ output.output = `workflow guard completed ${target}`;
11863
+ output.metadata = {
11864
+ ...withoutStaleConditionalMetadata(existingMetadata),
11865
+ ...metadata2(),
11866
+ workflowGuard: { status: "completed", target }
11867
+ };
11868
+ }
11869
+ function writeTerminalCompletionResult(output, result, target) {
11870
+ if (!isRecord7(output))
11855
11871
  return;
11856
- }
11857
- const reasonCode = "reasonCode" in result ? result.reasonCode : "guard-unavailable";
11858
- const resultStatus = result.status === "waiting" ? "unavailable" : result.status;
11872
+ const existingMetadata = isRecord7(output.metadata) ? output.metadata : {};
11859
11873
  output.title = "Workflow transition unavailable";
11860
11874
  output.output = JSON.stringify({
11861
- status: resultStatus,
11862
- reasonCode
11875
+ status: result.status,
11876
+ reasonCode: result.reasonCode
11863
11877
  });
11864
11878
  output.metadata = {
11879
+ ...withoutStaleConditionalMetadata(existingMetadata),
11865
11880
  ...metadata2(),
11866
11881
  workflowGuard: {
11867
- status: resultStatus,
11868
- target,
11869
- reasonCode
11882
+ status: result.status,
11883
+ ...targetMetadataField(target),
11884
+ reasonCode: result.reasonCode
11870
11885
  }
11871
11886
  };
11872
11887
  }
11888
+ function writeAbandonedCompletionResult(output, target, reasonCode) {
11889
+ writeTerminalCompletionResult(output, { target, status: "unavailable", reasonCode }, target);
11890
+ }
11891
+ function writeAbandonedCompletionMetadata(output, target, reasonCode) {
11892
+ if (!isRecord7(output))
11893
+ return;
11894
+ const existingMetadata = isRecord7(output.metadata) ? output.metadata : {};
11895
+ output.metadata = {
11896
+ ...withoutStaleConditionalMetadata(existingMetadata),
11897
+ ...metadata2(),
11898
+ workflowGuard: { status: "unavailable", target, reasonCode }
11899
+ };
11900
+ }
11873
11901
  function prepareSkill(host, args2) {
11874
11902
  const skill = normalizeSkill(host.tool, args2);
11875
11903
  if (!skill)
@@ -12286,6 +12314,14 @@ function createSessionRuntime(options, operationObservers, recoveredOperationCon
12286
12314
  callId: pending.callID,
12287
12315
  transitionId: pending.transitionId
12288
12316
  });
12317
+ writeAbandonedCompletionMetadata(output, pending.target, "failed-operation");
12318
+ abandonedCompletes.set(callDigest, pending.target);
12319
+ terminalCompletes.set(callDigest, {
12320
+ target: pending.target,
12321
+ status: "unavailable",
12322
+ reasonCode: "failed-operation",
12323
+ metadataOnly: true
12324
+ });
12289
12325
  return;
12290
12326
  }
12291
12327
  const result = guard.finalizeTransition({
@@ -12295,7 +12331,7 @@ function createSessionRuntime(options, operationObservers, recoveredOperationCon
12295
12331
  });
12296
12332
  if (result.status === "completed" || result.status === "duplicate") {
12297
12333
  finalizedCompletes.set(callDigest, pending);
12298
- writeCompletionResult(output, result, pending.target);
12334
+ writeSuccessfulCompletionResult(output, pending.target);
12299
12335
  return;
12300
12336
  }
12301
12337
  markUnavailable();
@@ -12307,7 +12343,14 @@ function createSessionRuntime(options, operationObservers, recoveredOperationCon
12307
12343
  };
12308
12344
  abandonedCompletes.set(callDigest, pending.target);
12309
12345
  terminalCompletes.set(callDigest, terminal);
12310
- writeCompletionResult(output, terminal, pending.target);
12346
+ writeTerminalCompletionResult(output, terminal, pending.target);
12347
+ }
12348
+ function writeTerminalReplay(output, terminal, target) {
12349
+ if (terminal.metadataOnly) {
12350
+ writeAbandonedCompletionMetadata(output, target, terminal.reasonCode);
12351
+ return;
12352
+ }
12353
+ writeTerminalCompletionResult(output, terminal, target);
12311
12354
  }
12312
12355
  function replayTerminalComplete(callDigest, finalTarget, output) {
12313
12356
  const questionChallengeID = blockedQuestionCalls.get(callDigest);
@@ -12318,10 +12361,15 @@ function createSessionRuntime(options, operationObservers, recoveredOperationCon
12318
12361
  return true;
12319
12362
  }
12320
12363
  }
12364
+ const terminal = terminalCompletes.get(callDigest);
12365
+ if (terminal?.target) {
12366
+ writeTerminalReplay(output, terminal, terminal.target);
12367
+ blockedCompletes.delete(callDigest);
12368
+ return true;
12369
+ }
12321
12370
  const replay = finalizedCompletes.get(callDigest);
12322
12371
  const abandonedTarget = abandonedCompletes.get(callDigest);
12323
12372
  const blockedTarget = blockedCompletes.get(callDigest);
12324
- const terminal = terminalCompletes.get(callDigest);
12325
12373
  const expectedTarget = replay?.target ?? abandonedTarget ?? blockedTarget;
12326
12374
  if (!expectedTarget)
12327
12375
  return false;
@@ -12331,8 +12379,6 @@ function createSessionRuntime(options, operationObservers, recoveredOperationCon
12331
12379
  }
12332
12380
  if (replay)
12333
12381
  replayComplete(callDigest);
12334
- if (terminal)
12335
- writeCompletionResult(output, terminal, expectedTarget);
12336
12382
  blockedCompletes.delete(callDigest);
12337
12383
  return true;
12338
12384
  }
@@ -12440,19 +12486,37 @@ function createSessionRuntime(options, operationObservers, recoveredOperationCon
12440
12486
  }
12441
12487
  return readbacks;
12442
12488
  }
12489
+ function finishMismatchedTarget(callDigest, pending, output) {
12490
+ abandonComplete(callDigest, pending, true);
12491
+ terminalCompletes.set(callDigest, {
12492
+ target: pending.target,
12493
+ status: "unavailable",
12494
+ reasonCode: "invalid-transition"
12495
+ });
12496
+ markUnavailable();
12497
+ writeAbandonedCompletionResult(output, pending.target, "invalid-transition");
12498
+ }
12499
+ function finishUnreplayableComplete(finalTarget, output) {
12500
+ markUnavailable();
12501
+ writeAbandonedCompletionResult(output, finalTarget, "guard-unavailable");
12502
+ }
12503
+ function finishUnavailableReadback(callDigest, pending, output) {
12504
+ abandonComplete(callDigest, pending, true);
12505
+ markUnavailable();
12506
+ writeAbandonedCompletionResult(output, pending.target, "finalization-failed");
12507
+ }
12443
12508
  async function finishComplete(host, output) {
12444
12509
  const callDigest = digestCall(ledger, host.callID);
12445
12510
  const pending = pendingCompletes.get(callDigest);
12446
12511
  const finalTarget = normalizeTarget(host.args);
12447
12512
  if (pending && (!finalTarget || finalTarget !== pending.target)) {
12448
- abandonComplete(callDigest, pending, false);
12449
- markUnavailable();
12513
+ finishMismatchedTarget(callDigest, pending, output);
12450
12514
  return;
12451
12515
  }
12452
12516
  if (!pending) {
12453
12517
  if (replayTerminalComplete(callDigest, finalTarget, output))
12454
12518
  return;
12455
- markUnavailable();
12519
+ finishUnreplayableComplete(finalTarget, output);
12456
12520
  return;
12457
12521
  }
12458
12522
  if (!options.observer) {
@@ -12461,8 +12525,7 @@ function createSessionRuntime(options, operationObservers, recoveredOperationCon
12461
12525
  }
12462
12526
  const readbacks = await completionReadbacks();
12463
12527
  if (readbacks.status === "unavailable") {
12464
- abandonComplete(callDigest, pending, true);
12465
- markUnavailable();
12528
+ finishUnavailableReadback(callDigest, pending, output);
12466
12529
  return;
12467
12530
  }
12468
12531
  finalizeComplete(callDigest, pending, output, readbacks.status === "ready" ? readbacks.readbacks : undefined);
@@ -1,4 +1,6 @@
1
1
  import type { Config } from '@opencode-ai/plugin';
2
+ import type { AgentConfig } from '@opencode-ai/sdk';
3
+ import { type PermissionSetting } from './validation.js';
2
4
  export interface ConfigHandlerDeps {
3
5
  directory: string;
4
6
  bundledSkillsDir: string;
@@ -11,6 +13,41 @@ export interface ConfigHandlerDeps {
11
13
  }
12
14
  export declare function toTitleCase(name: string): string;
13
15
  export declare function formatAgentDescription(name: string, description: string | undefined): string;
16
+ /**
17
+ * Per-tool permission rule map as actually emitted by {@link permissionFromRules}:
18
+ * each key is a match pattern (`'*'` or an exact name) mapped to a setting.
19
+ */
20
+ export type AgentPermissionRuleMap = Record<string, PermissionSetting>;
21
+ /**
22
+ * Agent permission shape as emitted onto `AgentConfig.permission`, narrowed at
23
+ * this module's boundary to include `skill`. The `@opencode-ai/sdk` v1 `Config`
24
+ * type this module targets omits `skill` from `AgentConfig['permission']`
25
+ * (it only appears on the SDK's v2 `PermissionRuleConfig` surface), but
26
+ * `applyPermissionOverlay`/`addManagedSkillRules` genuinely write a `skill`
27
+ * rule map identical in shape to `bash`. This type documents and reads that
28
+ * real runtime shape without migrating the module to the v2 type surface.
29
+ *
30
+ * `permissionFromRules` emits a rule map for whatever tool key gets passed to
31
+ * `setPermissionRule` — not just the named keys below (e.g. `task`, per
32
+ * `PermissionConfig` in validation.ts and the `task` overlay field applied at
33
+ * config-handler.ts's overlay pass). The named keys stay for documentation of
34
+ * the common cases; the index signature covers every other emitted tool key.
35
+ */
36
+ export interface EmittedAgentPermission {
37
+ edit?: AgentPermissionRuleMap;
38
+ bash?: AgentPermissionRuleMap;
39
+ webfetch?: AgentPermissionRuleMap;
40
+ doom_loop?: AgentPermissionRuleMap;
41
+ external_directory?: AgentPermissionRuleMap;
42
+ skill?: AgentPermissionRuleMap;
43
+ readonly [tool: string]: AgentPermissionRuleMap | undefined;
44
+ }
45
+ /**
46
+ * Read an agent's emitted permission map, narrowed to include `skill` (see
47
+ * {@link EmittedAgentPermission}). Use this instead of `AgentConfig['permission']`
48
+ * when a caller needs to observe the `skill` rule map this module writes.
49
+ */
50
+ export declare function readEmittedAgentPermission(agent: AgentConfig | undefined): EmittedAgentPermission | undefined;
14
51
  /**
15
52
  * Create the config hook handler for the Systematic plugin.
16
53
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fro.bot/systematic",
3
- "version": "3.16.3",
3
+ "version": "3.16.5",
4
4
  "description": "Compound-engineering loops for OpenCode, Pi, and Claude Code",
5
5
  "type": "module",
6
6
  "homepage": "https://fro.bot/systematic",
@@ -40,6 +40,8 @@
40
40
  "test:integration": "bun test tests/integration",
41
41
  "test:all": "bun test",
42
42
  "typecheck": "tsc --noEmit",
43
+ "typecheck:scripts": "tsc -p tsconfig.scripts.json",
44
+ "typecheck:all": "tsc -p tsconfig.tests.json",
43
45
  "lint": "biome check .",
44
46
  "fix": "bun run lint --fix",
45
47
  "docs:dev": "bun run --cwd docs dev",
@@ -93,10 +95,10 @@
93
95
  }
94
96
  },
95
97
  "devDependencies": {
96
- "@biomejs/biome": "2.5.11",
98
+ "@biomejs/biome": "2.5.12",
97
99
  "@earendil-works/pi-coding-agent": "0.83.0",
98
- "@opencode-ai/plugin": "1.18.21",
99
- "@opencode-ai/sdk": "1.18.21",
100
+ "@opencode-ai/plugin": "1.18.29",
101
+ "@opencode-ai/sdk": "1.18.29",
100
102
  "@semantic-release/exec": "7.1.0",
101
103
  "@tintinweb/pi-subagents": "0.14.3",
102
104
  "@types/bun": "latest",
@@ -110,7 +112,7 @@
110
112
  "rimraf": "6.1.3",
111
113
  "semantic-release": "25.0.9",
112
114
  "semantic-release-export-data": "1.2.0",
113
- "typebox": "1.3.25",
115
+ "typebox": "1.3.26",
114
116
  "typescript": "7.0.2"
115
117
  },
116
118
  "dependencies": {