@awebai/oats 0.23.2 → 0.24.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.
Files changed (172) hide show
  1. package/README.md +224 -391
  2. package/bin/oats-pi-sdk-host.mjs +17 -0
  3. package/bin/oats.mjs +470 -51
  4. package/capabilities/oats-okf/bin/oats-okf-binding.mjs +14 -0
  5. package/capabilities/oats-okf/bin/oats-okf.mjs +79 -11
  6. package/capabilities/oats-okf/lib/binding-wire.mjs +268 -0
  7. package/capabilities/oats-okf/lib/captured-worker.mjs +109 -0
  8. package/capabilities/oats-okf/lib/config.mjs +2 -1
  9. package/capabilities/oats-okf/lib/inspection.mjs +16 -1
  10. package/capabilities/oats-okf/lib/invocation-context.mjs +111 -0
  11. package/capabilities/oats-okf/lib/invocation-shape.mjs +135 -0
  12. package/capabilities/oats-okf/lib/io.mjs +1 -1
  13. package/capabilities/oats-okf/lib/portable-binding.mjs +199 -0
  14. package/capabilities/oats-okf/lib/source-contract.mjs +46 -0
  15. package/capabilities/oats-okf/lib/sources.mjs +123 -3
  16. package/capabilities/oats-okf/lib/stores.mjs +104 -25
  17. package/capabilities/oats-okf/lib/worker.mjs +69 -10
  18. package/capabilities/oats-okf/oats.json +35 -7
  19. package/capabilities/oats-okf/schemas/okf-portable-declaration.schema.json +87 -0
  20. package/capabilities/oats-okf/schemas/okf-portable-payload.schema.json +113 -0
  21. package/capabilities/oats-okf/skills/memory-harvest/SKILL.md +23 -5
  22. package/capabilities/oats-okf/skills/okf/SKILL.md +42 -1
  23. package/docs/artifact-approvals.schema.json +7 -0
  24. package/docs/capabilities.md +4 -0
  25. package/docs/capability-manifest.schema.json +37 -66
  26. package/docs/captured-invocation-context.schema.json +7 -0
  27. package/docs/captured-resolution.schema.json +7 -0
  28. package/docs/design/2026-09-14-artifact-retention-contract.md +190 -0
  29. package/docs/design/2026-09-14-portable-souls-and-git-workspaces.md +708 -0
  30. package/docs/design/2026-09-14-portable-souls-contract-amendments.md +85 -0
  31. package/docs/design/2026-09-14-portable-souls-explainer.md +750 -0
  32. package/docs/design/2026-09-15-captured-dispatch.md +127 -0
  33. package/docs/design/2026-09-15-captured-resolution-records.md +143 -0
  34. package/docs/design/2026-09-15-package-preparation.md +100 -0
  35. package/docs/design/2026-09-15-portable-data-contract.md +121 -0
  36. package/docs/design/2026-09-15-portable-declarations.md +189 -0
  37. package/docs/design/2026-09-15-portable-souls-handoff.md +150 -0
  38. package/docs/design/2026-09-15-portable-souls-implementation.md +417 -0
  39. package/docs/design/2026-09-15-selection-lock-and-approval.md +122 -0
  40. package/docs/design/2026-09-15-source-observation.md +119 -0
  41. package/docs/design/2026-09-16-captured-admission.md +77 -0
  42. package/docs/design/2026-09-16-captured-helper-dispatch.md +105 -0
  43. package/docs/design/2026-09-16-captured-launch-inputs.md +42 -0
  44. package/docs/design/2026-09-16-command-profile-preparation.md +86 -0
  45. package/docs/design/2026-09-16-fresh-install-first-rollout.md +47 -0
  46. package/docs/design/2026-09-16-fresh-operator-walkthrough.md +277 -0
  47. package/docs/design/2026-09-16-knowledge-capability-contract.md +61 -0
  48. package/docs/design/2026-09-16-messaging-capability-contract.md +59 -0
  49. package/docs/design/2026-09-16-portable-migration-evidence.md +156 -0
  50. package/docs/design/2026-09-16-portable-onboarding.md +177 -0
  51. package/docs/design/2026-09-16-prepare-request-transport.md +26 -0
  52. package/docs/design/2026-09-16-provider-binding-codecs.md +98 -0
  53. package/docs/design/2026-09-16-provider-binding-wire.md +247 -0
  54. package/docs/design/2026-09-17-capability-helper-input-contract.md +95 -0
  55. package/docs/design/2026-09-17-captured-backend-parity.md +53 -0
  56. package/docs/design/2026-09-17-captured-native-start.md +58 -0
  57. package/docs/design/2026-09-17-portable-boundary-hookup.md +19 -0
  58. package/docs/design/2026-09-17-portable-boundary-resources.md +52 -0
  59. package/docs/design/2026-09-17-public-captured-start.md +108 -0
  60. package/docs/design/2026-09-17-public-prepare-request.md +90 -0
  61. package/docs/design/2026-09-18-captured-pi-host.md +205 -0
  62. package/docs/design/2026-09-18-first-cut-release-checklist.md +131 -0
  63. package/docs/design/2026-09-18-herdr-protocol-compatibility.md +60 -0
  64. package/docs/design/2026-09-20-redesign-program-board.md +70 -0
  65. package/docs/design/2026-09-20-workspace-and-portable-adoption-plan.md +287 -0
  66. package/docs/design/2026-09-20-workspace-onboarding-public.md +124 -0
  67. package/docs/design/README.md +42 -0
  68. package/docs/desktop-cli-api.md +5 -2
  69. package/docs/execution-capsule.schema.json +108 -0
  70. package/docs/execution-targets.md +20 -7
  71. package/docs/first-team.md +1 -1
  72. package/docs/knowledge-theory.md +353 -111
  73. package/docs/knowledge.md +10 -1
  74. package/docs/layers.md +89 -354
  75. package/docs/oats-lock-v3.schema.json +7 -0
  76. package/docs/oats-member.schema.json +38 -0
  77. package/docs/oats-workspace.schema.json +68 -0
  78. package/docs/official-marketplace.md +79 -0
  79. package/docs/packages.md +4 -0
  80. package/docs/portable.schema.json +2512 -0
  81. package/docs/provider-check-input.schema.json +7 -0
  82. package/docs/release-notes/v0.24.0.md +104 -0
  83. package/docs/release-notes/v0.24.1.md +17 -0
  84. package/docs/schedules.md +126 -14
  85. package/docs/soul.schema.json +82 -0
  86. package/docs/souls-and-instances.md +20 -7
  87. package/docs/workspace-adoption.md +285 -0
  88. package/docs/workspaces.md +154 -0
  89. package/injects/oats-portable.md +16 -0
  90. package/injects/portable-instance-boundary.md +39 -0
  91. package/injects/portable-work-directory.md +29 -0
  92. package/lib/artifact-approvals.mjs +120 -0
  93. package/lib/artifact-tree.mjs +141 -0
  94. package/lib/capability-artifacts.mjs +179 -0
  95. package/lib/capability-execution.mjs +15 -0
  96. package/lib/capability-inputs.mjs +39 -0
  97. package/lib/capability-provenance.mjs +231 -0
  98. package/lib/captured-action-shape.mjs +21 -0
  99. package/lib/captured-admission-shape.mjs +20 -0
  100. package/lib/captured-binding-file.mjs +36 -0
  101. package/lib/captured-dispatch.mjs +66 -0
  102. package/lib/captured-instance-index.mjs +277 -0
  103. package/lib/captured-invocation-context.mjs +130 -0
  104. package/lib/captured-launch-request.mjs +46 -0
  105. package/lib/captured-operation-process.mjs +15 -0
  106. package/lib/captured-pi-custody.mjs +29 -0
  107. package/lib/captured-pi-host.mjs +167 -0
  108. package/lib/captured-pi-outcome.mjs +172 -0
  109. package/lib/captured-resolutions.mjs +275 -0
  110. package/lib/captured-scaffold.mjs +87 -0
  111. package/lib/captured-selector.mjs +28 -0
  112. package/lib/captured-session-backend.mjs +52 -0
  113. package/lib/captured-source-receipt-file.mjs +72 -0
  114. package/lib/config-data.mjs +104 -0
  115. package/lib/core.mjs +961 -566
  116. package/lib/errors.mjs +7 -0
  117. package/lib/helper-injection-policy.mjs +98 -0
  118. package/lib/herdr.mjs +18 -7
  119. package/lib/instruction-composition.mjs +31 -0
  120. package/lib/legacy-lock-codec.mjs +106 -0
  121. package/lib/manifest-settings.mjs +84 -0
  122. package/lib/package-closure.mjs +48 -0
  123. package/lib/package-materialization.mjs +83 -0
  124. package/lib/pi-sdk-host.mjs +229 -0
  125. package/lib/portable-artifacts.mjs +115 -0
  126. package/lib/portable-choices.mjs +82 -0
  127. package/lib/portable-composition.mjs +136 -0
  128. package/lib/portable-digest.mjs +105 -0
  129. package/lib/portable-files.mjs +26 -0
  130. package/lib/portable-identity.mjs +40 -0
  131. package/lib/portable-lock.mjs +117 -0
  132. package/lib/portable-migration-artifacts.mjs +135 -0
  133. package/lib/portable-migration-evidence.mjs +305 -0
  134. package/lib/portable-migration-store.mjs +199 -0
  135. package/lib/portable-migration.mjs +104 -0
  136. package/lib/portable-onboarding-acceptance.mjs +66 -0
  137. package/lib/portable-onboarding-request.mjs +49 -0
  138. package/lib/portable-onboarding.mjs +249 -0
  139. package/lib/portable-package-preparation.mjs +188 -0
  140. package/lib/portable-policy.mjs +44 -0
  141. package/lib/portable-shape.mjs +35 -0
  142. package/lib/portable-soul.mjs +38 -0
  143. package/lib/portable-state.mjs +80 -0
  144. package/lib/portable-values.mjs +181 -0
  145. package/lib/prepare-composition.mjs +151 -0
  146. package/lib/prepared-bindings.mjs +78 -0
  147. package/lib/prepared-resources.mjs +127 -0
  148. package/lib/provider-binding-broker.mjs +59 -0
  149. package/lib/provider-binding-wire.mjs +110 -0
  150. package/lib/provider-binding.mjs +22 -0
  151. package/lib/repository-observation.mjs +226 -0
  152. package/lib/resolution-shape.mjs +393 -0
  153. package/lib/schedule-capsule.mjs +206 -0
  154. package/lib/schedule.mjs +259 -38
  155. package/lib/servers.mjs +15 -0
  156. package/lib/soul-constraints.mjs +40 -0
  157. package/lib/source-projection.mjs +84 -0
  158. package/lib/source-spec.mjs +189 -0
  159. package/lib/workspace-definition.mjs +126 -0
  160. package/lib/workspace-discovery.mjs +146 -0
  161. package/package-catalog.json +2 -1
  162. package/package.json +3 -2
  163. package/packages/record/lib/capture-cc.mjs +14 -6
  164. package/packages/record/lib/formats.mjs +14 -3
  165. package/packages/record/lib/native-history.mjs +277 -7
  166. package/packages/record/lib/session-snapshot.mjs +25 -5
  167. package/packages/record/lib/sessions-for-home.mjs +30 -13
  168. package/skills/oats/SKILL.md +12 -7
  169. package/skills/oats-config/SKILL.md +11 -9
  170. package/skills/oats-packages/SKILL.md +12 -8
  171. package/skills/oats-portable/SKILL.md +115 -0
  172. package/skills/oats-portable-artifacts/SKILL.md +63 -0
package/bin/oats.mjs CHANGED
@@ -23,11 +23,11 @@ import { enableTmuxMouse, tmuxConfigPath, tmuxMouseEnabled } from "../lib/tmux-c
23
23
  import {
24
24
  LAYERS, WORK_MODES, LEGACY_HOME_CAPABILITIES_DIR, OATS_LOCK_FILE, OATS_VERSION, OAS_SCOPE_REMEDY, RETIRED_CAPABILITIES, detectOasScopes, retiredCapabilityReason, configChain, configCapabilityEntries, manifestOperations,
25
25
  acquireCapability, restoreCapabilities, marketplaceCapabilities,
26
- capabilityManifests, capabilityManifest, capabilityMissingRequires, capabilityIntegrity, capabilityTrust, capabilityExecutablePath,
27
- readCapabilityLocks, writeCapabilityLock,
26
+ capabilityManifests, capabilityManifest, capabilityMissingRequires, capabilityIntegrity, capabilityTrust, capabilityExecutablePath, activateCapturedScaffold, loadCapturedDispatch, inspectPortableOnboarding, prepareCapturedComposition, resolveCapturedHelper, capturedNativeSessionAvailability, scaffoldCapturedInstance, startCapturedInstanceSession, withCapturedBindingFile, withCapturedInvocationContextFile,
27
+ readCapabilityLocks, writeCapabilityLock, admitCapturedAction, beginCapturedIntent, settleCapturedIntent,
28
28
  parsePackageSource, inspectGitSourceRoot, acquirePackage, restorePackages, listInstalledPackages, readPackageLocks, readLockedConfigTemplates,
29
29
  officialCapabilityPackage, officialPackageCatalog,
30
- approveCapability, updatePackage, removePackage, migrateLegacyLock, applyLegacyLockMigration,
30
+ approveCapability, approveAvailableCapability, updatePackage, removePackage, migrateLegacyLock, applyLegacyLockMigration,
31
31
  packageIntegrity, capabilityArtifactIntegrity, verifyCapabilityInstallation, installedCapabilityDir, installedCapabilitiesDir, ownedCapabilitiesDir, loadPackageManifestAt,
32
32
  resolveOatsConfig, resolveWorkMode, composeInstanceAgentsMd, parseYamlNested, assertSafeConfigValue, assertSafeConfigWriteKey, stripInternalAnnotations, withConfigFile, packagedInject, teamAgentRoots,
33
33
  findTeamAgent, findTeamInstance, findCapabilityAgent, findInstanceHome, listCapabilityAgents, workspaceOf,
@@ -46,9 +46,18 @@ import { parseEnvelopeText, scheduleScopeOf, listSchedules, describe as describe
46
46
  import { hostUnitStatus, installHostUnit, uninstallHostUnit } from "../lib/schedule-host.mjs";
47
47
  import { receiveAttachment, uploadAttachment, readStreamBounded, MAX_ATTACHMENT_BYTES } from "../lib/attachments.mjs";
48
48
 
49
+ import { capturedSelector } from "../lib/captured-selector.mjs";
50
+ import { inspectCapturedPiOutcome } from "../lib/captured-pi-host.mjs";
51
+ import { readCapturedResolution } from "../lib/captured-resolutions.mjs";
52
+ import { readPortablePreparationRequest } from "../lib/portable-onboarding-request.mjs";
53
+ import { portableScope } from "../lib/portable-state.mjs";
54
+ import { CAPTURED_OPERATION_TIMEOUT_MS, runCapturedOperationProcess } from "../lib/captured-operation-process.mjs";
55
+ import { approveCapturedCapability } from "../lib/artifact-approvals.mjs";
56
+
49
57
  const args = process.argv.slice(2);
50
- const cmd = args[0];
58
+ let cmd = args[0];
51
59
  const HELP_WORDS = new Set(["help", "--help", "-h"]);
60
+ const KERNEL_COMMANDS = new Set(["prepare", "capture", "config", "create", "doctor", "inspect", "operation", "soul", "launch-config", "experimental", "init", "inject", "install", "list", "migrate", "pane", "recall", "remove", "retire", "root", "schedule", "server", "session", "setup", "spawn", "status", "trust", "type", "update", "use", "version"]);
52
61
  const flag = (name) => {
53
62
  const i = args.indexOf(`--${name}`);
54
63
  return i >= 0 ? (args[i + 1] && !args[i + 1].startsWith("--") ? args[i + 1] : true) : undefined;
@@ -86,6 +95,342 @@ const CLI_BIN = realpathSync(fileURLToPath(import.meta.url));
86
95
  const jsonFail = (code, message, details) => { console.log(JSON.stringify({ schemaVersion: 1, ok: false, error: { code, message: String(message), ...(details !== undefined ? { details } : {}) } })); process.exit(1); };
87
96
  const jsonOk = (result) => { console.log(JSON.stringify({ schemaVersion: 1, ok: true, result })); };
88
97
 
98
+ function inspectOnboardingCmd() {
99
+ const fail = (code, message) => JSON_MODE ? jsonFail(code, message) : die(message);
100
+ let file;
101
+ for (let index = 1; index < args.length; index++) {
102
+ if (args[index] === "--json") continue;
103
+ if (args[index] !== "--request" || file !== undefined || !args[index + 1] || args[index + 1].startsWith("--")) fail("E_BAD_ARGS", "source inspection accepts one --request <absolute-json> and --json only");
104
+ file = args[++index];
105
+ }
106
+ try {
107
+ const input = readPortablePreparationRequest({ file });
108
+ const result = inspectPortableOnboarding(input);
109
+ if (JSON_MODE) jsonOk(result); else console.log(JSON.stringify(result, null, 2));
110
+ } catch (error) { fail(error.code || "E_INSPECT_FAILED", error.message); }
111
+ }
112
+
113
+ function prepareCmd() {
114
+ const fail = (code, message, details) => JSON_MODE ? jsonFail(code, message, details) : die(message);
115
+ const values = new Map(), allowed = new Set(["request", "dir", "source", "revision", "export", "alias", "workspace", "workspace-revision", "work"]);
116
+ for (let index = 1; index < args.length; index++) {
117
+ if (args[index] === "--json") continue;
118
+ const key = args[index].startsWith("--") ? args[index].slice(2) : "";
119
+ if (!allowed.has(key) || values.has(key) || !args[index + 1] || args[index + 1].startsWith("--")) fail("E_BAD_ARGS", "prepare needs unique named source/context arguments; use prepare --help");
120
+ values.set(key, args[++index]);
121
+ }
122
+ try {
123
+ let input;
124
+ if (values.has("request")) {
125
+ // The shared leaf owns request bytes/exclusivity; this router alone owns
126
+ // argv and has already refused explicit captured selectors.
127
+ input = readPortablePreparationRequest({ file: values.get("request"),
128
+ inputFlags: Object.fromEntries([...values].filter(([key]) => key !== "request")) });
129
+ } else {
130
+ const deployment = values.get("dir"), alias = values.get("alias"), source = values.get("source");
131
+ if (!deployment || !isAbsolute(deployment) || !alias) fail("E_BAD_ARGS", "prepare needs --dir <absolute deployment> and --alias <name>");
132
+ if (source ? !values.get("revision") || !values.get("export") : !values.get("workspace") || values.has("revision") || values.has("export")) fail("E_BAD_ARGS", "choose a complete source/revision/export reference or a workspace-advertised alias");
133
+ if (values.has("workspace-revision") && !values.has("workspace")) fail("E_BAD_ARGS", "--workspace-revision requires --workspace");
134
+ const origin = { kind: "operator", document: { kind: "operator", id: "oats-prepare" }, pointer: "/source" };
135
+ input = { deployment, source: source ? { source, revision: values.get("revision"), soul: values.get("export"), alias } : alias, origin,
136
+ ...(values.has("work") ? { mode: values.get("work") } : {}),
137
+ ...(values.has("workspace") ? { workspace: { source: values.get("workspace"), origin: { ...origin, pointer: "/workspace" },
138
+ ...(values.has("workspace-revision") ? { revision: values.get("workspace-revision") } : {}) } } : {}) };
139
+ }
140
+ // Pass the whole request to the one public validator/resolver. Unknown
141
+ // fields are refused there, never filtered or filled from ambient state.
142
+ const result = prepareCapturedComposition(input);
143
+ if (!result.resolution) fail("needs-configuration", "preparation is incomplete; no executable resolution was published", result);
144
+ if (JSON_MODE) jsonOk(result); else console.log(JSON.stringify(result, null, 2));
145
+ } catch (error) { fail(error.code || "E_PREPARE_FAILED", error.message); }
146
+ }
147
+
148
+ /** Run one provider operation from immutable captured authority. A home is an
149
+ * explicit target only: its stored binding must name this exact record. */
150
+ function capturedOperation(selector, load, bail) {
151
+ if (args[1] !== "run") bail("E_USAGE", "usage: oats operation run <layer>:<name> --deployment <abs> --resolution <id> [--home <abs>] [--arg k=v ...] [--retry-intent <saved-id>] [--json]");
152
+ const address = args[2], match = typeof address === "string" ? OPERATION_ADDRESS_RE.exec(address) : null;
153
+ if (!match) bail("E_BAD_ARGS", `operation address must be <layer>:<name> with layer one of ${LAYERS.join(", ")} (got ${JSON.stringify(address)})`);
154
+ const [, slot, name] = match, given = Object.create(null);
155
+ let home, retryExecutionId;
156
+ for (let index = 3; index < args.length; index++) {
157
+ const token = args[index];
158
+ if (token === "--json") continue;
159
+ if (token === "--home") {
160
+ const value = args[++index];
161
+ if (home !== undefined || !value || value.startsWith("--") || !isAbsolute(value)) bail("E_BAD_ARGS", "--home needs one absolute instance home");
162
+ home = resolve(value); continue;
163
+ }
164
+ if (token === "--retry-intent") {
165
+ const value = args[++index];
166
+ if (retryExecutionId !== undefined || !value || !/^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/.test(value)) bail("E_BAD_ARGS", "--retry-intent requires one saved executionId");
167
+ retryExecutionId = value; continue;
168
+ }
169
+ if (token === "--arg") {
170
+ const value = args[++index], eq = value?.indexOf("=") ?? -1;
171
+ if (eq < 1) bail("E_BAD_ARGS", "--arg expects name=value");
172
+ const key = value.slice(0, eq);
173
+ if (Object.hasOwn(given, key)) bail("E_BAD_ARGS", `duplicate operation arg ${JSON.stringify(key)}`);
174
+ given[key] = value.slice(eq + 1); continue;
175
+ }
176
+ bail("E_BAD_ARGS", `unsupported captured operation argument ${JSON.stringify(token)}`);
177
+ }
178
+ let meta;
179
+ if (home) {
180
+ const metaFile = join(home, "instance.json");
181
+ if (!existsSync(metaFile)) bail("E_SESSION_UNKNOWN", `${home} is not an OATS instance home (no instance.json)`);
182
+ try { meta = JSON.parse(readFileSync(metaFile, "utf8")); } catch (error) { bail("E_SESSION_UNKNOWN", `${metaFile}: ${error.message}`); }
183
+ if (!meta || typeof meta !== "object" || Array.isArray(meta) || typeof meta.instance !== "string" || !meta.instance) bail("E_SESSION_UNKNOWN", `${metaFile}: invalid instance metadata`);
184
+ const binding = meta.executionBinding;
185
+ if (!binding) bail("migration-required", `${home} has no captured executionBinding; current configuration was not used`);
186
+ if (binding.schemaVersion !== 1 || typeof binding.deployment !== "string" || !isAbsolute(binding.deployment)
187
+ || realOrResolved(binding.deployment) !== realOrResolved(selector.deployment)
188
+ || binding.resolution?.schemaVersion !== 1 || binding.resolution.id !== selector.resolution.id) {
189
+ bail("E_HOME_MISMATCH", `${home} is not bound to captured resolution ${selector.resolution.id} in ${selector.deployment}`);
190
+ }
191
+ }
192
+ // Inspect is static: validate target and arguments before the action load runs
193
+ // the provider's mutable readiness check.
194
+ const inspected = load({ kind: "inspect" }), providerId = inspected.record.bindings[slot]?.capability;
195
+ const provider = providerId ? inspected.manifests.get(providerId) : undefined;
196
+ if (!provider) bail("capability-not-selected", `no captured ${slot} provider is selected`);
197
+ const operation = manifestOperations(provider).find((entry) => entry.name === name);
198
+ if (!operation) bail("operation-not-found", "captured provider does not declare this operation");
199
+ if (operation.context === "home" && !meta) bail("E_OPERATION_UNAVAILABLE", `${address} runs in an instance home; pass --home <abs>`);
200
+ if (operation.context === "scope" && meta) bail("E_BAD_ARGS", `${address} is a scope operation and does not accept --home`);
201
+ const declared = new Map(operation.args.map((entry) => [entry.name, entry]));
202
+ for (const key of Object.keys(given)) if (!declared.has(key)) bail("E_BAD_ARGS", `${address} takes no arg ${JSON.stringify(key)} (declared: ${[...declared.keys()].join(", ") || "none"})`);
203
+ for (const entry of operation.args) if (entry.required && given[entry.name] === undefined) bail("E_BAD_ARGS", `${address} needs --arg ${entry.name}=<value>: ${entry.description || "required"}`);
204
+ const action = { kind: "operation", slot, name };
205
+ const argFlags = operation.args.flatMap((entry) => given[entry.name] === undefined ? [] : [entry.flag, given[entry.name]]), cwd = home || selector.deployment;
206
+ if (operation.kind === "action" && !home) bail("admission-required", "scope mutation has no qualified incarnation/admission path; use an instance-scoped operation");
207
+ if (retryExecutionId !== undefined && operation.kind !== "action") bail("E_BAD_ARGS", "read-only operations do not retry mutation intents");
208
+ const admission = operation.kind === "action" ? admitCapturedAction({ deployment: selector.deployment, resolution: selector.resolution, home, action, input: { arguments: argFlags },
209
+ ...(retryExecutionId !== undefined ? { retryExecutionId } : {}) }) : null;
210
+ let settlement;
211
+ const admittedBail = (code, message, details) => bail(code, message, { ...details, ...(admission ? { intent: admission.intent } : {}),
212
+ ...(settlement ? { settlement, unconfirmed: settlement.state === "unconfirmed" } : {}) });
213
+ if (admission?.replayed) {
214
+ settlement = { state: "completed", receipt: admission.receipt };
215
+ if (!admission.replayable) admittedBail("needs-configuration", "completed operation cannot replay its retained outcome");
216
+ finishOperation({ r: { status: 0, stdout: JSON.stringify(admission.receipt) }, bail: admittedBail, address, provider, op: operation, argFlags, cwd, home, meta, intent: admission.intent }); return;
217
+ }
218
+ let loaded;
219
+ try { loaded = load(action, { invocationTarget: meta ? { home, work: join(home, "work"), name: meta.instance, agent: meta.agent } : null,
220
+ ...(admission ? { intent: admission.intent, priorReceipt: admission.receipt } : {}) }); }
221
+ catch (error) {
222
+ if (admission) {
223
+ settleCapturedIntent({ deployment: selector.deployment, home, intent: admission.intent, action, state: "blocked", receipt: admission.receipt });
224
+ settlement = { state: "blocked", receipt: admission.receipt };
225
+ }
226
+ admittedBail(error.code || "provider-unavailable", error.message);
227
+ }
228
+ const { capability, executable } = loaded;
229
+ const env = { ...process.env };
230
+ for (const key of Object.keys(env)) if (key.startsWith("OATS_") || key.startsWith("PI_AGENT_") || key === "PI_AGENTS_ROOT") delete env[key];
231
+ Object.assign(env, {
232
+ OATS_DEPLOYMENT: selector.deployment, OATS_RESOLUTION: selector.resolution.id,
233
+ OATS_CAPABILITY: capability.id, OATS_CAPABILITY_ROOT: capability.manifest._dir,
234
+ OATS_SETTINGS: JSON.stringify(capability.settings), OATS_CLI_BIN: CLI_BIN,
235
+ OATS_OPERATION: address, OATS_CONTEXT: selector.deployment, OATS_LEVEL: selector.deployment,
236
+ OATS_WORKSPACE: selector.deployment,
237
+ });
238
+ if (meta) Object.assign(env, { OATS_INSTANCE: meta.instance, OATS_INSTANCE_HOME: home, OATS_HOME: home,
239
+ PI_AGENT_INSTANCE: meta.instance, PI_AGENT_HOME: home, ...(meta.agent ? { OATS_AGENT: meta.agent } : {}) });
240
+ const invocation = loaded.invocation;
241
+ let child, cleanupError, started = false;
242
+ try {
243
+ child = withCapturedInvocationContextFile(invocation, contextEnv => withCapturedBindingFile(loaded, bindingEnv => {
244
+ if (admission) { beginCapturedIntent({ deployment: selector.deployment, home, intent: admission.intent, action }); started = true; }
245
+ return runCapturedOperationProcess({ file: executable.file, args: [...executable.args, ...argFlags, "--json"], cwd, env: { ...env, ...contextEnv, ...bindingEnv } });
246
+ }));
247
+ } catch (error) {
248
+ if (!error?.invocationCompleted) {
249
+ if (admission) {
250
+ settleCapturedIntent({ deployment: selector.deployment, home, intent: admission.intent, action, state: started ? "unconfirmed" : "blocked", receipt: admission.receipt });
251
+ settlement = { state: started ? "unconfirmed" : "blocked", receipt: admission.receipt };
252
+ }
253
+ admittedBail(error.code || "E_OPERATION_RESULT", error.message, { unconfirmed: started });
254
+ }
255
+ child = error.invocationResult; cleanupError = error;
256
+ }
257
+ if (admission) {
258
+ let envelope; try { envelope = JSON.parse(String(child.stdout || "").trim()); } catch { /* unconfirmed below */ }
259
+ const completed = !cleanupError && !child.error && child.status === 0 && envelope?.schemaVersion === 1 && envelope.ok === true;
260
+ const state = completed ? "completed" : "unconfirmed", receipt = envelope ?? parseEnvelopeText(String(child.stdout || "")) ?? admission.receipt;
261
+ try {
262
+ settleCapturedIntent({ deployment: selector.deployment, home, intent: admission.intent, action, state, receipt, replayable: completed });
263
+ settlement = { state, receipt };
264
+ }
265
+ catch (error) { admittedBail("E_OPERATION_RESULT", "operation ran but outcome custody could not be confirmed", { unconfirmed: true, envelope, custody: { code: error.code, message: error.message } }); }
266
+ }
267
+ finishOperation({ r: child, bail: admittedBail, address, provider: capability.manifest, op: operation, argFlags, cwd, home, meta, cleanupError, settlement, ...(admission ? { intent: admission.intent } : {}) });
268
+ }
269
+
270
+ /** Fresh explicit captured scaffold + spawn hooks. Placement is supplied by
271
+ * the operator; launch and non-directory work remain unsupported. */
272
+ function capturedSpawn(selector, load, bail) {
273
+ const subject = args[1]; let home; let noLaunch = false;
274
+ if (!subject || subject.startsWith("-")) bail("E_BAD_ARGS", "captured spawn needs the retained subject name");
275
+ for (let index = 2; index < args.length; index++) {
276
+ const token = args[index];
277
+ if (token === "--json") continue;
278
+ if (token === "--no-launch") { if (noLaunch) bail("E_BAD_ARGS", "duplicate --no-launch"); noLaunch = true; continue; }
279
+ if (token === "--home") {
280
+ const value = args[++index];
281
+ if (home !== undefined || !value || value.startsWith("--") || !isAbsolute(value)) bail("E_BAD_ARGS", "--home needs one absolute new instance home");
282
+ home = resolve(value); continue;
283
+ }
284
+ bail("E_BAD_ARGS", `unsupported captured spawn argument ${JSON.stringify(token)}`);
285
+ }
286
+ if (!home || !noLaunch) bail("E_BAD_ARGS", "captured spawn currently requires --home <absolute new home> and --no-launch");
287
+ const inspected = load({ kind: "inspect" }), expected = inspected.record.subject.kind === "persistent" ? inspected.record.subject.soul.alias : inspected.record.subject.name;
288
+ if (subject !== expected) bail("E_HOME_MISMATCH", `captured resolution subject is ${expected}, not ${subject}`);
289
+ const scaffold = scaffoldCapturedInstance({ deployment: selector.deployment, resolution: selector.resolution, home, instance: basename(home) });
290
+ let activated;
291
+ try {
292
+ activated = activateCapturedScaffold({ deployment: selector.deployment, resolution: selector.resolution, home,
293
+ extraEnv: process.env.OATS_HOME_DIR ? { OATS_HOME_DIR: process.env.OATS_HOME_DIR } : {} });
294
+ } catch (error) {
295
+ if (error?.home) bail(error.code || "E_SPAWN_FAILED", error.message, { home: error.home, cleanupRequired: true, failures: error.provenance || [],
296
+ ...(error.capturedCustody ? { unconfirmed: true, custody: error.capturedCustody } : {}) });
297
+ throw error;
298
+ }
299
+ const result = { ...scaffold, hooksPending: activated.hooksPending, cleanupRequired: activated.cleanupRequired, launchPending: true,
300
+ hookIntents: activated.hooks.intents, hookOrder: activated.hooks.order, warnings: activated.hooks.warnings };
301
+ if (JSON_MODE) jsonOk(result); else console.log(`Scaffolded ${result.instance} at ${result.home}; ${result.hooksPending ? "captured hook custody requires retry/reconciliation" : "captured hooks complete"}, launch pending`);
302
+ }
303
+
304
+ /** Native continuation of an already owned captured home. A helper selector is
305
+ * an exact edge from the SOURCE record, not a name/current-config resolver. */
306
+ function capturedSession(selector, bail) {
307
+ const inspecting = args[1] === "inspect";
308
+ if (!["start", "restart", "inspect"].includes(args[1])) bail("unsupported-action", "captured session supports start/restart or explicit Pi outcome inspect; no current-context fallback was used");
309
+ const values = new Map(), allowed = new Set(inspecting ? ["home", "helper", "native-record"] : ["home", "helper", "request", "retry-intent"]);
310
+ for (let index = 2; index < args.length; index++) {
311
+ if (args[index] === "--json") continue;
312
+ const key = args[index].startsWith("--") ? args[index].slice(2) : "", value = args[index + 1];
313
+ if (!allowed.has(key) || values.has(key) || !value || value.startsWith("--")) bail("E_BAD_ARGS", "captured session needs unique named home/helper/request/retry arguments");
314
+ values.set(key, value); index++;
315
+ }
316
+ const home = values.get("home"), retry = values.get("retry-intent");
317
+ if (!home || !isAbsolute(home) || resolve(home) !== home || home.includes("\0")) bail("E_BAD_ARGS", "captured session needs a normalized absolute --home");
318
+ if (retry !== undefined && !/^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/.test(retry)) bail("E_BAD_ARGS", "--retry-intent requires one saved executionId");
319
+ if (inspecting && !/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/.test(values.get("native-record") ?? "")) bail("E_BAD_ARGS", "captured Pi outcome inspect requires one explicit --native-record UUID");
320
+ const sourceExecutionBinding = { schemaVersion: 1, deployment: portableScope(selector.deployment), resolution: selector.resolution };
321
+ // Pure retained-subject check, before request files, provider or native calls.
322
+ // Dedicated helper IDs remain valid for scaffolding, not edge-less dispatch.
323
+ if (!values.has("helper") && readCapturedResolution(sourceExecutionBinding.deployment, sourceExecutionBinding.resolution).subject.kind === "helper") {
324
+ bail("helper-not-selected", "captured helper session needs SOURCE selectors plus --helper EXACT_SOURCE_HELPER_KEY");
325
+ }
326
+ // Reuse the same bounded strict object-file transport. Preparation and native
327
+ // request schemas remain separate; reject unknown fields before projection.
328
+ let request = {};
329
+ if (values.has("request")) {
330
+ const input = readPortablePreparationRequest({ file: values.get("request") });
331
+ if (input.schemaVersion !== 1 || Object.keys(input).some(key => !["schemaVersion", "backend", "task", "stopGraceMs"].includes(key))) bail("E_BAD_ARGS", "native request must be version1 with only backend/task/stopGraceMs");
332
+ if (Object.hasOwn(input, "backend") && (!input.backend || typeof input.backend !== "object" || Array.isArray(input.backend))) bail("E_BAD_ARGS", "a supplied native backend must be an explicit object, not omission");
333
+ const { schemaVersion, ...fields } = input; request = fields;
334
+ }
335
+ const helperSelection = values.has("helper") ? resolveCapturedHelper({ executionBinding: sourceExecutionBinding, helper: values.get("helper") }) : null;
336
+ const executionBinding = helperSelection?.executionBinding ?? sourceExecutionBinding;
337
+ try {
338
+ if (inspecting) {
339
+ const outcome = inspectCapturedPiOutcome(home, { ...executionBinding, nativeRecordId: values.get("native-record") });
340
+ const result = { outcome, executionBinding, ...(helperSelection ? { sourceExecutionBinding: helperSelection.sourceExecutionBinding, helper: helperSelection.helper } : {}) };
341
+ if (JSON_MODE) jsonOk(result); else console.log(JSON.stringify(result, null, 2));
342
+ return;
343
+ }
344
+ const native = startCapturedInstanceSession(home, { ...request, deployment: executionBinding.deployment, resolution: executionBinding.resolution,
345
+ restart: args[1] === "restart", ...(retry !== undefined ? { retryExecutionId: retry } : {}) });
346
+ const result = { ...native, executionBinding, ...(helperSelection ? { sourceExecutionBinding: helperSelection.sourceExecutionBinding, helper: helperSelection.helper } : {}) };
347
+ if (JSON_MODE) jsonOk(result); else console.log(`${result.replayed ? "Replayed captured dispatch receipt for" : "Dispatched captured native session for"} ${home}`);
348
+ } catch (error) {
349
+ bail(error.code || "E_SESSION_START_FAILED", error.message, { home: error.home ?? home, executionBinding,
350
+ ...(helperSelection ? { sourceExecutionBinding: helperSelection.sourceExecutionBinding, helper: helperSelection.helper } : {}),
351
+ ...(error.capturedCustody ? { custody: error.capturedCustody } : {}),
352
+ ...(error.nativeCustody ? { unconfirmed: true, nativeCustody: error.nativeCustody } : {}) });
353
+ }
354
+ }
355
+
356
+ /** Exact-selector dispatch enters before any current-context resolver. Its
357
+ * child receives the same selector, never an invoking agent's ambient identity. */
358
+ function capturedCommand(selector) {
359
+ const fail = (code, message, details) => JSON_MODE ? jsonFail(code, message, details) : die(message);
360
+ try {
361
+ const end = args.indexOf("--"), head = end < 0 ? args : args.slice(0, end);
362
+ const permitsHome = cmd === "operation" || cmd === "spawn" || cmd === "session";
363
+ const forbiddenContext = permitsHome ? ["--dir", "--server", "--soul", "--agents-root"] : ["--dir", "--home", "--server", "--soul", "--agents-root"];
364
+ if (head.some((arg) => forbiddenContext.includes(arg.split("=")[0]))) {
365
+ fail("E_BAD_ARGS", "captured selectors cannot be mixed with current-context selectors");
366
+ }
367
+ if (selector.artifactSet !== undefined) {
368
+ if (cmd !== "trust" || !args[1] || args[1].startsWith("-") || args.slice(2).some((arg) => arg !== "--json")) fail("E_BAD_ARGS", "artifact-set selectors support only explicit trust of one capability");
369
+ const result = approveAvailableCapability(selector.deployment, selector.artifactSet, args[1], {
370
+ kind: "operator", document: { kind: "operator", id: "oats-trust-artifact-set" }, pointer: "/capability",
371
+ });
372
+ if (JSON_MODE) jsonOk(result); else console.log(`${args[1]}: ${result.status}`);
373
+ return;
374
+ }
375
+ const target = { deployment: selector.deployment, resolution: selector.resolution };
376
+ const load = (action, extra = {}) => loadCapturedDispatch({ ...target, action, ...extra });
377
+ if (cmd === "inspect") {
378
+ let helperKey;
379
+ for (let index = 1; index < args.length; index++) {
380
+ if (["--json", "--composition"].includes(args[index])) continue;
381
+ if (args[index] !== "--helper" || helperKey !== undefined || !args[index + 1] || args[index + 1].startsWith("--")) fail("E_BAD_ARGS", "captured inspect accepts --json, --composition and one --helper <exact-map-key>");
382
+ helperKey = args[++index];
383
+ }
384
+ const helperSelection = helperKey === undefined ? null : resolveCapturedHelper({ executionBinding: { schemaVersion: 1, ...target }, helper: helperKey });
385
+ const selected = helperSelection?.executionBinding ?? target;
386
+ const loaded = loadCapturedDispatch({ deployment: selected.deployment, resolution: selected.resolution, action: { kind: args.includes("--composition") ? "compose" : "inspect" } });
387
+ const result = { resolution: loaded.resolution, capture: loaded.record.capture, nativeSession: capturedNativeSessionAvailability(),
388
+ ...(helperSelection ? { helperSelection } : {}),
389
+ capabilities: [...loaded.capabilities.values()].map(({ id, manifest }) => ({ id, version: manifest.version,
390
+ approval: loaded.approvals.find((entry) => entry.artifact.capability === id).status })),
391
+ helpers: Object.entries(loaded.record.helpers).map(([key, resolution]) => ({ key, resolution })),
392
+ hasComposition: !!loaded.record.dispatch.composition,
393
+ ...(loaded.composition ? { composition: loaded.composition } : {}) };
394
+ if (JSON_MODE) jsonOk(result); else console.log(JSON.stringify(result, null, 2));
395
+ return;
396
+ }
397
+ if (cmd === "trust") {
398
+ if (!args[1] || args[1].startsWith("-") || args.slice(2).some((arg) => arg !== "--json")) fail("E_BAD_ARGS", "captured trust needs one capability ID");
399
+ load({ kind: "inspect" }); // complete manifest validation before approval
400
+ const result = approveCapturedCapability(selector.deployment, selector.resolution, args[1], {
401
+ kind: "operator", document: { kind: "operator", id: "oats-trust" }, pointer: "/capability",
402
+ });
403
+ if (JSON_MODE) jsonOk(result); else console.log(`${args[1]}: ${result.status}`);
404
+ return;
405
+ }
406
+ if (cmd === "operation") { capturedOperation(selector, load, fail); return; }
407
+ if (cmd === "spawn") { capturedSpawn(selector, load, fail); return; }
408
+ if (cmd === "session") { capturedSession(selector, fail); return; }
409
+ if (!cmd || cmd.startsWith("-") || KERNEL_COMMANDS.has(cmd)) fail("unsupported-action", "this kernel command has not yet adopted captured selectors; no current-context fallback was used");
410
+ if (!args[1] || args[1] === "--json" || head.some((arg) => HELP_WORDS.has(arg))) {
411
+ const loaded = load({ kind: "inspect" });
412
+ const matches = [...loaded.manifests.values()].filter((manifest) => manifest.command === cmd);
413
+ if (matches.length !== 1) fail("capability-not-selected", "captured command namespace is absent or ambiguous");
414
+ const result = { capability: matches[0].capability, commands: Object.keys(matches[0].commands || {}), help: "manifest only; no executable ran" };
415
+ if (JSON_MODE) jsonOk(result); else console.log(JSON.stringify(result, null, 2));
416
+ return;
417
+ }
418
+ const loaded = load({ kind: "command", namespace: cmd, name: args[1] });
419
+ const env = { ...process.env };
420
+ for (const key of Object.keys(env)) if (key.startsWith("OATS_") || key.startsWith("PI_AGENT_") || key === "PI_AGENTS_ROOT") delete env[key];
421
+ Object.assign(env, { OATS_DEPLOYMENT: selector.deployment, OATS_RESOLUTION: selector.resolution.id,
422
+ OATS_CAPABILITY: loaded.capability.id, OATS_CAPABILITY_ROOT: loaded.capability.manifest._dir,
423
+ OATS_SETTINGS: JSON.stringify(loaded.capability.settings),
424
+ OATS_CLI_BIN: CLI_BIN, OATS_CONTEXT: selector.deployment, OATS_LEVEL: selector.deployment });
425
+ const forwarded = args.slice(2); if (forwarded[0] === "--") forwarded.shift();
426
+ const child = withCapturedInvocationContextFile(loaded.invocation, contextEnv => withCapturedBindingFile(loaded, bindingEnv => spawnSync(process.execPath, [loaded.executable.file, ...loaded.executable.args, ...forwarded], {
427
+ cwd: selector.deployment, env: { ...env, ...contextEnv, ...bindingEnv }, stdio: "inherit",
428
+ })));
429
+ if (child.error) fail("E_CAPABILITY_BROKEN", child.error.message);
430
+ process.exit(child.status ?? 1);
431
+ } catch (error) { fail(error.code || "E_CAPABILITY_BROKEN", error.message); }
432
+ }
433
+
89
434
  /** Level of a directory: laptop (home), repo (.git), else workspace. */
90
435
  function levelOf(dir) {
91
436
  const d = resolve(dir);
@@ -637,7 +982,55 @@ const OPERATION_ADDRESS_RE = /^(knowledge|messaging|tasks):([a-z][a-z0-9-]*)$/;
637
982
  const reportsRetainedEffectsText = (message) => /INCOMPLETE|quarantin|retain|could not (?:be )?(?:verif|confirm)/i.test(String(message || ""));
638
983
  // Comfortably below the scheduler's 5-minute command bound and any GUI
639
984
  // proxy, so the receipt always reaches the caller before a wrapper gives up.
640
- const OPERATION_TIMEOUT_MS = 4 * 60 * 1000;
985
+ const OPERATION_TIMEOUT_MS = CAPTURED_OPERATION_TIMEOUT_MS;
986
+ function finishOperation({ r, bail, address, provider, op, argFlags, cwd, home, meta, cleanupError, intent, settlement }) {
987
+ const stderr = String(r.stderr || "").trim();
988
+ const timedOut = r.error?.code === "ETIMEDOUT" || (r.status === null && ["SIGTERM", "SIGKILL"].includes(r.signal) && (!settlement || !r.error));
989
+ const base = { operation: address, capability: provider.capability, version: provider.version || null, argv: [provider.command, op.command, ...argFlags], cwd, target: home ? { home, instance: meta.instance } : null };
990
+ // Unconfirmed outcomes (a timeout, no valid receipt, a receipt contradicted
991
+ // by the exit status) carry what WAS observed in error.details, so a
992
+ // scheduler can keep the slot as unknown and reconcile by any name the
993
+ // provider managed to answer; they are never confirmed failures.
994
+ const observed = (envelope) => ({ exit: r.status, signal: r.signal || null, unconfirmed: true, ...(envelope && typeof envelope === "object" ? { envelope } : {}),
995
+ ...(stderr ? { stderr: stderr.slice(0, 2000) } : {}), ...(cleanupError ? { cleanup: { code: cleanupError.code || "E_OPERATION_CLEANUP", message: String(cleanupError.message || cleanupError).slice(0, 1000) } } : {}) });
996
+ if (r.error && !timedOut) bail("E_CAPABILITY_BROKEN", `${address}: ${r.error.message || r.error}`, settlement ? observed(parseEnvelopeText(String(r.stdout || ""))) : undefined);
997
+ if (timedOut) bail("E_OPERATION_TIMEOUT", `${address} (${provider.capability} ${op.command}) did not finish within ${OPERATION_TIMEOUT_MS / 1000} s; its effects are unconfirmed`, observed(parseEnvelopeText(String(r.stdout || ""))));
998
+ // Exactly one JSON-v1 envelope on stdout, nothing else, and an exit status
999
+ // that agrees with it: contaminated output or a success envelope from a
1000
+ // process that then failed is not a receipt.
1001
+ let envelope;
1002
+ try { envelope = JSON.parse(String(r.stdout || "").trim()); } catch { envelope = undefined; }
1003
+ if (!envelope || typeof envelope !== "object" || Array.isArray(envelope) || envelope.schemaVersion !== 1 || typeof envelope.ok !== "boolean") bail("E_OPERATION_RESULT", `${address} (${provider.capability} ${op.command}) did not answer exactly one JSON-v1 envelope on stdout (exit ${r.status}); its effects are unconfirmed${stderr ? `: ${stderr.slice(0, 400)}` : ""}`, observed(parseEnvelopeText(String(r.stdout || ""))));
1004
+ if (cleanupError) bail("E_OPERATION_RESULT", `${address} (${provider.capability} ${op.command}) answered, but private invocation cleanup could not be confirmed; its effects are unconfirmed`, observed(envelope));
1005
+ // A provider's own failure is relayed with its code; its WHOLE envelope
1006
+ // (a partial receipt such as result.instance of something it launched
1007
+ // before failing, and any details it gave) travels in error.details so a
1008
+ // scheduler can keep an unconfirmed outcome and reconcile that target.
1009
+ if (!envelope.ok) bail(envelope.error?.code || "E_OPERATION_FAILED", `${address}: ${envelope.error?.message || "failed"}`, { exit: r.status, envelope, ...(reportsRetainedEffectsText(envelope.error?.message) ? { unconfirmed: true } : {}) });
1010
+ if (r.status !== 0) bail("E_OPERATION_RESULT", `${address} (${provider.capability} ${op.command}) answered ok but exited ${r.status}; the receipt is not trusted and its effects are unconfirmed${stderr ? `: ${stderr.slice(0, 400)}` : ""}`, observed(envelope));
1011
+ const result = envelope.result && typeof envelope.result === "object" ? envelope.result : {};
1012
+ if (op.kind === "view") {
1013
+ const docs = result.documents;
1014
+ const bad = !Array.isArray(docs) || docs.some((d) => !d || typeof d !== "object" || typeof d.label !== "string" || !d.label
1015
+ || (d.kind !== undefined && !["markdown", "text"].includes(d.kind))
1016
+ || (d.path !== undefined && d.path !== null && (typeof d.path !== "string" || !isAbsolute(d.path)))
1017
+ || (d.text !== undefined && d.text !== null && typeof d.text !== "string"));
1018
+ if (bad) bail("E_OPERATION_RESULT", `${address} is a view operation but ${provider.capability} ${op.command} did not answer { documents: [{label, kind?, path?, text?}] }`);
1019
+ }
1020
+ // A launch receipt in the delegated result (a harvester the provider
1021
+ // spawned) is surfaced as top-level instance/home so the scheduler tracks
1022
+ // it exactly as it tracks a command job's launch; the source home itself
1023
+ // is `target`, never a launch.
1024
+ const receipt = {};
1025
+ if (typeof result.instance === "string" && result.instance !== meta?.instance) receipt.instance = result.instance;
1026
+ if (typeof result.home === "string" && result.home !== home) receipt.home = result.home;
1027
+ const out = { ...base, ...receipt, result, ...(intent ? { intent } : {}), ...(stderr ? { stderr: stderr.slice(0, 2000) } : {}) };
1028
+ if (JSON_MODE) { jsonOk(out); return; }
1029
+ console.log(`${address} via ${provider.capability}@${provider.version || "?"} (${provider.command} ${op.command}) in ${shortPath(cwd)}: ok`);
1030
+ if (op.kind === "view") for (const d of result.documents) console.log(` - ${d.label}${d.path ? ` (${shortPath(d.path)})` : ""}${d.text ? `: ${String(d.text).split("\n")[0].slice(0, 100)}` : ""}`);
1031
+ else console.log(JSON.stringify(result, null, 2));
1032
+ if (stderr) console.error(stderr);
1033
+ }
641
1034
  function operationCmd() {
642
1035
  const bail = (code, msg, details) => (JSON_MODE ? jsonFail(code, msg, details) : die(msg));
643
1036
  dropAmbientRoot();
@@ -754,50 +1147,7 @@ function operationCmd() {
754
1147
  });
755
1148
  if (op.context === "home") Object.assign(env, { OATS_INSTANCE: meta.instance, OATS_INSTANCE_HOME: home, OATS_HOME: home, PI_AGENT_INSTANCE: meta.instance, PI_AGENT_HOME: home });
756
1149
  const r = spawnSync("node", [abs, ...rest, ...argFlags, "--json"], { cwd, env, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], maxBuffer: 16 * 1024 * 1024, timeout: OPERATION_TIMEOUT_MS, killSignal: "SIGTERM" });
757
- const stderr = String(r.stderr || "").trim();
758
- const timedOut = r.error?.code === "ETIMEDOUT" || (r.status === null && r.signal === "SIGTERM");
759
- if (r.error && !timedOut) bail("E_CAPABILITY_BROKEN", `${address}: ${r.error.message || r.error}`);
760
- const base = { operation: address, capability: provider.capability, version: provider.version || null, argv: [provider.command, op.command, ...argFlags], cwd, target: home ? { home, instance: meta.instance } : null };
761
- // Unconfirmed outcomes (a timeout, no valid receipt, a receipt contradicted
762
- // by the exit status) carry what WAS observed in error.details, so a
763
- // scheduler can keep the slot as unknown and reconcile by any name the
764
- // provider managed to answer; they are never confirmed failures.
765
- const observed = (envelope) => ({ exit: r.status, signal: r.signal || null, unconfirmed: true, ...(envelope && typeof envelope === "object" ? { envelope } : {}), ...(stderr ? { stderr: stderr.slice(0, 2000) } : {}) });
766
- if (timedOut) bail("E_OPERATION_TIMEOUT", `${address} (${provider.capability} ${op.command}) did not finish within ${OPERATION_TIMEOUT_MS / 1000} s; its effects are unconfirmed`, observed(parseEnvelopeText(String(r.stdout || ""))));
767
- // Exactly one JSON-v1 envelope on stdout, nothing else, and an exit status
768
- // that agrees with it: contaminated output or a success envelope from a
769
- // process that then failed is not a receipt.
770
- let envelope;
771
- try { envelope = JSON.parse(String(r.stdout || "").trim()); } catch { envelope = undefined; }
772
- if (!envelope || typeof envelope !== "object" || Array.isArray(envelope) || envelope.schemaVersion !== 1 || typeof envelope.ok !== "boolean") bail("E_OPERATION_RESULT", `${address} (${provider.capability} ${op.command}) did not answer exactly one JSON-v1 envelope on stdout (exit ${r.status}); its effects are unconfirmed${stderr ? `: ${stderr.slice(0, 400)}` : ""}`, observed(parseEnvelopeText(String(r.stdout || ""))));
773
- // A provider's own failure is relayed with its code; its WHOLE envelope
774
- // (a partial receipt such as result.instance of something it launched
775
- // before failing, and any details it gave) travels in error.details so a
776
- // scheduler can keep an unconfirmed outcome and reconcile that target.
777
- if (!envelope.ok) bail(envelope.error?.code || "E_OPERATION_FAILED", `${address}: ${envelope.error?.message || "failed"}`, { exit: r.status, envelope, ...(reportsRetainedEffectsText(envelope.error?.message) ? { unconfirmed: true } : {}) });
778
- if (r.status !== 0) bail("E_OPERATION_RESULT", `${address} (${provider.capability} ${op.command}) answered ok but exited ${r.status}; the receipt is not trusted and its effects are unconfirmed${stderr ? `: ${stderr.slice(0, 400)}` : ""}`, observed(envelope));
779
- const result = envelope.result && typeof envelope.result === "object" ? envelope.result : {};
780
- if (op.kind === "view") {
781
- const docs = result.documents;
782
- const bad = !Array.isArray(docs) || docs.some((d) => !d || typeof d !== "object" || typeof d.label !== "string" || !d.label
783
- || (d.kind !== undefined && !["markdown", "text"].includes(d.kind))
784
- || (d.path !== undefined && d.path !== null && (typeof d.path !== "string" || !isAbsolute(d.path)))
785
- || (d.text !== undefined && d.text !== null && typeof d.text !== "string"));
786
- if (bad) bail("E_OPERATION_RESULT", `${address} is a view operation but ${provider.capability} ${op.command} did not answer { documents: [{label, kind?, path?, text?}] }`);
787
- }
788
- // A launch receipt in the delegated result (a harvester the provider
789
- // spawned) is surfaced as top-level instance/home so the scheduler tracks
790
- // it exactly as it tracks a command job's launch; the source home itself
791
- // is `target`, never a launch.
792
- const receipt = {};
793
- if (typeof result.instance === "string" && result.instance !== meta?.instance) receipt.instance = result.instance;
794
- if (typeof result.home === "string" && result.home !== home) receipt.home = result.home;
795
- const out = { ...base, ...receipt, result, ...(stderr ? { stderr: stderr.slice(0, 2000) } : {}) };
796
- if (JSON_MODE) { jsonOk(out); return; }
797
- console.log(`${address} via ${provider.capability}@${provider.version || "?"} (${provider.command} ${op.command}) in ${shortPath(cwd)}: ok`);
798
- if (op.kind === "view") for (const d of result.documents) console.log(` - ${d.label}${d.path ? ` (${shortPath(d.path)})` : ""}${d.text ? `: ${String(d.text).split("\n")[0].slice(0, 100)}` : ""}`);
799
- else console.log(JSON.stringify(result, null, 2));
800
- if (stderr) console.error(stderr);
1150
+ finishOperation({ r, bail, address, provider, op, argFlags, cwd, home, meta });
801
1151
  }
802
1152
 
803
1153
  // ---------- soul set: runtime defaults and instructions of an editable soul ----------
@@ -3822,6 +4172,7 @@ function scheduleCmd() {
3822
4172
 
3823
4173
  async function sessionCmd() {
3824
4174
  try {
4175
+ if (flag("native-record") !== undefined) throw Object.assign(new Error("--native-record outcome inspection requires exact captured deployment/resolution selectors"), { code: "E_BAD_ARGS" });
3825
4176
  const home = flag("home");
3826
4177
  let result;
3827
4178
  if (args[1] === "attach") {
@@ -4158,7 +4509,7 @@ function versionCmd() {
4158
4509
  // on it (an older CLI without the surface must fail closed with a
4159
4510
  // reason, not an argument error). `features`: kernel abilities a peer
4160
4511
  // must see before relying on them (retire-home: retire --home).
4161
- console.log(JSON.stringify({ schemaVersion: 1, name: "@awebai/oats", version: OATS_VERSION, desktopApi: 1, runtimes: ["pi", "claude", "codex"], sessionBackends: ["tmux", "herdr"], launchOptions: ["yolo"], remote: ["spawn", "retire", "status", "session", "session-start", "session-restart", "launch-config", "roster", "harvest", "schedule", "session-upload", "operations"], features: ["retire-home", "session-start", "session-restart", "launch-config", "schedule", "session-upload", "operations"], scheduleApi: SCHEDULE_API, operationsApi: 1 }));
4512
+ console.log(JSON.stringify({ schemaVersion: 1, name: "@awebai/oats", version: OATS_VERSION, desktopApi: 1, runtimes: ["pi", "claude", "codex"], sessionBackends: ["tmux", "herdr"], launchOptions: ["yolo"], remote: ["spawn", "retire", "status", "session", "session-start", "session-restart", "launch-config", "roster", "harvest", "schedule", "session-upload", "operations"], features: ["retire-home", "session-start", "session-restart", "launch-config", "schedule", "session-upload", "operations"], scheduleApi: SCHEDULE_API, operationsApi: 1, capturedDispatchApi: 1, capturedDispatchActions: ["inspect", "compose", "command", "operation", "spawn", "trust"] }));
4162
4513
  return;
4163
4514
  }
4164
4515
  console.log(`@awebai/oats ${OATS_VERSION} (desktop API v1)`);
@@ -4523,11 +4874,48 @@ async function serverRouteCmd() {
4523
4874
  // blame` pointing at the commit that last changed each command.
4524
4875
  const TYPED_CLI_FAILURES = new Set(["unsafe-config-key", "unsafe-config-value"]);
4525
4876
  try {
4877
+ // Inspect explicit selectors with the existing parser before new-work routing,
4878
+ // including selectors before the command. Inherited captures are not prepare inputs.
4879
+ let captured;
4880
+ try { captured = capturedSelector(args, {}); }
4881
+ catch (error) {
4882
+ if (JSON_MODE) jsonFail(error.code || "E_BAD_ARGS", error.message);
4883
+ die(error.message);
4884
+ }
4885
+ const sourceInspection = (cmd === "inspect" || captured?.args[0] === "inspect")
4886
+ && args.some(arg => arg === "--request" || arg.startsWith("--request="));
4887
+ if (sourceInspection) {
4888
+ if (captured) {
4889
+ if (JSON_MODE) jsonFail("E_BAD_ARGS", "source inspection is explicit new work and cannot use captured selectors");
4890
+ die("source inspection is explicit new work and cannot use captured selectors");
4891
+ }
4892
+ if (args.includes("--help") || args.includes("-h")) { if (JSON_MODE) jsonOk({ command: cmd, usage: usageLinesFor(cmd) }); else usageFor(cmd); process.exit(0); }
4893
+ inspectOnboardingCmd(); process.exit(0);
4894
+ }
4895
+ if (cmd === "prepare" || captured?.args[0] === "prepare") {
4896
+ if (captured) {
4897
+ if (JSON_MODE) jsonFail("E_BAD_ARGS", "prepare is explicit new work and cannot use captured selectors");
4898
+ die("prepare is explicit new work and cannot use captured selectors");
4899
+ }
4900
+ if (args.includes("--help") || args.includes("-h")) { if (JSON_MODE) jsonOk({ command: cmd, usage: usageLinesFor(cmd) }); else usageFor(cmd); process.exit(0); }
4901
+ prepareCmd(); process.exit(0);
4902
+ }
4903
+ // Other commands retain their existing explicit/inherited selection rules.
4904
+ try { captured ??= capturedSelector(args); }
4905
+ catch (error) {
4906
+ if (JSON_MODE) jsonFail(error.code || "E_BAD_ARGS", error.message);
4907
+ die(error.message);
4908
+ }
4909
+ if (captured) {
4910
+ args.splice(0, args.length, ...captured.args); cmd = args[0];
4911
+ // Host protocol negotiation describes this executable, not a mutable
4912
+ // configuration. An inherited capture must not break `oats version` probes.
4913
+ if (captured.explicit || cmd !== "version") { capturedCommand(captured); process.exit(0); }
4914
+ }
4526
4915
  // `--help`/`-h` anywhere after a kernel command prints that command's usage
4527
4916
  // and exits 0 BEFORE any dispatch: a fresh operator inspects --help before
4528
4917
  // using a command, and `install --help` once ran the bare restore while
4529
4918
  // `okf harvest --help` spawned a harvester (BeadHub, 2026-09-05).
4530
- const KERNEL_COMMANDS = new Set(["capture", "config", "create", "doctor", "inspect", "operation", "soul", "launch-config", "experimental", "init", "inject", "install", "list", "migrate", "pane", "recall", "remove", "retire", "root", "schedule", "server", "session", "setup", "spawn", "status", "trust", "type", "update", "use", "version"]);
4531
4919
  const wantsHelp = args.slice(1).some((a) => a === "--help" || a === "-h");
4532
4920
  if (cmd && KERNEL_COMMANDS.has(cmd) && wantsHelp) { if (JSON_MODE) { jsonOk({ command: cmd, usage: usageLinesFor(cmd) }); process.exit(0); } usageFor(cmd); process.exit(0); }
4533
4921
  if (flag("server") !== undefined && ["spawn", "retire", "status", "session", "okf", "schedule", "inspect", "operation", "use", "soul", "launch-config"].includes(cmd)) await serverRouteCmd();
@@ -4821,6 +5209,37 @@ The turn record (core — every conversation captured, searchable, replicated):
4821
5209
  design, repo checkout only; see
4822
5210
  packages/experimental/README.md
4823
5211
 
5212
+ oats inspect --request <absolute-json-file> [--json]
5213
+ read-only fresh source/workspace/member metadata;
5214
+ no current config, provider execution or preparation authority
5215
+ oats prepare --request <absolute-json-file> [--json]
5216
+ complete public preparation input; no mixed flags,
5217
+ inherited binding, implicit setup or launch authority
5218
+ oats prepare --dir <abs> --source <git repo> --revision <ref> --export <path>
5219
+ --alias <name> [--work <mode>] [--json] prepare retained commands and curriculum,
5220
+ no launch; provider gaps report incomplete
5221
+ oats prepare --dir <abs> --workspace <git repo> --alias <advertised alias>
5222
+ [--workspace-revision <ref>] [--work <mode>] [--json]
5223
+ same preparation through workspace imports
5224
+ oats inspect --deployment <abs> --resolution <id> [--composition] [--json]
5225
+ [--helper <exact-map-key>] inspect retained source/helper inputs, not today's configuration
5226
+ oats trust <capability> --deployment <abs> --resolution <id> [--json]
5227
+ explicitly approve that exact captured artifact
5228
+ oats <namespace> <command> --deployment <abs> --resolution <id> -- [args…]
5229
+ run the approved retained command; no ambient fallback
5230
+ oats operation run <layer>:<name> --deployment <abs> --resolution <id>
5231
+ [--home <abs>] [--arg k=v ...] [--retry-intent <saved-id>] [--json]
5232
+ home actions admit distinct requests; explicit retry reuses intent
5233
+ scope views stay read-only; scope mutation is not yet qualified
5234
+ oats spawn <captured subject> --deployment <abs> --resolution <id>
5235
+ --home <abs> --no-launch [--json] create a fresh directory scaffold and run captured hooks
5236
+ oats session inspect --deployment <abs> --resolution <id> --home <abs> --native-record <UUID> [--helper <exact-key>] --json
5237
+ read-only Pi completion observation; not admission or readiness
5238
+ oats session start|restart --deployment <abs> --resolution <id> --home <abs>
5239
+ [--helper <exact-source-map-key>] [--request <abs-json>] [--retry-intent <saved-id>] [--json]
5240
+ dispatch the owned captured home via existing native custody;
5241
+ version1 request: backend/task/stopGraceMs, no model override
5242
+
4824
5243
  oats <namespace> <command> [args…] run an operational command only when its
4825
5244
  capability is active (e.g. oats okf harvest)
4826
5245
 
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env node
2
+ import { runBindingWire } from '../lib/binding-wire.mjs';
3
+
4
+ const args=process.argv.slice(2);
5
+ if(args.includes('--help') || args.includes('-h')) {
6
+ process.stdout.write('oats okf provider binding phase (manifest-owned JSON stdin/stdout)\n');
7
+ } else {
8
+ const phase=args[0];
9
+ if(args.length!==1) {
10
+ await runBindingWire(phase,[],process.stdout);
11
+ } else {
12
+ await runBindingWire(phase);
13
+ }
14
+ }