@awak-app/simy-cli 0.5.60 → 0.5.62
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 +17 -0
- package/package.json +2 -2
- package/src/agent.js +27 -7
- package/src/backend-executable.js +36 -21
- package/src/cli-contract.js +1 -1
- package/src/codex-plugins/simy/hooks/delivery-loop.mjs +105 -16
- package/src/delivery-hooks/desktop.js +4 -2
- package/src/delivery-hooks/supervisor.js +28 -19
- package/src/desktop-host.js +69 -24
- package/src/runner.js +8 -2
- package/src/session-supervision/action-association-api.js +1 -1
- package/src/session-supervision/action-association-host.js +65 -35
- package/src/session-supervision/action-association.js +17 -10
- package/src/session-supervision/action-deadline.js +9 -1
- package/src/session-supervision/association-store.js +78 -21
- package/src/session-supervision/automatic-continuation.js +18 -1
- package/src/session-supervision/claude-sessions.js +48 -17
- package/src/session-supervision/codex-app-server.js +35 -14
- package/src/session-supervision/index.js +34 -10
- package/src/sqm/command.js +55 -45
- package/src/sqm/history-store.js +108 -0
- package/src/sqm/index.js +11 -1
- package/src/sqm/review-worker.js +26 -8
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
+
import { mkdir, readFile, readdir, rename, rm, writeFile } from "node:fs/promises";
|
|
3
|
+
import { basename, join } from "node:path";
|
|
4
|
+
import { formatSqmCoverage } from "./report.js";
|
|
5
|
+
import { defaultSessionRoot } from "../session-store.js";
|
|
6
|
+
|
|
7
|
+
const LIMIT = 100;
|
|
8
|
+
const hash = value => createHash("sha256").update(value).digest("hex");
|
|
9
|
+
|
|
10
|
+
export function sqmHistoryScope(webOrigin, session) {
|
|
11
|
+
if (!session?.account_id || !session?.organization_id) return null;
|
|
12
|
+
return { webOrigin: new URL(webOrigin).origin, accountId: session.account_id, organizationId: session.organization_id };
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// One atomic file per run prevents the daemon and independent CLI commands from
|
|
16
|
+
// overwriting each other's history. Only display metadata is persisted.
|
|
17
|
+
export function createSqmHistoryStore({ root = defaultSessionRoot(), now = () => Date.now() } = {}) {
|
|
18
|
+
const cache = new Map();
|
|
19
|
+
const directory = scope => scope?.webOrigin && scope?.accountId && scope?.organizationId
|
|
20
|
+
? join(root, "sqm-history", hash(JSON.stringify([new URL(scope.webOrigin).origin, scope.accountId, scope.organizationId]))) : null;
|
|
21
|
+
async function list(scope) {
|
|
22
|
+
const dir = directory(scope);
|
|
23
|
+
if (!dir) return [];
|
|
24
|
+
const cached = cache.get(dir);
|
|
25
|
+
if (cached && now() - cached.at < 3000) return cached.items;
|
|
26
|
+
let names;
|
|
27
|
+
try { names = await readdir(dir); } catch (error) { if (error.code === "ENOENT") return []; throw error; }
|
|
28
|
+
const items = (await Promise.all(names.filter(name => /^[a-f0-9]{64}\.json$/.test(name)).map(async name => {
|
|
29
|
+
try {
|
|
30
|
+
const text = await readFile(join(dir, name), "utf8");
|
|
31
|
+
if (text.length > 16_384) return null;
|
|
32
|
+
return metadata(JSON.parse(text));
|
|
33
|
+
} catch { return null; }
|
|
34
|
+
}))).filter(Boolean).sort((a, b) => b.startedAt.localeCompare(a.startedAt));
|
|
35
|
+
cache.set(dir, { at: now(), items });
|
|
36
|
+
return items;
|
|
37
|
+
}
|
|
38
|
+
return {
|
|
39
|
+
async record(scope, entry) {
|
|
40
|
+
const dir = directory(scope), item = metadata(entry);
|
|
41
|
+
if (!dir || !item) return false;
|
|
42
|
+
await mkdir(dir, { recursive: true, mode: 0o700 });
|
|
43
|
+
const target = join(dir, `${hash(item.key)}.json`), temp = `${target}.${randomUUID()}.tmp`;
|
|
44
|
+
try {
|
|
45
|
+
await writeFile(temp, JSON.stringify(item), { mode: 0o600 });
|
|
46
|
+
await rename(temp, target);
|
|
47
|
+
} finally { await rm(temp, { force: true }); cache.delete(dir); }
|
|
48
|
+
const items = await list(scope);
|
|
49
|
+
for (const kind of ["analysis", "check"]) {
|
|
50
|
+
await Promise.all(items.filter(row => row.kind === kind).slice(LIMIT).map(row => rm(join(dir, `${hash(row.key)}.json`), { force: true })));
|
|
51
|
+
}
|
|
52
|
+
cache.delete(dir);
|
|
53
|
+
return true;
|
|
54
|
+
},
|
|
55
|
+
async items(scope) {
|
|
56
|
+
// A saved running row is not evidence that this process owns its lease.
|
|
57
|
+
return (await list(scope)).map(item => ({ ...item, state: item.state === "running" ? "interrupted" : item.state }));
|
|
58
|
+
},
|
|
59
|
+
async detail(scope, key) {
|
|
60
|
+
const item = (await this.items(scope)).find(item => item.key === key);
|
|
61
|
+
if (!item) return null;
|
|
62
|
+
return { key, instruction: item.instruction || `SQM local review · ${item.provider || "local"}`,
|
|
63
|
+
result: item.result || "", error: item.state === "interrupted" ? "This client stopped before the local run finished. Check the web workspace for its current retry status." : item.error || "", links: [] };
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function metadata(entry) {
|
|
69
|
+
if (!entry || !["analysis", "check"].includes(entry.kind) || typeof entry.key !== "string" || !entry.key.startsWith(entry.kind === "analysis" ? "sqm:" : "sqm-check:")) return null;
|
|
70
|
+
if (!["running", "completed", "failed", "unavailable", "interrupted", "verification_pending"].includes(entry.state) || !Number.isFinite(Date.parse(entry.startedAt))) return null;
|
|
71
|
+
const item = {};
|
|
72
|
+
for (const name of ["key", "id", "kind", "label", "state", "provider", "phase", "repository", "startedAt", "finishedAt", "instruction", "result", "error"]) {
|
|
73
|
+
if (typeof entry[name] === "string") item[name] = entry[name].slice(0, ["instruction", "result", "error"].includes(name) ? 4096 : 512);
|
|
74
|
+
}
|
|
75
|
+
return item;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function projectSqmHistory(items, live = { activeCount: 0, items: [] }) {
|
|
79
|
+
const analyses = new Map(items.filter(item => item.kind === "analysis").map(item => [item.key, item]));
|
|
80
|
+
for (const item of live.items || []) analyses.set(item.key, { ...analyses.get(item.key), ...item });
|
|
81
|
+
const rows = items => items.sort((a, b) => (b.startedAt || "").localeCompare(a.startedAt || "")).slice(0, LIMIT)
|
|
82
|
+
.map(({ instruction, result, error, ...item }) => item);
|
|
83
|
+
return { sqmTasks: { activeCount: live.activeCount || 0, items: rows([...analyses.values()]) },
|
|
84
|
+
sqmChecks: { activeCount: 0, items: rows(items.filter(item => item.kind === "check")) } };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function createSqmCheckRecorder({ root, scope, phase, cwd, baseBranch, onError = () => console.warn("Could not save the local SQM check history.") }) {
|
|
88
|
+
if (!scope?.webOrigin || !scope?.accountId || !scope?.organizationId) return async () => {};
|
|
89
|
+
const history = createSqmHistoryStore({ root });
|
|
90
|
+
const key = `sqm-check:${randomUUID()}`;
|
|
91
|
+
const startedAt = new Date().toISOString();
|
|
92
|
+
return async result => {
|
|
93
|
+
const proof = result.proof, identity = proof?.repository_identity || result.repository_identity || result.min_check?.repository_identity;
|
|
94
|
+
const repository = identity?.repository || result.repository;
|
|
95
|
+
const outcome = proof?.outcome.status || result.min_check?.status || result.status;
|
|
96
|
+
const state = outcome === "unavailable" ? "unavailable" : ["failed", "preliminary_failed"].includes(outcome) ? "failed" : ["requires_human", "requires_post_deploy"].includes(outcome) ? "verification_pending" : "completed";
|
|
97
|
+
const summary = proof ? [`Full check: ${proof.outcome.status}`, formatSqmCoverage(proof), `Evidence upload: ${result.upload?.uploaded ? "uploaded" : "kept locally"}`].join("\n")
|
|
98
|
+
: state === "unavailable" ? "SQM could not obtain a verified result. Retry the command to see current diagnostics."
|
|
99
|
+
: state === "failed" && !identity ? "The command did not finish. Retry it to see current diagnostics."
|
|
100
|
+
: `Min-check: ${result.min_check?.status || result.status} · ${(result.preliminary_findings || result.min_check?.preliminary_findings)?.length || 0} preliminary finding(s) · ${(result.matched_modules || result.min_check?.matched_modules)?.length || 0} matched module(s).\nThis is a preflight; run the full check for signed verification.`;
|
|
101
|
+
try {
|
|
102
|
+
await history.record(scope, { key, id: key, kind: "check", phase, state, startedAt, finishedAt: new Date().toISOString(), repository,
|
|
103
|
+
label: `${phase === "min" ? "Min-check" : "Check"} · ${repository || basename(cwd)}`,
|
|
104
|
+
instruction: [repository || basename(cwd), `Base: ${identity?.base_ref || baseBranch}`, identity?.head_commit ? `Head: ${identity.head_commit}` : ""].filter(Boolean).join("\n"),
|
|
105
|
+
result: summary });
|
|
106
|
+
} catch { onError(); }
|
|
107
|
+
};
|
|
108
|
+
}
|
package/src/sqm/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createSqmCheckRecorder } from "./history-store.js";
|
|
1
2
|
import { evaluateKnowledgeBundle } from "./checkers.js";
|
|
2
3
|
import { loadKnowledgeBundle } from "./bundle-store.js";
|
|
3
4
|
import { uploadRunEvidence } from "./evidence-client.js";
|
|
@@ -15,6 +16,8 @@ export function createSqmChecker({
|
|
|
15
16
|
accountId,
|
|
16
17
|
organizationId,
|
|
17
18
|
deviceId,
|
|
19
|
+
webOrigin,
|
|
20
|
+
historyRoot,
|
|
18
21
|
evidenceEndpoint,
|
|
19
22
|
uploadRunEvidenceImpl = uploadRunEvidence,
|
|
20
23
|
inspectRepositoryImpl = inspectRepository,
|
|
@@ -27,7 +30,7 @@ export function createSqmChecker({
|
|
|
27
30
|
let pinnedRepository = null;
|
|
28
31
|
let latestMinCheck = null;
|
|
29
32
|
|
|
30
|
-
|
|
33
|
+
const checkCurrentRepository = async function(_attempt = null, { phase = "min" } = {}) {
|
|
31
34
|
let repository;
|
|
32
35
|
try {
|
|
33
36
|
repository = await inspectRepositoryImpl(cwd, { baseBranch, runCommand: dependencies.runCommand });
|
|
@@ -64,6 +67,13 @@ export function createSqmChecker({
|
|
|
64
67
|
return { ...unavailable(`SQM ${phase} execution failed: ${message(error)}`, repository.repository), bundle: bundleIdentity(loaded.bundle), warnings: [...loaded.warnings, `SQM ${phase} execution failed: ${message(error)}`] };
|
|
65
68
|
}
|
|
66
69
|
};
|
|
70
|
+
return async (attempt, options = {}) => {
|
|
71
|
+
const save = createSqmCheckRecorder({ root: historyRoot, scope: historyRoot ? { webOrigin, accountId, organizationId } : null, phase: options.phase || "min", cwd, baseBranch });
|
|
72
|
+
try {
|
|
73
|
+
const result = await checkCurrentRepository(attempt, options);
|
|
74
|
+
await save(result); return result;
|
|
75
|
+
} catch (error) { await save({ status: "failed" }); throw error; }
|
|
76
|
+
};
|
|
67
77
|
}
|
|
68
78
|
|
|
69
79
|
export async function runSqmMinCheck(options) {
|
package/src/sqm/review-worker.js
CHANGED
|
@@ -86,6 +86,9 @@ export function createSqmLocalReviewWorker({
|
|
|
86
86
|
beginWork = () => () => {},
|
|
87
87
|
onError = () => {},
|
|
88
88
|
onChange = () => {},
|
|
89
|
+
getHistoryScope = () => null,
|
|
90
|
+
recordHistory = async () => {},
|
|
91
|
+
onHistoryError = () => {},
|
|
89
92
|
} = {}) {
|
|
90
93
|
if (!api || typeof api.request !== "function") throw new TypeError("api.request is required");
|
|
91
94
|
let activeJobId = null;
|
|
@@ -93,9 +96,19 @@ export function createSqmLocalReviewWorker({
|
|
|
93
96
|
let closed = false;
|
|
94
97
|
let issue = null;
|
|
95
98
|
const history = [];
|
|
96
|
-
const
|
|
99
|
+
const historyScopes = new WeakMap();
|
|
100
|
+
const visibleHistory = () => history.filter(item => historyScopes.get(item) === JSON.stringify(getHistoryScope()));
|
|
101
|
+
const persist = async (scope, item) => {
|
|
102
|
+
try { await recordHistory(scope, item); } catch { onHistoryError(); /* History failure must not fail a leased review. */ }
|
|
103
|
+
};
|
|
104
|
+
const updateTask = async (id, state, scope) => {
|
|
97
105
|
const item = history.find(item => item.id === id);
|
|
98
|
-
if (item)
|
|
106
|
+
if (item) {
|
|
107
|
+
item.state = state; item.finishedAt = new Date().toISOString();
|
|
108
|
+
item.result = state === "completed" ? "SQM review submitted." : "";
|
|
109
|
+
item.error = state === "failed" ? "Local review failed. Check the SQM workspace for retry status." : "";
|
|
110
|
+
await persist(scope, item);
|
|
111
|
+
}
|
|
99
112
|
onChange();
|
|
100
113
|
};
|
|
101
114
|
|
|
@@ -108,10 +121,15 @@ export function createSqmLocalReviewWorker({
|
|
|
108
121
|
});
|
|
109
122
|
|
|
110
123
|
const processClaim = async (claim, provider) => {
|
|
124
|
+
const historyScope = getHistoryScope();
|
|
111
125
|
activeJobId = claim.job_id;
|
|
126
|
+
const priorIndex = history.findIndex(item => item.id === claim.job_id);
|
|
127
|
+
if (priorIndex >= 0) history.splice(priorIndex, 1);
|
|
112
128
|
history.unshift({ id: claim.job_id, key: `sqm:${claim.job_id}`, label: `SQM · ${String(claim.job_id).slice(0, 8)}`,
|
|
113
|
-
state: "running", provider, startedAt: new Date().toISOString() });
|
|
129
|
+
kind: "analysis", state: "running", provider, startedAt: new Date().toISOString() });
|
|
130
|
+
historyScopes.set(history[0], JSON.stringify(historyScope));
|
|
114
131
|
history.splice(50);
|
|
132
|
+
await persist(historyScope, history[0]);
|
|
115
133
|
onChange();
|
|
116
134
|
let heartbeatTimer = null;
|
|
117
135
|
const heartbeat = () => {
|
|
@@ -127,7 +145,7 @@ export function createSqmLocalReviewWorker({
|
|
|
127
145
|
analysis_output: analysis.output,
|
|
128
146
|
executor_model: analysis.model,
|
|
129
147
|
});
|
|
130
|
-
if (advanced.completed) { updateTask(claim.job_id, "completed"); return advanced; }
|
|
148
|
+
if (advanced.completed) { await updateTask(claim.job_id, "completed", historyScope); return advanced; }
|
|
131
149
|
const adjudication = await runReview(advanced.request, provider);
|
|
132
150
|
const completed = await api.request({
|
|
133
151
|
action: "complete",
|
|
@@ -136,10 +154,10 @@ export function createSqmLocalReviewWorker({
|
|
|
136
154
|
adjudication_output: adjudication.output,
|
|
137
155
|
executor_model: adjudication.model || analysis.model,
|
|
138
156
|
});
|
|
139
|
-
updateTask(claim.job_id, "completed");
|
|
157
|
+
await updateTask(claim.job_id, "completed", historyScope);
|
|
140
158
|
return completed;
|
|
141
159
|
} catch (error) {
|
|
142
|
-
updateTask(claim.job_id, "failed");
|
|
160
|
+
await updateTask(claim.job_id, "failed", historyScope);
|
|
143
161
|
throw error;
|
|
144
162
|
} finally {
|
|
145
163
|
if (heartbeatTimer) clearInterval(heartbeatTimer);
|
|
@@ -152,9 +170,9 @@ export function createSqmLocalReviewWorker({
|
|
|
152
170
|
snapshot() {
|
|
153
171
|
return { active_job_id: activeJobId, polling: Boolean(pollPromise), closed, issue };
|
|
154
172
|
},
|
|
155
|
-
desktopTasks() { return { activeCount: activeJobId ? 1 : 0, items:
|
|
173
|
+
desktopTasks() { return { activeCount: activeJobId ? 1 : 0, items: visibleHistory().map(item => ({ ...item })) }; },
|
|
156
174
|
desktopTaskDetail(key) {
|
|
157
|
-
const item =
|
|
175
|
+
const item = visibleHistory().find(item => item.key === key);
|
|
158
176
|
return item ? { key, instruction: `SQM local review · ${item.provider}`, result: item.state === "completed" ? "SQM review submitted." : "", error: item.state === "failed" ? "Local review failed. Check the SQM workspace for retry status." : "", links: [] } : null;
|
|
159
177
|
},
|
|
160
178
|
resetHistory() { if (!pollPromise) { history.splice(0); issue = null; } },
|