@botiverse/raft-sdk 0.3.2 → 0.4.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.
package/dist/esm/index.js CHANGED
@@ -4477,12 +4477,6 @@ const destructive = {
4477
4477
  destructive: true,
4478
4478
  audience: "both"
4479
4479
  };
4480
- const managedNaturalWrite = {
4481
- sideEffect: "write",
4482
- idempotency: "natural",
4483
- destructive: false,
4484
- audience: "managed"
4485
- };
4486
4480
  const managedWrite = {
4487
4481
  sideEffect: "write",
4488
4482
  idempotency: "none",
@@ -4543,15 +4537,6 @@ const AGENT_API_ROUTE_META = {
4543
4537
  taskConvert: write,
4544
4538
  taskAmend: destructive,
4545
4539
  taskHistory: read,
4546
- migrationBegin: {
4547
- sideEffect: "write",
4548
- idempotency: "none",
4549
- destructive: true,
4550
- audience: "managed"
4551
- },
4552
- migrationStatus: read,
4553
- migrationReady: managedNaturalWrite,
4554
- migrationArrived: managedNaturalWrite,
4555
4540
  reminderList: read,
4556
4541
  reminderCreate: managedWrite,
4557
4542
  reminderCancel: naturalDestructive,
@@ -5005,7 +4990,14 @@ const agentApiHistoryQuerySchema = passthroughObject({
5005
4990
  before: optionalStringSchema,
5006
4991
  after: optionalStringSchema,
5007
4992
  around: optionalStringSchema,
5008
- limit: optionalStringSchema
4993
+ limit: optionalStringSchema,
4994
+ /**
4995
+ * `"false"` reads without consuming: the Server returns the page but does
4996
+ * not advance the agent's read cursor, and reports no
4997
+ * `model_seen_up_to_seq`. For reads whose output may never reach the model
4998
+ * (code a hosted agent wrote). Absent or `"true"` keeps the default.
4999
+ */
5000
+ consume: _enum(["true", "false"]).optional()
5009
5001
  });
5010
5002
  const agentApiKnowledgeGetQuerySchema = passthroughObject({
5011
5003
  topic: string().trim().min(1),
@@ -6131,61 +6123,6 @@ const agentApiAppConfigResponseSchema = passthroughObject({
6131
6123
  overrides: record(string(), agentApiAppConfigValueSchema),
6132
6124
  effective: record(string(), agentApiAppConfigValueSchema)
6133
6125
  });
6134
- const agentApiMigrationStateSchema = _enum([
6135
- "provisioning",
6136
- "prep",
6137
- "ready",
6138
- "in_transit",
6139
- "arriving",
6140
- "starting",
6141
- "cancel_requested_pre_flip",
6142
- "cancel_requested_post_flip",
6143
- "canceled_pre_flip",
6144
- "canceled_post_flip",
6145
- "completed",
6146
- "aborted",
6147
- "failed"
6148
- ]);
6149
- const agentApiMigrationSummarySchema = passthroughObject({
6150
- id: string(),
6151
- agentId: string(),
6152
- sourceMachineId: string(),
6153
- targetMachineId: string(),
6154
- state: agentApiMigrationStateSchema,
6155
- manifestPath: nullableStringSchema,
6156
- manifestSha256: nullableStringSchema,
6157
- arrivalReportPath: nullableStringSchema,
6158
- arrivalReportSha256: nullableStringSchema,
6159
- abortReason: nullableStringSchema,
6160
- failureReason: nullableStringSchema,
6161
- prepDeadlineAt: string().datetime(),
6162
- transferDeadlineAt: string().datetime(),
6163
- arrivalDeadlineAt: string().datetime(),
6164
- readyAt: nullableStringSchema,
6165
- flippedAt: nullableStringSchema,
6166
- arrivedAt: nullableStringSchema,
6167
- completedAt: nullableStringSchema,
6168
- abortedAt: nullableStringSchema,
6169
- revision: number$1().int().positive(),
6170
- createdAt: string().datetime(),
6171
- updatedAt: string().datetime()
6172
- });
6173
- const agentApiMigrationBeginBodySchema = passthroughObject({
6174
- targetMachineId: string().trim().min(1),
6175
- prepDeadlineMs: number$1().int().positive().optional(),
6176
- transferDeadlineMs: number$1().int().positive().optional(),
6177
- arrivalDeadlineMs: number$1().int().positive().optional()
6178
- });
6179
- const agentApiMigrationReadyBodySchema = passthroughObject({
6180
- manifestPath: string().trim().min(1),
6181
- manifestSha256: optionalStringSchema
6182
- });
6183
- const agentApiMigrationArrivedBodySchema = passthroughObject({
6184
- reportPath: optionalStringSchema,
6185
- reportSha256: optionalStringSchema
6186
- });
6187
- const agentApiMigrationResponseSchema = passthroughObject({ migration: agentApiMigrationSummarySchema });
6188
- const agentApiMigrationStatusResponseSchema = passthroughObject({ migration: agentApiMigrationSummarySchema.nullable() });
6189
6126
  const agentApiPushWebhookRegisterBodySchema = object({
6190
6127
  /** Public HTTPS endpoint that receives `raft-agent-inbox-notice.v1` POSTs. */
6191
6128
  url: string().trim().min(1),
@@ -6787,58 +6724,6 @@ const agentApiContract = {
6787
6724
  request: { query: agentApiTaskHistoryQuerySchema },
6788
6725
  response: { body: agentApiTaskHistoryResponseSchema }
6789
6726
  }),
6790
- migrationBegin: route({
6791
- key: "migrationBegin",
6792
- method: "POST",
6793
- path: "/migrations",
6794
- client: {
6795
- resource: "migrations",
6796
- method: "begin"
6797
- },
6798
- capability: "server",
6799
- description: "Begin a migration for the bound agent credential.",
6800
- request: { body: agentApiMigrationBeginBodySchema },
6801
- response: { body: agentApiMigrationResponseSchema }
6802
- }),
6803
- migrationStatus: route({
6804
- key: "migrationStatus",
6805
- method: "GET",
6806
- path: "/migrations/current",
6807
- client: {
6808
- resource: "migrations",
6809
- method: "status"
6810
- },
6811
- capability: "read",
6812
- description: "Read the active migration for the bound agent credential, if any.",
6813
- request: {},
6814
- response: { body: agentApiMigrationStatusResponseSchema }
6815
- }),
6816
- migrationReady: route({
6817
- key: "migrationReady",
6818
- method: "POST",
6819
- path: "/migrations/ready",
6820
- client: {
6821
- resource: "migrations",
6822
- method: "ready"
6823
- },
6824
- capability: "read",
6825
- description: "Mark the bound agent's active migration prep phase ready.",
6826
- request: { body: agentApiMigrationReadyBodySchema },
6827
- response: { body: agentApiMigrationResponseSchema }
6828
- }),
6829
- migrationArrived: route({
6830
- key: "migrationArrived",
6831
- method: "POST",
6832
- path: "/migrations/arrived",
6833
- client: {
6834
- resource: "migrations",
6835
- method: "arrived"
6836
- },
6837
- capability: "read",
6838
- description: "Mark the bound agent's active migration arrival phase complete.",
6839
- request: { body: agentApiMigrationArrivedBodySchema },
6840
- response: { body: agentApiMigrationResponseSchema }
6841
- }),
6842
6727
  reminderList: route({
6843
6728
  key: "reminderList",
6844
6729
  method: "GET",
@@ -8871,106 +8756,6 @@ const AGENT_API_ROUTE_MANIFEST = [
8871
8756
  "body": true
8872
8757
  }
8873
8758
  },
8874
- {
8875
- "key": "migrationBegin",
8876
- "method": "POST",
8877
- "path": "/migrations",
8878
- "fullPath": "/internal/agent-api/migrations",
8879
- "client": {
8880
- "resource": "migrations",
8881
- "method": "begin"
8882
- },
8883
- "capability": "server",
8884
- "description": "Begin a migration for the bound agent credential.",
8885
- "sideEffect": "write",
8886
- "idempotency": "none",
8887
- "destructive": true,
8888
- "audience": "managed",
8889
- "request": {
8890
- "params": false,
8891
- "query": false,
8892
- "body": true
8893
- },
8894
- "response": {
8895
- "kind": "json",
8896
- "body": true
8897
- }
8898
- },
8899
- {
8900
- "key": "migrationStatus",
8901
- "method": "GET",
8902
- "path": "/migrations/current",
8903
- "fullPath": "/internal/agent-api/migrations/current",
8904
- "client": {
8905
- "resource": "migrations",
8906
- "method": "status"
8907
- },
8908
- "capability": "read",
8909
- "description": "Read the active migration for the bound agent credential, if any.",
8910
- "sideEffect": "read",
8911
- "idempotency": "natural",
8912
- "destructive": false,
8913
- "audience": "both",
8914
- "request": {
8915
- "params": false,
8916
- "query": false,
8917
- "body": false
8918
- },
8919
- "response": {
8920
- "kind": "json",
8921
- "body": true
8922
- }
8923
- },
8924
- {
8925
- "key": "migrationReady",
8926
- "method": "POST",
8927
- "path": "/migrations/ready",
8928
- "fullPath": "/internal/agent-api/migrations/ready",
8929
- "client": {
8930
- "resource": "migrations",
8931
- "method": "ready"
8932
- },
8933
- "capability": "read",
8934
- "description": "Mark the bound agent's active migration prep phase ready.",
8935
- "sideEffect": "write",
8936
- "idempotency": "natural",
8937
- "destructive": false,
8938
- "audience": "managed",
8939
- "request": {
8940
- "params": false,
8941
- "query": false,
8942
- "body": true
8943
- },
8944
- "response": {
8945
- "kind": "json",
8946
- "body": true
8947
- }
8948
- },
8949
- {
8950
- "key": "migrationArrived",
8951
- "method": "POST",
8952
- "path": "/migrations/arrived",
8953
- "fullPath": "/internal/agent-api/migrations/arrived",
8954
- "client": {
8955
- "resource": "migrations",
8956
- "method": "arrived"
8957
- },
8958
- "capability": "read",
8959
- "description": "Mark the bound agent's active migration arrival phase complete.",
8960
- "sideEffect": "write",
8961
- "idempotency": "natural",
8962
- "destructive": false,
8963
- "audience": "managed",
8964
- "request": {
8965
- "params": false,
8966
- "query": false,
8967
- "body": true
8968
- },
8969
- "response": {
8970
- "kind": "json",
8971
- "body": true
8972
- }
8973
- },
8974
8759
  {
8975
8760
  "key": "reminderList",
8976
8761
  "method": "GET",
@@ -9873,7 +9658,7 @@ const AGENT_API_ROUTE_MANIFEST = [
9873
9658
  }
9874
9659
  ];
9875
9660
  /** Content hash of AGENT_API_ROUTE_MANIFEST; see computeAgentApiManifestVersion. */
9876
- const AGENT_API_MANIFEST_VERSION = "337bf2c7ff90b908";
9661
+ const AGENT_API_MANIFEST_VERSION = "c7a793816a7a90ed";
9877
9662
  //#endregion
9878
9663
  //#region src/routes.ts
9879
9664
  const ROUTE_INFO = Object.fromEntries(AGENT_API_ROUTE_MANIFEST.map((entry) => {
@@ -10935,6 +10720,20 @@ function formatUtcTimestamp(value) {
10935
10720
  //#endregion
10936
10721
  //#region ../shared/src/agentMessageText.ts
10937
10722
  /**
10723
+ * Prefix every continuation line of a quoted body with ` │ `, so body text
10724
+ * never reaches column 0. Structural lines (`[target=…]` / `[1/5 seq=…]` headers,
10725
+ * read cursors, footers) always start at column 0, so a line-anchored reader
10726
+ * (`grep '^\['`) cannot be handed a forged one by message content. The `│` is
10727
+ * non-whitespace on purpose: a reader that trims each line still sees it. It is
10728
+ * the same quote marker as the freshness-hold digest previews. Remove the first
10729
+ * four characters after each line separator to recover the original body.
10730
+ */
10731
+ const AGENT_BODY_CONTINUATION_PREFIX = " │ ";
10732
+ const AGENT_BODY_LINE_SEPARATOR_GLOBAL = new RegExp(/\r\n|[\n\r\v\f\x1c\x1d\x1e\x85\u2028\u2029]/.source, "g");
10733
+ function indentAgentBodyContinuationLines(text) {
10734
+ return text.replace(AGENT_BODY_LINE_SEPARATOR_GLOBAL, (separator) => `${separator}${AGENT_BODY_CONTINUATION_PREFIX}`);
10735
+ }
10736
+ /**
10938
10737
  * The target an agent passes back to reply where a message came from:
10939
10738
  * `#channel`, `#channel:shortid`, `dm:@peer`, `dm:@peer:shortid`, or
10940
10739
  * `agent-event:shortid` for third-party app events.
@@ -10977,8 +10776,8 @@ function formatAgentTaskCurrentProjection(projection, taskNumber, neutralize) {
10977
10776
  const actorType = projection.amendedByType ?? projection.amended_by_type ?? null;
10978
10777
  const actor = actorName ? neutralizeRefs ? `user:${actorName}` : `@${actorName}` : actorType === "system" ? "system" : "<unresolved>";
10979
10778
  const amendedAt = projection.amendedAt ?? projection.amended_at ?? null;
10980
- const lines = [`[${taskNumber ? `task #${taskNumber} ` : "task "}superseded: current projection rev=${revision} source=${source} actor=${actor} time=${amendedAt ? formatUtcTimestamp(amendedAt) : "-"}]`, `Current title: ${neutralizeRefs ? renderPreviewText(projection.title ?? "") : projection.title ?? ""}`];
10981
- if (projection.description != null) lines.push(`Current description: ${neutralizeRefs ? renderPreviewText(projection.description) : projection.description}`);
10779
+ const lines = [`[${taskNumber ? `task #${taskNumber} ` : "task "}superseded: current projection rev=${revision} source=${source} actor=${actor} time=${amendedAt ? formatUtcTimestamp(amendedAt) : "-"}]`, `Current title: ${indentAgentBodyContinuationLines(neutralizeRefs ? renderPreviewText(projection.title ?? "") : projection.title ?? "")}`];
10780
+ if (projection.description != null) lines.push(`Current description: ${indentAgentBodyContinuationLines(neutralizeRefs ? renderPreviewText(projection.description) : projection.description)}`);
10982
10781
  return `\n${lines.join("\n")}`;
10983
10782
  }
10984
10783
  /** One received-message line: header bracket + sender + content + suffixes. */
@@ -10997,7 +10796,7 @@ function formatAgentMessageLine(m) {
10997
10796
  const msgId = m.message_id ? m.message_id.slice(0, 8) : "-";
10998
10797
  const time = m.timestamp ? formatUtcTimestamp(m.timestamp) : "-";
10999
10798
  const senderType = ` type=${m.sender_type}`;
11000
- const content = m.content ?? "";
10799
+ const content = indentAgentBodyContinuationLines(m.content ?? "");
11001
10800
  const attachSuffix = formatAgentAttachmentSuffix(m.attachments);
11002
10801
  const taskSuffix = m.task_status ? ` [task #${m.task_number} status=${m.task_status}${formatAgentTaskAssigneeSuffix(m.task_assignee_id, m.task_assignee_name)}]` : "";
11003
10802
  return `[target=${target} msg=${msgId} time=${time}${senderType}] ${formatAgentSenderHandle(m)}: ${content}${attachSuffix}${taskSuffix}${formatAgentReplyAffordanceSuffix(m)}${formatAgentTaskCurrentProjection(m.task_current_projection)}`;
@@ -11139,6 +10938,89 @@ function projectRaftMessagesInTarget(envelopes, target) {
11139
10938
  } : envelope)).filter((m) => m !== null);
11140
10939
  }
11141
10940
  //#endregion
10941
+ //#region ../shared/src/agentOps/interrupt.ts
10942
+ /** Narrow any outcome (SDK or command registry) to an interrupt, without knowing the command. */
10943
+ function isInterrupted(outcome) {
10944
+ return Boolean(outcome) && typeof outcome === "object" && outcome.state === "interrupted" && typeof outcome.interrupt === "object";
10945
+ }
10946
+ /** Build the `unread_messages` interrupt from a Server freshness hold. */
10947
+ function unreadMessagesInterrupt(input) {
10948
+ const { hold, target } = input;
10949
+ const withheld = input.withheld === true || hold.freshnessContextMode === "withheld";
10950
+ const heldMessages = withheld ? [] : projectRaftMessagesInTarget(hold.heldMessages ?? [], target);
10951
+ const newMessageCount = withheld ? hold.withheldMessageCount ?? hold.newMessageCount ?? 0 : hold.newMessageCount ?? heldMessages.length;
10952
+ const omittedMessageCount = withheld ? 0 : hold.omittedMessageCount ?? 0;
10953
+ return {
10954
+ reason: "unread_messages",
10955
+ context: input.context,
10956
+ resume: input.resume,
10957
+ ...input.cancel ? { cancel: input.cancel } : {},
10958
+ target,
10959
+ newMessageCount,
10960
+ heldMessages,
10961
+ omittedMessageCount,
10962
+ formalMentionCount: withheld ? 0 : hold.mentionAnnotation?.formalMentionCount ?? 0,
10963
+ seenUpToSeq: typeof hold.seenUpToSeq === "number" ? hold.seenUpToSeq : null,
10964
+ withheld,
10965
+ contextComplete: !withheld && heldMessages.length + omittedMessageCount >= newMessageCount
10966
+ };
10967
+ }
10968
+ /** `raft message send --send-draft` for the draft a held send saved, under its original key. */
10969
+ function sendDraftResume(target, idempotencyKey, extraArgv = []) {
10970
+ return {
10971
+ argv: [
10972
+ "message",
10973
+ "send",
10974
+ "--send-draft",
10975
+ "--target",
10976
+ target,
10977
+ "--expected-draft-key",
10978
+ idempotencyKey,
10979
+ ...extraArgv
10980
+ ],
10981
+ idempotencyKey
10982
+ };
10983
+ }
10984
+ /** `raft message send --discard-draft` for the draft a held send saved (compare-and-clear on its key). */
10985
+ function discardDraftCancel(target, idempotencyKey, extraArgv = []) {
10986
+ return { argv: [
10987
+ "message",
10988
+ "send",
10989
+ "--discard-draft",
10990
+ "--target",
10991
+ target,
10992
+ "--expected-draft-key",
10993
+ idempotencyKey,
10994
+ ...extraArgv
10995
+ ] };
10996
+ }
10997
+ /** `raft task claim` argv for a claim request (resume of a held claim). */
10998
+ function taskClaimArgv(target, taskNumbers, messageIds, extraArgv = []) {
10999
+ return [
11000
+ "task",
11001
+ "claim",
11002
+ "--target",
11003
+ target,
11004
+ ...taskNumbers.flatMap((n) => ["--number", String(n)]),
11005
+ ...messageIds.flatMap((id) => ["--message-id", id]),
11006
+ ...extraArgv
11007
+ ];
11008
+ }
11009
+ /** `raft task update` argv (resume of a held status update). */
11010
+ function taskUpdateArgv(target, taskNumber, status, extraArgv = []) {
11011
+ return [
11012
+ "task",
11013
+ "update",
11014
+ "--target",
11015
+ target,
11016
+ "--number",
11017
+ String(taskNumber),
11018
+ "--status",
11019
+ status,
11020
+ ...extraArgv
11021
+ ];
11022
+ }
11023
+ //#endregion
11142
11024
  //#region ../shared/src/agentOps/frontier.ts
11143
11025
  const MAX_EXACT_SEQS = 2500;
11144
11026
  function positiveInt(value) {
@@ -11190,11 +11072,12 @@ var SeenFrontier = class SeenFrontier {
11190
11072
  }
11191
11073
  }
11192
11074
  /**
11193
- * Attest that the model saw the context a held send/claim returned. Call it
11194
- * only after the held messages actually reached the model (the SDK cannot
11195
- * know that, so it never records this implicitly). Returns false and records
11196
- * nothing when the context was withheld, not fully shown
11197
- * (`contextComplete: false`), or the Server sent no boundary.
11075
+ * Attest that the model saw the context of an interrupt (`outcome.interrupt`
11076
+ * of an `interrupted` send, claim or task write). Call it only after
11077
+ * `interrupt.context` actually reached the model (the SDK cannot know that,
11078
+ * so it never records this implicitly). Returns false and records nothing
11079
+ * when the context was withheld, not fully shown (`contextComplete: false`),
11080
+ * or the Server sent no boundary.
11198
11081
  */
11199
11082
  recordHeld(held) {
11200
11083
  if (held.withheld || held.contextComplete === false || held.seenUpToSeq === null) return false;
@@ -11563,7 +11446,8 @@ async function readHistory(client, request, frontier) {
11563
11446
  ...request.after === void 0 ? {} : { after: String(request.after) },
11564
11447
  ...request.before === void 0 ? {} : { before: String(request.before) },
11565
11448
  ...request.around === void 0 ? {} : { around: String(request.around) },
11566
- ...request.limit === void 0 ? {} : { limit: String(request.limit) }
11449
+ ...request.limit === void 0 ? {} : { limit: String(request.limit) },
11450
+ ...request.consume === false ? { consume: "false" } : {}
11567
11451
  });
11568
11452
  if (!result.ok) return failureFromClientResult(result);
11569
11453
  const data = result.data;
@@ -11575,7 +11459,7 @@ async function readHistory(client, request, frontier) {
11575
11459
  lastReadSeq: typeof data.last_read_seq === "number" ? data.last_read_seq : null,
11576
11460
  modelSeenUpToSeq: typeof data.model_seen_up_to_seq === "number" ? data.model_seen_up_to_seq : null
11577
11461
  };
11578
- recordHistorySeen(frontier, request.target, page, request.around !== void 0);
11462
+ if (request.consume !== false) recordHistorySeen(frontier, request.target, page, request.around !== void 0);
11579
11463
  return {
11580
11464
  ok: true,
11581
11465
  state: page.messages.length > 0 ? "page" : "empty",
@@ -11586,6 +11470,7 @@ async function readHistory(client, request, frontier) {
11586
11470
  }
11587
11471
  /** Attempts for a keyed send whose request never reached the Server (transport failure only). */
11588
11472
  const SEND_TRANSPORT_ATTEMPTS = 3;
11473
+ /** Today's held text (unchanged); it is the interrupt's `context` and the outcome's `text`. */
11589
11474
  function heldText(held, action) {
11590
11475
  const noun = held.newMessageCount === 1 ? "message" : "messages";
11591
11476
  const head = `Held — ${held.newMessageCount} unread ${noun} in ${held.target}. ${action}`;
@@ -11598,38 +11483,29 @@ function heldText(held, action) {
11598
11483
  lines.push(`Full text: raft message read --target "${held.target}"`);
11599
11484
  return lines.join("\n");
11600
11485
  }
11601
- function heldNext(held) {
11486
+ /**
11487
+ * A Server freshness hold as the `unread_messages` interrupt: the structured
11488
+ * details, today's held text as `context`, and the given resume / cancel.
11489
+ */
11490
+ function heldInterrupt(target, data, action, resume, cancel) {
11491
+ const interrupt = unreadMessagesInterrupt({
11492
+ target,
11493
+ hold: data,
11494
+ context: "",
11495
+ resume,
11496
+ ...cancel ? { cancel } : {}
11497
+ });
11602
11498
  return {
11603
- kind: "resend",
11604
- command: `raft message read --target "${held.target}"`,
11605
- args: {
11606
- target: held.target,
11607
- ...held.continuation
11608
- },
11609
- why: held.withheld ? "Newer messages exist in this conversation but were withheld; read them, then send again with the same idempotencyKey." : !held.contextComplete ? "Newer messages arrived in this conversation and not all of them are shown here; read the conversation, then send again with the same idempotencyKey." : "Newer messages arrived in this conversation. Show the held messages to the model, call frontier.recordHeld(held) to attest that, then send again with these args (same idempotencyKey, seen.upToSeq)."
11499
+ ...interrupt,
11500
+ context: heldText(interrupt, action)
11610
11501
  };
11611
11502
  }
11612
- function projectHeld(target, data, idempotencyKey) {
11613
- const withheld = data.freshnessContextMode === "withheld";
11614
- const heldMessages = projectRaftMessagesInTarget(data.heldMessages ?? [], target);
11615
- const seenUpToSeq = typeof data.seenUpToSeq === "number" ? data.seenUpToSeq : null;
11616
- const newMessageCount = withheld ? data.withheldMessageCount ?? 0 : data.newMessageCount ?? heldMessages.length;
11617
- const omittedMessageCount = data.omittedMessageCount ?? 0;
11618
- const contextComplete = !withheld && heldMessages.length + omittedMessageCount >= newMessageCount;
11503
+ function heldSendNext(interrupt) {
11619
11504
  return {
11620
- target,
11621
- idempotencyKey,
11622
- continuation: {
11623
- idempotencyKey,
11624
- ...contextComplete && seenUpToSeq !== null ? { seen: { upToSeq: seenUpToSeq } } : {}
11625
- },
11626
- newMessageCount,
11627
- heldMessages,
11628
- omittedMessageCount,
11629
- formalMentionCount: data.mentionAnnotation?.formalMentionCount ?? 0,
11630
- seenUpToSeq,
11631
- withheld,
11632
- contextComplete
11505
+ kind: "resend",
11506
+ command: `raft message read --target "${interrupt.target}"`,
11507
+ args: { target: interrupt.target },
11508
+ why: interrupt.withheld ? "Newer messages exist in this conversation but were withheld; read them, then resume the send (interrupt.resume, same idempotencyKey) or cancel it." : !interrupt.contextComplete ? "Newer messages arrived in this conversation and not all of them are shown here; read the conversation, then resume the send (interrupt.resume, same idempotencyKey) or cancel it." : "Newer messages arrived in this conversation. Show interrupt.context to the model, call frontier.recordHeld(interrupt) to attest that, then resume the send (interrupt.resume, same idempotencyKey) or cancel it."
11633
11509
  };
11634
11510
  }
11635
11511
  function isHeldResponse(data) {
@@ -11656,7 +11532,7 @@ function sentOutcome(target, data) {
11656
11532
  text: `Message sent to ${target}. Message ID: ${sent.messageId}${warn}`
11657
11533
  };
11658
11534
  }
11659
- async function sendMessage(client, request, frontier, internal = {}) {
11535
+ async function sendMessage(client, request, frontier) {
11660
11536
  if (!request.target?.trim()) return failureOutcome(opError("INVALID_REQUEST", { message: "A target is required to send a message." }));
11661
11537
  if (typeof request.content !== "string" || !request.content.trim() && !request.attachmentIds?.length) return failureOutcome(opError("INVALID_REQUEST", { message: "Message content (or an attachment) is required." }));
11662
11538
  const idempotencyKey = request.idempotencyKey?.trim() || crypto.randomUUID();
@@ -11671,42 +11547,20 @@ async function sendMessage(client, request, frontier, internal = {}) {
11671
11547
  idempotencyKey,
11672
11548
  ...request.mentions?.length ? { mentions: request.mentions } : {},
11673
11549
  ...attestation.seenUpToSeq === void 0 ? {} : { seenUpToSeq: attestation.seenUpToSeq },
11674
- ...attestation.seenExactSeqs.length > 0 ? { seenExactSeqs: attestation.seenExactSeqs.slice(0, 2500) } : {},
11675
- ...internal.sendDraft ? { sendDraft: true } : {},
11676
- ...internal.continueAnyway ? { continueAnyway: true } : {},
11677
- ...internal.reholdCount ? { draftReholdCount: internal.reholdCount } : {}
11550
+ ...attestation.seenExactSeqs.length > 0 ? { seenExactSeqs: attestation.seenExactSeqs.slice(0, 2500) } : {}
11678
11551
  };
11679
11552
  let result = await client.messages.sendV2(body);
11680
11553
  for (let attempt = 1; !result.ok && result.error.kind === "transport" && attempt < SEND_TRANSPORT_ATTEMPTS; attempt += 1) result = await client.messages.sendV2(body);
11681
11554
  if (!result.ok) return failureFromClientResult(result);
11682
11555
  const data = result.data;
11683
11556
  if (isHeldResponse(data)) {
11684
- const reholdCount = (internal.reholdCount ?? 0) + 1;
11685
- const keyed = {
11686
- ...request,
11687
- idempotencyKey
11688
- };
11689
- const base = projectHeld(request.target, data, idempotencyKey);
11690
- const held = {
11691
- ...base,
11692
- resend: (options) => sendMessage(client, options.seen === "held" && base.contextComplete && typeof data.seenUpToSeq === "number" ? {
11693
- ...keyed,
11694
- seen: {
11695
- upToSeq: data.seenUpToSeq,
11696
- exactSeqs: attestation.seenExactSeqs
11697
- }
11698
- } : keyed, frontier, {
11699
- sendDraft: true,
11700
- reholdCount,
11701
- ...options.seen === "anyway" ? { continueAnyway: true } : {}
11702
- })
11703
- };
11557
+ const interrupt = heldInterrupt(request.target, data, "Your message was not sent.", sendDraftResume(request.target, idempotencyKey), discardDraftCancel(request.target, idempotencyKey));
11704
11558
  return {
11705
11559
  ok: true,
11706
- state: "held",
11707
- data: held,
11708
- next: heldNext(held),
11709
- text: heldText(held, "Your message was not sent.")
11560
+ state: "interrupted",
11561
+ interrupt,
11562
+ next: heldSendNext(interrupt),
11563
+ text: interrupt.context
11710
11564
  };
11711
11565
  }
11712
11566
  if (data.state === "sent") return sentOutcome(request.target, data);
@@ -11883,6 +11737,24 @@ function formatAgentTaskHistory(data) {
11883
11737
  //#endregion
11884
11738
  //#region ../shared/src/agentOps/tasks.ts
11885
11739
  const TASK_CLAIM_REASON_ALREADY_CLAIMED_BY_YOU = "already claimed by you";
11740
+ /**
11741
+ * A held task call: the interrupt whose resume is the identical command, with
11742
+ * no cancel (a held task call saved nothing). `context` is today's held text.
11743
+ */
11744
+ function heldTaskInterrupt(target, data, action, after, argv) {
11745
+ const interrupt = unreadMessagesInterrupt({
11746
+ target,
11747
+ hold: data,
11748
+ context: "",
11749
+ resume: { argv }
11750
+ });
11751
+ const noun = interrupt.newMessageCount === 1 ? "message" : "messages";
11752
+ const context = `Held — ${interrupt.newMessageCount} unread ${noun} in ${target}. ${action}\nRead them with: raft message read --target "${target}"\n${after}`;
11753
+ return {
11754
+ ...interrupt,
11755
+ context
11756
+ };
11757
+ }
11886
11758
  function rowState(result) {
11887
11759
  if (result.success) return "claimed";
11888
11760
  if (result.reason === TASK_CLAIM_REASON_ALREADY_CLAIMED_BY_YOU) return "already_yours";
@@ -11923,23 +11795,18 @@ async function claimTasks(client, request) {
11923
11795
  if (!result.ok) return failureFromClientResult(result);
11924
11796
  const data = result.data;
11925
11797
  if (isHeldResponse(data)) {
11926
- const { idempotencyKey: _unused, continuation: _c, ...heldBase } = projectHeld(request.target, data, "");
11927
- const held = {
11928
- ...heldBase,
11929
- request,
11930
- retry: () => claimTasks(client, request)
11931
- };
11798
+ const interrupt = heldTaskInterrupt(request.target, data, "Your task claim was not applied.", "After reviewing, rerun the claim if it is still correct.", taskClaimArgv(request.target, numbers, ids));
11932
11799
  return {
11933
11800
  ok: true,
11934
- state: "held",
11935
- data: held,
11801
+ state: "interrupted",
11802
+ interrupt,
11936
11803
  next: {
11937
11804
  kind: "retry_claim",
11938
11805
  command: `raft message read --target "${request.target}"`,
11939
- args: { ...request },
11940
- why: "Unread messages in this channel may change the task; read them (frontier.recordHeld(held) once the model saw them), then claim again with these args if it is still right."
11806
+ args: { target: request.target },
11807
+ why: "Unread messages in this channel may change the task; read them (frontier.recordHeld(interrupt) once the model saw them), then resume the claim (interrupt.resume) if it is still right."
11941
11808
  },
11942
- text: `Held — ${held.newMessageCount} unread ${held.newMessageCount === 1 ? "message" : "messages"} in ${request.target}. Your task claim was not applied.\nRead them with: raft message read --target "${request.target}"\nAfter reviewing, rerun the claim if it is still correct.`
11809
+ text: interrupt.context
11943
11810
  };
11944
11811
  }
11945
11812
  const rows = (data.results ?? []).map((row) => {
@@ -12076,23 +11943,20 @@ async function assignTask(client, request) {
12076
11943
  text: formatAgentTaskAssigned(request.taskNumber, assignee)
12077
11944
  };
12078
11945
  }
12079
- function heldTaskWrite(request, data, action, kind) {
12080
- const { idempotencyKey: _k, continuation: _c, ...base } = projectHeld(request.target, data, "");
12081
- const held = {
12082
- ...base,
12083
- request
12084
- };
11946
+ /** A held task write: the interrupt whose resume is the identical command; nothing to cancel. */
11947
+ function heldTaskWrite(target, data, action, kind, argv) {
11948
+ const interrupt = heldTaskInterrupt(target, data, action, "After reviewing, repeat the operation if it is still correct.", argv);
12085
11949
  return {
12086
11950
  ok: true,
12087
- state: "held",
12088
- data: held,
11951
+ state: "interrupted",
11952
+ interrupt,
12089
11953
  next: {
12090
11954
  kind,
12091
- command: `raft message read --target "${request.target}"`,
12092
- args: { ...request },
12093
- why: `Unread messages in this channel may change the task; read them (frontier.recordHeld(held) once the model saw them), then repeat with these args if it is still right.`
11955
+ command: `raft message read --target "${target}"`,
11956
+ args: { target },
11957
+ why: "Unread messages in this channel may change the task; read them (frontier.recordHeld(interrupt) once the model saw them), then resume (interrupt.resume) if it is still right."
12094
11958
  },
12095
- text: `Held — ${held.newMessageCount} unread ${held.newMessageCount === 1 ? "message" : "messages"} in ${request.target}. ${action}\nRead them with: raft message read --target "${request.target}"\nAfter reviewing, repeat the operation if it is still correct.`
11959
+ text: interrupt.context
12096
11960
  };
12097
11961
  }
12098
11962
  async function updateTaskStatus(client, request) {
@@ -12104,7 +11968,7 @@ async function updateTaskStatus(client, request) {
12104
11968
  status: request.status
12105
11969
  });
12106
11970
  if (!result.ok) return failureFromClientResult(result);
12107
- if (isHeldResponse(result.data)) return heldTaskWrite(request, result.data, "The status change was not applied.", "retry_update_status");
11971
+ if (isHeldResponse(result.data)) return heldTaskWrite(request.target, result.data, "The status change was not applied.", "retry_update_status", taskUpdateArgv(request.target, request.taskNumber, request.status));
12108
11972
  return {
12109
11973
  ok: true,
12110
11974
  state: "updated",
@@ -12116,6 +11980,18 @@ async function updateTaskStatus(client, request) {
12116
11980
  text: formatAgentTaskStatusUpdated(request.taskNumber, request.status)
12117
11981
  };
12118
11982
  }
11983
+ function taskAmendArgv(request) {
11984
+ return [
11985
+ "task",
11986
+ "amend",
11987
+ "--target",
11988
+ request.target,
11989
+ "--number",
11990
+ String(request.taskNumber),
11991
+ ...request.title === void 0 ? [] : ["--title", request.title],
11992
+ ...request.description === void 0 ? [] : request.description === null ? ["--clear-description"] : ["--description", request.description]
11993
+ ];
11994
+ }
12119
11995
  async function amendTask(client, request) {
12120
11996
  const invalid = requireTaskRef(request);
12121
11997
  if (invalid) return invalid;
@@ -12127,7 +12003,7 @@ async function amendTask(client, request) {
12127
12003
  ...request.description === void 0 ? {} : { description: request.description }
12128
12004
  });
12129
12005
  if (!result.ok) return failureFromClientResult(result);
12130
- if (isHeldResponse(result.data)) return heldTaskWrite(request, result.data, "The amendment was not applied.", "retry_amend");
12006
+ if (isHeldResponse(result.data)) return heldTaskWrite(request.target, result.data, "The amendment was not applied.", "retry_amend", taskAmendArgv(request));
12131
12007
  return {
12132
12008
  ok: true,
12133
12009
  state: "amended",
@@ -13284,12 +13160,101 @@ async function attachmentComments(client, request) {
13284
13160
  };
13285
13161
  }
13286
13162
  //#endregion
13163
+ //#region ../shared/src/zonedTime.ts
13164
+ const MS_PER_SECOND = 1e3;
13165
+ const MS_PER_MINUTE = 6e4;
13166
+ const MS_PER_DAY = 864e5;
13167
+ const formatters = /* @__PURE__ */ new Map();
13168
+ function formatterFor(timeZone) {
13169
+ let formatter = formatters.get(timeZone);
13170
+ if (!formatter) {
13171
+ formatter = new Intl.DateTimeFormat("en-US", {
13172
+ timeZone,
13173
+ hourCycle: "h23",
13174
+ era: "short",
13175
+ year: "numeric",
13176
+ month: "numeric",
13177
+ day: "numeric",
13178
+ hour: "numeric",
13179
+ minute: "numeric",
13180
+ second: "numeric"
13181
+ });
13182
+ formatters.set(timeZone, formatter);
13183
+ }
13184
+ return formatter;
13185
+ }
13186
+ /** Days from 1970-01-01 to the proleptic Gregorian date (H. Hinnant's days_from_civil). */
13187
+ function daysFromCivil(year, month, day) {
13188
+ const y = month <= 2 ? year - 1 : year;
13189
+ const era = Math.floor(y / 400);
13190
+ const yoe = y - era * 400;
13191
+ const mp = (month + 9) % 12;
13192
+ const doy = Math.floor((153 * mp + 2) / 5) + day - 1;
13193
+ const doe = yoe * 365 + Math.floor(yoe / 4) - Math.floor(yoe / 100) + doy;
13194
+ return era * 146097 + doe - 719468;
13195
+ }
13196
+ /** The wall clock as milliseconds on a UTC timeline (no Date, so no 0-99 year quirk or range limit). */
13197
+ function wallClockMs(wall) {
13198
+ return daysFromCivil(wall.year, wall.month, wall.day) * MS_PER_DAY + wall.hour * 36e5 + wall.minute * MS_PER_MINUTE + wall.second * MS_PER_SECOND + wall.millisecond;
13199
+ }
13200
+ function floorMod(value, modulus) {
13201
+ return (value % modulus + modulus) % modulus;
13202
+ }
13203
+ /** Wall clock of `epochMs` in `timeZone` plus the exact offset in ms (seconds precision). */
13204
+ function wallClockAt(epochMs, timeZone) {
13205
+ const parts = {};
13206
+ for (const part of formatterFor(timeZone).formatToParts(epochMs)) parts[part.type] = part.value;
13207
+ const eraYear = Number(parts.year);
13208
+ const wall = {
13209
+ year: /^b/i.test(parts.era ?? "") ? 1 - eraYear : eraYear,
13210
+ month: Number(parts.month),
13211
+ day: Number(parts.day),
13212
+ hour: Number(parts.hour),
13213
+ minute: Number(parts.minute),
13214
+ second: Number(parts.second),
13215
+ millisecond: floorMod(epochMs, MS_PER_SECOND)
13216
+ };
13217
+ return {
13218
+ wall,
13219
+ offsetMs: wallClockMs(wall) - epochMs
13220
+ };
13221
+ }
13222
+ function offsetMinutesOf(offsetMs) {
13223
+ return -Math.trunc(-offsetMs / MS_PER_MINUTE);
13224
+ }
13225
+ /** The wall clock and getTimezoneOffset-style offset of `epochMs` in `timeZone`. */
13226
+ function zonedInstant(epochMs, timeZone) {
13227
+ const { wall, offsetMs } = wallClockAt(epochMs, timeZone);
13228
+ return {
13229
+ ...wall,
13230
+ offsetMinutes: offsetMinutesOf(offsetMs)
13231
+ };
13232
+ }
13233
+ function pad2(n) {
13234
+ return String(n).padStart(2, "0");
13235
+ }
13236
+ /** `+08:00` / `-04:00` / `+05:30`, as the CLI has always printed offsets. */
13237
+ function formatUtcOffset(offsetMinutes) {
13238
+ const sign = offsetMinutes >= 0 ? "+" : "-";
13239
+ const abs = Math.abs(offsetMinutes);
13240
+ return `${sign}${pad2(Math.floor(abs / 60))}:${pad2(abs % 60)}`;
13241
+ }
13242
+ //#endregion
13287
13243
  //#region ../shared/src/agentText/search.ts
13288
- /** Local wall-clock rendering with offset, as the CLI prints search hit times. */
13289
- function formatLocalTimeWithOffset(iso) {
13244
+ /**
13245
+ * Wall-clock rendering with offset, as the CLI prints search hit times. With
13246
+ * `timeZone` (an IANA name) the time is rendered in that zone; without it, in
13247
+ * the process's local zone (the SDK's historical behaviour). The two agree
13248
+ * byte for byte when `timeZone` is the process zone.
13249
+ */
13250
+ function formatLocalTimeWithOffset(iso, timeZone) {
13290
13251
  const d = new Date(iso);
13291
13252
  if (isNaN(d.getTime())) return iso;
13292
13253
  const pad = (n) => String(n).padStart(2, "0");
13254
+ if (timeZone !== void 0) {
13255
+ const z = zonedInstant(d.getTime(), timeZone);
13256
+ return `${z.year}-${pad(z.month)}-${pad(z.day)} ${pad(z.hour)}:${pad(z.minute)}:${pad(z.second)} ${formatUtcOffset(z.offsetMinutes)}`;
13257
+ }
13293
13258
  const offsetMinutes = -d.getTimezoneOffset();
13294
13259
  const sign = offsetMinutes >= 0 ? "+" : "-";
13295
13260
  const absOffset = Math.abs(offsetMinutes);
@@ -13412,8 +13377,11 @@ function renderSearchPreview(content, query) {
13412
13377
  trailingOmit
13413
13378
  ].join("");
13414
13379
  }
13415
- /** Search results with <match>/<omit /> preview markup. */
13416
- function formatAgentSearchResults(query, data, offset, sort, limit) {
13380
+ /**
13381
+ * Search results with <match>/<omit /> preview markup. Hit times render in
13382
+ * `timeZone` when given, else in the process's local zone.
13383
+ */
13384
+ function formatAgentSearchResults(query, data, offset, sort, limit, timeZone) {
13417
13385
  const trimmedQuery = query.trim();
13418
13386
  const oldestShown = data.results?.length ? data.results[data.results.length - 1]?.createdAt : void 0;
13419
13387
  const nextPageHint = sort === "recent" && oldestShown ? `page with --before ${oldestShown} (pages OLDER only; NOT a complete traversal: stored times are microsecond but this key is millisecond, so rows inside the boundary millisecond can be skipped, and a full page sharing one timestamp can repeat indefinitely; copy the key verbatim)` : `page with --offset ${(offset ?? 0) + (data.results?.length ?? 0)}`;
@@ -13433,7 +13401,7 @@ function formatAgentSearchResults(query, data, offset, sort, limit) {
13433
13401
  `<result ref="${ref}">`,
13434
13402
  `Source: ${renderSearchSource(result)}`,
13435
13403
  `Sender: ${sender}${senderType}`,
13436
- `Time: ${result.createdAt ? formatLocalTimeWithOffset(result.createdAt) : "-"}`,
13404
+ `Time: ${result.createdAt ? formatLocalTimeWithOffset(result.createdAt, timeZone) : "-"}`,
13437
13405
  ...taskProjection ? [taskProjection] : [],
13438
13406
  "",
13439
13407
  "<preview>",
@@ -14144,9 +14112,9 @@ function createRaft(options) {
14144
14112
  ...request,
14145
14113
  idempotencyKey: pending.idempotencyKey
14146
14114
  } : request, frontier);
14147
- if (outcome.ok && outcome.state === "held") session.rememberContinuation({
14115
+ if (outcome.ok && outcome.state === "interrupted" && outcome.interrupt.resume.idempotencyKey) session.rememberContinuation({
14148
14116
  target: request.target,
14149
- idempotencyKey: outcome.data.idempotencyKey,
14117
+ idempotencyKey: outcome.interrupt.resume.idempotencyKey,
14150
14118
  contentHash,
14151
14119
  heldAt: (/* @__PURE__ */ new Date()).toISOString()
14152
14120
  });
@@ -14278,4 +14246,4 @@ function createRaft(options) {
14278
14246
  };
14279
14247
  }
14280
14248
  //#endregion
14281
- export { RAFT_INBOX_NOTICE_SCHEMA, RAFT_NOTICE_DELIVERY_ID_HEADER, RAFT_NOTICE_SIGNATURE_HEADER, RAFT_STATE_CONTINUATIONS_PER_TARGET, RAFT_STATE_CONTINUATIONS_TOTAL, RAFT_STATE_SCHEMA, RaftCredentialError, RaftSdkConfigurationError, SeenFrontier, bootstrapRaftCredential, createFileCredentialStore, createRaft, createRaftClient, createRaftClientFromStore, createRaftRoutes, describeRaftRoute, hasAgentMessageIdentity, hashRaftSendContent, inferAttachmentMimeType, joinRaftChannelByTarget, listRaftRoutes, parseRaftRegularChannelTarget, parseRaftState, projectRaftMessage, projectRaftMessages, readLatestReadThread, verifyInboxNotice };
14249
+ export { RAFT_INBOX_NOTICE_SCHEMA, RAFT_NOTICE_DELIVERY_ID_HEADER, RAFT_NOTICE_SIGNATURE_HEADER, RAFT_STATE_CONTINUATIONS_PER_TARGET, RAFT_STATE_CONTINUATIONS_TOTAL, RAFT_STATE_SCHEMA, RaftCredentialError, RaftSdkConfigurationError, SeenFrontier, bootstrapRaftCredential, createFileCredentialStore, createRaft, createRaftClient, createRaftClientFromStore, createRaftRoutes, describeRaftRoute, hasAgentMessageIdentity, hashRaftSendContent, inferAttachmentMimeType, isInterrupted, joinRaftChannelByTarget, listRaftRoutes, parseRaftRegularChannelTarget, parseRaftState, projectRaftMessage, projectRaftMessages, readLatestReadThread, verifyInboxNotice };