@cabane/companion 0.6.12 → 0.6.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -6295,6 +6295,8 @@ var SUB_AGENT_TOOL = "sub_agent";
6295
6295
  var SUB_AGENT_TOOL_NAME = `mcp__${COMPANION_LOCAL_MCP_SERVER}__${SUB_AGENT_TOOL}`;
6296
6296
  var WAKE_ME_TOOL = "wake_me";
6297
6297
  var WAKE_ME_TOOL_NAME = `mcp__${COMPANION_LOCAL_MCP_SERVER}__${WAKE_ME_TOOL}`;
6298
+ var CANCEL_WAKE_TOOL = "cancel_wake";
6299
+ var CANCEL_WAKE_TOOL_NAME = `mcp__${COMPANION_LOCAL_MCP_SERVER}__${CANCEL_WAKE_TOOL}`;
6298
6300
  function createSummonState() {
6299
6301
  return { agentId: null };
6300
6302
  }
@@ -6305,7 +6307,19 @@ function createAskState() {
6305
6307
  return { targetUserId: null, question: null, headline: null, options: null, questions: null };
6306
6308
  }
6307
6309
  function createWakeState() {
6308
- return { afterSeconds: null, at: null, note: null };
6310
+ return { afterSeconds: null, at: null, note: null, cancelled: false };
6311
+ }
6312
+ function wakeCommitField(state) {
6313
+ if (state.cancelled) return { wake: { cancel: true } };
6314
+ const { afterSeconds, at, note } = state;
6315
+ if (!note || afterSeconds === null && at === null) return {};
6316
+ return {
6317
+ wake: {
6318
+ ...afterSeconds !== null ? { afterSeconds } : {},
6319
+ ...at !== null ? { at } : {},
6320
+ note
6321
+ }
6322
+ };
6309
6323
  }
6310
6324
  function createSummonMcpServer(summonState, skipState, askState, subAgentCreate, wakeState) {
6311
6325
  return createSdkMcpServer({
@@ -6492,6 +6506,7 @@ function createSummonMcpServer(summonState, skipState, askState, subAgentCreate,
6492
6506
  wakeState.afterSeconds = args.afterSeconds ?? null;
6493
6507
  wakeState.at = args.at ?? null;
6494
6508
  wakeState.note = args.note;
6509
+ wakeState.cancelled = false;
6495
6510
  return {
6496
6511
  content: [
6497
6512
  {
@@ -6505,6 +6520,31 @@ function createSummonMcpServer(summonState, skipState, askState, subAgentCreate,
6505
6520
  };
6506
6521
  },
6507
6522
  { annotations: { readOnlyHint: true, openWorldHint: false }, alwaysLoad: true }
6523
+ ),
6524
+ // CT990: the inverse. No arguments — it can only ever stand down the
6525
+ // caller's own wake in the conversation it's holding a turn in.
6526
+ tool(
6527
+ CANCEL_WAKE_TOOL,
6528
+ 'Stand down your own wake \u2014 the inverse of `wake_me`. No arguments: it means "when this turn settles, leave no wake armed for me in this conversation." Reach for it when the thing you armed a wake to check has already happened, or the work it was watching is over \u2014 an armed wake you no longer need fires into a turn with nothing to do, and a supervision loop with no off switch is one you can only end by leaving it running. Whether you have one armed is printed in your turn context ("Wake armed: \u2026"); calling this with nothing armed is a clean no-op, no error. It writes the same per-turn slot as `wake_me`, so the two are last-call-wins against each other: `cancel_wake` then `wake_me` leaves the NEW wake armed, `wake_me` then `cancel_wake` leaves nothing armed. Like `wake_me` it takes effect when the turn SETTLES, not now. It reaches only your own wake in this conversation \u2014 never a peer\'s, and never a recurring schedule (those are `cabane.schedules.*`).',
6529
+ {},
6530
+ async () => {
6531
+ wakeState.cancelled = true;
6532
+ wakeState.afterSeconds = null;
6533
+ wakeState.at = null;
6534
+ wakeState.note = null;
6535
+ return {
6536
+ content: [
6537
+ {
6538
+ type: "text",
6539
+ text: JSON.stringify({
6540
+ cancelled: true,
6541
+ note: "Recorded. Any wake you have armed in this conversation is stood down when this turn ends. If you had none, nothing happens."
6542
+ })
6543
+ }
6544
+ ]
6545
+ };
6546
+ },
6547
+ { annotations: { readOnlyHint: true, openWorldHint: false }, alwaysLoad: true }
6508
6548
  )
6509
6549
  ] : []
6510
6550
  ]
@@ -6941,16 +6981,13 @@ var TurnCommitter = class {
6941
6981
  // null = no wake this turn → attach nothing). The server computes `fire_at` and
6942
6982
  // arms the CT441 schedule. Kept independent of summon/ask — a turn could
6943
6983
  // conceivably ask AND arm a wake.
6984
+ //
6985
+ // CT990: the field now carries three states, and the third can't be spelled by
6986
+ // absence — attaching NOTHING means "leave the standing wake alone", which is
6987
+ // exactly what a stand-down must not do. So a `cancel_wake` turn attaches the
6988
+ // discriminated `{ cancel: true }` payload instead.
6944
6989
  wakeField() {
6945
- const { afterSeconds, at, note } = this.deps.wakeState;
6946
- if (!note || afterSeconds === null && at === null) return {};
6947
- return {
6948
- wake: {
6949
- ...afterSeconds !== null ? { afterSeconds } : {},
6950
- ...at !== null ? { at } : {},
6951
- note
6952
- }
6953
- };
6990
+ return wakeCommitField(this.deps.wakeState);
6954
6991
  }
6955
6992
  };
6956
6993
 
@@ -7627,9 +7664,17 @@ ${reason}`,
7627
7664
  }
7628
7665
  }
7629
7666
  if (intent.wake) {
7630
- wakeState.afterSeconds = intent.wake.afterSeconds ?? null;
7631
- wakeState.at = intent.wake.at ?? null;
7632
- wakeState.note = intent.wake.note;
7667
+ if ("cancel" in intent.wake) {
7668
+ wakeState.cancelled = true;
7669
+ wakeState.afterSeconds = null;
7670
+ wakeState.at = null;
7671
+ wakeState.note = null;
7672
+ } else {
7673
+ wakeState.cancelled = false;
7674
+ wakeState.afterSeconds = intent.wake.afterSeconds ?? null;
7675
+ wakeState.at = intent.wake.at ?? null;
7676
+ wakeState.note = intent.wake.note;
7677
+ }
7633
7678
  }
7634
7679
  if (intent.summonAgentId) summonState.agentId = intent.summonAgentId;
7635
7680
  if (intent.skipped) {
@@ -7720,12 +7765,7 @@ ${reason}`,
7720
7765
  { reason: skipState.reason, turnId, ok: okResult },
7721
7766
  "agent skipped turn (skip_turn)"
7722
7767
  );
7723
- const { afterSeconds: wakeAfter, at: wakeAt, note: wakeNote } = wakeState;
7724
- const skipWake = wakeNote && (wakeAfter !== null || wakeAt !== null) ? {
7725
- ...wakeAfter !== null ? { afterSeconds: wakeAfter } : {},
7726
- ...wakeAt !== null ? { at: wakeAt } : {},
7727
- note: wakeNote
7728
- } : void 0;
7768
+ const { wake: skipWake } = wakeCommitField(wakeState);
7729
7769
  try {
7730
7770
  await this.opts.api.postTurnMessage(workspaceId, payload.conversationId, {
7731
7771
  body: SKIPPED_MARKER_BODY,
package/dist/runtime.js CHANGED
@@ -5948,6 +5948,8 @@ var SUB_AGENT_TOOL = "sub_agent";
5948
5948
  var SUB_AGENT_TOOL_NAME = `mcp__${COMPANION_LOCAL_MCP_SERVER}__${SUB_AGENT_TOOL}`;
5949
5949
  var WAKE_ME_TOOL = "wake_me";
5950
5950
  var WAKE_ME_TOOL_NAME = `mcp__${COMPANION_LOCAL_MCP_SERVER}__${WAKE_ME_TOOL}`;
5951
+ var CANCEL_WAKE_TOOL = "cancel_wake";
5952
+ var CANCEL_WAKE_TOOL_NAME = `mcp__${COMPANION_LOCAL_MCP_SERVER}__${CANCEL_WAKE_TOOL}`;
5951
5953
  function createSummonState() {
5952
5954
  return { agentId: null };
5953
5955
  }
@@ -5958,7 +5960,19 @@ function createAskState() {
5958
5960
  return { targetUserId: null, question: null, headline: null, options: null, questions: null };
5959
5961
  }
5960
5962
  function createWakeState() {
5961
- return { afterSeconds: null, at: null, note: null };
5963
+ return { afterSeconds: null, at: null, note: null, cancelled: false };
5964
+ }
5965
+ function wakeCommitField(state) {
5966
+ if (state.cancelled) return { wake: { cancel: true } };
5967
+ const { afterSeconds, at, note } = state;
5968
+ if (!note || afterSeconds === null && at === null) return {};
5969
+ return {
5970
+ wake: {
5971
+ ...afterSeconds !== null ? { afterSeconds } : {},
5972
+ ...at !== null ? { at } : {},
5973
+ note
5974
+ }
5975
+ };
5962
5976
  }
5963
5977
  function createSummonMcpServer(summonState, skipState, askState, subAgentCreate, wakeState) {
5964
5978
  return createSdkMcpServer({
@@ -6145,6 +6159,7 @@ function createSummonMcpServer(summonState, skipState, askState, subAgentCreate,
6145
6159
  wakeState.afterSeconds = args.afterSeconds ?? null;
6146
6160
  wakeState.at = args.at ?? null;
6147
6161
  wakeState.note = args.note;
6162
+ wakeState.cancelled = false;
6148
6163
  return {
6149
6164
  content: [
6150
6165
  {
@@ -6158,6 +6173,31 @@ function createSummonMcpServer(summonState, skipState, askState, subAgentCreate,
6158
6173
  };
6159
6174
  },
6160
6175
  { annotations: { readOnlyHint: true, openWorldHint: false }, alwaysLoad: true }
6176
+ ),
6177
+ // CT990: the inverse. No arguments — it can only ever stand down the
6178
+ // caller's own wake in the conversation it's holding a turn in.
6179
+ tool(
6180
+ CANCEL_WAKE_TOOL,
6181
+ 'Stand down your own wake \u2014 the inverse of `wake_me`. No arguments: it means "when this turn settles, leave no wake armed for me in this conversation." Reach for it when the thing you armed a wake to check has already happened, or the work it was watching is over \u2014 an armed wake you no longer need fires into a turn with nothing to do, and a supervision loop with no off switch is one you can only end by leaving it running. Whether you have one armed is printed in your turn context ("Wake armed: \u2026"); calling this with nothing armed is a clean no-op, no error. It writes the same per-turn slot as `wake_me`, so the two are last-call-wins against each other: `cancel_wake` then `wake_me` leaves the NEW wake armed, `wake_me` then `cancel_wake` leaves nothing armed. Like `wake_me` it takes effect when the turn SETTLES, not now. It reaches only your own wake in this conversation \u2014 never a peer\'s, and never a recurring schedule (those are `cabane.schedules.*`).',
6182
+ {},
6183
+ async () => {
6184
+ wakeState.cancelled = true;
6185
+ wakeState.afterSeconds = null;
6186
+ wakeState.at = null;
6187
+ wakeState.note = null;
6188
+ return {
6189
+ content: [
6190
+ {
6191
+ type: "text",
6192
+ text: JSON.stringify({
6193
+ cancelled: true,
6194
+ note: "Recorded. Any wake you have armed in this conversation is stood down when this turn ends. If you had none, nothing happens."
6195
+ })
6196
+ }
6197
+ ]
6198
+ };
6199
+ },
6200
+ { annotations: { readOnlyHint: true, openWorldHint: false }, alwaysLoad: true }
6161
6201
  )
6162
6202
  ] : []
6163
6203
  ]
@@ -6594,16 +6634,13 @@ var TurnCommitter = class {
6594
6634
  // null = no wake this turn → attach nothing). The server computes `fire_at` and
6595
6635
  // arms the CT441 schedule. Kept independent of summon/ask — a turn could
6596
6636
  // conceivably ask AND arm a wake.
6637
+ //
6638
+ // CT990: the field now carries three states, and the third can't be spelled by
6639
+ // absence — attaching NOTHING means "leave the standing wake alone", which is
6640
+ // exactly what a stand-down must not do. So a `cancel_wake` turn attaches the
6641
+ // discriminated `{ cancel: true }` payload instead.
6597
6642
  wakeField() {
6598
- const { afterSeconds, at, note } = this.deps.wakeState;
6599
- if (!note || afterSeconds === null && at === null) return {};
6600
- return {
6601
- wake: {
6602
- ...afterSeconds !== null ? { afterSeconds } : {},
6603
- ...at !== null ? { at } : {},
6604
- note
6605
- }
6606
- };
6643
+ return wakeCommitField(this.deps.wakeState);
6607
6644
  }
6608
6645
  };
6609
6646
 
@@ -7280,9 +7317,17 @@ ${reason}`,
7280
7317
  }
7281
7318
  }
7282
7319
  if (intent.wake) {
7283
- wakeState.afterSeconds = intent.wake.afterSeconds ?? null;
7284
- wakeState.at = intent.wake.at ?? null;
7285
- wakeState.note = intent.wake.note;
7320
+ if ("cancel" in intent.wake) {
7321
+ wakeState.cancelled = true;
7322
+ wakeState.afterSeconds = null;
7323
+ wakeState.at = null;
7324
+ wakeState.note = null;
7325
+ } else {
7326
+ wakeState.cancelled = false;
7327
+ wakeState.afterSeconds = intent.wake.afterSeconds ?? null;
7328
+ wakeState.at = intent.wake.at ?? null;
7329
+ wakeState.note = intent.wake.note;
7330
+ }
7286
7331
  }
7287
7332
  if (intent.summonAgentId) summonState.agentId = intent.summonAgentId;
7288
7333
  if (intent.skipped) {
@@ -7373,12 +7418,7 @@ ${reason}`,
7373
7418
  { reason: skipState.reason, turnId, ok: okResult },
7374
7419
  "agent skipped turn (skip_turn)"
7375
7420
  );
7376
- const { afterSeconds: wakeAfter, at: wakeAt, note: wakeNote } = wakeState;
7377
- const skipWake = wakeNote && (wakeAfter !== null || wakeAt !== null) ? {
7378
- ...wakeAfter !== null ? { afterSeconds: wakeAfter } : {},
7379
- ...wakeAt !== null ? { at: wakeAt } : {},
7380
- note: wakeNote
7381
- } : void 0;
7421
+ const { wake: skipWake } = wakeCommitField(wakeState);
7382
7422
  try {
7383
7423
  await this.opts.api.postTurnMessage(workspaceId, payload.conversationId, {
7384
7424
  body: SKIPPED_MARKER_BODY,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cabane/companion",
3
- "version": "0.6.12",
3
+ "version": "0.6.13",
4
4
  "type": "module",
5
5
  "description": "The Cabane Companion (headless): connect a coding agent on your machine to your Cabane workspace as a responder — drive work against your own codebase, files, and MCP servers without putting any of it in Cabane.",
6
6
  "license": "UNLICENSED",