@fungi.computer/oyster 0.6.0 → 0.7.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 (163) hide show
  1. package/README.md +132 -78
  2. package/dist/agent-archive-projection.d.ts +8 -0
  3. package/dist/agent-archive-projection.js +49 -0
  4. package/dist/agent-schedules.d.ts +50 -0
  5. package/dist/agent-schedules.js +17 -0
  6. package/dist/agents.d.ts +35 -14
  7. package/dist/agents.js +93 -144
  8. package/dist/app-builds.d.ts +7 -2
  9. package/dist/app-builds.js +19 -2
  10. package/dist/approval-capability.d.ts +1 -1
  11. package/dist/approval-capability.js +11 -16
  12. package/dist/approval-home.d.ts +2 -1
  13. package/dist/approval-home.js +5 -3
  14. package/dist/apps/catalog.js +1 -1
  15. package/dist/apps/gate.d.ts +4 -7
  16. package/dist/apps/gate.js +14 -29
  17. package/dist/apps/index.d.ts +1 -1
  18. package/dist/apps/installation.d.ts +4 -42
  19. package/dist/apps/installation.js +7 -75
  20. package/dist/apps/operations.js +0 -10
  21. package/dist/apps/permissions.d.ts +18 -8
  22. package/dist/apps/permissions.js +7 -1
  23. package/dist/auth.d.ts +1 -24
  24. package/dist/auth.js +9 -39
  25. package/dist/authorization.d.ts +307 -0
  26. package/dist/authorization.js +542 -0
  27. package/dist/boot-host-script.js +1 -1
  28. package/dist/build/build-jobs.d.ts +2 -2
  29. package/dist/build/candidate-builder.d.ts +2 -0
  30. package/dist/build/candidate-builder.js +5 -1
  31. package/dist/build/candidate-cli.js +3 -0
  32. package/dist/build/candidate-cli.mjs +59 -21
  33. package/dist/build/job-log.d.ts +23 -0
  34. package/dist/build/job-log.js +88 -0
  35. package/dist/build/local-build.d.ts +2 -0
  36. package/dist/build/local-build.js +11 -9
  37. package/dist/cloudflare.d.ts +4 -1
  38. package/dist/cloudflare.js +1 -1
  39. package/dist/files.js +9 -8
  40. package/dist/git-admission.d.ts +3 -3
  41. package/dist/git-admission.js +4 -6
  42. package/dist/git-door.d.ts +8 -7
  43. package/dist/git-door.js +18 -17
  44. package/dist/git.d.ts +1 -1
  45. package/dist/host.d.ts +33 -40
  46. package/dist/host.js +153 -272
  47. package/dist/installation-execution.js +10 -5
  48. package/dist/installation-operations.d.ts +4 -180
  49. package/dist/installation-operations.js +1 -5
  50. package/dist/installation-store-generation.d.ts +4 -1
  51. package/dist/installation-store-generation.js +59 -6
  52. package/dist/internal/delivery.js +0 -1
  53. package/dist/internal/host-gate.d.ts +3 -2
  54. package/dist/internal/host-gate.js +5 -18
  55. package/dist/internal/invocation-timing.d.ts +16 -0
  56. package/dist/internal/invocation-timing.js +23 -0
  57. package/dist/internal/registry-installations.js +1 -0
  58. package/dist/invocation.d.ts +15 -14
  59. package/dist/invocation.js +58 -26
  60. package/dist/keys.d.ts +2 -19
  61. package/dist/keys.js +1 -37
  62. package/dist/mcp.d.ts +15 -27
  63. package/dist/mcp.js +105 -155
  64. package/dist/page/agent-browser.d.ts +15 -0
  65. package/dist/page/agent-browser.js +162 -0
  66. package/dist/page/app-permissions-port.d.ts +3 -0
  67. package/dist/page/app-permissions-port.js +43 -0
  68. package/dist/page/app-permissions.d.ts +11 -0
  69. package/dist/page/app-permissions.js +296 -0
  70. package/dist/page/isolated-page-theme.d.ts +2 -0
  71. package/dist/page/isolated-page-theme.js +11 -0
  72. package/dist/page/mount.d.ts +2 -13
  73. package/dist/page/mount.js +7 -93
  74. package/dist/page-script.d.ts +0 -3
  75. package/dist/page-script.js +3 -4
  76. package/dist/standalone/agent-archive-owner.d.ts +42 -0
  77. package/dist/standalone/agent-archive-owner.js +116 -0
  78. package/dist/standalone/agent-archive-schedules.d.ts +6 -0
  79. package/dist/standalone/agent-archive-schedules.js +76 -0
  80. package/dist/standalone/agent-archive.d.ts +31 -0
  81. package/dist/standalone/agent-archive.js +67 -0
  82. package/dist/standalone/agent-computers.d.ts +7 -6
  83. package/dist/standalone/agent-computers.js +4 -7
  84. package/dist/standalone/agent-mail.d.ts +2 -2
  85. package/dist/standalone/agents.d.ts +157 -20
  86. package/dist/standalone/agents.js +234 -30
  87. package/dist/standalone/app-builds-binding.js +1 -1
  88. package/dist/standalone/app-set.js +2 -54
  89. package/dist/standalone/app-source.d.ts +3 -4
  90. package/dist/standalone/app-source.js +12 -9
  91. package/dist/standalone/builds.d.ts +2 -14
  92. package/dist/standalone/builds.js +15 -13
  93. package/dist/standalone/computer-fleet.d.ts +4 -4
  94. package/dist/standalone/computer-fleet.js +13 -42
  95. package/dist/standalone/computer.d.ts +4 -3
  96. package/dist/standalone/computer.js +52 -53
  97. package/dist/standalone/dashboard-approval.d.ts +10 -0
  98. package/dist/standalone/dashboard-approval.js +66 -0
  99. package/dist/standalone/machine-link.d.ts +4 -221
  100. package/dist/standalone/machine-link.js +21 -15
  101. package/dist/standalone/media.d.ts +1 -0
  102. package/dist/standalone/media.js +20 -14
  103. package/dist/standalone/owner-routes.d.ts +16 -0
  104. package/dist/standalone/owner-routes.js +21 -0
  105. package/dist/standalone/permission-resources.d.ts +5 -0
  106. package/dist/standalone/permission-resources.js +31 -0
  107. package/dist/standalone/registry.d.ts +7 -0
  108. package/dist/standalone/registry.js +3 -0
  109. package/dist/standalone/tenancy.d.ts +0 -3
  110. package/dist/standalone/tenancy.js +2 -5
  111. package/dist/standalone/worker-routes.d.ts +19 -0
  112. package/dist/standalone/worker-routes.js +9 -0
  113. package/dist/standalone/worker.d.ts +1 -1
  114. package/dist/standalone/worker.js +27 -35
  115. package/dist/standalone/workflow-binding.d.ts +8 -3
  116. package/dist/standalone/workflow-binding.js +14 -5
  117. package/dist/standalone/workflow-git-capture.js +2 -1
  118. package/dist/standalone/workflow-host.d.ts +8 -103
  119. package/dist/standalone/workflow-host.js +1 -1
  120. package/dist/standalone/workflow-node-capture.js +2 -2
  121. package/dist/standalone/workflow-root-authority.d.ts +1 -1
  122. package/dist/standalone/workflow-root-authority.js +1 -1
  123. package/dist/standalone/workflow-schedules.d.ts +14 -5
  124. package/dist/standalone/workflow-schedules.js +11 -5
  125. package/dist/standalone/workflow-webhooks.d.ts +4 -18
  126. package/dist/standalone/workflow-webhooks.js +5 -5
  127. package/dist/standalone/workflows.d.ts +36 -28
  128. package/dist/standalone/workflows.js +6 -3
  129. package/dist/storage-frame.d.ts +6 -6
  130. package/dist/testing/capabilities.d.ts +7 -6
  131. package/dist/testing/capabilities.js +3 -6
  132. package/dist/testing/installation.d.ts +0 -1
  133. package/dist/testing/installation.js +2 -5
  134. package/dist/workers-git.d.ts +12 -4
  135. package/dist/workers-git.js +38 -35
  136. package/dist/workers.d.ts +9 -1
  137. package/dist/workers.js +44 -8
  138. package/dist/workflows.d.ts +2 -0
  139. package/dist/workflows.js +1 -0
  140. package/docs/api/README.md +7 -0
  141. package/docs/api/agent-schedules.md +175 -0
  142. package/docs/api/agents.md +34 -13
  143. package/docs/api/app-storage.md +1 -1
  144. package/docs/api/apps.md +323 -225
  145. package/docs/api/auth.md +0 -55
  146. package/docs/api/authorization.md +1300 -0
  147. package/docs/api/build.md +40 -37
  148. package/docs/api/cloudflare.md +3 -3
  149. package/docs/api/git.md +20 -11
  150. package/docs/api/host.md +64 -192
  151. package/docs/api/index.md +1 -1
  152. package/docs/api/invocation.md +14 -14
  153. package/docs/api/keys.md +1 -98
  154. package/docs/api/mcp.md +43 -40
  155. package/docs/api/modules.md +2 -0
  156. package/docs/api/page/mount.md +16 -19
  157. package/docs/api/workers-git.md +46 -12
  158. package/docs/api/workers.md +124 -235
  159. package/package.json +39 -20
  160. package/dist/page/computer-target.d.ts +0 -3
  161. package/dist/page/computer-target.js +0 -9
  162. package/dist/standalone/channel-session-door.d.ts +0 -4
  163. package/dist/standalone/channel-session-door.js +0 -72
@@ -87,7 +87,7 @@ export declare function createTeamBuildPublisher(options: Readonly<{
87
87
  readonly expiresAt: number;
88
88
  readonly remote: string;
89
89
  readonly reportUrl: string;
90
- readonly status: "revoked" | "running" | "failed" | "succeeded" | "interrupted";
90
+ readonly status: "revoked" | "failed" | "running" | "succeeded" | "interrupted";
91
91
  readonly artifact: {
92
92
  readonly digest: string;
93
93
  readonly bytes: number;
@@ -103,7 +103,7 @@ export declare function createTeamBuildPublisher(options: Readonly<{
103
103
  readonly expiresAt: number;
104
104
  readonly remote: string;
105
105
  readonly reportUrl: string;
106
- readonly status: "revoked" | "running" | "failed" | "succeeded" | "interrupted";
106
+ readonly status: "revoked" | "failed" | "running" | "succeeded" | "interrupted";
107
107
  readonly artifact: {
108
108
  readonly digest: string;
109
109
  readonly bytes: number;
@@ -56,6 +56,8 @@ export declare function buildTeamCandidate(input: {
56
56
  outputPath: string;
57
57
  /** Trusted Computer-local cache; never part of customer configuration. */
58
58
  packageStoreDirectory?: string;
59
+ /** Host-owned observation of raw subprocess output; never source configuration. */
60
+ onOutput?: (stream: "stdout" | "stderr", bytes: Uint8Array) => void;
59
61
  }): Promise<Readonly<{
60
62
  digest: string;
61
63
  bytes: number;
@@ -48,15 +48,19 @@ export async function buildTeamCandidate(input) {
48
48
  const onPhase = (phase, milliseconds) => {
49
49
  phases[phase] += milliseconds;
50
50
  };
51
+ const onOutput = (stream, bytes) => {
52
+ input.onOutput?.(stream, bytes);
53
+ };
51
54
  const prepareStarted = performance.now();
52
55
  const candidate = await Effect.runPromise(createCapturedCandidatePreparer(config, {
53
56
  sourceCapture: createLocalCommitSourceCapture(input.repositoryPath, config.sourceRevision),
54
57
  buildExecutor: createLocalBuildExecutor(input.packageStoreDirectory === undefined
55
- ? { packageScripts: "run", onPhase }
58
+ ? { packageScripts: "run", onPhase, onOutput }
56
59
  : {
57
60
  packageScripts: "run",
58
61
  packageStoreDirectory: input.packageStoreDirectory,
59
62
  onPhase,
63
+ onOutput,
60
64
  }),
61
65
  }).prepareCandidate(parseReleaseId("rel_build")));
62
66
  const prepared = performance.now() - prepareStarted;
@@ -32,6 +32,9 @@ try {
32
32
  outputPath,
33
33
  // Computer scratch survives captured snapshot cleanup, without a global or durable Workspace store.
34
34
  packageStoreDirectory: "/var/tmp/fungi-app-package-store",
35
+ onOutput: (stream, bytes) => {
36
+ process[stream].write(bytes);
37
+ },
35
38
  });
36
39
  const reportUrl = process.env.TEAM_BUILD_REPORT;
37
40
  if (!reportUrl)
@@ -19518,6 +19518,11 @@ var appBackendBindingKinds = {
19518
19518
  requires: ["workflow.run", "workflow.arm"],
19519
19519
  effect: "write"
19520
19520
  },
19521
+ pauseSchedule: {
19522
+ cap: "app.workflow.run",
19523
+ requires: ["workflow.run", "workflow.arm"],
19524
+ effect: "write"
19525
+ },
19521
19526
  schedules: {
19522
19527
  cap: "app.workflow.read",
19523
19528
  requires: ["workflow.run"],
@@ -19659,7 +19664,22 @@ var appBackendBindingKinds = {
19659
19664
  name: "AGENTS",
19660
19665
  operations: {
19661
19666
  list: { cap: "agent.list", requires: ["agent.list"], effect: "read" },
19662
- send: { cap: "agent.send", requires: ["agent.send"], effect: "write" }
19667
+ send: { cap: "agent.send", requires: ["agent.send"], effect: "write" },
19668
+ schedules: {
19669
+ cap: "agent.read",
19670
+ requires: ["agent.read"],
19671
+ effect: "read"
19672
+ },
19673
+ pauseSchedule: {
19674
+ cap: "agent.manage",
19675
+ requires: ["agent.manage"],
19676
+ effect: "write"
19677
+ },
19678
+ deleteSchedule: {
19679
+ cap: "agent.manage",
19680
+ requires: ["agent.manage"],
19681
+ effect: "write"
19682
+ }
19663
19683
  }
19664
19684
  }
19665
19685
  };
@@ -19714,13 +19734,17 @@ var emptyAppDeclaration = Object.freeze({
19714
19734
 
19715
19735
  // ../puffball/dist/app-api.js
19716
19736
  var appApiMethodNameSchema = String4.check(makeFilter2(isAppApiMethodName));
19737
+ function isJsonContainer(value) {
19738
+ return value !== null && typeof value === "object";
19739
+ }
19717
19740
  function withinDepth(value, depth = 0) {
19718
19741
  if (depth > 16)
19719
19742
  return false;
19720
- if (is2(ArraySchema(Json2))(value))
19721
- return value.every((child) => withinDepth(child, depth + 1));
19722
- if (is2(JsonObject)(value))
19723
- return Object.values(value).every((child) => withinDepth(child, depth + 1));
19743
+ if (isJsonContainer(value)) {
19744
+ for (const child of Object.values(value))
19745
+ if (!withinDepth(child, depth + 1))
19746
+ return false;
19747
+ }
19724
19748
  return true;
19725
19749
  }
19726
19750
  var projection = JsonObject.pipe(check(makeFilter2((value) => new TextEncoder().encode(JSON.stringify(value)).byteLength <= 2e4 && withinDepth(value), { message: "App API schema exceeds its byte or depth bound" })));
@@ -19731,15 +19755,12 @@ var method = Struct({
19731
19755
  output: projection,
19732
19756
  pending: optional2(Literal2(true)),
19733
19757
  hidden: optional2(Literal2(true)),
19734
- anonymous: optional2(Literal2(true)),
19735
19758
  present: optional2(Struct({
19736
19759
  verb: String4.check(isMinLength(1), isMaxLength(32))
19737
19760
  })),
19738
19761
  effect: Literals(["read", "write", "external"]),
19739
19762
  requires: ArraySchema(Literals(appCapabilityIds)).pipe(check(makeFilter2((values) => new Set(values).size === values.length)))
19740
- }).check(makeFilter2((value) => value.anonymous !== true || value.effect === "read" && value.requires.length === 0, {
19741
- message: "Anonymous methods must be reads with no required capabilities"
19742
- }));
19763
+ });
19743
19764
  var appApiSchema = Struct({
19744
19765
  methods: Record(String4, method).pipe(check(makeFilter2((methods) => Object.keys(methods).every(is2(appApiMethodNameSchema)), { message: "Invalid App API method name" })), check(makeFilter2((methods) => Object.keys(methods).length <= 64)), check(makeFilter2((methods) => {
19745
19766
  const names = Object.keys(methods);
@@ -21842,6 +21863,7 @@ var BirdDogAgentJobPayloadSchema = Struct({
21842
21863
  });
21843
21864
  var BirdDogAgentJobInputSchema = Struct({
21844
21865
  work: BirdDogAgentWorkSchema,
21866
+ delivery: Literals(["steer", "followUp"]),
21845
21867
  mail: optional2(BirdDogMailSchema)
21846
21868
  });
21847
21869
 
@@ -35641,7 +35663,7 @@ async function stopHtmlGroup(child) {
35641
35663
  }
35642
35664
  throw new Error("HTML process group survived its five-second cleanup bound");
35643
35665
  }
35644
- function runHtmlCommand(args2, cwd, env, command = "pnpm") {
35666
+ function runHtmlCommand(args2, cwd, env, command = "pnpm", onOutput) {
35645
35667
  return acquireUseRelease2(
35646
35668
  sync2(() => {
35647
35669
  if (process.platform === "win32")
@@ -35656,11 +35678,13 @@ function runHtmlCommand(args2, cwd, env, command = "pnpm") {
35656
35678
  let stdout = "";
35657
35679
  let stderr = "";
35658
35680
  child.stdout.on("data", (chunk) => {
35681
+ onOutput?.("stdout", chunk);
35659
35682
  stdout += chunk.toString();
35660
35683
  if (Buffer2.byteLength(stdout) > 1024 * 1024)
35661
35684
  reject(new Error("HTML command output exceeded its bound"));
35662
35685
  });
35663
35686
  child.stderr.on("data", (chunk) => {
35687
+ onOutput?.("stderr", chunk);
35664
35688
  stderr += chunk.toString();
35665
35689
  if (Buffer2.byteLength(stderr) > 1024 * 1024)
35666
35690
  reject(new Error("HTML command diagnostics exceeded their bound"));
@@ -35701,10 +35725,10 @@ function snapshotBuildEnvironment(sourceSnapshotPath) {
35701
35725
  }
35702
35726
  function rebuildViteSnapshot(sourceSnapshotPath, recipe, options) {
35703
35727
  if (recipe.kind === "source-html")
35704
- return rebuildNpmSnapshot(sourceSnapshotPath, recipe);
35728
+ return rebuildNpmSnapshot(sourceSnapshotPath, recipe, options);
35705
35729
  return gen2(function* () {
35706
35730
  const env = snapshotBuildEnvironment(sourceSnapshotPath);
35707
- const run2 = (args2, cwd = sourceSnapshotPath) => runHtmlCommand(args2, cwd, env);
35731
+ const run2 = (args2, cwd = sourceSnapshotPath) => runHtmlCommand(args2, cwd, env, "pnpm", options.onOutput);
35708
35732
  if ((yield* run2(["--version"])).trim() !== "11.24.0") {
35709
35733
  return yield* fail5(new Error("Publisher requires pnpm 11.24.0"));
35710
35734
  }
@@ -35922,7 +35946,7 @@ function readHtmlBuildOutput(sourceSnapshotPath, recipe) {
35922
35946
  return Object.freeze({ kind: "bytes", bytes });
35923
35947
  });
35924
35948
  }
35925
- function rebuildNpmSnapshot(sourceSnapshotPath, recipe) {
35949
+ function rebuildNpmSnapshot(sourceSnapshotPath, recipe, options) {
35926
35950
  return gen2(function* () {
35927
35951
  const env = snapshotBuildEnvironment(sourceSnapshotPath);
35928
35952
  const home = join5(sourceSnapshotPath, ".publisher-home");
@@ -35939,7 +35963,8 @@ function rebuildNpmSnapshot(sourceSnapshotPath, recipe) {
35939
35963
  ["--version"],
35940
35964
  sourceSnapshotPath,
35941
35965
  env,
35942
- "npm"
35966
+ "npm",
35967
+ options.onOutput
35943
35968
  )).trim() !== recipe.toolchain.npm)
35944
35969
  return yield* fail5(
35945
35970
  new Error("Captured npm version differs from the recipe")
@@ -35948,7 +35973,8 @@ function rebuildNpmSnapshot(sourceSnapshotPath, recipe) {
35948
35973
  ["ci", "--include=dev", "--ignore-scripts", "--no-audit", "--no-fund"],
35949
35974
  sourceSnapshotPath,
35950
35975
  env,
35951
- "npm"
35976
+ "npm",
35977
+ options.onOutput
35952
35978
  );
35953
35979
  const program = join5(sourceSnapshotPath, ".publisher-build.mjs");
35954
35980
  yield* tryPromise2(async () => {
@@ -35969,7 +35995,8 @@ function rebuildNpmSnapshot(sourceSnapshotPath, recipe) {
35969
35995
  ],
35970
35996
  sourceSnapshotPath,
35971
35997
  env,
35972
- "node"
35998
+ "node",
35999
+ options.onOutput
35973
36000
  );
35974
36001
  return yield* readHtmlBuildOutput(sourceSnapshotPath, recipe);
35975
36002
  });
@@ -36110,7 +36137,9 @@ function createLocalBuildExecutor(options = {}) {
36110
36137
  if ((yield* runHtmlCommand(
36111
36138
  ["--version"],
36112
36139
  source.snapshotPath,
36113
- environment
36140
+ environment,
36141
+ "pnpm",
36142
+ options.onOutput
36114
36143
  )).trim() !== "11.24.0")
36115
36144
  return yield* fail5(
36116
36145
  new Error("Publisher requires pnpm 11.24.0")
@@ -36121,7 +36150,9 @@ function createLocalBuildExecutor(options = {}) {
36121
36150
  options.packageCacheDirectory
36122
36151
  ),
36123
36152
  source.snapshotPath,
36124
- environment
36153
+ environment,
36154
+ "pnpm",
36155
+ options.onOutput
36125
36156
  );
36126
36157
  }
36127
36158
  }
@@ -36272,6 +36303,9 @@ async function buildTeamCandidate(input) {
36272
36303
  const onPhase = (phase, milliseconds) => {
36273
36304
  phases[phase] += milliseconds;
36274
36305
  };
36306
+ const onOutput = (stream, bytes2) => {
36307
+ input.onOutput?.(stream, bytes2);
36308
+ };
36275
36309
  const prepareStarted = performance.now();
36276
36310
  const candidate = await runPromise2(
36277
36311
  createCapturedCandidatePreparer(config2, {
@@ -36280,10 +36314,11 @@ async function buildTeamCandidate(input) {
36280
36314
  config2.sourceRevision
36281
36315
  ),
36282
36316
  buildExecutor: createLocalBuildExecutor(
36283
- input.packageStoreDirectory === void 0 ? { packageScripts: "run", onPhase } : {
36317
+ input.packageStoreDirectory === void 0 ? { packageScripts: "run", onPhase, onOutput } : {
36284
36318
  packageScripts: "run",
36285
36319
  packageStoreDirectory: input.packageStoreDirectory,
36286
- onPhase
36320
+ onPhase,
36321
+ onOutput
36287
36322
  }
36288
36323
  )
36289
36324
  }).prepareCandidate(parseReleaseId("rel_build"))
@@ -36354,7 +36389,10 @@ try {
36354
36389
  repositoryPath,
36355
36390
  outputPath,
36356
36391
  // Computer scratch survives captured snapshot cleanup, without a global or durable Workspace store.
36357
- packageStoreDirectory: "/var/tmp/fungi-app-package-store"
36392
+ packageStoreDirectory: "/var/tmp/fungi-app-package-store",
36393
+ onOutput: (stream, bytes) => {
36394
+ process[stream].write(bytes);
36395
+ }
36358
36396
  });
36359
36397
  const reportUrl = process.env.TEAM_BUILD_REPORT;
36360
36398
  if (!reportUrl) throw new TeamCandidateBuildError("report_failed");
@@ -0,0 +1,23 @@
1
+ import * as Schema from "effect/Schema";
2
+ /** Maximum retained combined stdout/stderr bytes, split equally into head and tail. */
3
+ export declare const buildJobLogMaxBytes: number;
4
+ /** Base64url preserves raw command bytes, including split UTF-8 and terminal control bytes. */
5
+ export declare const buildJobLogSchema: Schema.Struct<{
6
+ readonly encoding: Schema.Literal<"base64url">;
7
+ readonly output: Schema.String;
8
+ readonly truncated: Schema.NullOr<Schema.Struct<{
9
+ readonly droppedBytes: Schema.Number;
10
+ }>>;
11
+ }>;
12
+ export type BuildJobLog = Schema.Schema.Type<typeof buildJobLogSchema>;
13
+ /** The artifact-upload door cannot write this host-owned job-log namespace. */
14
+ export declare const isBuildJobLogRef: (ref: string) => boolean;
15
+ /** One bounded raw-event sink; event arrival order is the combined output order. */
16
+ export declare function createBuildJobLog(): {
17
+ /** Retained buffer capacity, distinct from the transient encoded result. */
18
+ readonly allocatedBytes: number;
19
+ append(bytes: Uint8Array): void;
20
+ finish(): BuildJobLog;
21
+ };
22
+ /** Preserve BirdDog's redacted 200-character status/mail budget and name the full log. */
23
+ export declare function buildJobDiagnostic(jobId: string, detail: string): string;
@@ -0,0 +1,88 @@
1
+ import * as Schema from "effect/Schema";
2
+ import { base64url } from "jose";
3
+ import { boundedDiagnostic } from "@fungi.computer/bird-dog/effect";
4
+ /** Maximum retained combined stdout/stderr bytes, split equally into head and tail. */
5
+ export const buildJobLogMaxBytes = 2 * 1024 * 1024;
6
+ /** Base64url preserves raw command bytes, including split UTF-8 and terminal control bytes. */
7
+ export const buildJobLogSchema = Schema.Struct({
8
+ encoding: Schema.Literal("base64url"),
9
+ output: Schema.String.check(Schema.isMaxLength(Math.ceil(buildJobLogMaxBytes / 3) * 4)),
10
+ truncated: Schema.NullOr(Schema.Struct({
11
+ droppedBytes: Schema.Number.check(Schema.isInt(), Schema.isGreaterThan(0)),
12
+ })),
13
+ });
14
+ /** The artifact-upload door cannot write this host-owned job-log namespace. */
15
+ export const isBuildJobLogRef = (ref) => ref.startsWith("artifact:build-log-");
16
+ /** One bounded raw-event sink; event arrival order is the combined output order. */
17
+ export function createBuildJobLog() {
18
+ const half = buildJobLogMaxBytes / 2;
19
+ let head = new Uint8Array(0);
20
+ let tail = new Uint8Array(0);
21
+ let headBytes = 0;
22
+ let tailBytes = 0;
23
+ let tailPosition = 0;
24
+ let total = 0;
25
+ const capacityFor = (current, needed) => Math.min(half, Math.max(needed, current === 0 ? 4096 : current * 2));
26
+ return {
27
+ /** Retained buffer capacity, distinct from the transient encoded result. */
28
+ get allocatedBytes() {
29
+ return head.byteLength + tail.byteLength;
30
+ },
31
+ append(bytes) {
32
+ total += bytes.byteLength;
33
+ const prefix = Math.min(half - headBytes, bytes.byteLength);
34
+ if (headBytes + prefix > head.byteLength) {
35
+ const next = new Uint8Array(capacityFor(head.byteLength, headBytes + prefix));
36
+ next.set(head.subarray(0, headBytes));
37
+ head = next;
38
+ }
39
+ head.set(bytes.subarray(0, prefix), headBytes);
40
+ headBytes += prefix;
41
+ const remaining = bytes.subarray(prefix);
42
+ if (remaining.byteLength === 0)
43
+ return;
44
+ const needed = Math.min(half, tailBytes + remaining.byteLength);
45
+ if (needed > tail.byteLength) {
46
+ const next = new Uint8Array(capacityFor(tail.byteLength, needed));
47
+ const start = tailBytes === tail.byteLength ? tailPosition : 0;
48
+ const first = Math.min(tailBytes, tail.byteLength - start);
49
+ next.set(tail.subarray(start, start + first));
50
+ next.set(tail.subarray(0, tailBytes - first), first);
51
+ tail = next;
52
+ tailPosition = tailBytes;
53
+ }
54
+ const suffix = remaining.subarray(Math.max(0, remaining.byteLength - half));
55
+ if (remaining.byteLength >= half) {
56
+ tailBytes = 0;
57
+ tailPosition = 0;
58
+ }
59
+ const first = Math.min(suffix.byteLength, tail.byteLength - tailPosition);
60
+ tail.set(suffix.subarray(0, first), tailPosition);
61
+ tail.set(suffix.subarray(first), 0);
62
+ tailPosition = (tailPosition + suffix.byteLength) % tail.byteLength;
63
+ tailBytes = Math.min(half, tailBytes + remaining.byteLength);
64
+ },
65
+ finish() {
66
+ const bytes = new Uint8Array(headBytes + tailBytes);
67
+ bytes.set(head.subarray(0, headBytes));
68
+ const start = tailBytes === tail.byteLength ? tailPosition : 0;
69
+ const first = Math.min(tailBytes, tail.byteLength - start);
70
+ bytes.set(tail.subarray(start, start + first), headBytes);
71
+ bytes.set(tail.subarray(0, tailBytes - first), headBytes + first);
72
+ const droppedBytes = total - bytes.byteLength;
73
+ return {
74
+ encoding: "base64url",
75
+ output: base64url.encode(bytes),
76
+ truncated: droppedBytes === 0 ? null : { droppedBytes },
77
+ };
78
+ },
79
+ };
80
+ }
81
+ /** The job read door, not an artifact-serving URL. */
82
+ const buildJobLogReference = (jobId) => `/api/builds/${encodeURIComponent(jobId)}?log`;
83
+ /** Preserve BirdDog's redacted 200-character status/mail budget and name the full log. */
84
+ export function buildJobDiagnostic(jobId, detail) {
85
+ const reference = `Log: ${buildJobLogReference(jobId)}`;
86
+ const diagnostic = boundedDiagnostic(detail) ?? "Build finished";
87
+ return `${reference}\n${diagnostic.slice(0, 200 - reference.length - 1)}`;
88
+ }
@@ -53,6 +53,8 @@ export type LocalBuildExecutorOptions = Readonly<{
53
53
  packageScripts?: "run";
54
54
  /** Hears how long the dependency install and the compile each took. */
55
55
  onPhase?: (phase: "install" | "build", milliseconds: number) => void;
56
+ /** Forward raw install/build output to the admitted Computer's job log. */
57
+ onOutput?: (stream: "stdout" | "stderr", bytes: Uint8Array) => void;
56
58
  }>;
57
59
  /** Exact pinned esbuild options shared by local builds and artifact-budget proofs. */
58
60
  export declare function localSnapshotBuildOptions(source: CapturedSource, recipe: PinnedRecipe, emission: DefinitionEmission): esbuild.BuildOptions;
@@ -571,7 +571,7 @@ async function stopHtmlGroup(child) {
571
571
  }
572
572
  throw new Error("HTML process group survived its five-second cleanup bound");
573
573
  }
574
- function runHtmlCommand(args, cwd, env, command = "pnpm") {
574
+ function runHtmlCommand(args, cwd, env, command = "pnpm", onOutput) {
575
575
  return Effect.acquireUseRelease(Effect.sync(() => {
576
576
  if (process.platform === "win32")
577
577
  throw new Error("HTML builds require POSIX process groups");
@@ -585,11 +585,13 @@ function runHtmlCommand(args, cwd, env, command = "pnpm") {
585
585
  let stdout = "";
586
586
  let stderr = "";
587
587
  child.stdout.on("data", (chunk) => {
588
+ onOutput?.("stdout", chunk);
588
589
  stdout += chunk.toString();
589
590
  if (Buffer.byteLength(stdout) > 1024 * 1024)
590
591
  reject(new Error("HTML command output exceeded its bound"));
591
592
  });
592
593
  child.stderr.on("data", (chunk) => {
594
+ onOutput?.("stderr", chunk);
593
595
  stderr += chunk.toString();
594
596
  if (Buffer.byteLength(stderr) > 1024 * 1024)
595
597
  reject(new Error("HTML command diagnostics exceeded their bound"));
@@ -632,10 +634,10 @@ function snapshotBuildEnvironment(sourceSnapshotPath) {
632
634
  }
633
635
  function rebuildViteSnapshot(sourceSnapshotPath, recipe, options) {
634
636
  if (recipe.kind === "source-html")
635
- return rebuildNpmSnapshot(sourceSnapshotPath, recipe);
637
+ return rebuildNpmSnapshot(sourceSnapshotPath, recipe, options);
636
638
  return Effect.gen(function* () {
637
639
  const env = snapshotBuildEnvironment(sourceSnapshotPath);
638
- const run = (args, cwd = sourceSnapshotPath) => runHtmlCommand(args, cwd, env);
640
+ const run = (args, cwd = sourceSnapshotPath) => runHtmlCommand(args, cwd, env, "pnpm", options.onOutput);
639
641
  if ((yield* run(["--version"])).trim() !== "11.24.0") {
640
642
  return yield* Effect.fail(new Error("Publisher requires pnpm 11.24.0"));
641
643
  }
@@ -827,7 +829,7 @@ function readHtmlBuildOutput(sourceSnapshotPath, recipe) {
827
829
  return Object.freeze({ kind: "bytes", bytes });
828
830
  });
829
831
  }
830
- function rebuildNpmSnapshot(sourceSnapshotPath, recipe) {
832
+ function rebuildNpmSnapshot(sourceSnapshotPath, recipe, options) {
831
833
  return Effect.gen(function* () {
832
834
  const env = snapshotBuildEnvironment(sourceSnapshotPath);
833
835
  const home = join(sourceSnapshotPath, ".publisher-home");
@@ -840,9 +842,9 @@ function rebuildNpmSnapshot(sourceSnapshotPath, recipe) {
840
842
  await writeFile(userConfig, "", { flag: "wx" });
841
843
  await writeFile(globalConfig, "", { flag: "wx" });
842
844
  });
843
- if ((yield* runHtmlCommand(["--version"], sourceSnapshotPath, env, "npm")).trim() !== recipe.toolchain.npm)
845
+ if ((yield* runHtmlCommand(["--version"], sourceSnapshotPath, env, "npm", options.onOutput)).trim() !== recipe.toolchain.npm)
844
846
  return yield* Effect.fail(new Error("Captured npm version differs from the recipe"));
845
- yield* runHtmlCommand(["ci", "--include=dev", "--ignore-scripts", "--no-audit", "--no-fund"], sourceSnapshotPath, env, "npm");
847
+ yield* runHtmlCommand(["ci", "--include=dev", "--ignore-scripts", "--no-audit", "--no-fund"], sourceSnapshotPath, env, "npm", options.onOutput);
846
848
  const program = join(sourceSnapshotPath, ".publisher-build.mjs");
847
849
  yield* Effect.tryPromise(async () => {
848
850
  const { htmlBuildProgram } = await snapshotPrograms();
@@ -858,7 +860,7 @@ function rebuildNpmSnapshot(sourceSnapshotPath, recipe) {
858
860
  dirname(recipe.entry),
859
861
  "true",
860
862
  "",
861
- ], sourceSnapshotPath, env, "node");
863
+ ], sourceSnapshotPath, env, "node", options.onOutput);
862
864
  return yield* readHtmlBuildOutput(sourceSnapshotPath, recipe);
863
865
  });
864
866
  }
@@ -982,9 +984,9 @@ export function createLocalBuildExecutor(options = {}) {
982
984
  })))(await readFile(join(source.snapshotPath, "package.json"), "utf8"));
983
985
  });
984
986
  const environment = snapshotBuildEnvironment(source.snapshotPath);
985
- if ((yield* runHtmlCommand(["--version"], source.snapshotPath, environment)).trim() !== "11.24.0")
987
+ if ((yield* runHtmlCommand(["--version"], source.snapshotPath, environment, "pnpm", options.onOutput)).trim() !== "11.24.0")
986
988
  return yield* Effect.fail(new Error("Publisher requires pnpm 11.24.0"));
987
- yield* runHtmlCommand(frozenSnapshotInstallArguments(options.packageStoreDirectory, options.packageCacheDirectory), source.snapshotPath, environment);
989
+ yield* runHtmlCommand(frozenSnapshotInstallArguments(options.packageStoreDirectory, options.packageCacheDirectory), source.snapshotPath, environment, "pnpm", options.onOutput);
988
990
  }
989
991
  }
990
992
  const ui = yield* recipe.kind === "bundled-html"
@@ -9,6 +9,7 @@ import { type AppBackendBindingName } from "@fungi.computer/puffball/app";
9
9
  import { type InvocationFrame, type InvocationGate } from "./invocation.js";
10
10
  import * as Schema from "effect/Schema";
11
11
  import { z } from "zod";
12
+ import type { ResourceRef } from "./apps/installation.js";
12
13
  import type { Release } from "@fungi.computer/puffball";
13
14
  import { type UiBootCheckOutcome } from "./boot-gate.js";
14
15
  export { checkBackendConstruction } from "./boot-construction.js";
@@ -39,6 +40,8 @@ export type FrameBindingMethod<Result> = Readonly<{
39
40
  decodeResult: z.ZodType<Result>["parse"];
40
41
  /** Determine the declared operations needed for these decoded arguments. */
41
42
  requiredOperations: (args: readonly unknown[]) => readonly string[];
43
+ /** Select a concrete host resource after decoding, before provider dispatch. */
44
+ requestedResource?: (args: readonly unknown[]) => ResourceRef;
42
45
  }>;
43
46
  /** Method descriptors for the closed App binding vocabulary. */
44
47
  export type FrameBindingMethods<Result> = Readonly<Partial<Record<AppBackendBindingName, Readonly<Record<string, FrameBindingMethod<Result>>>>>>;
@@ -119,7 +122,7 @@ export type AppInstanceLoader<FacetClass> = Readonly<{
119
122
  getDurableObjectClass: (exportName: string) => FacetClass;
120
123
  }>;
121
124
  }>;
122
- /** End the native App facet when live authority closes early, before the gate admits again. */
125
+ /** Stop the shared native App facet at cohort drain or revocation. */
123
126
  export declare function createCloudflareInvocationGate<Host, FacetClass>(ctx: Pick<AppInstanceContext<FacetClass>, "facets">, close: (frame: InvocationFrame<Host>) => Promise<void>): InvocationGate<Host>;
124
127
  /**
125
128
  * Run an App Instance inside a SQLite-backed Durable Object on workerd, celld
@@ -267,7 +267,7 @@ const facetInvocationSchema = rpcHandleSchema({
267
267
  invoke: rpcMethod(z.union([z.json(), z.undefined()])).input(z.tuple([z.string(), z.array(z.unknown()), z.unknown()])),
268
268
  http: rpcMethod(facetHttpReplySchema).input(z.tuple([facetHttpInputSchema, z.unknown()])),
269
269
  });
270
- /** End the native App facet when live authority closes early, before the gate admits again. */
270
+ /** Stop the shared native App facet at cohort drain or revocation. */
271
271
  export function createCloudflareInvocationGate(ctx, close) {
272
272
  return createInvocationGate({
273
273
  abortGuest: () => {
package/dist/files.js CHANGED
@@ -6,7 +6,7 @@
6
6
  * the Oyster's workspace unchanged. Other sources (a git repository) implement
7
7
  * the same `FileSource`.
8
8
  */
9
- import { Hono } from "hono";
9
+ import { computer, routes } from "./authorization.js";
10
10
  import * as Schema from "effect/Schema";
11
11
  import { computerList, computerReadFile, computerWriteFile, } from "@fungi.computer/agar/computer";
12
12
  const MAX_FILE_BYTES = 25 * 1024 * 1024;
@@ -66,10 +66,11 @@ export function fileCapabilities(source) {
66
66
  },
67
67
  ];
68
68
  }
69
+ /** The workspace is the Computer's: a key needs `computer.use` (see `authorization.ts`). */
69
70
  function createFileRoutes(source) {
70
- const routes = new Hono();
71
+ const table = routes();
71
72
  const pathOf = (raw) => normalizeFilePath(raw ?? "");
72
- routes.get("/list", async (context) => {
73
+ table.get("/list", computer(), async (context) => {
73
74
  const path = pathOf(context.req.query("path"));
74
75
  if (path === null)
75
76
  return refusal(400, "Invalid path");
@@ -80,7 +81,7 @@ function createFileRoutes(source) {
80
81
  ? refusal(404, "No such directory")
81
82
  : Response.json(listing);
82
83
  });
83
- routes.get("/content", async (context) => {
84
+ table.get("/content", computer(), async (context) => {
84
85
  const path = pathOf(context.req.query("path"));
85
86
  if (path === null)
86
87
  return refusal(400, "Invalid path");
@@ -91,7 +92,7 @@ function createFileRoutes(source) {
91
92
  headers: { "Content-Type": "application/octet-stream" },
92
93
  });
93
94
  });
94
- routes.put("/content", async (context) => {
95
+ table.put("/content", computer(), async (context) => {
95
96
  const path = pathOf(context.req.query("path"));
96
97
  if (path === null || path === "/")
97
98
  return refusal(400, "Invalid path");
@@ -104,7 +105,7 @@ function createFileRoutes(source) {
104
105
  await source.write(path, bytes);
105
106
  return new Response(null, { status: 204 });
106
107
  });
107
- routes.post("/mkdir", async (context) => {
108
+ table.post("/mkdir", computer(), async (context) => {
108
109
  const body = Schema.decodeUnknownOption(mkdirSchema)(await context.req.json());
109
110
  const path = body._tag === "Some" ? normalizeFilePath(body.value.path) : null;
110
111
  if (path === null || path === "/")
@@ -112,12 +113,12 @@ function createFileRoutes(source) {
112
113
  await source.mkdir(path);
113
114
  return new Response(null, { status: 204 });
114
115
  });
115
- routes.post("/replace", async (context) => {
116
+ table.post("/replace", computer(), async (context) => {
116
117
  const body = Schema.decodeUnknownOption(replaceSchema)(await context.req.json());
117
118
  const path = body._tag === "Some" ? normalizeFilePath(body.value.path) : null;
118
119
  if (body._tag === "None" || path === null)
119
120
  return refusal(400, "Invalid request");
120
121
  return Response.json(await source.replace(path, body.value.expectedContent, body.value.content));
121
122
  });
122
- return routes;
123
+ return table;
123
124
  }
@@ -5,10 +5,10 @@ import type { AppDeclaration } from "@fungi.computer/puffball/app";
5
5
  import type { Installation } from "./apps/installation.js";
6
6
  /** Installation-scoped storage; transport and credentials stay with its provider adapter. */
7
7
  export type GitStore = GitBinding;
8
- /** Live host authority, reopened before every provider call. Null is an anonymous reader. */
8
+ /** Live host authority, reopened before every provider call. */
9
9
  export type GitGrants = Readonly<{
10
10
  /** Reopen the host authority for this repository and requested scope. */
11
- admits(writer: GitWriter | null, repository: string, scope: "read" | "write"): Promise<boolean>;
11
+ admits(writer: GitWriter, repository: string, scope: "read" | "write"): Promise<boolean>;
12
12
  }>;
13
13
  /** Reuse the live installation/verified Release rule without opening provider storage. */
14
14
  export declare function createInstallationGitAdmission(ports: Readonly<{
@@ -36,7 +36,7 @@ export type AdmittedGit = Readonly<{
36
36
  /** Recheck host read authority before exchanging provider bytes. */
37
37
  read(writer: Extract<GitWriter, {
38
38
  kind: "actor";
39
- }> | null, repository: string, request: GitExchangeRequest): Promise<GitExchangeResponse>;
39
+ }>, repository: string, request: GitExchangeRequest): Promise<GitExchangeResponse>;
40
40
  /** Bind installation-authored operations; null attribution is lifecycle-only. */
41
41
  binding(onBehalfOf: HostActor | null): GitBinding;
42
42
  }>;
@@ -5,13 +5,12 @@ import { acceptedRefs, parseReceivePack, refusalReport, } from "@fungi.computer/
5
5
  import { defineIdFamily } from "@fungi.computer/cairn/id";
6
6
  /** Reuse the live installation/verified Release rule without opening provider storage. */
7
7
  export function createInstallationGitAdmission(ports) {
8
- return async (writer) => {
8
+ return async () => {
9
9
  const current = await ports.currentInstallation();
10
10
  if (current === null)
11
11
  return false;
12
12
  const declaration = await ports.declaration(current);
13
- return (declaration?.capabilities.includes("git.repository") === true &&
14
- (writer !== null || current.visibility === "public"));
13
+ return declaration?.capabilities.includes("git.repository") === true;
15
14
  };
16
15
  }
17
16
  const ids = defineIdFamily({
@@ -26,9 +25,8 @@ export function createAdmittedGit(ports) {
26
25
  const { store, grants, policy, journal } = ports;
27
26
  async function admit(writer, repository, scope) {
28
27
  gitRepositoryNameSchema.parse(repository);
29
- if (writer !== null)
30
- gitWriterSchema.parse(writer);
31
- if (writer?.kind === "installation" &&
28
+ gitWriterSchema.parse(writer);
29
+ if (writer.kind === "installation" &&
32
30
  writer.installationId !== ports.installationId)
33
31
  throw new Error("Git installation authority mismatch");
34
32
  if (!(await grants.admits(writer, repository, scope)))