openfleet 0.4.0 → 0.4.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.
package/dist/index.js CHANGED
@@ -59,9 +59,13 @@ var models = {
59
59
  bigPickle: "opencode/big-pickle"
60
60
  }
61
61
  };
62
- var defaultModel = models.anthropic.sonnet;
62
+ var defaultModel = process.env.OPENFLEET_MODEL ?? models.anthropic.sonnet;
63
63
  var bigModel = defaultModel;
64
64
  var fallbackModel = models.freeModels.minimaxM25Free;
65
+ function parseModel(model) {
66
+ const [providerID, ...rest] = model.split("/");
67
+ return { providerID, modelID: rest.join("/") };
68
+ }
65
69
 
66
70
  // src/agents/architect.ts
67
71
  var SYSTEM_PROMPT = `You are Architect, Planner of the Openfleet.
@@ -1075,8 +1079,7 @@ async function sleep(time_ms) {
1075
1079
 
1076
1080
  // src/logger.ts
1077
1081
  import { appendFileSync, existsSync } from "fs";
1078
- import { join as join2 } from "path";
1079
- var LOG_FILE = join2(OPENFLEET_DIR, "openfleet.log");
1082
+ var LOG_FILE = PATHS.logFile;
1080
1083
  var dirVerified = false;
1081
1084
  function writeLog(level, msg, ...args) {
1082
1085
  const timestamp = new Date().toISOString();
@@ -1114,28 +1117,17 @@ Use this tool:
1114
1117
  },
1115
1118
  async execute(_args, context) {
1116
1119
  const { sessionID } = context;
1117
- try {
1118
- const { data: messages } = await ctx.client.session.messages({
1119
- path: { id: sessionID },
1120
- query: { directory: ctx.directory }
1121
- });
1122
- if (!messages || messages.length === 0) {
1123
- return "No messages to save.";
1124
- }
1125
- const lastAssistant = [...messages].reverse().find((m) => m.info.role === "assistant");
1126
- const providerID = lastAssistant?.info.providerID ?? "anthropic";
1127
- const modelID = lastAssistant?.info.modelID ?? "claude-sonnet-4";
1128
- await ctx.client.session.summarize({
1129
- path: { id: sessionID },
1130
- body: { providerID, modelID },
1131
- query: { directory: ctx.directory }
1132
- });
1120
+ const { providerID, modelID } = parseModel(defaultModel);
1121
+ ctx.client.session.summarize({
1122
+ path: { id: sessionID },
1123
+ body: { providerID, modelID },
1124
+ query: { directory: ctx.directory }
1125
+ }).then(() => {
1133
1126
  logger.info("Session compacted", { sessionID, providerID, modelID });
1134
- return `\u2705 Context compacted successfully.`;
1135
- } catch (error) {
1127
+ }).catch((error) => {
1136
1128
  logger.error("Failed to compact session", error);
1137
- return `\u274C Failed to compact session: ${error}`;
1138
- }
1129
+ });
1130
+ return `\u2705 Context compaction initiated.`;
1139
1131
  }
1140
1132
  });
1141
1133
  }
@@ -1144,83 +1136,6 @@ Use this tool:
1144
1136
  import { existsSync as existsSync3, readFileSync } from "fs";
1145
1137
  import path3 from "path";
1146
1138
 
1147
- // src/lib/fallback.ts
1148
- var CREDIT_BALANCE_PATTERNS = [
1149
- "credit balance is too low",
1150
- "insufficient credits",
1151
- "please go to plans & billing",
1152
- "purchase credits"
1153
- ];
1154
- var fallbackInProgress = new Set;
1155
- var fallbackSessions = new Set;
1156
- var lastFallbackTime = new Map;
1157
- var COOLDOWN_MS = 30000;
1158
- function isCreditBalanceError(message) {
1159
- const lower = message.toLowerCase();
1160
- return CREDIT_BALANCE_PATTERNS.some((p) => lower.includes(p));
1161
- }
1162
- function isSessionInFallback(sessionID) {
1163
- return fallbackSessions.has(sessionID);
1164
- }
1165
- function markSessionFallback(sessionID) {
1166
- fallbackSessions.add(sessionID);
1167
- }
1168
- function getFallbackModelOverride() {
1169
- const [providerID, modelID] = fallbackModel.split("/");
1170
- return { providerID, modelID };
1171
- }
1172
- async function handleCreditBalanceFallback(client, sessionID) {
1173
- if (fallbackInProgress.has(sessionID))
1174
- return;
1175
- const last = lastFallbackTime.get(sessionID);
1176
- if (last && Date.now() - last < COOLDOWN_MS)
1177
- return;
1178
- fallbackInProgress.add(sessionID);
1179
- fallbackSessions.add(sessionID);
1180
- lastFallbackTime.set(sessionID, Date.now());
1181
- try {
1182
- await client.session.abort({ path: { id: sessionID } });
1183
- const { data: messages } = await client.session.messages({
1184
- path: { id: sessionID }
1185
- });
1186
- if (!messages || messages.length === 0) {
1187
- throw new Error("No messages found after abort");
1188
- }
1189
- const lastUserMsg = [...messages].reverse().find((m) => m.info.role === "user");
1190
- if (!lastUserMsg) {
1191
- throw new Error("No user message found to revert to");
1192
- }
1193
- const textPart = lastUserMsg.parts.find((p) => p.type === "text");
1194
- if (!textPart || textPart.type !== "text")
1195
- return;
1196
- const messageID = lastUserMsg.info.id;
1197
- const text = textPart.text;
1198
- await client.session.revert({
1199
- path: { id: sessionID },
1200
- body: { messageID }
1201
- });
1202
- const [providerID, modelID] = fallbackModel.split("/");
1203
- await client.session.prompt({
1204
- path: { id: sessionID },
1205
- body: {
1206
- model: { providerID, modelID },
1207
- parts: [{ type: "text", text }]
1208
- }
1209
- });
1210
- logger.info("Credit balance fallback triggered", { sessionID, fallbackModel });
1211
- await client.tui.showToast({
1212
- body: {
1213
- message: "\u26A0\uFE0F Anthropic credit balance low \u2014 switched to Minimax M2.5 Free",
1214
- variant: "warning"
1215
- }
1216
- });
1217
- } catch (err) {
1218
- logger.error("Credit balance fallback failed", { sessionID, err });
1219
- } finally {
1220
- fallbackInProgress.delete(sessionID);
1221
- }
1222
- }
1223
-
1224
1139
  // src/transcript/writer.ts
1225
1140
  import { existsSync as existsSync2 } from "fs";
1226
1141
  import { appendFile, mkdir } from "fs/promises";
@@ -1425,11 +1340,6 @@ function createTranscriptHooks(ctx) {
1425
1340
  const session = await getSessionInfo(ctx, input.sessionID);
1426
1341
  await recordUserMessage(session, output.message, output.parts);
1427
1342
  },
1428
- "chat.model": async (input, output) => {
1429
- if (isSessionInFallback(input.sessionID)) {
1430
- output.model = getFallbackModelOverride();
1431
- }
1432
- },
1433
1343
  "tool.execute.before": async (input, output) => {
1434
1344
  const session = await getSessionInfo(ctx, input.sessionID);
1435
1345
  await recordToolUse(session, input.tool, input.callID, output.args);
@@ -1460,34 +1370,7 @@ function createTranscriptHooks(ctx) {
1460
1370
  });
1461
1371
  }
1462
1372
  },
1463
- event: async ({ event }) => {
1464
- if (event.type === "session.created") {
1465
- const { info } = event.properties;
1466
- if (info.parentID && isSessionInFallback(info.parentID)) {
1467
- markSessionFallback(info.id);
1468
- }
1469
- }
1470
- if (event.type === "session.status") {
1471
- const { sessionID, status } = event.properties;
1472
- if (status.type === "retry" && isCreditBalanceError(status.message)) {
1473
- await handleCreditBalanceFallback(ctx.client, sessionID);
1474
- }
1475
- }
1476
- if (event.type === "session.error") {
1477
- const { sessionID, error } = event.properties;
1478
- if (sessionID && error && "message" in error.data && isCreditBalanceError(String(error.data.message))) {
1479
- await handleCreditBalanceFallback(ctx.client, sessionID);
1480
- }
1481
- }
1482
- if (event.type === "message.updated") {
1483
- const { info } = event.properties;
1484
- if (info.role === "assistant" && info.error) {
1485
- if ("message" in info.error.data && isCreditBalanceError(String(info.error.data.message))) {
1486
- await handleCreditBalanceFallback(ctx.client, info.sessionID);
1487
- }
1488
- }
1489
- }
1490
- }
1373
+ event: async ({ event }) => {}
1491
1374
  };
1492
1375
  }
1493
1376
  // src/utils/directory-init.ts
@@ -1495,24 +1378,48 @@ import * as fs from "fs";
1495
1378
  import * as path4 from "path";
1496
1379
  import { fileURLToPath } from "url";
1497
1380
  // package.json
1498
- var version = "0.4.0";
1381
+ var version = "0.4.1";
1499
1382
 
1500
1383
  // src/utils/directory-init.ts
1501
1384
  var TEMPLATES_DIR = path4.join(path4.dirname(fileURLToPath(import.meta.url)), "templates", ".openfleet");
1385
+ var BUNDLED_MIGRATIONS_DIR = path4.join(TEMPLATES_DIR, "migrations");
1502
1386
  function initializeDirectories() {
1503
1387
  if (fs.existsSync(OPENFLEET_DIR)) {
1504
1388
  return;
1505
1389
  }
1506
1390
  copyDirectorySync(TEMPLATES_DIR, OPENFLEET_DIR);
1391
+ stampVersion();
1507
1392
  logger.info("Initialized .openfleet directory");
1508
1393
  }
1509
- function checkMigrationNeeded() {
1394
+ function getPendingMigrations() {
1510
1395
  if (!fs.existsSync(OPENFLEET_DIR))
1511
- return false;
1396
+ return [];
1397
+ if (!fs.existsSync(BUNDLED_MIGRATIONS_DIR))
1398
+ return [];
1399
+ const installedVersion = readInstalledVersion();
1400
+ return fs.readdirSync(BUNDLED_MIGRATIONS_DIR).filter((f) => f.endsWith(".md")).map((f) => f.replace(/\.md$/, "")).filter((v) => compareSemver(v, installedVersion) > 0 && compareSemver(v, version) <= 0).sort(compareSemver);
1401
+ }
1402
+ function stampVersion() {
1403
+ fs.writeFileSync(PATHS.versionFile, version);
1404
+ }
1405
+ function readInstalledVersion() {
1512
1406
  if (!fs.existsSync(PATHS.versionFile))
1513
- return true;
1514
- const installedVersion = fs.readFileSync(PATHS.versionFile, "utf-8").trim();
1515
- return installedVersion !== version;
1407
+ return "0.0.0";
1408
+ const raw = fs.readFileSync(PATHS.versionFile, "utf-8").trim();
1409
+ const parts = raw.split(".").map(Number);
1410
+ if (parts.length !== 3 || parts.some(isNaN))
1411
+ return "0.0.0";
1412
+ return raw;
1413
+ }
1414
+ function compareSemver(a, b) {
1415
+ const pa = a.split(".").map(Number);
1416
+ const pb = b.split(".").map(Number);
1417
+ for (let i = 0;i < 3; i++) {
1418
+ const diff = (pa[i] || 0) - (pb[i] || 0);
1419
+ if (diff !== 0)
1420
+ return diff;
1421
+ }
1422
+ return 0;
1516
1423
  }
1517
1424
  function copyDirectorySync(src, dest) {
1518
1425
  fs.mkdirSync(dest, { recursive: true });
@@ -1585,14 +1492,18 @@ var OpenfleetPlugin = async (ctx) => {
1585
1492
  const props = event.properties;
1586
1493
  if (!props?.info?.parentID) {
1587
1494
  setTimeout(async () => {
1588
- if (checkMigrationNeeded()) {
1495
+ const pending = getPendingMigrations();
1496
+ if (pending.length > 0) {
1497
+ const latest = pending[pending.length - 1];
1498
+ const message = pending.length === 1 ? `Run migration for v${latest}` : `${pending.length} migrations pending (v${pending[0]} \u2192 v${latest})`;
1589
1499
  await showToast(ctx, {
1590
1500
  title: "\u26A0\uFE0F Openfleet Migration Required",
1591
- message: "Copy this: 'github.com/scottsus/openfleet/issues/11' to the chat, to migrate to v0.4.0",
1501
+ message,
1592
1502
  variant: "warning",
1593
1503
  duration: 1e4
1594
1504
  });
1595
1505
  } else {
1506
+ stampVersion();
1596
1507
  await showFleetToast(ctx);
1597
1508
  }
1598
1509
  }, 0);
package/dist/models.d.ts CHANGED
@@ -25,7 +25,11 @@ export declare const models: {
25
25
  readonly bigPickle: "opencode/big-pickle";
26
26
  };
27
27
  };
28
- export declare const defaultModel: "anthropic/claude-sonnet-4-6";
29
- export declare const bigModel: "anthropic/claude-sonnet-4-6";
30
- export declare const smallModel: "anthropic/claude-sonnet-4-6";
28
+ export declare const defaultModel: string;
29
+ export declare const bigModel: string;
30
+ export declare const smallModel: string;
31
31
  export declare const fallbackModel: "opencode/minimax-m2.5-free";
32
+ export declare function parseModel(model: string): {
33
+ providerID: string;
34
+ modelID: string;
35
+ };
@@ -8,19 +8,6 @@ export declare function createTranscriptHooks(ctx: PluginInput): {
8
8
  message: unknown;
9
9
  parts: unknown[];
10
10
  }) => Promise<void>;
11
- "chat.model": (input: {
12
- sessionID: string;
13
- agent: string;
14
- model: {
15
- providerID: string;
16
- modelID: string;
17
- };
18
- }, output: {
19
- model: {
20
- providerID: string;
21
- modelID: string;
22
- };
23
- }) => Promise<void>;
24
11
  "tool.execute.before": (input: {
25
12
  sessionID: string;
26
13
  tool: string;
@@ -1,2 +1,9 @@
1
1
  export declare function initializeDirectories(): void;
2
- export declare function checkMigrationNeeded(): boolean;
2
+ /**
3
+ * Returns pending migration versions between the installed VERSION and current package version.
4
+ *
5
+ * Scans the bundled templates migrations dir (not runtime .openfleet/migrations/)
6
+ * since pre-0.4.0 installs won't have a migrations/ folder at all.
7
+ */
8
+ export declare function getPendingMigrations(): string[];
9
+ export declare function stampVersion(): void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openfleet",
3
- "version": "0.4.0",
3
+ "version": "0.4.2",
4
4
  "description": "SPARR framework agents + infinite context for OpenCode",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",