@botiverse/raft-sdk 0.3.1 → 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.
@@ -4478,12 +4478,6 @@ const destructive = {
4478
4478
  destructive: true,
4479
4479
  audience: "both"
4480
4480
  };
4481
- const managedNaturalWrite = {
4482
- sideEffect: "write",
4483
- idempotency: "natural",
4484
- destructive: false,
4485
- audience: "managed"
4486
- };
4487
4481
  const managedWrite = {
4488
4482
  sideEffect: "write",
4489
4483
  idempotency: "none",
@@ -4544,15 +4538,6 @@ const AGENT_API_ROUTE_META = {
4544
4538
  taskConvert: write,
4545
4539
  taskAmend: destructive,
4546
4540
  taskHistory: read,
4547
- migrationBegin: {
4548
- sideEffect: "write",
4549
- idempotency: "none",
4550
- destructive: true,
4551
- audience: "managed"
4552
- },
4553
- migrationStatus: read,
4554
- migrationReady: managedNaturalWrite,
4555
- migrationArrived: managedNaturalWrite,
4556
4541
  reminderList: read,
4557
4542
  reminderCreate: managedWrite,
4558
4543
  reminderCancel: naturalDestructive,
@@ -5006,7 +4991,14 @@ const agentApiHistoryQuerySchema = passthroughObject({
5006
4991
  before: optionalStringSchema,
5007
4992
  after: optionalStringSchema,
5008
4993
  around: optionalStringSchema,
5009
- limit: optionalStringSchema
4994
+ limit: optionalStringSchema,
4995
+ /**
4996
+ * `"false"` reads without consuming: the Server returns the page but does
4997
+ * not advance the agent's read cursor, and reports no
4998
+ * `model_seen_up_to_seq`. For reads whose output may never reach the model
4999
+ * (code a hosted agent wrote). Absent or `"true"` keeps the default.
5000
+ */
5001
+ consume: _enum(["true", "false"]).optional()
5010
5002
  });
5011
5003
  const agentApiKnowledgeGetQuerySchema = passthroughObject({
5012
5004
  topic: string().trim().min(1),
@@ -6132,61 +6124,6 @@ const agentApiAppConfigResponseSchema = passthroughObject({
6132
6124
  overrides: record(string(), agentApiAppConfigValueSchema),
6133
6125
  effective: record(string(), agentApiAppConfigValueSchema)
6134
6126
  });
6135
- const agentApiMigrationStateSchema = _enum([
6136
- "provisioning",
6137
- "prep",
6138
- "ready",
6139
- "in_transit",
6140
- "arriving",
6141
- "starting",
6142
- "cancel_requested_pre_flip",
6143
- "cancel_requested_post_flip",
6144
- "canceled_pre_flip",
6145
- "canceled_post_flip",
6146
- "completed",
6147
- "aborted",
6148
- "failed"
6149
- ]);
6150
- const agentApiMigrationSummarySchema = passthroughObject({
6151
- id: string(),
6152
- agentId: string(),
6153
- sourceMachineId: string(),
6154
- targetMachineId: string(),
6155
- state: agentApiMigrationStateSchema,
6156
- manifestPath: nullableStringSchema,
6157
- manifestSha256: nullableStringSchema,
6158
- arrivalReportPath: nullableStringSchema,
6159
- arrivalReportSha256: nullableStringSchema,
6160
- abortReason: nullableStringSchema,
6161
- failureReason: nullableStringSchema,
6162
- prepDeadlineAt: string().datetime(),
6163
- transferDeadlineAt: string().datetime(),
6164
- arrivalDeadlineAt: string().datetime(),
6165
- readyAt: nullableStringSchema,
6166
- flippedAt: nullableStringSchema,
6167
- arrivedAt: nullableStringSchema,
6168
- completedAt: nullableStringSchema,
6169
- abortedAt: nullableStringSchema,
6170
- revision: number$1().int().positive(),
6171
- createdAt: string().datetime(),
6172
- updatedAt: string().datetime()
6173
- });
6174
- const agentApiMigrationBeginBodySchema = passthroughObject({
6175
- targetMachineId: string().trim().min(1),
6176
- prepDeadlineMs: number$1().int().positive().optional(),
6177
- transferDeadlineMs: number$1().int().positive().optional(),
6178
- arrivalDeadlineMs: number$1().int().positive().optional()
6179
- });
6180
- const agentApiMigrationReadyBodySchema = passthroughObject({
6181
- manifestPath: string().trim().min(1),
6182
- manifestSha256: optionalStringSchema
6183
- });
6184
- const agentApiMigrationArrivedBodySchema = passthroughObject({
6185
- reportPath: optionalStringSchema,
6186
- reportSha256: optionalStringSchema
6187
- });
6188
- const agentApiMigrationResponseSchema = passthroughObject({ migration: agentApiMigrationSummarySchema });
6189
- const agentApiMigrationStatusResponseSchema = passthroughObject({ migration: agentApiMigrationSummarySchema.nullable() });
6190
6127
  const agentApiPushWebhookRegisterBodySchema = object({
6191
6128
  /** Public HTTPS endpoint that receives `raft-agent-inbox-notice.v1` POSTs. */
6192
6129
  url: string().trim().min(1),
@@ -6788,58 +6725,6 @@ const agentApiContract = {
6788
6725
  request: { query: agentApiTaskHistoryQuerySchema },
6789
6726
  response: { body: agentApiTaskHistoryResponseSchema }
6790
6727
  }),
6791
- migrationBegin: route({
6792
- key: "migrationBegin",
6793
- method: "POST",
6794
- path: "/migrations",
6795
- client: {
6796
- resource: "migrations",
6797
- method: "begin"
6798
- },
6799
- capability: "server",
6800
- description: "Begin a migration for the bound agent credential.",
6801
- request: { body: agentApiMigrationBeginBodySchema },
6802
- response: { body: agentApiMigrationResponseSchema }
6803
- }),
6804
- migrationStatus: route({
6805
- key: "migrationStatus",
6806
- method: "GET",
6807
- path: "/migrations/current",
6808
- client: {
6809
- resource: "migrations",
6810
- method: "status"
6811
- },
6812
- capability: "read",
6813
- description: "Read the active migration for the bound agent credential, if any.",
6814
- request: {},
6815
- response: { body: agentApiMigrationStatusResponseSchema }
6816
- }),
6817
- migrationReady: route({
6818
- key: "migrationReady",
6819
- method: "POST",
6820
- path: "/migrations/ready",
6821
- client: {
6822
- resource: "migrations",
6823
- method: "ready"
6824
- },
6825
- capability: "read",
6826
- description: "Mark the bound agent's active migration prep phase ready.",
6827
- request: { body: agentApiMigrationReadyBodySchema },
6828
- response: { body: agentApiMigrationResponseSchema }
6829
- }),
6830
- migrationArrived: route({
6831
- key: "migrationArrived",
6832
- method: "POST",
6833
- path: "/migrations/arrived",
6834
- client: {
6835
- resource: "migrations",
6836
- method: "arrived"
6837
- },
6838
- capability: "read",
6839
- description: "Mark the bound agent's active migration arrival phase complete.",
6840
- request: { body: agentApiMigrationArrivedBodySchema },
6841
- response: { body: agentApiMigrationResponseSchema }
6842
- }),
6843
6728
  reminderList: route({
6844
6729
  key: "reminderList",
6845
6730
  method: "GET",
@@ -8872,106 +8757,6 @@ const AGENT_API_ROUTE_MANIFEST = [
8872
8757
  "body": true
8873
8758
  }
8874
8759
  },
8875
- {
8876
- "key": "migrationBegin",
8877
- "method": "POST",
8878
- "path": "/migrations",
8879
- "fullPath": "/internal/agent-api/migrations",
8880
- "client": {
8881
- "resource": "migrations",
8882
- "method": "begin"
8883
- },
8884
- "capability": "server",
8885
- "description": "Begin a migration for the bound agent credential.",
8886
- "sideEffect": "write",
8887
- "idempotency": "none",
8888
- "destructive": true,
8889
- "audience": "managed",
8890
- "request": {
8891
- "params": false,
8892
- "query": false,
8893
- "body": true
8894
- },
8895
- "response": {
8896
- "kind": "json",
8897
- "body": true
8898
- }
8899
- },
8900
- {
8901
- "key": "migrationStatus",
8902
- "method": "GET",
8903
- "path": "/migrations/current",
8904
- "fullPath": "/internal/agent-api/migrations/current",
8905
- "client": {
8906
- "resource": "migrations",
8907
- "method": "status"
8908
- },
8909
- "capability": "read",
8910
- "description": "Read the active migration for the bound agent credential, if any.",
8911
- "sideEffect": "read",
8912
- "idempotency": "natural",
8913
- "destructive": false,
8914
- "audience": "both",
8915
- "request": {
8916
- "params": false,
8917
- "query": false,
8918
- "body": false
8919
- },
8920
- "response": {
8921
- "kind": "json",
8922
- "body": true
8923
- }
8924
- },
8925
- {
8926
- "key": "migrationReady",
8927
- "method": "POST",
8928
- "path": "/migrations/ready",
8929
- "fullPath": "/internal/agent-api/migrations/ready",
8930
- "client": {
8931
- "resource": "migrations",
8932
- "method": "ready"
8933
- },
8934
- "capability": "read",
8935
- "description": "Mark the bound agent's active migration prep phase ready.",
8936
- "sideEffect": "write",
8937
- "idempotency": "natural",
8938
- "destructive": false,
8939
- "audience": "managed",
8940
- "request": {
8941
- "params": false,
8942
- "query": false,
8943
- "body": true
8944
- },
8945
- "response": {
8946
- "kind": "json",
8947
- "body": true
8948
- }
8949
- },
8950
- {
8951
- "key": "migrationArrived",
8952
- "method": "POST",
8953
- "path": "/migrations/arrived",
8954
- "fullPath": "/internal/agent-api/migrations/arrived",
8955
- "client": {
8956
- "resource": "migrations",
8957
- "method": "arrived"
8958
- },
8959
- "capability": "read",
8960
- "description": "Mark the bound agent's active migration arrival phase complete.",
8961
- "sideEffect": "write",
8962
- "idempotency": "natural",
8963
- "destructive": false,
8964
- "audience": "managed",
8965
- "request": {
8966
- "params": false,
8967
- "query": false,
8968
- "body": true
8969
- },
8970
- "response": {
8971
- "kind": "json",
8972
- "body": true
8973
- }
8974
- },
8975
8760
  {
8976
8761
  "key": "reminderList",
8977
8762
  "method": "GET",
@@ -9874,7 +9659,7 @@ const AGENT_API_ROUTE_MANIFEST = [
9874
9659
  }
9875
9660
  ];
9876
9661
  /** Content hash of AGENT_API_ROUTE_MANIFEST; see computeAgentApiManifestVersion. */
9877
- const AGENT_API_MANIFEST_VERSION = "337bf2c7ff90b908";
9662
+ const AGENT_API_MANIFEST_VERSION = "c7a793816a7a90ed";
9878
9663
  //#endregion
9879
9664
  //#region src/routes.ts
9880
9665
  const ROUTE_INFO = Object.fromEntries(AGENT_API_ROUTE_MANIFEST.map((entry) => {
@@ -10936,6 +10721,20 @@ function formatUtcTimestamp(value) {
10936
10721
  //#endregion
10937
10722
  //#region ../shared/src/agentMessageText.ts
10938
10723
  /**
10724
+ * Prefix every continuation line of a quoted body with ` │ `, so body text
10725
+ * never reaches column 0. Structural lines (`[target=…]` / `[1/5 seq=…]` headers,
10726
+ * read cursors, footers) always start at column 0, so a line-anchored reader
10727
+ * (`grep '^\['`) cannot be handed a forged one by message content. The `│` is
10728
+ * non-whitespace on purpose: a reader that trims each line still sees it. It is
10729
+ * the same quote marker as the freshness-hold digest previews. Remove the first
10730
+ * four characters after each line separator to recover the original body.
10731
+ */
10732
+ const AGENT_BODY_CONTINUATION_PREFIX = " │ ";
10733
+ const AGENT_BODY_LINE_SEPARATOR_GLOBAL = new RegExp(/\r\n|[\n\r\v\f\x1c\x1d\x1e\x85\u2028\u2029]/.source, "g");
10734
+ function indentAgentBodyContinuationLines(text) {
10735
+ return text.replace(AGENT_BODY_LINE_SEPARATOR_GLOBAL, (separator) => `${separator}${AGENT_BODY_CONTINUATION_PREFIX}`);
10736
+ }
10737
+ /**
10939
10738
  * The target an agent passes back to reply where a message came from:
10940
10739
  * `#channel`, `#channel:shortid`, `dm:@peer`, `dm:@peer:shortid`, or
10941
10740
  * `agent-event:shortid` for third-party app events.
@@ -10978,8 +10777,8 @@ function formatAgentTaskCurrentProjection(projection, taskNumber, neutralize) {
10978
10777
  const actorType = projection.amendedByType ?? projection.amended_by_type ?? null;
10979
10778
  const actor = actorName ? neutralizeRefs ? `user:${actorName}` : `@${actorName}` : actorType === "system" ? "system" : "<unresolved>";
10980
10779
  const amendedAt = projection.amendedAt ?? projection.amended_at ?? null;
10981
- 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 ?? ""}`];
10982
- if (projection.description != null) lines.push(`Current description: ${neutralizeRefs ? renderPreviewText(projection.description) : projection.description}`);
10780
+ 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 ?? "")}`];
10781
+ if (projection.description != null) lines.push(`Current description: ${indentAgentBodyContinuationLines(neutralizeRefs ? renderPreviewText(projection.description) : projection.description)}`);
10983
10782
  return `\n${lines.join("\n")}`;
10984
10783
  }
10985
10784
  /** One received-message line: header bracket + sender + content + suffixes. */
@@ -10998,7 +10797,7 @@ function formatAgentMessageLine(m) {
10998
10797
  const msgId = m.message_id ? m.message_id.slice(0, 8) : "-";
10999
10798
  const time = m.timestamp ? formatUtcTimestamp(m.timestamp) : "-";
11000
10799
  const senderType = ` type=${m.sender_type}`;
11001
- const content = m.content ?? "";
10800
+ const content = indentAgentBodyContinuationLines(m.content ?? "");
11002
10801
  const attachSuffix = formatAgentAttachmentSuffix(m.attachments);
11003
10802
  const taskSuffix = m.task_status ? ` [task #${m.task_number} status=${m.task_status}${formatAgentTaskAssigneeSuffix(m.task_assignee_id, m.task_assignee_name)}]` : "";
11004
10803
  return `[target=${target} msg=${msgId} time=${time}${senderType}] ${formatAgentSenderHandle(m)}: ${content}${attachSuffix}${taskSuffix}${formatAgentReplyAffordanceSuffix(m)}${formatAgentTaskCurrentProjection(m.task_current_projection)}`;
@@ -11102,6 +10901,126 @@ function sortBySeq(messages) {
11102
10901
  function projectRaftMessages(envelopes) {
11103
10902
  return envelopes.map(projectRaftMessage).filter((m) => m !== null);
11104
10903
  }
10904
+ /** The conversation fields a target string names; `null` for a shape this cannot read. */
10905
+ function identityFromTarget(target) {
10906
+ const dm = /^dm:@([^:\s]+)(?::([^:\s]+))?$/.exec(target);
10907
+ if (dm) return dm[2] ? {
10908
+ channel_type: "thread",
10909
+ channel_name: dm[2],
10910
+ parent_channel_name: dm[1],
10911
+ parent_channel_type: "dm"
10912
+ } : {
10913
+ channel_type: "dm",
10914
+ channel_name: dm[1]
10915
+ };
10916
+ const channel = /^#([^:\s]+)(?::([^:\s]+))?$/.exec(target);
10917
+ if (channel) return channel[2] ? {
10918
+ channel_type: "thread",
10919
+ channel_name: channel[2],
10920
+ parent_channel_name: channel[1],
10921
+ parent_channel_type: "channel"
10922
+ } : {
10923
+ channel_type: "channel",
10924
+ channel_name: channel[1]
10925
+ };
10926
+ return null;
10927
+ }
10928
+ /**
10929
+ * Project envelopes that all belong to one known conversation. Some Server
10930
+ * responses (held previews) send envelopes without conversation fields
10931
+ * because the request already named the target; fill them from `target`
10932
+ * instead of dropping the message.
10933
+ */
10934
+ function projectRaftMessagesInTarget(envelopes, target) {
10935
+ const identity = identityFromTarget(target);
10936
+ return envelopes.map((envelope) => projectRaftMessage(identity && !hasAgentMessageIdentity(envelope) ? {
10937
+ ...envelope,
10938
+ ...identity
10939
+ } : envelope)).filter((m) => m !== null);
10940
+ }
10941
+ //#endregion
10942
+ //#region ../shared/src/agentOps/interrupt.ts
10943
+ /** Narrow any outcome (SDK or command registry) to an interrupt, without knowing the command. */
10944
+ function isInterrupted(outcome) {
10945
+ return Boolean(outcome) && typeof outcome === "object" && outcome.state === "interrupted" && typeof outcome.interrupt === "object";
10946
+ }
10947
+ /** Build the `unread_messages` interrupt from a Server freshness hold. */
10948
+ function unreadMessagesInterrupt(input) {
10949
+ const { hold, target } = input;
10950
+ const withheld = input.withheld === true || hold.freshnessContextMode === "withheld";
10951
+ const heldMessages = withheld ? [] : projectRaftMessagesInTarget(hold.heldMessages ?? [], target);
10952
+ const newMessageCount = withheld ? hold.withheldMessageCount ?? hold.newMessageCount ?? 0 : hold.newMessageCount ?? heldMessages.length;
10953
+ const omittedMessageCount = withheld ? 0 : hold.omittedMessageCount ?? 0;
10954
+ return {
10955
+ reason: "unread_messages",
10956
+ context: input.context,
10957
+ resume: input.resume,
10958
+ ...input.cancel ? { cancel: input.cancel } : {},
10959
+ target,
10960
+ newMessageCount,
10961
+ heldMessages,
10962
+ omittedMessageCount,
10963
+ formalMentionCount: withheld ? 0 : hold.mentionAnnotation?.formalMentionCount ?? 0,
10964
+ seenUpToSeq: typeof hold.seenUpToSeq === "number" ? hold.seenUpToSeq : null,
10965
+ withheld,
10966
+ contextComplete: !withheld && heldMessages.length + omittedMessageCount >= newMessageCount
10967
+ };
10968
+ }
10969
+ /** `raft message send --send-draft` for the draft a held send saved, under its original key. */
10970
+ function sendDraftResume(target, idempotencyKey, extraArgv = []) {
10971
+ return {
10972
+ argv: [
10973
+ "message",
10974
+ "send",
10975
+ "--send-draft",
10976
+ "--target",
10977
+ target,
10978
+ "--expected-draft-key",
10979
+ idempotencyKey,
10980
+ ...extraArgv
10981
+ ],
10982
+ idempotencyKey
10983
+ };
10984
+ }
10985
+ /** `raft message send --discard-draft` for the draft a held send saved (compare-and-clear on its key). */
10986
+ function discardDraftCancel(target, idempotencyKey, extraArgv = []) {
10987
+ return { argv: [
10988
+ "message",
10989
+ "send",
10990
+ "--discard-draft",
10991
+ "--target",
10992
+ target,
10993
+ "--expected-draft-key",
10994
+ idempotencyKey,
10995
+ ...extraArgv
10996
+ ] };
10997
+ }
10998
+ /** `raft task claim` argv for a claim request (resume of a held claim). */
10999
+ function taskClaimArgv(target, taskNumbers, messageIds, extraArgv = []) {
11000
+ return [
11001
+ "task",
11002
+ "claim",
11003
+ "--target",
11004
+ target,
11005
+ ...taskNumbers.flatMap((n) => ["--number", String(n)]),
11006
+ ...messageIds.flatMap((id) => ["--message-id", id]),
11007
+ ...extraArgv
11008
+ ];
11009
+ }
11010
+ /** `raft task update` argv (resume of a held status update). */
11011
+ function taskUpdateArgv(target, taskNumber, status, extraArgv = []) {
11012
+ return [
11013
+ "task",
11014
+ "update",
11015
+ "--target",
11016
+ target,
11017
+ "--number",
11018
+ String(taskNumber),
11019
+ "--status",
11020
+ status,
11021
+ ...extraArgv
11022
+ ];
11023
+ }
11105
11024
  //#endregion
11106
11025
  //#region ../shared/src/agentOps/frontier.ts
11107
11026
  const MAX_EXACT_SEQS = 2500;
@@ -11154,13 +11073,15 @@ var SeenFrontier = class SeenFrontier {
11154
11073
  }
11155
11074
  }
11156
11075
  /**
11157
- * Attest that the model saw the context a held send/claim returned. Call it
11158
- * only after the held messages actually reached the model (the SDK cannot
11159
- * know that, so it never records this implicitly). Returns false and records
11160
- * nothing when the context was withheld or the Server sent no boundary.
11076
+ * Attest that the model saw the context of an interrupt (`outcome.interrupt`
11077
+ * of an `interrupted` send, claim or task write). Call it only after
11078
+ * `interrupt.context` actually reached the model (the SDK cannot know that,
11079
+ * so it never records this implicitly). Returns false and records nothing
11080
+ * when the context was withheld, not fully shown (`contextComplete: false`),
11081
+ * or the Server sent no boundary.
11161
11082
  */
11162
11083
  recordHeld(held) {
11163
- if (held.withheld || held.seenUpToSeq === null) return false;
11084
+ if (held.withheld || held.contextComplete === false || held.seenUpToSeq === null) return false;
11164
11085
  this.recordUpTo(held.target, held.seenUpToSeq);
11165
11086
  return true;
11166
11087
  }
@@ -11526,7 +11447,8 @@ async function readHistory(client, request, frontier) {
11526
11447
  ...request.after === void 0 ? {} : { after: String(request.after) },
11527
11448
  ...request.before === void 0 ? {} : { before: String(request.before) },
11528
11449
  ...request.around === void 0 ? {} : { around: String(request.around) },
11529
- ...request.limit === void 0 ? {} : { limit: String(request.limit) }
11450
+ ...request.limit === void 0 ? {} : { limit: String(request.limit) },
11451
+ ...request.consume === false ? { consume: "false" } : {}
11530
11452
  });
11531
11453
  if (!result.ok) return failureFromClientResult(result);
11532
11454
  const data = result.data;
@@ -11538,7 +11460,7 @@ async function readHistory(client, request, frontier) {
11538
11460
  lastReadSeq: typeof data.last_read_seq === "number" ? data.last_read_seq : null,
11539
11461
  modelSeenUpToSeq: typeof data.model_seen_up_to_seq === "number" ? data.model_seen_up_to_seq : null
11540
11462
  };
11541
- recordHistorySeen(frontier, request.target, page, request.around !== void 0);
11463
+ if (request.consume !== false) recordHistorySeen(frontier, request.target, page, request.around !== void 0);
11542
11464
  return {
11543
11465
  ok: true,
11544
11466
  state: page.messages.length > 0 ? "page" : "empty",
@@ -11549,6 +11471,7 @@ async function readHistory(client, request, frontier) {
11549
11471
  }
11550
11472
  /** Attempts for a keyed send whose request never reached the Server (transport failure only). */
11551
11473
  const SEND_TRANSPORT_ATTEMPTS = 3;
11474
+ /** Today's held text (unchanged); it is the interrupt's `context` and the outcome's `text`. */
11552
11475
  function heldText(held, action) {
11553
11476
  const noun = held.newMessageCount === 1 ? "message" : "messages";
11554
11477
  const head = `Held — ${held.newMessageCount} unread ${noun} in ${held.target}. ${action}`;
@@ -11557,37 +11480,33 @@ function heldText(held, action) {
11557
11480
  if (held.formalMentionCount > 0) lines.push(`Note: ${held.formalMentionCount} of these messages formally @mention you.`);
11558
11481
  if (held.omittedMessageCount > 0) lines.push(`${held.omittedMessageCount} earlier ${held.omittedMessageCount === 1 ? "message" : "messages"} not shown.`);
11559
11482
  if (held.heldMessages.length > 0) lines.push(formatAgentMessages(held.heldMessages.map((m) => toAgentMessageLike(m.raw))));
11483
+ if (!held.contextComplete) lines.push("Not all of them are shown here; read the conversation before sending again.");
11560
11484
  lines.push(`Full text: raft message read --target "${held.target}"`);
11561
11485
  return lines.join("\n");
11562
11486
  }
11563
- function heldNext(held) {
11487
+ /**
11488
+ * A Server freshness hold as the `unread_messages` interrupt: the structured
11489
+ * details, today's held text as `context`, and the given resume / cancel.
11490
+ */
11491
+ function heldInterrupt(target, data, action, resume, cancel) {
11492
+ const interrupt = unreadMessagesInterrupt({
11493
+ target,
11494
+ hold: data,
11495
+ context: "",
11496
+ resume,
11497
+ ...cancel ? { cancel } : {}
11498
+ });
11564
11499
  return {
11565
- kind: "resend",
11566
- command: `raft message read --target "${held.target}"`,
11567
- args: {
11568
- target: held.target,
11569
- ...held.continuation
11570
- },
11571
- why: held.withheld ? "Newer messages exist in this conversation but were withheld; read them, 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)."
11500
+ ...interrupt,
11501
+ context: heldText(interrupt, action)
11572
11502
  };
11573
11503
  }
11574
- function projectHeld(target, data, idempotencyKey) {
11575
- const withheld = data.freshnessContextMode === "withheld";
11576
- const heldMessages = projectRaftMessages(data.heldMessages ?? []);
11577
- const seenUpToSeq = typeof data.seenUpToSeq === "number" ? data.seenUpToSeq : null;
11504
+ function heldSendNext(interrupt) {
11578
11505
  return {
11579
- target,
11580
- idempotencyKey,
11581
- continuation: {
11582
- idempotencyKey,
11583
- ...!withheld && seenUpToSeq !== null ? { seen: { upToSeq: seenUpToSeq } } : {}
11584
- },
11585
- newMessageCount: withheld ? data.withheldMessageCount ?? 0 : data.newMessageCount ?? heldMessages.length,
11586
- heldMessages,
11587
- omittedMessageCount: data.omittedMessageCount ?? 0,
11588
- formalMentionCount: data.mentionAnnotation?.formalMentionCount ?? 0,
11589
- seenUpToSeq,
11590
- withheld
11506
+ kind: "resend",
11507
+ command: `raft message read --target "${interrupt.target}"`,
11508
+ args: { target: interrupt.target },
11509
+ 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."
11591
11510
  };
11592
11511
  }
11593
11512
  function isHeldResponse(data) {
@@ -11614,7 +11533,7 @@ function sentOutcome(target, data) {
11614
11533
  text: `Message sent to ${target}. Message ID: ${sent.messageId}${warn}`
11615
11534
  };
11616
11535
  }
11617
- async function sendMessage(client, request, frontier, internal = {}) {
11536
+ async function sendMessage(client, request, frontier) {
11618
11537
  if (!request.target?.trim()) return failureOutcome(opError("INVALID_REQUEST", { message: "A target is required to send a message." }));
11619
11538
  if (typeof request.content !== "string" || !request.content.trim() && !request.attachmentIds?.length) return failureOutcome(opError("INVALID_REQUEST", { message: "Message content (or an attachment) is required." }));
11620
11539
  const idempotencyKey = request.idempotencyKey?.trim() || crypto.randomUUID();
@@ -11629,41 +11548,20 @@ async function sendMessage(client, request, frontier, internal = {}) {
11629
11548
  idempotencyKey,
11630
11549
  ...request.mentions?.length ? { mentions: request.mentions } : {},
11631
11550
  ...attestation.seenUpToSeq === void 0 ? {} : { seenUpToSeq: attestation.seenUpToSeq },
11632
- ...attestation.seenExactSeqs.length > 0 ? { seenExactSeqs: attestation.seenExactSeqs.slice(0, 2500) } : {},
11633
- ...internal.sendDraft ? { sendDraft: true } : {},
11634
- ...internal.continueAnyway ? { continueAnyway: true } : {},
11635
- ...internal.reholdCount ? { draftReholdCount: internal.reholdCount } : {}
11551
+ ...attestation.seenExactSeqs.length > 0 ? { seenExactSeqs: attestation.seenExactSeqs.slice(0, 2500) } : {}
11636
11552
  };
11637
11553
  let result = await client.messages.sendV2(body);
11638
11554
  for (let attempt = 1; !result.ok && result.error.kind === "transport" && attempt < SEND_TRANSPORT_ATTEMPTS; attempt += 1) result = await client.messages.sendV2(body);
11639
11555
  if (!result.ok) return failureFromClientResult(result);
11640
11556
  const data = result.data;
11641
11557
  if (isHeldResponse(data)) {
11642
- const reholdCount = (internal.reholdCount ?? 0) + 1;
11643
- const keyed = {
11644
- ...request,
11645
- idempotencyKey
11646
- };
11647
- const held = {
11648
- ...projectHeld(request.target, data, idempotencyKey),
11649
- resend: (options) => sendMessage(client, options.seen === "held" && typeof data.seenUpToSeq === "number" ? {
11650
- ...keyed,
11651
- seen: {
11652
- upToSeq: data.seenUpToSeq,
11653
- exactSeqs: attestation.seenExactSeqs
11654
- }
11655
- } : keyed, frontier, {
11656
- sendDraft: true,
11657
- reholdCount,
11658
- ...options.seen === "anyway" ? { continueAnyway: true } : {}
11659
- })
11660
- };
11558
+ const interrupt = heldInterrupt(request.target, data, "Your message was not sent.", sendDraftResume(request.target, idempotencyKey), discardDraftCancel(request.target, idempotencyKey));
11661
11559
  return {
11662
11560
  ok: true,
11663
- state: "held",
11664
- data: held,
11665
- next: heldNext(held),
11666
- text: heldText(held, "Your message was not sent.")
11561
+ state: "interrupted",
11562
+ interrupt,
11563
+ next: heldSendNext(interrupt),
11564
+ text: interrupt.context
11667
11565
  };
11668
11566
  }
11669
11567
  if (data.state === "sent") return sentOutcome(request.target, data);
@@ -11840,6 +11738,24 @@ function formatAgentTaskHistory(data) {
11840
11738
  //#endregion
11841
11739
  //#region ../shared/src/agentOps/tasks.ts
11842
11740
  const TASK_CLAIM_REASON_ALREADY_CLAIMED_BY_YOU = "already claimed by you";
11741
+ /**
11742
+ * A held task call: the interrupt whose resume is the identical command, with
11743
+ * no cancel (a held task call saved nothing). `context` is today's held text.
11744
+ */
11745
+ function heldTaskInterrupt(target, data, action, after, argv) {
11746
+ const interrupt = unreadMessagesInterrupt({
11747
+ target,
11748
+ hold: data,
11749
+ context: "",
11750
+ resume: { argv }
11751
+ });
11752
+ const noun = interrupt.newMessageCount === 1 ? "message" : "messages";
11753
+ const context = `Held — ${interrupt.newMessageCount} unread ${noun} in ${target}. ${action}\nRead them with: raft message read --target "${target}"\n${after}`;
11754
+ return {
11755
+ ...interrupt,
11756
+ context
11757
+ };
11758
+ }
11843
11759
  function rowState(result) {
11844
11760
  if (result.success) return "claimed";
11845
11761
  if (result.reason === TASK_CLAIM_REASON_ALREADY_CLAIMED_BY_YOU) return "already_yours";
@@ -11880,23 +11796,18 @@ async function claimTasks(client, request) {
11880
11796
  if (!result.ok) return failureFromClientResult(result);
11881
11797
  const data = result.data;
11882
11798
  if (isHeldResponse(data)) {
11883
- const { idempotencyKey: _unused, continuation: _c, ...heldBase } = projectHeld(request.target, data, "");
11884
- const held = {
11885
- ...heldBase,
11886
- request,
11887
- retry: () => claimTasks(client, request)
11888
- };
11799
+ 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));
11889
11800
  return {
11890
11801
  ok: true,
11891
- state: "held",
11892
- data: held,
11802
+ state: "interrupted",
11803
+ interrupt,
11893
11804
  next: {
11894
11805
  kind: "retry_claim",
11895
11806
  command: `raft message read --target "${request.target}"`,
11896
- args: { ...request },
11897
- 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."
11807
+ args: { target: request.target },
11808
+ 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."
11898
11809
  },
11899
- 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.`
11810
+ text: interrupt.context
11900
11811
  };
11901
11812
  }
11902
11813
  const rows = (data.results ?? []).map((row) => {
@@ -12033,23 +11944,20 @@ async function assignTask(client, request) {
12033
11944
  text: formatAgentTaskAssigned(request.taskNumber, assignee)
12034
11945
  };
12035
11946
  }
12036
- function heldTaskWrite(request, data, action, kind) {
12037
- const { idempotencyKey: _k, continuation: _c, ...base } = projectHeld(request.target, data, "");
12038
- const held = {
12039
- ...base,
12040
- request
12041
- };
11947
+ /** A held task write: the interrupt whose resume is the identical command; nothing to cancel. */
11948
+ function heldTaskWrite(target, data, action, kind, argv) {
11949
+ const interrupt = heldTaskInterrupt(target, data, action, "After reviewing, repeat the operation if it is still correct.", argv);
12042
11950
  return {
12043
11951
  ok: true,
12044
- state: "held",
12045
- data: held,
11952
+ state: "interrupted",
11953
+ interrupt,
12046
11954
  next: {
12047
11955
  kind,
12048
- command: `raft message read --target "${request.target}"`,
12049
- args: { ...request },
12050
- 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.`
11956
+ command: `raft message read --target "${target}"`,
11957
+ args: { target },
11958
+ 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."
12051
11959
  },
12052
- 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.`
11960
+ text: interrupt.context
12053
11961
  };
12054
11962
  }
12055
11963
  async function updateTaskStatus(client, request) {
@@ -12061,7 +11969,7 @@ async function updateTaskStatus(client, request) {
12061
11969
  status: request.status
12062
11970
  });
12063
11971
  if (!result.ok) return failureFromClientResult(result);
12064
- if (isHeldResponse(result.data)) return heldTaskWrite(request, result.data, "The status change was not applied.", "retry_update_status");
11972
+ 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));
12065
11973
  return {
12066
11974
  ok: true,
12067
11975
  state: "updated",
@@ -12073,6 +11981,18 @@ async function updateTaskStatus(client, request) {
12073
11981
  text: formatAgentTaskStatusUpdated(request.taskNumber, request.status)
12074
11982
  };
12075
11983
  }
11984
+ function taskAmendArgv(request) {
11985
+ return [
11986
+ "task",
11987
+ "amend",
11988
+ "--target",
11989
+ request.target,
11990
+ "--number",
11991
+ String(request.taskNumber),
11992
+ ...request.title === void 0 ? [] : ["--title", request.title],
11993
+ ...request.description === void 0 ? [] : request.description === null ? ["--clear-description"] : ["--description", request.description]
11994
+ ];
11995
+ }
12076
11996
  async function amendTask(client, request) {
12077
11997
  const invalid = requireTaskRef(request);
12078
11998
  if (invalid) return invalid;
@@ -12084,7 +12004,7 @@ async function amendTask(client, request) {
12084
12004
  ...request.description === void 0 ? {} : { description: request.description }
12085
12005
  });
12086
12006
  if (!result.ok) return failureFromClientResult(result);
12087
- if (isHeldResponse(result.data)) return heldTaskWrite(request, result.data, "The amendment was not applied.", "retry_amend");
12007
+ if (isHeldResponse(result.data)) return heldTaskWrite(request.target, result.data, "The amendment was not applied.", "retry_amend", taskAmendArgv(request));
12088
12008
  return {
12089
12009
  ok: true,
12090
12010
  state: "amended",
@@ -13241,12 +13161,101 @@ async function attachmentComments(client, request) {
13241
13161
  };
13242
13162
  }
13243
13163
  //#endregion
13164
+ //#region ../shared/src/zonedTime.ts
13165
+ const MS_PER_SECOND = 1e3;
13166
+ const MS_PER_MINUTE = 6e4;
13167
+ const MS_PER_DAY = 864e5;
13168
+ const formatters = /* @__PURE__ */ new Map();
13169
+ function formatterFor(timeZone) {
13170
+ let formatter = formatters.get(timeZone);
13171
+ if (!formatter) {
13172
+ formatter = new Intl.DateTimeFormat("en-US", {
13173
+ timeZone,
13174
+ hourCycle: "h23",
13175
+ era: "short",
13176
+ year: "numeric",
13177
+ month: "numeric",
13178
+ day: "numeric",
13179
+ hour: "numeric",
13180
+ minute: "numeric",
13181
+ second: "numeric"
13182
+ });
13183
+ formatters.set(timeZone, formatter);
13184
+ }
13185
+ return formatter;
13186
+ }
13187
+ /** Days from 1970-01-01 to the proleptic Gregorian date (H. Hinnant's days_from_civil). */
13188
+ function daysFromCivil(year, month, day) {
13189
+ const y = month <= 2 ? year - 1 : year;
13190
+ const era = Math.floor(y / 400);
13191
+ const yoe = y - era * 400;
13192
+ const mp = (month + 9) % 12;
13193
+ const doy = Math.floor((153 * mp + 2) / 5) + day - 1;
13194
+ const doe = yoe * 365 + Math.floor(yoe / 4) - Math.floor(yoe / 100) + doy;
13195
+ return era * 146097 + doe - 719468;
13196
+ }
13197
+ /** The wall clock as milliseconds on a UTC timeline (no Date, so no 0-99 year quirk or range limit). */
13198
+ function wallClockMs(wall) {
13199
+ 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;
13200
+ }
13201
+ function floorMod(value, modulus) {
13202
+ return (value % modulus + modulus) % modulus;
13203
+ }
13204
+ /** Wall clock of `epochMs` in `timeZone` plus the exact offset in ms (seconds precision). */
13205
+ function wallClockAt(epochMs, timeZone) {
13206
+ const parts = {};
13207
+ for (const part of formatterFor(timeZone).formatToParts(epochMs)) parts[part.type] = part.value;
13208
+ const eraYear = Number(parts.year);
13209
+ const wall = {
13210
+ year: /^b/i.test(parts.era ?? "") ? 1 - eraYear : eraYear,
13211
+ month: Number(parts.month),
13212
+ day: Number(parts.day),
13213
+ hour: Number(parts.hour),
13214
+ minute: Number(parts.minute),
13215
+ second: Number(parts.second),
13216
+ millisecond: floorMod(epochMs, MS_PER_SECOND)
13217
+ };
13218
+ return {
13219
+ wall,
13220
+ offsetMs: wallClockMs(wall) - epochMs
13221
+ };
13222
+ }
13223
+ function offsetMinutesOf(offsetMs) {
13224
+ return -Math.trunc(-offsetMs / MS_PER_MINUTE);
13225
+ }
13226
+ /** The wall clock and getTimezoneOffset-style offset of `epochMs` in `timeZone`. */
13227
+ function zonedInstant(epochMs, timeZone) {
13228
+ const { wall, offsetMs } = wallClockAt(epochMs, timeZone);
13229
+ return {
13230
+ ...wall,
13231
+ offsetMinutes: offsetMinutesOf(offsetMs)
13232
+ };
13233
+ }
13234
+ function pad2(n) {
13235
+ return String(n).padStart(2, "0");
13236
+ }
13237
+ /** `+08:00` / `-04:00` / `+05:30`, as the CLI has always printed offsets. */
13238
+ function formatUtcOffset(offsetMinutes) {
13239
+ const sign = offsetMinutes >= 0 ? "+" : "-";
13240
+ const abs = Math.abs(offsetMinutes);
13241
+ return `${sign}${pad2(Math.floor(abs / 60))}:${pad2(abs % 60)}`;
13242
+ }
13243
+ //#endregion
13244
13244
  //#region ../shared/src/agentText/search.ts
13245
- /** Local wall-clock rendering with offset, as the CLI prints search hit times. */
13246
- function formatLocalTimeWithOffset(iso) {
13245
+ /**
13246
+ * Wall-clock rendering with offset, as the CLI prints search hit times. With
13247
+ * `timeZone` (an IANA name) the time is rendered in that zone; without it, in
13248
+ * the process's local zone (the SDK's historical behaviour). The two agree
13249
+ * byte for byte when `timeZone` is the process zone.
13250
+ */
13251
+ function formatLocalTimeWithOffset(iso, timeZone) {
13247
13252
  const d = new Date(iso);
13248
13253
  if (isNaN(d.getTime())) return iso;
13249
13254
  const pad = (n) => String(n).padStart(2, "0");
13255
+ if (timeZone !== void 0) {
13256
+ const z = zonedInstant(d.getTime(), timeZone);
13257
+ return `${z.year}-${pad(z.month)}-${pad(z.day)} ${pad(z.hour)}:${pad(z.minute)}:${pad(z.second)} ${formatUtcOffset(z.offsetMinutes)}`;
13258
+ }
13250
13259
  const offsetMinutes = -d.getTimezoneOffset();
13251
13260
  const sign = offsetMinutes >= 0 ? "+" : "-";
13252
13261
  const absOffset = Math.abs(offsetMinutes);
@@ -13369,8 +13378,11 @@ function renderSearchPreview(content, query) {
13369
13378
  trailingOmit
13370
13379
  ].join("");
13371
13380
  }
13372
- /** Search results with <match>/<omit /> preview markup. */
13373
- function formatAgentSearchResults(query, data, offset, sort, limit) {
13381
+ /**
13382
+ * Search results with <match>/<omit /> preview markup. Hit times render in
13383
+ * `timeZone` when given, else in the process's local zone.
13384
+ */
13385
+ function formatAgentSearchResults(query, data, offset, sort, limit, timeZone) {
13374
13386
  const trimmedQuery = query.trim();
13375
13387
  const oldestShown = data.results?.length ? data.results[data.results.length - 1]?.createdAt : void 0;
13376
13388
  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)}`;
@@ -13390,7 +13402,7 @@ function formatAgentSearchResults(query, data, offset, sort, limit) {
13390
13402
  `<result ref="${ref}">`,
13391
13403
  `Source: ${renderSearchSource(result)}`,
13392
13404
  `Sender: ${sender}${senderType}`,
13393
- `Time: ${result.createdAt ? formatLocalTimeWithOffset(result.createdAt) : "-"}`,
13405
+ `Time: ${result.createdAt ? formatLocalTimeWithOffset(result.createdAt, timeZone) : "-"}`,
13394
13406
  ...taskProjection ? [taskProjection] : [],
13395
13407
  "",
13396
13408
  "<preview>",
@@ -14101,9 +14113,9 @@ function createRaft(options) {
14101
14113
  ...request,
14102
14114
  idempotencyKey: pending.idempotencyKey
14103
14115
  } : request, frontier);
14104
- if (outcome.ok && outcome.state === "held") session.rememberContinuation({
14116
+ if (outcome.ok && outcome.state === "interrupted" && outcome.interrupt.resume.idempotencyKey) session.rememberContinuation({
14105
14117
  target: request.target,
14106
- idempotencyKey: outcome.data.idempotencyKey,
14118
+ idempotencyKey: outcome.interrupt.resume.idempotencyKey,
14107
14119
  contentHash,
14108
14120
  heldAt: (/* @__PURE__ */ new Date()).toISOString()
14109
14121
  });
@@ -14254,6 +14266,7 @@ exports.describeRaftRoute = describeRaftRoute;
14254
14266
  exports.hasAgentMessageIdentity = hasAgentMessageIdentity;
14255
14267
  exports.hashRaftSendContent = hashRaftSendContent;
14256
14268
  exports.inferAttachmentMimeType = inferAttachmentMimeType;
14269
+ exports.isInterrupted = isInterrupted;
14257
14270
  exports.joinRaftChannelByTarget = joinRaftChannelByTarget;
14258
14271
  exports.listRaftRoutes = listRaftRoutes;
14259
14272
  exports.parseRaftRegularChannelTarget = parseRaftRegularChannelTarget;