openfleet 0.3.2 → 0.3.4
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/agents/actor.d.ts +2 -0
- package/dist/agents/housekeeping.d.ts +2 -0
- package/dist/agents/index.d.ts +13 -0
- package/dist/agents/names.d.ts +10 -0
- package/dist/agents/orchestrator.d.ts +2 -0
- package/dist/agents/planner.d.ts +2 -0
- package/dist/agents/read-only.d.ts +2 -0
- package/dist/agents/reflector.d.ts +2 -0
- package/dist/agents/reviewer.d.ts +2 -0
- package/dist/agents/scout.d.ts +2 -0
- package/dist/config.d.ts +29 -0
- package/dist/index.d.ts +2 -4
- package/dist/index.js +43 -45
- package/dist/lib/anthropic.d.ts +12 -0
- package/dist/lib/utils.d.ts +4 -0
- package/dist/logger.d.ts +8 -0
- package/dist/models.d.ts +24 -0
- package/dist/tools/save-conversation/counter.d.ts +31 -0
- package/dist/tools/save-conversation/index.d.ts +10 -0
- package/dist/tools/save-conversation/session-writer.d.ts +17 -0
- package/dist/tools/save-conversation/slug-generator.d.ts +14 -0
- package/dist/tools/save-conversation/types.d.ts +28 -0
- package/dist/transcript/hooks.d.ts +25 -0
- package/dist/transcript/index.d.ts +4 -0
- package/dist/transcript/recorder.d.ts +14 -0
- package/dist/transcript/types.d.ts +23 -0
- package/dist/transcript/writer.d.ts +3 -0
- package/dist/utils/directory-init.d.ts +1 -0
- package/dist/utils/toast.d.ts +40 -0
- package/package.json +7 -4
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export declare const agents: {
|
|
2
|
+
"Zeus (Orchestrator)": import("@opencode-ai/sdk").AgentConfig;
|
|
3
|
+
"Hera (Read-only Orchestrator)": import("@opencode-ai/sdk").AgentConfig;
|
|
4
|
+
"[Openfleet] Athena (Scout)": import("@opencode-ai/sdk").AgentConfig;
|
|
5
|
+
"[Openfleet] Apollo (Planner)": import("@opencode-ai/sdk").AgentConfig;
|
|
6
|
+
"[Openfleet] Hercules (Actor)": import("@opencode-ai/sdk").AgentConfig;
|
|
7
|
+
"[Openfleet] Chiron (Reviewer)": import("@opencode-ai/sdk").AgentConfig;
|
|
8
|
+
"[Openfleet] Mnemosyne (Reflector)": import("@opencode-ai/sdk").AgentConfig;
|
|
9
|
+
"[Openfleet] Hermes (Housekeeping)": import("@opencode-ai/sdk").AgentConfig;
|
|
10
|
+
};
|
|
11
|
+
export declare function configureAgents(config: {
|
|
12
|
+
agent?: Record<string, unknown>;
|
|
13
|
+
}): void;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare const AGENT_NAMES: {
|
|
2
|
+
readonly ORCHESTRATOR: "Zeus (Orchestrator)";
|
|
3
|
+
readonly READ_ONLY_ORCHESTRATOR: "Hera (Read-only Orchestrator)";
|
|
4
|
+
readonly SCOUT: "[Openfleet] Athena (Scout)";
|
|
5
|
+
readonly PLANNER: "[Openfleet] Apollo (Planner)";
|
|
6
|
+
readonly ACTOR: "[Openfleet] Hercules (Actor)";
|
|
7
|
+
readonly REVIEWER: "[Openfleet] Chiron (Reviewer)";
|
|
8
|
+
readonly REFLECTOR: "[Openfleet] Mnemosyne (Reflector)";
|
|
9
|
+
readonly HOUSEKEEPING: "[Openfleet] Hermes (Housekeeping)";
|
|
10
|
+
};
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export declare const OPENFLEET_DIR: string;
|
|
2
|
+
export declare const PATHS: {
|
|
3
|
+
readonly agentsMd: string;
|
|
4
|
+
readonly root: string;
|
|
5
|
+
readonly statusFile: string;
|
|
6
|
+
readonly templates: string;
|
|
7
|
+
readonly agents: string;
|
|
8
|
+
readonly agentZeus: string;
|
|
9
|
+
readonly agentAthena: string;
|
|
10
|
+
readonly agentApollo: string;
|
|
11
|
+
readonly agentHercules: string;
|
|
12
|
+
readonly agentChiron: string;
|
|
13
|
+
readonly agentMnemosyne: string;
|
|
14
|
+
readonly sessions: string;
|
|
15
|
+
readonly stories: string;
|
|
16
|
+
readonly docs: string;
|
|
17
|
+
readonly experience: string;
|
|
18
|
+
readonly runbooks: string;
|
|
19
|
+
readonly troubleshooting: string;
|
|
20
|
+
readonly lessons: string;
|
|
21
|
+
readonly blunders: string;
|
|
22
|
+
readonly standards: string;
|
|
23
|
+
readonly reviews: string;
|
|
24
|
+
readonly transcripts: string;
|
|
25
|
+
readonly logFile: string;
|
|
26
|
+
};
|
|
27
|
+
export declare function getCurrentWeek(): string;
|
|
28
|
+
export declare function getTodayDate(): string;
|
|
29
|
+
export declare function getFullDate(): string;
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// @bun
|
|
1
2
|
// src/config.ts
|
|
2
3
|
import * as path from "path";
|
|
3
4
|
var OPENFLEET_DIR = path.join(process.cwd(), ".openfleet");
|
|
@@ -548,7 +549,7 @@ In this example we see the following:
|
|
|
548
549
|
8. this blocker did not stop us from completing the story, with a note to come
|
|
549
550
|
back to token rotation afterwards
|
|
550
551
|
9. we implemented a similar approach, and resolved the token rotation story
|
|
551
|
-
10. we raise the PR for review \
|
|
552
|
+
10. we raise the PR for review \uD83E\uDD73
|
|
552
553
|
|
|
553
554
|
And note that there can be MANY layers of task nesting (5 or more) and that's
|
|
554
555
|
OK! It reflects the nature of software engineering, even when a task is well
|
|
@@ -644,7 +645,7 @@ git branch -d feat/<story>/<task>/<branch> # \u2190 DELETE merged branch
|
|
|
644
645
|
**Keep these branches:**
|
|
645
646
|
- active branches (currently working on)
|
|
646
647
|
- parent branches (story/task not yet complete)
|
|
647
|
-
- branches marked as \`\u23F8\uFE0F paused\` or \`\
|
|
648
|
+
- branches marked as \`\u23F8\uFE0F paused\` or \`\uD83D\uDEA7 blocked\` (may resume later)
|
|
648
649
|
|
|
649
650
|
**Delete these branches:**
|
|
650
651
|
- branches marked as \`\u2705 merged\` in task tree
|
|
@@ -660,8 +661,8 @@ A story/task tree should show:
|
|
|
660
661
|
- full hierarchy with proper indentation (task \u2192 subtask \u2192 branches)
|
|
661
662
|
- current position: \`\u2190 YOU ARE HERE\`
|
|
662
663
|
- active agents: \`\u2190 Hercules working\`
|
|
663
|
-
- phase progress: R\u2705 H\u2705 L\
|
|
664
|
-
- branch status: \u2705 merged, \
|
|
664
|
+
- phase progress: R\u2705 H\u2705 L\uD83D\uDD04 I\u23F3
|
|
665
|
+
- branch status: \u2705 merged, \uD83D\uDEA7 blocked, \u23F8\uFE0F paused
|
|
665
666
|
- git branch names
|
|
666
667
|
- timestamps for key events
|
|
667
668
|
|
|
@@ -1090,15 +1091,13 @@ import { join as join2 } from "path";
|
|
|
1090
1091
|
var LOG_FILE = join2(OPENFLEET_DIR, "openfleet.log");
|
|
1091
1092
|
var dirVerified = false;
|
|
1092
1093
|
function writeLog(level, msg, ...args) {
|
|
1093
|
-
const timestamp =
|
|
1094
|
+
const timestamp = new Date().toISOString();
|
|
1094
1095
|
const formattedArgs = args.length > 0 ? ` ${JSON.stringify(args)}` : "";
|
|
1095
1096
|
const logLine = `[${timestamp}] [${level.toUpperCase()}] ${msg}${formattedArgs}
|
|
1096
1097
|
`;
|
|
1097
1098
|
if (!dirVerified) {
|
|
1098
1099
|
if (!existsSync(OPENFLEET_DIR)) {
|
|
1099
|
-
throw new Error(
|
|
1100
|
-
`[openfleet] .openfleet directory not initialized. Call initializeDirectories() first.`
|
|
1101
|
-
);
|
|
1100
|
+
throw new Error(`[openfleet] .openfleet directory not initialized. Call initializeDirectories() first.`);
|
|
1102
1101
|
}
|
|
1103
1102
|
dirVerified = true;
|
|
1104
1103
|
}
|
|
@@ -1156,7 +1155,8 @@ function formatEntryAsMarkdown(entry) {
|
|
|
1156
1155
|
}
|
|
1157
1156
|
lines.push("---");
|
|
1158
1157
|
lines.push("");
|
|
1159
|
-
return lines.join(
|
|
1158
|
+
return lines.join(`
|
|
1159
|
+
`);
|
|
1160
1160
|
}
|
|
1161
1161
|
function formatUserMessage(entry) {
|
|
1162
1162
|
const lines = [];
|
|
@@ -1194,7 +1194,7 @@ function formatToolResult(entry) {
|
|
|
1194
1194
|
lines.push("### Output");
|
|
1195
1195
|
lines.push(...formatOutput(entry.output));
|
|
1196
1196
|
lines.push("");
|
|
1197
|
-
if (entry.metadata !==
|
|
1197
|
+
if (entry.metadata !== undefined) {
|
|
1198
1198
|
lines.push("### Metadata");
|
|
1199
1199
|
lines.push("```json");
|
|
1200
1200
|
lines.push(JSON.stringify(entry.metadata, null, 2));
|
|
@@ -1233,39 +1233,40 @@ function formatOutput(output) {
|
|
|
1233
1233
|
}
|
|
1234
1234
|
|
|
1235
1235
|
// src/transcript/recorder.ts
|
|
1236
|
-
var MAX_CACHE_SIZE =
|
|
1237
|
-
var toolInputCache =
|
|
1236
|
+
var MAX_CACHE_SIZE = 1000;
|
|
1237
|
+
var toolInputCache = new Map;
|
|
1238
1238
|
async function recordUserMessage(session, message, parts) {
|
|
1239
1239
|
const entry = {
|
|
1240
1240
|
type: "user",
|
|
1241
|
-
timestamp:
|
|
1241
|
+
timestamp: new Date().toISOString(),
|
|
1242
1242
|
content: extractContentFromParts(parts),
|
|
1243
1243
|
parts
|
|
1244
1244
|
};
|
|
1245
1245
|
await appendTranscriptEntry(session.sessionID, entry, session.parentID);
|
|
1246
1246
|
}
|
|
1247
|
-
async function recordToolUse(session,
|
|
1247
|
+
async function recordToolUse(session, tool, callID, args) {
|
|
1248
1248
|
if (toolInputCache.size >= MAX_CACHE_SIZE) {
|
|
1249
1249
|
const oldestKey = toolInputCache.keys().next().value;
|
|
1250
|
-
if (oldestKey)
|
|
1250
|
+
if (oldestKey)
|
|
1251
|
+
toolInputCache.delete(oldestKey);
|
|
1251
1252
|
}
|
|
1252
1253
|
toolInputCache.set(callID, args);
|
|
1253
1254
|
const entry = {
|
|
1254
1255
|
type: "tool_use",
|
|
1255
|
-
timestamp:
|
|
1256
|
-
tool
|
|
1256
|
+
timestamp: new Date().toISOString(),
|
|
1257
|
+
tool,
|
|
1257
1258
|
callID,
|
|
1258
1259
|
input: args
|
|
1259
1260
|
};
|
|
1260
1261
|
await appendTranscriptEntry(session.sessionID, entry, session.parentID);
|
|
1261
1262
|
}
|
|
1262
|
-
async function recordToolResult(session,
|
|
1263
|
+
async function recordToolResult(session, tool, callID, output) {
|
|
1263
1264
|
const cachedInput = toolInputCache.get(callID);
|
|
1264
1265
|
toolInputCache.delete(callID);
|
|
1265
1266
|
const entry = {
|
|
1266
1267
|
type: "tool_result",
|
|
1267
|
-
timestamp:
|
|
1268
|
-
tool
|
|
1268
|
+
timestamp: new Date().toISOString(),
|
|
1269
|
+
tool,
|
|
1269
1270
|
callID,
|
|
1270
1271
|
input: cachedInput,
|
|
1271
1272
|
output: {
|
|
@@ -1277,14 +1278,16 @@ async function recordToolResult(session, tool2, callID, output) {
|
|
|
1277
1278
|
await appendTranscriptEntry(session.sessionID, entry, session.parentID);
|
|
1278
1279
|
}
|
|
1279
1280
|
function extractContentFromParts(parts) {
|
|
1280
|
-
return parts.filter((part) => part.type === "text").map((part) => part.text).join(
|
|
1281
|
+
return parts.filter((part) => part.type === "text").map((part) => part.text).join(`
|
|
1282
|
+
`);
|
|
1281
1283
|
}
|
|
1282
1284
|
|
|
1283
1285
|
// src/transcript/hooks.ts
|
|
1284
|
-
var sessionInfoCache =
|
|
1286
|
+
var sessionInfoCache = new Map;
|
|
1285
1287
|
async function getSessionInfo(ctx, sessionID) {
|
|
1286
1288
|
const cached = sessionInfoCache.get(sessionID);
|
|
1287
|
-
if (cached)
|
|
1289
|
+
if (cached)
|
|
1290
|
+
return cached;
|
|
1288
1291
|
try {
|
|
1289
1292
|
const { data: session } = await ctx.client.session.get({
|
|
1290
1293
|
path: { id: sessionID },
|
|
@@ -1318,7 +1321,6 @@ function createTranscriptHooks(ctx) {
|
|
|
1318
1321
|
}
|
|
1319
1322
|
};
|
|
1320
1323
|
}
|
|
1321
|
-
|
|
1322
1324
|
// src/tools/save-conversation/counter.ts
|
|
1323
1325
|
import * as fs from "fs";
|
|
1324
1326
|
import * as path3 from "path";
|
|
@@ -1346,7 +1348,8 @@ async function getNextCounter(date) {
|
|
|
1346
1348
|
}
|
|
1347
1349
|
function parseFilename(filename) {
|
|
1348
1350
|
const match = filename.match(FILENAME_PATTERN);
|
|
1349
|
-
if (!match)
|
|
1351
|
+
if (!match)
|
|
1352
|
+
return null;
|
|
1350
1353
|
const [, counterStr, slug] = match;
|
|
1351
1354
|
const counter = parseInt(counterStr, 10);
|
|
1352
1355
|
if (isNaN(counter) || counter < 1 || counter > MAX_COUNTER) {
|
|
@@ -1360,7 +1363,7 @@ function ensureDateDir(dateDir) {
|
|
|
1360
1363
|
}
|
|
1361
1364
|
}
|
|
1362
1365
|
function getCurrentDate() {
|
|
1363
|
-
const now =
|
|
1366
|
+
const now = new Date;
|
|
1364
1367
|
const year = now.getUTCFullYear();
|
|
1365
1368
|
const month = String(now.getUTCMonth() + 1).padStart(2, "0");
|
|
1366
1369
|
const day = String(now.getUTCDate()).padStart(2, "0");
|
|
@@ -1431,7 +1434,7 @@ grep -A 5 "^## User Message" "${entry.transcriptPath}"
|
|
|
1431
1434
|
`;
|
|
1432
1435
|
}
|
|
1433
1436
|
function formatTokens(entry) {
|
|
1434
|
-
if (entry.tokensInput !==
|
|
1437
|
+
if (entry.tokensInput !== undefined && entry.tokensOutput !== undefined) {
|
|
1435
1438
|
const total = entry.tokensInput + entry.tokensOutput;
|
|
1436
1439
|
return `${total.toLocaleString()} (${entry.tokensInput.toLocaleString()} in, ${entry.tokensOutput.toLocaleString()} out)`;
|
|
1437
1440
|
}
|
|
@@ -1444,7 +1447,7 @@ function ensureDateDir2(dateDir) {
|
|
|
1444
1447
|
}
|
|
1445
1448
|
function calculateDuration(startTime, endTime) {
|
|
1446
1449
|
const diffMs = endTime.getTime() - startTime.getTime();
|
|
1447
|
-
const diffMinutes = Math.floor(diffMs /
|
|
1450
|
+
const diffMinutes = Math.floor(diffMs / 1000 / 60);
|
|
1448
1451
|
if (diffMinutes < 60) {
|
|
1449
1452
|
return `${diffMinutes} minute${diffMinutes !== 1 ? "s" : ""}`;
|
|
1450
1453
|
}
|
|
@@ -1573,7 +1576,7 @@ The tool will:
|
|
|
1573
1576
|
note: tool.schema.string().optional().describe("Optional note about what was accomplished")
|
|
1574
1577
|
},
|
|
1575
1578
|
async execute(args, context) {
|
|
1576
|
-
const startTime =
|
|
1579
|
+
const startTime = new Date;
|
|
1577
1580
|
const { sessionID } = context;
|
|
1578
1581
|
try {
|
|
1579
1582
|
const { data: messages } = await ctx.client.session.messages({
|
|
@@ -1589,7 +1592,7 @@ The tool will:
|
|
|
1589
1592
|
const title = slugToTitle(slug);
|
|
1590
1593
|
const date = getCurrentDate();
|
|
1591
1594
|
const counter = await getNextCounter(date);
|
|
1592
|
-
const endTime =
|
|
1595
|
+
const endTime = new Date;
|
|
1593
1596
|
const duration = calculateDuration(startTime, endTime);
|
|
1594
1597
|
const transcriptPath = getTranscriptPath(sessionID);
|
|
1595
1598
|
const summary = await generateSummary(messages, slug);
|
|
@@ -1669,9 +1672,7 @@ async function generateSummary(messages, slug) {
|
|
|
1669
1672
|
const messageCount = messages.length;
|
|
1670
1673
|
const userMessages = messages.filter((m) => m.info.role === "user").length;
|
|
1671
1674
|
const assistantMessages = messages.filter((m) => m.info.role === "assistant").length;
|
|
1672
|
-
return `Work session focused on: ${slugToTitle(
|
|
1673
|
-
slug
|
|
1674
|
-
)}. Exchanged ${messageCount} messages (${userMessages} user, ${assistantMessages} assistant). See transcript for full details.`;
|
|
1675
|
+
return `Work session focused on: ${slugToTitle(slug)}. Exchanged ${messageCount} messages (${userMessages} user, ${assistantMessages} assistant). See transcript for full details.`;
|
|
1675
1676
|
}
|
|
1676
1677
|
function buildContextString(messages, note) {
|
|
1677
1678
|
if (note) {
|
|
@@ -1691,11 +1692,7 @@ function buildContextString(messages, note) {
|
|
|
1691
1692
|
import * as fs3 from "fs";
|
|
1692
1693
|
import * as path5 from "path";
|
|
1693
1694
|
import { fileURLToPath } from "url";
|
|
1694
|
-
var TEMPLATES_DIR = path5.join(
|
|
1695
|
-
path5.dirname(fileURLToPath(import.meta.url)),
|
|
1696
|
-
"templates",
|
|
1697
|
-
".openfleet"
|
|
1698
|
-
);
|
|
1695
|
+
var TEMPLATES_DIR = path5.join(path5.dirname(fileURLToPath(import.meta.url)), "templates", ".openfleet");
|
|
1699
1696
|
function initializeDirectories() {
|
|
1700
1697
|
if (fs3.existsSync(OPENFLEET_DIR)) {
|
|
1701
1698
|
return;
|
|
@@ -1734,8 +1731,7 @@ function showSpinnerToast(ctx, options) {
|
|
|
1734
1731
|
variant: options.variant || "info",
|
|
1735
1732
|
duration: frameInterval + 50
|
|
1736
1733
|
}
|
|
1737
|
-
}).catch(() => {
|
|
1738
|
-
});
|
|
1734
|
+
}).catch(() => {});
|
|
1739
1735
|
await new Promise((resolve) => setTimeout(resolve, frameInterval));
|
|
1740
1736
|
frameIndex++;
|
|
1741
1737
|
}
|
|
@@ -1760,9 +1756,11 @@ var OpenfleetPlugin = async (ctx) => {
|
|
|
1760
1756
|
configureAgents(config);
|
|
1761
1757
|
},
|
|
1762
1758
|
event: async ({ event }) => {
|
|
1763
|
-
if (event.type !== "session.created")
|
|
1759
|
+
if (event.type !== "session.created")
|
|
1760
|
+
return;
|
|
1764
1761
|
const props = event.properties;
|
|
1765
|
-
if (props?.info?.parentID)
|
|
1762
|
+
if (props?.info?.parentID)
|
|
1763
|
+
return;
|
|
1766
1764
|
setTimeout(async () => {
|
|
1767
1765
|
await showFleetToast(ctx);
|
|
1768
1766
|
}, 0);
|
|
@@ -1776,10 +1774,10 @@ async function showFleetToast(ctx) {
|
|
|
1776
1774
|
message: "The Openfleet plugin is now at play.",
|
|
1777
1775
|
variant: "info"
|
|
1778
1776
|
});
|
|
1779
|
-
await sleep(
|
|
1777
|
+
await sleep(5000);
|
|
1780
1778
|
await stopSpinner();
|
|
1781
1779
|
}
|
|
1782
|
-
var
|
|
1780
|
+
var src_default = OpenfleetPlugin;
|
|
1783
1781
|
export {
|
|
1784
|
-
|
|
1782
|
+
src_default as default
|
|
1785
1783
|
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import Anthropic from "@anthropic-ai/sdk";
|
|
2
|
+
/**
|
|
3
|
+
* Gets or creates a singleton Anthropic client instance.
|
|
4
|
+
*
|
|
5
|
+
* This ensures we reuse the same client across the application
|
|
6
|
+
* instead of creating multiple instances.
|
|
7
|
+
*
|
|
8
|
+
* Example:
|
|
9
|
+
* >>> const client = getAnthropicClient();
|
|
10
|
+
* >>> const response = await client.messages.create({...});
|
|
11
|
+
*/
|
|
12
|
+
export declare function getAnthropicClient(): Anthropic;
|
package/dist/logger.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
type LogLevel = "debug" | "info" | "warn" | "error";
|
|
2
|
+
declare const logger: {
|
|
3
|
+
debug: (msg: string, ...args: unknown[]) => void;
|
|
4
|
+
info: (msg: string, ...args: unknown[]) => void;
|
|
5
|
+
warn: (msg: string, ...args: unknown[]) => void;
|
|
6
|
+
error: (msg: string, ...args: unknown[]) => void;
|
|
7
|
+
};
|
|
8
|
+
export { logger, type LogLevel };
|
package/dist/models.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export declare const models: {
|
|
2
|
+
readonly bedrock: {
|
|
3
|
+
readonly sonnet: "amazon-bedrock/anthropic.claude-sonnet-4-5-20250929-v1:0";
|
|
4
|
+
readonly opus: "amazon-bedrock/anthropic.claude-opus-4-5-20251101-v1:0";
|
|
5
|
+
readonly haiku: "amazon-bedrock/anthropic.claude-haiku-4-5-20251001-v1:0";
|
|
6
|
+
};
|
|
7
|
+
readonly anthropic: {
|
|
8
|
+
readonly sonnet: "anthropic/claude-sonnet-4-5";
|
|
9
|
+
readonly opus: "anthropic/claude-opus-4-5";
|
|
10
|
+
readonly haiku: "anthropic/claude-haiku-4-5";
|
|
11
|
+
};
|
|
12
|
+
readonly openai: {
|
|
13
|
+
readonly gpt5: "openai/gpt-5.2";
|
|
14
|
+
readonly o4Mini: "openai/o4-mini";
|
|
15
|
+
readonly o3: "openai/o3";
|
|
16
|
+
};
|
|
17
|
+
readonly google: {
|
|
18
|
+
readonly gemini3Pro: "google/gemini-3-pro-high";
|
|
19
|
+
readonly gemini3Flash: "google/gemini-3-flash";
|
|
20
|
+
readonly gemini25Pro: "google/gemini-2.5-pro";
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
export declare const defaultModel: "anthropic/claude-sonnet-4-5";
|
|
24
|
+
export declare const smallModel: "amazon-bedrock/anthropic.claude-haiku-4-5-20251001-v1:0";
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gets the next counter for a given date.
|
|
3
|
+
*
|
|
4
|
+
* This function:
|
|
5
|
+
* 1. looks inside the date subdirectory (sessions/YYYY-MM-DD/)
|
|
6
|
+
* 2. extracts all counters from files matching NNN_slug.md pattern
|
|
7
|
+
* 3. finds the highest counter
|
|
8
|
+
* 4. returns next counter (highest + 1), zero-padded to 3 digits
|
|
9
|
+
*
|
|
10
|
+
* Example:
|
|
11
|
+
* >>> const counter = await getNextCounter("2025-12-23");
|
|
12
|
+
* >>> counter
|
|
13
|
+
* '003'
|
|
14
|
+
*/
|
|
15
|
+
export declare function getNextCounter(date: string): Promise<string>;
|
|
16
|
+
/**
|
|
17
|
+
* Gets current date in YYYY-MM-DD format (UTC).
|
|
18
|
+
*/
|
|
19
|
+
export declare function getCurrentDate(): string;
|
|
20
|
+
/**
|
|
21
|
+
* Validates date format (YYYY-MM-DD).
|
|
22
|
+
*/
|
|
23
|
+
export declare function isValidDateFormat(date: string): boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Gets all session files for a specific date.
|
|
26
|
+
*/
|
|
27
|
+
export declare function getSessionsForDate(date: string): string[];
|
|
28
|
+
/**
|
|
29
|
+
* Builds filename from counter and slug (NNN_slug.md format).
|
|
30
|
+
*/
|
|
31
|
+
export declare function buildFilename(counter: string, slug: string): string;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { PluginInput } from "@opencode-ai/plugin";
|
|
2
|
+
export declare function createSaveConversationTool(ctx: PluginInput): {
|
|
3
|
+
description: string;
|
|
4
|
+
args: {
|
|
5
|
+
note: import("zod").ZodOptional<import("zod").ZodString>;
|
|
6
|
+
};
|
|
7
|
+
execute(args: {
|
|
8
|
+
note?: string | undefined;
|
|
9
|
+
}, context: import("@opencode-ai/plugin").ToolContext): Promise<string>;
|
|
10
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { SessionEntry } from "./types";
|
|
2
|
+
/**
|
|
3
|
+
* Writes a session entry to the sessions directory.
|
|
4
|
+
*
|
|
5
|
+
* This function:
|
|
6
|
+
* 1. ensures date subdirectory exists (sessions/YYYY-MM-DD/)
|
|
7
|
+
* 2. builds filename from counter and slug (NNN_slug.md)
|
|
8
|
+
* 3. generates enhanced session content
|
|
9
|
+
* 4. writes file atomically
|
|
10
|
+
* 5. returns full file path
|
|
11
|
+
*/
|
|
12
|
+
export declare function writeSession(entry: SessionEntry): string;
|
|
13
|
+
/**
|
|
14
|
+
* Calculates session duration from timestamp metadata.
|
|
15
|
+
* Returns formatted string like "45 minutes" or "2 hours 15 minutes".
|
|
16
|
+
*/
|
|
17
|
+
export declare function calculateDuration(startTime: Date, endTime: Date): string;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { SlugContext } from "./types";
|
|
2
|
+
/**
|
|
3
|
+
* Generates a semantic slug using simple context string.
|
|
4
|
+
*
|
|
5
|
+
* This function takes a context string (usually from user's note or session summary)
|
|
6
|
+
* and generates a kebab-case slug using Claude Haiku.
|
|
7
|
+
*
|
|
8
|
+
* Example:
|
|
9
|
+
* >>> const slug = await generateSlug("Implemented user authentication system");
|
|
10
|
+
* >>> slug
|
|
11
|
+
* 'implement-user-auth'
|
|
12
|
+
*/
|
|
13
|
+
export declare function generateSlug(contextString: string, context?: SlugContext): Promise<string>;
|
|
14
|
+
export declare function slugToTitle(slug: string): string;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export interface SaveConversationArgs {
|
|
2
|
+
note?: string;
|
|
3
|
+
}
|
|
4
|
+
export interface SessionEntry {
|
|
5
|
+
sessionID: string;
|
|
6
|
+
transcriptPath: string;
|
|
7
|
+
savedAt: string;
|
|
8
|
+
date: string;
|
|
9
|
+
counter: string;
|
|
10
|
+
slug: string;
|
|
11
|
+
title: string;
|
|
12
|
+
summary: string;
|
|
13
|
+
note?: string;
|
|
14
|
+
duration?: string;
|
|
15
|
+
messageCount: number;
|
|
16
|
+
tokensBefore: number;
|
|
17
|
+
tokensInput?: number;
|
|
18
|
+
tokensOutput?: number;
|
|
19
|
+
}
|
|
20
|
+
export interface SlugContext {
|
|
21
|
+
maxMessages?: number;
|
|
22
|
+
maxContextChars?: number;
|
|
23
|
+
}
|
|
24
|
+
export interface CounterInfo {
|
|
25
|
+
date: string;
|
|
26
|
+
counter: string;
|
|
27
|
+
highestFound: number;
|
|
28
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { PluginInput } from "@opencode-ai/plugin";
|
|
2
|
+
export declare function createTranscriptHooks(ctx: PluginInput): {
|
|
3
|
+
"chat.message": (input: {
|
|
4
|
+
sessionID: string;
|
|
5
|
+
}, output: {
|
|
6
|
+
message: unknown;
|
|
7
|
+
parts: unknown[];
|
|
8
|
+
}) => Promise<void>;
|
|
9
|
+
"tool.execute.before": (input: {
|
|
10
|
+
sessionID: string;
|
|
11
|
+
tool: string;
|
|
12
|
+
callID: string;
|
|
13
|
+
}, output: {
|
|
14
|
+
args: unknown;
|
|
15
|
+
}) => Promise<void>;
|
|
16
|
+
"tool.execute.after": (input: {
|
|
17
|
+
sessionID: string;
|
|
18
|
+
tool: string;
|
|
19
|
+
callID: string;
|
|
20
|
+
}, output: {
|
|
21
|
+
title: string;
|
|
22
|
+
output: string;
|
|
23
|
+
metadata?: unknown;
|
|
24
|
+
}) => Promise<void>;
|
|
25
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Part, UserMessage } from "@opencode-ai/sdk";
|
|
2
|
+
import { getTranscriptPath } from "./writer";
|
|
3
|
+
export interface SessionInfo {
|
|
4
|
+
sessionID: string;
|
|
5
|
+
parentID?: string;
|
|
6
|
+
}
|
|
7
|
+
export declare function recordUserMessage(session: SessionInfo, message: UserMessage, parts: Part[]): Promise<void>;
|
|
8
|
+
export declare function recordToolUse(session: SessionInfo, tool: string, callID: string, args: unknown): Promise<void>;
|
|
9
|
+
export declare function recordToolResult(session: SessionInfo, tool: string, callID: string, output: {
|
|
10
|
+
title: string;
|
|
11
|
+
output: string;
|
|
12
|
+
metadata?: unknown;
|
|
13
|
+
}): Promise<void>;
|
|
14
|
+
export { getTranscriptPath };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export interface BaseEntry {
|
|
2
|
+
timestamp: string;
|
|
3
|
+
}
|
|
4
|
+
export interface UserMessageEntry extends BaseEntry {
|
|
5
|
+
type: "user";
|
|
6
|
+
content: string;
|
|
7
|
+
parts: unknown[];
|
|
8
|
+
}
|
|
9
|
+
export interface ToolUseEntry extends BaseEntry {
|
|
10
|
+
type: "tool_use";
|
|
11
|
+
tool: string;
|
|
12
|
+
callID: string;
|
|
13
|
+
input: unknown;
|
|
14
|
+
}
|
|
15
|
+
export interface ToolResultEntry extends BaseEntry {
|
|
16
|
+
type: "tool_result";
|
|
17
|
+
tool: string;
|
|
18
|
+
callID: string;
|
|
19
|
+
input: unknown;
|
|
20
|
+
output: unknown;
|
|
21
|
+
metadata?: unknown;
|
|
22
|
+
}
|
|
23
|
+
export type TranscriptEntry = UserMessageEntry | ToolUseEntry | ToolResultEntry;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function initializeDirectories(): void;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { PluginInput } from "@opencode-ai/plugin";
|
|
2
|
+
type ToastVariant = "info" | "success" | "warning" | "error";
|
|
3
|
+
interface ToastOptions {
|
|
4
|
+
title: string;
|
|
5
|
+
message: string;
|
|
6
|
+
variant: ToastVariant;
|
|
7
|
+
duration: number;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Shows a static toast notification.
|
|
11
|
+
*/
|
|
12
|
+
export declare function showToast(ctx: PluginInput, options: ToastOptions): Promise<void>;
|
|
13
|
+
/**
|
|
14
|
+
* Shows an animated toast with spinner until stopped.
|
|
15
|
+
*
|
|
16
|
+
* Returns a function to stop the spinner and optionally show a final toast.
|
|
17
|
+
*/
|
|
18
|
+
export declare function showSpinnerToast(ctx: PluginInput, options: {
|
|
19
|
+
title: string;
|
|
20
|
+
message: string;
|
|
21
|
+
variant?: ToastVariant;
|
|
22
|
+
}): () => Promise<void>;
|
|
23
|
+
/**
|
|
24
|
+
* Shows spinner toast until promise resolves, then shows completion toast.
|
|
25
|
+
*/
|
|
26
|
+
export declare function showSpinnerUntil<T>(ctx: PluginInput, promise: Promise<T>, options: {
|
|
27
|
+
spinner: {
|
|
28
|
+
title: string;
|
|
29
|
+
message: string;
|
|
30
|
+
};
|
|
31
|
+
success: {
|
|
32
|
+
title: string;
|
|
33
|
+
message: string;
|
|
34
|
+
};
|
|
35
|
+
error: {
|
|
36
|
+
title: string;
|
|
37
|
+
message: string;
|
|
38
|
+
};
|
|
39
|
+
}): Promise<T>;
|
|
40
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openfleet",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.4",
|
|
4
4
|
"description": "SPARR framework agents + infinite context for OpenCode",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"url": "git+https://github.com/scottsus/openfleet.git"
|
|
20
20
|
},
|
|
21
21
|
"scripts": {
|
|
22
|
-
"build": "rm -rf dist &&
|
|
22
|
+
"build": "rm -rf dist && bun build src/index.ts --outdir dist --target bun --format esm --external @opencode-ai/plugin --external @opencode-ai/sdk --external @anthropic-ai/sdk && tsc --emitDeclarationOnly && cp -r src/templates dist/",
|
|
23
23
|
"dev": "tsup src/index.ts --format esm --dts --watch",
|
|
24
24
|
"typecheck": "tsc --noEmit",
|
|
25
25
|
"format": "prettier --write .",
|
|
@@ -32,10 +32,13 @@
|
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
34
|
"@anthropic-ai/sdk": "^0.71.2",
|
|
35
|
-
"@opencode-ai/plugin": "
|
|
36
|
-
"@opencode-ai/sdk": "
|
|
35
|
+
"@opencode-ai/plugin": "~1.0.224",
|
|
36
|
+
"@opencode-ai/sdk": "~1.0.224"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
|
+
"@anthropic-ai/sdk": "^0.71.2",
|
|
40
|
+
"@opencode-ai/plugin": "~1.0.224",
|
|
41
|
+
"@opencode-ai/sdk": "~1.0.224",
|
|
39
42
|
"@ianvs/prettier-plugin-sort-imports": "^4.7.0",
|
|
40
43
|
"@types/node": "^22",
|
|
41
44
|
"husky": "^9.1.7",
|