switchroom 0.19.44 → 0.19.46
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 +848 -826
- package/dist/host-control/main.js +1 -1
- package/package.json +1 -1
- package/profiles/_base/start.sh.hbs +48 -4
- package/telegram-plugin/dist/gateway/gateway.js +499 -322
- package/telegram-plugin/flushed-turn-supersede.ts +190 -16
- package/telegram-plugin/gateway/auth-command.ts +7 -4
- package/telegram-plugin/gateway/auth-loopback-relay.ts +39 -20
- package/telegram-plugin/gateway/cron-session.ts +31 -0
- package/telegram-plugin/gateway/gateway.ts +67 -66
- 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/auth-loopback-relay.test.ts +58 -0
- 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.46", COMMIT_SHA = "306c3ed1";
|
|
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) {
|
|
@@ -37460,11 +37439,14 @@ __export(exports_drive, {
|
|
|
37460
37439
|
selectGoogleWorkspaceScopes: () => selectGoogleWorkspaceScopes,
|
|
37461
37440
|
selectDriveAccountScopes: () => selectDriveAccountScopes,
|
|
37462
37441
|
runDriveOAuthFlow: () => runDriveOAuthFlow,
|
|
37442
|
+
resolveReconsentCapabilities: () => resolveReconsentCapabilities,
|
|
37463
37443
|
registerDriveCommand: () => registerDriveCommand,
|
|
37444
|
+
capabilitiesFromScopeString: () => capabilitiesFromScopeString,
|
|
37464
37445
|
__test: () => __test,
|
|
37465
37446
|
GOOGLE_SLIDES_SCOPE: () => GOOGLE_SLIDES_SCOPE,
|
|
37466
37447
|
GOOGLE_SHEETS_SCOPE: () => GOOGLE_SHEETS_SCOPE,
|
|
37467
37448
|
GOOGLE_DOCS_SCOPE: () => GOOGLE_DOCS_SCOPE,
|
|
37449
|
+
GOOGLE_CALENDAR_READONLY_SCOPE: () => GOOGLE_CALENDAR_READONLY_SCOPE,
|
|
37468
37450
|
DRIVE_WRITE_SCOPES: () => DRIVE_WRITE_SCOPES,
|
|
37469
37451
|
DRIVE_READONLY_SCOPES: () => DRIVE_READONLY_SCOPES
|
|
37470
37452
|
});
|
|
@@ -37482,7 +37464,29 @@ function selectDriveAccountScopes(write) {
|
|
|
37482
37464
|
function selectGoogleWorkspaceScopes(opts) {
|
|
37483
37465
|
const base = selectDriveAccountScopes(opts.write);
|
|
37484
37466
|
const workspace = workspaceScopesForTier(opts.tier ?? "core");
|
|
37485
|
-
|
|
37467
|
+
const calendar = opts.calendar ? [GOOGLE_CALENDAR_READONLY_SCOPE] : [];
|
|
37468
|
+
return [...new Set([...base, ...workspace, ...calendar])];
|
|
37469
|
+
}
|
|
37470
|
+
function capabilitiesFromScopeString(scope) {
|
|
37471
|
+
const have = new Set(scope.split(/\s+/).map((s) => s.trim()).filter((s) => s.length > 0));
|
|
37472
|
+
return {
|
|
37473
|
+
write: have.has("https://www.googleapis.com/auth/drive.file"),
|
|
37474
|
+
calendar: have.has(GOOGLE_CALENDAR_READONLY_SCOPE)
|
|
37475
|
+
};
|
|
37476
|
+
}
|
|
37477
|
+
function resolveReconsentCapabilities(requested, existing) {
|
|
37478
|
+
const keys = ["write", "calendar"];
|
|
37479
|
+
if (!existing) {
|
|
37480
|
+
return { effective: { ...requested }, carried: [], added: [] };
|
|
37481
|
+
}
|
|
37482
|
+
return {
|
|
37483
|
+
effective: {
|
|
37484
|
+
write: requested.write || existing.write,
|
|
37485
|
+
calendar: requested.calendar || existing.calendar
|
|
37486
|
+
},
|
|
37487
|
+
carried: keys.filter((k) => existing[k] && !requested[k]),
|
|
37488
|
+
added: keys.filter((k) => requested[k] && !existing[k])
|
|
37489
|
+
};
|
|
37486
37490
|
}
|
|
37487
37491
|
function getVaultPath(configPath) {
|
|
37488
37492
|
try {
|
|
@@ -37896,7 +37900,7 @@ function registerDriveCommand(program3, deps = {}) {
|
|
|
37896
37900
|
await runDisconnect({ agentName: agent }, deps);
|
|
37897
37901
|
});
|
|
37898
37902
|
}
|
|
37899
|
-
var EXIT_OK = 0, EXIT_DENIED = 1, EXIT_TIMEOUT = 2, EXIT_RATE_LIMITED = 3, EXIT_ERROR = 4, EXIT_ABORTED = 130, DRIVE_READONLY_SCOPES, DRIVE_WRITE_SCOPES, DEFAULT_SCOPES, GOOGLE_DOCS_SCOPE = "https://www.googleapis.com/auth/documents", GOOGLE_SHEETS_SCOPE = "https://www.googleapis.com/auth/spreadsheets", GOOGLE_SLIDES_SCOPE = "https://www.googleapis.com/auth/presentations", __test;
|
|
37903
|
+
var EXIT_OK = 0, EXIT_DENIED = 1, EXIT_TIMEOUT = 2, EXIT_RATE_LIMITED = 3, EXIT_ERROR = 4, EXIT_ABORTED = 130, DRIVE_READONLY_SCOPES, DRIVE_WRITE_SCOPES, DEFAULT_SCOPES, GOOGLE_DOCS_SCOPE = "https://www.googleapis.com/auth/documents", GOOGLE_SHEETS_SCOPE = "https://www.googleapis.com/auth/spreadsheets", GOOGLE_SLIDES_SCOPE = "https://www.googleapis.com/auth/presentations", GOOGLE_CALENDAR_READONLY_SCOPE = "https://www.googleapis.com/auth/calendar.readonly", __test;
|
|
37900
37904
|
var init_drive = __esm(() => {
|
|
37901
37905
|
init_source();
|
|
37902
37906
|
init_loader();
|
|
@@ -73049,764 +73053,170 @@ import { execFileSync as execFileSync11 } from "node:child_process";
|
|
|
73049
73053
|
init_atomic();
|
|
73050
73054
|
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
73055
|
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;
|
|
73056
|
+
var DEFAULT_MAX_TURNS = 50;
|
|
73057
|
+
var TOPIC_MAX_CHARS = 117;
|
|
73058
|
+
var TURN_TEXT_MAX_CHARS = 1200;
|
|
73059
|
+
function extractTurnsFromJsonl(path2, maxTurns) {
|
|
73060
|
+
let raw;
|
|
73061
|
+
try {
|
|
73062
|
+
raw = readFileSync20(path2, "utf-8");
|
|
73063
|
+
} catch {
|
|
73064
|
+
return [];
|
|
73197
73065
|
}
|
|
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)
|
|
73066
|
+
const turns = [];
|
|
73067
|
+
for (const line of raw.split(`
|
|
73068
|
+
`)) {
|
|
73069
|
+
if (!line.trim())
|
|
73256
73070
|
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)
|
|
73071
|
+
let obj;
|
|
73072
|
+
try {
|
|
73073
|
+
obj = JSON.parse(line);
|
|
73074
|
+
} catch {
|
|
73276
73075
|
continue;
|
|
73277
|
-
|
|
73076
|
+
}
|
|
73077
|
+
if (obj.type === "queue-operation") {
|
|
73078
|
+
const op = obj.operation;
|
|
73079
|
+
if (op !== "enqueue")
|
|
73080
|
+
continue;
|
|
73081
|
+
const content = obj.content;
|
|
73082
|
+
if (typeof content !== "string")
|
|
73083
|
+
continue;
|
|
73084
|
+
const text = extractChannelBody(content);
|
|
73085
|
+
if (text)
|
|
73086
|
+
turns.push({ role: "user", text });
|
|
73278
73087
|
continue;
|
|
73279
|
-
|
|
73280
|
-
if (
|
|
73088
|
+
}
|
|
73089
|
+
if (obj.type === "user" && obj.message && typeof obj.message === "object") {
|
|
73090
|
+
const content = obj.message.content;
|
|
73091
|
+
const raw2 = extractTextBlocks(content);
|
|
73092
|
+
const text = raw2 ? extractChannelBody(raw2) : null;
|
|
73093
|
+
if (text)
|
|
73094
|
+
turns.push({ role: "user", text });
|
|
73281
73095
|
continue;
|
|
73282
|
-
|
|
73283
|
-
if (
|
|
73096
|
+
}
|
|
73097
|
+
if (obj.type === "assistant" && obj.message && typeof obj.message === "object") {
|
|
73098
|
+
const content = obj.message.content;
|
|
73099
|
+
const text = extractTextBlocks(content);
|
|
73100
|
+
if (text)
|
|
73101
|
+
turns.push({ role: "assistant", text });
|
|
73284
73102
|
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
|
-
});
|
|
73103
|
+
}
|
|
73295
73104
|
}
|
|
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))
|
|
73105
|
+
const deduped = [];
|
|
73106
|
+
for (const t of turns) {
|
|
73107
|
+
const prev = deduped[deduped.length - 1];
|
|
73108
|
+
if (prev && prev.role === t.role && prev.text === t.text)
|
|
73316
73109
|
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
|
-
});
|
|
73110
|
+
deduped.push(t);
|
|
73326
73111
|
}
|
|
73327
|
-
|
|
73112
|
+
if (deduped.length <= maxTurns)
|
|
73113
|
+
return deduped;
|
|
73114
|
+
return deduped.slice(deduped.length - maxTurns);
|
|
73328
73115
|
}
|
|
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;
|
|
73116
|
+
function extractChannelBody(raw) {
|
|
73117
|
+
const m = raw.match(/<channel[^>]*>([\s\S]*?)<\/channel>/);
|
|
73118
|
+
if (m)
|
|
73119
|
+
return m[1].trim();
|
|
73120
|
+
const trimmed = raw.trim();
|
|
73121
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
73346
73122
|
}
|
|
73347
|
-
function
|
|
73348
|
-
|
|
73349
|
-
const
|
|
73350
|
-
|
|
73351
|
-
return true;
|
|
73123
|
+
function extractTextBlocks(content) {
|
|
73124
|
+
if (typeof content === "string") {
|
|
73125
|
+
const t = content.trim();
|
|
73126
|
+
return t.length > 0 ? t : null;
|
|
73352
73127
|
}
|
|
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))
|
|
73128
|
+
if (!Array.isArray(content))
|
|
73129
|
+
return null;
|
|
73130
|
+
const parts = [];
|
|
73131
|
+
for (const block of content) {
|
|
73132
|
+
if (!block || typeof block !== "object")
|
|
73366
73133
|
continue;
|
|
73367
|
-
|
|
73368
|
-
|
|
73369
|
-
|
|
73370
|
-
|
|
73371
|
-
|
|
73372
|
-
|
|
73373
|
-
});
|
|
73134
|
+
const b = block;
|
|
73135
|
+
if (b.type === "text" && typeof b.text === "string") {
|
|
73136
|
+
parts.push(b.text);
|
|
73137
|
+
} else if (b.type === "tool_result" && typeof b.content === "string") {
|
|
73138
|
+
parts.push(`[tool result] ${b.content.slice(0, 400)}`);
|
|
73139
|
+
}
|
|
73374
73140
|
}
|
|
73375
|
-
|
|
73141
|
+
const joined = parts.join(`
|
|
73142
|
+
`).trim();
|
|
73143
|
+
return joined.length > 0 ? joined : null;
|
|
73376
73144
|
}
|
|
73145
|
+
function formatTranscriptTail(turns) {
|
|
73146
|
+
const header = `# Handoff \u2014 previous session
|
|
73377
73147
|
|
|
73378
|
-
|
|
73379
|
-
|
|
73380
|
-
|
|
73381
|
-
|
|
73382
|
-
|
|
73383
|
-
|
|
73384
|
-
return [{ offset: 0, text }];
|
|
73148
|
+
` + "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.
|
|
73149
|
+
`;
|
|
73150
|
+
if (turns.length === 0) {
|
|
73151
|
+
return header + `
|
|
73152
|
+
_(No recent turns were recoverable from the previous session.)_
|
|
73153
|
+
`;
|
|
73385
73154
|
}
|
|
73386
|
-
const
|
|
73387
|
-
|
|
73388
|
-
|
|
73389
|
-
|
|
73390
|
-
|
|
73391
|
-
|
|
73392
|
-
|
|
73393
|
-
|
|
73155
|
+
const body = turns.map((t) => {
|
|
73156
|
+
let text = t.text;
|
|
73157
|
+
if (text.length > TURN_TEXT_MAX_CHARS) {
|
|
73158
|
+
text = text.slice(0, TURN_TEXT_MAX_CHARS) + `
|
|
73159
|
+
\u2026[truncated]`;
|
|
73160
|
+
}
|
|
73161
|
+
return `### ${t.role === "user" ? "User" : "Assistant"}
|
|
73162
|
+
${text}`;
|
|
73163
|
+
}).join(`
|
|
73164
|
+
|
|
73165
|
+
`);
|
|
73166
|
+
return `${header}
|
|
73167
|
+
## Recent turns
|
|
73168
|
+
|
|
73169
|
+
${body}
|
|
73170
|
+
`;
|
|
73171
|
+
}
|
|
73172
|
+
function deriveTopic(turns) {
|
|
73173
|
+
for (let i = turns.length - 1;i >= 0; i--) {
|
|
73174
|
+
if (turns[i].role === "user")
|
|
73175
|
+
return clampTopic(firstLine(turns[i].text));
|
|
73394
73176
|
}
|
|
73395
|
-
|
|
73177
|
+
if (turns.length > 0)
|
|
73178
|
+
return clampTopic(firstLine(turns[turns.length - 1].text));
|
|
73179
|
+
return "previous session";
|
|
73396
73180
|
}
|
|
73397
|
-
|
|
73398
|
-
|
|
73399
|
-
|
|
73400
|
-
var WINDOW = 40;
|
|
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);
|
|
73181
|
+
function firstLine(s) {
|
|
73182
|
+
const line = s.split(/\r?\n/).find((l) => l.trim().length > 0) ?? s;
|
|
73183
|
+
return line.trim();
|
|
73407
73184
|
}
|
|
73408
|
-
|
|
73409
|
-
|
|
73410
|
-
function sanitizeKeyName(raw) {
|
|
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";
|
|
73185
|
+
function clampTopic(s) {
|
|
73186
|
+
return s.length > TOPIC_MAX_CHARS ? s.slice(0, TOPIC_MAX_CHARS) + "\u2026" : s;
|
|
73414
73187
|
}
|
|
73415
|
-
function
|
|
73416
|
-
|
|
73417
|
-
const
|
|
73418
|
-
const
|
|
73419
|
-
|
|
73188
|
+
function writeSidecarsAtomic(agentDir, briefing, topic) {
|
|
73189
|
+
mkdirSync16(agentDir, { recursive: true });
|
|
73190
|
+
const handoffPath = join18(agentDir, ".handoff.md");
|
|
73191
|
+
const topicPath = join18(agentDir, ".handoff-topic");
|
|
73192
|
+
const handoffTmp = handoffPath + ".tmp";
|
|
73193
|
+
const topicTmp = topicPath + ".tmp";
|
|
73194
|
+
writeFileSync9(handoffTmp, briefing, "utf-8");
|
|
73195
|
+
writeFileSync9(topicTmp, topic, "utf-8");
|
|
73196
|
+
fsyncPathSync(handoffTmp);
|
|
73197
|
+
fsyncPathSync(topicTmp);
|
|
73198
|
+
renameSync7(handoffTmp, handoffPath);
|
|
73199
|
+
renameSync7(topicTmp, topicPath);
|
|
73200
|
+
try {
|
|
73201
|
+
fsyncPathSync(agentDir);
|
|
73202
|
+
} catch {}
|
|
73420
73203
|
}
|
|
73421
|
-
function
|
|
73422
|
-
|
|
73423
|
-
|
|
73424
|
-
|
|
73425
|
-
|
|
73426
|
-
base = `${inputs.rule_id}_${datePart(inputs.now)}`;
|
|
73204
|
+
async function buildHandoff(opts) {
|
|
73205
|
+
const maxTurns = opts.maxTurns ?? DEFAULT_MAX_TURNS;
|
|
73206
|
+
const turns = extractTurnsFromJsonl(opts.jsonlPath, maxTurns);
|
|
73207
|
+
if (turns.length === 0) {
|
|
73208
|
+
return "no-turns";
|
|
73427
73209
|
}
|
|
73428
|
-
|
|
73429
|
-
|
|
73430
|
-
|
|
73431
|
-
|
|
73432
|
-
|
|
73433
|
-
|
|
73434
|
-
|
|
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
|
-
});
|
|
73456
|
-
}
|
|
73457
|
-
function redactOne(raw) {
|
|
73458
|
-
let u;
|
|
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;
|
|
73776
|
-
}
|
|
73777
|
-
function writeSidecarsAtomic(agentDir, briefing, topic) {
|
|
73778
|
-
mkdirSync16(agentDir, { recursive: true });
|
|
73779
|
-
const handoffPath = join18(agentDir, ".handoff.md");
|
|
73780
|
-
const topicPath = join18(agentDir, ".handoff-topic");
|
|
73781
|
-
const handoffTmp = handoffPath + ".tmp";
|
|
73782
|
-
const topicTmp = topicPath + ".tmp";
|
|
73783
|
-
writeFileSync9(handoffTmp, briefing, "utf-8");
|
|
73784
|
-
writeFileSync9(topicTmp, topic, "utf-8");
|
|
73785
|
-
fsyncPathSync(handoffTmp);
|
|
73786
|
-
fsyncPathSync(topicTmp);
|
|
73787
|
-
renameSync7(handoffTmp, handoffPath);
|
|
73788
|
-
renameSync7(topicTmp, topicPath);
|
|
73789
|
-
try {
|
|
73790
|
-
fsyncPathSync(agentDir);
|
|
73791
|
-
} catch {}
|
|
73792
|
-
}
|
|
73793
|
-
async function buildHandoff(opts) {
|
|
73794
|
-
const maxTurns = opts.maxTurns ?? DEFAULT_MAX_TURNS;
|
|
73795
|
-
const turns = extractTurnsFromJsonl(opts.jsonlPath, maxTurns);
|
|
73796
|
-
if (turns.length === 0) {
|
|
73797
|
-
return "no-turns";
|
|
73798
|
-
}
|
|
73799
|
-
const briefing = formatTranscriptTail(turns);
|
|
73800
|
-
const topic = deriveTopic(turns);
|
|
73801
|
-
try {
|
|
73802
|
-
writeSidecarsAtomic(opts.agentDir, briefing, topic);
|
|
73803
|
-
} catch (err) {
|
|
73804
|
-
process.stderr.write(`handoff: sidecar write failed \u2014 ${errMsg(err)}
|
|
73805
|
-
`);
|
|
73806
|
-
return "write-error";
|
|
73807
|
-
}
|
|
73808
|
-
const mirrored = await mirrorToHindsight(briefing, opts).catch(() => true);
|
|
73809
|
-
return mirrored ? "ok" : HANDOFF_STATUS_MIRROR_SKIPPED;
|
|
73210
|
+
const briefing = formatTranscriptTail(turns);
|
|
73211
|
+
const topic = deriveTopic(turns);
|
|
73212
|
+
try {
|
|
73213
|
+
writeSidecarsAtomic(opts.agentDir, briefing, topic);
|
|
73214
|
+
} catch (err) {
|
|
73215
|
+
process.stderr.write(`handoff: sidecar write failed \u2014 ${errMsg(err)}
|
|
73216
|
+
`);
|
|
73217
|
+
return "write-error";
|
|
73218
|
+
}
|
|
73219
|
+
return "ok";
|
|
73810
73220
|
}
|
|
73811
73221
|
function errMsg(err) {
|
|
73812
73222
|
if (err && typeof err === "object" && "message" in err) {
|
|
@@ -73814,42 +73224,6 @@ function errMsg(err) {
|
|
|
73814
73224
|
}
|
|
73815
73225
|
return String(err);
|
|
73816
73226
|
}
|
|
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
73227
|
function findLatestSessionJsonl(claudeConfigDir) {
|
|
73854
73228
|
const projects = join18(claudeConfigDir, "projects");
|
|
73855
73229
|
if (!existsSync24(projects))
|
|
@@ -76856,8 +76230,8 @@ Bootstrapping agent: ${name}
|
|
|
76856
76230
|
const code = await new Promise((resolve25) => {
|
|
76857
76231
|
process.stdin.setEncoding("utf-8");
|
|
76858
76232
|
let buf = "";
|
|
76859
|
-
process.stdin.on("data", (
|
|
76860
|
-
buf +=
|
|
76233
|
+
process.stdin.on("data", (chunk) => {
|
|
76234
|
+
buf += chunk.toString();
|
|
76861
76235
|
const newlineIdx = buf.indexOf(`
|
|
76862
76236
|
`);
|
|
76863
76237
|
if (newlineIdx !== -1) {
|
|
@@ -76939,8 +76313,8 @@ switchroom agent add: ${name}
|
|
|
76939
76313
|
return new Promise((resolveLine) => {
|
|
76940
76314
|
process.stdin.setEncoding("utf-8");
|
|
76941
76315
|
let buf = "";
|
|
76942
|
-
const onData = (
|
|
76943
|
-
buf +=
|
|
76316
|
+
const onData = (chunk) => {
|
|
76317
|
+
buf += chunk.toString();
|
|
76944
76318
|
const newlineIdx = buf.indexOf(`
|
|
76945
76319
|
`);
|
|
76946
76320
|
if (newlineIdx !== -1) {
|
|
@@ -77955,10 +77329,15 @@ function pad(s, width) {
|
|
|
77955
77329
|
return s.length >= width ? s : s + " ".repeat(width - s.length);
|
|
77956
77330
|
}
|
|
77957
77331
|
function registerAccountAdd(accountParent) {
|
|
77958
|
-
accountParent.command("add <account>").description("Mint a Google OAuth refresh token for <account> and register it with the auth-broker. For Drive scopes the effective flow is desktop-loopback (device-code returns invalid_scope for Drive; OOB is retired) \u2014 use a Desktop OAuth client; on a headless host complete the browser step over an SSH port-forward. Add --write for create/edit (drive.file); default is read-only.").option("--replace", "Overwrite existing credentials for <account> (default refuses if account already registered)", false).option("--write", "Request Drive WRITE scope (drive.file: create + edit app-created files) in addition to read. Default is read-only \u2014 a read grant never silently becomes a write grant. Re-consent an existing account with `--replace --write`.", false).action(withConfigError(async (account, opts) => {
|
|
77332
|
+
accountParent.command("add <account>").description("Mint a Google OAuth refresh token for <account> and register it with the auth-broker. For Drive scopes the effective flow is desktop-loopback (device-code returns invalid_scope for Drive; OOB is retired) \u2014 use a Desktop OAuth client; on a headless host complete the browser step over an SSH port-forward. Add --write for create/edit (drive.file) and --calendar for read-only Calendar; default is Drive read-only.").option("--replace", "Overwrite existing credentials for <account> (default refuses if account already registered)", false).option("--write", "Request Drive WRITE scope (drive.file: create + edit app-created files) in addition to read. Default is read-only \u2014 a read grant never silently becomes a write grant. Re-consent an existing account with `--replace --write`.", false).option("--calendar", "Request READ-ONLY Calendar scope (calendar.readonly) so the gdrive MCP's list_calendars / get_events tools authenticate. Opt-in \u2014 no tier grants it by default, and switchroom never requests a Calendar WRITE scope. Re-consent an existing account with `--replace --calendar`.", false).action(withConfigError(async (account, opts) => {
|
|
77959
77333
|
const normalizedAccount = validateAndNormalizeAccountEmail(account);
|
|
77960
77334
|
const [
|
|
77961
|
-
{
|
|
77335
|
+
{
|
|
77336
|
+
runDriveOAuthFlow: runDriveOAuthFlow2,
|
|
77337
|
+
selectGoogleWorkspaceScopes: selectGoogleWorkspaceScopes2,
|
|
77338
|
+
capabilitiesFromScopeString: capabilitiesFromScopeString2,
|
|
77339
|
+
resolveReconsentCapabilities: resolveReconsentCapabilities2
|
|
77340
|
+
},
|
|
77962
77341
|
{ selectInitialTier: selectInitialTier2 },
|
|
77963
77342
|
{ brokerCall: brokerCall2 },
|
|
77964
77343
|
{ loadConfig: loadConfig2, resolvePath: resolvePath2 },
|
|
@@ -78028,8 +77407,14 @@ function registerAccountAdd(accountParent) {
|
|
|
78028
77407
|
clientSecretRaw = await resolveRef(clientSecretRaw, "google_client_secret");
|
|
78029
77408
|
}
|
|
78030
77409
|
const tier = gw.tier ?? "core";
|
|
77410
|
+
const existingEntry = (await brokerCall2(async (client) => client.listGoogleAccounts())).accounts.find((a) => a.account === normalizedAccount);
|
|
77411
|
+
const plan = resolveReconsentCapabilities2({ write: opts.write ?? false, calendar: opts.calendar ?? false }, existingEntry ? capabilitiesFromScopeString2(existingEntry.scope) : undefined);
|
|
77412
|
+
if (existingEntry && !opts.replace && plan.added.length > 0) {
|
|
77413
|
+
throw new Error(buildScopeReconsentRequiredError(normalizedAccount, plan.added));
|
|
77414
|
+
}
|
|
78031
77415
|
const accountScopes = selectGoogleWorkspaceScopes2({
|
|
78032
|
-
write:
|
|
77416
|
+
write: plan.effective.write,
|
|
77417
|
+
calendar: plan.effective.calendar,
|
|
78033
77418
|
tier
|
|
78034
77419
|
});
|
|
78035
77420
|
const oauthCfg = {
|
|
@@ -78037,9 +77422,15 @@ function registerAccountAdd(accountParent) {
|
|
|
78037
77422
|
client_secret: clientSecretRaw,
|
|
78038
77423
|
scopes: accountScopes
|
|
78039
77424
|
};
|
|
78040
|
-
if (
|
|
77425
|
+
if (plan.effective.write) {
|
|
78041
77426
|
console.log(source_default.yellow(" Requesting Drive WRITE scope (drive.file \u2014 create/edit app-created files)."));
|
|
78042
77427
|
}
|
|
77428
|
+
if (plan.effective.calendar) {
|
|
77429
|
+
console.log(source_default.yellow(" Requesting Calendar READ-ONLY scope (calendar.readonly \u2014 list calendars, read events)."));
|
|
77430
|
+
}
|
|
77431
|
+
for (const line of buildCarryForwardNotices(plan.carried)) {
|
|
77432
|
+
console.log(source_default.gray(line));
|
|
77433
|
+
}
|
|
78043
77434
|
console.log(source_default.gray(` Workspace tier: ${tier} \u2014 requesting Docs + Sheets` + (tier === "extended" || tier === "complete" ? " + Slides" : "") + " API scopes so the tier's tools can authenticate."));
|
|
78044
77435
|
console.log(source_default.gray(` Changing the tier later requires re-running this command
|
|
78045
77436
|
` + " (`--replace`) \u2014 OAuth scopes are fixed at consent time."));
|
|
@@ -78076,6 +77467,40 @@ function registerAccountAdd(accountParent) {
|
|
|
78076
77467
|
console.log();
|
|
78077
77468
|
}));
|
|
78078
77469
|
}
|
|
77470
|
+
var CAPABILITY_LABELS = {
|
|
77471
|
+
write: {
|
|
77472
|
+
flag: "--write",
|
|
77473
|
+
scope: "drive.file",
|
|
77474
|
+
summary: "Drive write (create + edit app-created files)"
|
|
77475
|
+
},
|
|
77476
|
+
calendar: {
|
|
77477
|
+
flag: "--calendar",
|
|
77478
|
+
scope: "calendar.readonly",
|
|
77479
|
+
summary: "Calendar read-only (list calendars, read events)"
|
|
77480
|
+
}
|
|
77481
|
+
};
|
|
77482
|
+
function buildScopeReconsentRequiredError(account, added) {
|
|
77483
|
+
const flags = added.map((k) => CAPABILITY_LABELS[k].flag);
|
|
77484
|
+
const scopes = added.map((k) => CAPABILITY_LABELS[k].scope).join(", ");
|
|
77485
|
+
return [
|
|
77486
|
+
`'${account}' is already registered with the auth-broker, and its stored ` + `token does not carry ${scopes}.`,
|
|
77487
|
+
"",
|
|
77488
|
+
"OAuth scopes are fixed at consent time \u2014 a stored token cannot be",
|
|
77489
|
+
"widened in place. Re-consent to mint a new token that includes it:",
|
|
77490
|
+
"",
|
|
77491
|
+
` switchroom auth google account add ${account} --replace ${flags.join(" ")}`,
|
|
77492
|
+
"",
|
|
77493
|
+
"Scopes the account already holds are carried forward automatically,",
|
|
77494
|
+
"so re-consenting will not drop existing capabilities."
|
|
77495
|
+
].join(`
|
|
77496
|
+
`);
|
|
77497
|
+
}
|
|
77498
|
+
function buildCarryForwardNotices(carried) {
|
|
77499
|
+
return carried.map((k) => {
|
|
77500
|
+
const { flag, scope, summary } = CAPABILITY_LABELS[k];
|
|
77501
|
+
return ` Carrying forward ${summary} \u2014 the existing token holds ${scope}, ` + `so it is re-requested even though ${flag} was not passed ` + `(re-consent would otherwise silently revoke it).`;
|
|
77502
|
+
});
|
|
77503
|
+
}
|
|
78079
77504
|
function oauthClientSetupGuidance(reason) {
|
|
78080
77505
|
return [
|
|
78081
77506
|
reason,
|
|
@@ -78971,8 +78396,8 @@ async function readHiddenLine2(prompt) {
|
|
|
78971
78396
|
stdin.setRawMode(true);
|
|
78972
78397
|
}
|
|
78973
78398
|
let line = "";
|
|
78974
|
-
const onData = (
|
|
78975
|
-
const s =
|
|
78399
|
+
const onData = (chunk) => {
|
|
78400
|
+
const s = chunk.toString("utf8");
|
|
78976
78401
|
for (const ch of s) {
|
|
78977
78402
|
if (ch === `
|
|
78978
78403
|
` || ch === "\r") {
|
|
@@ -79901,7 +79326,7 @@ function getVaultPath2(configPath) {
|
|
|
79901
79326
|
return resolvePath("~/.switchroom/vault.enc");
|
|
79902
79327
|
}
|
|
79903
79328
|
}
|
|
79904
|
-
function
|
|
79329
|
+
function maskToken(s) {
|
|
79905
79330
|
if (s.length >= 18)
|
|
79906
79331
|
return `${s.slice(0, 6)}...${s.slice(-4)}`;
|
|
79907
79332
|
return "***";
|
|
@@ -80189,7 +79614,7 @@ function registerVaultSweep(vault, program3) {
|
|
|
80189
79614
|
console.log("");
|
|
80190
79615
|
console.log(source_default.dim(`masked values for display only:`));
|
|
80191
79616
|
for (const v of values) {
|
|
80192
|
-
console.log(` vault:${v.key} \u2192 ${
|
|
79617
|
+
console.log(` vault:${v.key} \u2192 ${maskToken(v.value)}`);
|
|
80193
79618
|
}
|
|
80194
79619
|
if (opts.dryRun) {
|
|
80195
79620
|
console.log(source_default.yellow("dry-run \u2014 no files modified. Rerun without --dry-run to apply."));
|
|
@@ -83162,8 +82587,8 @@ class VaultBroker {
|
|
|
83162
82587
|
peer = this.testOpts._testIdentify ? this.testOpts._testIdentify(listenerSocketPath, socket) : identify(listenerSocketPath, socket);
|
|
83163
82588
|
}
|
|
83164
82589
|
let buffer = "";
|
|
83165
|
-
socket.on("data", (
|
|
83166
|
-
buffer +=
|
|
82590
|
+
socket.on("data", (chunk) => {
|
|
82591
|
+
buffer += chunk.toString("utf8");
|
|
83167
82592
|
if (Buffer.byteLength(buffer, "utf8") > MAX_FRAME_BYTES) {
|
|
83168
82593
|
const resp = encodeResponse(errorResponse("BAD_REQUEST", "Frame exceeds 64 KiB limit"));
|
|
83169
82594
|
socket.end(resp);
|
|
@@ -84272,8 +83697,8 @@ class VaultBroker {
|
|
|
84272
83697
|
const auditCaller = isOperator ? "operator" : unlockPeer !== null ? callerFromPeer(unlockPeer) : `pid:${process.pid}`;
|
|
84273
83698
|
const auditCgroup = isOperator ? undefined : unlockPeer?.systemdUnit ?? undefined;
|
|
84274
83699
|
let buffer = "";
|
|
84275
|
-
socket.on("data", (
|
|
84276
|
-
buffer +=
|
|
83700
|
+
socket.on("data", (chunk) => {
|
|
83701
|
+
buffer += chunk.toString("utf8");
|
|
84277
83702
|
const newlineIdx = buffer.indexOf(`
|
|
84278
83703
|
`);
|
|
84279
83704
|
if (newlineIdx === -1) {
|
|
@@ -85840,7 +85265,7 @@ function promptLine2(prompt, hidden = false) {
|
|
|
85840
85265
|
function readStdinToEnd() {
|
|
85841
85266
|
return new Promise((resolve31, reject) => {
|
|
85842
85267
|
const chunks = [];
|
|
85843
|
-
process.stdin.on("data", (
|
|
85268
|
+
process.stdin.on("data", (chunk) => chunks.push(chunk));
|
|
85844
85269
|
process.stdin.on("end", () => {
|
|
85845
85270
|
resolve31(Buffer.concat(chunks).toString("utf8"));
|
|
85846
85271
|
});
|
|
@@ -87518,6 +86943,595 @@ function formatCleanupPlan(plan, maxMerges = 10) {
|
|
|
87518
86943
|
`);
|
|
87519
86944
|
}
|
|
87520
86945
|
|
|
86946
|
+
// telegram-plugin/secret-detect/patterns.ts
|
|
86947
|
+
var ANCHORED_PATTERNS = [
|
|
86948
|
+
{ rule_id: "anthropic_api_key", regex: /\b(sk-ant-[A-Za-z0-9_-]{8,})\b/g, captureIndex: 1, slugHint: "anthropic_api_key" },
|
|
86949
|
+
{ 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" },
|
|
86950
|
+
{ rule_id: "openai_api_key", regex: /\b(sk-[A-Za-z0-9_-]{20,})\b/g, captureIndex: 1, slugHint: "openai_api_key" },
|
|
86951
|
+
{ rule_id: "github_pat_classic", regex: /\b(ghp_[A-Za-z0-9]{20,})\b/g, captureIndex: 1, slugHint: "github_pat" },
|
|
86952
|
+
{ rule_id: "github_pat_fine_grained", regex: /\b(github_pat_[A-Za-z0-9_]{20,})\b/g, captureIndex: 1, slugHint: "github_pat" },
|
|
86953
|
+
{ rule_id: "slack_token", regex: /\b(xox[baprs]-[A-Za-z0-9-]{10,})\b/g, captureIndex: 1, slugHint: "slack_token" },
|
|
86954
|
+
{ rule_id: "slack_app_token", regex: /\b(xapp-[A-Za-z0-9-]{10,})\b/g, captureIndex: 1, slugHint: "slack_app_token" },
|
|
86955
|
+
{ rule_id: "groq_api_key", regex: /\b(gsk_[A-Za-z0-9_-]{10,})\b/g, captureIndex: 1, slugHint: "groq_api_key" },
|
|
86956
|
+
{ rule_id: "google_api_key", regex: /\b(AIza[0-9A-Za-z\-_]{20,})\b/g, captureIndex: 1, slugHint: "google_api_key" },
|
|
86957
|
+
{ rule_id: "perplexity_api_key", regex: /\b(pplx-[A-Za-z0-9_-]{10,})\b/g, captureIndex: 1, slugHint: "perplexity_api_key" },
|
|
86958
|
+
{ rule_id: "npm_token", regex: /\b(npm_[A-Za-z0-9]{10,})\b/g, captureIndex: 1, slugHint: "npm_token" },
|
|
86959
|
+
{ rule_id: "telegram_bot_token_prefixed", regex: /\bbot(\d{6,}:[A-Za-z0-9_-]{20,})\b/g, captureIndex: 1, slugHint: "telegram_bot_token" },
|
|
86960
|
+
{ rule_id: "telegram_bot_token", regex: /\b(\d{6,}:[A-Za-z0-9_-]{20,})\b/g, captureIndex: 1, slugHint: "telegram_bot_token" },
|
|
86961
|
+
{ rule_id: "laravel_sanctum_token", regex: /\b(\d+\|[A-Za-z0-9]{40,})\b/g, captureIndex: 1, slugHint: "api_token" },
|
|
86962
|
+
{ rule_id: "aws_access_key", regex: /\b(AKIA[0-9A-Z]{16})\b/g, captureIndex: 1, slugHint: "aws_access_key" },
|
|
86963
|
+
{ 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" }
|
|
86964
|
+
];
|
|
86965
|
+
var STRUCTURED_PATTERNS = [
|
|
86966
|
+
{
|
|
86967
|
+
rule_id: "env_key_value",
|
|
86968
|
+
regex: /\b([A-Z0-9_]*(?:KEY|TOKEN|SECRET|PASSWORD|PASSWD))\b\s*[=:]\s*(["']?)([^\s"'\\]+)\2/g,
|
|
86969
|
+
captureIndex: 3,
|
|
86970
|
+
slugHint: "env"
|
|
86971
|
+
},
|
|
86972
|
+
{
|
|
86973
|
+
rule_id: "json_secret_field",
|
|
86974
|
+
regex: /"(?:apiKey|token|secret|password|passwd|accessToken|refreshToken)"\s*:\s*"([^"]+)"/g,
|
|
86975
|
+
captureIndex: 1,
|
|
86976
|
+
slugHint: "json_secret"
|
|
86977
|
+
},
|
|
86978
|
+
{
|
|
86979
|
+
rule_id: "cli_flag",
|
|
86980
|
+
regex: /--(?:api[-_]?key|hook[-_]?token|token|secret|password|passwd)\s+(["']?)([^\s"']+)\1/g,
|
|
86981
|
+
captureIndex: 2,
|
|
86982
|
+
slugHint: "cli_flag"
|
|
86983
|
+
},
|
|
86984
|
+
{
|
|
86985
|
+
rule_id: "bearer_auth_header",
|
|
86986
|
+
regex: /Authorization\s*[:=]\s*Bearer\s+([A-Za-z0-9._\-+=]+)/gi,
|
|
86987
|
+
captureIndex: 1,
|
|
86988
|
+
slugHint: "bearer_token"
|
|
86989
|
+
},
|
|
86990
|
+
{
|
|
86991
|
+
rule_id: "bearer_loose",
|
|
86992
|
+
regex: /\bBearer\s+([A-Za-z0-9._\-+=]{18,})\b/gi,
|
|
86993
|
+
captureIndex: 1,
|
|
86994
|
+
slugHint: "bearer_token"
|
|
86995
|
+
},
|
|
86996
|
+
{
|
|
86997
|
+
rule_id: "basic_auth_header",
|
|
86998
|
+
regex: /Authorization\s*[:=]\s*Basic\s+([A-Za-z0-9+/=]{8,})/gi,
|
|
86999
|
+
captureIndex: 1,
|
|
87000
|
+
slugHint: "basic_auth"
|
|
87001
|
+
},
|
|
87002
|
+
{
|
|
87003
|
+
rule_id: "pem_private_key",
|
|
87004
|
+
regex: /-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]+?-----END [A-Z ]*PRIVATE KEY-----/g,
|
|
87005
|
+
captureIndex: 0,
|
|
87006
|
+
slugHint: "pem_private_key"
|
|
87007
|
+
}
|
|
87008
|
+
];
|
|
87009
|
+
var PROVIDER_PATTERNS = [
|
|
87010
|
+
{ rule_id: "slack_webhook", regex: /(https:\/\/hooks\.slack\.com\/services\/[A-Za-z0-9_/]+)/g, captureIndex: 1, slugHint: "slack_webhook" },
|
|
87011
|
+
{ rule_id: "stripe_live_secret", regex: /\b(sk_live_[A-Za-z0-9]{24,})\b/g, captureIndex: 1, slugHint: "stripe_key" },
|
|
87012
|
+
{ rule_id: "stripe_restricted", regex: /\b(rk_live_[A-Za-z0-9]{24,})\b/g, captureIndex: 1, slugHint: "stripe_key" },
|
|
87013
|
+
{ 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" },
|
|
87014
|
+
{ rule_id: "gitlab_pat", regex: /\b(glpat-[A-Za-z0-9_-]{20})\b/g, captureIndex: 1, slugHint: "gitlab_pat" },
|
|
87015
|
+
{ rule_id: "huggingface_token", regex: /\b(hf_[A-Za-z0-9]{34,})\b/g, captureIndex: 1, slugHint: "huggingface_token" },
|
|
87016
|
+
{ rule_id: "twilio_api_key", regex: /\b(SK[0-9a-f]{32})\b/g, captureIndex: 1, slugHint: "twilio_api_key" },
|
|
87017
|
+
{ rule_id: "mailgun_key", regex: /\b(key-[0-9a-f]{32})\b/g, captureIndex: 1, slugHint: "mailgun_key" },
|
|
87018
|
+
{ rule_id: "digitalocean_pat", regex: /\b(dop_v1_[a-f0-9]{64})\b/g, captureIndex: 1, slugHint: "digitalocean_token" },
|
|
87019
|
+
{ rule_id: "digitalocean_oauth", regex: /\b(doo_v1_[a-f0-9]{64})\b/g, captureIndex: 1, slugHint: "digitalocean_token" },
|
|
87020
|
+
{ rule_id: "digitalocean_refresh", regex: /\b(dor_v1_[a-f0-9]{64})\b/g, captureIndex: 1, slugHint: "digitalocean_token" },
|
|
87021
|
+
{ 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" },
|
|
87022
|
+
{ rule_id: "linear_api_key", regex: /\b(lin_api_[A-Za-z0-9]{40})\b/g, captureIndex: 1, slugHint: "linear_api_key" },
|
|
87023
|
+
{ rule_id: "shopify_access_token", regex: /\b(shpat_[a-fA-F0-9]{32})\b/g, captureIndex: 1, slugHint: "shopify_token" },
|
|
87024
|
+
{ rule_id: "shopify_shared_secret", regex: /\b(shpss_[a-fA-F0-9]{32})\b/g, captureIndex: 1, slugHint: "shopify_token" },
|
|
87025
|
+
{ rule_id: "shopify_private_app", regex: /\b(shppa_[a-fA-F0-9]{32})\b/g, captureIndex: 1, slugHint: "shopify_token" },
|
|
87026
|
+
{ rule_id: "square_access_token", regex: /\b(sq0atp-[A-Za-z0-9_-]{22})\b/g, captureIndex: 1, slugHint: "square_token" },
|
|
87027
|
+
{ rule_id: "square_oauth_secret", regex: /\b(sq0csp-[A-Za-z0-9_-]{43})\b/g, captureIndex: 1, slugHint: "square_token" },
|
|
87028
|
+
{ rule_id: "newrelic_key", regex: /\b(NRAK-[A-Z0-9]{27})\b/g, captureIndex: 1, slugHint: "newrelic_key" },
|
|
87029
|
+
{ rule_id: "notion_token", regex: /\b(ntn_[A-Za-z0-9]{46})\b/g, captureIndex: 1, slugHint: "notion_token" },
|
|
87030
|
+
{ rule_id: "planetscale_password", regex: /\b(pscale_pw_[A-Za-z0-9_.-]{43})\b/g, captureIndex: 1, slugHint: "planetscale_token" },
|
|
87031
|
+
{ rule_id: "planetscale_token", regex: /\b(pscale_tkn_[A-Za-z0-9_.-]{43})\b/g, captureIndex: 1, slugHint: "planetscale_token" },
|
|
87032
|
+
{ rule_id: "supabase_service_key", regex: /\b(sbp_[a-f0-9]{40})\b/g, captureIndex: 1, slugHint: "supabase_key" },
|
|
87033
|
+
{ rule_id: "atlassian_token", regex: /\b(ATATT[A-Za-z0-9_\-=]{20,})\b/g, captureIndex: 1, slugHint: "atlassian_token" },
|
|
87034
|
+
{ rule_id: "dropbox_token", regex: /\b(sl\.[A-Za-z0-9_-]{130,})/g, captureIndex: 1, slugHint: "dropbox_token" },
|
|
87035
|
+
{ rule_id: "databricks_token", regex: /\b(dapi[a-f0-9]{32})\b/g, captureIndex: 1, slugHint: "databricks_token" },
|
|
87036
|
+
{ 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" },
|
|
87037
|
+
{ rule_id: "pypi_token", regex: /\b(pypi-AgEIcHlwaS[A-Za-z0-9_-]{50,})/g, captureIndex: 1, slugHint: "pypi_token" },
|
|
87038
|
+
{ rule_id: "aws_temp_access_key", regex: /\b(ASIA[0-9A-Z]{16})\b/g, captureIndex: 1, slugHint: "aws_access_key" },
|
|
87039
|
+
{ rule_id: "gcp_oauth_token", regex: /\b(ya29\.[A-Za-z0-9_-]{30,})/g, captureIndex: 1, slugHint: "gcp_oauth_token" }
|
|
87040
|
+
];
|
|
87041
|
+
var ALL_PATTERNS = [...ANCHORED_PATTERNS, ...PROVIDER_PATTERNS, ...STRUCTURED_PATTERNS];
|
|
87042
|
+
|
|
87043
|
+
// telegram-plugin/secret-detect/entropy.ts
|
|
87044
|
+
function shannonEntropy(s) {
|
|
87045
|
+
if (s.length === 0)
|
|
87046
|
+
return 0;
|
|
87047
|
+
const counts = new Map;
|
|
87048
|
+
for (const ch of s) {
|
|
87049
|
+
counts.set(ch, (counts.get(ch) ?? 0) + 1);
|
|
87050
|
+
}
|
|
87051
|
+
let h = 0;
|
|
87052
|
+
const len = s.length;
|
|
87053
|
+
for (const c of counts.values()) {
|
|
87054
|
+
const p = c / len;
|
|
87055
|
+
h -= p * Math.log2(p);
|
|
87056
|
+
}
|
|
87057
|
+
return h;
|
|
87058
|
+
}
|
|
87059
|
+
|
|
87060
|
+
// telegram-plugin/secret-detect/inert-values.ts
|
|
87061
|
+
var INERT_VALUE_RE = [
|
|
87062
|
+
/^\[REDACTED(?::[A-Za-z0-9_]+)?\]$/i,
|
|
87063
|
+
/^\$\{?[A-Za-z_][A-Za-z0-9_]*\}?$/,
|
|
87064
|
+
/^\{\{[^\n\r\u2028\u2029]*\}\}$/,
|
|
87065
|
+
/^<[^<>\n\r\u2028\u2029]*>$/,
|
|
87066
|
+
/^%[A-Za-z_][A-Za-z0-9_]*%$/,
|
|
87067
|
+
/^vault:[A-Za-z0-9_./-]*$/i,
|
|
87068
|
+
/^[*x\u2022.]+$/i,
|
|
87069
|
+
/^(?:process\.env|import\.meta\.env|os\.environ)(?:\.[A-Za-z_$][A-Za-z0-9_$]*|\[["']?[A-Za-z_$][A-Za-z0-9_$]*["']?\])*$/,
|
|
87070
|
+
/^(?: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,
|
|
87071
|
+
/^[a-z]+(?: [a-z]+){2,}$/
|
|
87072
|
+
];
|
|
87073
|
+
var CREDENTIAL_RUN_RE = /[A-Za-z0-9]{12,}/g;
|
|
87074
|
+
var MIXED_CLASS_RUN_MIN = 12;
|
|
87075
|
+
var SINGLE_CLASS_RUN_MIN = 16;
|
|
87076
|
+
function runIsCredentialShaped(run) {
|
|
87077
|
+
let classes = 0;
|
|
87078
|
+
if (/[a-z]/.test(run))
|
|
87079
|
+
classes++;
|
|
87080
|
+
if (/[A-Z]/.test(run))
|
|
87081
|
+
classes++;
|
|
87082
|
+
if (/[0-9]/.test(run))
|
|
87083
|
+
classes++;
|
|
87084
|
+
return run.length >= (classes >= 2 ? MIXED_CLASS_RUN_MIN : SINGLE_CLASS_RUN_MIN);
|
|
87085
|
+
}
|
|
87086
|
+
function hasCredentialShapedRun(value) {
|
|
87087
|
+
for (const run of value.match(CREDENTIAL_RUN_RE) ?? []) {
|
|
87088
|
+
if (runIsCredentialShaped(run))
|
|
87089
|
+
return true;
|
|
87090
|
+
}
|
|
87091
|
+
return false;
|
|
87092
|
+
}
|
|
87093
|
+
function isInertValue(value) {
|
|
87094
|
+
if (!INERT_VALUE_RE.some((re) => re.test(value)))
|
|
87095
|
+
return false;
|
|
87096
|
+
return !hasCredentialShapedRun(value);
|
|
87097
|
+
}
|
|
87098
|
+
var INERT_GATED_RULES = new Set([
|
|
87099
|
+
"env_key_value",
|
|
87100
|
+
"json_secret_field",
|
|
87101
|
+
"cli_flag"
|
|
87102
|
+
]);
|
|
87103
|
+
var TRAILING_PUNCT_RE = /[.,;:!?)\]}]+$/;
|
|
87104
|
+
function stripTrailingPunctuation(value) {
|
|
87105
|
+
return value.replace(TRAILING_PUNCT_RE, "");
|
|
87106
|
+
}
|
|
87107
|
+
|
|
87108
|
+
// telegram-plugin/secret-detect/kv-scanner.ts
|
|
87109
|
+
var KV_RE = /\b([A-Za-z_][A-Za-z0-9_-]*(?:password|passwd|token|secret|key|api[_-]?key))\s*[:=]\s*["']?([^\s"'\\]{8,})["']?/gi;
|
|
87110
|
+
var KV_ENTROPY_THRESHOLD = 4;
|
|
87111
|
+
var MEMORABLE_PW_RE = /\b([A-Za-z0-9_-]*(?:password|passwd|passphrase|pwd))\b\s*(?:[:=]\s*|\s+is\s+)(["']?)([^\s"']{8,64})\2/gi;
|
|
87112
|
+
var MEMORABLE_PW_RULE_ID = "memorable_password";
|
|
87113
|
+
var MEMORABLE_PW_MIN_CLASSES = 2;
|
|
87114
|
+
function charClassCount(value) {
|
|
87115
|
+
let n = 0;
|
|
87116
|
+
if (/[a-z]/.test(value))
|
|
87117
|
+
n++;
|
|
87118
|
+
if (/[A-Z]/.test(value))
|
|
87119
|
+
n++;
|
|
87120
|
+
if (/[0-9]/.test(value))
|
|
87121
|
+
n++;
|
|
87122
|
+
if (/[^A-Za-z0-9]/.test(value))
|
|
87123
|
+
n++;
|
|
87124
|
+
return n;
|
|
87125
|
+
}
|
|
87126
|
+
function looksLikeMemorablePassword(value) {
|
|
87127
|
+
if (isInertValue(value))
|
|
87128
|
+
return false;
|
|
87129
|
+
const core = stripTrailingPunctuation(value);
|
|
87130
|
+
if (core.length < 8 || core.length > 64)
|
|
87131
|
+
return false;
|
|
87132
|
+
if (isInertValue(core))
|
|
87133
|
+
return false;
|
|
87134
|
+
if (new Set(core).size < 4)
|
|
87135
|
+
return false;
|
|
87136
|
+
return charClassCount(core) >= MEMORABLE_PW_MIN_CLASSES;
|
|
87137
|
+
}
|
|
87138
|
+
function scanMemorablePasswords(text) {
|
|
87139
|
+
const hits = [];
|
|
87140
|
+
MEMORABLE_PW_RE.lastIndex = 0;
|
|
87141
|
+
let m;
|
|
87142
|
+
while ((m = MEMORABLE_PW_RE.exec(text)) !== null) {
|
|
87143
|
+
const keyName = m[1];
|
|
87144
|
+
const value = m[3];
|
|
87145
|
+
if (!value || !looksLikeMemorablePassword(value))
|
|
87146
|
+
continue;
|
|
87147
|
+
const valueOffsetInMatch = m[0].indexOf(value, keyName.length);
|
|
87148
|
+
if (valueOffsetInMatch < 0)
|
|
87149
|
+
continue;
|
|
87150
|
+
const start = m.index + valueOffsetInMatch;
|
|
87151
|
+
hits.push({
|
|
87152
|
+
rule_id: MEMORABLE_PW_RULE_ID,
|
|
87153
|
+
start,
|
|
87154
|
+
end: start + value.length,
|
|
87155
|
+
matched_text: value,
|
|
87156
|
+
key_name: keyName,
|
|
87157
|
+
confidence: "ambiguous"
|
|
87158
|
+
});
|
|
87159
|
+
}
|
|
87160
|
+
return hits;
|
|
87161
|
+
}
|
|
87162
|
+
function scanKeyValue(text) {
|
|
87163
|
+
const hits = [];
|
|
87164
|
+
KV_RE.lastIndex = 0;
|
|
87165
|
+
let m;
|
|
87166
|
+
while ((m = KV_RE.exec(text)) !== null) {
|
|
87167
|
+
const [, keyName, value] = m;
|
|
87168
|
+
if (!value)
|
|
87169
|
+
continue;
|
|
87170
|
+
if (isInertValue(value))
|
|
87171
|
+
continue;
|
|
87172
|
+
const h = shannonEntropy(value);
|
|
87173
|
+
if (h < KV_ENTROPY_THRESHOLD)
|
|
87174
|
+
continue;
|
|
87175
|
+
const valueOffsetInMatch = m[0].indexOf(value, keyName.length);
|
|
87176
|
+
if (valueOffsetInMatch < 0)
|
|
87177
|
+
continue;
|
|
87178
|
+
const start = m.index + valueOffsetInMatch;
|
|
87179
|
+
const end = start + value.length;
|
|
87180
|
+
hits.push({
|
|
87181
|
+
rule_id: "kv_entropy",
|
|
87182
|
+
start,
|
|
87183
|
+
end,
|
|
87184
|
+
matched_text: value,
|
|
87185
|
+
key_name: keyName,
|
|
87186
|
+
confidence: "ambiguous"
|
|
87187
|
+
});
|
|
87188
|
+
}
|
|
87189
|
+
return hits;
|
|
87190
|
+
}
|
|
87191
|
+
|
|
87192
|
+
// telegram-plugin/secret-detect/db-uri.ts
|
|
87193
|
+
var DB_URI_RE = /\b([a-zA-Z][a-zA-Z0-9+.-]+):\/\/([^\s:/@]+):([^\s/?#]+)@([^\s/@?#]+)/g;
|
|
87194
|
+
var DB_URI_RULE_ID = "db_uri_password";
|
|
87195
|
+
function isInertPassword(value) {
|
|
87196
|
+
if (value.startsWith("[REDACTED"))
|
|
87197
|
+
return true;
|
|
87198
|
+
return /^[*x]+$/i.test(value);
|
|
87199
|
+
}
|
|
87200
|
+
function scanDbUris(text) {
|
|
87201
|
+
const hits = [];
|
|
87202
|
+
DB_URI_RE.lastIndex = 0;
|
|
87203
|
+
let m;
|
|
87204
|
+
while ((m = DB_URI_RE.exec(text)) !== null) {
|
|
87205
|
+
const scheme = m[1];
|
|
87206
|
+
const user = m[2];
|
|
87207
|
+
const password = m[3];
|
|
87208
|
+
if (isInertPassword(password))
|
|
87209
|
+
continue;
|
|
87210
|
+
const start = m.index + scheme.length + 3 + user.length + 1;
|
|
87211
|
+
hits.push({
|
|
87212
|
+
rule_id: DB_URI_RULE_ID,
|
|
87213
|
+
start,
|
|
87214
|
+
end: start + password.length,
|
|
87215
|
+
matched_text: password,
|
|
87216
|
+
key_name: `${scheme}_password`,
|
|
87217
|
+
confidence: "ambiguous"
|
|
87218
|
+
});
|
|
87219
|
+
}
|
|
87220
|
+
return hits;
|
|
87221
|
+
}
|
|
87222
|
+
|
|
87223
|
+
// telegram-plugin/secret-detect/generic-entropy.ts
|
|
87224
|
+
var CANDIDATE_RE = /[A-Za-z0-9]{28,}/g;
|
|
87225
|
+
var GENERIC_MIN_DISTINCT = 18;
|
|
87226
|
+
var MAX_GENERIC_HITS = 20;
|
|
87227
|
+
function hasDistinctChars(tok, n) {
|
|
87228
|
+
const seen = new Uint8Array(128);
|
|
87229
|
+
let distinct = 0;
|
|
87230
|
+
for (let i = 0;i < tok.length; i++) {
|
|
87231
|
+
const c = tok.charCodeAt(i);
|
|
87232
|
+
if (seen[c] === 0) {
|
|
87233
|
+
seen[c] = 1;
|
|
87234
|
+
if (++distinct >= n)
|
|
87235
|
+
return true;
|
|
87236
|
+
}
|
|
87237
|
+
}
|
|
87238
|
+
return false;
|
|
87239
|
+
}
|
|
87240
|
+
function hasDigit(tok) {
|
|
87241
|
+
for (let i = 0;i < tok.length; i++) {
|
|
87242
|
+
const c = tok.charCodeAt(i);
|
|
87243
|
+
if (c >= 48 && c <= 57)
|
|
87244
|
+
return true;
|
|
87245
|
+
}
|
|
87246
|
+
return false;
|
|
87247
|
+
}
|
|
87248
|
+
function scanGenericSecrets(text) {
|
|
87249
|
+
const hits = [];
|
|
87250
|
+
CANDIDATE_RE.lastIndex = 0;
|
|
87251
|
+
let m;
|
|
87252
|
+
while ((m = CANDIDATE_RE.exec(text)) !== null) {
|
|
87253
|
+
if (hits.length >= MAX_GENERIC_HITS)
|
|
87254
|
+
break;
|
|
87255
|
+
const tok = m[0];
|
|
87256
|
+
if (!hasDigit(tok))
|
|
87257
|
+
continue;
|
|
87258
|
+
if (!hasDistinctChars(tok, GENERIC_MIN_DISTINCT))
|
|
87259
|
+
continue;
|
|
87260
|
+
hits.push({
|
|
87261
|
+
rule_id: "generic_high_entropy",
|
|
87262
|
+
start: m.index,
|
|
87263
|
+
end: m.index + tok.length,
|
|
87264
|
+
matched_text: tok,
|
|
87265
|
+
confidence: "ambiguous"
|
|
87266
|
+
});
|
|
87267
|
+
}
|
|
87268
|
+
return hits;
|
|
87269
|
+
}
|
|
87270
|
+
|
|
87271
|
+
// telegram-plugin/secret-detect/chunker.ts
|
|
87272
|
+
var CHUNK_THRESHOLD = 32 * 1024;
|
|
87273
|
+
var WINDOW_SIZE = 16 * 1024;
|
|
87274
|
+
var OVERLAP = 8 * 1024;
|
|
87275
|
+
function chunk(text) {
|
|
87276
|
+
if (text.length <= CHUNK_THRESHOLD) {
|
|
87277
|
+
return [{ offset: 0, text }];
|
|
87278
|
+
}
|
|
87279
|
+
const out = [];
|
|
87280
|
+
let offset = 0;
|
|
87281
|
+
while (offset < text.length) {
|
|
87282
|
+
const end = Math.min(offset + WINDOW_SIZE, text.length);
|
|
87283
|
+
out.push({ offset, text: text.slice(offset, end) });
|
|
87284
|
+
if (end >= text.length)
|
|
87285
|
+
break;
|
|
87286
|
+
offset = end - OVERLAP;
|
|
87287
|
+
}
|
|
87288
|
+
return out;
|
|
87289
|
+
}
|
|
87290
|
+
|
|
87291
|
+
// telegram-plugin/secret-detect/suppressor.ts
|
|
87292
|
+
var MARKERS = ["test", "mock", "example", "fixture", "dummy"];
|
|
87293
|
+
var WINDOW = 40;
|
|
87294
|
+
var MARKER_RE = new RegExp(`\\b(?:${MARKERS.join("|")})\\b`, "i");
|
|
87295
|
+
function isSuppressed(text, start, end) {
|
|
87296
|
+
const left = Math.max(0, start - WINDOW);
|
|
87297
|
+
const right = Math.min(text.length, end + WINDOW);
|
|
87298
|
+
const context = text.slice(left, start) + text.slice(end, right);
|
|
87299
|
+
return MARKER_RE.test(context);
|
|
87300
|
+
}
|
|
87301
|
+
|
|
87302
|
+
// telegram-plugin/secret-detect/slug.ts
|
|
87303
|
+
function sanitizeKeyName(raw) {
|
|
87304
|
+
const up = raw.toUpperCase();
|
|
87305
|
+
const cleaned = up.replace(/[^A-Z0-9_]+/g, "_").replace(/_+/g, "_").replace(/^_+|_+$/g, "");
|
|
87306
|
+
return cleaned.length > 0 ? cleaned : "SECRET";
|
|
87307
|
+
}
|
|
87308
|
+
function datePart(now = new Date) {
|
|
87309
|
+
const y = now.getUTCFullYear();
|
|
87310
|
+
const m = String(now.getUTCMonth() + 1).padStart(2, "0");
|
|
87311
|
+
const d = String(now.getUTCDate()).padStart(2, "0");
|
|
87312
|
+
return `${y}${m}${d}`;
|
|
87313
|
+
}
|
|
87314
|
+
function deriveSlug(inputs, existing) {
|
|
87315
|
+
let base;
|
|
87316
|
+
if (inputs.key_name && inputs.key_name.trim().length > 0) {
|
|
87317
|
+
base = sanitizeKeyName(inputs.key_name);
|
|
87318
|
+
} else {
|
|
87319
|
+
base = `${inputs.rule_id}_${datePart(inputs.now)}`;
|
|
87320
|
+
}
|
|
87321
|
+
if (!existing.has(base))
|
|
87322
|
+
return base;
|
|
87323
|
+
let n = 2;
|
|
87324
|
+
while (existing.has(`${base}_${n}`))
|
|
87325
|
+
n++;
|
|
87326
|
+
return `${base}_${n}`;
|
|
87327
|
+
}
|
|
87328
|
+
// telegram-plugin/secret-detect/url-redact.ts
|
|
87329
|
+
var SENSITIVE_PARAMS = new Set([
|
|
87330
|
+
"token",
|
|
87331
|
+
"key",
|
|
87332
|
+
"api_key",
|
|
87333
|
+
"apikey",
|
|
87334
|
+
"secret",
|
|
87335
|
+
"access_token",
|
|
87336
|
+
"password",
|
|
87337
|
+
"pass",
|
|
87338
|
+
"auth",
|
|
87339
|
+
"client_secret",
|
|
87340
|
+
"refresh_token",
|
|
87341
|
+
"signature"
|
|
87342
|
+
]);
|
|
87343
|
+
var URL_RE = /\b(?:https?|wss?|ftp):\/\/[^\s<>"']+/gi;
|
|
87344
|
+
function redactUrls(text) {
|
|
87345
|
+
return text.replace(URL_RE, (m) => {
|
|
87346
|
+
const redacted = redactOne(m);
|
|
87347
|
+
return redacted ?? m;
|
|
87348
|
+
});
|
|
87349
|
+
}
|
|
87350
|
+
function redactOne(raw) {
|
|
87351
|
+
let u;
|
|
87352
|
+
try {
|
|
87353
|
+
u = new URL(raw);
|
|
87354
|
+
} catch {
|
|
87355
|
+
return null;
|
|
87356
|
+
}
|
|
87357
|
+
let changed = false;
|
|
87358
|
+
if (u.username || u.password) {
|
|
87359
|
+
u.username = "***";
|
|
87360
|
+
u.password = "";
|
|
87361
|
+
changed = true;
|
|
87362
|
+
}
|
|
87363
|
+
for (const [key] of u.searchParams) {
|
|
87364
|
+
if (SENSITIVE_PARAMS.has(key.toLowerCase())) {
|
|
87365
|
+
u.searchParams.set(key, "***");
|
|
87366
|
+
changed = true;
|
|
87367
|
+
}
|
|
87368
|
+
}
|
|
87369
|
+
return changed ? u.toString() : null;
|
|
87370
|
+
}
|
|
87371
|
+
|
|
87372
|
+
// telegram-plugin/secret-detect/index.ts
|
|
87373
|
+
function detectSecrets(text) {
|
|
87374
|
+
if (!text || text.length === 0)
|
|
87375
|
+
return [];
|
|
87376
|
+
const windows = chunk(text);
|
|
87377
|
+
const raw = [];
|
|
87378
|
+
for (const win of windows) {
|
|
87379
|
+
for (const p of ALL_PATTERNS) {
|
|
87380
|
+
const re = new RegExp(p.regex.source, p.regex.flags.includes("g") ? p.regex.flags : p.regex.flags + "g");
|
|
87381
|
+
let m;
|
|
87382
|
+
while ((m = re.exec(win.text)) !== null) {
|
|
87383
|
+
if (m[0].length === 0) {
|
|
87384
|
+
re.lastIndex++;
|
|
87385
|
+
continue;
|
|
87386
|
+
}
|
|
87387
|
+
const cap = p.captureIndex === 0 ? m[0] : m[p.captureIndex];
|
|
87388
|
+
if (!cap)
|
|
87389
|
+
continue;
|
|
87390
|
+
const matchStart = p.captureIndex === 0 ? m.index : m.index + m[0].indexOf(cap);
|
|
87391
|
+
if (matchStart < 0)
|
|
87392
|
+
continue;
|
|
87393
|
+
const globalStart = win.offset + matchStart;
|
|
87394
|
+
const globalEnd = globalStart + cap.length;
|
|
87395
|
+
const keyName = p.rule_id === "env_key_value" ? m[1] : undefined;
|
|
87396
|
+
if (INERT_GATED_RULES.has(p.rule_id) && isInertValue(cap))
|
|
87397
|
+
continue;
|
|
87398
|
+
if (p.rule_id === "env_key_value") {
|
|
87399
|
+
const ENV_KV_MIN_LEN = 12;
|
|
87400
|
+
const ENV_KV_MIN_ENTROPY = 3.5;
|
|
87401
|
+
if (cap.length < ENV_KV_MIN_LEN)
|
|
87402
|
+
continue;
|
|
87403
|
+
if (shannonEntropy(cap) < ENV_KV_MIN_ENTROPY)
|
|
87404
|
+
continue;
|
|
87405
|
+
}
|
|
87406
|
+
raw.push({
|
|
87407
|
+
rule_id: p.rule_id,
|
|
87408
|
+
start: globalStart,
|
|
87409
|
+
end: globalEnd,
|
|
87410
|
+
matched_text: cap,
|
|
87411
|
+
key_name: keyName,
|
|
87412
|
+
confidence: "high"
|
|
87413
|
+
});
|
|
87414
|
+
}
|
|
87415
|
+
}
|
|
87416
|
+
const kvHits = scanKeyValue(win.text);
|
|
87417
|
+
for (const h of kvHits) {
|
|
87418
|
+
raw.push({ ...h, start: h.start + win.offset, end: h.end + win.offset });
|
|
87419
|
+
}
|
|
87420
|
+
for (const h of scanDbUris(win.text)) {
|
|
87421
|
+
raw.push({ ...h, start: h.start + win.offset, end: h.end + win.offset });
|
|
87422
|
+
}
|
|
87423
|
+
for (const h of scanMemorablePasswords(win.text)) {
|
|
87424
|
+
raw.push({ ...h, start: h.start + win.offset, end: h.end + win.offset });
|
|
87425
|
+
}
|
|
87426
|
+
const genHits = scanGenericSecrets(win.text);
|
|
87427
|
+
for (const h of genHits) {
|
|
87428
|
+
raw.push({ ...h, start: h.start + win.offset, end: h.end + win.offset });
|
|
87429
|
+
}
|
|
87430
|
+
}
|
|
87431
|
+
const deduped = dedupeRaw(raw);
|
|
87432
|
+
const final = dropOverlaps(deduped);
|
|
87433
|
+
const existing = new Set;
|
|
87434
|
+
const out = [];
|
|
87435
|
+
for (const h of final) {
|
|
87436
|
+
const suggested_slug = deriveSlug({ key_name: h.key_name, rule_id: h.rule_id }, existing);
|
|
87437
|
+
existing.add(suggested_slug);
|
|
87438
|
+
out.push({
|
|
87439
|
+
rule_id: h.rule_id,
|
|
87440
|
+
matched_text: h.matched_text,
|
|
87441
|
+
start: h.start,
|
|
87442
|
+
end: h.end,
|
|
87443
|
+
confidence: h.confidence,
|
|
87444
|
+
suppressed: isSuppressed(text, h.start, h.end),
|
|
87445
|
+
suggested_slug,
|
|
87446
|
+
key_name: h.key_name
|
|
87447
|
+
});
|
|
87448
|
+
}
|
|
87449
|
+
out.sort((a, b) => a.start - b.start);
|
|
87450
|
+
return out;
|
|
87451
|
+
}
|
|
87452
|
+
function dedupeRaw(raw) {
|
|
87453
|
+
const seen = new Map;
|
|
87454
|
+
for (const h of raw) {
|
|
87455
|
+
const key = `${h.start}:${h.end}`;
|
|
87456
|
+
const existing = seen.get(key);
|
|
87457
|
+
if (!existing) {
|
|
87458
|
+
seen.set(key, h);
|
|
87459
|
+
continue;
|
|
87460
|
+
}
|
|
87461
|
+
if (existing.confidence === "ambiguous" && h.confidence === "high") {
|
|
87462
|
+
seen.set(key, h);
|
|
87463
|
+
}
|
|
87464
|
+
}
|
|
87465
|
+
return Array.from(seen.values());
|
|
87466
|
+
}
|
|
87467
|
+
function dropOverlaps(hits) {
|
|
87468
|
+
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))));
|
|
87469
|
+
out.sort((a, b) => a.start - b.start || a.end - b.end);
|
|
87470
|
+
return out;
|
|
87471
|
+
}
|
|
87472
|
+
|
|
87473
|
+
// telegram-plugin/secret-detect/redact.ts
|
|
87474
|
+
var REDACTED_MARKER = "[REDACTED]";
|
|
87475
|
+
function redact(text) {
|
|
87476
|
+
if (!text || text.length === 0)
|
|
87477
|
+
return text;
|
|
87478
|
+
const urlScrubbed = redactUrls(text);
|
|
87479
|
+
const hits = detectSecrets(urlScrubbed).filter((h) => h.rule_id !== "generic_high_entropy");
|
|
87480
|
+
if (hits.length === 0)
|
|
87481
|
+
return urlScrubbed;
|
|
87482
|
+
const sorted = [...hits].sort((a, b) => b.start - a.start);
|
|
87483
|
+
let out = urlScrubbed;
|
|
87484
|
+
for (const h of sorted) {
|
|
87485
|
+
out = out.slice(0, h.start) + redactedMarker(h.rule_id) + out.slice(h.end);
|
|
87486
|
+
}
|
|
87487
|
+
return out;
|
|
87488
|
+
}
|
|
87489
|
+
function redactedMarker(ruleId) {
|
|
87490
|
+
const trimmed = ruleId.replace(/^(kv|env)_/, "");
|
|
87491
|
+
if (!trimmed || trimmed === "key_value" || trimmed === "kv_entropy") {
|
|
87492
|
+
return REDACTED_MARKER;
|
|
87493
|
+
}
|
|
87494
|
+
return `[REDACTED:${trimmed}]`;
|
|
87495
|
+
}
|
|
87496
|
+
// src/memory/hindsight-write-redaction.ts
|
|
87497
|
+
function redactJsonStrings(value) {
|
|
87498
|
+
if (typeof value === "string")
|
|
87499
|
+
return redact(value);
|
|
87500
|
+
if (Array.isArray(value))
|
|
87501
|
+
return value.map(redactJsonStrings);
|
|
87502
|
+
if (value && typeof value === "object") {
|
|
87503
|
+
const out = {};
|
|
87504
|
+
for (const [k, v] of Object.entries(value)) {
|
|
87505
|
+
out[k] = redactJsonStrings(v);
|
|
87506
|
+
}
|
|
87507
|
+
return out;
|
|
87508
|
+
}
|
|
87509
|
+
return value;
|
|
87510
|
+
}
|
|
87511
|
+
var REDACTED_ITEM_FIELDS = ["content", "context"];
|
|
87512
|
+
function redactMemoryWriteBody(body) {
|
|
87513
|
+
if (!body || typeof body !== "object" || Array.isArray(body))
|
|
87514
|
+
return body;
|
|
87515
|
+
const src = body;
|
|
87516
|
+
if (!Array.isArray(src.items))
|
|
87517
|
+
return body;
|
|
87518
|
+
const items = src.items.map((item) => {
|
|
87519
|
+
if (!item || typeof item !== "object" || Array.isArray(item))
|
|
87520
|
+
return item;
|
|
87521
|
+
const row = { ...item };
|
|
87522
|
+
for (const field of REDACTED_ITEM_FIELDS) {
|
|
87523
|
+
if (typeof row[field] === "string") {
|
|
87524
|
+
row[field] = redact(row[field]);
|
|
87525
|
+
}
|
|
87526
|
+
}
|
|
87527
|
+
if (row.metadata && typeof row.metadata === "object") {
|
|
87528
|
+
row.metadata = redactJsonStrings(row.metadata);
|
|
87529
|
+
}
|
|
87530
|
+
return row;
|
|
87531
|
+
});
|
|
87532
|
+
return { ...src, items };
|
|
87533
|
+
}
|
|
87534
|
+
|
|
87521
87535
|
// src/cli/memory.ts
|
|
87522
87536
|
init_helpers();
|
|
87523
87537
|
init_hindsight();
|
|
@@ -97162,14 +97176,10 @@ function registerHandoffCommand(program3) {
|
|
|
97162
97176
|
jsonlPath: jsonl,
|
|
97163
97177
|
agentDir,
|
|
97164
97178
|
agentName,
|
|
97165
|
-
maxTurns: cappedMaxTurns
|
|
97166
|
-
observationScopes: agentConfig?.memory?.observation_scopes
|
|
97179
|
+
maxTurns: cappedMaxTurns
|
|
97167
97180
|
});
|
|
97168
97181
|
process.stderr.write(`handoff: ${status}
|
|
97169
97182
|
`);
|
|
97170
|
-
if (status === HANDOFF_STATUS_MIRROR_SKIPPED) {
|
|
97171
|
-
process.exitCode = 1;
|
|
97172
|
-
}
|
|
97173
97183
|
const retention = pruneSessionJsonl(claudeConfigDir, {
|
|
97174
97184
|
maxCount: continuity?.session_retention_max_count ?? DEFAULT_SESSION_RETENTION_MAX_COUNT,
|
|
97175
97185
|
maxAgeDays: continuity?.session_retention_max_age_days ?? DEFAULT_SESSION_RETENTION_MAX_AGE_DAYS,
|
|
@@ -100240,12 +100250,25 @@ function findMissingWorkspaceScopes(seedScope, tier) {
|
|
|
100240
100250
|
return requiredWorkspaceScopesForTier(tier).filter((s) => !have.has(s));
|
|
100241
100251
|
}
|
|
100242
100252
|
var DRIVE_FILE_SCOPE = "https://www.googleapis.com/auth/drive.file";
|
|
100243
|
-
|
|
100253
|
+
var CALENDAR_READONLY_SCOPE = "https://www.googleapis.com/auth/calendar.readonly";
|
|
100254
|
+
function seedOptInCapabilities(seedScope) {
|
|
100255
|
+
const have = new Set(seedScope.split(/\s+/).map((s) => s.trim()).filter((s) => s.length > 0));
|
|
100256
|
+
return {
|
|
100257
|
+
write: have.has(DRIVE_FILE_SCOPE),
|
|
100258
|
+
calendar: have.has(CALENDAR_READONLY_SCOPE)
|
|
100259
|
+
};
|
|
100260
|
+
}
|
|
100261
|
+
function buildMissingScopeWarning(missing, tier, accountEmail, granted) {
|
|
100244
100262
|
const short = missing.map((s) => s.replace(/^https:\/\/www\.googleapis\.com\/auth\//, "")).join(", ");
|
|
100263
|
+
const suffix = (granted.write ? " --write" : "") + (granted.calendar ? " --calendar" : "");
|
|
100264
|
+
const preserved = [
|
|
100265
|
+
granted.write ? "--write preserves the existing Drive write capability" : "",
|
|
100266
|
+
granted.calendar ? "--calendar preserves the existing Calendar read capability" : ""
|
|
100267
|
+
].filter((s) => s.length > 0);
|
|
100245
100268
|
return `drive-mcp-launcher: WARNING \u2014 the Google account '${accountEmail}' was ` + `consented WITHOUT the scope(s) needed for tier '${tier ?? "core"}': ` + `${short}.
|
|
100246
100269
|
` + ` The matching MCP tools (Docs / Sheets / Slides create+edit) will FAIL ` + `to authenticate. OAuth scopes are fixed at consent time \u2014 re-run on the ` + `host to re-mint the token with the correct scopes:
|
|
100247
|
-
` + ` switchroom auth google account add ${accountEmail} --replace` + `${
|
|
100248
|
-
` + ` (scopes are derived from \`google_workspace.tier\` \u2014 set the tier ` + `before re-running${
|
|
100270
|
+
` + ` switchroom auth google account add ${accountEmail} --replace` + `${suffix}
|
|
100271
|
+
` + ` (scopes are derived from \`google_workspace.tier\` \u2014 set the tier ` + `before re-running${preserved.length > 0 ? "; " + preserved.join("; ") : ""}` + `). Drive read/file tools are unaffected.
|
|
100249
100272
|
`;
|
|
100250
100273
|
}
|
|
100251
100274
|
function buildUvxArgs(tier) {
|
|
@@ -100493,8 +100516,7 @@ async function runDriveMcpLauncher(opts) {
|
|
|
100493
100516
|
const tier = opts.tier ?? configSecrets.tier;
|
|
100494
100517
|
const missingScopes = findMissingWorkspaceScopes(brokerCreds.scope, tier);
|
|
100495
100518
|
if (missingScopes.length > 0) {
|
|
100496
|
-
|
|
100497
|
-
process.stderr.write(buildMissingScopeWarning(missingScopes, tier, brokerCreds.accountEmail, hasWriteScope));
|
|
100519
|
+
process.stderr.write(buildMissingScopeWarning(missingScopes, tier, brokerCreds.accountEmail, seedOptInCapabilities(brokerCreds.scope)));
|
|
100498
100520
|
}
|
|
100499
100521
|
const args = buildUvxArgs(tier);
|
|
100500
100522
|
const env2 = buildChildEnv(process.env, credentialsDir, brokerCreds.accountEmail);
|