vericify 1.2.0 → 1.3.1
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 +56 -29
- package/package.json +3 -1
- package/src/adapters/claude-attach.js +1 -0
- package/src/adapters/handshake/antigravity.js +81 -0
- package/src/adapters/handshake/claude-code.js +150 -0
- package/src/adapters/handshake/codex.js +75 -0
- package/src/adapters/handshake/common.js +130 -0
- package/src/adapters/handshake/cursor.js +100 -0
- package/src/adapters/handshake/index.js +35 -0
- package/src/adapters/handshake/vscode-copilot.js +82 -0
- package/src/adapters/registry.js +22 -1
- package/src/api.js +2 -0
- package/src/context/delta.js +97 -0
- package/src/context/id.js +61 -0
- package/src/context/packet.js +210 -0
- package/src/context/select.js +65 -0
- package/src/index.js +65 -15
- package/src/projection/runs.js +2 -2
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import { createHandshakeResult, emitHandshakeStartedEvent, mergeJsonFile, writeInstructionBlock } from "./common.js";
|
|
4
|
+
|
|
5
|
+
const VERICIFY_MARKER = "<!-- vericify-instruction -->";
|
|
6
|
+
|
|
7
|
+
function cursorInstructionBlock() {
|
|
8
|
+
return `---
|
|
9
|
+
description: Vericify process post instructions
|
|
10
|
+
alwaysApply: true
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Vericify Process Posts
|
|
14
|
+
|
|
15
|
+
When working in this workspace, emit native Vericify process posts at these moments:
|
|
16
|
+
|
|
17
|
+
- **Blocker:** \`vericify post --run-id workspace:current --agent-id cursor --kind blocker --summary "<what is blocking>"\`
|
|
18
|
+
- **Handoff:** \`vericify post --run-id workspace:current --agent-id cursor --kind handoff_note --summary "<handoff context>"\`
|
|
19
|
+
- **Completion:** \`vericify post --run-id workspace:current --agent-id cursor --kind completion --summary "<what changed>"\`
|
|
20
|
+
- **Intentional silence:** \`vericify post --run-id workspace:current --agent-id cursor --kind stale_ack --summary "<reason>"\``;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function cursorHandshake(workspaceRoot, options = {}) {
|
|
24
|
+
const result = createHandshakeResult();
|
|
25
|
+
const skippedReasons = [];
|
|
26
|
+
const cursorDir = resolve(workspaceRoot, ".cursor");
|
|
27
|
+
const cursorMcpPath = resolve(cursorDir, "mcp.json");
|
|
28
|
+
const rulesDir = resolve(cursorDir, "rules");
|
|
29
|
+
const fallbackRulesPath = resolve(workspaceRoot, ".cursorrules");
|
|
30
|
+
const mcpPlaceholder = {
|
|
31
|
+
_vericify: true,
|
|
32
|
+
mcpServers: {
|
|
33
|
+
vericify: {
|
|
34
|
+
command: "vericify-mcp",
|
|
35
|
+
args: ["--workspace-root", "."],
|
|
36
|
+
_note: "vericify-mcp not yet installed. Run: npm install -g vericify-mcp",
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
if (existsSync(cursorDir)) {
|
|
42
|
+
try {
|
|
43
|
+
const mcpResult = mergeJsonFile(cursorMcpPath, (existing) => {
|
|
44
|
+
const current = existing && typeof existing === "object" ? existing : {};
|
|
45
|
+
return {
|
|
46
|
+
...current,
|
|
47
|
+
_vericify: true,
|
|
48
|
+
mcpServers: {
|
|
49
|
+
...(current.mcpServers && typeof current.mcpServers === "object" ? current.mcpServers : {}),
|
|
50
|
+
vericify: current.mcpServers?.vericify ?? mcpPlaceholder.mcpServers.vericify,
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
});
|
|
54
|
+
result.mcp_written = Boolean(mcpResult.written);
|
|
55
|
+
result.mcp_path = cursorMcpPath;
|
|
56
|
+
if (mcpResult.skipped) skippedReasons.push(mcpResult.reason);
|
|
57
|
+
} catch (error) {
|
|
58
|
+
skippedReasons.push(`.cursor/mcp.json write failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
59
|
+
}
|
|
60
|
+
} else {
|
|
61
|
+
skippedReasons.push(".cursor directory is missing; MCP placeholder skipped");
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
let instructionTarget = null;
|
|
65
|
+
if (existsSync(rulesDir)) {
|
|
66
|
+
instructionTarget = resolve(rulesDir, "vericify.mdc");
|
|
67
|
+
} else if (existsSync(fallbackRulesPath)) {
|
|
68
|
+
instructionTarget = fallbackRulesPath;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (instructionTarget) {
|
|
72
|
+
try {
|
|
73
|
+
const instructionResult = writeInstructionBlock(instructionTarget, VERICIFY_MARKER, cursorInstructionBlock(), {
|
|
74
|
+
createIfAbsent: true,
|
|
75
|
+
createDirs: true,
|
|
76
|
+
});
|
|
77
|
+
result.instruction_written = Boolean(instructionResult.written);
|
|
78
|
+
result.instruction_path = instructionTarget;
|
|
79
|
+
if (instructionResult.skipped) skippedReasons.push(instructionResult.reason);
|
|
80
|
+
} catch (error) {
|
|
81
|
+
skippedReasons.push(`Cursor instruction write failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
82
|
+
}
|
|
83
|
+
} else {
|
|
84
|
+
skippedReasons.push("No .cursor/rules/ or .cursorrules file exists; instruction block skipped");
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const eventResult = emitHandshakeStartedEvent(workspaceRoot, {
|
|
88
|
+
adapterId: "cursor",
|
|
89
|
+
sessionId: options.sessionId ?? null,
|
|
90
|
+
hooksWritten: false,
|
|
91
|
+
instructionWritten: result.instruction_written,
|
|
92
|
+
mcpWritten: result.mcp_written,
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
...result,
|
|
97
|
+
event_emitted: eventResult.event_emitted,
|
|
98
|
+
skipped_reasons: [...skippedReasons, ...eventResult.skipped_reasons].filter(Boolean),
|
|
99
|
+
};
|
|
100
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { createHandshakeResult } from "./common.js";
|
|
2
|
+
import { antigravityHandshake } from "./antigravity.js";
|
|
3
|
+
import { claudeCodeHandshake } from "./claude-code.js";
|
|
4
|
+
import { codexHandshake } from "./codex.js";
|
|
5
|
+
import { cursorHandshake } from "./cursor.js";
|
|
6
|
+
import { vscodeCopilotHandshake } from "./vscode-copilot.js";
|
|
7
|
+
|
|
8
|
+
const HANDSHAKE_FNS = {
|
|
9
|
+
"claude-code": claudeCodeHandshake,
|
|
10
|
+
codex: codexHandshake,
|
|
11
|
+
cursor: cursorHandshake,
|
|
12
|
+
"vscode-copilot-chat": vscodeCopilotHandshake,
|
|
13
|
+
antigravity: antigravityHandshake,
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export function runHandshake(workspaceRoot, options = {}) {
|
|
17
|
+
const adapterId = String(options.adapterId ?? "").trim();
|
|
18
|
+
const fn = HANDSHAKE_FNS[adapterId];
|
|
19
|
+
if (!fn) {
|
|
20
|
+
return {
|
|
21
|
+
skipped: true,
|
|
22
|
+
reason: "No handshake defined for this adapter.",
|
|
23
|
+
...createHandshakeResult(),
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
return fn(workspaceRoot, options);
|
|
29
|
+
} catch (error) {
|
|
30
|
+
return {
|
|
31
|
+
...createHandshakeResult(),
|
|
32
|
+
skipped_reasons: [`Handshake failed: ${error instanceof Error ? error.message : String(error)}`],
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { existsSync, mkdirSync } from "node:fs";
|
|
2
|
+
import { dirname, resolve } from "node:path";
|
|
3
|
+
import { createHandshakeResult, emitHandshakeStartedEvent, mergeJsonFile, writeInstructionBlock } from "./common.js";
|
|
4
|
+
|
|
5
|
+
const VERICIFY_MARKER = "<!-- vericify-instruction -->";
|
|
6
|
+
|
|
7
|
+
function copilotInstructionBlock() {
|
|
8
|
+
return `${VERICIFY_MARKER}
|
|
9
|
+
## Vericify Process Posts
|
|
10
|
+
|
|
11
|
+
When working in this workspace, emit native Vericify process posts at these moments:
|
|
12
|
+
|
|
13
|
+
- **Blocker:** \`vericify post --run-id workspace:current --agent-id vscode-copilot-chat --kind blocker --summary "<what is blocking>"\`
|
|
14
|
+
- **Handoff:** \`vericify post --run-id workspace:current --agent-id vscode-copilot-chat --kind handoff_note --summary "<handoff context>"\`
|
|
15
|
+
- **Completion:** \`vericify post --run-id workspace:current --agent-id vscode-copilot-chat --kind completion --summary "<what changed>"\`
|
|
16
|
+
- **Intentional silence:** \`vericify post --run-id workspace:current --agent-id vscode-copilot-chat --kind stale_ack --summary "<reason>"\`
|
|
17
|
+
<!-- /vericify-instruction -->`;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function vscodeCopilotHandshake(workspaceRoot, options = {}) {
|
|
21
|
+
const result = createHandshakeResult();
|
|
22
|
+
const skippedReasons = [];
|
|
23
|
+
const vscodeDir = resolve(workspaceRoot, ".vscode");
|
|
24
|
+
const githubDir = resolve(workspaceRoot, ".github");
|
|
25
|
+
const mcpPath = resolve(vscodeDir, "mcp.json");
|
|
26
|
+
const instructionPath = resolve(githubDir, "copilot-instructions.md");
|
|
27
|
+
|
|
28
|
+
if (existsSync(vscodeDir)) {
|
|
29
|
+
try {
|
|
30
|
+
const mcpResult = mergeJsonFile(mcpPath, (existing) => {
|
|
31
|
+
const current = existing && typeof existing === "object" ? existing : {};
|
|
32
|
+
return {
|
|
33
|
+
...current,
|
|
34
|
+
servers: {
|
|
35
|
+
...(current.servers && typeof current.servers === "object" ? current.servers : {}),
|
|
36
|
+
vericify: current.servers?.vericify ?? {
|
|
37
|
+
_vericify: true,
|
|
38
|
+
type: "stdio",
|
|
39
|
+
command: "vericify-mcp",
|
|
40
|
+
args: ["--workspace-root", "${workspaceFolder}"],
|
|
41
|
+
_note: "vericify-mcp not yet installed. Run: npm install -g vericify-mcp",
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
});
|
|
46
|
+
result.mcp_written = Boolean(mcpResult.written);
|
|
47
|
+
result.mcp_path = mcpPath;
|
|
48
|
+
if (mcpResult.skipped) skippedReasons.push(mcpResult.reason);
|
|
49
|
+
} catch (error) {
|
|
50
|
+
skippedReasons.push(`.vscode/mcp.json write failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
51
|
+
}
|
|
52
|
+
} else {
|
|
53
|
+
skippedReasons.push(".vscode directory is missing; MCP placeholder skipped");
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
try {
|
|
57
|
+
mkdirSync(dirname(instructionPath), { recursive: true });
|
|
58
|
+
const instructionResult = writeInstructionBlock(instructionPath, VERICIFY_MARKER, copilotInstructionBlock(), {
|
|
59
|
+
createIfAbsent: true,
|
|
60
|
+
createDirs: true,
|
|
61
|
+
});
|
|
62
|
+
result.instruction_written = Boolean(instructionResult.written);
|
|
63
|
+
result.instruction_path = instructionPath;
|
|
64
|
+
if (instructionResult.skipped) skippedReasons.push(instructionResult.reason);
|
|
65
|
+
} catch (error) {
|
|
66
|
+
skippedReasons.push(`copilot instructions write failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const eventResult = emitHandshakeStartedEvent(workspaceRoot, {
|
|
70
|
+
adapterId: "vscode-copilot-chat",
|
|
71
|
+
sessionId: options.sessionId ?? null,
|
|
72
|
+
hooksWritten: false,
|
|
73
|
+
instructionWritten: result.instruction_written,
|
|
74
|
+
mcpWritten: result.mcp_written,
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
...result,
|
|
79
|
+
event_emitted: eventResult.event_emitted,
|
|
80
|
+
skipped_reasons: [...skippedReasons, ...eventResult.skipped_reasons].filter(Boolean),
|
|
81
|
+
};
|
|
82
|
+
}
|
package/src/adapters/registry.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
2
|
import { resolve } from "node:path";
|
|
3
|
+
import { runHandshake } from "./handshake/index.js";
|
|
3
4
|
import { readAdapterAttachments, upsertAdapterAttachment } from "../store/adapter-attachments.js";
|
|
4
5
|
|
|
5
6
|
const ADAPTER_DEFINITIONS = [
|
|
@@ -90,7 +91,27 @@ export function readWorkspaceAdapterAttachments(workspaceRoot) {
|
|
|
90
91
|
}
|
|
91
92
|
|
|
92
93
|
export function attachWorkspaceAdapter(workspaceRoot, input) {
|
|
93
|
-
|
|
94
|
+
const attachment = upsertAdapterAttachment(workspaceRoot, input);
|
|
95
|
+
let handshake;
|
|
96
|
+
try {
|
|
97
|
+
handshake = runHandshake(workspaceRoot, {
|
|
98
|
+
adapterId: input.adapter_id,
|
|
99
|
+
sessionId: input.session_id,
|
|
100
|
+
captureMode: input.capture_mode,
|
|
101
|
+
});
|
|
102
|
+
} catch (error) {
|
|
103
|
+
handshake = { error: error instanceof Error ? error.message : String(error) };
|
|
104
|
+
}
|
|
105
|
+
const skippedReasons = [
|
|
106
|
+
...(Array.isArray(handshake?.skipped_reasons) ? handshake.skipped_reasons : []),
|
|
107
|
+
...(handshake?.reason ? [handshake.reason] : []),
|
|
108
|
+
...(handshake?.error ? [`Handshake error: ${handshake.error}`] : []),
|
|
109
|
+
];
|
|
110
|
+
return {
|
|
111
|
+
...attachment,
|
|
112
|
+
skipped_reasons: skippedReasons,
|
|
113
|
+
handshake,
|
|
114
|
+
};
|
|
94
115
|
}
|
|
95
116
|
|
|
96
117
|
export function detectWorkspaceAdapters(workspaceRoot, attachments = undefined) {
|
package/src/api.js
CHANGED
|
@@ -7,6 +7,8 @@ export {
|
|
|
7
7
|
loadWorkspaceState,
|
|
8
8
|
} from "./adapters/index.js";
|
|
9
9
|
export { buildRunComparison, findRunById } from "./compare/engine.js";
|
|
10
|
+
export { buildCompactDelta } from "./context/delta.js";
|
|
11
|
+
export { buildCompactPacket, buildCompactPacketDetails } from "./context/packet.js";
|
|
10
12
|
export { projectWorkspaceState } from "./projection/runs.js";
|
|
11
13
|
export { publishRunArtifact } from "./publish/artifact.js";
|
|
12
14
|
export { enqueueSyncOutboxItem } from "./sync/outbox.js";
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { buildCompactPacketDetails } from "./packet.js";
|
|
2
|
+
import { decodeCompactCursor, isCompactCursor } from "./id.js";
|
|
3
|
+
|
|
4
|
+
function oneLine(value) {
|
|
5
|
+
return String(value ?? "")
|
|
6
|
+
.replace(/\s+/g, " ")
|
|
7
|
+
.trim();
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function summarizeChangeSummary(current, previous) {
|
|
11
|
+
const rows = [];
|
|
12
|
+
if (!previous) return rows;
|
|
13
|
+
|
|
14
|
+
if (previous.run !== current.run) {
|
|
15
|
+
rows.push(`run switched from ${previous.run ?? "unknown"} to ${current.run}.`);
|
|
16
|
+
}
|
|
17
|
+
if (previous.latest_checkpoint_id !== current.latest_checkpoint_id) {
|
|
18
|
+
rows.push(
|
|
19
|
+
`latest checkpoint moved from ${previous.latest_checkpoint_id ?? "none"} to ${current.latest_checkpoint_id ?? "none"}.`
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
if (previous.last_activity_id !== current.last_activity_id) {
|
|
23
|
+
rows.push(`latest activity is now ${current.last_activity_id ?? "unknown"}.`);
|
|
24
|
+
}
|
|
25
|
+
if (previous.last_meaningful_update_at !== current.last_meaningful_update_at) {
|
|
26
|
+
rows.push(`last meaningful update changed to ${current.last_meaningful_update_at ?? "unknown"}.`);
|
|
27
|
+
}
|
|
28
|
+
if (previous.packet_digest !== current.packet_digest) {
|
|
29
|
+
rows.push("packet content changed.");
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return [...new Set(rows)].slice(0, 3);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function buildFallbackChangeSummary(current, since) {
|
|
36
|
+
return [
|
|
37
|
+
`No compact cursor matched ${since}.`,
|
|
38
|
+
`Current checkpoint is ${current.latest_checkpoint_id ?? "unknown"}.`,
|
|
39
|
+
];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function determineChanged(currentCursor, since, selectedRun) {
|
|
43
|
+
if (isCompactCursor(since)) {
|
|
44
|
+
const previous = decodeCompactCursor(since);
|
|
45
|
+
if (!previous) {
|
|
46
|
+
return { changed: true, previous: null };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
changed:
|
|
51
|
+
previous.run !== currentCursor.run ||
|
|
52
|
+
previous.status !== currentCursor.status ||
|
|
53
|
+
previous.latest_checkpoint_id !== currentCursor.latest_checkpoint_id ||
|
|
54
|
+
previous.last_meaningful_update_at !== currentCursor.last_meaningful_update_at ||
|
|
55
|
+
previous.last_activity_id !== currentCursor.last_activity_id ||
|
|
56
|
+
previous.packet_digest !== currentCursor.packet_digest,
|
|
57
|
+
previous,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (selectedRun?.run?.run_id === "workspace:current") {
|
|
62
|
+
throw new Error(
|
|
63
|
+
"workspace:current resume requires a vcx_... cursor; run vericify context and reuse the emitted id."
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const latestCheckpointId = currentCursor.latest_checkpoint_id;
|
|
68
|
+
const changed = since !== latestCheckpointId;
|
|
69
|
+
return { changed, previous: null };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function buildCompactDelta(projected, options = {}) {
|
|
73
|
+
const since = String(options.since ?? "").trim();
|
|
74
|
+
if (!since) {
|
|
75
|
+
throw new Error("Compact delta requires --since.");
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const details = buildCompactPacketDetails(projected, options);
|
|
79
|
+
const currentCursor = details.cursor.payload;
|
|
80
|
+
const { changed, previous } = determineChanged(currentCursor, since, details.selectedRun);
|
|
81
|
+
const delta = {
|
|
82
|
+
since,
|
|
83
|
+
changed,
|
|
84
|
+
...details.packet,
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
if (changed) {
|
|
88
|
+
const change_summary = isCompactCursor(since) && previous
|
|
89
|
+
? summarizeChangeSummary(currentCursor, previous)
|
|
90
|
+
: buildFallbackChangeSummary(currentCursor, since);
|
|
91
|
+
if (change_summary.length) {
|
|
92
|
+
delta.change_summary = change_summary;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return delta;
|
|
97
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { hashText } from "../core/util.js";
|
|
2
|
+
|
|
3
|
+
const VCX_PREFIX = "vcx_";
|
|
4
|
+
|
|
5
|
+
function encodeBase64Url(value) {
|
|
6
|
+
return Buffer.from(value, "utf8").toString("base64url");
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function decodeBase64Url(value) {
|
|
10
|
+
return Buffer.from(value, "base64url").toString("utf8");
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function isCompactCursor(value) {
|
|
14
|
+
return String(value ?? "").startsWith(VCX_PREFIX);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function decodeCompactCursor(value) {
|
|
18
|
+
if (!isCompactCursor(value)) return null;
|
|
19
|
+
try {
|
|
20
|
+
const raw = decodeBase64Url(String(value).slice(VCX_PREFIX.length));
|
|
21
|
+
const parsed = JSON.parse(raw);
|
|
22
|
+
return parsed && typeof parsed === "object" ? parsed : null;
|
|
23
|
+
} catch {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function buildCompactCursorPayload({
|
|
29
|
+
run,
|
|
30
|
+
status,
|
|
31
|
+
latest_checkpoint_id,
|
|
32
|
+
last_meaningful_update_at,
|
|
33
|
+
last_activity_id,
|
|
34
|
+
packet_digest,
|
|
35
|
+
}) {
|
|
36
|
+
return {
|
|
37
|
+
version: 1,
|
|
38
|
+
run: String(run ?? ""),
|
|
39
|
+
status: String(status ?? ""),
|
|
40
|
+
latest_checkpoint_id: latest_checkpoint_id ?? null,
|
|
41
|
+
last_meaningful_update_at: last_meaningful_update_at ?? null,
|
|
42
|
+
last_activity_id: last_activity_id ?? null,
|
|
43
|
+
packet_digest: String(packet_digest ?? ""),
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function encodeCompactCursor(payload) {
|
|
48
|
+
return `${VCX_PREFIX}${encodeBase64Url(JSON.stringify(payload))}`;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function buildCompactCursor(input) {
|
|
52
|
+
const payload = buildCompactCursorPayload(input);
|
|
53
|
+
return {
|
|
54
|
+
cursor: encodeCompactCursor(payload),
|
|
55
|
+
payload,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function compactPacketDigest(packet) {
|
|
60
|
+
return hashText(JSON.stringify(packet));
|
|
61
|
+
}
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import { buildRunComparison } from "../compare/engine.js";
|
|
2
|
+
import { firstDefined, unique } from "../core/util.js";
|
|
3
|
+
import { isMeaningfulActivity } from "../projection/live-signal.js";
|
|
4
|
+
import { buildCompactCursor, compactPacketDigest } from "./id.js";
|
|
5
|
+
import { selectCompactRun } from "./select.js";
|
|
6
|
+
|
|
7
|
+
function oneLine(value) {
|
|
8
|
+
return String(value ?? "")
|
|
9
|
+
.replace(/\s+/g, " ")
|
|
10
|
+
.trim();
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function truncate(value, max = 160) {
|
|
14
|
+
const text = oneLine(value);
|
|
15
|
+
if (text.length <= max) return text;
|
|
16
|
+
return `${text.slice(0, Math.max(0, max - 1)).trimEnd()}…`;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function splitFocusText(value) {
|
|
20
|
+
return oneLine(value)
|
|
21
|
+
.split(/(?:\n|,|;)+/)
|
|
22
|
+
.map((part) => part.trim())
|
|
23
|
+
.filter(Boolean);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function formatEvent(item) {
|
|
27
|
+
const parts = [];
|
|
28
|
+
if (item?.actor_id) parts.push(oneLine(item.actor_id));
|
|
29
|
+
if (item?.label) parts.push(oneLine(item.label));
|
|
30
|
+
if (item?.summary) parts.push(truncate(item.summary, 120));
|
|
31
|
+
return parts.length ? parts.join(": ") : truncate(item?.activity_id ?? "activity");
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function latestMeaningfulActivity(runDetail) {
|
|
35
|
+
const meaningful = (runDetail?.activity_items ?? [])
|
|
36
|
+
.filter((item) => isMeaningfulActivity(item))
|
|
37
|
+
.sort((left, right) => String(left.timestamp ?? "").localeCompare(String(right.timestamp ?? "")));
|
|
38
|
+
return meaningful.at(-1) ?? null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function nullIfUndefined(value) {
|
|
42
|
+
return value === undefined ? null : value;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const RECOMMENDED_ACTION_SENTENCES = {
|
|
46
|
+
accept_handoff: "Accept the handoff and continue with the incoming work.",
|
|
47
|
+
write_note: "Write the next note or blocker update.",
|
|
48
|
+
resolve_blocker: "Resolve the blocker before continuing.",
|
|
49
|
+
update_state: "Post the current state update.",
|
|
50
|
+
clarify_owner: "Clarify ownership before proceeding.",
|
|
51
|
+
ack_stale: "Acknowledge the stale state and re-check progress.",
|
|
52
|
+
observe_only: "Observe only. No urgent operator action.",
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
function sentenceForRecommendedAction(action) {
|
|
56
|
+
return RECOMMENDED_ACTION_SENTENCES[String(action ?? "").trim()] ?? null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function deriveFocus(runDetail, latestActivity = null) {
|
|
60
|
+
const focus = [];
|
|
61
|
+
const summaryLabels = runDetail?.run_summary?.current_node_labels ?? [];
|
|
62
|
+
focus.push(...summaryLabels.slice(0, 3).map((label) => oneLine(label)));
|
|
63
|
+
|
|
64
|
+
const latestCheckpoint = runDetail?.recent_checkpoints?.at(-1);
|
|
65
|
+
focus.push(...splitFocusText(latestCheckpoint?.task_delta_summary ?? ""));
|
|
66
|
+
if (!focus.length) {
|
|
67
|
+
focus.push(...splitFocusText(latestCheckpoint?.process_summary?.split("\n")?.[0] ?? ""));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const meaningfulActivity = latestActivity ?? latestMeaningfulActivity(runDetail);
|
|
71
|
+
if (meaningfulActivity?.summary) {
|
|
72
|
+
focus.push(truncate(meaningfulActivity.summary, 120));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (!focus.length && runDetail?.run?.title) {
|
|
76
|
+
focus.push(oneLine(runDetail.run.title));
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return unique(focus).slice(0, 3);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function deriveBlockers(runDetail) {
|
|
83
|
+
const blockers = [];
|
|
84
|
+
const blockedNodes = (runDetail?.nodes ?? []).filter((node) => node.status === "blocked").map((node) => node.title);
|
|
85
|
+
blockers.push(...blockedNodes.map((title) => oneLine(title)));
|
|
86
|
+
|
|
87
|
+
const signal = runDetail?.live_signal ?? {};
|
|
88
|
+
if (["blocked", "overdue_handoff"].includes(signal.attention_class)) {
|
|
89
|
+
blockers.push(`Attention class: ${signal.attention_class}`);
|
|
90
|
+
}
|
|
91
|
+
if (signal.attention_reason) {
|
|
92
|
+
blockers.push(truncate(signal.attention_reason, 160));
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return unique(blockers).slice(0, 3);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function deriveLastEvents(runDetail) {
|
|
99
|
+
const meaningful = (runDetail?.activity_items ?? []).filter((item) => isMeaningfulActivity(item));
|
|
100
|
+
const source = meaningful.length
|
|
101
|
+
? meaningful
|
|
102
|
+
: (runDetail?.recent_checkpoints ?? []).map((checkpoint, index) => ({
|
|
103
|
+
activity_id: checkpoint.checkpoint_id ?? `${runDetail?.run?.run_id ?? "run"}:checkpoint:${index + 1}`,
|
|
104
|
+
actor_id: runDetail?.run?.run_id ?? "run",
|
|
105
|
+
label: "checkpoint",
|
|
106
|
+
summary: checkpoint.task_delta_summary ?? checkpoint.process_summary?.split("\n")?.[0] ?? "",
|
|
107
|
+
timestamp: checkpoint.timestamp,
|
|
108
|
+
}));
|
|
109
|
+
|
|
110
|
+
return [...source]
|
|
111
|
+
.sort((left, right) => String(left.timestamp ?? "").localeCompare(String(right.timestamp ?? "")))
|
|
112
|
+
.slice(-5)
|
|
113
|
+
.map((item) => formatEvent(item));
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function deriveLiveSignalSubset(runDetail) {
|
|
117
|
+
const signal = runDetail?.live_signal ?? {};
|
|
118
|
+
return {
|
|
119
|
+
attention_class: nullIfUndefined(signal.attention_class),
|
|
120
|
+
expected_next_event: nullIfUndefined(signal.expected_next_event),
|
|
121
|
+
recommended_post_kind: nullIfUndefined(signal.recommended_post_kind),
|
|
122
|
+
needs_narrative: nullIfUndefined(signal.needs_narrative),
|
|
123
|
+
should_interrupt_agent: nullIfUndefined(signal.should_interrupt_agent),
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function deriveRecommendedAction(selectedRun, comparisonRun) {
|
|
128
|
+
const liveSignalAction = oneLine(selectedRun?.live_signal?.recommended_owner_action ?? "");
|
|
129
|
+
const mappedAction = sentenceForRecommendedAction(liveSignalAction);
|
|
130
|
+
if (mappedAction) return mappedAction;
|
|
131
|
+
|
|
132
|
+
if (comparisonRun) {
|
|
133
|
+
const comparison = buildRunComparison(selectedRun, comparisonRun);
|
|
134
|
+
const recommended = comparison.recommended_actions?.[0];
|
|
135
|
+
if (recommended) return oneLine(recommended);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return "Observe only. No urgent operator action.";
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function buildPacketFingerprint(packet) {
|
|
142
|
+
return {
|
|
143
|
+
run: packet.run,
|
|
144
|
+
status: packet.status,
|
|
145
|
+
focus: packet.focus,
|
|
146
|
+
blockers: packet.blockers,
|
|
147
|
+
live_signal: packet.live_signal,
|
|
148
|
+
last_5_events: packet.last_5_events,
|
|
149
|
+
recommended_action: packet.recommended_action,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export function buildCompactPacketDetails(projected, options = {}) {
|
|
154
|
+
const selection = selectCompactRun(projected, options);
|
|
155
|
+
const selectedRun = selection.selectedRun;
|
|
156
|
+
const comparisonRun = selection.comparisonRun;
|
|
157
|
+
|
|
158
|
+
if (!selectedRun) {
|
|
159
|
+
throw new Error("Compact packet requires a selected run.");
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const latestActivity = latestMeaningfulActivity(selectedRun);
|
|
163
|
+
const focus = deriveFocus(selectedRun, latestActivity);
|
|
164
|
+
const blockers = deriveBlockers(selectedRun);
|
|
165
|
+
const last_5_events = deriveLastEvents(selectedRun);
|
|
166
|
+
const live_signal = deriveLiveSignalSubset(selectedRun);
|
|
167
|
+
const recommended_action = deriveRecommendedAction(selectedRun, comparisonRun);
|
|
168
|
+
|
|
169
|
+
const packet = {
|
|
170
|
+
run: selectedRun.run.run_id,
|
|
171
|
+
status: selectedRun.run.status,
|
|
172
|
+
focus,
|
|
173
|
+
blockers,
|
|
174
|
+
live_signal,
|
|
175
|
+
last_5_events,
|
|
176
|
+
recommended_action,
|
|
177
|
+
};
|
|
178
|
+
const fingerprint = buildPacketFingerprint(packet);
|
|
179
|
+
const packet_digest = compactPacketDigest(fingerprint);
|
|
180
|
+
const latestCheckpoint = selectedRun.recent_checkpoints?.at(-1);
|
|
181
|
+
const cursor = buildCompactCursor({
|
|
182
|
+
run: packet.run,
|
|
183
|
+
status: packet.status,
|
|
184
|
+
latest_checkpoint_id: latestCheckpoint?.checkpoint_id ?? null,
|
|
185
|
+
last_meaningful_update_at: firstDefined(
|
|
186
|
+
selectedRun.live_signal?.last_meaningful_update_at,
|
|
187
|
+
latestActivity?.timestamp,
|
|
188
|
+
selectedRun.run.updated_at
|
|
189
|
+
),
|
|
190
|
+
last_activity_id: firstDefined(latestActivity?.activity_id, latestCheckpoint?.checkpoint_id),
|
|
191
|
+
packet_digest,
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
return {
|
|
195
|
+
packet: {
|
|
196
|
+
id: cursor.cursor,
|
|
197
|
+
...packet,
|
|
198
|
+
},
|
|
199
|
+
selection,
|
|
200
|
+
selectedRun,
|
|
201
|
+
comparisonRun,
|
|
202
|
+
fingerprint,
|
|
203
|
+
cursor,
|
|
204
|
+
packet_digest,
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export function buildCompactPacket(projected, options = {}) {
|
|
209
|
+
return buildCompactPacketDetails(projected, options).packet;
|
|
210
|
+
}
|