@cabane/companion 0.6.82 → 0.6.84
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 +38 -1
- package/dist/runtime.js +38 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -7576,6 +7576,9 @@ var COMPANION_LOCAL_TOOL_GLOB = `mcp__${COMPANION_LOCAL_MCP_SERVER}__*`;
|
|
|
7576
7576
|
var SKIP_TURN_TOOL = "skip_turn";
|
|
7577
7577
|
var ASK_TOOL = "ask";
|
|
7578
7578
|
var ASK_TOOL_NAME = `mcp__${COMPANION_LOCAL_MCP_SERVER}__${ASK_TOOL}`;
|
|
7579
|
+
var WITHDRAW_ASK_TOOL = "withdraw_ask";
|
|
7580
|
+
var WITHDRAW_ASK_TOOL_NAME = `mcp__${COMPANION_LOCAL_MCP_SERVER}__${WITHDRAW_ASK_TOOL}`;
|
|
7581
|
+
var MAX_WITHDRAW_REASON = 400;
|
|
7579
7582
|
var REPLY_TO_TOOL = "reply_to";
|
|
7580
7583
|
var REPLY_TO_TOOL_NAME = `mcp__${COMPANION_LOCAL_MCP_SERVER}__${REPLY_TO_TOOL}`;
|
|
7581
7584
|
var MAX_ASK_ITEMS = 20;
|
|
@@ -7691,7 +7694,7 @@ function createTurnControlMcpServer(acts, skipState, replyState) {
|
|
|
7691
7694
|
] : [],
|
|
7692
7695
|
tool(
|
|
7693
7696
|
ASK_TOOL,
|
|
7694
|
-
`Ask a HUMAN a structured question (or a short LIST of them) you need answered to continue, then END your turn \u2014 don't wait for the reply. Use it when you genuinely can't proceed without a person's input (a decision only they can make, a missing fact). Pass \`targetUserId\` (a workspace member's user id \u2014 every person's id is on the roster in your turn context). Two forms: a SINGLE question \u2014 a \`headline\` (the actual question as one clear, capitalized sentence ending in \`?\`, "Do we go to prod?") plus a short \`question\` body for the framing the headline can't hold \u2014 OR, when a plan ends with SEVERAL bounded decisions at once, a \`questions\` array of 1\u2013${MAX_ASK_ITEMS} items, each \`{ headline, body?, options? }\`. **Prefer the list over cramming the extra decisions into prose or dropping them** \u2014 one ask carrying every question, never pick one and bury the rest. Each question keeps the same form rules: a one-sentence \`headline\`, a short \`body\` frame (NOT a report \u2014 your status, links, and detail go in your REPLY; inline markdown only, no lists or headings), and 2\u20134 \`options\` when the answer is a bounded choice \u2014 for a yes/no go-ahead always pass them, so it's one click, not a typed reply. Provide EITHER \`question\` (single) or \`questions\` (array), never both. The ask is a first-class attention item aimed at that person; your final reply carries the CONTEXT, the ask carries the QUESTION(S). It is written and delivered THE MOMENT you call this \u2014 the person sees it in their inbox while your turn is still running \u2014 so call it once you know the question; a second call is a second question, not an edit. The human sends ONE response: supplied answers are recorded, omissions become terminally Unanswered. Ask only when blocked \u2014 never ceremonially.
|
|
7697
|
+
`Ask a HUMAN a structured question (or a short LIST of them) you need answered to continue, then END your turn \u2014 don't wait for the reply. Use it when you genuinely can't proceed without a person's input (a decision only they can make, a missing fact). Pass \`targetUserId\` (a workspace member's user id \u2014 every person's id is on the roster in your turn context). Two forms: a SINGLE question \u2014 a \`headline\` (the actual question as one clear, capitalized sentence ending in \`?\`, "Do we go to prod?") plus a short \`question\` body for the framing the headline can't hold \u2014 OR, when a plan ends with SEVERAL bounded decisions at once, a \`questions\` array of 1\u2013${MAX_ASK_ITEMS} items, each \`{ headline, body?, options? }\`. **Prefer the list over cramming the extra decisions into prose or dropping them** \u2014 one ask carrying every question, never pick one and bury the rest. Each question keeps the same form rules: a one-sentence \`headline\`, a short \`body\` frame (NOT a report \u2014 your status, links, and detail go in your REPLY; inline markdown only, no lists or headings), and 2\u20134 \`options\` when the answer is a bounded choice \u2014 for a yes/no go-ahead always pass them, so it's one click, not a typed reply. Provide EITHER \`question\` (single) or \`questions\` (array), never both. The ask is a first-class attention item aimed at that person; your final reply carries the CONTEXT, the ask carries the QUESTION(S). It is written and delivered THE MOMENT you call this \u2014 the person sees it in their inbox while your turn is still running \u2014 so call it once you know the question; a second call is a second question, not an edit. The human sends ONE response: supplied answers are recorded, omissions become terminally Unanswered. Ask only when blocked \u2014 never ceremonially. \`replaces\` withdraws a stale ask of yours (or anyone's) in this conversation in the same write, with the reason shown to the person. Targets a human only; to hand work to another AGENT use send/dispatch instead.`,
|
|
7695
7698
|
{
|
|
7696
7699
|
targetUserId: z13.string().uuid().describe(
|
|
7697
7700
|
"The workspace member (human) to ask \u2014 a user id, from your turn context's roster."
|
|
@@ -7713,6 +7716,14 @@ function createTurnControlMcpServer(acts, skipState, replyState) {
|
|
|
7713
7716
|
})
|
|
7714
7717
|
).min(1).max(MAX_ASK_ITEMS).optional().describe(
|
|
7715
7718
|
`MULTI-question form: 1\u2013${MAX_ASK_ITEMS} questions to ask at once, when a plan ends with several bounded decisions. Provide EITHER this or \`question\`/\`headline\`/\`options\`, not both.`
|
|
7719
|
+
),
|
|
7720
|
+
replaces: z13.object({
|
|
7721
|
+
askId: z13.string().uuid().describe("The id of the open ask to withdraw."),
|
|
7722
|
+
reason: z13.string().min(1).max(MAX_WITHDRAW_REASON).describe(
|
|
7723
|
+
"Why the old question is no longer the question \u2014 shown to the person on their Done row."
|
|
7724
|
+
)
|
|
7725
|
+
}).optional().describe(
|
|
7726
|
+
"Withdraw a stale ask in this conversation as part of raising this one. Both land in a single write, so the person goes from the old question to the new one and never sees neither or both."
|
|
7716
7727
|
)
|
|
7717
7728
|
},
|
|
7718
7729
|
async (args) => {
|
|
@@ -7729,6 +7740,9 @@ function createTurnControlMcpServer(acts, skipState, replyState) {
|
|
|
7729
7740
|
return performAct(acts, {
|
|
7730
7741
|
kind: "ask",
|
|
7731
7742
|
callId: randomUUID(),
|
|
7743
|
+
// CT1350: the swap rides the ask, so the server commits both halves
|
|
7744
|
+
// in one transaction — the person never sees neither or both.
|
|
7745
|
+
...args.replaces ? { replaces: args.replaces } : {},
|
|
7732
7746
|
ask: hasArray ? {
|
|
7733
7747
|
targetUserId: args.targetUserId,
|
|
7734
7748
|
questions: args.questions.map((q) => ({
|
|
@@ -7783,6 +7797,29 @@ function createTurnControlMcpServer(acts, skipState, replyState) {
|
|
|
7783
7797
|
},
|
|
7784
7798
|
{ annotations: ACT_TOOL, alwaysLoad: true }
|
|
7785
7799
|
),
|
|
7800
|
+
// CT1350: the standalone withdrawal. Not annotated destructive — an ask
|
|
7801
|
+
// is a question, not data, and a harness that gated this on a live
|
|
7802
|
+
// operator would hang a turn nobody is watching (the `cancel_wake`
|
|
7803
|
+
// reasoning). Not idempotent either: a second call with the same id is
|
|
7804
|
+
// refused with `ask_not_open`, which is information, not a no-op.
|
|
7805
|
+
tool(
|
|
7806
|
+
WITHDRAW_ASK_TOOL,
|
|
7807
|
+
"Withdraw an open ask \u2014 yours or anyone's in this conversation \u2014 because the question is moot, was wrong, or has been overtaken. `reason` is required and is shown to the person and, if it wasn't your ask, to the agent who asked it. If there is a current question to put in its place, don't call this \u2014 call `ask` with `replaces` so the two land as one and the person never sees neither or both. Never withdraw to hurry an answer; to act on a deadline, arm a `wake_me` and decide then.",
|
|
7808
|
+
{
|
|
7809
|
+
askId: z13.string().uuid().describe(
|
|
7810
|
+
"The open ask to withdraw. Ask ids ride the conversation's message rows (`ask: { id, status, items }`), and your turn context lists the ones you have open."
|
|
7811
|
+
),
|
|
7812
|
+
reason: z13.string().min(1).max(MAX_WITHDRAW_REASON).describe(
|
|
7813
|
+
'Why this question is no longer the question ("merged via GitHub #1551"). Required, and shown to the person on their Done row \u2014 a bare "withdrawn" tells them nothing.'
|
|
7814
|
+
)
|
|
7815
|
+
},
|
|
7816
|
+
async (args) => performAct(acts, {
|
|
7817
|
+
kind: "withdraw_ask",
|
|
7818
|
+
callId: randomUUID(),
|
|
7819
|
+
withdraw: { askId: args.askId, reason: args.reason }
|
|
7820
|
+
}),
|
|
7821
|
+
{ annotations: ACT_TOOL, alwaysLoad: true }
|
|
7822
|
+
),
|
|
7786
7823
|
// CT990: the inverse. No arguments — it can only ever stand down the
|
|
7787
7824
|
// caller's own wake in the conversation it's holding a turn in.
|
|
7788
7825
|
tool(
|
package/dist/runtime.js
CHANGED
|
@@ -7075,6 +7075,9 @@ var COMPANION_LOCAL_TOOL_GLOB = `mcp__${COMPANION_LOCAL_MCP_SERVER}__*`;
|
|
|
7075
7075
|
var SKIP_TURN_TOOL = "skip_turn";
|
|
7076
7076
|
var ASK_TOOL = "ask";
|
|
7077
7077
|
var ASK_TOOL_NAME = `mcp__${COMPANION_LOCAL_MCP_SERVER}__${ASK_TOOL}`;
|
|
7078
|
+
var WITHDRAW_ASK_TOOL = "withdraw_ask";
|
|
7079
|
+
var WITHDRAW_ASK_TOOL_NAME = `mcp__${COMPANION_LOCAL_MCP_SERVER}__${WITHDRAW_ASK_TOOL}`;
|
|
7080
|
+
var MAX_WITHDRAW_REASON = 400;
|
|
7078
7081
|
var REPLY_TO_TOOL = "reply_to";
|
|
7079
7082
|
var REPLY_TO_TOOL_NAME = `mcp__${COMPANION_LOCAL_MCP_SERVER}__${REPLY_TO_TOOL}`;
|
|
7080
7083
|
var MAX_ASK_ITEMS = 20;
|
|
@@ -7190,7 +7193,7 @@ function createTurnControlMcpServer(acts, skipState, replyState) {
|
|
|
7190
7193
|
] : [],
|
|
7191
7194
|
tool(
|
|
7192
7195
|
ASK_TOOL,
|
|
7193
|
-
`Ask a HUMAN a structured question (or a short LIST of them) you need answered to continue, then END your turn \u2014 don't wait for the reply. Use it when you genuinely can't proceed without a person's input (a decision only they can make, a missing fact). Pass \`targetUserId\` (a workspace member's user id \u2014 every person's id is on the roster in your turn context). Two forms: a SINGLE question \u2014 a \`headline\` (the actual question as one clear, capitalized sentence ending in \`?\`, "Do we go to prod?") plus a short \`question\` body for the framing the headline can't hold \u2014 OR, when a plan ends with SEVERAL bounded decisions at once, a \`questions\` array of 1\u2013${MAX_ASK_ITEMS} items, each \`{ headline, body?, options? }\`. **Prefer the list over cramming the extra decisions into prose or dropping them** \u2014 one ask carrying every question, never pick one and bury the rest. Each question keeps the same form rules: a one-sentence \`headline\`, a short \`body\` frame (NOT a report \u2014 your status, links, and detail go in your REPLY; inline markdown only, no lists or headings), and 2\u20134 \`options\` when the answer is a bounded choice \u2014 for a yes/no go-ahead always pass them, so it's one click, not a typed reply. Provide EITHER \`question\` (single) or \`questions\` (array), never both. The ask is a first-class attention item aimed at that person; your final reply carries the CONTEXT, the ask carries the QUESTION(S). It is written and delivered THE MOMENT you call this \u2014 the person sees it in their inbox while your turn is still running \u2014 so call it once you know the question; a second call is a second question, not an edit. The human sends ONE response: supplied answers are recorded, omissions become terminally Unanswered. Ask only when blocked \u2014 never ceremonially.
|
|
7196
|
+
`Ask a HUMAN a structured question (or a short LIST of them) you need answered to continue, then END your turn \u2014 don't wait for the reply. Use it when you genuinely can't proceed without a person's input (a decision only they can make, a missing fact). Pass \`targetUserId\` (a workspace member's user id \u2014 every person's id is on the roster in your turn context). Two forms: a SINGLE question \u2014 a \`headline\` (the actual question as one clear, capitalized sentence ending in \`?\`, "Do we go to prod?") plus a short \`question\` body for the framing the headline can't hold \u2014 OR, when a plan ends with SEVERAL bounded decisions at once, a \`questions\` array of 1\u2013${MAX_ASK_ITEMS} items, each \`{ headline, body?, options? }\`. **Prefer the list over cramming the extra decisions into prose or dropping them** \u2014 one ask carrying every question, never pick one and bury the rest. Each question keeps the same form rules: a one-sentence \`headline\`, a short \`body\` frame (NOT a report \u2014 your status, links, and detail go in your REPLY; inline markdown only, no lists or headings), and 2\u20134 \`options\` when the answer is a bounded choice \u2014 for a yes/no go-ahead always pass them, so it's one click, not a typed reply. Provide EITHER \`question\` (single) or \`questions\` (array), never both. The ask is a first-class attention item aimed at that person; your final reply carries the CONTEXT, the ask carries the QUESTION(S). It is written and delivered THE MOMENT you call this \u2014 the person sees it in their inbox while your turn is still running \u2014 so call it once you know the question; a second call is a second question, not an edit. The human sends ONE response: supplied answers are recorded, omissions become terminally Unanswered. Ask only when blocked \u2014 never ceremonially. \`replaces\` withdraws a stale ask of yours (or anyone's) in this conversation in the same write, with the reason shown to the person. Targets a human only; to hand work to another AGENT use send/dispatch instead.`,
|
|
7194
7197
|
{
|
|
7195
7198
|
targetUserId: z13.string().uuid().describe(
|
|
7196
7199
|
"The workspace member (human) to ask \u2014 a user id, from your turn context's roster."
|
|
@@ -7212,6 +7215,14 @@ function createTurnControlMcpServer(acts, skipState, replyState) {
|
|
|
7212
7215
|
})
|
|
7213
7216
|
).min(1).max(MAX_ASK_ITEMS).optional().describe(
|
|
7214
7217
|
`MULTI-question form: 1\u2013${MAX_ASK_ITEMS} questions to ask at once, when a plan ends with several bounded decisions. Provide EITHER this or \`question\`/\`headline\`/\`options\`, not both.`
|
|
7218
|
+
),
|
|
7219
|
+
replaces: z13.object({
|
|
7220
|
+
askId: z13.string().uuid().describe("The id of the open ask to withdraw."),
|
|
7221
|
+
reason: z13.string().min(1).max(MAX_WITHDRAW_REASON).describe(
|
|
7222
|
+
"Why the old question is no longer the question \u2014 shown to the person on their Done row."
|
|
7223
|
+
)
|
|
7224
|
+
}).optional().describe(
|
|
7225
|
+
"Withdraw a stale ask in this conversation as part of raising this one. Both land in a single write, so the person goes from the old question to the new one and never sees neither or both."
|
|
7215
7226
|
)
|
|
7216
7227
|
},
|
|
7217
7228
|
async (args) => {
|
|
@@ -7228,6 +7239,9 @@ function createTurnControlMcpServer(acts, skipState, replyState) {
|
|
|
7228
7239
|
return performAct(acts, {
|
|
7229
7240
|
kind: "ask",
|
|
7230
7241
|
callId: randomUUID(),
|
|
7242
|
+
// CT1350: the swap rides the ask, so the server commits both halves
|
|
7243
|
+
// in one transaction — the person never sees neither or both.
|
|
7244
|
+
...args.replaces ? { replaces: args.replaces } : {},
|
|
7231
7245
|
ask: hasArray ? {
|
|
7232
7246
|
targetUserId: args.targetUserId,
|
|
7233
7247
|
questions: args.questions.map((q) => ({
|
|
@@ -7282,6 +7296,29 @@ function createTurnControlMcpServer(acts, skipState, replyState) {
|
|
|
7282
7296
|
},
|
|
7283
7297
|
{ annotations: ACT_TOOL, alwaysLoad: true }
|
|
7284
7298
|
),
|
|
7299
|
+
// CT1350: the standalone withdrawal. Not annotated destructive — an ask
|
|
7300
|
+
// is a question, not data, and a harness that gated this on a live
|
|
7301
|
+
// operator would hang a turn nobody is watching (the `cancel_wake`
|
|
7302
|
+
// reasoning). Not idempotent either: a second call with the same id is
|
|
7303
|
+
// refused with `ask_not_open`, which is information, not a no-op.
|
|
7304
|
+
tool(
|
|
7305
|
+
WITHDRAW_ASK_TOOL,
|
|
7306
|
+
"Withdraw an open ask \u2014 yours or anyone's in this conversation \u2014 because the question is moot, was wrong, or has been overtaken. `reason` is required and is shown to the person and, if it wasn't your ask, to the agent who asked it. If there is a current question to put in its place, don't call this \u2014 call `ask` with `replaces` so the two land as one and the person never sees neither or both. Never withdraw to hurry an answer; to act on a deadline, arm a `wake_me` and decide then.",
|
|
7307
|
+
{
|
|
7308
|
+
askId: z13.string().uuid().describe(
|
|
7309
|
+
"The open ask to withdraw. Ask ids ride the conversation's message rows (`ask: { id, status, items }`), and your turn context lists the ones you have open."
|
|
7310
|
+
),
|
|
7311
|
+
reason: z13.string().min(1).max(MAX_WITHDRAW_REASON).describe(
|
|
7312
|
+
'Why this question is no longer the question ("merged via GitHub #1551"). Required, and shown to the person on their Done row \u2014 a bare "withdrawn" tells them nothing.'
|
|
7313
|
+
)
|
|
7314
|
+
},
|
|
7315
|
+
async (args) => performAct(acts, {
|
|
7316
|
+
kind: "withdraw_ask",
|
|
7317
|
+
callId: randomUUID(),
|
|
7318
|
+
withdraw: { askId: args.askId, reason: args.reason }
|
|
7319
|
+
}),
|
|
7320
|
+
{ annotations: ACT_TOOL, alwaysLoad: true }
|
|
7321
|
+
),
|
|
7285
7322
|
// CT990: the inverse. No arguments — it can only ever stand down the
|
|
7286
7323
|
// caller's own wake in the conversation it's holding a turn in.
|
|
7287
7324
|
tool(
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cabane/companion",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.84",
|
|
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",
|