@toon-protocol/rig 4.6.0 → 5.0.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 (35) hide show
  1. package/README.md +118 -25
  2. package/dist/{chunk-X232W4TG.js → chunk-2TG4YHEC.js} +89 -59
  3. package/dist/chunk-2TG4YHEC.js.map +1 -0
  4. package/dist/{chunk-DCGV6ALH.js → chunk-4WVH2ITA.js} +158 -9
  5. package/dist/chunk-4WVH2ITA.js.map +1 -0
  6. package/dist/{chunk-U5A7MHBP.js → chunk-7VIVQHRG.js} +1 -1
  7. package/dist/chunk-7VIVQHRG.js.map +1 -0
  8. package/dist/chunk-CHVOXCDV.js +0 -0
  9. package/dist/chunk-CHVOXCDV.js.map +1 -0
  10. package/dist/{chunk-XCLUB2CY.js → chunk-LMZSAFAB.js} +44 -15
  11. package/dist/chunk-LMZSAFAB.js.map +1 -0
  12. package/dist/{chunk-JBFBD7HV.js → chunk-QKL53YZS.js} +7 -3
  13. package/dist/chunk-QKL53YZS.js.map +1 -0
  14. package/dist/{chunk-UE5TAFEU.js → chunk-ZQGXFFBP.js} +4 -4
  15. package/dist/chunk-ZQGXFFBP.js.map +1 -0
  16. package/dist/{ci-serve-AYFZOY4O.js → ci-serve-BU7NDT7O.js} +152 -26
  17. package/dist/ci-serve-BU7NDT7O.js.map +1 -0
  18. package/dist/cli/rig.js +6 -5
  19. package/dist/cli/rig.js.map +1 -1
  20. package/dist/index.d.ts +117 -4
  21. package/dist/index.js +40 -7
  22. package/dist/index.js.map +1 -1
  23. package/dist/standalone/index.d.ts +109 -74
  24. package/dist/standalone/index.js +13 -3
  25. package/dist/{standalone-mode-UQSP4677.js → standalone-mode-AT4S5EZG.js} +148 -50
  26. package/dist/standalone-mode-AT4S5EZG.js.map +1 -0
  27. package/package.json +2 -2
  28. package/dist/chunk-DCGV6ALH.js.map +0 -1
  29. package/dist/chunk-JBFBD7HV.js.map +0 -1
  30. package/dist/chunk-U5A7MHBP.js.map +0 -1
  31. package/dist/chunk-UE5TAFEU.js.map +0 -1
  32. package/dist/chunk-X232W4TG.js.map +0 -1
  33. package/dist/chunk-XCLUB2CY.js.map +0 -1
  34. package/dist/ci-serve-AYFZOY4O.js.map +0 -1
  35. package/dist/standalone-mode-UQSP4677.js.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/standalone/connector-publisher.ts"],"sourcesContent":["/**\n * The paid write path on `@toon-protocol/client` 4.x: a {@link Publisher}\n * that pays a TOON connector per request.\n *\n * Everything a paid command needs comes from one connector URL: the client\n * reads the node's own `GET /ilp` (addresses, prices, x402 batch-settlement\n * terms, sealing key: connector ADR 0050), opens or resumes an x402 channel,\n * and pays each request with a signed voucher for the running total\n * (connector ADR 0075). There is no relay to discover peers\n * on (kind:10032 was removed by ADR 0046) and no topology to negotiate: a\n * destination is a route the node prices, and `send()` pays what the route\n * costs — including the per-kibibyte part of an ADR 0065 schedule, which the\n * client works out from the sealed payload so this publisher never has to.\n *\n * Three request shapes, all ordinary paid POSTs:\n *\n * 1. `publishEvent` — a NIP-34 event signed with the OWNER's Nostr key,\n * sent as `{ event }` to the publish route; the relay answers\n * `{ eventId, storedAt, payment? }`.\n * 2. `uploadGitObject` / `uploadBlob` — a kind:5094 store job carrying the\n * bytes base64 in an `i` tag; the store answers the Arweave tx id.\n * 3. `submitStoreJob` — a kind:5095 / 5096 job with `param` tags; the\n * DVM's answer comes back as data, refusal included (ADR 0020: an answer\n * is an answer, and the payer was charged for it).\n *\n * The store route may terminate on a different node than the one addressed\n * (the edge forwards it). Then the payload must be sealed to THAT node's\n * identity — `sealTo` — and, when the store node holds its own channel, paid\n * through its own client. Both are options here; the common single-node case\n * needs neither.\n */\n\nimport { finalizeEvent } from 'nostr-tools/pure';\nimport { contentTypeForPath, DEFAULT_CONTENT_TYPE } from '../mime.js';\nimport type { UnsignedEvent } from '../nip34-events.js';\nimport {\n uploadChargeFor,\n type BlobUpload,\n type FeeRates,\n type GitObjectUpload,\n type PublishReceipt,\n type Publisher,\n type StoreJobRequest,\n type StoreJobResponse,\n type UploadReceipt,\n} from '../publisher.js';\n\n// ---------------------------------------------------------------------------\n// The slice of ToonClient this publisher drives (structural, so tests mock it)\n// ---------------------------------------------------------------------------\n\n/** A paid request, as `ToonClient.send` takes it. */\nexport interface PaidRequest {\n method?: string;\n target?: string;\n headers?: Record<string, string> | [string, string][];\n body?: string | Uint8Array | object;\n}\n\nexport interface PaidSendOptions {\n /** The terminating connector's `GET /ilp` URL or raw key, on a forwarded route. */\n sealTo?: string | Uint8Array;\n timeoutMs?: number;\n}\n\nexport interface PaidFulfilled {\n fulfilled: true;\n status: number;\n body: Uint8Array;\n /** What this request paid; absent on a route priced at zero. */\n claim?: { amount: bigint };\n}\n\nexport interface PaidRefused {\n fulfilled: false;\n code: string;\n message: string;\n refusedBy?: string;\n}\n\nexport type PaidSendResult = PaidFulfilled | PaidRefused;\n\n/** The route terms a connector publishes: a base price and an optional slope. */\nexport interface RouteTerms {\n price: bigint;\n pricePerKib?: bigint;\n}\n\n/** The slice of `ToonClient` (4.x) this publisher needs. */\nexport interface PaidClientLike {\n send(\n destination: string,\n request: PaidRequest,\n options?: PaidSendOptions\n ): Promise<PaidSendResult>;\n /** The node's terms for a destination, or `null` when it prices no such route. */\n routePrice(destination: string): Promise<RouteTerms | null>;\n}\n\n/** One leg of the write path: who pays, where to, and whom to seal to. */\nexport interface PaidLeg {\n client: PaidClientLike;\n destination: string;\n sealTo?: string | Uint8Array;\n}\n\nexport interface ConnectorPublisherOptions {\n /** Pays the publish route and signs nothing — the Nostr key below signs. */\n publish: PaidLeg;\n /** Pays the store route (often the same client as `publish`). */\n store: PaidLeg;\n /** The owner's Nostr secret key: signs every event this publisher sends. */\n nostrSecretKey: Uint8Array;\n /**\n * A configured per-event fee. Informational floor only: the connector's\n * published price for the publish route wins when it is higher, and the\n * client pays that price whatever this says.\n */\n eventFee?: bigint;\n /** Per-packet timeout for uploads (the store's Arweave round trip). */\n uploadTimeoutMs?: number;\n warn?(line: string): void;\n}\n\nexport class ConnectorPublishError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'ConnectorPublishError';\n }\n}\n\nconst ARWEAVE_TX_ID_REGEX = /^[A-Za-z0-9_-]{43}$/;\n\nfunction decodeUtf8(bytes: Uint8Array): string {\n return new TextDecoder().decode(bytes);\n}\n\nfunction nowSeconds(): number {\n return Math.floor(Date.now() / 1000);\n}\n\nfunction describeRefusal(result: PaidRefused): string {\n const by = result.refusedBy ? ` by the ${result.refusedBy}` : '';\n return `${result.code}${by}: ${result.message}`;\n}\n\n/**\n * Read the Arweave tx id out of a store's kind:5094 answer.\n *\n * The store answers one of two ways — `{ txId }` as JSON, or the bare id\n * base64 in `data` — and refuses with `{ accept: false, error }`. A non-2xx\n * status is a refusal too; both are thrown, because an upload without an id\n * is an upload that did not happen (even though the packet was paid for).\n */\nexport function extractArweaveTxId(status: number, body: Uint8Array): string {\n const text = decodeUtf8(body);\n if (status < 200 || status >= 300) {\n throw new ConnectorPublishError(\n `the store answered HTTP ${String(status)}${text ? ` - ${text}` : ''}`\n );\n }\n let parsed: unknown;\n try {\n parsed = JSON.parse(text);\n } catch {\n if (ARWEAVE_TX_ID_REGEX.test(text.trim())) return text.trim();\n throw new ConnectorPublishError(\n `the store's answer was not valid JSON: ${JSON.stringify(text.slice(0, 200))}`\n );\n }\n const answer = (parsed ?? {}) as {\n accept?: unknown;\n error?: unknown;\n txId?: unknown;\n data?: unknown;\n result?: unknown;\n };\n if (answer.accept === false) {\n const reason = typeof answer.error === 'string' ? `: ${answer.error}` : '';\n throw new ConnectorPublishError(\n `upload refused by the store (accept:false)${reason}`\n );\n }\n if (\n typeof answer.txId === 'string' &&\n ARWEAVE_TX_ID_REGEX.test(answer.txId)\n ) {\n return answer.txId;\n }\n const nested = answer.result as { txId?: unknown } | undefined;\n if (\n nested &&\n typeof nested.txId === 'string' &&\n ARWEAVE_TX_ID_REGEX.test(nested.txId)\n ) {\n return nested.txId;\n }\n if (typeof answer.data === 'string' && answer.data.length > 0) {\n const decoded = Buffer.from(answer.data, 'base64').toString('utf8');\n if (ARWEAVE_TX_ID_REGEX.test(decoded)) return decoded;\n }\n throw new ConnectorPublishError(\n `the store's answer carried no Arweave tx id: ${JSON.stringify(text.slice(0, 200))}`\n );\n}\n\n// ---------------------------------------------------------------------------\n// The publisher\n// ---------------------------------------------------------------------------\n\nexport class ConnectorPublisher implements Publisher {\n private readonly publish: PaidLeg;\n private readonly store: PaidLeg;\n private readonly nostrSecretKey: Uint8Array;\n private readonly configuredEventFee: bigint;\n private readonly uploadTimeoutMs: number | undefined;\n private readonly warn: (line: string) => void;\n private rates: Promise<FeeRates> | undefined;\n\n constructor(options: ConnectorPublisherOptions) {\n this.publish = options.publish;\n this.store = options.store;\n this.nostrSecretKey = options.nostrSecretKey;\n this.configuredEventFee = options.eventFee ?? 0n;\n this.uploadTimeoutMs = options.uploadTimeoutMs;\n this.warn = options.warn ?? (() => undefined);\n }\n\n /** Where events go, and where objects go. */\n get destinations(): { publish: string; store: string } {\n return { publish: this.publish.destination, store: this.store.destination };\n }\n\n /**\n * The two routes' published terms, read once per publisher. A route the\n * node does not price is a configuration error and says which knob to fix.\n */\n getFeeRates(): Promise<FeeRates> {\n this.rates ??= this.readFeeRates();\n return this.rates;\n }\n\n private async readFeeRates(): Promise<FeeRates> {\n const [publishTerms, storeTerms] = await Promise.all([\n this.publish.client.routePrice(this.publish.destination),\n this.store.client.routePrice(this.store.destination),\n ]);\n if (!publishTerms) {\n throw new ConnectorPublishError(\n `the connector prices no route for the publish destination ${this.publish.destination} ` +\n '(set publishDestination / TOON_CLIENT_PUBLISH_DESTINATION to one of its GET /ilp routes)'\n );\n }\n if (!storeTerms) {\n throw new ConnectorPublishError(\n `the connector prices no route for the store destination ${this.store.destination} ` +\n '(set storeDestination / TOON_CLIENT_STORE_DESTINATION to one of its GET /ilp routes)'\n );\n }\n if (\n publishTerms.pricePerKib !== undefined &&\n publishTerms.pricePerKib > 0n\n ) {\n this.warn(\n `rig: the publish route ${this.publish.destination} meters by size ` +\n `(${String(publishTerms.pricePerKib)}/KiB); the per-event estimate shows its base price only`\n );\n }\n const eventFee =\n this.configuredEventFee > publishTerms.price\n ? this.configuredEventFee\n : publishTerms.price;\n return {\n uploadFee: storeTerms.price,\n ...(storeTerms.pricePerKib !== undefined && storeTerms.pricePerKib > 0n\n ? { uploadPerKib: storeTerms.pricePerKib }\n : {}),\n eventFee,\n };\n }\n\n private sign(template: UnsignedEvent) {\n return finalizeEvent(\n {\n kind: template.kind,\n content: template.content,\n tags: template.tags,\n created_at: template.created_at,\n },\n this.nostrSecretKey\n );\n }\n\n /** One paid kind:5094 write; returns the tx id and what the voucher spent. */\n private async storeWrite(\n tags: string[][],\n bodyBytes: number,\n what: string\n ): Promise<UploadReceipt> {\n const rates = await this.getFeeRates();\n const estimate = uploadChargeFor(rates, bodyBytes);\n const event = this.sign({\n kind: 5094,\n content: '',\n created_at: nowSeconds(),\n tags: [...tags, ['bid', estimate.toString(), 'usdc']],\n });\n const result = await this.store.client.send(\n this.store.destination,\n { body: { event } },\n {\n ...(this.store.sealTo !== undefined\n ? { sealTo: this.store.sealTo }\n : {}),\n ...(this.uploadTimeoutMs !== undefined\n ? { timeoutMs: this.uploadTimeoutMs }\n : {}),\n }\n );\n if (!result.fulfilled) {\n throw new ConnectorPublishError(\n `${what} rejected — ${describeRefusal(result)}`\n );\n }\n let txId: string;\n try {\n txId = extractArweaveTxId(result.status, result.body);\n } catch (err) {\n throw new ConnectorPublishError(\n `${what}: ${err instanceof Error ? err.message : String(err)}`\n );\n }\n return { txId, feePaid: result.claim?.amount ?? estimate };\n }\n\n async uploadGitObject(upload: GitObjectUpload): Promise<UploadReceipt> {\n // No size gate (#102): the store route prices per KiB, so a body above\n // FREE_TIER_MAX_ITEM_BYTES is a paid upload rather than a refusal.\n const contentType =\n upload.type === 'blob'\n ? contentTypeForPath(upload.path)\n : DEFAULT_CONTENT_TYPE;\n return this.storeWrite(\n [\n ['i', upload.body.toString('base64'), 'blob'],\n ['output', contentType],\n ['Git-SHA', upload.sha],\n ['Git-Type', upload.type],\n ['Repo', upload.repoId],\n ],\n upload.body.length,\n `git-object upload (${upload.sha})`\n );\n }\n\n async uploadBlob(upload: BlobUpload): Promise<UploadReceipt> {\n // No size gate (#102), matching uploadGitObject: the store route prices\n // per KiB, so a blob above FREE_TIER_MAX_ITEM_BYTES costs more rather than\n // being refused.\n return this.storeWrite(\n [\n ['i', upload.body.toString('base64'), 'blob'],\n ['output', upload.contentType],\n ...(upload.repoId ? [['Repo', upload.repoId]] : []),\n ],\n upload.body.length,\n 'blob upload'\n );\n }\n\n /**\n * One NIP-90 job to the store node's DVM, over the paid path. A refusal is\n * returned, not thrown: `accept: false` and a non-2xx status are the\n * answer's content, and the payer was charged for the answer (ADR 0020).\n */\n async submitStoreJob(request: StoreJobRequest): Promise<StoreJobResponse> {\n const rates = await this.getFeeRates();\n const fee = rates.uploadFee;\n const event = this.sign({\n kind: request.kind,\n content: '',\n created_at: nowSeconds(),\n tags: [\n ...request.params.map(([key, value]) => ['param', key, value]),\n ['bid', fee.toString(), 'usdc'],\n ],\n });\n const result = await this.store.client.send(\n this.store.destination,\n { body: { event } },\n this.store.sealTo !== undefined ? { sealTo: this.store.sealTo } : {}\n );\n if (!result.fulfilled) {\n throw new ConnectorPublishError(\n `kind:${String(request.kind)} job rejected — ${describeRefusal(result)}`\n );\n }\n const text = decodeUtf8(result.body);\n let body: {\n accept?: unknown;\n code?: unknown;\n message?: unknown;\n result?: unknown;\n };\n try {\n body = JSON.parse(text) as typeof body;\n } catch {\n throw new ConnectorPublishError(\n `kind:${String(request.kind)} job answer was not valid JSON ` +\n `(HTTP ${String(result.status)}): ${JSON.stringify(text.slice(0, 200))}`\n );\n }\n return {\n status: result.status,\n accept: body.accept === true,\n ...(typeof body.code === 'string' ? { code: body.code } : {}),\n ...(typeof body.message === 'string' ? { message: body.message } : {}),\n ...(body.result !== null && typeof body.result === 'object'\n ? { result: body.result as Record<string, unknown> }\n : {}),\n feePaid: result.claim?.amount ?? fee,\n };\n }\n\n /**\n * Sign the event with the owner's key and pay to publish it. `relayUrls`\n * is the seam's plural surface: this publisher routes to one configured\n * destination, so more than one relay is refused rather than half-done.\n */\n async publishEvent(\n event: UnsignedEvent,\n relayUrls: string[]\n ): Promise<PublishReceipt> {\n if (relayUrls.length > 1) {\n throw new ConnectorPublishError(\n `multi-relay publish is not supported (got ${String(relayUrls.length)} relays) — ` +\n 'the connector publisher routes to a single publish destination'\n );\n }\n const rates = await this.getFeeRates();\n const signed = this.sign(event);\n const result = await this.publish.client.send(\n this.publish.destination,\n { body: { event: signed } },\n this.publish.sealTo !== undefined ? { sealTo: this.publish.sealTo } : {}\n );\n if (!result.fulfilled) {\n throw new ConnectorPublishError(\n `publish rejected (kind ${String(event.kind)}) — ${describeRefusal(result)}`\n );\n }\n if (result.status < 200 || result.status >= 300) {\n throw new ConnectorPublishError(\n `publish (kind ${String(event.kind)}): the relay answered HTTP ${String(result.status)} - ${decodeUtf8(result.body)}`\n );\n }\n let eventId = signed.id;\n try {\n const answer = JSON.parse(decodeUtf8(result.body)) as {\n eventId?: unknown;\n };\n if (typeof answer.eventId === 'string' && answer.eventId)\n eventId = answer.eventId;\n } catch {\n // A 2xx without a JSON body still stored the event we signed.\n }\n return { eventId, feePaid: result.claim?.amount ?? rates.eventFee };\n }\n}\n"],"mappings":"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","names":[]}
@@ -1,14 +1,15 @@
1
1
  import {
2
- PREFERRED_GATEWAY,
3
2
  WS_URL_RE,
4
3
  identityReport,
5
4
  loadPaidSession,
6
5
  renderIdentityLine,
7
6
  rigVersion,
8
7
  withForcedStandalone
9
- } from "./chunk-X232W4TG.js";
8
+ } from "./chunk-2TG4YHEC.js";
10
9
  import {
10
+ CI_LIVE_LOG_TAIL_INTERVAL_MS,
11
11
  CI_MANUAL_TRIGGER_KIND,
12
+ CI_RUNNER_CHANNEL_KEY,
12
13
  CI_SECRET_UPDATE_KIND,
13
14
  CI_SERVICE_REQUEST_KIND,
14
15
  CI_SERVICE_STOP_KIND,
@@ -16,6 +17,7 @@ import {
16
17
  applySecretUpdate,
17
18
  buildCiAdvertisement,
18
19
  buildCiJobResult,
20
+ buildCiLiveLogTail,
19
21
  buildCiWorkflowProgress,
20
22
  buildCiWorkflowResult,
21
23
  collectRepoObjects,
@@ -26,6 +28,8 @@ import {
26
28
  emitCliError,
27
29
  generateSecretsKey,
28
30
  hexToNpub,
31
+ liveLogTailBudget,
32
+ liveLogTailEventBudget,
29
33
  matchesPullRequest,
30
34
  matchesPush,
31
35
  missingObjectsMessage,
@@ -39,27 +43,33 @@ import {
39
43
  repoAddress,
40
44
  runGit,
41
45
  selectServiceRequests,
46
+ sliceLogTail,
42
47
  validateSecretUpdate,
43
48
  writeGitObjects
44
- } from "./chunk-DCGV6ALH.js";
49
+ } from "./chunk-4WVH2ITA.js";
50
+ import {
51
+ PREFERRED_GATEWAY,
52
+ configuredGateway,
53
+ readGatewaysFor,
54
+ shaResolverFor
55
+ } from "./chunk-CHVOXCDV.js";
45
56
  import {
46
- ARWEAVE_GATEWAYS,
47
57
  REPOSITORY_STATE_KIND,
48
58
  authorizedStatusAuthors,
49
59
  defaultWebSocketFactory,
50
60
  fetchRemoteState,
51
61
  parseStateRefTags,
52
62
  queryRelay
53
- } from "./chunk-JBFBD7HV.js";
63
+ } from "./chunk-QKL53YZS.js";
54
64
  import {
55
65
  ChannelMapStore,
56
66
  channelStatus,
57
67
  contentTypeForPath,
58
68
  resolveChannelPaths
59
- } from "./chunk-XCLUB2CY.js";
69
+ } from "./chunk-LMZSAFAB.js";
60
70
  import {
61
71
  uploadChargeFor
62
- } from "./chunk-U5A7MHBP.js";
72
+ } from "./chunk-7VIVQHRG.js";
63
73
 
64
74
  // src/cli/ci-serve.ts
65
75
  import { join as join4 } from "path";
@@ -431,7 +441,11 @@ var ActRunner = class {
431
441
  const line = buffered.slice(0, nl);
432
442
  buffered = buffered.slice(nl + 1);
433
443
  const parsedLine = parseActJsonLine(line);
434
- if (!parsedLine) continue;
444
+ if (!parsedLine) {
445
+ request.onLog?.(CI_RUNNER_CHANNEL_KEY, `${line}
446
+ `);
447
+ continue;
448
+ }
435
449
  lines.push(parsedLine);
436
450
  if (request.onLog && parsedLine.exitCode === void 0) {
437
451
  request.onLog(
@@ -444,7 +458,11 @@ var ActRunner = class {
444
458
  });
445
459
  stream.on("end", () => {
446
460
  const parsedLine = parseActJsonLine(buffered);
447
- if (parsedLine) lines.push(parsedLine);
461
+ if (parsedLine) {
462
+ lines.push(parsedLine);
463
+ } else if (buffered !== "") {
464
+ request.onLog?.(CI_RUNNER_CHANNEL_KEY, buffered);
465
+ }
448
466
  });
449
467
  };
450
468
  consume(child.stdout);
@@ -463,7 +481,7 @@ var ActRunner = class {
463
481
  startedAtMs,
464
482
  (note) => {
465
483
  this.options.warn?.(note);
466
- request.onLog?.("runner", `${note}
484
+ request.onLog?.(CI_RUNNER_CHANNEL_KEY, `${note}
467
485
  `);
468
486
  }
469
487
  );
@@ -890,6 +908,10 @@ var DEFAULT_RUN_TIMEOUT_MS = 30 * 60 * 1e3;
890
908
  var DEFAULT_ADVERTISEMENT_TTL = 30 * 60;
891
909
  var RENEW_FRACTION = 5 / 6;
892
910
  var LOG_TAIL_BYTES = 4096;
911
+ var LOG_UPLOAD_CAP_BYTES = 1024 * 1024;
912
+ var LOG_UPLOAD_HEAD_BYTES = 512 * 1024;
913
+ var LOG_UPLOAD_MARKER_BUDGET_BYTES = 256;
914
+ var LOG_UPLOAD_TAIL_BYTES = 512 * 1024 - LOG_UPLOAD_MARKER_BUDGET_BYTES;
893
915
  var SECRETS_KEY_MAX_AGE = 86400;
894
916
  function nowSeconds() {
895
917
  return Math.floor(Date.now() / 1e3);
@@ -903,6 +925,20 @@ function logTail(log) {
903
925
  omitted: bytes.byteLength - LOG_TAIL_BYTES
904
926
  };
905
927
  }
928
+ function boundLogForUpload(log) {
929
+ const bytes = Buffer.from(log, "utf-8");
930
+ if (bytes.byteLength <= LOG_UPLOAD_CAP_BYTES) return bytes;
931
+ const head = bytes.subarray(0, LOG_UPLOAD_HEAD_BYTES);
932
+ const tail = bytes.subarray(bytes.byteLength - LOG_UPLOAD_TAIL_BYTES);
933
+ const omitted = bytes.byteLength - head.byteLength - tail.byteLength;
934
+ const marker = Buffer.from(
935
+ `
936
+ [... ${omitted} bytes omitted (log capped at ${LOG_UPLOAD_CAP_BYTES} bytes) ...]
937
+ `,
938
+ "utf-8"
939
+ );
940
+ return Buffer.concat([head, marker, tail]);
941
+ }
906
942
  function gatewayRawUrl(gatewayUrl, txId) {
907
943
  return `${gatewayUrl.replace(/\/+$/, "")}/raw/${txId}`;
908
944
  }
@@ -1210,6 +1246,62 @@ async function startCoordinator(opts) {
1210
1246
  );
1211
1247
  };
1212
1248
  scheduleRenew();
1249
+ const secretValues = Object.values(run.secrets);
1250
+ const streamed = /* @__PURE__ */ new Map();
1251
+ const finishedJobs = /* @__PURE__ */ new Set();
1252
+ let tailClosed = false;
1253
+ const recordLog = (jobId, chunk) => {
1254
+ if (tailClosed || chunk === "") return;
1255
+ streamed.set(jobId, (streamed.get(jobId) ?? "") + chunk);
1256
+ };
1257
+ const publishLiveLogTail = async () => {
1258
+ const live = [];
1259
+ for (const [key, text] of streamed) {
1260
+ if (key !== CI_RUNNER_CHANNEL_KEY && finishedJobs.has(key)) continue;
1261
+ live.push([key, redactSecretValues(text, secretValues)]);
1262
+ }
1263
+ const budget = liveLogTailBudget(live.length);
1264
+ const jobs = [];
1265
+ let runnerChannel;
1266
+ for (const [key, text] of live) {
1267
+ const sliced = sliceLogTail(text, budget);
1268
+ if (key === CI_RUNNER_CHANNEL_KEY) runnerChannel = sliced;
1269
+ else jobs.push({ job: key, ...sliced });
1270
+ }
1271
+ const now2 = clock();
1272
+ try {
1273
+ await publish(
1274
+ buildCiLiveLogTail(
1275
+ {
1276
+ trigger,
1277
+ runId: run.runId,
1278
+ jobs,
1279
+ ...runnerChannel ? { runner: runnerChannel } : {},
1280
+ expiresAt: now2 + adTtl
1281
+ },
1282
+ now2
1283
+ )
1284
+ );
1285
+ } catch (err) {
1286
+ log(
1287
+ `[ci] ${label}: live log tail not published: ${redactSecretValues(
1288
+ err instanceof Error ? err.message : String(err),
1289
+ secretValues
1290
+ )}`
1291
+ );
1292
+ }
1293
+ };
1294
+ let tailTimer = null;
1295
+ let tailPublished = false;
1296
+ const scheduleTail = () => {
1297
+ tailTimer = scheduler.setTimeout(() => {
1298
+ if (tailClosed) return;
1299
+ tailPublished = true;
1300
+ void publishLiveLogTail();
1301
+ scheduleTail();
1302
+ }, CI_LIVE_LOG_TAIL_INTERVAL_MS);
1303
+ };
1304
+ scheduleTail();
1213
1305
  const abort = new AbortController();
1214
1306
  activeAborts.add(abort);
1215
1307
  run.abort = abort;
@@ -1227,6 +1319,7 @@ async function startCoordinator(opts) {
1227
1319
  trigger,
1228
1320
  secrets: run.secrets,
1229
1321
  timeoutMs,
1322
+ onLog: recordLog,
1230
1323
  signal: abort.signal
1231
1324
  }),
1232
1325
  new Promise((resolve2) => {
@@ -1260,19 +1353,19 @@ async function startCoordinator(opts) {
1260
1353
  } finally {
1261
1354
  scheduler.clearTimeout(timeoutHandle);
1262
1355
  scheduler.clearTimeout(renewTimer);
1356
+ scheduler.clearTimeout(tailTimer);
1263
1357
  activeAborts.delete(abort);
1264
1358
  }
1265
1359
  let conclusion = result.conclusion;
1266
1360
  if (timedOut) conclusion = "timed_out";
1267
1361
  else if (run.superseded || stopping && abort.signal.aborted)
1268
1362
  conclusion = "cancelled";
1269
- const secretValues = Object.values(run.secrets);
1270
1363
  for (const job of result.jobs) {
1271
1364
  const jobLog = redactSecretValues(job.log, secretValues);
1272
1365
  const { tail, omitted } = logTail(jobLog);
1273
1366
  const logReceipt = await serial.run(
1274
1367
  () => uploadBlob({
1275
- body: Buffer.from(jobLog, "utf-8"),
1368
+ body: boundLogForUpload(jobLog),
1276
1369
  contentType: "text/plain; charset=utf-8",
1277
1370
  repoId: repo.repoId
1278
1371
  })
@@ -1334,8 +1427,11 @@ async function startCoordinator(opts) {
1334
1427
  pubkey: me,
1335
1428
  jobId: job.jobId
1336
1429
  });
1430
+ finishedJobs.add(job.jobId);
1337
1431
  await publish(progress("in_progress"));
1338
1432
  }
1433
+ if (tailPublished) await publishLiveLogTail();
1434
+ tailClosed = true;
1339
1435
  const now = clock();
1340
1436
  const resultEventId = await publish(
1341
1437
  buildCiWorkflowResult(
@@ -2021,6 +2117,11 @@ Options:
2021
2117
  --act-bin <path> the act executable (default: RIG_ACT_BIN, else PATH)
2022
2118
  --platform <label=img> runs-on label \u2192 Docker image; repeatable
2023
2119
  (default: ubuntu-latest=${DEFAULT_ACT_PLATFORMS["ubuntu-latest"]})
2120
+ --pull pull the runner image before each run (act's default)
2121
+ --no-pull never pull: run the image already on this host \u2014
2122
+ what a locally built or otherwise private runner
2123
+ image needs, since act cannot pull it (act's own
2124
+ spelling, --pull=false, is accepted too)
2024
2125
  --workdir <dir> where commits are checked out for runs (default:
2025
2126
  <state-dir>/work)
2026
2127
  --once stop after the first run concludes (its Workflow
@@ -2046,9 +2147,18 @@ half-published (story 15).
2046
2147
  Stop with Ctrl-C (SIGINT) or SIGTERM: active runs conclude \`cancelled\`.`;
2047
2148
  var ServeUsageError = class extends Error {
2048
2149
  };
2150
+ function pullSpelling(arg) {
2151
+ if (!arg.startsWith("--pull=")) return arg;
2152
+ const value = arg.slice("--pull=".length);
2153
+ if (value === "false") return "--no-pull";
2154
+ if (value === "true") return "--pull";
2155
+ throw new ServeUsageError(
2156
+ `--pull takes no value \u2014 use --pull or --no-pull, got ${JSON.stringify(arg)}`
2157
+ );
2158
+ }
2049
2159
  function parseServeArgs(args) {
2050
2160
  const { values } = parseArgs({
2051
- args,
2161
+ args: args.map(pullSpelling),
2052
2162
  options: {
2053
2163
  relay: { type: "string" },
2054
2164
  repo: { type: "string", multiple: true },
@@ -2058,6 +2168,8 @@ function parseServeArgs(args) {
2058
2168
  gateway: { type: "string" },
2059
2169
  "act-bin": { type: "string" },
2060
2170
  platform: { type: "string", multiple: true },
2171
+ pull: { type: "boolean" },
2172
+ "no-pull": { type: "boolean" },
2061
2173
  workdir: { type: "string" },
2062
2174
  once: { type: "boolean", default: false },
2063
2175
  json: { type: "boolean", default: false },
@@ -2133,6 +2245,10 @@ function parseServeArgs(args) {
2133
2245
  platforms[spec.slice(0, eq)] = spec.slice(eq + 1);
2134
2246
  }
2135
2247
  }
2248
+ if (values.pull && values["no-pull"]) {
2249
+ throw new ServeUsageError("--pull and --no-pull cannot both be given");
2250
+ }
2251
+ const pull = values.pull ? true : values["no-pull"] ? false : void 0;
2136
2252
  return {
2137
2253
  relay: values.relay,
2138
2254
  repos,
@@ -2142,18 +2258,26 @@ function parseServeArgs(args) {
2142
2258
  ...values.gateway !== void 0 ? { gateway: values.gateway } : {},
2143
2259
  ...values["act-bin"] !== void 0 ? { actBin: values["act-bin"] } : {},
2144
2260
  ...platforms ? { platforms } : {},
2261
+ ...pull !== void 0 ? { pull } : {},
2145
2262
  ...values.workdir !== void 0 ? { workdir: values.workdir } : {},
2146
2263
  once: values.once,
2147
2264
  json: values.json
2148
2265
  };
2149
2266
  }
2150
- function estimateRunCost(estimate) {
2151
- return BigInt(estimate.events) * estimate.rates.eventFee + BigInt(estimate.uploads) * uploadChargeFor(estimate.rates, 64 * 1024);
2267
+ function actRunnerOptions(flags) {
2268
+ return {
2269
+ ...flags.actBin !== void 0 ? { actBin: flags.actBin } : {},
2270
+ ...flags.platforms ? { platforms: flags.platforms } : {},
2271
+ ...flags.pull !== void 0 ? { pull: flags.pull } : {}
2272
+ };
2273
+ }
2274
+ function estimateRunCost(estimate, runTimeoutMs) {
2275
+ return BigInt(estimate.events) * estimate.rates.eventFee + BigInt(estimate.uploads) * uploadChargeFor(estimate.rates, LOG_UPLOAD_CAP_BYTES) + BigInt(liveLogTailEventBudget(runTimeoutMs)) * estimate.rates.eventFee;
2152
2276
  }
2153
2277
  function makeAffordabilityCheck(args) {
2154
- const { ctx, env, log } = args;
2278
+ const { ctx, env, log, runTimeoutMs } = args;
2155
2279
  return async (estimate) => {
2156
- const cost = estimateRunCost(estimate);
2280
+ const cost = estimateRunCost(estimate, runTimeoutMs);
2157
2281
  const store = new ChannelMapStore(resolveChannelPaths(env));
2158
2282
  const mine = store.list().filter(
2159
2283
  (record) => record.identity.toLowerCase() === ctx.ownerPubkey.toLowerCase() && record.supersededAt === void 0
@@ -2239,16 +2363,13 @@ async function runCiServe(args, deps) {
2239
2363
  io.err(CI_SERVE_USAGE);
2240
2364
  return 2;
2241
2365
  }
2242
- const gateway = (flags.gateway ?? forced.env["RIG_ARWEAVE_GATEWAY"] ?? PREFERRED_GATEWAY).replace(/\/+$/, "");
2243
- const readGateways = [`${gateway}/raw`, ...ARWEAVE_GATEWAYS];
2366
+ const gateway = configuredGateway(flags.gateway, forced.env) ?? PREFERRED_GATEWAY;
2367
+ const readGateways = readGatewaysFor(gateway);
2244
2368
  let runner;
2245
2369
  if (forced.runner) {
2246
2370
  runner = forced.runner;
2247
2371
  } else {
2248
- const act = new ActRunner({
2249
- ...flags.actBin !== void 0 ? { actBin: flags.actBin } : {},
2250
- ...flags.platforms ? { platforms: flags.platforms } : {}
2251
- });
2372
+ const act = new ActRunner(actRunnerOptions(flags));
2252
2373
  if (act.binary(forced.env) === null) {
2253
2374
  io.err(
2254
2375
  "rig ci serve: the act executable was not found \u2014 install nektos/act (https://github.com/nektos/act) on this host, or point --act-bin / RIG_ACT_BIN at it. Nothing was started or paid."
@@ -2291,11 +2412,14 @@ async function runCiServe(args, deps) {
2291
2412
  canAfford: forced.canAfford ?? makeAffordabilityCheck({
2292
2413
  ctx,
2293
2414
  env: forced.env,
2294
- log: (line) => io.err(line)
2415
+ log: (line) => io.err(line),
2416
+ runTimeoutMs: flags.timeoutMs
2295
2417
  }),
2296
2418
  ...forced.webSocketFactory ? { webSocketFactory: forced.webSocketFactory } : {},
2297
2419
  ...forced.fetchFn ? { fetchFn: forced.fetchFn } : {},
2298
- ...forced.resolveSha ? { resolveSha: forced.resolveSha } : {},
2420
+ // SHAs the object map does not cover are resolved against the SAME
2421
+ // permaweb the objects are read from, not arweave.net (#183).
2422
+ resolveSha: forced.resolveSha ?? shaResolverFor(flags.gateway, forced.env),
2299
2423
  ...forced.clock ? { clock: forced.clock } : {},
2300
2424
  ...firstRun ? { onRunConcluded: (run) => concludeFirstRun(run) } : {},
2301
2425
  log: (line) => io.err(line)
@@ -2365,8 +2489,10 @@ async function runCiServe(args, deps) {
2365
2489
  }
2366
2490
  export {
2367
2491
  CI_SERVE_USAGE,
2492
+ actRunnerOptions,
2368
2493
  estimateRunCost,
2369
2494
  makeAffordabilityCheck,
2495
+ parseServeArgs,
2370
2496
  runCiServe
2371
2497
  };
2372
- //# sourceMappingURL=ci-serve-AYFZOY4O.js.map
2498
+ //# sourceMappingURL=ci-serve-BU7NDT7O.js.map