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 +52 -141
- package/dist/models.d.ts +7 -3
- package/dist/transcript/hooks.d.ts +0 -13
- package/dist/utils/directory-init.d.ts +8 -1
- package/package.json +1 -1
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
|
-
|
|
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
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
}
|
|
1122
|
-
|
|
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
|
-
|
|
1135
|
-
} catch (error) {
|
|
1127
|
+
}).catch((error) => {
|
|
1136
1128
|
logger.error("Failed to compact session", error);
|
|
1137
|
-
|
|
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.
|
|
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
|
|
1394
|
+
function getPendingMigrations() {
|
|
1510
1395
|
if (!fs.existsSync(OPENFLEET_DIR))
|
|
1511
|
-
return
|
|
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
|
|
1514
|
-
const
|
|
1515
|
-
|
|
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
|
-
|
|
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
|
|
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:
|
|
29
|
-
export declare const bigModel:
|
|
30
|
-
export declare const smallModel:
|
|
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
|
-
|
|
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;
|