letagents 0.12.12 → 0.12.14

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.
Files changed (55) hide show
  1. package/dist/mcp/git-remote.js +7 -7
  2. package/dist/mcp/local-state/agent-sessions.js +63 -5
  3. package/dist/mcp/local-state/local-chat.js +189 -48
  4. package/dist/mcp/local-state/storage.js +16 -9
  5. package/dist/mcp/server/daemon-tool-executor.js +92 -0
  6. package/dist/mcp/server/register-tools.js +4 -2
  7. package/dist/mcp/server/runtime/agent-sessions.js +16 -7
  8. package/dist/mcp/server/runtime/api.js +11 -3
  9. package/dist/mcp/server/runtime/daemon-tool-context.js +11 -0
  10. package/dist/mcp/server/runtime/execution-profile.js +1 -0
  11. package/dist/mcp/server/runtime/messages.js +55 -0
  12. package/dist/mcp/server/runtime/presence.js +3 -3
  13. package/dist/mcp/server/runtime/room-api.js +2 -2
  14. package/dist/mcp/server/runtime/room-state.js +19 -9
  15. package/dist/mcp/server/runtime/rooms.js +54 -33
  16. package/dist/mcp/server/runtime/supervisor-bridge.js +129 -11
  17. package/dist/mcp/server/runtime/tool-surface-policy.js +7 -0
  18. package/dist/mcp/server/runtime/worker-bearer.js +17 -2
  19. package/dist/mcp/server/runtime-contract.js +1 -0
  20. package/dist/mcp/server/runtime.js +6 -6
  21. package/dist/mcp/server/supervised-tool-facade.js +107 -6
  22. package/dist/mcp/server/tools/messages/read-tool.js +54 -97
  23. package/dist/mcp/server/tools/messages/reasoning-tool.js +2 -0
  24. package/dist/mcp/server/tools/messages/send-tool.js +2 -0
  25. package/dist/mcp/server/tools/messages/status-tool.js +2 -0
  26. package/dist/mcp/server/tools/messages/wait-tool.js +303 -71
  27. package/dist/mcp/server/tools/onboarding/status-tool.js +4 -4
  28. package/dist/mcp/server/tools/rooms/inspection-tools.js +15 -10
  29. package/dist/mcp/server/tools/rooms/repo-initialization-tool.js +2 -1
  30. package/dist/mcp/server/tools/tasks/board-tools.js +34 -2
  31. package/dist/mcp/sse-client.js +163 -20
  32. package/dist/shared/activation-routing.js +163 -23
  33. package/dist/shared/agent-presence.js +6 -0
  34. package/dist/shared/desktop-release-manifest.js +63 -0
  35. package/dist/shared/desktop-release.js +60 -0
  36. package/dist/shared/scoped-ids.js +6 -0
  37. package/package.json +7 -3
  38. package/shared/execution-approval-projection.d.mts +32 -0
  39. package/shared/execution-approval-projection.mjs +107 -0
  40. package/shared/execution-approval-publication-item.d.mts +20 -0
  41. package/shared/execution-approval-publication-item.mjs +61 -0
  42. package/shared/execution-approval-publication.d.mts +53 -0
  43. package/shared/execution-approval-publication.mjs +136 -0
  44. package/shared/execution-delegation-decision.d.mts +37 -0
  45. package/shared/execution-delegation-decision.mjs +73 -0
  46. package/shared/message-contracts.d.mts +32 -0
  47. package/shared/message-contracts.mjs +109 -0
  48. package/shared/room-agent-work.d.mts +31 -0
  49. package/shared/room-agent-work.mjs +44 -0
  50. package/shared/room-resource-invalidation.d.mts +38 -0
  51. package/shared/room-resource-invalidation.mjs +50 -0
  52. package/shared/routing-aliases.d.mts +18 -0
  53. package/shared/routing-aliases.mjs +66 -0
  54. package/shared/sqlite-thread-routing.d.mts +72 -0
  55. package/shared/sqlite-thread-routing.mjs +1038 -0
@@ -0,0 +1,60 @@
1
+ const MAC_DESKTOP_BETA_VERSION = "0.1.5";
2
+ const MAC_DESKTOP_PUBLIC_BASE_URL = "https://downloads.letagents.chat/desktop";
3
+ function macDesktopAsset(architecture, sha256) {
4
+ const fileName = `LetAgents-${MAC_DESKTOP_BETA_VERSION}-darwin-${architecture}.dmg`;
5
+ return {
6
+ fileName,
7
+ publicUrl: `${MAC_DESKTOP_PUBLIC_BASE_URL}/v${MAC_DESKTOP_BETA_VERSION}/${fileName}`,
8
+ sha256,
9
+ };
10
+ }
11
+ export const MAC_DESKTOP_BETA_RELEASE = {
12
+ version: MAC_DESKTOP_BETA_VERSION,
13
+ tag: `desktop-v${MAC_DESKTOP_BETA_VERSION}`,
14
+ assets: {
15
+ arm64: macDesktopAsset("arm64", "704355796cee5214a9cfbb79105a035e0e00dc3a3aa57be7d389b97cf35fc804"),
16
+ x64: macDesktopAsset("x64", "1e4b0b8b2c42ad8eaaf652fc887ab089445c8dd83233d763a6acdaa6617c2471"),
17
+ },
18
+ };
19
+ export const MAC_DESKTOP_BETA_CHECKSUM_RELEASES = [
20
+ MAC_DESKTOP_BETA_RELEASE,
21
+ {
22
+ version: "0.1.4",
23
+ assets: {
24
+ arm64: {
25
+ fileName: "LetAgents-0.1.4-darwin-arm64.dmg",
26
+ sha256: "27abe236232d33db10ed4533f4a7443a66f93568e9fa73a2ca472b6467fcf1cb",
27
+ },
28
+ x64: {
29
+ fileName: "LetAgents-0.1.4-darwin-x64.dmg",
30
+ sha256: "4c807ad0c799b4e46ab81d1098e5b65934d6b0b49f0a99ced2713897e1c2bc35",
31
+ },
32
+ },
33
+ },
34
+ {
35
+ version: "0.1.3",
36
+ assets: {
37
+ arm64: {
38
+ fileName: "LetAgents-0.1.3-darwin-arm64.dmg",
39
+ sha256: "6010454bc7375a38571d707f90c077207a7d2b49b01a1db1655b03f4def9b502",
40
+ },
41
+ x64: {
42
+ fileName: "LetAgents-0.1.3-darwin-x64.dmg",
43
+ sha256: "b7574e17ef87aebf418926478de10d9937fd1a96ca0c7a53b826a109302c5560",
44
+ },
45
+ },
46
+ },
47
+ {
48
+ version: "0.1.2",
49
+ assets: {
50
+ arm64: {
51
+ fileName: "LetAgents-0.1.2-darwin-arm64.dmg",
52
+ sha256: "e5355deced8383bc7d024ec60b109a38dde69dfeb6b6339352e1f5bc5c53bd43",
53
+ },
54
+ x64: {
55
+ fileName: "LetAgents-0.1.2-darwin-x64.dmg",
56
+ sha256: "67d2896b806695dae8c0224b3bc2780aee6902e897569c983ecc1bdb0330b6b0",
57
+ },
58
+ },
59
+ },
60
+ ];
@@ -0,0 +1,6 @@
1
+ import { POSTGRES_INTEGER_MAX, parsePositivePgIntegerScopedId as parseSharedPositivePgIntegerScopedId, } from "../../shared/message-contracts.mjs";
2
+ export { POSTGRES_INTEGER_MAX };
3
+ /** Parse the canonical `<prefix>_<positive PostgreSQL integer>` wire form. */
4
+ export function parsePositivePgIntegerScopedId(value, prefix) {
5
+ return parseSharedPositivePgIntegerScopedId(value, prefix);
6
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "letagents",
3
- "version": "0.12.12",
3
+ "version": "0.12.14",
4
4
  "description": "Let Agents Chat — MCP server for AI agent communication",
5
5
  "type": "module",
6
6
  "main": "dist/mcp/server.js",
@@ -11,6 +11,7 @@
11
11
  "dist/mcp/**",
12
12
  "dist/api/board-intent-payloads.js",
13
13
  "dist/shared/**",
14
+ "shared/**",
14
15
  "README.md"
15
16
  ],
16
17
  "scripts": {
@@ -24,6 +25,9 @@
24
25
  "test:orchestrator": "node --import tsx --test src/orchestrator/__tests__/*.test.ts",
25
26
  "test:web": "node --import tsx --test src/web/tests/*.test.ts src/web/src/composables/*.test.ts",
26
27
  "test:dependency-age": "node --test scripts/verify-dependency-age.test.mjs",
28
+ "test:docker-context": "node --test scripts/verify-docker-context.test.mjs",
29
+ "test:workflow-supply-chain": "node --test scripts/verify-workflow-supply-chain.test.mjs scripts/verify-dependency-advisories.test.mjs",
30
+ "test:notifications": "node --import tsx --test src/api/notifications/__tests__/*.test.ts",
27
31
  "verify:dependency-age": "node scripts/verify-dependency-age.mjs",
28
32
  "db:generate": "drizzle-kit generate",
29
33
  "db:migrate": "tsx src/api/migrate.ts",
@@ -31,7 +35,7 @@
31
35
  "prepublishOnly": "npm run build",
32
36
  "dev:api": "tsx src/api/server.ts",
33
37
  "dev:mcp": "tsx src/mcp/server.ts",
34
- "dev:desktop": "npm --prefix apps/desktop run dev"
38
+ "dev:desktop": "node scripts/ensure-desktop-dependencies.mjs && npm --prefix apps/desktop run dev"
35
39
  },
36
40
  "keywords": [
37
41
  "mcp",
@@ -66,7 +70,7 @@
66
70
  "@esbuild-kit/core-utils": {
67
71
  "esbuild": "^0.25.12"
68
72
  },
69
- "fast-uri": "3.1.5",
73
+ "fast-uri": "3.1.6",
70
74
  "hono": "4.12.34",
71
75
  "ip-address": "10.3.1"
72
76
  }
@@ -0,0 +1,32 @@
1
+ export const EXECUTION_APPROVAL_PROJECTION_VERSION: 1;
2
+ export const EXECUTION_APPROVAL_PROJECTION_MAX_FILES: 128;
3
+ export const EXECUTION_APPROVAL_PROJECTION_MAX_PATH_BYTES: 4096;
4
+ export const EXECUTION_APPROVAL_PROJECTION_MAX_BYTES: number;
5
+
6
+ export type ExecutionApprovalProjectionChangeKind = "add" | "delete" | "update" | "move";
7
+ export type ExecutionApprovalProjectionChange = {
8
+ path: string;
9
+ kind: ExecutionApprovalProjectionChangeKind;
10
+ move_path: string | null;
11
+ added_lines: number;
12
+ removed_lines: number;
13
+ diff_bytes: number;
14
+ };
15
+ export type ExecutionApprovalProjectionV1 = {
16
+ version: 1;
17
+ category: "file_change";
18
+ path_scope: "workspace_relative";
19
+ changes: ExecutionApprovalProjectionChange[];
20
+ totals: {
21
+ file_count: number;
22
+ added_lines: number;
23
+ removed_lines: number;
24
+ diff_bytes: number;
25
+ };
26
+ };
27
+
28
+ export function isExecutionApprovalProjectionPath(value: unknown): value is string;
29
+ /** Return the canonical allowlisted projection, or reject it as a whole. */
30
+ export function parseExecutionApprovalProjectionV1(value: unknown): ExecutionApprovalProjectionV1 | null;
31
+ /** Serialize only the canonical, bounded bytes that a delegate may see. */
32
+ export function serializeExecutionApprovalProjectionV1(value: unknown): string | null;
@@ -0,0 +1,107 @@
1
+ export const EXECUTION_APPROVAL_PROJECTION_VERSION = 1;
2
+ export const EXECUTION_APPROVAL_PROJECTION_MAX_FILES = 128;
3
+ export const EXECUTION_APPROVAL_PROJECTION_MAX_PATH_BYTES = 4096;
4
+ export const EXECUTION_APPROVAL_PROJECTION_MAX_BYTES = 24 * 1024;
5
+
6
+ const ROOT_KEYS = ["version", "category", "path_scope", "changes", "totals"];
7
+ const CHANGE_KEYS = ["path", "kind", "move_path", "added_lines", "removed_lines", "diff_bytes"];
8
+ const TOTAL_KEYS = ["file_count", "added_lines", "removed_lines", "diff_bytes"];
9
+ const CHANGE_KINDS = ["add", "delete", "update", "move"];
10
+ const UNSAFE_PATH_CHARACTERS = /[\u0000-\u001f\u007f-\u009f\u202a-\u202e\u2066-\u2069]/;
11
+
12
+ function exactKeys(value, keys) {
13
+ return !!value && typeof value === "object" && !Array.isArray(value)
14
+ && Object.keys(value).length === keys.length
15
+ && keys.every((key) => Object.hasOwn(value, key));
16
+ }
17
+
18
+ function count(value) {
19
+ return Number.isSafeInteger(value) && value >= 0;
20
+ }
21
+
22
+ export function isExecutionApprovalProjectionPath(value) {
23
+ return typeof value === "string"
24
+ && value.length > 0
25
+ && value.normalize("NFC") === value
26
+ && new TextEncoder().encode(value).byteLength <= EXECUTION_APPROVAL_PROJECTION_MAX_PATH_BYTES
27
+ && !value.startsWith("/")
28
+ && !value.includes("\\")
29
+ && !UNSAFE_PATH_CHARACTERS.test(value)
30
+ && value.split("/").every((part) => part.length > 0 && part !== "." && part !== "..");
31
+ }
32
+
33
+ function compareChanges(left, right) {
34
+ for (const [a, b] of [[left.path, right.path], [left.kind, right.kind], [left.move_path ?? "", right.move_path ?? ""]]) {
35
+ if (a < b) return -1;
36
+ if (a > b) return 1;
37
+ }
38
+ return 0;
39
+ }
40
+
41
+ /** Return the canonical allowlisted projection, or reject it as a whole. */
42
+ export function parseExecutionApprovalProjectionV1(value) {
43
+ if (!exactKeys(value, ROOT_KEYS)
44
+ || value.version !== EXECUTION_APPROVAL_PROJECTION_VERSION
45
+ || value.category !== "file_change"
46
+ || value.path_scope !== "workspace_relative"
47
+ || !Array.isArray(value.changes)
48
+ || value.changes.length < 1
49
+ || value.changes.length > EXECUTION_APPROVAL_PROJECTION_MAX_FILES
50
+ || !exactKeys(value.totals, TOTAL_KEYS)) return null;
51
+
52
+ const changes = [];
53
+ for (const candidate of value.changes) {
54
+ if (!exactKeys(candidate, CHANGE_KEYS)
55
+ || !isExecutionApprovalProjectionPath(candidate.path)
56
+ || !CHANGE_KINDS.includes(candidate.kind)
57
+ || !count(candidate.added_lines)
58
+ || !count(candidate.removed_lines)
59
+ || !count(candidate.diff_bytes)
60
+ || (candidate.kind === "move") !== (candidate.move_path !== null)
61
+ || (candidate.move_path !== null && !isExecutionApprovalProjectionPath(candidate.move_path))) return null;
62
+ changes.push({
63
+ path: candidate.path,
64
+ kind: candidate.kind,
65
+ move_path: candidate.move_path,
66
+ added_lines: candidate.added_lines,
67
+ removed_lines: candidate.removed_lines,
68
+ diff_bytes: candidate.diff_bytes,
69
+ });
70
+ }
71
+ changes.sort(compareChanges);
72
+ const occupied = new Set();
73
+ for (const change of changes) {
74
+ if (occupied.has(change.path)) return null;
75
+ occupied.add(change.path);
76
+ if (change.move_path !== null) {
77
+ if (occupied.has(change.move_path)) return null;
78
+ occupied.add(change.move_path);
79
+ }
80
+ }
81
+ const totals = changes.reduce((result, change) => ({
82
+ file_count: result.file_count + 1,
83
+ added_lines: result.added_lines + change.added_lines,
84
+ removed_lines: result.removed_lines + change.removed_lines,
85
+ diff_bytes: result.diff_bytes + change.diff_bytes,
86
+ }), { file_count: 0, added_lines: 0, removed_lines: 0, diff_bytes: 0 });
87
+ if (!Object.values(totals).every(Number.isSafeInteger)
88
+ || TOTAL_KEYS.some((key) => value.totals[key] !== totals[key])) return null;
89
+
90
+ return {
91
+ version: EXECUTION_APPROVAL_PROJECTION_VERSION,
92
+ category: "file_change",
93
+ path_scope: "workspace_relative",
94
+ changes,
95
+ totals,
96
+ };
97
+ }
98
+
99
+ /** Serialize only the canonical, bounded bytes that a delegate may see. */
100
+ export function serializeExecutionApprovalProjectionV1(value) {
101
+ const parsed = parseExecutionApprovalProjectionV1(value);
102
+ if (!parsed) return null;
103
+ const serialized = JSON.stringify(parsed);
104
+ return new TextEncoder().encode(serialized).byteLength <= EXECUTION_APPROVAL_PROJECTION_MAX_BYTES
105
+ ? serialized
106
+ : null;
107
+ }
@@ -0,0 +1,20 @@
1
+ export type ExecutionApprovalPublicationItem = {
2
+ publication_id: string;
3
+ room_id: string;
4
+ agent_key: string;
5
+ delegation_instance_id: string;
6
+ delegation_revision: number;
7
+ request_id: string;
8
+ request_version: number;
9
+ request_sha256: string;
10
+ projection_sha256: string;
11
+ published_at: string;
12
+ expires_at: string;
13
+ };
14
+
15
+ export function isExecutionApprovalPublicationIdentity(value: unknown): value is string;
16
+ export function isExecutionApprovalPublicationDigest(value: unknown): value is string;
17
+ export function isExecutionApprovalPublicationVersion(value: unknown): value is number;
18
+ export function parseExecutionApprovalPublicationItem(
19
+ value: unknown,
20
+ ): ExecutionApprovalPublicationItem | null;
@@ -0,0 +1,61 @@
1
+ const ITEM_KEYS = [
2
+ "publication_id",
3
+ "room_id",
4
+ "agent_key",
5
+ "delegation_instance_id",
6
+ "delegation_revision",
7
+ "request_id",
8
+ "request_version",
9
+ "request_sha256",
10
+ "projection_sha256",
11
+ "published_at",
12
+ "expires_at",
13
+ ];
14
+
15
+ function exactKeys(value, keys) {
16
+ return !!value && typeof value === "object" && !Array.isArray(value)
17
+ && Object.keys(value).length === keys.length
18
+ && keys.every((key) => Object.hasOwn(value, key));
19
+ }
20
+
21
+ export function isExecutionApprovalPublicationIdentity(value) {
22
+ return typeof value === "string"
23
+ && value.length > 0
24
+ && value.length <= 512
25
+ && value.trim() === value
26
+ && !/[\u0000-\u001f\u007f]/.test(value);
27
+ }
28
+
29
+ export function isExecutionApprovalPublicationDigest(value) {
30
+ return typeof value === "string" && /^[0-9a-f]{64}$/.test(value);
31
+ }
32
+
33
+ export function isExecutionApprovalPublicationVersion(value) {
34
+ return Number.isSafeInteger(value) && value >= 1 && value <= 2_147_483_647;
35
+ }
36
+
37
+ function canonicalTimestamp(value) {
38
+ if (typeof value !== "string") return null;
39
+ const timestamp = Date.parse(value);
40
+ return Number.isFinite(timestamp) && new Date(timestamp).toISOString() === value
41
+ ? timestamp
42
+ : null;
43
+ }
44
+
45
+ /** Parse the exact public inventory item shared by the API and browser. */
46
+ export function parseExecutionApprovalPublicationItem(value) {
47
+ if (!exactKeys(value, ITEM_KEYS)
48
+ || !isExecutionApprovalPublicationIdentity(value.publication_id)
49
+ || !isExecutionApprovalPublicationIdentity(value.room_id)
50
+ || !isExecutionApprovalPublicationIdentity(value.agent_key)
51
+ || !isExecutionApprovalPublicationIdentity(value.delegation_instance_id)
52
+ || !isExecutionApprovalPublicationVersion(value.delegation_revision)
53
+ || !isExecutionApprovalPublicationIdentity(value.request_id)
54
+ || !isExecutionApprovalPublicationVersion(value.request_version)
55
+ || !isExecutionApprovalPublicationDigest(value.request_sha256)
56
+ || !isExecutionApprovalPublicationDigest(value.projection_sha256)) return null;
57
+ const publishedAt = canonicalTimestamp(value.published_at);
58
+ const expiresAt = canonicalTimestamp(value.expires_at);
59
+ if (publishedAt === null || expiresAt === null || expiresAt <= publishedAt) return null;
60
+ return Object.fromEntries(ITEM_KEYS.map((key) => [key, value[key]]));
61
+ }
@@ -0,0 +1,53 @@
1
+ import type { ExecutionApprovalProjectionV1 } from "./execution-approval-projection.mjs";
2
+ import type { ExecutionApprovalPublicationItem } from "./execution-approval-publication-item.mjs";
3
+
4
+ export {
5
+ isExecutionApprovalPublicationDigest,
6
+ isExecutionApprovalPublicationIdentity,
7
+ isExecutionApprovalPublicationVersion,
8
+ parseExecutionApprovalPublicationItem,
9
+ } from "./execution-approval-publication-item.mjs";
10
+ export type { ExecutionApprovalPublicationItem } from "./execution-approval-publication-item.mjs";
11
+
12
+ export const EXECUTION_APPROVAL_PUBLICATION_VERSION: 1;
13
+ export const EXECUTION_APPROVAL_PUBLICATION_MAX_JSON_BYTES: number;
14
+
15
+ export type ExecutionApprovalPublicationInput = {
16
+ version: 1;
17
+ room_id: string;
18
+ source_message_id: string;
19
+ delegation_instance_id: string;
20
+ delegation_revision: number;
21
+ request_id: string;
22
+ request_version: number;
23
+ request_sha256: string;
24
+ projection_sha256: string;
25
+ projection_json: string;
26
+ produced_at: string;
27
+ expires_at: string;
28
+ };
29
+
30
+ export type ExecutionApprovalPublicationReceipt = {
31
+ status: "created" | "replayed";
32
+ publication_digest: string;
33
+ publication: ExecutionApprovalPublicationItem;
34
+ };
35
+
36
+ export type ExecutionApprovalPublicationCloseInput = {
37
+ publication_digest: string;
38
+ };
39
+
40
+ export type ExecutionApprovalPublicationCloseReceipt = {
41
+ status: "closed" | "replayed";
42
+ publication_id: string;
43
+ publication_digest: string;
44
+ closed_at: string;
45
+ };
46
+
47
+ export function parseExecutionApprovalPublicationInput(value: unknown): ExecutionApprovalPublicationInput | null;
48
+ export function parseExecutionApprovalPublicationReceipt(value: unknown): ExecutionApprovalPublicationReceipt | null;
49
+ export function parseExecutionApprovalPublicationCloseInput(value: unknown): ExecutionApprovalPublicationCloseInput | null;
50
+ export function parseExecutionApprovalPublicationCloseReceipt(value: unknown): ExecutionApprovalPublicationCloseReceipt | null;
51
+ export function executionApprovalPublicationSha256(value: unknown): string | null;
52
+
53
+ export type { ExecutionApprovalProjectionV1 };
@@ -0,0 +1,136 @@
1
+ import { createHash } from "node:crypto";
2
+
3
+ import {
4
+ parseExecutionApprovalProjectionV1,
5
+ serializeExecutionApprovalProjectionV1,
6
+ } from "./execution-approval-projection.mjs";
7
+ import {
8
+ isExecutionApprovalPublicationDigest,
9
+ isExecutionApprovalPublicationIdentity,
10
+ isExecutionApprovalPublicationVersion,
11
+ parseExecutionApprovalPublicationItem,
12
+ } from "./execution-approval-publication-item.mjs";
13
+
14
+ export {
15
+ isExecutionApprovalPublicationDigest,
16
+ isExecutionApprovalPublicationIdentity,
17
+ isExecutionApprovalPublicationVersion,
18
+ parseExecutionApprovalPublicationItem,
19
+ } from "./execution-approval-publication-item.mjs";
20
+
21
+ export const EXECUTION_APPROVAL_PUBLICATION_VERSION = 1;
22
+ export const EXECUTION_APPROVAL_PUBLICATION_MAX_JSON_BYTES = 24 * 1024;
23
+
24
+ const INPUT_KEYS = [
25
+ "version",
26
+ "room_id",
27
+ "source_message_id",
28
+ "delegation_instance_id",
29
+ "delegation_revision",
30
+ "request_id",
31
+ "request_version",
32
+ "request_sha256",
33
+ "projection_sha256",
34
+ "projection_json",
35
+ "produced_at",
36
+ "expires_at",
37
+ ];
38
+ const RECEIPT_KEYS = ["status", "publication_digest", "publication"];
39
+ const CLOSE_INPUT_KEYS = ["publication_digest"];
40
+ const CLOSE_RECEIPT_KEYS = ["status", "publication_id", "publication_digest", "closed_at"];
41
+
42
+ function exactKeys(value, keys) {
43
+ return !!value && typeof value === "object" && !Array.isArray(value)
44
+ && Object.keys(value).length === keys.length
45
+ && keys.every((key) => Object.hasOwn(value, key));
46
+ }
47
+
48
+ function canonicalTimestamp(value) {
49
+ if (typeof value !== "string") return null;
50
+ const timestamp = Date.parse(value);
51
+ return Number.isFinite(timestamp) && new Date(timestamp).toISOString() === value
52
+ ? timestamp
53
+ : null;
54
+ }
55
+
56
+ function sourceMessage(value) {
57
+ return typeof value === "string"
58
+ && /^msg_[1-9]\d{0,9}$/.test(value)
59
+ && Number(value.slice(4)) <= 2_147_483_647;
60
+ }
61
+
62
+ function canonicalProjection(value) {
63
+ if (typeof value !== "string"
64
+ || new TextEncoder().encode(value).byteLength > EXECUTION_APPROVAL_PUBLICATION_MAX_JSON_BYTES) return false;
65
+ let parsed;
66
+ try { parsed = JSON.parse(value); } catch { return false; }
67
+ const projection = parseExecutionApprovalProjectionV1(parsed);
68
+ return projection !== null && serializeExecutionApprovalProjectionV1(projection) === value;
69
+ }
70
+
71
+ /** Parse exact daemon-to-server publication bytes; digest verification remains the receiver's job. */
72
+ export function parseExecutionApprovalPublicationInput(value) {
73
+ if (!exactKeys(value, INPUT_KEYS)
74
+ || value.version !== EXECUTION_APPROVAL_PUBLICATION_VERSION
75
+ || !isExecutionApprovalPublicationIdentity(value.room_id)
76
+ || !sourceMessage(value.source_message_id)
77
+ || !isExecutionApprovalPublicationIdentity(value.delegation_instance_id)
78
+ || !isExecutionApprovalPublicationVersion(value.delegation_revision)
79
+ || !isExecutionApprovalPublicationIdentity(value.request_id)
80
+ || !isExecutionApprovalPublicationVersion(value.request_version)
81
+ || !isExecutionApprovalPublicationDigest(value.request_sha256)
82
+ || !isExecutionApprovalPublicationDigest(value.projection_sha256)
83
+ || !canonicalProjection(value.projection_json)) return null;
84
+ const producedAt = canonicalTimestamp(value.produced_at);
85
+ const expiresAt = canonicalTimestamp(value.expires_at);
86
+ if (producedAt === null || expiresAt === null || expiresAt <= producedAt) return null;
87
+ return Object.fromEntries(INPUT_KEYS.map((key) => [key, value[key]]));
88
+ }
89
+
90
+ export function parseExecutionApprovalPublicationReceipt(value) {
91
+ if (!exactKeys(value, RECEIPT_KEYS)
92
+ || !["created", "replayed"].includes(value.status)
93
+ || !isExecutionApprovalPublicationDigest(value.publication_digest)) return null;
94
+ const publication = parseExecutionApprovalPublicationItem(value.publication);
95
+ return publication ? {
96
+ status: value.status,
97
+ publication_digest: value.publication_digest,
98
+ publication,
99
+ } : null;
100
+ }
101
+
102
+ /** Parse the content-free host acknowledgement that a publication is no longer actionable. */
103
+ export function parseExecutionApprovalPublicationCloseInput(value) {
104
+ return exactKeys(value, CLOSE_INPUT_KEYS)
105
+ && isExecutionApprovalPublicationDigest(value.publication_digest)
106
+ ? { publication_digest: value.publication_digest }
107
+ : null;
108
+ }
109
+
110
+ export function parseExecutionApprovalPublicationCloseReceipt(value) {
111
+ if (!exactKeys(value, CLOSE_RECEIPT_KEYS)
112
+ || !["closed", "replayed"].includes(value.status)
113
+ || !isExecutionApprovalPublicationIdentity(value.publication_id)
114
+ || !isExecutionApprovalPublicationDigest(value.publication_digest)
115
+ || canonicalTimestamp(value.closed_at) === null) return null;
116
+ return Object.fromEntries(CLOSE_RECEIPT_KEYS.map((key) => [key, value[key]]));
117
+ }
118
+
119
+ function stableJson(value) {
120
+ if (Array.isArray(value)) return `[${value.map(stableJson).join(",")}]`;
121
+ if (value && typeof value === "object") {
122
+ return `{${Object.entries(value)
123
+ .sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0)
124
+ .map(([key, entry]) => `${JSON.stringify(key)}:${stableJson(entry)}`)
125
+ .join(",")}}`;
126
+ }
127
+ return JSON.stringify(value);
128
+ }
129
+
130
+ /** Digest exact, validated publication input with the server's stable-key wire algorithm. */
131
+ export function executionApprovalPublicationSha256(value) {
132
+ const publication = parseExecutionApprovalPublicationInput(value);
133
+ return publication === null
134
+ ? null
135
+ : createHash("sha256").update(stableJson(publication), "utf8").digest("hex");
136
+ }
@@ -0,0 +1,37 @@
1
+ export const EXECUTION_DELEGATION_DECISIONS: readonly ["allow_once", "deny"];
2
+ export const EXECUTION_DELEGATION_DECISION_APPLICABILITY_MS: number;
3
+
4
+ export type ExecutionDelegationDecisionChoice =
5
+ typeof EXECUTION_DELEGATION_DECISIONS[number];
6
+
7
+ export function isExecutionDelegationDecision(
8
+ value: unknown,
9
+ ): value is ExecutionDelegationDecisionChoice;
10
+ export function isExecutionDelegationIdentity(value: unknown): value is string;
11
+ export function isExecutionDelegationDigest(value: unknown): value is string;
12
+ export function isExecutionDelegationPositiveInt32(value: unknown): value is number;
13
+
14
+ export type ExecutionDelegationDecisionIntent = {
15
+ decision_id: string;
16
+ delegation_instance_id: string;
17
+ delegation_revision: number;
18
+ actor_account_id: string;
19
+ request_id: string;
20
+ request_version: number;
21
+ request_sha256: string;
22
+ projection_sha256: string;
23
+ decision: ExecutionDelegationDecisionChoice;
24
+ decided_at: string;
25
+ owner_account_id: string;
26
+ room_id: string;
27
+ agent_key: string;
28
+ approver_account_id: string;
29
+ category: "file_change";
30
+ risk_ceiling: "low";
31
+ scope_sha256: string;
32
+ };
33
+
34
+ /** Return the exact host-delivered decision intent, or reject it as a whole. */
35
+ export function parseExecutionDelegationDecisionIntent(
36
+ value: unknown,
37
+ ): ExecutionDelegationDecisionIntent | null;
@@ -0,0 +1,73 @@
1
+ export const EXECUTION_DELEGATION_DECISIONS = ["allow_once", "deny"];
2
+ export const EXECUTION_DELEGATION_DECISION_APPLICABILITY_MS = 24 * 60 * 60 * 1000;
3
+
4
+ export function isExecutionDelegationDecision(value) {
5
+ return EXECUTION_DELEGATION_DECISIONS.includes(value);
6
+ }
7
+
8
+ const INTENT_KEYS = [
9
+ "decision_id",
10
+ "delegation_instance_id",
11
+ "delegation_revision",
12
+ "actor_account_id",
13
+ "request_id",
14
+ "request_version",
15
+ "request_sha256",
16
+ "projection_sha256",
17
+ "decision",
18
+ "decided_at",
19
+ "owner_account_id",
20
+ "room_id",
21
+ "agent_key",
22
+ "approver_account_id",
23
+ "category",
24
+ "risk_ceiling",
25
+ "scope_sha256",
26
+ ];
27
+
28
+ function exactKeys(value, keys) {
29
+ return !!value && typeof value === "object" && !Array.isArray(value)
30
+ && Object.keys(value).length === keys.length
31
+ && keys.every((key) => Object.hasOwn(value, key));
32
+ }
33
+
34
+ export function isExecutionDelegationIdentity(value) {
35
+ return typeof value === "string"
36
+ && value.length > 0
37
+ && value.length <= 512
38
+ && value.trim() === value
39
+ && !/[\u0000-\u001f\u007f]/.test(value);
40
+ }
41
+
42
+ export function isExecutionDelegationDigest(value) {
43
+ return typeof value === "string" && /^[0-9a-f]{64}$/.test(value);
44
+ }
45
+
46
+ export function isExecutionDelegationPositiveInt32(value) {
47
+ return Number.isSafeInteger(value) && value >= 1 && value <= 2_147_483_647;
48
+ }
49
+
50
+ /** Return the exact host-delivered decision intent, or reject it as a whole. */
51
+ export function parseExecutionDelegationDecisionIntent(value) {
52
+ if (!exactKeys(value, INTENT_KEYS)
53
+ || !isExecutionDelegationIdentity(value.decision_id)
54
+ || !isExecutionDelegationIdentity(value.delegation_instance_id)
55
+ || !isExecutionDelegationPositiveInt32(value.delegation_revision)
56
+ || !isExecutionDelegationIdentity(value.actor_account_id)
57
+ || !isExecutionDelegationIdentity(value.request_id)
58
+ || !isExecutionDelegationPositiveInt32(value.request_version)
59
+ || !isExecutionDelegationDigest(value.request_sha256)
60
+ || !isExecutionDelegationDigest(value.projection_sha256)
61
+ || !isExecutionDelegationDecision(value.decision)
62
+ || typeof value.decided_at !== "string" || !Number.isFinite(Date.parse(value.decided_at))
63
+ || !isExecutionDelegationIdentity(value.owner_account_id)
64
+ || !isExecutionDelegationIdentity(value.room_id)
65
+ || !isExecutionDelegationIdentity(value.agent_key)
66
+ || !isExecutionDelegationIdentity(value.approver_account_id)
67
+ || value.actor_account_id !== value.approver_account_id
68
+ || value.category !== "file_change"
69
+ || value.risk_ceiling !== "low"
70
+ || !isExecutionDelegationDigest(value.scope_sha256)) return null;
71
+
72
+ return Object.fromEntries(INTENT_KEYS.map((key) => [key, value[key]]));
73
+ }
@@ -0,0 +1,32 @@
1
+ export const POSTGRES_INTEGER_MAX: number;
2
+ export const MESSAGE_SENDER_MAX_CODE_POINTS: number;
3
+ export const MESSAGE_SENDER_MAX_UTF8_BYTES: number;
4
+ export function parsePositivePgIntegerScopedId(value: unknown, prefix: string): number | null;
5
+ export function isMessageSenderWithinBounds(sender: unknown): sender is string;
6
+ export type ParsedAccountAgentRouting =
7
+ | { version: 1; authority: "invalid" }
8
+ | {
9
+ version: 1;
10
+ authority: "receipts";
11
+ recipientAgentKeys: string[];
12
+ recipientSessions: Array<{
13
+ agentKey: string;
14
+ agentSessionId: string;
15
+ successorAgentSessionId?: string;
16
+ }>;
17
+ controlAuthorized: boolean;
18
+ }
19
+ | {
20
+ version: 1;
21
+ authority: "legacy";
22
+ recipientAgentKeys: string[];
23
+ recipientSessions: Array<{
24
+ agentKey: string;
25
+ agentSessionId: string;
26
+ activationReason: string;
27
+ }>;
28
+ controlAuthorized: boolean;
29
+ };
30
+ export function parseAccountAgentRoutingEnvelope(
31
+ routing: unknown,
32
+ ): ParsedAccountAgentRouting | undefined;