@wrongstack/core 0.309.0 → 0.309.1

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.
@@ -17098,11 +17098,13 @@ var CHAOS_MONKEY_AGENT = {
17098
17098
  tools: [...TOOLS.build],
17099
17099
  skillNames: ["testing", "typescript-strict"],
17100
17100
  spawnBudgetExempt: true,
17101
- // Follow fleet worktree policy (NOT 'required'): mutation targets are
17102
- // often freshly written and uncommitted — a worktree spawned from HEAD
17103
- // would not contain them and every mutant would drift. Callers pass
17104
- // `worktree: 'off'` in the mutation_test input for uncommitted targets.
17105
- worktree: "auto",
17101
+ // Run in the live checkout: mutation targets are usually freshly
17102
+ // written and uncommitted — a worktree spawned from HEAD would not
17103
+ // contain them and every mutant would drift. The mutation_test tool
17104
+ // honors this value as its default; callers can still override per
17105
+ // call via its `chaosWorktree` input when targets are committed and
17106
+ // isolation is wanted.
17107
+ worktree: "off",
17106
17108
  // Report travels via submit_result + final text, not the leader's stream.
17107
17109
  textStream: "silent",
17108
17110
  toolStream: "silent"
@@ -20441,6 +20443,10 @@ var DefaultSkillLoader = class {
20441
20443
  );
20442
20444
  for (const e of entries) {
20443
20445
  if (!await entryIsDirectory(dir, e)) continue;
20446
+ if (!isValidSkillNameFormat(e.name)) {
20447
+ this.skipped.push({ dir, entry: e.name, reason: "invalid-name-format" });
20448
+ continue;
20449
+ }
20444
20450
  const skillFile = path15.join(dir, e.name, "SKILL.md");
20445
20451
  let raw;
20446
20452
  try {
@@ -21510,9 +21516,21 @@ function renderCommandLine(command, args) {
21510
21516
  });
21511
21517
  return [command, ...rendered].join(" ");
21512
21518
  }
21513
- function subjectForToolInput(toolName, input, subjectKey) {
21519
+ function renderSubjectFields(obj, fields) {
21520
+ const parts = [];
21521
+ for (const field of fields) {
21522
+ const value = obj[field];
21523
+ if (value === void 0 || value === null || value === "" || value === false) continue;
21524
+ const str = String(value);
21525
+ parts.push(`${field}=${/\s/.test(str) ? `"${str.replace(/"/g, '\\"')}"` : str}`);
21526
+ }
21527
+ return parts.join(" ");
21528
+ }
21529
+ function subjectForToolInput(toolName, input, subjectKey, subjectFields) {
21514
21530
  if (!input || typeof input !== "object") return void 0;
21515
21531
  const obj = input;
21532
+ const extra = subjectFields && subjectFields.length > 0 ? renderSubjectFields(obj, subjectFields) : "";
21533
+ const withExtra = (base) => extra ? `${base} ${extra}` : base;
21516
21534
  if (subjectKey) {
21517
21535
  const value = obj[subjectKey];
21518
21536
  if (Array.isArray(value)) {
@@ -21528,9 +21546,9 @@ function subjectForToolInput(toolName, input, subjectKey) {
21528
21546
  if (subjectKey === "command") {
21529
21547
  const rendered = renderCommandLine(value, obj["args"]);
21530
21548
  if (value === "commit" && obj["dry_run"] === true) {
21531
- return `${escapeGlobSubject(rendered)}:dry-run`;
21549
+ return `${escapeGlobSubject(withExtra(rendered))}:dry-run`;
21532
21550
  }
21533
- return escapeGlobSubject(rendered);
21551
+ return escapeGlobSubject(withExtra(rendered));
21534
21552
  }
21535
21553
  if (subjectKey === "directory" && obj["dry_run"] === true) {
21536
21554
  return `${escapeGlobSubject(value)}:dry-run`;
@@ -22781,7 +22799,7 @@ var ToolExecutor = class _ToolExecutor {
22781
22799
  return { result, tool, durationMs: Date.now() - start };
22782
22800
  }
22783
22801
  if (effectivePermission === "confirm") {
22784
- const suggestedPattern = boundary.decision === "confirm" ? `kanban-boundary:${boundary.path ?? tool.name}` : subjectForToolInput(tool.name, use.input, tool.subjectKey) ?? tool.name;
22802
+ const suggestedPattern = boundary.decision === "confirm" ? `kanban-boundary:${boundary.path ?? tool.name}` : subjectForToolInput(tool.name, use.input, tool.subjectKey, tool.subjectFields) ?? tool.name;
22785
22803
  if (this.opts.confirmAwaiter) {
22786
22804
  const awaiter = this.opts.confirmAwaiter;
22787
22805
  const choice = await new Promise(
package/dist/hq/index.js CHANGED
@@ -702,6 +702,14 @@ var PATTERNS = [
702
702
  anchor: "sk-ant-"
703
703
  },
704
704
  { type: "openai_key", regex: /(?<![A-Za-z0-9])sk-(?:proj-)?[A-Za-z0-9_-]{20,}(?![A-Za-z0-9])/g, anchor: "sk-" },
705
+ {
706
+ // `xai` is a first-class provider in this codebase, but its key shape was
707
+ // absent here — so the one credential format WrongStack itself hands users
708
+ // was the one the scrubber could not recognize (audit 2026-08-20).
709
+ type: "xai_key",
710
+ regex: /(?<![A-Za-z0-9])xai-[A-Za-z0-9]{20,}(?![A-Za-z0-9])/g,
711
+ anchor: "xai-"
712
+ },
705
713
  { type: "github_pat", regex: /(?<![A-Za-z0-9])ghp_[A-Za-z0-9]{36,}(?![A-Za-z0-9])/g, anchor: "ghp_" },
706
714
  { type: "github_pat_v2", regex: /(?<![A-Za-z0-9])github_pat_[A-Za-z0-9_]{50,}(?![A-Za-z0-9])/g, anchor: "github_pat_" },
707
715
  { type: "aws_access_key", regex: /(?<![A-Za-z0-9])AKIA[0-9A-Z]{16}(?![A-Za-z0-9])/g, anchor: "AKIA" },
@@ -792,8 +800,8 @@ var PATTERNS = [
792
800
  // replacement so the separator between adjacent secrets is preserved
793
801
  // rather than collapsed. Capture groups are therefore: 1=leading
794
802
  // delimiter, 2=key name, 3=value.
795
- regex: /(^|\s)([A-Z_]{4,}(?:KEY|TOKEN|SECRET|PASSWORD|PWD))\s*[:=]\s*['"]?([A-Za-z0-9_/+=-]{20,512})['"]?(?=\s|$)/g,
796
- anchor: ["KEY", "TOKEN", "SECRET", "PASSWORD", "PWD"]
803
+ regex: /(^|\s)([A-Z_]{4,}(?:KEY|TOKEN|SECRET|PASSWORD|PWD|PASSPHRASE))\s*[:=]\s*['"]?([A-Za-z0-9_/+=-]{20,512})['"]?(?=\s|$)/g,
804
+ anchor: ["KEY", "TOKEN", "SECRET", "PASSWORD", "PWD", "PASSPHRASE"]
797
805
  },
798
806
  {
799
807
  type: "json_credential_key",
@@ -910,6 +918,27 @@ var JSON_CREDENTIAL_REGEX = PATTERNS.find((p) => p.type === "json_credential_key
910
918
  var COMBINED_REPLACEMENTS = SIMPLE_PATTERNS.map((p) => `[REDACTED:${p.type}]`);
911
919
  var SCRUB_CHUNK_BYTES = 64 * 1024;
912
920
  var SCRUB_OVERLAP_BYTES = 1024;
921
+ var PEM_PRIVATE_KEY_BEGIN_RE = /-----BEGIN (?:RSA|EC|OPENSSH|DSA|PGP)? ?PRIVATE KEY-----/;
922
+ var PEM_END_MARKER = "-----END";
923
+ var MAX_PEM_BLOCK_BYTES = 64 * 1024;
924
+ var PEM_END_LINE_TOLERANCE = 64;
925
+ function extendChunkBoundaryPastPem(text, chunkStart, proposedEnd) {
926
+ const head = text.slice(chunkStart, proposedEnd);
927
+ const lastBegin = head.lastIndexOf("-----BEGIN ");
928
+ if (lastBegin === -1) return proposedEnd;
929
+ const fromBegin = text.slice(chunkStart + lastBegin);
930
+ const marker = PEM_PRIVATE_KEY_BEGIN_RE.exec(fromBegin);
931
+ if (!marker || marker.index !== 0) return proposedEnd;
932
+ const bodyStart = marker[0].length;
933
+ const cap = Math.min(text.length, chunkStart + lastBegin + MAX_PEM_BLOCK_BYTES);
934
+ const closeIdx = fromBegin.indexOf(PEM_END_MARKER, bodyStart);
935
+ if (closeIdx === -1 || chunkStart + lastBegin + closeIdx >= cap + PEM_END_LINE_TOLERANCE) {
936
+ return proposedEnd;
937
+ }
938
+ const lineEnd = fromBegin.indexOf("\n", closeIdx);
939
+ const end = lineEnd === -1 ? text.length : chunkStart + lastBegin + lineEnd + 1;
940
+ return Math.max(proposedEnd, end);
941
+ }
913
942
  var PATTERN_ANCHORS = [
914
943
  ...new Set(
915
944
  PATTERNS.flatMap(
@@ -946,6 +975,7 @@ var DefaultSecretScrubber = class {
946
975
  }
947
976
  }
948
977
  end = safe === -1 ? end : safe + 1;
978
+ end = extendChunkBoundaryPastPem(text, i, end);
949
979
  }
950
980
  out.push(this.scrubOne(text.slice(i, end)));
951
981
  i = end;
@@ -3080,6 +3110,15 @@ function deriveSessionStatus(agents) {
3080
3110
  (a) => a.status === "running" || a.status === "streaming" || a.status === "waiting_user"
3081
3111
  ) ? "active" : "idle";
3082
3112
  }
3113
+ function downgradeStaleAgentStatuses(agents, nowMs) {
3114
+ const cutoff = nowMs - HQ_STALE_SNAPSHOT_WINDOW_MS;
3115
+ return agents.map((agent) => {
3116
+ if (agent.status !== "running" && agent.status !== "streaming") return agent;
3117
+ const lastActivityAt = Date.parse(agent.lastActivityAt);
3118
+ if (!Number.isFinite(lastActivityAt) || lastActivityAt >= cutoff) return agent;
3119
+ return { ...agent, status: "idle" };
3120
+ });
3121
+ }
3083
3122
  function startSessionTelemetryBridge(opts) {
3084
3123
  const now = opts.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
3085
3124
  const publisher = opts.publisher;
@@ -3100,6 +3139,7 @@ function startSessionTelemetryBridge(opts) {
3100
3139
  let lastPublishedAtMs = Date.now();
3101
3140
  let disposed = false;
3102
3141
  function buildSnapshot() {
3142
+ const effectiveAgents = downgradeStaleAgentStatuses(agents, Date.parse(now()));
3103
3143
  return {
3104
3144
  sessionId: opts.sessionId,
3105
3145
  clientKind: identity.kind,
@@ -3107,11 +3147,11 @@ function startSessionTelemetryBridge(opts) {
3107
3147
  projectId: project.projectId,
3108
3148
  projectName: opts.projectName ?? project.projectName,
3109
3149
  projectRoot: opts.projectRoot,
3110
- status: deriveSessionStatus(agents),
3150
+ status: deriveSessionStatus(effectiveAgents),
3111
3151
  startedAt,
3112
3152
  lastActivityAt,
3113
- agentCount: agents.length,
3114
- agents,
3153
+ agentCount: effectiveAgents.length,
3154
+ agents: effectiveAgents,
3115
3155
  ...identity.hostname !== void 0 ? { hostname: identity.hostname } : {},
3116
3156
  ...identity.pid !== void 0 ? { pid: identity.pid } : {},
3117
3157
  ...opts.gitBranch !== void 0 ? { gitBranch: opts.gitBranch } : {}