reasonix 0.12.8 → 0.12.14

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.
package/dist/index.d.ts CHANGED
@@ -491,11 +491,28 @@ interface ImmutablePrefixOptions {
491
491
  }
492
492
  declare class ImmutablePrefix {
493
493
  readonly system: string;
494
- readonly toolSpecs: readonly ToolSpec[];
494
+ /**
495
+ * Backing array for `toolSpecs`. Originally `Object.freeze`d at
496
+ * construction (hence the class name) — but `addTool` now lets the
497
+ * dashboard register `semantic_search` after a mid-session
498
+ * `reasonix index` build without forcing the user to restart. Each
499
+ * add is documented to cost one cache-miss turn (the cached prefix
500
+ * on DeepSeek's side is keyed by the full tool list); subsequent
501
+ * turns re-cache against the new shape.
502
+ */
503
+ private _toolSpecs;
495
504
  readonly fewShots: readonly ChatMessage[];
496
505
  constructor(opts: ImmutablePrefixOptions);
506
+ get toolSpecs(): readonly ToolSpec[];
497
507
  toMessages(): ChatMessage[];
498
508
  tools(): ToolSpec[];
509
+ /**
510
+ * Add a tool spec to the prefix. Returns `true` if added, `false`
511
+ * if a tool with the same name was already present (callers can
512
+ * decide whether to ignore or surface the no-op). The model picks
513
+ * up the new tool on the next turn after the cache busts once.
514
+ */
515
+ addTool(spec: ToolSpec): boolean;
499
516
  get fingerprint(): string;
500
517
  }
501
518
  declare class AppendOnlyLog {
package/dist/index.js CHANGED
@@ -1157,23 +1157,48 @@ function blockToString(block) {
1157
1157
  import { createHash } from "crypto";
1158
1158
  var ImmutablePrefix = class {
1159
1159
  system;
1160
- toolSpecs;
1160
+ /**
1161
+ * Backing array for `toolSpecs`. Originally `Object.freeze`d at
1162
+ * construction (hence the class name) — but `addTool` now lets the
1163
+ * dashboard register `semantic_search` after a mid-session
1164
+ * `reasonix index` build without forcing the user to restart. Each
1165
+ * add is documented to cost one cache-miss turn (the cached prefix
1166
+ * on DeepSeek's side is keyed by the full tool list); subsequent
1167
+ * turns re-cache against the new shape.
1168
+ */
1169
+ _toolSpecs;
1161
1170
  fewShots;
1162
1171
  constructor(opts) {
1163
1172
  this.system = opts.system;
1164
- this.toolSpecs = Object.freeze([...opts.toolSpecs ?? []]);
1173
+ this._toolSpecs = [...opts.toolSpecs ?? []];
1165
1174
  this.fewShots = Object.freeze([...opts.fewShots ?? []]);
1166
1175
  }
1176
+ get toolSpecs() {
1177
+ return this._toolSpecs;
1178
+ }
1167
1179
  toMessages() {
1168
1180
  return [{ role: "system", content: this.system }, ...this.fewShots.map((m) => ({ ...m }))];
1169
1181
  }
1170
1182
  tools() {
1171
- return this.toolSpecs.map((t) => structuredClone(t));
1183
+ return this._toolSpecs.map((t) => structuredClone(t));
1184
+ }
1185
+ /**
1186
+ * Add a tool spec to the prefix. Returns `true` if added, `false`
1187
+ * if a tool with the same name was already present (callers can
1188
+ * decide whether to ignore or surface the no-op). The model picks
1189
+ * up the new tool on the next turn after the cache busts once.
1190
+ */
1191
+ addTool(spec) {
1192
+ const name = spec.function?.name;
1193
+ if (!name) return false;
1194
+ if (this._toolSpecs.some((t) => t.function?.name === name)) return false;
1195
+ this._toolSpecs.push(spec);
1196
+ return true;
1172
1197
  }
1173
1198
  get fingerprint() {
1174
1199
  const blob = JSON.stringify({
1175
1200
  system: this.system,
1176
- tools: this.toolSpecs,
1201
+ tools: this._toolSpecs,
1177
1202
  shots: this.fewShots
1178
1203
  });
1179
1204
  return createHash("sha256").update(blob).digest("hex").slice(0, 16);
@@ -2657,6 +2682,7 @@ var CacheFirstLoop = class {
2657
2682
  return;
2658
2683
  }
2659
2684
  }
2685
+ let workspaceSwitchPending = false;
2660
2686
  for (const call of repairedCalls) {
2661
2687
  const name = call.function?.name ?? "";
2662
2688
  const args = call.function?.arguments ?? "{}";
@@ -2679,7 +2705,11 @@ var CacheFirstLoop = class {
2679
2705
  });
2680
2706
  for (const w of hookWarnings(preReport.outcomes, this._turn)) yield w;
2681
2707
  let result;
2682
- if (preReport.blocked) {
2708
+ if (workspaceSwitchPending) {
2709
+ result = JSON.stringify({
2710
+ error: `${name}: deferred because change_workspace in the same batch is awaiting the user's approval. Re-issue this call on your next turn \u2014 the sandbox root may have changed.`
2711
+ });
2712
+ } else if (preReport.blocked) {
2683
2713
  const blocking = preReport.outcomes[preReport.outcomes.length - 1];
2684
2714
  const reason = (blocking?.stderr || blocking?.stdout || "blocked by PreToolUse hook").trim();
2685
2715
  result = `[hook block] ${blocking?.hook.command ?? "<unknown>"}
@@ -2689,6 +2719,9 @@ ${reason}`;
2689
2719
  signal,
2690
2720
  maxResultTokens: DEFAULT_MAX_RESULT_TOKENS
2691
2721
  });
2722
+ if (name === "change_workspace" && result.includes('"WorkspaceConfirmationError:')) {
2723
+ workspaceSwitchPending = true;
2724
+ }
2692
2725
  const postReport = await runHooks({
2693
2726
  hooks: this.hooks,
2694
2727
  payload: {