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/README.md +8 -1
- package/dist/cli/index.js +213 -24
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +43 -1
- package/dist/index.js +126 -12
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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.
|
|
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.
|
|
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.
|
|
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 =
|
|
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
|
|
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
|
|
1345
|
+
return join2(homedir2(), ".reasonix", "config.json");
|
|
1239
1346
|
}
|
|
1240
1347
|
function readConfig(path = defaultConfigPath()) {
|
|
1241
1348
|
try {
|
|
1242
|
-
const raw =
|
|
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
|
-
|
|
1357
|
+
mkdirSync2(dirname2(path), { recursive: true });
|
|
1251
1358
|
writeFileSync(path, JSON.stringify(cfg, null, 2), "utf8");
|
|
1252
1359
|
try {
|
|
1253
|
-
|
|
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
|