@wrongstack/core 0.306.0 → 0.306.3

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 (37) hide show
  1. package/dist/chronicle/index.js +6 -1
  2. package/dist/chronicle/project-server.js +13 -3
  3. package/dist/coordination/index.d.ts +1 -0
  4. package/dist/coordination/index.js +210 -103
  5. package/dist/coordination/mailbox-codecs.d.ts +29 -10
  6. package/dist/coordination/mailbox-constants.d.ts +30 -16
  7. package/dist/coordination/mailbox-health.d.ts +16 -0
  8. package/dist/coordination/mailbox-http-validation.d.ts +2 -1
  9. package/dist/coordination/mailbox-parse-state.d.ts +28 -10
  10. package/dist/coordination/mailbox-project-server.js +136 -20
  11. package/dist/coordination/mailbox-types.d.ts +44 -6
  12. package/dist/coordination/package-outdated-watcher.d.ts +15 -1
  13. package/dist/coordination/sqlite-mailbox-credentials.d.ts +26 -0
  14. package/dist/coordination/sqlite-mailbox.d.ts +26 -0
  15. package/dist/coordination/techstack-mailbox-consumer.d.ts +17 -0
  16. package/dist/core/index.js +39 -12
  17. package/dist/defaults/index.js +58 -87
  18. package/dist/execution/index.js +10 -3
  19. package/dist/hq/index.js +6 -39
  20. package/dist/index.js +239 -153
  21. package/dist/infrastructure/index.js +6 -39
  22. package/dist/plugin/index.js +6 -2
  23. package/dist/security/file-permissions.d.ts +12 -35
  24. package/dist/security/index.js +8 -51
  25. package/dist/security/kanban-boundary.d.ts +3 -1
  26. package/dist/session-catalog/project-server.js +6 -39
  27. package/dist/storage/index.js +56 -49
  28. package/dist/types/blocks.d.ts +9 -0
  29. package/dist/utils/index.d.ts +1 -0
  30. package/dist/utils/index.js +22 -0
  31. package/dist/utils/memory-evidence-fence.d.ts +47 -0
  32. package/instructions/agents/code-reviewer.md +3 -0
  33. package/instructions/coordination/subagent-baseline.md +10 -1
  34. package/instructions/system-lite.md +8 -5
  35. package/instructions/system-pro.md +26 -0
  36. package/instructions/system.md +21 -0
  37. package/package.json +3 -3
@@ -24,6 +24,23 @@ export interface TechStackConsumerOptions {
24
24
  }>;
25
25
  /** Agent id that the consumer watches for. Default: 'tech-stack'. */
26
26
  targetAgent?: string | undefined;
27
+ /**
28
+ * The only sender whose `assign` messages may spawn an agent. Default:
29
+ * `'dep-watcher'` — the `watcherAgentId` default of
30
+ * {@link attachDepWatcherBridge}, which is the pipeline this consumer exists
31
+ * to serve. Matched on the base identity, so a session-qualified
32
+ * `dep-watcher@<tag>` also passes.
33
+ *
34
+ * Without this the consumer acted on an `assign` addressed to `tech-stack`
35
+ * from ANY sender. The mailbox is a shared bus: every agent on the project
36
+ * can send one with `mail_send`, and so can any external credential holding
37
+ * `mail.send.actionable` when the HTTP bridge is enabled. The message body
38
+ * then chose a file path and was pasted verbatim into the task of a freshly
39
+ * spawned subagent holding `read`, `fetch` and `mailbox` — a peer-writable
40
+ * path into an agent that can read files, reach the network, and broadcast
41
+ * to everyone. Restricting the sender is what makes the spawn intentional.
42
+ */
43
+ senderAgentId?: string | undefined;
27
44
  /** Agent id that sends the completion ack. Default: 'tech-stack-consumer'. */
28
45
  consumerAgentId?: string | undefined;
29
46
  /** Polling interval in ms. Default: 5000. */
@@ -1505,24 +1505,34 @@ var RemoteMailbox = class {
1505
1505
  }
1506
1506
  publishHqRegistryEvent(event, payload) {
1507
1507
  const publisher = this.hqPublisher;
1508
- if (!publisher || !event.startsWith("mailbox.agent_") && !event.startsWith("mailbox.client_")) {
1508
+ if (!publisher || this.closed || !event.startsWith("mailbox.agent_") && !event.startsWith("mailbox.client_")) {
1509
1509
  return;
1510
1510
  }
1511
1511
  const mailboxId = `${path5.basename(this.projectDir)}:mailbox`;
1512
1512
  const record = typeof payload === "object" && payload !== null ? payload : {};
1513
1513
  const agentId = typeof record["agentId"] === "string" ? record["agentId"] : void 0;
1514
1514
  const action = event === "mailbox.agent_registered" ? "agent.registered" : event === "mailbox.agent_heartbeat" ? "agent.heartbeat" : event === "mailbox.agent_deregistered" ? "agent.deregistered" : void 0;
1515
- void this.getAgentStatuses().then((statuses) => {
1516
- const agent = agentId ? statuses.find((candidate) => candidate.agentId === agentId) : void 0;
1515
+ if (action !== "agent.registered") {
1517
1516
  if (action) {
1518
1517
  publisher.publishMailboxEvent({
1519
1518
  mailboxId,
1520
1519
  action,
1521
- ...agent ? { agent } : {},
1522
1520
  ...agentId ? { summary: agentId } : {}
1523
1521
  });
1524
1522
  }
1525
1523
  if (action !== "agent.heartbeat") this.scheduleHqSnapshot(mailboxId);
1524
+ return;
1525
+ }
1526
+ void this.getAgentStatuses().then((statuses) => {
1527
+ if (this.closed) return;
1528
+ const agent = agentId ? statuses.find((candidate) => candidate.agentId === agentId) : void 0;
1529
+ publisher.publishMailboxEvent({
1530
+ mailboxId,
1531
+ action,
1532
+ ...agent ? { agent } : {},
1533
+ ...agentId ? { summary: agentId } : {}
1534
+ });
1535
+ this.scheduleHqSnapshot(mailboxId);
1526
1536
  }).catch(() => {
1527
1537
  });
1528
1538
  }
@@ -1654,7 +1664,12 @@ import * as syncFs from "node:fs";
1654
1664
  import * as path7 from "node:path";
1655
1665
 
1656
1666
  // src/security/file-permissions.ts
1657
- var SECRET_FILE_MODE = 384;
1667
+ import {
1668
+ restrictDirPermissions,
1669
+ restrictFilePermissions,
1670
+ SECRET_DIR_MODE,
1671
+ SECRET_FILE_MODE
1672
+ } from "@wrongstack/persistence";
1658
1673
 
1659
1674
  // src/utils/pid.ts
1660
1675
  function isPidAlive(pid) {
@@ -7389,6 +7404,24 @@ function deriveCachePrefixKey(systemPrompt) {
7389
7404
  return key;
7390
7405
  }
7391
7406
 
7407
+ // src/utils/memory-evidence-fence.ts
7408
+ var MEMORY_EVIDENCE_TAG = "memory_evidence";
7409
+ var FENCE_DELIMITER = /\[[ \t]*\/?[ \t]*memory_evidence\b[^\]\n]*\]/gi;
7410
+ function sanitizeMemoryEvidenceBody(text) {
7411
+ return text.replace(FENCE_DELIMITER, (match) => `(${match.slice(1, -1)})`);
7412
+ }
7413
+ function sanitizeMemoryEvidenceSource(source) {
7414
+ const collapsed = source.replace(/[^a-z0-9_.-]+/gi, "-").replace(/^-+|-+$/g, "").slice(0, 80).replace(/-+$/, "");
7415
+ return collapsed || "memory";
7416
+ }
7417
+ function formatMemoryEvidenceBlock(source, body) {
7418
+ const label = sanitizeMemoryEvidenceSource(source);
7419
+ const safe = sanitizeMemoryEvidenceBody(body);
7420
+ return `[${MEMORY_EVIDENCE_TAG} source="${label}"]
7421
+ ${safe}
7422
+ [/${MEMORY_EVIDENCE_TAG}]`;
7423
+ }
7424
+
7392
7425
  // src/utils/message-invariants.ts
7393
7426
  function repairToolUseAdjacency(messages) {
7394
7427
  const removedToolUses = [];
@@ -8014,15 +8047,9 @@ function buildMemoryEvidenceBlocks(ctx) {
8014
8047
  if (remaining <= 0) break;
8015
8048
  const text = entry.text.trim();
8016
8049
  if (!text) continue;
8017
- const source = entry.source.replace(/[^a-z0-9_.-]+/gi, "-").slice(0, 80) || "memory";
8018
8050
  const bounded = text.slice(0, remaining);
8019
8051
  remaining -= bounded.length;
8020
- blocks.push({
8021
- type: "text",
8022
- text: `[memory_evidence source="${source}"]
8023
- ${bounded}
8024
- [/memory_evidence]`
8025
- });
8052
+ blocks.push({ type: "text", text: formatMemoryEvidenceBlock(entry.source, bounded) });
8026
8053
  }
8027
8054
  return blocks;
8028
8055
  }
@@ -9820,45 +9820,12 @@ import * as fsp17 from "node:fs/promises";
9820
9820
  import * as path23 from "node:path";
9821
9821
 
9822
9822
  // src/security/file-permissions.ts
9823
- import { chmod } from "node:fs/promises";
9824
- var SECRET_FILE_MODE = 384;
9825
- async function restrictFilePermissions(filePath, opts) {
9826
- const label = opts?.label ?? "file-permissions";
9827
- const warn = opts?.warn ?? ((msg) => console.warn(msg));
9828
- if (process.platform === "win32") {
9829
- try {
9830
- const { execFile: execFile2 } = await import("node:child_process");
9831
- const { promisify: promisify2 } = await import("node:util");
9832
- const execFileAsync = promisify2(execFile2);
9833
- const user = windowsAccountName();
9834
- if (!user) {
9835
- warn(
9836
- `[${label}] Could not determine the current Windows user for ${filePath}; skipping icacls hardening.`
9837
- );
9838
- return;
9839
- }
9840
- await execFileAsync("icacls", [filePath, "/inheritance:r", "/grant:r", `${user}:(F)`], {
9841
- windowsHide: true
9842
- });
9843
- } catch {
9844
- warn(
9845
- `[${label}] Could not restrict permissions on ${filePath} \u2014 it may be readable by other users on this system.`
9846
- );
9847
- }
9848
- } else {
9849
- try {
9850
- await chmod(filePath, SECRET_FILE_MODE);
9851
- } catch {
9852
- }
9853
- }
9854
- }
9855
- function windowsAccountName() {
9856
- const username = process.env.USERNAME || process.env.USER;
9857
- if (!username || username.includes("\0")) return void 0;
9858
- const domain = process.env.USERDOMAIN;
9859
- if (domain && !domain.includes("\0")) return `${domain}\\${username}`;
9860
- return username;
9861
- }
9823
+ import {
9824
+ restrictDirPermissions,
9825
+ restrictFilePermissions,
9826
+ SECRET_DIR_MODE,
9827
+ SECRET_FILE_MODE
9828
+ } from "@wrongstack/persistence";
9862
9829
 
9863
9830
  // src/security/secret-scrubber.ts
9864
9831
  var PATTERNS = [
@@ -14038,6 +14005,44 @@ function userInputTitle(content) {
14038
14005
  return sessionContentPreview(content, 60);
14039
14006
  }
14040
14007
 
14008
+ // src/storage/session-writer-scrubber.ts
14009
+ function scrubSessionWriterEvent(event, secretScrubber) {
14010
+ const persistMessage = (message) => {
14011
+ const { _estTokens: _ignored, ...persisted } = message;
14012
+ return {
14013
+ ...persisted,
14014
+ content: typeof persisted.content === "string" ? secretScrubber?.scrub(persisted.content) ?? persisted.content : secretScrubber?.scrubObject(persisted.content) ?? persisted.content
14015
+ };
14016
+ };
14017
+ if (event.type === "context_snapshot" || event.type === "messages_replaced") {
14018
+ return { ...event, messages: event.messages.map(persistMessage) };
14019
+ }
14020
+ if (event.type === "message_appended" || event.type === "message_updated") {
14021
+ return { ...event, message: persistMessage(event.message) };
14022
+ }
14023
+ if (!secretScrubber) return event;
14024
+ if (event.type === "user_input") {
14025
+ return {
14026
+ ...event,
14027
+ content: typeof event.content === "string" ? secretScrubber.scrub(event.content) : secretScrubber.scrubObject(event.content)
14028
+ };
14029
+ }
14030
+ if (event.type === "llm_response") {
14031
+ return { ...event, content: secretScrubber.scrubObject(event.content) };
14032
+ }
14033
+ if (event.type === "file_snapshot") {
14034
+ return {
14035
+ ...event,
14036
+ files: event.files.map((f) => ({
14037
+ ...f,
14038
+ before: f.before !== null ? secretScrubber.scrub(f.before) : null,
14039
+ after: f.after !== null ? secretScrubber.scrub(f.after) : null
14040
+ }))
14041
+ };
14042
+ }
14043
+ return event;
14044
+ }
14045
+
14041
14046
  // src/storage/session-writer-truncate.ts
14042
14047
  import * as fsp6 from "node:fs/promises";
14043
14048
  var CHUNK_SIZE = 65536;
@@ -14182,45 +14187,11 @@ async function rewriteSessionToCheckpoint(filePath, checkpointByteOffset) {
14182
14187
  }
14183
14188
  }
14184
14189
 
14185
- // src/storage/session-writer-scrubber.ts
14186
- function scrubSessionWriterEvent(event, secretScrubber) {
14187
- const persistMessage = (message) => {
14188
- const { _estTokens: _ignored, ...persisted } = message;
14189
- return {
14190
- ...persisted,
14191
- content: typeof persisted.content === "string" ? secretScrubber?.scrub(persisted.content) ?? persisted.content : secretScrubber?.scrubObject(persisted.content) ?? persisted.content
14192
- };
14193
- };
14194
- if (event.type === "context_snapshot" || event.type === "messages_replaced") {
14195
- return { ...event, messages: event.messages.map(persistMessage) };
14196
- }
14197
- if (event.type === "message_appended" || event.type === "message_updated") {
14198
- return { ...event, message: persistMessage(event.message) };
14199
- }
14200
- if (!secretScrubber) return event;
14201
- if (event.type === "user_input") {
14202
- return {
14203
- ...event,
14204
- content: typeof event.content === "string" ? secretScrubber.scrub(event.content) : secretScrubber.scrubObject(event.content)
14205
- };
14206
- }
14207
- if (event.type === "llm_response") {
14208
- return { ...event, content: secretScrubber.scrubObject(event.content) };
14209
- }
14210
- if (event.type === "file_snapshot") {
14211
- return {
14212
- ...event,
14213
- files: event.files.map((f) => ({
14214
- ...f,
14215
- before: f.before !== null ? secretScrubber.scrub(f.before) : null,
14216
- after: f.after !== null ? secretScrubber.scrub(f.after) : null
14217
- }))
14218
- };
14219
- }
14220
- return event;
14221
- }
14222
-
14223
14190
  // src/storage/file-session-writer.ts
14191
+ function isClosedHandleError(err) {
14192
+ const code = err?.code;
14193
+ return code === "EBADF" || code === "ERR_CLOSED_RESOURCE" || code === "ERR_INVALID_HANDLE";
14194
+ }
14224
14195
  var FileSessionWriter = class _FileSessionWriter {
14225
14196
  constructor(id, handle, startedAt, meta, events, opts = {}, traceId) {
14226
14197
  this.id = id;
@@ -14392,8 +14363,7 @@ var FileSessionWriter = class _FileSessionWriter {
14392
14363
  try {
14393
14364
  return await this.handle.appendFile(data, "utf8");
14394
14365
  } catch (err) {
14395
- const nodeErr = err;
14396
- if (nodeErr?.code === "EBADF") {
14366
+ if (isClosedHandleError(err)) {
14397
14367
  this.handle = await fsp7.open(this.filePath, "a", 384);
14398
14368
  return await this.handle.appendFile(data, "utf8");
14399
14369
  }
@@ -14433,8 +14403,8 @@ var FileSessionWriter = class _FileSessionWriter {
14433
14403
  bufferSynchronousEvent(event) {
14434
14404
  if (this.closed) return;
14435
14405
  void this.ensureInit();
14436
- this.observeForSummary(event);
14437
- const appendEvent = event.type === "file_snapshot" ? scrubSessionWriterEvent(event, this.secretScrubber) : event;
14406
+ const appendEvent = scrubSessionWriterEvent(event, this.secretScrubber);
14407
+ this.observeForSummary(appendEvent);
14438
14408
  try {
14439
14409
  this._onAppend?.(appendEvent);
14440
14410
  } catch {
@@ -14587,8 +14557,7 @@ var FileSessionWriter = class _FileSessionWriter {
14587
14557
  try {
14588
14558
  await this.handle.datasync();
14589
14559
  } catch (err) {
14590
- const nodeErr = err;
14591
- if (nodeErr?.code === "EBADF") {
14560
+ if (isClosedHandleError(err)) {
14592
14561
  this.handle = await fsp7.open(this.filePath, "a", 384);
14593
14562
  return;
14594
14563
  }
@@ -14825,6 +14794,7 @@ var FileSessionWriter = class _FileSessionWriter {
14825
14794
  return this.closePromise;
14826
14795
  }
14827
14796
  async doClose() {
14797
+ await this.ensureInit();
14828
14798
  if (this.pendingFileSnapshots.length > 0) {
14829
14799
  await this.writeFileSnapshot(this.activePromptIndex ?? 0, [...this.pendingFileSnapshots]);
14830
14800
  this.pendingFileSnapshots = [];
@@ -14840,8 +14810,7 @@ var FileSessionWriter = class _FileSessionWriter {
14840
14810
  try {
14841
14811
  await this.handle.datasync();
14842
14812
  } catch (err) {
14843
- const nodeErr = err;
14844
- if (nodeErr?.code !== "EBADF") throw err;
14813
+ if (!isClosedHandleError(err)) throw err;
14845
14814
  }
14846
14815
  const endedAt = (/* @__PURE__ */ new Date()).toISOString();
14847
14816
  const observedActivityMs = Date.parse(this.lastActivityAt);
@@ -28277,7 +28246,8 @@ async function evaluateToolKanbanBoundary(tool, input, ctx, options = {}) {
28277
28246
  decision: "block",
28278
28247
  reason: "Active card is not implementation-ready: " + readiness.issues.map((issue) => issue.message).join(" | "),
28279
28248
  boardId: board.id,
28280
- taskId: task.id
28249
+ taskId: task.id,
28250
+ readinessIssues: readiness.issues
28281
28251
  };
28282
28252
  }
28283
28253
  if (task.lifecycle?.currentStage !== "running" || task.assignment?.status !== "running") {
@@ -29068,7 +29038,8 @@ ${errorDetails}`,
29068
29038
  type: "tool_result",
29069
29039
  tool_use_id: use.id,
29070
29040
  content: `Tool "${tool.name}" blocked by Kanban boundary. ${boundary.reason ?? ""}`.trim(),
29071
- is_error: true
29041
+ is_error: true,
29042
+ _kanbanBoundary: boundary
29072
29043
  };
29073
29044
  budget = this.budgetForString(result.content, budget);
29074
29045
  return { result, tool, durationMs: Date.now() - start };
@@ -7270,7 +7270,12 @@ import { dirname as dirname3 } from "node:path";
7270
7270
  import { createInterface } from "node:readline";
7271
7271
 
7272
7272
  // src/security/file-permissions.ts
7273
- var SECRET_FILE_MODE = 384;
7273
+ import {
7274
+ restrictDirPermissions,
7275
+ restrictFilePermissions,
7276
+ SECRET_DIR_MODE,
7277
+ SECRET_FILE_MODE
7278
+ } from "@wrongstack/persistence";
7274
7279
 
7275
7280
  // src/coordination/brain-ledger.ts
7276
7281
  var QUESTION_MAX = 200;
@@ -21401,7 +21406,8 @@ async function evaluateToolKanbanBoundary(tool, input, ctx, options = {}) {
21401
21406
  decision: "block",
21402
21407
  reason: "Active card is not implementation-ready: " + readiness.issues.map((issue) => issue.message).join(" | "),
21403
21408
  boardId: board.id,
21404
- taskId: task.id
21409
+ taskId: task.id,
21410
+ readinessIssues: readiness.issues
21405
21411
  };
21406
21412
  }
21407
21413
  if (task.lifecycle?.currentStage !== "running" || task.assignment?.status !== "running") {
@@ -22192,7 +22198,8 @@ ${errorDetails}`,
22192
22198
  type: "tool_result",
22193
22199
  tool_use_id: use.id,
22194
22200
  content: `Tool "${tool.name}" blocked by Kanban boundary. ${boundary.reason ?? ""}`.trim(),
22195
- is_error: true
22201
+ is_error: true,
22202
+ _kanbanBoundary: boundary
22196
22203
  };
22197
22204
  budget = this.budgetForString(result.content, budget);
22198
22205
  return { result, tool, durationMs: Date.now() - start };
package/dist/hq/index.js CHANGED
@@ -2050,45 +2050,12 @@ import * as fs3 from "node:fs/promises";
2050
2050
  import * as path4 from "node:path";
2051
2051
 
2052
2052
  // src/security/file-permissions.ts
2053
- import { chmod } from "node:fs/promises";
2054
- var SECRET_FILE_MODE = 384;
2055
- async function restrictFilePermissions(filePath, opts) {
2056
- const label = opts?.label ?? "file-permissions";
2057
- const warn = opts?.warn ?? ((msg) => console.warn(msg));
2058
- if (process.platform === "win32") {
2059
- try {
2060
- const { execFile } = await import("node:child_process");
2061
- const { promisify } = await import("node:util");
2062
- const execFileAsync = promisify(execFile);
2063
- const user = windowsAccountName();
2064
- if (!user) {
2065
- warn(
2066
- `[${label}] Could not determine the current Windows user for ${filePath}; skipping icacls hardening.`
2067
- );
2068
- return;
2069
- }
2070
- await execFileAsync("icacls", [filePath, "/inheritance:r", "/grant:r", `${user}:(F)`], {
2071
- windowsHide: true
2072
- });
2073
- } catch {
2074
- warn(
2075
- `[${label}] Could not restrict permissions on ${filePath} \u2014 it may be readable by other users on this system.`
2076
- );
2077
- }
2078
- } else {
2079
- try {
2080
- await chmod(filePath, SECRET_FILE_MODE);
2081
- } catch {
2082
- }
2083
- }
2084
- }
2085
- function windowsAccountName() {
2086
- const username = process.env.USERNAME || process.env.USER;
2087
- if (!username || username.includes("\0")) return void 0;
2088
- const domain = process.env.USERDOMAIN;
2089
- if (domain && !domain.includes("\0")) return `${domain}\\${username}`;
2090
- return username;
2091
- }
2053
+ import {
2054
+ restrictDirPermissions,
2055
+ restrictFilePermissions,
2056
+ SECRET_DIR_MODE,
2057
+ SECRET_FILE_MODE
2058
+ } from "@wrongstack/persistence";
2092
2059
 
2093
2060
  // src/utils/pid.ts
2094
2061
  function isPidAlive(pid) {