@cabane/companion 0.6.11 → 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 +65 -21
- package/dist/runtime.js +65 -21
- package/package.json +1 -1
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
|
-
|
|
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
|
|
|
@@ -7246,6 +7283,8 @@ var Dispatcher = class {
|
|
|
7246
7283
|
effectiveCwd = void 0;
|
|
7247
7284
|
}
|
|
7248
7285
|
let hookEnv;
|
|
7286
|
+
const triggerIsPrepareFailure = message.body.startsWith(PREPARE_FAILED_PREFIX);
|
|
7287
|
+
const prepareFailureDispatch = turnContext.dispatchedByAgentId && !triggerIsPrepareFailure ? { dispatch: turnContext.dispatchedByAgentId } : {};
|
|
7249
7288
|
if (prepareHook) {
|
|
7250
7289
|
const cached2 = readPrepared(workspaceId, payload.conversationId, payload.agentId);
|
|
7251
7290
|
if (cached2) {
|
|
@@ -7275,7 +7314,8 @@ var Dispatcher = class {
|
|
|
7275
7314
|
${reason}`,
|
|
7276
7315
|
kind: "final",
|
|
7277
7316
|
turnId,
|
|
7278
|
-
parentMessageId: payload.messageId
|
|
7317
|
+
parentMessageId: payload.messageId,
|
|
7318
|
+
...prepareFailureDispatch
|
|
7279
7319
|
});
|
|
7280
7320
|
} catch (postErr) {
|
|
7281
7321
|
turnLog.warn(
|
|
@@ -7349,7 +7389,8 @@ ${reason}`,
|
|
|
7349
7389
|
kind: "final",
|
|
7350
7390
|
turnId,
|
|
7351
7391
|
// CT113: stamp the parent even on the prepare-failed close.
|
|
7352
|
-
parentMessageId: payload.messageId
|
|
7392
|
+
parentMessageId: payload.messageId,
|
|
7393
|
+
...prepareFailureDispatch
|
|
7353
7394
|
});
|
|
7354
7395
|
} catch (postErr) {
|
|
7355
7396
|
turnLog.warn(
|
|
@@ -7623,9 +7664,17 @@ ${reason}`,
|
|
|
7623
7664
|
}
|
|
7624
7665
|
}
|
|
7625
7666
|
if (intent.wake) {
|
|
7626
|
-
|
|
7627
|
-
|
|
7628
|
-
|
|
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
|
+
}
|
|
7629
7678
|
}
|
|
7630
7679
|
if (intent.summonAgentId) summonState.agentId = intent.summonAgentId;
|
|
7631
7680
|
if (intent.skipped) {
|
|
@@ -7716,12 +7765,7 @@ ${reason}`,
|
|
|
7716
7765
|
{ reason: skipState.reason, turnId, ok: okResult },
|
|
7717
7766
|
"agent skipped turn (skip_turn)"
|
|
7718
7767
|
);
|
|
7719
|
-
const {
|
|
7720
|
-
const skipWake = wakeNote && (wakeAfter !== null || wakeAt !== null) ? {
|
|
7721
|
-
...wakeAfter !== null ? { afterSeconds: wakeAfter } : {},
|
|
7722
|
-
...wakeAt !== null ? { at: wakeAt } : {},
|
|
7723
|
-
note: wakeNote
|
|
7724
|
-
} : void 0;
|
|
7768
|
+
const { wake: skipWake } = wakeCommitField(wakeState);
|
|
7725
7769
|
try {
|
|
7726
7770
|
await this.opts.api.postTurnMessage(workspaceId, payload.conversationId, {
|
|
7727
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
|
-
|
|
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
|
|
|
@@ -6899,6 +6936,8 @@ var Dispatcher = class {
|
|
|
6899
6936
|
effectiveCwd = void 0;
|
|
6900
6937
|
}
|
|
6901
6938
|
let hookEnv;
|
|
6939
|
+
const triggerIsPrepareFailure = message.body.startsWith(PREPARE_FAILED_PREFIX);
|
|
6940
|
+
const prepareFailureDispatch = turnContext.dispatchedByAgentId && !triggerIsPrepareFailure ? { dispatch: turnContext.dispatchedByAgentId } : {};
|
|
6902
6941
|
if (prepareHook) {
|
|
6903
6942
|
const cached2 = readPrepared(workspaceId, payload.conversationId, payload.agentId);
|
|
6904
6943
|
if (cached2) {
|
|
@@ -6928,7 +6967,8 @@ var Dispatcher = class {
|
|
|
6928
6967
|
${reason}`,
|
|
6929
6968
|
kind: "final",
|
|
6930
6969
|
turnId,
|
|
6931
|
-
parentMessageId: payload.messageId
|
|
6970
|
+
parentMessageId: payload.messageId,
|
|
6971
|
+
...prepareFailureDispatch
|
|
6932
6972
|
});
|
|
6933
6973
|
} catch (postErr) {
|
|
6934
6974
|
turnLog.warn(
|
|
@@ -7002,7 +7042,8 @@ ${reason}`,
|
|
|
7002
7042
|
kind: "final",
|
|
7003
7043
|
turnId,
|
|
7004
7044
|
// CT113: stamp the parent even on the prepare-failed close.
|
|
7005
|
-
parentMessageId: payload.messageId
|
|
7045
|
+
parentMessageId: payload.messageId,
|
|
7046
|
+
...prepareFailureDispatch
|
|
7006
7047
|
});
|
|
7007
7048
|
} catch (postErr) {
|
|
7008
7049
|
turnLog.warn(
|
|
@@ -7276,9 +7317,17 @@ ${reason}`,
|
|
|
7276
7317
|
}
|
|
7277
7318
|
}
|
|
7278
7319
|
if (intent.wake) {
|
|
7279
|
-
|
|
7280
|
-
|
|
7281
|
-
|
|
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
|
+
}
|
|
7282
7331
|
}
|
|
7283
7332
|
if (intent.summonAgentId) summonState.agentId = intent.summonAgentId;
|
|
7284
7333
|
if (intent.skipped) {
|
|
@@ -7369,12 +7418,7 @@ ${reason}`,
|
|
|
7369
7418
|
{ reason: skipState.reason, turnId, ok: okResult },
|
|
7370
7419
|
"agent skipped turn (skip_turn)"
|
|
7371
7420
|
);
|
|
7372
|
-
const {
|
|
7373
|
-
const skipWake = wakeNote && (wakeAfter !== null || wakeAt !== null) ? {
|
|
7374
|
-
...wakeAfter !== null ? { afterSeconds: wakeAfter } : {},
|
|
7375
|
-
...wakeAt !== null ? { at: wakeAt } : {},
|
|
7376
|
-
note: wakeNote
|
|
7377
|
-
} : void 0;
|
|
7421
|
+
const { wake: skipWake } = wakeCommitField(wakeState);
|
|
7378
7422
|
try {
|
|
7379
7423
|
await this.opts.api.postTurnMessage(workspaceId, payload.conversationId, {
|
|
7380
7424
|
body: SKIPPED_MARKER_BODY,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cabane/companion",
|
|
3
|
-
"version": "0.6.
|
|
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",
|