@wrongstack/core 0.309.0 → 0.310.0

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.
Files changed (129) hide show
  1. package/dist/chronicle/index.js +113 -41
  2. package/dist/chronicle/metrics-ingest.d.ts +7 -0
  3. package/dist/chronicle/metrics-schema.d.ts +4 -1
  4. package/dist/chronicle/project-server.js +94 -38
  5. package/dist/chronicle/query.d.ts +1 -0
  6. package/dist/chronicle/sqlite-journal-schema.d.ts +2 -1
  7. package/dist/chronicle/types.d.ts +2 -0
  8. package/dist/{agent-status-helpers.d.ts → coordination/agent-status-helpers.d.ts} +1 -1
  9. package/dist/{agent-status-tracker.d.ts → coordination/agent-status-tracker.d.ts} +2 -2
  10. package/dist/{middleware → coordination}/collab-pause.d.ts +1 -1
  11. package/dist/coordination/director-kanban-queue-helpers.d.ts +1 -1
  12. package/dist/coordination/director.d.ts +7 -0
  13. package/dist/coordination/explore-companion.d.ts +9 -6
  14. package/dist/coordination/fleet-status-tool.d.ts +1 -1
  15. package/dist/coordination/index.d.ts +5 -1
  16. package/dist/coordination/index.js +2392 -405
  17. package/dist/coordination/kanban-dispatch-port.d.ts +30 -0
  18. package/dist/coordination/kanban-ops-port.d.ts +21 -0
  19. package/dist/coordination/mailbox-hooks.d.ts +1 -1
  20. package/dist/coordination/mailbox-project-server.js +14 -9
  21. package/dist/coordination/mutation-engine.d.ts +5 -3
  22. package/dist/core/context.d.ts +35 -44
  23. package/dist/core/conversation-state.d.ts +16 -52
  24. package/dist/core/index.js +92 -25
  25. package/dist/core/provider-runner.d.ts +2 -2
  26. package/dist/core/run-env.d.ts +7 -28
  27. package/dist/core/streaming-response-builder.d.ts +2 -2
  28. package/dist/execution/index.js +138 -162
  29. package/dist/goal/index.js +100 -22
  30. package/dist/hq/auth-store.d.ts +9 -0
  31. package/dist/hq/cost-bridge.d.ts +1 -1
  32. package/dist/hq/index.js +69 -7
  33. package/dist/index.d.ts +12 -5
  34. package/dist/index.js +21185 -26807
  35. package/dist/infrastructure/index.js +232 -135
  36. package/dist/infrastructure/provider-cache-ledger.d.ts +1 -1
  37. package/dist/infrastructure/token-counter.d.ts +5 -1
  38. package/dist/kernel/events/file-events.d.ts +6 -0
  39. package/dist/kernel/events/provider-events.d.ts +10 -7
  40. package/dist/kernel/events/session-events.d.ts +4 -4
  41. package/dist/kernel/events/tool-events.d.ts +12 -2
  42. package/dist/observability/index.js +1 -1
  43. package/dist/plugin/index.js +218 -47
  44. package/dist/prompts/index.js +360 -3
  45. package/dist/security/auto-approve-policy.d.ts +2 -2
  46. package/dist/security/index.d.ts +1 -0
  47. package/dist/security/index.js +224 -68
  48. package/dist/security/kanban-boundary.d.ts +1 -1
  49. package/dist/security/kanban-governance-port.d.ts +28 -0
  50. package/dist/security/permission-helpers.d.ts +11 -0
  51. package/dist/security/permission-policy.d.ts +10 -1
  52. package/dist/security/yolo-risk.d.ts +17 -0
  53. package/dist/session-catalog/index.js +34 -5
  54. package/dist/session-catalog/project-server.js +34 -5
  55. package/dist/session-catalog/protocol.d.ts +1 -1
  56. package/dist/session-catalog/registry.d.ts +1 -1
  57. package/dist/session-catalog/store-schema.d.ts +1 -1
  58. package/dist/session-catalog/store.d.ts +1 -1
  59. package/dist/skills/index.js +39 -6
  60. package/dist/storage/annotations-store.d.ts +1 -1
  61. package/dist/storage/board-store-port.d.ts +45 -0
  62. package/dist/storage/completed-work-checkpoint.d.ts +1 -1
  63. package/dist/storage/config-loader/types.d.ts +1 -1
  64. package/dist/storage/event-bus-port.d.ts +27 -0
  65. package/dist/storage/file-session-writer.d.ts +47 -5
  66. package/dist/storage/goal-coordination.d.ts +1 -1
  67. package/dist/storage/goal-store.d.ts +1 -1
  68. package/dist/storage/index.d.ts +3 -4
  69. package/dist/storage/index.js +1107 -1462
  70. package/dist/storage/plan-store.d.ts +1 -1
  71. package/dist/storage/queue-store.d.ts +1 -1
  72. package/dist/storage/replay-log-store.d.ts +1 -1
  73. package/dist/storage/session-recovery.d.ts +10 -0
  74. package/dist/storage/session-resume-validation.d.ts +13 -1
  75. package/dist/storage/session-store/events.d.ts +1 -1
  76. package/dist/storage/session-store/load-session-data.d.ts +1 -1
  77. package/dist/storage/session-store/rename-session.d.ts +1 -1
  78. package/dist/storage/session-store/resume-session.d.ts +3 -1
  79. package/dist/storage/session-store/session-store-index.d.ts +2 -1
  80. package/dist/storage/session-store/types.d.ts +1 -1
  81. package/dist/storage/session-store.d.ts +70 -0
  82. package/dist/storage/session-summary-tracker.d.ts +8 -0
  83. package/dist/storage/session-write-buffer.d.ts +31 -2
  84. package/dist/storage/task-store.d.ts +1 -1
  85. package/dist/storage/todos-checkpoint.d.ts +1 -1
  86. package/dist/storage/tool-audit-log.d.ts +1 -1
  87. package/dist/tasking/index.js +100 -22
  88. package/dist/tasking/task-tracker.d.ts +24 -1
  89. package/dist/types/compactor.d.ts +2 -2
  90. package/dist/types/context.d.ts +212 -0
  91. package/dist/types/conversation-state.d.ts +109 -0
  92. package/dist/types/error-handler.d.ts +2 -2
  93. package/dist/types/file-event-record.d.ts +6 -0
  94. package/dist/types/index.d.ts +3 -3
  95. package/dist/types/index.js +17 -1
  96. package/dist/types/permission.d.ts +3 -3
  97. package/dist/types/plugin.d.ts +3 -3
  98. package/dist/types/provider-runner.d.ts +2 -2
  99. package/dist/types/provider.d.ts +10 -0
  100. package/dist/types/run-env.d.ts +32 -0
  101. package/dist/types/session.d.ts +3 -0
  102. package/dist/types/slash-command.d.ts +2 -2
  103. package/dist/types/token-counter.d.ts +30 -1
  104. package/dist/types/tool-executor.d.ts +2 -2
  105. package/dist/types/tool.d.ts +20 -5
  106. package/dist/utils/context-breakdown.d.ts +2 -2
  107. package/dist/utils/context-evidence.d.ts +12 -12
  108. package/dist/utils/crash-shield.d.ts +9 -0
  109. package/dist/utils/heap-watchdog.js +11 -3
  110. package/dist/utils/index.d.ts +2 -1
  111. package/dist/utils/index.js +126 -160
  112. package/dist/utils/regex-guard.d.ts +7 -30
  113. package/dist/utils/terminal-sanitize.d.ts +41 -0
  114. package/dist/utils/todos-format.d.ts +1 -1
  115. package/dist/utils/tool-subject.d.ts +1 -1
  116. package/dist/utils/tree-kill.d.ts +2 -0
  117. package/dist/utils/tree-kill.js +1 -0
  118. package/instructions/agents/chaos-monkey.md +5 -1
  119. package/instructions/coordination/subagent-baseline.md +10 -0
  120. package/instructions/system-lite.md +2 -0
  121. package/instructions/system-pro.md +40 -0
  122. package/instructions/system.md +15 -0
  123. package/package.json +7 -10
  124. package/dist/defaults/index.d.ts +0 -69
  125. package/dist/defaults/index.js +0 -37755
  126. /package/dist/{fleet-notifier.d.ts → coordination/fleet-notifier.d.ts} +0 -0
  127. /package/dist/{session-registry-atomic-file.d.ts → session-catalog/session-registry-atomic-file.d.ts} +0 -0
  128. /package/dist/{session-registry-types.d.ts → session-catalog/session-registry-types.d.ts} +0 -0
  129. /package/dist/{session-registry.d.ts → session-catalog/session-registry.d.ts} +0 -0
@@ -740,7 +740,7 @@ import * as fs5 from "node:fs";
740
740
  import * as path5 from "node:path";
741
741
 
742
742
  // src/coordination/sqlite-mailbox-schema.ts
743
- import { createRequire } from "node:module";
743
+ import { loadRuntimeDatabaseSync } from "@wrongstack/persistence";
744
744
 
745
745
  // src/utils/sqlite-warning.ts
746
746
  var SQLITE_EXPERIMENTAL_WARNING_RE = /sqlite is an experimental feature/i;
@@ -770,8 +770,7 @@ var DatabaseSyncCtor;
770
770
  function loadDatabaseSync() {
771
771
  if (DatabaseSyncCtor) return DatabaseSyncCtor;
772
772
  return withSqliteExperimentalWarningSuppressed(() => {
773
- const require2 = createRequire(import.meta.url);
774
- DatabaseSyncCtor = require2("node:sqlite").DatabaseSync;
773
+ DatabaseSyncCtor = loadRuntimeDatabaseSync();
775
774
  return DatabaseSyncCtor;
776
775
  });
777
776
  }
@@ -804,6 +803,14 @@ var PATTERNS = [
804
803
  anchor: "sk-ant-"
805
804
  },
806
805
  { type: "openai_key", regex: /(?<![A-Za-z0-9])sk-(?:proj-)?[A-Za-z0-9_-]{20,}(?![A-Za-z0-9])/g, anchor: "sk-" },
806
+ {
807
+ // `xai` is a first-class provider in this codebase, but its key shape was
808
+ // absent here — so the one credential format WrongStack itself hands users
809
+ // was the one the scrubber could not recognize (audit 2026-08-20).
810
+ type: "xai_key",
811
+ regex: /(?<![A-Za-z0-9])xai-[A-Za-z0-9]{20,}(?![A-Za-z0-9])/g,
812
+ anchor: "xai-"
813
+ },
807
814
  { type: "github_pat", regex: /(?<![A-Za-z0-9])ghp_[A-Za-z0-9]{36,}(?![A-Za-z0-9])/g, anchor: "ghp_" },
808
815
  { type: "github_pat_v2", regex: /(?<![A-Za-z0-9])github_pat_[A-Za-z0-9_]{50,}(?![A-Za-z0-9])/g, anchor: "github_pat_" },
809
816
  { type: "aws_access_key", regex: /(?<![A-Za-z0-9])AKIA[0-9A-Z]{16}(?![A-Za-z0-9])/g, anchor: "AKIA" },
@@ -894,8 +901,8 @@ var PATTERNS = [
894
901
  // replacement so the separator between adjacent secrets is preserved
895
902
  // rather than collapsed. Capture groups are therefore: 1=leading
896
903
  // delimiter, 2=key name, 3=value.
897
- regex: /(^|\s)([A-Z_]{4,}(?:KEY|TOKEN|SECRET|PASSWORD|PWD))\s*[:=]\s*['"]?([A-Za-z0-9_/+=-]{20,512})['"]?(?=\s|$)/g,
898
- anchor: ["KEY", "TOKEN", "SECRET", "PASSWORD", "PWD"]
904
+ regex: /(^|\s)([A-Z_]{4,}(?:KEY|TOKEN|SECRET|PASSWORD|PWD|PASSPHRASE))\s*[:=]\s*['"]?([A-Za-z0-9_/+=-]{20,512})['"]?(?=\s|$)/g,
905
+ anchor: ["KEY", "TOKEN", "SECRET", "PASSWORD", "PWD", "PASSPHRASE"]
899
906
  },
900
907
  {
901
908
  type: "json_credential_key",
@@ -1012,6 +1019,27 @@ var JSON_CREDENTIAL_REGEX = PATTERNS.find((p) => p.type === "json_credential_key
1012
1019
  var COMBINED_REPLACEMENTS = SIMPLE_PATTERNS.map((p) => `[REDACTED:${p.type}]`);
1013
1020
  var SCRUB_CHUNK_BYTES = 64 * 1024;
1014
1021
  var SCRUB_OVERLAP_BYTES = 1024;
1022
+ var PEM_PRIVATE_KEY_BEGIN_RE = /-----BEGIN (?:RSA|EC|OPENSSH|DSA|PGP)? ?PRIVATE KEY-----/;
1023
+ var PEM_END_MARKER = "-----END";
1024
+ var MAX_PEM_BLOCK_BYTES = 64 * 1024;
1025
+ var PEM_END_LINE_TOLERANCE = 64;
1026
+ function extendChunkBoundaryPastPem(text, chunkStart, proposedEnd) {
1027
+ const head = text.slice(chunkStart, proposedEnd);
1028
+ const lastBegin = head.lastIndexOf("-----BEGIN ");
1029
+ if (lastBegin === -1) return proposedEnd;
1030
+ const fromBegin = text.slice(chunkStart + lastBegin);
1031
+ const marker = PEM_PRIVATE_KEY_BEGIN_RE.exec(fromBegin);
1032
+ if (!marker || marker.index !== 0) return proposedEnd;
1033
+ const bodyStart = marker[0].length;
1034
+ const cap = Math.min(text.length, chunkStart + lastBegin + MAX_PEM_BLOCK_BYTES);
1035
+ const closeIdx = fromBegin.indexOf(PEM_END_MARKER, bodyStart);
1036
+ if (closeIdx === -1 || chunkStart + lastBegin + closeIdx >= cap + PEM_END_LINE_TOLERANCE) {
1037
+ return proposedEnd;
1038
+ }
1039
+ const lineEnd = fromBegin.indexOf("\n", closeIdx);
1040
+ const end = lineEnd === -1 ? text.length : chunkStart + lastBegin + lineEnd + 1;
1041
+ return Math.max(proposedEnd, end);
1042
+ }
1015
1043
  var PATTERN_ANCHORS = [
1016
1044
  ...new Set(
1017
1045
  PATTERNS.flatMap(
@@ -1048,6 +1076,7 @@ var DefaultSecretScrubber = class {
1048
1076
  }
1049
1077
  }
1050
1078
  end = safe === -1 ? end : safe + 1;
1079
+ end = extendChunkBoundaryPastPem(text, i, end);
1051
1080
  }
1052
1081
  out.push(this.scrubOne(text.slice(i, end)));
1053
1082
  i = end;
@@ -184,7 +184,7 @@ import * as fs2 from "node:fs";
184
184
  import * as path3 from "node:path";
185
185
 
186
186
  // src/coordination/sqlite-mailbox-schema.ts
187
- import { createRequire } from "node:module";
187
+ import { loadRuntimeDatabaseSync } from "@wrongstack/persistence";
188
188
 
189
189
  // src/utils/sqlite-warning.ts
190
190
  var SQLITE_EXPERIMENTAL_WARNING_RE = /sqlite is an experimental feature/i;
@@ -214,8 +214,7 @@ var DatabaseSyncCtor;
214
214
  function loadDatabaseSync() {
215
215
  if (DatabaseSyncCtor) return DatabaseSyncCtor;
216
216
  return withSqliteExperimentalWarningSuppressed(() => {
217
- const require2 = createRequire(import.meta.url);
218
- DatabaseSyncCtor = require2("node:sqlite").DatabaseSync;
217
+ DatabaseSyncCtor = loadRuntimeDatabaseSync();
219
218
  return DatabaseSyncCtor;
220
219
  });
221
220
  }
@@ -248,6 +247,14 @@ var PATTERNS = [
248
247
  anchor: "sk-ant-"
249
248
  },
250
249
  { type: "openai_key", regex: /(?<![A-Za-z0-9])sk-(?:proj-)?[A-Za-z0-9_-]{20,}(?![A-Za-z0-9])/g, anchor: "sk-" },
250
+ {
251
+ // `xai` is a first-class provider in this codebase, but its key shape was
252
+ // absent here — so the one credential format WrongStack itself hands users
253
+ // was the one the scrubber could not recognize (audit 2026-08-20).
254
+ type: "xai_key",
255
+ regex: /(?<![A-Za-z0-9])xai-[A-Za-z0-9]{20,}(?![A-Za-z0-9])/g,
256
+ anchor: "xai-"
257
+ },
251
258
  { type: "github_pat", regex: /(?<![A-Za-z0-9])ghp_[A-Za-z0-9]{36,}(?![A-Za-z0-9])/g, anchor: "ghp_" },
252
259
  { type: "github_pat_v2", regex: /(?<![A-Za-z0-9])github_pat_[A-Za-z0-9_]{50,}(?![A-Za-z0-9])/g, anchor: "github_pat_" },
253
260
  { type: "aws_access_key", regex: /(?<![A-Za-z0-9])AKIA[0-9A-Z]{16}(?![A-Za-z0-9])/g, anchor: "AKIA" },
@@ -338,8 +345,8 @@ var PATTERNS = [
338
345
  // replacement so the separator between adjacent secrets is preserved
339
346
  // rather than collapsed. Capture groups are therefore: 1=leading
340
347
  // delimiter, 2=key name, 3=value.
341
- regex: /(^|\s)([A-Z_]{4,}(?:KEY|TOKEN|SECRET|PASSWORD|PWD))\s*[:=]\s*['"]?([A-Za-z0-9_/+=-]{20,512})['"]?(?=\s|$)/g,
342
- anchor: ["KEY", "TOKEN", "SECRET", "PASSWORD", "PWD"]
348
+ regex: /(^|\s)([A-Z_]{4,}(?:KEY|TOKEN|SECRET|PASSWORD|PWD|PASSPHRASE))\s*[:=]\s*['"]?([A-Za-z0-9_/+=-]{20,512})['"]?(?=\s|$)/g,
349
+ anchor: ["KEY", "TOKEN", "SECRET", "PASSWORD", "PWD", "PASSPHRASE"]
343
350
  },
344
351
  {
345
352
  type: "json_credential_key",
@@ -456,6 +463,27 @@ var JSON_CREDENTIAL_REGEX = PATTERNS.find((p) => p.type === "json_credential_key
456
463
  var COMBINED_REPLACEMENTS = SIMPLE_PATTERNS.map((p) => `[REDACTED:${p.type}]`);
457
464
  var SCRUB_CHUNK_BYTES = 64 * 1024;
458
465
  var SCRUB_OVERLAP_BYTES = 1024;
466
+ var PEM_PRIVATE_KEY_BEGIN_RE = /-----BEGIN (?:RSA|EC|OPENSSH|DSA|PGP)? ?PRIVATE KEY-----/;
467
+ var PEM_END_MARKER = "-----END";
468
+ var MAX_PEM_BLOCK_BYTES = 64 * 1024;
469
+ var PEM_END_LINE_TOLERANCE = 64;
470
+ function extendChunkBoundaryPastPem(text, chunkStart, proposedEnd) {
471
+ const head = text.slice(chunkStart, proposedEnd);
472
+ const lastBegin = head.lastIndexOf("-----BEGIN ");
473
+ if (lastBegin === -1) return proposedEnd;
474
+ const fromBegin = text.slice(chunkStart + lastBegin);
475
+ const marker = PEM_PRIVATE_KEY_BEGIN_RE.exec(fromBegin);
476
+ if (!marker || marker.index !== 0) return proposedEnd;
477
+ const bodyStart = marker[0].length;
478
+ const cap = Math.min(text.length, chunkStart + lastBegin + MAX_PEM_BLOCK_BYTES);
479
+ const closeIdx = fromBegin.indexOf(PEM_END_MARKER, bodyStart);
480
+ if (closeIdx === -1 || chunkStart + lastBegin + closeIdx >= cap + PEM_END_LINE_TOLERANCE) {
481
+ return proposedEnd;
482
+ }
483
+ const lineEnd = fromBegin.indexOf("\n", closeIdx);
484
+ const end = lineEnd === -1 ? text.length : chunkStart + lastBegin + lineEnd + 1;
485
+ return Math.max(proposedEnd, end);
486
+ }
459
487
  var PATTERN_ANCHORS = [
460
488
  ...new Set(
461
489
  PATTERNS.flatMap(
@@ -492,6 +520,7 @@ var DefaultSecretScrubber = class {
492
520
  }
493
521
  }
494
522
  end = safe === -1 ? end : safe + 1;
523
+ end = extendChunkBoundaryPastPem(text, i, end);
495
524
  }
496
525
  out.push(this.scrubOne(text.slice(i, end)));
497
526
  i = end;
@@ -1,4 +1,4 @@
1
- import type { SessionRegistryEntry } from '../session-registry-types.js';
1
+ import type { SessionRegistryEntry } from './session-registry-types.js';
2
2
  import type { SessionSummary } from '../types/session.js';
3
3
  export declare const SESSION_CATALOG_PROTOCOL_VERSION = 1;
4
4
  export declare const SESSION_CATALOG_MAX_FRAME_CHARS: number;
@@ -1,4 +1,4 @@
1
- import type { AgentEntry, SessionRegistryEntry } from '../session-registry-types.js';
1
+ import type { AgentEntry, SessionRegistryEntry } from './session-registry-types.js';
2
2
  import type { ResumeReservation, SessionCatalogEvent } from './protocol.js';
3
3
  export interface SessionResumeClaim {
4
4
  reservation: ResumeReservation;
@@ -1,5 +1,5 @@
1
1
  import type { DatabaseSync } from 'node:sqlite';
2
- import type { SessionRegistryEntry } from '../session-registry-types.js';
2
+ import type { SessionRegistryEntry } from './session-registry-types.js';
3
3
  export declare const SCHEMA_VERSION = 1;
4
4
  export declare const MAX_LEASE_MS = 120000;
5
5
  export declare const MAX_RESERVATION_MS = 60000;
@@ -1,4 +1,4 @@
1
- import type { SessionRegistryEntry } from '../session-registry-types.js';
1
+ import type { SessionRegistryEntry } from './session-registry-types.js';
2
2
  import type { SessionSummary } from '../types/session.js';
3
3
  import type { CatalogSessionRecord, MaintenanceLease, ResumeReservation, SessionCatalogHealth, SessionCatalogListArgs, SessionLeaseCredential } from './protocol.js';
4
4
  export declare class SessionCatalogStore {
@@ -888,6 +888,32 @@ function numField(rec, key) {
888
888
  import { spawn } from "node:child_process";
889
889
  import * as fs3 from "node:fs/promises";
890
890
  import * as path3 from "node:path";
891
+
892
+ // src/utils/win32-cmd.ts
893
+ var WIN32_CMD_META = /[&|<>"%\r\n\0]/;
894
+ function buildWin32CmdShimInvocation(command, args = []) {
895
+ assertSafeWin32CmdArgs([command, ...args]);
896
+ const line = ["call", quoteWin32CmdArg(command), ...args.map(quoteWin32CmdArg)].join(" ");
897
+ return {
898
+ command: process.env["COMSPEC"] ?? "cmd.exe",
899
+ args: ["/d", "/c", line],
900
+ windowsVerbatimArguments: true
901
+ };
902
+ }
903
+ function assertSafeWin32CmdArgs(args) {
904
+ for (const arg of args) {
905
+ if (typeof arg === "string" && WIN32_CMD_META.test(arg)) {
906
+ throw new Error(
907
+ 'win32 cmd shim spawn: argument contains a shell metacharacter (one of & | < > ", or a newline) that could enable command injection through the .cmd/.bat wrapper - refusing to run. Offending argument: ' + JSON.stringify(arg)
908
+ );
909
+ }
910
+ }
911
+ }
912
+ function quoteWin32CmdArg(arg) {
913
+ return `"${arg}"`;
914
+ }
915
+
916
+ // src/skills/skill-generator.ts
891
917
  async function validateSkillNameAvailable(name, loader) {
892
918
  const formatViolations = validateSkillName(name);
893
919
  const conflicts = loader ? (await loader.listEntries()).filter((e) => e.name === name) : [];
@@ -989,14 +1015,21 @@ async function openInEditor(filePath, env = process.env) {
989
1015
  context: { filePath }
990
1016
  });
991
1017
  }
992
- const child = spawn(parts[0], [...parts.slice(1), filePath], {
1018
+ const editorArgs = [...parts.slice(1), filePath];
1019
+ const child = shell ? (() => {
1020
+ const inv = buildWin32CmdShimInvocation(parts[0], editorArgs);
1021
+ return spawn(inv.command, inv.args, {
1022
+ stdio: "ignore",
1023
+ detached: true,
1024
+ windowsVerbatimArguments: inv.windowsVerbatimArguments,
1025
+ // Suppresses the console flash the cmd.exe wrapper would otherwise
1026
+ // show before the editor appears. Repo convention — see
1027
+ // `core/tests/architecture/spawn-convention.test.ts`.
1028
+ windowsHide: true
1029
+ });
1030
+ })() : spawn(parts[0], editorArgs, {
993
1031
  stdio: "ignore",
994
1032
  detached: true,
995
- shell,
996
- // `shell` routes through cmd.exe on win32, which flashes a console window
997
- // before the editor appears. A GUI editor is unaffected by the flag; the
998
- // shell wrapper is what it suppresses. Repo convention — see
999
- // `core/tests/architecture/spawn-convention.test.ts`.
1000
1033
  windowsHide: true
1001
1034
  });
1002
1035
  child.unref();
@@ -1,4 +1,4 @@
1
- import type { EventBus } from '../kernel/events.js';
1
+ import type { EventBus } from './event-bus-port.js';
2
2
  /**
3
3
  * L2-B: AnnotationsStore — sidecar storage for collaboration annotations
4
4
  * (Phase 2 of idea #13 from IDEAS.md).
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Kanban board-store port for the storage area (goal coordination).
3
+ *
4
+ * `goal-kanban.ts` and `goal-coordination.ts` previously imported the
5
+ * concrete functions from `@wrongstack/kanban` directly, keeping a
6
+ * core → kanban runtime edge (the layering inversion catalogued as
7
+ * roadmap #11 / report §2.1). This port is the leaf contract the storage
8
+ * area codes against; the CLI composition root registers the real
9
+ * implementation at boot (TOKENS-style dependency injection).
10
+ *
11
+ * Type-only imports of kanban's data shapes remain permitted — they erase
12
+ * at compile time and do not create a runtime edge. Only *callable*
13
+ * surface is ported.
14
+ *
15
+ * Un-registered default: goal features fail soft (board sync disabled)
16
+ * exactly as before the extraction, so embedding contexts without kanban
17
+ * keep working.
18
+ */
19
+ import type { CreateKanbanBoardInput, CreateKanbanTaskInput, KanbanBoard, KanbanBoardSummary, KanbanEventContext, KanbanTask, UpdateKanbanTaskInput } from '@wrongstack/kanban';
20
+ export interface BoardStorePort {
21
+ createBoard(projectRoot: string, input: CreateKanbanBoardInput): Promise<KanbanBoard>;
22
+ listBoards(projectRoot: string): Promise<KanbanBoardSummary[]>;
23
+ getBoard(projectRoot: string, boardId: string): Promise<KanbanBoard | null>;
24
+ removeBoard(projectRoot: string, boardId: string): Promise<boolean>;
25
+ addTask(projectRoot: string, boardId: string, input: CreateKanbanTaskInput, eventContext?: KanbanEventContext): Promise<{
26
+ board: KanbanBoard;
27
+ task: KanbanTask;
28
+ } | null>;
29
+ updateTask(projectRoot: string, boardId: string, taskId: string, input: UpdateKanbanTaskInput, eventContext?: KanbanEventContext): Promise<KanbanBoard | null>;
30
+ }
31
+ /** Composition-root hook: register the real kanban-backed implementation. */
32
+ export declare function setBoardStorePort(impl: BoardStorePort): void;
33
+ /**
34
+ * Resolve the board-store port. Callers that must fail soft should catch
35
+ * the not-wired error, same semantics as kanban being absent.
36
+ */
37
+ export declare function boardStore(): BoardStorePort;
38
+ /**
39
+ * Fail-soft resolver: the port when wired, otherwise `null`. For code paths
40
+ * whose documented behavior when kanban is absent is a no-op or empty
41
+ * result (goal sync, previews) — avoids the synchronous throw breaking
42
+ * `.catch()` fail-soft chains in non-CLI embeddings.
43
+ */
44
+ export declare function tryBoardStore(): BoardStorePort | null;
45
+ //# sourceMappingURL=board-store-port.d.ts.map
@@ -1,4 +1,4 @@
1
- import type { EventBus } from '../kernel/events.js';
1
+ import type { EventBus } from './event-bus-port.js';
2
2
  import type { CompletedWorkEvidence } from '../types/context-evidence.js';
3
3
  export interface CompletedWorkCheckpointFile {
4
4
  version: 1;
@@ -1,4 +1,4 @@
1
- import type { EventBus } from '../../kernel/events.js';
1
+ import type { EventBus } from '../event-bus-port.js';
2
2
  import type { Config } from '../../types/config.js';
3
3
  import type { Logger } from '../../types/logger.js';
4
4
  import type { SecretVault } from '../../types/secret-vault.js';
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Event-bus port for the storage area.
3
+ *
4
+ * Storage modules receive a bus instance from the composition root and use
5
+ * it only to *emit* `storage.*` events (verified: zero subscribe/logger/
6
+ * constructor calls across packages/core/src/storage). Importing the concrete
7
+ * `EventBus` class from `../kernel/events.js` kept a storage → kernel edge in
8
+ * the dependency graph; this port severs it.
9
+ *
10
+ * Structurally satisfied by the kernel `EventBus` class (its typed-generic
11
+ * `emit` is compatible with this loose signature via method-bivariance), so
12
+ * composition-root wiring needs no adapter.
13
+ *
14
+ * Surface policy: emit-only. If a storage consumer ever needs to subscribe,
15
+ * widen this port deliberately instead of re-importing the kernel type.
16
+ */
17
+ export interface EventBus {
18
+ /**
19
+ * Emit a storage-domain event onto the bus.
20
+ *
21
+ * Loose parameter typing is deliberate: the concrete kernel bus carries the
22
+ * full `EventMap` typing; storage call sites pass literal `'storage.*'`
23
+ * names whose payloads the kernel map validates at the wiring boundary.
24
+ */
25
+ emit(event: string, payload?: unknown): void;
26
+ }
27
+ //# sourceMappingURL=event-bus-port.d.ts.map
@@ -1,5 +1,5 @@
1
1
  import * as fsp from 'node:fs/promises';
2
- import type { EventBus } from '../kernel/events.js';
2
+ import type { EventBus } from './event-bus-port.js';
3
3
  import type { SecretScrubber } from '../types/secret-scrubber.js';
4
4
  import type { FileSnapshot, SessionEvent, SessionMetadata, SessionSummary, SessionWriter } from '../types/session.js';
5
5
  import type { SessionCheckpointCas } from './session-checkpoint-cas.js';
@@ -46,6 +46,15 @@ export declare class FileSessionWriter implements SessionWriter {
46
46
  get onAppendBatch(): ((events: SessionEvent[]) => void) | undefined;
47
47
  set onAppendBatch(cb: ((events: SessionEvent[]) => void) | undefined);
48
48
  private readonly onCloseCb?;
49
+ /** Mid-session metadata checkpoint throttle. 0 disables checkpointing. */
50
+ private readonly metadataCheckpointMs;
51
+ /** One-shot guard for the "interval set but no sink" warning below. */
52
+ private _checkpointNoSinkWarned;
53
+ private readonly onMetadataCheckpointCb?;
54
+ /** Set whenever summary counters changed since the last metadata checkpoint. */
55
+ private metadataDirty;
56
+ private metadataTimer;
57
+ private metadataCheckpointInFlight;
49
58
  /** Implements SessionWriter.traceId — propagated from ContextInit.traceId. */
50
59
  traceId: string | undefined;
51
60
  /**
@@ -104,15 +113,47 @@ export declare class FileSessionWriter implements SessionWriter {
104
113
  onClose?: ((summary: SessionSummary) => void | Promise<void>) | undefined;
105
114
  /** Reconcile an explicit name changed while this writer remained open. */
106
115
  resolveName?: (() => Promise<Pick<SessionSummary, 'name'> | null>) | undefined;
116
+ /**
117
+ * Mid-session metadata checkpoint throttle (ms). While the session is
118
+ * live and dirty, the summary sidecar + index row are refreshed at most
119
+ * this often, so a SIGKILLed process still leaves accurate listing
120
+ * metadata instead of its create-time stub. 0 disables checkpointing —
121
+ * killed sessions then stay visible through the store's list()-union
122
+ * scan, but only with analyzer-derived metadata rather than tracked
123
+ * counters. Default 10_000.
124
+ */
125
+ metadataCheckpointMs?: number | undefined;
126
+ /**
127
+ * Persists a mid-session summary snapshot (store-level index row /
128
+ * catalog upsert). The sidecar file itself is written by the writer
129
+ * under its manifest lock; this callback covers the index side.
130
+ */
131
+ onMetadataCheckpoint?: ((summary: SessionSummary) => void | Promise<void>) | undefined;
107
132
  }, traceId?: string | undefined);
108
133
  get pendingToolUses(): string[];
109
134
  private writeSessionStartLazy;
135
+ /**
136
+ * Arm the mid-session metadata checkpoint timer if it is not already armed.
137
+ * Called after every observed event; the timer itself is unref'd so an idle
138
+ * session never keeps the process alive for a cosmetic sidecar refresh.
139
+ */
140
+ private scheduleMetadataCheckpoint;
141
+ /**
142
+ * Persist a mid-session summary snapshot: the `.summary.json` sidecar under
143
+ * the manifest lock, then the store-level index row / catalog upsert via
144
+ * `onMetadataCheckpoint`. Runs at most once per throttle interval and only
145
+ * when summary counters changed since the last checkpoint; a failed
146
+ * checkpoint stays dirty and retries on the next armed tick.
147
+ */
148
+ private runMetadataCheckpoint;
110
149
  append(event: SessionEvent): Promise<void>;
111
150
  appendBatch(events: SessionEvent[]): Promise<void>;
112
151
  /**
113
152
  * Flush buffered events to disk immediately. Critical events
114
- * (user_input, llm_response) call this so they survive SIGKILL/crash
115
- * instead of sitting in the in-memory buffer for up to 500ms.
153
+ * (user_input, llm_response, checkpoint, in_flight_*) already flush
154
+ * themselves inside append()/appendBatch(), so calling this matters for
155
+ * non-critical tails that would otherwise sit in the in-memory buffer
156
+ * for up to 500ms.
116
157
  *
117
158
  * Idempotent — cancels any pending timer, writes whatever has accumulated,
118
159
  * then asks the OS to synchronize the file data before resolving. Even an
@@ -123,8 +164,9 @@ export declare class FileSessionWriter implements SessionWriter {
123
164
  * Last-gasp synchronous drain for hard-exit paths (process.exit after
124
165
  * rapid Ctrl+C). The async write chain cannot be awaited when the process
125
166
  * is about to die, but whatever still sits in the in-memory buffer CAN be
126
- * saved with a blocking append. Best-effort: an in-flight async write may
127
- * be cut off by the exit regardless; errors here are swallowed.
167
+ * saved with a blocking append. A failed sync append leaves the buffer
168
+ * intact so a subsequent close()/flush() can retry. An in-flight async
169
+ * write may still be cut off by a hard exit.
128
170
  */
129
171
  flushSync(): void;
130
172
  close(): Promise<void>;
@@ -1,5 +1,5 @@
1
1
  import type { BrainArbiter } from '../coordination/brain.js';
2
- import type { EventBus } from '../kernel/events.js';
2
+ import type { EventBus } from './event-bus-port.js';
3
3
  import { type GoalFile } from './goal-store.js';
4
4
  export interface CompletedGoalDeliverable {
5
5
  index: number;
@@ -1,4 +1,4 @@
1
- import type { EventBus } from '../kernel/events.js';
1
+ import type { EventBus } from './event-bus-port.js';
2
2
  /**
3
3
  * Long-running autonomous mission. A goal survives across sessions and
4
4
  * drives the EternalAutonomyEngine — every iteration of the engine
@@ -1,10 +1,9 @@
1
- export { AgentStatusTracker, type AgentStatusTrackerOptions, } from '../agent-status-tracker.js';
2
- export { FleetNotifier, type FleetNotifierOptions, } from '../fleet-notifier.js';
1
+ export { type BoardStorePort, boardStore, setBoardStorePort, } from './board-store-port.js';
3
2
  export { type CatalogSessionRecord, type MaintenanceLease, type ResumeReservation, type SessionCatalogHealth, SessionCatalogProjectClient, type SessionCatalogProjectClientOptions, type SessionLeaseCredential, type SessionResumeClaim, } from '../session-catalog/index.js';
4
3
  export { getProjectSessionRegistry as getSessionRegistry, hasProjectSessionRegistry as hasSessionRegistry, ProjectSessionRegistry as SessionRegistry, } from '../session-catalog/registry.js';
5
- export type { AgentEntry, AgentLiveStatus, SessionLiveStatus, SessionRegistryEntry, SessionWebUIEndpointHint, } from '../session-registry.js';
4
+ export type { AgentEntry, AgentLiveStatus, SessionLiveStatus, SessionRegistryEntry, SessionWebUIEndpointHint, } from '../session-catalog/session-registry.js';
6
5
  /** @deprecated Test/migration adapter; production ownership uses SessionRegistry above. */
7
- export { SessionRegistry as LegacySessionRegistry } from '../session-registry.js';
6
+ export { SessionRegistry as LegacySessionRegistry } from '../session-catalog/session-registry.js';
8
7
  export type { SyncCategory, SyncConfig } from '../types/config.js';
9
8
  export type { DefaultSessionReaderOptions, SessionReader } from '../types/session-reader.js';
10
9
  export { type Annotation, AnnotationsStore, type AnnotationsStoreOptions, } from './annotations-store.js';