worktrust 0.8.0 → 0.8.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/README.md +2 -0
- package/log-session.mjs +7 -3
- package/package.json +1 -1
- package/preserve-lines.mjs +23 -0
- package/preserve.mjs +15 -8
- package/stretch-evidence.mjs +41 -3
- package/worktrust.mjs +1 -1
package/README.md
CHANGED
|
@@ -167,6 +167,8 @@ nothing it does not:
|
|
|
167
167
|
| `context`, `routing` | how often the context was compacted and which context commands you used (compact, clear, resume, context, model, memory; never a command of your own), how many models answered and how often the model changed (0.7.4) |
|
|
168
168
|
| `steering` | the moments you stepped in while the agent worked (stopping it, or a message mid-task), how many changed what the agent did next, and how many were followed by a passing check (0.7.5) |
|
|
169
169
|
| `tools`, `complexity` | how many different tools the agent used, and the stretch's task complexity C1 to C5 by the rule complexity/1 (points for layers touched, tools, subagents, duration, a recovered failure, kinds of check and a delivery; the rule is in stretch-evidence.mjs), with its points and the rule's version (0.7.6) |
|
|
170
|
+
| `oversight`, `planning`, `changes` | how often you refused an action or a plan the agent proposed, and how often a guard (the client's safety layer) refused one; how often the agent wrote its to-do list, the most items, and of the last list how many were done (never what they say); how many files the stretch's commits changed and how many of them tests (never a path) (0.8.1) |
|
|
171
|
+
| `supplements` | a supplement line: when a newer CLI measures more of a stretch already archived, the new fields go into a line of their own that names that stretch; nothing earlier is rewritten, and the chain and the day's root cover it (0.8.2) |
|
|
170
172
|
| `collector_version` | the CLI version that measured it |
|
|
171
173
|
| `seq`, `prev`, `hash` | the line's place, the hash of the line before it, and its own hash |
|
|
172
174
|
|
package/log-session.mjs
CHANGED
|
@@ -135,6 +135,8 @@ const DAY_SECONDS = 86400;
|
|
|
135
135
|
const TOOL_CAP = 1800;
|
|
136
136
|
/** Tools whose result waits for the person: the gap to it is the person's time. Names only; nothing of the call is read. */
|
|
137
137
|
const WAITS_FOR_PERSON = new Set(["AskUserQuestion", "ExitPlanMode"]);
|
|
138
|
+
/** A path that holds tests, by the conventions of the common test runners (read here; the path never leaves). */
|
|
139
|
+
const TEST_PATH = /(^|[\\/])(__tests__|tests?|spec|e2e)[\\/]|\.(test|spec)\.[cm]?[jt]sx?$|_test\.(go|py|rb)$|(^|[\\/])test_[^\\/]+\.py$|Tests?\.(java|kt|cs|swift)$/;
|
|
138
140
|
/** The derived record of a stretch, kept for the local archive only (see payloadFor). */
|
|
139
141
|
const DERIVED = new WeakMap();
|
|
140
142
|
const AGENT_RUNS_MAX = 10000, AGENT_SECONDS_MAX = 2592000;
|
|
@@ -211,7 +213,7 @@ const excluded = (path) => excludes.some((text) => text && path.includes(text));
|
|
|
211
213
|
const { CODEX_ROLLOUT = /(?!)/, codexIdOf = (file) => String(file).split(/[\\/]/).at(-1).replace(/\.jsonl$/, ""), antigravityIdOf = (file) => String(file).split(/[\\/]/).at(-4), codexLines, codexRolloutFiles = function* () {}, codexCwd = () => null, ANTIGRAVITY_TRANSCRIPT = /(?!)/, antigravityLines, antigravityRoots = () => [], antigravityTranscripts = function* () {}, antigravityContext = () => ({}), rememberAntigravity = () => null } = (await import("./transcript-readers.mjs").catch(() => null)) ?? {};
|
|
212
214
|
// THE STRETCH'S DERIVED RECORD (0.7.4: split from this file): tool calls named by kind, verification, recovery, delegation,
|
|
213
215
|
// context and routing, for the local archive only. A copy without the module still measures and sends exactly as before.
|
|
214
|
-
const { callOf = (block) => ({ id: block.id, kinds: [], family: String(block.name), digest: "" }), deriveStretch = () => ({}), complexityOf = null } = (await import("./stretch-evidence.mjs").catch(() => null)) ?? {};
|
|
216
|
+
const { callOf = (block) => ({ id: block.id, kinds: [], family: String(block.name), digest: "" }), deriveStretch = () => ({}), complexityOf = null, resultOf = (block) => ({ id: block.tool_use_id, failed: block.is_error === true, refusal: null }) } = (await import("./stretch-evidence.mjs").catch(() => null)) ?? {};
|
|
215
217
|
const { DATABASE_SESSION = /(?!)/, databaseLines = () => null, databaseSessions = function* () {} } = (await import("./session-databases.mjs").catch(() => null)) ?? {};
|
|
216
218
|
const DATABASE_CLIENTS = ["hermes", "goose", "opencode", "openclaw", "cursor", "copilot"]; // the registry's keys, the name each line carries
|
|
217
219
|
const LIVE_SOURCE = new RegExp(`^(codex|antigravity|${DATABASE_CLIENTS.join("|")}):`); // a sweep entry's id → the client it names (every database client, 0.6.16)
|
|
@@ -398,7 +400,7 @@ const messageOf = (line, at) => {
|
|
|
398
400
|
// 0.7.1: each tool call's kinds (never its command) and each tool result's outcome, joined on the call's id in `measure`.
|
|
399
401
|
// 0.7.2: and the call's FAMILY (the tool, and its check kinds) and a digest of its input, compared here and never kept.
|
|
400
402
|
calls: line.type === "assistant" ? blocks.filter((block) => block && block.type === "tool_use" && typeof block.id === "string").map(callOf) : [],
|
|
401
|
-
results: blocks.filter((block) => block && block.type === "tool_result" && typeof block.tool_use_id === "string").map(
|
|
403
|
+
results: blocks.filter((block) => block && block.type === "tool_result" && typeof block.tool_use_id === "string").map(resultOf),
|
|
402
404
|
};
|
|
403
405
|
};
|
|
404
406
|
|
|
@@ -624,7 +626,9 @@ function payloadFor(stretch) {
|
|
|
624
626
|
};
|
|
625
627
|
// WHAT STAYS ON THIS COMPUTER (0.7.1): the stretch's derived record rides beside the payload for the local archive, never in it.
|
|
626
628
|
// 0.7.6: and the stretch's complexity class, read from what the hook knows here (layers, subagents, duration) and the record.
|
|
627
|
-
|
|
629
|
+
// 0.8.1: what the stretch changed, as counts of files (never their paths): how many, and how many of them tests.
|
|
630
|
+
const changed = [...new Set(touched.paths)], tests = changed.filter((path) => TEST_PATH.test(path)).length;
|
|
631
|
+
if (stretch.derived) DERIVED.set(payload, { ...stretch.derived, ...(changed.length > 0 ? { changes: { files: changed.length, test_files: tests } } : {}), ...(complexityOf ? { complexity: complexityOf(stretch.derived, { layers: Math.max(layers.length, layer ? 1 : 0), agentRuns: stretch.agents?.runs ?? 0, agentPeak: stretch.agents?.peak ?? 0, seconds: stretch.seconds }) } : {}) });
|
|
628
632
|
return payload;
|
|
629
633
|
}
|
|
630
634
|
|
package/package.json
CHANGED
package/preserve-lines.mjs
CHANGED
|
@@ -83,6 +83,14 @@ export function archiveLine(entry, behaviour = null, { ids = {}, collector } = {
|
|
|
83
83
|
// TOOL BREADTH AND COMPLEXITY (0.7.6): distinct tools; a class C1–C5 with its points and the rule's version.
|
|
84
84
|
if (whole(entry.tools) && entry.tools > 0) line.tools = entry.tools;
|
|
85
85
|
if (entry.complexity && /^C[1-5]$/.test(entry.complexity.class ?? "") && whole(entry.complexity.points) && /^complexity\/\d+$/.test(entry.complexity.rule ?? "")) line.complexity = { class: entry.complexity.class, points: entry.complexity.points, rule: entry.complexity.rule };
|
|
86
|
+
// OVERSIGHT, PLANNING AND CHANGES (0.8.1): refusals by the person and by a guard; the agent's to-do lists; files changed and test files among them.
|
|
87
|
+
const pick = (record, keys) => (record && typeof record === "object" && keys.every((key) => whole(record[key])) ? Object.fromEntries(keys.map((key) => [key, record[key]])) : null);
|
|
88
|
+
const oversight = pick(entry.oversight, ["refused", "plans_rejected", "guard_denied"]);
|
|
89
|
+
if (oversight) line.oversight = oversight;
|
|
90
|
+
const planning = pick(entry.planning, ["lists", "items_max", "items_last", "done_last"]);
|
|
91
|
+
if (planning && planning.lists > 0 && planning.done_last <= planning.items_last && planning.items_last <= planning.items_max) line.planning = planning;
|
|
92
|
+
const changes = pick(entry.changes, ["files", "test_files"]);
|
|
93
|
+
if (changes && changes.test_files <= changes.files) line.changes = changes;
|
|
86
94
|
// The behaviour signals of this stretch (0.7.0): keys of the counter's rubric and whole counts, never a word of a turn.
|
|
87
95
|
if (behaviour && behaviour.signals && typeof behaviour.analyzer_version === "string" && /^counter@\d+\.\d+\.\d+$/.test(behaviour.analyzer_version)) {
|
|
88
96
|
const kept = Object.entries(behaviour.signals).filter(([key, count]) => SIGNAL.test(key) && Number.isInteger(count) && count > 0).sort();
|
|
@@ -108,6 +116,7 @@ export function archiveEntries(dir, ownKey) {
|
|
|
108
116
|
if (!existsSync(file)) continue;
|
|
109
117
|
for (const text of readFileSync(file, "utf8").split("\n").filter(Boolean)) {
|
|
110
118
|
let line; try { line = JSON.parse(text); } catch { continue; }
|
|
119
|
+
if (line.supplements) continue; // a supplement adds fields to a stretch, it is no stretch of its own
|
|
111
120
|
const proof = read(join(dir, "proofs", `${line.day}.json`));
|
|
112
121
|
if (!ownKey || !proof || typeof proof.signature !== "string" || proof.public_key !== ownKey) continue;
|
|
113
122
|
const entry = {
|
|
@@ -122,3 +131,17 @@ export function archiveEntries(dir, ownKey) {
|
|
|
122
131
|
}
|
|
123
132
|
return out;
|
|
124
133
|
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* A SUPPLEMENT FOR A STRETCH ALREADY ARCHIVED (0.8.2). The archive only grows: a line is never rewritten. When a newer
|
|
137
|
+
* CLI measures more of a stretch that is already in it (verification, recovery, the signals …), the new fields go into
|
|
138
|
+
* a line of their own that names the stretch it adds to (`supplements`) and carries only what no earlier line of that
|
|
139
|
+
* stretch carries, in the same chain, under the same day's root. Null when there is nothing new.
|
|
140
|
+
*/
|
|
141
|
+
export const DERIVED_KEYS = ["signals", "analyzer_version", "verification", "delivery", "recovery", "delegation", "context", "routing", "steering", "tools", "complexity", "oversight", "planning", "changes", "interrupts", "steers", "utc_offset"];
|
|
142
|
+
export function supplementFor(line, earlier) {
|
|
143
|
+
const missing = DERIVED_KEYS.filter((key) => line[key] !== undefined && !earlier.some((old) => old[key] !== undefined));
|
|
144
|
+
if (missing.length === 0) return null;
|
|
145
|
+
const { device_id: device, profile_id: profile } = line;
|
|
146
|
+
return { client: line.client, day: line.day, started_at: line.started_at, ended_at: line.ended_at, supplements: line.stretch_ref, ...Object.fromEntries(missing.map((key) => [key, line[key]])), ...(device ? { device_id: device } : {}), ...(profile ? { profile_id: profile } : {}), collector_version: line.collector_version };
|
|
147
|
+
}
|
package/preserve.mjs
CHANGED
|
@@ -31,7 +31,7 @@ import { dirname, join, relative } from "node:path";
|
|
|
31
31
|
import { createInterface } from "node:readline/promises";
|
|
32
32
|
import { fileURLToPath } from "node:url";
|
|
33
33
|
import { summaryLines } from "./preserve-summary.mjs";
|
|
34
|
-
import { archiveLine, stretchSignals } from "./preserve-lines.mjs";
|
|
34
|
+
import { archiveLine, stretchSignals, supplementFor } from "./preserve-lines.mjs";
|
|
35
35
|
|
|
36
36
|
const args = process.argv.slice(2).filter((arg) => arg !== "preserve");
|
|
37
37
|
const has = (name) => args.includes(`--${name}`);
|
|
@@ -260,12 +260,16 @@ function archive(dir) {
|
|
|
260
260
|
const rows = hook.stdout.split("\n").filter(Boolean), end = (() => { try { return JSON.parse(rows.at(-1) ?? "").archive_end; } catch { return undefined; } })();
|
|
261
261
|
if (!Number.isInteger(end) || end !== rows.length - 1) { say(` The session hook's answer came back incomplete (${rows.length - 1} of ${end ?? "an unknown number of"} lines), so nothing was added. Run it again.`); process.exit(1); }
|
|
262
262
|
const signals = stretchSignals(HERE, say);
|
|
263
|
-
const known = new Set(existing.lines.map((line) => line.stretch_ref)), fresh = [], measured = new Set();
|
|
263
|
+
const known = new Set(existing.lines.map((line) => line.stretch_ref)), fresh = [], measured = new Set(), additions = [];
|
|
264
|
+
const byRef = new Map(); // 0.8.2: every earlier line of a stretch, its supplements included
|
|
265
|
+
for (const line of existing.lines) { const ref = line.supplements ?? line.stretch_ref; byRef.set(ref, [...(byRef.get(ref) ?? []), line]); }
|
|
264
266
|
let waiting = 0;
|
|
265
267
|
for (const text of rows.slice(0, -1)) {
|
|
266
268
|
let entry; try { entry = JSON.parse(text); } catch { continue; }
|
|
267
269
|
const line = archiveLine(entry, signals?.get(entry.stretch_ref) ?? null, { ids: IDS, collector: COLLECTOR });
|
|
268
|
-
if (line && line.day < today()) measured.add(line.stretch_ref);
|
|
270
|
+
if (line && line.day < today()) measured.add(line.stretch_ref);
|
|
271
|
+
if (line && byRef.has(line.stretch_ref)) { const extra = supplementFor(line, byRef.get(line.stretch_ref)); if (extra) { additions.push(extra); byRef.set(line.stretch_ref, [...byRef.get(line.stretch_ref), extra]); } continue; }
|
|
272
|
+
if (!line || known.has(line.stretch_ref)) continue; // a file present twice is one stretch
|
|
269
273
|
if (line.day >= today()) { waiting += 1; continue; }
|
|
270
274
|
known.add(line.stretch_ref);
|
|
271
275
|
fresh.push(line);
|
|
@@ -274,7 +278,7 @@ function archive(dir) {
|
|
|
274
278
|
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
275
279
|
let prev = existing.lines.at(-1)?.hash ?? null, seq = existing.lines.length;
|
|
276
280
|
const all = [...existing.lines];
|
|
277
|
-
for (const line of fresh) {
|
|
281
|
+
for (const line of [...fresh, ...additions]) {
|
|
278
282
|
const chained = { ...line, seq: (seq += 1), prev };
|
|
279
283
|
const hashed = { ...chained, hash: sha256(canonical(chained)) };
|
|
280
284
|
mkdirSync(join(dir, line.day.slice(0, 4), line.day.slice(5, 7)), { recursive: true });
|
|
@@ -288,16 +292,18 @@ function archive(dir) {
|
|
|
288
292
|
const key = deviceKey();
|
|
289
293
|
const ours = (document) => !document || (key && typeof document.signature === "string" && document.public_key === key.publicX);
|
|
290
294
|
mkdirSync(join(dir, "proofs"), { recursive: true });
|
|
291
|
-
for (const day of new Set(fresh.map((line) => line.day))) {
|
|
295
|
+
for (const day of new Set([...fresh, ...additions].map((line) => line.day))) {
|
|
292
296
|
const own = !existing.days?.has(day) || ours(existing.proofs?.get(day));
|
|
293
297
|
const body = proofBody(day, all.filter((line) => line.day === day), own ? key : null);
|
|
294
298
|
writeFileSync(join(dir, "proofs", `${day}.json`), `${JSON.stringify(sealed(body, own ? key : null), null, 1)}\n`);
|
|
295
299
|
}
|
|
296
300
|
// The manifest is sealed the same way: a new archive, or one whose manifest this key already signed.
|
|
297
301
|
const manifestKey = !existing.manifest || ours(existing.manifest) ? key : null;
|
|
298
|
-
if (fresh.length > 0 || !existing.manifest) writeFileSync(join(dir, "manifest.json"), `${JSON.stringify(sealed({ format: "worktrust-ai-evidence/1", collector_version: COLLECTOR, created: existing.manifest?.created ?? new Date().toISOString(), updated: new Date().toISOString(), clients: [...new Set(all.map((line) => line.client))].sort(), ...IDS, count: all.length, head: prev, ...(manifestKey ? { public_key: manifestKey.publicX } : {}) }, manifestKey), null, 1)}\n`);
|
|
302
|
+
if (fresh.length + additions.length > 0 || !existing.manifest) writeFileSync(join(dir, "manifest.json"), `${JSON.stringify(sealed({ format: "worktrust-ai-evidence/1", collector_version: COLLECTOR, created: existing.manifest?.created ?? new Date().toISOString(), updated: new Date().toISOString(), clients: [...new Set(all.map((line) => line.client))].sort(), ...IDS, count: all.length, head: prev, ...(manifestKey ? { public_key: manifestKey.publicX } : {}) }, manifestKey), null, 1)}\n`);
|
|
299
303
|
say();
|
|
300
|
-
|
|
304
|
+
const stretches = all.filter((line) => !line.supplements).length;
|
|
305
|
+
say(` ✓ ${fresh.length} new day-stretch${fresh.length === 1 ? "" : "es"} archived in ${dir}: it holds ${stretches}, the session hook measures ${measured.size} finished ones here now${key ? ", each day's root signed with this computer's device key" : ", unsigned (no device key on this computer)"}.`);
|
|
306
|
+
if (additions.length > 0) say(` ${additions.length} stretch${additions.length === 1 ? "" : "es"} already here gained the newer measurements, each in a supplement line; nothing earlier was rewritten.`);
|
|
301
307
|
if (waiting > 0) say(` ${waiting} of today wait for tomorrow's run: a day is archived once it is over.`);
|
|
302
308
|
say(" Metadata only: clocks, counts, model names, a hash of each project's name. Nothing was sent.");
|
|
303
309
|
}
|
|
@@ -308,7 +314,8 @@ function verifyArchive(dir) {
|
|
|
308
314
|
if (result.lines.length === 0) { say(` Nothing to verify in ${dir}.`); return; }
|
|
309
315
|
const daySeal = result.signed === 0 ? "no day signed" : result.ours === result.signed ? `${result.signed} signed by this computer's key` : `${result.signed} signed (${result.ours} by this computer's key, ${result.signed - result.ours} by another key named in them)`;
|
|
310
316
|
const seal = typeof result.manifest?.signature !== "string" ? "manifest unsigned" : result.manifest.public_key === deviceKey()?.publicX ? "manifest signed by this computer's key" : "manifest signed by another key named in it";
|
|
311
|
-
|
|
317
|
+
const added = result.lines.filter((line) => line.supplements).length;
|
|
318
|
+
say(` OK ${result.lines.length - added} stretches${added ? ` (and ${added} supplement${added === 1 ? "" : "s"})` : ""} · ${result.days.size} days · the chain is whole · every day's root matches · ${daySeal} · ${seal}`);
|
|
312
319
|
}
|
|
313
320
|
|
|
314
321
|
/** --summary: what a person or a team reads from the archive, counted locally (preserve-summary.mjs) once the chain checks out. */
|
package/stretch-evidence.mjs
CHANGED
|
@@ -32,8 +32,23 @@ export const callKindsOf = (block) => {
|
|
|
32
32
|
const command = block && SHELL_TOOLS.has(String(block.name)) ? block.input?.command ?? block.input?.CommandLine ?? block.input?.cmd : null;
|
|
33
33
|
return typeof command === "string" ? CALL_KINDS.filter(([, pattern]) => pattern.test(command)).map(([kind]) => kind) : [];
|
|
34
34
|
};
|
|
35
|
+
/**
|
|
36
|
+
* A TOOL RESULT AS THE STRETCH READS IT (0.8.1): its call's id, whether it failed, and, read here and never kept, WHO
|
|
37
|
+
* refused it when it was refused: the PERSON (Claude Code's fixed sentence when they decline a tool use or a plan) or a
|
|
38
|
+
* GUARD (the client's own safety layer or auto-mode classifier). Framework L8 and L5.
|
|
39
|
+
*/
|
|
40
|
+
const PERSON_REFUSED = /^\s*(The user doesn't want to (proceed|take this action)|User rejected|The user rejected|The user declined)/i;
|
|
41
|
+
const GUARD_REFUSED = /^\s*Permission for this (command|action) was denied by/i;
|
|
42
|
+
export const resultOf = (block) => {
|
|
43
|
+
const text = typeof block.content === "string" ? block.content : Array.isArray(block.content) ? block.content.map((part) => (part && typeof part.text === "string" ? part.text : "")).join("") : "";
|
|
44
|
+
const failed = block.is_error === true;
|
|
45
|
+
return { id: block.tool_use_id, failed, refusal: failed && PERSON_REFUSED.test(text) ? "person" : failed && GUARD_REFUSED.test(text) ? "guard" : null };
|
|
46
|
+
};
|
|
47
|
+
/** A to-do list the agent wrote (TodoWrite): how many items and how many done, never what they say. */
|
|
48
|
+
const planOf = (block) => (block.name === "TodoWrite" && Array.isArray(block.input?.todos) ? { items: block.input.todos.length, done: block.input.todos.filter((todo) => todo && todo.status === "completed").length } : null);
|
|
49
|
+
|
|
35
50
|
/** One tool call as the stretch reads it: its id, its kinds, its FAMILY (the tool and its check kinds) and a digest of its input (compared, never kept). */
|
|
36
|
-
export const callOf = (block) => { const kinds = callKindsOf(block); return { id: block.id, kinds, family: `${String(block.name)}|${kinds.join("+")}`, digest: createHash("sha256").update(JSON.stringify(block.input ?? null)).digest("base64url").slice(0, 16) }; };
|
|
51
|
+
export const callOf = (block) => { const kinds = callKindsOf(block), plan = planOf(block); return { id: block.id, kinds, family: `${String(block.name)}|${kinds.join("+")}`, digest: createHash("sha256").update(JSON.stringify(block.input ?? null)).digest("base64url").slice(0, 16), ...(plan ? { plan } : {}) }; };
|
|
37
52
|
|
|
38
53
|
/**
|
|
39
54
|
* VERIFICATION PER STRETCH (0.7.1; framework L7, L12): per kind of check, how many ran and how many failed; per step of
|
|
@@ -194,9 +209,32 @@ export function complexityOf(derived, { layers = 0, agentRuns = 0, agentPeak = 0
|
|
|
194
209
|
/** How many different tools the stretch's agent used (framework L11: tool breadth); null without a tool call. */
|
|
195
210
|
const toolsOf = (messages) => { const names = new Set(); for (const message of messages) for (const call of message.calls ?? []) names.add(call.family.split("|")[0]); return names.size > 0 ? names.size : null; };
|
|
196
211
|
|
|
212
|
+
/**
|
|
213
|
+
* OVERSIGHT AND PLANNING PER STRETCH (0.8.1; framework L8, L5, L2). Oversight: how often the PERSON refused an action
|
|
214
|
+
* or a plan the agent proposed, and how often a GUARD (the client's safety layer) refused one. Planning: how often the
|
|
215
|
+
* agent wrote its to-do list, the most items one list held, and of the last list how many were done. Null where none.
|
|
216
|
+
*/
|
|
217
|
+
function oversightOf(messages) {
|
|
218
|
+
let person = 0, plans = 0, guard = 0;
|
|
219
|
+
const tools = new Map();
|
|
220
|
+
for (const message of messages) for (const call of message.calls ?? []) tools.set(call.id, call.family.split("|")[0]);
|
|
221
|
+
for (const message of messages) for (const result of message.results ?? []) {
|
|
222
|
+
if (result.refusal === "person") { if (tools.get(result.id) === "ExitPlanMode") plans += 1; else person += 1; }
|
|
223
|
+
if (result.refusal === "guard") guard += 1;
|
|
224
|
+
}
|
|
225
|
+
return person + plans + guard > 0 ? { refused: person, plans_rejected: plans, guard_denied: guard } : null;
|
|
226
|
+
}
|
|
227
|
+
function planningOf(messages) {
|
|
228
|
+
const lists = [];
|
|
229
|
+
for (const message of messages) for (const call of message.calls ?? []) if (call.plan) lists.push(call.plan);
|
|
230
|
+
if (lists.length === 0) return null;
|
|
231
|
+
const last = lists.at(-1);
|
|
232
|
+
return { lists: lists.length, items_max: Math.max(...lists.map((list) => list.items)), items_last: last.items, done_last: last.done };
|
|
233
|
+
}
|
|
234
|
+
|
|
197
235
|
/** The stretch's whole derived record; `helpers` are the hook's own readers of a human turn, so both read it the same way. */
|
|
198
236
|
export function deriveStretch(messages, helpers) {
|
|
199
237
|
const context = contextOf(messages, helpers), routing = routingOf(messages), steering = steeringOf(messages, helpers);
|
|
200
|
-
const tools = toolsOf(messages);
|
|
201
|
-
return { ...verificationOf(messages, helpers), ...(context ? { context } : {}), ...(routing ? { routing } : {}), ...(steering ? { steering } : {}), ...(tools ? { tools } : {}) };
|
|
238
|
+
const tools = toolsOf(messages), oversight = oversightOf(messages), planning = planningOf(messages);
|
|
239
|
+
return { ...(oversight ? { oversight } : {}), ...(planning ? { planning } : {}), ...verificationOf(messages, helpers), ...(context ? { context } : {}), ...(routing ? { routing } : {}), ...(steering ? { steering } : {}), ...(tools ? { tools } : {}) };
|
|
202
240
|
}
|
package/worktrust.mjs
CHANGED
|
@@ -73,7 +73,7 @@ const command = args.find((arg, at) => !arg.startsWith("--") && !(at > 0 && VALU
|
|
|
73
73
|
const flag = (name) => { const at = args.indexOf(`--${name}`); return at >= 0 ? args[at + 1] : undefined; };
|
|
74
74
|
const has = (name) => args.includes(`--${name}`);
|
|
75
75
|
/** This CLI's version, said to the door so the app can tell which computer runs an old one (check-cli-package holds it equal to package.json). */
|
|
76
|
-
const CLI_VERSION = "0.8.
|
|
76
|
+
const CLI_VERSION = "0.8.2";
|
|
77
77
|
const ORIGIN = (flag("origin") ?? process.env.WORKTRUST_ORIGIN ?? "https://app.worktrust.io").replace(/\/$/, "");
|
|
78
78
|
const MCP = flag("url") ?? process.env.WORKTRUST_MCP_URL ?? `${ORIGIN}/api/mcp`;
|
|
79
79
|
const HOME_DIR = join(homedir(), ".worktrust");
|