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.
@@ -2120,7 +2120,7 @@ var init_esm = __esm(() => {
2120
2120
  });
2121
2121
 
2122
2122
  // src/build-info.ts
2123
- var VERSION = "0.19.44", COMMIT_SHA = "82d3b0b2";
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
- function isObservationScope(value) {
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", (chunk2) => this.onData(chunk2));
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(chunk2) {
36339
- this.buffer += chunk2.toString("utf8");
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
- return [...new Set([...base, ...workspace])];
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
- // telegram-plugin/secret-detect/patterns.ts
73054
- var ANCHORED_PATTERNS = [
73055
- { rule_id: "anthropic_api_key", regex: /\b(sk-ant-[A-Za-z0-9_-]{8,})\b/g, captureIndex: 1, slugHint: "anthropic_api_key" },
73056
- { 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" },
73057
- { rule_id: "openai_api_key", regex: /\b(sk-[A-Za-z0-9_-]{20,})\b/g, captureIndex: 1, slugHint: "openai_api_key" },
73058
- { rule_id: "github_pat_classic", regex: /\b(ghp_[A-Za-z0-9]{20,})\b/g, captureIndex: 1, slugHint: "github_pat" },
73059
- { rule_id: "github_pat_fine_grained", regex: /\b(github_pat_[A-Za-z0-9_]{20,})\b/g, captureIndex: 1, slugHint: "github_pat" },
73060
- { rule_id: "slack_token", regex: /\b(xox[baprs]-[A-Za-z0-9-]{10,})\b/g, captureIndex: 1, slugHint: "slack_token" },
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
- return false;
73199
- }
73200
- function isInertValue(value) {
73201
- if (!INERT_VALUE_RE.some((re) => re.test(value)))
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
- const start = m.index + valueOffsetInMatch;
73258
- hits.push({
73259
- rule_id: MEMORABLE_PW_RULE_ID,
73260
- start,
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
- if (isInertValue(value))
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
- const h = shannonEntropy(value);
73280
- if (h < KV_ENTROPY_THRESHOLD)
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
- const valueOffsetInMatch = m[0].indexOf(value, keyName.length);
73283
- if (valueOffsetInMatch < 0)
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
- const start = m.index + valueOffsetInMatch;
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
- return hits;
73297
- }
73298
-
73299
- // telegram-plugin/secret-detect/db-uri.ts
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
- const start = m.index + scheme.length + 3 + user.length + 1;
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
- return hits;
73112
+ if (deduped.length <= maxTurns)
73113
+ return deduped;
73114
+ return deduped.slice(deduped.length - maxTurns);
73328
73115
  }
73329
-
73330
- // telegram-plugin/secret-detect/generic-entropy.ts
73331
- var CANDIDATE_RE = /[A-Za-z0-9]{28,}/g;
73332
- var GENERIC_MIN_DISTINCT = 18;
73333
- var MAX_GENERIC_HITS = 20;
73334
- function hasDistinctChars(tok, n) {
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 hasDigit(tok) {
73348
- for (let i = 0;i < tok.length; i++) {
73349
- const c = tok.charCodeAt(i);
73350
- if (c >= 48 && c <= 57)
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
- return false;
73354
- }
73355
- function scanGenericSecrets(text) {
73356
- const hits = [];
73357
- CANDIDATE_RE.lastIndex = 0;
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
- hits.push({
73368
- rule_id: "generic_high_entropy",
73369
- start: m.index,
73370
- end: m.index + tok.length,
73371
- matched_text: tok,
73372
- confidence: "ambiguous"
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
- return hits;
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
- // telegram-plugin/secret-detect/chunker.ts
73379
- var CHUNK_THRESHOLD = 32 * 1024;
73380
- var WINDOW_SIZE = 16 * 1024;
73381
- var OVERLAP = 8 * 1024;
73382
- function chunk(text) {
73383
- if (text.length <= CHUNK_THRESHOLD) {
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 out = [];
73387
- let offset = 0;
73388
- while (offset < text.length) {
73389
- const end = Math.min(offset + WINDOW_SIZE, text.length);
73390
- out.push({ offset, text: text.slice(offset, end) });
73391
- if (end >= text.length)
73392
- break;
73393
- offset = end - OVERLAP;
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
- return out;
73177
+ if (turns.length > 0)
73178
+ return clampTopic(firstLine(turns[turns.length - 1].text));
73179
+ return "previous session";
73396
73180
  }
73397
-
73398
- // telegram-plugin/secret-detect/suppressor.ts
73399
- var MARKERS = ["test", "mock", "example", "fixture", "dummy"];
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
- // telegram-plugin/secret-detect/slug.ts
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 datePart(now = new Date) {
73416
- const y = now.getUTCFullYear();
73417
- const m = String(now.getUTCMonth() + 1).padStart(2, "0");
73418
- const d = String(now.getUTCDate()).padStart(2, "0");
73419
- return `${y}${m}${d}`;
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 deriveSlug(inputs, existing) {
73422
- let base;
73423
- if (inputs.key_name && inputs.key_name.trim().length > 0) {
73424
- base = sanitizeKeyName(inputs.key_name);
73425
- } else {
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
- if (!existing.has(base))
73429
- return base;
73430
- let n = 2;
73431
- while (existing.has(`${base}_${n}`))
73432
- n++;
73433
- return `${base}_${n}`;
73434
- }
73435
- // telegram-plugin/secret-detect/url-redact.ts
73436
- var SENSITIVE_PARAMS = new Set([
73437
- "token",
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", (chunk2) => {
76860
- buf += chunk2.toString();
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 = (chunk2) => {
76943
- buf += chunk2.toString();
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
- { runDriveOAuthFlow: runDriveOAuthFlow2, selectGoogleWorkspaceScopes: selectGoogleWorkspaceScopes2 },
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: opts.write ?? false,
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 (opts.write) {
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 = (chunk2) => {
78975
- const s = chunk2.toString("utf8");
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 maskToken2(s) {
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 ${maskToken2(v.value)}`);
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", (chunk2) => {
83166
- buffer += chunk2.toString("utf8");
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", (chunk2) => {
84276
- buffer += chunk2.toString("utf8");
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", (chunk2) => chunks.push(chunk2));
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
- function buildMissingScopeWarning(missing, tier, accountEmail, hasWriteScope) {
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` + `${hasWriteScope ? " --write" : ""}
100248
- ` + ` (scopes are derived from \`google_workspace.tier\` \u2014 set the tier ` + `before re-running${hasWriteScope ? "; --write preserves the existing " + "Drive write capability" : ""}). Drive read/file tools are unaffected.
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
- const hasWriteScope = brokerCreds.scope.split(/\s+/).map((s) => s.trim()).includes(DRIVE_FILE_SCOPE);
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);