reasonix 0.0.5 → 0.0.6

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
@@ -463,6 +463,12 @@ interface CacheFirstLoopOptions {
463
463
  * since the default selector scores samples by plan-state uncertainty.
464
464
  */
465
465
  branch?: number | BranchOptions;
466
+ /**
467
+ * Session name. When set, the loop pre-loads the session's prior messages
468
+ * into its log on construction, and appends every new log entry to
469
+ * `~/.reasonix/sessions/<name>.jsonl` so the next run can resume.
470
+ */
471
+ session?: string;
466
472
  }
467
473
  /**
468
474
  * Pillar 1 — Cache-First Loop.
@@ -494,9 +500,13 @@ declare class CacheFirstLoop {
494
500
  harvestOptions: HarvestOptions;
495
501
  branchEnabled: boolean;
496
502
  branchOptions: BranchOptions;
503
+ sessionName: string | null;
504
+ /** Number of messages that were pre-loaded from the session file. */
505
+ readonly resumedMessageCount: number;
497
506
  private _turn;
498
507
  private _streamPreference;
499
508
  constructor(opts: CacheFirstLoopOptions);
509
+ private appendAndPersist;
500
510
  /**
501
511
  * Reconfigure model/harvest/branch/stream mid-session. The loop's log,
502
512
  * scratch, and stats are preserved — only the per-turn behavior changes.
@@ -510,6 +520,38 @@ declare class CacheFirstLoop {
510
520
  private assistantMessage;
511
521
  }
512
522
 
523
+ /**
524
+ * Session persistence.
525
+ *
526
+ * Every turn's log entries (user / assistant / tool messages) are appended to
527
+ * a JSONL file under `~/.reasonix/sessions/<name>.jsonl`. Next time the user
528
+ * starts the CLI with the same session name, the loop pre-loads the file
529
+ * into its AppendOnlyLog so the new turn has full prior context.
530
+ *
531
+ * Design notes:
532
+ * - JSONL rather than JSON so concurrent writes don't corrupt.
533
+ * - 0600 permissions on Unix (chmod no-ops on Windows).
534
+ * - Name sanitization keeps paths safe: only [\w-] and CJK letters pass;
535
+ * anything else is replaced with underscore, max 64 chars.
536
+ * - The loop's stats/session aren't persisted — only the message log.
537
+ * Cost accounting resets each run (by design — old costs are sunk).
538
+ */
539
+
540
+ interface SessionInfo {
541
+ name: string;
542
+ path: string;
543
+ size: number;
544
+ messageCount: number;
545
+ mtime: Date;
546
+ }
547
+ declare function sessionsDir(): string;
548
+ declare function sessionPath(name: string): string;
549
+ declare function sanitizeName(name: string): string;
550
+ declare function loadSessionMessages(name: string): ChatMessage[];
551
+ declare function appendSessionMessage(name: string, message: ChatMessage): void;
552
+ declare function listSessions(): SessionInfo[];
553
+ declare function deleteSession(name: string): boolean;
554
+
513
555
  /**
514
556
  * Minimal `.env` loader; no dependency on dotenv.
515
557
  *
@@ -548,4 +590,4 @@ declare function redactKey(key: string): string;
548
590
 
549
591
  declare const VERSION = "0.0.1";
550
592
 
551
- export { AppendOnlyLog, type BranchOptions, type BranchProgress, type BranchResult, type BranchSample, type BranchSelector, type BranchSummary, CacheFirstLoop, type CacheFirstLoopOptions, type ChatMessage, type ChatResponse, DeepSeekClient, type DeepSeekClientOptions, type EventRole, type FlattenDecision, type HarvestOptions, ImmutablePrefix, type ImmutablePrefixOptions, type JSONSchema, type LoopEvent, type ReasonixConfig, type ReconfigurableOptions, type RepairReport, type RetryInfo, type RetryOptions, type Role, type ScavengeOptions, type ScavengeResult, SessionStats, type SessionSummary, StormBreaker, type StreamChunk, type ToolCall, ToolCallRepair, type ToolCallRepairOptions, type ToolDefinition, type ToolFunctionSpec, ToolRegistry, type ToolSpec, type TruncationRepairResult, type TurnStats, type TypedPlanState, Usage, VERSION, VolatileScratch, aggregateBranchUsage, analyzeSchema, claudeEquivalentCost, costUsd, defaultConfigPath, defaultSelector, emptyPlanState, fetchWithRetry, flattenSchema, harvest, isPlanStateEmpty, isPlausibleKey, loadApiKey, loadDotenv, nestArguments, readConfig, redactKey, repairTruncatedJson, runBranches, saveApiKey, scavengeToolCalls, writeConfig };
593
+ export { AppendOnlyLog, type BranchOptions, type BranchProgress, type BranchResult, type BranchSample, type BranchSelector, type BranchSummary, CacheFirstLoop, type CacheFirstLoopOptions, type ChatMessage, type ChatResponse, DeepSeekClient, type DeepSeekClientOptions, type EventRole, type FlattenDecision, type HarvestOptions, ImmutablePrefix, type ImmutablePrefixOptions, type JSONSchema, type LoopEvent, type ReasonixConfig, type ReconfigurableOptions, type RepairReport, type RetryInfo, type RetryOptions, type Role, type ScavengeOptions, type ScavengeResult, type SessionInfo, SessionStats, type SessionSummary, StormBreaker, type StreamChunk, type ToolCall, ToolCallRepair, type ToolCallRepairOptions, type ToolDefinition, type ToolFunctionSpec, ToolRegistry, type ToolSpec, type TruncationRepairResult, type TurnStats, type TypedPlanState, Usage, VERSION, VolatileScratch, aggregateBranchUsage, analyzeSchema, appendSessionMessage, claudeEquivalentCost, costUsd, defaultConfigPath, defaultSelector, deleteSession, emptyPlanState, fetchWithRetry, flattenSchema, harvest, isPlanStateEmpty, isPlausibleKey, listSessions, loadApiKey, loadDotenv, loadSessionMessages, nestArguments, readConfig, redactKey, repairTruncatedJson, runBranches, sanitizeName as sanitizeSessionName, saveApiKey, scavengeToolCalls, sessionPath, sessionsDir, writeConfig };
package/dist/index.js CHANGED
@@ -778,6 +778,93 @@ function signature2(call) {
778
778
  return `${call.function?.name ?? ""}::${call.function?.arguments ?? ""}`;
779
779
  }
780
780
 
781
+ // src/session.ts
782
+ import {
783
+ appendFileSync,
784
+ chmodSync,
785
+ existsSync,
786
+ mkdirSync,
787
+ readFileSync,
788
+ readdirSync,
789
+ statSync,
790
+ unlinkSync
791
+ } from "fs";
792
+ import { homedir } from "os";
793
+ import { dirname, join } from "path";
794
+ function sessionsDir() {
795
+ return join(homedir(), ".reasonix", "sessions");
796
+ }
797
+ function sessionPath(name) {
798
+ return join(sessionsDir(), `${sanitizeName(name)}.jsonl`);
799
+ }
800
+ function sanitizeName(name) {
801
+ const cleaned = name.replace(/[^\w\-\u4e00-\u9fa5]/g, "_").slice(0, 64);
802
+ return cleaned || "default";
803
+ }
804
+ function loadSessionMessages(name) {
805
+ const path = sessionPath(name);
806
+ if (!existsSync(path)) return [];
807
+ try {
808
+ const raw = readFileSync(path, "utf8");
809
+ const out = [];
810
+ for (const line of raw.split(/\r?\n/)) {
811
+ const trimmed = line.trim();
812
+ if (!trimmed) continue;
813
+ try {
814
+ const msg = JSON.parse(trimmed);
815
+ if (msg && typeof msg === "object" && "role" in msg) out.push(msg);
816
+ } catch {
817
+ }
818
+ }
819
+ return out;
820
+ } catch {
821
+ return [];
822
+ }
823
+ }
824
+ function appendSessionMessage(name, message) {
825
+ const path = sessionPath(name);
826
+ mkdirSync(dirname(path), { recursive: true });
827
+ appendFileSync(path, `${JSON.stringify(message)}
828
+ `, "utf8");
829
+ try {
830
+ chmodSync(path, 384);
831
+ } catch {
832
+ }
833
+ }
834
+ function listSessions() {
835
+ const dir = sessionsDir();
836
+ if (!existsSync(dir)) return [];
837
+ try {
838
+ const files = readdirSync(dir).filter((f) => f.endsWith(".jsonl"));
839
+ return files.map((file) => {
840
+ const path = join(dir, file);
841
+ const stat = statSync(path);
842
+ const name = file.replace(/\.jsonl$/, "");
843
+ const messageCount = countLines(path);
844
+ return { name, path, size: stat.size, messageCount, mtime: stat.mtime };
845
+ }).sort((a, b) => b.mtime.getTime() - a.mtime.getTime());
846
+ } catch {
847
+ return [];
848
+ }
849
+ }
850
+ function deleteSession(name) {
851
+ const path = sessionPath(name);
852
+ try {
853
+ unlinkSync(path);
854
+ return true;
855
+ } catch {
856
+ return false;
857
+ }
858
+ }
859
+ function countLines(path) {
860
+ try {
861
+ const raw = readFileSync(path, "utf8");
862
+ return raw.split(/\r?\n/).filter((l) => l.trim()).length;
863
+ } catch {
864
+ return 0;
865
+ }
866
+ }
867
+
781
868
  // src/telemetry.ts
782
869
  var DEEPSEEK_PRICING = {
783
870
  "deepseek-chat": { inputCacheHit: 0.07, inputCacheMiss: 0.27, output: 1.1 },
@@ -934,6 +1021,9 @@ var CacheFirstLoop = class {
934
1021
  harvestOptions;
935
1022
  branchEnabled;
936
1023
  branchOptions;
1024
+ sessionName;
1025
+ /** Number of messages that were pre-loaded from the session file. */
1026
+ resumedMessageCount;
937
1027
  _turn = 0;
938
1028
  _streamPreference;
939
1029
  constructor(opts) {
@@ -957,6 +1047,23 @@ var CacheFirstLoop = class {
957
1047
  this.stream = this.branchEnabled ? false : this._streamPreference;
958
1048
  const allowedNames = /* @__PURE__ */ new Set([...this.prefix.toolSpecs.map((s) => s.function.name)]);
959
1049
  this.repair = new ToolCallRepair({ allowedToolNames: allowedNames });
1050
+ this.sessionName = opts.session ?? null;
1051
+ if (this.sessionName) {
1052
+ const prior = loadSessionMessages(this.sessionName);
1053
+ for (const msg of prior) this.log.append(msg);
1054
+ this.resumedMessageCount = prior.length;
1055
+ } else {
1056
+ this.resumedMessageCount = 0;
1057
+ }
1058
+ }
1059
+ appendAndPersist(message) {
1060
+ this.log.append(message);
1061
+ if (this.sessionName) {
1062
+ try {
1063
+ appendSessionMessage(this.sessionName, message);
1064
+ } catch {
1065
+ }
1066
+ }
960
1067
  }
961
1068
  /**
962
1069
  * Reconfigure model/harvest/branch/stream mid-session. The loop's log,
@@ -1144,7 +1251,7 @@ var CacheFirstLoop = class {
1144
1251
  }
1145
1252
  const turnStats = this.stats.record(this._turn, this.model, usage ?? new Usage());
1146
1253
  if (pendingUser !== null) {
1147
- this.log.append({ role: "user", content: pendingUser });
1254
+ this.appendAndPersist({ role: "user", content: pendingUser });
1148
1255
  pendingUser = null;
1149
1256
  }
1150
1257
  this.scratch.reasoning = reasoningContent || null;
@@ -1153,7 +1260,7 @@ var CacheFirstLoop = class {
1153
1260
  toolCalls,
1154
1261
  reasoningContent || null
1155
1262
  );
1156
- this.log.append(this.assistantMessage(assistantContent, repairedCalls));
1263
+ this.appendAndPersist(this.assistantMessage(assistantContent, repairedCalls));
1157
1264
  yield {
1158
1265
  turn: this._turn,
1159
1266
  role: "assistant_final",
@@ -1171,7 +1278,7 @@ var CacheFirstLoop = class {
1171
1278
  const name = call.function?.name ?? "";
1172
1279
  const args = call.function?.arguments ?? "{}";
1173
1280
  const result = await this.tools.dispatch(name, args);
1174
- this.log.append({
1281
+ this.appendAndPersist({
1175
1282
  role: "tool",
1176
1283
  tool_call_id: call.id ?? "",
1177
1284
  name,
@@ -1207,12 +1314,12 @@ function summarizeBranch(chosen, samples) {
1207
1314
  }
1208
1315
 
1209
1316
  // src/env.ts
1210
- import { readFileSync } from "fs";
1317
+ import { readFileSync as readFileSync2 } from "fs";
1211
1318
  import { resolve } from "path";
1212
1319
  function loadDotenv(path = ".env") {
1213
1320
  let raw;
1214
1321
  try {
1215
- raw = readFileSync(resolve(process.cwd(), path), "utf8");
1322
+ raw = readFileSync2(resolve(process.cwd(), path), "utf8");
1216
1323
  } catch {
1217
1324
  return;
1218
1325
  }
@@ -1231,15 +1338,15 @@ function loadDotenv(path = ".env") {
1231
1338
  }
1232
1339
 
1233
1340
  // src/config.ts
1234
- import { chmodSync, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
1235
- import { homedir } from "os";
1236
- import { dirname, join } from "path";
1341
+ import { chmodSync as chmodSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync } from "fs";
1342
+ import { homedir as homedir2 } from "os";
1343
+ import { dirname as dirname2, join as join2 } from "path";
1237
1344
  function defaultConfigPath() {
1238
- return join(homedir(), ".reasonix", "config.json");
1345
+ return join2(homedir2(), ".reasonix", "config.json");
1239
1346
  }
1240
1347
  function readConfig(path = defaultConfigPath()) {
1241
1348
  try {
1242
- const raw = readFileSync2(path, "utf8");
1349
+ const raw = readFileSync3(path, "utf8");
1243
1350
  const parsed = JSON.parse(raw);
1244
1351
  if (parsed && typeof parsed === "object") return parsed;
1245
1352
  } catch {
@@ -1247,10 +1354,10 @@ function readConfig(path = defaultConfigPath()) {
1247
1354
  return {};
1248
1355
  }
1249
1356
  function writeConfig(cfg, path = defaultConfigPath()) {
1250
- mkdirSync(dirname(path), { recursive: true });
1357
+ mkdirSync2(dirname2(path), { recursive: true });
1251
1358
  writeFileSync(path, JSON.stringify(cfg, null, 2), "utf8");
1252
1359
  try {
1253
- chmodSync(path, 384);
1360
+ chmodSync2(path, 384);
1254
1361
  } catch {
1255
1362
  }
1256
1363
  }
@@ -1289,25 +1396,32 @@ export {
1289
1396
  VolatileScratch,
1290
1397
  aggregateBranchUsage,
1291
1398
  analyzeSchema,
1399
+ appendSessionMessage,
1292
1400
  claudeEquivalentCost,
1293
1401
  costUsd,
1294
1402
  defaultConfigPath,
1295
1403
  defaultSelector,
1404
+ deleteSession,
1296
1405
  emptyPlanState,
1297
1406
  fetchWithRetry,
1298
1407
  flattenSchema,
1299
1408
  harvest,
1300
1409
  isPlanStateEmpty,
1301
1410
  isPlausibleKey,
1411
+ listSessions,
1302
1412
  loadApiKey,
1303
1413
  loadDotenv,
1414
+ loadSessionMessages,
1304
1415
  nestArguments,
1305
1416
  readConfig,
1306
1417
  redactKey,
1307
1418
  repairTruncatedJson,
1308
1419
  runBranches,
1420
+ sanitizeName as sanitizeSessionName,
1309
1421
  saveApiKey,
1310
1422
  scavengeToolCalls,
1423
+ sessionPath,
1424
+ sessionsDir,
1311
1425
  writeConfig
1312
1426
  };
1313
1427
  //# sourceMappingURL=index.js.map