@awebai/oats 0.25.9 → 0.26.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 (177) hide show
  1. package/README.md +8 -6
  2. package/bin/oats.mjs +576 -1714
  3. package/capabilities/oats-authoring/oats-package.json +2 -2
  4. package/capabilities/oats-authoring/oats.json +2 -2
  5. package/capabilities/oats-authoring/skills/integration-authoring/SKILL.md +46 -25
  6. package/capabilities/oats-authoring/skills/soul-craft/SKILL.md +13 -6
  7. package/capabilities/oats-aweb/bin/oats-aweb.mjs +279 -93
  8. package/capabilities/oats-aweb/injects/aweb.md +7 -2
  9. package/capabilities/oats-aweb/lib/binding-wire.mjs +89 -13
  10. package/capabilities/oats-aweb/lib/captured-native.mjs +1 -1
  11. package/capabilities/oats-aweb/lib/grant-custody.mjs +38 -0
  12. package/capabilities/oats-aweb/oats.json +8 -4
  13. package/capabilities/oats-jira/bin/oats-jira.mjs +4 -4
  14. package/capabilities/oats-jira/oats.json +2 -2
  15. package/capabilities/oats-jira/skills/jira-tasks/SKILL.md +6 -3
  16. package/capabilities/oats-linear/bin/oats-linear-hook.mjs +6 -4
  17. package/capabilities/oats-linear/oats.json +2 -2
  18. package/capabilities/oats-linear/skills/linear-tasks/SKILL.md +6 -0
  19. package/capabilities/oats-review/oats.json +3 -2
  20. package/docs/capabilities.md +218 -47
  21. package/docs/capability-manifest.schema.json +13 -4
  22. package/docs/configuration.md +17 -5
  23. package/docs/conventions.md +16 -26
  24. package/docs/design/2026-09-08-expert-assisted-deployment-proposal.md +1 -1
  25. package/docs/design/2026-09-13-knowledge-and-memory-direction.md +3 -3
  26. package/docs/design/2026-09-14-portable-souls-and-git-workspaces.md +2 -2
  27. package/docs/design/2026-09-15-portable-souls-handoff.md +2 -2
  28. package/docs/design/2026-09-15-portable-souls-implementation.md +1 -1
  29. package/docs/design/2026-09-20-redesign-program-board.md +2 -2
  30. package/docs/design/2026-09-23-workspace-module-contracts.md +1 -1
  31. package/docs/design/2026-09-23-workspace-v2-implementation-plan.md +1 -1
  32. package/docs/design/2026-09-24-desktop-phase-f-boundary.md +34 -1
  33. package/docs/design/2026-09-24-phase-d-plan.md +57 -0
  34. package/docs/design/2026-09-25-teams-contract.md +226 -0
  35. package/docs/design/README.md +3 -3
  36. package/docs/design/launch-configurations.md +20 -16
  37. package/docs/design/operations-contract.md +27 -10
  38. package/docs/desktop-cli-api.md +537 -261
  39. package/docs/desktop-instance-start.md +1 -1
  40. package/docs/desktop.md +7 -13
  41. package/docs/execution-targets.md +16 -18
  42. package/docs/first-team.md +14 -17
  43. package/docs/implementation.md +28 -59
  44. package/docs/integrations.md +64 -33
  45. package/docs/knowledge-capability-authoring.md +1 -1
  46. package/docs/knowledge-reference/package-craft.md +10 -8
  47. package/docs/knowledge-theory.md +1 -1
  48. package/docs/knowledge.md +10 -11
  49. package/docs/layers.md +16 -17
  50. package/docs/oats-local.schema.json +29 -1
  51. package/docs/oats-membership.schema.json +5 -3
  52. package/docs/oats-package.schema.json +2 -2
  53. package/docs/oats-workspace.schema.json +1 -1
  54. package/docs/{official-marketplace.md → official-catalog.md} +15 -16
  55. package/docs/packages.md +75 -52
  56. package/docs/release-notes/v0.22.0.md +1 -1
  57. package/docs/release-notes/v0.23.1.md +1 -1
  58. package/docs/release-notes/v0.26.0.md +670 -0
  59. package/docs/schedules.md +48 -126
  60. package/docs/soul.schema.json +11 -4
  61. package/docs/souls-and-instances.md +56 -43
  62. package/docs/workspaces.md +80 -58
  63. package/injects/instance-boundary.md +1 -1
  64. package/injects/work-attached.md +1 -1
  65. package/injects/work-workspace.md +2 -2
  66. package/lib/{portable-files.mjs → bounded-read.mjs} +6 -6
  67. package/lib/{portable-values.mjs → canonical-json.mjs} +3 -12
  68. package/lib/capability-contract.mjs +110 -0
  69. package/lib/config-data.mjs +2 -2
  70. package/lib/core.mjs +700 -4824
  71. package/lib/digest.mjs +12 -0
  72. package/lib/instance-inspect.mjs +396 -0
  73. package/lib/instance-lifecycle.mjs +3 -4
  74. package/lib/instance-resolution.mjs +212 -26
  75. package/lib/instruction-composition.mjs +0 -20
  76. package/lib/materialize.mjs +6 -4
  77. package/lib/operator-dispatch.mjs +33 -13
  78. package/lib/packages.mjs +25 -190
  79. package/lib/provider-binding.mjs +4 -2
  80. package/lib/provider-reasons.mjs +3 -68
  81. package/lib/resolve.mjs +204 -68
  82. package/lib/schedule.mjs +97 -272
  83. package/lib/servers.mjs +13 -13
  84. package/lib/{portable-shape.mjs → shape.mjs} +4 -3
  85. package/lib/tree-copy.mjs +44 -0
  86. package/lib/workspace.mjs +125 -20
  87. package/package-catalog.json +6 -6
  88. package/package.json +1 -1
  89. package/skills/integration-authoring/SKILL.md +48 -40
  90. package/skills/oats-getting-started/SKILL.md +105 -110
  91. package/skills/oats-support/SKILL.md +2 -2
  92. package/skills/soul-craft/SKILL.md +13 -6
  93. package/bin/oats-pi-sdk-host.mjs +0 -17
  94. package/docs/2026-09-03-architecture-proposal.md +0 -642
  95. package/docs/artifact-approvals.schema.json +0 -7
  96. package/docs/captured-invocation-context.schema.json +0 -7
  97. package/docs/captured-resolution.schema.json +0 -7
  98. package/docs/design/package-engine-contract.md +0 -813
  99. package/docs/design/package-runtime-api.md +0 -588
  100. package/docs/desktop-succession.md +0 -57
  101. package/docs/execution-capsule.schema.json +0 -108
  102. package/docs/first-team-demo.md +0 -92
  103. package/docs/knowledge-migration.md +0 -147
  104. package/docs/migration-from-oas.md +0 -103
  105. package/docs/oats-config.schema.json +0 -172
  106. package/docs/oats-lock-v3.schema.json +0 -7
  107. package/docs/oats-lock.schema.json +0 -175
  108. package/docs/operating-team-migration.md +0 -470
  109. package/docs/portable.schema.json +0 -2512
  110. package/docs/provider-check-input.schema.json +0 -7
  111. package/docs/rebuild-to-v2.md +0 -511
  112. package/docs/workspace-adoption.md +0 -74
  113. package/injects/framework-workspace.md +0 -7
  114. package/injects/local-soul.md +0 -19
  115. package/injects/oats-portable.md +0 -20
  116. package/injects/oats.md +0 -11
  117. package/injects/portable-instance-boundary.md +0 -39
  118. package/injects/portable-work-directory.md +0 -29
  119. package/lib/artifact-approvals.mjs +0 -120
  120. package/lib/artifact-tree.mjs +0 -141
  121. package/lib/capability-artifacts.mjs +0 -179
  122. package/lib/capability-execution.mjs +0 -15
  123. package/lib/capability-inputs.mjs +0 -39
  124. package/lib/capability-provenance.mjs +0 -231
  125. package/lib/captured-action-shape.mjs +0 -21
  126. package/lib/captured-admission-shape.mjs +0 -20
  127. package/lib/captured-binding-file.mjs +0 -36
  128. package/lib/captured-dispatch.mjs +0 -66
  129. package/lib/captured-instance-index.mjs +0 -277
  130. package/lib/captured-invocation-context.mjs +0 -130
  131. package/lib/captured-launch-request.mjs +0 -66
  132. package/lib/captured-operation-process.mjs +0 -15
  133. package/lib/captured-pi-custody.mjs +0 -29
  134. package/lib/captured-pi-host.mjs +0 -167
  135. package/lib/captured-pi-outcome.mjs +0 -172
  136. package/lib/captured-resolutions.mjs +0 -275
  137. package/lib/captured-scaffold.mjs +0 -87
  138. package/lib/captured-selector.mjs +0 -28
  139. package/lib/captured-session-backend.mjs +0 -52
  140. package/lib/captured-source-receipt-file.mjs +0 -72
  141. package/lib/helper-injection-policy.mjs +0 -104
  142. package/lib/legacy-lock-codec.mjs +0 -106
  143. package/lib/manifest-settings.mjs +0 -84
  144. package/lib/package-closure.mjs +0 -48
  145. package/lib/package-materialization.mjs +0 -83
  146. package/lib/pi-sdk-host.mjs +0 -229
  147. package/lib/portable-artifacts.mjs +0 -115
  148. package/lib/portable-choices.mjs +0 -82
  149. package/lib/portable-composition.mjs +0 -136
  150. package/lib/portable-digest.mjs +0 -105
  151. package/lib/portable-identity.mjs +0 -40
  152. package/lib/portable-lock.mjs +0 -117
  153. package/lib/portable-onboarding-request.mjs +0 -49
  154. package/lib/portable-onboarding.mjs +0 -256
  155. package/lib/portable-package-preparation.mjs +0 -188
  156. package/lib/portable-policy.mjs +0 -44
  157. package/lib/portable-soul.mjs +0 -42
  158. package/lib/portable-state.mjs +0 -80
  159. package/lib/prepare-composition.mjs +0 -170
  160. package/lib/prepared-bindings.mjs +0 -92
  161. package/lib/prepared-resources.mjs +0 -127
  162. package/lib/provider-binding-broker.mjs +0 -65
  163. package/lib/provider-binding-wire.mjs +0 -116
  164. package/lib/readiness.mjs +0 -225
  165. package/lib/repository-observation.mjs +0 -226
  166. package/lib/resolution-shape.mjs +0 -393
  167. package/lib/schedule-capsule.mjs +0 -206
  168. package/lib/soul-constraints.mjs +0 -40
  169. package/lib/source-projection.mjs +0 -84
  170. package/lib/source-spec.mjs +0 -189
  171. package/lib/workspace-definition.mjs +0 -126
  172. package/lib/workspace-discovery.mjs +0 -146
  173. package/skills/oats/SKILL.md +0 -162
  174. package/skills/oats-config/SKILL.md +0 -164
  175. package/skills/oats-packages/SKILL.md +0 -184
  176. package/skills/oats-portable/SKILL.md +0 -115
  177. package/skills/oats-portable-artifacts/SKILL.md +0 -63
@@ -1,83 +0,0 @@
1
- /** Shared materialization mechanics. Kernel manifest/runtime validators are
2
- * supplied once by the caller; this module selects no source, lock or approval. */
3
- import { chmodSync, lstatSync, mkdirSync, mkdtempSync, readdirSync, renameSync, rmSync, rmdirSync, writeFileSync } from "node:fs";
4
- import { dirname, join } from "node:path";
5
- import { capabilityArtifactIntegrity, copyTreeSafe } from "./artifact-tree.mjs";
6
- import { CAPABILITY_INSTALLATION_FILE } from "./capability-provenance.mjs";
7
- import { executableSurfaceOf } from "./capability-execution.mjs";
8
- import { oatsError } from "./errors.mjs";
9
-
10
- /** Provenance format v1 is byte-stable across writer kernels and digest versions:
11
- * exactly these keys/order, two-space JSON, one LF, mode0644, no writer version. */
12
- function capabilityInstallationRecord(cap, pkg) {
13
- return {
14
- schemaVersion: 1,
15
- capability: cap.id,
16
- version: cap.manifest.version,
17
- package: pkg.package,
18
- packageVersion: pkg.version,
19
- source: pkg.source,
20
- commit: pkg.commit,
21
- packagePath: pkg.path,
22
- capabilityPath: cap.rel,
23
- };
24
- }
25
- function assertMaterializationInput(root) {
26
- if (!lstatSync(root).isDirectory()) throw oatsError("invalid-package-manifest", "a materialized capability root must be a real directory, not a source alias");
27
- let metadata;
28
- try { metadata = lstatSync(join(root, CAPABILITY_INSTALLATION_FILE)); }
29
- catch (error) { if (error.code === "ENOENT") return; throw error; }
30
- if (!metadata.isFile() || (metadata.mode & 0o100) !== 0 || !readdirSync(root).includes(CAPABILITY_INSTALLATION_FILE)) {
31
- throw oatsError("invalid-package-manifest", "source provenance entry must not be a link, executable, directory or filesystem alias");
32
- }
33
- }
34
- function writeCapabilityInstallation(dest, cap, pkg) {
35
- assertMaterializationInput(dest);
36
- const temporary = mkdtempSync(join(dest, ".installation-")), candidate = join(temporary, "record.json");
37
- let failed = false, primary;
38
- try {
39
- writeFileSync(candidate, JSON.stringify(capabilityInstallationRecord(cap, pkg), null, 2) + "\n", { flag: "wx", mode: 0o644 });
40
- chmodSync(candidate, 0o644);
41
- // Replace the reserved entry itself, never follow a source-controlled link
42
- // or mutate another file sharing its inode. Valid v1 serialization is unchanged.
43
- renameSync(candidate, join(dest, CAPABILITY_INSTALLATION_FILE));
44
- } catch (error) { failed = true; primary = error; throw error; }
45
- finally {
46
- try { rmSync(candidate, { force: true }); rmdirSync(temporary); }
47
- catch (cleanup) {
48
- const error = failed ? new AggregateError([primary, cleanup], "provenance write and staging cleanup failed", { cause: primary })
49
- : new Error("provenance staging cleanup failed", { cause: cleanup });
50
- error.code = failed ? primary.code : cleanup.code; error.stagingPath = temporary; throw error;
51
- }
52
- }
53
- }
54
-
55
- export function createCapabilityMaterializer({ materializeCapabilityDeps, assertCapabilitySelfContained, assertMaterializedDepsContained, assertNoNativeBinaries }) {
56
- for (const validate of [materializeCapabilityDeps, assertCapabilitySelfContained, assertMaterializedDepsContained, assertNoNativeBinaries]) {
57
- if (typeof validate !== "function") throw new TypeError("materializer requires the kernel's complete validation callbacks");
58
- }
59
- /** The returned legacy integrity remains literal compatibility evidence. New
60
- * preparation computes the explicit new-format integrity over this same tree. */
61
- return function materializeCapability({ cap, pkg, artifactsDir }) {
62
- assertMaterializationInput(cap.dir);
63
- const rep = materializeCapabilityDeps(cap.dir);
64
- if (rep.error) throw oatsError("invalid-package-manifest", `runtime dependency materialization failed for capability "${cap.id}" of package "${pkg.package}": ${rep.error}`);
65
- assertCapabilitySelfContained(cap.dir, cap.manifest);
66
- assertMaterializedDepsContained(cap.dir);
67
- assertNoNativeBinaries(cap.dir);
68
- const dest = join(artifactsDir, cap.id);
69
- mkdirSync(dirname(dest), { recursive: true });
70
- if (cap.rel === ".") {
71
- // Published flat packages need their original root for subsequent template
72
- // reads; copy instead of moving, preserving literal links and real modes.
73
- copyTreeSafe(cap.dir, dest);
74
- } else renameSync(cap.dir, dest);
75
- writeCapabilityInstallation(dest, cap, pkg);
76
- return {
77
- capability: cap.id, version: cap.manifest.version, package: pkg.package, path: cap.rel,
78
- dir: dest, integrity: capabilityArtifactIntegrity(dest),
79
- layer: cap.manifest.layer ?? null,
80
- executableSurface: executableSurfaceOf(cap.manifest), manifest: cap.manifest,
81
- };
82
- };
83
- }
@@ -1,229 +0,0 @@
1
- /** Explicit, zero-plugin Pi SDK host. No auth wrapper, default resource loader,
2
- * private SDK imports, model substitution or interpretation of legacy Pi argv. */
3
- import { lstatSync, realpathSync } from "node:fs";
4
- import { isAbsolute, join, resolve, sep } from "node:path";
5
- import { fileURLToPath, pathToFileURL } from "node:url";
6
- import { readPortableBytes } from "./portable-files.mjs";
7
- import { parseStrictJson } from "./portable-values.mjs";
8
- import { oatsError } from "./errors.mjs";
9
-
10
- export const PI_SDK_HOST_VERSION = "1";
11
- export const PI_SDK_VERSION = "0.85.1";
12
- export const PI_SDK_HOST = fileURLToPath(new URL("../bin/oats-pi-sdk-host.mjs", import.meta.url));
13
- const fail = (code, message) => { throw oatsError(code, message); };
14
- const absolute = value => typeof value === "string" && value.length > 0 && !value.includes("\0") && isAbsolute(value) && resolve(value) === value;
15
- export function isPiSdkHost(recipe) {
16
- return recipe?.runtime === "pi" && recipe.executable === PI_SDK_HOST;
17
- }
18
-
19
- export function parsePiHostModel(value) {
20
- if (typeof value !== "string" || !/^[A-Za-z0-9][A-Za-z0-9._-]*\/[^\s\0*?\\]+$/.test(value)
21
- || value.includes("!") || value.includes("$") || value.includes("..")) {
22
- fail("E_PI_HOST_MODEL", "captured Pi host requires an exact provider/model identifier");
23
- }
24
- const slash = value.indexOf("/");
25
- return { provider: value.slice(0, slash), id: value.slice(slash + 1) };
26
- }
27
-
28
- /** Only operator-owned options; internal home/session/model/task are NEVER here. */
29
- export function parsePiHostRecipeArgs(args) {
30
- const keys = ["--oats-pi-host", "--mode", "--thinking", "--sdk-root", "--sdk-version"];
31
- if (!Array.isArray(args) || args.length !== keys.length * 2
32
- || args.some(value => typeof value !== "string" || value.includes("\0"))
33
- || keys.some((key, i) => args[i * 2] !== key)
34
- || args[1] !== PI_SDK_HOST_VERSION || args[3] !== "print" || args[5] !== "medium"
35
- || !absolute(args[7]) || args[9] !== PI_SDK_VERSION) {
36
- fail("E_PI_HOST_ARGS", "unsupported captured Pi SDK host profile or arguments");
37
- }
38
- return { hostVersion: args[1], mode: args[3], thinkingLevel: args[5], sdkRoot: args[7], sdkVersion: args[9] };
39
- }
40
-
41
- export function validatePiHostRecipe(recipe) {
42
- if (!isPiSdkHost(recipe) || recipe.executableResource || (recipe.executableResolvedFrom !== undefined && recipe.executableResolvedFrom !== "explicit-host")) {
43
- fail("E_PI_HOST_SELECTION", "Pi SDK host requires its exact explicit kernel executable");
44
- }
45
- const options = parsePiHostRecipeArgs(recipe.args);
46
- parsePiHostModel(recipe.model);
47
- if (!recipe.env || typeof recipe.env !== "object" || Array.isArray(recipe.env) || Object.keys(recipe.env).length
48
- || recipe.yolo !== false) fail("E_PI_HOST_SELECTION", "Pi host-v1 has no launch environment contributions or yolo policy");
49
- if (recipe.hooks && (Object.keys(recipe.hooks.env ?? {}).length || Object.keys(recipe.hooks.launch ?? {}).length || (recipe.hooks.contributions ?? []).length)) {
50
- fail("E_PI_HOST_SELECTION", "Pi host-v1 does not accept launch-hook contributions");
51
- }
52
- return options;
53
- }
54
-
55
- export function piHostArgv(recipe, { home, sessionDir, taskFile = join(home, "TASK.md") }) {
56
- validatePiHostRecipe(recipe);
57
- if (![home, sessionDir, taskFile].every(absolute) || taskFile !== join(home, "TASK.md")) fail("E_PI_HOST_ARGS", "invalid kernel Pi host paths");
58
- return ["--home", home, "--session-dir", sessionDir, "--model", recipe.model, "--task-file", taskFile, ...recipe.args];
59
- }
60
-
61
- export function parsePiHostArgv(argv) {
62
- const keys = ["--home", "--session-dir", "--model", "--task-file"];
63
- if (!Array.isArray(argv) || argv.length !== 18 || keys.some((key, i) => argv[i * 2] !== key)) {
64
- fail("E_PI_HOST_ARGS", "Pi host accepts only the kernel-rendered argv prefix and closed host profile");
65
- }
66
- const [home, sessionDir, model, taskFile] = keys.map((_, i) => argv[i * 2 + 1]);
67
- if (![home, sessionDir, taskFile].every(absolute) || taskFile !== join(home, "TASK.md")) fail("E_PI_HOST_ARGS", "invalid kernel Pi host paths");
68
- const modelSelection = parsePiHostModel(model);
69
- return { home, sessionDir, model, modelSelection, taskFile, ...parsePiHostRecipeArgs(argv.slice(8)) };
70
- }
71
-
72
- /** Package metadata only; no SDK import, auth inspection, refresh or model call. */
73
- export function resolvePiSdkEntry({ sdkRoot, sdkVersion }) {
74
- if (!absolute(sdkRoot) || sdkVersion !== PI_SDK_VERSION || !lstatSync(sdkRoot).isDirectory() || realpathSync(sdkRoot) !== sdkRoot) {
75
- fail("E_PI_HOST_SDK", "Pi SDK root must be the exact physical selected installation");
76
- }
77
- // OATS-managed payloads must use their own retained-resource approval surface.
78
- if (sdkRoot.split(sep).includes(".agents")) fail("E_PI_HOST_SDK", "managed OATS resources cannot be used as an external SDK installation");
79
- const pkg = parseStrictJson(readPortableBytes(join(sdkRoot, "package.json")));
80
- const entry = pkg.exports?.["."]?.import;
81
- if (pkg.name !== "@earendil-works/pi-coding-agent" || pkg.version !== sdkVersion || typeof entry !== "string" || !entry.startsWith("./")) {
82
- fail("E_PI_HOST_SDK", "selected SDK package/name/version/public export does not match the host profile");
83
- }
84
- const file = resolve(sdkRoot, entry);
85
- if (!file.startsWith(sdkRoot + sep) || realpathSync(file) !== file || !lstatSync(file).isFile()) fail("E_PI_HOST_SDK", "Pi public SDK export escapes its selected installation");
86
- return pathToFileURL(file).href;
87
- }
88
-
89
- export function readPiHostTask(taskFile) {
90
- const bytes = readPortableBytes(taskFile);
91
- if (bytes.length > 512 * 1024) fail("E_PI_HOST_TASK", "captured Pi task exceeds the launch bound");
92
- let task;
93
- try { task = new TextDecoder("utf-8", { fatal: true }).decode(bytes); }
94
- catch { fail("E_PI_HOST_TASK", "captured Pi task is not UTF-8"); }
95
- if (!task.trim() || task.includes("\0")) fail("E_PI_HOST_TASK", "captured Pi print requires a nonempty task");
96
- return { bytes, task };
97
- }
98
-
99
- /** Public ResourceLoader interface. verify() reopens retained authority on EVERY
100
- * reload, before loadSkills reads anything. No SDK discovery/loader is copied. */
101
- export function createPiResourceLoader(sdk, { cwd, agentDir, verify }) {
102
- let agents, skills, extensions;
103
- const ready = () => { if (!agents) fail("E_PI_HOST_CURRICULUM", "Pi curriculum has not been verified"); };
104
- return {
105
- getExtensions() { ready(); return extensions; },
106
- getSkills() { ready(); return skills; },
107
- getPrompts: () => ({ prompts: [], diagnostics: [] }),
108
- getThemes: () => ({ themes: [], diagnostics: [] }),
109
- getAgentsFiles() { ready(); return { agentsFiles: [agents] }; },
110
- getSystemPrompt: () => undefined,
111
- getSystemPromptSource: () => undefined,
112
- getAppendSystemPrompt: () => [],
113
- getAppendSystemPromptSources: () => [],
114
- extendResources(paths) {
115
- if (!paths || typeof paths !== "object" || Array.isArray(paths)
116
- || Object.keys(paths).some(key => !["skillPaths", "promptPaths", "themePaths"].includes(key))
117
- || Object.values(paths).some(value => !Array.isArray(value) || value.length)) {
118
- fail("E_PI_HOST_CURRICULUM", "unselected Pi resource additions are not supported");
119
- }
120
- },
121
- async reload() {
122
- agents = skills = extensions = undefined; // failed reload cannot expose a stale verified view
123
- const selected = await verify();
124
- if (selected.cwd !== cwd || typeof selected.text !== "string" || !Array.isArray(selected.skills)) fail("E_PI_HOST_CURRICULUM", "Pi retained curriculum context changed");
125
- const result = sdk.loadSkills({ cwd, agentDir, skillPaths: selected.skills.map(skill => skill.path), includeDefaults: false });
126
- if (result.diagnostics.length || result.skills.length !== selected.skills.length) fail("E_PI_HOST_CURRICULUM", "Pi skill loader did not consume the exact retained skill set");
127
- for (const wanted of selected.skills) {
128
- const found = result.skills.filter(skill => skill.name === wanted.name);
129
- if (found.length !== 1 || realpathSync(found[0].filePath) !== realpathSync(wanted.path)) fail("E_PI_HOST_CURRICULUM", "Pi skill identity differs from its retained selection");
130
- }
131
- await verify(); // no selected-file drift during SDK parsing
132
- agents = { path: selected.agentsPath, content: selected.text };
133
- skills = result;
134
- extensions = { extensions: [], errors: [], runtime: sdk.createExtensionRuntime() };
135
- },
136
- };
137
- }
138
-
139
- /** Print exposes no history UI/import/resume/branch operations. The proxy is a
140
- * before-delegation boundary around the real public SDK runtime, not an engine
141
- * replacement. It never calls a late factory after opening an unowned target. */
142
- export function guardPiPrintRuntime(runtime) {
143
- const unsupported = new Set(["switchSession", "importFromJsonl", "fork", "newSession"]);
144
- return new Proxy(runtime, {
145
- get(target, key) {
146
- if (unsupported.has(key)) return () => fail("E_PI_HOST_HISTORY", "session replacement/import is not supported by this explicit print profile");
147
- const value = Reflect.get(target, key, target);
148
- return typeof value === "function" ? value.bind(target) : value;
149
- },
150
- });
151
- }
152
-
153
- /** Non-authorizing metadata from actual PUBLIC SDK/session objects. No prompt,
154
- * content, raw error, credential/profile or request-as-result material is copied. */
155
- export function observePiSdkSession(session, sdkVersion) {
156
- const text = value => typeof value === "string" && value.length > 0 && value.length <= 4096 && !value.includes("\0") ? value : null;
157
- const manager = session.sessionManager, header = manager.getHeader();
158
- const messages = session.messages, last = messages.at(-1);
159
- const entry = manager.getBranch().findLast(value => value.type === "message");
160
- const native = entry?.message;
161
- const matchesLast = last?.role === "assistant" && native?.role === "assistant"
162
- && native.timestamp === last.timestamp && native.provider === last.provider
163
- && native.model === last.model && native.stopReason === last.stopReason;
164
- const finalAssistant = matchesLast && text(entry.id) && text(native.provider) && text(native.model)
165
- && Number.isFinite(native.timestamp) && native.timestamp >= 0
166
- && ["stop", "length", "toolUse", "error", "aborted"].includes(native.stopReason)
167
- ? { entryId: entry.id, provider: native.provider, model: native.model,
168
- responseModel: text(native.responseModel), stopReason: native.stopReason, timestamp: native.timestamp } : null;
169
- return {
170
- sdkVersion: text(sdkVersion),
171
- header: header?.type === "session" && Number.isSafeInteger(header.version) && text(header.id) && text(header.cwd) && text(header.timestamp)
172
- ? { type: "session", version: header.version, id: header.id, cwd: header.cwd, timestamp: header.timestamp } : null,
173
- sessionId: text(manager.getSessionId()), sessionFile: text(manager.getSessionFile()),
174
- model: text(session.model?.provider) && text(session.model?.id) ? { provider: session.model.provider, id: session.model.id } : null,
175
- finalAssistant,
176
- };
177
- }
178
-
179
- /** Production uses native services in the user's UNCHANGED profile. No options
180
- * are passed to ModelRuntime to override stores/auth/config/refresh behavior.
181
- * Dependencies are injectable for UNIT orchestration checks, not readiness. */
182
- export async function runPiSdkHost(argv, { verifyContext, onOutcome, importSdk = url => import(url), cwd = process.cwd() } = {}) {
183
- const options = parsePiHostArgv(argv);
184
- if (typeof verifyContext !== "function" || realpathSync(cwd) !== options.home) fail("E_PI_HOST_CUSTODY", "Pi host requires its admitted captured home context");
185
- const verify = () => verifyContext(options);
186
- await verify();
187
- const { task } = readPiHostTask(options.taskFile);
188
- const entry = resolvePiSdkEntry(options);
189
- const sdk = await importSdk(entry); // the verified PUBLIC root export only
190
- if (sdk.VERSION !== options.sdkVersion) fail("E_PI_HOST_SDK", "loaded Pi SDK version differs from selection");
191
- await verify();
192
- const agentDir = sdk.getAgentDir(); // native profile resolution, never sessionDir
193
- const modelRuntime = await sdk.ModelRuntime.create();
194
- const model = modelRuntime.getModel(options.modelSelection.provider, options.modelSelection.id);
195
- if (!model || model.provider !== options.modelSelection.provider || model.id !== options.modelSelection.id) fail("E_PI_HOST_MODEL", "selected native Pi model is unavailable; no default was selected");
196
- const factory = async ({ cwd: target, agentDir: profile, sessionManager, sessionStartEvent }) => {
197
- await verify();
198
- if (target !== options.home || profile !== agentDir || sessionManager.getCwd() !== target || sessionManager.getSessionDir() !== options.sessionDir) fail("E_PI_HOST_HISTORY", "Pi runtime context differs from its owned history");
199
- const settingsManager = sdk.SettingsManager.create(target, agentDir);
200
- const resourceLoader = createPiResourceLoader(sdk, { cwd: target, agentDir, verify });
201
- await resourceLoader.reload();
202
- const services = { cwd: target, agentDir, modelRuntime, settingsManager, resourceLoader, diagnostics: [] };
203
- const built = await sdk.createAgentSessionFromServices({ services, sessionManager, sessionStartEvent, model, thinkingLevel: options.thinkingLevel });
204
- if (built.session.model?.id !== model.id || built.session.model?.provider !== model.provider || built.modelFallbackMessage) fail("E_PI_HOST_MODEL", "Pi substituted the captured model");
205
- return { ...built, services, diagnostics: [] };
206
- };
207
- await verify();
208
- const sessionManager = sdk.SessionManager.create(options.home, options.sessionDir);
209
- const runtime = await sdk.createAgentSessionRuntime(factory, { cwd: options.home, agentDir, sessionManager });
210
- let handedToPrint = false, observation = null;
211
- try {
212
- if (onOutcome) runtime.setBeforeSessionInvalidate(() => {
213
- // Public synchronous hook: capture real session data BEFORE disposal, never
214
- // synthesize a header/model/final message from the requested launch values.
215
- try { observation = observePiSdkSession(runtime.session, sdk.VERSION); }
216
- catch { observation = null; } // unavailable evidence cannot qualify success
217
- });
218
- await verify();
219
- if (readPiHostTask(options.taskFile).task !== task) fail("E_PI_HOST_TASK", "captured task changed during Pi startup");
220
- handedToPrint = true; // native runPrintMode owns normal disposal/signals/errors
221
- const exitCode = await sdk.runPrintMode(guardPiPrintRuntime(runtime), { mode: "text", initialMessage: task });
222
- const verified = await verify(); // no successful host outcome after observed custody drift
223
- if (!Number.isInteger(exitCode) || exitCode < 0 || exitCode > 255) fail("E_PI_HOST_OUTCOME", "native print mode returned no valid status");
224
- if (onOutcome) await onOutcome(options, { exitCode, observation }, verified);
225
- return exitCode;
226
- } finally {
227
- if (!handedToPrint) await runtime.dispose();
228
- }
229
- }
@@ -1,115 +0,0 @@
1
- /** New-format immutable tree storage. Selection, source identity observation,
2
- * provenance/requirements, approval and dispatch remain separate upper layers.
3
- * No ambient lock/config/network lookup and no migration of legacy evidence. */
4
- import { lstatSync, mkdirSync, realpathSync } from "node:fs";
5
- import { isAbsolute, join, relative, resolve } from "node:path";
6
- import { assertRetainableTree, ensureStoreIgnore } from "./capability-artifacts.mjs";
7
- import { isMaterializedCapabilityId } from "./capability-provenance.mjs";
8
- import { publishArtifactTree } from "./artifact-tree.mjs";
9
- import { canonicalJson } from "./portable-values.mjs";
10
- import { TREE_FORMAT, jsonIntegrity, treeIntegrity, validateIntegrity } from "./portable-digest.mjs";
11
- import { oatsError } from "./errors.mjs";
12
-
13
- const exists = (path) => {
14
- try { return lstatSync(path); }
15
- catch (error) { if (error.code === "ENOENT") return null; throw error; }
16
- };
17
- const outside = (root, target) => {
18
- const path = relative(root, target);
19
- return path === ".." || path.startsWith(`..${process.platform === "win32" ? "\\" : "/"}`) || isAbsolute(path);
20
- };
21
-
22
- function referenceParts(reference) {
23
- canonicalJson(reference, { maxBytes: 64 * 1024, maxDepth: 16, maxEntries: 1024 });
24
- if (!reference || typeof reference !== "object" || Array.isArray(reference)) {
25
- throw oatsError("invalid-artifact-reference", "expected a portable artifact reference");
26
- }
27
- validateIntegrity(reference.integrity, [TREE_FORMAT]);
28
- const keys = Object.keys(reference).sort().join(",");
29
- let root, extra = [];
30
- if (reference.kind === "capability" && keys === "capability,integrity,kind" && isMaterializedCapabilityId(reference.capability)) {
31
- root = [".agents", "capabilities", "artifacts"]; extra = [reference.capability];
32
- } else if (reference.kind === "soul" && keys === "identity,integrity,kind"
33
- && reference.identity && typeof reference.identity === "object" && !Array.isArray(reference.identity)
34
- && Object.keys(reference.identity).length > 0) {
35
- // The source/record codec qualifies the identity and witness. Storage only
36
- // derives an opaque safe key from that exact identity; aliases are not keys.
37
- root = [".agents", "soul-artifacts"]; extra = [jsonIntegrity(reference.identity).value];
38
- } else if (reference.kind === "resource" && keys === "integrity,kind") {
39
- root = [".agents", "resource-artifacts"];
40
- } else throw oatsError("invalid-artifact-reference", "invalid portable artifact reference kind/fields");
41
- return { root, tail: [...extra, "tree-exec-v1", reference.integrity.value] };
42
- }
43
- function explicitScope(scope) {
44
- if (typeof scope !== "string" || !isAbsolute(scope)) {
45
- throw oatsError("invalid-artifact-store", "artifact deployment scope must be explicit and absolute");
46
- }
47
- try {
48
- const actual = realpathSync(scope);
49
- if (!lstatSync(actual).isDirectory()) throw oatsError("invalid-artifact-store", "artifact scope is not a directory");
50
- return actual;
51
- } catch (error) {
52
- if (error.code === "ENOENT") throw oatsError("artifact-not-found", "artifact deployment scope is absent");
53
- throw error;
54
- }
55
- }
56
-
57
- /** Lexical path only. This does not verify the store, bytes, provenance or trust. */
58
- export function portableArtifactDir(scope, reference) {
59
- if (typeof scope !== "string" || !isAbsolute(scope)) {
60
- throw oatsError("invalid-artifact-store", "artifact deployment scope must be explicit and absolute");
61
- }
62
- const { root, tail } = referenceParts(reference);
63
- return join(resolve(scope), ...root, ...tail);
64
- }
65
- function storeDirectory(scope, reference, create) {
66
- const parts = referenceParts(reference), prefix = [...parts.root, ...parts.tail.slice(0, -1)];
67
- let path = explicitScope(scope);
68
- for (let index = 0; index < prefix.length; index++) {
69
- path = join(path, prefix[index]);
70
- if (create) {
71
- try { mkdirSync(path, { mode: 0o700 }); }
72
- catch (error) { if (error.code !== "EEXIST") throw error; }
73
- }
74
- const stat = exists(path);
75
- if (!stat) throw oatsError("artifact-not-found", "portable artifact store is absent");
76
- if (!stat.isDirectory()) throw oatsError("invalid-artifact-store", "managed artifact store component is not a real directory");
77
- if (create && index === parts.root.length - 1) ensureStoreIgnore(path);
78
- }
79
- return path;
80
- }
81
- function verifyTree(path, reference) {
82
- const stat = exists(path);
83
- if (!stat) throw oatsError("artifact-not-found", "portable artifact tree is absent");
84
- if (!stat.isDirectory()) throw oatsError("invalid-artifact", "portable artifact root must be a real directory");
85
- // Digest traversal is bounded and does not follow links. Then qualify link
86
- // containment; neither check uses the original source or an ambient package row.
87
- const actual = treeIntegrity(path);
88
- if (actual.value !== reference.integrity.value) throw oatsError("integrity-drift", "portable artifact digest does not match its reference");
89
- assertRetainableTree(path);
90
- return path;
91
- }
92
-
93
- /** Byte/containment verification only, not a complete captured-resolution proof. */
94
- export function verifyPortableArtifact(scope, reference) {
95
- const root = storeDirectory(scope, reference, false);
96
- const dir = join(root, reference.integrity.value);
97
- verifyTree(dir, reference);
98
- return { reference, dir };
99
- }
100
- export function retainPortableArtifact(scope, sourceDir, reference) {
101
- referenceParts(reference);
102
- const deployment = explicitScope(scope);
103
- if (typeof sourceDir !== "string" || !isAbsolute(sourceDir)) {
104
- throw oatsError("invalid-artifact", "artifact input must be an explicit materialized directory");
105
- }
106
- verifyTree(sourceDir, reference);
107
- const candidate = portableArtifactDir(deployment, reference), source = realpathSync(sourceDir);
108
- if (!outside(source, candidate) && source !== candidate) {
109
- throw oatsError("invalid-artifact", "artifact source contains its destination store");
110
- }
111
- const parent = storeDirectory(deployment, reference, true), dir = join(parent, reference.integrity.value);
112
- if (exists(dir)) { verifyTree(dir, reference); return { reference, dir, status: "kept" }; }
113
- const status = publishArtifactTree(sourceDir, dir, (tree) => verifyTree(tree, reference));
114
- return { reference, dir, status };
115
- }
@@ -1,82 +0,0 @@
1
- /** The single bounded choice resolver: equality/presence constraints plus
2
- * fallback values. No expressions, source fetching, provider internals or trust.
3
- * Field codecs represent a disabled/unbound selection as null; false/[] may be
4
- * legitimate DATA values. This engine does not guess meaning from strings. */
5
- import { canonicalJson, compareUtf8 } from "./portable-values.mjs";
6
- import { invalidShape, objectAt, stringAt } from "./portable-shape.mjs";
7
-
8
- const PRIORITY = Object.freeze({ "manifest-default": -1, "workspace-default": 0, "soul-default": 1, "import-adoption": 2, operator: 3 });
9
- export const CHOICE_KINDS = Object.freeze(Object.keys(PRIORITY));
10
- const equal = (a, b) => canonicalJson(a) === canonicalJson(b);
11
- const originOrder = (a, b) => compareUtf8(canonicalJson(a.origin), canonicalJson(b.origin))
12
- || compareUtf8(canonicalJson(a), canonicalJson(b));
13
-
14
- export function resolveChoices({ requirements = [], candidates = [] } = {}) {
15
- canonicalJson({ requirements, candidates });
16
- if (!Array.isArray(requirements) || !Array.isArray(candidates)) invalidShape("", "choice inputs must be arrays");
17
- const groups = new Map(), choices = Object.create(null), problems = [];
18
- const group = (key) => {
19
- stringAt(key, "/key");
20
- if (!key.startsWith("/")) invalidShape("/key", "choice keys are absolute JSON pointers");
21
- if (!groups.has(key)) groups.set(key, { requirements: [], candidates: [] });
22
- return groups.get(key);
23
- };
24
- for (const [index, requirement] of requirements.entries()) {
25
- const at = `/requirements/${index}`;
26
- objectAt(requirement, ["key", "kind", "value", "origin"], ["key", "kind", "origin"], at);
27
- if (!["equals", "required"].includes(requirement.kind)) invalidShape(`${at}/kind`, "unsupported constraint kind");
28
- if ((requirement.kind === "equals") !== Object.hasOwn(requirement, "value")) invalidShape(at, "constraint value does not match its kind");
29
- objectAt(requirement.origin, null, [], `${at}/origin`);
30
- group(requirement.key).requirements.push(requirement);
31
- }
32
- for (const [index, candidate] of candidates.entries()) {
33
- const at = `/candidates/${index}`;
34
- objectAt(candidate, ["key", "kind", "value", "origin"], ["key", "kind", "value", "origin"], at);
35
- if (!Object.hasOwn(PRIORITY, candidate.kind)) invalidShape(`${at}/kind`, "unsupported choice origin kind");
36
- objectAt(candidate.origin, null, [], `${at}/origin`);
37
- group(candidate.key).candidates.push(candidate);
38
- }
39
- const conflict = (key, a, b, message) => {
40
- problems.push({ code: "requirement-conflict", key, message, origins: [a.origin, b.origin] });
41
- };
42
- for (const key of [...groups.keys()].sort(compareUtf8)) {
43
- const input = groups.get(key);
44
- input.requirements.sort(originOrder);
45
- input.candidates.sort((a, b) => PRIORITY[a.kind] - PRIORITY[b.kind] || originOrder(a, b));
46
- const fixed = input.requirements.filter((item) => item.kind === "equals");
47
- const presence = input.requirements.find((item) => item.kind === "required");
48
- const seed = fixed[0];
49
- let value = seed ? seed.value : null, selectedBy = seed ? seed.origin : null;
50
- for (const other of fixed.slice(1)) if (!equal(seed.value, other.value)) conflict(key, seed, other, "hard requirements disagree");
51
- if (seed && presence && seed.value === null) conflict(key, presence, seed, "required value is explicitly absent");
52
- const considered = [];
53
- let previous;
54
- for (const candidate of input.candidates) {
55
- if (previous && PRIORITY[candidate.kind] === PRIORITY[previous.kind] && !equal(candidate.value, previous.value)) {
56
- conflict(key, previous, candidate, "equal-authority choices disagree");
57
- }
58
- previous = candidate;
59
- const incompatible = (seed && !equal(candidate.value, seed.value)) || (presence && candidate.value === null);
60
- if (incompatible) {
61
- if (PRIORITY[candidate.kind] >= PRIORITY["import-adoption"]) {
62
- conflict(key, seed ?? presence, candidate, "explicit choice cannot erase a hard requirement");
63
- }
64
- considered.push({ kind: candidate.kind, value: candidate.value, origin: candidate.origin, disposition: "overridden" });
65
- continue;
66
- }
67
- if (!seed || PRIORITY[candidate.kind] >= PRIORITY["import-adoption"]) {
68
- value = candidate.value; selectedBy = candidate.origin;
69
- }
70
- considered.push({ kind: candidate.kind, value: candidate.value, origin: candidate.origin, disposition: "overridden" });
71
- }
72
- for (const entry of considered) {
73
- if (selectedBy && equal(entry.origin, selectedBy) && equal(entry.value, value)) entry.disposition = "selected";
74
- }
75
- if (presence && value === null && !problems.some((item) => item.key === key)) {
76
- problems.push({ code: "needs-configuration", key, message: "required value has no concrete binding", origins: [presence.origin] });
77
- }
78
- choices[key] = { value, selectedBy, constraints: input.requirements.map(({ key: _, ...constraint }) => constraint), considered };
79
- }
80
- return { contractVersion: 1, status: problems.some((item) => item.code === "requirement-conflict")
81
- ? "conflict" : problems.length ? "needs-configuration" : "resolved", choices, problems };
82
- }