@agentcomms/events-daemon 0.18.0 → 0.18.2

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
@@ -40,19 +40,28 @@ safe way to see what a rule would send before you point it at a webhook.
40
40
  agent-events target add '{"targetId":"log","version":1,"kind":"dry-run","retentionMs":86400000}'
41
41
 
42
42
  # 2. a rule: every received Gmail message with a subject, mapped to its subject only
43
- agent-events sources list # the Gmail account ids you can name in a rule
43
+ agent-events sources list # every connected account a rule can name: its source, id and name
44
44
  agent-events rule create "$(cat rule.json)"
45
45
 
46
46
  # 3. turn it on: each step prints an approval, which you approve by typing its challenge
47
- agent-events rule enable mail-subjects 1
48
- agent-events approve <approval-id>
49
47
  agent-events enable-all # the standing approval that starts collection
50
48
  agent-events approve <approval-id>
49
+ agent-events rule enable mail-subjects 1
50
+ agent-events approve <approval-id> # offers to test the target first; answering no is fine
51
51
 
52
- # 4. read what matched
52
+ # 4. read what matched: the list gives each record's id, and show prints that record
53
53
  agent-events dryrun list
54
+ agent-events dryrun show <record-id>
54
55
  ```
55
56
 
57
+ Switch collection on before approving the rule: a pending target can be tested only while collection is on. If a test
58
+ cannot run, the approval says why and still asks for its challenge.
59
+
60
+ An approval that `rule enable` or `enable-all` prints lasts 30 minutes. If it runs out, or is revoked, before you
61
+ approve it, nothing is turned on. On its next tick, or when it next starts, the service cancels the activation that
62
+ was waiting for it, and `status` stops counting it as pending. Run `rule enable` again for a new approval. An update
63
+ that lapsed this way no longer blocks the rule's next version.
64
+
56
65
  `rule.json`:
57
66
 
58
67
  ```json
@@ -832,7 +832,7 @@ async function removeTarget(targetId, options = {}) {
832
832
  //#endregion
833
833
  //#region src/version.ts
834
834
  /** The package version, read from package.json at build time. */
835
- const VERSION = "0.18.0";
835
+ const VERSION = "0.18.2";
836
836
  //#endregion
837
837
  //#region src/mcp/server.ts
838
838
  function reply(data) {
package/dist/cli.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { $ as sourcesList, $t as assertControlSupported, A as normaliseWhatsAppSourceOptions, At as decideHeld, B as addTarget, Bt as CONTROL_PROTOCOL_VERSIONS, C as canonicalSseSubscriber, Ct as enableAll, D as normaliseResendSourceOptions, Dt as ACTIVATION_FAILURE, E as normaliseGmailSourceOptions, Et as doctor, F as canonicalJudgeBudget, Ft as catalogueList, G as addSubscriber, Gt as isControlRequest, H as targetsList, Ht as controlFailure, I as canonicalJudgeKindEnablement, It as catalogueShow, J as updateSubscriber, Jt as newInstanceRecord, K as removeSubscriber, Kt as negotiateVersion, L as judgeKindDefinition, Lt as approvalView, M as JUDGE_BUDGET_ID, Mt as dropDelivery, N as JUDGE_KIND_DEFINITIONS, Nt as heldList, O as normaliseSlackSourceOptions, Ot as isActivationFailureCode, P as canonicalJudge, Pt as retryDelivery, Q as sourceShow$1, Qt as writeInstance, R as createEventsMcpServer, Rt as EventControlClient, S as normaliseActivationDocument, St as updateJudge, T as classifySourceOptionChange, Tt as assertDoctorReportV2, U as updateTarget, Ut as encodeControlFrame, V as removeTarget$1, Vt as ControlFrameReader, W as targetTest, Wt as isControlHello, X as status, Xt as readInstance, Y as stop, Yt as processIsLive, Z as loadSqlite, Zt as removeInstance, _ as activationDocumentDigest, _t as judgeTest, an as ensureEventSocketDirectory, at as rulesList, b as canonicalTarget, bt as removeJudgeModel, cn as installErrorSink, ct as ruleTestRetained, d as B3_SECRET_TERMINAL_ORIGIN, dt as addJudge, en as assertSocketPathFits, et as createRule$1, f as isB3ControlOperation, ft as budgetShow, g as isOwnerControlWireOperation, gt as judgeModelStatus, h as OWNER_CONTROL_WIRES, ht as judgeKindsList, in as ensureEventPaths, it as ruleShow$1, j as APPROVED_TYPESAFE_MODELS, jt as deliveriesList, k as normaliseSourceOptions, kt as disableAll, lt as pause, m as renderB3ApprovalPreviewV1, mt as enableJudgeKind, nn as verifyControlSocket, nt as enableRule, on as ensureOwnerOnlyFile, ot as updateRule, p as isB3SecretOperation, pt as disableJudgeKind, q as subscribersList, qt as mayRecoverStaleInstance, rn as verifyPrivateSocketDirectory, rt as removeRule, sn as eventPaths, st as ruleTest, tn as controlEndpoint, tt as disableRule$1, ut as resume, v as canonicalFullRuleDocument, vt as judgesList, w as sseDeliveryTarget, wt as assertDoctorReportV1, x as disclosureBindingFor, xt as updateBudget, y as canonicalSubscriber, yt as removeJudge, z as VERSION, zt as probeControl } from "./b3-contract-B6UBBK7F.mjs";
2
+ import { $ as sourcesList, $t as assertControlSupported, A as normaliseWhatsAppSourceOptions, At as decideHeld, B as addTarget, Bt as CONTROL_PROTOCOL_VERSIONS, C as canonicalSseSubscriber, Ct as enableAll, D as normaliseResendSourceOptions, Dt as ACTIVATION_FAILURE, E as normaliseGmailSourceOptions, Et as doctor, F as canonicalJudgeBudget, Ft as catalogueList, G as addSubscriber, Gt as isControlRequest, H as targetsList, Ht as controlFailure, I as canonicalJudgeKindEnablement, It as catalogueShow, J as updateSubscriber, Jt as newInstanceRecord, K as removeSubscriber, Kt as negotiateVersion, L as judgeKindDefinition, Lt as approvalView, M as JUDGE_BUDGET_ID, Mt as dropDelivery, N as JUDGE_KIND_DEFINITIONS, Nt as heldList, O as normaliseSlackSourceOptions, Ot as isActivationFailureCode, P as canonicalJudge, Pt as retryDelivery, Q as sourceShow$1, Qt as writeInstance, R as createEventsMcpServer, Rt as EventControlClient, S as normaliseActivationDocument, St as updateJudge, T as classifySourceOptionChange, Tt as assertDoctorReportV2, U as updateTarget, Ut as encodeControlFrame, V as removeTarget$1, Vt as ControlFrameReader, W as targetTest, Wt as isControlHello, X as status, Xt as readInstance, Y as stop, Yt as processIsLive, Z as loadSqlite, Zt as removeInstance, _ as activationDocumentDigest, _t as judgeTest, an as ensureEventSocketDirectory, at as rulesList, b as canonicalTarget, bt as removeJudgeModel, cn as installErrorSink, ct as ruleTestRetained, d as B3_SECRET_TERMINAL_ORIGIN, dt as addJudge, en as assertSocketPathFits, et as createRule$1, f as isB3ControlOperation, ft as budgetShow, g as isOwnerControlWireOperation, gt as judgeModelStatus, h as OWNER_CONTROL_WIRES, ht as judgeKindsList, in as ensureEventPaths, it as ruleShow$1, j as APPROVED_TYPESAFE_MODELS, jt as deliveriesList, k as normaliseSourceOptions, kt as disableAll, lt as pause, m as renderB3ApprovalPreviewV1, mt as enableJudgeKind, nn as verifyControlSocket, nt as enableRule, on as ensureOwnerOnlyFile, ot as updateRule, p as isB3SecretOperation, pt as disableJudgeKind, q as subscribersList, qt as mayRecoverStaleInstance, rn as verifyPrivateSocketDirectory, rt as removeRule, sn as eventPaths, st as ruleTest, tn as controlEndpoint, tt as disableRule$1, ut as resume, v as canonicalFullRuleDocument, vt as judgesList, w as sseDeliveryTarget, wt as assertDoctorReportV1, x as disclosureBindingFor, xt as updateBudget, y as canonicalSubscriber, yt as removeJudge, z as VERSION, zt as probeControl } from "./b3-contract-BUm3TGtz.mjs";
3
3
  import { a as EventDomainError, d as isJudgeKind, f as validateLocalEndpoint, g as parseIpLiteral, h as assertApprovedAddressSet, i as webhookDeliveryTarget, l as JUDGE_KIND_TYPESAFE, m as assertAddressAllowed, n as canonicalWebhookTarget, o as JUDGE_KINDS, p as AddressPolicyError, r as secretWebhookUrlDescriptor, s as JUDGE_KIND_LAYA, t as canonicalSecretWebhookUrl, u as assertJudgeKindEnabled } from "./webhook-target-DyDTwWhV.mjs";
4
4
  import { a as WriterQuiescer, c as openEventSecretStore, o as withEventWriterLease, r as RecordStorageError, s as migrateEventSecrets, t as EventRecordCipher } from "./records-DsUxYBwb.mjs";
5
5
  import process$1 from "node:process";
@@ -9555,6 +9555,26 @@ async function assertLiveEventAccount(config, scope) {
9555
9555
  source: scope.source
9556
9556
  } });
9557
9557
  }
9558
+ /**
9559
+ * Every connected account a rule may name, loaded afresh: Gmail's inboxes by their stable `ibx_…` id and the other
9560
+ * channels' accounts by their `acc_…` id, each beside the name a person knows it by. What `sources list` shows, so
9561
+ * a first rule's account id can be read from it before any rule is active (CUE-474).
9562
+ */
9563
+ async function connectedEventAccounts(config) {
9564
+ const current = await config.load();
9565
+ const found = [];
9566
+ for (const [account, inbox] of Object.entries(current.inboxes)) if (inbox.provider === "gmail") found.push({
9567
+ source: "gmail",
9568
+ accountId: inbox.id,
9569
+ account
9570
+ });
9571
+ for (const [account, entry] of Object.entries(current.accounts)) if (entry.platform === "slack" || entry.platform === "resend" || entry.platform === "whatsapp") found.push({
9572
+ source: entry.platform,
9573
+ accountId: entry.id,
9574
+ account
9575
+ });
9576
+ return found.sort((left, right) => `${left.source}\u0000${left.account}`.localeCompare(`${right.source}\u0000${right.account}`));
9577
+ }
9558
9578
  /** The current stable ids for one source, loaded afresh at the boundary. */
9559
9579
  async function liveEventAccountIds(config, source) {
9560
9580
  const current = await config.load();
@@ -11915,6 +11935,7 @@ var ActivationRuntime = class {
11915
11935
  };
11916
11936
  }
11917
11937
  const oldId = `${input.ruleId}@${active.version}`;
11938
+ await this.#settleLapsedApprovals(oldId);
11918
11939
  if (this.#store.database.prepare("SELECT 1 AS present FROM activation_intents WHERE replacement_of_version = ? AND status IN ('pending', 'pending-completion')").get(oldId) !== void 0) throw new CommsError("TRANSIENT", "this rule already has a replacement completing", { details: { reason: "REPLACEMENT_PENDING" } });
11919
11940
  const live = await liveEventAccountIds(this.#config, parent.source.channel);
11920
11941
  const points = this.#unionReplacementPoints(parent, document.rule).filter((point) => point.ruleVersion !== parent.version || live.has(point.accountId) && this.#holdsActivePoint(parent.ruleId, parent.version, {
@@ -12134,6 +12155,7 @@ var ActivationRuntime = class {
12134
12155
  this.#cancel(intent.id, ACTIVATION_FAILURE.APPROVAL_ABSENT);
12135
12156
  continue;
12136
12157
  }
12158
+ if (this.#settleLapsedApproval(intent.id, intent.approval_id, record)) continue;
12137
12159
  if (record.record.state === "approved") {
12138
12160
  const binding = this.#settleOnDrift(intent);
12139
12161
  if (binding === null) continue;
@@ -12168,7 +12190,8 @@ var ActivationRuntime = class {
12168
12190
  }
12169
12191
  /**
12170
12192
  * A live owner retries only work that core has already marked used. Unlike startup recovery this never claims an
12171
- * approved disclosure and it always carries the immutable usedAt back through the ordinary completion path.
12193
+ * approved disclosure and it always carries the immutable usedAt back through the ordinary completion path. After
12194
+ * that claimed work, it settles every unclaimed intent whose approval lapsed unused, as startup recovery does.
12172
12195
  */
12173
12196
  async resumeClaimedCompletions() {
12174
12197
  const rows = this.#store.database.prepare("SELECT id, kind, document, digest, effect, replacement_of_version, status, approval_id, claimed_at, completion_deadline FROM activation_intents WHERE status = 'pending-completion' ORDER BY id").all();
@@ -12197,6 +12220,36 @@ var ActivationRuntime = class {
12197
12220
  throw error;
12198
12221
  }
12199
12222
  }
12223
+ await this.#settleLapsedApprovals();
12224
+ }
12225
+ /** Settles the unclaimed pending intents — of one replacement slot, or every one — whose approval lapsed unused. */
12226
+ async #settleLapsedApprovals(replacementOfVersion) {
12227
+ const rows = this.#store.database.prepare("SELECT id, approval_id, replacement_of_version FROM activation_intents WHERE status = 'pending' AND claimed_at IS NULL AND approval_id IS NOT NULL ORDER BY id").all();
12228
+ for (const row of rows) {
12229
+ if (replacementOfVersion !== void 0 && row.replacement_of_version !== replacementOfVersion) continue;
12230
+ this.#settleLapsedApproval(row.id, row.approval_id, await this.#approvals.get(row.approval_id));
12231
+ }
12232
+ }
12233
+ /**
12234
+ * CUE-491: a disclosure approval that core reads as expired or revoked can never be approved or claimed again, so
12235
+ * its unclaimed intent is cancelled (D2: expired, revoked and absent drop). Left pending, every pass would carry it
12236
+ * for good, and an update's would hold its rule's one replacement slot, refusing the next update as
12237
+ * REPLACEMENT_PENDING. Core's answer crossed an await: the write re-reads the intent under its transaction and
12238
+ * settles only the same unclaimed pending one. The failure code reuses APPROVAL_ABSENT — no usable approval
12239
+ * remains, as the claimed path already records for a non-used one — because the B3 control and doctor v1 schemas
12240
+ * that close the code set are pinned.
12241
+ */
12242
+ #settleLapsedApproval(intentId, approvalId, record) {
12243
+ if (record?.form !== "v2" || record.record.kind !== "disclosure") return false;
12244
+ if (record.record.state !== "expired" && record.record.state !== "revoked") return false;
12245
+ let settled = false;
12246
+ this.#store.immediate(() => {
12247
+ const latest = this.#store.database.prepare("SELECT status, approval_id, claimed_at FROM activation_intents WHERE id = ?").get(intentId);
12248
+ if (latest?.status !== "pending" || latest.claimed_at !== null || latest.approval_id !== approvalId) return;
12249
+ this.#cancel(intentId, ACTIVATION_FAILURE.APPROVAL_ABSENT);
12250
+ settled = true;
12251
+ });
12252
+ return settled;
12200
12253
  }
12201
12254
  /**
12202
12255
  * The live binding of a claimed intent, or null after settling it: a binding that no longer holds (its pointer or
@@ -14543,7 +14596,8 @@ var B3TargetTestControl = class {
14543
14596
  SELECT 1 FROM object_revocations AS revoked
14544
14597
  WHERE revoked.kind = 'target' AND revoked.object_id = target.target_id AND revoked.version = target.version
14545
14598
  )`).get(targetId, targetVersion);
14546
- if (row === void 0 || row.enabled !== 1) return refuse("the exact target version is not live");
14599
+ if (row === void 0) return refuse("the exact target version is not live");
14600
+ if (row.enabled !== 1) return refuse("collection is switched off: switch it on with enable-all first, or approve without the test");
14547
14601
  if (selectorKind === "active") {
14548
14602
  if (this.#store.database.prepare(`SELECT 1 AS present
14549
14603
  FROM target_version_references AS reference
@@ -21069,7 +21123,7 @@ async function startOwnerWithLock(options, stateDir, paths, lock, started, onDat
21069
21123
  control = await (options.startControlServer ?? startControlServer)({
21070
21124
  endpoint: instance.endpoint,
21071
21125
  token,
21072
- handle: async (request) => handleControl(owner, lifecycle, database, activations, dryrun, sourceRegistry, writerQuiescer, request, ownerB3Service, doctorReport),
21126
+ handle: async (request) => handleControl(owner, lifecycle, database, activations, dryrun, sourceRegistry, writerQuiescer, request, ownerB3Service, doctorReport, () => connectedEventAccounts(core.config)),
21073
21127
  verifyEndpoint: () => verifyPrivateSocketDirectory(paths)
21074
21128
  });
21075
21129
  if (options.startControlServer === void 0) {
@@ -21100,7 +21154,7 @@ async function runEventOwner(options = {}) {
21100
21154
  await owner.stop();
21101
21155
  }
21102
21156
  }
21103
- async function handleControl(owner, lifecycle, database, activations, dispatcher, sourceRegistry, writerQuiescer, request, b3OwnerService, doctorReport) {
21157
+ async function handleControl(owner, lifecycle, database, activations, dispatcher, sourceRegistry, writerQuiescer, request, b3OwnerService, doctorReport, connectedAccounts = async () => []) {
21104
21158
  if (!isOwnerControlWireOperation(request.operation)) throw new CommsError("USAGE", "the local event control operation is not recognised");
21105
21159
  if (isB3ControlOperation(request.operation)) {
21106
21160
  if (!b3OwnerService) throw new CommsError("USAGE", "the local B3 control operation is not available");
@@ -21143,8 +21197,8 @@ async function handleControl(owner, lifecycle, database, activations, dispatcher
21143
21197
  channel: definition.type.split(".")[0]
21144
21198
  };
21145
21199
  }
21146
- case OWNER_CONTROL_WIRES.sourcesList: return sourceRows(database, sourceRegistry);
21147
- case OWNER_CONTROL_WIRES.sourceShow: return sourceShow(database, sourceRegistry, requiredText(request.args.source, "source"));
21200
+ case OWNER_CONTROL_WIRES.sourcesList: return sourceRows(database, sourceRegistry, await connectedAccounts());
21201
+ case OWNER_CONTROL_WIRES.sourceShow: return sourceShow(database, sourceRegistry, requiredText(request.args.source, "source"), await connectedAccounts());
21148
21202
  case OWNER_CONTROL_WIRES.rulesList: return ruleRows(database);
21149
21203
  case OWNER_CONTROL_WIRES.ruleShow: return ruleShow(database, requiredText(request.args.ruleId, "rule id"));
21150
21204
  case OWNER_CONTROL_WIRES.createRule:
@@ -21217,7 +21271,12 @@ function targetRows(database) {
21217
21271
  function createTarget(database, document) {
21218
21272
  return domain(() => new ImmutableVersions(database.database).createTarget(document));
21219
21273
  }
21220
- function sourceRows(database, sourceRegistry) {
21274
+ /**
21275
+ * Each source's accounts: the scopes active rules read, and every connected account a rule may name, which has no
21276
+ * cursor scope until a rule reads it. Each carries the name it was connected under, so a person, or an agent writing
21277
+ * a first rule, can tell `ibx_…` from `ibx_…` (CUE-474).
21278
+ */
21279
+ function sourceRows(database, sourceRegistry, connected = []) {
21221
21280
  const rows = database.database.prepare("SELECT document FROM rule_versions WHERE state IN ('active', 'superseded')").all();
21222
21281
  const scopes = /* @__PURE__ */ new Map();
21223
21282
  for (const row of rows) {
@@ -21239,17 +21298,28 @@ function sourceRows(database, sourceRegistry) {
21239
21298
  } catch {}
21240
21299
  }
21241
21300
  }
21242
- const ordered = [...scopes.values()].sort((left, right) => `${left.source}\u0000${left.accountId}\u0000${left.cursorScope}`.localeCompare(`${right.source}\u0000${right.accountId}\u0000${right.cursorScope}`));
21301
+ const nameOf = new Map(connected.map((entry) => [`${entry.source}\u0000${entry.accountId}`, entry.account]));
21302
+ const accounts = [...scopes.values()].map((scope) => ({
21303
+ ...scope,
21304
+ account: nameOf.get(`${scope.source}\u0000${scope.accountId}`) ?? null
21305
+ }));
21306
+ for (const entry of connected) if (!accounts.some((row) => row.source === entry.source && row.accountId === entry.accountId)) accounts.push({
21307
+ source: entry.source,
21308
+ accountId: entry.accountId,
21309
+ account: entry.account,
21310
+ cursorScope: null
21311
+ });
21312
+ const ordered = accounts.sort((left, right) => `${left.source}\u0000${left.accountId}\u0000${left.cursorScope ?? ""}`.localeCompare(`${right.source}\u0000${right.accountId}\u0000${right.cursorScope ?? ""}`));
21243
21313
  return sourceRegistry.sources().map((source) => ({
21244
21314
  source,
21245
21315
  accounts: ordered.filter((scope) => scope.source === source)
21246
21316
  }));
21247
21317
  }
21248
- function sourceShow(database, sourceRegistry, source) {
21318
+ function sourceShow(database, sourceRegistry, source, connected = []) {
21249
21319
  if (!sourceRegistry.sources().includes(source)) throw new CommsError("NOT_FOUND", "the requested source is not configured");
21250
21320
  return {
21251
21321
  source,
21252
- accounts: sourceRows(database, sourceRegistry).find((row) => row.source === source).accounts
21322
+ accounts: sourceRows(database, sourceRegistry, connected).find((row) => row.source === source).accounts
21253
21323
  };
21254
21324
  }
21255
21325
  async function acquireWithStaleRecovery(paths) {
@@ -21397,6 +21467,22 @@ function terminalSafeJson(value) {
21397
21467
  function renderApprovalViewForTerminal(result) {
21398
21468
  return escapeForDisplay(renderB3ApprovalPreviewV1(result).terminal);
21399
21469
  }
21470
+ /**
21471
+ * An optional test the owner refused is reported and the approval goes on to its challenge (CUE-475): declining the
21472
+ * test was always allowed, so a test that could not run must not end the approval for the person.
21473
+ */
21474
+ async function optionalTest(label, test) {
21475
+ try {
21476
+ return await test();
21477
+ } catch (error) {
21478
+ if (!isCommsError(error)) throw error;
21479
+ return {
21480
+ ...label,
21481
+ test: "not run",
21482
+ reason: error.message
21483
+ };
21484
+ }
21485
+ }
21400
21486
  /** Runs the only interactive terminal approval path; the owner, not this client, made every pending-target selector. */
21401
21487
  async function runTerminalApprovalFlow(input) {
21402
21488
  const start = await input.start(input.approvalId);
@@ -21404,13 +21490,14 @@ async function runTerminalApprovalFlow(input) {
21404
21490
  const selector = start.pendingTargetSelectors[index];
21405
21491
  if (selector === void 0) continue;
21406
21492
  const answer = await input.terminal.question(`Test pending target ${index + 1} with the fixed synthetic payload before approving? [y/N] `);
21407
- if (/^\s*(?:y|yes)\s*$/iu.test(answer)) input.print(await input.testPendingTarget(selector));
21493
+ if (/^\s*(?:y|yes)\s*$/iu.test(answer)) input.print(await optionalTest({ pendingTarget: index + 1 }, () => input.testPendingTarget(selector)));
21408
21494
  }
21409
21495
  for (let index = 0; index < start.pendingRuleJudgeTestSelectors.length; index += 1) {
21410
21496
  const selector = start.pendingRuleJudgeTestSelectors[index];
21411
21497
  if (selector === void 0) continue;
21412
21498
  const answer = await input.terminal.question(`Test pending rule judge ${index + 1} with the fixed synthetic payload before approving? [y/N] `);
21413
- if (/^\s*(?:y|yes)\s*$/iu.test(answer) && input.testPendingRuleJudge !== void 0) input.print(await input.testPendingRuleJudge(selector));
21499
+ const testPendingRuleJudge = input.testPendingRuleJudge;
21500
+ if (/^\s*(?:y|yes)\s*$/iu.test(answer) && testPendingRuleJudge !== void 0) input.print(await optionalTest({ pendingRuleJudge: index + 1 }, () => testPendingRuleJudge(selector)));
21414
21501
  }
21415
21502
  const answer = await input.terminal.question(`Type this approval challenge: ${escapeForDisplay(start.challenge)}\n> `);
21416
21503
  input.print(await input.approve(input.approvalId, answer));
@@ -21436,6 +21523,7 @@ async function run(argv, deps = {}) {
21436
21523
  }
21437
21524
  const env = deps.env ?? process.env;
21438
21525
  const program = new Command();
21526
+ runEnvironment.set(program, env);
21439
21527
  let ran = false;
21440
21528
  program.name("agent-events").description("The local event-emission service: one foreground owner and local control clients.").option("--json", "write machine-readable output").option("--no-color", "never colour the output").option("--config-dir <dir>", "pin the configuration directory for this run").option("--state-dir <dir>", "pin the state directory for this run").option("--data-dir <dir>", "pin the data directory for this run").option("--secrets-dir <dir>", "pin the secrets directory for this run").option("--downloads-dir <dir>", "pin the downloads directory for this run").configureOutput({
21441
21529
  writeOut: (text) => streams.stdout.write(escapeForDisplay(text)),
@@ -21868,10 +21956,19 @@ async function run(argv, deps = {}) {
21868
21956
  return 1;
21869
21957
  }
21870
21958
  }
21959
+ /**
21960
+ * The state directory a control client looks in: the one pinned for this run, or the one the environment this CLI was
21961
+ * given resolves to. A caller that passes its own `env` to `run` reaches its own owner, never the one the process
21962
+ * environment names.
21963
+ */
21871
21964
  function controlOptions(program) {
21872
21965
  const { stateDir } = program.opts();
21873
- return stateDir === void 0 ? {} : { stateDir };
21966
+ if (stateDir !== void 0) return { stateDir };
21967
+ const env = runEnvironment.get(program);
21968
+ return env === void 0 ? {} : { stateDir: resolvePaths({ env }).stateDir };
21874
21969
  }
21970
+ /** The environment each program was made with, read by `controlOptions` outside `run`'s closure. */
21971
+ const runEnvironment = /* @__PURE__ */ new WeakMap();
21875
21972
  function parseVersion(value) {
21876
21973
  const version = Number(value);
21877
21974
  if (!Number.isSafeInteger(version) || version < 1) throw new CommsError("USAGE", "rule version must be a positive integer");
package/dist/index.mjs CHANGED
@@ -1,2 +1,2 @@
1
- import { R as createEventsMcpServer, X as status, a as B3_CONTROL_V1_OPERATION_SPECS, c as B3_CONTROL_V1_SUCCESSOR_REQUIREMENT, d as B3_SECRET_TERMINAL_ORIGIN, i as B3_CONTROL_ERROR_CODES, l as B3_DESKTOP_CONTROL_OPERATIONS, m as renderB3ApprovalPreviewV1, n as B3_CAPABILITIES, o as B3_CONTROL_V1_RESULT_OPERATIONS, r as B3_CONTROL_CONTRACT, s as B3_CONTROL_V1_SCHEMA_VERSION, t as B3_APPROVAL_VIEW_VARIANTS, u as B3_JUDGE_UNAVAILABLE_VARIANTS } from "./b3-contract-B6UBBK7F.mjs";
1
+ import { R as createEventsMcpServer, X as status, a as B3_CONTROL_V1_OPERATION_SPECS, c as B3_CONTROL_V1_SUCCESSOR_REQUIREMENT, d as B3_SECRET_TERMINAL_ORIGIN, i as B3_CONTROL_ERROR_CODES, l as B3_DESKTOP_CONTROL_OPERATIONS, m as renderB3ApprovalPreviewV1, n as B3_CAPABILITIES, o as B3_CONTROL_V1_RESULT_OPERATIONS, r as B3_CONTROL_CONTRACT, s as B3_CONTROL_V1_SCHEMA_VERSION, t as B3_APPROVAL_VIEW_VARIANTS, u as B3_JUDGE_UNAVAILABLE_VARIANTS } from "./b3-contract-BUm3TGtz.mjs";
2
2
  export { B3_APPROVAL_VIEW_VARIANTS, B3_CAPABILITIES, B3_CONTROL_CONTRACT, B3_CONTROL_ERROR_CODES, B3_CONTROL_V1_OPERATION_SPECS, B3_CONTROL_V1_RESULT_OPERATIONS, B3_CONTROL_V1_SCHEMA_VERSION, B3_CONTROL_V1_SUCCESSOR_REQUIREMENT, B3_DESKTOP_CONTROL_OPERATIONS, B3_JUDGE_UNAVAILABLE_VARIANTS, B3_SECRET_TERMINAL_ORIGIN, createEventsMcpServer, renderB3ApprovalPreviewV1, status };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentcomms/events-daemon",
3
- "version": "0.18.0",
3
+ "version": "0.18.2",
4
4
  "description": "The local event service for agent-communications: rules you approve deliver Gmail, Slack, Resend and WhatsApp events to a signed webhook, a local live stream or a dry-run log.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -34,12 +34,12 @@
34
34
  "access": "public"
35
35
  },
36
36
  "dependencies": {
37
- "@agentcomms/events": "0.18.0",
38
- "@agentcomms/resend": "0.18.0",
39
- "@agentcomms/gmail": "0.18.0",
40
- "@agentcomms/slack": "0.18.0",
41
- "@agentcomms/core": "0.18.0",
42
- "@agentcomms/whatsapp": "0.18.0"
37
+ "@agentcomms/core": "0.18.2",
38
+ "@agentcomms/events": "0.18.2",
39
+ "@agentcomms/gmail": "0.18.2",
40
+ "@agentcomms/resend": "0.18.2",
41
+ "@agentcomms/slack": "0.18.2",
42
+ "@agentcomms/whatsapp": "0.18.2"
43
43
  },
44
44
  "devDependencies": {
45
45
  "@modelcontextprotocol/client": "2.3.1",
@@ -70,5 +70,5 @@
70
70
  "typecheck": "tsc -p tsconfig.json",
71
71
  "test": "node --import ../../test/helpers/loopback-seal-preload.mjs --experimental-strip-types --disable-warning=ExperimentalWarning --test \"test/**/*.test.ts\""
72
72
  },
73
- "gitHead": "a55c5383b35a8214b9e481e2a148b0e29a663a9f"
73
+ "gitHead": "e053c074e8f90d2b36d4d3b0dd127e073ccd0053"
74
74
  }