@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 +1 -1
- package/dist/index.js +92 -29
- package/dist/lib/config-handler.d.ts +37 -0
- package/package.json +7 -5
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.
|
|
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
|
|
11854
|
+
function targetMetadataField(target) {
|
|
11855
|
+
return target ? { target } : {};
|
|
11856
|
+
}
|
|
11857
|
+
function writeSuccessfulCompletionResult(output, target) {
|
|
11846
11858
|
if (!isRecord7(output))
|
|
11847
11859
|
return;
|
|
11848
|
-
|
|
11849
|
-
|
|
11850
|
-
|
|
11851
|
-
|
|
11852
|
-
|
|
11853
|
-
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+
"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.
|
|
98
|
+
"@biomejs/biome": "2.5.12",
|
|
97
99
|
"@earendil-works/pi-coding-agent": "0.83.0",
|
|
98
|
-
"@opencode-ai/plugin": "1.18.
|
|
99
|
-
"@opencode-ai/sdk": "1.18.
|
|
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.
|
|
115
|
+
"typebox": "1.3.26",
|
|
114
116
|
"typescript": "7.0.2"
|
|
115
117
|
},
|
|
116
118
|
"dependencies": {
|