@cabane/companion 0.6.97 → 0.6.98
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 +60 -94
- package/dist/runtime.js +60 -94
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -2607,11 +2607,12 @@ var CabaneApi = class {
|
|
|
2607
2607
|
// - terminal 4xx (a deterministic rejection a replay can't fix) → rethrow,
|
|
2608
2608
|
// so the caller's error path runs as it does today (e.g. the committer
|
|
2609
2609
|
// re-arms its empty-final fallback).
|
|
2610
|
-
// - retryable (transport / 5xx —
|
|
2611
|
-
//
|
|
2612
|
-
//
|
|
2613
|
-
//
|
|
2614
|
-
//
|
|
2610
|
+
// - retryable (transport / 5xx — commit outcome may be unknown) → queue
|
|
2611
|
+
// and return. This acknowledges local delivery responsibility, not server
|
|
2612
|
+
// persistence. Replays retain the endpoint's identity: messages/thinking/
|
|
2613
|
+
// text use (turnId, seq) within each kind for nonzero seq; tool activity
|
|
2614
|
+
// uses (turnId, toolUseId). Legacy zero/missing seq bypasses row deduplication.
|
|
2615
|
+
// Server fences and the outbox's discard/eviction bounds still apply.
|
|
2615
2616
|
async durableCommit(kind, path, body, turnId, seq, signal) {
|
|
2616
2617
|
try {
|
|
2617
2618
|
await this.request("POST", path, body, {
|
|
@@ -2737,14 +2738,11 @@ var CabaneApi = class {
|
|
|
2737
2738
|
(e) => e.turnId === turnId && e.kind === "message" && e.body?.kind !== "runtime_notice"
|
|
2738
2739
|
);
|
|
2739
2740
|
}
|
|
2740
|
-
//
|
|
2741
|
-
//
|
|
2742
|
-
//
|
|
2743
|
-
//
|
|
2744
|
-
//
|
|
2745
|
-
// turn identically to claude-code. CT1354: those two verbs are all it
|
|
2746
|
-
// carries now; the outgoing acts commit at the call (`turnAct`). Agent-PAT
|
|
2747
|
-
// authed + self-scoped (`:agentId` must match the PAT's agent).
|
|
2741
|
+
// Read the skip intent recorded by the URL turn-control MCP surface.
|
|
2742
|
+
// Codex/opencode use this server-side state; settle imports skipped/skipReason
|
|
2743
|
+
// into the same local skip state used by claude-code's in-process tools.
|
|
2744
|
+
// Outgoing acts commit when called and are not buffered in this intent.
|
|
2745
|
+
// Agent-PAT authenticated and scoped to this agent and turn.
|
|
2748
2746
|
getTurnIntent(workspaceId, conversationId, agentId, turnId) {
|
|
2749
2747
|
const q = `turnId=${encodeURIComponent(turnId)}`;
|
|
2750
2748
|
return this.request(
|
|
@@ -2752,19 +2750,13 @@ var CabaneApi = class {
|
|
|
2752
2750
|
`/api/workspaces/${workspaceId}/conversations/${conversationId}/participants/agents/${agentId}/turn-intent?${q}`
|
|
2753
2751
|
);
|
|
2754
2752
|
}
|
|
2755
|
-
//
|
|
2756
|
-
//
|
|
2757
|
-
//
|
|
2758
|
-
//
|
|
2759
|
-
//
|
|
2760
|
-
//
|
|
2761
|
-
//
|
|
2762
|
-
// Bounded retry, on purpose: the act is a durable write whose loss strands a
|
|
2763
|
-
// person or a peer, and `act.callId` — minted once per tool invocation by the
|
|
2764
|
-
// caller — is what makes the retry safe: the server keys on `(turnId,
|
|
2765
|
-
// callId)`, so an attempt that landed before the response was lost is
|
|
2766
|
-
// replayed as the same row, never a second message. The caller's `signal`
|
|
2767
|
-
// is the per-call deadline (the tool must answer the model, not hang it).
|
|
2753
|
+
// Commit one outgoing act (ask, withdraw_ask, wake_me, cancel_wake) at
|
|
2754
|
+
// invocation. The server records the effect and receipt in one transaction;
|
|
2755
|
+
// an ok:false response is a recorded refusal for the tool to report.
|
|
2756
|
+
// Retries retain the caller's callId and recover the same receipt while the
|
|
2757
|
+
// HTTP route admits the request. Currently that route rejects ended spans
|
|
2758
|
+
// before receipt lookup: a 409 cannot prove an earlier attempt had no effect.
|
|
2759
|
+
// The caller's signal bounds how long the model waits for the tool result.
|
|
2768
2760
|
turnAct(workspaceId, turnId, act, signal) {
|
|
2769
2761
|
return this.request(
|
|
2770
2762
|
"POST",
|
|
@@ -2824,13 +2816,9 @@ var CabaneApi = class {
|
|
|
2824
2816
|
// loop; null when it settles) plus once with the session id on the
|
|
2825
2817
|
// first `system/init` frame.
|
|
2826
2818
|
//
|
|
2827
|
-
//
|
|
2828
|
-
//
|
|
2829
|
-
//
|
|
2830
|
-
// task fixes), and it drains with last-writer-wins per pair (see
|
|
2831
|
-
// `durableActiveRunWrite`). The session-id-only write (first-frame capture) is
|
|
2832
|
-
// left best-effort: it's lower-stakes and self-heals on the next turn, so it
|
|
2833
|
-
// stays a single-shot PATCH and is deliberately out of CT93's scope.
|
|
2819
|
+
// Admission is synchronous and bounded-retry: a start must return before
|
|
2820
|
+
// the model runs. Only a clear can be queued after a transient failure.
|
|
2821
|
+
// Session-id-only capture is a single-shot PATCH without outbox delivery.
|
|
2834
2822
|
async setActiveRun(workspaceId, conversationId, agentId, body) {
|
|
2835
2823
|
const path = `/api/workspaces/${workspaceId}/conversations/${conversationId}/participants/agents/${agentId}/active-run`;
|
|
2836
2824
|
const touchesFlag = Object.prototype.hasOwnProperty.call(body, "activeRunStartedAt");
|
|
@@ -2906,25 +2894,16 @@ var CabaneApi = class {
|
|
|
2906
2894
|
// CT29: per-device liveness moved off the per-workspace agent PAT and onto the
|
|
2907
2895
|
// device token — see `DeviceApi.heartbeat`. There is no agent-PAT heartbeat
|
|
2908
2896
|
// anymore.
|
|
2909
|
-
//
|
|
2910
|
-
// the
|
|
2911
|
-
//
|
|
2912
|
-
//
|
|
2913
|
-
//
|
|
2914
|
-
//
|
|
2915
|
-
//
|
|
2916
|
-
//
|
|
2917
|
-
//
|
|
2918
|
-
//
|
|
2919
|
-
// safe because `request` only retries the provably-non-committed failures
|
|
2920
|
-
// (transport errors + 5xx), never a 4xx, so no `(turnId, kind)` row is
|
|
2921
|
-
// double-posted. The caller threads its turn abort signal so a cancel
|
|
2922
|
-
// mid-commit aborts the in-flight POST rather than letting it land.
|
|
2923
|
-
// Workzone: a commit is a message — the agent's first or last words — or the
|
|
2924
|
-
// runtime's own line (`runtime_notice`). The words between tool calls go
|
|
2925
|
-
// through `reportText`, the trail's door. `seq` is the companion's per-turn
|
|
2926
|
-
// monotonic counter, stamped on the row so the merged timeline orders the
|
|
2927
|
-
// commit deterministically against the persisted tool/thinking rows.
|
|
2897
|
+
// Commit the agent's first/last words (message) or a runtime_notice through
|
|
2898
|
+
// the public messages endpoint. Intermediate working text uses reportText.
|
|
2899
|
+
// Attribution comes from the agent credential; a new speech row can offer
|
|
2900
|
+
// work to other agents under the server's current routing rules.
|
|
2901
|
+
// Retried messages retain (turnId, seq), the server's duplicate identity for
|
|
2902
|
+
// nonzero seq (legacy zero/missing seq bypasses it),
|
|
2903
|
+
// so response loss need not produce a second row or repeat routing effects.
|
|
2904
|
+
// seq also orders speech against the turn's tool/thinking/text trail.
|
|
2905
|
+
// Transport/5xx failures get bounded retries and optional outbox delivery;
|
|
2906
|
+
// terminal 4xx and cancellation stop delivery rather than proving no commit.
|
|
2928
2907
|
postTurnMessage(workspaceId, conversationId, body, signal) {
|
|
2929
2908
|
return this.durableCommit(
|
|
2930
2909
|
"message",
|
|
@@ -2935,10 +2914,11 @@ var CabaneApi = class {
|
|
|
2935
2914
|
signal
|
|
2936
2915
|
);
|
|
2937
2916
|
}
|
|
2938
|
-
//
|
|
2939
|
-
//
|
|
2940
|
-
//
|
|
2941
|
-
//
|
|
2917
|
+
// Report a tool transition (start/done/error). The server checks span
|
|
2918
|
+
// ownership, upserts by (turnId, toolUseId), and publishes agent_activity SSE.
|
|
2919
|
+
// Ended spans accept late activity. Persistence failure is warn-only there:
|
|
2920
|
+
// a successful response can still represent live SSE without a stored row.
|
|
2921
|
+
// Agent-PAT authenticated; the URL agent must match the credential.
|
|
2942
2922
|
reportActivity(workspaceId, conversationId, agentId, body, signal) {
|
|
2943
2923
|
return this.durableCommit(
|
|
2944
2924
|
"activity",
|
|
@@ -2949,11 +2929,10 @@ var CabaneApi = class {
|
|
|
2949
2929
|
signal
|
|
2950
2930
|
);
|
|
2951
2931
|
}
|
|
2952
|
-
//
|
|
2953
|
-
//
|
|
2954
|
-
//
|
|
2955
|
-
//
|
|
2956
|
-
// cosmetic (no retry), but the abort signal is threaded so a cancel stops it.
|
|
2932
|
+
// Report thinking through bounded retry and optional outbox delivery.
|
|
2933
|
+
// The server checks ownership, inserts by (turnId, type, seq), and publishes SSE,
|
|
2934
|
+
// including for ended spans. As with activity, persistence failure is
|
|
2935
|
+
// warn-only and a successful response does not guarantee a stored row.
|
|
2957
2936
|
reportThinking(workspaceId, conversationId, agentId, body, signal) {
|
|
2958
2937
|
return this.durableCommit(
|
|
2959
2938
|
"thinking",
|
|
@@ -2964,10 +2943,10 @@ var CabaneApi = class {
|
|
|
2964
2943
|
signal
|
|
2965
2944
|
);
|
|
2966
2945
|
}
|
|
2967
|
-
//
|
|
2968
|
-
//
|
|
2969
|
-
// replay
|
|
2970
|
-
//
|
|
2946
|
+
// Report working text to message_activity without landing speech or waking
|
|
2947
|
+
// agents. The server requires an owned, running span and persists before SSE.
|
|
2948
|
+
// Outbox replay retains (turnId, type, seq) for nonzero seq, but an ended
|
|
2949
|
+
// span rejects it: this queue does not promise delivery after settlement.
|
|
2971
2950
|
reportText(workspaceId, conversationId, agentId, body, signal) {
|
|
2972
2951
|
return this.durableCommit(
|
|
2973
2952
|
"text",
|
|
@@ -3772,25 +3751,15 @@ var turnRequestSchema = z8.object({
|
|
|
3772
3751
|
mcpUrl: z8.string(),
|
|
3773
3752
|
bearer: z8.string(),
|
|
3774
3753
|
activeConversationId: z8.string(),
|
|
3775
|
-
//
|
|
3776
|
-
//
|
|
3777
|
-
//
|
|
3778
|
-
//
|
|
3779
|
-
//
|
|
3780
|
-
// verbs they can't get from the companion's in-process SDK server. Optional:
|
|
3781
|
-
// claude-code ignores it (it mounts the in-process instance instead), and
|
|
3782
|
-
// every existing `cabane`-block fixture keeps parsing unchanged; the
|
|
3783
|
-
// companion always populates it (`build-options.ts`).
|
|
3754
|
+
// Scoped turn-control MCP URL. Codex/opencode mount it as cabane_companion
|
|
3755
|
+
// with the bearer and active-conversation header. It supplies ask,
|
|
3756
|
+
// withdraw_ask, wake_me, cancel_wake and skip_turn. Claude Code mounts the
|
|
3757
|
+
// companion's in-process instance instead. Optional for compatibility;
|
|
3758
|
+
// the companion populates it in build-options.ts.
|
|
3784
3759
|
turnControlUrl: z8.string().optional(),
|
|
3785
|
-
//
|
|
3786
|
-
// adapters
|
|
3787
|
-
//
|
|
3788
|
-
// native runtime's interim tool surface calls the workspace-scoped REST API
|
|
3789
|
-
// DIRECTLY, so it needs the id host-side rather than trusting the model to
|
|
3790
|
-
// pass it. Optional so every existing `cabane`-block constructor (the three
|
|
3791
|
-
// adapters' conformance fixtures, tests) keeps parsing unchanged — the companion
|
|
3792
|
-
// always populates it (`build-options.ts`), and the native adapter fails the
|
|
3793
|
-
// turn loudly when it is somehow absent rather than guessing.
|
|
3760
|
+
// Workspace coordinate retained for envelope compatibility. Current
|
|
3761
|
+
// adapters use MCP, whose workspace operations take workspaceId explicitly.
|
|
3762
|
+
// The companion supplies this field; it is not required by those adapters.
|
|
3794
3763
|
workspaceId: z8.string().optional()
|
|
3795
3764
|
}),
|
|
3796
3765
|
// Machine-local resolution (host-filled): the checkout cwd, extra env from a
|
|
@@ -5255,8 +5224,8 @@ var OPENCODE_ADDENDUM = [
|
|
|
5255
5224
|
"collision runs the other way too: `read` is a host tool here AND `files.read` is an",
|
|
5256
5225
|
"operation name, so keep the bare verb and the dotted name apart. If a tool ever appears with",
|
|
5257
5226
|
"an `mcp__\u2026__` prefix, that prefix is not part of its name here. One more harness fact: you",
|
|
5258
|
-
"can interleave narration with tool calls, but
|
|
5259
|
-
"turn's
|
|
5227
|
+
"can interleave narration with tool calls, but your final message is the",
|
|
5228
|
+
"turn's answer \u2014 write your closing answer last."
|
|
5260
5229
|
].join(" ");
|
|
5261
5230
|
|
|
5262
5231
|
// packages/agent-runtime/src/opencode/events.ts
|
|
@@ -6320,7 +6289,7 @@ var CODEX_ADDENDUM = [
|
|
|
6320
6289
|
"possibly deferred too. There is no Cabane `read`/`write`/`search` tool \u2014 those are",
|
|
6321
6290
|
"operation NAMES you pass to `query` / `mutate`. One more harness fact: you can interleave",
|
|
6322
6291
|
"narration",
|
|
6323
|
-
"with tool calls, but
|
|
6292
|
+
"with tool calls, but your final message is the turn's answer \u2014 write your",
|
|
6324
6293
|
"closing answer last."
|
|
6325
6294
|
].join(" ");
|
|
6326
6295
|
|
|
@@ -7921,7 +7890,7 @@ var MAX_WAKE_NOTE_BYTES = 2e3;
|
|
|
7921
7890
|
var MAX_WITHDRAW_REASON_BYTES = 400;
|
|
7922
7891
|
var MAX_SKIP_REASON_BYTES = 500;
|
|
7923
7892
|
var TURN_CONTROL_TOOL_DESCRIPTIONS = {
|
|
7924
|
-
ask: `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
|
|
7893
|
+
ask: `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\`. Two forms: a SINGLE question \u2014 a \`headline\` 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? }\`. Provide EITHER \`question\` (single) or \`questions\` (array), never both. **Several decisions landing together are ONE call with \`questions\`** \u2014 never cram the extras into prose, drop all but one, or fire an ask each: a second \`ask\` is a second inbox item competing with the first, never an edit to it. Each question keeps the same form: 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 ${MIN_ASK_OPTIONS}\u2013${MAX_ASK_OPTIONS} \`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. 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. The human sends ONE response: supplied answers are recorded, omissions become terminally Unanswered. A person can DISMISS a question instead of answering it \u2014 it ends, you are not told, nothing wakes you. Check an ask's status before re-raising a vanished one. 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 bring a peer AGENT in, name them by \`@handle\` in your reply.`,
|
|
7925
7894
|
cancel_wake: `Stand down your own wake \u2014 the inverse of \`wake_me\`. No arguments: it cancels the wake you have armed in this conversation, NOW, when you call it. 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. Each call acts as it arrives: \`cancel_wake\` then \`wake_me\` leaves the NEW wake armed; \`wake_me\` then \`cancel_wake\` leaves nothing armed. It reaches only your own wake in this conversation \u2014 never a peer's, and never a recurring schedule (those are \`schedules.*\`).`,
|
|
7926
7895
|
skip_turn: `End your current turn WITHOUT posting a reply. Call this when you've been woken to read a message that genuinely doesn't need a response from you \u2014 a thanks/aside, a question already answered, chatter outside your lane, or something another participant clearly owns. Your turn ends silently: no message bubble is posted. The \`reason\` is a short free-text note for telemetry (e.g. "already answered by cabane", "thanks, nothing to add"). Prefer this over posting a low-value "ok!"/"got it" reply. Don't also write a reply when you skip \u2014 skipping IS the whole turn.`,
|
|
7927
7896
|
wake_me: `Wake yourself later \u2014 end this turn now and be re-dispatched at a time you pick, with a note you write to yourself. Use it for "wait until X": when the thing you need hasn't happened yet (a PR isn't merged, a human hasn't answered), arm a wake, end your turn, and you're woken later to CHECK \u2014 read the workspace, and either act or re-arm. Ground the delay before you arm it. Almost every wake is short \u2014 seconds to a couple of hours \u2014 waiting on a condition you can name: a session limit resetting, a PR merging. Reach past a few hours only when (a) a human asked for that timing, or (b) the wait is pinned to a real external event you can name \u2014 a report that only runs Mondays, a known reset time. A speculative far-future check-in you invented yourself is the one thing not to arm: if no one asked and you can't name both what clears the wait and why it takes that long, don't arm it \u2014 finish now, or raise an \`ask\`. Pass EXACTLY ONE of \`afterSeconds\` (a relative delay \u2014 \`300\` for five minutes) or \`at\` (an absolute ISO-8601 timestamp WITH a zone, e.g. \`2026-07-16T09:00:00-07:00\` \u2014 YOU compute it; the system never parses natural-language time). \`note\` is a message to your future self \u2014 it becomes the body of the wake message that re-dispatches you, so write the condition to re-check ("check whether CT441 merged yet"). The wake arms NOW, when you call this, and the delay counts from this call; a later \`wake_me\` in the same turn replaces it, and \`cancel_wake\` stands it down. A wake that comes due while your turn is still running waits and is delivered after your reply. This is the ONLY sanctioned way to schedule your own continuation; never reach for a host cron/scheduler. Guardrails: at least 60s out, at most 14 days \u2014 a wake that can't be armed returns an error here, in this call, so you can recompute; widen the interval as a loop ages (5m \u2192 15m \u2192 1h\u2026) rather than hammering; after many consecutive re-arms with no other activity you'll be steered to raise an \`ask\` to the human instead.`,
|
|
@@ -9181,8 +9150,7 @@ var TurnExecution = class {
|
|
|
9181
9150
|
// CT238: this turn's conversation, forwarded as the active-conversation
|
|
9182
9151
|
// header so a cross-thread post/spawn stamps its origin.
|
|
9183
9152
|
activeConversationId: payload.conversationId,
|
|
9184
|
-
//
|
|
9185
|
-
// tool surface scopes its calls with it.
|
|
9153
|
+
// Workspace coordinate retained in the compatible TurnRequest envelope.
|
|
9186
9154
|
workspaceId,
|
|
9187
9155
|
// CT289: the operator's auto-memory escape hatch (machine-local), when set.
|
|
9188
9156
|
...this.opts.local.claudeCode ? { claudeCode: this.opts.local.claudeCode } : {}
|
|
@@ -9707,11 +9675,9 @@ var Dispatcher = class {
|
|
|
9707
9675
|
} catch {
|
|
9708
9676
|
}
|
|
9709
9677
|
}
|
|
9710
|
-
//
|
|
9711
|
-
//
|
|
9712
|
-
//
|
|
9713
|
-
// supports ("a replay reports, it never re-points"). Omitted on a fresh
|
|
9714
|
-
// dispatch, where minting is correct.
|
|
9678
|
+
// The supervisor supplies a stable turnId on both fresh and resumed dispatch.
|
|
9679
|
+
// resumed is separate: only replay seeds sequence/speech recovery from the
|
|
9680
|
+
// prior span. A direct caller omitting turnId gets a fresh id.
|
|
9715
9681
|
//
|
|
9716
9682
|
// CT1261: a short `handle` creates a `TurnExecution` and runs it. The
|
|
9717
9683
|
// supervisor seam hands the execution exactly the three cross-turn duties —
|
package/dist/runtime.js
CHANGED
|
@@ -2025,11 +2025,12 @@ var CabaneApi = class {
|
|
|
2025
2025
|
// - terminal 4xx (a deterministic rejection a replay can't fix) → rethrow,
|
|
2026
2026
|
// so the caller's error path runs as it does today (e.g. the committer
|
|
2027
2027
|
// re-arms its empty-final fallback).
|
|
2028
|
-
// - retryable (transport / 5xx —
|
|
2029
|
-
//
|
|
2030
|
-
//
|
|
2031
|
-
//
|
|
2032
|
-
//
|
|
2028
|
+
// - retryable (transport / 5xx — commit outcome may be unknown) → queue
|
|
2029
|
+
// and return. This acknowledges local delivery responsibility, not server
|
|
2030
|
+
// persistence. Replays retain the endpoint's identity: messages/thinking/
|
|
2031
|
+
// text use (turnId, seq) within each kind for nonzero seq; tool activity
|
|
2032
|
+
// uses (turnId, toolUseId). Legacy zero/missing seq bypasses row deduplication.
|
|
2033
|
+
// Server fences and the outbox's discard/eviction bounds still apply.
|
|
2033
2034
|
async durableCommit(kind, path, body, turnId, seq, signal) {
|
|
2034
2035
|
try {
|
|
2035
2036
|
await this.request("POST", path, body, {
|
|
@@ -2155,14 +2156,11 @@ var CabaneApi = class {
|
|
|
2155
2156
|
(e) => e.turnId === turnId && e.kind === "message" && e.body?.kind !== "runtime_notice"
|
|
2156
2157
|
);
|
|
2157
2158
|
}
|
|
2158
|
-
//
|
|
2159
|
-
//
|
|
2160
|
-
//
|
|
2161
|
-
//
|
|
2162
|
-
//
|
|
2163
|
-
// turn identically to claude-code. CT1354: those two verbs are all it
|
|
2164
|
-
// carries now; the outgoing acts commit at the call (`turnAct`). Agent-PAT
|
|
2165
|
-
// authed + self-scoped (`:agentId` must match the PAT's agent).
|
|
2159
|
+
// Read the skip intent recorded by the URL turn-control MCP surface.
|
|
2160
|
+
// Codex/opencode use this server-side state; settle imports skipped/skipReason
|
|
2161
|
+
// into the same local skip state used by claude-code's in-process tools.
|
|
2162
|
+
// Outgoing acts commit when called and are not buffered in this intent.
|
|
2163
|
+
// Agent-PAT authenticated and scoped to this agent and turn.
|
|
2166
2164
|
getTurnIntent(workspaceId, conversationId, agentId, turnId) {
|
|
2167
2165
|
const q = `turnId=${encodeURIComponent(turnId)}`;
|
|
2168
2166
|
return this.request(
|
|
@@ -2170,19 +2168,13 @@ var CabaneApi = class {
|
|
|
2170
2168
|
`/api/workspaces/${workspaceId}/conversations/${conversationId}/participants/agents/${agentId}/turn-intent?${q}`
|
|
2171
2169
|
);
|
|
2172
2170
|
}
|
|
2173
|
-
//
|
|
2174
|
-
//
|
|
2175
|
-
//
|
|
2176
|
-
//
|
|
2177
|
-
//
|
|
2178
|
-
//
|
|
2179
|
-
//
|
|
2180
|
-
// Bounded retry, on purpose: the act is a durable write whose loss strands a
|
|
2181
|
-
// person or a peer, and `act.callId` — minted once per tool invocation by the
|
|
2182
|
-
// caller — is what makes the retry safe: the server keys on `(turnId,
|
|
2183
|
-
// callId)`, so an attempt that landed before the response was lost is
|
|
2184
|
-
// replayed as the same row, never a second message. The caller's `signal`
|
|
2185
|
-
// is the per-call deadline (the tool must answer the model, not hang it).
|
|
2171
|
+
// Commit one outgoing act (ask, withdraw_ask, wake_me, cancel_wake) at
|
|
2172
|
+
// invocation. The server records the effect and receipt in one transaction;
|
|
2173
|
+
// an ok:false response is a recorded refusal for the tool to report.
|
|
2174
|
+
// Retries retain the caller's callId and recover the same receipt while the
|
|
2175
|
+
// HTTP route admits the request. Currently that route rejects ended spans
|
|
2176
|
+
// before receipt lookup: a 409 cannot prove an earlier attempt had no effect.
|
|
2177
|
+
// The caller's signal bounds how long the model waits for the tool result.
|
|
2186
2178
|
turnAct(workspaceId, turnId, act, signal) {
|
|
2187
2179
|
return this.request(
|
|
2188
2180
|
"POST",
|
|
@@ -2242,13 +2234,9 @@ var CabaneApi = class {
|
|
|
2242
2234
|
// loop; null when it settles) plus once with the session id on the
|
|
2243
2235
|
// first `system/init` frame.
|
|
2244
2236
|
//
|
|
2245
|
-
//
|
|
2246
|
-
//
|
|
2247
|
-
//
|
|
2248
|
-
// task fixes), and it drains with last-writer-wins per pair (see
|
|
2249
|
-
// `durableActiveRunWrite`). The session-id-only write (first-frame capture) is
|
|
2250
|
-
// left best-effort: it's lower-stakes and self-heals on the next turn, so it
|
|
2251
|
-
// stays a single-shot PATCH and is deliberately out of CT93's scope.
|
|
2237
|
+
// Admission is synchronous and bounded-retry: a start must return before
|
|
2238
|
+
// the model runs. Only a clear can be queued after a transient failure.
|
|
2239
|
+
// Session-id-only capture is a single-shot PATCH without outbox delivery.
|
|
2252
2240
|
async setActiveRun(workspaceId, conversationId, agentId, body) {
|
|
2253
2241
|
const path = `/api/workspaces/${workspaceId}/conversations/${conversationId}/participants/agents/${agentId}/active-run`;
|
|
2254
2242
|
const touchesFlag = Object.prototype.hasOwnProperty.call(body, "activeRunStartedAt");
|
|
@@ -2324,25 +2312,16 @@ var CabaneApi = class {
|
|
|
2324
2312
|
// CT29: per-device liveness moved off the per-workspace agent PAT and onto the
|
|
2325
2313
|
// device token — see `DeviceApi.heartbeat`. There is no agent-PAT heartbeat
|
|
2326
2314
|
// anymore.
|
|
2327
|
-
//
|
|
2328
|
-
// the
|
|
2329
|
-
//
|
|
2330
|
-
//
|
|
2331
|
-
//
|
|
2332
|
-
//
|
|
2333
|
-
//
|
|
2334
|
-
//
|
|
2335
|
-
//
|
|
2336
|
-
//
|
|
2337
|
-
// safe because `request` only retries the provably-non-committed failures
|
|
2338
|
-
// (transport errors + 5xx), never a 4xx, so no `(turnId, kind)` row is
|
|
2339
|
-
// double-posted. The caller threads its turn abort signal so a cancel
|
|
2340
|
-
// mid-commit aborts the in-flight POST rather than letting it land.
|
|
2341
|
-
// Workzone: a commit is a message — the agent's first or last words — or the
|
|
2342
|
-
// runtime's own line (`runtime_notice`). The words between tool calls go
|
|
2343
|
-
// through `reportText`, the trail's door. `seq` is the companion's per-turn
|
|
2344
|
-
// monotonic counter, stamped on the row so the merged timeline orders the
|
|
2345
|
-
// commit deterministically against the persisted tool/thinking rows.
|
|
2315
|
+
// Commit the agent's first/last words (message) or a runtime_notice through
|
|
2316
|
+
// the public messages endpoint. Intermediate working text uses reportText.
|
|
2317
|
+
// Attribution comes from the agent credential; a new speech row can offer
|
|
2318
|
+
// work to other agents under the server's current routing rules.
|
|
2319
|
+
// Retried messages retain (turnId, seq), the server's duplicate identity for
|
|
2320
|
+
// nonzero seq (legacy zero/missing seq bypasses it),
|
|
2321
|
+
// so response loss need not produce a second row or repeat routing effects.
|
|
2322
|
+
// seq also orders speech against the turn's tool/thinking/text trail.
|
|
2323
|
+
// Transport/5xx failures get bounded retries and optional outbox delivery;
|
|
2324
|
+
// terminal 4xx and cancellation stop delivery rather than proving no commit.
|
|
2346
2325
|
postTurnMessage(workspaceId, conversationId, body, signal) {
|
|
2347
2326
|
return this.durableCommit(
|
|
2348
2327
|
"message",
|
|
@@ -2353,10 +2332,11 @@ var CabaneApi = class {
|
|
|
2353
2332
|
signal
|
|
2354
2333
|
);
|
|
2355
2334
|
}
|
|
2356
|
-
//
|
|
2357
|
-
//
|
|
2358
|
-
//
|
|
2359
|
-
//
|
|
2335
|
+
// Report a tool transition (start/done/error). The server checks span
|
|
2336
|
+
// ownership, upserts by (turnId, toolUseId), and publishes agent_activity SSE.
|
|
2337
|
+
// Ended spans accept late activity. Persistence failure is warn-only there:
|
|
2338
|
+
// a successful response can still represent live SSE without a stored row.
|
|
2339
|
+
// Agent-PAT authenticated; the URL agent must match the credential.
|
|
2360
2340
|
reportActivity(workspaceId, conversationId, agentId, body, signal) {
|
|
2361
2341
|
return this.durableCommit(
|
|
2362
2342
|
"activity",
|
|
@@ -2367,11 +2347,10 @@ var CabaneApi = class {
|
|
|
2367
2347
|
signal
|
|
2368
2348
|
);
|
|
2369
2349
|
}
|
|
2370
|
-
//
|
|
2371
|
-
//
|
|
2372
|
-
//
|
|
2373
|
-
//
|
|
2374
|
-
// cosmetic (no retry), but the abort signal is threaded so a cancel stops it.
|
|
2350
|
+
// Report thinking through bounded retry and optional outbox delivery.
|
|
2351
|
+
// The server checks ownership, inserts by (turnId, type, seq), and publishes SSE,
|
|
2352
|
+
// including for ended spans. As with activity, persistence failure is
|
|
2353
|
+
// warn-only and a successful response does not guarantee a stored row.
|
|
2375
2354
|
reportThinking(workspaceId, conversationId, agentId, body, signal) {
|
|
2376
2355
|
return this.durableCommit(
|
|
2377
2356
|
"thinking",
|
|
@@ -2382,10 +2361,10 @@ var CabaneApi = class {
|
|
|
2382
2361
|
signal
|
|
2383
2362
|
);
|
|
2384
2363
|
}
|
|
2385
|
-
//
|
|
2386
|
-
//
|
|
2387
|
-
// replay
|
|
2388
|
-
//
|
|
2364
|
+
// Report working text to message_activity without landing speech or waking
|
|
2365
|
+
// agents. The server requires an owned, running span and persists before SSE.
|
|
2366
|
+
// Outbox replay retains (turnId, type, seq) for nonzero seq, but an ended
|
|
2367
|
+
// span rejects it: this queue does not promise delivery after settlement.
|
|
2389
2368
|
reportText(workspaceId, conversationId, agentId, body, signal) {
|
|
2390
2369
|
return this.durableCommit(
|
|
2391
2370
|
"text",
|
|
@@ -3269,25 +3248,15 @@ var turnRequestSchema = z8.object({
|
|
|
3269
3248
|
mcpUrl: z8.string(),
|
|
3270
3249
|
bearer: z8.string(),
|
|
3271
3250
|
activeConversationId: z8.string(),
|
|
3272
|
-
//
|
|
3273
|
-
//
|
|
3274
|
-
//
|
|
3275
|
-
//
|
|
3276
|
-
//
|
|
3277
|
-
// verbs they can't get from the companion's in-process SDK server. Optional:
|
|
3278
|
-
// claude-code ignores it (it mounts the in-process instance instead), and
|
|
3279
|
-
// every existing `cabane`-block fixture keeps parsing unchanged; the
|
|
3280
|
-
// companion always populates it (`build-options.ts`).
|
|
3251
|
+
// Scoped turn-control MCP URL. Codex/opencode mount it as cabane_companion
|
|
3252
|
+
// with the bearer and active-conversation header. It supplies ask,
|
|
3253
|
+
// withdraw_ask, wake_me, cancel_wake and skip_turn. Claude Code mounts the
|
|
3254
|
+
// companion's in-process instance instead. Optional for compatibility;
|
|
3255
|
+
// the companion populates it in build-options.ts.
|
|
3281
3256
|
turnControlUrl: z8.string().optional(),
|
|
3282
|
-
//
|
|
3283
|
-
// adapters
|
|
3284
|
-
//
|
|
3285
|
-
// native runtime's interim tool surface calls the workspace-scoped REST API
|
|
3286
|
-
// DIRECTLY, so it needs the id host-side rather than trusting the model to
|
|
3287
|
-
// pass it. Optional so every existing `cabane`-block constructor (the three
|
|
3288
|
-
// adapters' conformance fixtures, tests) keeps parsing unchanged — the companion
|
|
3289
|
-
// always populates it (`build-options.ts`), and the native adapter fails the
|
|
3290
|
-
// turn loudly when it is somehow absent rather than guessing.
|
|
3257
|
+
// Workspace coordinate retained for envelope compatibility. Current
|
|
3258
|
+
// adapters use MCP, whose workspace operations take workspaceId explicitly.
|
|
3259
|
+
// The companion supplies this field; it is not required by those adapters.
|
|
3291
3260
|
workspaceId: z8.string().optional()
|
|
3292
3261
|
}),
|
|
3293
3262
|
// Machine-local resolution (host-filled): the checkout cwd, extra env from a
|
|
@@ -4752,8 +4721,8 @@ var OPENCODE_ADDENDUM = [
|
|
|
4752
4721
|
"collision runs the other way too: `read` is a host tool here AND `files.read` is an",
|
|
4753
4722
|
"operation name, so keep the bare verb and the dotted name apart. If a tool ever appears with",
|
|
4754
4723
|
"an `mcp__\u2026__` prefix, that prefix is not part of its name here. One more harness fact: you",
|
|
4755
|
-
"can interleave narration with tool calls, but
|
|
4756
|
-
"turn's
|
|
4724
|
+
"can interleave narration with tool calls, but your final message is the",
|
|
4725
|
+
"turn's answer \u2014 write your closing answer last."
|
|
4757
4726
|
].join(" ");
|
|
4758
4727
|
|
|
4759
4728
|
// packages/agent-runtime/src/opencode/events.ts
|
|
@@ -5817,7 +5786,7 @@ var CODEX_ADDENDUM = [
|
|
|
5817
5786
|
"possibly deferred too. There is no Cabane `read`/`write`/`search` tool \u2014 those are",
|
|
5818
5787
|
"operation NAMES you pass to `query` / `mutate`. One more harness fact: you can interleave",
|
|
5819
5788
|
"narration",
|
|
5820
|
-
"with tool calls, but
|
|
5789
|
+
"with tool calls, but your final message is the turn's answer \u2014 write your",
|
|
5821
5790
|
"closing answer last."
|
|
5822
5791
|
].join(" ");
|
|
5823
5792
|
|
|
@@ -7418,7 +7387,7 @@ var MAX_WAKE_NOTE_BYTES = 2e3;
|
|
|
7418
7387
|
var MAX_WITHDRAW_REASON_BYTES = 400;
|
|
7419
7388
|
var MAX_SKIP_REASON_BYTES = 500;
|
|
7420
7389
|
var TURN_CONTROL_TOOL_DESCRIPTIONS = {
|
|
7421
|
-
ask: `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
|
|
7390
|
+
ask: `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\`. Two forms: a SINGLE question \u2014 a \`headline\` 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? }\`. Provide EITHER \`question\` (single) or \`questions\` (array), never both. **Several decisions landing together are ONE call with \`questions\`** \u2014 never cram the extras into prose, drop all but one, or fire an ask each: a second \`ask\` is a second inbox item competing with the first, never an edit to it. Each question keeps the same form: 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 ${MIN_ASK_OPTIONS}\u2013${MAX_ASK_OPTIONS} \`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. 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. The human sends ONE response: supplied answers are recorded, omissions become terminally Unanswered. A person can DISMISS a question instead of answering it \u2014 it ends, you are not told, nothing wakes you. Check an ask's status before re-raising a vanished one. 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 bring a peer AGENT in, name them by \`@handle\` in your reply.`,
|
|
7422
7391
|
cancel_wake: `Stand down your own wake \u2014 the inverse of \`wake_me\`. No arguments: it cancels the wake you have armed in this conversation, NOW, when you call it. 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. Each call acts as it arrives: \`cancel_wake\` then \`wake_me\` leaves the NEW wake armed; \`wake_me\` then \`cancel_wake\` leaves nothing armed. It reaches only your own wake in this conversation \u2014 never a peer's, and never a recurring schedule (those are \`schedules.*\`).`,
|
|
7423
7392
|
skip_turn: `End your current turn WITHOUT posting a reply. Call this when you've been woken to read a message that genuinely doesn't need a response from you \u2014 a thanks/aside, a question already answered, chatter outside your lane, or something another participant clearly owns. Your turn ends silently: no message bubble is posted. The \`reason\` is a short free-text note for telemetry (e.g. "already answered by cabane", "thanks, nothing to add"). Prefer this over posting a low-value "ok!"/"got it" reply. Don't also write a reply when you skip \u2014 skipping IS the whole turn.`,
|
|
7424
7393
|
wake_me: `Wake yourself later \u2014 end this turn now and be re-dispatched at a time you pick, with a note you write to yourself. Use it for "wait until X": when the thing you need hasn't happened yet (a PR isn't merged, a human hasn't answered), arm a wake, end your turn, and you're woken later to CHECK \u2014 read the workspace, and either act or re-arm. Ground the delay before you arm it. Almost every wake is short \u2014 seconds to a couple of hours \u2014 waiting on a condition you can name: a session limit resetting, a PR merging. Reach past a few hours only when (a) a human asked for that timing, or (b) the wait is pinned to a real external event you can name \u2014 a report that only runs Mondays, a known reset time. A speculative far-future check-in you invented yourself is the one thing not to arm: if no one asked and you can't name both what clears the wait and why it takes that long, don't arm it \u2014 finish now, or raise an \`ask\`. Pass EXACTLY ONE of \`afterSeconds\` (a relative delay \u2014 \`300\` for five minutes) or \`at\` (an absolute ISO-8601 timestamp WITH a zone, e.g. \`2026-07-16T09:00:00-07:00\` \u2014 YOU compute it; the system never parses natural-language time). \`note\` is a message to your future self \u2014 it becomes the body of the wake message that re-dispatches you, so write the condition to re-check ("check whether CT441 merged yet"). The wake arms NOW, when you call this, and the delay counts from this call; a later \`wake_me\` in the same turn replaces it, and \`cancel_wake\` stands it down. A wake that comes due while your turn is still running waits and is delivered after your reply. This is the ONLY sanctioned way to schedule your own continuation; never reach for a host cron/scheduler. Guardrails: at least 60s out, at most 14 days \u2014 a wake that can't be armed returns an error here, in this call, so you can recompute; widen the interval as a loop ages (5m \u2192 15m \u2192 1h\u2026) rather than hammering; after many consecutive re-arms with no other activity you'll be steered to raise an \`ask\` to the human instead.`,
|
|
@@ -8678,8 +8647,7 @@ var TurnExecution = class {
|
|
|
8678
8647
|
// CT238: this turn's conversation, forwarded as the active-conversation
|
|
8679
8648
|
// header so a cross-thread post/spawn stamps its origin.
|
|
8680
8649
|
activeConversationId: payload.conversationId,
|
|
8681
|
-
//
|
|
8682
|
-
// tool surface scopes its calls with it.
|
|
8650
|
+
// Workspace coordinate retained in the compatible TurnRequest envelope.
|
|
8683
8651
|
workspaceId,
|
|
8684
8652
|
// CT289: the operator's auto-memory escape hatch (machine-local), when set.
|
|
8685
8653
|
...this.opts.local.claudeCode ? { claudeCode: this.opts.local.claudeCode } : {}
|
|
@@ -9204,11 +9172,9 @@ var Dispatcher = class {
|
|
|
9204
9172
|
} catch {
|
|
9205
9173
|
}
|
|
9206
9174
|
}
|
|
9207
|
-
//
|
|
9208
|
-
//
|
|
9209
|
-
//
|
|
9210
|
-
// supports ("a replay reports, it never re-points"). Omitted on a fresh
|
|
9211
|
-
// dispatch, where minting is correct.
|
|
9175
|
+
// The supervisor supplies a stable turnId on both fresh and resumed dispatch.
|
|
9176
|
+
// resumed is separate: only replay seeds sequence/speech recovery from the
|
|
9177
|
+
// prior span. A direct caller omitting turnId gets a fresh id.
|
|
9212
9178
|
//
|
|
9213
9179
|
// CT1261: a short `handle` creates a `TurnExecution` and runs it. The
|
|
9214
9180
|
// supervisor seam hands the execution exactly the three cross-turn duties —
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cabane/companion",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.98",
|
|
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",
|