@borgee/agents-host 0.2.44 → 0.2.56

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 (56) hide show
  1. package/README.md +23 -27
  2. package/dist/agents-host.d.ts +26 -5
  3. package/dist/agents-host.js +163 -192
  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 +18 -1
  10. package/dist/context/claude-file-brief.d.ts +2 -0
  11. package/dist/context/claude-file-brief.js +83 -0
  12. package/dist/context/compaction.d.ts +20 -0
  13. package/dist/context/compaction.js +59 -0
  14. package/dist/context/injection.d.ts +39 -6
  15. package/dist/context/injection.js +317 -26
  16. package/dist/context/main-session-delegation.d.ts +1 -1
  17. package/dist/context/projection-strategy.d.ts +24 -0
  18. package/dist/context/projection-strategy.js +90 -0
  19. package/dist/context/prompt.d.ts +16 -1
  20. package/dist/context/prompt.js +456 -22
  21. package/dist/context/resolved-workspace.d.ts +2 -0
  22. package/dist/context/resolved-workspace.js +64 -0
  23. package/dist/context/skill-manual.d.ts +1 -0
  24. package/dist/context/skill-manual.js +4 -1
  25. package/dist/context/turn-preparation.d.ts +8 -2
  26. package/dist/context/turn-preparation.js +56 -14
  27. package/dist/gateway/localhost-gateway.js +2 -0
  28. package/dist/managed-daemon.js +122 -9
  29. package/dist/plugin-sdk.js +276 -359
  30. package/dist/plugin-sdk.js.map +4 -4
  31. package/dist/progress-to-activity.d.ts +16 -0
  32. package/dist/progress-to-activity.js +24 -0
  33. package/dist/projection-strategy-values.d.ts +4 -0
  34. package/dist/projection-strategy-values.js +28 -0
  35. package/dist/providers/acp-progress-collector.d.ts +44 -0
  36. package/dist/providers/acp-progress-collector.js +130 -0
  37. package/dist/providers/awaiting-user.d.ts +2 -3
  38. package/dist/providers/awaiting-user.js +5 -7
  39. package/dist/providers/claude/activity-metadata.d.ts +14 -0
  40. package/dist/providers/claude/activity-metadata.js +81 -0
  41. package/dist/providers/claude/cli-client.d.ts +1 -2
  42. package/dist/providers/claude/cli-client.js +190 -117
  43. package/dist/providers/codex/cli-client.js +3 -83
  44. package/dist/providers/codex/project-doc.js +12 -11
  45. package/dist/providers/copilot/cli-client.js +3 -83
  46. package/dist/providers/create-provider.d.ts +2 -0
  47. package/dist/providers/create-provider.js +20 -4
  48. package/dist/state-paths.d.ts +1 -1
  49. package/dist/state-paths.js +3 -3
  50. package/dist/task-thread-resolution.d.ts +3 -2
  51. package/dist/types.d.ts +130 -6
  52. package/package.json +2 -2
  53. package/skills/borgee-agent/SKILL.md +9 -1
  54. package/skills/borgee-agent/references/task-properties.md +5 -2
  55. package/dist/durable-cursor-store.d.ts +0 -5
  56. 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);
@@ -3869,32 +3754,33 @@ var CONFIG_ACK_REASONS = /* @__PURE__ */ new Set([
3869
3754
  "unknown"
3870
3755
  ]);
3871
3756
  var WS_CLOSE_AUTH_FAILED = 4004;
3757
+ var RESUME_REPLAY_MODE = "latest_n";
3758
+ var RESUME_SINCE_CURSOR = 0;
3759
+ var RESUME_LATEST_N = 200;
3872
3760
  var RESULT_STATUS_OK = "ok";
3761
+ var STOP_TURN_ACTION = "stop_turn";
3762
+ var STOP_TURN_CHANNEL_ID_INVALID = "stop_turn_channel_id_invalid";
3763
+ var SERVER_REQUEST_FAILED = "server_request_failed";
3873
3764
  var BppTransport = class {
3874
3765
  performTimeoutMs;
3875
3766
  heartbeatIntervalMs;
3876
3767
  reconnectBaseMs;
3877
3768
  reconnectMaxMs;
3878
3769
  autoReconnect;
3879
- resumeAckTimeoutMs;
3880
3770
  configApplyTimeoutMs;
3881
3771
  ctx;
3882
3772
  handlers;
3883
3773
  serverRequestHandler;
3774
+ stopTurnHandler;
3884
3775
  ws;
3885
3776
  pending = /* @__PURE__ */ new Map();
3886
- legacyAckSockets = /* @__PURE__ */ new WeakSet();
3887
- legacyReconnectSockets = /* @__PURE__ */ new WeakSet();
3888
3777
  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.
3778
+ // ONLINE = this socket's handshake has been written and its identity is known.
3779
+ // Outbound semantic actions gate on this rather than on a raw open socket, so a
3780
+ // send issued during a reconnect never races ahead of the handshake.
3891
3781
  online = false;
3892
3782
  terminal = false;
3893
- resumeWaiter;
3894
- replayRemaining = 0;
3895
- replayHighWater = -1;
3896
- lastConsumedCursor = -1;
3897
- cursorFailed = false;
3783
+ inboundFailed = false;
3898
3784
  inboundChain = Promise.resolve();
3899
3785
  configChain = Promise.resolve();
3900
3786
  configAcks = /* @__PURE__ */ new Map();
@@ -3903,7 +3789,8 @@ var BppTransport = class {
3903
3789
  reconnectAttempt = 0;
3904
3790
  heartbeatTimer;
3905
3791
  reconnectTimer;
3906
- // connect 后经 get_me 解析到的 agentId (opts 未给时)。用于 cursor + 上行帧。
3792
+ // The agent id resolved by get_me when the caller supplied none; stamped on
3793
+ // every outbound frame that names an agent.
3907
3794
  agentId = "";
3908
3795
  constructor(options = {}) {
3909
3796
  this.performTimeoutMs = options.performTimeoutMs ?? 3e4;
@@ -3911,15 +3798,11 @@ var BppTransport = class {
3911
3798
  this.reconnectBaseMs = options.reconnectBaseMs ?? 500;
3912
3799
  this.reconnectMaxMs = options.reconnectMaxMs ?? 3e4;
3913
3800
  this.autoReconnect = options.autoReconnect ?? true;
3914
- this.resumeAckTimeoutMs = options.resumeAckTimeoutMs ?? 5e3;
3915
3801
  this.configApplyTimeoutMs = options.configApplyTimeoutMs ?? 5e3;
3916
3802
  }
3917
3803
  get logger() {
3918
3804
  return this.ctx?.logger;
3919
3805
  }
3920
- get cursorStore() {
3921
- return this.ctx?.cursorStore;
3922
- }
3923
3806
  currentAgentId() {
3924
3807
  return this.agentId || this.ctx?.agentId || "";
3925
3808
  }
@@ -3929,6 +3812,9 @@ var BppTransport = class {
3929
3812
  setServerRequestHandler(handler) {
3930
3813
  this.serverRequestHandler = handler;
3931
3814
  }
3815
+ setStopTurnHandler(handler) {
3816
+ this.stopTurnHandler = handler;
3817
+ }
3932
3818
  getResolvedAgentId() {
3933
3819
  return this.currentAgentId();
3934
3820
  }
@@ -3948,8 +3834,6 @@ var BppTransport = class {
3948
3834
  this.online = false;
3949
3835
  this.stopHeartbeat();
3950
3836
  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
3837
  this.rejectAllPending(new BorgeeError("transport closed", "bpp.transport_closed"));
3954
3838
  const ws = this.ws;
3955
3839
  this.ws = void 0;
@@ -3974,17 +3858,21 @@ var BppTransport = class {
3974
3858
  this.sendTyping(action);
3975
3859
  return void 0;
3976
3860
  }
3861
+ if (action.op === "report_activity") {
3862
+ this.sendActivity(action);
3863
+ return void 0;
3864
+ }
3977
3865
  const deadline = Date.now() + this.performTimeoutMs;
3978
3866
  if (this.closed || this.terminal) {
3979
3867
  throw new BorgeeError("transport not connected", "bpp.not_connected");
3980
3868
  }
3981
- if (opts?.preResume) {
3869
+ if (opts?.handshake) {
3982
3870
  if (!this.isOpen())
3983
3871
  throw new BorgeeError("transport not connected", "bpp.not_connected");
3984
3872
  } else if (!this.online) {
3985
3873
  await this.waitUntilOnline(action.op, deadline);
3986
3874
  }
3987
- const nonce = randomUUID2();
3875
+ const nonce = randomUUID();
3988
3876
  const frame = {
3989
3877
  type: "semantic_action",
3990
3878
  agent_id: this.currentAgentId(),
@@ -4004,7 +3892,7 @@ var BppTransport = class {
4004
3892
  }
4005
3893
  // ---- socket 生命周期 ----
4006
3894
  // 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.
3895
+ // so a handshake completing on a prior socket cannot flip online across a reconnect.
4008
3896
  goOnline(source) {
4009
3897
  if (!source || source !== this.ws)
4010
3898
  return;
@@ -4015,7 +3903,7 @@ var BppTransport = class {
4015
3903
  isOpen() {
4016
3904
  return this.ws !== void 0 && this.ws.readyState === import_websocket.default.OPEN;
4017
3905
  }
4018
- openSocket(mode = "canonical") {
3906
+ openSocket() {
4019
3907
  const ctx = this.ctx;
4020
3908
  if (!ctx)
4021
3909
  throw new BorgeeError("connect() called without context", "bpp.no_context");
@@ -4043,7 +3931,7 @@ var BppTransport = class {
4043
3931
  settled = true;
4044
3932
  reject(error);
4045
3933
  }
4046
- }, mode);
3934
+ });
4047
3935
  });
4048
3936
  ws.on("message", (data) => {
4049
3937
  this.onMessage(rawToString(data), ws);
@@ -4056,11 +3944,7 @@ var BppTransport = class {
4056
3944
  }
4057
3945
  });
4058
3946
  ws.on("close", (code, reason) => {
4059
- const legacyReconnect = this.legacyReconnectSockets.has(ws);
4060
- if (legacyReconnect)
4061
- this.legacyReconnectSockets.delete(ws);
4062
3947
  connectionAbort.abort();
4063
- this.rejectResumeWaiter(ws, new BorgeeError("ws closed before resume acknowledgement", "bpp.resume_incomplete"));
4064
3948
  const wasCurrent = this.ws === ws;
4065
3949
  if (wasCurrent) {
4066
3950
  this.online = false;
@@ -4077,11 +3961,11 @@ var BppTransport = class {
4077
3961
  if (wasCurrent) {
4078
3962
  this.rejectAllPending(authFailed ? new BorgeeError("authentication failed: invalid or revoked API key", "bpp.auth_failed") : new BorgeeError("connection lost", "bpp.connection_lost"));
4079
3963
  }
4080
- if (!settled && !legacyReconnect) {
3964
+ if (!settled) {
4081
3965
  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"));
3966
+ 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
3967
  }
4084
- if (authFailed || legacyReconnect || !wasCurrent) {
3968
+ if (authFailed || !wasCurrent) {
4085
3969
  if (authFailed && wasCurrent) {
4086
3970
  this.rejectReconnectWaiters(new BorgeeError("authentication failed: invalid or revoked API key", "bpp.auth_failed"));
4087
3971
  }
@@ -4095,7 +3979,7 @@ var BppTransport = class {
4095
3979
  });
4096
3980
  });
4097
3981
  }
4098
- async onOpen(ws, resolve, reject, mode) {
3982
+ async onOpen(ws, resolve, reject) {
4099
3983
  const ctx = this.ctx;
4100
3984
  if (!ctx)
4101
3985
  return;
@@ -4109,154 +3993,33 @@ var BppTransport = class {
4109
3993
  this.sendTo(ws, connectFrame);
4110
3994
  try {
4111
3995
  if (!this.currentAgentId()) {
4112
- const identity = await this.perform({ op: "get_me" }, { preResume: true });
3996
+ const identity = await this.perform({ op: "get_me" }, { handshake: true });
4113
3997
  const resolved = identity && typeof identity === "object" ? identity.id : "";
4114
3998
  if (!resolved)
4115
3999
  throw new BorgeeError("authenticated agent identity could not be resolved", "bpp.identity_unresolved");
4116
4000
  this.agentId = resolved;
4117
4001
  }
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);
4002
+ this.sendResumeRequest(ws);
4003
+ this.startHeartbeat();
4004
+ this.goOnline(ws);
4005
+ resolve();
4125
4006
  } catch (error) {
4126
- const failure = error instanceof BorgeeError ? error : new BorgeeError(`cursor read failed: ${String(error)}`, "bpp.cursor_store_read_failed");
4007
+ const failure = error instanceof BorgeeError ? error : new BorgeeError(`agent identity handshake failed: ${String(error)}`, "bpp.identity_unresolved");
4127
4008
  reject(failure);
4128
4009
  this.failTerminal(failure, ws);
4129
4010
  }
4130
4011
  }
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";
4012
+ // The resume is written for one reader: a server that still holds durable
4013
+ // frames behind a pre-ack barrier releases it when this frame arrives. Its
4014
+ // answer is not read, so the request asks for nothing.
4015
+ sendResumeRequest(ws) {
4136
4016
  const frame = {
4137
4017
  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
4018
+ replay_mode: RESUME_REPLAY_MODE,
4019
+ since_cursor: RESUME_SINCE_CURSOR,
4020
+ latest_n: RESUME_LATEST_N
4176
4021
  };
4177
- this.replayRemaining = 0;
4178
- this.replayHighWater = Math.max(this.replayHighWater, sinceCursor);
4179
4022
  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
4023
  }
4261
4024
  scheduleReconnect() {
4262
4025
  if (this.closed || this.terminal || !this.autoReconnect)
@@ -4312,13 +4075,7 @@ var BppTransport = class {
4312
4075
  void this.handleServerRequest(env);
4313
4076
  break;
4314
4077
  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);
4078
+ this.handleInbound(env);
4322
4079
  break;
4323
4080
  case "semantic_action_result":
4324
4081
  this.handleActionResult(env);
@@ -4329,6 +4086,7 @@ var BppTransport = class {
4329
4086
  case "agent_config_update":
4330
4087
  this.handleConfigUpdate(env, source);
4331
4088
  break;
4089
+ case "session.resume_ack":
4332
4090
  case "pong":
4333
4091
  case "ping":
4334
4092
  case "agent_toggle":
@@ -4337,40 +4095,14 @@ var BppTransport = class {
4337
4095
  break;
4338
4096
  }
4339
4097
  }
4340
- handleInbound(frame, source) {
4341
- this.inboundChain = this.inboundChain.then(async () => {
4342
- if (this.cursorFailed)
4098
+ handleInbound(frame) {
4099
+ this.inboundChain = this.inboundChain.then(() => {
4100
+ if (this.inboundFailed)
4343
4101
  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)
4362
- 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
- }
4102
+ this.handlers?.onInbound(mapInbound(frame));
4371
4103
  }).catch((error) => {
4372
- this.cursorFailed = true;
4373
- this.failTerminal(this.cursorFailure(error), this.ws);
4104
+ this.inboundFailed = true;
4105
+ this.failTerminal(this.inboundFailure(error), this.ws);
4374
4106
  });
4375
4107
  }
4376
4108
  handleActionResult(frame) {
@@ -4380,7 +4112,7 @@ var BppTransport = class {
4380
4112
  this.pending.delete(frame.nonce);
4381
4113
  clearTimeout(p.timer);
4382
4114
  if (frame.status === RESULT_STATUS_OK) {
4383
- const result = decodeActionResult(p.op, frame.payload, frame.cursor);
4115
+ const result = decodeActionResult(p.op, frame.payload);
4384
4116
  if (p.op === "get_me" && result && typeof result === "object") {
4385
4117
  const id = result.id;
4386
4118
  if (id)
@@ -4509,6 +4241,10 @@ var BppTransport = class {
4509
4241
  return;
4510
4242
  const data = env.data ?? {};
4511
4243
  const action = typeof data.action === "string" ? data.action : "";
4244
+ if (action === STOP_TURN_ACTION) {
4245
+ await this.answerStopTurn(id, data);
4246
+ return;
4247
+ }
4512
4248
  if (!this.serverRequestHandler) {
4513
4249
  this.send({ type: "response", id, error: "no_server_request_handler" });
4514
4250
  return;
@@ -4520,6 +4256,40 @@ var BppTransport = class {
4520
4256
  this.send({ type: "response", id, error: err instanceof Error ? err.message : String(err) });
4521
4257
  }
4522
4258
  }
4259
+ // Precedence: the specific handler wins, an onServerRequest callback gets
4260
+ // stop_turn only while no specific one is registered, and with neither the
4261
+ // SDK answers the request itself. That order is what lets a plugin that
4262
+ // already switches on the action name inside one callback keep working
4263
+ // unchanged, and lets it move onto onStopTurn one action at a time.
4264
+ async answerStopTurn(id, data) {
4265
+ const channelId = data.channel_id;
4266
+ if (typeof channelId !== "string" || channelId === "") {
4267
+ this.send({ type: "response", id, error: STOP_TURN_CHANNEL_ID_INVALID });
4268
+ return;
4269
+ }
4270
+ const specific = this.stopTurnHandler;
4271
+ const generic = this.serverRequestHandler;
4272
+ try {
4273
+ const answer = specific ? await specific(channelId) : generic ? await generic(STOP_TURN_ACTION, data) : { aborted: false };
4274
+ this.send({ type: "response", id, data: this.asStopTurnResult(answer) });
4275
+ } catch (err) {
4276
+ this.logger?.warn("bpp.stop_turn_handler_failed", err);
4277
+ this.send({ type: "response", id, error: SERVER_REQUEST_FAILED });
4278
+ }
4279
+ }
4280
+ // The rail's answer is one boolean and nothing else, so that is what goes on
4281
+ // the wire whatever the handler returned. A callback switching on action names
4282
+ // answers actions it does not implement with something else entirely — and
4283
+ // "the callback does not implement stopping" is one of the things false
4284
+ // already covers, so it is reported as such rather than as a broken agent.
4285
+ asStopTurnResult(answer) {
4286
+ const aborted = answer?.aborted;
4287
+ if (typeof aborted !== "boolean") {
4288
+ this.logger?.warn("bpp.stop_turn_answer_unusable", answer);
4289
+ return { aborted: false };
4290
+ }
4291
+ return { aborted };
4292
+ }
4523
4293
  // ---- task 生命周期上行 ----
4524
4294
  sendTaskStarted(action) {
4525
4295
  const frame = {
@@ -4544,6 +4314,59 @@ var BppTransport = class {
4544
4314
  };
4545
4315
  this.send(frame);
4546
4316
  }
4317
+ // sendActivity flattens the four-shape union onto one frame. The union is
4318
+ // how a producer says which shape it is reporting; the wire puts the
4319
+ // discriminator beside its payload with no wrapper object, matching both the
4320
+ // protocol this relays and inbound_message's own `kind`.
4321
+ //
4322
+ // The frame names no agent: identity is stamped by the server from the
4323
+ // authenticated connection, so nothing here can report as another agent.
4324
+ sendActivity(action) {
4325
+ const frame = {
4326
+ type: "agent_activity",
4327
+ channel_id: action.channelId,
4328
+ sequence: action.sequence,
4329
+ shape: action.activity.shape,
4330
+ activity_id: "",
4331
+ label: "",
4332
+ description: "",
4333
+ kind: "",
4334
+ status: "",
4335
+ detail: "",
4336
+ parent_id: "",
4337
+ subagent: false,
4338
+ reason: "",
4339
+ stream: "",
4340
+ text: "",
4341
+ turn_state: ""
4342
+ };
4343
+ switch (action.activity.shape) {
4344
+ case "activity":
4345
+ frame.activity_id = action.activity.id;
4346
+ frame.label = action.activity.label ?? "";
4347
+ frame.description = action.activity.description ?? "";
4348
+ frame.kind = action.activity.kind ?? "";
4349
+ frame.status = action.activity.status ?? "";
4350
+ frame.detail = action.activity.detail ?? "";
4351
+ frame.parent_id = action.activity.parentId ?? "";
4352
+ frame.subagent = action.activity.subagent ?? false;
4353
+ frame.reason = action.activity.reason ?? "";
4354
+ if (action.activity.paths)
4355
+ frame.paths = action.activity.paths;
4356
+ break;
4357
+ case "plan":
4358
+ frame.plan = action.activity.entries.map((entry) => ({ label: entry.label, status: entry.status }));
4359
+ break;
4360
+ case "output":
4361
+ frame.stream = action.activity.stream;
4362
+ frame.text = action.activity.text;
4363
+ break;
4364
+ case "turn":
4365
+ frame.turn_state = action.activity.state;
4366
+ break;
4367
+ }
4368
+ this.send(frame);
4369
+ }
4547
4370
  sendTyping(action) {
4548
4371
  this.send({ type: "typing", channel_id: action.channelId });
4549
4372
  }
@@ -4566,28 +4389,10 @@ var BppTransport = class {
4566
4389
  this.logger?.warn("bpp.send_failed", err);
4567
4390
  }
4568
4391
  }
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) {
4392
+ inboundFailure(error) {
4580
4393
  if (error instanceof BorgeeError)
4581
4394
  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);
4395
+ return new BorgeeError(`inbound dispatch failed: ${String(error)}`, "bpp.inbound_dispatch_failed");
4591
4396
  }
4592
4397
  failTerminal(error, ws) {
4593
4398
  if (this.terminal)
@@ -4595,8 +4400,6 @@ var BppTransport = class {
4595
4400
  this.terminal = true;
4596
4401
  this.online = false;
4597
4402
  this.handlers?.onStateChange({ status: "error", reason: "unknown", code: error.code });
4598
- if (ws)
4599
- this.rejectResumeWaiter(ws, error);
4600
4403
  this.rejectReconnectWaiters(error);
4601
4404
  this.rejectAllPending(error);
4602
4405
  try {
@@ -4739,11 +4542,11 @@ function encodeActionPayload(action) {
4739
4542
  return {};
4740
4543
  }
4741
4544
  }
4742
- function decodeActionResult(op, payloadJSON, frameCursor) {
4545
+ function decodeActionResult(op, payloadJSON) {
4743
4546
  const p = safeParseJSON(payloadJSON);
4744
4547
  switch (op) {
4745
4548
  case "send_message":
4746
- return { messageId: String(p.message_id ?? ""), cursor: frameCursor };
4549
+ return { messageId: String(p.message_id ?? "") };
4747
4550
  case "create_dm":
4748
4551
  return { channelId: String(p.channel_id ?? "") };
4749
4552
  case "get_me":
@@ -4856,7 +4659,6 @@ function mapInbound(frame) {
4856
4659
  const kind = frame.kind;
4857
4660
  const base = {
4858
4661
  kind,
4859
- cursor: frame.cursor,
4860
4662
  channelId: frame.channel_id,
4861
4663
  channelType: frame.channel_type,
4862
4664
  authorName: frame.author_name,
@@ -4931,6 +4733,102 @@ function rawToString(data) {
4931
4733
  return String(data);
4932
4734
  }
4933
4735
 
4736
+ // ../sdk/plugin-ts/dist/activity-reporter.js
4737
+ var DEFAULT_THROTTLE_MS = 150;
4738
+ var OUTPUT_TAIL_CHARS = 2e3;
4739
+ var defaultScheduler = (callback, delayMs) => {
4740
+ const timer = setTimeout(callback, delayMs);
4741
+ timer.unref?.();
4742
+ return () => clearTimeout(timer);
4743
+ };
4744
+ function outputTail(text) {
4745
+ if (text.length <= OUTPUT_TAIL_CHARS) {
4746
+ return text;
4747
+ }
4748
+ const tail = text.slice(text.length - OUTPUT_TAIL_CHARS);
4749
+ return /^[\uDC00-\uDFFF]/.test(tail) ? tail.slice(1) : tail;
4750
+ }
4751
+ function throttleKey(activity) {
4752
+ switch (activity.shape) {
4753
+ case "activity":
4754
+ return `activity:${activity.id}`;
4755
+ case "plan":
4756
+ return "plan";
4757
+ case "output":
4758
+ return `output:${activity.stream}`;
4759
+ }
4760
+ }
4761
+ var TurnActivityReporter = class {
4762
+ options;
4763
+ windows = /* @__PURE__ */ new Map();
4764
+ throttleMs;
4765
+ scheduler;
4766
+ ended = false;
4767
+ constructor(options) {
4768
+ this.options = options;
4769
+ this.throttleMs = options.throttleMs ?? DEFAULT_THROTTLE_MS;
4770
+ this.scheduler = options.scheduler ?? defaultScheduler;
4771
+ }
4772
+ start() {
4773
+ this.report({ shape: "turn", state: "started" });
4774
+ }
4775
+ progress(activity) {
4776
+ if (this.ended) {
4777
+ return;
4778
+ }
4779
+ this.offer(activity.shape === "output" ? { ...activity, text: outputTail(activity.text) } : activity);
4780
+ }
4781
+ /**
4782
+ * Ends the turn. What the windows still hold goes first, so the last thing a
4783
+ * reader sees of a turn is that turn's own last word rather than whatever
4784
+ * happened to fall outside a window.
4785
+ */
4786
+ end() {
4787
+ if (this.ended) {
4788
+ return;
4789
+ }
4790
+ this.ended = true;
4791
+ this.drain();
4792
+ this.report({ shape: "turn", state: "ended" });
4793
+ }
4794
+ offer(activity) {
4795
+ const key = throttleKey(activity);
4796
+ const open = this.windows.get(key);
4797
+ if (open) {
4798
+ open.latest = activity;
4799
+ return;
4800
+ }
4801
+ this.report(activity);
4802
+ this.openWindow(key);
4803
+ }
4804
+ openWindow(key) {
4805
+ const window = { latest: null, cancel: null };
4806
+ window.cancel = this.scheduler(() => {
4807
+ this.windows.delete(key);
4808
+ if (window.latest) {
4809
+ this.offer(window.latest);
4810
+ }
4811
+ }, this.throttleMs);
4812
+ this.windows.set(key, window);
4813
+ }
4814
+ drain() {
4815
+ const held = [...this.windows.values()];
4816
+ this.windows.clear();
4817
+ for (const window of held) {
4818
+ window.cancel?.();
4819
+ if (window.latest) {
4820
+ this.report(window.latest);
4821
+ }
4822
+ }
4823
+ }
4824
+ report(activity) {
4825
+ if (this.options.canReport && !this.options.canReport()) {
4826
+ return;
4827
+ }
4828
+ this.options.deliver({ channelId: this.options.channelId, activity });
4829
+ }
4830
+ };
4831
+
4934
4832
  // ../sdk/plugin-ts/dist/client.js
4935
4833
  var noopLogger = {
4936
4834
  debug() {
@@ -4946,21 +4844,19 @@ var Client = class {
4946
4844
  opts;
4947
4845
  t;
4948
4846
  logger;
4949
- cursorStore;
4950
4847
  _agentId;
4848
+ activitySequence = 0;
4951
4849
  _state = { status: "connecting" };
4952
4850
  listeners = {
4953
4851
  message: /* @__PURE__ */ new Set(),
4954
4852
  configUpdate: /* @__PURE__ */ new Set(),
4955
4853
  permissionDenied: /* @__PURE__ */ new Set(),
4956
- connectionState: /* @__PURE__ */ new Set(),
4957
- replaySummary: /* @__PURE__ */ new Set()
4854
+ connectionState: /* @__PURE__ */ new Set()
4958
4855
  };
4959
4856
  constructor(opts, transport) {
4960
4857
  this.opts = opts;
4961
4858
  this.t = transport;
4962
4859
  this.logger = opts.logger ?? noopLogger;
4963
- this.cursorStore = opts.cursorStore ?? new MemoryCursorStore();
4964
4860
  this._agentId = opts.agentId ?? "";
4965
4861
  }
4966
4862
  get agentId() {
@@ -5060,6 +4956,31 @@ var Client = class {
5060
4956
  reason: input.reason
5061
4957
  }).catch((err) => this.logger.warn("reportTaskFinished failed", err));
5062
4958
  }
4959
+ reportActivity(input) {
4960
+ void this.t.perform({
4961
+ op: "report_activity",
4962
+ channelId: input.channelId,
4963
+ sequence: this.nextActivitySequence(),
4964
+ activity: input.activity
4965
+ }).catch((err) => this.logger.warn("reportActivity failed", err));
4966
+ }
4967
+ reportTurnActivity(input) {
4968
+ return new TurnActivityReporter({
4969
+ ...input,
4970
+ deliver: (report) => this.reportActivity(report)
4971
+ });
4972
+ }
4973
+ /**
4974
+ * The counter every activity report carries. It belongs to the connection
4975
+ * rather than to a producer: both readers use it only to drop anything not
4976
+ * newer, so two producers on one agent numbering independently would silently
4977
+ * erase each other, and the failure would present as a missing report rather
4978
+ * than as a wrong one. Nothing outside this client can supply or observe it.
4979
+ */
4980
+ nextActivitySequence() {
4981
+ this.activitySequence += 1;
4982
+ return this.activitySequence;
4983
+ }
5063
4984
  // ─── Task CRUD (BPP semantic actions) ───────────────────
5064
4985
  // Semantic actions rather than REST: the blueprint's protocol red line
5065
4986
  // (plugin-protocol.md 1.3) is that a plugin does not reach past the semantic
@@ -5143,6 +5064,9 @@ var Client = class {
5143
5064
  onServerRequest(handler) {
5144
5065
  this.t.setServerRequestHandler?.(handler);
5145
5066
  }
5067
+ onStopTurn(handler) {
5068
+ this.t.setStopTurnHandler?.(handler);
5069
+ }
5146
5070
  emit(event, payload) {
5147
5071
  for (const h of this.listeners[event])
5148
5072
  h(payload);
@@ -5153,7 +5077,6 @@ var Client = class {
5153
5077
  onConfigUpdate: (u) => this.emit("configUpdate", u),
5154
5078
  applyConfigUpdate: this.opts.configUpdateHandler,
5155
5079
  onPermissionDenied: (d) => this.emit("permissionDenied", d),
5156
- onReplaySummary: (summary) => this.emit("replaySummary", summary),
5157
5080
  onStateChange: (s) => {
5158
5081
  this._state = s;
5159
5082
  this.emit("connectionState", s);
@@ -5166,12 +5089,8 @@ var Client = class {
5166
5089
  apiKey: this.opts.apiKey,
5167
5090
  agentId: this._agentId,
5168
5091
  pluginId: this.opts.pluginId,
5169
- cursorStore: this.cursorStore,
5170
5092
  logger: this.logger,
5171
- resumeAckTimeoutMs: this.opts.resumeAckTimeoutMs,
5172
- configApplyTimeoutMs: this.opts.configApplyTimeoutMs,
5173
- replayMode: this.opts.replayMode,
5174
- replayLatestN: this.opts.replayLatestN
5093
+ configApplyTimeoutMs: this.opts.configApplyTimeoutMs
5175
5094
  };
5176
5095
  }
5177
5096
  };
@@ -5180,8 +5099,6 @@ function createBorgeePlugin(opts) {
5180
5099
  }
5181
5100
  export {
5182
5101
  BorgeeError,
5183
- FileCursorStore,
5184
- MemoryCursorStore,
5185
5102
  PermissionDeniedError,
5186
5103
  createBorgeePlugin
5187
5104
  };