@sabaiway/agent-workflow-kit 14.22.0 → 14.24.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 (34) hide show
  1. package/CHANGELOG.md +74 -0
  2. package/README.md +1 -1
  3. package/SKILL.md +1 -1
  4. package/bridges/antigravity-cli-bridge/SKILL.md +16 -10
  5. package/bridges/antigravity-cli-bridge/bin/agy-jev.test.mjs +105 -0
  6. package/bridges/antigravity-cli-bridge/bin/agy-review.sh +20 -6
  7. package/bridges/antigravity-cli-bridge/bin/agy.sh +26 -11
  8. package/bridges/antigravity-cli-bridge/capability.json +3 -3
  9. package/bridges/antigravity-cli-bridge/references/models-and-flags.md +4 -3
  10. package/bridges/antigravity-cli-bridge/setup/README.md +2 -1
  11. package/bridges/codex-cli-bridge/SKILL.md +44 -24
  12. package/bridges/codex-cli-bridge/bin/codex-exec-jev.test.mjs +385 -0
  13. package/bridges/codex-cli-bridge/bin/codex-exec.sh +262 -42
  14. package/bridges/codex-cli-bridge/bin/codex-exec.test.mjs +16 -18
  15. package/bridges/codex-cli-bridge/bin/codex-model-posture.test.mjs +16 -7
  16. package/bridges/codex-cli-bridge/bin/codex-review-honesty.test.mjs +1 -0
  17. package/bridges/codex-cli-bridge/bin/codex-review-jev.test.mjs +301 -0
  18. package/bridges/codex-cli-bridge/bin/codex-review.sh +240 -26
  19. package/bridges/codex-cli-bridge/bin/codex-review.test.mjs +9 -9
  20. package/bridges/codex-cli-bridge/capability.json +10 -10
  21. package/bridges/codex-cli-bridge/references/driving-codex.md +18 -14
  22. package/bridges/codex-cli-bridge/references/sandbox-and-flags.md +61 -22
  23. package/bridges/codex-cli-bridge/setup/README.md +8 -5
  24. package/capability.json +1 -1
  25. package/package.json +1 -1
  26. package/references/modes/recommendations.md +5 -4
  27. package/references/modes/velocity.md +1 -1
  28. package/tools/ack-write.mjs +1 -1
  29. package/tools/bridge-sandbox-recipe.mjs +40 -0
  30. package/tools/detect-backends.mjs +3 -3
  31. package/tools/manifest/schema.md +9 -9
  32. package/tools/manifest/validate.mjs +5 -5
  33. package/tools/recommendations.mjs +17 -46
  34. package/tools/velocity-profile.mjs +64 -14
@@ -14,6 +14,7 @@ import { findOnPath } from './detect-backends.mjs';
14
14
  import { SHELL_METACHARACTERS, hasShellMetacharacter, isSeedablePathToken } from './repo-lex.mjs';
15
15
  import { isDirectRun } from './direct-run.mjs';
16
16
  import { compareSemver } from './semver-lite.mjs';
17
+ import { bundledSandboxRecipe } from './bridge-sandbox-recipe.mjs';
17
18
  // The declared-path resolution + segment containment the allowWrite degrade shares with the
18
19
  // advisor's worktrees-dir convergence lane — ONE leaf, so the two readings cannot drift.
19
20
  import { resolveDeclaredDir, dirCovers, isResolvableDeclaredEntry } from './declared-paths.mjs';
@@ -388,9 +389,11 @@ dry-run preview byte-strings. Never touches settings.local.json.
388
389
  --bridge-tier (own consent) seeds the bridge REVIEW wrappers' CODE mode for PLACED bridges
389
390
  (codex-review code, agy-review code - never the execution/probe wrappers, never plan/diff modes)
390
391
  + the quoted grounding pre-step rule, and the wrapper names into sandbox.excludedCommands (they
391
- need network - ${HOST_HONORS_QUALIFIER} the harness runs them outside the sandbox). Consented
392
- posture: an auto-allowed review wrapper runs UNATTENDED and sends the assembled repo payload to its
393
- subscription backend (see the printed tier notice).
392
+ need network - ${HOST_HONORS_QUALIFIER} the harness runs them outside the sandbox). It also
393
+ seeds the placed bridges' declared hosts into sandbox.network.allowedDomains - ${HOST_HONORS_QUALIFIER}
394
+ every sandboxed command of the project can then reach those hosts; the writable state dirs stay
395
+ hand-apply. Consented posture: an auto-allowed review wrapper runs UNATTENDED and sends the
396
+ assembled repo payload to its subscription backend (see the printed tier notice).
394
397
 
395
398
  --autonomy renders docs/ai/autonomy.json into the settings blocks it OWNS — the sandbox block +
396
399
  permissions.ask/deny red-lines + permissions.defaultMode. POLICY-ONLY: never seeds the allowlist and
@@ -634,7 +637,7 @@ const assertTargetWritable = (absPath, deps = {}) => {
634
637
 
635
638
  const formatJson = (data, eol) => `${JSON.stringify(data, null, SETTINGS_JSON_INDENT).replace(JSON_NEWLINE_PATTERN, eol)}${eol}`;
636
639
 
637
- const mergeProjectSettings = (projectData, toAdd, acceptEdits, excludedToAdd = []) => {
640
+ const mergeProjectSettings = (projectData, toAdd, acceptEdits, excludedToAdd = [], hostsToAdd = []) => {
638
641
  const base = projectData ?? {};
639
642
  const permissions = getPermissions(base);
640
643
  const allow = getAllowEntries(base);
@@ -644,15 +647,26 @@ const mergeProjectSettings = (projectData, toAdd, acceptEdits, excludedToAdd = [
644
647
  allow: mergedAllow,
645
648
  ...(acceptEdits === true ? { defaultMode: ACCEPT_EDITS_MODE } : {}),
646
649
  };
647
- // The bridge tier's second surface: sandbox.excludedCommands (merge-don't-clobber — foreign
648
- // entries and sandbox sub-keys preserved; only the tier's wrapper names append, deduped). The
649
- // flagless/kit-tools paths pass no entries, so the sandbox block stays untouched for them.
650
+ // The bridge tier's second and third surfaces: sandbox.excludedCommands and
651
+ // sandbox.network.allowedDomains (merge-don't-clobber — foreign entries and sandbox sub-keys
652
+ // preserved; only the tier's entries append, deduped). The flagless/kit-tools paths pass no
653
+ // entries, so the sandbox block stays untouched for them.
650
654
  const existingSandbox = isJsonObject(base.sandbox) ? base.sandbox : {};
651
655
  const existingExcluded = Array.isArray(existingSandbox.excludedCommands) ? existingSandbox.excludedCommands : [];
652
656
  const newExcluded = excludedToAdd.filter((cmd) => !existingExcluded.includes(cmd));
653
- const sandboxBlock = newExcluded.length
654
- ? { sandbox: { ...existingSandbox, excludedCommands: [...existingExcluded, ...newExcluded] } }
655
- : {};
657
+ const existingNetwork = isJsonObject(existingSandbox.network) ? existingSandbox.network : {};
658
+ const existingHosts = Array.isArray(existingNetwork.allowedDomains) ? existingNetwork.allowedDomains : [];
659
+ const newHosts = hostsToAdd.filter((host) => !existingHosts.includes(host));
660
+ const sandboxBlock =
661
+ newExcluded.length || newHosts.length
662
+ ? {
663
+ sandbox: {
664
+ ...existingSandbox,
665
+ ...(newExcluded.length ? { excludedCommands: [...existingExcluded, ...newExcluded] } : {}),
666
+ ...(newHosts.length ? { network: { ...existingNetwork, allowedDomains: [...existingHosts, ...newHosts] } } : {}),
667
+ },
668
+ }
669
+ : {};
656
670
  return { ...base, permissions: mergedPermissions, ...sandboxBlock };
657
671
  };
658
672
 
@@ -683,9 +697,11 @@ const formatKitTier = (result) =>
683
697
  // resolution states the exfiltration surface, never pretends it away.
684
698
  export const KIT_BRIDGE_TIER_NOTICE =
685
699
  'bridge-wrappers tier: seeds the REVIEW wrappers only, and only their CODE mode (`codex-review code`, `agy-review code` — never codex-exec/agy-run: delegated execution keeps its human prompt; never the plan/diff modes: their file arguments can point outside the repo, so they keep their prompt), each derived ONLY when its bridge is PLACED on PATH, plus the grounding pre-step rule in its rendered quoted byte-form. POSTURE (what this consent covers): an auto-allowed review wrapper runs UNATTENDED — it reads any repo file it is pointed at and sends the assembled payload to its subscription backend, and prefix rules cannot inspect arguments, so a code-mode argument that names a readable file (agy\'s --facts/--decided) rides the same consent — the same documented residual class as the autonomy red-line rules; that is the tier\'s PURPOSE (unattended council review runs) and its residual — tier entries get NO PreToolUse-hook coverage. The grounding entry\'s writer surface is bounded by grounding.mjs\'s OWN scratch-destination guard (a tracked or in-repo-not-ignored --out is refused by the tool). The wrapper names are ALSO seeded into sandbox.excludedCommands IN THE PROJECT settings.json (an exclusion only in settings.local.json was live-observed NOT to route — the wrapper then runs sandboxed and dies on a read-only HOME): ' +
686
- // The one interpolated seam in this notice: the shared host-conditional qualifier, so the tier's
700
+ // The interpolated seams in this notice: the shared host-conditional qualifier, so the tier's
687
701
  // routing promise and the render's degrade lines can never drift apart.
688
- `${HOST_HONORS_QUALIFIER} the harness runs an excluded command OUTSIDE the sandbox (the wrappers need network), so a plain allowlisted invocation triggers no sandbox-bypass approval — whether a host honors them is not knowable from here, and where it does not the wrapper simply starts sandboxed (fail-safe, never a silent widening). INVOCATION SHAPE: a prefix rule matches only a PLAIN invocation starting with the wrapper name — an env-var prefix or a compound chain never matches (redirects are fine).`;
702
+ `${HOST_HONORS_QUALIFIER} the harness runs an excluded command OUTSIDE the sandbox (the wrappers need network), so a plain allowlisted invocation triggers no sandbox-bypass approval — whether a host honors them is not knowable from here, and where it does not the wrapper simply starts sandboxed (fail-safe, never a silent widening). ` +
703
+ `The placed bridges' declared hosts (each bundled manifest's networkHosts) are seeded into sandbox.network.allowedDomains IN THE PROJECT settings.json: ${HOST_HONORS_QUALIFIER}, a wrapper the harness keeps sandboxed still reaches them, and every sandboxed command of the project can then reach those hosts, not only the wrappers — that widening is part of this consent, and the --autonomy preview reports it as a network weakening; the writable state dirs (writableDirs) stay hand-apply. ` +
704
+ 'INVOCATION SHAPE: a prefix rule matches only a PLAIN invocation starting with the wrapper name — an env-var prefix or a compound chain never matches (redirects are fine).';
689
705
 
690
706
  const formatBridgeTier = (result) =>
691
707
  result.bridgeTier
@@ -696,6 +712,13 @@ const formatBridgeTier = (result) =>
696
712
  `${result.wrote ? 'added' : 'would add'} sandbox.excludedCommands entries: ${result.excludedToAdd.length}`,
697
713
  ...formatEntryList(result.excludedToAdd),
698
714
  `already present (excludedCommands): ${result.excludedAlreadyPresent.length}`,
715
+ ...(result.hostsToAdd.length + result.hostsAlreadyPresent.length
716
+ ? [
717
+ `${result.wrote ? 'added' : 'would add'} sandbox.network.allowedDomains entries: ${result.hostsToAdd.length}`,
718
+ ...formatEntryList(result.hostsToAdd),
719
+ `already present (allowedDomains): ${result.hostsAlreadyPresent.length}`,
720
+ ]
721
+ : []),
699
722
  ...result.bridgeSkips.map((s) => ` skipped: ${s.reason}`),
700
723
  KIT_BRIDGE_TIER_NOTICE,
701
724
  ]
@@ -947,7 +970,19 @@ export const preflightVelocityProfile = ({ cwd }, deps = {}) => {
947
970
  };
948
971
  };
949
972
 
950
- export const planVelocityProfile = (preflight, { acceptEdits, kitTools, bridgeTier, findWrapper } = {}) => {
973
+ // The tier merges into the project's sandbox.network.allowedDomains, so a wrong-typed value there
974
+ // STOPs the tier with zero writes; every other mode keeps today's reading of the two keys.
975
+ const assertNetworkMergeable = (sandbox) => {
976
+ if (sandbox.network !== undefined && !isJsonObject(sandbox.network)) {
977
+ throw makeVelocityProfileError(VELOCITY_MALFORMED, `${SETTINGS_FILE}: sandbox.network must be a JSON object`);
978
+ }
979
+ if (sandbox.network?.allowedDomains !== undefined && !Array.isArray(sandbox.network.allowedDomains)) {
980
+ throw makeVelocityProfileError(VELOCITY_MALFORMED, `${SETTINGS_FILE}: sandbox.network.allowedDomains must be an array`);
981
+ }
982
+ return sandbox.network?.allowedDomains ?? [];
983
+ };
984
+
985
+ export const planVelocityProfile = (preflight, { acceptEdits, kitTools, bridgeTier, findWrapper, bundleRoot, readdir, readFile } = {}) => {
951
986
  const projectAllow = getAllowEntries(preflight.projectSettings?.data);
952
987
  const toAdd = UNIVERSAL_READONLY_ALLOWLIST.filter((entry) => !projectAllow.includes(entry));
953
988
  const alreadyPresent = UNIVERSAL_READONLY_ALLOWLIST.filter((entry) => projectAllow.includes(entry));
@@ -961,6 +996,10 @@ export const planVelocityProfile = (preflight, { acceptEdits, kitTools, bridgeTi
961
996
  const existingExcluded = Array.isArray(existingSandbox.excludedCommands) ? existingSandbox.excludedCommands : [];
962
997
  const excludedToAdd = bridge.excludedCommands.filter((cmd) => !existingExcluded.includes(cmd));
963
998
  const excludedAlreadyPresent = bridge.excludedCommands.filter((cmd) => existingExcluded.includes(cmd));
999
+ const existingHosts = bridgeTier === true ? assertNetworkMergeable(existingSandbox) : [];
1000
+ const hosts = bridgeTier === true ? bundledSandboxRecipe(bridge.placed, { bundleRoot, readdir, readFile }).hosts : [];
1001
+ const hostsToAdd = hosts.filter((host) => !existingHosts.includes(host));
1002
+ const hostsAlreadyPresent = hosts.filter((host) => existingHosts.includes(host));
964
1003
  return {
965
1004
  toAdd,
966
1005
  alreadyPresent,
@@ -973,6 +1012,8 @@ export const planVelocityProfile = (preflight, { acceptEdits, kitTools, bridgeTi
973
1012
  bridgeSkips: bridge.skips,
974
1013
  excludedToAdd,
975
1014
  excludedAlreadyPresent,
1015
+ hostsToAdd,
1016
+ hostsAlreadyPresent,
976
1017
  setsDefaultMode: acceptEdits === true,
977
1018
  };
978
1019
  };
@@ -980,7 +1021,15 @@ export const planVelocityProfile = (preflight, { acceptEdits, kitTools, bridgeTi
980
1021
  export const writeVelocityProfile = ({ cwd, acceptEdits = false, dryRun = true, kitTools = false, bridgeTier = false } = {}, deps = {}) => {
981
1022
  const projectDir = cwd ?? deps.cwd ?? process.cwd();
982
1023
  const preflight = preflightVelocityProfile({ cwd: projectDir }, deps);
983
- const plan = planVelocityProfile(preflight, { acceptEdits, kitTools, bridgeTier, findWrapper: deps.findWrapper });
1024
+ const plan = planVelocityProfile(preflight, {
1025
+ acceptEdits,
1026
+ kitTools,
1027
+ bridgeTier,
1028
+ findWrapper: deps.findWrapper,
1029
+ bundleRoot: deps.bundleRoot,
1030
+ readdir: deps.readdir,
1031
+ readFile: deps.readFile,
1032
+ });
984
1033
  // Drift guard runs on BOTH dry-run and apply (so a dry-run faithfully predicts the apply) and
985
1034
  // validates the FULL audited core, not just the to-add delta — a drifted core entry is caught even
986
1035
  // when it is already present in the user's allow list (and toAdd is a subset of the core anyway).
@@ -1016,6 +1065,7 @@ export const writeVelocityProfile = ({ cwd, acceptEdits = false, dryRun = true,
1016
1065
  [...plan.toAdd, ...plan.tierToAdd, ...plan.bridgeToAdd],
1017
1066
  acceptEdits,
1018
1067
  plan.excludedToAdd,
1068
+ plan.hostsToAdd,
1019
1069
  );
1020
1070
  fs.writeFile(settingsPath, formatJson(merged, preflight.projectSettings.eol ?? LF), UTF8);
1021
1071
  return { wrote: true, dryRun: false, settingsPath, ...resultBase };