@opentag/dispatcher 0.3.3 → 0.3.4

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/dist/index.js CHANGED
@@ -16,7 +16,7 @@ import {
16
16
  } from "@opentag/slack";
17
17
  import { createTelegramSendMessageDraftPayload, createTelegramSendMessagePayload, parseTelegramThreadKey } from "@opentag/telegram";
18
18
  var DEFAULT_SLACK_SOURCE_RECEIPT_TIMEOUT_MS = 5e3;
19
- var DEFAULT_LARK_RECEIVED_REACTION = "OK";
19
+ var DEFAULT_LARK_RECEIVED_REACTION = "Typing";
20
20
  function slackUpdateUriFrom(postMessageUri) {
21
21
  return postMessageUri.replace(/\/chat\.postMessage$/, "/chat.update");
22
22
  }
@@ -920,6 +920,16 @@ function shouldDeliverRunStatusUpdate(presentation, input) {
920
920
  function larkLifecycleStatusMessageKey(input) {
921
921
  return input.provider === "lark" ? `${input.runId}:status` : void 0;
922
922
  }
923
+ function isTerminalRun(run) {
924
+ return ["succeeded", "failed", "cancelled", "interrupted", "timed_out"].includes(run.status);
925
+ }
926
+ function shouldUseDelayedLarkStatusCard(provider, options) {
927
+ return provider === "lark" && options.enabled !== false;
928
+ }
929
+ function safeExecutorLabel(executor) {
930
+ if (!executor || !/^[a-z0-9._-]{1,40}$/i.test(executor)) return "the selected agent";
931
+ return executor;
932
+ }
923
933
  var CreateRunnerSchema = z.object({
924
934
  runnerId: z.string().min(1),
925
935
  name: z.string().min(1)
@@ -1992,7 +2002,7 @@ async function deliverAndAudit(input) {
1992
2002
  ...input.message.blocks ? { blocks: input.message.blocks } : {},
1993
2003
  ...input.message.rich ? { rich: input.message.rich } : {}
1994
2004
  });
1995
- await deliverCallbackDelivery({
2005
+ return deliverCallbackDelivery({
1996
2006
  repo: input.repo,
1997
2007
  sink: input.sink,
1998
2008
  delivery,
@@ -2116,6 +2126,13 @@ function createDispatcherApp(input) {
2116
2126
  const sourceReceiptSink = input.sourceReceiptSink ?? noopSourceReceiptSink;
2117
2127
  const presentation = input.presentation ?? createDefaultCallbackPresentation();
2118
2128
  const callbackRetry = input.callbackRetry ?? {};
2129
+ const larkStatusCardOptions = input.larkStatusCards ?? {};
2130
+ const larkStatusCardDelayMs = larkStatusCardOptions.delayMs ?? 1e4;
2131
+ const larkStatusCardMinUpdateIntervalMs = larkStatusCardOptions.minUpdateIntervalMs ?? 5e3;
2132
+ const larkStatusCardNow = larkStatusCardOptions.now ?? (() => Date.now());
2133
+ const setLarkStatusCardTimeout = larkStatusCardOptions.setTimeout ?? ((callback, delayMs) => globalThis.setTimeout(callback, delayMs));
2134
+ const clearLarkStatusCardTimeout = larkStatusCardOptions.clearTimeout ?? ((handle) => globalThis.clearTimeout(handle));
2135
+ const delayedLarkStatusCards = /* @__PURE__ */ new Map();
2119
2136
  const maxRequestBodyBytes = input.maxRequestBodyBytes ?? DEFAULT_MAX_REQUEST_BODY_BYTES;
2120
2137
  const runnerTokens = configuredRunnerTokens(input);
2121
2138
  const revokedRunnerTokenFingerprints = normalizeRevokedRunnerTokenFingerprints(input.revokedRunnerTokenFingerprints);
@@ -2178,6 +2195,145 @@ function createDispatcherApp(input) {
2178
2195
  message: "Run status callback suppressed by platform liveness strategy; use status or audit for details."
2179
2196
  });
2180
2197
  };
2198
+ function delayedLarkStatusMessage(input2) {
2199
+ if (input2.phase === "queued") return "Waiting for the local runner.";
2200
+ if (input2.phase === "progress") return "OpenTag is still working.";
2201
+ return `Running with ${safeExecutorLabel(input2.run.executor)}.`;
2202
+ }
2203
+ function delayedLarkStatusState(input2) {
2204
+ if (input2.phase === "queued") return "queued";
2205
+ return "running";
2206
+ }
2207
+ async function appendDelayedLarkStatusFailure(input2) {
2208
+ await repo.appendRunEvent({
2209
+ runId: input2.runId,
2210
+ type: "callback.progress.failed",
2211
+ payload: {
2212
+ provider: "lark",
2213
+ reason: "delayed_status_card",
2214
+ error: input2.error instanceof Error ? input2.error.message : String(input2.error)
2215
+ },
2216
+ visibility: "audit",
2217
+ importance: "low",
2218
+ message: "Delayed Lark status card update failed."
2219
+ });
2220
+ }
2221
+ async function deliverDelayedLarkStatusCard(input2) {
2222
+ if (!shouldUseDelayedLarkStatusCard(input2.event.callback.provider, larkStatusCardOptions)) return false;
2223
+ if (!input2.event.callback.threadKey) return false;
2224
+ if (isTerminalRun(input2.run)) return false;
2225
+ const statusMessageKey = larkLifecycleStatusMessageKey({ provider: input2.event.callback.provider, runId: input2.run.id });
2226
+ if (!statusMessageKey) return false;
2227
+ const state = delayedLarkStatusCards.get(input2.run.id) ?? { cardCreated: false };
2228
+ delayedLarkStatusCards.set(input2.run.id, state);
2229
+ if (!input2.createIfMissing && !state.cardCreated) return false;
2230
+ const now = larkStatusCardNow();
2231
+ const phaseChanged = state.lastPhase !== input2.phase;
2232
+ const intervalElapsed = !state.lastUpdateAt || now - state.lastUpdateAt >= larkStatusCardMinUpdateIntervalMs;
2233
+ if (!input2.createIfMissing && !phaseChanged && !intervalElapsed) return false;
2234
+ const statusPresentation = presentation.runStatusPresentation({
2235
+ runId: input2.run.id,
2236
+ state: delayedLarkStatusState({ run: input2.run, phase: input2.phase }),
2237
+ message: delayedLarkStatusMessage({ run: input2.run, phase: input2.phase }),
2238
+ nextAction: "Use /status here for active-run and queue state, or wait for the final result.",
2239
+ detailVisibility: "source_thread"
2240
+ });
2241
+ const rendered = presentation.render({
2242
+ provider: input2.event.callback.provider,
2243
+ presentation: statusPresentation
2244
+ });
2245
+ const delivered = await deliverAndAudit({
2246
+ repo,
2247
+ sink: callbackSink,
2248
+ retry: callbackRetry,
2249
+ message: {
2250
+ runId: input2.run.id,
2251
+ kind: "progress",
2252
+ provider: input2.event.callback.provider,
2253
+ uri: input2.event.callback.uri,
2254
+ body: rendered.body,
2255
+ ...input2.event.target.agentId ? { agentId: input2.event.target.agentId } : {},
2256
+ threadKey: input2.event.callback.threadKey,
2257
+ ...rendered.blocks?.length ? { blocks: rendered.blocks } : {},
2258
+ ...rendered.rich ? { rich: rendered.rich } : {},
2259
+ statusMessageKey
2260
+ }
2261
+ });
2262
+ if (!delivered) return false;
2263
+ state.cardCreated = true;
2264
+ state.lastPhase = input2.phase;
2265
+ state.lastUpdateAt = now;
2266
+ return true;
2267
+ }
2268
+ function scheduleDelayedLarkStatusCard(input2) {
2269
+ if (!shouldUseDelayedLarkStatusCard(input2.event.callback.provider, larkStatusCardOptions)) return;
2270
+ if (!input2.event.callback.threadKey) return;
2271
+ if (larkStatusCardDelayMs < 0) return;
2272
+ const existing = delayedLarkStatusCards.get(input2.run.id);
2273
+ if (existing?.timer || existing?.cardCreated) return;
2274
+ const state = existing ?? { cardCreated: false };
2275
+ const timer = setLarkStatusCardTimeout(() => {
2276
+ delete state.timer;
2277
+ void (async () => {
2278
+ try {
2279
+ const latestRun = await repo.getRun({ runId: input2.run.id });
2280
+ if (!latestRun || isTerminalRun(latestRun.run)) {
2281
+ delayedLarkStatusCards.delete(input2.run.id);
2282
+ return;
2283
+ }
2284
+ const phase = latestRun.run.status === "queued" ? "queued" : "running";
2285
+ await deliverDelayedLarkStatusCard({
2286
+ run: latestRun.run,
2287
+ event: input2.event,
2288
+ phase,
2289
+ createIfMissing: true
2290
+ });
2291
+ } catch (error) {
2292
+ await appendDelayedLarkStatusFailure({ runId: input2.run.id, error });
2293
+ }
2294
+ })();
2295
+ }, larkStatusCardDelayMs);
2296
+ if (timer && typeof timer === "object" && "unref" in timer && typeof timer.unref === "function") {
2297
+ timer.unref();
2298
+ }
2299
+ state.timer = timer;
2300
+ delayedLarkStatusCards.set(input2.run.id, state);
2301
+ }
2302
+ function cancelPendingDelayedLarkStatusCard(runId) {
2303
+ const state = delayedLarkStatusCards.get(runId);
2304
+ if (!state?.timer) return;
2305
+ clearLarkStatusCardTimeout(state.timer);
2306
+ delete state.timer;
2307
+ }
2308
+ function clearDelayedLarkStatusCard(runId) {
2309
+ cancelPendingDelayedLarkStatusCard(runId);
2310
+ delayedLarkStatusCards.delete(runId);
2311
+ }
2312
+ async function patchDelayedLarkStatusCard(input2) {
2313
+ let state = delayedLarkStatusCards.get(input2.run.id);
2314
+ if (!state?.cardCreated) {
2315
+ const statusMessageKey = larkLifecycleStatusMessageKey({ provider: input2.event.callback.provider, runId: input2.run.id });
2316
+ const externalMessageId = statusMessageKey && input2.event.callback.threadKey ? await repo.findCallbackExternalMessageId({
2317
+ runId: input2.run.id,
2318
+ provider: input2.event.callback.provider,
2319
+ threadKey: input2.event.callback.threadKey,
2320
+ statusMessageKey
2321
+ }) : void 0;
2322
+ if (!externalMessageId) return;
2323
+ state = state ?? { cardCreated: true };
2324
+ state.cardCreated = true;
2325
+ delayedLarkStatusCards.set(input2.run.id, state);
2326
+ }
2327
+ try {
2328
+ await deliverDelayedLarkStatusCard({
2329
+ run: input2.run,
2330
+ event: input2.event,
2331
+ phase: input2.phase
2332
+ });
2333
+ } catch (error) {
2334
+ await appendDelayedLarkStatusFailure({ runId: input2.run.id, error });
2335
+ }
2336
+ }
2181
2337
  async function deliverPromotedFollowUpAcknowledgement(input2) {
2182
2338
  if (!presentation.shouldDeliverAcknowledgement(input2.event.callback.provider)) return;
2183
2339
  const acknowledgementPresentation = presentation.acknowledgementPresentation({ runId: input2.run.id });
@@ -2691,6 +2847,9 @@ function createDispatcherApp(input) {
2691
2847
  ...parsed.event.target.agentId ? { agentId: parsed.event.target.agentId } : {}
2692
2848
  }
2693
2849
  });
2850
+ if (sourceReceiptDelivery.delivered) {
2851
+ scheduleDelayedLarkStatusCard({ run, event: parsed.event });
2852
+ }
2694
2853
  const shouldDeliverAcknowledgement = presentation.shouldDeliverAcknowledgement(parsed.event.callback.provider) || shouldDeliverSourceReceipt(parsed.event.callback.provider) && !sourceReceiptDelivery.delivered;
2695
2854
  if (shouldDeliverAcknowledgement) {
2696
2855
  const acknowledgementPresentation = presentation.acknowledgementPresentation({ runId: run.id });
@@ -3169,6 +3328,11 @@ function createDispatcherApp(input) {
3169
3328
  } else if (presentation.shouldDeliverStatusUpdate(provider)) {
3170
3329
  await appendSuppressedRunStatusCallback({ runId, provider, state: "running" });
3171
3330
  }
3331
+ await patchDelayedLarkStatusCard({
3332
+ run: stored.run,
3333
+ event: stored.event,
3334
+ phase: "running"
3335
+ });
3172
3336
  return c.json({ ok: true });
3173
3337
  });
3174
3338
  app.post("/v1/runs/:runId/progress", async () => {
@@ -3237,6 +3401,11 @@ function createDispatcherApp(input) {
3237
3401
  message: "Progress callback suppressed by platform liveness strategy; use status or audit for details."
3238
3402
  });
3239
3403
  }
3404
+ await patchDelayedLarkStatusCard({
3405
+ run: stored.run,
3406
+ event: stored.event,
3407
+ phase: "progress"
3408
+ });
3240
3409
  return c.json({ ok: true });
3241
3410
  });
3242
3411
  app.post("/v1/runs/:runId/complete", async () => {
@@ -3260,6 +3429,7 @@ function createDispatcherApp(input) {
3260
3429
  if (outcome === "duplicate") return c.json({ ok: true, replayed: true });
3261
3430
  const stored = await repo.getRun({ runId });
3262
3431
  if (!stored) return c.json({ error: "run_not_found" }, 404);
3432
+ cancelPendingDelayedLarkStatusCard(runId);
3263
3433
  const receiptContext = await actionReceiptContextForFinal({
3264
3434
  event: stored.event,
3265
3435
  result: parsed.result,
@@ -3322,6 +3492,7 @@ function createDispatcherApp(input) {
3322
3492
  ...finalCallback.rich ? { rich: finalCallback.rich } : {}
3323
3493
  }
3324
3494
  });
3495
+ clearDelayedLarkStatusCard(runId);
3325
3496
  const shouldPromoteFollowUp = parsed.result.conclusion !== "needs_human" && parsed.result.conclusion !== "cancelled";
3326
3497
  const promotedFollowUp = shouldPromoteFollowUp ? await promoteNextFollowUpAfterTerminalRun({ activeRunId: runId }) : null;
3327
3498
  return c.json({