@viewportai/daemon 0.5.1 → 0.5.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/context-command.d.ts.map +1 -1
- package/dist/cli/context-command.js +81 -3
- package/dist/cli/context-command.js.map +1 -1
- package/dist/cli/context-sync-target.d.ts +2 -1
- package/dist/cli/context-sync-target.d.ts.map +1 -1
- package/dist/cli/context-sync-target.js +28 -0
- package/dist/cli/context-sync-target.js.map +1 -1
- package/dist/cli/hook-command.d.ts.map +1 -1
- package/dist/cli/hook-command.js +23 -1
- package/dist/cli/hook-command.js.map +1 -1
- package/dist/cli/install-command.d.ts.map +1 -1
- package/dist/cli/install-command.js +31 -6
- package/dist/cli/install-command.js.map +1 -1
- package/dist/cli/lifecycle-commands.d.ts.map +1 -1
- package/dist/cli/lifecycle-commands.js +6 -0
- package/dist/cli/lifecycle-commands.js.map +1 -1
- package/dist/cli/lifecycle-pair-command.js +2 -21
- package/dist/cli/lifecycle-pair-command.js.map +1 -1
- package/dist/cli/lifecycle-pair-server.d.ts.map +1 -1
- package/dist/cli/lifecycle-pair-server.js +4 -0
- package/dist/cli/lifecycle-pair-server.js.map +1 -1
- package/dist/cli/open-url.d.ts +2 -0
- package/dist/cli/open-url.d.ts.map +1 -0
- package/dist/cli/open-url.js +21 -0
- package/dist/cli/open-url.js.map +1 -0
- package/dist/cli/skills-command.d.ts.map +1 -1
- package/dist/cli/skills-command.js +27 -0
- package/dist/cli/skills-command.js.map +1 -1
- package/dist/context/local-edge-store.d.ts +12 -1
- package/dist/context/local-edge-store.d.ts.map +1 -1
- package/dist/context/local-edge-store.js +26 -0
- package/dist/context/local-edge-store.js.map +1 -1
- package/dist/context/local-edge-sync.d.ts +76 -0
- package/dist/context/local-edge-sync.d.ts.map +1 -1
- package/dist/context/local-edge-sync.js +304 -3
- package/dist/context/local-edge-sync.js.map +1 -1
- package/dist/context/local-edge-types.d.ts +9 -0
- package/dist/context/local-edge-types.d.ts.map +1 -1
- package/dist/core/daemon.d.ts +5 -0
- package/dist/core/daemon.d.ts.map +1 -1
- package/dist/core/daemon.js +11 -0
- package/dist/core/daemon.js.map +1 -1
- package/dist/hooks/ephemeral-plan-drafts.d.ts +34 -0
- package/dist/hooks/ephemeral-plan-drafts.d.ts.map +1 -0
- package/dist/hooks/ephemeral-plan-drafts.js +62 -0
- package/dist/hooks/ephemeral-plan-drafts.js.map +1 -0
- package/dist/hooks/installers/base.d.ts +2 -2
- package/dist/hooks/installers/base.d.ts.map +1 -1
- package/dist/hooks/installers/claude.d.ts.map +1 -1
- package/dist/hooks/installers/claude.js +35 -16
- package/dist/hooks/installers/claude.js.map +1 -1
- package/dist/hooks/plan-extractor.d.ts.map +1 -1
- package/dist/hooks/plan-extractor.js +6 -1
- package/dist/hooks/plan-extractor.js.map +1 -1
- package/dist/hooks/platform-plan-sync.d.ts +10 -8
- package/dist/hooks/platform-plan-sync.d.ts.map +1 -1
- package/dist/hooks/platform-plan-sync.js +67 -36
- package/dist/hooks/platform-plan-sync.js.map +1 -1
- package/dist/hooks/router.d.ts.map +1 -1
- package/dist/hooks/router.js +14 -0
- package/dist/hooks/router.js.map +1 -1
- package/dist/hooks/specific-events.d.ts.map +1 -1
- package/dist/hooks/specific-events.js +78 -2
- package/dist/hooks/specific-events.js.map +1 -1
- package/dist/hooks/trusted-edge-plan-artifacts.d.ts +114 -0
- package/dist/hooks/trusted-edge-plan-artifacts.d.ts.map +1 -0
- package/dist/hooks/trusted-edge-plan-artifacts.js +389 -0
- package/dist/hooks/trusted-edge-plan-artifacts.js.map +1 -0
- package/dist/relay/bridge-token-issuer.d.ts +1 -0
- package/dist/relay/bridge-token-issuer.d.ts.map +1 -1
- package/dist/relay/bridge-token-issuer.js +1 -1
- package/dist/relay/bridge-token-issuer.js.map +1 -1
- package/dist/server/context-preview-service.d.ts +26 -0
- package/dist/server/context-preview-service.d.ts.map +1 -0
- package/dist/server/context-preview-service.js +71 -0
- package/dist/server/context-preview-service.js.map +1 -0
- package/dist/server/http-context-routes.d.ts.map +1 -1
- package/dist/server/http-context-routes.js +19 -15
- package/dist/server/http-context-routes.js.map +1 -1
- package/dist/server/rate-limiter.d.ts.map +1 -1
- package/dist/server/rate-limiter.js +5 -0
- package/dist/server/rate-limiter.js.map +1 -1
- package/dist/server/trusted-edge-command-capability.d.ts +14 -0
- package/dist/server/trusted-edge-command-capability.d.ts.map +1 -0
- package/dist/server/trusted-edge-command-capability.js +103 -0
- package/dist/server/trusted-edge-command-capability.js.map +1 -0
- package/dist/server/ws-command-handlers.d.ts.map +1 -1
- package/dist/server/ws-command-handlers.js +135 -0
- package/dist/server/ws-command-handlers.js.map +1 -1
- package/dist/server/ws-protocol.d.ts +199 -0
- package/dist/server/ws-protocol.d.ts.map +1 -1
- package/dist/server/ws-protocol.js +81 -0
- package/dist/server/ws-protocol.js.map +1 -1
- package/dist/startup.d.ts.map +1 -1
- package/dist/startup.js +18 -1
- package/dist/startup.js.map +1 -1
- package/docs/protocol-matrix.json +66 -0
- package/docs/{relay-noise-conformance-vectors.json → test-vectors/relay-noise-v2.json} +1 -0
- package/docs/{relay-noise-v3-conformance-vectors.json → test-vectors/relay-noise-v3.json} +1 -0
- package/package.json +1 -1
|
@@ -36,6 +36,12 @@ export function emitSpecificHookEvent(eventBus, kind, data, ctx) {
|
|
|
36
36
|
toolResponse: data.tool_response,
|
|
37
37
|
});
|
|
38
38
|
break;
|
|
39
|
+
case 'PreToolUse':
|
|
40
|
+
emitPlanProposalFromExitPlanMode(eventBus, data, ctx);
|
|
41
|
+
break;
|
|
42
|
+
case 'PermissionRequest':
|
|
43
|
+
emitPlanProposalFromExitPlanMode(eventBus, data, ctx);
|
|
44
|
+
break;
|
|
39
45
|
case 'PostToolUseFailure':
|
|
40
46
|
eventBus.emit('hook:tool-failed', {
|
|
41
47
|
sessionId: ctx.sessionId,
|
|
@@ -51,7 +57,9 @@ export function emitSpecificHookEvent(eventBus, kind, data, ctx) {
|
|
|
51
57
|
adapter: ctx.adapter,
|
|
52
58
|
lastMessage: data.last_assistant_message,
|
|
53
59
|
});
|
|
54
|
-
emitExplicitPlanProposalFromStop(eventBus, data, ctx)
|
|
60
|
+
if (!emitExplicitPlanProposalFromStop(eventBus, data, ctx)) {
|
|
61
|
+
emitPlanModeProposalFromStop(eventBus, data, ctx);
|
|
62
|
+
}
|
|
55
63
|
break;
|
|
56
64
|
case 'SubagentStart':
|
|
57
65
|
eventBus.emit('hook:subagent-start', {
|
|
@@ -90,6 +98,30 @@ export function emitSpecificHookEvent(eventBus, kind, data, ctx) {
|
|
|
90
98
|
break;
|
|
91
99
|
}
|
|
92
100
|
}
|
|
101
|
+
function emitPlanProposalFromExitPlanMode(eventBus, data, ctx) {
|
|
102
|
+
if (data.tool_name !== 'ExitPlanMode')
|
|
103
|
+
return;
|
|
104
|
+
const toolInput = readRecord(data.tool_input);
|
|
105
|
+
const plan = toolInput?.plan;
|
|
106
|
+
if (typeof plan !== 'string' || plan.trim().length === 0)
|
|
107
|
+
return;
|
|
108
|
+
const planFilePath = typeof toolInput?.planFilePath === 'string' ? toolInput.planFilePath : undefined;
|
|
109
|
+
eventBus.emit('hook:plan-proposed', {
|
|
110
|
+
sessionId: ctx.sessionId,
|
|
111
|
+
adapter: ctx.adapter,
|
|
112
|
+
cwd: ctx.cwd,
|
|
113
|
+
title: titleFromMarkdown(plan),
|
|
114
|
+
body: plan.trim(),
|
|
115
|
+
source: `${ctx.adapter}-exit-plan-mode`,
|
|
116
|
+
sourceRef: `hook://exit-plan-mode/${ctx.sessionId}`,
|
|
117
|
+
metadata: {
|
|
118
|
+
schema: PLAN_PROPOSAL_SCHEMA_VERSION,
|
|
119
|
+
extractedFrom: 'exit-plan-mode',
|
|
120
|
+
planFilePath,
|
|
121
|
+
hookRequestId: typeof data.hook_request_id === 'string' ? data.hook_request_id : undefined,
|
|
122
|
+
},
|
|
123
|
+
});
|
|
124
|
+
}
|
|
93
125
|
function readRecord(value) {
|
|
94
126
|
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
95
127
|
return undefined;
|
|
@@ -103,10 +135,17 @@ function planBodyFromData(data) {
|
|
|
103
135
|
}
|
|
104
136
|
return '';
|
|
105
137
|
}
|
|
138
|
+
function titleFromMarkdown(markdown) {
|
|
139
|
+
const firstHeading = markdown
|
|
140
|
+
.split(/\r?\n/)
|
|
141
|
+
.map((line) => line.trim())
|
|
142
|
+
.find((line) => /^#{1,3}\s+\S/.test(line));
|
|
143
|
+
return firstHeading?.replace(/^#{1,3}\s+/, '').trim();
|
|
144
|
+
}
|
|
106
145
|
function emitExplicitPlanProposalFromStop(eventBus, data, ctx) {
|
|
107
146
|
const proposal = extractPlanProposalFromText(data.last_assistant_message);
|
|
108
147
|
if (!proposal)
|
|
109
|
-
return;
|
|
148
|
+
return false;
|
|
110
149
|
eventBus.emit('hook:plan-proposed', {
|
|
111
150
|
sessionId: ctx.sessionId,
|
|
112
151
|
adapter: ctx.adapter,
|
|
@@ -121,5 +160,42 @@ function emitExplicitPlanProposalFromStop(eventBus, data, ctx) {
|
|
|
121
160
|
extractedFrom: 'explicit-marker',
|
|
122
161
|
},
|
|
123
162
|
});
|
|
163
|
+
return true;
|
|
164
|
+
}
|
|
165
|
+
function emitPlanModeProposalFromStop(eventBus, data, ctx) {
|
|
166
|
+
if (ctx.adapter !== 'claude')
|
|
167
|
+
return;
|
|
168
|
+
if (data.permission_mode !== 'plan')
|
|
169
|
+
return;
|
|
170
|
+
const body = data.last_assistant_message;
|
|
171
|
+
if (typeof body !== 'string')
|
|
172
|
+
return;
|
|
173
|
+
const trimmed = body.trim();
|
|
174
|
+
if (!looksLikePlanMarkdown(trimmed))
|
|
175
|
+
return;
|
|
176
|
+
eventBus.emit('hook:plan-proposed', {
|
|
177
|
+
sessionId: ctx.sessionId,
|
|
178
|
+
adapter: ctx.adapter,
|
|
179
|
+
cwd: ctx.cwd,
|
|
180
|
+
title: titleFromMarkdown(trimmed) ?? 'Claude plan',
|
|
181
|
+
body: trimmed,
|
|
182
|
+
source: `${ctx.adapter}-plan-mode-stop`,
|
|
183
|
+
sourceRef: `hook://stop/${ctx.sessionId}`,
|
|
184
|
+
metadata: {
|
|
185
|
+
schema: PLAN_PROPOSAL_SCHEMA_VERSION,
|
|
186
|
+
extractedFrom: 'claude-plan-mode-stop',
|
|
187
|
+
},
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
function looksLikePlanMarkdown(markdown) {
|
|
191
|
+
if (!markdown)
|
|
192
|
+
return false;
|
|
193
|
+
const lower = markdown.toLowerCase();
|
|
194
|
+
if (/^#{1,3}\s+plan\b/m.test(markdown))
|
|
195
|
+
return true;
|
|
196
|
+
if (/^plan\s*:/i.test(markdown))
|
|
197
|
+
return true;
|
|
198
|
+
return (lower.includes('phase') &&
|
|
199
|
+
(lower.includes('goal') || lower.includes('recommended order') || lower.includes('risk')));
|
|
124
200
|
}
|
|
125
201
|
//# sourceMappingURL=specific-events.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"specific-events.js","sourceRoot":"","sources":["../../src/hooks/specific-events.ts"],"names":[],"mappings":"AAGA,OAAO,EACL,2BAA2B,EAC3B,4BAA4B,EAC5B,4BAA4B,GAC7B,MAAM,qBAAqB,CAAC;AAQ7B,MAAM,UAAU,qBAAqB,CACnC,QAAyC,EACzC,IAAmB,EACnB,IAA6B,EAC7B,GAAyB;IAEzB,QAAQ,IAAI,EAAE,CAAC;QACb,KAAK,cAAc;YACjB,QAAQ,CAAC,IAAI,CAAC,oBAAoB,EAAE;gBAClC,SAAS,EAAE,GAAG,CAAC,SAAS;gBACxB,OAAO,EAAE,GAAG,CAAC,OAAO;gBACpB,GAAG,EAAE,IAAI,CAAC,GAAyB;gBACnC,MAAM,EAAE,IAAI,CAAC,MAA4B;gBACzC,SAAS,EAAE,IAAI,CAAC,UAAgC;gBAChD,KAAK,EAAE,IAAI,CAAC,KAA2B;aACxC,CAAC,CAAC;YACH,MAAM;QACR,KAAK,YAAY;YACf,QAAQ,CAAC,IAAI,CAAC,kBAAkB,EAAE;gBAChC,SAAS,EAAE,GAAG,CAAC,SAAS;gBACxB,OAAO,EAAE,GAAG,CAAC,OAAO;gBACpB,MAAM,EAAE,IAAI,CAAC,MAA4B;aAC1C,CAAC,CAAC;YACH,MAAM;QACR,KAAK,cAAc;YACjB,QAAQ,CAAC,IAAI,CAAC,mBAAmB,EAAE;gBACjC,SAAS,EAAE,GAAG,CAAC,SAAS;gBACxB,OAAO,EAAE,GAAG,CAAC,OAAO;gBACpB,OAAO,EAAG,IAAI,CAAC,OAAkB,IAAI,EAAE;gBACvC,KAAK,EAAE,IAAI,CAAC,KAA2B;gBACvC,gBAAgB,EAAE,IAAI,CAAC,iBAAuC;aAC/D,CAAC,CAAC;YACH,MAAM;QACR,KAAK,aAAa;YAChB,QAAQ,CAAC,IAAI,CAAC,qBAAqB,EAAE;gBACnC,SAAS,EAAE,GAAG,CAAC,SAAS;gBACxB,OAAO,EAAE,GAAG,CAAC,OAAO;gBACpB,QAAQ,EAAG,IAAI,CAAC,SAAoB,IAAI,EAAE;gBAC1C,SAAS,EAAE,IAAI,CAAC,UAAU;gBAC1B,YAAY,EAAE,IAAI,CAAC,aAAa;aACjC,CAAC,CAAC;YACH,MAAM;QACR,KAAK,oBAAoB;YACvB,QAAQ,CAAC,IAAI,CAAC,kBAAkB,EAAE;gBAChC,SAAS,EAAE,GAAG,CAAC,SAAS;gBACxB,OAAO,EAAE,GAAG,CAAC,OAAO;gBACpB,QAAQ,EAAG,IAAI,CAAC,SAAoB,IAAI,EAAE;gBAC1C,KAAK,EAAE,IAAI,CAAC,KAA2B;gBACvC,WAAW,EAAE,IAAI,CAAC,YAAmC;aACtD,CAAC,CAAC;YACH,MAAM;QACR,KAAK,MAAM;YACT,QAAQ,CAAC,IAAI,CAAC,WAAW,EAAE;gBACzB,SAAS,EAAE,GAAG,CAAC,SAAS;gBACxB,OAAO,EAAE,GAAG,CAAC,OAAO;gBACpB,WAAW,EAAE,IAAI,CAAC,sBAA4C;aAC/D,CAAC,CAAC;YACH,gCAAgC,CAAC,QAAQ,EAAE,IAAI,EAAE,GAAG,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"specific-events.js","sourceRoot":"","sources":["../../src/hooks/specific-events.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { transportFetch, type TlsVerifyMode } from '../cli/network.js';
|
|
2
|
+
import type { DaemonEvents } from '../core/events.js';
|
|
3
|
+
export declare const TRUSTED_EDGE_PLAN_BODY_SCHEMA = "viewport.plan_body_encrypted/v1";
|
|
4
|
+
declare const PLAN_BODY_ALGORITHM = "AES-GCM-256";
|
|
5
|
+
type PlanProposedEvent = DaemonEvents['hook:plan-proposed'];
|
|
6
|
+
export interface TrustedEdgePlanEnvelope {
|
|
7
|
+
schema: typeof TRUSTED_EDGE_PLAN_BODY_SCHEMA;
|
|
8
|
+
algorithm: typeof PLAN_BODY_ALGORITHM;
|
|
9
|
+
key_ref: string;
|
|
10
|
+
ciphertext: string;
|
|
11
|
+
iv: string;
|
|
12
|
+
tag: string;
|
|
13
|
+
digest: string;
|
|
14
|
+
aad: Record<string, unknown>;
|
|
15
|
+
}
|
|
16
|
+
export interface TrustedEdgePlanFeedbackEnvelope {
|
|
17
|
+
schema: 'viewport.plan_feedback_field_encrypted/v1';
|
|
18
|
+
algorithm: typeof PLAN_BODY_ALGORITHM;
|
|
19
|
+
key_ref: string;
|
|
20
|
+
ciphertext: string;
|
|
21
|
+
iv: string;
|
|
22
|
+
tag: string;
|
|
23
|
+
digest: string;
|
|
24
|
+
aad: Record<string, unknown>;
|
|
25
|
+
}
|
|
26
|
+
export interface TrustedEdgePlanRecord {
|
|
27
|
+
workspaceId: string;
|
|
28
|
+
planId?: string;
|
|
29
|
+
sourceRef: string;
|
|
30
|
+
keyRef: string;
|
|
31
|
+
rawKey: string;
|
|
32
|
+
bodySha256: string;
|
|
33
|
+
createdAt: string;
|
|
34
|
+
updatedAt: string;
|
|
35
|
+
}
|
|
36
|
+
export interface TrustedEdgePlanWrappingKey {
|
|
37
|
+
keyId: string;
|
|
38
|
+
label: string;
|
|
39
|
+
algorithm: 'RSA-OAEP-256';
|
|
40
|
+
publicKeyJwk: Record<string, unknown>;
|
|
41
|
+
privateKeyJwk: Record<string, unknown>;
|
|
42
|
+
createdAt: string;
|
|
43
|
+
lastSeenAt: string;
|
|
44
|
+
}
|
|
45
|
+
export interface TrustedEdgePlanBodyKeyGrant {
|
|
46
|
+
schema: 'viewport.plan_body_key_grant/v1';
|
|
47
|
+
algorithm: 'RSA-OAEP-256';
|
|
48
|
+
recipient_user_id: number;
|
|
49
|
+
recipient_key_id: string;
|
|
50
|
+
key_ref: string;
|
|
51
|
+
encrypted_key: string;
|
|
52
|
+
}
|
|
53
|
+
export interface TrustedEdgePlanGrantRecipient {
|
|
54
|
+
user_id: number;
|
|
55
|
+
key_id: string;
|
|
56
|
+
public_key_jwk: Record<string, unknown>;
|
|
57
|
+
}
|
|
58
|
+
export interface TrustedEdgePlanSyncTarget {
|
|
59
|
+
workspaceId: string;
|
|
60
|
+
serverUrl: string;
|
|
61
|
+
credential: string;
|
|
62
|
+
tlsVerify?: TlsVerifyMode;
|
|
63
|
+
caCertPath?: string;
|
|
64
|
+
tlsPins?: string[];
|
|
65
|
+
}
|
|
66
|
+
export declare function saveTrustedEdgePlanDraft(options: {
|
|
67
|
+
event: PlanProposedEvent;
|
|
68
|
+
target: TrustedEdgePlanSyncTarget;
|
|
69
|
+
home?: string;
|
|
70
|
+
fetchImpl?: typeof transportFetch;
|
|
71
|
+
}): Promise<{
|
|
72
|
+
planId: string;
|
|
73
|
+
sourceRef: string;
|
|
74
|
+
envelope: TrustedEdgePlanEnvelope;
|
|
75
|
+
}>;
|
|
76
|
+
export declare function decryptTrustedEdgePlanBody(options: {
|
|
77
|
+
workspaceId: string;
|
|
78
|
+
planId?: string;
|
|
79
|
+
sourceRef?: string;
|
|
80
|
+
envelope: TrustedEdgePlanEnvelope;
|
|
81
|
+
bodyKeyGrants?: TrustedEdgePlanBodyKeyGrant[];
|
|
82
|
+
home?: string;
|
|
83
|
+
}): Promise<{
|
|
84
|
+
body: string;
|
|
85
|
+
bodySha256: string;
|
|
86
|
+
keyRef: string;
|
|
87
|
+
}>;
|
|
88
|
+
export declare function encryptTrustedEdgePlanFeedbackField(options: {
|
|
89
|
+
workspaceId: string;
|
|
90
|
+
planId?: string;
|
|
91
|
+
sourceRef?: string;
|
|
92
|
+
envelope: TrustedEdgePlanEnvelope;
|
|
93
|
+
text: string;
|
|
94
|
+
aad?: Record<string, unknown>;
|
|
95
|
+
bodyKeyGrants?: TrustedEdgePlanBodyKeyGrant[];
|
|
96
|
+
home?: string;
|
|
97
|
+
}): Promise<TrustedEdgePlanFeedbackEnvelope>;
|
|
98
|
+
export declare function publishTrustedEdgePlanWrappingKey(options: {
|
|
99
|
+
target: TrustedEdgePlanSyncTarget;
|
|
100
|
+
home?: string;
|
|
101
|
+
label?: string;
|
|
102
|
+
fetchImpl?: typeof transportFetch;
|
|
103
|
+
}): Promise<TrustedEdgePlanWrappingKey>;
|
|
104
|
+
export declare function wrapTrustedEdgePlanBodyKey(options: {
|
|
105
|
+
workspaceId: string;
|
|
106
|
+
planId?: string;
|
|
107
|
+
sourceRef?: string;
|
|
108
|
+
envelope: TrustedEdgePlanEnvelope;
|
|
109
|
+
bodyKeyGrants?: TrustedEdgePlanBodyKeyGrant[];
|
|
110
|
+
recipients: TrustedEdgePlanGrantRecipient[];
|
|
111
|
+
home?: string;
|
|
112
|
+
}): Promise<TrustedEdgePlanBodyKeyGrant[]>;
|
|
113
|
+
export {};
|
|
114
|
+
//# sourceMappingURL=trusted-edge-plan-artifacts.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"trusted-edge-plan-artifacts.d.ts","sourceRoot":"","sources":["../../src/hooks/trusted-edge-plan-artifacts.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
import fs from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { configDir } from '../core/config.js';
|
|
5
|
+
import { stableJson } from '../context/local-edge-crypto.js';
|
|
6
|
+
import { transportFetch } from '../cli/network.js';
|
|
7
|
+
export const TRUSTED_EDGE_PLAN_BODY_SCHEMA = 'viewport.plan_body_encrypted/v1';
|
|
8
|
+
const TRUSTED_EDGE_PLAN_KEY_STORE_SCHEMA = 'viewport.trusted_edge_plan_keys/v1';
|
|
9
|
+
const PLAN_BODY_ALGORITHM = 'AES-GCM-256';
|
|
10
|
+
export async function saveTrustedEdgePlanDraft(options) {
|
|
11
|
+
const sourceRef = options.event.sourceRef ?? `agent-hook:${options.event.sessionId}`;
|
|
12
|
+
await publishTrustedEdgePlanWrappingKey({
|
|
13
|
+
target: options.target,
|
|
14
|
+
home: options.home,
|
|
15
|
+
fetchImpl: options.fetchImpl,
|
|
16
|
+
});
|
|
17
|
+
const encrypted = await encryptTrustedEdgePlanBody({
|
|
18
|
+
workspaceId: options.target.workspaceId,
|
|
19
|
+
sourceRef,
|
|
20
|
+
sessionId: options.event.sessionId,
|
|
21
|
+
body: options.event.body,
|
|
22
|
+
source: options.event.source ?? options.event.adapter ?? null,
|
|
23
|
+
});
|
|
24
|
+
await upsertTrustedEdgePlanKey({
|
|
25
|
+
workspaceId: options.target.workspaceId,
|
|
26
|
+
sourceRef,
|
|
27
|
+
keyRef: encrypted.envelope.key_ref,
|
|
28
|
+
rawKey: encrypted.rawKey.toString('base64'),
|
|
29
|
+
bodySha256: encrypted.envelope.digest,
|
|
30
|
+
}, options.home);
|
|
31
|
+
const payload = await postJson(options.fetchImpl ?? transportFetch, `${options.target.serverUrl.replace(/\/+$/, '')}/api/runtime/workspaces/${encodeURIComponent(options.target.workspaceId)}/agent-hooks/plans`, {
|
|
32
|
+
credential: options.target.credential,
|
|
33
|
+
hook_event_name: 'PlanProposed',
|
|
34
|
+
schema: 'viewport.plan_proposal/v1',
|
|
35
|
+
session_id: options.event.sessionId,
|
|
36
|
+
cwd: options.event.cwd ?? null,
|
|
37
|
+
title: options.event.title?.trim() || 'Agent plan',
|
|
38
|
+
summary: options.event.summary?.trim() || null,
|
|
39
|
+
body_encryption: encrypted.envelope,
|
|
40
|
+
source: options.event.source ?? options.event.adapter ?? null,
|
|
41
|
+
source_ref: sourceRef,
|
|
42
|
+
payload: {
|
|
43
|
+
...(options.event.metadata ?? {}),
|
|
44
|
+
privacy: 'trusted-edge',
|
|
45
|
+
encrypted_by: 'vpd',
|
|
46
|
+
},
|
|
47
|
+
}, {
|
|
48
|
+
tlsVerify: options.target.tlsVerify,
|
|
49
|
+
caCertPath: options.target.caCertPath,
|
|
50
|
+
tlsPins: options.target.tlsPins,
|
|
51
|
+
});
|
|
52
|
+
const plan = objectField(payload, 'data');
|
|
53
|
+
const planId = stringField(plan, 'id');
|
|
54
|
+
await upsertTrustedEdgePlanKey({
|
|
55
|
+
workspaceId: options.target.workspaceId,
|
|
56
|
+
planId,
|
|
57
|
+
sourceRef,
|
|
58
|
+
keyRef: encrypted.envelope.key_ref,
|
|
59
|
+
rawKey: encrypted.rawKey.toString('base64'),
|
|
60
|
+
bodySha256: encrypted.envelope.digest,
|
|
61
|
+
}, options.home);
|
|
62
|
+
return { planId, sourceRef, envelope: encrypted.envelope };
|
|
63
|
+
}
|
|
64
|
+
export async function decryptTrustedEdgePlanBody(options) {
|
|
65
|
+
const record = await resolveTrustedEdgePlanKey({
|
|
66
|
+
workspaceId: options.workspaceId,
|
|
67
|
+
planId: options.planId,
|
|
68
|
+
sourceRef: options.sourceRef,
|
|
69
|
+
keyRef: options.envelope.key_ref,
|
|
70
|
+
bodyKeyGrants: options.bodyKeyGrants,
|
|
71
|
+
}, options.home);
|
|
72
|
+
if (!record) {
|
|
73
|
+
throw new Error('Trusted edge does not have the key for this plan.');
|
|
74
|
+
}
|
|
75
|
+
const body = decryptEnvelope(options.envelope, Buffer.from(record.rawKey, 'base64'));
|
|
76
|
+
const digest = digestText(body);
|
|
77
|
+
if (record.bodySha256 &&
|
|
78
|
+
record.bodySha256 !== digest &&
|
|
79
|
+
record.bodySha256 !== options.envelope.digest) {
|
|
80
|
+
throw new Error('Trusted edge plan key does not match this encrypted body.');
|
|
81
|
+
}
|
|
82
|
+
return { body, bodySha256: digest, keyRef: record.keyRef };
|
|
83
|
+
}
|
|
84
|
+
export async function encryptTrustedEdgePlanFeedbackField(options) {
|
|
85
|
+
const record = await resolveTrustedEdgePlanKey({
|
|
86
|
+
workspaceId: options.workspaceId,
|
|
87
|
+
planId: options.planId,
|
|
88
|
+
sourceRef: options.sourceRef,
|
|
89
|
+
keyRef: options.envelope.key_ref,
|
|
90
|
+
bodyKeyGrants: options.bodyKeyGrants,
|
|
91
|
+
}, options.home);
|
|
92
|
+
if (!record) {
|
|
93
|
+
throw new Error('Trusted edge does not have the key for this plan.');
|
|
94
|
+
}
|
|
95
|
+
const rawKey = Buffer.from(record.rawKey, 'base64');
|
|
96
|
+
const aad = options.aad ?? {};
|
|
97
|
+
const iv = crypto.randomBytes(12);
|
|
98
|
+
const cipher = crypto.createCipheriv('aes-256-gcm', rawKey, iv);
|
|
99
|
+
cipher.setAAD(Buffer.from(stableJson(aad), 'utf8'));
|
|
100
|
+
const ciphertext = Buffer.concat([cipher.update(options.text, 'utf8'), cipher.final()]);
|
|
101
|
+
return {
|
|
102
|
+
schema: 'viewport.plan_feedback_field_encrypted/v1',
|
|
103
|
+
algorithm: PLAN_BODY_ALGORITHM,
|
|
104
|
+
key_ref: options.envelope.key_ref,
|
|
105
|
+
ciphertext: ciphertext.toString('base64'),
|
|
106
|
+
iv: iv.toString('base64'),
|
|
107
|
+
tag: cipher.getAuthTag().toString('base64'),
|
|
108
|
+
digest: digestText(options.text),
|
|
109
|
+
aad,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
export async function publishTrustedEdgePlanWrappingKey(options) {
|
|
113
|
+
const key = await ensureTrustedEdgePlanWrappingKey(options.home, options.label);
|
|
114
|
+
await postJson(options.fetchImpl ?? transportFetch, `${options.target.serverUrl.replace(/\/+$/, '')}/api/runtime/workspaces/${encodeURIComponent(options.target.workspaceId)}/plan-encryption-keys`, {
|
|
115
|
+
credential: options.target.credential,
|
|
116
|
+
key_id: key.keyId,
|
|
117
|
+
label: key.label,
|
|
118
|
+
algorithm: key.algorithm,
|
|
119
|
+
public_key_jwk: key.publicKeyJwk,
|
|
120
|
+
}, {
|
|
121
|
+
tlsVerify: options.target.tlsVerify,
|
|
122
|
+
caCertPath: options.target.caCertPath,
|
|
123
|
+
tlsPins: options.target.tlsPins,
|
|
124
|
+
});
|
|
125
|
+
return key;
|
|
126
|
+
}
|
|
127
|
+
export async function wrapTrustedEdgePlanBodyKey(options) {
|
|
128
|
+
const record = await resolveTrustedEdgePlanKey({
|
|
129
|
+
workspaceId: options.workspaceId,
|
|
130
|
+
planId: options.planId,
|
|
131
|
+
sourceRef: options.sourceRef,
|
|
132
|
+
keyRef: options.envelope.key_ref,
|
|
133
|
+
bodyKeyGrants: options.bodyKeyGrants,
|
|
134
|
+
}, options.home);
|
|
135
|
+
if (!record) {
|
|
136
|
+
throw new Error('Trusted edge does not have the key for this plan.');
|
|
137
|
+
}
|
|
138
|
+
return wrapRawPlanBodyKey({
|
|
139
|
+
rawKey: Buffer.from(record.rawKey, 'base64'),
|
|
140
|
+
keyRef: options.envelope.key_ref,
|
|
141
|
+
recipients: options.recipients,
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
async function ensureTrustedEdgePlanWrappingKey(home = configDir(), label = 'Trusted edge plan key') {
|
|
145
|
+
const store = await readTrustedEdgePlanKeyStore(home);
|
|
146
|
+
const existing = store.wrappingKeys?.find((key) => key.algorithm === 'RSA-OAEP-256');
|
|
147
|
+
if (existing) {
|
|
148
|
+
existing.lastSeenAt = new Date().toISOString();
|
|
149
|
+
await writeTrustedEdgePlanKeyStore(store, home);
|
|
150
|
+
return existing;
|
|
151
|
+
}
|
|
152
|
+
const pair = crypto.generateKeyPairSync('rsa', {
|
|
153
|
+
modulusLength: 2048,
|
|
154
|
+
publicExponent: 0x10001,
|
|
155
|
+
});
|
|
156
|
+
const now = new Date().toISOString();
|
|
157
|
+
const key = {
|
|
158
|
+
keyId: `trusted-edge-${crypto.randomUUID()}`,
|
|
159
|
+
label,
|
|
160
|
+
algorithm: 'RSA-OAEP-256',
|
|
161
|
+
publicKeyJwk: withPublicJwkMetadata(pair.publicKey.export({ format: 'jwk' })),
|
|
162
|
+
privateKeyJwk: withPrivateJwkMetadata(pair.privateKey.export({ format: 'jwk' })),
|
|
163
|
+
createdAt: now,
|
|
164
|
+
lastSeenAt: now,
|
|
165
|
+
};
|
|
166
|
+
store.wrappingKeys = [...(store.wrappingKeys ?? []), key];
|
|
167
|
+
await writeTrustedEdgePlanKeyStore(store, home);
|
|
168
|
+
return key;
|
|
169
|
+
}
|
|
170
|
+
async function resolveTrustedEdgePlanKey(input, home = configDir()) {
|
|
171
|
+
const existing = await findTrustedEdgePlanKey(input, home);
|
|
172
|
+
if (existing)
|
|
173
|
+
return existing;
|
|
174
|
+
const unwrapped = await unwrapTrustedEdgePlanKeyGrant(input, home);
|
|
175
|
+
if (!unwrapped)
|
|
176
|
+
return null;
|
|
177
|
+
const recordInput = {
|
|
178
|
+
workspaceId: input.workspaceId,
|
|
179
|
+
planId: input.planId,
|
|
180
|
+
sourceRef: input.sourceRef ?? `grant:${input.keyRef}`,
|
|
181
|
+
keyRef: input.keyRef,
|
|
182
|
+
rawKey: unwrapped.toString('base64'),
|
|
183
|
+
bodySha256: '',
|
|
184
|
+
};
|
|
185
|
+
await upsertTrustedEdgePlanKey(recordInput, home);
|
|
186
|
+
return findTrustedEdgePlanKey(input, home);
|
|
187
|
+
}
|
|
188
|
+
async function unwrapTrustedEdgePlanKeyGrant(input, home = configDir()) {
|
|
189
|
+
const grants = input.bodyKeyGrants ?? [];
|
|
190
|
+
if (grants.length === 0)
|
|
191
|
+
return null;
|
|
192
|
+
const store = await readTrustedEdgePlanKeyStore(home);
|
|
193
|
+
const wrappingKeys = store.wrappingKeys ?? [];
|
|
194
|
+
for (const localKey of wrappingKeys) {
|
|
195
|
+
const grant = grants.find((candidate) => candidate.key_ref === input.keyRef &&
|
|
196
|
+
candidate.recipient_key_id === localKey.keyId &&
|
|
197
|
+
candidate.algorithm === 'RSA-OAEP-256');
|
|
198
|
+
if (!grant)
|
|
199
|
+
continue;
|
|
200
|
+
try {
|
|
201
|
+
return crypto.privateDecrypt({
|
|
202
|
+
key: crypto.createPrivateKey({
|
|
203
|
+
key: localKey.privateKeyJwk,
|
|
204
|
+
format: 'jwk',
|
|
205
|
+
}),
|
|
206
|
+
oaepHash: 'sha256',
|
|
207
|
+
}, Buffer.from(grant.encrypted_key, 'base64'));
|
|
208
|
+
}
|
|
209
|
+
catch {
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
return null;
|
|
214
|
+
}
|
|
215
|
+
async function wrapRawPlanBodyKey(input) {
|
|
216
|
+
const unique = new Map();
|
|
217
|
+
for (const recipient of input.recipients) {
|
|
218
|
+
unique.set(`${recipient.user_id}:${recipient.key_id}`, recipient);
|
|
219
|
+
}
|
|
220
|
+
return [...unique.values()].map((recipient) => {
|
|
221
|
+
const publicKey = crypto.createPublicKey({
|
|
222
|
+
key: recipient.public_key_jwk,
|
|
223
|
+
format: 'jwk',
|
|
224
|
+
});
|
|
225
|
+
const encryptedKey = crypto.publicEncrypt({
|
|
226
|
+
key: publicKey,
|
|
227
|
+
oaepHash: 'sha256',
|
|
228
|
+
}, input.rawKey);
|
|
229
|
+
return {
|
|
230
|
+
schema: 'viewport.plan_body_key_grant/v1',
|
|
231
|
+
algorithm: 'RSA-OAEP-256',
|
|
232
|
+
recipient_user_id: recipient.user_id,
|
|
233
|
+
recipient_key_id: recipient.key_id,
|
|
234
|
+
key_ref: input.keyRef,
|
|
235
|
+
encrypted_key: encryptedKey.toString('base64'),
|
|
236
|
+
};
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
function withPublicJwkMetadata(jwk) {
|
|
240
|
+
return {
|
|
241
|
+
...jwk,
|
|
242
|
+
alg: 'RSA-OAEP-256',
|
|
243
|
+
key_ops: ['encrypt'],
|
|
244
|
+
ext: true,
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
function withPrivateJwkMetadata(jwk) {
|
|
248
|
+
return {
|
|
249
|
+
...jwk,
|
|
250
|
+
alg: 'RSA-OAEP-256',
|
|
251
|
+
key_ops: ['decrypt'],
|
|
252
|
+
ext: true,
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
async function encryptTrustedEdgePlanBody(input) {
|
|
256
|
+
const rawKey = crypto.randomBytes(32);
|
|
257
|
+
const keyRef = `trusted-edge-plan-${crypto.randomUUID()}`;
|
|
258
|
+
const aad = {
|
|
259
|
+
purpose: 'trusted-edge-plan-body',
|
|
260
|
+
workspace_id: input.workspaceId,
|
|
261
|
+
source_ref: input.sourceRef,
|
|
262
|
+
session_id: input.sessionId,
|
|
263
|
+
source: input.source,
|
|
264
|
+
created_at: new Date().toISOString(),
|
|
265
|
+
};
|
|
266
|
+
const iv = crypto.randomBytes(12);
|
|
267
|
+
const cipher = crypto.createCipheriv('aes-256-gcm', rawKey, iv);
|
|
268
|
+
cipher.setAAD(Buffer.from(stableJson(aad), 'utf8'));
|
|
269
|
+
const ciphertext = Buffer.concat([cipher.update(input.body, 'utf8'), cipher.final()]);
|
|
270
|
+
return {
|
|
271
|
+
rawKey,
|
|
272
|
+
envelope: {
|
|
273
|
+
schema: TRUSTED_EDGE_PLAN_BODY_SCHEMA,
|
|
274
|
+
algorithm: PLAN_BODY_ALGORITHM,
|
|
275
|
+
key_ref: keyRef,
|
|
276
|
+
ciphertext: ciphertext.toString('base64'),
|
|
277
|
+
iv: iv.toString('base64'),
|
|
278
|
+
tag: cipher.getAuthTag().toString('base64'),
|
|
279
|
+
digest: digestText(input.body),
|
|
280
|
+
aad,
|
|
281
|
+
},
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
function decryptEnvelope(envelope, rawKey) {
|
|
285
|
+
if (envelope.schema !== TRUSTED_EDGE_PLAN_BODY_SCHEMA) {
|
|
286
|
+
throw new Error(`Unsupported plan body schema: ${envelope.schema}`);
|
|
287
|
+
}
|
|
288
|
+
if (envelope.algorithm !== PLAN_BODY_ALGORITHM) {
|
|
289
|
+
throw new Error(`Unsupported plan body algorithm: ${envelope.algorithm}`);
|
|
290
|
+
}
|
|
291
|
+
if (!envelope.tag) {
|
|
292
|
+
throw new Error('Trusted-edge plan envelope is missing an authentication tag.');
|
|
293
|
+
}
|
|
294
|
+
const decipher = crypto.createDecipheriv('aes-256-gcm', rawKey, Buffer.from(envelope.iv, 'base64'));
|
|
295
|
+
decipher.setAAD(Buffer.from(stableJson(envelope.aad ?? {}), 'utf8'));
|
|
296
|
+
decipher.setAuthTag(Buffer.from(envelope.tag, 'base64'));
|
|
297
|
+
return Buffer.concat([
|
|
298
|
+
decipher.update(Buffer.from(envelope.ciphertext, 'base64')),
|
|
299
|
+
decipher.final(),
|
|
300
|
+
]).toString('utf8');
|
|
301
|
+
}
|
|
302
|
+
async function upsertTrustedEdgePlanKey(input, home = configDir()) {
|
|
303
|
+
const store = await readTrustedEdgePlanKeyStore(home);
|
|
304
|
+
const now = new Date().toISOString();
|
|
305
|
+
const existing = store.records.find((record) => record.workspaceId === input.workspaceId &&
|
|
306
|
+
record.keyRef === input.keyRef &&
|
|
307
|
+
((input.planId && record.planId === input.planId) ||
|
|
308
|
+
(!input.planId && record.sourceRef === input.sourceRef)));
|
|
309
|
+
if (existing) {
|
|
310
|
+
Object.assign(existing, input, { updatedAt: now });
|
|
311
|
+
}
|
|
312
|
+
else {
|
|
313
|
+
store.records.push({ ...input, createdAt: now, updatedAt: now });
|
|
314
|
+
}
|
|
315
|
+
await writeTrustedEdgePlanKeyStore(store, home);
|
|
316
|
+
}
|
|
317
|
+
async function findTrustedEdgePlanKey(input, home = configDir()) {
|
|
318
|
+
const store = await readTrustedEdgePlanKeyStore(home);
|
|
319
|
+
return (store.records.find((record) => record.workspaceId === input.workspaceId &&
|
|
320
|
+
record.keyRef === input.keyRef &&
|
|
321
|
+
((input.planId && record.planId === input.planId) ||
|
|
322
|
+
(input.sourceRef && record.sourceRef === input.sourceRef))) ?? null);
|
|
323
|
+
}
|
|
324
|
+
async function readTrustedEdgePlanKeyStore(home = configDir()) {
|
|
325
|
+
try {
|
|
326
|
+
const raw = await fs.readFile(trustedEdgePlanKeyStorePath(home), 'utf8');
|
|
327
|
+
const parsed = JSON.parse(raw);
|
|
328
|
+
if (parsed.schema !== TRUSTED_EDGE_PLAN_KEY_STORE_SCHEMA || !Array.isArray(parsed.records)) {
|
|
329
|
+
throw new Error('Invalid trusted-edge plan key store.');
|
|
330
|
+
}
|
|
331
|
+
return parsed;
|
|
332
|
+
}
|
|
333
|
+
catch (error) {
|
|
334
|
+
if (error.code === 'ENOENT') {
|
|
335
|
+
return { schema: TRUSTED_EDGE_PLAN_KEY_STORE_SCHEMA, records: [] };
|
|
336
|
+
}
|
|
337
|
+
throw error;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
async function writeTrustedEdgePlanKeyStore(store, home = configDir()) {
|
|
341
|
+
const filePath = trustedEdgePlanKeyStorePath(home);
|
|
342
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
343
|
+
await fs.writeFile(filePath, JSON.stringify(store, null, 2) + '\n', {
|
|
344
|
+
encoding: 'utf8',
|
|
345
|
+
mode: 0o600,
|
|
346
|
+
});
|
|
347
|
+
await fs.chmod(filePath, 0o600);
|
|
348
|
+
}
|
|
349
|
+
function trustedEdgePlanKeyStorePath(home = configDir()) {
|
|
350
|
+
return path.join(home, 'plans', 'trusted-edge-keys.json');
|
|
351
|
+
}
|
|
352
|
+
async function postJson(fetchImpl, url, body, transportOptions = {}) {
|
|
353
|
+
const response = await fetchImpl(url, {
|
|
354
|
+
method: 'POST',
|
|
355
|
+
headers: { 'content-type': 'application/json', accept: 'application/json' },
|
|
356
|
+
body: JSON.stringify(body),
|
|
357
|
+
timeoutMs: 5_000,
|
|
358
|
+
...transportOptions,
|
|
359
|
+
});
|
|
360
|
+
const payload = await response.json().catch(() => null);
|
|
361
|
+
if (!response.ok) {
|
|
362
|
+
const reason = payload && typeof payload === 'object' && 'reason' in payload
|
|
363
|
+
? String(payload.reason)
|
|
364
|
+
: `${response.status} ${response.statusText}`;
|
|
365
|
+
throw new Error(`Plan hook sync request failed: ${reason}`);
|
|
366
|
+
}
|
|
367
|
+
return payload;
|
|
368
|
+
}
|
|
369
|
+
function digestText(value) {
|
|
370
|
+
return `sha256:${crypto.createHash('sha256').update(value, 'utf8').digest('hex')}`;
|
|
371
|
+
}
|
|
372
|
+
function objectField(value, field) {
|
|
373
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
374
|
+
throw new Error(`Expected response object while reading ${field}`);
|
|
375
|
+
}
|
|
376
|
+
const child = value[field];
|
|
377
|
+
if (!child || typeof child !== 'object' || Array.isArray(child)) {
|
|
378
|
+
throw new Error(`Plan hook sync response did not include ${field}`);
|
|
379
|
+
}
|
|
380
|
+
return child;
|
|
381
|
+
}
|
|
382
|
+
function stringField(value, field) {
|
|
383
|
+
const child = value[field];
|
|
384
|
+
if (typeof child !== 'string' || child.trim().length === 0) {
|
|
385
|
+
throw new Error(`Plan hook sync response did not include ${field}`);
|
|
386
|
+
}
|
|
387
|
+
return child;
|
|
388
|
+
}
|
|
389
|
+
//# sourceMappingURL=trusted-edge-plan-artifacts.js.map
|