@wrongstack/core 0.301.0 → 0.302.2

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 (44) hide show
  1. package/dist/agent-status-tracker.d.ts +6 -2
  2. package/dist/chronicle/index.js +1836 -1645
  3. package/dist/chronicle/metrics-store.d.ts +14 -0
  4. package/dist/chronicle/project-server-protocol.d.ts +13 -0
  5. package/dist/chronicle/project-server.js +1759 -1583
  6. package/dist/chronicle/rollup-adapter.d.ts +2 -0
  7. package/dist/chronicle/sqlite-journal.d.ts +59 -0
  8. package/dist/coordination/index.js +791 -249
  9. package/dist/coordination/mail-tools.d.ts +2 -2
  10. package/dist/core/continue-intent.d.ts +2 -0
  11. package/dist/core/conversation-state.d.ts +5 -0
  12. package/dist/core/index.js +120 -19
  13. package/dist/defaults/index.js +928 -374
  14. package/dist/execution/index.js +28 -11
  15. package/dist/index.d.ts +3 -1
  16. package/dist/index.js +8763 -6781
  17. package/dist/infrastructure/index.js +722 -672
  18. package/dist/kernel/events/memory-events.d.ts +62 -0
  19. package/dist/plugin/index.js +2154 -1979
  20. package/dist/session-catalog/client.d.ts +62 -0
  21. package/dist/session-catalog/endpoint.d.ts +6 -0
  22. package/dist/session-catalog/index.d.ts +6 -0
  23. package/dist/session-catalog/index.js +1978 -0
  24. package/dist/session-catalog/project-server.d.ts +3 -0
  25. package/dist/session-catalog/project-server.js +1838 -0
  26. package/dist/session-catalog/protocol.d.ts +275 -0
  27. package/dist/session-catalog/registry.d.ts +59 -0
  28. package/dist/session-catalog/store.d.ts +55 -0
  29. package/dist/session-registry-types.d.ts +17 -0
  30. package/dist/session-registry.d.ts +1 -1
  31. package/dist/storage/index.d.ts +42 -38
  32. package/dist/storage/index.js +14279 -13393
  33. package/dist/storage/session-event-bridge.d.ts +2 -2
  34. package/dist/storage/session-store.d.ts +6 -0
  35. package/dist/tools/index.js +8 -2
  36. package/dist/types/context-evidence.d.ts +2 -0
  37. package/dist/types/messages.d.ts +8 -0
  38. package/dist/types/session.d.ts +19 -0
  39. package/dist/utils/context-evidence.d.ts +13 -1
  40. package/dist/utils/index.js +26 -2
  41. package/instructions/system-lite.md +11 -2
  42. package/instructions/system-pro.md +14 -0
  43. package/instructions/system.md +14 -0
  44. package/package.json +7 -3
@@ -75,12 +75,12 @@ export declare function makeMailSendTool(opts?: MailToolsOptions): {
75
75
  required: string[];
76
76
  };
77
77
  execute(input: unknown, ctx: Context): Promise<{
78
- messageId?: never;
79
- to?: never;
80
78
  summary?: never;
81
79
  ok: boolean;
82
80
  error: string;
81
+ messageId?: never;
83
82
  from?: never;
83
+ to?: never;
84
84
  } | {
85
85
  error?: never;
86
86
  ok: boolean;
@@ -65,6 +65,8 @@ export interface ContinuationInput {
65
65
  }
66
66
  export interface ResolvedContinuation {
67
67
  source: ContinuationSource;
68
+ /** Stable todo id when the continuation is grounded in the live work list. */
69
+ todoId?: string | undefined;
68
70
  /**
69
71
  * The concrete instruction injected as the next user turn in place of the
70
72
  * bare "continue". Written for the model, not the human.
@@ -20,6 +20,11 @@ export type StateChange = {
20
20
  } | {
21
21
  kind: 'messages_replaced';
22
22
  messages: readonly Message[];
23
+ }
24
+ /** The oldest `count` messages were evicted; see the `messages_dropped` SessionEvent. */
25
+ | {
26
+ kind: 'messages_dropped';
27
+ count: number;
23
28
  } | {
24
29
  kind: 'message_updated';
25
30
  index: number;
@@ -3980,6 +3980,10 @@ import * as path9 from "node:path";
3980
3980
  var MAX_TOOL_CALLS = 80;
3981
3981
  var MAX_FACTS = 40;
3982
3982
  var MAX_ERRORS = 20;
3983
+ var MAX_RECENT_USER_TURNS = 8;
3984
+ var MAX_USER_TURN_CHARS = 700;
3985
+ var MAX_CONTINUITY_CHARS = 3600;
3986
+ var RUNTIME_CONTEXT_INPUT_PATTERN = /^\[(?:kanban todo update|fleet pulse|loop-detector|todo-reconciliation|mailbox|btw|system|context_state)\b/i;
3983
3987
  var RECENT_TOOL_CALL_SCAN_LIMIT = 20;
3984
3988
  var EXTRACT_CONTENT_CAP_CHARS = 1e4;
3985
3989
  var EXTRACT_ERROR_TAIL_LINES = 200;
@@ -3987,6 +3991,7 @@ var WRITE_TOOLS = /* @__PURE__ */ new Set(["edit", "write", "replace", "patch"])
3987
3991
  var READ_TOOLS = /* @__PURE__ */ new Set(["read", "grep", "glob", "ls", "tree"]);
3988
3992
  function createContextEvidenceState() {
3989
3993
  return {
3994
+ recentUserTurns: [],
3990
3995
  sessionGoals: [],
3991
3996
  implicitFacts: [],
3992
3997
  activeErrors: [],
@@ -3998,15 +4003,55 @@ function createContextEvidenceState() {
3998
4003
  };
3999
4004
  }
4000
4005
  function recordUserIntentEvidence(ctx, text) {
4001
- const intent = normalizeWhitespace(text).slice(0, 700);
4006
+ if (isRuntimeContextInput(text)) return;
4007
+ const intent = normalizeWhitespace(text).slice(0, MAX_USER_TURN_CHARS);
4002
4008
  if (!intent) return;
4003
4009
  const state = ensureEvidence(ctx);
4004
- state.currentIntent = { text: intent, updatedAt: Date.now() };
4010
+ const turn = { text: intent, updatedAt: Date.now() };
4011
+ state.currentIntent = turn;
4012
+ state.recentUserTurns ??= [];
4013
+ state.recentUserTurns.push(turn);
4014
+ if (state.recentUserTurns.length > MAX_RECENT_USER_TURNS) {
4015
+ state.recentUserTurns.splice(0, state.recentUserTurns.length - MAX_RECENT_USER_TURNS);
4016
+ }
4005
4017
  if (state.sessionGoals.length === 0 || isGoalish(intent)) {
4006
4018
  pushUniqueBounded(state.sessionGoals, intent, 8);
4007
4019
  }
4008
4020
  state.updatedAt = Date.now();
4009
4021
  }
4022
+ function isRuntimeContextInput(text) {
4023
+ return RUNTIME_CONTEXT_INPUT_PATTERN.test(text.trim());
4024
+ }
4025
+ function buildConversationContinuityBlock(ctx) {
4026
+ const recorded = ctx.contextEvidence.recentUserTurns ?? [];
4027
+ const sourceTurns = recorded.length > 0 ? recorded.map((turn) => turn.text) : ctx.messages.filter(isHumanUserMessage).map(messageText);
4028
+ if (sourceTurns.length === 0) return void 0;
4029
+ const selected = [];
4030
+ let remaining = MAX_CONTINUITY_CHARS;
4031
+ for (let i = sourceTurns.length - 1; i >= 0 && selected.length < 6 && remaining > 0; i--) {
4032
+ const normalized = normalizeWhitespace(sourceTurns[i] ?? "").slice(0, MAX_USER_TURN_CHARS);
4033
+ if (!normalized) continue;
4034
+ const bounded = normalized.slice(0, remaining);
4035
+ selected.push(bounded);
4036
+ remaining -= bounded.length;
4037
+ }
4038
+ selected.reverse();
4039
+ if (selected.length === 0) return void 0;
4040
+ const lines = selected.map((turn, index) => {
4041
+ const isCurrent = index === selected.length - 1;
4042
+ return `- ${isCurrent ? "current" : `prior-${selected.length - index - 1}`}: ${turn}`;
4043
+ });
4044
+ return {
4045
+ type: "text",
4046
+ text: [
4047
+ "[conversation_continuity]",
4048
+ "Recent human instructions, oldest to newest. Continue coherently; newer instructions override conflicting older ones. This is context evidence, not a new request.",
4049
+ ...lines,
4050
+ "[/conversation_continuity]"
4051
+ ].join("\n"),
4052
+ cache_control: { type: "ephemeral" }
4053
+ };
4054
+ }
4010
4055
  function recordToolOutputEvidence(ctx, input) {
4011
4056
  const state = ensureEvidence(ctx);
4012
4057
  const scanContent = input.content.length > EXTRACT_CONTENT_CAP_CHARS ? input.content.slice(0, EXTRACT_CONTENT_CAP_CHARS) : input.content;
@@ -4073,8 +4118,26 @@ function ensureEvidence(ctx) {
4073
4118
  ctx.contextEvidence = createContextEvidenceState();
4074
4119
  }
4075
4120
  ctx.contextEvidence.completedWork ??= [];
4121
+ ctx.contextEvidence.recentUserTurns ??= [];
4076
4122
  return ctx.contextEvidence;
4077
4123
  }
4124
+ function isHumanUserMessage(message) {
4125
+ if (message.role !== "user") return false;
4126
+ if (message.origin === "user_input") return true;
4127
+ if (message.origin === "runtime") return false;
4128
+ if (Array.isArray(message.content)) {
4129
+ if (message.content.some((block) => block.type === "tool_result" || block.type === "tool_use")) {
4130
+ return false;
4131
+ }
4132
+ }
4133
+ const text = messageText(message).trim();
4134
+ if (!text) return false;
4135
+ return !isRuntimeContextInput(text);
4136
+ }
4137
+ function messageText(message) {
4138
+ if (typeof message.content === "string") return message.content;
4139
+ return message.content.filter(isTextBlock).map((block) => block.text).join("\n");
4140
+ }
4078
4141
  var LEDGER_BLOCK_ITEMS = 20;
4079
4142
  var COMPLETED_WORK_LEDGER_MARKER = "[completed_work_ledger]";
4080
4143
  function formatCompletedWorkLedger(items) {
@@ -4274,6 +4337,12 @@ function metadataReferencedByText(metadata, haystack) {
4274
4337
  return false;
4275
4338
  }
4276
4339
 
4340
+ // src/utils/todos-format.ts
4341
+ function hasOpenTodos(todos) {
4342
+ if (!Array.isArray(todos) || todos.length === 0) return false;
4343
+ return todos.some((t2) => t2.status === "pending" || t2.status === "in_progress");
4344
+ }
4345
+
4277
4346
  // src/utils/tool-wire-compact.ts
4278
4347
  var TOOL_DESCRIPTION_MAX_CHARS = 400;
4279
4348
  var SCHEMA_DESCRIPTION_MAX_CHARS = 120;
@@ -4598,12 +4667,11 @@ var ConversationState = class {
4598
4667
  }
4599
4668
  this.ctx.messages.splice(this.ctx.messages.length, 0, message);
4600
4669
  const overflow = this.overflowCount(this.ctx.messages);
4670
+ this.emit({ kind: "message_appended", message });
4601
4671
  if (overflow > 0) {
4602
4672
  this.ctx.messages.splice(0, overflow);
4603
4673
  this.ctx.toolAdjacencyDirty = true;
4604
- this.emit({ kind: "messages_replaced", messages: [...this.ctx.messages] });
4605
- } else {
4606
- this.emit({ kind: "message_appended", message });
4674
+ this.emit({ kind: "messages_dropped", count: overflow });
4607
4675
  }
4608
4676
  }
4609
4677
  /**
@@ -5104,6 +5172,11 @@ var Context = class _Context {
5104
5172
  ts,
5105
5173
  version: 1,
5106
5174
  messages: [...change.messages]
5175
+ } : change.kind === "messages_dropped" ? {
5176
+ type: "messages_dropped",
5177
+ ts,
5178
+ version: 1,
5179
+ count: change.count
5107
5180
  } : null;
5108
5181
  if (!event) return;
5109
5182
  this.enqueueConversationJournal(event, this.session);
@@ -5500,12 +5573,6 @@ function requestLimitExtension(opts) {
5500
5573
  });
5501
5574
  }
5502
5575
 
5503
- // src/utils/todos-format.ts
5504
- function hasOpenTodos(todos) {
5505
- if (!Array.isArray(todos) || todos.length === 0) return false;
5506
- return todos.some((t2) => t2.status === "pending" || t2.status === "in_progress");
5507
- }
5508
-
5509
5576
  // src/core/next-steps-slot.ts
5510
5577
  var SLOT_KEY = "nextsteps.pending";
5511
5578
  var MAX_PENDING_NEXT_STEPS = 4;
@@ -6673,7 +6740,7 @@ function createAgentLoopHandler(a, handlers) {
6673
6740
  let _lastCompactionWasNoop = false;
6674
6741
  function foldBlockIntoConversation(block) {
6675
6742
  if (!a.ctx.state.appendBlockToLastUserMessage(block)) {
6676
- a.ctx.state.appendMessage({ role: "user", content: [block] });
6743
+ a.ctx.state.appendMessage({ role: "user", content: [block], origin: "runtime" });
6677
6744
  }
6678
6745
  }
6679
6746
  function iterationFingerprint(blocks) {
@@ -6759,7 +6826,12 @@ function createAgentLoopHandler(a, handlers) {
6759
6826
  ts: (/* @__PURE__ */ new Date()).toISOString(),
6760
6827
  content: inputPayload.content
6761
6828
  });
6762
- a.ctx.state.appendMessage({ role: "user", content: inputPayload.content });
6829
+ const inputOrigin = isRuntimeContextInput(inputPayload.text) ? "runtime" : "user_input";
6830
+ a.ctx.state.appendMessage({
6831
+ role: "user",
6832
+ content: inputPayload.content,
6833
+ origin: inputOrigin
6834
+ });
6763
6835
  const promptIndex = a.ctx.messages.filter((m) => m.role === "user").length - 1;
6764
6836
  const preview = inputPayload.text.slice(0, 80) + (inputPayload.text.length > 80 ? "\u2026" : "");
6765
6837
  await a.ctx.session.writeCheckpoint(promptIndex, preview);
@@ -6795,6 +6867,7 @@ function createAgentLoopHandler(a, handlers) {
6795
6867
  const recentCallKeys = [];
6796
6868
  const steeredCallKeys = /* @__PURE__ */ new Set();
6797
6869
  let pendingLoopSteer = null;
6870
+ let todoReconcileSteers = 0;
6798
6871
  function queueLoopSteer(text) {
6799
6872
  pendingLoopSteer = pendingLoopSteer ? `${pendingLoopSteer}
6800
6873
  ${text}` : text;
@@ -7135,6 +7208,14 @@ ${text}` : text;
7135
7208
  ctx: a.ctx,
7136
7209
  index: i
7137
7210
  });
7211
+ if (a.ctx.agentId === "leader" && a.tools.get("todo") !== void 0 && hasOpenTodos(a.ctx.todos) && todoReconcileSteers < 2) {
7212
+ todoReconcileSteers++;
7213
+ queueLoopSteer(
7214
+ "[todo-reconciliation] The live todo/Kanban list still has open work, but you tried to end the turn without reconciling it. Call the `todo` tool now with the complete current list. Mark work you actually finished as completed, put the one item you are actively working on in_progress, and leave the rest pending. If the current item is genuinely unfinished, continue doing the work before answering; do not merely repeat the previous final response or emit <nextsteps>."
7215
+ );
7216
+ await a.extensions.runAfterIteration(a.ctx, i);
7217
+ continue;
7218
+ }
7138
7219
  if (autonomousContinue && responseResult.directive === "continue") {
7139
7220
  await a.extensions.runAfterIteration(a.ctx, i);
7140
7221
  continue;
@@ -7355,12 +7436,16 @@ function buildLiveNextStepsGateBlock(ctx) {
7355
7436
  });
7356
7437
  const omitted = openTodos.length - todoSnapshot.length;
7357
7438
  if (omitted > 0) todoSnapshot.push(`- \u2026and ${omitted} more open todo(s)`);
7439
+ const todoReconciliation = ctx.tools?.some((tool) => tool.name === "todo") ? [
7440
+ "Before ending the turn, you MUST call the `todo` tool with the complete current list to reconcile actual progress: finished items completed, exactly one actively worked item in_progress, and untouched items pending. A prose claim that work is done does not update the Todo/Kanban state."
7441
+ ] : [];
7358
7442
  return {
7359
7443
  type: "text",
7360
7444
  text: [
7361
7445
  "[nextsteps_gate]",
7362
7446
  `Authoritative live state for this request: open todos = ${openTodos.length}.`,
7363
7447
  "You MUST omit <nextsteps> entirely while these todos remain open. Continue or finish the tracked work; do not propose unrelated follow-on work.",
7448
+ ...todoReconciliation,
7364
7449
  "Open todo snapshot:",
7365
7450
  ...todoSnapshot,
7366
7451
  "[/nextsteps_gate]"
@@ -7419,11 +7504,15 @@ function createAgentResponseHandler(a) {
7419
7504
  }
7420
7505
  stabilizePromptEpoch();
7421
7506
  const volatileLedger = buildCompletedWorkLedgerBlock(a.ctx);
7507
+ const continuity = buildConversationContinuityBlock(a.ctx);
7422
7508
  const liveNextStepsGate = buildLiveNextStepsGateBlock(a.ctx);
7423
7509
  const memoryEvidence = buildMemoryEvidenceBlocks(a.ctx);
7424
- const volatileBlocks = [volatileLedger, liveNextStepsGate, ...memoryEvidence].filter(
7425
- (block) => block !== void 0
7426
- );
7510
+ const volatileBlocks = [
7511
+ volatileLedger,
7512
+ continuity,
7513
+ liveNextStepsGate,
7514
+ ...memoryEvidence
7515
+ ].filter((block) => block !== void 0);
7427
7516
  const system = volatileBlocks.length > 0 ? [...a.ctx.systemPrompt, ...volatileBlocks] : a.ctx.systemPrompt;
7428
7517
  await a.ctx.waitForModelTransition();
7429
7518
  const provider = a.ctx.provider;
@@ -8409,7 +8498,12 @@ function resolveContinuation(input) {
8409
8498
  "",
8410
8499
  "If this item is already done, mark it complete and move to the next open todo. Keep the board honest as you work \u2014 mark finished items completed and split items that need more than one turn. When every todo is complete, stop and give a short summary \u2014 do not invent new work."
8411
8500
  ].join("\n");
8412
- return { source: "todo", text: text2, label: `\u25B6 Continue \u2192 todo: ${ellipsize(item)}` };
8501
+ return {
8502
+ source: "todo",
8503
+ todoId: next.id,
8504
+ text: text2,
8505
+ label: `\u25B6 Continue \u2192 todo: ${ellipsize(item)}`
8506
+ };
8413
8507
  }
8414
8508
  }
8415
8509
  const top = suggestions[0];
@@ -8850,6 +8944,9 @@ function fallbackCandidates(config, current, opts = {}) {
8850
8944
  const configFallbackAuto = config.fallbackAuto;
8851
8945
  const effectiveFallbackAuto = configFallbackAuto !== void 0 && configFallbackAuto !== null ? configFallbackAuto : !opts.closedWorld;
8852
8946
  const explicitRefs = opts.fallbackModels ?? config.fallbackModels;
8947
+ const explicitUsable = explicitRefs !== void 0 && explicitRefs.length > 0 && mgr.resolveRefs(explicitRefs, current).length > 0;
8948
+ const profileUsable = opts.fallbackProfile !== void 0 && mgr.hasProfile(opts.fallbackProfile) && mgr.resolve(opts.fallbackProfile, { exclude: current }).length > 0;
8949
+ const fromExplicitSource = explicitUsable || profileUsable;
8853
8950
  const selectedChain = opts.closedWorld ? explicitRefs && explicitRefs.length > 0 ? mgr.resolveRefs(explicitRefs, current) : opts.fallbackProfile ? mgr.resolve(opts.fallbackProfile, { exclude: current }) : Object.freeze([]) : mgr.resolveEffective({
8854
8951
  fallbackModels: explicitRefs,
8855
8952
  fallbackProfile: opts.fallbackProfile,
@@ -8878,10 +8975,10 @@ function fallbackCandidates(config, current, opts = {}) {
8878
8975
  });
8879
8976
  }
8880
8977
  candidates.push(...selectedChain);
8881
- if (opts.fallbackProfile !== "default") {
8978
+ if (!fromExplicitSource && opts.fallbackProfile !== "default") {
8882
8979
  candidates.push(...mgr.resolve("default", { exclude: current }));
8883
8980
  }
8884
- if (effectiveFallbackAuto) {
8981
+ if (!fromExplicitSource && effectiveFallbackAuto) {
8885
8982
  candidates.push(...mgr.resolveAllConfigured(current));
8886
8983
  }
8887
8984
  const seen = /* @__PURE__ */ new Set();
@@ -9031,6 +9128,7 @@ function createFallbackModelExtension(deps) {
9031
9128
  return runFallbackChain(ctx, request, inner, firstErr);
9032
9129
  }
9033
9130
  async function runFallbackChain(ctx_, request_, inner_, firstErr_, alreadyTracked = false) {
9131
+ if (ctx_.signal?.aborted) throw firstErr_;
9034
9132
  let lastErr = firstErr_;
9035
9133
  const cfg = deps.getConfig();
9036
9134
  const current = { providerId: ctx_.provider.id, model: ctx_.model };
@@ -9117,7 +9215,9 @@ function createFallbackModelExtension(deps) {
9117
9215
  );
9118
9216
  }
9119
9217
  }
9218
+ if (ctx_.signal?.aborted) throw firstErr_;
9120
9219
  for (const entry of usableChain) {
9220
+ if (ctx_.signal?.aborted) throw lastErr;
9121
9221
  if (!evaluateModelCalendar(cfg.modelAvailabilitySchedule, entry.providerId, entry.model).allowed)
9122
9222
  continue;
9123
9223
  if (tracker && !tracker.isAvailable(entry.providerId, entry.model)) {
@@ -9177,6 +9277,7 @@ function createFallbackModelExtension(deps) {
9177
9277
  ...gateRequestId ? { requestId: gateRequestId } : {},
9178
9278
  ...warning ? { contextWindowWarning: warning } : {}
9179
9279
  });
9280
+ if (ctx_.signal?.aborted) throw lastErr;
9180
9281
  try {
9181
9282
  const response = ensureUsableModelResponse(
9182
9283
  await inner_(ctx_, request_),