@cabane/companion 0.6.87 → 0.6.88
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 +57 -9
- package/dist/runtime.js +57 -9
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -2647,6 +2647,35 @@ var CabaneApi = class {
|
|
|
2647
2647
|
signal
|
|
2648
2648
|
);
|
|
2649
2649
|
}
|
|
2650
|
+
// CT1357: post ONE ordinary addressed message — not turn speech, no `turnId`
|
|
2651
|
+
// — so `dispatch` is honoured and the target agent is woken. The turn-commit
|
|
2652
|
+
// fork ignores `dispatch` (an act rides its own call now), which leaves this
|
|
2653
|
+
// as the only way a companion can address a peer OUTSIDE a running turn.
|
|
2654
|
+
//
|
|
2655
|
+
// The prepare-failure handback is exactly that case and the reason this
|
|
2656
|
+
// exists. It fires before the turn is ever admitted — the active-run PATCH
|
|
2657
|
+
// that admits it comes after the prepare hook — so `turnAct` is not available:
|
|
2658
|
+
// its route resolves the turn first and 404s when there is none. The failure
|
|
2659
|
+
// notice's own commit is what admits the turn, and by then it has also settled
|
|
2660
|
+
// it. An ordinary addressed post depends on no lease at all, which is what
|
|
2661
|
+
// makes it the right shape here.
|
|
2662
|
+
//
|
|
2663
|
+
// Retried like any durable write, and therefore REQUIRING `idempotencyKey`:
|
|
2664
|
+
// this is not outbox-queued (there is no `(turnId, seq)` for a non-turn row),
|
|
2665
|
+
// so without a key a committed request whose response was lost — or a
|
|
2666
|
+
// redelivered dispatch event — writes a second row and wakes the peer twice.
|
|
2667
|
+
// The server dedupes on `(conversation_id, idempotency_key)`: the repeat
|
|
2668
|
+
// returns the existing row and raises no second dispatch (CT239). Required,
|
|
2669
|
+
// not optional, so a future caller cannot omit it and silently lose the
|
|
2670
|
+
// guarantee.
|
|
2671
|
+
postAddressedMessage(workspaceId, conversationId, body, signal) {
|
|
2672
|
+
return this.request(
|
|
2673
|
+
"POST",
|
|
2674
|
+
`/api/workspaces/${workspaceId}/conversations/${conversationId}/messages`,
|
|
2675
|
+
body,
|
|
2676
|
+
signal ? { retry: true, signal } : { retry: true }
|
|
2677
|
+
);
|
|
2678
|
+
}
|
|
2650
2679
|
// SJ477: report one tool-activity transition (start / done / error) for the
|
|
2651
2680
|
// live activity cards. Transient — the server publishes an `agent_activity`
|
|
2652
2681
|
// SSE and writes no row. Agent-PAT authed; the URL `:agentId` must match the
|
|
@@ -8617,12 +8646,31 @@ var TurnExecution = class {
|
|
|
8617
8646
|
}
|
|
8618
8647
|
let hookEnv;
|
|
8619
8648
|
const triggerIsPrepareFailure = this.turnContext.message.body.startsWith(PREPARE_FAILED_PREFIX);
|
|
8620
|
-
const
|
|
8621
|
-
|
|
8622
|
-
|
|
8649
|
+
const sendPrepareFailureHandback = async () => {
|
|
8650
|
+
if (!this.turnContext.dispatchedByAgentId || triggerIsPrepareFailure) return;
|
|
8651
|
+
try {
|
|
8652
|
+
await this.opts.api.postAddressedMessage(workspaceId, payload.conversationId, {
|
|
8653
|
+
body: `${PREPARE_FAILED_PREFIX}
|
|
8623
8654
|
|
|
8624
|
-
The dispatched turn could not start. Re-dispatch it after repairing the preparation failure shown in this conversation
|
|
8625
|
-
|
|
8655
|
+
The dispatched turn could not start. Re-dispatch it after repairing the preparation failure shown in this conversation.`,
|
|
8656
|
+
dispatch: this.turnContext.dispatchedByAgentId,
|
|
8657
|
+
// Keyed on the TRIGGER, not the turn: a redelivered dispatch event is
|
|
8658
|
+
// the replay this has to survive, and it mints a fresh `turnId` while
|
|
8659
|
+
// carrying the same `messageId`. One handback per failed pickup of a
|
|
8660
|
+
// given trigger — which is also CT992's rule, that the first failure
|
|
8661
|
+
// is the one carrying information. A genuine re-dispatch after a
|
|
8662
|
+
// repair authors a NEW trigger message, so it keys differently and
|
|
8663
|
+
// hands back again.
|
|
8664
|
+
idempotencyKey: `prepare-failure-handback:${payload.messageId}`,
|
|
8665
|
+
parentMessageId: payload.messageId
|
|
8666
|
+
});
|
|
8667
|
+
} catch (err) {
|
|
8668
|
+
turnLog.warn(
|
|
8669
|
+
{ err: err instanceof Error ? err.message : String(err) },
|
|
8670
|
+
"dispatcher: prepare-failure handback failed"
|
|
8671
|
+
);
|
|
8672
|
+
}
|
|
8673
|
+
};
|
|
8626
8674
|
if (prepareHook) {
|
|
8627
8675
|
let cached2 = readPrepared(workspaceId, payload.conversationId, payload.agentId);
|
|
8628
8676
|
if (cached2 && !checkoutState(cached2.cwd).ok) {
|
|
@@ -8661,8 +8709,7 @@ The dispatched turn could not start. Re-dispatch it after repairing the preparat
|
|
|
8661
8709
|
${reason}`,
|
|
8662
8710
|
kind: "final",
|
|
8663
8711
|
turnId,
|
|
8664
|
-
parentMessageId: payload.messageId
|
|
8665
|
-
...prepareFailureDispatch
|
|
8712
|
+
parentMessageId: payload.messageId
|
|
8666
8713
|
});
|
|
8667
8714
|
} catch (postErr) {
|
|
8668
8715
|
turnLog.warn(
|
|
@@ -8670,6 +8717,7 @@ ${reason}`,
|
|
|
8670
8717
|
"dispatcher: prepare-rejection post failed"
|
|
8671
8718
|
);
|
|
8672
8719
|
}
|
|
8720
|
+
await sendPrepareFailureHandback();
|
|
8673
8721
|
throw this.concluded(`prepare_failed: ${reason}`);
|
|
8674
8722
|
}
|
|
8675
8723
|
}
|
|
@@ -8737,8 +8785,7 @@ ${reason}`,
|
|
|
8737
8785
|
kind: "final",
|
|
8738
8786
|
turnId,
|
|
8739
8787
|
// CT113: stamp the parent even on the prepare-failed close.
|
|
8740
|
-
parentMessageId: payload.messageId
|
|
8741
|
-
...prepareFailureDispatch
|
|
8788
|
+
parentMessageId: payload.messageId
|
|
8742
8789
|
});
|
|
8743
8790
|
} catch (postErr) {
|
|
8744
8791
|
turnLog.warn(
|
|
@@ -8746,6 +8793,7 @@ ${reason}`,
|
|
|
8746
8793
|
"dispatcher: prepare-failure post failed"
|
|
8747
8794
|
);
|
|
8748
8795
|
}
|
|
8796
|
+
await sendPrepareFailureHandback();
|
|
8749
8797
|
const failReason = `prepare_failed: ${reason}`;
|
|
8750
8798
|
throw this.concluded(failReason);
|
|
8751
8799
|
}
|
package/dist/runtime.js
CHANGED
|
@@ -2065,6 +2065,35 @@ var CabaneApi = class {
|
|
|
2065
2065
|
signal
|
|
2066
2066
|
);
|
|
2067
2067
|
}
|
|
2068
|
+
// CT1357: post ONE ordinary addressed message — not turn speech, no `turnId`
|
|
2069
|
+
// — so `dispatch` is honoured and the target agent is woken. The turn-commit
|
|
2070
|
+
// fork ignores `dispatch` (an act rides its own call now), which leaves this
|
|
2071
|
+
// as the only way a companion can address a peer OUTSIDE a running turn.
|
|
2072
|
+
//
|
|
2073
|
+
// The prepare-failure handback is exactly that case and the reason this
|
|
2074
|
+
// exists. It fires before the turn is ever admitted — the active-run PATCH
|
|
2075
|
+
// that admits it comes after the prepare hook — so `turnAct` is not available:
|
|
2076
|
+
// its route resolves the turn first and 404s when there is none. The failure
|
|
2077
|
+
// notice's own commit is what admits the turn, and by then it has also settled
|
|
2078
|
+
// it. An ordinary addressed post depends on no lease at all, which is what
|
|
2079
|
+
// makes it the right shape here.
|
|
2080
|
+
//
|
|
2081
|
+
// Retried like any durable write, and therefore REQUIRING `idempotencyKey`:
|
|
2082
|
+
// this is not outbox-queued (there is no `(turnId, seq)` for a non-turn row),
|
|
2083
|
+
// so without a key a committed request whose response was lost — or a
|
|
2084
|
+
// redelivered dispatch event — writes a second row and wakes the peer twice.
|
|
2085
|
+
// The server dedupes on `(conversation_id, idempotency_key)`: the repeat
|
|
2086
|
+
// returns the existing row and raises no second dispatch (CT239). Required,
|
|
2087
|
+
// not optional, so a future caller cannot omit it and silently lose the
|
|
2088
|
+
// guarantee.
|
|
2089
|
+
postAddressedMessage(workspaceId, conversationId, body, signal) {
|
|
2090
|
+
return this.request(
|
|
2091
|
+
"POST",
|
|
2092
|
+
`/api/workspaces/${workspaceId}/conversations/${conversationId}/messages`,
|
|
2093
|
+
body,
|
|
2094
|
+
signal ? { retry: true, signal } : { retry: true }
|
|
2095
|
+
);
|
|
2096
|
+
}
|
|
2068
2097
|
// SJ477: report one tool-activity transition (start / done / error) for the
|
|
2069
2098
|
// live activity cards. Transient — the server publishes an `agent_activity`
|
|
2070
2099
|
// SSE and writes no row. Agent-PAT authed; the URL `:agentId` must match the
|
|
@@ -8114,12 +8143,31 @@ var TurnExecution = class {
|
|
|
8114
8143
|
}
|
|
8115
8144
|
let hookEnv;
|
|
8116
8145
|
const triggerIsPrepareFailure = this.turnContext.message.body.startsWith(PREPARE_FAILED_PREFIX);
|
|
8117
|
-
const
|
|
8118
|
-
|
|
8119
|
-
|
|
8146
|
+
const sendPrepareFailureHandback = async () => {
|
|
8147
|
+
if (!this.turnContext.dispatchedByAgentId || triggerIsPrepareFailure) return;
|
|
8148
|
+
try {
|
|
8149
|
+
await this.opts.api.postAddressedMessage(workspaceId, payload.conversationId, {
|
|
8150
|
+
body: `${PREPARE_FAILED_PREFIX}
|
|
8120
8151
|
|
|
8121
|
-
The dispatched turn could not start. Re-dispatch it after repairing the preparation failure shown in this conversation
|
|
8122
|
-
|
|
8152
|
+
The dispatched turn could not start. Re-dispatch it after repairing the preparation failure shown in this conversation.`,
|
|
8153
|
+
dispatch: this.turnContext.dispatchedByAgentId,
|
|
8154
|
+
// Keyed on the TRIGGER, not the turn: a redelivered dispatch event is
|
|
8155
|
+
// the replay this has to survive, and it mints a fresh `turnId` while
|
|
8156
|
+
// carrying the same `messageId`. One handback per failed pickup of a
|
|
8157
|
+
// given trigger — which is also CT992's rule, that the first failure
|
|
8158
|
+
// is the one carrying information. A genuine re-dispatch after a
|
|
8159
|
+
// repair authors a NEW trigger message, so it keys differently and
|
|
8160
|
+
// hands back again.
|
|
8161
|
+
idempotencyKey: `prepare-failure-handback:${payload.messageId}`,
|
|
8162
|
+
parentMessageId: payload.messageId
|
|
8163
|
+
});
|
|
8164
|
+
} catch (err) {
|
|
8165
|
+
turnLog.warn(
|
|
8166
|
+
{ err: err instanceof Error ? err.message : String(err) },
|
|
8167
|
+
"dispatcher: prepare-failure handback failed"
|
|
8168
|
+
);
|
|
8169
|
+
}
|
|
8170
|
+
};
|
|
8123
8171
|
if (prepareHook) {
|
|
8124
8172
|
let cached2 = readPrepared(workspaceId, payload.conversationId, payload.agentId);
|
|
8125
8173
|
if (cached2 && !checkoutState(cached2.cwd).ok) {
|
|
@@ -8158,8 +8206,7 @@ The dispatched turn could not start. Re-dispatch it after repairing the preparat
|
|
|
8158
8206
|
${reason}`,
|
|
8159
8207
|
kind: "final",
|
|
8160
8208
|
turnId,
|
|
8161
|
-
parentMessageId: payload.messageId
|
|
8162
|
-
...prepareFailureDispatch
|
|
8209
|
+
parentMessageId: payload.messageId
|
|
8163
8210
|
});
|
|
8164
8211
|
} catch (postErr) {
|
|
8165
8212
|
turnLog.warn(
|
|
@@ -8167,6 +8214,7 @@ ${reason}`,
|
|
|
8167
8214
|
"dispatcher: prepare-rejection post failed"
|
|
8168
8215
|
);
|
|
8169
8216
|
}
|
|
8217
|
+
await sendPrepareFailureHandback();
|
|
8170
8218
|
throw this.concluded(`prepare_failed: ${reason}`);
|
|
8171
8219
|
}
|
|
8172
8220
|
}
|
|
@@ -8234,8 +8282,7 @@ ${reason}`,
|
|
|
8234
8282
|
kind: "final",
|
|
8235
8283
|
turnId,
|
|
8236
8284
|
// CT113: stamp the parent even on the prepare-failed close.
|
|
8237
|
-
parentMessageId: payload.messageId
|
|
8238
|
-
...prepareFailureDispatch
|
|
8285
|
+
parentMessageId: payload.messageId
|
|
8239
8286
|
});
|
|
8240
8287
|
} catch (postErr) {
|
|
8241
8288
|
turnLog.warn(
|
|
@@ -8243,6 +8290,7 @@ ${reason}`,
|
|
|
8243
8290
|
"dispatcher: prepare-failure post failed"
|
|
8244
8291
|
);
|
|
8245
8292
|
}
|
|
8293
|
+
await sendPrepareFailureHandback();
|
|
8246
8294
|
const failReason = `prepare_failed: ${reason}`;
|
|
8247
8295
|
throw this.concluded(failReason);
|
|
8248
8296
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cabane/companion",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.88",
|
|
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",
|