@integrity-labs/agt-cli 0.28.840 → 0.28.841

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.
@@ -29854,611 +29854,32 @@ var DirectChatClaimTracker = class {
29854
29854
  }
29855
29855
  };
29856
29856
 
29857
- // src/direct-chat-session-state.ts
29858
- import { existsSync, readFileSync } from "fs";
29859
- import { join } from "path";
29860
- function readDirectChatSessionState(agentDir, nowMs) {
29861
- const path = join(agentDir, "direct-chat-session.json");
29862
- try {
29863
- if (!existsSync(path)) return { fresh: false, startedAtMs: nowMs };
29864
- const parsed = JSON.parse(readFileSync(path, "utf8"));
29865
- if (!parsed || typeof parsed !== "object") return { fresh: false, startedAtMs: nowMs };
29866
- const o = parsed;
29867
- const fresh = o.fresh === true;
29868
- const startedAtMs = typeof o.startedAtMs === "number" && Number.isFinite(o.startedAtMs) ? o.startedAtMs : nowMs;
29869
- return { fresh, startedAtMs };
29870
- } catch {
29871
- return { fresh: false, startedAtMs: nowMs };
29872
- }
29873
- }
29857
+ // ../core/dist/types/agent.js
29858
+ var FRAMEWORK_MODEL_SLOTS = {
29859
+ "claude-code": ["primary", "small_fast", "advisor"],
29860
+ "opencode": ["primary"]
29861
+ };
29862
+ var KNOWN_MODEL_SLOTS = new Set(Object.values(FRAMEWORK_MODEL_SLOTS).flat());
29874
29863
 
29875
- // src/direct-chat-channel-meta.ts
29876
- function buildDirectChatChannelMeta(input) {
29877
- const identity = {};
29878
- if (input.userName) identity.user_name = input.userName;
29879
- if (input.userEmail) identity.user_email = input.userEmail;
29880
- if (input.userRole) identity.user_role = input.userRole;
29881
- const page = {};
29882
- if (input.pageSlug) page.page_slug = input.pageSlug;
29883
- if (input.pageTitle) page.page_title = input.pageTitle;
29884
- if (input.pageUrl) page.page_url = input.pageUrl;
29885
- if (input.pageContentUrl) page.page_content_url = input.pageContentUrl;
29886
- if (input.pageArtefactId) page.page_artefact_id = input.pageArtefactId;
29887
- if (input.pageGated != null) page.page_gated = input.pageGated ? "true" : "false";
29888
- return {
29889
- session_id: input.sessionId,
29890
- user: "webapp",
29891
- source: "direct-chat",
29892
- ...identity,
29893
- ...page,
29894
- // String flag the agent's channel instructions (and the renderer) read:
29895
- // 'false' = notice (do not reply), 'true' = normal message (reply expected).
29896
- requires_reply: input.isNotice ? "false" : "true",
29897
- // ENG-6407 / ADR-0024 Slice 1: provenance + reply-obligation as first-class
29898
- // tag attributes. Direct chat is conversational; a notice owes no reply, so
29899
- // expects_reply mirrors the existing requires_reply flag. Recorded only - no
29900
- // consumer gates on these yet (Slice 2).
29901
- lane: "conversational",
29902
- expects_reply: input.isNotice ? "false" : "true"
29903
- };
29904
- }
29864
+ // ../core/dist/types/model-policy.js
29865
+ var KNOWN_MODEL_POLICY_SLOTS = [...KNOWN_MODEL_SLOTS].sort();
29866
+ var MODEL_POLICY_SLOTS_IN_ORDER = [...KNOWN_MODEL_SLOTS];
29867
+ var MODEL_POLICY_SLOTS_PENDING_ACTUATOR = /* @__PURE__ */ new Set();
29868
+ var OFFERABLE_MODEL_POLICY_SLOTS = MODEL_POLICY_SLOTS_IN_ORDER.filter((slot) => !MODEL_POLICY_SLOTS_PENDING_ACTUATOR.has(slot));
29905
29869
 
29906
- // src/turn-initiator-marker.ts
29907
- import { writeFileSync, readFileSync as readFileSync2, mkdirSync, renameSync } from "fs";
29908
- import { dirname, join as join2 } from "path";
29909
- var TURN_INITIATOR_MAX_AGE_MS = 5 * 60 * 1e3;
29910
- var TURN_INITIATOR_LEDGER_MAX_ENTRIES = 20;
29911
- var TURN_INITIATOR_LEDGER_FILENAME = ".turn-initiator-ledger.json";
29912
- function turnInitiatorLedgerPath(singleSlotFile) {
29913
- return join2(dirname(singleSlotFile), TURN_INITIATOR_LEDGER_FILENAME);
29914
- }
29915
- function foldTurnInitiatorLedger(existing, marker, maxAgeMs = TURN_INITIATOR_MAX_AGE_MS, maxEntries = TURN_INITIATOR_LEDGER_MAX_ENTRIES) {
29916
- const now = marker.ts;
29917
- const key = (e) => `${e.channel}\0${e.channel_ref ?? ""}`;
29918
- const markerKey = key(marker);
29919
- const prior = existing && Array.isArray(existing.entries) ? existing.entries : [];
29920
- const kept = prior.filter(
29921
- (e) => e && typeof e.ts === "number" && Number.isFinite(e.ts) && now - e.ts <= maxAgeMs && now - e.ts >= 0 && key(e) !== markerKey
29922
- // the same thread is replaced by the new entry below
29923
- );
29924
- kept.push({
29925
- channel: marker.channel,
29926
- sender_id: marker.sender_id,
29927
- ...marker.sender_name ? { sender_name: marker.sender_name } : {},
29928
- ...marker.channel_ref ? { channel_ref: marker.channel_ref } : {},
29929
- ts: marker.ts
29930
- });
29931
- kept.sort((a, b) => b.ts - a.ts);
29932
- return { v: 1, entries: kept.slice(0, maxEntries) };
29933
- }
29934
- function updateTurnInitiatorLedger(singleSlotFile, marker) {
29935
- const ledgerFile = turnInitiatorLedgerPath(singleSlotFile);
29936
- let existing = null;
29937
- try {
29938
- const parsed = JSON.parse(readFileSync2(ledgerFile, "utf8"));
29939
- if (parsed && parsed.v === 1 && Array.isArray(parsed.entries)) existing = parsed;
29940
- } catch {
29941
- }
29942
- const next = foldTurnInitiatorLedger(existing, marker);
29943
- const tmp = `${ledgerFile}.tmp`;
29944
- writeFileSync(tmp, JSON.stringify(next), "utf8");
29945
- renameSync(tmp, ledgerFile);
29946
- }
29947
- function writeTurnInitiatorMarker(input) {
29948
- const file = process.env["AGT_TURN_INITIATOR_FILE"];
29949
- if (!file || !input.sender_id) return;
29950
- try {
29951
- mkdirSync(dirname(file), { recursive: true });
29952
- const marker = { ...input, ts: Date.now() };
29953
- const tmp = `${file}.tmp`;
29954
- writeFileSync(tmp, JSON.stringify(marker), "utf8");
29955
- renameSync(tmp, file);
29956
- try {
29957
- updateTurnInitiatorLedger(file, marker);
29958
- } catch {
29959
- }
29960
- } catch {
29961
- }
29962
- }
29870
+ // ../core/dist/types/integration.js
29871
+ var HITL_TIER_ORDER = [
29872
+ "read",
29873
+ "write",
29874
+ "write_high_risk",
29875
+ "write_destructive",
29876
+ "admin"
29877
+ ];
29878
+ var HITL_TIER_RANK = Object.freeze(Object.fromEntries(HITL_TIER_ORDER.map((tier, i) => [tier, i])));
29963
29879
 
29964
- // src/scheduled-turn-marker.ts
29965
- import { readFileSync as readFileSync3, unlinkSync } from "fs";
29966
- import { join as join3 } from "path";
29967
- var SCHEDULED_TURN_MARKER_FILENAME = ".current-scheduled-turn.json";
29968
- var SCHEDULED_TURN_MAX_AGE_MS = 15 * 60 * 1e3;
29969
- function clearScheduledTurnMarker(agentDir) {
29970
- if (!agentDir) return;
29971
- try {
29972
- unlinkSync(join3(agentDir, SCHEDULED_TURN_MARKER_FILENAME));
29973
- } catch {
29974
- }
29975
- }
29976
-
29977
- // src/direct-chat-pending-inbound.ts
29978
- import {
29979
- existsSync as existsSync2,
29980
- mkdirSync as mkdirSync2,
29981
- readdirSync,
29982
- readFileSync as readFileSync4,
29983
- renameSync as renameSync2,
29984
- unlinkSync as unlinkSync2,
29985
- writeFileSync as writeFileSync2
29986
- } from "fs";
29987
- import { join as join4 } from "path";
29988
- function hexEncodeSegment(value) {
29989
- return Buffer.from(value, "utf8").toString("hex");
29990
- }
29991
- function directChatMarkerName(sessionId, messageId) {
29992
- return `${hexEncodeSegment(sessionId)}__${hexEncodeSegment(messageId)}.json`;
29993
- }
29994
- var defaultClearMarkerFile = (fullPath) => {
29995
- try {
29996
- if (existsSync2(fullPath)) unlinkSync2(fullPath);
29997
- } catch {
29998
- }
29999
- };
30000
- function directChatInboundId(sessionId, messageId) {
30001
- return `direct-chat|${sessionId.length}|${sessionId}|${messageId}`;
30002
- }
30003
- function writeDirectChatPendingInboundMarker(dir, sessionId, messageId) {
30004
- if (!dir || !sessionId || !messageId) return;
30005
- try {
30006
- mkdirSync2(dir, { recursive: true });
30007
- const marker = {
30008
- channel: "direct-chat",
30009
- session_id: sessionId,
30010
- message_id: messageId,
30011
- received_at: (/* @__PURE__ */ new Date()).toISOString(),
30012
- inbound_id: directChatInboundId(sessionId, messageId)
30013
- };
30014
- const final = join4(dir, directChatMarkerName(sessionId, messageId));
30015
- const tmp = `${final}.tmp`;
30016
- writeFileSync2(tmp, JSON.stringify(marker), "utf8");
30017
- renameSync2(tmp, final);
30018
- } catch {
30019
- }
30020
- }
30021
- function clearDirectChatPendingMarkersForSession(dir, sessionId, op = defaultClearMarkerFile) {
30022
- if (!dir || !sessionId) return 0;
30023
- const prefix = `${hexEncodeSegment(sessionId)}__`;
30024
- let cleared = 0;
30025
- let names;
30026
- try {
30027
- names = readdirSync(dir);
30028
- } catch {
30029
- return 0;
30030
- }
30031
- for (const name of names) {
30032
- if (!name.startsWith(prefix) || !name.endsWith(".json")) continue;
30033
- op(join4(dir, name));
30034
- cleared += 1;
30035
- }
30036
- return cleared;
30037
- }
30038
- function readDirectChatPendingInboundMarker(dir, sessionId, messageId, readFileImpl = (path) => readFileSync4(path, "utf8")) {
30039
- if (!dir || !sessionId || !messageId) return null;
30040
- try {
30041
- const raw = readFileImpl(join4(dir, directChatMarkerName(sessionId, messageId)));
30042
- return JSON.parse(raw);
30043
- } catch {
30044
- return null;
30045
- }
30046
- }
30047
- var MAX_REPORTED_ORPHANS = 20;
30048
- function sweepAgedDirectChatMarkers(dir, deps) {
30049
- const result = {
30050
- cleared: 0,
30051
- orphaned: 0,
30052
- orphanedIdentities: [],
30053
- orphanedTruncated: false
30054
- };
30055
- if (!dir) return result;
30056
- let names;
30057
- try {
30058
- names = deps.readdir(dir);
30059
- } catch {
30060
- return result;
30061
- }
30062
- for (const name of names) {
30063
- if (!name.endsWith(".json") || name.startsWith(".")) continue;
30064
- const full = join4(dir, name);
30065
- let marker;
30066
- try {
30067
- marker = JSON.parse(deps.readFile(full));
30068
- } catch {
30069
- continue;
30070
- }
30071
- if (!deps.isAged(marker.received_at)) continue;
30072
- const undelivered = !deps.hasDelivery(marker.inbound_id);
30073
- try {
30074
- deps.unlink(full);
30075
- } catch {
30076
- continue;
30077
- }
30078
- result.cleared += 1;
30079
- if (undelivered) {
30080
- const identity = {
30081
- inboundId: marker.inbound_id,
30082
- receivedAt: marker.received_at,
30083
- sessionId: marker.session_id
30084
- };
30085
- try {
30086
- deps.onOrphaned(identity);
30087
- } catch {
30088
- }
30089
- result.orphaned += 1;
30090
- if (result.orphanedIdentities.length < MAX_REPORTED_ORPHANS) {
30091
- result.orphanedIdentities.push(identity);
30092
- } else {
30093
- result.orphanedTruncated = true;
30094
- }
30095
- }
30096
- }
30097
- return result;
30098
- }
30099
-
30100
- // src/direct-chat-recovery-outbox.ts
30101
- async function consumeDirectChatRecoveryFile(fullPath, filename, deps) {
30102
- if (filename.endsWith(".tmp") || filename.endsWith(".poison")) return "skipped";
30103
- let payload;
30104
- try {
30105
- payload = JSON.parse(deps.readFile(fullPath));
30106
- } catch (err) {
30107
- deps.log(`recovery outbox parse failed (${filename}): ${err.message}`);
30108
- try {
30109
- deps.renameFile(fullPath, `${fullPath}.parse-error.poison`);
30110
- } catch {
30111
- }
30112
- return "poison";
30113
- }
30114
- if (!payload || !payload.session_id || !payload.text) {
30115
- deps.log(`recovery outbox malformed (${filename}): missing session_id or text`);
30116
- if (payload && payload.marker_name) {
30117
- try {
30118
- deps.removeLedgerEntry(payload.marker_name);
30119
- } catch {
30120
- }
30121
- }
30122
- try {
30123
- deps.renameFile(fullPath, `${fullPath}.malformed.poison`);
30124
- } catch {
30125
- }
30126
- return "poison";
30127
- }
30128
- const content = deps.sanitize(payload.text);
30129
- const messageIds = deps.peekClaims(payload.session_id);
30130
- let result;
30131
- try {
30132
- result = await deps.deliver({
30133
- sessionId: payload.session_id,
30134
- content,
30135
- messageIds
30136
- });
30137
- } catch (err) {
30138
- result = { ok: false, error: err.message };
30139
- }
30140
- if (result.ok && !result.error) {
30141
- deps.clearClaims(payload.session_id, messageIds);
30142
- deps.clearMarkers(payload.session_id);
30143
- if (payload.marker_name) deps.removeLedgerEntry(payload.marker_name);
30144
- try {
30145
- deps.unlinkFile(fullPath);
30146
- } catch {
30147
- }
30148
- deps.log(`ghost-reply recovery sent (session=${payload.session_id})`);
30149
- return "delivered";
30150
- }
30151
- if (payload.marker_name) deps.removeLedgerEntry(payload.marker_name);
30152
- try {
30153
- deps.unlinkFile(fullPath);
30154
- } catch {
30155
- }
30156
- deps.log(
30157
- `ghost-reply recovery send failed (session=${payload.session_id}): ${result.error ?? "unknown"} - re-armed for retry`
30158
- );
30159
- return "failed";
30160
- }
30161
-
30162
- // src/inbound-delivery-ledger.ts
30163
- import { existsSync as existsSync3, mkdirSync as mkdirSync3, readdirSync as readdirSync2, readFileSync as readFileSync5, renameSync as renameSync3, writeFileSync as writeFileSync3 } from "fs";
30164
- import { join as join5 } from "path";
30165
- function safeInboundId(inboundId) {
30166
- return inboundId.replace(/[^A-Za-z0-9_-]/g, "_");
30167
- }
30168
- var defaultDeps = {
30169
- mkdir: (dir) => mkdirSync3(dir, { recursive: true }),
30170
- writeFile: (path, data) => writeFileSync3(path, data, "utf8"),
30171
- rename: (from, to) => renameSync3(from, to),
30172
- readdir: (dir) => readdirSync2(dir),
30173
- readFile: (path) => readFileSync5(path, "utf8"),
30174
- exists: (path) => existsSync3(path)
30175
- };
30176
- function writeInboundDeliveryLedgerEntry(dir, record2, deps = defaultDeps) {
30177
- if (!dir || !record2.inbound_id || !record2.conv_key) return;
30178
- const safe = safeInboundId(record2.inbound_id);
30179
- if (!safe || safe.includes("/") || safe.includes("\\") || safe.includes("..")) return;
30180
- try {
30181
- deps.mkdir(dir);
30182
- const final = join5(dir, `${safe}.json`);
30183
- const tmp = `${final}.tmp`;
30184
- deps.writeFile(tmp, JSON.stringify(record2));
30185
- deps.rename(tmp, final);
30186
- } catch {
30187
- }
30188
- }
30189
- function inboundDeliveryLedgerHas(dir, inboundId, deps = defaultDeps) {
30190
- if (!dir || !inboundId) return false;
30191
- const safe = safeInboundId(inboundId);
30192
- if (!safe || safe.includes("/") || safe.includes("\\") || safe.includes("..")) return false;
30193
- try {
30194
- return deps.exists(join5(dir, `${safe}.json`));
30195
- } catch {
30196
- return false;
30197
- }
30198
- }
30199
-
30200
- // src/ack-reaction.ts
30201
- import { readdirSync as readdirSync3, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "fs";
30202
- import { join as join7 } from "path";
30203
-
30204
- // src/flags-cache-read.ts
30205
- import { existsSync as existsSync4, readFileSync as readFileSync6 } from "fs";
30206
- import { homedir } from "os";
30207
- import { join as join6 } from "path";
30208
- function defaultFlagsCachePath() {
30209
- return join6(homedir(), ".augmented", "flags-cache.json");
30210
- }
30211
- function envBoolean(raw) {
30212
- if (raw === void 0) return void 0;
30213
- const v = raw.trim().toLowerCase();
30214
- if (v === "") return void 0;
30215
- if (v === "1" || v === "true" || v === "yes" || v === "on") return true;
30216
- if (v === "0" || v === "false" || v === "no" || v === "off") return false;
30217
- return void 0;
30218
- }
30219
- function cachedBoolean(key, path) {
30220
- try {
30221
- if (!existsSync4(path)) return void 0;
30222
- const parsed = JSON.parse(readFileSync6(path, "utf8"));
30223
- if (!parsed || typeof parsed !== "object") return void 0;
30224
- const flags = parsed.flags;
30225
- if (!flags || typeof flags !== "object") return void 0;
30226
- const value = flags[key];
30227
- return typeof value === "boolean" ? value : void 0;
30228
- } catch {
30229
- return void 0;
30230
- }
30231
- }
30232
- function resolveHostBooleanFlag(opts) {
30233
- const env2 = opts.env ?? process.env;
30234
- const envValue = envBoolean(env2[opts.envVar]);
30235
- if (envValue !== void 0) return envValue;
30236
- const cached2 = cachedBoolean(opts.key, opts.cachePath ?? defaultFlagsCachePath());
30237
- if (cached2 !== void 0) return cached2;
30238
- return opts.defaultValue;
30239
- }
30240
-
30241
- // ../core/dist/channels/deflection-causes.js
30242
- var DEFLECTION_CAUSES = [
30243
- "integration_down",
30244
- "session_dead",
30245
- "wedged",
30246
- "busy",
30247
- "duplicate",
30248
- // ENG-8387: an `app_mention` — the CANONICAL delivery of an @mention — dropped
30249
- // by the fresh-ingress dedup. Split out of the generic `duplicate` bucket
30250
- // because the two mean very different things: `duplicate` is dominated by the
30251
- // deliberate, high-volume ENG-6378 echo drop and by routine reconnect
30252
- // redelivery, both benign, while a dropped app_mention has the exact shape of
30253
- // a lost mention. Conflated, the second is invisible inside the first — which
30254
- // is how ENG-6378's silent loss ran unobserved.
30255
- "duplicate_mention",
30256
- "mention_echo_orphan",
30257
- "replay_orphaned",
30258
- "replay_exhausted",
30259
- // ENG-9414 — see the type union above for why these two exist. Both are
30260
- // message-loss classes the pre-existing causes were structurally unable to
30261
- // observe, which is why four destroyed customer inbounds in one hour
30262
- // produced zero alerts.
30263
- "aged_out_unrecoverable",
30264
- "marker_corrupt",
30265
- "unknown"
30266
- ];
30267
- var DEFLECTION_CAUSE_SET = new Set(DEFLECTION_CAUSES);
30268
-
30269
- // src/ack-reaction.ts
30270
- var REPLY_WEDGED_THRESHOLD_MS = 5 * 60 * 1e3;
30271
- var ACK_PANE_FRESH_THRESHOLD_MS = 6e4;
30272
- function classifyUndeliverableCause(i) {
30273
- if (!i.hasTarget) return null;
30274
- if (!i.integrationReady) return "integration_down";
30275
- if (i.tmux === "dead") return "session_dead";
30276
- if (!i.withinStartupGrace && i.claude === "dead") return "session_dead";
30277
- const threshold = i.pendingStaleThresholdMs ?? REPLY_WEDGED_THRESHOLD_MS;
30278
- if (i.oldestPendingAgeMs != null && i.oldestPendingAgeMs > threshold) {
30279
- const paneFreshThreshold = i.paneFreshThresholdMs ?? ACK_PANE_FRESH_THRESHOLD_MS;
30280
- const paneIsFresh = i.paneLogFreshAgeMs != null && i.paneLogFreshAgeMs <= paneFreshThreshold;
30281
- if (paneIsFresh && i.tmux === "alive" && i.claude === "alive") {
30282
- return null;
30283
- }
30284
- return "wedged";
30285
- }
30286
- return null;
30287
- }
30288
- var UNDELIVERABLE_NOTICE_THROTTLE_MS = 5 * 60 * 1e3;
30289
- function shouldPostUndeliverableNotice(lastNoticeAtMs, nowMs, throttleMs = UNDELIVERABLE_NOTICE_THROTTLE_MS) {
30290
- return lastNoticeAtMs == null || nowMs - lastNoticeAtMs >= throttleMs;
30291
- }
30292
- function undeliverableNoticeText(replay) {
30293
- const honest = noticePromiseIsHonest(replay);
30294
- return honest ? "\u23F3 I can't get to this right now \u2014 no need to resend; I'll pick it up automatically as soon as I'm free." : "\u23F3 I can't get to this right now. Please resend in a few minutes \u2014 I may not see this one.";
30295
- }
30296
- function undeliverableNoticeTextForRepeat(i) {
30297
- if (i.priorNotices <= 0) return undeliverableNoticeText(i.replay);
30298
- if (i.priorNotices === 1) {
30299
- return noticePromiseIsHonest(i.replay) ? "\u23F3 Still stuck on this one \u2014 the automatic pickup I mentioned has not happened. Please resend when you can." : "\u23F3 Still stuck on this one, and I still have not picked it up. Resending is the reliable way through.";
30300
- }
30301
- return null;
30302
- }
30303
- function noticePromiseIsHonest(replay) {
30304
- if (replay === void 0) return false;
30305
- if (typeof replay === "boolean") return replay;
30306
- return markerReplayable({
30307
- enabled: replay.enabled ?? channelReplayEnabled(),
30308
- hasPayload: replay.hasPayload,
30309
- discretionary: replay.discretionary
30310
- });
30311
- }
30312
- var BUSY_ACK_THRESHOLD_MS = 12e4;
30313
- var BUSY_ACK_NOTICE_THROTTLE_MS = 20 * 60 * 1e3;
30314
- function channelBusyAckEnabled() {
30315
- return resolveHostBooleanFlag({
30316
- key: "channel-busy-ack",
30317
- envVar: "AGT_CHANNEL_BUSY_ACK_ENABLED",
30318
- defaultValue: false
30319
- });
30320
- }
30321
- function channelBusyAckThresholdMs() {
30322
- const raw = parseInt(process.env.AGT_CHANNEL_BUSY_ACK_THRESHOLD_MS ?? "", 10);
30323
- return Number.isFinite(raw) && raw > 0 ? raw : BUSY_ACK_THRESHOLD_MS;
30324
- }
30325
- function decideBusyAck(i) {
30326
- if (!i.hasTarget) return false;
30327
- if (!i.arrivedWhileBusy) return false;
30328
- if (!i.stillPending) return false;
30329
- if (!Number.isFinite(i.pendingAgeMs)) return false;
30330
- const threshold = i.thresholdMs ?? channelBusyAckThresholdMs();
30331
- if (i.pendingAgeMs < threshold) return false;
30332
- if (!i.sessionAlive) return false;
30333
- const paneFreshThreshold = i.paneFreshThresholdMs ?? ACK_PANE_FRESH_THRESHOLD_MS;
30334
- return i.paneLogFreshAgeMs != null && i.paneLogFreshAgeMs <= paneFreshThreshold;
30335
- }
30336
- function busyAckNoticeText() {
30337
- return "\u{1F6E0}\uFE0F I'm in the middle of something right now \u2014 I'll follow up on this as soon as I'm free.";
30338
- }
30339
- var GIVE_UP_SIGNAL_MAX_AGE_MS = 30 * 60 * 1e3;
30340
- function turnFailedNoticeText(failureClass) {
30341
- const cause = failureClass === "overloaded" ? "the service I run on is overloaded right now \u2014 nothing to do with you or what you asked for" : failureClass === "server_error" ? "something upstream of me is failing right now \u2014 nothing to do with you or what you asked for" : null;
30342
- if (cause === null) {
30343
- return "\u26A0\uFE0F Something went wrong on my side and I couldn\u2019t finish that. Sorry \u2014 send it again when you get a chance and I\u2019ll pick it straight up.";
30344
- }
30345
- return `\u26A0\uFE0F I couldn\u2019t finish that \u2014 ${cause}. Sorry \u2014 send it again when you get a chance and I\u2019ll pick it straight up.`;
30346
- }
30347
- function oldestPendingMarkerAgeMs(dir, now = Date.now(), opts) {
30348
- if (!dir) return null;
30349
- let names;
30350
- try {
30351
- names = readdirSync3(dir);
30352
- } catch {
30353
- return null;
30354
- }
30355
- let oldest = null;
30356
- for (const name of names) {
30357
- if (!name.endsWith(".json")) continue;
30358
- let receivedAt;
30359
- try {
30360
- const raw = JSON.parse(readFileSync7(join7(dir, name), "utf-8"));
30361
- if (raw.discretionary === true) continue;
30362
- if (!opts?.includeSeen && typeof raw.seen_at === "string" && raw.seen_at) continue;
30363
- receivedAt = raw.received_at;
30364
- } catch {
30365
- continue;
30366
- }
30367
- if (!receivedAt) continue;
30368
- const t = Date.parse(receivedAt);
30369
- if (Number.isNaN(t)) continue;
30370
- const age = now - t;
30371
- if (age < 0) continue;
30372
- if (oldest == null || age > oldest) oldest = age;
30373
- }
30374
- return oldest;
30375
- }
30376
- function hasInFlightInbound(dir, now = Date.now()) {
30377
- return oldestPendingMarkerAgeMs(dir, now, { includeSeen: true }) != null;
30378
- }
30379
- function channelOrphanMarkerMs() {
30380
- const raw = parseInt(process.env.AGT_CHANNEL_ORPHAN_MARKER_MS ?? "", 10);
30381
- return Number.isFinite(raw) && raw > 0 ? Math.max(raw, 12e4) : 30 * 6e4;
30382
- }
30383
- var ORPHAN_SWEEP_INTERVAL_MS = 30 * 60 * 1e3;
30384
- function orphanSweepIntervalMs() {
30385
- return Math.max(6e4, Math.min(ORPHAN_SWEEP_INTERVAL_MS, channelOrphanMarkerMs()));
30386
- }
30387
- var PANE_FRESH_DEFER_MAX_MS = 10 * 60 * 1e3;
30388
- function channelReplayEnabled() {
30389
- return resolveHostBooleanFlag({
30390
- key: "channel-replay",
30391
- envVar: "AGT_CHANNEL_REPLAY_ENABLED",
30392
- defaultValue: true
30393
- });
30394
- }
30395
- function combineMarkerReplayFacts(markers) {
30396
- if (markers.length === 0) return null;
30397
- return {
30398
- hasPayload: markers.every((m) => m.hasPayload),
30399
- discretionary: markers.some((m) => m.discretionary === true)
30400
- };
30401
- }
30402
- function markerReplayable(i) {
30403
- if (!i.enabled) return false;
30404
- if (!i.hasPayload) return false;
30405
- if (i.discretionary) return false;
30406
- return true;
30407
- }
30408
- function isMarkerGenuinelyAged(receivedAt, nowMs, thresholdMs) {
30409
- const t = Date.parse(receivedAt ?? "");
30410
- return Number.isFinite(t) && t <= nowMs && nowMs - t >= thresholdMs;
30411
- }
30412
- var DEFLECTION_COUNTER_SUFFIX = "-deflections.json";
30413
- function deflectionCounterPath(agentDir, channel) {
30414
- return join7(agentDir, `${channel}${DEFLECTION_COUNTER_SUFFIX}`);
30415
- }
30416
- function recordChannelDeflection(agentDir, channel, cause) {
30417
- if (!agentDir) return;
30418
- const path = deflectionCounterPath(agentDir, channel);
30419
- let counts = {};
30420
- try {
30421
- const parsed = JSON.parse(readFileSync7(path, "utf-8"));
30422
- if (parsed && typeof parsed === "object") counts = parsed;
30423
- } catch {
30424
- }
30425
- counts[cause] = (counts[cause] ?? 0) + 1;
30426
- try {
30427
- writeFileSync4(path, JSON.stringify(counts), { mode: 384 });
30428
- } catch {
30429
- }
30430
- }
30431
-
30432
- // src/turn-failure-watch.ts
30433
- import { readdirSync as readdirSync5, statSync as statSync2 } from "fs";
30434
- import { join as join9 } from "path";
30435
-
30436
- // ../core/dist/types/agent.js
30437
- var FRAMEWORK_MODEL_SLOTS = {
30438
- "claude-code": ["primary", "small_fast", "advisor"],
30439
- "opencode": ["primary"]
30440
- };
30441
- var KNOWN_MODEL_SLOTS = new Set(Object.values(FRAMEWORK_MODEL_SLOTS).flat());
30442
-
30443
- // ../core/dist/types/model-policy.js
30444
- var KNOWN_MODEL_POLICY_SLOTS = [...KNOWN_MODEL_SLOTS].sort();
30445
- var MODEL_POLICY_SLOTS_IN_ORDER = [...KNOWN_MODEL_SLOTS];
30446
- var MODEL_POLICY_SLOTS_PENDING_ACTUATOR = /* @__PURE__ */ new Set();
30447
- var OFFERABLE_MODEL_POLICY_SLOTS = MODEL_POLICY_SLOTS_IN_ORDER.filter((slot) => !MODEL_POLICY_SLOTS_PENDING_ACTUATOR.has(slot));
30448
-
30449
- // ../core/dist/types/integration.js
30450
- var HITL_TIER_ORDER = [
30451
- "read",
30452
- "write",
30453
- "write_high_risk",
30454
- "write_destructive",
30455
- "admin"
30456
- ];
30457
- var HITL_TIER_RANK = Object.freeze(Object.fromEntries(HITL_TIER_ORDER.map((tier, i) => [tier, i])));
30458
-
30459
- // ../core/dist/schemas/validators.js
30460
- var import__ = __toESM(require__(), 1);
30461
- var import_ajv_formats2 = __toESM(require_dist(), 1);
29880
+ // ../core/dist/schemas/validators.js
29881
+ var import__ = __toESM(require__(), 1);
29882
+ var import_ajv_formats2 = __toESM(require_dist(), 1);
30462
29883
 
30463
29884
  // ../core/dist/schemas/charter.frontmatter.v1.json
30464
29885
  var charter_frontmatter_v1_default = {
@@ -35109,6 +34530,28 @@ function formatCursorAdvanceShortfall(verdict, ctx) {
35109
34530
  return `[direct-chat] cursor advance shortfall route=/${ctx.route} site=${ctx.site} session=${ctx.sessionId} expected=${verdict.expected} consumed=${verdict.consumed} reason=${verdict.reason} partial=${verdict.partial} cleared_anyway=${cleared}`;
35110
34531
  }
35111
34532
 
34533
+ // ../core/dist/direct-chat/history-limit.js
34534
+ var DIRECT_CHAT_HISTORY_DEFAULT_LIMIT = 50;
34535
+ var DIRECT_CHAT_HISTORY_MAX_LIMIT = 200;
34536
+ function clampDirectChatHistoryLimit(raw) {
34537
+ let n;
34538
+ if (typeof raw === "number") {
34539
+ n = raw;
34540
+ } else if (typeof raw === "string" && raw.trim() !== "") {
34541
+ n = Number(raw);
34542
+ } else {
34543
+ return DIRECT_CHAT_HISTORY_DEFAULT_LIMIT;
34544
+ }
34545
+ if (!Number.isFinite(n))
34546
+ return DIRECT_CHAT_HISTORY_DEFAULT_LIMIT;
34547
+ const floored = Math.floor(n);
34548
+ if (floored < 1)
34549
+ return 1;
34550
+ if (floored > DIRECT_CHAT_HISTORY_MAX_LIMIT)
34551
+ return DIRECT_CHAT_HISTORY_MAX_LIMIT;
34552
+ return floored;
34553
+ }
34554
+
35112
34555
  // ../core/dist/onboarding/state-machine.js
35113
34556
  var AREA_ORDER = [
35114
34557
  "framing",
@@ -37929,47 +37372,658 @@ var FLAG_REGISTRY = [
37929
37372
  // the literal truth here rather than the trap it is one flag above.
37930
37373
  }
37931
37374
  ];
37932
- var REGISTRY_BY_KEY = new Map(FLAG_REGISTRY.map((definition) => [definition.key, definition]));
37375
+ var REGISTRY_BY_KEY = new Map(FLAG_REGISTRY.map((definition) => [definition.key, definition]));
37376
+
37377
+ // ../core/dist/feature-flags/schema-version.js
37378
+ function projectDefinition(definition) {
37379
+ const parts = [
37380
+ `k=${definition.key}`,
37381
+ `t=${definition.flagType}`,
37382
+ `d=${String(definition.defaultValue)}`,
37383
+ `p=${definition.public === true ? 1 : 0}`,
37384
+ `s=${definition.sensitive === true ? 1 : 0}`
37385
+ ];
37386
+ if (definition.flagType === "enum") {
37387
+ parts.push(`a=${[...definition.allowedValues].sort().join(",")}`);
37388
+ }
37389
+ return parts.join("|");
37390
+ }
37391
+ function fnv1aHex(input) {
37392
+ let hash = 2166136261;
37393
+ for (let i = 0; i < input.length; i += 1) {
37394
+ hash ^= input.charCodeAt(i);
37395
+ hash = Math.imul(hash, 16777619) >>> 0;
37396
+ }
37397
+ return hash.toString(16).padStart(8, "0");
37398
+ }
37399
+ function computeFlagsSchemaVersion() {
37400
+ const canonical = [...FLAG_REGISTRY].map(projectDefinition).sort().join("\n");
37401
+ return `v1:${fnv1aHex(canonical)}`;
37402
+ }
37403
+ var FLAGS_SCHEMA_VERSION = computeFlagsSchemaVersion();
37404
+
37405
+ // ../core/dist/restart/breaker-thresholds.js
37406
+ var RESTART_BREAKER_PROVISIONING_MAX = 5;
37407
+ var BIND_FAILURE_QUARANTINE_THRESHOLD = deriveBindFailureQuarantineThreshold(RESTART_BREAKER_PROVISIONING_MAX);
37408
+ function deriveBindFailureQuarantineThreshold(provisioningMax) {
37409
+ return Math.max(2, provisioningMax - 2);
37410
+ }
37411
+ var MIN_PROVISIONING_MAX_FOR_QUARANTINE_ORDERING = BIND_FAILURE_QUARANTINE_THRESHOLD + 1;
37412
+
37413
+ // ../core/dist/restart/forced-update-deadline.js
37414
+ var FORCED_UPDATE_RELAX_AFTER_MS = 7 * 6e4;
37415
+ var FORCED_UPDATE_HARD_DEADLINE_MS = 15 * 6e4;
37416
+
37417
+ // src/direct-chat-history.ts
37418
+ var clampHistoryLimit = clampDirectChatHistoryLimit;
37419
+ function formatDirectChatMessages(messages) {
37420
+ return messages.map((m) => {
37421
+ const who = m.role === "assistant" ? "You" : m.role === "user" ? "User" : m.role;
37422
+ return `[${m.created_at}] ${who}: ${m.content}`;
37423
+ }).join("\n");
37424
+ }
37425
+ function parseHistoryResponse(input) {
37426
+ if (!input.httpOk) {
37427
+ const message = input.body && typeof input.body === "object" && "error" in input.body ? String(input.body.error) : `http_${input.status}`;
37428
+ return { ok: false, error: message };
37429
+ }
37430
+ if (!input.body || typeof input.body !== "object" || Array.isArray(input.body)) {
37431
+ return { ok: false, error: "malformed_response" };
37432
+ }
37433
+ const raw = input.body.messages;
37434
+ if (!Array.isArray(raw)) return { ok: false, error: "malformed_response" };
37435
+ const messages = [];
37436
+ for (const row of raw) {
37437
+ if (!row || typeof row !== "object") continue;
37438
+ const r = row;
37439
+ if (typeof r.content !== "string") continue;
37440
+ messages.push({
37441
+ role: typeof r.role === "string" ? r.role : "unknown",
37442
+ content: r.content,
37443
+ created_at: typeof r.created_at === "string" ? r.created_at : ""
37444
+ });
37445
+ }
37446
+ return { ok: true, count: messages.length, formatted: formatDirectChatMessages(messages) };
37447
+ }
37448
+
37449
+ // src/direct-chat-session-state.ts
37450
+ import { existsSync, readFileSync } from "fs";
37451
+ import { join } from "path";
37452
+ function readDirectChatSessionState(agentDir, nowMs) {
37453
+ const path = join(agentDir, "direct-chat-session.json");
37454
+ try {
37455
+ if (!existsSync(path)) return { fresh: false, startedAtMs: nowMs };
37456
+ const parsed = JSON.parse(readFileSync(path, "utf8"));
37457
+ if (!parsed || typeof parsed !== "object") return { fresh: false, startedAtMs: nowMs };
37458
+ const o = parsed;
37459
+ const fresh = o.fresh === true;
37460
+ const startedAtMs = typeof o.startedAtMs === "number" && Number.isFinite(o.startedAtMs) ? o.startedAtMs : nowMs;
37461
+ return { fresh, startedAtMs };
37462
+ } catch {
37463
+ return { fresh: false, startedAtMs: nowMs };
37464
+ }
37465
+ }
37466
+
37467
+ // src/direct-chat-channel-meta.ts
37468
+ function buildDirectChatChannelMeta(input) {
37469
+ const identity = {};
37470
+ if (input.userName) identity.user_name = input.userName;
37471
+ if (input.userEmail) identity.user_email = input.userEmail;
37472
+ if (input.userRole) identity.user_role = input.userRole;
37473
+ const page = {};
37474
+ if (input.pageSlug) page.page_slug = input.pageSlug;
37475
+ if (input.pageTitle) page.page_title = input.pageTitle;
37476
+ if (input.pageUrl) page.page_url = input.pageUrl;
37477
+ if (input.pageContentUrl) page.page_content_url = input.pageContentUrl;
37478
+ if (input.pageArtefactId) page.page_artefact_id = input.pageArtefactId;
37479
+ if (input.pageGated != null) page.page_gated = input.pageGated ? "true" : "false";
37480
+ return {
37481
+ session_id: input.sessionId,
37482
+ user: "webapp",
37483
+ source: "direct-chat",
37484
+ ...identity,
37485
+ ...page,
37486
+ // String flag the agent's channel instructions (and the renderer) read:
37487
+ // 'false' = notice (do not reply), 'true' = normal message (reply expected).
37488
+ requires_reply: input.isNotice ? "false" : "true",
37489
+ // ENG-6407 / ADR-0024 Slice 1: provenance + reply-obligation as first-class
37490
+ // tag attributes. Direct chat is conversational; a notice owes no reply, so
37491
+ // expects_reply mirrors the existing requires_reply flag. Recorded only - no
37492
+ // consumer gates on these yet (Slice 2).
37493
+ lane: "conversational",
37494
+ expects_reply: input.isNotice ? "false" : "true"
37495
+ };
37496
+ }
37497
+
37498
+ // src/turn-initiator-marker.ts
37499
+ import { writeFileSync, readFileSync as readFileSync2, mkdirSync, renameSync } from "fs";
37500
+ import { dirname, join as join2 } from "path";
37501
+ var TURN_INITIATOR_MAX_AGE_MS = 5 * 60 * 1e3;
37502
+ var TURN_INITIATOR_LEDGER_MAX_ENTRIES = 20;
37503
+ var TURN_INITIATOR_LEDGER_FILENAME = ".turn-initiator-ledger.json";
37504
+ function turnInitiatorLedgerPath(singleSlotFile) {
37505
+ return join2(dirname(singleSlotFile), TURN_INITIATOR_LEDGER_FILENAME);
37506
+ }
37507
+ function foldTurnInitiatorLedger(existing, marker, maxAgeMs = TURN_INITIATOR_MAX_AGE_MS, maxEntries = TURN_INITIATOR_LEDGER_MAX_ENTRIES) {
37508
+ const now = marker.ts;
37509
+ const key = (e) => `${e.channel}\0${e.channel_ref ?? ""}`;
37510
+ const markerKey = key(marker);
37511
+ const prior = existing && Array.isArray(existing.entries) ? existing.entries : [];
37512
+ const kept = prior.filter(
37513
+ (e) => e && typeof e.ts === "number" && Number.isFinite(e.ts) && now - e.ts <= maxAgeMs && now - e.ts >= 0 && key(e) !== markerKey
37514
+ // the same thread is replaced by the new entry below
37515
+ );
37516
+ kept.push({
37517
+ channel: marker.channel,
37518
+ sender_id: marker.sender_id,
37519
+ ...marker.sender_name ? { sender_name: marker.sender_name } : {},
37520
+ ...marker.channel_ref ? { channel_ref: marker.channel_ref } : {},
37521
+ ts: marker.ts
37522
+ });
37523
+ kept.sort((a, b) => b.ts - a.ts);
37524
+ return { v: 1, entries: kept.slice(0, maxEntries) };
37525
+ }
37526
+ function updateTurnInitiatorLedger(singleSlotFile, marker) {
37527
+ const ledgerFile = turnInitiatorLedgerPath(singleSlotFile);
37528
+ let existing = null;
37529
+ try {
37530
+ const parsed = JSON.parse(readFileSync2(ledgerFile, "utf8"));
37531
+ if (parsed && parsed.v === 1 && Array.isArray(parsed.entries)) existing = parsed;
37532
+ } catch {
37533
+ }
37534
+ const next = foldTurnInitiatorLedger(existing, marker);
37535
+ const tmp = `${ledgerFile}.tmp`;
37536
+ writeFileSync(tmp, JSON.stringify(next), "utf8");
37537
+ renameSync(tmp, ledgerFile);
37538
+ }
37539
+ function writeTurnInitiatorMarker(input) {
37540
+ const file = process.env["AGT_TURN_INITIATOR_FILE"];
37541
+ if (!file || !input.sender_id) return;
37542
+ try {
37543
+ mkdirSync(dirname(file), { recursive: true });
37544
+ const marker = { ...input, ts: Date.now() };
37545
+ const tmp = `${file}.tmp`;
37546
+ writeFileSync(tmp, JSON.stringify(marker), "utf8");
37547
+ renameSync(tmp, file);
37548
+ try {
37549
+ updateTurnInitiatorLedger(file, marker);
37550
+ } catch {
37551
+ }
37552
+ } catch {
37553
+ }
37554
+ }
37555
+
37556
+ // src/scheduled-turn-marker.ts
37557
+ import { readFileSync as readFileSync3, unlinkSync } from "fs";
37558
+ import { join as join3 } from "path";
37559
+ var SCHEDULED_TURN_MARKER_FILENAME2 = ".current-scheduled-turn.json";
37560
+ var SCHEDULED_TURN_MAX_AGE_MS = 15 * 60 * 1e3;
37561
+ function clearScheduledTurnMarker(agentDir) {
37562
+ if (!agentDir) return;
37563
+ try {
37564
+ unlinkSync(join3(agentDir, SCHEDULED_TURN_MARKER_FILENAME2));
37565
+ } catch {
37566
+ }
37567
+ }
37568
+
37569
+ // src/direct-chat-pending-inbound.ts
37570
+ import {
37571
+ existsSync as existsSync2,
37572
+ mkdirSync as mkdirSync2,
37573
+ readdirSync,
37574
+ readFileSync as readFileSync4,
37575
+ renameSync as renameSync2,
37576
+ unlinkSync as unlinkSync2,
37577
+ writeFileSync as writeFileSync2
37578
+ } from "fs";
37579
+ import { join as join4 } from "path";
37580
+ function hexEncodeSegment(value) {
37581
+ return Buffer.from(value, "utf8").toString("hex");
37582
+ }
37583
+ function directChatMarkerName(sessionId, messageId) {
37584
+ return `${hexEncodeSegment(sessionId)}__${hexEncodeSegment(messageId)}.json`;
37585
+ }
37586
+ var defaultClearMarkerFile = (fullPath) => {
37587
+ try {
37588
+ if (existsSync2(fullPath)) unlinkSync2(fullPath);
37589
+ } catch {
37590
+ }
37591
+ };
37592
+ function directChatInboundId(sessionId, messageId) {
37593
+ return `direct-chat|${sessionId.length}|${sessionId}|${messageId}`;
37594
+ }
37595
+ function writeDirectChatPendingInboundMarker(dir, sessionId, messageId) {
37596
+ if (!dir || !sessionId || !messageId) return;
37597
+ try {
37598
+ mkdirSync2(dir, { recursive: true });
37599
+ const marker = {
37600
+ channel: "direct-chat",
37601
+ session_id: sessionId,
37602
+ message_id: messageId,
37603
+ received_at: (/* @__PURE__ */ new Date()).toISOString(),
37604
+ inbound_id: directChatInboundId(sessionId, messageId)
37605
+ };
37606
+ const final = join4(dir, directChatMarkerName(sessionId, messageId));
37607
+ const tmp = `${final}.tmp`;
37608
+ writeFileSync2(tmp, JSON.stringify(marker), "utf8");
37609
+ renameSync2(tmp, final);
37610
+ } catch {
37611
+ }
37612
+ }
37613
+ function clearDirectChatPendingMarkersForSession(dir, sessionId, op = defaultClearMarkerFile) {
37614
+ if (!dir || !sessionId) return 0;
37615
+ const prefix = `${hexEncodeSegment(sessionId)}__`;
37616
+ let cleared = 0;
37617
+ let names;
37618
+ try {
37619
+ names = readdirSync(dir);
37620
+ } catch {
37621
+ return 0;
37622
+ }
37623
+ for (const name of names) {
37624
+ if (!name.startsWith(prefix) || !name.endsWith(".json")) continue;
37625
+ op(join4(dir, name));
37626
+ cleared += 1;
37627
+ }
37628
+ return cleared;
37629
+ }
37630
+ function readDirectChatPendingInboundMarker(dir, sessionId, messageId, readFileImpl = (path) => readFileSync4(path, "utf8")) {
37631
+ if (!dir || !sessionId || !messageId) return null;
37632
+ try {
37633
+ const raw = readFileImpl(join4(dir, directChatMarkerName(sessionId, messageId)));
37634
+ return JSON.parse(raw);
37635
+ } catch {
37636
+ return null;
37637
+ }
37638
+ }
37639
+ var MAX_REPORTED_ORPHANS = 20;
37640
+ function sweepAgedDirectChatMarkers(dir, deps) {
37641
+ const result = {
37642
+ cleared: 0,
37643
+ orphaned: 0,
37644
+ orphanedIdentities: [],
37645
+ orphanedTruncated: false
37646
+ };
37647
+ if (!dir) return result;
37648
+ let names;
37649
+ try {
37650
+ names = deps.readdir(dir);
37651
+ } catch {
37652
+ return result;
37653
+ }
37654
+ for (const name of names) {
37655
+ if (!name.endsWith(".json") || name.startsWith(".")) continue;
37656
+ const full = join4(dir, name);
37657
+ let marker;
37658
+ try {
37659
+ marker = JSON.parse(deps.readFile(full));
37660
+ } catch {
37661
+ continue;
37662
+ }
37663
+ if (!deps.isAged(marker.received_at)) continue;
37664
+ const undelivered = !deps.hasDelivery(marker.inbound_id);
37665
+ try {
37666
+ deps.unlink(full);
37667
+ } catch {
37668
+ continue;
37669
+ }
37670
+ result.cleared += 1;
37671
+ if (undelivered) {
37672
+ const identity = {
37673
+ inboundId: marker.inbound_id,
37674
+ receivedAt: marker.received_at,
37675
+ sessionId: marker.session_id
37676
+ };
37677
+ try {
37678
+ deps.onOrphaned(identity);
37679
+ } catch {
37680
+ }
37681
+ result.orphaned += 1;
37682
+ if (result.orphanedIdentities.length < MAX_REPORTED_ORPHANS) {
37683
+ result.orphanedIdentities.push(identity);
37684
+ } else {
37685
+ result.orphanedTruncated = true;
37686
+ }
37687
+ }
37688
+ }
37689
+ return result;
37690
+ }
37691
+
37692
+ // src/direct-chat-recovery-outbox.ts
37693
+ async function consumeDirectChatRecoveryFile(fullPath, filename, deps) {
37694
+ if (filename.endsWith(".tmp") || filename.endsWith(".poison")) return "skipped";
37695
+ let payload;
37696
+ try {
37697
+ payload = JSON.parse(deps.readFile(fullPath));
37698
+ } catch (err) {
37699
+ deps.log(`recovery outbox parse failed (${filename}): ${err.message}`);
37700
+ try {
37701
+ deps.renameFile(fullPath, `${fullPath}.parse-error.poison`);
37702
+ } catch {
37703
+ }
37704
+ return "poison";
37705
+ }
37706
+ if (!payload || !payload.session_id || !payload.text) {
37707
+ deps.log(`recovery outbox malformed (${filename}): missing session_id or text`);
37708
+ if (payload && payload.marker_name) {
37709
+ try {
37710
+ deps.removeLedgerEntry(payload.marker_name);
37711
+ } catch {
37712
+ }
37713
+ }
37714
+ try {
37715
+ deps.renameFile(fullPath, `${fullPath}.malformed.poison`);
37716
+ } catch {
37717
+ }
37718
+ return "poison";
37719
+ }
37720
+ const content = deps.sanitize(payload.text);
37721
+ const messageIds = deps.peekClaims(payload.session_id);
37722
+ let result;
37723
+ try {
37724
+ result = await deps.deliver({
37725
+ sessionId: payload.session_id,
37726
+ content,
37727
+ messageIds
37728
+ });
37729
+ } catch (err) {
37730
+ result = { ok: false, error: err.message };
37731
+ }
37732
+ if (result.ok && !result.error) {
37733
+ deps.clearClaims(payload.session_id, messageIds);
37734
+ deps.clearMarkers(payload.session_id);
37735
+ if (payload.marker_name) deps.removeLedgerEntry(payload.marker_name);
37736
+ try {
37737
+ deps.unlinkFile(fullPath);
37738
+ } catch {
37739
+ }
37740
+ deps.log(`ghost-reply recovery sent (session=${payload.session_id})`);
37741
+ return "delivered";
37742
+ }
37743
+ if (payload.marker_name) deps.removeLedgerEntry(payload.marker_name);
37744
+ try {
37745
+ deps.unlinkFile(fullPath);
37746
+ } catch {
37747
+ }
37748
+ deps.log(
37749
+ `ghost-reply recovery send failed (session=${payload.session_id}): ${result.error ?? "unknown"} - re-armed for retry`
37750
+ );
37751
+ return "failed";
37752
+ }
37753
+
37754
+ // src/inbound-delivery-ledger.ts
37755
+ import { existsSync as existsSync3, mkdirSync as mkdirSync3, readdirSync as readdirSync2, readFileSync as readFileSync5, renameSync as renameSync3, writeFileSync as writeFileSync3 } from "fs";
37756
+ import { join as join5 } from "path";
37757
+ function safeInboundId(inboundId) {
37758
+ return inboundId.replace(/[^A-Za-z0-9_-]/g, "_");
37759
+ }
37760
+ var defaultDeps = {
37761
+ mkdir: (dir) => mkdirSync3(dir, { recursive: true }),
37762
+ writeFile: (path, data) => writeFileSync3(path, data, "utf8"),
37763
+ rename: (from, to) => renameSync3(from, to),
37764
+ readdir: (dir) => readdirSync2(dir),
37765
+ readFile: (path) => readFileSync5(path, "utf8"),
37766
+ exists: (path) => existsSync3(path)
37767
+ };
37768
+ function writeInboundDeliveryLedgerEntry(dir, record2, deps = defaultDeps) {
37769
+ if (!dir || !record2.inbound_id || !record2.conv_key) return;
37770
+ const safe = safeInboundId(record2.inbound_id);
37771
+ if (!safe || safe.includes("/") || safe.includes("\\") || safe.includes("..")) return;
37772
+ try {
37773
+ deps.mkdir(dir);
37774
+ const final = join5(dir, `${safe}.json`);
37775
+ const tmp = `${final}.tmp`;
37776
+ deps.writeFile(tmp, JSON.stringify(record2));
37777
+ deps.rename(tmp, final);
37778
+ } catch {
37779
+ }
37780
+ }
37781
+ function inboundDeliveryLedgerHas(dir, inboundId, deps = defaultDeps) {
37782
+ if (!dir || !inboundId) return false;
37783
+ const safe = safeInboundId(inboundId);
37784
+ if (!safe || safe.includes("/") || safe.includes("\\") || safe.includes("..")) return false;
37785
+ try {
37786
+ return deps.exists(join5(dir, `${safe}.json`));
37787
+ } catch {
37788
+ return false;
37789
+ }
37790
+ }
37791
+
37792
+ // src/ack-reaction.ts
37793
+ import { readdirSync as readdirSync3, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "fs";
37794
+ import { join as join7 } from "path";
37795
+
37796
+ // src/flags-cache-read.ts
37797
+ import { existsSync as existsSync4, readFileSync as readFileSync6 } from "fs";
37798
+ import { homedir } from "os";
37799
+ import { join as join6 } from "path";
37800
+ function defaultFlagsCachePath() {
37801
+ return join6(homedir(), ".augmented", "flags-cache.json");
37802
+ }
37803
+ function envBoolean(raw) {
37804
+ if (raw === void 0) return void 0;
37805
+ const v = raw.trim().toLowerCase();
37806
+ if (v === "") return void 0;
37807
+ if (v === "1" || v === "true" || v === "yes" || v === "on") return true;
37808
+ if (v === "0" || v === "false" || v === "no" || v === "off") return false;
37809
+ return void 0;
37810
+ }
37811
+ function cachedBoolean(key, path) {
37812
+ try {
37813
+ if (!existsSync4(path)) return void 0;
37814
+ const parsed = JSON.parse(readFileSync6(path, "utf8"));
37815
+ if (!parsed || typeof parsed !== "object") return void 0;
37816
+ const flags = parsed.flags;
37817
+ if (!flags || typeof flags !== "object") return void 0;
37818
+ const value = flags[key];
37819
+ return typeof value === "boolean" ? value : void 0;
37820
+ } catch {
37821
+ return void 0;
37822
+ }
37823
+ }
37824
+ function resolveHostBooleanFlag(opts) {
37825
+ const env2 = opts.env ?? process.env;
37826
+ const envValue = envBoolean(env2[opts.envVar]);
37827
+ if (envValue !== void 0) return envValue;
37828
+ const cached2 = cachedBoolean(opts.key, opts.cachePath ?? defaultFlagsCachePath());
37829
+ if (cached2 !== void 0) return cached2;
37830
+ return opts.defaultValue;
37831
+ }
37832
+
37833
+ // ../core/dist/channels/deflection-causes.js
37834
+ var DEFLECTION_CAUSES = [
37835
+ "integration_down",
37836
+ "session_dead",
37837
+ "wedged",
37838
+ "busy",
37839
+ "duplicate",
37840
+ // ENG-8387: an `app_mention` — the CANONICAL delivery of an @mention — dropped
37841
+ // by the fresh-ingress dedup. Split out of the generic `duplicate` bucket
37842
+ // because the two mean very different things: `duplicate` is dominated by the
37843
+ // deliberate, high-volume ENG-6378 echo drop and by routine reconnect
37844
+ // redelivery, both benign, while a dropped app_mention has the exact shape of
37845
+ // a lost mention. Conflated, the second is invisible inside the first — which
37846
+ // is how ENG-6378's silent loss ran unobserved.
37847
+ "duplicate_mention",
37848
+ "mention_echo_orphan",
37849
+ "replay_orphaned",
37850
+ "replay_exhausted",
37851
+ // ENG-9414 — see the type union above for why these two exist. Both are
37852
+ // message-loss classes the pre-existing causes were structurally unable to
37853
+ // observe, which is why four destroyed customer inbounds in one hour
37854
+ // produced zero alerts.
37855
+ "aged_out_unrecoverable",
37856
+ "marker_corrupt",
37857
+ "unknown"
37858
+ ];
37859
+ var DEFLECTION_CAUSE_SET = new Set(DEFLECTION_CAUSES);
37933
37860
 
37934
- // ../core/dist/feature-flags/schema-version.js
37935
- function projectDefinition(definition) {
37936
- const parts = [
37937
- `k=${definition.key}`,
37938
- `t=${definition.flagType}`,
37939
- `d=${String(definition.defaultValue)}`,
37940
- `p=${definition.public === true ? 1 : 0}`,
37941
- `s=${definition.sensitive === true ? 1 : 0}`
37942
- ];
37943
- if (definition.flagType === "enum") {
37944
- parts.push(`a=${[...definition.allowedValues].sort().join(",")}`);
37861
+ // src/ack-reaction.ts
37862
+ var REPLY_WEDGED_THRESHOLD_MS = 5 * 60 * 1e3;
37863
+ var ACK_PANE_FRESH_THRESHOLD_MS = 6e4;
37864
+ function classifyUndeliverableCause(i) {
37865
+ if (!i.hasTarget) return null;
37866
+ if (!i.integrationReady) return "integration_down";
37867
+ if (i.tmux === "dead") return "session_dead";
37868
+ if (!i.withinStartupGrace && i.claude === "dead") return "session_dead";
37869
+ const threshold = i.pendingStaleThresholdMs ?? REPLY_WEDGED_THRESHOLD_MS;
37870
+ if (i.oldestPendingAgeMs != null && i.oldestPendingAgeMs > threshold) {
37871
+ const paneFreshThreshold = i.paneFreshThresholdMs ?? ACK_PANE_FRESH_THRESHOLD_MS;
37872
+ const paneIsFresh = i.paneLogFreshAgeMs != null && i.paneLogFreshAgeMs <= paneFreshThreshold;
37873
+ if (paneIsFresh && i.tmux === "alive" && i.claude === "alive") {
37874
+ return null;
37875
+ }
37876
+ return "wedged";
37945
37877
  }
37946
- return parts.join("|");
37878
+ return null;
37947
37879
  }
37948
- function fnv1aHex(input) {
37949
- let hash = 2166136261;
37950
- for (let i = 0; i < input.length; i += 1) {
37951
- hash ^= input.charCodeAt(i);
37952
- hash = Math.imul(hash, 16777619) >>> 0;
37880
+ var UNDELIVERABLE_NOTICE_THROTTLE_MS = 5 * 60 * 1e3;
37881
+ function shouldPostUndeliverableNotice(lastNoticeAtMs, nowMs, throttleMs = UNDELIVERABLE_NOTICE_THROTTLE_MS) {
37882
+ return lastNoticeAtMs == null || nowMs - lastNoticeAtMs >= throttleMs;
37883
+ }
37884
+ function undeliverableNoticeText(replay) {
37885
+ const honest = noticePromiseIsHonest(replay);
37886
+ return honest ? "\u23F3 I can't get to this right now \u2014 no need to resend; I'll pick it up automatically as soon as I'm free." : "\u23F3 I can't get to this right now. Please resend in a few minutes \u2014 I may not see this one.";
37887
+ }
37888
+ function undeliverableNoticeTextForRepeat(i) {
37889
+ if (i.priorNotices <= 0) return undeliverableNoticeText(i.replay);
37890
+ if (i.priorNotices === 1) {
37891
+ return noticePromiseIsHonest(i.replay) ? "\u23F3 Still stuck on this one \u2014 the automatic pickup I mentioned has not happened. Please resend when you can." : "\u23F3 Still stuck on this one, and I still have not picked it up. Resending is the reliable way through.";
37953
37892
  }
37954
- return hash.toString(16).padStart(8, "0");
37893
+ return null;
37955
37894
  }
37956
- function computeFlagsSchemaVersion() {
37957
- const canonical = [...FLAG_REGISTRY].map(projectDefinition).sort().join("\n");
37958
- return `v1:${fnv1aHex(canonical)}`;
37895
+ function noticePromiseIsHonest(replay) {
37896
+ if (replay === void 0) return false;
37897
+ if (typeof replay === "boolean") return replay;
37898
+ return markerReplayable({
37899
+ enabled: replay.enabled ?? channelReplayEnabled(),
37900
+ hasPayload: replay.hasPayload,
37901
+ discretionary: replay.discretionary
37902
+ });
37959
37903
  }
37960
- var FLAGS_SCHEMA_VERSION = computeFlagsSchemaVersion();
37961
-
37962
- // ../core/dist/restart/breaker-thresholds.js
37963
- var RESTART_BREAKER_PROVISIONING_MAX = 5;
37964
- var BIND_FAILURE_QUARANTINE_THRESHOLD = deriveBindFailureQuarantineThreshold(RESTART_BREAKER_PROVISIONING_MAX);
37965
- function deriveBindFailureQuarantineThreshold(provisioningMax) {
37966
- return Math.max(2, provisioningMax - 2);
37904
+ var BUSY_ACK_THRESHOLD_MS = 12e4;
37905
+ var BUSY_ACK_NOTICE_THROTTLE_MS = 20 * 60 * 1e3;
37906
+ function channelBusyAckEnabled() {
37907
+ return resolveHostBooleanFlag({
37908
+ key: "channel-busy-ack",
37909
+ envVar: "AGT_CHANNEL_BUSY_ACK_ENABLED",
37910
+ defaultValue: false
37911
+ });
37912
+ }
37913
+ function channelBusyAckThresholdMs() {
37914
+ const raw = parseInt(process.env.AGT_CHANNEL_BUSY_ACK_THRESHOLD_MS ?? "", 10);
37915
+ return Number.isFinite(raw) && raw > 0 ? raw : BUSY_ACK_THRESHOLD_MS;
37916
+ }
37917
+ function decideBusyAck(i) {
37918
+ if (!i.hasTarget) return false;
37919
+ if (!i.arrivedWhileBusy) return false;
37920
+ if (!i.stillPending) return false;
37921
+ if (!Number.isFinite(i.pendingAgeMs)) return false;
37922
+ const threshold = i.thresholdMs ?? channelBusyAckThresholdMs();
37923
+ if (i.pendingAgeMs < threshold) return false;
37924
+ if (!i.sessionAlive) return false;
37925
+ const paneFreshThreshold = i.paneFreshThresholdMs ?? ACK_PANE_FRESH_THRESHOLD_MS;
37926
+ return i.paneLogFreshAgeMs != null && i.paneLogFreshAgeMs <= paneFreshThreshold;
37927
+ }
37928
+ function busyAckNoticeText() {
37929
+ return "\u{1F6E0}\uFE0F I'm in the middle of something right now \u2014 I'll follow up on this as soon as I'm free.";
37930
+ }
37931
+ var GIVE_UP_SIGNAL_MAX_AGE_MS = 30 * 60 * 1e3;
37932
+ function turnFailedNoticeText(failureClass) {
37933
+ const cause = failureClass === "overloaded" ? "the service I run on is overloaded right now \u2014 nothing to do with you or what you asked for" : failureClass === "server_error" ? "something upstream of me is failing right now \u2014 nothing to do with you or what you asked for" : null;
37934
+ if (cause === null) {
37935
+ return "\u26A0\uFE0F Something went wrong on my side and I couldn\u2019t finish that. Sorry \u2014 send it again when you get a chance and I\u2019ll pick it straight up.";
37936
+ }
37937
+ return `\u26A0\uFE0F I couldn\u2019t finish that \u2014 ${cause}. Sorry \u2014 send it again when you get a chance and I\u2019ll pick it straight up.`;
37938
+ }
37939
+ function oldestPendingMarkerAgeMs(dir, now = Date.now(), opts) {
37940
+ if (!dir) return null;
37941
+ let names;
37942
+ try {
37943
+ names = readdirSync3(dir);
37944
+ } catch {
37945
+ return null;
37946
+ }
37947
+ let oldest = null;
37948
+ for (const name of names) {
37949
+ if (!name.endsWith(".json")) continue;
37950
+ let receivedAt;
37951
+ try {
37952
+ const raw = JSON.parse(readFileSync7(join7(dir, name), "utf-8"));
37953
+ if (raw.discretionary === true) continue;
37954
+ if (!opts?.includeSeen && typeof raw.seen_at === "string" && raw.seen_at) continue;
37955
+ receivedAt = raw.received_at;
37956
+ } catch {
37957
+ continue;
37958
+ }
37959
+ if (!receivedAt) continue;
37960
+ const t = Date.parse(receivedAt);
37961
+ if (Number.isNaN(t)) continue;
37962
+ const age = now - t;
37963
+ if (age < 0) continue;
37964
+ if (oldest == null || age > oldest) oldest = age;
37965
+ }
37966
+ return oldest;
37967
+ }
37968
+ function hasInFlightInbound(dir, now = Date.now()) {
37969
+ return oldestPendingMarkerAgeMs(dir, now, { includeSeen: true }) != null;
37970
+ }
37971
+ function channelOrphanMarkerMs() {
37972
+ const raw = parseInt(process.env.AGT_CHANNEL_ORPHAN_MARKER_MS ?? "", 10);
37973
+ return Number.isFinite(raw) && raw > 0 ? Math.max(raw, 12e4) : 30 * 6e4;
37974
+ }
37975
+ var ORPHAN_SWEEP_INTERVAL_MS = 30 * 60 * 1e3;
37976
+ function orphanSweepIntervalMs() {
37977
+ return Math.max(6e4, Math.min(ORPHAN_SWEEP_INTERVAL_MS, channelOrphanMarkerMs()));
37978
+ }
37979
+ var PANE_FRESH_DEFER_MAX_MS = 10 * 60 * 1e3;
37980
+ function channelReplayEnabled() {
37981
+ return resolveHostBooleanFlag({
37982
+ key: "channel-replay",
37983
+ envVar: "AGT_CHANNEL_REPLAY_ENABLED",
37984
+ defaultValue: true
37985
+ });
37986
+ }
37987
+ function combineMarkerReplayFacts(markers) {
37988
+ if (markers.length === 0) return null;
37989
+ return {
37990
+ hasPayload: markers.every((m) => m.hasPayload),
37991
+ discretionary: markers.some((m) => m.discretionary === true)
37992
+ };
37993
+ }
37994
+ function markerReplayable(i) {
37995
+ if (!i.enabled) return false;
37996
+ if (!i.hasPayload) return false;
37997
+ if (i.discretionary) return false;
37998
+ return true;
37999
+ }
38000
+ function isMarkerGenuinelyAged(receivedAt, nowMs, thresholdMs) {
38001
+ const t = Date.parse(receivedAt ?? "");
38002
+ return Number.isFinite(t) && t <= nowMs && nowMs - t >= thresholdMs;
38003
+ }
38004
+ var DEFLECTION_COUNTER_SUFFIX = "-deflections.json";
38005
+ function deflectionCounterPath(agentDir, channel) {
38006
+ return join7(agentDir, `${channel}${DEFLECTION_COUNTER_SUFFIX}`);
38007
+ }
38008
+ function recordChannelDeflection(agentDir, channel, cause) {
38009
+ if (!agentDir) return;
38010
+ const path = deflectionCounterPath(agentDir, channel);
38011
+ let counts = {};
38012
+ try {
38013
+ const parsed = JSON.parse(readFileSync7(path, "utf-8"));
38014
+ if (parsed && typeof parsed === "object") counts = parsed;
38015
+ } catch {
38016
+ }
38017
+ counts[cause] = (counts[cause] ?? 0) + 1;
38018
+ try {
38019
+ writeFileSync4(path, JSON.stringify(counts), { mode: 384 });
38020
+ } catch {
38021
+ }
37967
38022
  }
37968
- var MIN_PROVISIONING_MAX_FOR_QUARANTINE_ORDERING = BIND_FAILURE_QUARANTINE_THRESHOLD + 1;
37969
38023
 
37970
- // ../core/dist/restart/forced-update-deadline.js
37971
- var FORCED_UPDATE_RELAX_AFTER_MS = 7 * 6e4;
37972
- var FORCED_UPDATE_HARD_DEADLINE_MS = 15 * 6e4;
38024
+ // src/turn-failure-watch.ts
38025
+ import { readdirSync as readdirSync5, statSync as statSync2 } from "fs";
38026
+ import { join as join9 } from "path";
37973
38027
 
37974
38028
  // src/rate-limit-watch.ts
37975
38029
  import { closeSync, fstatSync, openSync, readSync, readdirSync as readdirSync4, statSync } from "fs";
@@ -39540,6 +39594,31 @@ mcp.setRequestHandler(ListToolsRequestSchema, async () => ({
39540
39594
  },
39541
39595
  required: ["session_id"]
39542
39596
  }
39597
+ },
39598
+ {
39599
+ // ENG-9797. The direct-chat half of ENG-9693: agents were told to re-read
39600
+ // a Slack thread when they lose the trail, and told that direct chat had
39601
+ // no way to do it. It does now.
39602
+ //
39603
+ // READ-ONLY, so it is deliberately absent from DIRECT_CHAT_EGRESS_TOOLS —
39604
+ // an impersonating operator may read a conversation back; what they may
39605
+ // not do is speak in it.
39606
+ name: "direct_chat.read_history",
39607
+ description: 'Re-read this direct-chat conversation from the beginning. Use it when the trail matters and you cannot see it: the user says "as I mentioned" or "like we discussed", a decision was made earlier and you are about to re-ask it, or a message arrives replayed="true" and you cannot recall the original. Making someone repeat themselves reads as not listening. Returns the stored turns oldest-to-newest for the session_id in the <channel> tag; it can only read conversations that belong to you.',
39608
+ inputSchema: {
39609
+ type: "object",
39610
+ properties: {
39611
+ session_id: {
39612
+ type: "string",
39613
+ description: "Session ID (from the session_id attribute in the <channel> tag)"
39614
+ },
39615
+ limit: {
39616
+ type: "number",
39617
+ description: `How many of the most recent messages to return (default ${DIRECT_CHAT_HISTORY_DEFAULT_LIMIT}, max ${DIRECT_CHAT_HISTORY_MAX_LIMIT}). The transcript still reads oldest-to-newest.`
39618
+ }
39619
+ },
39620
+ required: ["session_id"]
39621
+ }
39543
39622
  }
39544
39623
  ]
39545
39624
  }));
@@ -39735,6 +39814,44 @@ mcp.setRequestHandler(CallToolRequestSchema, async (req) => {
39735
39814
  };
39736
39815
  }
39737
39816
  }
39817
+ if (name === "direct_chat.read_history") {
39818
+ const { session_id, limit: rawLimit } = args;
39819
+ const limit = clampHistoryLimit(rawLimit);
39820
+ try {
39821
+ const res = await apiPost("/host/direct-chat/history", {
39822
+ agent_id: AGT_AGENT_ID,
39823
+ session_id,
39824
+ limit
39825
+ });
39826
+ let body = null;
39827
+ try {
39828
+ body = await res.json();
39829
+ } catch {
39830
+ }
39831
+ const result = parseHistoryResponse({ httpOk: res.ok, status: res.status, body });
39832
+ if (!result.ok) {
39833
+ return {
39834
+ content: [{ type: "text", text: `Could not read the conversation: ${result.error}` }],
39835
+ isError: true
39836
+ };
39837
+ }
39838
+ return {
39839
+ content: [
39840
+ {
39841
+ type: "text",
39842
+ text: result.count > 0 ? `Direct chat history (${result.count} messages, oldest\u2192newest):
39843
+
39844
+ ${result.formatted}` : "No stored messages for this conversation yet."
39845
+ }
39846
+ ]
39847
+ };
39848
+ } catch (err) {
39849
+ return {
39850
+ content: [{ type: "text", text: `Failed: ${err.message}` }],
39851
+ isError: true
39852
+ };
39853
+ }
39854
+ }
39738
39855
  throw new Error(`Unknown tool: ${name}`);
39739
39856
  });
39740
39857
  await mcp.connect(new StdioServerTransport());