@devness/useai 0.6.11 → 0.6.12
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 +169 -9
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -684,7 +684,7 @@ var VERSION;
|
|
|
684
684
|
var init_version = __esm({
|
|
685
685
|
"../shared/dist/constants/version.js"() {
|
|
686
686
|
"use strict";
|
|
687
|
-
VERSION = "0.6.
|
|
687
|
+
VERSION = "0.6.12";
|
|
688
688
|
}
|
|
689
689
|
});
|
|
690
690
|
|
|
@@ -34589,6 +34589,12 @@ var init_session_state = __esm({
|
|
|
34589
34589
|
modelId;
|
|
34590
34590
|
/** Token estimates for the useai_start tool call. */
|
|
34591
34591
|
startCallTokensEst;
|
|
34592
|
+
/** Whether a UseAI session is actively in-progress (between useai_start and useai_end). */
|
|
34593
|
+
inProgress;
|
|
34594
|
+
/** Timestamp when the current session was marked in-progress. */
|
|
34595
|
+
inProgressSince;
|
|
34596
|
+
/** Session ID that was auto-sealed by seal-active hook (for useai_end fallback). */
|
|
34597
|
+
autoSealedSessionId;
|
|
34592
34598
|
constructor() {
|
|
34593
34599
|
this.sessionId = generateSessionId();
|
|
34594
34600
|
this.conversationId = generateSessionId();
|
|
@@ -34596,6 +34602,9 @@ var init_session_state = __esm({
|
|
|
34596
34602
|
this.mcpSessionId = null;
|
|
34597
34603
|
this.modelId = null;
|
|
34598
34604
|
this.startCallTokensEst = null;
|
|
34605
|
+
this.inProgress = false;
|
|
34606
|
+
this.inProgressSince = null;
|
|
34607
|
+
this.autoSealedSessionId = null;
|
|
34599
34608
|
this.sessionStartTime = Date.now();
|
|
34600
34609
|
this.heartbeatCount = 0;
|
|
34601
34610
|
this.sessionRecordCount = 0;
|
|
@@ -34622,6 +34631,8 @@ var init_session_state = __esm({
|
|
|
34622
34631
|
this.sessionPromptWordCount = null;
|
|
34623
34632
|
this.modelId = null;
|
|
34624
34633
|
this.startCallTokensEst = null;
|
|
34634
|
+
this.inProgress = false;
|
|
34635
|
+
this.inProgressSince = null;
|
|
34625
34636
|
this.detectProject();
|
|
34626
34637
|
}
|
|
34627
34638
|
detectProject() {
|
|
@@ -34715,7 +34726,7 @@ __export(register_tools_exports, {
|
|
|
34715
34726
|
registerTools: () => registerTools
|
|
34716
34727
|
});
|
|
34717
34728
|
import { createHash as createHash3, randomUUID as randomUUID3 } from "crypto";
|
|
34718
|
-
import { existsSync as existsSync8, renameSync as renameSync2 } from "fs";
|
|
34729
|
+
import { existsSync as existsSync8, readFileSync as readFileSync5, renameSync as renameSync2 } from "fs";
|
|
34719
34730
|
import { join as join7 } from "path";
|
|
34720
34731
|
function coerceJsonString(schema) {
|
|
34721
34732
|
return external_exports.preprocess((val) => {
|
|
@@ -34750,6 +34761,130 @@ function resolveClient2(server2, session2) {
|
|
|
34750
34761
|
}
|
|
34751
34762
|
session2.setClient(detectClient());
|
|
34752
34763
|
}
|
|
34764
|
+
function enrichAutoSealedSession(sealedSessionId, session2, args) {
|
|
34765
|
+
const sealedPath = join7(SEALED_DIR, `${sealedSessionId}.jsonl`);
|
|
34766
|
+
const activePath = join7(ACTIVE_DIR, `${sealedSessionId}.jsonl`);
|
|
34767
|
+
const chainPath = existsSync8(sealedPath) ? sealedPath : existsSync8(activePath) ? activePath : null;
|
|
34768
|
+
if (!chainPath) {
|
|
34769
|
+
return "No active session to end (already sealed or never started).";
|
|
34770
|
+
}
|
|
34771
|
+
let startData = {};
|
|
34772
|
+
let duration3 = 0;
|
|
34773
|
+
let endedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
34774
|
+
let startedAt2 = endedAt;
|
|
34775
|
+
try {
|
|
34776
|
+
const content = readFileSync5(chainPath, "utf-8").trim();
|
|
34777
|
+
const lines = content.split("\n").filter(Boolean);
|
|
34778
|
+
if (lines.length > 0) {
|
|
34779
|
+
const firstRecord = JSON.parse(lines[0]);
|
|
34780
|
+
startData = firstRecord.data;
|
|
34781
|
+
startedAt2 = firstRecord.timestamp;
|
|
34782
|
+
const lastRecord = JSON.parse(lines[lines.length - 1]);
|
|
34783
|
+
if (lastRecord.type === "session_seal" && lastRecord.data["seal"]) {
|
|
34784
|
+
try {
|
|
34785
|
+
const sealObj = JSON.parse(lastRecord.data["seal"]);
|
|
34786
|
+
duration3 = sealObj.duration_seconds ?? Math.round((new Date(lastRecord.timestamp).getTime() - new Date(startedAt2).getTime()) / 1e3);
|
|
34787
|
+
endedAt = sealObj.ended_at ?? lastRecord.timestamp;
|
|
34788
|
+
} catch {
|
|
34789
|
+
duration3 = Math.round((new Date(lastRecord.timestamp).getTime() - new Date(startedAt2).getTime()) / 1e3);
|
|
34790
|
+
endedAt = lastRecord.timestamp;
|
|
34791
|
+
}
|
|
34792
|
+
} else {
|
|
34793
|
+
duration3 = Math.round((new Date(lastRecord.timestamp).getTime() - new Date(startedAt2).getTime()) / 1e3);
|
|
34794
|
+
endedAt = lastRecord.timestamp;
|
|
34795
|
+
}
|
|
34796
|
+
}
|
|
34797
|
+
} catch {
|
|
34798
|
+
return "No active session to end (chain file unreadable).";
|
|
34799
|
+
}
|
|
34800
|
+
const taskType = args.task_type ?? startData.task_type ?? "coding";
|
|
34801
|
+
const languages = args.languages ?? [];
|
|
34802
|
+
const filesTouched = args.files_touched_count ?? 0;
|
|
34803
|
+
let milestoneCount = 0;
|
|
34804
|
+
if (args.milestones && args.milestones.length > 0) {
|
|
34805
|
+
const config2 = getConfig();
|
|
34806
|
+
if (config2.milestone_tracking) {
|
|
34807
|
+
const durationMinutes = Math.round(duration3 / 60);
|
|
34808
|
+
const allMilestones = getMilestones();
|
|
34809
|
+
for (const m of args.milestones) {
|
|
34810
|
+
allMilestones.push({
|
|
34811
|
+
id: `m_${randomUUID3().slice(0, 8)}`,
|
|
34812
|
+
session_id: sealedSessionId,
|
|
34813
|
+
title: m.title,
|
|
34814
|
+
private_title: m.private_title,
|
|
34815
|
+
project: startData.project ?? session2.project ?? void 0,
|
|
34816
|
+
category: m.category,
|
|
34817
|
+
complexity: m.complexity ?? "medium",
|
|
34818
|
+
duration_minutes: durationMinutes,
|
|
34819
|
+
languages,
|
|
34820
|
+
client: startData.client ?? session2.clientName,
|
|
34821
|
+
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
34822
|
+
published: false,
|
|
34823
|
+
published_at: null,
|
|
34824
|
+
chain_hash: ""
|
|
34825
|
+
});
|
|
34826
|
+
milestoneCount++;
|
|
34827
|
+
}
|
|
34828
|
+
writeJson(MILESTONES_FILE, allMilestones);
|
|
34829
|
+
}
|
|
34830
|
+
}
|
|
34831
|
+
let sessionScore;
|
|
34832
|
+
let frameworkId;
|
|
34833
|
+
if (args.evaluation) {
|
|
34834
|
+
const config2 = getConfig();
|
|
34835
|
+
const framework = getFramework(config2.evaluation_framework);
|
|
34836
|
+
sessionScore = Math.round(framework.computeSessionScore(args.evaluation));
|
|
34837
|
+
frameworkId = framework.id;
|
|
34838
|
+
}
|
|
34839
|
+
const richSeal = {
|
|
34840
|
+
session_id: sealedSessionId,
|
|
34841
|
+
conversation_id: startData.conversation_id,
|
|
34842
|
+
conversation_index: startData.conversation_index,
|
|
34843
|
+
client: startData.client ?? session2.clientName,
|
|
34844
|
+
task_type: taskType,
|
|
34845
|
+
languages,
|
|
34846
|
+
files_touched: filesTouched,
|
|
34847
|
+
project: startData.project ?? session2.project ?? void 0,
|
|
34848
|
+
title: startData.title ?? void 0,
|
|
34849
|
+
private_title: startData.private_title ?? void 0,
|
|
34850
|
+
model: startData.model ?? session2.modelId ?? void 0,
|
|
34851
|
+
evaluation: args.evaluation ?? void 0,
|
|
34852
|
+
session_score: sessionScore,
|
|
34853
|
+
evaluation_framework: frameworkId,
|
|
34854
|
+
started_at: startedAt2,
|
|
34855
|
+
ended_at: endedAt,
|
|
34856
|
+
duration_seconds: duration3,
|
|
34857
|
+
heartbeat_count: 0,
|
|
34858
|
+
record_count: 0,
|
|
34859
|
+
chain_start_hash: "",
|
|
34860
|
+
chain_end_hash: "",
|
|
34861
|
+
seal_signature: ""
|
|
34862
|
+
};
|
|
34863
|
+
const allSessions = getSessions();
|
|
34864
|
+
const existingIdx = allSessions.findIndex((s) => s.session_id === sealedSessionId);
|
|
34865
|
+
if (existingIdx >= 0) {
|
|
34866
|
+
const existing = allSessions[existingIdx];
|
|
34867
|
+
allSessions[existingIdx] = {
|
|
34868
|
+
...existing,
|
|
34869
|
+
// Enrich with data from useai_end call
|
|
34870
|
+
task_type: taskType,
|
|
34871
|
+
languages,
|
|
34872
|
+
files_touched: filesTouched,
|
|
34873
|
+
evaluation: args.evaluation ?? existing.evaluation,
|
|
34874
|
+
session_score: sessionScore ?? existing.session_score,
|
|
34875
|
+
evaluation_framework: frameworkId ?? existing.evaluation_framework
|
|
34876
|
+
};
|
|
34877
|
+
} else {
|
|
34878
|
+
allSessions.push(richSeal);
|
|
34879
|
+
}
|
|
34880
|
+
writeJson(SESSIONS_FILE, allSessions);
|
|
34881
|
+
const durationStr = formatDuration(duration3);
|
|
34882
|
+
const langStr = languages.length > 0 ? ` using ${languages.join(", ")}` : "";
|
|
34883
|
+
const milestoneStr = milestoneCount > 0 ? ` \xB7 ${milestoneCount} milestone${milestoneCount > 1 ? "s" : ""} recorded` : "";
|
|
34884
|
+
const evalStr = args.evaluation ? ` \xB7 eval: ${args.evaluation.task_outcome} (prompt: ${args.evaluation.prompt_quality}/5)` : "";
|
|
34885
|
+
const scoreStr = sessionScore !== void 0 ? ` \xB7 score: ${sessionScore}/100 (${frameworkId})` : "";
|
|
34886
|
+
return `Session ended (enriched auto-seal): ${durationStr} ${taskType}${langStr}${milestoneStr}${evalStr}${scoreStr}`;
|
|
34887
|
+
}
|
|
34753
34888
|
function registerTools(server2, session2, opts) {
|
|
34754
34889
|
server2.tool(
|
|
34755
34890
|
"useai_start",
|
|
@@ -34768,6 +34903,7 @@ function registerTools(server2, session2, opts) {
|
|
|
34768
34903
|
opts.sealBeforeReset();
|
|
34769
34904
|
}
|
|
34770
34905
|
session2.reset();
|
|
34906
|
+
session2.autoSealedSessionId = null;
|
|
34771
34907
|
resolveClient2(server2, session2);
|
|
34772
34908
|
if (conversation_id) {
|
|
34773
34909
|
if (conversation_id !== prevConvId) {
|
|
@@ -34795,6 +34931,8 @@ function registerTools(server2, session2, opts) {
|
|
|
34795
34931
|
if (private_title) chainData.private_title = private_title;
|
|
34796
34932
|
if (model) chainData.model = model;
|
|
34797
34933
|
const record2 = session2.appendToChain("session_start", chainData);
|
|
34934
|
+
session2.inProgress = true;
|
|
34935
|
+
session2.inProgressSince = Date.now();
|
|
34798
34936
|
writeMcpMapping(session2.mcpSessionId, session2.sessionId);
|
|
34799
34937
|
const responseText = `useai session started \u2014 ${session2.sessionTaskType} on ${session2.clientName} \xB7 ${session2.sessionId.slice(0, 8)} \xB7 conv ${session2.conversationId.slice(0, 8)}#${session2.conversationIndex} \xB7 ${session2.signingAvailable ? "signed" : "unsigned"}`;
|
|
34800
34938
|
const paramsJson = JSON.stringify({ task_type, title, private_title, project, model });
|
|
@@ -34862,6 +35000,17 @@ function registerTools(server2, session2, opts) {
|
|
|
34862
35000
|
},
|
|
34863
35001
|
async ({ task_type, languages, files_touched_count, milestones: milestonesInput, evaluation }) => {
|
|
34864
35002
|
if (session2.sessionRecordCount === 0) {
|
|
35003
|
+
if (session2.autoSealedSessionId) {
|
|
35004
|
+
const enrichResult = enrichAutoSealedSession(
|
|
35005
|
+
session2.autoSealedSessionId,
|
|
35006
|
+
session2,
|
|
35007
|
+
{ task_type, languages, files_touched_count, milestones: milestonesInput, evaluation }
|
|
35008
|
+
);
|
|
35009
|
+
session2.autoSealedSessionId = null;
|
|
35010
|
+
session2.inProgress = false;
|
|
35011
|
+
session2.inProgressSince = null;
|
|
35012
|
+
return { content: [{ type: "text", text: enrichResult }] };
|
|
35013
|
+
}
|
|
34865
35014
|
return {
|
|
34866
35015
|
content: [{ type: "text", text: "No active session to end (already sealed or never started)." }]
|
|
34867
35016
|
};
|
|
@@ -35009,6 +35158,8 @@ function registerTools(server2, session2, opts) {
|
|
|
35009
35158
|
const sessions2 = getSessions().filter((s) => s.session_id !== seal.session_id);
|
|
35010
35159
|
sessions2.push(seal);
|
|
35011
35160
|
writeJson(SESSIONS_FILE, sessions2);
|
|
35161
|
+
session2.inProgress = false;
|
|
35162
|
+
session2.inProgressSince = null;
|
|
35012
35163
|
return {
|
|
35013
35164
|
content: [
|
|
35014
35165
|
{
|
|
@@ -35521,7 +35672,7 @@ __export(daemon_exports, {
|
|
|
35521
35672
|
});
|
|
35522
35673
|
import { createServer } from "http";
|
|
35523
35674
|
import { createHash as createHash4, randomUUID as randomUUID4 } from "crypto";
|
|
35524
|
-
import { existsSync as existsSync10, readdirSync, readFileSync as
|
|
35675
|
+
import { existsSync as existsSync10, readdirSync, readFileSync as readFileSync6, appendFileSync as appendFileSync2, renameSync as renameSync3, writeFileSync as writeFileSync5, unlinkSync as unlinkSync5, statSync } from "fs";
|
|
35525
35676
|
import { join as join9 } from "path";
|
|
35526
35677
|
function getActiveUseaiSessionIds() {
|
|
35527
35678
|
const ids = /* @__PURE__ */ new Set();
|
|
@@ -35534,7 +35685,7 @@ function sealOrphanFile(sessionId) {
|
|
|
35534
35685
|
const filePath = join9(ACTIVE_DIR, `${sessionId}.jsonl`);
|
|
35535
35686
|
if (!existsSync10(filePath)) return;
|
|
35536
35687
|
try {
|
|
35537
|
-
const content =
|
|
35688
|
+
const content = readFileSync6(filePath, "utf-8").trim();
|
|
35538
35689
|
if (!content) return;
|
|
35539
35690
|
const lines = content.split("\n").filter(Boolean);
|
|
35540
35691
|
if (lines.length === 0) return;
|
|
@@ -35772,8 +35923,10 @@ function autoSealSession(active) {
|
|
|
35772
35923
|
upsertSessionSeal(seal);
|
|
35773
35924
|
}
|
|
35774
35925
|
function sealSessionData(active) {
|
|
35926
|
+
const sealedId = active.session.sessionId;
|
|
35775
35927
|
autoSealSession(active);
|
|
35776
35928
|
active.session.reset();
|
|
35929
|
+
active.session.autoSealedSessionId = sealedId;
|
|
35777
35930
|
}
|
|
35778
35931
|
function resetIdleTimer(sessionId) {
|
|
35779
35932
|
const active = sessions.get(sessionId);
|
|
@@ -35877,7 +36030,7 @@ function readChainMetadata(useaiSessionId) {
|
|
|
35877
36030
|
const chainPath = existsSync10(activePath) ? activePath : existsSync10(sealedPath) ? sealedPath : null;
|
|
35878
36031
|
if (!chainPath) return null;
|
|
35879
36032
|
try {
|
|
35880
|
-
const firstLine =
|
|
36033
|
+
const firstLine = readFileSync6(chainPath, "utf-8").split("\n")[0];
|
|
35881
36034
|
if (!firstLine) return null;
|
|
35882
36035
|
const record2 = JSON.parse(firstLine);
|
|
35883
36036
|
const d = record2.data;
|
|
@@ -35946,7 +36099,7 @@ function recoverHeartbeat(staleMcpSessionId, rpcId, res) {
|
|
|
35946
36099
|
return true;
|
|
35947
36100
|
}
|
|
35948
36101
|
try {
|
|
35949
|
-
const content =
|
|
36102
|
+
const content = readFileSync6(chainPath, "utf-8").trim();
|
|
35950
36103
|
const lines = content.split("\n").filter(Boolean);
|
|
35951
36104
|
if (lines.length === 0) return false;
|
|
35952
36105
|
const firstRecord = JSON.parse(lines[0]);
|
|
@@ -35981,7 +36134,7 @@ function recoverEndSession(staleMcpSessionId, args, rpcId, res) {
|
|
|
35981
36134
|
const chainPath = existsSync10(activePath) ? activePath : existsSync10(sealedPath) ? sealedPath : null;
|
|
35982
36135
|
if (!chainPath) return false;
|
|
35983
36136
|
const isAlreadySealed = chainPath === sealedPath;
|
|
35984
|
-
const content =
|
|
36137
|
+
const content = readFileSync6(chainPath, "utf-8").trim();
|
|
35985
36138
|
if (!content) return false;
|
|
35986
36139
|
const lines = content.split("\n").filter(Boolean);
|
|
35987
36140
|
if (lines.length === 0) return false;
|
|
@@ -36387,8 +36540,14 @@ async function startDaemon(port) {
|
|
|
36387
36540
|
}
|
|
36388
36541
|
if (url.pathname === "/api/seal-active" && req.method === "POST") {
|
|
36389
36542
|
let sealed = 0;
|
|
36543
|
+
let skipped = 0;
|
|
36390
36544
|
for (const [, active] of sessions) {
|
|
36391
36545
|
if (active.session.sessionRecordCount > 0 && !isSessionAlreadySealed(active.session)) {
|
|
36546
|
+
const { inProgress, inProgressSince } = active.session;
|
|
36547
|
+
if (inProgress && inProgressSince && Date.now() - inProgressSince < SEAL_GRACE_MS) {
|
|
36548
|
+
skipped++;
|
|
36549
|
+
continue;
|
|
36550
|
+
}
|
|
36392
36551
|
sealSessionData(active);
|
|
36393
36552
|
sealed++;
|
|
36394
36553
|
}
|
|
@@ -36397,7 +36556,7 @@ async function startDaemon(port) {
|
|
|
36397
36556
|
"Content-Type": "application/json",
|
|
36398
36557
|
"Access-Control-Allow-Origin": "*"
|
|
36399
36558
|
});
|
|
36400
|
-
res.end(JSON.stringify({ sealed }));
|
|
36559
|
+
res.end(JSON.stringify({ sealed, skipped }));
|
|
36401
36560
|
return;
|
|
36402
36561
|
}
|
|
36403
36562
|
if ((url.pathname.startsWith("/api/local/") || url.pathname === "/api/seal-active") && req.method === "OPTIONS") {
|
|
@@ -36554,7 +36713,7 @@ async function startDaemon(port) {
|
|
|
36554
36713
|
console.log(`UseAI daemon listening on http://127.0.0.1:${listenPort}`);
|
|
36555
36714
|
console.log(`PID: ${process.pid}`);
|
|
36556
36715
|
}
|
|
36557
|
-
var IDLE_TIMEOUT_MS, sessions, daemonSigningKey, ORPHAN_SWEEP_INTERVAL_MS, startedAt, updateCheckCache, UPDATE_CHECK_TTL_MS;
|
|
36716
|
+
var IDLE_TIMEOUT_MS, SEAL_GRACE_MS, sessions, daemonSigningKey, ORPHAN_SWEEP_INTERVAL_MS, startedAt, updateCheckCache, UPDATE_CHECK_TTL_MS;
|
|
36558
36717
|
var init_daemon2 = __esm({
|
|
36559
36718
|
"src/daemon.ts"() {
|
|
36560
36719
|
"use strict";
|
|
@@ -36568,6 +36727,7 @@ var init_daemon2 = __esm({
|
|
|
36568
36727
|
init_html();
|
|
36569
36728
|
init_local_api();
|
|
36570
36729
|
IDLE_TIMEOUT_MS = 30 * 60 * 1e3;
|
|
36730
|
+
SEAL_GRACE_MS = 30 * 60 * 1e3;
|
|
36571
36731
|
sessions = /* @__PURE__ */ new Map();
|
|
36572
36732
|
daemonSigningKey = null;
|
|
36573
36733
|
ORPHAN_SWEEP_INTERVAL_MS = 15 * 60 * 1e3;
|