@evident-ai/cli 3.2.1-dev.6d96a16 → 3.2.1-dev.7244588
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/README.md +4 -1
- package/dist/index.js +190 -57
- package/dist/index.js.map +1 -1
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -111,7 +111,10 @@ Options:
|
|
|
111
111
|
`7d`, `24h`). Setting this (or `--session-cleanup-max-count`) is what enables
|
|
112
112
|
cleanup — there is no separate on/off flag. An invalid value is warned about
|
|
113
113
|
and ignored, which can leave cleanup off if it was the only rule set. Env:
|
|
114
|
-
`EVIDENT_SESSION_CLEANUP_MAX_AGE`.
|
|
114
|
+
`EVIDENT_SESSION_CLEANUP_MAX_AGE`. With cleanup off, a runner whose local
|
|
115
|
+
session store has grown large warns once at startup, on the runner's page,
|
|
116
|
+
naming this flag — pruning stops the store growing, but it does not shrink
|
|
117
|
+
what has already grown.
|
|
115
118
|
- `--session-cleanup-max-count <n>` — Keep only the newest N OpenCode sessions
|
|
116
119
|
by last activity, deleting the rest. Also enables cleanup; combines with
|
|
117
120
|
`--session-cleanup-max-age` as OR. A session with a turn actively in progress
|
package/dist/index.js
CHANGED
|
@@ -1023,7 +1023,7 @@ async function claudeUsage() {
|
|
|
1023
1023
|
|
|
1024
1024
|
// src/commands/run.ts
|
|
1025
1025
|
import { homedir as homedir3 } from "os";
|
|
1026
|
-
import { isAbsolute as isAbsolute2, join as
|
|
1026
|
+
import { isAbsolute as isAbsolute2, join as join4, parse, resolve as resolvePath } from "path";
|
|
1027
1027
|
import chalk6 from "chalk";
|
|
1028
1028
|
|
|
1029
1029
|
// ../../packages/types/src/agents/index.ts
|
|
@@ -2106,7 +2106,9 @@ function messageRunState(messages, userMessageId) {
|
|
|
2106
2106
|
}
|
|
2107
2107
|
if (!reply) return "queued";
|
|
2108
2108
|
if (isAssistantInFlight(reply)) return "running";
|
|
2109
|
-
|
|
2109
|
+
if (errorOf(reply) != null) return "failed";
|
|
2110
|
+
if (isAmbiguousTerminalFinish(reply)) return "running";
|
|
2111
|
+
return "done";
|
|
2110
2112
|
}
|
|
2111
2113
|
function isPreamblePinnedRunning(messages, userMessageId) {
|
|
2112
2114
|
if (messageRunState(messages, userMessageId) !== "running") return false;
|
|
@@ -2116,6 +2118,19 @@ function isPreamblePinnedRunning(messages, userMessageId) {
|
|
|
2116
2118
|
function isB2AbandonmentConfirmed(params) {
|
|
2117
2119
|
return params.pinnedForMs >= params.minPinnedMs && params.descendantOngoing === false;
|
|
2118
2120
|
}
|
|
2121
|
+
function isAmbiguousTerminalFinish(m) {
|
|
2122
|
+
if (completedOf(m) == null) return false;
|
|
2123
|
+
if (errorOf(m) != null) return false;
|
|
2124
|
+
const finish = finishOf(m);
|
|
2125
|
+
return finish !== "tool-calls" && finish !== "stop";
|
|
2126
|
+
}
|
|
2127
|
+
function isAmbiguousFinishPinnedRunning(messages, userMessageId) {
|
|
2128
|
+
const reply = findLastAssistantReplyFor(messages, userMessageId);
|
|
2129
|
+
return isAmbiguousTerminalFinish(reply);
|
|
2130
|
+
}
|
|
2131
|
+
function isAmbiguousFinishResolved(params) {
|
|
2132
|
+
return params.sessionOngoing === false || params.pinnedForMs >= params.maxPinnedMs;
|
|
2133
|
+
}
|
|
2119
2134
|
function messageError(messages, userMessageId) {
|
|
2120
2135
|
const reply = findLastAssistantReplyFor(messages, userMessageId);
|
|
2121
2136
|
const error2 = errorOf(reply);
|
|
@@ -2322,6 +2337,31 @@ function resolveSessionCleanupConfig(flags, env = process.env) {
|
|
|
2322
2337
|
return { enabled, maxAgeMs, maxCount, intervalMs, warnings };
|
|
2323
2338
|
}
|
|
2324
2339
|
|
|
2340
|
+
// src/lib/opencode/session-db-size.ts
|
|
2341
|
+
import { statSync as statSync2 } from "fs";
|
|
2342
|
+
import { join as join2 } from "path";
|
|
2343
|
+
var LARGE_DB_THRESHOLD_BYTES = 268435456;
|
|
2344
|
+
function statSessionDbBytes(homeDir) {
|
|
2345
|
+
const dbPath = join2(homeDir, ".local", "share", "opencode", "opencode.db");
|
|
2346
|
+
try {
|
|
2347
|
+
return statSync2(dbPath).size;
|
|
2348
|
+
} catch (err) {
|
|
2349
|
+
const isMissingFile = err instanceof Error && "code" in err && err.code === "ENOENT";
|
|
2350
|
+
if (!isMissingFile) {
|
|
2351
|
+
console.error(
|
|
2352
|
+
`[statSessionDbBytes] could not stat ${dbPath}: ${err instanceof Error ? err.message : String(err)}`
|
|
2353
|
+
);
|
|
2354
|
+
}
|
|
2355
|
+
return null;
|
|
2356
|
+
}
|
|
2357
|
+
}
|
|
2358
|
+
function buildSessionStoreSizeWarning(input) {
|
|
2359
|
+
const { dbBytes, cleanupEnabled } = input;
|
|
2360
|
+
if (dbBytes === null || dbBytes <= LARGE_DB_THRESHOLD_BYTES || cleanupEnabled) return null;
|
|
2361
|
+
const mib = Math.round(dbBytes / 1024 / 1024);
|
|
2362
|
+
return `Session store is large: opencode.db is ${mib} MiB and automatic session cleanup is off. Enable it with --session-cleanup-max-age 24h (env EVIDENT_SESSION_CLEANUP_MAX_AGE) to stop it growing \u2014 a store much larger than this can exceed a hosted runner's session-history restore budget on the next start, losing this runner's session history.`;
|
|
2363
|
+
}
|
|
2364
|
+
|
|
2325
2365
|
// src/lib/tunnel/connection.ts
|
|
2326
2366
|
import WebSocket2 from "ws";
|
|
2327
2367
|
|
|
@@ -2757,7 +2797,7 @@ import { homedir as homedir2 } from "os";
|
|
|
2757
2797
|
// src/lib/file-push.ts
|
|
2758
2798
|
import { randomUUID } from "crypto";
|
|
2759
2799
|
import { chmod, mkdir, open as open2, realpath, rename, unlink } from "fs/promises";
|
|
2760
|
-
import { basename, dirname as dirname2, isAbsolute, join as
|
|
2800
|
+
import { basename, dirname as dirname2, isAbsolute, join as join3, relative, resolve as resolve2, sep } from "path";
|
|
2761
2801
|
var FILE_MODE = 384;
|
|
2762
2802
|
var DIRECTORY_MODE = 448;
|
|
2763
2803
|
async function writePushedFile(request) {
|
|
@@ -2790,7 +2830,7 @@ async function writePushedFile(request) {
|
|
|
2790
2830
|
const { existingAncestor, missingSegments } = await resolveNearestExistingAncestor(
|
|
2791
2831
|
dirname2(candidate)
|
|
2792
2832
|
);
|
|
2793
|
-
const realTarget =
|
|
2833
|
+
const realTarget = join3(existingAncestor, ...missingSegments, basename(candidate));
|
|
2794
2834
|
const allowedDirectory = await findContainingAllowedDirectory(allowedDirectories, realTarget);
|
|
2795
2835
|
if (allowedDirectory === null) {
|
|
2796
2836
|
return refuse("path_not_allowed", "The runner does not allow writing to that location.", {
|
|
@@ -2826,7 +2866,7 @@ function expandAndValidate(requestedPath, homeDir) {
|
|
|
2826
2866
|
if (requestedPath.trim() === "" || requestedPath.includes("\0")) {
|
|
2827
2867
|
return null;
|
|
2828
2868
|
}
|
|
2829
|
-
const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ?
|
|
2869
|
+
const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ? join3(homeDir, requestedPath.slice(2)) : requestedPath;
|
|
2830
2870
|
if (expanded.split(/[/\\]/).includes("..")) {
|
|
2831
2871
|
return null;
|
|
2832
2872
|
}
|
|
@@ -2899,13 +2939,13 @@ function contains(realDirectory, realTarget) {
|
|
|
2899
2939
|
async function createMissingDirectories(existingAncestor, missingSegments) {
|
|
2900
2940
|
let current = existingAncestor;
|
|
2901
2941
|
for (const segment of missingSegments) {
|
|
2902
|
-
current =
|
|
2942
|
+
current = join3(current, segment);
|
|
2903
2943
|
await mkdir(current, { recursive: true, mode: DIRECTORY_MODE });
|
|
2904
2944
|
await chmod(current, DIRECTORY_MODE);
|
|
2905
2945
|
}
|
|
2906
2946
|
}
|
|
2907
2947
|
async function writeAtomically(realTarget, content) {
|
|
2908
|
-
const temporaryPath =
|
|
2948
|
+
const temporaryPath = join3(dirname2(realTarget), `.evident-push-${randomUUID()}.tmp`);
|
|
2909
2949
|
let handle;
|
|
2910
2950
|
try {
|
|
2911
2951
|
handle = await open2(temporaryPath, "wx", FILE_MODE);
|
|
@@ -3178,6 +3218,7 @@ var DEFAULT_STUCK_QUEUED_MS = 6e4;
|
|
|
3178
3218
|
var HEARTBEAT_MS = 6e4;
|
|
3179
3219
|
var ABSOLUTE_MAX_PROCESSING_MS = 6 * 60 * 60 * 1e3;
|
|
3180
3220
|
var B2_ABANDONMENT_MIN_PINNED_MS = 3 * 6e4;
|
|
3221
|
+
var AMBIGUOUS_FINISH_MAX_PINNED_MS = 3 * 6e4;
|
|
3181
3222
|
var B2_ABANDONMENT_RECHECK_MS = HEARTBEAT_MS;
|
|
3182
3223
|
var POLL_MISS_GRACE_MS = HEARTBEAT_MS;
|
|
3183
3224
|
var MAX_SUPERSEDED_CONVERSATIONS = 256;
|
|
@@ -4012,6 +4053,9 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
4012
4053
|
return this.reattachRedrive(conv, sessionId, message, ocId);
|
|
4013
4054
|
}
|
|
4014
4055
|
if (ongoing === false) {
|
|
4056
|
+
if (state === "running" && isAmbiguousFinishPinnedRunning(messages, ocId ?? "")) {
|
|
4057
|
+
return this.settleRedrive(conv, sessionId, message, ocId, messages, "done");
|
|
4058
|
+
}
|
|
4015
4059
|
this.clearRedriveUnresolved(message.id);
|
|
4016
4060
|
void this.postSignal(conv.id, message.id, "redrive_redispatched");
|
|
4017
4061
|
return "dispatch";
|
|
@@ -4623,7 +4667,9 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
4623
4667
|
deliveryDeadlineAnchored: false,
|
|
4624
4668
|
b2PinnedSinceMs: 0,
|
|
4625
4669
|
b2LastDescendantCheckMs: 0,
|
|
4626
|
-
b2AbandonedSignalled: false
|
|
4670
|
+
b2AbandonedSignalled: false,
|
|
4671
|
+
ambiguousPinnedSinceMs: 0,
|
|
4672
|
+
ambiguousResolved: false
|
|
4627
4673
|
});
|
|
4628
4674
|
}
|
|
4629
4675
|
/**
|
|
@@ -4702,7 +4748,9 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
4702
4748
|
deliveryDeadlineAnchored: false,
|
|
4703
4749
|
b2PinnedSinceMs: 0,
|
|
4704
4750
|
b2LastDescendantCheckMs: 0,
|
|
4705
|
-
b2AbandonedSignalled: false
|
|
4751
|
+
b2AbandonedSignalled: false,
|
|
4752
|
+
ambiguousPinnedSinceMs: 0,
|
|
4753
|
+
ambiguousResolved: false
|
|
4706
4754
|
});
|
|
4707
4755
|
}
|
|
4708
4756
|
/**
|
|
@@ -4970,6 +5018,49 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
4970
5018
|
}
|
|
4971
5019
|
}
|
|
4972
5020
|
}
|
|
5021
|
+
const ambiguousPinnedNow = activelyRunning && isAmbiguousFinishPinnedRunning(messages, inFlight.opencodeMessageId);
|
|
5022
|
+
if (!ambiguousPinnedNow) {
|
|
5023
|
+
if (snapshotReadable) {
|
|
5024
|
+
inFlight.ambiguousPinnedSinceMs = 0;
|
|
5025
|
+
inFlight.ambiguousResolved = false;
|
|
5026
|
+
}
|
|
5027
|
+
} else {
|
|
5028
|
+
if (inFlight.ambiguousResolved) {
|
|
5029
|
+
await this.settleMessageDone(sessionId, watcher, inFlight, messages);
|
|
5030
|
+
return;
|
|
5031
|
+
}
|
|
5032
|
+
if (inFlight.ambiguousPinnedSinceMs === 0) {
|
|
5033
|
+
inFlight.ambiguousPinnedSinceMs = this.now();
|
|
5034
|
+
const reply = findLastAssistantReplyFor(messages, inFlight.opencodeMessageId);
|
|
5035
|
+
const finish = reply?.info?.finish ?? reply?.finish;
|
|
5036
|
+
this.log({
|
|
5037
|
+
level: "warn",
|
|
5038
|
+
message: `Message ${id.slice(0, 8)} pinned running by an unrecognised finish ("${finish ?? "(absent)"}") \u2014 corroborating against opencode's session status before settling (issue #1493)`,
|
|
5039
|
+
conversation_id: conv.id,
|
|
5040
|
+
message_id: id
|
|
5041
|
+
});
|
|
5042
|
+
}
|
|
5043
|
+
const pinnedForMs = this.now() - inFlight.ambiguousPinnedSinceMs;
|
|
5044
|
+
const ongoing = await isSessionOngoing(this.port, sessionId);
|
|
5045
|
+
if (isAmbiguousFinishResolved({
|
|
5046
|
+
pinnedForMs,
|
|
5047
|
+
maxPinnedMs: AMBIGUOUS_FINISH_MAX_PINNED_MS,
|
|
5048
|
+
sessionOngoing: ongoing
|
|
5049
|
+
})) {
|
|
5050
|
+
inFlight.ambiguousResolved = true;
|
|
5051
|
+
this.log({
|
|
5052
|
+
level: "warn",
|
|
5053
|
+
message: `Message ${id.slice(0, 8)} ambiguous-finish-pinned for ${Math.round(pinnedForMs / 1e3)}s \u2014 resolved (${ongoing === false ? "session confirmed not-ongoing" : "pin exceeded the no-hang cap"}) \u2014 settling done`,
|
|
5054
|
+
conversation_id: conv.id,
|
|
5055
|
+
message_id: id
|
|
5056
|
+
});
|
|
5057
|
+
void this.postSignal(conv.id, id, "ambiguous_finish_resolved", {
|
|
5058
|
+
watched_for_ms: pinnedForMs
|
|
5059
|
+
});
|
|
5060
|
+
await this.settleMessageDone(sessionId, watcher, inFlight, messages);
|
|
5061
|
+
return;
|
|
5062
|
+
}
|
|
5063
|
+
}
|
|
4973
5064
|
if (activelyRunning && this.now() - inFlight.processingAnchorMs >= ABSOLUTE_MAX_PROCESSING_MS) {
|
|
4974
5065
|
this.log({
|
|
4975
5066
|
level: "warn",
|
|
@@ -5224,48 +5315,7 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
5224
5315
|
const ocId = row.opencode_message_id;
|
|
5225
5316
|
const state = messageRunState(messages, ocId ?? "");
|
|
5226
5317
|
if (state === "done") {
|
|
5227
|
-
|
|
5228
|
-
this.log({
|
|
5229
|
-
level: "debug",
|
|
5230
|
-
message: `Re-adopt: message ${row.id.slice(0, 8)} markDone is terminally undeliverable \u2014 left to the cron; skipping until it leaves processing`,
|
|
5231
|
-
conversation_id: row.conversation_id,
|
|
5232
|
-
message_id: row.id
|
|
5233
|
-
});
|
|
5234
|
-
return;
|
|
5235
|
-
}
|
|
5236
|
-
this.log({
|
|
5237
|
-
level: "info",
|
|
5238
|
-
message: `Re-adopt: message ${row.id.slice(0, 8)} completed while unwatched \u2014 marking done`,
|
|
5239
|
-
conversation_id: row.conversation_id,
|
|
5240
|
-
message_id: row.id
|
|
5241
|
-
});
|
|
5242
|
-
try {
|
|
5243
|
-
const title = await this.resolveSessionTitle(sessionId, row.conversation_id);
|
|
5244
|
-
const usage = messageUsage(messages, ocId ?? "");
|
|
5245
|
-
await this.markDone(row.conversation_id, row.id, sessionId, ocId, title, usage);
|
|
5246
|
-
} catch (err) {
|
|
5247
|
-
if (err instanceof ChannelAuthError) throw err;
|
|
5248
|
-
if (err instanceof ChannelTerminalError) {
|
|
5249
|
-
this.doneUndeliverable.add(row.id);
|
|
5250
|
-
this.log({
|
|
5251
|
-
level: "warn",
|
|
5252
|
-
message: `Re-adopt: failed to mark message ${row.id.slice(0, 8)} done (terminal HTTP ${err.status}) \u2014 parking until it leaves processing; leaving for the cron safety net: ${err.message}`,
|
|
5253
|
-
conversation_id: row.conversation_id,
|
|
5254
|
-
message_id: row.id
|
|
5255
|
-
});
|
|
5256
|
-
void this.postSignal(row.conversation_id, row.id, "readopt_undeliverable");
|
|
5257
|
-
return;
|
|
5258
|
-
}
|
|
5259
|
-
this.log({
|
|
5260
|
-
level: "warn",
|
|
5261
|
-
message: `Re-adopt: failed to mark message ${row.id.slice(0, 8)} done (will retry next drain): ${err instanceof Error ? err.message : String(err)}`,
|
|
5262
|
-
conversation_id: row.conversation_id,
|
|
5263
|
-
message_id: row.id
|
|
5264
|
-
});
|
|
5265
|
-
return;
|
|
5266
|
-
}
|
|
5267
|
-
this.dontRedispatch.delete(row.id);
|
|
5268
|
-
void this.postSignal(row.conversation_id, row.id, "readopt_done");
|
|
5318
|
+
await this.deliverReadoptedDone(sessionId, row, messages, ocId);
|
|
5269
5319
|
return;
|
|
5270
5320
|
}
|
|
5271
5321
|
const restartAborted = state === "failed" && sessionOngoing === false && isAbortedTerminalReply(messages, ocId ?? "");
|
|
@@ -5330,6 +5380,17 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
5330
5380
|
const ongoing = sessionOngoing;
|
|
5331
5381
|
statusReadableOngoing = ongoing;
|
|
5332
5382
|
if (ongoing === false) {
|
|
5383
|
+
if (isAmbiguousFinishPinnedRunning(messages, ocId ?? "")) {
|
|
5384
|
+
const finish = reply?.info?.finish ?? reply?.finish;
|
|
5385
|
+
this.log({
|
|
5386
|
+
level: "info",
|
|
5387
|
+
message: `Re-adopt: message ${row.id.slice(0, 8)} has an ambiguous finish ("${finish ?? "(absent)"}") but session ${sessionId.slice(0, 8)} is confirmed not-ongoing per GET /session/status \u2014 delivering the existing reply instead of re-dispatching`,
|
|
5388
|
+
conversation_id: row.conversation_id,
|
|
5389
|
+
message_id: row.id
|
|
5390
|
+
});
|
|
5391
|
+
await this.deliverReadoptedDone(sessionId, row, messages, ocId);
|
|
5392
|
+
return;
|
|
5393
|
+
}
|
|
5333
5394
|
this.log({
|
|
5334
5395
|
level: "info",
|
|
5335
5396
|
message: `Re-adopt: message ${row.id.slice(0, 8)} running/${shape} but session ${sessionId.slice(0, 8)} is not-ongoing per GET /session/status (absent/idle) \u2014 re-dispatching from scratch (status-gated recovery)`,
|
|
@@ -5406,6 +5467,63 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
5406
5467
|
}
|
|
5407
5468
|
await this.forceReadoptRun(sessionId, row);
|
|
5408
5469
|
}
|
|
5470
|
+
/**
|
|
5471
|
+
* Deliver a `processing` row whose correlated reply already completed while
|
|
5472
|
+
* nobody was watching (ADR-0046) — the `readoptOne` `state === 'done'` body,
|
|
5473
|
+
* extracted (#1493 Task 2.4) so the ambiguous-finish guard above can call the
|
|
5474
|
+
* SAME delivery instead of duplicating it.
|
|
5475
|
+
*
|
|
5476
|
+
* EVEN IF the row was previously parked in `dontRedispatch` (a give-up stops
|
|
5477
|
+
* re-dispatch, not delivery — Bugbot #202). Guarded EXACTLY like the watcher's
|
|
5478
|
+
* `settleMessageDone`: auth re-throws; terminal → park in `doneUndeliverable` +
|
|
5479
|
+
* leave for cron; transient → log + leave for the next drain (the still-
|
|
5480
|
+
* `processing` row is re-read and retried). markDone is idempotent server-side
|
|
5481
|
+
* (status-gated), so a repeat can never double-post.
|
|
5482
|
+
*/
|
|
5483
|
+
async deliverReadoptedDone(sessionId, row, messages, ocId) {
|
|
5484
|
+
if (this.doneUndeliverable.has(row.id)) {
|
|
5485
|
+
this.log({
|
|
5486
|
+
level: "debug",
|
|
5487
|
+
message: `Re-adopt: message ${row.id.slice(0, 8)} markDone is terminally undeliverable \u2014 left to the cron; skipping until it leaves processing`,
|
|
5488
|
+
conversation_id: row.conversation_id,
|
|
5489
|
+
message_id: row.id
|
|
5490
|
+
});
|
|
5491
|
+
return;
|
|
5492
|
+
}
|
|
5493
|
+
this.log({
|
|
5494
|
+
level: "info",
|
|
5495
|
+
message: `Re-adopt: message ${row.id.slice(0, 8)} completed while unwatched \u2014 marking done`,
|
|
5496
|
+
conversation_id: row.conversation_id,
|
|
5497
|
+
message_id: row.id
|
|
5498
|
+
});
|
|
5499
|
+
try {
|
|
5500
|
+
const title = await this.resolveSessionTitle(sessionId, row.conversation_id);
|
|
5501
|
+
const usage = messageUsage(messages, ocId ?? "");
|
|
5502
|
+
await this.markDone(row.conversation_id, row.id, sessionId, ocId, title, usage);
|
|
5503
|
+
} catch (err) {
|
|
5504
|
+
if (err instanceof ChannelAuthError) throw err;
|
|
5505
|
+
if (err instanceof ChannelTerminalError) {
|
|
5506
|
+
this.doneUndeliverable.add(row.id);
|
|
5507
|
+
this.log({
|
|
5508
|
+
level: "warn",
|
|
5509
|
+
message: `Re-adopt: failed to mark message ${row.id.slice(0, 8)} done (terminal HTTP ${err.status}) \u2014 parking until it leaves processing; leaving for the cron safety net: ${err.message}`,
|
|
5510
|
+
conversation_id: row.conversation_id,
|
|
5511
|
+
message_id: row.id
|
|
5512
|
+
});
|
|
5513
|
+
void this.postSignal(row.conversation_id, row.id, "readopt_undeliverable");
|
|
5514
|
+
return;
|
|
5515
|
+
}
|
|
5516
|
+
this.log({
|
|
5517
|
+
level: "warn",
|
|
5518
|
+
message: `Re-adopt: failed to mark message ${row.id.slice(0, 8)} done (will retry next drain): ${err instanceof Error ? err.message : String(err)}`,
|
|
5519
|
+
conversation_id: row.conversation_id,
|
|
5520
|
+
message_id: row.id
|
|
5521
|
+
});
|
|
5522
|
+
return;
|
|
5523
|
+
}
|
|
5524
|
+
this.dontRedispatch.delete(row.id);
|
|
5525
|
+
void this.postSignal(row.conversation_id, row.id, "readopt_done");
|
|
5526
|
+
}
|
|
5409
5527
|
/**
|
|
5410
5528
|
* Re-dispatch an orphaned (`unknown`/null-id) `processing` row (ADR-0046 §2).
|
|
5411
5529
|
*
|
|
@@ -6035,17 +6153,25 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
6035
6153
|
* the aborted-in-flight production bug after a restart.
|
|
6036
6154
|
* - `b2` — a COMPLETED reply pinned running only by `finish === "tool-calls"`
|
|
6037
6155
|
* (the sub-agent preamble — #253's shape).
|
|
6038
|
-
* - `
|
|
6039
|
-
*
|
|
6040
|
-
*
|
|
6041
|
-
* `
|
|
6156
|
+
* - `ambiguous` — a COMPLETED, non-errored reply whose `finish` is neither
|
|
6157
|
+
* "tool-calls" nor "stop" (issue #1493, class 4 — see
|
|
6158
|
+
* `isAmbiguousFinishPinnedRunning`/Task 2.4).
|
|
6159
|
+
* - `other` — any other shape (defensive; a running row is normally b1, b2 or
|
|
6160
|
+
* ambiguous).
|
|
6161
|
+
* Reads `info.time.completed` / `info.finish` / `info.error` (tolerating the
|
|
6162
|
+
* legacy top-level shape) directly rather than re-importing the module-private
|
|
6163
|
+
* `completedOf`/`finishOf`/`errorOf` — this is a display label only, not a
|
|
6164
|
+
* correctness predicate (that is `isAmbiguousFinishPinnedRunning`'s job).
|
|
6042
6165
|
*/
|
|
6043
6166
|
replyCompletionShape(reply) {
|
|
6044
6167
|
if (!reply) return "other";
|
|
6045
6168
|
const completed = reply.info?.time?.completed ?? reply.time?.completed;
|
|
6046
6169
|
if (completed == null) return "b1";
|
|
6047
6170
|
const finish = reply.info?.finish ?? reply.finish;
|
|
6048
|
-
|
|
6171
|
+
if (finish === "tool-calls") return "b2";
|
|
6172
|
+
const error2 = reply.info?.error ?? reply.error;
|
|
6173
|
+
if (finish !== "stop" && error2 == null) return "ambiguous";
|
|
6174
|
+
return "other";
|
|
6049
6175
|
}
|
|
6050
6176
|
/**
|
|
6051
6177
|
* Attribute a surfaced interaction to the in-flight message it paused on (M-1).
|
|
@@ -6662,7 +6788,7 @@ function resolveFileSyncDirectories(raw, homeDir) {
|
|
|
6662
6788
|
if (trimmed === "") {
|
|
6663
6789
|
throw new Error("--enable-file-sync-to requires a directory path (got an empty value)");
|
|
6664
6790
|
}
|
|
6665
|
-
const expanded = trimmed === "~" ? homeDir : trimmed.startsWith("~/") ?
|
|
6791
|
+
const expanded = trimmed === "~" ? homeDir : trimmed.startsWith("~/") ? join4(homeDir, trimmed.slice(2)) : trimmed;
|
|
6666
6792
|
if (!isAbsolute2(expanded)) {
|
|
6667
6793
|
throw new Error(`--enable-file-sync-to requires an absolute directory path; got "${entry}"`);
|
|
6668
6794
|
}
|
|
@@ -7025,6 +7151,13 @@ function scheduleSessionCleanup(state, driver, options) {
|
|
|
7025
7151
|
for (const warning2 of config.warnings) {
|
|
7026
7152
|
logActivity(state, { type: "info", level: "warn", message: `Session cleanup: ${warning2}` });
|
|
7027
7153
|
}
|
|
7154
|
+
const sizeWarning = buildSessionStoreSizeWarning({
|
|
7155
|
+
dbBytes: statSessionDbBytes(homedir3()),
|
|
7156
|
+
cleanupEnabled: config.enabled
|
|
7157
|
+
});
|
|
7158
|
+
if (sizeWarning !== null) {
|
|
7159
|
+
logActivity(state, { type: "info", level: "warn", message: sizeWarning });
|
|
7160
|
+
}
|
|
7028
7161
|
if (!config.enabled) return;
|
|
7029
7162
|
logActivity(state, {
|
|
7030
7163
|
type: "info",
|