@oddington/sdk 0.1.37 → 0.1.38

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -6,11 +6,12 @@ Data and Files APIs work in browsers. Installing the package grants no authority
6
6
 
7
7
  Hosted Data and shared Files reach OS directly through `/api/data` and `/api/files`.
8
8
  Inbox owns workspace-hosted `/api/inbox/check`, `/api/inbox/publish`,
9
- `/api/inbox/resolve` and `/api/inbox/files`. Request-key recovery and captured
9
+ `/api/inbox/resolve`. Request-key recovery and captured
10
10
  publication validation are internal behavior of these operations. Retrying the
11
11
  same key and content returns the original result; changed content conflicts.
12
- Existing Inbox-retained files remain separate from shared Files, with their
13
- original identities, bytes and audience. The SDK exposes no raw-credential API.
12
+ Inbox File previews and downloads use package-root `files.read(fileId)` with
13
+ current workspace access. Item visibility does not grant File access. The SDK
14
+ exposes no raw-credential API.
14
15
 
15
16
  Hosted apps use `inbox.questions.list({ routines })` for `AnswerableQuestion[]`.
16
17
  Derive `routines` from the existing app declaration with
@@ -65,7 +66,7 @@ Each step runs at most once and returns `{ summary, data }`. Unknown or repeated
65
66
  steps fail the Run even if routine code catches the error; unreached steps are
66
67
  skipped. Omitted input normalizes to `{}`. A top-level schema can supply a validated
67
68
  immutable default with `.default(value)`, such as
68
- `schema.object({ files: schema.array(file(), { maxItems: 32 }) }).default({ files: [] })`.
69
+ `schema.object({ files: schema.array(file()) }).default({ files: [] })`.
69
70
  Nested defaults are unsupported and rejected. Omitted steps permit no step call;
70
71
  omitted result permits no returned value. `file()` declares a complete OS File descriptor.
71
72
  Its optional `storage` selects local or remote placement and its optional `path`
@@ -101,8 +102,10 @@ AI calls drain before completion. The host retains authority, isolation,
101
102
  cancellation and byte limits. Local and remote Runs use the same API.
102
103
 
103
104
  The bounded grammar supports closed objects with required properties, arrays with
104
- an explicit `maxItems` from 1 to 64, booleans, strings (at most 4096 code points),
105
+ an optional positive safe-integer `maxItems` business bound, booleans, strings (at most 4096 code points),
105
106
  finite numbers and safe integers with optional inclusive bounds, and Files.
107
+ Omitting `maxItems` imposes no item-count ceiling; the complete serialized input
108
+ still shares the Run's byte bound, including every File descriptor and other field.
106
109
  Definitions allow at most 64 steps, 64 total object properties across their schemas,
107
110
  and eight schema levels. Input, each step input, each summary, each detail value,
108
111
  and the result accept at most 256 KiB of UTF-8 serialized JSON, without truncation.
@@ -252,7 +255,7 @@ Local Data selects from the Host's exact published index; a warm selection
252
255
  neither initiates nor waits for renewal or synchronous hosted authority checks.
253
256
  Local Files returns `unsupported_context` after checking context. Neither path
254
257
  falls back to hosted services. Local app routines use the same local execution
255
- and explicit publication lifecycle as the CLI.
258
+ and live capability calls as the CLI.
256
259
 
257
260
  Apps can assess a declared routine without starting it:
258
261
 
@@ -268,19 +271,17 @@ const detail = await run.step('read'); // Inferred summary and data for this new
268
271
  const recent = await app.routines.receipts.runs.list();
269
272
  const saved = await app.routines.receipts.runs.get(recent[0].id);
270
273
  console.log(await saved.current());
271
- // await saved.publish(); // Only in response to an explicit publication action.
272
274
  ```
273
275
 
274
276
  The fixed build generates types from each routine definition; no app-authored
275
277
  contract map is needed. Descriptions combine safe structure with live readiness
276
278
  and connection availability. Starts validate the current definition and input;
277
- retained reads and publication recheck complete saved member/source/capability
278
- scope. No installation is needed. Bindings accept no endpoint, credential,
279
+ retained local reads check the established session and saved Run ownership. No installation is needed. Bindings accept no endpoint, credential,
279
280
  command, path or configuration selector. A local app uses its live Local Host;
280
281
  a hosted app uses its existing member session and workspace context to reach
281
282
  the current remote Run owner through the same bindings. Operations reject with
282
283
  `AppRoutineError.reason`: `authority_unavailable`, `execution_unavailable`, or
283
- `invalid_input`. Starts and publications are not automatically retried. A
284
+ `invalid_input`. Starts are not automatically retried. A
284
285
  disconnected watch ends and requires `current()`. History returns at most 20
285
286
  summaries without deleting older Runs. Each JSON value is bounded to 256 KiB.
286
287
  Transport envelopes allow 17 MiB per summary, 514 KiB per detail and 20 summaries
@@ -402,33 +403,28 @@ await files.enrich({ file: report.id, name: 'read.md', content: revisedMarkdown,
402
403
  await files.upload(report.id); // Move a local File and every retained enrichment to remote storage.
403
404
  ```
404
405
 
405
- Hosted Files retains remote originals and caller-produced named enrichments;
406
- `storage: 'local'` requires a managed CLI parent. Configure that parent with
407
- `OS_FILES_ROOT` pointing to an empty or already OS-managed dedicated
408
- directory outside tracked Git content. `OS_FILES_DEFAULT=local|remote`
409
- selects its default; without it, remote remains the default. Per-File `storage`
410
- overrides it. The root stays private to the parent. Local creation without a
411
- configured root fails with `unsupported_context`. Files have one durable home;
412
- `both` is invalid. Local retention establishes no hosted registration.
413
- Fresh runs recover the same creator's File by its request key, and verify
414
- retained bytes before returning a descriptor or content. A dead parent fails
415
- closed. `os routine run <name>` stages newly created Files locally for that Run,
416
- including local-placement Files, and defers promotion, enrichments and explicit
417
- uploads until `os routine publish <run>`. Legacy Inbox file capture remains
418
- additive. Existing hosted Files are read-only during local execution.
419
- `upload(id)` preserves the File descriptor, creator access and existing Source
420
- references. Local bytes remain authoritative until all remote objects verify;
421
- retrying a completed upload returns the same File. Later reads and enrichments
422
- follow remote storage; any remaining managed local bytes are disposable cache.
423
- An unreferenced local File is disclosed remotely only when explicitly uploaded
424
- or successfully published as a Source. An authorized hosted caller receives
425
- non-retryable `local_only` (409) until upload succeeds; unrelated callers receive
426
- `access_denied`.
406
+ Hosted Files retains remote originals and caller-produced named enrichments.
407
+ A managed local CLI parent stores local Files beneath the signed-in active
408
+ workspace's `~/.os/<workspace>/files` root; callers cannot configure another
409
+ root. Local Runs default new Files to local storage, hosted Runs to remote.
410
+ An existing local or remote File keeps its placement. Every completed File has
411
+ one workspace registry row, including local Files whose bytes stay on this
412
+ device. Request-key replay and reusable enrichments preserve the same identity
413
+ across member calls, Runs and restarts.
414
+
415
+ Explicit remote creation, upload and enrichment complete during their calls.
416
+ `upload(id)` verifies the original and every enrichment before changing the same
417
+ File's placement; retrying a completed upload returns that File. Later reads
418
+ and enrichments follow its pinned remote store. A Source reference leaves a
419
+ local File local. Inbox publication uploads that same File before exposing the
420
+ Ask or Digest. Completed effects survive later Run failure. A runtime without
421
+ the local bytes reports `local_only` (409).
427
422
  Unnamed inputs require a safe `name`; streams also require declared `size`.
428
423
  Blob and byte-array declarations must match their intrinsic size. Originals are
429
- bounded to 16 MiB per object, metadata to 4 KiB, and descriptor/error bodies to 16 KiB.
430
- Operations share four concurrent slots and a 30-second deadline;
431
- transport retries at most once with the same prepared bytes and request key.
424
+ bounded to 5 GiB per original and 16 MiB per enrichment. Large originals stream
425
+ through bounded multipart transfers; local writes and reads use bounded disk
426
+ buffers. Operations share four concurrent slots and idle/request deadlines;
427
+ transport retries preserve the same bytes and request key.
432
428
  Omitting a key generates one for that invocation. Supply a stable key to recover
433
429
  across processes or fresh routine runs. Caller inputs remain owned by the caller.
434
430
  Each request resolves current authority; hosted routine Files calls use the
@@ -441,24 +437,10 @@ versus received size. Streaming rejection marks partial received counts with
441
437
  failure evidence without raw names, keys, file content or provider text. Older
442
438
  errors without `detail` remain valid. Reads return the successful Web
443
439
  Response without consuming its body. Local transfer is bounded and buffered
444
- before constructing that Response. Creation grants private creator access;
445
- workspace membership alone never grants another creator's File.
446
-
447
- `await files.delete(fileId)` removes an unreferenced File's original and all
448
- retained enrichments under its current member or installed-routine creator.
449
- It returns `void` only after physical cleanup and durable completion; a completed
450
- retry succeeds. Independent retention returns `conflict` without changing the
451
- File. `access_denied` and `integrity` remain terminal; `unavailable` and
452
- `capacity` are retryable. Pending deletion denies reads, writes, new uses and
453
- creation-key replay while the same current creator retries cleanup. Deletion
454
- uses the authenticated parent for managed local Files and uploaded caches.
455
- R2 cleanup verifies the registered immutable key and native revision, removes
456
- only that object and confirms absence. Each physical writer owns a distinct
457
- candidate key, so a delayed writer cannot recreate the deleted representation.
458
- Pending admission is never success.
459
- When a completion reply is lost, retry `files.delete` with the same File ID and
460
- current creator authority. Keep stable creation keys separate from deletion
461
- recovery: a completed deleted key is denied and cannot recreate the File.
440
+ before constructing that Response. Current workspace members can use every File in that workspace. A
441
+ routine's `files: "read"` permits get/read; `files: "read-write"` also permits
442
+ create/enrich/upload. References in Sources, Runs and Inbox items grant no
443
+ additional access. Files has no public delete operation.
462
444
 
463
445
  `enrich` accepts a string encoded as UTF-8 or exact `Uint8Array` bytes, a safe
464
446
  flat filename, and an optional MIME hint. OS verifies text/Markdown/JSON or
@@ -570,12 +552,11 @@ Binding is inert. Each operation resolves current workspace, endpoint and
570
552
  short-lived authority from the trusted app host or managed routine context.
571
553
  Customer calls cannot configure those values. Missing or expired context
572
554
  rejects; a named handle does not expand repository access. During
573
- `os routine run <name>`, the same calls use the matched checkout's committed
574
- version through a private parent transport. Dirty files are ignored. An OS
575
- cache outside the checkout reuses derived data; reads require no remote access
576
- or preparation storage. Later accepted record effects advance the run's view
577
- to their exact proposed version. Login, installed grants and provider authority
578
- retain their normal checks. A dead local parent fails without hosted fallback.
555
+ `os routine run <name>`, the same calls read current schema-governed Markdown
556
+ in the trusted checkout, including staged, modified and untracked Records. Each
557
+ operation reads the files as it begins. Later calls see completed writes and
558
+ external edits. Current local Data acquires no hosted binding or index. A dead
559
+ local parent fails without hosted fallback.
579
560
 
580
561
  Each graph selection sends one request and returns only selected canonical
581
562
  values, expanded forward links and exact opaque public versions. Cursors
@@ -706,15 +687,11 @@ projection, highlighting, embedding or historical version option.
706
687
 
707
688
  Matches have deterministic order and complete canonical Records. Empty matches
708
689
  succeed with the exact searched version. Members search latest published data;
709
- routines search their captured version, advancing only through their own
710
- publication. Hosted and app children publish immediately. During
711
- `os routine run <name>`, search uses the same committed local Index as select,
712
- ignores dirty/untracked files and advances only to that run's exact proposed
713
- version after its own Changes publication. Validation does not advance reads;
714
- another publisher does not move the captured view. Search Records can supply
715
- guards for subsequent Changes. Missing or incomplete search data, including
716
- retained versions without it, fails retryably with `unavailable` without an
717
- alternate read path.
690
+ hosted routines search their admitted hosted version. Local routines search the
691
+ schema-governed Markdown currently in the checkout, including staged, modified
692
+ and untracked Records. Each call refreshes its operation-local view; a later call
693
+ sees completed Changes writes and external edits. Search Records supply captured
694
+ versions for subsequent Changes. Explicit History keeps its hosted contract.
718
695
 
719
696
  `where` combines field conditions with AND. Alongside exact scalar/null equality
720
697
  and `$in`, declared one-valued `integer`, `number` and `date` fields accept
@@ -771,9 +748,10 @@ only after its exact index is ready. Later current reads discover that version.
771
748
  Deleted identities keep their captured versions; deletion creates no new Record
772
749
  version or tombstone. A Source deletion removes only that guarded Record, retaining
773
750
  its File, enrichments and existing durable uses.
774
- During `os routine run <name>`, publication retains the exact proposal and
775
- advances only run-local reads. `os routine publish <run>` later applies and indexes
776
- that immutable proposal; unrelated staged and unstaged work is preserved.
751
+ During a local Run, `changes.publish()` validates the complete set once before
752
+ writing individual complete files atomically. Completed paths remain after a later
753
+ filesystem failure. `validate()` grants no later permission to write; subsequent
754
+ operations read the current checkout. No later Run publication is required.
777
755
 
778
756
  `invalid_change` and known-unapplied `conflict` reject the batch.
779
757
  `publication_pending` is retryable and may report `uncertain` or `applied`;
@@ -816,42 +794,41 @@ records separately. `AirtableOperationError` reports `unapplied`, `partial` or
816
794
  work; never repeat the whole change blindly. Credentials must never be retained
817
795
  in files, publication, logs, handler input, errors or receipts.
818
796
 
819
- Managed routine launches supply workspace, scoped capability and working root.
820
- The defaults read `OS_ROUTINE_WORKSPACE`, `OS_ROUTINE_RUN` and
821
- `OS_INBOX_WORKING_ROOT` (or the current directory). Callers may supply the
822
- existing `workspace`, `capability` and `workingRoot` options. Runtime context
797
+ Managed routine launches supply workspace and scoped capability.
798
+ The defaults read `OS_ROUTINE_WORKSPACE` and `OS_ROUTINE_RUN`. Hosts may supply
799
+ `workspace`, `capability` and `request` options. Runtime context
823
800
  provides authority; callers cannot select repository remotes or expand grants.
824
801
  `os routine run <name> [--input <file|->]` executes the conventional inert
825
- definition with declared JSON input and captures OS-owned changes.
826
- `os routine publish <run>` sends those exact changes and recovers confirmed
827
- receipts without invoking business code again. Filesystem, model and installed
828
- connection effects occur immediately, including before a later failure.
829
- No-change success retains Run evidence without requiring publish. `--json`
830
- returns typed Run state/counts/result, or confirmed publish receipts/links.
802
+ definition with declared JSON input. Data writes and hosted capability calls
803
+ complete live; successful earlier work remains after a later failure. The Run
804
+ retains exact local paths, safe hosted effects and Git provenance. `--json`
805
+ returns typed Run state, result/failure and retained local activity.
831
806
  The former `prepare`, `run --local`, command/argv and authority-selection
832
807
  flags are rejected. `os routine deploy <canonical-id>` explicitly delivers the
833
808
  complete definition, including its response handlers; `--yes` provides automation
834
809
  consent. `os routine run <canonical-id> --remote` runs the current successful
835
810
  deployment with the same input/defaults, ordered steps and result validation.
836
811
  Remote handles expose `current()`, `watch()` and `step(id)`; `runs.list()` and
837
- `runs.get(id)` read retained bounded history. Remote summaries omit publication.
838
- Checkout-backed app children retain immediate publication.
839
-
840
- Retain evidence with `inbox.files.add({ path: 'evidence.txt' })`. Paths stay within
841
- the invocation's working root and uploaded bytes are verified against the retained
842
- file. Use `data.repository(name)` for Record reads and `changes()` for guarded
843
- creates, updates and deletions. Local Runs read their own successive proposals
844
- and leave ordinary uncommitted files for review. `os routine publish` sends the
845
- retained logical changes explicitly. Inbox publication has no Record methods;
812
+ `runs.get(id)` read retained bounded history. Remote summaries omit local activity.
813
+ App-started local routines use the same current-checkout and live-effects owner.
814
+
815
+ Retain evidence with shared `files.create({ file: bytes, name: 'evidence.txt' })`
816
+ and put the returned File into `fileBlock({ label, files })`. Inbox has no byte
817
+ intake API. Local publication uploads that same File before exposing the item. Use `data.repository(name)` for Record reads and `changes()` for guarded
818
+ creates, updates and deletions. Local Runs read current checkout files and leave
819
+ completed Data writes as ordinary uncommitted files for review, even after later
820
+ failure. Inbox publication has no Record methods;
846
821
  managed Response handlers retain their separately injected repository capability.
847
822
 
848
823
  Hosts publishing as a member supply the existing `request` option:
849
824
 
850
825
  ```ts
851
- const inbox = routinePublicationClient({ workspace, workingRoot, request });
826
+ const inbox = routinePublicationClient({ workspace, request });
852
827
  ```
853
828
 
854
- Here `request` is the host's authorized member transport. A member can publish an
829
+ Here `request` is the host's authorized member transport and takes precedence
830
+ over ambient Run routing. The CLI uses its active workspace and shared Files to
831
+ upload referenced local Files before sending publication. A member can publish an
855
832
  initial handlerless Ask or Digest where allowed; a member login supplies no
856
833
  routine repository-write authority. Login, session storage and refresh belong
857
834
  to `@oddington/cli`.
package/dist/browser.js CHANGED
@@ -185,7 +185,11 @@ function parseAppRoutineContract(value) {
185
185
  };
186
186
  }
187
187
  case "array":
188
- return !fields(value2, ["type", "items", "maxItems"]) || !Number.isSafeInteger(value2.maxItems) || value2.maxItems < 1 || value2.maxItems > 64 ? invalidContract() : { type: "array", items: schema2(value2.items, depth + 1), maxItems: value2.maxItems };
188
+ return !fields(value2, ["type", "items"], ["maxItems"]) || owns(value2, "maxItems") && (!Number.isSafeInteger(value2.maxItems) || value2.maxItems < 1) ? invalidContract() : {
189
+ type: "array",
190
+ items: schema2(value2.items, depth + 1),
191
+ ...owns(value2, "maxItems") ? { maxItems: value2.maxItems } : {}
192
+ };
189
193
  case "boolean":
190
194
  return fields(value2, ["type"]) ? { type: value2.type } : invalidContract();
191
195
  case "file":
@@ -238,7 +242,8 @@ function validateRoutineSchemaValue(schema2, value, rejected) {
238
242
  return;
239
243
  case "array":
240
244
  if (!denseArray(value2)) return fail4("wrong_type");
241
- if (value2.length > schema3.maxItems) return fail4("out_of_bounds", { constraint: "maxItems", limit: schema3.maxItems });
245
+ if (schema3.maxItems !== void 0 && value2.length > schema3.maxItems)
246
+ return fail4("out_of_bounds", { constraint: "maxItems", limit: schema3.maxItems });
242
247
  value2.forEach((child, index) => check(schema3.items, child, `${path}[${index}]`));
243
248
  return;
244
249
  case "boolean":
@@ -887,8 +892,8 @@ var schema = {
887
892
  object(properties) {
888
893
  return declaration({ type: "object", properties: Object.fromEntries(Object.entries(properties).map(([key, value]) => [key, nestedSchema(value)])), required: Object.keys(properties), additionalProperties: !1 });
889
894
  },
890
- array(items, options) {
891
- return declaration({ type: "array", items: nestedSchema(items), maxItems: options.maxItems });
895
+ array(items, options = {}) {
896
+ return assertRoutineFields(options, ["maxItems"]), declaration({ type: "array", items: nestedSchema(items), ...options });
892
897
  },
893
898
  string(options = {}) {
894
899
  return declaration({ type: "string", ...options });
@@ -1665,7 +1670,7 @@ async function send(suffix, init2, descriptor, initial, parent) {
1665
1670
  parseFileErrorDetail(error?.detail)
1666
1671
  ) : unavailable2();
1667
1672
  }
1668
- if (response2.status !== (descriptor === 204 ? 204 : 200) || descriptor === 204 && response2.body !== null)
1673
+ if (response2.status !== 200)
1669
1674
  throw response2.body?.cancel().catch(() => {
1670
1675
  }), unavailable2();
1671
1676
  try {
@@ -1674,7 +1679,6 @@ async function send(suffix, init2, descriptor, initial, parent) {
1674
1679
  throw response2.body?.cancel().catch(() => {
1675
1680
  }), error;
1676
1681
  }
1677
- if (descriptor === 204) return;
1678
1682
  if (!descriptor) return response2;
1679
1683
  let value = await result(response2, descriptor === "control" ? CONTROL_RESULT_BYTES : RESULT_BYTES);
1680
1684
  if (descriptor === "control")
@@ -1773,10 +1777,15 @@ async function multipartCreate(input, metadata, exchange, release = () => {
1773
1777
  await upload?.beforeComplete();
1774
1778
  let completion = control({ parts: receipts });
1775
1779
  if (new TextEncoder().encode(completion.body).byteLength > 128 * 1024) throw unavailable2();
1776
- return parseFileDescriptor(await request2(
1780
+ let completed = parseFileDescriptor(await request2(
1777
1781
  (signal) => exchange(`${route}/complete`, completion, !0, signal),
1778
1782
  TIMEOUT_MS + Math.ceil(metadata.size / (1024 * 1024)) * 1e3
1779
1783
  ));
1784
+ if (!upload) {
1785
+ let acknowledged = await request2((signal) => exchange(`${route}/acknowledge`, control(completed), "control", signal));
1786
+ if (!acknowledged || typeof acknowledged != "object" || Object.keys(acknowledged).join(",") !== "acknowledged" || acknowledged.acknowledged !== !0) throw unavailable2();
1787
+ }
1788
+ return completed;
1780
1789
  } catch (error) {
1781
1790
  throw reader && await reader.cancel(error).catch(() => {
1782
1791
  }), ticket && await request2((signal) => exchange(`${route}/abort`, control({}), "control", signal)).catch(() => {
@@ -1839,9 +1848,6 @@ async function read(id3, names) {
1839
1848
  });
1840
1849
  }
1841
1850
  var files = {
1842
- delete(id3) {
1843
- return operation((context, signal) => send(idPath(id3), { method: "DELETE" }, 204, context, signal));
1844
- },
1845
1851
  async upload(id3) {
1846
1852
  return operation(async (context, signal) => {
1847
1853
  let file2 = await send(`${idPath(id3)}/upload`, { method: "POST" }, !0, context, signal);
@@ -2452,8 +2458,7 @@ var operations = {
2452
2458
  "items.listRecipientCandidates": ["itemId"],
2453
2459
  "items.addRecipient": ["itemId", "personId"],
2454
2460
  "questions.answer": ["itemId", "revision", "questionId", "response"],
2455
- "questions.searchRecords": ["itemId", "revision", "questionId", "query"],
2456
- "files.read": ["itemId", "fileId"]
2461
+ "questions.searchRecords": ["itemId", "revision", "questionId", "query"]
2457
2462
  };
2458
2463
  function assert(value) {
2459
2464
  if (!value) throw new Error("Invalid Inbox result");
@@ -3259,34 +3264,6 @@ async function send2(operation2, input, admitted) {
3259
3264
  if (response2.status !== 200)
3260
3265
  throw response2.body?.cancel().catch(() => {
3261
3266
  }), unavailable3();
3262
- if (operation2 === "files.read") {
3263
- if (!response2.body) throw unavailable3();
3264
- let source2 = response2.body, reader, size = 0, cancel = async (reason) => {
3265
- try {
3266
- await (reader ? reader.cancel(reason) : source2.cancel(reason));
3267
- } finally {
3268
- reader?.releaseLock();
3269
- }
3270
- }, body = new ReadableStream({
3271
- async pull(controller) {
3272
- try {
3273
- check(), reader ??= source2.getReader();
3274
- let next = await reader.read();
3275
- if (check(), next.done) {
3276
- reader.releaseLock(), controller.close();
3277
- return;
3278
- }
3279
- if (size += next.value.byteLength, size > 25 * 1024 * 1024) throw unavailable3();
3280
- controller.enqueue(next.value);
3281
- } catch (error) {
3282
- controller.error(error), await cancel(error).catch(() => {
3283
- });
3284
- }
3285
- },
3286
- cancel
3287
- }, { highWaterMark: 0 });
3288
- return new Response(body, { status: response2.status, headers: response2.headers });
3289
- }
3290
3267
  let bytes = await limitedBody(response2, INBOX_RESULT_BYTES);
3291
3268
  if (check(), !bytes) throw unavailable3();
3292
3269
  let value = JSON.parse(new TextDecoder("utf-8", { fatal: !0 }).decode(bytes)), parsed = parseInboxResult(operation2, value);
@@ -3365,8 +3342,7 @@ var inbox = {
3365
3342
  },
3366
3343
  answer: async (input) => await send2("questions.answer", input),
3367
3344
  searchRecords: async (input) => await send2("questions.searchRecords", input)
3368
- },
3369
- files: { read: async (input) => await send2("files.read", input) }
3345
+ }
3370
3346
  };
3371
3347
 
3372
3348
  var sources = /* @__PURE__ */ new WeakMap();
@@ -3882,7 +3858,6 @@ function routineRunHandle(operations2) {
3882
3858
  return Object.freeze({
3883
3859
  current: operations2.current,
3884
3860
  step: operations2.step,
3885
- ...operations2.publish ? { publish: operations2.publish } : {},
3886
3861
  async *watch() {
3887
3862
  let previous;
3888
3863
  for (; ; ) {
@@ -4013,7 +3988,7 @@ function summary(value, key, context, id3) {
4013
3988
  return fail3("execution_unavailable");
4014
3989
  }
4015
3990
  let run = value;
4016
- return (!only(run, "id,routine,placement,status,createdAt,startedAt,finishedAt,steps,result,failure,publication") || typeof run.id != "string" || !uuid2.test(run.id) || id3 !== void 0 && run.id !== id3 || run.routine !== key || run.placement !== "local" || !["queued", "running", "succeeded", "failed"].includes(run.status) || !timestamp(run.createdAt) || !optionalTimes(run) || !Array.isArray(run.steps) || run.steps.length > 64 || run.steps.some((step) => !only(step, "id,title,status,startedAt,finishedAt,summary,detailAvailable") || typeof step.detailAvailable != "boolean" || !validStep(step)) || new Set(run.steps.map((step) => step.id)).size !== run.steps.length || Object.hasOwn(run, "result") && (run.status !== "succeeded" || !bounded2(run.result)) || run.failure !== void 0 && (run.status !== "failed" || !only(run.failure, "message") || !message(run.failure.message)) || run.publication !== void 0 && (run.status !== "succeeded" || !validPublication(run.publication))) && fail3("execution_unavailable"), run;
3991
+ return (!only(run, "id,routine,placement,status,createdAt,startedAt,finishedAt,steps,result,failure,local") || typeof run.id != "string" || !uuid2.test(run.id) || id3 !== void 0 && run.id !== id3 || run.routine !== key || run.placement !== "local" || !["queued", "running", "succeeded", "failed"].includes(run.status) || !timestamp(run.createdAt) || !optionalTimes(run) || !Array.isArray(run.steps) || run.steps.length > 64 || run.steps.some((step) => !only(step, "id,title,status,startedAt,finishedAt,summary,detailAvailable") || typeof step.detailAvailable != "boolean" || !validStep(step)) || new Set(run.steps.map((step) => step.id)).size !== run.steps.length || Object.hasOwn(run, "result") && (run.status !== "succeeded" || !bounded2(run.result)) || run.failure !== void 0 && (run.status !== "failed" || !only(run.failure, "message") || !message(run.failure.message)) || run.local !== void 0 && !validLocalActivity(run.local)) && fail3("execution_unavailable"), run;
4017
3992
  }
4018
3993
  function only(value, allowed) {
4019
3994
  return !!value && typeof value == "object" && !Array.isArray(value) && Object.keys(value).every((key) => allowed.split(",").includes(key));
@@ -4025,8 +4000,9 @@ function optionalTimes(value) {
4025
4000
  function validStep(step) {
4026
4001
  return typeof step.id == "string" && identifier.test(step.id) && typeof step.title == "string" && step.title.length > 0 && step.title.length <= 256 && ["pending", "running", "completed", "failed", "skipped"].includes(step.status) && optionalTimes(step) && (!Object.hasOwn(step, "summary") || bounded2(step.summary)) && (!Object.hasOwn(step, "data") || bounded2(step.data));
4027
4002
  }
4028
- function validPublication(value) {
4029
- return value && only(value, value.state === "failed" ? "state,message" : "state") && (["not_needed", "ready", "published"].includes(value.state) || value.state === "failed" && message(value.message));
4003
+ function validLocalActivity(value) {
4004
+ let paths = (value2) => Array.isArray(value2) && value2.every((path) => typeof path == "string" && path.length > 0 && path.length <= 4096 && !path.startsWith("/") && !path.includes("\\") && !path.split("/").some((part) => !part || part === "." || part === "..") && !/[\x00-\x1f\x7f]/.test(path));
4005
+ return value && only(value, "data,effects,git") && bounded2(value) && Array.isArray(value.data) && Array.isArray(value.effects) && value.data.every((item) => only(item, "repository,created,updated,deleted,failures") && typeof item.repository == "string" && identifier.test(item.repository) && paths(item.created) && paths(item.updated) && paths(item.deleted) && (item.failures === void 0 || Array.isArray(item.failures) && item.failures.every((code) => code === "unavailable"))) && value.effects.every((item) => only(item, "capability,operation,status,reference,summary") && ["files", "inbox", "ai"].includes(item.capability) && typeof item.operation == "string" && /^[a-zA-Z][a-zA-Z0-9-]{0,62}$/.test(item.operation) && ["completed", "failed"].includes(item.status) && (item.reference === void 0 || typeof item.reference == "string" && uuid2.test(item.reference)) && (!Object.hasOwn(item, "summary") || bounded2(item.summary))) && (value.git === void 0 || only(value.git, "branch,commit,dirty") && typeof value.git.dirty == "boolean" && (value.git.branch === void 0 || typeof value.git.branch == "string" && value.git.branch.length <= 256 && !/[\x00-\x1f\x7f]/.test(value.git.branch)) && (value.git.commit === void 0 || typeof value.git.commit == "string" && /^[0-9a-f]{40}$/.test(value.git.commit)));
4030
4006
  }
4031
4007
  function jsonInput(value, ancestors = /* @__PURE__ */ new Set()) {
4032
4008
  if (value === null || typeof value == "string" || typeof value == "boolean" || typeof value == "number" && Number.isFinite(value)) return;
@@ -4062,9 +4038,6 @@ function handle(key, id3, context) {
4062
4038
  return fail3("execution_unavailable");
4063
4039
  }
4064
4040
  return (!only(value, "id,title,status,startedAt,finishedAt,summary,data") || value.id !== step || !validStep(value)) && fail3("execution_unavailable"), value;
4065
- },
4066
- async publish() {
4067
- return context.kind !== "local" && fail3("execution_unavailable"), summary(await request(key, `runs/${id3}/publish`, { method: "POST" }, SUMMARY_BYTES, context), key, context, id3);
4068
4041
  }
4069
4042
  });
4070
4043
  }
@@ -4144,21 +4117,31 @@ var app = /* @__PURE__ */ Object.freeze({
4144
4117
  })
4145
4118
  });
4146
4119
 
4147
- function validateFileAddOptions(value) {
4148
- let v = serializable(value);
4149
- if (!v || typeof v != "object" || Array.isArray(v) || Object.keys(v).some((k) => !["path", "name"].includes(k)) || typeof v.path != "string" || !v.path || v.path !== v.path.trim() || /[\\\x00-\x1f]/.test(v.path) || v.path.split("/").some((part) => !part || part === "." || part === "..") || /^[A-Za-z]:/.test(v.path))
4150
- throw new InboxContractError("File path must be relative to the configured working root");
4151
- if (v.name !== void 0 && (typeof v.name != "string" || !v.name || v.name !== v.name.trim() || v.name.length > 255 || /[\x00-\x1f\x7f/\\]/.test(v.name))) throw new InboxContractError("File name must be a safe filename");
4152
- return v;
4120
+ async function requestInboxPublication(operation2, request2, caller) {
4121
+ let attempt = () => {
4122
+ if (caller?.aborted) throw caller.reason;
4123
+ let timeout = AbortSignal.timeout(3e4);
4124
+ return request2(caller ? AbortSignal.any([caller, timeout]) : timeout);
4125
+ };
4126
+ if (operation2 !== "publish") return attempt();
4127
+ try {
4128
+ let response2 = await attempt();
4129
+ if (response2.status !== 408 && response2.status !== 429 && response2.status < 500) return response2;
4130
+ await response2.body?.cancel();
4131
+ } catch (error) {
4132
+ if (caller?.aborted) throw error;
4133
+ }
4134
+ if (caller?.aborted) throw caller.reason;
4135
+ return attempt();
4153
4136
  }
4154
4137
 
4155
4138
  function routinePublicationClient2(options = {}) {
4156
- let environment = {}, installed2 = routineTransport(), local2 = installed2?.request ?? void 0, workspace = installed2 ? "installed" : options.workspace ?? environment.OS_ROUTINE_WORKSPACE, capability = options.capability ?? environment.OS_ROUTINE_RUN;
4139
+ let environment = {}, installed2 = options.request ? void 0 : routineTransport(), local2 = options.request ? void 0 : installed2?.request ?? void 0, workspace = installed2 ? "installed" : options.workspace ?? environment.OS_ROUTINE_WORKSPACE, capability = options.capability ?? (options.request ? void 0 : environment.OS_ROUTINE_RUN);
4157
4140
  if (!workspace || !/^[a-z0-9][a-z0-9-]*$/.test(workspace) || capability !== void 0 && !/^[0-9a-f]{64}$/.test(capability))
4158
4141
  throw new Error("A workspace and valid optional routine capability are required");
4159
- let workingRoot = options.workingRoot ?? environment.OS_INBOX_WORKING_ROOT ?? void 0, headers = capability ? { "x-os-run": capability } : {}, request2 = options.request ?? (capability ? async (path, init2) => {
4142
+ let headers = capability ? { "x-os-run": capability } : {}, request2 = options.request ?? (capability ? async (path, init2) => {
4160
4143
  let origin = `https://${workspace}.oddington.ai`, target = new URL(path, `${origin}/`);
4161
- if (workspace.length > 63 || ["auth", "www", "api", "os"].includes(workspace) || target.origin !== origin || target.search || target.hash || target.username || target.password || !/^\/api\/inbox\/(check|publish|resolve|files)$/.test(path) || init2?.method !== "POST")
4144
+ if (workspace.length > 63 || ["auth", "www", "api", "os"].includes(workspace) || target.origin !== origin || target.search || target.hash || target.username || target.password || !/^\/api\/inbox\/(check|publish|resolve)$/.test(path) || init2?.method !== "POST")
4162
4145
  throw new Error("Invalid routine endpoint");
4163
4146
  return fetch(target, { ...init2, redirect: "error" });
4164
4147
  } : async () => {
@@ -4166,11 +4149,12 @@ function routinePublicationClient2(options = {}) {
4166
4149
  });
4167
4150
  async function send3(operation2, body) {
4168
4151
  if (local2) return await local2(operation2, body);
4169
- let response2 = await request2(`/api/inbox/${operation2}`, {
4152
+ let response2 = await requestInboxPublication(operation2, (signal) => request2(`/api/inbox/${operation2}`, {
4170
4153
  method: "POST",
4171
4154
  headers: { "content-type": "application/json", ...headers },
4172
- body: JSON.stringify(body)
4173
- }), result2 = await response2.json();
4155
+ body: JSON.stringify(body),
4156
+ signal
4157
+ })), result2 = await response2.json();
4174
4158
  if (!response2.ok) throw new Error(result2.error ?? "Could not confirm routine publication");
4175
4159
  return result2;
4176
4160
  }
@@ -4183,13 +4167,6 @@ function routinePublicationClient2(options = {}) {
4183
4167
  await send3("resolve", body);
4184
4168
  }
4185
4169
  }),
4186
- files: {
4187
- async add(value) {
4188
- let input = validateFileAddOptions(value);
4189
- if (local2) return validateInboxFile(await local2("files/add", input));
4190
- throw workingRoot ? new Error("Inbox file reads require a Node working root") : new Error("Inbox file reads require a working root");
4191
- }
4192
- },
4193
4170
  async check(body) {
4194
4171
  await send3("check", { requestKey: body.requestKey, item: validatePublication(body.item) });
4195
4172
  }