@wrongstack/core 0.282.2 → 0.283.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 (47) hide show
  1. package/dist/{agent-subagent-runner-DCczSoQj.d.ts → agent-subagent-runner-BsuWhB28.d.ts} +2 -2
  2. package/dist/coordination/index.d.ts +12 -12
  3. package/dist/coordination/index.js +315 -41
  4. package/dist/coordination/index.js.map +1 -1
  5. package/dist/defaults/index.d.ts +12 -12
  6. package/dist/defaults/index.js +316 -53
  7. package/dist/defaults/index.js.map +1 -1
  8. package/dist/{events-BOv8h6I1.d.ts → events-KmxSmvho.d.ts} +5 -0
  9. package/dist/execution/index.d.ts +7 -7
  10. package/dist/extension/index.d.ts +2 -2
  11. package/dist/{global-mailbox-MDDFhLYh.d.ts → global-mailbox-rWVt8YlO.d.ts} +70 -7
  12. package/dist/{goal-store-BEmDmSKF.d.ts → goal-store-8owBXJT2.d.ts} +1 -1
  13. package/dist/hq/index.d.ts +3 -3
  14. package/dist/hq/index.js +106 -32
  15. package/dist/hq/index.js.map +1 -1
  16. package/dist/{index-Dd-PJJ8A.d.ts → index-BhCteHAF.d.ts} +1 -1
  17. package/dist/index.d.ts +289 -19
  18. package/dist/index.js +919 -110
  19. package/dist/index.js.map +1 -1
  20. package/dist/infrastructure/index.d.ts +1 -1
  21. package/dist/kernel/index.d.ts +4 -4
  22. package/dist/kernel/index.js.map +1 -1
  23. package/dist/models/index.js +105 -42
  24. package/dist/models/index.js.map +1 -1
  25. package/dist/{multi-agent-coordinator-BRqtpn-a.d.ts → multi-agent-coordinator-CrjeTB9i.d.ts} +1 -1
  26. package/dist/{null-fleet-bus-BkptvIVo.d.ts → null-fleet-bus-D4e9R7Qc.d.ts} +9 -4
  27. package/dist/observability/index.d.ts +1 -1
  28. package/dist/{parallel-eternal-engine-BXECuOVG.d.ts → parallel-eternal-engine-iY2uxocj.d.ts} +4 -4
  29. package/dist/{provider-runner-CJtCs1Rw.d.ts → provider-runner-oQhDaZmH.d.ts} +1 -1
  30. package/dist/sdd/index.d.ts +4 -4
  31. package/dist/sdd/index.js.map +1 -1
  32. package/dist/storage/index.d.ts +5 -5
  33. package/dist/storage/index.js.map +1 -1
  34. package/dist/{todos-checkpoint-Bw83WMh9.d.ts → todos-checkpoint-BwCrj4Cb.d.ts} +1 -1
  35. package/dist/{tool-executor-4mWN2vHW.d.ts → tool-executor-kElSEgO0.d.ts} +4 -4
  36. package/dist/types/index.d.ts +8 -8
  37. package/dist/types/index.js +105 -45
  38. package/dist/types/index.js.map +1 -1
  39. package/dist/{worktree-manager-BrtjUFYk.d.ts → worktree-manager-BjyAW30D.d.ts} +1 -1
  40. package/instructions/modes/audit-lite.md +13 -0
  41. package/instructions/modes/debug-lite.md +13 -0
  42. package/instructions/modes/plan-lite.md +14 -0
  43. package/instructions/modes/refactor-lite.md +13 -0
  44. package/instructions/modes/research-lite.md +13 -0
  45. package/instructions/modes/review-lite.md +14 -0
  46. package/instructions/modes/test-lite.md +13 -0
  47. package/package.json +1 -1
@@ -424,6 +424,11 @@ interface EventMap {
424
424
  };
425
425
  status: number;
426
426
  providerSwitched: boolean;
427
+ contextWindowWarning?: {
428
+ fromMaxContext: number;
429
+ toMaxContext: number;
430
+ currentTokens?: number | undefined;
431
+ } | undefined;
427
432
  };
428
433
  'tool.started': {
429
434
  sessionId?: string | undefined;
@@ -1,14 +1,14 @@
1
- export { A as AutoCompactionMiddleware, a as AutonomousRunner, b as AutonomousRunnerOptions, c as AutonomyPromptContributorOptions, C as CompactorOptions, d as CompactorStrategy, Z as ContextWindowBudgetSnapshot, $ as DEFAULT_RECOVERY_STRATEGIES, D as DefaultDesignKitLoader, e as DefaultErrorHandler, f as DefaultPromptLoader, g as DefaultRetryPolicy, h as DefaultSkillLoader, i as DesignKitLoaderOptions, k as DoneCheckResult, l as DoneConditionChecker, H as HybridCompactor, I as IntelligentCompactor, m as IntelligentCompactorOptions, P as PersistedActiveKit, n as PromptLoaderOptions, a0 as RecoveryStrategy, S as SelectiveCompactor, o as SelectiveCompactorOptions, p as SkillLoaderOptions, q as StrategyCompactorOptions, T as ToolExecutor, _ as _resetDesignKitLoaderMemo, r as _resetDesignRulesCache, s as activateDesign, u as buildGoalPreamble, a1 as buildRecoveryStrategies, v as clearActiveKit, w as clearPersistedActiveKit, x as createStrategyCompactor, y as designProjectDir, z as detectFrontendFile, B as detectFrontendIntent, E as getDesignKitLoader, F as getDesignState, G as installDesignStudioMiddleware, J as loadActiveKit, K as loadProjectDesignRules, L as makeAutonomyPromptContributor, M as makeDesignDetectToolCallMiddleware, N as makeDesignDetectUserInputMiddleware, O as makeDesignStudioRequestMiddleware, R as recordKitChoice, V as renderPrompt, W as resolveBundledDesignKitsDir, X as setActiveKit } from '../tool-executor-4mWN2vHW.js';
1
+ export { A as AutoCompactionMiddleware, a as AutonomousRunner, b as AutonomousRunnerOptions, c as AutonomyPromptContributorOptions, C as CompactorOptions, d as CompactorStrategy, Z as ContextWindowBudgetSnapshot, $ as DEFAULT_RECOVERY_STRATEGIES, D as DefaultDesignKitLoader, e as DefaultErrorHandler, f as DefaultPromptLoader, g as DefaultRetryPolicy, h as DefaultSkillLoader, i as DesignKitLoaderOptions, k as DoneCheckResult, l as DoneConditionChecker, H as HybridCompactor, I as IntelligentCompactor, m as IntelligentCompactorOptions, P as PersistedActiveKit, n as PromptLoaderOptions, a0 as RecoveryStrategy, S as SelectiveCompactor, o as SelectiveCompactorOptions, p as SkillLoaderOptions, q as StrategyCompactorOptions, T as ToolExecutor, _ as _resetDesignKitLoaderMemo, r as _resetDesignRulesCache, s as activateDesign, u as buildGoalPreamble, a1 as buildRecoveryStrategies, v as clearActiveKit, w as clearPersistedActiveKit, x as createStrategyCompactor, y as designProjectDir, z as detectFrontendFile, B as detectFrontendIntent, E as getDesignKitLoader, F as getDesignState, G as installDesignStudioMiddleware, J as loadActiveKit, K as loadProjectDesignRules, L as makeAutonomyPromptContributor, M as makeDesignDetectToolCallMiddleware, N as makeDesignDetectUserInputMiddleware, O as makeDesignStudioRequestMiddleware, R as recordKitChoice, V as renderPrompt, W as resolveBundledDesignKitsDir, X as setActiveKit } from '../tool-executor-kElSEgO0.js';
2
2
  import { P as Provider, a as ReasoningConfig, r as Capabilities, c as Request } from '../tool-LVSLiE1s.js';
3
- import { e as BrainDecision, h as BrainDecisionRequest, B as BrainArbiter } from '../events-BOv8h6I1.js';
4
- export { E as EternalAutonomyEngine, h as EternalAutonomyOptions, i as EternalEngineState, I as IterationStage, P as ParallelEngineState, j as ParallelEternalEngine, k as ParallelEternalOptions, l as ParallelIterationStage } from '../parallel-eternal-engine-BXECuOVG.js';
3
+ import { e as BrainDecision, h as BrainDecisionRequest, B as BrainArbiter } from '../events-KmxSmvho.js';
4
+ export { E as EternalAutonomyEngine, h as EternalAutonomyOptions, i as EternalEngineState, I as IterationStage, P as ParallelEngineState, j as ParallelEternalEngine, k as ParallelEternalOptions, l as ParallelIterationStage } from '../parallel-eternal-engine-iY2uxocj.js';
5
5
  import { h as ModelRuntimeConfig } from '../config-DkCbA3Bw.js';
6
6
  import '../pipeline-ixSFe5jg.js';
7
7
  import '../mailbox-types-BGZWrYTJ.js';
8
8
  import '../session-event-bridge-Bkp7ORfc.js';
9
9
  import '../compactor-BxddyCFu.js';
10
- import '../agent-subagent-runner-DCczSoQj.js';
11
- import '../index-Dd-PJJ8A.js';
10
+ import '../agent-subagent-runner-BsuWhB28.js';
11
+ import '../index-BhCteHAF.js';
12
12
  import '../logger-B63L5bTg.js';
13
13
  import '../permission-B6YAtNFz.js';
14
14
  import '../observability-D-HZN_mF.js';
@@ -18,8 +18,8 @@ import '../selector-Bx2HgUkU.js';
18
18
  import '../skill-DHniprNl.js';
19
19
  import '../wstack-paths-C3K40Qst.js';
20
20
  import '../prompt-DLd35n4Q.js';
21
- import '../goal-store-BEmDmSKF.js';
22
- import '../multi-agent-coordinator-BRqtpn-a.js';
21
+ import '../goal-store-8owBXJT2.js';
22
+ import '../multi-agent-coordinator-CrjeTB9i.js';
23
23
  import 'node:events';
24
24
 
25
25
  /**
@@ -1,4 +1,4 @@
1
- export { j as AfterIterationHook, k as AfterRunHook, l as AfterToolExecutionHook, i as AgentExtension, B as BeforeIterationHook, m as BeforeRunHook, n as BeforeToolExecutionHook, E as ExtensionRegistry, O as OnErrorHook, v as ProviderRunnerFn, t as ProviderRunnerWrapper } from '../index-Dd-PJJ8A.js';
1
+ export { j as AfterIterationHook, k as AfterRunHook, l as AfterToolExecutionHook, i as AgentExtension, B as BeforeIterationHook, m as BeforeRunHook, n as BeforeToolExecutionHook, E as ExtensionRegistry, O as OnErrorHook, v as ProviderRunnerFn, t as ProviderRunnerWrapper } from '../index-BhCteHAF.js';
2
2
  import '../tool-LVSLiE1s.js';
3
3
  import '../logger-B63L5bTg.js';
4
4
  import '../pipeline-ixSFe5jg.js';
@@ -6,5 +6,5 @@ import '../mailbox-types-BGZWrYTJ.js';
6
6
  import '../config-DkCbA3Bw.js';
7
7
  import '../permission-B6YAtNFz.js';
8
8
  import '../observability-D-HZN_mF.js';
9
- import '../events-BOv8h6I1.js';
9
+ import '../events-KmxSmvho.js';
10
10
  import '../secret-scrubber-3MHDDAtm.js';
@@ -1,5 +1,5 @@
1
1
  import { M as MailboxMessage, a as MailboxAgentStatus, b as Mailbox, c as MailboxSendInput, d as MailboxQuery, e as MailboxAckInput, f as MailboxAckBatchInput, A as AgentRegistrationInput, g as AgentHeartbeatInput, C as ClientRegistrationInput, h as ClientHeartbeatInput, i as ClientStatus, P as PurgeOptions, j as PurgeResult } from './mailbox-types-BGZWrYTJ.js';
2
- import { E as EventBus } from './events-BOv8h6I1.js';
2
+ import { E as EventBus } from './events-KmxSmvho.js';
3
3
 
4
4
  declare const HQ_PROTOCOL_VERSION: 1;
5
5
  type HqProtocolVersion = typeof HQ_PROTOCOL_VERSION;
@@ -789,6 +789,17 @@ declare class GlobalMailbox implements Mailbox {
789
789
  private _messageCacheMtime;
790
790
  /** Size of the file when `_messageCache` was populated (extra guard). */
791
791
  private _messageCacheSize;
792
+ /**
793
+ * Serializes reads of the message file so overlapping concurrent
794
+ * `_readMessagesCached()` calls don't both enter the incremental "file
795
+ * only grew" branch against the same stale `_messageCacheSize` and
796
+ * each push the same tail bytes onto the cache (duplicating every
797
+ * appended message). The chain runs each read to completion before
798
+ * the next starts, in issue order — readers are read-only and never
799
+ * conflict with each other on content, only on the cache mutation
800
+ * that follows the read. Pattern mirrors `DefaultMemoryStore.runSerialized`.
801
+ */
802
+ private _readChain;
792
803
  /**
793
804
  * @param projectDir — `~/.wrongstack/projects/<slug>/`
794
805
  * @param events — optional EventBus for real-time TUI/WebUI notifications
@@ -843,10 +854,28 @@ declare class GlobalMailbox implements Mailbox {
843
854
  * from writers that just took the file lock — the read reflects the
844
855
  * authoritative post-lock state and should be served to subsequent
845
856
  * queries without re-reading.
857
+ *
858
+ * The mtime/size are captured from the stat at read time so the cache
859
+ * trackers match exactly what was parsed. Writers that subsequently
860
+ * rewrite the file MUST re-stat after the write and re-promote with the
861
+ * post-write values (see ackMany / softDelete / restore / purgeStale /
862
+ * clearAll) — otherwise a concurrent reader misclassifies the rewrite
863
+ * as a "file only grew" append and corrupts the cache.
846
864
  */
847
865
  private _readMessagesFresh;
848
866
  /**
849
- * Read messages, consulting the mtime-bounded in-memory cache first.
867
+ * Stat the message file, returning its mtimeMs and size. Returns
868
+ * `-1/-1` when the file does not yet exist (ENOENT) so callers can
869
+ * still promote a cache snapshot — the next read will re-stat and
870
+ * fall through to a full re-read. Call from inside the file lock so
871
+ * the result reflects the post-write on-disk state, not a later
872
+ * intermediate state from another process.
873
+ */
874
+ private _statMessageFile;
875
+ /**
876
+ * Read messages, consulting the mtime-bounded in-memory cache first,
877
+ * serialized so concurrent callers don't both mutate the cache.
878
+ *
850
879
  * The mailbox file is shared across processes; every `send`/`ack`/
851
880
  * `clearAll`/`purgeStale` takes the file lock, so writes are serialized
852
881
  * and a changed mtimeMs is a definitive freshness signal. When the
@@ -857,19 +886,53 @@ declare class GlobalMailbox implements Mailbox {
857
886
  * When the file only grew (new messages appended by another process),
858
887
  * we read and parse just the tail bytes instead of the entire file.
859
888
  * This avoids re-parsing the full 10K-message history on every check.
889
+ *
890
+ * SERIALIZATION: the actual work is chained onto `_readChain` so two
891
+ * overlapping calls can't both pass the `st.size > _messageCacheSize`
892
+ * incremental check against the same stale tracker and each push the
893
+ * same tail bytes onto the cache (duplicating every appended message).
894
+ * Readers don't conflict on file content — only on the cache mutation
895
+ * that follows the read — so we run them one at a time in issue order.
896
+ * Errors in the chain are swallowed so a failed read never poisons
897
+ * subsequent reads; each caller observes and re-throws its own error.
860
898
  */
861
899
  private _readMessagesCached;
862
900
  /**
863
- * Replace the in-memory cache. Caller is responsible for guaranteeing
864
- * that `messages` reflects the current on-disk state (e.g. they just
865
- * read or wrote it under the file lock).
901
+ * The un-serialized body of {@link _readMessagesCached}. Reads the
902
+ * message file with the mtime-bounded cache + incremental-tail
903
+ * optimization. Must only be called from `_readMessagesCached` so the
904
+ * cache mutations here don't race a sibling read.
905
+ */
906
+ private _readMessagesCachedWork;
907
+ /**
908
+ * Replace the in-memory cache, setting the mtime/size trackers
909
+ * synchronously in the same step. Callers MUST pass the stat of the
910
+ * on-disk file state that produced `messages`.
911
+ *
912
+ * Why both are required (not fire-and-forget): `_readMessagesCached()`
913
+ * validates the cache against a fresh stat using these two trackers.
914
+ * If the cache array is updated but the trackers lag behind (e.g. via
915
+ * a deferred `stat().then(...)`), a concurrent reader landing in that
916
+ * window sees a mismatched mtime and falls into the incremental
917
+ * "file only grew" branch — parsing rewritten bytes as an appended
918
+ * tail and corrupting the cache with duplicates/garbage. So both
919
+ * values are captured under the file lock and applied synchronously
920
+ * here, matching the fix already shipped in DefaultMailbox.
866
921
  */
867
922
  private _setMessageCache;
868
923
  /**
869
924
  * Append a single just-sent message to the in-memory cache without
870
925
  * re-reading the file. The caller must hold the file lock (or have
871
- * just released it after a successful append) so the cache stays
872
- * consistent with on-disk state.
926
+ * just released it after a successful append) and MUST pass the
927
+ * post-append stat so the mtime/size trackers advance in lock-step
928
+ * with the pushed content.
929
+ *
930
+ * Why the stat is required here: without it, `_messageCacheSize`
931
+ * stays at the pre-append value. A concurrent `_readMessagesCached()`
932
+ * then sees `st.size > _messageCacheSize` (the file did grow), takes
933
+ * the incremental branch, and re-reads the just-appended tail —
934
+ * pushing the same message onto the cache a second time. Setting the
935
+ * trackers here closes that window for the local-process append path.
873
936
  */
874
937
  private _pushToCache;
875
938
  private _ensureRegistry;
@@ -1,4 +1,4 @@
1
- import { E as EventBus } from './events-BOv8h6I1.js';
1
+ import { E as EventBus } from './events-KmxSmvho.js';
2
2
 
3
3
  /**
4
4
  * Long-running autonomous mission. A goal survives across sessions and
@@ -1,6 +1,6 @@
1
- import { $ as HqRedactionPolicy, t as HqEventEnvelope, W as HqPublisher, p as HqClientIdentity, ac as HqSocketFactory, i as HqClientCapability, X as HqPublisherCommandHandler, G as GlobalMailbox, ab as HqSnapshot, _ as HqQueuedCommand, c as HqAlertMessage, aj as HqTranscriptEntry } from '../global-mailbox-MDDFhLYh.js';
2
- export { D as DEFAULT_HQ_REDACTION_POLICY, H as HQ_PROTOCOL_VERSION, a as HqAgentMessagePayload, b as HqAgentStatusPayload, d as HqBrainEventKind, e as HqBrainEventPayload, f as HqBrowserEventMessage, g as HqBrowserMessage, h as HqBrowserSnapshotMessage, j as HqClientCommandAckMessage, k as HqClientCommandPollMessage, l as HqClientEventMessage, m as HqClientHeartbeatPayload, n as HqClientHelloMessage, o as HqClientHelloPayload, q as HqClientKind, r as HqClientMessage, s as HqClientRecord, u as HqEventPayloadResult, v as HqEventType, w as HqFleetEventPayload, x as HqFleetSnapshotPayload, y as HqFleetSummary, z as HqGitSnapshotPayload, A as HqMachineRecord, B as HqMailboxAgentStatus, C as HqMailboxAgentSummary, E as HqMailboxEventAction, F as HqMailboxEventPayload, I as HqMailboxMappingOptions, J as HqMailboxMessageSummary, K as HqMailboxMessageType, L as HqMailboxPriority, M as HqMailboxSnapshotOptions, N as HqMailboxSnapshotPayload, O as HqMailboxSummary, P as HqParseResult, Q as HqPathPolicy, R as HqProjectIdentity, S as HqProjectRecord, T as HqProjectStatus, U as HqProtocolVersion, V as HqPublishEventOptions, Y as HqPublisherCommandResult, Z as HqPublisherOptions, a0 as HqServerCommandBatchMessage, a1 as HqServerMessage, a2 as HqSessionAgentLiveStatus, a3 as HqSessionAgentSummary, a4 as HqSessionEndedPayload, a5 as HqSessionLiveStatus, a6 as HqSessionSnapshotPayload, a7 as HqSessionStartedPayload, a8 as HqSessionStatus, a9 as HqSessionStatusPayload, aa as HqSessionSummary, ad as HqSocketLike, ae as HqSubagentSummary, af as HqToolArgsPolicy, ag as HqToolCompletedPayload, ah as HqToolStartedPayload, ai as HqTranscriptAppendPayload, ak as HqTranscriptRole, al as HqUsagePayload, am as HqWelcomePayload, an as HqWorklistCounts, ao as HqWorklistSnapshotPayload, ap as HqWorkspaceKind, aq as HqWorktreeEventKind, ar as HqWorktreeEventPayload, as as createHqEventEnvelope, at as createMailboxEventPayload, au as createMailboxSnapshotPayload, av as createMailboxSnapshotPayloadFromMailbox, aw as mapMailboxAgentToHqSummary, ax as mapMailboxMessageToHqSummary, ay as parseHqEventPayload, az as parseHqFrame } from '../global-mailbox-MDDFhLYh.js';
3
- import { E as EventBus, T as TrackedAgentSnapshot } from '../events-BOv8h6I1.js';
1
+ import { $ as HqRedactionPolicy, t as HqEventEnvelope, W as HqPublisher, p as HqClientIdentity, ac as HqSocketFactory, i as HqClientCapability, X as HqPublisherCommandHandler, G as GlobalMailbox, ab as HqSnapshot, _ as HqQueuedCommand, c as HqAlertMessage, aj as HqTranscriptEntry } from '../global-mailbox-rWVt8YlO.js';
2
+ export { D as DEFAULT_HQ_REDACTION_POLICY, H as HQ_PROTOCOL_VERSION, a as HqAgentMessagePayload, b as HqAgentStatusPayload, d as HqBrainEventKind, e as HqBrainEventPayload, f as HqBrowserEventMessage, g as HqBrowserMessage, h as HqBrowserSnapshotMessage, j as HqClientCommandAckMessage, k as HqClientCommandPollMessage, l as HqClientEventMessage, m as HqClientHeartbeatPayload, n as HqClientHelloMessage, o as HqClientHelloPayload, q as HqClientKind, r as HqClientMessage, s as HqClientRecord, u as HqEventPayloadResult, v as HqEventType, w as HqFleetEventPayload, x as HqFleetSnapshotPayload, y as HqFleetSummary, z as HqGitSnapshotPayload, A as HqMachineRecord, B as HqMailboxAgentStatus, C as HqMailboxAgentSummary, E as HqMailboxEventAction, F as HqMailboxEventPayload, I as HqMailboxMappingOptions, J as HqMailboxMessageSummary, K as HqMailboxMessageType, L as HqMailboxPriority, M as HqMailboxSnapshotOptions, N as HqMailboxSnapshotPayload, O as HqMailboxSummary, P as HqParseResult, Q as HqPathPolicy, R as HqProjectIdentity, S as HqProjectRecord, T as HqProjectStatus, U as HqProtocolVersion, V as HqPublishEventOptions, Y as HqPublisherCommandResult, Z as HqPublisherOptions, a0 as HqServerCommandBatchMessage, a1 as HqServerMessage, a2 as HqSessionAgentLiveStatus, a3 as HqSessionAgentSummary, a4 as HqSessionEndedPayload, a5 as HqSessionLiveStatus, a6 as HqSessionSnapshotPayload, a7 as HqSessionStartedPayload, a8 as HqSessionStatus, a9 as HqSessionStatusPayload, aa as HqSessionSummary, ad as HqSocketLike, ae as HqSubagentSummary, af as HqToolArgsPolicy, ag as HqToolCompletedPayload, ah as HqToolStartedPayload, ai as HqTranscriptAppendPayload, ak as HqTranscriptRole, al as HqUsagePayload, am as HqWelcomePayload, an as HqWorklistCounts, ao as HqWorklistSnapshotPayload, ap as HqWorkspaceKind, aq as HqWorktreeEventKind, ar as HqWorktreeEventPayload, as as createHqEventEnvelope, at as createMailboxEventPayload, au as createMailboxSnapshotPayload, av as createMailboxSnapshotPayloadFromMailbox, aw as mapMailboxAgentToHqSummary, ax as mapMailboxMessageToHqSummary, ay as parseHqEventPayload, az as parseHqFrame } from '../global-mailbox-rWVt8YlO.js';
3
+ import { E as EventBus, T as TrackedAgentSnapshot } from '../events-KmxSmvho.js';
4
4
  import { K as HqClientConfig } from '../config-DkCbA3Bw.js';
5
5
  import '../mailbox-types-BGZWrYTJ.js';
6
6
  import '../tool-LVSLiE1s.js';
package/dist/hq/index.js CHANGED
@@ -1506,6 +1506,17 @@ var GlobalMailbox = class {
1506
1506
  _messageCacheMtime = -1;
1507
1507
  /** Size of the file when `_messageCache` was populated (extra guard). */
1508
1508
  _messageCacheSize = -1;
1509
+ /**
1510
+ * Serializes reads of the message file so overlapping concurrent
1511
+ * `_readMessagesCached()` calls don't both enter the incremental "file
1512
+ * only grew" branch against the same stale `_messageCacheSize` and
1513
+ * each push the same tail bytes onto the cache (duplicating every
1514
+ * appended message). The chain runs each read to completion before
1515
+ * the next starts, in issue order — readers are read-only and never
1516
+ * conflict with each other on content, only on the cache mutation
1517
+ * that follows the read. Pattern mirrors `DefaultMemoryStore.runSerialized`.
1518
+ */
1519
+ _readChain = Promise.resolve([]);
1509
1520
  /**
1510
1521
  * @param projectDir — `~/.wrongstack/projects/<slug>/`
1511
1522
  * @param events — optional EventBus for real-time TUI/WebUI notifications
@@ -1559,7 +1570,8 @@ var GlobalMailbox = class {
1559
1570
  await fsp.mkdir(path2.dirname(this.messagePath), { recursive: true });
1560
1571
  await withFileLock(this.messagePath, async () => {
1561
1572
  await fsp.appendFile(this.messagePath, line, "utf8");
1562
- this._pushToCache(msg);
1573
+ const { mtimeMs, size } = await this._statMessageFile();
1574
+ this._pushToCache(msg, mtimeMs, size);
1563
1575
  });
1564
1576
  this.publishHqMailboxEvent({
1565
1577
  mailboxId: this.hqMailboxId,
@@ -1603,7 +1615,6 @@ var GlobalMailbox = class {
1603
1615
  for (const a of input.acks) {
1604
1616
  byId.set(a.messageId, a);
1605
1617
  }
1606
- let cacheSnapshot = null;
1607
1618
  await withFileLock(this.messagePath, async () => {
1608
1619
  const all = await this._readMessagesFresh();
1609
1620
  const now = (/* @__PURE__ */ new Date()).toISOString();
@@ -1631,9 +1642,9 @@ var GlobalMailbox = class {
1631
1642
  const serialized = all.map((m) => JSON.stringify(m)).join(LINE_SEPARATOR) + LINE_SEPARATOR;
1632
1643
  await fsp.writeFile(this.messagePath, serialized, "utf8");
1633
1644
  }
1634
- cacheSnapshot = all;
1645
+ const { mtimeMs, size } = await this._statMessageFile();
1646
+ this._setMessageCache(all, mtimeMs, size);
1635
1647
  });
1636
- if (cacheSnapshot) this._setMessageCache(cacheSnapshot);
1637
1648
  for (const message of updated) {
1638
1649
  this.publishHqMailboxEvent({
1639
1650
  mailboxId: this.hqMailboxId,
@@ -1657,7 +1668,6 @@ var GlobalMailbox = class {
1657
1668
  }
1658
1669
  async softDelete(mailId, by) {
1659
1670
  let updated = null;
1660
- let cacheSnapshot = null;
1661
1671
  await withFileLock(this.messagePath, async () => {
1662
1672
  const all = await this._readMessagesFresh();
1663
1673
  const now = (/* @__PURE__ */ new Date()).toISOString();
@@ -1673,9 +1683,9 @@ var GlobalMailbox = class {
1673
1683
  }
1674
1684
  const serialized = all.map((m) => JSON.stringify(m)).join(LINE_SEPARATOR) + LINE_SEPARATOR;
1675
1685
  await fsp.writeFile(this.messagePath, serialized, "utf8");
1676
- cacheSnapshot = all;
1686
+ const { mtimeMs, size } = await this._statMessageFile();
1687
+ this._setMessageCache(all, mtimeMs, size);
1677
1688
  });
1678
- if (cacheSnapshot) this._setMessageCache(cacheSnapshot);
1679
1689
  if (updated !== null) {
1680
1690
  this.publishHqMailboxEvent({
1681
1691
  mailboxId: this.hqMailboxId,
@@ -1688,7 +1698,6 @@ var GlobalMailbox = class {
1688
1698
  }
1689
1699
  async restore(mailId) {
1690
1700
  let updated = null;
1691
- let cacheSnapshot = null;
1692
1701
  await withFileLock(this.messagePath, async () => {
1693
1702
  const all = await this._readMessagesFresh();
1694
1703
  for (const m of all) {
@@ -1699,9 +1708,9 @@ var GlobalMailbox = class {
1699
1708
  }
1700
1709
  const serialized = all.map((m) => JSON.stringify(m)).join(LINE_SEPARATOR) + LINE_SEPARATOR;
1701
1710
  await fsp.writeFile(this.messagePath, serialized, "utf8");
1702
- cacheSnapshot = all;
1711
+ const { mtimeMs, size } = await this._statMessageFile();
1712
+ this._setMessageCache(all, mtimeMs, size);
1703
1713
  });
1704
- if (cacheSnapshot) this._setMessageCache(cacheSnapshot);
1705
1714
  if (updated !== null) {
1706
1715
  this.publishHqMailboxEvent({
1707
1716
  mailboxId: this.hqMailboxId,
@@ -1946,8 +1955,9 @@ var GlobalMailbox = class {
1946
1955
  async clearAll() {
1947
1956
  await withFileLock(this.messagePath, async () => {
1948
1957
  await fsp.writeFile(this.messagePath, "", "utf8");
1958
+ const { mtimeMs, size } = await this._statMessageFile();
1959
+ this._setMessageCache([], mtimeMs, size);
1949
1960
  });
1950
- this._setMessageCache([]);
1951
1961
  }
1952
1962
  async purgeStale(opts) {
1953
1963
  const COMPLETED_MAX_AGE_MS = opts?.completedMaxAgeMs ?? 864e5;
@@ -1979,7 +1989,8 @@ var GlobalMailbox = class {
1979
1989
  const content = kept.map((m) => JSON.stringify(m)).join(LINE_SEPARATOR) + LINE_SEPARATOR;
1980
1990
  await fsp.writeFile(this.messagePath, content, "utf8");
1981
1991
  }
1982
- this._setMessageCache(kept);
1992
+ const { mtimeMs, size } = await this._statMessageFile();
1993
+ this._setMessageCache(kept, mtimeMs, size);
1983
1994
  });
1984
1995
  return {
1985
1996
  completedPurged,
@@ -2066,14 +2077,43 @@ var GlobalMailbox = class {
2066
2077
  * from writers that just took the file lock — the read reflects the
2067
2078
  * authoritative post-lock state and should be served to subsequent
2068
2079
  * queries without re-reading.
2080
+ *
2081
+ * The mtime/size are captured from the stat at read time so the cache
2082
+ * trackers match exactly what was parsed. Writers that subsequently
2083
+ * rewrite the file MUST re-stat after the write and re-promote with the
2084
+ * post-write values (see ackMany / softDelete / restore / purgeStale /
2085
+ * clearAll) — otherwise a concurrent reader misclassifies the rewrite
2086
+ * as a "file only grew" append and corrupts the cache.
2069
2087
  */
2070
2088
  async _readMessagesFresh() {
2071
2089
  const all = await this._readMessages();
2072
- this._setMessageCache(all);
2090
+ const { mtimeMs, size } = await this._statMessageFile();
2091
+ this._setMessageCache(all, mtimeMs, size);
2073
2092
  return all;
2074
2093
  }
2075
2094
  /**
2076
- * Read messages, consulting the mtime-bounded in-memory cache first.
2095
+ * Stat the message file, returning its mtimeMs and size. Returns
2096
+ * `-1/-1` when the file does not yet exist (ENOENT) so callers can
2097
+ * still promote a cache snapshot — the next read will re-stat and
2098
+ * fall through to a full re-read. Call from inside the file lock so
2099
+ * the result reflects the post-write on-disk state, not a later
2100
+ * intermediate state from another process.
2101
+ */
2102
+ async _statMessageFile() {
2103
+ try {
2104
+ const st = await fsp.stat(this.messagePath);
2105
+ return { mtimeMs: st.mtimeMs, size: st.size };
2106
+ } catch (err) {
2107
+ if (err.code === "ENOENT") {
2108
+ return { mtimeMs: -1, size: -1 };
2109
+ }
2110
+ throw err;
2111
+ }
2112
+ }
2113
+ /**
2114
+ * Read messages, consulting the mtime-bounded in-memory cache first,
2115
+ * serialized so concurrent callers don't both mutate the cache.
2116
+ *
2077
2117
  * The mailbox file is shared across processes; every `send`/`ack`/
2078
2118
  * `clearAll`/`purgeStale` takes the file lock, so writes are serialized
2079
2119
  * and a changed mtimeMs is a definitive freshness signal. When the
@@ -2084,8 +2124,28 @@ var GlobalMailbox = class {
2084
2124
  * When the file only grew (new messages appended by another process),
2085
2125
  * we read and parse just the tail bytes instead of the entire file.
2086
2126
  * This avoids re-parsing the full 10K-message history on every check.
2127
+ *
2128
+ * SERIALIZATION: the actual work is chained onto `_readChain` so two
2129
+ * overlapping calls can't both pass the `st.size > _messageCacheSize`
2130
+ * incremental check against the same stale tracker and each push the
2131
+ * same tail bytes onto the cache (duplicating every appended message).
2132
+ * Readers don't conflict on file content — only on the cache mutation
2133
+ * that follows the read — so we run them one at a time in issue order.
2134
+ * Errors in the chain are swallowed so a failed read never poisons
2135
+ * subsequent reads; each caller observes and re-throws its own error.
2136
+ */
2137
+ _readMessagesCached() {
2138
+ const run = this._readChain.catch(() => void 0).then(() => this._readMessagesCachedWork());
2139
+ this._readChain = run.catch(() => []);
2140
+ return run;
2141
+ }
2142
+ /**
2143
+ * The un-serialized body of {@link _readMessagesCached}. Reads the
2144
+ * message file with the mtime-bounded cache + incremental-tail
2145
+ * optimization. Must only be called from `_readMessagesCached` so the
2146
+ * cache mutations here don't race a sibling read.
2087
2147
  */
2088
- async _readMessagesCached() {
2148
+ async _readMessagesCachedWork() {
2089
2149
  try {
2090
2150
  const st = await fsp.stat(this.messagePath);
2091
2151
  if (this._messageCache !== null && this._messageCacheMtime === st.mtimeMs && this._messageCacheSize === st.size) {
@@ -2114,9 +2174,19 @@ var GlobalMailbox = class {
2114
2174
  }
2115
2175
  }
2116
2176
  /**
2117
- * Replace the in-memory cache. Caller is responsible for guaranteeing
2118
- * that `messages` reflects the current on-disk state (e.g. they just
2119
- * read or wrote it under the file lock).
2177
+ * Replace the in-memory cache, setting the mtime/size trackers
2178
+ * synchronously in the same step. Callers MUST pass the stat of the
2179
+ * on-disk file state that produced `messages`.
2180
+ *
2181
+ * Why both are required (not fire-and-forget): `_readMessagesCached()`
2182
+ * validates the cache against a fresh stat using these two trackers.
2183
+ * If the cache array is updated but the trackers lag behind (e.g. via
2184
+ * a deferred `stat().then(...)`), a concurrent reader landing in that
2185
+ * window sees a mismatched mtime and falls into the incremental
2186
+ * "file only grew" branch — parsing rewritten bytes as an appended
2187
+ * tail and corrupting the cache with duplicates/garbage. So both
2188
+ * values are captured under the file lock and applied synchronously
2189
+ * here, matching the fix already shipped in DefaultMailbox.
2120
2190
  */
2121
2191
  _setMessageCache(messages, mtime, size) {
2122
2192
  if (messages.length > MESSAGE_CACHE_MAX_ENTRIES) {
@@ -2126,25 +2196,27 @@ var GlobalMailbox = class {
2126
2196
  return;
2127
2197
  }
2128
2198
  this._messageCache = messages;
2129
- if (mtime !== void 0 && size !== void 0) {
2130
- this._messageCacheMtime = mtime;
2131
- this._messageCacheSize = size;
2132
- } else {
2133
- void fsp.stat(this.messagePath).then((st) => {
2134
- this._messageCacheMtime = st.mtimeMs;
2135
- this._messageCacheSize = st.size;
2136
- }).catch(() => {
2137
- });
2138
- }
2199
+ this._messageCacheMtime = mtime;
2200
+ this._messageCacheSize = size;
2139
2201
  }
2140
2202
  /**
2141
2203
  * Append a single just-sent message to the in-memory cache without
2142
2204
  * re-reading the file. The caller must hold the file lock (or have
2143
- * just released it after a successful append) so the cache stays
2144
- * consistent with on-disk state.
2205
+ * just released it after a successful append) and MUST pass the
2206
+ * post-append stat so the mtime/size trackers advance in lock-step
2207
+ * with the pushed content.
2208
+ *
2209
+ * Why the stat is required here: without it, `_messageCacheSize`
2210
+ * stays at the pre-append value. A concurrent `_readMessagesCached()`
2211
+ * then sees `st.size > _messageCacheSize` (the file did grow), takes
2212
+ * the incremental branch, and re-reads the just-appended tail —
2213
+ * pushing the same message onto the cache a second time. Setting the
2214
+ * trackers here closes that window for the local-process append path.
2145
2215
  */
2146
- _pushToCache(msg) {
2147
- if (this._messageCache === null) return;
2216
+ _pushToCache(msg, mtime, size) {
2217
+ if (this._messageCache === null) {
2218
+ return;
2219
+ }
2148
2220
  if (this._messageCache.length >= MESSAGE_CACHE_MAX_ENTRIES) {
2149
2221
  this._messageCache = null;
2150
2222
  this._messageCacheMtime = -1;
@@ -2152,6 +2224,8 @@ var GlobalMailbox = class {
2152
2224
  return;
2153
2225
  }
2154
2226
  this._messageCache.push(msg);
2227
+ this._messageCacheMtime = mtime;
2228
+ this._messageCacheSize = size;
2155
2229
  }
2156
2230
  async _ensureRegistry() {
2157
2231
  await fsp.mkdir(path2.dirname(this.registryPath), { recursive: true });