@cabane/companion 0.6.78 → 0.6.80
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 +176 -13
- package/dist/runtime.js +176 -13
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -5100,6 +5100,10 @@ async function* decodeOpencodeStream(events, ctx) {
|
|
|
5100
5100
|
if (sealed) yield sealed;
|
|
5101
5101
|
ok = false;
|
|
5102
5102
|
const errorText = readSessionError(ev.properties);
|
|
5103
|
+
const notice = errorText.trim().slice(0, 200).trimEnd();
|
|
5104
|
+
if (notice && !/^[\w:-]+$/.test(notice)) {
|
|
5105
|
+
yield { type: "runtime_notice", body: notice };
|
|
5106
|
+
}
|
|
5103
5107
|
const failure = classifyErrorText(errorText);
|
|
5104
5108
|
reason = failure ? encodeFailureReason(failure) : `error:${errorText.slice(0, 200)}`;
|
|
5105
5109
|
settled = true;
|
|
@@ -5509,6 +5513,7 @@ var opencodeAdapter = createOpencodeAdapter();
|
|
|
5509
5513
|
// packages/agent-runtime/src/opencode/conformance.ts
|
|
5510
5514
|
var ABORT_SENTINEL2 = { __abortHere: true };
|
|
5511
5515
|
var NEW_SESSION_ID = "sess_new";
|
|
5516
|
+
var BOUNDED_NOTICE = "You've hit your usage limit \xB7 ".padEnd(200, "x");
|
|
5512
5517
|
var COMPANION_POLICY = {
|
|
5513
5518
|
hostFs: false,
|
|
5514
5519
|
web: true,
|
|
@@ -5700,6 +5705,7 @@ var OPENCODE_CONFORMANCE_FIXTURES = [
|
|
|
5700
5705
|
expected: [
|
|
5701
5706
|
sessionEvent2(NEW_SESSION_ID),
|
|
5702
5707
|
{ type: "text", body: "Partial work.", terminal: false },
|
|
5708
|
+
{ type: "runtime_notice", body: "provider exploded" },
|
|
5703
5709
|
{ type: "result", ok: false, reason: "error:provider exploded" }
|
|
5704
5710
|
]
|
|
5705
5711
|
},
|
|
@@ -5716,7 +5722,11 @@ var OPENCODE_CONFORMANCE_FIXTURES = [
|
|
|
5716
5722
|
properties: { error: { name: "RateLimitError", message: "rate limit exceeded (429)" } }
|
|
5717
5723
|
}
|
|
5718
5724
|
],
|
|
5719
|
-
expected: [
|
|
5725
|
+
expected: [
|
|
5726
|
+
sessionEvent2(NEW_SESSION_ID),
|
|
5727
|
+
{ type: "runtime_notice", body: "RateLimitError: rate limit exceeded (429)" },
|
|
5728
|
+
{ type: "result", ok: false, reason: "rate_limited" }
|
|
5729
|
+
]
|
|
5720
5730
|
},
|
|
5721
5731
|
{
|
|
5722
5732
|
// CT592: a subscription cap surfaced as text ("usage limit reached") → the cap
|
|
@@ -5730,7 +5740,11 @@ var OPENCODE_CONFORMANCE_FIXTURES = [
|
|
|
5730
5740
|
properties: { error: { name: "Error", message: "Claude usage limit reached" } }
|
|
5731
5741
|
}
|
|
5732
5742
|
],
|
|
5733
|
-
expected: [
|
|
5743
|
+
expected: [
|
|
5744
|
+
sessionEvent2(NEW_SESSION_ID),
|
|
5745
|
+
{ type: "runtime_notice", body: "Claude usage limit reached" },
|
|
5746
|
+
{ type: "result", ok: false, reason: "usage_capped" }
|
|
5747
|
+
]
|
|
5734
5748
|
},
|
|
5735
5749
|
{
|
|
5736
5750
|
// CT592: a transient provider outage (5xx / overload) surfaced as text → the
|
|
@@ -5746,7 +5760,11 @@ var OPENCODE_CONFORMANCE_FIXTURES = [
|
|
|
5746
5760
|
}
|
|
5747
5761
|
}
|
|
5748
5762
|
],
|
|
5749
|
-
expected: [
|
|
5763
|
+
expected: [
|
|
5764
|
+
sessionEvent2(NEW_SESSION_ID),
|
|
5765
|
+
{ type: "runtime_notice", body: "AI_APICallError: overloaded_error (529)" },
|
|
5766
|
+
{ type: "result", ok: false, reason: "server_error" }
|
|
5767
|
+
]
|
|
5750
5768
|
},
|
|
5751
5769
|
{
|
|
5752
5770
|
// CT558: an expired/invalid credential classifies as `auth_expired`.
|
|
@@ -5758,7 +5776,66 @@ var OPENCODE_CONFORMANCE_FIXTURES = [
|
|
|
5758
5776
|
properties: { error: { name: "AuthenticationError", message: "HTTP 401 Unauthorized" } }
|
|
5759
5777
|
}
|
|
5760
5778
|
],
|
|
5761
|
-
expected: [
|
|
5779
|
+
expected: [
|
|
5780
|
+
sessionEvent2(NEW_SESSION_ID),
|
|
5781
|
+
{ type: "runtime_notice", body: "AuthenticationError: HTTP 401 Unauthorized" },
|
|
5782
|
+
{ type: "result", ok: false, reason: "auth_expired" }
|
|
5783
|
+
]
|
|
5784
|
+
},
|
|
5785
|
+
{
|
|
5786
|
+
// CT1351: an unclassified failure remains as informative as a classified one:
|
|
5787
|
+
// its provider text is visible while the raw machine reason stays unchanged.
|
|
5788
|
+
name: "unclassified failure keeps a runtime notice and raw reason",
|
|
5789
|
+
request: makeRequest2(),
|
|
5790
|
+
nativeStream: [errored("something local broke")],
|
|
5791
|
+
expected: [
|
|
5792
|
+
sessionEvent2(NEW_SESSION_ID),
|
|
5793
|
+
{ type: "runtime_notice", body: "something local broke" },
|
|
5794
|
+
{ type: "result", ok: false, reason: "error:something local broke" }
|
|
5795
|
+
]
|
|
5796
|
+
},
|
|
5797
|
+
{
|
|
5798
|
+
name: "runtime notice is trimmed and bounded to 200 characters",
|
|
5799
|
+
request: makeRequest2(),
|
|
5800
|
+
nativeStream: [errored(` ${BOUNDED_NOTICE}${"y".repeat(20)} `)],
|
|
5801
|
+
expected: [
|
|
5802
|
+
sessionEvent2(NEW_SESSION_ID),
|
|
5803
|
+
{ type: "runtime_notice", body: BOUNDED_NOTICE },
|
|
5804
|
+
{ type: "result", ok: false, reason: "usage_capped" }
|
|
5805
|
+
]
|
|
5806
|
+
},
|
|
5807
|
+
{
|
|
5808
|
+
// CT1351: empty, whitespace-only, and bare status-token failures still set
|
|
5809
|
+
// the machine reason but do not add an empty/unhelpful transcript notice.
|
|
5810
|
+
name: "empty failure text emits no runtime notice",
|
|
5811
|
+
request: makeRequest2(),
|
|
5812
|
+
nativeStream: [errored("")],
|
|
5813
|
+
expected: [
|
|
5814
|
+
sessionEvent2(NEW_SESSION_ID),
|
|
5815
|
+
{ type: "result", ok: false, reason: "error:unknown" }
|
|
5816
|
+
]
|
|
5817
|
+
},
|
|
5818
|
+
{
|
|
5819
|
+
name: "whitespace-only failure text emits no runtime notice",
|
|
5820
|
+
request: makeRequest2(),
|
|
5821
|
+
nativeStream: [errored(" ")],
|
|
5822
|
+
expected: [sessionEvent2(NEW_SESSION_ID), { type: "result", ok: false, reason: "error: " }]
|
|
5823
|
+
},
|
|
5824
|
+
{
|
|
5825
|
+
name: "bare status token emits no runtime notice",
|
|
5826
|
+
request: makeRequest2(),
|
|
5827
|
+
nativeStream: [errored("429")],
|
|
5828
|
+
expected: [sessionEvent2(NEW_SESSION_ID), { type: "result", ok: false, reason: "rate_limited" }]
|
|
5829
|
+
},
|
|
5830
|
+
{
|
|
5831
|
+
name: "single-word provider sentence emits a runtime notice",
|
|
5832
|
+
request: makeRequest2(),
|
|
5833
|
+
nativeStream: [errored("Unavailable.")],
|
|
5834
|
+
expected: [
|
|
5835
|
+
sessionEvent2(NEW_SESSION_ID),
|
|
5836
|
+
{ type: "runtime_notice", body: "Unavailable." },
|
|
5837
|
+
{ type: "result", ok: false, reason: "error:Unavailable." }
|
|
5838
|
+
]
|
|
5762
5839
|
},
|
|
5763
5840
|
{
|
|
5764
5841
|
// CT1144: the same zero-content success the claude-code suite pins, in
|
|
@@ -6180,6 +6257,10 @@ async function* decodeCodexStream(events, ctx) {
|
|
|
6180
6257
|
yield* flushInterim();
|
|
6181
6258
|
ok = false;
|
|
6182
6259
|
const text = readErrorMessage(ev);
|
|
6260
|
+
const notice = text.trim().slice(0, 200).trimEnd();
|
|
6261
|
+
if (notice && !/^[\w:-]+$/.test(notice)) {
|
|
6262
|
+
yield { type: "runtime_notice", body: notice };
|
|
6263
|
+
}
|
|
6183
6264
|
const failure = classifyErrorText(text);
|
|
6184
6265
|
reason = failure ? encodeFailureReason(failure) : `error:${text.slice(0, 200)}`;
|
|
6185
6266
|
settled = true;
|
|
@@ -6498,6 +6579,7 @@ var codexAdapter = createCodexAdapter();
|
|
|
6498
6579
|
// packages/agent-runtime/src/codex/conformance.ts
|
|
6499
6580
|
var ABORT_SENTINEL3 = { __abortHere: true };
|
|
6500
6581
|
var NEW_THREAD_ID = "th_new";
|
|
6582
|
+
var BOUNDED_NOTICE2 = "You've hit your usage limit \xB7 ".padEnd(200, "x");
|
|
6501
6583
|
var COMPANION_POLICY2 = {
|
|
6502
6584
|
hostFs: false,
|
|
6503
6585
|
web: true,
|
|
@@ -7027,6 +7109,7 @@ var CODEX_CONFORMANCE_FIXTURES = [
|
|
|
7027
7109
|
expected: [
|
|
7028
7110
|
sessionEvent3(NEW_THREAD_ID),
|
|
7029
7111
|
{ type: "text", body: "Partial work.", terminal: false },
|
|
7112
|
+
{ type: "runtime_notice", body: "provider exploded" },
|
|
7030
7113
|
{ type: "result", ok: false, reason: "error:provider exploded" }
|
|
7031
7114
|
]
|
|
7032
7115
|
},
|
|
@@ -7043,6 +7126,7 @@ var CODEX_CONFORMANCE_FIXTURES = [
|
|
|
7043
7126
|
expected: [
|
|
7044
7127
|
sessionEvent3(NEW_THREAD_ID),
|
|
7045
7128
|
{ type: "text", body: "Starting.", terminal: false },
|
|
7129
|
+
{ type: "runtime_notice", body: "stream died" },
|
|
7046
7130
|
{ type: "result", ok: false, reason: "error:stream died" }
|
|
7047
7131
|
]
|
|
7048
7132
|
},
|
|
@@ -7055,25 +7139,47 @@ var CODEX_CONFORMANCE_FIXTURES = [
|
|
|
7055
7139
|
name: "turn.failed 429 \u2192 rate_limited (throttle)",
|
|
7056
7140
|
request: makeRequest3(),
|
|
7057
7141
|
nativeStream: [threadStarted(NEW_THREAD_ID), turnFailed("429 Too Many Requests")],
|
|
7058
|
-
expected: [
|
|
7142
|
+
expected: [
|
|
7143
|
+
sessionEvent3(NEW_THREAD_ID),
|
|
7144
|
+
{ type: "runtime_notice", body: "429 Too Many Requests" },
|
|
7145
|
+
{ type: "result", ok: false, reason: "rate_limited" }
|
|
7146
|
+
]
|
|
7059
7147
|
},
|
|
7060
7148
|
{
|
|
7061
7149
|
name: "turn.failed overload \u2192 server_error",
|
|
7062
7150
|
request: makeRequest3(),
|
|
7063
7151
|
nativeStream: [threadStarted(NEW_THREAD_ID), turnFailed("503 Service Unavailable")],
|
|
7064
|
-
expected: [
|
|
7152
|
+
expected: [
|
|
7153
|
+
sessionEvent3(NEW_THREAD_ID),
|
|
7154
|
+
{ type: "runtime_notice", body: "503 Service Unavailable" },
|
|
7155
|
+
{ type: "result", ok: false, reason: "server_error" }
|
|
7156
|
+
]
|
|
7065
7157
|
},
|
|
7066
7158
|
{
|
|
7067
7159
|
name: "turn.failed usage cap \u2192 usage_capped",
|
|
7068
7160
|
request: makeRequest3(),
|
|
7069
|
-
nativeStream: [
|
|
7070
|
-
|
|
7161
|
+
nativeStream: [
|
|
7162
|
+
threadStarted(NEW_THREAD_ID),
|
|
7163
|
+
turnFailed("You've hit your session limit \xB7 resets 8:20pm (UTC)")
|
|
7164
|
+
],
|
|
7165
|
+
expected: [
|
|
7166
|
+
sessionEvent3(NEW_THREAD_ID),
|
|
7167
|
+
{
|
|
7168
|
+
type: "runtime_notice",
|
|
7169
|
+
body: "You've hit your session limit \xB7 resets 8:20pm (UTC)"
|
|
7170
|
+
},
|
|
7171
|
+
{ type: "result", ok: false, reason: "usage_capped" }
|
|
7172
|
+
]
|
|
7071
7173
|
},
|
|
7072
7174
|
{
|
|
7073
7175
|
name: "turn.failed auth \u2192 auth_expired",
|
|
7074
7176
|
request: makeRequest3(),
|
|
7075
7177
|
nativeStream: [threadStarted(NEW_THREAD_ID), turnFailed("401 Unauthorized: invalid api key")],
|
|
7076
|
-
expected: [
|
|
7178
|
+
expected: [
|
|
7179
|
+
sessionEvent3(NEW_THREAD_ID),
|
|
7180
|
+
{ type: "runtime_notice", body: "401 Unauthorized: invalid api key" },
|
|
7181
|
+
{ type: "result", ok: false, reason: "auth_expired" }
|
|
7182
|
+
]
|
|
7077
7183
|
},
|
|
7078
7184
|
{
|
|
7079
7185
|
// CT592: a Codex failure text the classifier can't place still keeps today's
|
|
@@ -7083,9 +7189,53 @@ var CODEX_CONFORMANCE_FIXTURES = [
|
|
|
7083
7189
|
nativeStream: [threadStarted(NEW_THREAD_ID), turnFailed("something local broke")],
|
|
7084
7190
|
expected: [
|
|
7085
7191
|
sessionEvent3(NEW_THREAD_ID),
|
|
7192
|
+
{ type: "runtime_notice", body: "something local broke" },
|
|
7086
7193
|
{ type: "result", ok: false, reason: "error:something local broke" }
|
|
7087
7194
|
]
|
|
7088
7195
|
},
|
|
7196
|
+
{
|
|
7197
|
+
name: "runtime notice is trimmed and bounded to 200 characters",
|
|
7198
|
+
request: makeRequest3(),
|
|
7199
|
+
nativeStream: [
|
|
7200
|
+
threadStarted(NEW_THREAD_ID),
|
|
7201
|
+
turnFailed(` ${BOUNDED_NOTICE2}${"y".repeat(20)} `)
|
|
7202
|
+
],
|
|
7203
|
+
expected: [
|
|
7204
|
+
sessionEvent3(NEW_THREAD_ID),
|
|
7205
|
+
{ type: "runtime_notice", body: BOUNDED_NOTICE2 },
|
|
7206
|
+
{ type: "result", ok: false, reason: "usage_capped" }
|
|
7207
|
+
]
|
|
7208
|
+
},
|
|
7209
|
+
{
|
|
7210
|
+
// CT1351: empty, whitespace-only, and bare status-token failures still set
|
|
7211
|
+
// the machine reason but do not add an empty/unhelpful transcript notice.
|
|
7212
|
+
name: "empty failure text emits no runtime notice",
|
|
7213
|
+
request: makeRequest3(),
|
|
7214
|
+
nativeStream: [threadStarted(NEW_THREAD_ID), turnFailed("")],
|
|
7215
|
+
expected: [sessionEvent3(NEW_THREAD_ID), { type: "result", ok: false, reason: "error:unknown" }]
|
|
7216
|
+
},
|
|
7217
|
+
{
|
|
7218
|
+
name: "whitespace-only failure text emits no runtime notice",
|
|
7219
|
+
request: makeRequest3(),
|
|
7220
|
+
nativeStream: [threadStarted(NEW_THREAD_ID), turnFailed(" ")],
|
|
7221
|
+
expected: [sessionEvent3(NEW_THREAD_ID), { type: "result", ok: false, reason: "error: " }]
|
|
7222
|
+
},
|
|
7223
|
+
{
|
|
7224
|
+
name: "bare status token emits no runtime notice",
|
|
7225
|
+
request: makeRequest3(),
|
|
7226
|
+
nativeStream: [threadStarted(NEW_THREAD_ID), turnFailed("429")],
|
|
7227
|
+
expected: [sessionEvent3(NEW_THREAD_ID), { type: "result", ok: false, reason: "rate_limited" }]
|
|
7228
|
+
},
|
|
7229
|
+
{
|
|
7230
|
+
name: "single-word provider sentence emits a runtime notice",
|
|
7231
|
+
request: makeRequest3(),
|
|
7232
|
+
nativeStream: [threadStarted(NEW_THREAD_ID), turnFailed("Unavailable.")],
|
|
7233
|
+
expected: [
|
|
7234
|
+
sessionEvent3(NEW_THREAD_ID),
|
|
7235
|
+
{ type: "runtime_notice", body: "Unavailable." },
|
|
7236
|
+
{ type: "result", ok: false, reason: "error:Unavailable." }
|
|
7237
|
+
]
|
|
7238
|
+
},
|
|
7089
7239
|
{
|
|
7090
7240
|
// CT481: fail LOUD on unknown model metadata. An unknown `--model` makes Codex
|
|
7091
7241
|
// emit an `error` item ("… Defaulting to fallback metadata …") and then go
|
|
@@ -7430,7 +7580,14 @@ function createSkipState() {
|
|
|
7430
7580
|
return { skipped: false, reason: null };
|
|
7431
7581
|
}
|
|
7432
7582
|
function createAskState() {
|
|
7433
|
-
return {
|
|
7583
|
+
return {
|
|
7584
|
+
targetUserId: null,
|
|
7585
|
+
question: null,
|
|
7586
|
+
headline: null,
|
|
7587
|
+
options: null,
|
|
7588
|
+
questions: null,
|
|
7589
|
+
order: null
|
|
7590
|
+
};
|
|
7434
7591
|
}
|
|
7435
7592
|
function createWakeState() {
|
|
7436
7593
|
return { afterSeconds: null, at: null, note: null, cancelled: false };
|
|
@@ -7576,6 +7733,7 @@ function createTurnControlMcpServer(sendState, skipState, askState, wakeState, r
|
|
|
7576
7733
|
};
|
|
7577
7734
|
}
|
|
7578
7735
|
askState.targetUserId = args.targetUserId;
|
|
7736
|
+
askState.order = controlOrder?.next() ?? null;
|
|
7579
7737
|
if (hasArray) {
|
|
7580
7738
|
askState.questions = args.questions;
|
|
7581
7739
|
askState.question = null;
|
|
@@ -8123,9 +8281,11 @@ var TurnCommitter = class {
|
|
|
8123
8281
|
// the server sorts it after every recorded one — it binds the turn's closing
|
|
8124
8282
|
// words.
|
|
8125
8283
|
//
|
|
8126
|
-
//
|
|
8127
|
-
//
|
|
8128
|
-
//
|
|
8284
|
+
// CT1347: the ask has a position too. Its carrier used to queue last however
|
|
8285
|
+
// early the tool fired, because a landed open ask held the conversation and an
|
|
8286
|
+
// ask ahead of its own turn's send would have stranded that send behind a
|
|
8287
|
+
// person's response time. Nothing holds now, so the ask is an addressed intent
|
|
8288
|
+
// like the other two and queues where it was called.
|
|
8129
8289
|
orderField(fields) {
|
|
8130
8290
|
const order = {};
|
|
8131
8291
|
const replyOrder = this.deps.replyState.order;
|
|
@@ -8133,6 +8293,9 @@ var TurnCommitter = class {
|
|
|
8133
8293
|
if (fields.dispatch !== void 0 && this.deps.sendState.order !== null) {
|
|
8134
8294
|
order.send = this.deps.sendState.order;
|
|
8135
8295
|
}
|
|
8296
|
+
if (fields.ask !== void 0 && this.deps.askState.order !== null) {
|
|
8297
|
+
order.ask = this.deps.askState.order;
|
|
8298
|
+
}
|
|
8136
8299
|
return Object.keys(order).length > 0 ? { turnControlOrder: order } : {};
|
|
8137
8300
|
}
|
|
8138
8301
|
// The declared reply: explicit `reply_to` first, else the runtime's own
|
package/dist/runtime.js
CHANGED
|
@@ -4599,6 +4599,10 @@ async function* decodeOpencodeStream(events, ctx) {
|
|
|
4599
4599
|
if (sealed) yield sealed;
|
|
4600
4600
|
ok = false;
|
|
4601
4601
|
const errorText = readSessionError(ev.properties);
|
|
4602
|
+
const notice = errorText.trim().slice(0, 200).trimEnd();
|
|
4603
|
+
if (notice && !/^[\w:-]+$/.test(notice)) {
|
|
4604
|
+
yield { type: "runtime_notice", body: notice };
|
|
4605
|
+
}
|
|
4602
4606
|
const failure = classifyErrorText(errorText);
|
|
4603
4607
|
reason = failure ? encodeFailureReason(failure) : `error:${errorText.slice(0, 200)}`;
|
|
4604
4608
|
settled = true;
|
|
@@ -5008,6 +5012,7 @@ var opencodeAdapter = createOpencodeAdapter();
|
|
|
5008
5012
|
// packages/agent-runtime/src/opencode/conformance.ts
|
|
5009
5013
|
var ABORT_SENTINEL2 = { __abortHere: true };
|
|
5010
5014
|
var NEW_SESSION_ID = "sess_new";
|
|
5015
|
+
var BOUNDED_NOTICE = "You've hit your usage limit \xB7 ".padEnd(200, "x");
|
|
5011
5016
|
var COMPANION_POLICY = {
|
|
5012
5017
|
hostFs: false,
|
|
5013
5018
|
web: true,
|
|
@@ -5199,6 +5204,7 @@ var OPENCODE_CONFORMANCE_FIXTURES = [
|
|
|
5199
5204
|
expected: [
|
|
5200
5205
|
sessionEvent2(NEW_SESSION_ID),
|
|
5201
5206
|
{ type: "text", body: "Partial work.", terminal: false },
|
|
5207
|
+
{ type: "runtime_notice", body: "provider exploded" },
|
|
5202
5208
|
{ type: "result", ok: false, reason: "error:provider exploded" }
|
|
5203
5209
|
]
|
|
5204
5210
|
},
|
|
@@ -5215,7 +5221,11 @@ var OPENCODE_CONFORMANCE_FIXTURES = [
|
|
|
5215
5221
|
properties: { error: { name: "RateLimitError", message: "rate limit exceeded (429)" } }
|
|
5216
5222
|
}
|
|
5217
5223
|
],
|
|
5218
|
-
expected: [
|
|
5224
|
+
expected: [
|
|
5225
|
+
sessionEvent2(NEW_SESSION_ID),
|
|
5226
|
+
{ type: "runtime_notice", body: "RateLimitError: rate limit exceeded (429)" },
|
|
5227
|
+
{ type: "result", ok: false, reason: "rate_limited" }
|
|
5228
|
+
]
|
|
5219
5229
|
},
|
|
5220
5230
|
{
|
|
5221
5231
|
// CT592: a subscription cap surfaced as text ("usage limit reached") → the cap
|
|
@@ -5229,7 +5239,11 @@ var OPENCODE_CONFORMANCE_FIXTURES = [
|
|
|
5229
5239
|
properties: { error: { name: "Error", message: "Claude usage limit reached" } }
|
|
5230
5240
|
}
|
|
5231
5241
|
],
|
|
5232
|
-
expected: [
|
|
5242
|
+
expected: [
|
|
5243
|
+
sessionEvent2(NEW_SESSION_ID),
|
|
5244
|
+
{ type: "runtime_notice", body: "Claude usage limit reached" },
|
|
5245
|
+
{ type: "result", ok: false, reason: "usage_capped" }
|
|
5246
|
+
]
|
|
5233
5247
|
},
|
|
5234
5248
|
{
|
|
5235
5249
|
// CT592: a transient provider outage (5xx / overload) surfaced as text → the
|
|
@@ -5245,7 +5259,11 @@ var OPENCODE_CONFORMANCE_FIXTURES = [
|
|
|
5245
5259
|
}
|
|
5246
5260
|
}
|
|
5247
5261
|
],
|
|
5248
|
-
expected: [
|
|
5262
|
+
expected: [
|
|
5263
|
+
sessionEvent2(NEW_SESSION_ID),
|
|
5264
|
+
{ type: "runtime_notice", body: "AI_APICallError: overloaded_error (529)" },
|
|
5265
|
+
{ type: "result", ok: false, reason: "server_error" }
|
|
5266
|
+
]
|
|
5249
5267
|
},
|
|
5250
5268
|
{
|
|
5251
5269
|
// CT558: an expired/invalid credential classifies as `auth_expired`.
|
|
@@ -5257,7 +5275,66 @@ var OPENCODE_CONFORMANCE_FIXTURES = [
|
|
|
5257
5275
|
properties: { error: { name: "AuthenticationError", message: "HTTP 401 Unauthorized" } }
|
|
5258
5276
|
}
|
|
5259
5277
|
],
|
|
5260
|
-
expected: [
|
|
5278
|
+
expected: [
|
|
5279
|
+
sessionEvent2(NEW_SESSION_ID),
|
|
5280
|
+
{ type: "runtime_notice", body: "AuthenticationError: HTTP 401 Unauthorized" },
|
|
5281
|
+
{ type: "result", ok: false, reason: "auth_expired" }
|
|
5282
|
+
]
|
|
5283
|
+
},
|
|
5284
|
+
{
|
|
5285
|
+
// CT1351: an unclassified failure remains as informative as a classified one:
|
|
5286
|
+
// its provider text is visible while the raw machine reason stays unchanged.
|
|
5287
|
+
name: "unclassified failure keeps a runtime notice and raw reason",
|
|
5288
|
+
request: makeRequest2(),
|
|
5289
|
+
nativeStream: [errored("something local broke")],
|
|
5290
|
+
expected: [
|
|
5291
|
+
sessionEvent2(NEW_SESSION_ID),
|
|
5292
|
+
{ type: "runtime_notice", body: "something local broke" },
|
|
5293
|
+
{ type: "result", ok: false, reason: "error:something local broke" }
|
|
5294
|
+
]
|
|
5295
|
+
},
|
|
5296
|
+
{
|
|
5297
|
+
name: "runtime notice is trimmed and bounded to 200 characters",
|
|
5298
|
+
request: makeRequest2(),
|
|
5299
|
+
nativeStream: [errored(` ${BOUNDED_NOTICE}${"y".repeat(20)} `)],
|
|
5300
|
+
expected: [
|
|
5301
|
+
sessionEvent2(NEW_SESSION_ID),
|
|
5302
|
+
{ type: "runtime_notice", body: BOUNDED_NOTICE },
|
|
5303
|
+
{ type: "result", ok: false, reason: "usage_capped" }
|
|
5304
|
+
]
|
|
5305
|
+
},
|
|
5306
|
+
{
|
|
5307
|
+
// CT1351: empty, whitespace-only, and bare status-token failures still set
|
|
5308
|
+
// the machine reason but do not add an empty/unhelpful transcript notice.
|
|
5309
|
+
name: "empty failure text emits no runtime notice",
|
|
5310
|
+
request: makeRequest2(),
|
|
5311
|
+
nativeStream: [errored("")],
|
|
5312
|
+
expected: [
|
|
5313
|
+
sessionEvent2(NEW_SESSION_ID),
|
|
5314
|
+
{ type: "result", ok: false, reason: "error:unknown" }
|
|
5315
|
+
]
|
|
5316
|
+
},
|
|
5317
|
+
{
|
|
5318
|
+
name: "whitespace-only failure text emits no runtime notice",
|
|
5319
|
+
request: makeRequest2(),
|
|
5320
|
+
nativeStream: [errored(" ")],
|
|
5321
|
+
expected: [sessionEvent2(NEW_SESSION_ID), { type: "result", ok: false, reason: "error: " }]
|
|
5322
|
+
},
|
|
5323
|
+
{
|
|
5324
|
+
name: "bare status token emits no runtime notice",
|
|
5325
|
+
request: makeRequest2(),
|
|
5326
|
+
nativeStream: [errored("429")],
|
|
5327
|
+
expected: [sessionEvent2(NEW_SESSION_ID), { type: "result", ok: false, reason: "rate_limited" }]
|
|
5328
|
+
},
|
|
5329
|
+
{
|
|
5330
|
+
name: "single-word provider sentence emits a runtime notice",
|
|
5331
|
+
request: makeRequest2(),
|
|
5332
|
+
nativeStream: [errored("Unavailable.")],
|
|
5333
|
+
expected: [
|
|
5334
|
+
sessionEvent2(NEW_SESSION_ID),
|
|
5335
|
+
{ type: "runtime_notice", body: "Unavailable." },
|
|
5336
|
+
{ type: "result", ok: false, reason: "error:Unavailable." }
|
|
5337
|
+
]
|
|
5261
5338
|
},
|
|
5262
5339
|
{
|
|
5263
5340
|
// CT1144: the same zero-content success the claude-code suite pins, in
|
|
@@ -5679,6 +5756,10 @@ async function* decodeCodexStream(events, ctx) {
|
|
|
5679
5756
|
yield* flushInterim();
|
|
5680
5757
|
ok = false;
|
|
5681
5758
|
const text = readErrorMessage(ev);
|
|
5759
|
+
const notice = text.trim().slice(0, 200).trimEnd();
|
|
5760
|
+
if (notice && !/^[\w:-]+$/.test(notice)) {
|
|
5761
|
+
yield { type: "runtime_notice", body: notice };
|
|
5762
|
+
}
|
|
5682
5763
|
const failure = classifyErrorText(text);
|
|
5683
5764
|
reason = failure ? encodeFailureReason(failure) : `error:${text.slice(0, 200)}`;
|
|
5684
5765
|
settled = true;
|
|
@@ -5997,6 +6078,7 @@ var codexAdapter = createCodexAdapter();
|
|
|
5997
6078
|
// packages/agent-runtime/src/codex/conformance.ts
|
|
5998
6079
|
var ABORT_SENTINEL3 = { __abortHere: true };
|
|
5999
6080
|
var NEW_THREAD_ID = "th_new";
|
|
6081
|
+
var BOUNDED_NOTICE2 = "You've hit your usage limit \xB7 ".padEnd(200, "x");
|
|
6000
6082
|
var COMPANION_POLICY2 = {
|
|
6001
6083
|
hostFs: false,
|
|
6002
6084
|
web: true,
|
|
@@ -6526,6 +6608,7 @@ var CODEX_CONFORMANCE_FIXTURES = [
|
|
|
6526
6608
|
expected: [
|
|
6527
6609
|
sessionEvent3(NEW_THREAD_ID),
|
|
6528
6610
|
{ type: "text", body: "Partial work.", terminal: false },
|
|
6611
|
+
{ type: "runtime_notice", body: "provider exploded" },
|
|
6529
6612
|
{ type: "result", ok: false, reason: "error:provider exploded" }
|
|
6530
6613
|
]
|
|
6531
6614
|
},
|
|
@@ -6542,6 +6625,7 @@ var CODEX_CONFORMANCE_FIXTURES = [
|
|
|
6542
6625
|
expected: [
|
|
6543
6626
|
sessionEvent3(NEW_THREAD_ID),
|
|
6544
6627
|
{ type: "text", body: "Starting.", terminal: false },
|
|
6628
|
+
{ type: "runtime_notice", body: "stream died" },
|
|
6545
6629
|
{ type: "result", ok: false, reason: "error:stream died" }
|
|
6546
6630
|
]
|
|
6547
6631
|
},
|
|
@@ -6554,25 +6638,47 @@ var CODEX_CONFORMANCE_FIXTURES = [
|
|
|
6554
6638
|
name: "turn.failed 429 \u2192 rate_limited (throttle)",
|
|
6555
6639
|
request: makeRequest3(),
|
|
6556
6640
|
nativeStream: [threadStarted(NEW_THREAD_ID), turnFailed("429 Too Many Requests")],
|
|
6557
|
-
expected: [
|
|
6641
|
+
expected: [
|
|
6642
|
+
sessionEvent3(NEW_THREAD_ID),
|
|
6643
|
+
{ type: "runtime_notice", body: "429 Too Many Requests" },
|
|
6644
|
+
{ type: "result", ok: false, reason: "rate_limited" }
|
|
6645
|
+
]
|
|
6558
6646
|
},
|
|
6559
6647
|
{
|
|
6560
6648
|
name: "turn.failed overload \u2192 server_error",
|
|
6561
6649
|
request: makeRequest3(),
|
|
6562
6650
|
nativeStream: [threadStarted(NEW_THREAD_ID), turnFailed("503 Service Unavailable")],
|
|
6563
|
-
expected: [
|
|
6651
|
+
expected: [
|
|
6652
|
+
sessionEvent3(NEW_THREAD_ID),
|
|
6653
|
+
{ type: "runtime_notice", body: "503 Service Unavailable" },
|
|
6654
|
+
{ type: "result", ok: false, reason: "server_error" }
|
|
6655
|
+
]
|
|
6564
6656
|
},
|
|
6565
6657
|
{
|
|
6566
6658
|
name: "turn.failed usage cap \u2192 usage_capped",
|
|
6567
6659
|
request: makeRequest3(),
|
|
6568
|
-
nativeStream: [
|
|
6569
|
-
|
|
6660
|
+
nativeStream: [
|
|
6661
|
+
threadStarted(NEW_THREAD_ID),
|
|
6662
|
+
turnFailed("You've hit your session limit \xB7 resets 8:20pm (UTC)")
|
|
6663
|
+
],
|
|
6664
|
+
expected: [
|
|
6665
|
+
sessionEvent3(NEW_THREAD_ID),
|
|
6666
|
+
{
|
|
6667
|
+
type: "runtime_notice",
|
|
6668
|
+
body: "You've hit your session limit \xB7 resets 8:20pm (UTC)"
|
|
6669
|
+
},
|
|
6670
|
+
{ type: "result", ok: false, reason: "usage_capped" }
|
|
6671
|
+
]
|
|
6570
6672
|
},
|
|
6571
6673
|
{
|
|
6572
6674
|
name: "turn.failed auth \u2192 auth_expired",
|
|
6573
6675
|
request: makeRequest3(),
|
|
6574
6676
|
nativeStream: [threadStarted(NEW_THREAD_ID), turnFailed("401 Unauthorized: invalid api key")],
|
|
6575
|
-
expected: [
|
|
6677
|
+
expected: [
|
|
6678
|
+
sessionEvent3(NEW_THREAD_ID),
|
|
6679
|
+
{ type: "runtime_notice", body: "401 Unauthorized: invalid api key" },
|
|
6680
|
+
{ type: "result", ok: false, reason: "auth_expired" }
|
|
6681
|
+
]
|
|
6576
6682
|
},
|
|
6577
6683
|
{
|
|
6578
6684
|
// CT592: a Codex failure text the classifier can't place still keeps today's
|
|
@@ -6582,9 +6688,53 @@ var CODEX_CONFORMANCE_FIXTURES = [
|
|
|
6582
6688
|
nativeStream: [threadStarted(NEW_THREAD_ID), turnFailed("something local broke")],
|
|
6583
6689
|
expected: [
|
|
6584
6690
|
sessionEvent3(NEW_THREAD_ID),
|
|
6691
|
+
{ type: "runtime_notice", body: "something local broke" },
|
|
6585
6692
|
{ type: "result", ok: false, reason: "error:something local broke" }
|
|
6586
6693
|
]
|
|
6587
6694
|
},
|
|
6695
|
+
{
|
|
6696
|
+
name: "runtime notice is trimmed and bounded to 200 characters",
|
|
6697
|
+
request: makeRequest3(),
|
|
6698
|
+
nativeStream: [
|
|
6699
|
+
threadStarted(NEW_THREAD_ID),
|
|
6700
|
+
turnFailed(` ${BOUNDED_NOTICE2}${"y".repeat(20)} `)
|
|
6701
|
+
],
|
|
6702
|
+
expected: [
|
|
6703
|
+
sessionEvent3(NEW_THREAD_ID),
|
|
6704
|
+
{ type: "runtime_notice", body: BOUNDED_NOTICE2 },
|
|
6705
|
+
{ type: "result", ok: false, reason: "usage_capped" }
|
|
6706
|
+
]
|
|
6707
|
+
},
|
|
6708
|
+
{
|
|
6709
|
+
// CT1351: empty, whitespace-only, and bare status-token failures still set
|
|
6710
|
+
// the machine reason but do not add an empty/unhelpful transcript notice.
|
|
6711
|
+
name: "empty failure text emits no runtime notice",
|
|
6712
|
+
request: makeRequest3(),
|
|
6713
|
+
nativeStream: [threadStarted(NEW_THREAD_ID), turnFailed("")],
|
|
6714
|
+
expected: [sessionEvent3(NEW_THREAD_ID), { type: "result", ok: false, reason: "error:unknown" }]
|
|
6715
|
+
},
|
|
6716
|
+
{
|
|
6717
|
+
name: "whitespace-only failure text emits no runtime notice",
|
|
6718
|
+
request: makeRequest3(),
|
|
6719
|
+
nativeStream: [threadStarted(NEW_THREAD_ID), turnFailed(" ")],
|
|
6720
|
+
expected: [sessionEvent3(NEW_THREAD_ID), { type: "result", ok: false, reason: "error: " }]
|
|
6721
|
+
},
|
|
6722
|
+
{
|
|
6723
|
+
name: "bare status token emits no runtime notice",
|
|
6724
|
+
request: makeRequest3(),
|
|
6725
|
+
nativeStream: [threadStarted(NEW_THREAD_ID), turnFailed("429")],
|
|
6726
|
+
expected: [sessionEvent3(NEW_THREAD_ID), { type: "result", ok: false, reason: "rate_limited" }]
|
|
6727
|
+
},
|
|
6728
|
+
{
|
|
6729
|
+
name: "single-word provider sentence emits a runtime notice",
|
|
6730
|
+
request: makeRequest3(),
|
|
6731
|
+
nativeStream: [threadStarted(NEW_THREAD_ID), turnFailed("Unavailable.")],
|
|
6732
|
+
expected: [
|
|
6733
|
+
sessionEvent3(NEW_THREAD_ID),
|
|
6734
|
+
{ type: "runtime_notice", body: "Unavailable." },
|
|
6735
|
+
{ type: "result", ok: false, reason: "error:Unavailable." }
|
|
6736
|
+
]
|
|
6737
|
+
},
|
|
6588
6738
|
{
|
|
6589
6739
|
// CT481: fail LOUD on unknown model metadata. An unknown `--model` makes Codex
|
|
6590
6740
|
// emit an `error` item ("… Defaulting to fallback metadata …") and then go
|
|
@@ -6929,7 +7079,14 @@ function createSkipState() {
|
|
|
6929
7079
|
return { skipped: false, reason: null };
|
|
6930
7080
|
}
|
|
6931
7081
|
function createAskState() {
|
|
6932
|
-
return {
|
|
7082
|
+
return {
|
|
7083
|
+
targetUserId: null,
|
|
7084
|
+
question: null,
|
|
7085
|
+
headline: null,
|
|
7086
|
+
options: null,
|
|
7087
|
+
questions: null,
|
|
7088
|
+
order: null
|
|
7089
|
+
};
|
|
6933
7090
|
}
|
|
6934
7091
|
function createWakeState() {
|
|
6935
7092
|
return { afterSeconds: null, at: null, note: null, cancelled: false };
|
|
@@ -7075,6 +7232,7 @@ function createTurnControlMcpServer(sendState, skipState, askState, wakeState, r
|
|
|
7075
7232
|
};
|
|
7076
7233
|
}
|
|
7077
7234
|
askState.targetUserId = args.targetUserId;
|
|
7235
|
+
askState.order = controlOrder?.next() ?? null;
|
|
7078
7236
|
if (hasArray) {
|
|
7079
7237
|
askState.questions = args.questions;
|
|
7080
7238
|
askState.question = null;
|
|
@@ -7622,9 +7780,11 @@ var TurnCommitter = class {
|
|
|
7622
7780
|
// the server sorts it after every recorded one — it binds the turn's closing
|
|
7623
7781
|
// words.
|
|
7624
7782
|
//
|
|
7625
|
-
//
|
|
7626
|
-
//
|
|
7627
|
-
//
|
|
7783
|
+
// CT1347: the ask has a position too. Its carrier used to queue last however
|
|
7784
|
+
// early the tool fired, because a landed open ask held the conversation and an
|
|
7785
|
+
// ask ahead of its own turn's send would have stranded that send behind a
|
|
7786
|
+
// person's response time. Nothing holds now, so the ask is an addressed intent
|
|
7787
|
+
// like the other two and queues where it was called.
|
|
7628
7788
|
orderField(fields) {
|
|
7629
7789
|
const order = {};
|
|
7630
7790
|
const replyOrder = this.deps.replyState.order;
|
|
@@ -7632,6 +7792,9 @@ var TurnCommitter = class {
|
|
|
7632
7792
|
if (fields.dispatch !== void 0 && this.deps.sendState.order !== null) {
|
|
7633
7793
|
order.send = this.deps.sendState.order;
|
|
7634
7794
|
}
|
|
7795
|
+
if (fields.ask !== void 0 && this.deps.askState.order !== null) {
|
|
7796
|
+
order.ask = this.deps.askState.order;
|
|
7797
|
+
}
|
|
7635
7798
|
return Object.keys(order).length > 0 ? { turnControlOrder: order } : {};
|
|
7636
7799
|
}
|
|
7637
7800
|
// The declared reply: explicit `reply_to` first, else the runtime's own
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cabane/companion",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.80",
|
|
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",
|