switchroom 0.19.44 → 0.19.45
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 +940 -1006
- package/dist/host-control/main.js +1 -1
- package/package.json +1 -1
- package/telegram-plugin/dist/gateway/gateway.js +486 -314
- package/telegram-plugin/flushed-turn-supersede.ts +190 -16
- package/telegram-plugin/gateway/cron-session.ts +31 -0
- package/telegram-plugin/gateway/gateway.ts +66 -65
- package/telegram-plugin/gateway/outbound-send-path.ts +60 -8
- package/telegram-plugin/gateway/reply-owner-wiring.ts +128 -0
- package/telegram-plugin/gateway/stream-render.ts +91 -1
- package/telegram-plugin/gateway/subagent-handback-marker.ts +49 -9
- package/telegram-plugin/gateway/subagent-reply-authority.ts +147 -0
- package/telegram-plugin/gateway/turn-end.ts +9 -1
- package/telegram-plugin/reply-owner-resolve.ts +102 -26
- package/telegram-plugin/tests/flushed-turn-supersede.test.ts +110 -8
- package/telegram-plugin/tests/narrative-lane-golden.test.ts +2 -0
- package/telegram-plugin/tests/reply-owner-resolve.test.ts +297 -27
- package/telegram-plugin/tests/reply-quote-wire.test.ts +2 -0
- package/telegram-plugin/tests/send-reply-golden.test.ts +567 -5
- package/telegram-plugin/tests/stream-render-golden.test.ts +204 -1
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.19.
|
|
2123
|
+
var VERSION = "0.19.45", COMMIT_SHA = "2ad33d5d";
|
|
2124
2124
|
|
|
2125
2125
|
// src/cli/resolve-version.ts
|
|
2126
2126
|
import { existsSync, readFileSync } from "node:fs";
|
|
@@ -13572,28 +13572,7 @@ var init_zod = __esm(() => {
|
|
|
13572
13572
|
});
|
|
13573
13573
|
|
|
13574
13574
|
// src/memory/observation-scopes.ts
|
|
13575
|
-
|
|
13576
|
-
return typeof value === "string" && OBSERVATION_SCOPES.includes(value);
|
|
13577
|
-
}
|
|
13578
|
-
function observationScopesHint() {
|
|
13579
|
-
return OBSERVATION_SCOPES.join(", ");
|
|
13580
|
-
}
|
|
13581
|
-
function classifyObservationScope(raw) {
|
|
13582
|
-
if (raw === undefined)
|
|
13583
|
-
return { kind: "unset" };
|
|
13584
|
-
const value = raw.trim();
|
|
13585
|
-
if (!value)
|
|
13586
|
-
return { kind: "unset" };
|
|
13587
|
-
if (!isObservationScope(value))
|
|
13588
|
-
return { kind: "invalid", raw };
|
|
13589
|
-
return { kind: "set", scope: value };
|
|
13590
|
-
}
|
|
13591
|
-
function resolveObservationScope(configured, env2 = process.env) {
|
|
13592
|
-
if (configured !== undefined)
|
|
13593
|
-
return classifyObservationScope(configured);
|
|
13594
|
-
return classifyObservationScope(env2[OBSERVATION_SCOPES_ENV]);
|
|
13595
|
-
}
|
|
13596
|
-
var OBSERVATION_SCOPES, OBSERVATION_SCOPES_ENV = "HINDSIGHT_OBSERVATION_SCOPES", OBSERVATION_SCOPE_STRATEGIES;
|
|
13575
|
+
var OBSERVATION_SCOPES, OBSERVATION_SCOPE_STRATEGIES;
|
|
13597
13576
|
var init_observation_scopes = __esm(() => {
|
|
13598
13577
|
OBSERVATION_SCOPES = [
|
|
13599
13578
|
"per_tag",
|
|
@@ -36321,7 +36300,7 @@ class AuthBrokerClient {
|
|
|
36321
36300
|
sock.once("error", onError);
|
|
36322
36301
|
sock.once("connect", () => {
|
|
36323
36302
|
sock.removeListener("error", onError);
|
|
36324
|
-
sock.on("data", (
|
|
36303
|
+
sock.on("data", (chunk) => this.onData(chunk));
|
|
36325
36304
|
sock.on("error", (err) => this.onSocketError(err));
|
|
36326
36305
|
sock.on("close", () => this.onSocketClose());
|
|
36327
36306
|
this.socket = sock;
|
|
@@ -36335,8 +36314,8 @@ class AuthBrokerClient {
|
|
|
36335
36314
|
this.connecting = null;
|
|
36336
36315
|
}
|
|
36337
36316
|
}
|
|
36338
|
-
onData(
|
|
36339
|
-
this.buffer +=
|
|
36317
|
+
onData(chunk) {
|
|
36318
|
+
this.buffer += chunk.toString("utf8");
|
|
36340
36319
|
let idx;
|
|
36341
36320
|
while ((idx = this.buffer.indexOf(`
|
|
36342
36321
|
`)) !== -1) {
|
|
@@ -73049,730 +73028,137 @@ import { execFileSync as execFileSync11 } from "node:child_process";
|
|
|
73049
73028
|
init_atomic();
|
|
73050
73029
|
import { readFileSync as readFileSync20, writeFileSync as writeFileSync9, renameSync as renameSync7, mkdirSync as mkdirSync16, existsSync as existsSync24, statSync as statSync11, readdirSync as readdirSync10 } from "node:fs";
|
|
73051
73030
|
import { join as join18 } from "node:path";
|
|
73052
|
-
|
|
73053
|
-
|
|
73054
|
-
var
|
|
73055
|
-
|
|
73056
|
-
|
|
73057
|
-
|
|
73058
|
-
|
|
73059
|
-
|
|
73060
|
-
|
|
73061
|
-
{ rule_id: "slack_app_token", regex: /\b(xapp-[A-Za-z0-9-]{10,})\b/g, captureIndex: 1, slugHint: "slack_app_token" },
|
|
73062
|
-
{ rule_id: "groq_api_key", regex: /\b(gsk_[A-Za-z0-9_-]{10,})\b/g, captureIndex: 1, slugHint: "groq_api_key" },
|
|
73063
|
-
{ rule_id: "google_api_key", regex: /\b(AIza[0-9A-Za-z\-_]{20,})\b/g, captureIndex: 1, slugHint: "google_api_key" },
|
|
73064
|
-
{ rule_id: "perplexity_api_key", regex: /\b(pplx-[A-Za-z0-9_-]{10,})\b/g, captureIndex: 1, slugHint: "perplexity_api_key" },
|
|
73065
|
-
{ rule_id: "npm_token", regex: /\b(npm_[A-Za-z0-9]{10,})\b/g, captureIndex: 1, slugHint: "npm_token" },
|
|
73066
|
-
{ rule_id: "telegram_bot_token_prefixed", regex: /\bbot(\d{6,}:[A-Za-z0-9_-]{20,})\b/g, captureIndex: 1, slugHint: "telegram_bot_token" },
|
|
73067
|
-
{ rule_id: "telegram_bot_token", regex: /\b(\d{6,}:[A-Za-z0-9_-]{20,})\b/g, captureIndex: 1, slugHint: "telegram_bot_token" },
|
|
73068
|
-
{ rule_id: "laravel_sanctum_token", regex: /\b(\d+\|[A-Za-z0-9]{40,})\b/g, captureIndex: 1, slugHint: "api_token" },
|
|
73069
|
-
{ rule_id: "aws_access_key", regex: /\b(AKIA[0-9A-Z]{16})\b/g, captureIndex: 1, slugHint: "aws_access_key" },
|
|
73070
|
-
{ rule_id: "jwt", regex: /\b(eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,})\b/g, captureIndex: 1, slugHint: "jwt" }
|
|
73071
|
-
];
|
|
73072
|
-
var STRUCTURED_PATTERNS = [
|
|
73073
|
-
{
|
|
73074
|
-
rule_id: "env_key_value",
|
|
73075
|
-
regex: /\b([A-Z0-9_]*(?:KEY|TOKEN|SECRET|PASSWORD|PASSWD))\b\s*[=:]\s*(["']?)([^\s"'\\]+)\2/g,
|
|
73076
|
-
captureIndex: 3,
|
|
73077
|
-
slugHint: "env"
|
|
73078
|
-
},
|
|
73079
|
-
{
|
|
73080
|
-
rule_id: "json_secret_field",
|
|
73081
|
-
regex: /"(?:apiKey|token|secret|password|passwd|accessToken|refreshToken)"\s*:\s*"([^"]+)"/g,
|
|
73082
|
-
captureIndex: 1,
|
|
73083
|
-
slugHint: "json_secret"
|
|
73084
|
-
},
|
|
73085
|
-
{
|
|
73086
|
-
rule_id: "cli_flag",
|
|
73087
|
-
regex: /--(?:api[-_]?key|hook[-_]?token|token|secret|password|passwd)\s+(["']?)([^\s"']+)\1/g,
|
|
73088
|
-
captureIndex: 2,
|
|
73089
|
-
slugHint: "cli_flag"
|
|
73090
|
-
},
|
|
73091
|
-
{
|
|
73092
|
-
rule_id: "bearer_auth_header",
|
|
73093
|
-
regex: /Authorization\s*[:=]\s*Bearer\s+([A-Za-z0-9._\-+=]+)/gi,
|
|
73094
|
-
captureIndex: 1,
|
|
73095
|
-
slugHint: "bearer_token"
|
|
73096
|
-
},
|
|
73097
|
-
{
|
|
73098
|
-
rule_id: "bearer_loose",
|
|
73099
|
-
regex: /\bBearer\s+([A-Za-z0-9._\-+=]{18,})\b/gi,
|
|
73100
|
-
captureIndex: 1,
|
|
73101
|
-
slugHint: "bearer_token"
|
|
73102
|
-
},
|
|
73103
|
-
{
|
|
73104
|
-
rule_id: "basic_auth_header",
|
|
73105
|
-
regex: /Authorization\s*[:=]\s*Basic\s+([A-Za-z0-9+/=]{8,})/gi,
|
|
73106
|
-
captureIndex: 1,
|
|
73107
|
-
slugHint: "basic_auth"
|
|
73108
|
-
},
|
|
73109
|
-
{
|
|
73110
|
-
rule_id: "pem_private_key",
|
|
73111
|
-
regex: /-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]+?-----END [A-Z ]*PRIVATE KEY-----/g,
|
|
73112
|
-
captureIndex: 0,
|
|
73113
|
-
slugHint: "pem_private_key"
|
|
73114
|
-
}
|
|
73115
|
-
];
|
|
73116
|
-
var PROVIDER_PATTERNS = [
|
|
73117
|
-
{ rule_id: "slack_webhook", regex: /(https:\/\/hooks\.slack\.com\/services\/[A-Za-z0-9_/]+)/g, captureIndex: 1, slugHint: "slack_webhook" },
|
|
73118
|
-
{ rule_id: "stripe_live_secret", regex: /\b(sk_live_[A-Za-z0-9]{24,})\b/g, captureIndex: 1, slugHint: "stripe_key" },
|
|
73119
|
-
{ rule_id: "stripe_restricted", regex: /\b(rk_live_[A-Za-z0-9]{24,})\b/g, captureIndex: 1, slugHint: "stripe_key" },
|
|
73120
|
-
{ rule_id: "sendgrid_api_key", regex: /\b(SG\.[A-Za-z0-9_-]{22}\.[A-Za-z0-9_-]{43})\b/g, captureIndex: 1, slugHint: "sendgrid_key" },
|
|
73121
|
-
{ rule_id: "gitlab_pat", regex: /\b(glpat-[A-Za-z0-9_-]{20})\b/g, captureIndex: 1, slugHint: "gitlab_pat" },
|
|
73122
|
-
{ rule_id: "huggingface_token", regex: /\b(hf_[A-Za-z0-9]{34,})\b/g, captureIndex: 1, slugHint: "huggingface_token" },
|
|
73123
|
-
{ rule_id: "twilio_api_key", regex: /\b(SK[0-9a-f]{32})\b/g, captureIndex: 1, slugHint: "twilio_api_key" },
|
|
73124
|
-
{ rule_id: "mailgun_key", regex: /\b(key-[0-9a-f]{32})\b/g, captureIndex: 1, slugHint: "mailgun_key" },
|
|
73125
|
-
{ rule_id: "digitalocean_pat", regex: /\b(dop_v1_[a-f0-9]{64})\b/g, captureIndex: 1, slugHint: "digitalocean_token" },
|
|
73126
|
-
{ rule_id: "digitalocean_oauth", regex: /\b(doo_v1_[a-f0-9]{64})\b/g, captureIndex: 1, slugHint: "digitalocean_token" },
|
|
73127
|
-
{ rule_id: "digitalocean_refresh", regex: /\b(dor_v1_[a-f0-9]{64})\b/g, captureIndex: 1, slugHint: "digitalocean_token" },
|
|
73128
|
-
{ rule_id: "doppler_token", regex: /\b(dp\.(?:pt|st|ct|sa|scim|audit)\.[A-Za-z0-9]{40,44})\b/g, captureIndex: 1, slugHint: "doppler_token" },
|
|
73129
|
-
{ rule_id: "linear_api_key", regex: /\b(lin_api_[A-Za-z0-9]{40})\b/g, captureIndex: 1, slugHint: "linear_api_key" },
|
|
73130
|
-
{ rule_id: "shopify_access_token", regex: /\b(shpat_[a-fA-F0-9]{32})\b/g, captureIndex: 1, slugHint: "shopify_token" },
|
|
73131
|
-
{ rule_id: "shopify_shared_secret", regex: /\b(shpss_[a-fA-F0-9]{32})\b/g, captureIndex: 1, slugHint: "shopify_token" },
|
|
73132
|
-
{ rule_id: "shopify_private_app", regex: /\b(shppa_[a-fA-F0-9]{32})\b/g, captureIndex: 1, slugHint: "shopify_token" },
|
|
73133
|
-
{ rule_id: "square_access_token", regex: /\b(sq0atp-[A-Za-z0-9_-]{22})\b/g, captureIndex: 1, slugHint: "square_token" },
|
|
73134
|
-
{ rule_id: "square_oauth_secret", regex: /\b(sq0csp-[A-Za-z0-9_-]{43})\b/g, captureIndex: 1, slugHint: "square_token" },
|
|
73135
|
-
{ rule_id: "newrelic_key", regex: /\b(NRAK-[A-Z0-9]{27})\b/g, captureIndex: 1, slugHint: "newrelic_key" },
|
|
73136
|
-
{ rule_id: "notion_token", regex: /\b(ntn_[A-Za-z0-9]{46})\b/g, captureIndex: 1, slugHint: "notion_token" },
|
|
73137
|
-
{ rule_id: "planetscale_password", regex: /\b(pscale_pw_[A-Za-z0-9_.-]{43})\b/g, captureIndex: 1, slugHint: "planetscale_token" },
|
|
73138
|
-
{ rule_id: "planetscale_token", regex: /\b(pscale_tkn_[A-Za-z0-9_.-]{43})\b/g, captureIndex: 1, slugHint: "planetscale_token" },
|
|
73139
|
-
{ rule_id: "supabase_service_key", regex: /\b(sbp_[a-f0-9]{40})\b/g, captureIndex: 1, slugHint: "supabase_key" },
|
|
73140
|
-
{ rule_id: "atlassian_token", regex: /\b(ATATT[A-Za-z0-9_\-=]{20,})\b/g, captureIndex: 1, slugHint: "atlassian_token" },
|
|
73141
|
-
{ rule_id: "dropbox_token", regex: /\b(sl\.[A-Za-z0-9_-]{130,})/g, captureIndex: 1, slugHint: "dropbox_token" },
|
|
73142
|
-
{ rule_id: "databricks_token", regex: /\b(dapi[a-f0-9]{32})\b/g, captureIndex: 1, slugHint: "databricks_token" },
|
|
73143
|
-
{ rule_id: "grafana_service_account", regex: /\b(glsa_[A-Za-z0-9]{32}_[a-fA-F0-9]{8})\b/g, captureIndex: 1, slugHint: "grafana_token" },
|
|
73144
|
-
{ rule_id: "pypi_token", regex: /\b(pypi-AgEIcHlwaS[A-Za-z0-9_-]{50,})/g, captureIndex: 1, slugHint: "pypi_token" },
|
|
73145
|
-
{ rule_id: "aws_temp_access_key", regex: /\b(ASIA[0-9A-Z]{16})\b/g, captureIndex: 1, slugHint: "aws_access_key" },
|
|
73146
|
-
{ rule_id: "gcp_oauth_token", regex: /\b(ya29\.[A-Za-z0-9_-]{30,})/g, captureIndex: 1, slugHint: "gcp_oauth_token" }
|
|
73147
|
-
];
|
|
73148
|
-
var ALL_PATTERNS = [...ANCHORED_PATTERNS, ...PROVIDER_PATTERNS, ...STRUCTURED_PATTERNS];
|
|
73149
|
-
|
|
73150
|
-
// telegram-plugin/secret-detect/entropy.ts
|
|
73151
|
-
function shannonEntropy(s) {
|
|
73152
|
-
if (s.length === 0)
|
|
73153
|
-
return 0;
|
|
73154
|
-
const counts = new Map;
|
|
73155
|
-
for (const ch of s) {
|
|
73156
|
-
counts.set(ch, (counts.get(ch) ?? 0) + 1);
|
|
73157
|
-
}
|
|
73158
|
-
let h = 0;
|
|
73159
|
-
const len = s.length;
|
|
73160
|
-
for (const c of counts.values()) {
|
|
73161
|
-
const p = c / len;
|
|
73162
|
-
h -= p * Math.log2(p);
|
|
73163
|
-
}
|
|
73164
|
-
return h;
|
|
73165
|
-
}
|
|
73166
|
-
|
|
73167
|
-
// telegram-plugin/secret-detect/inert-values.ts
|
|
73168
|
-
var INERT_VALUE_RE = [
|
|
73169
|
-
/^\[REDACTED(?::[A-Za-z0-9_]+)?\]$/i,
|
|
73170
|
-
/^\$\{?[A-Za-z_][A-Za-z0-9_]*\}?$/,
|
|
73171
|
-
/^\{\{[^\n\r\u2028\u2029]*\}\}$/,
|
|
73172
|
-
/^<[^<>\n\r\u2028\u2029]*>$/,
|
|
73173
|
-
/^%[A-Za-z_][A-Za-z0-9_]*%$/,
|
|
73174
|
-
/^vault:[A-Za-z0-9_./-]*$/i,
|
|
73175
|
-
/^[*x\u2022.]+$/i,
|
|
73176
|
-
/^(?:process\.env|import\.meta\.env|os\.environ)(?:\.[A-Za-z_$][A-Za-z0-9_$]*|\[["']?[A-Za-z_$][A-Za-z0-9_$]*["']?\])*$/,
|
|
73177
|
-
/^(?:changeme|change-me|replaceme|replace-me|placeholder|todo|tbd|yourkey|your-key|yourpassword|your-password|yoursecret|your-secret|yourtoken|your-token)(?:[-_][A-Za-z0-9-]+)?$/i,
|
|
73178
|
-
/^[a-z]+(?: [a-z]+){2,}$/
|
|
73179
|
-
];
|
|
73180
|
-
var CREDENTIAL_RUN_RE = /[A-Za-z0-9]{12,}/g;
|
|
73181
|
-
var MIXED_CLASS_RUN_MIN = 12;
|
|
73182
|
-
var SINGLE_CLASS_RUN_MIN = 16;
|
|
73183
|
-
function runIsCredentialShaped(run) {
|
|
73184
|
-
let classes = 0;
|
|
73185
|
-
if (/[a-z]/.test(run))
|
|
73186
|
-
classes++;
|
|
73187
|
-
if (/[A-Z]/.test(run))
|
|
73188
|
-
classes++;
|
|
73189
|
-
if (/[0-9]/.test(run))
|
|
73190
|
-
classes++;
|
|
73191
|
-
return run.length >= (classes >= 2 ? MIXED_CLASS_RUN_MIN : SINGLE_CLASS_RUN_MIN);
|
|
73192
|
-
}
|
|
73193
|
-
function hasCredentialShapedRun(value) {
|
|
73194
|
-
for (const run of value.match(CREDENTIAL_RUN_RE) ?? []) {
|
|
73195
|
-
if (runIsCredentialShaped(run))
|
|
73196
|
-
return true;
|
|
73031
|
+
var DEFAULT_MAX_TURNS = 50;
|
|
73032
|
+
var TOPIC_MAX_CHARS = 117;
|
|
73033
|
+
var TURN_TEXT_MAX_CHARS = 1200;
|
|
73034
|
+
function extractTurnsFromJsonl(path2, maxTurns) {
|
|
73035
|
+
let raw;
|
|
73036
|
+
try {
|
|
73037
|
+
raw = readFileSync20(path2, "utf-8");
|
|
73038
|
+
} catch {
|
|
73039
|
+
return [];
|
|
73197
73040
|
}
|
|
73198
|
-
|
|
73199
|
-
|
|
73200
|
-
|
|
73201
|
-
|
|
73202
|
-
return false;
|
|
73203
|
-
return !hasCredentialShapedRun(value);
|
|
73204
|
-
}
|
|
73205
|
-
var INERT_GATED_RULES = new Set([
|
|
73206
|
-
"env_key_value",
|
|
73207
|
-
"json_secret_field",
|
|
73208
|
-
"cli_flag"
|
|
73209
|
-
]);
|
|
73210
|
-
var TRAILING_PUNCT_RE = /[.,;:!?)\]}]+$/;
|
|
73211
|
-
function stripTrailingPunctuation(value) {
|
|
73212
|
-
return value.replace(TRAILING_PUNCT_RE, "");
|
|
73213
|
-
}
|
|
73214
|
-
|
|
73215
|
-
// telegram-plugin/secret-detect/kv-scanner.ts
|
|
73216
|
-
var KV_RE = /\b([A-Za-z_][A-Za-z0-9_-]*(?:password|passwd|token|secret|key|api[_-]?key))\s*[:=]\s*["']?([^\s"'\\]{8,})["']?/gi;
|
|
73217
|
-
var KV_ENTROPY_THRESHOLD = 4;
|
|
73218
|
-
var MEMORABLE_PW_RE = /\b([A-Za-z0-9_-]*(?:password|passwd|passphrase|pwd))\b\s*(?:[:=]\s*|\s+is\s+)(["']?)([^\s"']{8,64})\2/gi;
|
|
73219
|
-
var MEMORABLE_PW_RULE_ID = "memorable_password";
|
|
73220
|
-
var MEMORABLE_PW_MIN_CLASSES = 2;
|
|
73221
|
-
function charClassCount(value) {
|
|
73222
|
-
let n = 0;
|
|
73223
|
-
if (/[a-z]/.test(value))
|
|
73224
|
-
n++;
|
|
73225
|
-
if (/[A-Z]/.test(value))
|
|
73226
|
-
n++;
|
|
73227
|
-
if (/[0-9]/.test(value))
|
|
73228
|
-
n++;
|
|
73229
|
-
if (/[^A-Za-z0-9]/.test(value))
|
|
73230
|
-
n++;
|
|
73231
|
-
return n;
|
|
73232
|
-
}
|
|
73233
|
-
function looksLikeMemorablePassword(value) {
|
|
73234
|
-
if (isInertValue(value))
|
|
73235
|
-
return false;
|
|
73236
|
-
const core = stripTrailingPunctuation(value);
|
|
73237
|
-
if (core.length < 8 || core.length > 64)
|
|
73238
|
-
return false;
|
|
73239
|
-
if (isInertValue(core))
|
|
73240
|
-
return false;
|
|
73241
|
-
if (new Set(core).size < 4)
|
|
73242
|
-
return false;
|
|
73243
|
-
return charClassCount(core) >= MEMORABLE_PW_MIN_CLASSES;
|
|
73244
|
-
}
|
|
73245
|
-
function scanMemorablePasswords(text) {
|
|
73246
|
-
const hits = [];
|
|
73247
|
-
MEMORABLE_PW_RE.lastIndex = 0;
|
|
73248
|
-
let m;
|
|
73249
|
-
while ((m = MEMORABLE_PW_RE.exec(text)) !== null) {
|
|
73250
|
-
const keyName = m[1];
|
|
73251
|
-
const value = m[3];
|
|
73252
|
-
if (!value || !looksLikeMemorablePassword(value))
|
|
73253
|
-
continue;
|
|
73254
|
-
const valueOffsetInMatch = m[0].indexOf(value, keyName.length);
|
|
73255
|
-
if (valueOffsetInMatch < 0)
|
|
73041
|
+
const turns = [];
|
|
73042
|
+
for (const line of raw.split(`
|
|
73043
|
+
`)) {
|
|
73044
|
+
if (!line.trim())
|
|
73256
73045
|
continue;
|
|
73257
|
-
|
|
73258
|
-
|
|
73259
|
-
|
|
73260
|
-
|
|
73261
|
-
end: start + value.length,
|
|
73262
|
-
matched_text: value,
|
|
73263
|
-
key_name: keyName,
|
|
73264
|
-
confidence: "ambiguous"
|
|
73265
|
-
});
|
|
73266
|
-
}
|
|
73267
|
-
return hits;
|
|
73268
|
-
}
|
|
73269
|
-
function scanKeyValue(text) {
|
|
73270
|
-
const hits = [];
|
|
73271
|
-
KV_RE.lastIndex = 0;
|
|
73272
|
-
let m;
|
|
73273
|
-
while ((m = KV_RE.exec(text)) !== null) {
|
|
73274
|
-
const [, keyName, value] = m;
|
|
73275
|
-
if (!value)
|
|
73046
|
+
let obj;
|
|
73047
|
+
try {
|
|
73048
|
+
obj = JSON.parse(line);
|
|
73049
|
+
} catch {
|
|
73276
73050
|
continue;
|
|
73277
|
-
|
|
73051
|
+
}
|
|
73052
|
+
if (obj.type === "queue-operation") {
|
|
73053
|
+
const op = obj.operation;
|
|
73054
|
+
if (op !== "enqueue")
|
|
73055
|
+
continue;
|
|
73056
|
+
const content = obj.content;
|
|
73057
|
+
if (typeof content !== "string")
|
|
73058
|
+
continue;
|
|
73059
|
+
const text = extractChannelBody(content);
|
|
73060
|
+
if (text)
|
|
73061
|
+
turns.push({ role: "user", text });
|
|
73278
73062
|
continue;
|
|
73279
|
-
|
|
73280
|
-
if (
|
|
73063
|
+
}
|
|
73064
|
+
if (obj.type === "user" && obj.message && typeof obj.message === "object") {
|
|
73065
|
+
const content = obj.message.content;
|
|
73066
|
+
const raw2 = extractTextBlocks(content);
|
|
73067
|
+
const text = raw2 ? extractChannelBody(raw2) : null;
|
|
73068
|
+
if (text)
|
|
73069
|
+
turns.push({ role: "user", text });
|
|
73281
73070
|
continue;
|
|
73282
|
-
|
|
73283
|
-
if (
|
|
73071
|
+
}
|
|
73072
|
+
if (obj.type === "assistant" && obj.message && typeof obj.message === "object") {
|
|
73073
|
+
const content = obj.message.content;
|
|
73074
|
+
const text = extractTextBlocks(content);
|
|
73075
|
+
if (text)
|
|
73076
|
+
turns.push({ role: "assistant", text });
|
|
73284
73077
|
continue;
|
|
73285
|
-
|
|
73286
|
-
const end = start + value.length;
|
|
73287
|
-
hits.push({
|
|
73288
|
-
rule_id: "kv_entropy",
|
|
73289
|
-
start,
|
|
73290
|
-
end,
|
|
73291
|
-
matched_text: value,
|
|
73292
|
-
key_name: keyName,
|
|
73293
|
-
confidence: "ambiguous"
|
|
73294
|
-
});
|
|
73078
|
+
}
|
|
73295
73079
|
}
|
|
73296
|
-
|
|
73297
|
-
|
|
73298
|
-
|
|
73299
|
-
|
|
73300
|
-
var DB_URI_RE = /\b([a-zA-Z][a-zA-Z0-9+.-]+):\/\/([^\s:/@]+):([^\s/?#]+)@([^\s/@?#]+)/g;
|
|
73301
|
-
var DB_URI_RULE_ID = "db_uri_password";
|
|
73302
|
-
function isInertPassword(value) {
|
|
73303
|
-
if (value.startsWith("[REDACTED"))
|
|
73304
|
-
return true;
|
|
73305
|
-
return /^[*x]+$/i.test(value);
|
|
73306
|
-
}
|
|
73307
|
-
function scanDbUris(text) {
|
|
73308
|
-
const hits = [];
|
|
73309
|
-
DB_URI_RE.lastIndex = 0;
|
|
73310
|
-
let m;
|
|
73311
|
-
while ((m = DB_URI_RE.exec(text)) !== null) {
|
|
73312
|
-
const scheme = m[1];
|
|
73313
|
-
const user = m[2];
|
|
73314
|
-
const password = m[3];
|
|
73315
|
-
if (isInertPassword(password))
|
|
73080
|
+
const deduped = [];
|
|
73081
|
+
for (const t of turns) {
|
|
73082
|
+
const prev = deduped[deduped.length - 1];
|
|
73083
|
+
if (prev && prev.role === t.role && prev.text === t.text)
|
|
73316
73084
|
continue;
|
|
73317
|
-
|
|
73318
|
-
hits.push({
|
|
73319
|
-
rule_id: DB_URI_RULE_ID,
|
|
73320
|
-
start,
|
|
73321
|
-
end: start + password.length,
|
|
73322
|
-
matched_text: password,
|
|
73323
|
-
key_name: `${scheme}_password`,
|
|
73324
|
-
confidence: "ambiguous"
|
|
73325
|
-
});
|
|
73085
|
+
deduped.push(t);
|
|
73326
73086
|
}
|
|
73327
|
-
|
|
73087
|
+
if (deduped.length <= maxTurns)
|
|
73088
|
+
return deduped;
|
|
73089
|
+
return deduped.slice(deduped.length - maxTurns);
|
|
73328
73090
|
}
|
|
73329
|
-
|
|
73330
|
-
|
|
73331
|
-
|
|
73332
|
-
|
|
73333
|
-
|
|
73334
|
-
|
|
73335
|
-
const seen = new Uint8Array(128);
|
|
73336
|
-
let distinct = 0;
|
|
73337
|
-
for (let i = 0;i < tok.length; i++) {
|
|
73338
|
-
const c = tok.charCodeAt(i);
|
|
73339
|
-
if (seen[c] === 0) {
|
|
73340
|
-
seen[c] = 1;
|
|
73341
|
-
if (++distinct >= n)
|
|
73342
|
-
return true;
|
|
73343
|
-
}
|
|
73344
|
-
}
|
|
73345
|
-
return false;
|
|
73091
|
+
function extractChannelBody(raw) {
|
|
73092
|
+
const m = raw.match(/<channel[^>]*>([\s\S]*?)<\/channel>/);
|
|
73093
|
+
if (m)
|
|
73094
|
+
return m[1].trim();
|
|
73095
|
+
const trimmed = raw.trim();
|
|
73096
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
73346
73097
|
}
|
|
73347
|
-
function
|
|
73348
|
-
|
|
73349
|
-
const
|
|
73350
|
-
|
|
73351
|
-
return true;
|
|
73098
|
+
function extractTextBlocks(content) {
|
|
73099
|
+
if (typeof content === "string") {
|
|
73100
|
+
const t = content.trim();
|
|
73101
|
+
return t.length > 0 ? t : null;
|
|
73352
73102
|
}
|
|
73353
|
-
|
|
73354
|
-
|
|
73355
|
-
|
|
73356
|
-
const
|
|
73357
|
-
|
|
73358
|
-
let m;
|
|
73359
|
-
while ((m = CANDIDATE_RE.exec(text)) !== null) {
|
|
73360
|
-
if (hits.length >= MAX_GENERIC_HITS)
|
|
73361
|
-
break;
|
|
73362
|
-
const tok = m[0];
|
|
73363
|
-
if (!hasDigit(tok))
|
|
73364
|
-
continue;
|
|
73365
|
-
if (!hasDistinctChars(tok, GENERIC_MIN_DISTINCT))
|
|
73103
|
+
if (!Array.isArray(content))
|
|
73104
|
+
return null;
|
|
73105
|
+
const parts = [];
|
|
73106
|
+
for (const block of content) {
|
|
73107
|
+
if (!block || typeof block !== "object")
|
|
73366
73108
|
continue;
|
|
73367
|
-
|
|
73368
|
-
|
|
73369
|
-
|
|
73370
|
-
|
|
73371
|
-
|
|
73372
|
-
|
|
73373
|
-
});
|
|
73109
|
+
const b = block;
|
|
73110
|
+
if (b.type === "text" && typeof b.text === "string") {
|
|
73111
|
+
parts.push(b.text);
|
|
73112
|
+
} else if (b.type === "tool_result" && typeof b.content === "string") {
|
|
73113
|
+
parts.push(`[tool result] ${b.content.slice(0, 400)}`);
|
|
73114
|
+
}
|
|
73374
73115
|
}
|
|
73375
|
-
|
|
73116
|
+
const joined = parts.join(`
|
|
73117
|
+
`).trim();
|
|
73118
|
+
return joined.length > 0 ? joined : null;
|
|
73376
73119
|
}
|
|
73120
|
+
function formatTranscriptTail(turns) {
|
|
73121
|
+
const header = `# Handoff \u2014 previous session
|
|
73377
73122
|
|
|
73378
|
-
|
|
73379
|
-
|
|
73380
|
-
|
|
73381
|
-
|
|
73382
|
-
|
|
73383
|
-
|
|
73384
|
-
return [{ offset: 0, text }];
|
|
73385
|
-
}
|
|
73386
|
-
const out = [];
|
|
73387
|
-
let offset = 0;
|
|
73388
|
-
while (offset < text.length) {
|
|
73389
|
-
const end = Math.min(offset + WINDOW_SIZE, text.length);
|
|
73390
|
-
out.push({ offset, text: text.slice(offset, end) });
|
|
73391
|
-
if (end >= text.length)
|
|
73392
|
-
break;
|
|
73393
|
-
offset = end - OVERLAP;
|
|
73123
|
+
` + "You are resuming this agent's work. There is no generated summary " + "\u2014 below is the **raw tail of the previous session's transcript** " + "(oldest first, most recent last). Read it to reorient, then carry " + "on. Anything important worth keeping long-term should already be " + `in your memory files \u2014 check those too.
|
|
73124
|
+
`;
|
|
73125
|
+
if (turns.length === 0) {
|
|
73126
|
+
return header + `
|
|
73127
|
+
_(No recent turns were recoverable from the previous session.)_
|
|
73128
|
+
`;
|
|
73394
73129
|
}
|
|
73395
|
-
|
|
73396
|
-
|
|
73130
|
+
const body = turns.map((t) => {
|
|
73131
|
+
let text = t.text;
|
|
73132
|
+
if (text.length > TURN_TEXT_MAX_CHARS) {
|
|
73133
|
+
text = text.slice(0, TURN_TEXT_MAX_CHARS) + `
|
|
73134
|
+
\u2026[truncated]`;
|
|
73135
|
+
}
|
|
73136
|
+
return `### ${t.role === "user" ? "User" : "Assistant"}
|
|
73137
|
+
${text}`;
|
|
73138
|
+
}).join(`
|
|
73397
73139
|
|
|
73398
|
-
|
|
73399
|
-
|
|
73400
|
-
|
|
73401
|
-
var MARKER_RE = new RegExp(`\\b(?:${MARKERS.join("|")})\\b`, "i");
|
|
73402
|
-
function isSuppressed(text, start, end) {
|
|
73403
|
-
const left = Math.max(0, start - WINDOW);
|
|
73404
|
-
const right = Math.min(text.length, end + WINDOW);
|
|
73405
|
-
const context = text.slice(left, start) + text.slice(end, right);
|
|
73406
|
-
return MARKER_RE.test(context);
|
|
73407
|
-
}
|
|
73140
|
+
`);
|
|
73141
|
+
return `${header}
|
|
73142
|
+
## Recent turns
|
|
73408
73143
|
|
|
73409
|
-
|
|
73410
|
-
|
|
73411
|
-
const up = raw.toUpperCase();
|
|
73412
|
-
const cleaned = up.replace(/[^A-Z0-9_]+/g, "_").replace(/_+/g, "_").replace(/^_+|_+$/g, "");
|
|
73413
|
-
return cleaned.length > 0 ? cleaned : "SECRET";
|
|
73414
|
-
}
|
|
73415
|
-
function datePart(now = new Date) {
|
|
73416
|
-
const y = now.getUTCFullYear();
|
|
73417
|
-
const m = String(now.getUTCMonth() + 1).padStart(2, "0");
|
|
73418
|
-
const d = String(now.getUTCDate()).padStart(2, "0");
|
|
73419
|
-
return `${y}${m}${d}`;
|
|
73144
|
+
${body}
|
|
73145
|
+
`;
|
|
73420
73146
|
}
|
|
73421
|
-
function
|
|
73422
|
-
let
|
|
73423
|
-
|
|
73424
|
-
|
|
73425
|
-
} else {
|
|
73426
|
-
base = `${inputs.rule_id}_${datePart(inputs.now)}`;
|
|
73147
|
+
function deriveTopic(turns) {
|
|
73148
|
+
for (let i = turns.length - 1;i >= 0; i--) {
|
|
73149
|
+
if (turns[i].role === "user")
|
|
73150
|
+
return clampTopic(firstLine(turns[i].text));
|
|
73427
73151
|
}
|
|
73428
|
-
if (
|
|
73429
|
-
return
|
|
73430
|
-
|
|
73431
|
-
while (existing.has(`${base}_${n}`))
|
|
73432
|
-
n++;
|
|
73433
|
-
return `${base}_${n}`;
|
|
73152
|
+
if (turns.length > 0)
|
|
73153
|
+
return clampTopic(firstLine(turns[turns.length - 1].text));
|
|
73154
|
+
return "previous session";
|
|
73434
73155
|
}
|
|
73435
|
-
|
|
73436
|
-
|
|
73437
|
-
|
|
73438
|
-
"key",
|
|
73439
|
-
"api_key",
|
|
73440
|
-
"apikey",
|
|
73441
|
-
"secret",
|
|
73442
|
-
"access_token",
|
|
73443
|
-
"password",
|
|
73444
|
-
"pass",
|
|
73445
|
-
"auth",
|
|
73446
|
-
"client_secret",
|
|
73447
|
-
"refresh_token",
|
|
73448
|
-
"signature"
|
|
73449
|
-
]);
|
|
73450
|
-
var URL_RE = /\b(?:https?|wss?|ftp):\/\/[^\s<>"']+/gi;
|
|
73451
|
-
function redactUrls(text) {
|
|
73452
|
-
return text.replace(URL_RE, (m) => {
|
|
73453
|
-
const redacted = redactOne(m);
|
|
73454
|
-
return redacted ?? m;
|
|
73455
|
-
});
|
|
73156
|
+
function firstLine(s) {
|
|
73157
|
+
const line = s.split(/\r?\n/).find((l) => l.trim().length > 0) ?? s;
|
|
73158
|
+
return line.trim();
|
|
73456
73159
|
}
|
|
73457
|
-
function
|
|
73458
|
-
|
|
73459
|
-
try {
|
|
73460
|
-
u = new URL(raw);
|
|
73461
|
-
} catch {
|
|
73462
|
-
return null;
|
|
73463
|
-
}
|
|
73464
|
-
let changed = false;
|
|
73465
|
-
if (u.username || u.password) {
|
|
73466
|
-
u.username = "***";
|
|
73467
|
-
u.password = "";
|
|
73468
|
-
changed = true;
|
|
73469
|
-
}
|
|
73470
|
-
for (const [key] of u.searchParams) {
|
|
73471
|
-
if (SENSITIVE_PARAMS.has(key.toLowerCase())) {
|
|
73472
|
-
u.searchParams.set(key, "***");
|
|
73473
|
-
changed = true;
|
|
73474
|
-
}
|
|
73475
|
-
}
|
|
73476
|
-
return changed ? u.toString() : null;
|
|
73477
|
-
}
|
|
73478
|
-
|
|
73479
|
-
// telegram-plugin/secret-detect/index.ts
|
|
73480
|
-
function detectSecrets(text) {
|
|
73481
|
-
if (!text || text.length === 0)
|
|
73482
|
-
return [];
|
|
73483
|
-
const windows = chunk(text);
|
|
73484
|
-
const raw = [];
|
|
73485
|
-
for (const win of windows) {
|
|
73486
|
-
for (const p of ALL_PATTERNS) {
|
|
73487
|
-
const re = new RegExp(p.regex.source, p.regex.flags.includes("g") ? p.regex.flags : p.regex.flags + "g");
|
|
73488
|
-
let m;
|
|
73489
|
-
while ((m = re.exec(win.text)) !== null) {
|
|
73490
|
-
if (m[0].length === 0) {
|
|
73491
|
-
re.lastIndex++;
|
|
73492
|
-
continue;
|
|
73493
|
-
}
|
|
73494
|
-
const cap = p.captureIndex === 0 ? m[0] : m[p.captureIndex];
|
|
73495
|
-
if (!cap)
|
|
73496
|
-
continue;
|
|
73497
|
-
const matchStart = p.captureIndex === 0 ? m.index : m.index + m[0].indexOf(cap);
|
|
73498
|
-
if (matchStart < 0)
|
|
73499
|
-
continue;
|
|
73500
|
-
const globalStart = win.offset + matchStart;
|
|
73501
|
-
const globalEnd = globalStart + cap.length;
|
|
73502
|
-
const keyName = p.rule_id === "env_key_value" ? m[1] : undefined;
|
|
73503
|
-
if (INERT_GATED_RULES.has(p.rule_id) && isInertValue(cap))
|
|
73504
|
-
continue;
|
|
73505
|
-
if (p.rule_id === "env_key_value") {
|
|
73506
|
-
const ENV_KV_MIN_LEN = 12;
|
|
73507
|
-
const ENV_KV_MIN_ENTROPY = 3.5;
|
|
73508
|
-
if (cap.length < ENV_KV_MIN_LEN)
|
|
73509
|
-
continue;
|
|
73510
|
-
if (shannonEntropy(cap) < ENV_KV_MIN_ENTROPY)
|
|
73511
|
-
continue;
|
|
73512
|
-
}
|
|
73513
|
-
raw.push({
|
|
73514
|
-
rule_id: p.rule_id,
|
|
73515
|
-
start: globalStart,
|
|
73516
|
-
end: globalEnd,
|
|
73517
|
-
matched_text: cap,
|
|
73518
|
-
key_name: keyName,
|
|
73519
|
-
confidence: "high"
|
|
73520
|
-
});
|
|
73521
|
-
}
|
|
73522
|
-
}
|
|
73523
|
-
const kvHits = scanKeyValue(win.text);
|
|
73524
|
-
for (const h of kvHits) {
|
|
73525
|
-
raw.push({ ...h, start: h.start + win.offset, end: h.end + win.offset });
|
|
73526
|
-
}
|
|
73527
|
-
for (const h of scanDbUris(win.text)) {
|
|
73528
|
-
raw.push({ ...h, start: h.start + win.offset, end: h.end + win.offset });
|
|
73529
|
-
}
|
|
73530
|
-
for (const h of scanMemorablePasswords(win.text)) {
|
|
73531
|
-
raw.push({ ...h, start: h.start + win.offset, end: h.end + win.offset });
|
|
73532
|
-
}
|
|
73533
|
-
const genHits = scanGenericSecrets(win.text);
|
|
73534
|
-
for (const h of genHits) {
|
|
73535
|
-
raw.push({ ...h, start: h.start + win.offset, end: h.end + win.offset });
|
|
73536
|
-
}
|
|
73537
|
-
}
|
|
73538
|
-
const deduped = dedupeRaw(raw);
|
|
73539
|
-
const final = dropOverlaps(deduped);
|
|
73540
|
-
const existing = new Set;
|
|
73541
|
-
const out = [];
|
|
73542
|
-
for (const h of final) {
|
|
73543
|
-
const suggested_slug = deriveSlug({ key_name: h.key_name, rule_id: h.rule_id }, existing);
|
|
73544
|
-
existing.add(suggested_slug);
|
|
73545
|
-
out.push({
|
|
73546
|
-
rule_id: h.rule_id,
|
|
73547
|
-
matched_text: h.matched_text,
|
|
73548
|
-
start: h.start,
|
|
73549
|
-
end: h.end,
|
|
73550
|
-
confidence: h.confidence,
|
|
73551
|
-
suppressed: isSuppressed(text, h.start, h.end),
|
|
73552
|
-
suggested_slug,
|
|
73553
|
-
key_name: h.key_name
|
|
73554
|
-
});
|
|
73555
|
-
}
|
|
73556
|
-
out.sort((a, b) => a.start - b.start);
|
|
73557
|
-
return out;
|
|
73558
|
-
}
|
|
73559
|
-
function dedupeRaw(raw) {
|
|
73560
|
-
const seen = new Map;
|
|
73561
|
-
for (const h of raw) {
|
|
73562
|
-
const key = `${h.start}:${h.end}`;
|
|
73563
|
-
const existing = seen.get(key);
|
|
73564
|
-
if (!existing) {
|
|
73565
|
-
seen.set(key, h);
|
|
73566
|
-
continue;
|
|
73567
|
-
}
|
|
73568
|
-
if (existing.confidence === "ambiguous" && h.confidence === "high") {
|
|
73569
|
-
seen.set(key, h);
|
|
73570
|
-
}
|
|
73571
|
-
}
|
|
73572
|
-
return Array.from(seen.values());
|
|
73573
|
-
}
|
|
73574
|
-
function dropOverlaps(hits) {
|
|
73575
|
-
const out = hits.filter((h) => !(h.confidence === "ambiguous" && hits.some((o) => o !== h && o.start <= h.start && o.end >= h.end && !(o.start === h.start && o.end === h.end))));
|
|
73576
|
-
out.sort((a, b) => a.start - b.start || a.end - b.end);
|
|
73577
|
-
return out;
|
|
73578
|
-
}
|
|
73579
|
-
|
|
73580
|
-
// telegram-plugin/secret-detect/redact.ts
|
|
73581
|
-
var REDACTED_MARKER = "[REDACTED]";
|
|
73582
|
-
function redact(text) {
|
|
73583
|
-
if (!text || text.length === 0)
|
|
73584
|
-
return text;
|
|
73585
|
-
const urlScrubbed = redactUrls(text);
|
|
73586
|
-
const hits = detectSecrets(urlScrubbed).filter((h) => h.rule_id !== "generic_high_entropy");
|
|
73587
|
-
if (hits.length === 0)
|
|
73588
|
-
return urlScrubbed;
|
|
73589
|
-
const sorted = [...hits].sort((a, b) => b.start - a.start);
|
|
73590
|
-
let out = urlScrubbed;
|
|
73591
|
-
for (const h of sorted) {
|
|
73592
|
-
out = out.slice(0, h.start) + redactedMarker(h.rule_id) + out.slice(h.end);
|
|
73593
|
-
}
|
|
73594
|
-
return out;
|
|
73595
|
-
}
|
|
73596
|
-
function redactedMarker(ruleId) {
|
|
73597
|
-
const trimmed = ruleId.replace(/^(kv|env)_/, "");
|
|
73598
|
-
if (!trimmed || trimmed === "key_value" || trimmed === "kv_entropy") {
|
|
73599
|
-
return REDACTED_MARKER;
|
|
73600
|
-
}
|
|
73601
|
-
return `[REDACTED:${trimmed}]`;
|
|
73602
|
-
}
|
|
73603
|
-
// src/memory/hindsight-write-redaction.ts
|
|
73604
|
-
function redactJsonStrings(value) {
|
|
73605
|
-
if (typeof value === "string")
|
|
73606
|
-
return redact(value);
|
|
73607
|
-
if (Array.isArray(value))
|
|
73608
|
-
return value.map(redactJsonStrings);
|
|
73609
|
-
if (value && typeof value === "object") {
|
|
73610
|
-
const out = {};
|
|
73611
|
-
for (const [k, v] of Object.entries(value)) {
|
|
73612
|
-
out[k] = redactJsonStrings(v);
|
|
73613
|
-
}
|
|
73614
|
-
return out;
|
|
73615
|
-
}
|
|
73616
|
-
return value;
|
|
73617
|
-
}
|
|
73618
|
-
var REDACTED_ITEM_FIELDS = ["content", "context"];
|
|
73619
|
-
function redactMemoryWriteBody(body) {
|
|
73620
|
-
if (!body || typeof body !== "object" || Array.isArray(body))
|
|
73621
|
-
return body;
|
|
73622
|
-
const src = body;
|
|
73623
|
-
if (!Array.isArray(src.items))
|
|
73624
|
-
return body;
|
|
73625
|
-
const items = src.items.map((item) => {
|
|
73626
|
-
if (!item || typeof item !== "object" || Array.isArray(item))
|
|
73627
|
-
return item;
|
|
73628
|
-
const row = { ...item };
|
|
73629
|
-
for (const field of REDACTED_ITEM_FIELDS) {
|
|
73630
|
-
if (typeof row[field] === "string") {
|
|
73631
|
-
row[field] = redact(row[field]);
|
|
73632
|
-
}
|
|
73633
|
-
}
|
|
73634
|
-
if (row.metadata && typeof row.metadata === "object") {
|
|
73635
|
-
row.metadata = redactJsonStrings(row.metadata);
|
|
73636
|
-
}
|
|
73637
|
-
return row;
|
|
73638
|
-
});
|
|
73639
|
-
return { ...src, items };
|
|
73640
|
-
}
|
|
73641
|
-
|
|
73642
|
-
// src/agents/handoff-summarizer.ts
|
|
73643
|
-
init_observation_scopes();
|
|
73644
|
-
var HANDOFF_STATUS_MIRROR_SKIPPED = "mirror-skipped-invalid-scope";
|
|
73645
|
-
var DEFAULT_MAX_TURNS = 50;
|
|
73646
|
-
var TOPIC_MAX_CHARS = 117;
|
|
73647
|
-
var TURN_TEXT_MAX_CHARS = 1200;
|
|
73648
|
-
function extractTurnsFromJsonl(path2, maxTurns) {
|
|
73649
|
-
let raw;
|
|
73650
|
-
try {
|
|
73651
|
-
raw = readFileSync20(path2, "utf-8");
|
|
73652
|
-
} catch {
|
|
73653
|
-
return [];
|
|
73654
|
-
}
|
|
73655
|
-
const turns = [];
|
|
73656
|
-
for (const line of raw.split(`
|
|
73657
|
-
`)) {
|
|
73658
|
-
if (!line.trim())
|
|
73659
|
-
continue;
|
|
73660
|
-
let obj;
|
|
73661
|
-
try {
|
|
73662
|
-
obj = JSON.parse(line);
|
|
73663
|
-
} catch {
|
|
73664
|
-
continue;
|
|
73665
|
-
}
|
|
73666
|
-
if (obj.type === "queue-operation") {
|
|
73667
|
-
const op = obj.operation;
|
|
73668
|
-
if (op !== "enqueue")
|
|
73669
|
-
continue;
|
|
73670
|
-
const content = obj.content;
|
|
73671
|
-
if (typeof content !== "string")
|
|
73672
|
-
continue;
|
|
73673
|
-
const text = extractChannelBody(content);
|
|
73674
|
-
if (text)
|
|
73675
|
-
turns.push({ role: "user", text });
|
|
73676
|
-
continue;
|
|
73677
|
-
}
|
|
73678
|
-
if (obj.type === "user" && obj.message && typeof obj.message === "object") {
|
|
73679
|
-
const content = obj.message.content;
|
|
73680
|
-
const raw2 = extractTextBlocks(content);
|
|
73681
|
-
const text = raw2 ? extractChannelBody(raw2) : null;
|
|
73682
|
-
if (text)
|
|
73683
|
-
turns.push({ role: "user", text });
|
|
73684
|
-
continue;
|
|
73685
|
-
}
|
|
73686
|
-
if (obj.type === "assistant" && obj.message && typeof obj.message === "object") {
|
|
73687
|
-
const content = obj.message.content;
|
|
73688
|
-
const text = extractTextBlocks(content);
|
|
73689
|
-
if (text)
|
|
73690
|
-
turns.push({ role: "assistant", text });
|
|
73691
|
-
continue;
|
|
73692
|
-
}
|
|
73693
|
-
}
|
|
73694
|
-
const deduped = [];
|
|
73695
|
-
for (const t of turns) {
|
|
73696
|
-
const prev = deduped[deduped.length - 1];
|
|
73697
|
-
if (prev && prev.role === t.role && prev.text === t.text)
|
|
73698
|
-
continue;
|
|
73699
|
-
deduped.push(t);
|
|
73700
|
-
}
|
|
73701
|
-
if (deduped.length <= maxTurns)
|
|
73702
|
-
return deduped;
|
|
73703
|
-
return deduped.slice(deduped.length - maxTurns);
|
|
73704
|
-
}
|
|
73705
|
-
function extractChannelBody(raw) {
|
|
73706
|
-
const m = raw.match(/<channel[^>]*>([\s\S]*?)<\/channel>/);
|
|
73707
|
-
if (m)
|
|
73708
|
-
return m[1].trim();
|
|
73709
|
-
const trimmed = raw.trim();
|
|
73710
|
-
return trimmed.length > 0 ? trimmed : null;
|
|
73711
|
-
}
|
|
73712
|
-
function extractTextBlocks(content) {
|
|
73713
|
-
if (typeof content === "string") {
|
|
73714
|
-
const t = content.trim();
|
|
73715
|
-
return t.length > 0 ? t : null;
|
|
73716
|
-
}
|
|
73717
|
-
if (!Array.isArray(content))
|
|
73718
|
-
return null;
|
|
73719
|
-
const parts = [];
|
|
73720
|
-
for (const block of content) {
|
|
73721
|
-
if (!block || typeof block !== "object")
|
|
73722
|
-
continue;
|
|
73723
|
-
const b = block;
|
|
73724
|
-
if (b.type === "text" && typeof b.text === "string") {
|
|
73725
|
-
parts.push(b.text);
|
|
73726
|
-
} else if (b.type === "tool_result" && typeof b.content === "string") {
|
|
73727
|
-
parts.push(`[tool result] ${b.content.slice(0, 400)}`);
|
|
73728
|
-
}
|
|
73729
|
-
}
|
|
73730
|
-
const joined = parts.join(`
|
|
73731
|
-
`).trim();
|
|
73732
|
-
return joined.length > 0 ? joined : null;
|
|
73733
|
-
}
|
|
73734
|
-
function formatTranscriptTail(turns) {
|
|
73735
|
-
const header = `# Handoff \u2014 previous session
|
|
73736
|
-
|
|
73737
|
-
` + "You are resuming this agent's work. There is no generated summary " + "\u2014 below is the **raw tail of the previous session's transcript** " + "(oldest first, most recent last). Read it to reorient, then carry " + "on. Anything important worth keeping long-term should already be " + `in your memory files \u2014 check those too.
|
|
73738
|
-
`;
|
|
73739
|
-
if (turns.length === 0) {
|
|
73740
|
-
return header + `
|
|
73741
|
-
_(No recent turns were recoverable from the previous session.)_
|
|
73742
|
-
`;
|
|
73743
|
-
}
|
|
73744
|
-
const body = turns.map((t) => {
|
|
73745
|
-
let text = t.text;
|
|
73746
|
-
if (text.length > TURN_TEXT_MAX_CHARS) {
|
|
73747
|
-
text = text.slice(0, TURN_TEXT_MAX_CHARS) + `
|
|
73748
|
-
\u2026[truncated]`;
|
|
73749
|
-
}
|
|
73750
|
-
return `### ${t.role === "user" ? "User" : "Assistant"}
|
|
73751
|
-
${text}`;
|
|
73752
|
-
}).join(`
|
|
73753
|
-
|
|
73754
|
-
`);
|
|
73755
|
-
return `${header}
|
|
73756
|
-
## Recent turns
|
|
73757
|
-
|
|
73758
|
-
${body}
|
|
73759
|
-
`;
|
|
73760
|
-
}
|
|
73761
|
-
function deriveTopic(turns) {
|
|
73762
|
-
for (let i = turns.length - 1;i >= 0; i--) {
|
|
73763
|
-
if (turns[i].role === "user")
|
|
73764
|
-
return clampTopic(firstLine(turns[i].text));
|
|
73765
|
-
}
|
|
73766
|
-
if (turns.length > 0)
|
|
73767
|
-
return clampTopic(firstLine(turns[turns.length - 1].text));
|
|
73768
|
-
return "previous session";
|
|
73769
|
-
}
|
|
73770
|
-
function firstLine(s) {
|
|
73771
|
-
const line = s.split(/\r?\n/).find((l) => l.trim().length > 0) ?? s;
|
|
73772
|
-
return line.trim();
|
|
73773
|
-
}
|
|
73774
|
-
function clampTopic(s) {
|
|
73775
|
-
return s.length > TOPIC_MAX_CHARS ? s.slice(0, TOPIC_MAX_CHARS) + "\u2026" : s;
|
|
73160
|
+
function clampTopic(s) {
|
|
73161
|
+
return s.length > TOPIC_MAX_CHARS ? s.slice(0, TOPIC_MAX_CHARS) + "\u2026" : s;
|
|
73776
73162
|
}
|
|
73777
73163
|
function writeSidecarsAtomic(agentDir, briefing, topic) {
|
|
73778
73164
|
mkdirSync16(agentDir, { recursive: true });
|
|
@@ -73805,8 +73191,7 @@ async function buildHandoff(opts) {
|
|
|
73805
73191
|
`);
|
|
73806
73192
|
return "write-error";
|
|
73807
73193
|
}
|
|
73808
|
-
|
|
73809
|
-
return mirrored ? "ok" : HANDOFF_STATUS_MIRROR_SKIPPED;
|
|
73194
|
+
return "ok";
|
|
73810
73195
|
}
|
|
73811
73196
|
function errMsg(err) {
|
|
73812
73197
|
if (err && typeof err === "object" && "message" in err) {
|
|
@@ -73814,42 +73199,6 @@ function errMsg(err) {
|
|
|
73814
73199
|
}
|
|
73815
73200
|
return String(err);
|
|
73816
73201
|
}
|
|
73817
|
-
async function mirrorToHindsight(briefing, opts) {
|
|
73818
|
-
const url = opts.hindsightUrl ?? process.env.HINDSIGHT_API_URL;
|
|
73819
|
-
const bankId = opts.hindsightBankId ?? process.env.HINDSIGHT_BANK_ID ?? "default";
|
|
73820
|
-
if (!url)
|
|
73821
|
-
return true;
|
|
73822
|
-
const fetchFn = opts.fetch ?? fetch;
|
|
73823
|
-
const endpoint = `${url.replace(/\/$/, "")}/v1/default/banks/${encodeURIComponent(bankId)}/memories`;
|
|
73824
|
-
const scope = resolveObservationScope(opts.observationScopes, opts.env ?? process.env);
|
|
73825
|
-
if (scope.kind === "invalid") {
|
|
73826
|
-
process.stderr.write(`handoff: hindsight mirror SKIPPED \u2014 observation scope ` + `${JSON.stringify(scope.raw)} is not valid ` + `(accepted: ${observationScopesHint()}). Fix ` + `memory.observation_scopes in switchroom.yaml (or the ` + `${OBSERVATION_SCOPES_ENV} export), then \`switchroom apply\` and ` + `restart the agent. The on-disk handoff sidecars were written ` + `normally; only the recallable Hindsight copy was skipped.
|
|
73827
|
-
`);
|
|
73828
|
-
return false;
|
|
73829
|
-
}
|
|
73830
|
-
const body = redactMemoryWriteBody({
|
|
73831
|
-
items: [
|
|
73832
|
-
{
|
|
73833
|
-
content: briefing,
|
|
73834
|
-
document_id: "session_handoff",
|
|
73835
|
-
tags: ["session_handoff", opts.agentName],
|
|
73836
|
-
...scope.kind === "set" ? { observation_scopes: scope.scope } : {}
|
|
73837
|
-
}
|
|
73838
|
-
],
|
|
73839
|
-
async: true
|
|
73840
|
-
});
|
|
73841
|
-
try {
|
|
73842
|
-
await fetchFn(endpoint, {
|
|
73843
|
-
method: "POST",
|
|
73844
|
-
headers: { "content-type": "application/json" },
|
|
73845
|
-
body: JSON.stringify(body)
|
|
73846
|
-
});
|
|
73847
|
-
} catch (err) {
|
|
73848
|
-
process.stderr.write(`handoff: hindsight mirror failed \u2014 ${errMsg(err)}
|
|
73849
|
-
`);
|
|
73850
|
-
}
|
|
73851
|
-
return true;
|
|
73852
|
-
}
|
|
73853
73202
|
function findLatestSessionJsonl(claudeConfigDir) {
|
|
73854
73203
|
const projects = join18(claudeConfigDir, "projects");
|
|
73855
73204
|
if (!existsSync24(projects))
|
|
@@ -76856,8 +76205,8 @@ Bootstrapping agent: ${name}
|
|
|
76856
76205
|
const code = await new Promise((resolve25) => {
|
|
76857
76206
|
process.stdin.setEncoding("utf-8");
|
|
76858
76207
|
let buf = "";
|
|
76859
|
-
process.stdin.on("data", (
|
|
76860
|
-
buf +=
|
|
76208
|
+
process.stdin.on("data", (chunk) => {
|
|
76209
|
+
buf += chunk.toString();
|
|
76861
76210
|
const newlineIdx = buf.indexOf(`
|
|
76862
76211
|
`);
|
|
76863
76212
|
if (newlineIdx !== -1) {
|
|
@@ -76939,8 +76288,8 @@ switchroom agent add: ${name}
|
|
|
76939
76288
|
return new Promise((resolveLine) => {
|
|
76940
76289
|
process.stdin.setEncoding("utf-8");
|
|
76941
76290
|
let buf = "";
|
|
76942
|
-
const onData = (
|
|
76943
|
-
buf +=
|
|
76291
|
+
const onData = (chunk) => {
|
|
76292
|
+
buf += chunk.toString();
|
|
76944
76293
|
const newlineIdx = buf.indexOf(`
|
|
76945
76294
|
`);
|
|
76946
76295
|
if (newlineIdx !== -1) {
|
|
@@ -78971,8 +78320,8 @@ async function readHiddenLine2(prompt) {
|
|
|
78971
78320
|
stdin.setRawMode(true);
|
|
78972
78321
|
}
|
|
78973
78322
|
let line = "";
|
|
78974
|
-
const onData = (
|
|
78975
|
-
const s =
|
|
78323
|
+
const onData = (chunk) => {
|
|
78324
|
+
const s = chunk.toString("utf8");
|
|
78976
78325
|
for (const ch of s) {
|
|
78977
78326
|
if (ch === `
|
|
78978
78327
|
` || ch === "\r") {
|
|
@@ -79901,7 +79250,7 @@ function getVaultPath2(configPath) {
|
|
|
79901
79250
|
return resolvePath("~/.switchroom/vault.enc");
|
|
79902
79251
|
}
|
|
79903
79252
|
}
|
|
79904
|
-
function
|
|
79253
|
+
function maskToken(s) {
|
|
79905
79254
|
if (s.length >= 18)
|
|
79906
79255
|
return `${s.slice(0, 6)}...${s.slice(-4)}`;
|
|
79907
79256
|
return "***";
|
|
@@ -80189,7 +79538,7 @@ function registerVaultSweep(vault, program3) {
|
|
|
80189
79538
|
console.log("");
|
|
80190
79539
|
console.log(source_default.dim(`masked values for display only:`));
|
|
80191
79540
|
for (const v of values) {
|
|
80192
|
-
console.log(` vault:${v.key} \u2192 ${
|
|
79541
|
+
console.log(` vault:${v.key} \u2192 ${maskToken(v.value)}`);
|
|
80193
79542
|
}
|
|
80194
79543
|
if (opts.dryRun) {
|
|
80195
79544
|
console.log(source_default.yellow("dry-run \u2014 no files modified. Rerun without --dry-run to apply."));
|
|
@@ -83162,8 +82511,8 @@ class VaultBroker {
|
|
|
83162
82511
|
peer = this.testOpts._testIdentify ? this.testOpts._testIdentify(listenerSocketPath, socket) : identify(listenerSocketPath, socket);
|
|
83163
82512
|
}
|
|
83164
82513
|
let buffer = "";
|
|
83165
|
-
socket.on("data", (
|
|
83166
|
-
buffer +=
|
|
82514
|
+
socket.on("data", (chunk) => {
|
|
82515
|
+
buffer += chunk.toString("utf8");
|
|
83167
82516
|
if (Buffer.byteLength(buffer, "utf8") > MAX_FRAME_BYTES) {
|
|
83168
82517
|
const resp = encodeResponse(errorResponse("BAD_REQUEST", "Frame exceeds 64 KiB limit"));
|
|
83169
82518
|
socket.end(resp);
|
|
@@ -84272,8 +83621,8 @@ class VaultBroker {
|
|
|
84272
83621
|
const auditCaller = isOperator ? "operator" : unlockPeer !== null ? callerFromPeer(unlockPeer) : `pid:${process.pid}`;
|
|
84273
83622
|
const auditCgroup = isOperator ? undefined : unlockPeer?.systemdUnit ?? undefined;
|
|
84274
83623
|
let buffer = "";
|
|
84275
|
-
socket.on("data", (
|
|
84276
|
-
buffer +=
|
|
83624
|
+
socket.on("data", (chunk) => {
|
|
83625
|
+
buffer += chunk.toString("utf8");
|
|
84277
83626
|
const newlineIdx = buffer.indexOf(`
|
|
84278
83627
|
`);
|
|
84279
83628
|
if (newlineIdx === -1) {
|
|
@@ -85840,7 +85189,7 @@ function promptLine2(prompt, hidden = false) {
|
|
|
85840
85189
|
function readStdinToEnd() {
|
|
85841
85190
|
return new Promise((resolve31, reject) => {
|
|
85842
85191
|
const chunks = [];
|
|
85843
|
-
process.stdin.on("data", (
|
|
85192
|
+
process.stdin.on("data", (chunk) => chunks.push(chunk));
|
|
85844
85193
|
process.stdin.on("end", () => {
|
|
85845
85194
|
resolve31(Buffer.concat(chunks).toString("utf8"));
|
|
85846
85195
|
});
|
|
@@ -87265,257 +86614,846 @@ function registerLinearAgentCommand(program3) {
|
|
|
87265
86614
|
} else {
|
|
87266
86615
|
console.log(source_default.green(`\u2713 Default Linear team for '${opts.agent}' set to ${opts.team.trim()}.`));
|
|
87267
86616
|
}
|
|
87268
|
-
console.log(source_default.gray(` Run 'switchroom agent restart ${opts.agent}' to pick up the change.`));
|
|
87269
|
-
}));
|
|
86617
|
+
console.log(source_default.gray(` Run 'switchroom agent restart ${opts.agent}' to pick up the change.`));
|
|
86618
|
+
}));
|
|
86619
|
+
}
|
|
86620
|
+
function printLinearInstructions(opts, vaultKey) {
|
|
86621
|
+
const base = opts.webhookBase ?? "https://<your-switchroom-web-host>";
|
|
86622
|
+
const webhookUrl = `${base.replace(/\/$/, "")}/webhook/${opts.agent}/linear`;
|
|
86623
|
+
console.log("");
|
|
86624
|
+
console.log(source_default.bold("Next steps in Linear (browser, one-time per agent):"));
|
|
86625
|
+
console.log(source_default.gray(" 1. Create / open your Linear OAuth app with actor=app and scopes"));
|
|
86626
|
+
console.log(source_default.gray(" app:mentionable + app:assignable (https://linear.app/developers/agents)."));
|
|
86627
|
+
if (opts.clientId) {
|
|
86628
|
+
const redirect = opts.redirectUri ?? `${base.replace(/\/$/, "")}/oauth/callback`;
|
|
86629
|
+
const authorizeUrl = `https://linear.app/oauth/authorize?` + `client_id=${encodeURIComponent(opts.clientId)}` + `&redirect_uri=${encodeURIComponent(redirect)}` + `&response_type=code` + `&scope=${encodeURIComponent("read,write,app:assignable,app:mentionable")}` + `&actor=app`;
|
|
86630
|
+
console.log(source_default.gray(" 2. Authorize the app as an actor (open in a browser):"));
|
|
86631
|
+
console.log(source_default.cyan(` ${authorizeUrl}`));
|
|
86632
|
+
console.log(source_default.gray(" The redirect delivers the app token you pass to this verb via --token."));
|
|
86633
|
+
} else {
|
|
86634
|
+
console.log(source_default.gray(" 2. Authorize the app (actor=app) in a browser; capture the app token and"));
|
|
86635
|
+
console.log(source_default.gray(" re-run this verb with --token (and optionally --client-id to print the"));
|
|
86636
|
+
console.log(source_default.gray(" authorize URL)."));
|
|
86637
|
+
}
|
|
86638
|
+
console.log(source_default.gray(" 3. Register this webhook URL on the app (AgentSessionEvent + Issue/Comment):"));
|
|
86639
|
+
console.log(source_default.cyan(` ${webhookUrl}`));
|
|
86640
|
+
console.log(source_default.gray(" Use Linear's signing secret as the webhook secret \u2014 store it in the vault " + `under webhook/${opts.agent}/linear (e.g. 'switchroom telegram enable webhook ` + `--agent ${opts.agent} --source linear --secret <signing-secret>').`));
|
|
86641
|
+
console.log("");
|
|
86642
|
+
console.log(source_default.gray(` Token is read at runtime from vault:${vaultKey} (actor=app).`));
|
|
86643
|
+
}
|
|
86644
|
+
function makeUnifiedDiff2(before, after) {
|
|
86645
|
+
const a = before.split(`
|
|
86646
|
+
`);
|
|
86647
|
+
const b = after.split(`
|
|
86648
|
+
`);
|
|
86649
|
+
const out = [];
|
|
86650
|
+
let i = 0, j = 0;
|
|
86651
|
+
while (i < a.length || j < b.length) {
|
|
86652
|
+
if (i < a.length && j < b.length && a[i] === b[j]) {
|
|
86653
|
+
out.push(` ${a[i]}`);
|
|
86654
|
+
i++;
|
|
86655
|
+
j++;
|
|
86656
|
+
} else if (j < b.length && (i >= a.length || a[i] !== b[j])) {
|
|
86657
|
+
out.push(source_default.green(`+ ${b[j]}`));
|
|
86658
|
+
j++;
|
|
86659
|
+
} else {
|
|
86660
|
+
out.push(source_default.red(`- ${a[i]}`));
|
|
86661
|
+
i++;
|
|
86662
|
+
}
|
|
86663
|
+
}
|
|
86664
|
+
return out.join(`
|
|
86665
|
+
`);
|
|
86666
|
+
}
|
|
86667
|
+
function fail3(msg) {
|
|
86668
|
+
console.error(source_default.red(`Error: ${msg}`));
|
|
86669
|
+
process.exit(1);
|
|
86670
|
+
}
|
|
86671
|
+
|
|
86672
|
+
// src/cli/memory.ts
|
|
86673
|
+
init_source();
|
|
86674
|
+
init_hindsight2();
|
|
86675
|
+
|
|
86676
|
+
// src/memory/search.ts
|
|
86677
|
+
init_hindsight2();
|
|
86678
|
+
function shellQuote2(s) {
|
|
86679
|
+
return "'" + s.replace(/'/g, `'"'"'`) + "'";
|
|
86680
|
+
}
|
|
86681
|
+
function searchMemory(query, collection) {
|
|
86682
|
+
return `hindsight recall --collection ${shellQuote2(collection)} --query ${shellQuote2(query)}`;
|
|
86683
|
+
}
|
|
86684
|
+
function getMemoryStats(collection) {
|
|
86685
|
+
return `hindsight stats --collection ${shellQuote2(collection)}`;
|
|
86686
|
+
}
|
|
86687
|
+
function reflectAcrossAgents(config) {
|
|
86688
|
+
const eligible = [];
|
|
86689
|
+
const excluded = [];
|
|
86690
|
+
for (const agentName of Object.keys(config.agents)) {
|
|
86691
|
+
const collection = getCollectionForAgent(agentName, config);
|
|
86692
|
+
if (isStrictIsolation(agentName, config)) {
|
|
86693
|
+
excluded.push({ agent: agentName, collection });
|
|
86694
|
+
} else {
|
|
86695
|
+
eligible.push({ agent: agentName, collection });
|
|
86696
|
+
}
|
|
86697
|
+
}
|
|
86698
|
+
const commands = eligible.map(({ collection }) => `hindsight reflect --collection ${shellQuote2(collection)}`);
|
|
86699
|
+
return { eligible, excluded, commands };
|
|
86700
|
+
}
|
|
86701
|
+
|
|
86702
|
+
// src/memory/observation-scope-cleanup.ts
|
|
86703
|
+
var CLEANUP_VOLATILE_PATTERNS = [
|
|
86704
|
+
/^parent_session:/,
|
|
86705
|
+
/^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}(-sub-.+)?$/
|
|
86706
|
+
];
|
|
86707
|
+
var CLEANUP_STALE_PATTERNS = [/^agent-[0-9a-f]{8,}$/];
|
|
86708
|
+
function matchesAny(tag, patterns) {
|
|
86709
|
+
return patterns.some((p) => p.test(tag));
|
|
86710
|
+
}
|
|
86711
|
+
function scopeKey(tags) {
|
|
86712
|
+
return tags.length === 0 ? "shared" : [...tags].sort().join(",");
|
|
86713
|
+
}
|
|
86714
|
+
function planScopeCleanup(bankId, scopes, options = {}) {
|
|
86715
|
+
const volatilePatterns = options.volatilePatterns ?? CLEANUP_VOLATILE_PATTERNS;
|
|
86716
|
+
const stalePatterns = options.stalePatterns ?? CLEANUP_STALE_PATTERNS;
|
|
86717
|
+
const retireStale = options.retireStale ?? false;
|
|
86718
|
+
const totalScopes = scopes.length;
|
|
86719
|
+
const totalObservations = scopes.reduce((n, s) => n + s.count, 0);
|
|
86720
|
+
const reclassified = [];
|
|
86721
|
+
const breakdown = {
|
|
86722
|
+
allVolatileScopes: 0,
|
|
86723
|
+
allVolatileObservations: 0,
|
|
86724
|
+
mixedScopes: 0,
|
|
86725
|
+
mixedObservations: 0,
|
|
86726
|
+
staleScopes: 0,
|
|
86727
|
+
staleObservations: 0
|
|
86728
|
+
};
|
|
86729
|
+
const targets = new Map;
|
|
86730
|
+
for (const s of scopes) {
|
|
86731
|
+
const volatileTags = s.tags.filter((t) => matchesAny(t, volatilePatterns));
|
|
86732
|
+
const staleTags = s.tags.filter((t) => matchesAny(t, stalePatterns));
|
|
86733
|
+
const stripped = s.tags.filter((t) => {
|
|
86734
|
+
if (matchesAny(t, volatilePatterns))
|
|
86735
|
+
return false;
|
|
86736
|
+
if (retireStale && matchesAny(t, stalePatterns))
|
|
86737
|
+
return false;
|
|
86738
|
+
return true;
|
|
86739
|
+
});
|
|
86740
|
+
const toKey = scopeKey(stripped);
|
|
86741
|
+
const changed = scopeKey(s.tags) !== toKey;
|
|
86742
|
+
const hasStable = s.tags.some((t) => !matchesAny(t, volatilePatterns) && !matchesAny(t, stalePatterns));
|
|
86743
|
+
if (staleTags.length > 0) {
|
|
86744
|
+
breakdown.staleScopes += 1;
|
|
86745
|
+
breakdown.staleObservations += s.count;
|
|
86746
|
+
}
|
|
86747
|
+
if (volatileTags.length > 0 && !hasStable && staleTags.length === 0) {
|
|
86748
|
+
breakdown.allVolatileScopes += 1;
|
|
86749
|
+
breakdown.allVolatileObservations += s.count;
|
|
86750
|
+
} else if (volatileTags.length > 0 && hasStable) {
|
|
86751
|
+
breakdown.mixedScopes += 1;
|
|
86752
|
+
breakdown.mixedObservations += s.count;
|
|
86753
|
+
}
|
|
86754
|
+
const entry = targets.get(toKey) ?? { sources: 0, observations: 0, changed: false };
|
|
86755
|
+
entry.sources += 1;
|
|
86756
|
+
entry.observations += s.count;
|
|
86757
|
+
entry.changed = entry.changed || changed;
|
|
86758
|
+
targets.set(toKey, entry);
|
|
86759
|
+
if (changed) {
|
|
86760
|
+
reclassified.push({
|
|
86761
|
+
from: s.tags,
|
|
86762
|
+
to: toKey,
|
|
86763
|
+
count: s.count,
|
|
86764
|
+
strippedVolatile: volatileTags,
|
|
86765
|
+
strippedStale: retireStale ? staleTags : []
|
|
86766
|
+
});
|
|
86767
|
+
}
|
|
86768
|
+
}
|
|
86769
|
+
reclassified.sort((a, b) => b.count - a.count);
|
|
86770
|
+
const merges = [...targets.entries()].filter(([, v]) => v.sources > 1 && v.changed).map(([target, v]) => ({ target, sourceScopes: v.sources, observations: v.observations })).sort((a, b) => b.observations - a.observations);
|
|
86771
|
+
const affectedObservations = reclassified.reduce((n, r) => n + r.count, 0);
|
|
86772
|
+
const resultingScopeCount = targets.size;
|
|
86773
|
+
return {
|
|
86774
|
+
bankId,
|
|
86775
|
+
ok: true,
|
|
86776
|
+
totalScopes,
|
|
86777
|
+
totalObservations,
|
|
86778
|
+
reclassified,
|
|
86779
|
+
merges,
|
|
86780
|
+
affectedScopes: reclassified.length,
|
|
86781
|
+
affectedObservations,
|
|
86782
|
+
resultingScopeCount,
|
|
86783
|
+
scopeReduction: totalScopes - resultingScopeCount,
|
|
86784
|
+
breakdown
|
|
86785
|
+
};
|
|
86786
|
+
}
|
|
86787
|
+
async function getJson2(url, opts) {
|
|
86788
|
+
const fetchImpl = opts?.fetchImpl ?? fetch;
|
|
86789
|
+
const timeoutMs = opts?.timeoutMs ?? 15000;
|
|
86790
|
+
const controller = new AbortController;
|
|
86791
|
+
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
86792
|
+
try {
|
|
86793
|
+
const resp = await fetchImpl(url, { signal: controller.signal });
|
|
86794
|
+
clearTimeout(timeout);
|
|
86795
|
+
if (!resp.ok)
|
|
86796
|
+
return { ok: false, reason: `HTTP ${resp.status}` };
|
|
86797
|
+
return { ok: true, data: await resp.json() };
|
|
86798
|
+
} catch (err) {
|
|
86799
|
+
clearTimeout(timeout);
|
|
86800
|
+
if (err.name === "AbortError")
|
|
86801
|
+
return { ok: false, reason: "Timeout" };
|
|
86802
|
+
return { ok: false, reason: String(err.message ?? err) };
|
|
86803
|
+
}
|
|
86804
|
+
}
|
|
86805
|
+
async function planBankCleanup(apiUrl, bankId, options = {}, fetchOpts) {
|
|
86806
|
+
const base = hindsightRestBase(apiUrl);
|
|
86807
|
+
const bank = encodeURIComponent(bankId);
|
|
86808
|
+
const resp = await getJson2(`${base}/v1/default/banks/${bank}/observations/scopes`, fetchOpts);
|
|
86809
|
+
if (!resp.ok) {
|
|
86810
|
+
return {
|
|
86811
|
+
bankId,
|
|
86812
|
+
ok: false,
|
|
86813
|
+
reason: resp.reason,
|
|
86814
|
+
totalScopes: 0,
|
|
86815
|
+
totalObservations: 0,
|
|
86816
|
+
reclassified: [],
|
|
86817
|
+
merges: [],
|
|
86818
|
+
affectedScopes: 0,
|
|
86819
|
+
affectedObservations: 0,
|
|
86820
|
+
resultingScopeCount: 0,
|
|
86821
|
+
scopeReduction: 0,
|
|
86822
|
+
breakdown: {
|
|
86823
|
+
allVolatileScopes: 0,
|
|
86824
|
+
allVolatileObservations: 0,
|
|
86825
|
+
mixedScopes: 0,
|
|
86826
|
+
mixedObservations: 0,
|
|
86827
|
+
staleScopes: 0,
|
|
86828
|
+
staleObservations: 0
|
|
86829
|
+
}
|
|
86830
|
+
};
|
|
86831
|
+
}
|
|
86832
|
+
const raw = resp.data?.scopes;
|
|
86833
|
+
const scopes = [];
|
|
86834
|
+
if (Array.isArray(raw)) {
|
|
86835
|
+
for (const entry of raw) {
|
|
86836
|
+
const tags = entry?.tags;
|
|
86837
|
+
const count = entry?.count;
|
|
86838
|
+
if (!Array.isArray(tags) || typeof count !== "number")
|
|
86839
|
+
continue;
|
|
86840
|
+
if (!tags.every((t) => typeof t === "string"))
|
|
86841
|
+
continue;
|
|
86842
|
+
scopes.push({ tags, count });
|
|
86843
|
+
}
|
|
86844
|
+
}
|
|
86845
|
+
return planScopeCleanup(bankId, scopes, options);
|
|
86846
|
+
}
|
|
86847
|
+
function formatCleanupPlan(plan, maxMerges = 10) {
|
|
86848
|
+
if (!plan.ok) {
|
|
86849
|
+
return `bank ${plan.bankId}: UNREADABLE (${plan.reason ?? "unknown"})`;
|
|
86850
|
+
}
|
|
86851
|
+
const b = plan.breakdown;
|
|
86852
|
+
const lines = [];
|
|
86853
|
+
lines.push(`bank ${plan.bankId}`);
|
|
86854
|
+
lines.push(` today: ${plan.totalScopes} scopes, ${plan.totalObservations.toLocaleString()} observations`);
|
|
86855
|
+
lines.push(` legacy fragmentation:` + ` all-volatile ${b.allVolatileScopes} scopes / ${b.allVolatileObservations.toLocaleString()} obs;` + ` mixed ${b.mixedScopes} scopes / ${b.mixedObservations.toLocaleString()} obs;` + ` stale agent-<hex> ${b.staleScopes} scopes / ${b.staleObservations.toLocaleString()} obs`);
|
|
86856
|
+
lines.push(` WOULD change: ${plan.affectedScopes} scopes` + ` (${plan.affectedObservations.toLocaleString()} obs re-homed),` + ` ${plan.totalScopes} \u2192 ${plan.resultingScopeCount} scopes` + ` (\u2212${plan.scopeReduction})`);
|
|
86857
|
+
if (plan.merges.length > 0) {
|
|
86858
|
+
lines.push(` top merges:`);
|
|
86859
|
+
for (const m of plan.merges.slice(0, maxMerges)) {
|
|
86860
|
+
lines.push(` ${m.sourceScopes} scopes \u2192 ${m.target} (${m.observations.toLocaleString()} obs)`);
|
|
86861
|
+
}
|
|
86862
|
+
if (plan.merges.length > maxMerges) {
|
|
86863
|
+
lines.push(` +${plan.merges.length - maxMerges} more merge targets`);
|
|
86864
|
+
}
|
|
86865
|
+
}
|
|
86866
|
+
return lines.join(`
|
|
86867
|
+
`);
|
|
86868
|
+
}
|
|
86869
|
+
|
|
86870
|
+
// telegram-plugin/secret-detect/patterns.ts
|
|
86871
|
+
var ANCHORED_PATTERNS = [
|
|
86872
|
+
{ rule_id: "anthropic_api_key", regex: /\b(sk-ant-[A-Za-z0-9_-]{8,})\b/g, captureIndex: 1, slugHint: "anthropic_api_key" },
|
|
86873
|
+
{ rule_id: "anthropic_oauth_code", regex: /(?:^|\s)([A-Za-z0-9_-]{20,}#[A-Za-z0-9_-]{20,})(?=\s|$)/gm, captureIndex: 1, slugHint: "anthropic_oauth_code" },
|
|
86874
|
+
{ rule_id: "openai_api_key", regex: /\b(sk-[A-Za-z0-9_-]{20,})\b/g, captureIndex: 1, slugHint: "openai_api_key" },
|
|
86875
|
+
{ rule_id: "github_pat_classic", regex: /\b(ghp_[A-Za-z0-9]{20,})\b/g, captureIndex: 1, slugHint: "github_pat" },
|
|
86876
|
+
{ rule_id: "github_pat_fine_grained", regex: /\b(github_pat_[A-Za-z0-9_]{20,})\b/g, captureIndex: 1, slugHint: "github_pat" },
|
|
86877
|
+
{ rule_id: "slack_token", regex: /\b(xox[baprs]-[A-Za-z0-9-]{10,})\b/g, captureIndex: 1, slugHint: "slack_token" },
|
|
86878
|
+
{ rule_id: "slack_app_token", regex: /\b(xapp-[A-Za-z0-9-]{10,})\b/g, captureIndex: 1, slugHint: "slack_app_token" },
|
|
86879
|
+
{ rule_id: "groq_api_key", regex: /\b(gsk_[A-Za-z0-9_-]{10,})\b/g, captureIndex: 1, slugHint: "groq_api_key" },
|
|
86880
|
+
{ rule_id: "google_api_key", regex: /\b(AIza[0-9A-Za-z\-_]{20,})\b/g, captureIndex: 1, slugHint: "google_api_key" },
|
|
86881
|
+
{ rule_id: "perplexity_api_key", regex: /\b(pplx-[A-Za-z0-9_-]{10,})\b/g, captureIndex: 1, slugHint: "perplexity_api_key" },
|
|
86882
|
+
{ rule_id: "npm_token", regex: /\b(npm_[A-Za-z0-9]{10,})\b/g, captureIndex: 1, slugHint: "npm_token" },
|
|
86883
|
+
{ rule_id: "telegram_bot_token_prefixed", regex: /\bbot(\d{6,}:[A-Za-z0-9_-]{20,})\b/g, captureIndex: 1, slugHint: "telegram_bot_token" },
|
|
86884
|
+
{ rule_id: "telegram_bot_token", regex: /\b(\d{6,}:[A-Za-z0-9_-]{20,})\b/g, captureIndex: 1, slugHint: "telegram_bot_token" },
|
|
86885
|
+
{ rule_id: "laravel_sanctum_token", regex: /\b(\d+\|[A-Za-z0-9]{40,})\b/g, captureIndex: 1, slugHint: "api_token" },
|
|
86886
|
+
{ rule_id: "aws_access_key", regex: /\b(AKIA[0-9A-Z]{16})\b/g, captureIndex: 1, slugHint: "aws_access_key" },
|
|
86887
|
+
{ rule_id: "jwt", regex: /\b(eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,})\b/g, captureIndex: 1, slugHint: "jwt" }
|
|
86888
|
+
];
|
|
86889
|
+
var STRUCTURED_PATTERNS = [
|
|
86890
|
+
{
|
|
86891
|
+
rule_id: "env_key_value",
|
|
86892
|
+
regex: /\b([A-Z0-9_]*(?:KEY|TOKEN|SECRET|PASSWORD|PASSWD))\b\s*[=:]\s*(["']?)([^\s"'\\]+)\2/g,
|
|
86893
|
+
captureIndex: 3,
|
|
86894
|
+
slugHint: "env"
|
|
86895
|
+
},
|
|
86896
|
+
{
|
|
86897
|
+
rule_id: "json_secret_field",
|
|
86898
|
+
regex: /"(?:apiKey|token|secret|password|passwd|accessToken|refreshToken)"\s*:\s*"([^"]+)"/g,
|
|
86899
|
+
captureIndex: 1,
|
|
86900
|
+
slugHint: "json_secret"
|
|
86901
|
+
},
|
|
86902
|
+
{
|
|
86903
|
+
rule_id: "cli_flag",
|
|
86904
|
+
regex: /--(?:api[-_]?key|hook[-_]?token|token|secret|password|passwd)\s+(["']?)([^\s"']+)\1/g,
|
|
86905
|
+
captureIndex: 2,
|
|
86906
|
+
slugHint: "cli_flag"
|
|
86907
|
+
},
|
|
86908
|
+
{
|
|
86909
|
+
rule_id: "bearer_auth_header",
|
|
86910
|
+
regex: /Authorization\s*[:=]\s*Bearer\s+([A-Za-z0-9._\-+=]+)/gi,
|
|
86911
|
+
captureIndex: 1,
|
|
86912
|
+
slugHint: "bearer_token"
|
|
86913
|
+
},
|
|
86914
|
+
{
|
|
86915
|
+
rule_id: "bearer_loose",
|
|
86916
|
+
regex: /\bBearer\s+([A-Za-z0-9._\-+=]{18,})\b/gi,
|
|
86917
|
+
captureIndex: 1,
|
|
86918
|
+
slugHint: "bearer_token"
|
|
86919
|
+
},
|
|
86920
|
+
{
|
|
86921
|
+
rule_id: "basic_auth_header",
|
|
86922
|
+
regex: /Authorization\s*[:=]\s*Basic\s+([A-Za-z0-9+/=]{8,})/gi,
|
|
86923
|
+
captureIndex: 1,
|
|
86924
|
+
slugHint: "basic_auth"
|
|
86925
|
+
},
|
|
86926
|
+
{
|
|
86927
|
+
rule_id: "pem_private_key",
|
|
86928
|
+
regex: /-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]+?-----END [A-Z ]*PRIVATE KEY-----/g,
|
|
86929
|
+
captureIndex: 0,
|
|
86930
|
+
slugHint: "pem_private_key"
|
|
86931
|
+
}
|
|
86932
|
+
];
|
|
86933
|
+
var PROVIDER_PATTERNS = [
|
|
86934
|
+
{ rule_id: "slack_webhook", regex: /(https:\/\/hooks\.slack\.com\/services\/[A-Za-z0-9_/]+)/g, captureIndex: 1, slugHint: "slack_webhook" },
|
|
86935
|
+
{ rule_id: "stripe_live_secret", regex: /\b(sk_live_[A-Za-z0-9]{24,})\b/g, captureIndex: 1, slugHint: "stripe_key" },
|
|
86936
|
+
{ rule_id: "stripe_restricted", regex: /\b(rk_live_[A-Za-z0-9]{24,})\b/g, captureIndex: 1, slugHint: "stripe_key" },
|
|
86937
|
+
{ rule_id: "sendgrid_api_key", regex: /\b(SG\.[A-Za-z0-9_-]{22}\.[A-Za-z0-9_-]{43})\b/g, captureIndex: 1, slugHint: "sendgrid_key" },
|
|
86938
|
+
{ rule_id: "gitlab_pat", regex: /\b(glpat-[A-Za-z0-9_-]{20})\b/g, captureIndex: 1, slugHint: "gitlab_pat" },
|
|
86939
|
+
{ rule_id: "huggingface_token", regex: /\b(hf_[A-Za-z0-9]{34,})\b/g, captureIndex: 1, slugHint: "huggingface_token" },
|
|
86940
|
+
{ rule_id: "twilio_api_key", regex: /\b(SK[0-9a-f]{32})\b/g, captureIndex: 1, slugHint: "twilio_api_key" },
|
|
86941
|
+
{ rule_id: "mailgun_key", regex: /\b(key-[0-9a-f]{32})\b/g, captureIndex: 1, slugHint: "mailgun_key" },
|
|
86942
|
+
{ rule_id: "digitalocean_pat", regex: /\b(dop_v1_[a-f0-9]{64})\b/g, captureIndex: 1, slugHint: "digitalocean_token" },
|
|
86943
|
+
{ rule_id: "digitalocean_oauth", regex: /\b(doo_v1_[a-f0-9]{64})\b/g, captureIndex: 1, slugHint: "digitalocean_token" },
|
|
86944
|
+
{ rule_id: "digitalocean_refresh", regex: /\b(dor_v1_[a-f0-9]{64})\b/g, captureIndex: 1, slugHint: "digitalocean_token" },
|
|
86945
|
+
{ rule_id: "doppler_token", regex: /\b(dp\.(?:pt|st|ct|sa|scim|audit)\.[A-Za-z0-9]{40,44})\b/g, captureIndex: 1, slugHint: "doppler_token" },
|
|
86946
|
+
{ rule_id: "linear_api_key", regex: /\b(lin_api_[A-Za-z0-9]{40})\b/g, captureIndex: 1, slugHint: "linear_api_key" },
|
|
86947
|
+
{ rule_id: "shopify_access_token", regex: /\b(shpat_[a-fA-F0-9]{32})\b/g, captureIndex: 1, slugHint: "shopify_token" },
|
|
86948
|
+
{ rule_id: "shopify_shared_secret", regex: /\b(shpss_[a-fA-F0-9]{32})\b/g, captureIndex: 1, slugHint: "shopify_token" },
|
|
86949
|
+
{ rule_id: "shopify_private_app", regex: /\b(shppa_[a-fA-F0-9]{32})\b/g, captureIndex: 1, slugHint: "shopify_token" },
|
|
86950
|
+
{ rule_id: "square_access_token", regex: /\b(sq0atp-[A-Za-z0-9_-]{22})\b/g, captureIndex: 1, slugHint: "square_token" },
|
|
86951
|
+
{ rule_id: "square_oauth_secret", regex: /\b(sq0csp-[A-Za-z0-9_-]{43})\b/g, captureIndex: 1, slugHint: "square_token" },
|
|
86952
|
+
{ rule_id: "newrelic_key", regex: /\b(NRAK-[A-Z0-9]{27})\b/g, captureIndex: 1, slugHint: "newrelic_key" },
|
|
86953
|
+
{ rule_id: "notion_token", regex: /\b(ntn_[A-Za-z0-9]{46})\b/g, captureIndex: 1, slugHint: "notion_token" },
|
|
86954
|
+
{ rule_id: "planetscale_password", regex: /\b(pscale_pw_[A-Za-z0-9_.-]{43})\b/g, captureIndex: 1, slugHint: "planetscale_token" },
|
|
86955
|
+
{ rule_id: "planetscale_token", regex: /\b(pscale_tkn_[A-Za-z0-9_.-]{43})\b/g, captureIndex: 1, slugHint: "planetscale_token" },
|
|
86956
|
+
{ rule_id: "supabase_service_key", regex: /\b(sbp_[a-f0-9]{40})\b/g, captureIndex: 1, slugHint: "supabase_key" },
|
|
86957
|
+
{ rule_id: "atlassian_token", regex: /\b(ATATT[A-Za-z0-9_\-=]{20,})\b/g, captureIndex: 1, slugHint: "atlassian_token" },
|
|
86958
|
+
{ rule_id: "dropbox_token", regex: /\b(sl\.[A-Za-z0-9_-]{130,})/g, captureIndex: 1, slugHint: "dropbox_token" },
|
|
86959
|
+
{ rule_id: "databricks_token", regex: /\b(dapi[a-f0-9]{32})\b/g, captureIndex: 1, slugHint: "databricks_token" },
|
|
86960
|
+
{ rule_id: "grafana_service_account", regex: /\b(glsa_[A-Za-z0-9]{32}_[a-fA-F0-9]{8})\b/g, captureIndex: 1, slugHint: "grafana_token" },
|
|
86961
|
+
{ rule_id: "pypi_token", regex: /\b(pypi-AgEIcHlwaS[A-Za-z0-9_-]{50,})/g, captureIndex: 1, slugHint: "pypi_token" },
|
|
86962
|
+
{ rule_id: "aws_temp_access_key", regex: /\b(ASIA[0-9A-Z]{16})\b/g, captureIndex: 1, slugHint: "aws_access_key" },
|
|
86963
|
+
{ rule_id: "gcp_oauth_token", regex: /\b(ya29\.[A-Za-z0-9_-]{30,})/g, captureIndex: 1, slugHint: "gcp_oauth_token" }
|
|
86964
|
+
];
|
|
86965
|
+
var ALL_PATTERNS = [...ANCHORED_PATTERNS, ...PROVIDER_PATTERNS, ...STRUCTURED_PATTERNS];
|
|
86966
|
+
|
|
86967
|
+
// telegram-plugin/secret-detect/entropy.ts
|
|
86968
|
+
function shannonEntropy(s) {
|
|
86969
|
+
if (s.length === 0)
|
|
86970
|
+
return 0;
|
|
86971
|
+
const counts = new Map;
|
|
86972
|
+
for (const ch of s) {
|
|
86973
|
+
counts.set(ch, (counts.get(ch) ?? 0) + 1);
|
|
86974
|
+
}
|
|
86975
|
+
let h = 0;
|
|
86976
|
+
const len = s.length;
|
|
86977
|
+
for (const c of counts.values()) {
|
|
86978
|
+
const p = c / len;
|
|
86979
|
+
h -= p * Math.log2(p);
|
|
86980
|
+
}
|
|
86981
|
+
return h;
|
|
86982
|
+
}
|
|
86983
|
+
|
|
86984
|
+
// telegram-plugin/secret-detect/inert-values.ts
|
|
86985
|
+
var INERT_VALUE_RE = [
|
|
86986
|
+
/^\[REDACTED(?::[A-Za-z0-9_]+)?\]$/i,
|
|
86987
|
+
/^\$\{?[A-Za-z_][A-Za-z0-9_]*\}?$/,
|
|
86988
|
+
/^\{\{[^\n\r\u2028\u2029]*\}\}$/,
|
|
86989
|
+
/^<[^<>\n\r\u2028\u2029]*>$/,
|
|
86990
|
+
/^%[A-Za-z_][A-Za-z0-9_]*%$/,
|
|
86991
|
+
/^vault:[A-Za-z0-9_./-]*$/i,
|
|
86992
|
+
/^[*x\u2022.]+$/i,
|
|
86993
|
+
/^(?:process\.env|import\.meta\.env|os\.environ)(?:\.[A-Za-z_$][A-Za-z0-9_$]*|\[["']?[A-Za-z_$][A-Za-z0-9_$]*["']?\])*$/,
|
|
86994
|
+
/^(?:changeme|change-me|replaceme|replace-me|placeholder|todo|tbd|yourkey|your-key|yourpassword|your-password|yoursecret|your-secret|yourtoken|your-token)(?:[-_][A-Za-z0-9-]+)?$/i,
|
|
86995
|
+
/^[a-z]+(?: [a-z]+){2,}$/
|
|
86996
|
+
];
|
|
86997
|
+
var CREDENTIAL_RUN_RE = /[A-Za-z0-9]{12,}/g;
|
|
86998
|
+
var MIXED_CLASS_RUN_MIN = 12;
|
|
86999
|
+
var SINGLE_CLASS_RUN_MIN = 16;
|
|
87000
|
+
function runIsCredentialShaped(run) {
|
|
87001
|
+
let classes = 0;
|
|
87002
|
+
if (/[a-z]/.test(run))
|
|
87003
|
+
classes++;
|
|
87004
|
+
if (/[A-Z]/.test(run))
|
|
87005
|
+
classes++;
|
|
87006
|
+
if (/[0-9]/.test(run))
|
|
87007
|
+
classes++;
|
|
87008
|
+
return run.length >= (classes >= 2 ? MIXED_CLASS_RUN_MIN : SINGLE_CLASS_RUN_MIN);
|
|
87009
|
+
}
|
|
87010
|
+
function hasCredentialShapedRun(value) {
|
|
87011
|
+
for (const run of value.match(CREDENTIAL_RUN_RE) ?? []) {
|
|
87012
|
+
if (runIsCredentialShaped(run))
|
|
87013
|
+
return true;
|
|
87014
|
+
}
|
|
87015
|
+
return false;
|
|
87016
|
+
}
|
|
87017
|
+
function isInertValue(value) {
|
|
87018
|
+
if (!INERT_VALUE_RE.some((re) => re.test(value)))
|
|
87019
|
+
return false;
|
|
87020
|
+
return !hasCredentialShapedRun(value);
|
|
87021
|
+
}
|
|
87022
|
+
var INERT_GATED_RULES = new Set([
|
|
87023
|
+
"env_key_value",
|
|
87024
|
+
"json_secret_field",
|
|
87025
|
+
"cli_flag"
|
|
87026
|
+
]);
|
|
87027
|
+
var TRAILING_PUNCT_RE = /[.,;:!?)\]}]+$/;
|
|
87028
|
+
function stripTrailingPunctuation(value) {
|
|
87029
|
+
return value.replace(TRAILING_PUNCT_RE, "");
|
|
87030
|
+
}
|
|
87031
|
+
|
|
87032
|
+
// telegram-plugin/secret-detect/kv-scanner.ts
|
|
87033
|
+
var KV_RE = /\b([A-Za-z_][A-Za-z0-9_-]*(?:password|passwd|token|secret|key|api[_-]?key))\s*[:=]\s*["']?([^\s"'\\]{8,})["']?/gi;
|
|
87034
|
+
var KV_ENTROPY_THRESHOLD = 4;
|
|
87035
|
+
var MEMORABLE_PW_RE = /\b([A-Za-z0-9_-]*(?:password|passwd|passphrase|pwd))\b\s*(?:[:=]\s*|\s+is\s+)(["']?)([^\s"']{8,64})\2/gi;
|
|
87036
|
+
var MEMORABLE_PW_RULE_ID = "memorable_password";
|
|
87037
|
+
var MEMORABLE_PW_MIN_CLASSES = 2;
|
|
87038
|
+
function charClassCount(value) {
|
|
87039
|
+
let n = 0;
|
|
87040
|
+
if (/[a-z]/.test(value))
|
|
87041
|
+
n++;
|
|
87042
|
+
if (/[A-Z]/.test(value))
|
|
87043
|
+
n++;
|
|
87044
|
+
if (/[0-9]/.test(value))
|
|
87045
|
+
n++;
|
|
87046
|
+
if (/[^A-Za-z0-9]/.test(value))
|
|
87047
|
+
n++;
|
|
87048
|
+
return n;
|
|
87049
|
+
}
|
|
87050
|
+
function looksLikeMemorablePassword(value) {
|
|
87051
|
+
if (isInertValue(value))
|
|
87052
|
+
return false;
|
|
87053
|
+
const core = stripTrailingPunctuation(value);
|
|
87054
|
+
if (core.length < 8 || core.length > 64)
|
|
87055
|
+
return false;
|
|
87056
|
+
if (isInertValue(core))
|
|
87057
|
+
return false;
|
|
87058
|
+
if (new Set(core).size < 4)
|
|
87059
|
+
return false;
|
|
87060
|
+
return charClassCount(core) >= MEMORABLE_PW_MIN_CLASSES;
|
|
87061
|
+
}
|
|
87062
|
+
function scanMemorablePasswords(text) {
|
|
87063
|
+
const hits = [];
|
|
87064
|
+
MEMORABLE_PW_RE.lastIndex = 0;
|
|
87065
|
+
let m;
|
|
87066
|
+
while ((m = MEMORABLE_PW_RE.exec(text)) !== null) {
|
|
87067
|
+
const keyName = m[1];
|
|
87068
|
+
const value = m[3];
|
|
87069
|
+
if (!value || !looksLikeMemorablePassword(value))
|
|
87070
|
+
continue;
|
|
87071
|
+
const valueOffsetInMatch = m[0].indexOf(value, keyName.length);
|
|
87072
|
+
if (valueOffsetInMatch < 0)
|
|
87073
|
+
continue;
|
|
87074
|
+
const start = m.index + valueOffsetInMatch;
|
|
87075
|
+
hits.push({
|
|
87076
|
+
rule_id: MEMORABLE_PW_RULE_ID,
|
|
87077
|
+
start,
|
|
87078
|
+
end: start + value.length,
|
|
87079
|
+
matched_text: value,
|
|
87080
|
+
key_name: keyName,
|
|
87081
|
+
confidence: "ambiguous"
|
|
87082
|
+
});
|
|
87083
|
+
}
|
|
87084
|
+
return hits;
|
|
87085
|
+
}
|
|
87086
|
+
function scanKeyValue(text) {
|
|
87087
|
+
const hits = [];
|
|
87088
|
+
KV_RE.lastIndex = 0;
|
|
87089
|
+
let m;
|
|
87090
|
+
while ((m = KV_RE.exec(text)) !== null) {
|
|
87091
|
+
const [, keyName, value] = m;
|
|
87092
|
+
if (!value)
|
|
87093
|
+
continue;
|
|
87094
|
+
if (isInertValue(value))
|
|
87095
|
+
continue;
|
|
87096
|
+
const h = shannonEntropy(value);
|
|
87097
|
+
if (h < KV_ENTROPY_THRESHOLD)
|
|
87098
|
+
continue;
|
|
87099
|
+
const valueOffsetInMatch = m[0].indexOf(value, keyName.length);
|
|
87100
|
+
if (valueOffsetInMatch < 0)
|
|
87101
|
+
continue;
|
|
87102
|
+
const start = m.index + valueOffsetInMatch;
|
|
87103
|
+
const end = start + value.length;
|
|
87104
|
+
hits.push({
|
|
87105
|
+
rule_id: "kv_entropy",
|
|
87106
|
+
start,
|
|
87107
|
+
end,
|
|
87108
|
+
matched_text: value,
|
|
87109
|
+
key_name: keyName,
|
|
87110
|
+
confidence: "ambiguous"
|
|
87111
|
+
});
|
|
87112
|
+
}
|
|
87113
|
+
return hits;
|
|
87114
|
+
}
|
|
87115
|
+
|
|
87116
|
+
// telegram-plugin/secret-detect/db-uri.ts
|
|
87117
|
+
var DB_URI_RE = /\b([a-zA-Z][a-zA-Z0-9+.-]+):\/\/([^\s:/@]+):([^\s/?#]+)@([^\s/@?#]+)/g;
|
|
87118
|
+
var DB_URI_RULE_ID = "db_uri_password";
|
|
87119
|
+
function isInertPassword(value) {
|
|
87120
|
+
if (value.startsWith("[REDACTED"))
|
|
87121
|
+
return true;
|
|
87122
|
+
return /^[*x]+$/i.test(value);
|
|
87123
|
+
}
|
|
87124
|
+
function scanDbUris(text) {
|
|
87125
|
+
const hits = [];
|
|
87126
|
+
DB_URI_RE.lastIndex = 0;
|
|
87127
|
+
let m;
|
|
87128
|
+
while ((m = DB_URI_RE.exec(text)) !== null) {
|
|
87129
|
+
const scheme = m[1];
|
|
87130
|
+
const user = m[2];
|
|
87131
|
+
const password = m[3];
|
|
87132
|
+
if (isInertPassword(password))
|
|
87133
|
+
continue;
|
|
87134
|
+
const start = m.index + scheme.length + 3 + user.length + 1;
|
|
87135
|
+
hits.push({
|
|
87136
|
+
rule_id: DB_URI_RULE_ID,
|
|
87137
|
+
start,
|
|
87138
|
+
end: start + password.length,
|
|
87139
|
+
matched_text: password,
|
|
87140
|
+
key_name: `${scheme}_password`,
|
|
87141
|
+
confidence: "ambiguous"
|
|
87142
|
+
});
|
|
87143
|
+
}
|
|
87144
|
+
return hits;
|
|
87145
|
+
}
|
|
87146
|
+
|
|
87147
|
+
// telegram-plugin/secret-detect/generic-entropy.ts
|
|
87148
|
+
var CANDIDATE_RE = /[A-Za-z0-9]{28,}/g;
|
|
87149
|
+
var GENERIC_MIN_DISTINCT = 18;
|
|
87150
|
+
var MAX_GENERIC_HITS = 20;
|
|
87151
|
+
function hasDistinctChars(tok, n) {
|
|
87152
|
+
const seen = new Uint8Array(128);
|
|
87153
|
+
let distinct = 0;
|
|
87154
|
+
for (let i = 0;i < tok.length; i++) {
|
|
87155
|
+
const c = tok.charCodeAt(i);
|
|
87156
|
+
if (seen[c] === 0) {
|
|
87157
|
+
seen[c] = 1;
|
|
87158
|
+
if (++distinct >= n)
|
|
87159
|
+
return true;
|
|
87160
|
+
}
|
|
87161
|
+
}
|
|
87162
|
+
return false;
|
|
87270
87163
|
}
|
|
87271
|
-
function
|
|
87272
|
-
|
|
87273
|
-
|
|
87274
|
-
|
|
87275
|
-
|
|
87276
|
-
console.log(source_default.gray(" 1. Create / open your Linear OAuth app with actor=app and scopes"));
|
|
87277
|
-
console.log(source_default.gray(" app:mentionable + app:assignable (https://linear.app/developers/agents)."));
|
|
87278
|
-
if (opts.clientId) {
|
|
87279
|
-
const redirect = opts.redirectUri ?? `${base.replace(/\/$/, "")}/oauth/callback`;
|
|
87280
|
-
const authorizeUrl = `https://linear.app/oauth/authorize?` + `client_id=${encodeURIComponent(opts.clientId)}` + `&redirect_uri=${encodeURIComponent(redirect)}` + `&response_type=code` + `&scope=${encodeURIComponent("read,write,app:assignable,app:mentionable")}` + `&actor=app`;
|
|
87281
|
-
console.log(source_default.gray(" 2. Authorize the app as an actor (open in a browser):"));
|
|
87282
|
-
console.log(source_default.cyan(` ${authorizeUrl}`));
|
|
87283
|
-
console.log(source_default.gray(" The redirect delivers the app token you pass to this verb via --token."));
|
|
87284
|
-
} else {
|
|
87285
|
-
console.log(source_default.gray(" 2. Authorize the app (actor=app) in a browser; capture the app token and"));
|
|
87286
|
-
console.log(source_default.gray(" re-run this verb with --token (and optionally --client-id to print the"));
|
|
87287
|
-
console.log(source_default.gray(" authorize URL)."));
|
|
87164
|
+
function hasDigit(tok) {
|
|
87165
|
+
for (let i = 0;i < tok.length; i++) {
|
|
87166
|
+
const c = tok.charCodeAt(i);
|
|
87167
|
+
if (c >= 48 && c <= 57)
|
|
87168
|
+
return true;
|
|
87288
87169
|
}
|
|
87289
|
-
|
|
87290
|
-
console.log(source_default.cyan(` ${webhookUrl}`));
|
|
87291
|
-
console.log(source_default.gray(" Use Linear's signing secret as the webhook secret \u2014 store it in the vault " + `under webhook/${opts.agent}/linear (e.g. 'switchroom telegram enable webhook ` + `--agent ${opts.agent} --source linear --secret <signing-secret>').`));
|
|
87292
|
-
console.log("");
|
|
87293
|
-
console.log(source_default.gray(` Token is read at runtime from vault:${vaultKey} (actor=app).`));
|
|
87170
|
+
return false;
|
|
87294
87171
|
}
|
|
87295
|
-
function
|
|
87296
|
-
const
|
|
87297
|
-
|
|
87298
|
-
|
|
87299
|
-
|
|
87300
|
-
|
|
87301
|
-
|
|
87302
|
-
|
|
87303
|
-
if (
|
|
87304
|
-
|
|
87305
|
-
|
|
87306
|
-
|
|
87307
|
-
|
|
87308
|
-
|
|
87309
|
-
|
|
87310
|
-
|
|
87311
|
-
|
|
87312
|
-
|
|
87313
|
-
}
|
|
87172
|
+
function scanGenericSecrets(text) {
|
|
87173
|
+
const hits = [];
|
|
87174
|
+
CANDIDATE_RE.lastIndex = 0;
|
|
87175
|
+
let m;
|
|
87176
|
+
while ((m = CANDIDATE_RE.exec(text)) !== null) {
|
|
87177
|
+
if (hits.length >= MAX_GENERIC_HITS)
|
|
87178
|
+
break;
|
|
87179
|
+
const tok = m[0];
|
|
87180
|
+
if (!hasDigit(tok))
|
|
87181
|
+
continue;
|
|
87182
|
+
if (!hasDistinctChars(tok, GENERIC_MIN_DISTINCT))
|
|
87183
|
+
continue;
|
|
87184
|
+
hits.push({
|
|
87185
|
+
rule_id: "generic_high_entropy",
|
|
87186
|
+
start: m.index,
|
|
87187
|
+
end: m.index + tok.length,
|
|
87188
|
+
matched_text: tok,
|
|
87189
|
+
confidence: "ambiguous"
|
|
87190
|
+
});
|
|
87314
87191
|
}
|
|
87315
|
-
return
|
|
87316
|
-
`);
|
|
87192
|
+
return hits;
|
|
87317
87193
|
}
|
|
87318
|
-
|
|
87319
|
-
|
|
87320
|
-
|
|
87194
|
+
|
|
87195
|
+
// telegram-plugin/secret-detect/chunker.ts
|
|
87196
|
+
var CHUNK_THRESHOLD = 32 * 1024;
|
|
87197
|
+
var WINDOW_SIZE = 16 * 1024;
|
|
87198
|
+
var OVERLAP = 8 * 1024;
|
|
87199
|
+
function chunk(text) {
|
|
87200
|
+
if (text.length <= CHUNK_THRESHOLD) {
|
|
87201
|
+
return [{ offset: 0, text }];
|
|
87202
|
+
}
|
|
87203
|
+
const out = [];
|
|
87204
|
+
let offset = 0;
|
|
87205
|
+
while (offset < text.length) {
|
|
87206
|
+
const end = Math.min(offset + WINDOW_SIZE, text.length);
|
|
87207
|
+
out.push({ offset, text: text.slice(offset, end) });
|
|
87208
|
+
if (end >= text.length)
|
|
87209
|
+
break;
|
|
87210
|
+
offset = end - OVERLAP;
|
|
87211
|
+
}
|
|
87212
|
+
return out;
|
|
87321
87213
|
}
|
|
87322
87214
|
|
|
87323
|
-
//
|
|
87324
|
-
|
|
87325
|
-
|
|
87215
|
+
// telegram-plugin/secret-detect/suppressor.ts
|
|
87216
|
+
var MARKERS = ["test", "mock", "example", "fixture", "dummy"];
|
|
87217
|
+
var WINDOW = 40;
|
|
87218
|
+
var MARKER_RE = new RegExp(`\\b(?:${MARKERS.join("|")})\\b`, "i");
|
|
87219
|
+
function isSuppressed(text, start, end) {
|
|
87220
|
+
const left = Math.max(0, start - WINDOW);
|
|
87221
|
+
const right = Math.min(text.length, end + WINDOW);
|
|
87222
|
+
const context = text.slice(left, start) + text.slice(end, right);
|
|
87223
|
+
return MARKER_RE.test(context);
|
|
87224
|
+
}
|
|
87326
87225
|
|
|
87327
|
-
//
|
|
87328
|
-
|
|
87329
|
-
|
|
87330
|
-
|
|
87226
|
+
// telegram-plugin/secret-detect/slug.ts
|
|
87227
|
+
function sanitizeKeyName(raw) {
|
|
87228
|
+
const up = raw.toUpperCase();
|
|
87229
|
+
const cleaned = up.replace(/[^A-Z0-9_]+/g, "_").replace(/_+/g, "_").replace(/^_+|_+$/g, "");
|
|
87230
|
+
return cleaned.length > 0 ? cleaned : "SECRET";
|
|
87331
87231
|
}
|
|
87332
|
-
function
|
|
87333
|
-
|
|
87232
|
+
function datePart(now = new Date) {
|
|
87233
|
+
const y = now.getUTCFullYear();
|
|
87234
|
+
const m = String(now.getUTCMonth() + 1).padStart(2, "0");
|
|
87235
|
+
const d = String(now.getUTCDate()).padStart(2, "0");
|
|
87236
|
+
return `${y}${m}${d}`;
|
|
87334
87237
|
}
|
|
87335
|
-
function
|
|
87336
|
-
|
|
87238
|
+
function deriveSlug(inputs, existing) {
|
|
87239
|
+
let base;
|
|
87240
|
+
if (inputs.key_name && inputs.key_name.trim().length > 0) {
|
|
87241
|
+
base = sanitizeKeyName(inputs.key_name);
|
|
87242
|
+
} else {
|
|
87243
|
+
base = `${inputs.rule_id}_${datePart(inputs.now)}`;
|
|
87244
|
+
}
|
|
87245
|
+
if (!existing.has(base))
|
|
87246
|
+
return base;
|
|
87247
|
+
let n = 2;
|
|
87248
|
+
while (existing.has(`${base}_${n}`))
|
|
87249
|
+
n++;
|
|
87250
|
+
return `${base}_${n}`;
|
|
87337
87251
|
}
|
|
87338
|
-
|
|
87339
|
-
|
|
87340
|
-
|
|
87341
|
-
|
|
87342
|
-
|
|
87343
|
-
|
|
87344
|
-
|
|
87345
|
-
|
|
87346
|
-
|
|
87252
|
+
// telegram-plugin/secret-detect/url-redact.ts
|
|
87253
|
+
var SENSITIVE_PARAMS = new Set([
|
|
87254
|
+
"token",
|
|
87255
|
+
"key",
|
|
87256
|
+
"api_key",
|
|
87257
|
+
"apikey",
|
|
87258
|
+
"secret",
|
|
87259
|
+
"access_token",
|
|
87260
|
+
"password",
|
|
87261
|
+
"pass",
|
|
87262
|
+
"auth",
|
|
87263
|
+
"client_secret",
|
|
87264
|
+
"refresh_token",
|
|
87265
|
+
"signature"
|
|
87266
|
+
]);
|
|
87267
|
+
var URL_RE = /\b(?:https?|wss?|ftp):\/\/[^\s<>"']+/gi;
|
|
87268
|
+
function redactUrls(text) {
|
|
87269
|
+
return text.replace(URL_RE, (m) => {
|
|
87270
|
+
const redacted = redactOne(m);
|
|
87271
|
+
return redacted ?? m;
|
|
87272
|
+
});
|
|
87273
|
+
}
|
|
87274
|
+
function redactOne(raw) {
|
|
87275
|
+
let u;
|
|
87276
|
+
try {
|
|
87277
|
+
u = new URL(raw);
|
|
87278
|
+
} catch {
|
|
87279
|
+
return null;
|
|
87280
|
+
}
|
|
87281
|
+
let changed = false;
|
|
87282
|
+
if (u.username || u.password) {
|
|
87283
|
+
u.username = "***";
|
|
87284
|
+
u.password = "";
|
|
87285
|
+
changed = true;
|
|
87286
|
+
}
|
|
87287
|
+
for (const [key] of u.searchParams) {
|
|
87288
|
+
if (SENSITIVE_PARAMS.has(key.toLowerCase())) {
|
|
87289
|
+
u.searchParams.set(key, "***");
|
|
87290
|
+
changed = true;
|
|
87347
87291
|
}
|
|
87348
87292
|
}
|
|
87349
|
-
|
|
87350
|
-
return { eligible, excluded, commands };
|
|
87293
|
+
return changed ? u.toString() : null;
|
|
87351
87294
|
}
|
|
87352
87295
|
|
|
87353
|
-
//
|
|
87354
|
-
|
|
87355
|
-
|
|
87356
|
-
|
|
87357
|
-
|
|
87358
|
-
|
|
87359
|
-
|
|
87360
|
-
|
|
87361
|
-
|
|
87362
|
-
|
|
87363
|
-
|
|
87364
|
-
|
|
87365
|
-
|
|
87366
|
-
|
|
87367
|
-
|
|
87368
|
-
|
|
87369
|
-
|
|
87370
|
-
|
|
87371
|
-
|
|
87372
|
-
|
|
87373
|
-
|
|
87374
|
-
|
|
87375
|
-
|
|
87376
|
-
|
|
87377
|
-
|
|
87378
|
-
|
|
87379
|
-
|
|
87380
|
-
|
|
87381
|
-
|
|
87382
|
-
|
|
87383
|
-
|
|
87384
|
-
|
|
87385
|
-
|
|
87386
|
-
|
|
87387
|
-
|
|
87388
|
-
|
|
87389
|
-
|
|
87390
|
-
|
|
87391
|
-
|
|
87392
|
-
|
|
87393
|
-
|
|
87394
|
-
|
|
87395
|
-
|
|
87396
|
-
breakdown.staleObservations += s.count;
|
|
87296
|
+
// telegram-plugin/secret-detect/index.ts
|
|
87297
|
+
function detectSecrets(text) {
|
|
87298
|
+
if (!text || text.length === 0)
|
|
87299
|
+
return [];
|
|
87300
|
+
const windows = chunk(text);
|
|
87301
|
+
const raw = [];
|
|
87302
|
+
for (const win of windows) {
|
|
87303
|
+
for (const p of ALL_PATTERNS) {
|
|
87304
|
+
const re = new RegExp(p.regex.source, p.regex.flags.includes("g") ? p.regex.flags : p.regex.flags + "g");
|
|
87305
|
+
let m;
|
|
87306
|
+
while ((m = re.exec(win.text)) !== null) {
|
|
87307
|
+
if (m[0].length === 0) {
|
|
87308
|
+
re.lastIndex++;
|
|
87309
|
+
continue;
|
|
87310
|
+
}
|
|
87311
|
+
const cap = p.captureIndex === 0 ? m[0] : m[p.captureIndex];
|
|
87312
|
+
if (!cap)
|
|
87313
|
+
continue;
|
|
87314
|
+
const matchStart = p.captureIndex === 0 ? m.index : m.index + m[0].indexOf(cap);
|
|
87315
|
+
if (matchStart < 0)
|
|
87316
|
+
continue;
|
|
87317
|
+
const globalStart = win.offset + matchStart;
|
|
87318
|
+
const globalEnd = globalStart + cap.length;
|
|
87319
|
+
const keyName = p.rule_id === "env_key_value" ? m[1] : undefined;
|
|
87320
|
+
if (INERT_GATED_RULES.has(p.rule_id) && isInertValue(cap))
|
|
87321
|
+
continue;
|
|
87322
|
+
if (p.rule_id === "env_key_value") {
|
|
87323
|
+
const ENV_KV_MIN_LEN = 12;
|
|
87324
|
+
const ENV_KV_MIN_ENTROPY = 3.5;
|
|
87325
|
+
if (cap.length < ENV_KV_MIN_LEN)
|
|
87326
|
+
continue;
|
|
87327
|
+
if (shannonEntropy(cap) < ENV_KV_MIN_ENTROPY)
|
|
87328
|
+
continue;
|
|
87329
|
+
}
|
|
87330
|
+
raw.push({
|
|
87331
|
+
rule_id: p.rule_id,
|
|
87332
|
+
start: globalStart,
|
|
87333
|
+
end: globalEnd,
|
|
87334
|
+
matched_text: cap,
|
|
87335
|
+
key_name: keyName,
|
|
87336
|
+
confidence: "high"
|
|
87337
|
+
});
|
|
87338
|
+
}
|
|
87397
87339
|
}
|
|
87398
|
-
|
|
87399
|
-
|
|
87400
|
-
|
|
87401
|
-
} else if (volatileTags.length > 0 && hasStable) {
|
|
87402
|
-
breakdown.mixedScopes += 1;
|
|
87403
|
-
breakdown.mixedObservations += s.count;
|
|
87340
|
+
const kvHits = scanKeyValue(win.text);
|
|
87341
|
+
for (const h of kvHits) {
|
|
87342
|
+
raw.push({ ...h, start: h.start + win.offset, end: h.end + win.offset });
|
|
87404
87343
|
}
|
|
87405
|
-
const
|
|
87406
|
-
|
|
87407
|
-
|
|
87408
|
-
|
|
87409
|
-
|
|
87410
|
-
|
|
87411
|
-
|
|
87412
|
-
|
|
87413
|
-
|
|
87414
|
-
count: s.count,
|
|
87415
|
-
strippedVolatile: volatileTags,
|
|
87416
|
-
strippedStale: retireStale ? staleTags : []
|
|
87417
|
-
});
|
|
87344
|
+
for (const h of scanDbUris(win.text)) {
|
|
87345
|
+
raw.push({ ...h, start: h.start + win.offset, end: h.end + win.offset });
|
|
87346
|
+
}
|
|
87347
|
+
for (const h of scanMemorablePasswords(win.text)) {
|
|
87348
|
+
raw.push({ ...h, start: h.start + win.offset, end: h.end + win.offset });
|
|
87349
|
+
}
|
|
87350
|
+
const genHits = scanGenericSecrets(win.text);
|
|
87351
|
+
for (const h of genHits) {
|
|
87352
|
+
raw.push({ ...h, start: h.start + win.offset, end: h.end + win.offset });
|
|
87418
87353
|
}
|
|
87419
87354
|
}
|
|
87420
|
-
|
|
87421
|
-
const
|
|
87422
|
-
const
|
|
87423
|
-
const
|
|
87424
|
-
|
|
87425
|
-
|
|
87426
|
-
|
|
87427
|
-
|
|
87428
|
-
|
|
87429
|
-
|
|
87430
|
-
|
|
87431
|
-
|
|
87432
|
-
|
|
87433
|
-
|
|
87434
|
-
|
|
87435
|
-
|
|
87436
|
-
|
|
87355
|
+
const deduped = dedupeRaw(raw);
|
|
87356
|
+
const final = dropOverlaps(deduped);
|
|
87357
|
+
const existing = new Set;
|
|
87358
|
+
const out = [];
|
|
87359
|
+
for (const h of final) {
|
|
87360
|
+
const suggested_slug = deriveSlug({ key_name: h.key_name, rule_id: h.rule_id }, existing);
|
|
87361
|
+
existing.add(suggested_slug);
|
|
87362
|
+
out.push({
|
|
87363
|
+
rule_id: h.rule_id,
|
|
87364
|
+
matched_text: h.matched_text,
|
|
87365
|
+
start: h.start,
|
|
87366
|
+
end: h.end,
|
|
87367
|
+
confidence: h.confidence,
|
|
87368
|
+
suppressed: isSuppressed(text, h.start, h.end),
|
|
87369
|
+
suggested_slug,
|
|
87370
|
+
key_name: h.key_name
|
|
87371
|
+
});
|
|
87372
|
+
}
|
|
87373
|
+
out.sort((a, b) => a.start - b.start);
|
|
87374
|
+
return out;
|
|
87437
87375
|
}
|
|
87438
|
-
|
|
87439
|
-
const
|
|
87440
|
-
const
|
|
87441
|
-
|
|
87442
|
-
|
|
87443
|
-
|
|
87444
|
-
|
|
87445
|
-
|
|
87446
|
-
|
|
87447
|
-
|
|
87448
|
-
|
|
87449
|
-
|
|
87450
|
-
clearTimeout(timeout);
|
|
87451
|
-
if (err.name === "AbortError")
|
|
87452
|
-
return { ok: false, reason: "Timeout" };
|
|
87453
|
-
return { ok: false, reason: String(err.message ?? err) };
|
|
87376
|
+
function dedupeRaw(raw) {
|
|
87377
|
+
const seen = new Map;
|
|
87378
|
+
for (const h of raw) {
|
|
87379
|
+
const key = `${h.start}:${h.end}`;
|
|
87380
|
+
const existing = seen.get(key);
|
|
87381
|
+
if (!existing) {
|
|
87382
|
+
seen.set(key, h);
|
|
87383
|
+
continue;
|
|
87384
|
+
}
|
|
87385
|
+
if (existing.confidence === "ambiguous" && h.confidence === "high") {
|
|
87386
|
+
seen.set(key, h);
|
|
87387
|
+
}
|
|
87454
87388
|
}
|
|
87389
|
+
return Array.from(seen.values());
|
|
87455
87390
|
}
|
|
87456
|
-
|
|
87457
|
-
const
|
|
87458
|
-
|
|
87459
|
-
|
|
87460
|
-
|
|
87461
|
-
|
|
87462
|
-
|
|
87463
|
-
|
|
87464
|
-
|
|
87465
|
-
|
|
87466
|
-
|
|
87467
|
-
|
|
87468
|
-
|
|
87469
|
-
|
|
87470
|
-
|
|
87471
|
-
|
|
87472
|
-
|
|
87473
|
-
|
|
87474
|
-
|
|
87475
|
-
allVolatileObservations: 0,
|
|
87476
|
-
mixedScopes: 0,
|
|
87477
|
-
mixedObservations: 0,
|
|
87478
|
-
staleScopes: 0,
|
|
87479
|
-
staleObservations: 0
|
|
87480
|
-
}
|
|
87481
|
-
};
|
|
87391
|
+
function dropOverlaps(hits) {
|
|
87392
|
+
const out = hits.filter((h) => !(h.confidence === "ambiguous" && hits.some((o) => o !== h && o.start <= h.start && o.end >= h.end && !(o.start === h.start && o.end === h.end))));
|
|
87393
|
+
out.sort((a, b) => a.start - b.start || a.end - b.end);
|
|
87394
|
+
return out;
|
|
87395
|
+
}
|
|
87396
|
+
|
|
87397
|
+
// telegram-plugin/secret-detect/redact.ts
|
|
87398
|
+
var REDACTED_MARKER = "[REDACTED]";
|
|
87399
|
+
function redact(text) {
|
|
87400
|
+
if (!text || text.length === 0)
|
|
87401
|
+
return text;
|
|
87402
|
+
const urlScrubbed = redactUrls(text);
|
|
87403
|
+
const hits = detectSecrets(urlScrubbed).filter((h) => h.rule_id !== "generic_high_entropy");
|
|
87404
|
+
if (hits.length === 0)
|
|
87405
|
+
return urlScrubbed;
|
|
87406
|
+
const sorted = [...hits].sort((a, b) => b.start - a.start);
|
|
87407
|
+
let out = urlScrubbed;
|
|
87408
|
+
for (const h of sorted) {
|
|
87409
|
+
out = out.slice(0, h.start) + redactedMarker(h.rule_id) + out.slice(h.end);
|
|
87482
87410
|
}
|
|
87483
|
-
|
|
87484
|
-
|
|
87485
|
-
|
|
87486
|
-
|
|
87487
|
-
|
|
87488
|
-
|
|
87489
|
-
if (!Array.isArray(tags) || typeof count !== "number")
|
|
87490
|
-
continue;
|
|
87491
|
-
if (!tags.every((t) => typeof t === "string"))
|
|
87492
|
-
continue;
|
|
87493
|
-
scopes.push({ tags, count });
|
|
87494
|
-
}
|
|
87411
|
+
return out;
|
|
87412
|
+
}
|
|
87413
|
+
function redactedMarker(ruleId) {
|
|
87414
|
+
const trimmed = ruleId.replace(/^(kv|env)_/, "");
|
|
87415
|
+
if (!trimmed || trimmed === "key_value" || trimmed === "kv_entropy") {
|
|
87416
|
+
return REDACTED_MARKER;
|
|
87495
87417
|
}
|
|
87496
|
-
return
|
|
87418
|
+
return `[REDACTED:${trimmed}]`;
|
|
87497
87419
|
}
|
|
87498
|
-
|
|
87499
|
-
|
|
87500
|
-
|
|
87420
|
+
// src/memory/hindsight-write-redaction.ts
|
|
87421
|
+
function redactJsonStrings(value) {
|
|
87422
|
+
if (typeof value === "string")
|
|
87423
|
+
return redact(value);
|
|
87424
|
+
if (Array.isArray(value))
|
|
87425
|
+
return value.map(redactJsonStrings);
|
|
87426
|
+
if (value && typeof value === "object") {
|
|
87427
|
+
const out = {};
|
|
87428
|
+
for (const [k, v] of Object.entries(value)) {
|
|
87429
|
+
out[k] = redactJsonStrings(v);
|
|
87430
|
+
}
|
|
87431
|
+
return out;
|
|
87501
87432
|
}
|
|
87502
|
-
|
|
87503
|
-
|
|
87504
|
-
|
|
87505
|
-
|
|
87506
|
-
|
|
87507
|
-
|
|
87508
|
-
|
|
87509
|
-
|
|
87510
|
-
|
|
87511
|
-
|
|
87433
|
+
return value;
|
|
87434
|
+
}
|
|
87435
|
+
var REDACTED_ITEM_FIELDS = ["content", "context"];
|
|
87436
|
+
function redactMemoryWriteBody(body) {
|
|
87437
|
+
if (!body || typeof body !== "object" || Array.isArray(body))
|
|
87438
|
+
return body;
|
|
87439
|
+
const src = body;
|
|
87440
|
+
if (!Array.isArray(src.items))
|
|
87441
|
+
return body;
|
|
87442
|
+
const items = src.items.map((item) => {
|
|
87443
|
+
if (!item || typeof item !== "object" || Array.isArray(item))
|
|
87444
|
+
return item;
|
|
87445
|
+
const row = { ...item };
|
|
87446
|
+
for (const field of REDACTED_ITEM_FIELDS) {
|
|
87447
|
+
if (typeof row[field] === "string") {
|
|
87448
|
+
row[field] = redact(row[field]);
|
|
87449
|
+
}
|
|
87512
87450
|
}
|
|
87513
|
-
if (
|
|
87514
|
-
|
|
87451
|
+
if (row.metadata && typeof row.metadata === "object") {
|
|
87452
|
+
row.metadata = redactJsonStrings(row.metadata);
|
|
87515
87453
|
}
|
|
87516
|
-
|
|
87517
|
-
|
|
87518
|
-
|
|
87454
|
+
return row;
|
|
87455
|
+
});
|
|
87456
|
+
return { ...src, items };
|
|
87519
87457
|
}
|
|
87520
87458
|
|
|
87521
87459
|
// src/cli/memory.ts
|
|
@@ -97162,14 +97100,10 @@ function registerHandoffCommand(program3) {
|
|
|
97162
97100
|
jsonlPath: jsonl,
|
|
97163
97101
|
agentDir,
|
|
97164
97102
|
agentName,
|
|
97165
|
-
maxTurns: cappedMaxTurns
|
|
97166
|
-
observationScopes: agentConfig?.memory?.observation_scopes
|
|
97103
|
+
maxTurns: cappedMaxTurns
|
|
97167
97104
|
});
|
|
97168
97105
|
process.stderr.write(`handoff: ${status}
|
|
97169
97106
|
`);
|
|
97170
|
-
if (status === HANDOFF_STATUS_MIRROR_SKIPPED) {
|
|
97171
|
-
process.exitCode = 1;
|
|
97172
|
-
}
|
|
97173
97107
|
const retention = pruneSessionJsonl(claudeConfigDir, {
|
|
97174
97108
|
maxCount: continuity?.session_retention_max_count ?? DEFAULT_SESSION_RETENTION_MAX_COUNT,
|
|
97175
97109
|
maxAgeDays: continuity?.session_retention_max_age_days ?? DEFAULT_SESSION_RETENTION_MAX_AGE_DAYS,
|