@borgee/agents-host 0.2.44 → 0.2.62

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/README.md +24 -27
  2. package/dist/agents-host.d.ts +27 -5
  3. package/dist/agents-host.js +266 -201
  4. package/dist/chat/chat-control-plane.d.ts +3 -0
  5. package/dist/chat/sdk-chat-control-plane.d.ts +4 -3
  6. package/dist/chat/sdk-chat-control-plane.js +3 -0
  7. package/dist/cli.js +1 -5
  8. package/dist/compatibility-gates.d.ts +4 -0
  9. package/dist/compatibility-gates.js +20 -1
  10. package/dist/config.d.ts +2 -0
  11. package/dist/config.js +21 -0
  12. package/dist/context/claude-file-brief.d.ts +2 -0
  13. package/dist/context/claude-file-brief.js +83 -0
  14. package/dist/context/compaction.d.ts +20 -0
  15. package/dist/context/compaction.js +59 -0
  16. package/dist/context/injection.d.ts +58 -7
  17. package/dist/context/injection.js +370 -36
  18. package/dist/context/main-session-delegation.d.ts +1 -1
  19. package/dist/context/projection-strategy.d.ts +24 -0
  20. package/dist/context/projection-strategy.js +90 -0
  21. package/dist/context/prompt.d.ts +16 -1
  22. package/dist/context/prompt.js +464 -26
  23. package/dist/context/resolved-workspace.d.ts +3 -0
  24. package/dist/context/resolved-workspace.js +106 -0
  25. package/dist/context/skill-manual.d.ts +1 -0
  26. package/dist/context/skill-manual.js +4 -1
  27. package/dist/context/turn-preparation.d.ts +8 -2
  28. package/dist/context/turn-preparation.js +64 -14
  29. package/dist/gateway/localhost-gateway.js +4 -5
  30. package/dist/local-config.js +11 -1
  31. package/dist/managed-daemon.js +127 -9
  32. package/dist/plugin-sdk.js +264 -364
  33. package/dist/plugin-sdk.js.map +4 -4
  34. package/dist/policy/copilot-permission.d.ts +1 -0
  35. package/dist/policy/copilot-permission.js +18 -0
  36. package/dist/policy/gateway-authorization.js +2 -2
  37. package/dist/progress-to-activity.d.ts +16 -0
  38. package/dist/progress-to-activity.js +24 -0
  39. package/dist/projection-strategy-values.d.ts +4 -0
  40. package/dist/projection-strategy-values.js +28 -0
  41. package/dist/providers/acp-progress-collector.d.ts +44 -0
  42. package/dist/providers/acp-progress-collector.js +130 -0
  43. package/dist/providers/awaiting-user.d.ts +2 -3
  44. package/dist/providers/awaiting-user.js +5 -7
  45. package/dist/providers/claude/activity-metadata.d.ts +14 -0
  46. package/dist/providers/claude/activity-metadata.js +81 -0
  47. package/dist/providers/claude/cli-client.d.ts +2 -3
  48. package/dist/providers/claude/cli-client.js +220 -120
  49. package/dist/providers/codex/cli-client.d.ts +2 -0
  50. package/dist/providers/codex/cli-client.js +28 -104
  51. package/dist/providers/codex/project-doc.js +12 -11
  52. package/dist/providers/copilot/cli-client.d.ts +1 -1
  53. package/dist/providers/copilot/cli-client.js +12 -86
  54. package/dist/providers/create-provider.d.ts +2 -0
  55. package/dist/providers/create-provider.js +22 -4
  56. package/dist/state-paths.d.ts +1 -1
  57. package/dist/state-paths.js +3 -3
  58. package/dist/task-thread-resolution.d.ts +3 -2
  59. package/dist/types.d.ts +143 -6
  60. package/package.json +2 -2
  61. package/skills/borgee-agent/SKILL.md +12 -4
  62. package/skills/borgee-agent/references/errors.md +2 -2
  63. package/skills/borgee-agent/references/task-properties.md +6 -2
  64. package/dist/durable-cursor-store.d.ts +0 -5
  65. package/dist/durable-cursor-store.js +0 -7
@@ -3224,7 +3224,7 @@ var require_stream = __commonJS({
3224
3224
  };
3225
3225
  duplex._final = function(callback) {
3226
3226
  if (ws.readyState === ws.CONNECTING) {
3227
- ws.once("open", function open2() {
3227
+ ws.once("open", function open() {
3228
3228
  duplex._final(callback);
3229
3229
  });
3230
3230
  return;
@@ -3245,7 +3245,7 @@ var require_stream = __commonJS({
3245
3245
  };
3246
3246
  duplex._write = function(chunk, encoding, callback) {
3247
3247
  if (ws.readyState === ws.CONNECTING) {
3248
- ws.once("open", function open2() {
3248
+ ws.once("open", function open() {
3249
3249
  duplex._write(chunk, encoding, callback);
3250
3250
  });
3251
3251
  return;
@@ -3706,123 +3706,8 @@ var require_websocket_server = __commonJS({
3706
3706
  }
3707
3707
  });
3708
3708
 
3709
- // ../sdk/plugin-ts/dist/cursor-store.js
3710
- import { randomUUID } from "node:crypto";
3711
- import { mkdir, open, readFile, rename, unlink } from "node:fs/promises";
3712
- import { dirname } from "node:path";
3713
- var MemoryCursorStore = class {
3714
- cursors = /* @__PURE__ */ new Map();
3715
- async read(agentId) {
3716
- return this.cursors.get(agentId) ?? -1;
3717
- }
3718
- async write(agentId, cursor) {
3719
- this.cursors.set(agentId, cursor);
3720
- }
3721
- };
3722
- var nodeCursorFileOps = {
3723
- mkdir: (path) => mkdir(path, { recursive: true }),
3724
- open: (path, flags, mode) => open(path, flags, mode),
3725
- rename,
3726
- unlink
3727
- };
3728
- var FileCursorStore = class {
3729
- resolvePathFn;
3730
- fs;
3731
- platform;
3732
- constructor(options) {
3733
- this.resolvePathFn = options.resolvePath;
3734
- this.fs = options.fs ?? nodeCursorFileOps;
3735
- this.platform = options.platform ?? process.platform;
3736
- }
3737
- async read(agentId) {
3738
- const filePath = this.resolvePathFn(agentId);
3739
- let raw;
3740
- try {
3741
- raw = await readFile(filePath, "utf8");
3742
- } catch (error) {
3743
- if (error.code === "ENOENT") {
3744
- return -1;
3745
- }
3746
- throw error;
3747
- }
3748
- const parsed = JSON.parse(raw);
3749
- if (!Number.isSafeInteger(parsed.cursor) || parsed.cursor < 0) {
3750
- throw new Error(`invalid cursor file: ${filePath}`);
3751
- }
3752
- return parsed.cursor;
3753
- }
3754
- async write(agentId, cursor) {
3755
- if (!Number.isSafeInteger(cursor) || cursor < 0) {
3756
- throw new Error(`invalid cursor: ${cursor}`);
3757
- }
3758
- const filePath = this.resolvePathFn(agentId);
3759
- const parentPath = dirname(filePath);
3760
- await this.fs.mkdir(parentPath);
3761
- const temporaryPath = `${filePath}.${process.pid}.${randomUUID()}.tmp`;
3762
- let temporaryHandle;
3763
- try {
3764
- temporaryHandle = await this.fs.open(temporaryPath, "wx", 384);
3765
- await temporaryHandle.writeFile(JSON.stringify({ cursor, updatedAt: Date.now() }), {
3766
- encoding: "utf8"
3767
- });
3768
- await temporaryHandle.sync();
3769
- await temporaryHandle.close();
3770
- temporaryHandle = void 0;
3771
- await this.fs.rename(temporaryPath, filePath);
3772
- await this.syncDirectory(parentPath);
3773
- } catch (error) {
3774
- if (temporaryHandle) {
3775
- try {
3776
- await temporaryHandle.close();
3777
- } catch {
3778
- }
3779
- }
3780
- try {
3781
- await this.fs.unlink(temporaryPath);
3782
- } catch {
3783
- }
3784
- throw error;
3785
- }
3786
- }
3787
- shouldIgnoreDirectorySyncError(error) {
3788
- const code = error.code;
3789
- return this.platform === "win32" && (code === "EPERM" || code === "EINVAL");
3790
- }
3791
- async syncDirectory(path) {
3792
- let directoryHandle;
3793
- try {
3794
- try {
3795
- directoryHandle = await this.fs.open(path, "r");
3796
- } catch (error) {
3797
- if (!this.shouldIgnoreDirectorySyncError(error)) {
3798
- throw error;
3799
- }
3800
- return;
3801
- }
3802
- try {
3803
- await directoryHandle.sync();
3804
- } catch (error) {
3805
- if (!this.shouldIgnoreDirectorySyncError(error)) {
3806
- throw error;
3807
- }
3808
- }
3809
- } catch (error) {
3810
- if (directoryHandle) {
3811
- try {
3812
- await directoryHandle.close();
3813
- } catch {
3814
- }
3815
- }
3816
- throw error;
3817
- }
3818
- if (directoryHandle) {
3819
- await directoryHandle.close();
3820
- }
3821
- }
3822
- };
3823
-
3824
3709
  // ../sdk/plugin-ts/dist/bpp/bpp-transport.js
3825
- import { randomUUID as randomUUID2 } from "node:crypto";
3710
+ import { randomUUID } from "node:crypto";
3826
3711
 
3827
3712
  // ../../node_modules/.pnpm/ws@8.21.1/node_modules/ws/wrapper.mjs
3828
3713
  var import_stream = __toESM(require_stream(), 1);
@@ -3870,31 +3755,29 @@ var CONFIG_ACK_REASONS = /* @__PURE__ */ new Set([
3870
3755
  ]);
3871
3756
  var WS_CLOSE_AUTH_FAILED = 4004;
3872
3757
  var RESULT_STATUS_OK = "ok";
3758
+ var STOP_TURN_ACTION = "stop_turn";
3759
+ var STOP_TURN_CHANNEL_ID_INVALID = "stop_turn_channel_id_invalid";
3760
+ var SERVER_REQUEST_FAILED = "server_request_failed";
3873
3761
  var BppTransport = class {
3874
3762
  performTimeoutMs;
3875
3763
  heartbeatIntervalMs;
3876
3764
  reconnectBaseMs;
3877
3765
  reconnectMaxMs;
3878
3766
  autoReconnect;
3879
- resumeAckTimeoutMs;
3880
3767
  configApplyTimeoutMs;
3881
3768
  ctx;
3882
3769
  handlers;
3883
3770
  serverRequestHandler;
3771
+ stopTurnHandler;
3884
3772
  ws;
3885
3773
  pending = /* @__PURE__ */ new Map();
3886
- legacyAckSockets = /* @__PURE__ */ new WeakSet();
3887
- legacyReconnectSockets = /* @__PURE__ */ new WeakSet();
3888
3774
  closed = false;
3889
- // ONLINE = resume acknowledged and replay drained. Outbound semantic actions gate on this,
3890
- // not on a raw open socket, so a send issued during reconnect never races ahead of session.resume.
3775
+ // ONLINE = this socket's handshake has been written and its identity is known.
3776
+ // Outbound semantic actions gate on this rather than on a raw open socket, so a
3777
+ // send issued during a reconnect never races ahead of the handshake.
3891
3778
  online = false;
3892
3779
  terminal = false;
3893
- resumeWaiter;
3894
- replayRemaining = 0;
3895
- replayHighWater = -1;
3896
- lastConsumedCursor = -1;
3897
- cursorFailed = false;
3780
+ inboundFailed = false;
3898
3781
  inboundChain = Promise.resolve();
3899
3782
  configChain = Promise.resolve();
3900
3783
  configAcks = /* @__PURE__ */ new Map();
@@ -3903,7 +3786,8 @@ var BppTransport = class {
3903
3786
  reconnectAttempt = 0;
3904
3787
  heartbeatTimer;
3905
3788
  reconnectTimer;
3906
- // connect 后经 get_me 解析到的 agentId (opts 未给时)。用于 cursor + 上行帧。
3789
+ // The agent id resolved by get_me when the caller supplied none; stamped on
3790
+ // every outbound frame that names an agent.
3907
3791
  agentId = "";
3908
3792
  constructor(options = {}) {
3909
3793
  this.performTimeoutMs = options.performTimeoutMs ?? 3e4;
@@ -3911,15 +3795,11 @@ var BppTransport = class {
3911
3795
  this.reconnectBaseMs = options.reconnectBaseMs ?? 500;
3912
3796
  this.reconnectMaxMs = options.reconnectMaxMs ?? 3e4;
3913
3797
  this.autoReconnect = options.autoReconnect ?? true;
3914
- this.resumeAckTimeoutMs = options.resumeAckTimeoutMs ?? 5e3;
3915
3798
  this.configApplyTimeoutMs = options.configApplyTimeoutMs ?? 5e3;
3916
3799
  }
3917
3800
  get logger() {
3918
3801
  return this.ctx?.logger;
3919
3802
  }
3920
- get cursorStore() {
3921
- return this.ctx?.cursorStore;
3922
- }
3923
3803
  currentAgentId() {
3924
3804
  return this.agentId || this.ctx?.agentId || "";
3925
3805
  }
@@ -3929,6 +3809,9 @@ var BppTransport = class {
3929
3809
  setServerRequestHandler(handler) {
3930
3810
  this.serverRequestHandler = handler;
3931
3811
  }
3812
+ setStopTurnHandler(handler) {
3813
+ this.stopTurnHandler = handler;
3814
+ }
3932
3815
  getResolvedAgentId() {
3933
3816
  return this.currentAgentId();
3934
3817
  }
@@ -3948,8 +3831,6 @@ var BppTransport = class {
3948
3831
  this.online = false;
3949
3832
  this.stopHeartbeat();
3950
3833
  this.rejectReconnectWaiters(new BorgeeError("transport not connected", "bpp.not_connected"));
3951
- if (this.resumeWaiter)
3952
- this.rejectResumeWaiter(this.resumeWaiter.ws, new BorgeeError("transport closed", "bpp.transport_closed"));
3953
3834
  this.rejectAllPending(new BorgeeError("transport closed", "bpp.transport_closed"));
3954
3835
  const ws = this.ws;
3955
3836
  this.ws = void 0;
@@ -3974,17 +3855,21 @@ var BppTransport = class {
3974
3855
  this.sendTyping(action);
3975
3856
  return void 0;
3976
3857
  }
3858
+ if (action.op === "report_activity") {
3859
+ this.sendActivity(action);
3860
+ return void 0;
3861
+ }
3977
3862
  const deadline = Date.now() + this.performTimeoutMs;
3978
3863
  if (this.closed || this.terminal) {
3979
3864
  throw new BorgeeError("transport not connected", "bpp.not_connected");
3980
3865
  }
3981
- if (opts?.preResume) {
3866
+ if (opts?.handshake) {
3982
3867
  if (!this.isOpen())
3983
3868
  throw new BorgeeError("transport not connected", "bpp.not_connected");
3984
3869
  } else if (!this.online) {
3985
3870
  await this.waitUntilOnline(action.op, deadline);
3986
3871
  }
3987
- const nonce = randomUUID2();
3872
+ const nonce = randomUUID();
3988
3873
  const frame = {
3989
3874
  type: "semantic_action",
3990
3875
  agent_id: this.currentAgentId(),
@@ -4004,7 +3889,7 @@ var BppTransport = class {
4004
3889
  }
4005
3890
  // ---- socket 生命周期 ----
4006
3891
  // Transition the given socket to ONLINE. Bails if the socket is stale (a reconnect replaced it),
4007
- // so a late replay-completion from a prior socket cannot flip online across a reconnect.
3892
+ // so a handshake completing on a prior socket cannot flip online across a reconnect.
4008
3893
  goOnline(source) {
4009
3894
  if (!source || source !== this.ws)
4010
3895
  return;
@@ -4015,7 +3900,7 @@ var BppTransport = class {
4015
3900
  isOpen() {
4016
3901
  return this.ws !== void 0 && this.ws.readyState === import_websocket.default.OPEN;
4017
3902
  }
4018
- openSocket(mode = "canonical") {
3903
+ openSocket() {
4019
3904
  const ctx = this.ctx;
4020
3905
  if (!ctx)
4021
3906
  throw new BorgeeError("connect() called without context", "bpp.no_context");
@@ -4043,7 +3928,7 @@ var BppTransport = class {
4043
3928
  settled = true;
4044
3929
  reject(error);
4045
3930
  }
4046
- }, mode);
3931
+ });
4047
3932
  });
4048
3933
  ws.on("message", (data) => {
4049
3934
  this.onMessage(rawToString(data), ws);
@@ -4056,11 +3941,7 @@ var BppTransport = class {
4056
3941
  }
4057
3942
  });
4058
3943
  ws.on("close", (code, reason) => {
4059
- const legacyReconnect = this.legacyReconnectSockets.has(ws);
4060
- if (legacyReconnect)
4061
- this.legacyReconnectSockets.delete(ws);
4062
3944
  connectionAbort.abort();
4063
- this.rejectResumeWaiter(ws, new BorgeeError("ws closed before resume acknowledgement", "bpp.resume_incomplete"));
4064
3945
  const wasCurrent = this.ws === ws;
4065
3946
  if (wasCurrent) {
4066
3947
  this.online = false;
@@ -4077,11 +3958,11 @@ var BppTransport = class {
4077
3958
  if (wasCurrent) {
4078
3959
  this.rejectAllPending(authFailed ? new BorgeeError("authentication failed: invalid or revoked API key", "bpp.auth_failed") : new BorgeeError("connection lost", "bpp.connection_lost"));
4079
3960
  }
4080
- if (!settled && !legacyReconnect) {
3961
+ if (!settled) {
4081
3962
  settled = true;
4082
- reject(authFailed ? new BorgeeError("authentication failed: invalid or revoked API key", "bpp.auth_failed") : new BorgeeError(opened ? "ws closed before resume acknowledgement" : "ws closed before open", opened ? "bpp.resume_incomplete" : "bpp.connect_failed"));
3963
+ reject(authFailed ? new BorgeeError("authentication failed: invalid or revoked API key", "bpp.auth_failed") : new BorgeeError(opened ? "ws closed before the handshake was written" : "ws closed before open", opened ? "bpp.handshake_incomplete" : "bpp.connect_failed"));
4083
3964
  }
4084
- if (authFailed || legacyReconnect || !wasCurrent) {
3965
+ if (authFailed || !wasCurrent) {
4085
3966
  if (authFailed && wasCurrent) {
4086
3967
  this.rejectReconnectWaiters(new BorgeeError("authentication failed: invalid or revoked API key", "bpp.auth_failed"));
4087
3968
  }
@@ -4095,7 +3976,7 @@ var BppTransport = class {
4095
3976
  });
4096
3977
  });
4097
3978
  }
4098
- async onOpen(ws, resolve, reject, mode) {
3979
+ async onOpen(ws, resolve, reject) {
4099
3980
  const ctx = this.ctx;
4100
3981
  if (!ctx)
4101
3982
  return;
@@ -4109,155 +3990,21 @@ var BppTransport = class {
4109
3990
  this.sendTo(ws, connectFrame);
4110
3991
  try {
4111
3992
  if (!this.currentAgentId()) {
4112
- const identity = await this.perform({ op: "get_me" }, { preResume: true });
3993
+ const identity = await this.perform({ op: "get_me" }, { handshake: true });
4113
3994
  const resolved = identity && typeof identity === "object" ? identity.id : "";
4114
3995
  if (!resolved)
4115
3996
  throw new BorgeeError("authenticated agent identity could not be resolved", "bpp.identity_unresolved");
4116
3997
  this.agentId = resolved;
4117
3998
  }
4118
- const cursor = await this.cursorStore?.read(this.currentAgentId()) ?? -1;
4119
- this.lastConsumedCursor = Math.max(this.lastConsumedCursor, cursor);
4120
- if (mode === "legacy") {
4121
- this.sendLegacyReconnect(ws, Math.max(cursor, 0), resolve);
4122
- return;
4123
- }
4124
- this.sendResumeRequest(ws, Math.max(cursor, 0), resolve, reject);
3999
+ this.startHeartbeat();
4000
+ this.goOnline(ws);
4001
+ resolve();
4125
4002
  } catch (error) {
4126
- const failure = error instanceof BorgeeError ? error : new BorgeeError(`cursor read failed: ${String(error)}`, "bpp.cursor_store_read_failed");
4003
+ const failure = error instanceof BorgeeError ? error : new BorgeeError(`agent identity handshake failed: ${String(error)}`, "bpp.identity_unresolved");
4127
4004
  reject(failure);
4128
4005
  this.failTerminal(failure, ws);
4129
4006
  }
4130
4007
  }
4131
- sendResumeRequest(ws, sinceCursor, resolve, reject) {
4132
- const ctx = this.ctx;
4133
- if (!ctx || ws !== this.ws)
4134
- return;
4135
- const replayMode = ctx.replayMode ?? "latest_n";
4136
- const frame = {
4137
- type: "session.resume",
4138
- replay_mode: replayMode,
4139
- since_cursor: sinceCursor,
4140
- ...replayMode === "latest_n" ? { latest_n: ctx.replayLatestN ?? 200 } : {}
4141
- };
4142
- this.handlers?.onStateChange({ status: "resuming" });
4143
- const priorWaiter = this.resumeWaiter;
4144
- if (priorWaiter?.ws === ws) {
4145
- clearTimeout(priorWaiter.timer);
4146
- }
4147
- const timeoutMs = ctx.resumeAckTimeoutMs ?? this.resumeAckTimeoutMs;
4148
- let waiter;
4149
- const timer = setTimeout(() => {
4150
- if (this.resumeWaiter !== waiter || this.ws !== ws)
4151
- return;
4152
- this.resumeWaiter = void 0;
4153
- if (this.retryWithLegacyReconnect(ws, resolve, reject))
4154
- return;
4155
- const error = new BorgeeError("session.resume_ack timed out", "bpp.resume_ack_timeout");
4156
- reject(error);
4157
- this.failTerminal(error, ws);
4158
- }, timeoutMs);
4159
- waiter = { ws, resolve, reject, timer };
4160
- this.resumeWaiter = waiter;
4161
- this.send(frame);
4162
- }
4163
- sendLegacyReconnect(ws, sinceCursor, resolve) {
4164
- const ctx = this.ctx;
4165
- if (!ctx || ws !== this.ws || ws.readyState !== import_websocket.default.OPEN)
4166
- return;
4167
- this.legacyAckSockets.add(ws);
4168
- const now = Date.now();
4169
- const frame = {
4170
- type: "reconnect_handshake",
4171
- plugin_id: ctx.pluginId ?? "",
4172
- agent_id: this.currentAgentId(),
4173
- last_known_cursor: sinceCursor,
4174
- disconnect_at: now,
4175
- reconnect_at: now
4176
- };
4177
- this.replayRemaining = 0;
4178
- this.replayHighWater = Math.max(this.replayHighWater, sinceCursor);
4179
- this.sendTo(ws, frame);
4180
- resolve();
4181
- this.startHeartbeat();
4182
- this.goOnline(ws);
4183
- }
4184
- retryWithLegacyReconnect(ws, resolve, reject) {
4185
- if (ws !== this.ws || ws.readyState !== import_websocket.default.OPEN)
4186
- return false;
4187
- this.legacyReconnectSockets.add(ws);
4188
- try {
4189
- ws.close();
4190
- } catch {
4191
- this.legacyReconnectSockets.delete(ws);
4192
- return false;
4193
- }
4194
- void this.openSocket("legacy").then(resolve).catch(reject);
4195
- return true;
4196
- }
4197
- finishReplayIfReady(source) {
4198
- if (this.replayRemaining !== 0)
4199
- return Promise.resolve();
4200
- const highWater = this.replayHighWater;
4201
- if (highWater <= this.lastConsumedCursor) {
4202
- this.goOnline(source);
4203
- return Promise.resolve();
4204
- }
4205
- return this.persistCursor(highWater).then(() => {
4206
- this.goOnline(source);
4207
- });
4208
- }
4209
- handleResumeAck(frame, source) {
4210
- const waiter = this.resumeWaiter;
4211
- const compatibilityAck = source === this.ws && this.legacyAckSockets.has(source) && (!waiter || waiter.ws !== source);
4212
- if (!compatibilityAck && (!waiter || waiter.ws !== source || source !== this.ws))
4213
- return;
4214
- if (waiter?.ws === source)
4215
- clearTimeout(waiter.timer);
4216
- if (!Number.isSafeInteger(frame.count) || frame.count < 0 || frame.count > 500 || !Number.isSafeInteger(frame.high_water) || frame.high_water < 0) {
4217
- if (waiter?.ws === source)
4218
- this.resumeWaiter = void 0;
4219
- if (compatibilityAck)
4220
- this.legacyAckSockets.delete(source);
4221
- const error = new BorgeeError("invalid session.resume_ack bounds", "bpp.resume_ack_invalid");
4222
- if (waiter?.ws === source)
4223
- waiter.reject(error);
4224
- this.failTerminal(error, source);
4225
- return;
4226
- }
4227
- if (frame.reset) {
4228
- if (waiter?.ws === source)
4229
- this.resumeWaiter = void 0;
4230
- void this.resetCursor(frame.high_water).then(() => {
4231
- if (compatibilityAck)
4232
- this.legacyAckSockets.delete(source);
4233
- this.replayRemaining = 0;
4234
- this.replayHighWater = frame.high_water;
4235
- waiter?.resolve();
4236
- this.startHeartbeat();
4237
- this.inboundChain = this.inboundChain.then(() => this.finishReplayIfReady(source)).catch((error) => {
4238
- this.failTerminal(this.cursorFailure(error), source);
4239
- });
4240
- }).catch((error) => {
4241
- const failure = this.cursorFailure(error);
4242
- waiter?.reject(failure);
4243
- this.failTerminal(failure, source);
4244
- });
4245
- return;
4246
- }
4247
- if (waiter?.ws === source)
4248
- this.resumeWaiter = void 0;
4249
- if (compatibilityAck)
4250
- this.legacyAckSockets.delete(source);
4251
- this.replayRemaining = frame.count;
4252
- this.replayHighWater = frame.high_water;
4253
- waiter?.resolve();
4254
- this.startHeartbeat();
4255
- if (frame.count === 0) {
4256
- this.inboundChain = this.inboundChain.then(() => this.finishReplayIfReady(source)).catch((error) => {
4257
- this.failTerminal(this.cursorFailure(error), source);
4258
- });
4259
- }
4260
- }
4261
4008
  scheduleReconnect() {
4262
4009
  if (this.closed || this.terminal || !this.autoReconnect)
4263
4010
  return;
@@ -4312,13 +4059,7 @@ var BppTransport = class {
4312
4059
  void this.handleServerRequest(env);
4313
4060
  break;
4314
4061
  case "inbound_message":
4315
- this.handleInbound(env, source);
4316
- break;
4317
- case "session.resume_ack":
4318
- this.handleResumeAck(env, source);
4319
- break;
4320
- case "session.summary":
4321
- this.handleSummary(env, source);
4062
+ this.handleInbound(env);
4322
4063
  break;
4323
4064
  case "semantic_action_result":
4324
4065
  this.handleActionResult(env);
@@ -4337,40 +4078,14 @@ var BppTransport = class {
4337
4078
  break;
4338
4079
  }
4339
4080
  }
4340
- handleInbound(frame, source) {
4341
- this.inboundChain = this.inboundChain.then(async () => {
4342
- if (this.cursorFailed)
4343
- return;
4344
- if (!Number.isSafeInteger(frame.cursor) || frame.cursor <= this.lastConsumedCursor) {
4345
- throw new BorgeeError(`non-monotonic inbound cursor ${frame.cursor}`, "bpp.cursor_non_monotonic");
4346
- }
4347
- const event = mapInbound(frame);
4348
- this.handlers?.onInbound(event);
4349
- await this.persistCursor(frame.cursor);
4350
- if (this.replayRemaining > 0) {
4351
- this.replayRemaining -= 1;
4352
- await this.finishReplayIfReady(source);
4353
- }
4354
- }).catch((error) => {
4355
- this.cursorFailed = true;
4356
- this.failTerminal(this.cursorFailure(error), this.ws);
4357
- });
4358
- }
4359
- handleSummary(frame, source) {
4360
- this.inboundChain = this.inboundChain.then(async () => {
4361
- if (this.cursorFailed)
4081
+ handleInbound(frame) {
4082
+ this.inboundChain = this.inboundChain.then(() => {
4083
+ if (this.inboundFailed)
4362
4084
  return;
4363
- if (!Number.isSafeInteger(frame.cursor) || frame.cursor < this.lastConsumedCursor)
4364
- throw new BorgeeError(`non-monotonic summary cursor ${frame.cursor}`, "bpp.cursor_non_monotonic");
4365
- this.handlers?.onReplaySummary({ cursor: frame.cursor, missedCount: frame.missed_count, sinceCursor: frame.since_cursor, throughCursor: frame.through_cursor, summary: frame.summary });
4366
- await this.persistCursor(frame.cursor);
4367
- if (this.replayRemaining > 0) {
4368
- this.replayRemaining -= 1;
4369
- await this.finishReplayIfReady(source);
4370
- }
4085
+ this.handlers?.onInbound(mapInbound(frame));
4371
4086
  }).catch((error) => {
4372
- this.cursorFailed = true;
4373
- this.failTerminal(this.cursorFailure(error), this.ws);
4087
+ this.inboundFailed = true;
4088
+ this.failTerminal(this.inboundFailure(error), this.ws);
4374
4089
  });
4375
4090
  }
4376
4091
  handleActionResult(frame) {
@@ -4380,7 +4095,7 @@ var BppTransport = class {
4380
4095
  this.pending.delete(frame.nonce);
4381
4096
  clearTimeout(p.timer);
4382
4097
  if (frame.status === RESULT_STATUS_OK) {
4383
- const result = decodeActionResult(p.op, frame.payload, frame.cursor);
4098
+ const result = decodeActionResult(p.op, frame.payload);
4384
4099
  if (p.op === "get_me" && result && typeof result === "object") {
4385
4100
  const id = result.id;
4386
4101
  if (id)
@@ -4509,6 +4224,10 @@ var BppTransport = class {
4509
4224
  return;
4510
4225
  const data = env.data ?? {};
4511
4226
  const action = typeof data.action === "string" ? data.action : "";
4227
+ if (action === STOP_TURN_ACTION) {
4228
+ await this.answerStopTurn(id, data);
4229
+ return;
4230
+ }
4512
4231
  if (!this.serverRequestHandler) {
4513
4232
  this.send({ type: "response", id, error: "no_server_request_handler" });
4514
4233
  return;
@@ -4520,6 +4239,40 @@ var BppTransport = class {
4520
4239
  this.send({ type: "response", id, error: err instanceof Error ? err.message : String(err) });
4521
4240
  }
4522
4241
  }
4242
+ // Precedence: the specific handler wins, an onServerRequest callback gets
4243
+ // stop_turn only while no specific one is registered, and with neither the
4244
+ // SDK answers the request itself. That order is what lets a plugin that
4245
+ // already switches on the action name inside one callback keep working
4246
+ // unchanged, and lets it move onto onStopTurn one action at a time.
4247
+ async answerStopTurn(id, data) {
4248
+ const channelId = data.channel_id;
4249
+ if (typeof channelId !== "string" || channelId === "") {
4250
+ this.send({ type: "response", id, error: STOP_TURN_CHANNEL_ID_INVALID });
4251
+ return;
4252
+ }
4253
+ const specific = this.stopTurnHandler;
4254
+ const generic = this.serverRequestHandler;
4255
+ try {
4256
+ const answer = specific ? await specific(channelId) : generic ? await generic(STOP_TURN_ACTION, data) : { aborted: false };
4257
+ this.send({ type: "response", id, data: this.asStopTurnResult(answer) });
4258
+ } catch (err) {
4259
+ this.logger?.warn("bpp.stop_turn_handler_failed", err);
4260
+ this.send({ type: "response", id, error: SERVER_REQUEST_FAILED });
4261
+ }
4262
+ }
4263
+ // The rail's answer is one boolean and nothing else, so that is what goes on
4264
+ // the wire whatever the handler returned. A callback switching on action names
4265
+ // answers actions it does not implement with something else entirely — and
4266
+ // "the callback does not implement stopping" is one of the things false
4267
+ // already covers, so it is reported as such rather than as a broken agent.
4268
+ asStopTurnResult(answer) {
4269
+ const aborted = answer?.aborted;
4270
+ if (typeof aborted !== "boolean") {
4271
+ this.logger?.warn("bpp.stop_turn_answer_unusable", answer);
4272
+ return { aborted: false };
4273
+ }
4274
+ return { aborted };
4275
+ }
4523
4276
  // ---- task 生命周期上行 ----
4524
4277
  sendTaskStarted(action) {
4525
4278
  const frame = {
@@ -4544,6 +4297,59 @@ var BppTransport = class {
4544
4297
  };
4545
4298
  this.send(frame);
4546
4299
  }
4300
+ // sendActivity flattens the four-shape union onto one frame. The union is
4301
+ // how a producer says which shape it is reporting; the wire puts the
4302
+ // discriminator beside its payload with no wrapper object, matching both the
4303
+ // protocol this relays and inbound_message's own `kind`.
4304
+ //
4305
+ // The frame names no agent: identity is stamped by the server from the
4306
+ // authenticated connection, so nothing here can report as another agent.
4307
+ sendActivity(action) {
4308
+ const frame = {
4309
+ type: "agent_activity",
4310
+ channel_id: action.channelId,
4311
+ sequence: action.sequence,
4312
+ shape: action.activity.shape,
4313
+ activity_id: "",
4314
+ label: "",
4315
+ description: "",
4316
+ kind: "",
4317
+ status: "",
4318
+ detail: "",
4319
+ parent_id: "",
4320
+ subagent: false,
4321
+ reason: "",
4322
+ stream: "",
4323
+ text: "",
4324
+ turn_state: ""
4325
+ };
4326
+ switch (action.activity.shape) {
4327
+ case "activity":
4328
+ frame.activity_id = action.activity.id;
4329
+ frame.label = action.activity.label ?? "";
4330
+ frame.description = action.activity.description ?? "";
4331
+ frame.kind = action.activity.kind ?? "";
4332
+ frame.status = action.activity.status ?? "";
4333
+ frame.detail = action.activity.detail ?? "";
4334
+ frame.parent_id = action.activity.parentId ?? "";
4335
+ frame.subagent = action.activity.subagent ?? false;
4336
+ frame.reason = action.activity.reason ?? "";
4337
+ if (action.activity.paths)
4338
+ frame.paths = action.activity.paths;
4339
+ break;
4340
+ case "plan":
4341
+ frame.plan = action.activity.entries.map((entry) => ({ label: entry.label, status: entry.status }));
4342
+ break;
4343
+ case "output":
4344
+ frame.stream = action.activity.stream;
4345
+ frame.text = action.activity.text;
4346
+ break;
4347
+ case "turn":
4348
+ frame.turn_state = action.activity.state;
4349
+ break;
4350
+ }
4351
+ this.send(frame);
4352
+ }
4547
4353
  sendTyping(action) {
4548
4354
  this.send({ type: "typing", channel_id: action.channelId });
4549
4355
  }
@@ -4566,28 +4372,10 @@ var BppTransport = class {
4566
4372
  this.logger?.warn("bpp.send_failed", err);
4567
4373
  }
4568
4374
  }
4569
- async persistCursor(cursor) {
4570
- if (cursor <= this.lastConsumedCursor)
4571
- return;
4572
- await this.cursorStore?.write(this.currentAgentId(), cursor);
4573
- this.lastConsumedCursor = cursor;
4574
- }
4575
- async resetCursor(cursor) {
4576
- await this.cursorStore?.write(this.currentAgentId(), cursor);
4577
- this.lastConsumedCursor = cursor;
4578
- }
4579
- cursorFailure(error) {
4375
+ inboundFailure(error) {
4580
4376
  if (error instanceof BorgeeError)
4581
4377
  return error;
4582
- return new BorgeeError(`cursor persistence failed: ${String(error)}`, "bpp.cursor_store_write_failed");
4583
- }
4584
- rejectResumeWaiter(ws, error) {
4585
- const waiter = this.resumeWaiter;
4586
- if (!waiter || waiter.ws !== ws)
4587
- return;
4588
- clearTimeout(waiter.timer);
4589
- this.resumeWaiter = void 0;
4590
- waiter.reject(error);
4378
+ return new BorgeeError(`inbound dispatch failed: ${String(error)}`, "bpp.inbound_dispatch_failed");
4591
4379
  }
4592
4380
  failTerminal(error, ws) {
4593
4381
  if (this.terminal)
@@ -4595,8 +4383,6 @@ var BppTransport = class {
4595
4383
  this.terminal = true;
4596
4384
  this.online = false;
4597
4385
  this.handlers?.onStateChange({ status: "error", reason: "unknown", code: error.code });
4598
- if (ws)
4599
- this.rejectResumeWaiter(ws, error);
4600
4386
  this.rejectReconnectWaiters(error);
4601
4387
  this.rejectAllPending(error);
4602
4388
  try {
@@ -4739,11 +4525,11 @@ function encodeActionPayload(action) {
4739
4525
  return {};
4740
4526
  }
4741
4527
  }
4742
- function decodeActionResult(op, payloadJSON, frameCursor) {
4528
+ function decodeActionResult(op, payloadJSON) {
4743
4529
  const p = safeParseJSON(payloadJSON);
4744
4530
  switch (op) {
4745
4531
  case "send_message":
4746
- return { messageId: String(p.message_id ?? ""), cursor: frameCursor };
4532
+ return { messageId: String(p.message_id ?? "") };
4747
4533
  case "create_dm":
4748
4534
  return { channelId: String(p.channel_id ?? "") };
4749
4535
  case "get_me":
@@ -4856,7 +4642,6 @@ function mapInbound(frame) {
4856
4642
  const kind = frame.kind;
4857
4643
  const base = {
4858
4644
  kind,
4859
- cursor: frame.cursor,
4860
4645
  channelId: frame.channel_id,
4861
4646
  channelType: frame.channel_type,
4862
4647
  authorName: frame.author_name,
@@ -4931,6 +4716,102 @@ function rawToString(data) {
4931
4716
  return String(data);
4932
4717
  }
4933
4718
 
4719
+ // ../sdk/plugin-ts/dist/activity-reporter.js
4720
+ var DEFAULT_THROTTLE_MS = 150;
4721
+ var OUTPUT_TAIL_CHARS = 2e3;
4722
+ var defaultScheduler = (callback, delayMs) => {
4723
+ const timer = setTimeout(callback, delayMs);
4724
+ timer.unref?.();
4725
+ return () => clearTimeout(timer);
4726
+ };
4727
+ function outputTail(text) {
4728
+ if (text.length <= OUTPUT_TAIL_CHARS) {
4729
+ return text;
4730
+ }
4731
+ const tail = text.slice(text.length - OUTPUT_TAIL_CHARS);
4732
+ return /^[\uDC00-\uDFFF]/.test(tail) ? tail.slice(1) : tail;
4733
+ }
4734
+ function throttleKey(activity) {
4735
+ switch (activity.shape) {
4736
+ case "activity":
4737
+ return `activity:${activity.id}`;
4738
+ case "plan":
4739
+ return "plan";
4740
+ case "output":
4741
+ return `output:${activity.stream}`;
4742
+ }
4743
+ }
4744
+ var TurnActivityReporter = class {
4745
+ options;
4746
+ windows = /* @__PURE__ */ new Map();
4747
+ throttleMs;
4748
+ scheduler;
4749
+ ended = false;
4750
+ constructor(options) {
4751
+ this.options = options;
4752
+ this.throttleMs = options.throttleMs ?? DEFAULT_THROTTLE_MS;
4753
+ this.scheduler = options.scheduler ?? defaultScheduler;
4754
+ }
4755
+ start() {
4756
+ this.report({ shape: "turn", state: "started" });
4757
+ }
4758
+ progress(activity) {
4759
+ if (this.ended) {
4760
+ return;
4761
+ }
4762
+ this.offer(activity.shape === "output" ? { ...activity, text: outputTail(activity.text) } : activity);
4763
+ }
4764
+ /**
4765
+ * Ends the turn. What the windows still hold goes first, so the last thing a
4766
+ * reader sees of a turn is that turn's own last word rather than whatever
4767
+ * happened to fall outside a window.
4768
+ */
4769
+ end() {
4770
+ if (this.ended) {
4771
+ return;
4772
+ }
4773
+ this.ended = true;
4774
+ this.drain();
4775
+ this.report({ shape: "turn", state: "ended" });
4776
+ }
4777
+ offer(activity) {
4778
+ const key = throttleKey(activity);
4779
+ const open = this.windows.get(key);
4780
+ if (open) {
4781
+ open.latest = activity;
4782
+ return;
4783
+ }
4784
+ this.report(activity);
4785
+ this.openWindow(key);
4786
+ }
4787
+ openWindow(key) {
4788
+ const window = { latest: null, cancel: null };
4789
+ window.cancel = this.scheduler(() => {
4790
+ this.windows.delete(key);
4791
+ if (window.latest) {
4792
+ this.offer(window.latest);
4793
+ }
4794
+ }, this.throttleMs);
4795
+ this.windows.set(key, window);
4796
+ }
4797
+ drain() {
4798
+ const held = [...this.windows.values()];
4799
+ this.windows.clear();
4800
+ for (const window of held) {
4801
+ window.cancel?.();
4802
+ if (window.latest) {
4803
+ this.report(window.latest);
4804
+ }
4805
+ }
4806
+ }
4807
+ report(activity) {
4808
+ if (this.options.canReport && !this.options.canReport()) {
4809
+ return;
4810
+ }
4811
+ this.options.deliver({ channelId: this.options.channelId, activity });
4812
+ }
4813
+ };
4814
+
4934
4815
  // ../sdk/plugin-ts/dist/client.js
4935
4816
  var noopLogger = {
4936
4817
  debug() {
@@ -4946,21 +4827,19 @@ var Client = class {
4946
4827
  opts;
4947
4828
  t;
4948
4829
  logger;
4949
- cursorStore;
4950
4830
  _agentId;
4831
+ activitySequence = 0;
4951
4832
  _state = { status: "connecting" };
4952
4833
  listeners = {
4953
4834
  message: /* @__PURE__ */ new Set(),
4954
4835
  configUpdate: /* @__PURE__ */ new Set(),
4955
4836
  permissionDenied: /* @__PURE__ */ new Set(),
4956
- connectionState: /* @__PURE__ */ new Set(),
4957
- replaySummary: /* @__PURE__ */ new Set()
4837
+ connectionState: /* @__PURE__ */ new Set()
4958
4838
  };
4959
4839
  constructor(opts, transport) {
4960
4840
  this.opts = opts;
4961
4841
  this.t = transport;
4962
4842
  this.logger = opts.logger ?? noopLogger;
4963
- this.cursorStore = opts.cursorStore ?? new MemoryCursorStore();
4964
4843
  this._agentId = opts.agentId ?? "";
4965
4844
  }
4966
4845
  get agentId() {
@@ -5060,6 +4939,31 @@ var Client = class {
5060
4939
  reason: input.reason
5061
4940
  }).catch((err) => this.logger.warn("reportTaskFinished failed", err));
5062
4941
  }
4942
+ reportActivity(input) {
4943
+ void this.t.perform({
4944
+ op: "report_activity",
4945
+ channelId: input.channelId,
4946
+ sequence: this.nextActivitySequence(),
4947
+ activity: input.activity
4948
+ }).catch((err) => this.logger.warn("reportActivity failed", err));
4949
+ }
4950
+ reportTurnActivity(input) {
4951
+ return new TurnActivityReporter({
4952
+ ...input,
4953
+ deliver: (report) => this.reportActivity(report)
4954
+ });
4955
+ }
4956
+ /**
4957
+ * The counter every activity report carries. It belongs to the connection
4958
+ * rather than to a producer: both readers use it only to drop anything not
4959
+ * newer, so two producers on one agent numbering independently would silently
4960
+ * erase each other, and the failure would present as a missing report rather
4961
+ * than as a wrong one. Nothing outside this client can supply or observe it.
4962
+ */
4963
+ nextActivitySequence() {
4964
+ this.activitySequence += 1;
4965
+ return this.activitySequence;
4966
+ }
5063
4967
  // ─── Task CRUD (BPP semantic actions) ───────────────────
5064
4968
  // Semantic actions rather than REST: the blueprint's protocol red line
5065
4969
  // (plugin-protocol.md 1.3) is that a plugin does not reach past the semantic
@@ -5143,6 +5047,9 @@ var Client = class {
5143
5047
  onServerRequest(handler) {
5144
5048
  this.t.setServerRequestHandler?.(handler);
5145
5049
  }
5050
+ onStopTurn(handler) {
5051
+ this.t.setStopTurnHandler?.(handler);
5052
+ }
5146
5053
  emit(event, payload) {
5147
5054
  for (const h of this.listeners[event])
5148
5055
  h(payload);
@@ -5153,7 +5060,6 @@ var Client = class {
5153
5060
  onConfigUpdate: (u) => this.emit("configUpdate", u),
5154
5061
  applyConfigUpdate: this.opts.configUpdateHandler,
5155
5062
  onPermissionDenied: (d) => this.emit("permissionDenied", d),
5156
- onReplaySummary: (summary) => this.emit("replaySummary", summary),
5157
5063
  onStateChange: (s) => {
5158
5064
  this._state = s;
5159
5065
  this.emit("connectionState", s);
@@ -5166,12 +5072,8 @@ var Client = class {
5166
5072
  apiKey: this.opts.apiKey,
5167
5073
  agentId: this._agentId,
5168
5074
  pluginId: this.opts.pluginId,
5169
- cursorStore: this.cursorStore,
5170
5075
  logger: this.logger,
5171
- resumeAckTimeoutMs: this.opts.resumeAckTimeoutMs,
5172
- configApplyTimeoutMs: this.opts.configApplyTimeoutMs,
5173
- replayMode: this.opts.replayMode,
5174
- replayLatestN: this.opts.replayLatestN
5076
+ configApplyTimeoutMs: this.opts.configApplyTimeoutMs
5175
5077
  };
5176
5078
  }
5177
5079
  };
@@ -5180,8 +5082,6 @@ function createBorgeePlugin(opts) {
5180
5082
  }
5181
5083
  export {
5182
5084
  BorgeeError,
5183
- FileCursorStore,
5184
- MemoryCursorStore,
5185
5085
  PermissionDeniedError,
5186
5086
  createBorgeePlugin
5187
5087
  };