switchroom 0.21.10 → 0.21.12
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/switchroom.js +143 -27
- package/dist/host-control/main.js +1 -1
- package/package.json +5 -2
- package/skills/dev-protocol/SKILL.md +14 -10
- package/skills/switchroom-release/SKILL.md +11 -5
- package/telegram-plugin/dist/gateway/gateway.js +385 -35
- package/telegram-plugin/format.ts +56 -0
- package/telegram-plugin/gateway/answer-route-overrides.ts +163 -0
- package/telegram-plugin/gateway/answer-thread-resolve.test.ts +175 -1
- package/telegram-plugin/gateway/answer-thread-resolve.ts +58 -6
- package/telegram-plugin/gateway/escalation-staleness.ts +526 -0
- package/telegram-plugin/gateway/gateway.ts +61 -68
- package/telegram-plugin/gateway/obligation-wiring.ts +91 -3
- package/telegram-plugin/gateway/outbound-send-path.ts +55 -1
- package/telegram-plugin/gateway/reply-route-log.test.ts +134 -0
- package/telegram-plugin/gateway/reply-route-log.ts +118 -0
- package/telegram-plugin/gateway/stream-render.ts +1 -1
- package/telegram-plugin/history.ts +21 -0
- package/telegram-plugin/registry/subagents-bugs.test.ts +3 -3
- package/telegram-plugin/render/html-fold.ts +22 -4
- package/telegram-plugin/render/parse.ts +9 -1
- package/telegram-plugin/render/render.ts +14 -13
- package/telegram-plugin/tests/answer-route-side-effect.test.ts +111 -0
- package/telegram-plugin/tests/catch-all-forwarded-history.test.ts +3 -3
- package/telegram-plugin/tests/escalation-staleness.test.ts +1275 -0
- package/telegram-plugin/tests/forwarded-rich-message-coalesce.test.ts +6 -6
- package/telegram-plugin/tests/forwarded-rich-message.test.ts +8 -8
- package/telegram-plugin/tests/history.test.ts +78 -0
- package/telegram-plugin/tests/multitopic-routing-wiring.test.ts +29 -1
- package/telegram-plugin/tests/orphaned-db-sweep.test.ts +17 -1
- package/telegram-plugin/tests/render/html-dialect-content-loss.test.ts +76 -3
- package/telegram-plugin/tests/send-reply-golden.test.ts +192 -3
- package/telegram-plugin/tests/status-pin.test.ts +2 -2
- package/telegram-plugin/tests/subagent-handback-inbound-builder.test.ts +2 -2
- package/telegram-plugin/tests/subagent-progress-inbound-builder.test.ts +2 -2
- package/telegram-plugin/tests/telegram-format.test.ts +52 -0
- package/telegram-plugin/tests/turn-supersede-finalizes-prior-card.test.ts +1 -1
- package/telegram-plugin/tests/worker-origin-gap-dispatch.test.ts +1 -1
- package/telegram-plugin/uat/scenarios/jtbd-supergroup-reply-channel.test.ts +1 -1
- package/telegram-plugin/worker-activity-feed.ts +2 -2
package/dist/cli/switchroom.js
CHANGED
|
@@ -2120,7 +2120,7 @@ var init_esm = __esm(() => {
|
|
|
2120
2120
|
});
|
|
2121
2121
|
|
|
2122
2122
|
// src/build-info.ts
|
|
2123
|
-
var VERSION = "0.21.
|
|
2123
|
+
var VERSION = "0.21.12", COMMIT_SHA = "66041822", COMMIT_DATE = "2026-08-14T20:57:00Z";
|
|
2124
2124
|
|
|
2125
2125
|
// src/cli/resolve-version.ts
|
|
2126
2126
|
import { existsSync, readFileSync } from "node:fs";
|
|
@@ -84729,6 +84729,45 @@ var init_server4 = __esm(() => {
|
|
|
84729
84729
|
var ROUTE_FIELD_SHIP_TS = 1785456000;
|
|
84730
84730
|
|
|
84731
84731
|
// src/fleet-health/detect.ts
|
|
84732
|
+
function emptyGwHits() {
|
|
84733
|
+
const out = {};
|
|
84734
|
+
for (const name of GATEWAY_SIGNAL_NAMES)
|
|
84735
|
+
out[name] = 0;
|
|
84736
|
+
return out;
|
|
84737
|
+
}
|
|
84738
|
+
function orphanedDbAlarmLanes(alarmLine) {
|
|
84739
|
+
const names = [];
|
|
84740
|
+
for (const m of alarmLine.matchAll(ORPHANED_DB_ALARM_TARGET_RE)) {
|
|
84741
|
+
const target = m[1];
|
|
84742
|
+
names.push(target.slice(target.lastIndexOf("/") + 1));
|
|
84743
|
+
}
|
|
84744
|
+
if (names.length === 0)
|
|
84745
|
+
return null;
|
|
84746
|
+
const declared = ORPHANED_DB_ALARM_COUNT_RE.exec(alarmLine)?.[1];
|
|
84747
|
+
if (declared === undefined || names.length !== Number(declared))
|
|
84748
|
+
return null;
|
|
84749
|
+
return names;
|
|
84750
|
+
}
|
|
84751
|
+
function classifyOrphanedDbTick(lines, alarmIdx) {
|
|
84752
|
+
const lanes = orphanedDbAlarmLanes(lines[alarmIdx] ?? "");
|
|
84753
|
+
if (lanes === null || !lanes.every((n) => n.startsWith("history.db"))) {
|
|
84754
|
+
return "unrecovered";
|
|
84755
|
+
}
|
|
84756
|
+
let verdict = null;
|
|
84757
|
+
for (let j = alarmIdx + 1;j < lines.length; j++) {
|
|
84758
|
+
const line = lines[j];
|
|
84759
|
+
if (!line || !ORPHANED_DB_SWEEP_LINE_RE.test(line))
|
|
84760
|
+
continue;
|
|
84761
|
+
if (GATEWAY_SIGNATURES["orphaned-db-handle"].test(line))
|
|
84762
|
+
break;
|
|
84763
|
+
if (ORPHANED_DB_LANE_VETO_RE.test(line))
|
|
84764
|
+
return "unrecovered";
|
|
84765
|
+
verdict ??= ORPHANED_DB_RECOVERED_RE.test(line) ? "recovered" : "unrecovered";
|
|
84766
|
+
if (verdict === "unrecovered")
|
|
84767
|
+
return "unrecovered";
|
|
84768
|
+
}
|
|
84769
|
+
return verdict ?? "unrecovered";
|
|
84770
|
+
}
|
|
84732
84771
|
function parseTurns(text) {
|
|
84733
84772
|
const out = [];
|
|
84734
84773
|
for (const raw of text.split(`
|
|
@@ -84832,27 +84871,39 @@ function detectTurnFindings(agent, turns, opts = {}) {
|
|
|
84832
84871
|
}
|
|
84833
84872
|
function detectGatewayFindings(agent, logText, logName = `logs/${agent}/gateway-supervisor.log`) {
|
|
84834
84873
|
const findings = [];
|
|
84835
|
-
const gw_hits =
|
|
84836
|
-
"duplicate-delivery-represent": 0,
|
|
84837
|
-
"represent-escalation": 0,
|
|
84838
|
-
"reply-delivery-failure": 0,
|
|
84839
|
-
"orphaned-db-handle": 0
|
|
84840
|
-
};
|
|
84874
|
+
const gw_hits = emptyGwHits();
|
|
84841
84875
|
const lines = logText.split(`
|
|
84842
84876
|
`);
|
|
84877
|
+
const eventIndex = new Map;
|
|
84843
84878
|
for (const [name, re] of Object.entries(GATEWAY_SIGNATURES)) {
|
|
84844
84879
|
for (let i2 = 0;i2 < lines.length; i2++) {
|
|
84845
84880
|
const line = lines[i2];
|
|
84846
84881
|
if (!line)
|
|
84847
84882
|
continue;
|
|
84848
84883
|
if (re.test(line)) {
|
|
84849
|
-
|
|
84884
|
+
const signal = name === "orphaned-db-handle" && classifyOrphanedDbTick(lines, i2) === "recovered" ? "orphaned-db-handle-recovered" : name;
|
|
84885
|
+
gw_hits[signal] += 1;
|
|
84886
|
+
const origin = extractTurnId(line);
|
|
84887
|
+
const pointer = `${logName}:${i2 + 1}`;
|
|
84888
|
+
const ts = extractTs(line);
|
|
84889
|
+
if (origin !== null) {
|
|
84890
|
+
const eventKey = `${signal}|${origin}`;
|
|
84891
|
+
const at = eventIndex.get(eventKey);
|
|
84892
|
+
if (at !== undefined) {
|
|
84893
|
+
const prev = findings[at];
|
|
84894
|
+
if (ts !== null || prev.ts === null) {
|
|
84895
|
+
findings[at] = { ...prev, log_pointer: pointer, ts };
|
|
84896
|
+
}
|
|
84897
|
+
continue;
|
|
84898
|
+
}
|
|
84899
|
+
eventIndex.set(eventKey, findings.length);
|
|
84900
|
+
}
|
|
84850
84901
|
findings.push({
|
|
84851
|
-
signal
|
|
84902
|
+
signal,
|
|
84852
84903
|
agent,
|
|
84853
|
-
turn_id:
|
|
84854
|
-
log_pointer:
|
|
84855
|
-
ts
|
|
84904
|
+
turn_id: origin ?? `${agent}:gw#${i2 + 1}`,
|
|
84905
|
+
log_pointer: pointer,
|
|
84906
|
+
ts
|
|
84856
84907
|
});
|
|
84857
84908
|
}
|
|
84858
84909
|
}
|
|
@@ -84891,14 +84942,27 @@ function scanAgent(agent, turnsText, gatewayText, opts = {}) {
|
|
|
84891
84942
|
escalate
|
|
84892
84943
|
};
|
|
84893
84944
|
}
|
|
84894
|
-
var HANG_MS = 360000, HANG_MAXTOOLS = 2, SILENT_NOOP_FLOOR_TS = 1783900800, GATEWAY_SIGNATURES, ISO_TS_RE;
|
|
84945
|
+
var HANG_MS = 360000, HANG_MAXTOOLS = 2, SILENT_NOOP_FLOOR_TS = 1783900800, GATEWAY_SIGNATURES, GATEWAY_SIGNAL_MEMBERS, GATEWAY_SIGNAL_NAMES, ORPHANED_DB_RECOVERED_RE, ORPHANED_DB_SWEEP_LINE_RE, ORPHANED_DB_ALARM_TARGET_RE, ORPHANED_DB_ALARM_COUNT_RE, ORPHANED_DB_LANE_VETO_RE, ISO_TS_RE;
|
|
84895
84946
|
var init_detect = __esm(() => {
|
|
84896
84947
|
GATEWAY_SIGNATURES = {
|
|
84897
84948
|
"duplicate-delivery-represent": /represent duplicate-send/,
|
|
84898
|
-
"represent-escalation": /obligation escalation/,
|
|
84949
|
+
"represent-escalation": /obligation escalation (?:delivered \+ closed|PERMANENTLY undeliverable)/,
|
|
84899
84950
|
"reply-delivery-failure": /tg-post method=sendRichMessage[^\n]*status=err(?![a-z])/,
|
|
84900
84951
|
"orphaned-db-handle": /orphaned-db-sweep DETECTED \d+ deleted-inode DB handle/
|
|
84901
84952
|
};
|
|
84953
|
+
GATEWAY_SIGNAL_MEMBERS = {
|
|
84954
|
+
"duplicate-delivery-represent": true,
|
|
84955
|
+
"represent-escalation": true,
|
|
84956
|
+
"reply-delivery-failure": true,
|
|
84957
|
+
"orphaned-db-handle": true,
|
|
84958
|
+
"orphaned-db-handle-recovered": true
|
|
84959
|
+
};
|
|
84960
|
+
GATEWAY_SIGNAL_NAMES = Object.keys(GATEWAY_SIGNAL_MEMBERS);
|
|
84961
|
+
ORPHANED_DB_RECOVERED_RE = /orphaned-db-sweep reopened history\.db \u2014 writes are durable again/;
|
|
84962
|
+
ORPHANED_DB_SWEEP_LINE_RE = /orphaned-db-sweep /;
|
|
84963
|
+
ORPHANED_DB_ALARM_TARGET_RE = /\bfd=\d+ (\S+)/g;
|
|
84964
|
+
ORPHANED_DB_ALARM_COUNT_RE = /DETECTED (\d+) deleted-inode DB handle/;
|
|
84965
|
+
ORPHANED_DB_LANE_VETO_RE = /orphaned-db-sweep found (?:an orphaned registry\.db handle|orphaned handle\(s\) on )/;
|
|
84902
84966
|
ISO_TS_RE = /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(Z|[+-]\d{2}:?\d{2})?/;
|
|
84903
84967
|
});
|
|
84904
84968
|
|
|
@@ -84906,6 +84970,12 @@ var init_detect = __esm(() => {
|
|
|
84906
84970
|
function mapSignal(signal) {
|
|
84907
84971
|
return SIGNAL_MAP[signal];
|
|
84908
84972
|
}
|
|
84973
|
+
function countingUnitFor(signal) {
|
|
84974
|
+
return GATEWAY_SIGNAL_SET.has(signal) ? "gateway-event" : "log-line";
|
|
84975
|
+
}
|
|
84976
|
+
function siblingDedupKeys(key) {
|
|
84977
|
+
return SIBLING_DEDUP_KEYS.get(key) ?? [];
|
|
84978
|
+
}
|
|
84909
84979
|
function dedupKeyFor(finding) {
|
|
84910
84980
|
const m = mapSignal(finding.signal);
|
|
84911
84981
|
return `${m.job_spec}:${m.signature}`;
|
|
@@ -84935,8 +85005,9 @@ function reachFactor(reachCount) {
|
|
|
84935
85005
|
function issuePriority(severity, frequency, reachCount, newestIso, now2 = new Date, windowDays = 30) {
|
|
84936
85006
|
return severity * frequencyFactor(frequency) * reachFactor(reachCount) * recencyFactor(newestIso, now2, windowDays);
|
|
84937
85007
|
}
|
|
84938
|
-
var SIGNAL_MAP, ALL_JOB_SPECS;
|
|
85008
|
+
var SIGNAL_MAP, ALL_JOB_SPECS, GATEWAY_SIGNAL_SET, LEGACY_COUNTING_UNIT = "log-line", RECLASSIFICATION_GROUPS, SIBLING_DEDUP_KEYS;
|
|
84939
85009
|
var init_mapping = __esm(() => {
|
|
85010
|
+
init_detect();
|
|
84940
85011
|
SIGNAL_MAP = {
|
|
84941
85012
|
"silent-no-op-candidate": {
|
|
84942
85013
|
failure_mode: "silent-no-op",
|
|
@@ -84968,6 +85039,12 @@ var init_mapping = __esm(() => {
|
|
|
84968
85039
|
job_spec: "survive-reboots-and-real-life",
|
|
84969
85040
|
signature: "orphaned-db-handle:deleted-inode-writes"
|
|
84970
85041
|
},
|
|
85042
|
+
"orphaned-db-handle-recovered": {
|
|
85043
|
+
failure_mode: "drift",
|
|
85044
|
+
severity: 1,
|
|
85045
|
+
job_spec: "survive-reboots-and-real-life",
|
|
85046
|
+
signature: "orphaned-db-handle:recovered-in-tick"
|
|
85047
|
+
},
|
|
84971
85048
|
"hang-long-stalled": {
|
|
84972
85049
|
failure_mode: "partial",
|
|
84973
85050
|
severity: 2,
|
|
@@ -85048,6 +85125,23 @@ var init_mapping = __esm(() => {
|
|
|
85048
85125
|
"talk-to-agents-from-anywhere",
|
|
85049
85126
|
"track-plan-quota-live"
|
|
85050
85127
|
];
|
|
85128
|
+
GATEWAY_SIGNAL_SET = new Set(GATEWAY_SIGNAL_NAMES);
|
|
85129
|
+
RECLASSIFICATION_GROUPS = [
|
|
85130
|
+
["orphaned-db-handle", "orphaned-db-handle-recovered"],
|
|
85131
|
+
["silent-no-op-candidate", "flush-recovered-turn"]
|
|
85132
|
+
];
|
|
85133
|
+
SIBLING_DEDUP_KEYS = (() => {
|
|
85134
|
+
const m = new Map;
|
|
85135
|
+
for (const group of RECLASSIFICATION_GROUPS) {
|
|
85136
|
+
const keys = group.map((s) => {
|
|
85137
|
+
const map2 = mapSignal(s);
|
|
85138
|
+
return `${map2.job_spec}:${map2.signature}`;
|
|
85139
|
+
});
|
|
85140
|
+
for (const key of keys)
|
|
85141
|
+
m.set(key, keys.filter((k) => k !== key));
|
|
85142
|
+
}
|
|
85143
|
+
return m;
|
|
85144
|
+
})();
|
|
85051
85145
|
});
|
|
85052
85146
|
|
|
85053
85147
|
// src/fleet-health/ledger.ts
|
|
@@ -85063,7 +85157,8 @@ function indexPriorIssues(prior) {
|
|
|
85063
85157
|
status: iss.status,
|
|
85064
85158
|
job_spec: rec.job_spec,
|
|
85065
85159
|
failure_mode: iss.failure_mode,
|
|
85066
|
-
severity: iss.severity
|
|
85160
|
+
severity: iss.severity,
|
|
85161
|
+
counting_unit: iss.counting_unit ?? LEGACY_COUNTING_UNIT
|
|
85067
85162
|
});
|
|
85068
85163
|
}
|
|
85069
85164
|
}
|
|
@@ -85109,7 +85204,8 @@ function buildLedger(findings, opts = {}) {
|
|
|
85109
85204
|
occurrences: [],
|
|
85110
85205
|
reach: new Set,
|
|
85111
85206
|
newest: null,
|
|
85112
|
-
count: 0
|
|
85207
|
+
count: 0,
|
|
85208
|
+
counting_unit: countingUnitFor(f.signal)
|
|
85113
85209
|
};
|
|
85114
85210
|
aggs.set(key, agg);
|
|
85115
85211
|
}
|
|
@@ -85128,12 +85224,16 @@ function buildLedger(findings, opts = {}) {
|
|
|
85128
85224
|
for (const agg of aggs.values()) {
|
|
85129
85225
|
const count = agg.count;
|
|
85130
85226
|
const prior = priorIdx.get(agg.dedup_key);
|
|
85227
|
+
const unitChanged = prior !== undefined && prior.counting_unit !== agg.counting_unit;
|
|
85131
85228
|
let status = "open";
|
|
85132
|
-
if (
|
|
85133
|
-
status = "resolved-pending-verify";
|
|
85229
|
+
if (unitChanged) {
|
|
85230
|
+
status = count <= RESOLVED_THRESHOLD && prior.status === "resolved-pending-verify" ? "resolved-pending-verify" : "open";
|
|
85134
85231
|
} else if (prior?.status === "resolved-pending-verify" && count <= RESOLVED_THRESHOLD) {
|
|
85135
85232
|
status = "closed";
|
|
85233
|
+
} else if (prior && count <= RESOLVED_THRESHOLD && count < prior.frequency) {
|
|
85234
|
+
status = "resolved-pending-verify";
|
|
85136
85235
|
}
|
|
85236
|
+
const reopened = prior?.status === "closed" && status !== "closed";
|
|
85137
85237
|
const issue2 = {
|
|
85138
85238
|
dedup_key: agg.dedup_key,
|
|
85139
85239
|
failure_mode: agg.failure_mode,
|
|
@@ -85143,7 +85243,9 @@ function buildLedger(findings, opts = {}) {
|
|
|
85143
85243
|
recency: agg.newest,
|
|
85144
85244
|
occurrences: agg.occurrences,
|
|
85145
85245
|
...prior?.gh_issue !== undefined ? { gh_issue: prior.gh_issue } : {},
|
|
85146
|
-
status
|
|
85246
|
+
status,
|
|
85247
|
+
...reopened ? { reopened: true } : {},
|
|
85248
|
+
counting_unit: agg.counting_unit
|
|
85147
85249
|
};
|
|
85148
85250
|
const list2 = byJob.get(agg.job_spec) ?? [];
|
|
85149
85251
|
list2.push(issue2);
|
|
@@ -85155,6 +85257,7 @@ function buildLedger(findings, opts = {}) {
|
|
|
85155
85257
|
if (prior.status !== "open" && prior.status !== "resolved-pending-verify") {
|
|
85156
85258
|
continue;
|
|
85157
85259
|
}
|
|
85260
|
+
const migratedTo = siblingDedupKeys(dedup_key).filter((k) => aggs.has(k));
|
|
85158
85261
|
const issue2 = {
|
|
85159
85262
|
dedup_key,
|
|
85160
85263
|
failure_mode: prior.failure_mode,
|
|
@@ -85164,7 +85267,10 @@ function buildLedger(findings, opts = {}) {
|
|
|
85164
85267
|
recency: null,
|
|
85165
85268
|
occurrences: [],
|
|
85166
85269
|
...prior.gh_issue !== undefined ? { gh_issue: prior.gh_issue } : {},
|
|
85167
|
-
status: "closed"
|
|
85270
|
+
status: "closed",
|
|
85271
|
+
close_reason: migratedTo.length > 0 ? "reclassified" : "count-drop",
|
|
85272
|
+
...migratedTo.length > 0 ? { reclassified_into: migratedTo.sort() } : {},
|
|
85273
|
+
counting_unit: prior.counting_unit
|
|
85168
85274
|
};
|
|
85169
85275
|
const list2 = byJob.get(prior.job_spec) ?? [];
|
|
85170
85276
|
list2.push(issue2);
|
|
@@ -129717,19 +129823,29 @@ function syncIssue(deps, repo, job_spec, iss) {
|
|
|
129717
129823
|
if (!upd.ok) {
|
|
129718
129824
|
deps.log(`fleet-health: gh issue edit #${num3} failed: ${upd.stderr}`);
|
|
129719
129825
|
}
|
|
129720
|
-
if (iss.
|
|
129721
|
-
const
|
|
129826
|
+
if (iss.reopened) {
|
|
129827
|
+
const r = deps.run([
|
|
129722
129828
|
"issue",
|
|
129723
|
-
"
|
|
129829
|
+
"reopen",
|
|
129724
129830
|
num3,
|
|
129725
129831
|
"-R",
|
|
129726
129832
|
repo,
|
|
129727
129833
|
"--comment",
|
|
129728
|
-
`
|
|
129834
|
+
`Reopened by the Fleet Health sensor: this defect is back (frequency ${iss.frequency}` + ` in the current scan window). The earlier close was not a durable fix.`
|
|
129729
129835
|
]);
|
|
129730
|
-
if (
|
|
129731
|
-
deps.log(`fleet-health:
|
|
129836
|
+
if (r.ok)
|
|
129837
|
+
deps.log(`fleet-health: reopened GH #${num3} for ${iss.dedup_key}`);
|
|
129732
129838
|
else
|
|
129839
|
+
deps.log(`fleet-health: gh issue reopen #${num3} failed: ${r.stderr}`);
|
|
129840
|
+
return;
|
|
129841
|
+
}
|
|
129842
|
+
if (iss.status === "closed") {
|
|
129843
|
+
const reclassified = iss.close_reason === "reclassified";
|
|
129844
|
+
const comment = reclassified ? `No occurrences in the current scan window because these findings were` + ` RECLASSIFIED into ${(iss.reclassified_into ?? []).join(", ")} \u2014 the same` + ` detected condition re-sorted by its outcome. This is NOT a verified fix;` + ` the evidence now lives on the sibling issue. Closed by the Fleet Health sensor.` : `Verified count-drop (frequency ${iss.frequency} \u2264 resolved threshold).` + ` Closed by the Fleet Health sensor.`;
|
|
129845
|
+
const c = deps.run(["issue", "close", num3, "-R", repo, "--comment", comment]);
|
|
129846
|
+
if (c.ok) {
|
|
129847
|
+
deps.log(`fleet-health: closed GH #${num3} (${reclassified ? "reclassified" : "count-drop"})` + ` for ${iss.dedup_key}`);
|
|
129848
|
+
} else
|
|
129733
129849
|
deps.log(`fleet-health: gh issue close #${num3} failed: ${c.stderr}`);
|
|
129734
129850
|
}
|
|
129735
129851
|
}
|
|
@@ -21565,7 +21565,7 @@ function allocateAgentUid(name) {
|
|
|
21565
21565
|
}
|
|
21566
21566
|
|
|
21567
21567
|
// src/build-info.ts
|
|
21568
|
-
var VERSION = "0.21.
|
|
21568
|
+
var VERSION = "0.21.12";
|
|
21569
21569
|
|
|
21570
21570
|
// src/setup/hindsight-recall-passthrough.ts
|
|
21571
21571
|
var HINDSIGHT_RECALL_TAG_WEIGHT_SEED = Object.freeze({ sidechain: 0.8 });
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "switchroom",
|
|
3
3
|
"//version": "NOT the release version — source of truth is the git tag, resolved by scripts/build.mjs:resolveVersion() (see CLAUDE.md > Standard release process). This field is stale by design and only the Layer-4 dev/non-tag fallback for build.mjs + src/cli/resolve-version.ts; do NOT bump it expecting a release to pick it up. npm-pack tarball naming needs a real version — do that as an UNCOMMITTED pack-time bump (see release step 6), never a committed one.",
|
|
4
|
-
"version": "0.21.
|
|
4
|
+
"version": "0.21.12",
|
|
5
5
|
"description": "Run Claude Code 24/7 on your Claude Pro/Max subscription over Telegram. Open-source alternative to OpenClaw and NanoClaw — no API keys.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"bin": {
|
|
@@ -28,9 +28,10 @@
|
|
|
28
28
|
"test:vitest": "vitest run",
|
|
29
29
|
"test:bun": "bun test telegram-plugin/tests/hermes-messages-paging.test.ts telegram-plugin/tests/hermes-session-search.test.ts telegram-plugin/tests/agent-state-dir-preload.test.ts telegram-plugin/tests/hindsight-bank-preload.test.ts telegram-plugin/tests/catch-all-forwarded-history.test.ts src/vault/grants.test.ts src/vault/grants-db.test.ts src/vault/write-grants.test.ts src/vault/broker/server-grants.test.ts src/vault/broker/server-write-grants.test.ts src/vault/broker/server-scope-persist.test.ts src/vault/broker/server-tokenless-scope.test.ts src/vault/broker/server-mint-grant-passphrase-attest.test.ts src/vault/broker/server-passphrase-attest.test.ts src/vault/broker/server-mint-grant-posture-attest.test.ts src/vault/broker/server-admin-only-keys.test.ts src/vault/broker/client-token.test.ts src/vault/broker/server-unlock.test.ts src/vault/broker/auto-unlock.test.ts src/vault/broker/drift-detection.test.ts tests/vault-broker-passphrase.test.ts src/cli/vault-get-broker.test.ts src/vault/resolver-via-broker.test.ts src/vault/broker/scope.test.ts src/vault/broker/server.test.ts src/litellm/provision-apply-e2e.test.ts src/drive/disconnect.test.ts src/drive/grants.test.ts src/drive/oauth.test.ts src/drive/onboarding.test.ts src/drive/reconciler.test.ts src/drive/vault-slots.test.ts src/drive/wrapper.test.ts src/vault/approvals/kernel.test.ts src/vault/approvals/approval-origin.test.ts src/vault/approvals/self-approval-bypass.test.ts src/vault/approvals/schema-idempotent.test.ts src/vault/broker/server-approvals.test.ts telegram-plugin/tests/boot-probes.test.ts telegram-plugin/tests/boot-version-string.test.ts telegram-plugin/tests/history.test.ts telegram-plugin/tests/boot-briefing-builder.test.ts telegram-plugin/tests/cross-turn-card-gate.test.ts telegram-plugin/tests/emission-authority-open-gate.test.ts telegram-plugin/tests/emission-authority-ping-gate.test.ts telegram-plugin/tests/emission-authority-card-drain-gate.test.ts telegram-plugin/tests/per-topic-current-turn.test.ts telegram-plugin/tests/history-reaper.test.ts telegram-plugin/tests/ipc-server-client.test.ts telegram-plugin/tests/ipc-server-race.test.ts telegram-plugin/tests/ipc-server-buzz-dedup.test.ts telegram-plugin/tests/ipc-server-query-pending-permission.test.ts telegram-plugin/tests/ipc-server-check-pre-approved.test.ts telegram-plugin/tests/rollout-narration-edit-socket.test.ts telegram-plugin/tests/gateway-bridge.test.ts telegram-plugin/tests/gateway-startup-mutex.test.ts telegram-plugin/tests/gateway-clean-shutdown-marker.test.ts telegram-plugin/tests/boot-card-dedupe.test.ts telegram-plugin/tests/boot-card-reason.test.ts telegram-plugin/tests/progress-update.test.ts telegram-plugin/tests/progress-fallback-cap.test.ts telegram-plugin/tests/progress-cap.test.ts telegram-plugin/tests/quota-cache.test.ts telegram-plugin/tests/silent-reply-guard.test.ts telegram-plugin/tests/unhandled-rejection-policy.test.ts telegram-plugin/tests/registry-turns.test.ts telegram-plugin/registry/subagents.test.ts telegram-plugin/registry/subagents-bugs.test.ts telegram-plugin/tests/subagent-watcher-parent-turn-key.test.ts telegram-plugin/tests/worker-origin-gap-dispatch.test.ts telegram-plugin/tests/subagent-nested-dispatch.test.ts telegram-plugin/tests/nested-worker-visibility-harness.test.ts telegram-plugin/tests/turns-writer.test.ts telegram-plugin/tests/resume-inbound-builder.test.ts telegram-plugin/tests/subagent-tracker-hooks.test.ts telegram-plugin/tests/resolve-calling-subagent.test.ts telegram-plugin/tests/gateway-update-placeholder-dispatch.test.ts telegram-plugin/tests/status-query-telemetry.test.ts telegram-plugin/tests/reaction-trigger.test.ts telegram-plugin/tests/reaction-trigger-flow.test.ts telegram-plugin/tests/subagent-watcher-workflow-visibility.test.ts telegram-plugin/uat/load-env.test.ts telegram-plugin/uat/feed-matcher.test.ts telegram-plugin/uat/uat-driver.test.ts telegram-plugin/gateway/webhook-ingest-server.test.ts telegram-plugin/tests/skill-proposal-card.test.ts",
|
|
30
30
|
"test:watch": "vitest",
|
|
31
|
-
"lint": "tsc --noEmit && node scripts/check-plugin-references.mjs && bash scripts/check-bot-api-wrapping.sh && node scripts/check-bun-test-imports.mjs && node scripts/check-test-runner-coverage.mjs && node scripts/check-bun-module-mock-scope.mjs && node scripts/check-no-pii-secrets.mjs && node scripts/check-bench-baseline-anonymised.mjs && node scripts/check-vault-test-hermeticity.mjs && node scripts/check-auth-test-hermeticity.mjs && node scripts/check-agent-state-dir-hermeticity.mjs && node scripts/check-hindsight-bank-hermeticity.mjs && node scripts/check-parked-turn-start-hermeticity.mjs && node scripts/check-no-broadcast-delivery.mjs && node scripts/check-stale-tool-descriptions.mjs && node scripts/check-mcp-instructions-budget.mjs && node scripts/check-web-subscription-honest.mjs && node scripts/check-no-unpinned-npx-playwright.mjs && node scripts/check-gateway-line-ratchet.mjs && node scripts/check-retry-flood-hooks.mjs && node scripts/check-callback-ctx-wrapping.mjs && node scripts/check-ctx-send-wrapping.mjs && node scripts/check-status-pin-single-path.mjs && node scripts/check-litellm-config-guard.mjs && node scripts/check-release-asset-names.mjs && node scripts/check-foreign-db-readonly.mjs && node scripts/check-claude-cli-lockstep.mjs && node scripts/check-changelog-entry.mjs && node scripts/check-agent-attribution-trailers.mjs && node scripts/check-hindsight-write-redaction.mjs && bun scripts/check-secret-pattern-parity.ts && bun scripts/check-hostd-template-guard.ts",
|
|
31
|
+
"lint": "tsc --noEmit && node scripts/check-plugin-references.mjs && bash scripts/check-bot-api-wrapping.sh && node scripts/check-bun-test-imports.mjs && node scripts/check-test-runner-coverage.mjs && node scripts/check-bun-module-mock-scope.mjs && node scripts/check-no-pii-secrets.mjs && node scripts/check-bench-baseline-anonymised.mjs && node scripts/check-vault-test-hermeticity.mjs && node scripts/check-auth-test-hermeticity.mjs && node scripts/check-agent-state-dir-hermeticity.mjs && node scripts/check-hindsight-bank-hermeticity.mjs && node scripts/check-parked-turn-start-hermeticity.mjs && node scripts/check-no-broadcast-delivery.mjs && node scripts/check-stale-tool-descriptions.mjs && node scripts/check-mcp-instructions-budget.mjs && node scripts/check-web-subscription-honest.mjs && node scripts/check-no-unpinned-npx-playwright.mjs && node scripts/check-gateway-line-ratchet.mjs && node scripts/check-retry-flood-hooks.mjs && node scripts/check-callback-ctx-wrapping.mjs && node scripts/check-ctx-send-wrapping.mjs && node scripts/check-status-pin-single-path.mjs && node scripts/check-litellm-config-guard.mjs && node scripts/check-release-asset-names.mjs && node scripts/check-foreign-db-readonly.mjs && node scripts/check-claude-cli-lockstep.mjs && node scripts/check-changelog-entry.mjs && node scripts/check-agent-attribution-trailers.mjs && node scripts/check-hindsight-write-redaction.mjs && node scripts/check-mutation-coverage.mjs && bun scripts/check-secret-pattern-parity.ts && bun scripts/check-hostd-template-guard.ts",
|
|
32
32
|
"lint:tsc": "tsc --noEmit",
|
|
33
33
|
"lint:hindsight-write-redaction": "node scripts/check-hindsight-write-redaction.mjs",
|
|
34
|
+
"lint:mutation-coverage": "node scripts/check-mutation-coverage.mjs --all",
|
|
34
35
|
"lint:secret-pattern-parity": "bun scripts/check-secret-pattern-parity.ts",
|
|
35
36
|
"lint:plugin-references": "node scripts/check-plugin-references.mjs",
|
|
36
37
|
"lint:bot-api-wrapping": "bash scripts/check-bot-api-wrapping.sh",
|
|
@@ -56,6 +57,7 @@
|
|
|
56
57
|
"lint:claude-cli-lockstep": "node scripts/check-claude-cli-lockstep.mjs",
|
|
57
58
|
"lint:changelog-entry": "node scripts/check-changelog-entry.mjs",
|
|
58
59
|
"changelog:generate": "node scripts/gen-changelog-entry.mjs",
|
|
60
|
+
"changelog:cut": "node scripts/cut-changelog-release.mjs",
|
|
59
61
|
"lint:agent-attribution-trailers": "node scripts/check-agent-attribution-trailers.mjs",
|
|
60
62
|
"lint:hostd-template-guard": "bun scripts/check-hostd-template-guard.ts",
|
|
61
63
|
"lint:bench-baseline-anonymised": "node scripts/check-bench-baseline-anonymised.mjs",
|
|
@@ -82,6 +84,7 @@
|
|
|
82
84
|
"@types/bun": "^1.3.11",
|
|
83
85
|
"@types/node": "^22.0.0",
|
|
84
86
|
"@vitest/coverage-v8": "3.2.4",
|
|
87
|
+
"commonmark": "0.31.2",
|
|
85
88
|
"typescript": "^5.7.0",
|
|
86
89
|
"vitest": "^3.2.4"
|
|
87
90
|
},
|
|
@@ -139,17 +139,21 @@ is not a rebuttal.
|
|
|
139
139
|
`AWAITING_CHECKS` means a required workflow is not listening for
|
|
140
140
|
`merge_group`; `.github/MERGE-QUEUE.md` owns that failure mode and the
|
|
141
141
|
invariants that prevent it.
|
|
142
|
-
- **Stage the
|
|
143
|
-
changes shippable code must add a
|
|
142
|
+
- **Stage the changelog note author-side, before `gh pr create`.** A PR that
|
|
143
|
+
changes shippable code must add a NEW `changelog.d/` fragment file (#4469's
|
|
144
144
|
`check-changelog-entry.mjs`, part of `npm run lint`, enforces it). Don't
|
|
145
|
-
hand-
|
|
146
|
-
|
|
147
|
-
(idempotently — a second run is a no-op)
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
145
|
+
hand-edit `CHANGELOG.md`: run `bun run changelog:generate` in the repo — it
|
|
146
|
+
derives a per-PR fragment (`changelog.d/<pr>-<slug>.<type>.md`) from your
|
|
147
|
+
conventional-commit title (idempotently — a second run is a no-op) and
|
|
148
|
+
respects the escape hatches. Then commit the fragment so it lands in your
|
|
149
|
+
own push. Fragment files are per-PR, so they never conflict with another
|
|
150
|
+
in-flight PR — editing the shared `## Unreleased` section does, which is
|
|
151
|
+
exactly the conflict class fragments replace (see `changelog.d/README.md`).
|
|
152
|
+
It runs AUTHOR-side, not in CI, because a CI commit-back with the default
|
|
153
|
+
`GITHUB_TOKEN` cannot re-trigger the required checks and would wedge the PR
|
|
154
|
+
(`.github/MERGE-QUEUE.md` § "Author-side changelog generation"). Opt a
|
|
155
|
+
docs/chore/test PR out with the `no-changelog` label or a `[skip changelog]`
|
|
156
|
+
token on its own line.
|
|
153
157
|
- **A test that wouldn't fail on the bug it guards is not a test.** Assert the
|
|
154
158
|
observable outcome, not that the code path executed.
|
|
155
159
|
- **Prefer a deterministic mechanism over prompt discipline.** If a check, a
|
|
@@ -22,14 +22,20 @@ Cut a release of `switchroom/switchroom` and get it live on the fleet. This is a
|
|
|
22
22
|
1. `git fetch origin`, confirm `main` is at the commit you want released.
|
|
23
23
|
2. `gh pr list --state open` — confirm no PR meant for this release is still open. Ask the operator if unsure.
|
|
24
24
|
3. Confirm CI on `main` is green (`gh run list --branch main --limit 3`).
|
|
25
|
-
4. Read
|
|
25
|
+
4. Read the staged notes — they live in TWO places, both **continuously maintained** as PRs merge and both enforced by `scripts/check-changelog-entry.mjs` (part of `npm run lint`): the `changelog.d/` **fragment files** (the primary path — every shippable PR adds its own `<pr>-<slug>.<type>.md`; see `changelog.d/README.md`) and any legacy/hand-staged entries under `## Unreleased` in `CHANGELOG.md`. Together they should already read as a near-complete draft — you assemble and tidy, you do NOT author from scratch. **Nothing staged at release time (no fragments AND an empty `## Unreleased`) is an ANOMALY, not the normal state** — `bun run changelog:cut` refuses to cut it: it means either there genuinely is nothing to release, OR the enforcement was bypassed (`no-changelog` labels / `[skip changelog]` tokens on their own line on PRs that should have staged notes). Before assuming the former, cross-check against `git log <last-tag>..origin/main` — if real shippable work landed but nothing is staged, investigate and reconstruct rather than shipping a hollow release (this is exactly the v0.20.12 failure mode the enforcement exists to prevent).
|
|
26
26
|
5. Pick the next version: read the latest tag (`git tag --list 'v*' --sort=-v:refname | head -1`) and bump the patch (or minor if the operator asks). Confirm with the operator which.
|
|
27
27
|
|
|
28
|
-
## Step 1 —
|
|
28
|
+
## Step 1 — Assemble the changelog (the release commit is CHANGELOG + fragment deletions only)
|
|
29
29
|
|
|
30
|
-
- The
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
- The notes are staged continuously (see pre-flight 4), so this step is **one command + a review**, not authorship:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
bun run changelog:cut -- --version vX.Y.Z --summary "<one-line summary>"
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
`scripts/cut-changelog-release.mjs` folds every `changelog.d/` fragment (plus anything hand-staged under `## Unreleased`) into a new `## vX.Y.Z — <summary>` section grouped by category, **re-seeds a fresh empty `## Unreleased` block** (header + convention comment) below `# Changelog`, and **deletes the consumed fragment files**. Use `--dry-run` first to preview the assembled section. It FAILS LOUDLY on a double cut or an empty cut — if it refuses for "nothing to release", STOP and resolve the anomaly (pre-flight 4); do not invent a release note.
|
|
37
|
+
- Read through the assembled section and lightly edit for grouping/summary before committing.
|
|
38
|
+
- The release commit touches **CHANGELOG.md and the deleted `changelog.d/` fragments only**. Do NOT bump `package.json` (placeholder discipline). (A CHANGELOG + fragment-deletion PR changes no shippable code, so `check-changelog-entry.mjs` passes it without a staged note of its own — deleting fragments never counts as staging one.)
|
|
33
39
|
- Branch protection blocks direct push to `main`, so: create a `release/vX.Y.Z` branch, push it, open a `chore: release vX.Y.Z` PR (base `main`), arm auto-merge (squash, delete-branch) on green CI.
|
|
34
40
|
|
|
35
41
|
## Step 2 — Create the DRAFT release on a PINNED SHA, then push the tag
|