@undefineds.co/linx 0.2.16 → 0.2.17
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 +6 -3
- package/dist/generated/version.js +3 -0
- package/dist/generated/version.js.map +1 -0
- package/dist/index.js +139 -259
- package/dist/index.js.map +1 -1
- package/dist/lib/account-api.js +1 -1
- package/dist/lib/account-api.js.map +1 -1
- package/dist/lib/account-session.js +1 -1
- package/dist/lib/account-session.js.map +1 -1
- package/dist/lib/ai-command.js +105 -56
- package/dist/lib/ai-command.js.map +1 -1
- package/dist/lib/chat-api.js +19 -177
- package/dist/lib/chat-api.js.map +1 -1
- package/dist/lib/codex-plugin/bridge.js.map +1 -1
- package/dist/lib/codex-plugin/codex-native-proxy.js.map +1 -1
- package/dist/lib/codex-plugin/index.js.map +1 -1
- package/dist/lib/codex-plugin/runner.js.map +1 -1
- package/dist/lib/credentials-store.js +1 -7
- package/dist/lib/credentials-store.js.map +1 -1
- package/dist/lib/default-model.js +0 -1
- package/dist/lib/default-model.js.map +1 -1
- package/dist/lib/login-command.js +2 -17
- package/dist/lib/login-command.js.map +1 -1
- package/dist/lib/models.js +27 -2
- package/dist/lib/models.js.map +1 -1
- package/dist/lib/oidc-auth.js +13 -78
- package/dist/lib/oidc-auth.js.map +1 -1
- package/dist/lib/oidc-session-storage.js.map +1 -1
- package/dist/lib/pi-adapter/auth.js +5 -12
- package/dist/lib/pi-adapter/auth.js.map +1 -1
- package/dist/lib/pi-adapter/branding.js +76 -554
- package/dist/lib/pi-adapter/branding.js.map +1 -1
- package/dist/lib/pi-adapter/index.js.map +1 -1
- package/dist/lib/pi-adapter/interactive.js +4 -154
- package/dist/lib/pi-adapter/interactive.js.map +1 -1
- package/dist/lib/pi-adapter/runtime.js +25 -140
- package/dist/lib/pi-adapter/runtime.js.map +1 -1
- package/dist/lib/pi-adapter/stream.js +4 -154
- package/dist/lib/pi-adapter/stream.js.map +1 -1
- package/dist/lib/pi-adapter/theme.js.map +1 -1
- package/dist/lib/pod-chat-store.js +1 -1
- package/dist/lib/pod-chat-store.js.map +1 -1
- package/dist/lib/profile-identity.js +60 -16
- package/dist/lib/profile-identity.js.map +1 -1
- package/dist/lib/prompt.js.map +1 -1
- package/dist/lib/runtime-target.js +1 -1
- package/dist/lib/runtime-target.js.map +1 -1
- package/dist/lib/solid-auth.js.map +1 -1
- package/dist/lib/thread-utils.js.map +1 -1
- package/dist/lib/watch/archive.js +1 -1
- package/dist/lib/watch/archive.js.map +1 -1
- package/dist/lib/watch/auth.js +1 -1
- package/dist/lib/watch/auth.js.map +1 -1
- package/dist/lib/watch/codex-composer.js.map +1 -1
- package/dist/lib/watch/codex-footer.js.map +1 -1
- package/dist/lib/watch/codex-overlay.js.map +1 -1
- package/dist/lib/watch/codex-request-form.js +1 -1
- package/dist/lib/watch/codex-request-form.js.map +1 -1
- package/dist/lib/watch/codex-request-input.js.map +1 -1
- package/dist/lib/watch/display.js.map +1 -1
- package/dist/lib/watch/format.js.map +1 -1
- package/dist/lib/watch/hooks/claude.js +0 -4
- package/dist/lib/watch/hooks/claude.js.map +1 -1
- package/dist/lib/watch/hooks/codebuddy.js +0 -4
- package/dist/lib/watch/hooks/codebuddy.js.map +1 -1
- package/dist/lib/watch/hooks/codex.js +0 -4
- package/dist/lib/watch/hooks/codex.js.map +1 -1
- package/dist/lib/watch/hooks/index.js.map +1 -1
- package/dist/lib/watch/hooks/shared.js +1 -0
- package/dist/lib/watch/hooks/shared.js.map +1 -1
- package/dist/lib/watch/index.js.map +1 -1
- package/dist/lib/watch/pod-ai.js +37 -29
- package/dist/lib/watch/pod-ai.js.map +1 -1
- package/dist/lib/watch/pod-approval.js +216 -846
- package/dist/lib/watch/pod-approval.js.map +1 -1
- package/dist/lib/watch/pod-persistence.js +78 -184
- package/dist/lib/watch/pod-persistence.js.map +1 -1
- package/dist/lib/watch/runner.js +38 -243
- package/dist/lib/watch/runner.js.map +1 -1
- package/dist/lib/watch/types.js.map +1 -1
- package/dist/skills/drizzle-solid/SKILL.md +340 -0
- package/dist/skills/pod-storage/SKILL.md +60 -0
- package/dist/skills/solid-modeling/SKILL.md +274 -0
- package/dist/skills/xpod-componentsjs/SKILL.md +284 -0
- package/dist/watch-cli.js +34 -8
- package/dist/watch-cli.js.map +1 -1
- package/package.json +9 -3
- package/vendor/client/dist/client/index.d.ts +118 -0
- package/vendor/client/dist/client/index.js +260 -0
- package/vendor/client/dist/index.d.ts +1 -0
- package/vendor/client/dist/index.js +1 -0
- package/vendor/client/dist/watch/index.d.ts +226 -0
- package/vendor/client/dist/watch/index.js +1114 -0
- package/vendor/client/package.json +9 -0
- package/dist/lib/node-warning-filter.js +0 -34
- package/dist/lib/node-warning-filter.js.map +0 -1
- package/dist/lib/pi-adapter/pod-approval.js +0 -8
- package/dist/lib/pi-adapter/pod-approval.js.map +0 -1
- package/dist/lib/pi-adapter/pod-mirror-mapping.js +0 -189
- package/dist/lib/pi-adapter/pod-mirror-mapping.js.map +0 -1
- package/dist/lib/pi-adapter/pod-mirror.js +0 -334
- package/dist/lib/pi-adapter/pod-mirror.js.map +0 -1
- package/dist/lib/pi-adapter/pod-native.js +0 -478
- package/dist/lib/pi-adapter/pod-native.js.map +0 -1
- package/dist/lib/pi-adapter/session.js +0 -727
- package/dist/lib/pi-adapter/session.js.map +0 -1
- package/dist/lib/pod-data-session.js +0 -110
- package/dist/lib/pod-data-session.js.map +0 -1
- package/dist/lib/watch/secretary.js +0 -238
- package/dist/lib/watch/secretary.js.map +0 -1
- package/vendor/agent-runtime/dist/acp.d.ts +0 -27
- package/vendor/agent-runtime/dist/acp.js +0 -86
- package/vendor/agent-runtime/dist/companion-model.d.ts +0 -7
- package/vendor/agent-runtime/dist/companion-model.js +0 -12
- package/vendor/agent-runtime/dist/index.d.ts +0 -3
- package/vendor/agent-runtime/dist/index.js +0 -3
- package/vendor/agent-runtime/dist/turn-controller.d.ts +0 -69
- package/vendor/agent-runtime/dist/turn-controller.js +0 -129
- package/vendor/agent-runtime/package.json +0 -11
|
@@ -1,25 +1,17 @@
|
|
|
1
1
|
import { setTimeout as delay } from 'node:timers/promises';
|
|
2
|
-
|
|
3
|
-
import { AS, ODRL, UDFS } from '@undefineds.co/models/namespaces';
|
|
4
|
-
import { ApprovalVocab, AuditVocab, GrantVocab, InboxNotificationVocab } from '@undefineds.co/models/vocab/sidecar';
|
|
5
|
-
import { resolveWatchGrantCoverage } from './secretary.js';
|
|
6
|
-
import { buildApprovalDocumentUrl, RDF_TYPE, buildApprovalResourceUrl, buildAuditDocumentUrl, buildAuditResourceUrl, buildGrantResourceUrl, buildInboxResourceUrl, firstIri, firstLiteral, iri, listTurtleResources, listTurtleResourcesRecursive, literal, parseManagedTurtleBlocks, readTurtleResource, subjectIdFromResourceUrl, upsertManagedTurtleBlock, } from '../pi-adapter/pod-native.js';
|
|
7
|
-
const WATCH_CHAT_ID_PREFIX = 'linx-watch';
|
|
2
|
+
const WATCH_CHAT_ID = 'linx-watch';
|
|
8
3
|
const WATCH_AGENT_ID = 'linx-watch-assistant';
|
|
9
4
|
const REMOTE_APPROVAL_POLICY_VERSION = 'linx-watch-remote-approval/v1';
|
|
10
5
|
const DEFAULT_REMOTE_APPROVAL_POLL_MS = 1000;
|
|
11
|
-
const DEFAULT_WARN_ONLY_TIMEOUT_MS = 5000;
|
|
12
|
-
const DEFAULT_APPROVAL_LIST_DAYS = 7;
|
|
13
|
-
const MAX_GRANT_POLICY_LENGTH = 1200;
|
|
14
|
-
const MAX_APPROVAL_CONTEXT_LENGTH = 1400;
|
|
15
|
-
const MIN_GRANT_COVERAGE_CONFIDENCE = 0.75;
|
|
16
|
-
const MAX_GRANT_COVERAGE_CANDIDATES = 5;
|
|
17
|
-
const remoteApprovalClientCache = new WeakMap();
|
|
18
6
|
function createAbortError() {
|
|
19
7
|
const error = new Error('The operation was aborted.');
|
|
20
8
|
error.name = 'AbortError';
|
|
21
9
|
return error;
|
|
22
10
|
}
|
|
11
|
+
async function dynamicImport(specifier) {
|
|
12
|
+
const loader = new Function('modulePath', 'return import(modulePath)');
|
|
13
|
+
return loader(specifier);
|
|
14
|
+
}
|
|
23
15
|
function normalizeString(value) {
|
|
24
16
|
return typeof value === 'string' && value.trim() ? value.trim() : undefined;
|
|
25
17
|
}
|
|
@@ -45,26 +37,14 @@ function getPodBaseUrl(webIdOrUri) {
|
|
|
45
37
|
}
|
|
46
38
|
return webIdOrUri.replace(/\/$/, '');
|
|
47
39
|
}
|
|
48
|
-
function
|
|
49
|
-
return `${
|
|
50
|
-
}
|
|
51
|
-
function buildThreadUri(webId, record) {
|
|
52
|
-
return `${getPodBaseUrl(webId)}/.data/chat/${buildWatchChatId(record)}/index.ttl#${record.id}`;
|
|
40
|
+
function buildThreadUri(webId, threadId) {
|
|
41
|
+
return `${getPodBaseUrl(webId)}/.data/chat/${WATCH_CHAT_ID}/index.ttl#${threadId}`;
|
|
53
42
|
}
|
|
54
43
|
function buildApprovalUri(webIdOrUri, approvalId) {
|
|
55
|
-
return
|
|
56
|
-
}
|
|
57
|
-
function buildApprovalUriForDate(webIdOrUri, approvalId, createdAt) {
|
|
58
|
-
return buildApprovalResourceUrl(webIdOrUri, approvalId, createdAt);
|
|
59
|
-
}
|
|
60
|
-
function documentUrlFromResourceUri(resourceUri) {
|
|
61
|
-
return resourceUri.split('#', 1)[0] ?? resourceUri;
|
|
44
|
+
return `${getPodBaseUrl(webIdOrUri)}/.data/approvals/${approvalId}.ttl`;
|
|
62
45
|
}
|
|
63
46
|
function buildGrantUri(webIdOrUri, grantId) {
|
|
64
|
-
return
|
|
65
|
-
}
|
|
66
|
-
function buildGrantSchemaUri(webIdOrUri) {
|
|
67
|
-
return `${getPodBaseUrl(webIdOrUri)}/settings/autonomy/schema/grant.ttl#GrantWikiPage`;
|
|
47
|
+
return `${getPodBaseUrl(webIdOrUri)}/settings/autonomy/grants/${grantId}.ttl`;
|
|
68
48
|
}
|
|
69
49
|
function buildAgentUri(webId) {
|
|
70
50
|
return `${getPodBaseUrl(webId)}/.data/agents/${WATCH_AGENT_ID}.ttl`;
|
|
@@ -123,6 +103,16 @@ function buildRequestMessage(request) {
|
|
|
123
103
|
}
|
|
124
104
|
return request.message;
|
|
125
105
|
}
|
|
106
|
+
function buildRequestAuditContext(record, request) {
|
|
107
|
+
return {
|
|
108
|
+
kind: request.kind,
|
|
109
|
+
message: buildRequestMessage(request),
|
|
110
|
+
...(request.kind === 'command-approval' && request.command ? { command: request.command } : {}),
|
|
111
|
+
...(request.kind === 'command-approval' && request.cwd ? { cwd: request.cwd } : {}),
|
|
112
|
+
backend: record.backend,
|
|
113
|
+
sessionId: record.id,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
126
116
|
function extractToolCallId(request) {
|
|
127
117
|
if (!isRecord(request.raw)) {
|
|
128
118
|
return crypto.randomUUID();
|
|
@@ -134,7 +124,7 @@ function extractToolCallId(request) {
|
|
|
134
124
|
?? crypto.randomUUID();
|
|
135
125
|
}
|
|
136
126
|
function encodeDecisionReason(decision, note) {
|
|
137
|
-
return
|
|
127
|
+
return JSON.stringify({
|
|
138
128
|
decision,
|
|
139
129
|
...(note?.trim() ? { note: note.trim() } : {}),
|
|
140
130
|
});
|
|
@@ -161,193 +151,34 @@ function parseDecisionReason(value) {
|
|
|
161
151
|
return null;
|
|
162
152
|
}
|
|
163
153
|
}
|
|
164
|
-
|
|
165
|
-
try {
|
|
166
|
-
await Promise.race([
|
|
167
|
-
task(),
|
|
168
|
-
runtime.sleep(DEFAULT_WARN_ONLY_TIMEOUT_MS).then(() => {
|
|
169
|
-
throw new Error(`Pod side-effect sync timed out after ${DEFAULT_WARN_ONLY_TIMEOUT_MS}ms`);
|
|
170
|
-
}),
|
|
171
|
-
]);
|
|
172
|
-
}
|
|
173
|
-
catch (error) {
|
|
174
|
-
if (runtime.onWarning) {
|
|
175
|
-
runtime.onWarning(error);
|
|
176
|
-
return;
|
|
177
|
-
}
|
|
178
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
179
|
-
process.emitWarning(`LinX Pod sync failed: ${message}`);
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
function safeJsonStringify(value) {
|
|
183
|
-
try {
|
|
184
|
-
return JSON.stringify(value);
|
|
185
|
-
}
|
|
186
|
-
catch {
|
|
187
|
-
return JSON.stringify({ error: 'unserializable_context' });
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
function truncatePodLiteral(value, maxLength) {
|
|
191
|
-
if (value.length <= maxLength) {
|
|
192
|
-
return value;
|
|
193
|
-
}
|
|
194
|
-
return `${value.slice(0, Math.max(0, maxLength - 15))}...[truncated]`;
|
|
195
|
-
}
|
|
196
|
-
function safeCompactJson(value, maxLength) {
|
|
197
|
-
return truncatePodLiteral(safeJsonStringify(value), maxLength);
|
|
198
|
-
}
|
|
199
|
-
function compactApprovalContext(request) {
|
|
200
|
-
return safeCompactJson({
|
|
201
|
-
kind: request.kind,
|
|
202
|
-
message: request.message,
|
|
203
|
-
toolName: request.toolName,
|
|
204
|
-
action: request.action,
|
|
205
|
-
risk: request.risk,
|
|
206
|
-
...(request.command ? { command: request.command } : {}),
|
|
207
|
-
...(request.cwd ? { cwd: request.cwd } : {}),
|
|
208
|
-
...(request.approvalOptions ? { approvalOptions: request.approvalOptions } : {}),
|
|
209
|
-
...(request.expiresAt ? { expiresAt: normalizeDateLike(request.expiresAt) } : {}),
|
|
210
|
-
...(request.context ? { sourceContext: truncatePodLiteral(request.context, 500) } : {}),
|
|
211
|
-
}, MAX_APPROVAL_CONTEXT_LENGTH);
|
|
212
|
-
}
|
|
213
|
-
function grantWikiTitleFromApproval(row, explicitTitle) {
|
|
214
|
-
const explicit = normalizeString(explicitTitle);
|
|
215
|
-
if (explicit) {
|
|
216
|
-
return truncatePodLiteral(explicit, 160);
|
|
217
|
-
}
|
|
218
|
-
return truncatePodLiteral(`${row.toolName} grant wiki for ${extractSessionId(row.session)}`, 160);
|
|
219
|
-
}
|
|
220
|
-
function grantWikiSummaryFromApproval(row, explicitSummary) {
|
|
221
|
-
const explicit = normalizeString(explicitSummary);
|
|
222
|
-
if (explicit) {
|
|
223
|
-
return truncatePodLiteral(explicit, 500);
|
|
224
|
-
}
|
|
225
|
-
return truncatePodLiteral(`Authorization wiki page for ${row.toolName}. AI Secretary must read the page body before reusing this grant.`, 500);
|
|
226
|
-
}
|
|
227
|
-
function grantWikiBodyFromApproval(row, explicitBody) {
|
|
228
|
-
const explicit = normalizeString(explicitBody);
|
|
229
|
-
if (explicit) {
|
|
230
|
-
return truncatePodLiteral(explicit, MAX_GRANT_POLICY_LENGTH);
|
|
231
|
-
}
|
|
232
|
-
return truncatePodLiteral([
|
|
233
|
-
'# Grant Semantics',
|
|
234
|
-
'',
|
|
235
|
-
'This page follows the LLM Wiki pattern: it is the maintained wiki view AI Secretary reads before reusing an authorization.',
|
|
236
|
-
'',
|
|
237
|
-
'## Covers',
|
|
238
|
-
`- Requests semantically inside target ${row.target}.`,
|
|
239
|
-
`- Action family ${row.action}.`,
|
|
240
|
-
`- Risk no higher than ${row.risk}.`,
|
|
241
|
-
'',
|
|
242
|
-
'## Does Not Cover',
|
|
243
|
-
'- Requests that are materially broader than the source approval.',
|
|
244
|
-
'- Requests that change from read-oriented to write/destructive behavior.',
|
|
245
|
-
'- Requests that touch credentials, secrets, package installation, new network side effects, or workspace boundaries unless explicitly documented here.',
|
|
246
|
-
'',
|
|
247
|
-
'## Source Context',
|
|
248
|
-
row.context ?? safeJsonStringify({ toolName: row.toolName, action: row.action, risk: row.risk }),
|
|
249
|
-
].join('\n'), MAX_GRANT_POLICY_LENGTH);
|
|
250
|
-
}
|
|
251
|
-
function grantIndexTextFromWikiBody(body) {
|
|
252
|
-
return truncatePodLiteral(body, MAX_GRANT_POLICY_LENGTH);
|
|
253
|
-
}
|
|
254
|
-
function grantWikiTagsFromApproval(row, explicitTags) {
|
|
255
|
-
const tags = [
|
|
256
|
-
'autonomy',
|
|
257
|
-
'grant',
|
|
258
|
-
row.toolName,
|
|
259
|
-
row.risk,
|
|
260
|
-
...(explicitTags ?? []),
|
|
261
|
-
]
|
|
262
|
-
.map((tag) => tag.trim())
|
|
263
|
-
.filter(Boolean);
|
|
264
|
-
return safeJsonStringify([...new Set(tags)]);
|
|
265
|
-
}
|
|
266
|
-
function grantContextFromApproval(row) {
|
|
267
|
-
return safeCompactJson({
|
|
268
|
-
sourceApproval: buildApprovalUriForDate(row.session, row.id, new Date(toIsoString(row.createdAt, new Date().toISOString()))),
|
|
269
|
-
session: row.session,
|
|
270
|
-
toolCallId: row.toolCallId,
|
|
271
|
-
toolName: row.toolName,
|
|
272
|
-
target: row.target,
|
|
273
|
-
action: row.action,
|
|
274
|
-
risk: row.risk,
|
|
275
|
-
approvalContext: row.context,
|
|
276
|
-
}, MAX_APPROVAL_CONTEXT_LENGTH);
|
|
277
|
-
}
|
|
278
|
-
function literalValues(predicates, predicate) {
|
|
279
|
-
return (predicates.get(predicate) ?? [])
|
|
280
|
-
.map((object) => isRecord(object) && object.type === 'literal' && typeof object.value === 'string' ? object.value : '')
|
|
281
|
-
.filter(Boolean);
|
|
282
|
-
}
|
|
283
|
-
function iriValues(predicates, predicate) {
|
|
284
|
-
return (predicates.get(predicate) ?? [])
|
|
285
|
-
.map((object) => isRecord(object) && object.type === 'iri' && typeof object.value === 'string' ? object.value : '')
|
|
286
|
-
.filter(Boolean);
|
|
287
|
-
}
|
|
288
|
-
function grantSourceHash(row) {
|
|
289
|
-
return `approval:${row.id}:${row.toolCallId}:${row.risk}`;
|
|
290
|
-
}
|
|
291
|
-
function encodeApprovalOptions(options) {
|
|
292
|
-
if (!options || options.length === 0) {
|
|
293
|
-
return undefined;
|
|
294
|
-
}
|
|
295
|
-
return safeJsonStringify(options);
|
|
296
|
-
}
|
|
297
|
-
function parseApprovalOptions(value) {
|
|
154
|
+
function parseRequestAuditContext(value) {
|
|
298
155
|
if (typeof value !== 'string' || !value.trim()) {
|
|
299
|
-
return
|
|
156
|
+
return null;
|
|
300
157
|
}
|
|
301
158
|
try {
|
|
302
159
|
const parsed = JSON.parse(value);
|
|
303
|
-
if (!
|
|
304
|
-
return
|
|
160
|
+
if (!isRecord(parsed)) {
|
|
161
|
+
return null;
|
|
305
162
|
}
|
|
306
|
-
const
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
...(kind ? { kind } : {}),
|
|
322
|
-
...(description ? { description } : {}),
|
|
323
|
-
};
|
|
324
|
-
})
|
|
325
|
-
.filter((option) => option !== null);
|
|
326
|
-
return options.length > 0 ? options : undefined;
|
|
163
|
+
const kind = normalizeString(parsed.kind);
|
|
164
|
+
const message = normalizeString(parsed.message);
|
|
165
|
+
const backend = normalizeString(parsed.backend);
|
|
166
|
+
const sessionId = normalizeString(parsed.sessionId);
|
|
167
|
+
if (!kind || !message || !backend || !sessionId) {
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
return {
|
|
171
|
+
kind: kind,
|
|
172
|
+
message,
|
|
173
|
+
backend: backend,
|
|
174
|
+
sessionId,
|
|
175
|
+
...(normalizeString(parsed.command) ? { command: normalizeString(parsed.command) } : {}),
|
|
176
|
+
...(normalizeString(parsed.cwd) ? { cwd: normalizeString(parsed.cwd) } : {}),
|
|
177
|
+
};
|
|
327
178
|
}
|
|
328
179
|
catch {
|
|
329
|
-
return
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
function normalizeDateLike(value) {
|
|
333
|
-
if (value instanceof Date) {
|
|
334
|
-
return Number.isFinite(value.getTime()) ? value.toISOString() : undefined;
|
|
335
|
-
}
|
|
336
|
-
if (typeof value !== 'string' || !value.trim()) {
|
|
337
|
-
return undefined;
|
|
338
|
-
}
|
|
339
|
-
const parsed = new Date(value);
|
|
340
|
-
return Number.isFinite(parsed.getTime()) ? parsed.toISOString() : undefined;
|
|
341
|
-
}
|
|
342
|
-
function resolveApprovalExpiresAt(request, now) {
|
|
343
|
-
const explicit = normalizeDateLike(request.expiresAt);
|
|
344
|
-
if (explicit) {
|
|
345
|
-
return explicit;
|
|
346
|
-
}
|
|
347
|
-
if (typeof request.timeoutMs === 'number' && Number.isFinite(request.timeoutMs) && request.timeoutMs > 0) {
|
|
348
|
-
return new Date(now.getTime() + request.timeoutMs);
|
|
180
|
+
return null;
|
|
349
181
|
}
|
|
350
|
-
return undefined;
|
|
351
182
|
}
|
|
352
183
|
function extractSessionId(sessionUri) {
|
|
353
184
|
if (sessionUri.includes('#')) {
|
|
@@ -369,42 +200,36 @@ function decisionFromApprovalRow(row) {
|
|
|
369
200
|
}
|
|
370
201
|
return 'accept';
|
|
371
202
|
}
|
|
372
|
-
function
|
|
203
|
+
function requestAuditForApproval(approvalUri, audits) {
|
|
204
|
+
const matches = audits.filter((audit) => audit.approval === approvalUri && audit.action === 'approval_requested');
|
|
205
|
+
matches.sort((left, right) => toIsoString(right.createdAt, '').localeCompare(toIsoString(left.createdAt, '')));
|
|
206
|
+
return matches[0];
|
|
207
|
+
}
|
|
208
|
+
function normalizeApprovalSummary(row, audits) {
|
|
209
|
+
const approvalUri = buildApprovalUri(row.session, row.id);
|
|
210
|
+
const requestAudit = requestAuditForApproval(approvalUri, audits);
|
|
211
|
+
const requestContext = parseRequestAuditContext(requestAudit?.context);
|
|
373
212
|
const createdAt = toIsoString(row.createdAt, new Date(0).toISOString());
|
|
374
213
|
const sessionUri = row.session;
|
|
375
214
|
const decision = decisionFromApprovalRow(row);
|
|
376
|
-
const approvalOptions = parseApprovalOptions(row.approvalOptions);
|
|
377
215
|
return {
|
|
378
216
|
id: row.id,
|
|
379
|
-
...(normalizeString(row.approvalUri) ? { approvalUri: normalizeString(row.approvalUri) } : {}),
|
|
380
217
|
sessionId: extractSessionId(sessionUri),
|
|
381
218
|
sessionUri,
|
|
382
219
|
toolCallId: row.toolCallId,
|
|
383
220
|
toolName: row.toolName,
|
|
384
221
|
risk: normalizeString(row.risk) ?? 'medium',
|
|
385
222
|
status: normalizeString(row.status) ?? 'pending',
|
|
386
|
-
message:
|
|
223
|
+
message: requestContext?.message ?? row.toolName,
|
|
224
|
+
...(requestContext?.command ? { command: requestContext.command } : {}),
|
|
225
|
+
...(requestContext?.cwd ? { cwd: requestContext.cwd } : {}),
|
|
387
226
|
...(normalizeString(row.assignedTo) ? { assignedTo: normalizeString(row.assignedTo) } : {}),
|
|
388
227
|
...(normalizeString(row.decisionBy) ? { decisionBy: normalizeString(row.decisionBy) } : {}),
|
|
389
228
|
...(decision ? { decision } : {}),
|
|
390
|
-
...(approvalOptions ? { approvalOptions } : {}),
|
|
391
229
|
createdAt,
|
|
392
|
-
...(row.expiresAt ? { expiresAt: toIsoString(row.expiresAt, createdAt) } : {}),
|
|
393
230
|
...(row.resolvedAt ? { resolvedAt: toIsoString(row.resolvedAt, createdAt) } : {}),
|
|
394
231
|
};
|
|
395
232
|
}
|
|
396
|
-
function formatApprovalMessage(row) {
|
|
397
|
-
if (row.toolName === 'commandExecution') {
|
|
398
|
-
return 'Command execution approval';
|
|
399
|
-
}
|
|
400
|
-
if (row.toolName === 'fileChange') {
|
|
401
|
-
return 'File change approval';
|
|
402
|
-
}
|
|
403
|
-
if (row.toolName === 'permissionRequest') {
|
|
404
|
-
return 'Permission approval';
|
|
405
|
-
}
|
|
406
|
-
return row.toolName;
|
|
407
|
-
}
|
|
408
233
|
function formatSummaryHeadline(summary) {
|
|
409
234
|
return `${summary.id} | ${summary.status} | ${summary.risk} | session=${summary.sessionId}`;
|
|
410
235
|
}
|
|
@@ -423,11 +248,72 @@ export function isRemoteApprovalAbortError(error) {
|
|
|
423
248
|
function missingRemoteApprovalCredentialsMessage() {
|
|
424
249
|
return 'LinX remote approval requires `linx login` first.';
|
|
425
250
|
}
|
|
251
|
+
function unsupportedRemoteApprovalAuthMessage() {
|
|
252
|
+
return 'LinX remote approval requires client credentials auth in `~/.linx`.';
|
|
253
|
+
}
|
|
426
254
|
async function createDefaultRuntime() {
|
|
255
|
+
const [credentialsStore, solidAuth, models] = await Promise.all([
|
|
256
|
+
dynamicImport(new URL('../credentials-store.js', import.meta.url).href),
|
|
257
|
+
dynamicImport(new URL('../solid-auth.js', import.meta.url).href),
|
|
258
|
+
dynamicImport(new URL('...co/models.js', import.meta.url).href),
|
|
259
|
+
]);
|
|
427
260
|
return {
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
261
|
+
loadCredentials: credentialsStore.loadCredentials,
|
|
262
|
+
getClientCredentials: credentialsStore.getClientCredentials,
|
|
263
|
+
authenticate: solidAuth.authenticate,
|
|
264
|
+
createStore(session) {
|
|
265
|
+
const db = models.drizzle(session, {
|
|
266
|
+
logger: false,
|
|
267
|
+
disableInteropDiscovery: true,
|
|
268
|
+
schema: models.solidSchema,
|
|
269
|
+
});
|
|
270
|
+
let initialized = false;
|
|
271
|
+
async function ensureInitialized() {
|
|
272
|
+
if (initialized) {
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
initialized = true;
|
|
276
|
+
await db.init([
|
|
277
|
+
models.approvalTable,
|
|
278
|
+
models.auditTable,
|
|
279
|
+
models.grantTable,
|
|
280
|
+
models.inboxNotificationTable,
|
|
281
|
+
]).catch(() => undefined);
|
|
282
|
+
}
|
|
283
|
+
return {
|
|
284
|
+
async listApprovals() {
|
|
285
|
+
await ensureInitialized();
|
|
286
|
+
return await db.select().from(models.approvalTable).execute();
|
|
287
|
+
},
|
|
288
|
+
async insertApproval(row) {
|
|
289
|
+
await ensureInitialized();
|
|
290
|
+
await db.insert(models.approvalTable).values(row).execute();
|
|
291
|
+
},
|
|
292
|
+
async updateApproval(id, patch) {
|
|
293
|
+
await ensureInitialized();
|
|
294
|
+
await db.update(models.approvalTable).set(patch).where(models.eq(models.approvalTable.id, id)).execute();
|
|
295
|
+
},
|
|
296
|
+
async listAudits() {
|
|
297
|
+
await ensureInitialized();
|
|
298
|
+
return await db.select().from(models.auditTable).execute();
|
|
299
|
+
},
|
|
300
|
+
async insertAudit(row) {
|
|
301
|
+
await ensureInitialized();
|
|
302
|
+
await db.insert(models.auditTable).values(row).execute();
|
|
303
|
+
},
|
|
304
|
+
async listGrants() {
|
|
305
|
+
await ensureInitialized();
|
|
306
|
+
return await db.select().from(models.grantTable).execute();
|
|
307
|
+
},
|
|
308
|
+
async insertGrant(row) {
|
|
309
|
+
await ensureInitialized();
|
|
310
|
+
await db.insert(models.grantTable).values(row).execute();
|
|
311
|
+
},
|
|
312
|
+
async insertInboxNotification(row) {
|
|
313
|
+
await ensureInitialized();
|
|
314
|
+
await db.insert(models.inboxNotificationTable).values(row).execute();
|
|
315
|
+
},
|
|
316
|
+
};
|
|
431
317
|
},
|
|
432
318
|
sleep(ms) {
|
|
433
319
|
return delay(ms);
|
|
@@ -435,529 +321,100 @@ async function createDefaultRuntime() {
|
|
|
435
321
|
now() {
|
|
436
322
|
return new Date();
|
|
437
323
|
},
|
|
438
|
-
resolveGrantCoverage: resolveWatchGrantCoverage,
|
|
439
324
|
};
|
|
440
325
|
}
|
|
441
326
|
async function withRemoteApprovalStore(runtime, fn) {
|
|
442
|
-
const
|
|
443
|
-
if (!
|
|
327
|
+
const stored = runtime.loadCredentials();
|
|
328
|
+
if (!stored) {
|
|
444
329
|
throw new Error(missingRemoteApprovalCredentialsMessage());
|
|
445
330
|
}
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
stored: client.session.credentials,
|
|
450
|
-
});
|
|
451
|
-
}
|
|
452
|
-
async function getRemoteApprovalClient(runtime) {
|
|
453
|
-
let promise = remoteApprovalClientCache.get(runtime);
|
|
454
|
-
if (!promise) {
|
|
455
|
-
promise = createRemoteApprovalClient(runtime)
|
|
456
|
-
.then((client) => {
|
|
457
|
-
if (!client) {
|
|
458
|
-
remoteApprovalClientCache.delete(runtime);
|
|
459
|
-
}
|
|
460
|
-
return client;
|
|
461
|
-
})
|
|
462
|
-
.catch((error) => {
|
|
463
|
-
remoteApprovalClientCache.delete(runtime);
|
|
464
|
-
throw error;
|
|
465
|
-
});
|
|
466
|
-
remoteApprovalClientCache.set(runtime, promise);
|
|
467
|
-
}
|
|
468
|
-
return promise;
|
|
469
|
-
}
|
|
470
|
-
async function createRemoteApprovalClient(runtime) {
|
|
471
|
-
const session = await runtime.getPodDataSession();
|
|
472
|
-
if (!session) {
|
|
473
|
-
return null;
|
|
474
|
-
}
|
|
475
|
-
return {
|
|
476
|
-
session,
|
|
477
|
-
store: runtime.createStore(session.webId, session.fetch),
|
|
478
|
-
};
|
|
479
|
-
}
|
|
480
|
-
function createNativeRemoteApprovalStore(webId, fetcher) {
|
|
481
|
-
return {
|
|
482
|
-
listApprovals: () => listApprovalRows(webId, fetcher),
|
|
483
|
-
findApproval: (id, options) => findApprovalRow(webId, fetcher, id, options),
|
|
484
|
-
insertApproval: (row) => writeApprovalRow(webId, fetcher, row),
|
|
485
|
-
async updateApproval(id, patch) {
|
|
486
|
-
const existing = (await listApprovalRows(webId, fetcher)).find((row) => row.id === id);
|
|
487
|
-
if (!existing) {
|
|
488
|
-
throw new Error(`Remote approval not found: ${id}`);
|
|
489
|
-
}
|
|
490
|
-
await writeApprovalRow(webId, fetcher, { ...existing, ...patch });
|
|
491
|
-
},
|
|
492
|
-
listAudits: () => listAuditRows(webId, fetcher),
|
|
493
|
-
insertAudit: (row) => writeAuditRow(webId, fetcher, row),
|
|
494
|
-
listGrants: () => listGrantRows(webId, fetcher),
|
|
495
|
-
insertGrant: (row) => writeGrantRow(webId, fetcher, row),
|
|
496
|
-
insertInboxNotification: (row) => writeInboxNotificationRow(webId, fetcher, row),
|
|
497
|
-
};
|
|
498
|
-
}
|
|
499
|
-
async function findApprovalRow(webId, fetcher, id, options = {}) {
|
|
500
|
-
if (options.resourceUri) {
|
|
501
|
-
return readApprovalRowFromResource(fetcher, options.resourceUri);
|
|
502
|
-
}
|
|
503
|
-
if (options.createdAt) {
|
|
504
|
-
const createdAt = new Date(toIsoString(options.createdAt, new Date().toISOString()));
|
|
505
|
-
return readApprovalRowFromResource(fetcher, buildApprovalResourceUrl(webId, id, createdAt));
|
|
331
|
+
const clientCredentials = runtime.getClientCredentials(stored);
|
|
332
|
+
if (!clientCredentials) {
|
|
333
|
+
throw new Error(unsupportedRemoteApprovalAuthMessage());
|
|
506
334
|
}
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
return null;
|
|
513
|
-
}
|
|
514
|
-
for (const [subject, predicates] of parseManagedTurtleBlocks(turtle, documentUrlFromResourceUri(resourceUri))) {
|
|
515
|
-
if (subject !== resourceUri) {
|
|
516
|
-
continue;
|
|
517
|
-
}
|
|
518
|
-
const row = approvalRowFromPredicates(subject, predicates);
|
|
519
|
-
if (row) {
|
|
520
|
-
return row;
|
|
521
|
-
}
|
|
335
|
+
const { session } = await runtime.authenticate(clientCredentials.clientId, clientCredentials.clientSecret, stored.url);
|
|
336
|
+
const webId = session.info.webId ?? stored.webId;
|
|
337
|
+
if (!webId) {
|
|
338
|
+
await session.logout().catch(() => undefined);
|
|
339
|
+
throw new Error('Remote approval authentication succeeded without a WebID.');
|
|
522
340
|
}
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
];
|
|
530
|
-
const rows = [];
|
|
531
|
-
for (const url of [...new Set(urls)].filter((entry) => entry.endsWith('.ttl'))) {
|
|
532
|
-
const turtle = await readTurtleResource(fetcher, url).catch(() => null);
|
|
533
|
-
if (!turtle)
|
|
534
|
-
continue;
|
|
535
|
-
for (const [subject, predicates] of parseManagedTurtleBlocks(turtle, url)) {
|
|
536
|
-
const row = approvalRowFromPredicates(subject, predicates);
|
|
537
|
-
if (row)
|
|
538
|
-
rows.push(row);
|
|
539
|
-
}
|
|
540
|
-
}
|
|
541
|
-
return rows;
|
|
542
|
-
}
|
|
543
|
-
function recentApprovalDocumentUrls(webId, days = DEFAULT_APPROVAL_LIST_DAYS) {
|
|
544
|
-
const urls = [];
|
|
545
|
-
const base = Date.now();
|
|
546
|
-
for (let offset = 0; offset < days; offset += 1) {
|
|
547
|
-
const date = new Date(base - offset * 24 * 60 * 60 * 1000);
|
|
548
|
-
urls.push(buildApprovalDocumentUrl(webId, date));
|
|
549
|
-
}
|
|
550
|
-
return urls;
|
|
551
|
-
}
|
|
552
|
-
async function writeApprovalRow(webId, fetcher, row) {
|
|
553
|
-
const createdAt = new Date(toIsoString(row.createdAt, new Date().toISOString()));
|
|
554
|
-
const documentUrl = buildApprovalDocumentUrl(webId, createdAt);
|
|
555
|
-
const subjectUrl = buildApprovalResourceUrl(webId, row.id, createdAt);
|
|
556
|
-
await upsertManagedTurtleBlock(fetcher, documentUrl, {
|
|
557
|
-
subject: subjectUrl,
|
|
558
|
-
triples: [
|
|
559
|
-
{ predicate: RDF_TYPE, object: iri(UDFS.ApprovalRequest) },
|
|
560
|
-
{ predicate: ApprovalVocab.session, object: iri(row.session) },
|
|
561
|
-
{ predicate: ApprovalVocab.toolCallId, object: literal(row.toolCallId) },
|
|
562
|
-
{ predicate: ApprovalVocab.toolName, object: literal(row.toolName) },
|
|
563
|
-
{ predicate: ApprovalVocab.target, object: iri(row.target) },
|
|
564
|
-
{ predicate: ApprovalVocab.action, object: iri(row.action) },
|
|
565
|
-
{ predicate: ApprovalVocab.risk, object: literal(row.risk) },
|
|
566
|
-
{ predicate: ApprovalVocab.status, object: literal(row.status) },
|
|
567
|
-
...(row.assignedTo ? [{ predicate: ApprovalVocab.assignedTo, object: iri(row.assignedTo) }] : []),
|
|
568
|
-
...(row.decisionBy ? [{ predicate: ApprovalVocab.decisionBy, object: iri(row.decisionBy) }] : []),
|
|
569
|
-
...(row.decisionRole ? [{ predicate: ApprovalVocab.decisionRole, object: literal(row.decisionRole) }] : []),
|
|
570
|
-
...(row.onBehalfOf ? [{ predicate: ApprovalVocab.onBehalfOf, object: iri(row.onBehalfOf) }] : []),
|
|
571
|
-
...(row.reason ? [{ predicate: ApprovalVocab.reason, object: literal(row.reason) }] : []),
|
|
572
|
-
...(row.context ? [{ predicate: ApprovalVocab.context, object: literal(row.context) }] : []),
|
|
573
|
-
...(row.approvalOptions ? [{ predicate: ApprovalVocab.approvalOptions, object: literal(row.approvalOptions) }] : []),
|
|
574
|
-
...(row.policyVersion ? [{ predicate: ApprovalVocab.policyVersion, object: literal(row.policyVersion) }] : []),
|
|
575
|
-
{ predicate: ApprovalVocab.createdAt, object: literal(toIsoString(row.createdAt, new Date().toISOString())) },
|
|
576
|
-
...(row.expiresAt ? [{ predicate: ApprovalVocab.expiresAt, object: literal(toIsoString(row.expiresAt, new Date().toISOString())) }] : []),
|
|
577
|
-
...(row.resolvedAt ? [{ predicate: ApprovalVocab.resolvedAt, object: literal(toIsoString(row.resolvedAt, new Date().toISOString())) }] : []),
|
|
578
|
-
],
|
|
579
|
-
});
|
|
580
|
-
}
|
|
581
|
-
async function listAuditRows(webId, fetcher) {
|
|
582
|
-
const urls = await listTurtleResourcesRecursive(fetcher, `${getPodBaseUrl(webId)}/.data/audits/`);
|
|
583
|
-
const rows = [];
|
|
584
|
-
for (const url of urls.filter((entry) => entry.endsWith('.ttl'))) {
|
|
585
|
-
const turtle = await readTurtleResource(fetcher, url).catch(() => null);
|
|
586
|
-
if (!turtle)
|
|
587
|
-
continue;
|
|
588
|
-
for (const [subject, predicates] of parseManagedTurtleBlocks(turtle, url)) {
|
|
589
|
-
const row = auditRowFromPredicates(subject, predicates);
|
|
590
|
-
if (row)
|
|
591
|
-
rows.push(row);
|
|
592
|
-
}
|
|
593
|
-
}
|
|
594
|
-
return rows;
|
|
595
|
-
}
|
|
596
|
-
async function writeAuditRow(webId, fetcher, row) {
|
|
597
|
-
const createdAt = new Date(toIsoString(row.createdAt, new Date().toISOString()));
|
|
598
|
-
const documentUrl = buildAuditDocumentUrl(webId, createdAt);
|
|
599
|
-
const subjectUrl = buildAuditResourceUrl(webId, row.id, createdAt);
|
|
600
|
-
await upsertManagedTurtleBlock(fetcher, documentUrl, {
|
|
601
|
-
subject: subjectUrl,
|
|
602
|
-
triples: [
|
|
603
|
-
{ predicate: RDF_TYPE, object: iri(UDFS.AuditEntry) },
|
|
604
|
-
{ predicate: AuditVocab.action, object: literal(row.action) },
|
|
605
|
-
{ predicate: AuditVocab.actor, object: iri(row.actor) },
|
|
606
|
-
{ predicate: AuditVocab.actorRole, object: literal(row.actorRole) },
|
|
607
|
-
...(row.onBehalfOf ? [{ predicate: AuditVocab.onBehalfOf, object: iri(row.onBehalfOf) }] : []),
|
|
608
|
-
...(row.session ? [{ predicate: AuditVocab.session, object: iri(row.session) }] : []),
|
|
609
|
-
...(row.entry ? [{ predicate: AuditVocab.entry, object: iri(row.entry) }] : []),
|
|
610
|
-
...(row.toolCallId ? [{ predicate: AuditVocab.toolCallId, object: literal(row.toolCallId) }] : []),
|
|
611
|
-
...(row.toolName ? [{ predicate: AuditVocab.toolName, object: literal(row.toolName) }] : []),
|
|
612
|
-
...(row.approval ? [{ predicate: AuditVocab.approval, object: iri(row.approval) }] : []),
|
|
613
|
-
...(row.policyVersion ? [{ predicate: AuditVocab.policyVersion, object: literal(row.policyVersion) }] : []),
|
|
614
|
-
{ predicate: AuditVocab.createdAt, object: literal(toIsoString(row.createdAt, new Date().toISOString())) },
|
|
615
|
-
],
|
|
616
|
-
});
|
|
617
|
-
}
|
|
618
|
-
async function listGrantRows(webId, fetcher) {
|
|
619
|
-
const urls = [
|
|
620
|
-
...await listTurtleResources(fetcher, `${getPodBaseUrl(webId)}/settings/autonomy/grants/`).catch(() => []),
|
|
621
|
-
];
|
|
622
|
-
const rows = [];
|
|
623
|
-
for (const url of urls.filter((entry) => entry.endsWith('.ttl'))) {
|
|
624
|
-
const turtle = await readTurtleResource(fetcher, url).catch(() => null);
|
|
625
|
-
if (!turtle)
|
|
626
|
-
continue;
|
|
627
|
-
for (const [subject, predicates] of parseManagedTurtleBlocks(turtle, url)) {
|
|
628
|
-
const row = grantRowFromPredicates(subject, predicates);
|
|
629
|
-
if (row)
|
|
630
|
-
rows.push(row);
|
|
631
|
-
}
|
|
632
|
-
}
|
|
633
|
-
return rows;
|
|
634
|
-
}
|
|
635
|
-
async function writeGrantRow(webId, fetcher, row) {
|
|
636
|
-
const id = normalizeString(row.id) ?? crypto.randomUUID();
|
|
637
|
-
const subjectUrl = buildGrantResourceUrl(webId, id);
|
|
638
|
-
const documentUrl = subjectUrl;
|
|
639
|
-
const target = normalizeString(row.target);
|
|
640
|
-
const action = normalizeString(row.action);
|
|
641
|
-
const effect = normalizeString(row.effect);
|
|
642
|
-
const decisionBy = normalizeString(row.decisionBy);
|
|
643
|
-
const decisionRole = normalizeString(row.decisionRole);
|
|
644
|
-
if (!target || !action || !effect || !decisionBy || !decisionRole) {
|
|
645
|
-
throw new Error(`Invalid remote approval grant row: ${id}`);
|
|
646
|
-
}
|
|
647
|
-
await upsertManagedTurtleBlock(fetcher, documentUrl, {
|
|
648
|
-
subject: subjectUrl,
|
|
649
|
-
triples: [
|
|
650
|
-
{ predicate: RDF_TYPE, object: iri(ODRL.Policy) },
|
|
651
|
-
{ predicate: RDF_TYPE, object: iri(UDFS.AutonomyGrant) },
|
|
652
|
-
{ predicate: GrantVocab.target, object: iri(target) },
|
|
653
|
-
{ predicate: GrantVocab.action, object: iri(action) },
|
|
654
|
-
...(normalizeString(row.title) ? [{ predicate: GrantVocab.title, object: literal(truncatePodLiteral(normalizeString(row.title), 160)) }] : []),
|
|
655
|
-
...(normalizeString(row.summary) ? [{ predicate: GrantVocab.summary, object: literal(truncatePodLiteral(normalizeString(row.summary), 500)) }] : []),
|
|
656
|
-
...(normalizeString(row.body) ? [{ predicate: GrantVocab.body, object: literal(truncatePodLiteral(normalizeString(row.body), MAX_GRANT_POLICY_LENGTH)) }] : []),
|
|
657
|
-
...(normalizeString(row.schema) ? [{ predicate: GrantVocab.schema, object: iri(normalizeString(row.schema)) }] : []),
|
|
658
|
-
...(normalizeString(row.pageKind) ? [{ predicate: GrantVocab.pageKind, object: literal(normalizeString(row.pageKind)) }] : []),
|
|
659
|
-
...(normalizeString(row.wikiStatus) ? [{ predicate: GrantVocab.wikiStatus, object: literal(normalizeString(row.wikiStatus)) }] : []),
|
|
660
|
-
...(normalizeString(row.tags) ? [{ predicate: GrantVocab.tags, object: literal(truncatePodLiteral(normalizeString(row.tags), 500)) }] : []),
|
|
661
|
-
...(normalizeString(row.source) ? [{ predicate: GrantVocab.source, object: literal(normalizeString(row.source)) }] : []),
|
|
662
|
-
...(normalizeString(row.sourceHash) ? [{ predicate: GrantVocab.sourceHash, object: literal(normalizeString(row.sourceHash)) }] : []),
|
|
663
|
-
...(row.compiledAt ? [{ predicate: GrantVocab.compiledAt, object: literal(toIsoString(row.compiledAt, new Date().toISOString())) }] : []),
|
|
664
|
-
...(row.compiledFrom ?? []).map((value) => ({ predicate: GrantVocab.compiledFrom, object: iri(value) })),
|
|
665
|
-
...(row.related ?? []).map((value) => ({ predicate: GrantVocab.related, object: iri(value) })),
|
|
666
|
-
{ predicate: GrantVocab.effect, object: literal(effect) },
|
|
667
|
-
...(normalizeString(row.riskCeiling) ? [{ predicate: GrantVocab.riskCeiling, object: literal(normalizeString(row.riskCeiling)) }] : []),
|
|
668
|
-
...(normalizeString(row.policy) ? [{ predicate: GrantVocab.policy, object: literal(truncatePodLiteral(normalizeString(row.policy), MAX_GRANT_POLICY_LENGTH)) }] : []),
|
|
669
|
-
...(normalizeString(row.context) ? [{ predicate: GrantVocab.context, object: literal(truncatePodLiteral(normalizeString(row.context), MAX_APPROVAL_CONTEXT_LENGTH)) }] : []),
|
|
670
|
-
{ predicate: GrantVocab.decisionBy, object: iri(decisionBy) },
|
|
671
|
-
{ predicate: GrantVocab.decisionRole, object: literal(decisionRole) },
|
|
672
|
-
...(normalizeString(row.onBehalfOf) ? [{ predicate: GrantVocab.onBehalfOf, object: iri(normalizeString(row.onBehalfOf)) }] : []),
|
|
673
|
-
{ predicate: GrantVocab.createdAt, object: literal(toIsoString(row.createdAt, new Date().toISOString())) },
|
|
674
|
-
...(normalizeString(row.revokedAt) ? [{ predicate: GrantVocab.revokedAt, object: literal(normalizeString(row.revokedAt)) }] : []),
|
|
675
|
-
],
|
|
676
|
-
});
|
|
677
|
-
}
|
|
678
|
-
async function writeInboxNotificationRow(webId, fetcher, row) {
|
|
679
|
-
const url = buildInboxResourceUrl(webId, row.id);
|
|
680
|
-
await upsertManagedTurtleBlock(fetcher, url, {
|
|
681
|
-
subject: url,
|
|
682
|
-
triples: [
|
|
683
|
-
{ predicate: RDF_TYPE, object: iri(AS.Announce) },
|
|
684
|
-
...(row.actor ? [{ predicate: InboxNotificationVocab.actor, object: iri(row.actor) }] : []),
|
|
685
|
-
{ predicate: InboxNotificationVocab.object, object: iri(row.object) },
|
|
686
|
-
{ predicate: InboxNotificationVocab.createdAt, object: literal(toIsoString(row.createdAt, new Date().toISOString())) },
|
|
687
|
-
],
|
|
688
|
-
});
|
|
689
|
-
}
|
|
690
|
-
function approvalRowFromPredicates(url, predicates) {
|
|
691
|
-
const session = firstIri(predicates, ApprovalVocab.session);
|
|
692
|
-
const toolCallId = firstLiteral(predicates, ApprovalVocab.toolCallId);
|
|
693
|
-
const toolName = firstLiteral(predicates, ApprovalVocab.toolName);
|
|
694
|
-
const target = firstIri(predicates, ApprovalVocab.target);
|
|
695
|
-
const action = firstIri(predicates, ApprovalVocab.action);
|
|
696
|
-
const risk = firstLiteral(predicates, ApprovalVocab.risk);
|
|
697
|
-
const status = firstLiteral(predicates, ApprovalVocab.status);
|
|
698
|
-
const createdAt = firstLiteral(predicates, ApprovalVocab.createdAt);
|
|
699
|
-
if (!session || !toolCallId || !toolName || !target || !action || !risk || !status || !createdAt) {
|
|
700
|
-
return null;
|
|
701
|
-
}
|
|
702
|
-
return {
|
|
703
|
-
id: subjectIdFromResourceUrl(url),
|
|
704
|
-
session,
|
|
705
|
-
toolCallId,
|
|
706
|
-
toolName,
|
|
707
|
-
target,
|
|
708
|
-
action,
|
|
709
|
-
risk,
|
|
710
|
-
status,
|
|
711
|
-
assignedTo: firstIri(predicates, ApprovalVocab.assignedTo),
|
|
712
|
-
decisionBy: firstIri(predicates, ApprovalVocab.decisionBy),
|
|
713
|
-
decisionRole: firstLiteral(predicates, ApprovalVocab.decisionRole),
|
|
714
|
-
onBehalfOf: firstIri(predicates, ApprovalVocab.onBehalfOf),
|
|
715
|
-
reason: firstLiteral(predicates, ApprovalVocab.reason),
|
|
716
|
-
context: firstLiteral(predicates, ApprovalVocab.context),
|
|
717
|
-
approvalOptions: firstLiteral(predicates, ApprovalVocab.approvalOptions),
|
|
718
|
-
policyVersion: firstLiteral(predicates, ApprovalVocab.policyVersion),
|
|
719
|
-
createdAt,
|
|
720
|
-
expiresAt: firstLiteral(predicates, ApprovalVocab.expiresAt),
|
|
721
|
-
resolvedAt: firstLiteral(predicates, ApprovalVocab.resolvedAt),
|
|
722
|
-
};
|
|
723
|
-
}
|
|
724
|
-
function auditRowFromPredicates(url, predicates) {
|
|
725
|
-
const action = firstLiteral(predicates, AuditVocab.action);
|
|
726
|
-
const actor = firstIri(predicates, AuditVocab.actor);
|
|
727
|
-
const actorRole = firstLiteral(predicates, AuditVocab.actorRole);
|
|
728
|
-
const createdAt = firstLiteral(predicates, AuditVocab.createdAt);
|
|
729
|
-
if (!action || !actor || !actorRole || !createdAt) {
|
|
730
|
-
return null;
|
|
731
|
-
}
|
|
732
|
-
return {
|
|
733
|
-
id: subjectIdFromResourceUrl(url),
|
|
734
|
-
action,
|
|
735
|
-
actor,
|
|
736
|
-
actorRole,
|
|
737
|
-
onBehalfOf: firstIri(predicates, AuditVocab.onBehalfOf),
|
|
738
|
-
session: firstIri(predicates, AuditVocab.session),
|
|
739
|
-
entry: firstIri(predicates, AuditVocab.entry),
|
|
740
|
-
toolCallId: firstLiteral(predicates, AuditVocab.toolCallId),
|
|
741
|
-
toolName: firstLiteral(predicates, AuditVocab.toolName),
|
|
742
|
-
approval: firstIri(predicates, AuditVocab.approval),
|
|
743
|
-
policyVersion: firstLiteral(predicates, AuditVocab.policyVersion),
|
|
744
|
-
createdAt,
|
|
745
|
-
};
|
|
746
|
-
}
|
|
747
|
-
function grantRowFromPredicates(url, predicates) {
|
|
748
|
-
const target = firstIri(predicates, GrantVocab.target);
|
|
749
|
-
const action = firstIri(predicates, GrantVocab.action);
|
|
750
|
-
const effect = firstLiteral(predicates, GrantVocab.effect);
|
|
751
|
-
const decisionBy = firstIri(predicates, GrantVocab.decisionBy);
|
|
752
|
-
const decisionRole = firstLiteral(predicates, GrantVocab.decisionRole);
|
|
753
|
-
const createdAt = firstLiteral(predicates, GrantVocab.createdAt);
|
|
754
|
-
if (!target || !action || !effect || !decisionBy || !decisionRole || !createdAt) {
|
|
755
|
-
return null;
|
|
756
|
-
}
|
|
757
|
-
return {
|
|
758
|
-
id: subjectIdFromResourceUrl(url),
|
|
759
|
-
target,
|
|
760
|
-
action,
|
|
761
|
-
title: firstLiteral(predicates, GrantVocab.title),
|
|
762
|
-
summary: firstLiteral(predicates, GrantVocab.summary),
|
|
763
|
-
body: firstLiteral(predicates, GrantVocab.body),
|
|
764
|
-
schema: firstIri(predicates, GrantVocab.schema),
|
|
765
|
-
pageKind: firstLiteral(predicates, GrantVocab.pageKind),
|
|
766
|
-
wikiStatus: firstLiteral(predicates, GrantVocab.wikiStatus),
|
|
767
|
-
tags: firstLiteral(predicates, GrantVocab.tags),
|
|
768
|
-
source: firstLiteral(predicates, GrantVocab.source),
|
|
769
|
-
sourceHash: firstLiteral(predicates, GrantVocab.sourceHash),
|
|
770
|
-
compiledAt: firstLiteral(predicates, GrantVocab.compiledAt),
|
|
771
|
-
compiledFrom: iriValues(predicates, GrantVocab.compiledFrom),
|
|
772
|
-
related: iriValues(predicates, GrantVocab.related),
|
|
773
|
-
effect,
|
|
774
|
-
riskCeiling: firstLiteral(predicates, GrantVocab.riskCeiling),
|
|
775
|
-
policy: firstLiteral(predicates, GrantVocab.policy),
|
|
776
|
-
context: firstLiteral(predicates, GrantVocab.context),
|
|
777
|
-
decisionBy,
|
|
778
|
-
decisionRole,
|
|
779
|
-
onBehalfOf: firstIri(predicates, GrantVocab.onBehalfOf),
|
|
780
|
-
createdAt,
|
|
781
|
-
revokedAt: firstLiteral(predicates, GrantVocab.revokedAt),
|
|
782
|
-
};
|
|
783
|
-
}
|
|
784
|
-
function isActiveAllowGrant(grant) {
|
|
785
|
-
return grant.effect === 'allow' && !grant.revokedAt && !!(normalizeString(grant.body) || normalizeString(grant.policy));
|
|
786
|
-
}
|
|
787
|
-
function isGrantRiskCandidate(grant, requestRisk) {
|
|
788
|
-
const ceiling = riskScore(typeof grant.riskCeiling === 'string' ? grant.riskCeiling : undefined);
|
|
789
|
-
return ceiling === 0 || ceiling >= riskScore(requestRisk);
|
|
790
|
-
}
|
|
791
|
-
function rankGrantCandidate(grant, requestContext) {
|
|
792
|
-
let score = 0;
|
|
793
|
-
if (grant.target === requestContext.target) {
|
|
794
|
-
score += 4;
|
|
795
|
-
}
|
|
796
|
-
if (grant.action === requestContext.action) {
|
|
797
|
-
score += 3;
|
|
798
|
-
}
|
|
799
|
-
if (grant.schema) {
|
|
800
|
-
score += 2;
|
|
801
|
-
}
|
|
802
|
-
if (grant.pageKind === 'autonomy-grant') {
|
|
803
|
-
score += 1;
|
|
804
|
-
}
|
|
805
|
-
return score;
|
|
806
|
-
}
|
|
807
|
-
function selectSemanticGrantCandidates(grants, requestContext) {
|
|
808
|
-
const risk = normalizeString(requestContext.risk) ?? 'medium';
|
|
809
|
-
return grants
|
|
810
|
-
.filter((grant) => isActiveAllowGrant(grant) && isGrantRiskCandidate(grant, risk))
|
|
811
|
-
.sort((left, right) => rankGrantCandidate(right, requestContext) - rankGrantCandidate(left, requestContext))
|
|
812
|
-
.slice(0, MAX_GRANT_COVERAGE_CANDIDATES);
|
|
813
|
-
}
|
|
814
|
-
function acceptsGrantCoverage(decision) {
|
|
815
|
-
return decision?.covers === true
|
|
816
|
-
&& typeof decision.confidence === 'number'
|
|
817
|
-
&& decision.confidence >= MIN_GRANT_COVERAGE_CONFIDENCE;
|
|
818
|
-
}
|
|
819
|
-
async function resolveSemanticGrantDecision(options) {
|
|
820
|
-
const candidates = selectSemanticGrantCandidates(options.grants, options.requestContext);
|
|
821
|
-
if (candidates.length === 0) {
|
|
822
|
-
return null;
|
|
341
|
+
try {
|
|
342
|
+
return await fn({
|
|
343
|
+
store: runtime.createStore(session),
|
|
344
|
+
webId,
|
|
345
|
+
stored,
|
|
346
|
+
});
|
|
823
347
|
}
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
const coverage = await resolver({
|
|
827
|
-
record: options.record,
|
|
828
|
-
request: options.request,
|
|
829
|
-
requestContext: options.requestContext,
|
|
830
|
-
grant,
|
|
831
|
-
}).catch(() => null);
|
|
832
|
-
if (acceptsGrantCoverage(coverage)) {
|
|
833
|
-
return 'accept_for_session';
|
|
834
|
-
}
|
|
348
|
+
finally {
|
|
349
|
+
await session.logout().catch(() => undefined);
|
|
835
350
|
}
|
|
836
|
-
return null;
|
|
837
|
-
}
|
|
838
|
-
function buildWatchGrantRequestContext(input) {
|
|
839
|
-
return {
|
|
840
|
-
session: buildThreadUri(input.webId, input.record),
|
|
841
|
-
target: buildThreadUri(input.webId, input.record),
|
|
842
|
-
action: buildActionUri(input.request),
|
|
843
|
-
risk: buildRisk(input.request),
|
|
844
|
-
toolName: buildToolName(input.request),
|
|
845
|
-
cwd: input.record.cwd,
|
|
846
|
-
backend: input.record.backend,
|
|
847
|
-
mode: input.record.mode,
|
|
848
|
-
};
|
|
849
|
-
}
|
|
850
|
-
function buildGenericGrantRequestContext(input) {
|
|
851
|
-
return {
|
|
852
|
-
session: input.subject.sessionUri,
|
|
853
|
-
target: input.subject.targetUri ?? input.subject.sessionUri,
|
|
854
|
-
action: input.request.action,
|
|
855
|
-
risk: input.request.risk,
|
|
856
|
-
toolName: input.request.toolName,
|
|
857
|
-
cwd: input.request.cwd,
|
|
858
|
-
kind: input.request.kind,
|
|
859
|
-
};
|
|
860
351
|
}
|
|
861
352
|
export async function createRemoteWatchApproval(options) {
|
|
862
353
|
const activeRuntime = options.runtime ?? await createDefaultRuntime();
|
|
863
|
-
return
|
|
864
|
-
subject: ({ webId }) => ({
|
|
865
|
-
sessionUri: buildThreadUri(webId, options.record),
|
|
866
|
-
actorUri: buildAgentUri(webId),
|
|
867
|
-
policyVersion: REMOTE_APPROVAL_POLICY_VERSION,
|
|
868
|
-
}),
|
|
869
|
-
request: ({ sessionUri }) => ({
|
|
870
|
-
kind: options.request.kind,
|
|
871
|
-
message: buildRequestMessage(options.request),
|
|
872
|
-
toolCallId: extractToolCallId(options.request),
|
|
873
|
-
toolName: buildToolName(options.request),
|
|
874
|
-
action: buildActionUri(options.request),
|
|
875
|
-
risk: buildRisk(options.request),
|
|
876
|
-
...(options.request.kind === 'command-approval' && options.request.command ? { command: options.request.command } : {}),
|
|
877
|
-
...(options.request.kind === 'command-approval' && options.request.cwd ? { cwd: options.request.cwd } : {}),
|
|
878
|
-
...(options.request.approvalOptions ? { approvalOptions: options.request.approvalOptions } : {}),
|
|
879
|
-
...(options.request.timeoutMs ? { timeoutMs: options.request.timeoutMs } : {}),
|
|
880
|
-
...(options.request.expiresAt ? { expiresAt: options.request.expiresAt } : {}),
|
|
881
|
-
entry: sessionUri,
|
|
882
|
-
}),
|
|
883
|
-
runtime: activeRuntime,
|
|
884
|
-
});
|
|
885
|
-
}
|
|
886
|
-
export async function createRemoteApproval(options) {
|
|
887
|
-
const activeRuntime = options.runtime ?? await createDefaultRuntime();
|
|
888
|
-
return withRemoteApprovalStore(activeRuntime, async ({ store, webId, stored }) => {
|
|
889
|
-
const subject = typeof options.subject === 'function'
|
|
890
|
-
? options.subject({ webId, stored })
|
|
891
|
-
: options.subject;
|
|
892
|
-
const request = typeof options.request === 'function'
|
|
893
|
-
? options.request({ webId, stored, sessionUri: subject.sessionUri })
|
|
894
|
-
: options.request;
|
|
354
|
+
return withRemoteApprovalStore(activeRuntime, async ({ store, webId }) => {
|
|
895
355
|
const approvalId = crypto.randomUUID();
|
|
896
356
|
const now = activeRuntime.now();
|
|
897
|
-
const sessionUri =
|
|
898
|
-
const approvalUri =
|
|
899
|
-
const
|
|
900
|
-
const
|
|
901
|
-
const onBehalfOf = subject.onBehalfOf ?? webId;
|
|
902
|
-
const policyVersion = subject.policyVersion ?? REMOTE_APPROVAL_POLICY_VERSION;
|
|
903
|
-
const requestEntry = request.entry ?? approvalUri;
|
|
904
|
-
const expiresAt = resolveApprovalExpiresAt(request, now);
|
|
905
|
-
const approvalOptions = encodeApprovalOptions(request.approvalOptions);
|
|
906
|
-
const context = compactApprovalContext(request);
|
|
357
|
+
const sessionUri = buildThreadUri(webId, options.record.id);
|
|
358
|
+
const approvalUri = buildApprovalUri(webId, approvalId);
|
|
359
|
+
const toolCallId = extractToolCallId(options.request);
|
|
360
|
+
const requestContext = buildRequestAuditContext(options.record, options.request);
|
|
907
361
|
await store.insertApproval({
|
|
908
362
|
id: approvalId,
|
|
909
363
|
session: sessionUri,
|
|
910
|
-
toolCallId
|
|
911
|
-
toolName: request
|
|
912
|
-
target:
|
|
913
|
-
action: request
|
|
914
|
-
risk: request
|
|
364
|
+
toolCallId,
|
|
365
|
+
toolName: buildToolName(options.request),
|
|
366
|
+
target: sessionUri,
|
|
367
|
+
action: buildActionUri(options.request),
|
|
368
|
+
risk: buildRisk(options.request),
|
|
915
369
|
status: 'pending',
|
|
916
|
-
assignedTo,
|
|
917
|
-
|
|
918
|
-
...(approvalOptions ? { approvalOptions } : {}),
|
|
919
|
-
policyVersion,
|
|
370
|
+
assignedTo: webId,
|
|
371
|
+
policyVersion: REMOTE_APPROVAL_POLICY_VERSION,
|
|
920
372
|
createdAt: now,
|
|
921
|
-
...(expiresAt ? { expiresAt } : {}),
|
|
922
373
|
});
|
|
923
|
-
|
|
374
|
+
await store.insertAudit({
|
|
924
375
|
id: crypto.randomUUID(),
|
|
925
376
|
action: 'approval_requested',
|
|
926
|
-
actor:
|
|
377
|
+
actor: buildAgentUri(webId),
|
|
927
378
|
actorRole: 'secretary',
|
|
928
|
-
onBehalfOf,
|
|
379
|
+
onBehalfOf: webId,
|
|
929
380
|
session: sessionUri,
|
|
930
|
-
|
|
931
|
-
toolCallId: request.toolCallId,
|
|
932
|
-
toolName: request.toolName,
|
|
381
|
+
toolCallId,
|
|
933
382
|
approval: approvalUri,
|
|
934
|
-
|
|
383
|
+
context: JSON.stringify(requestContext),
|
|
384
|
+
policyVersion: REMOTE_APPROVAL_POLICY_VERSION,
|
|
935
385
|
createdAt: now,
|
|
936
|
-
};
|
|
937
|
-
await
|
|
938
|
-
await warnOnly(activeRuntime, () => store.insertInboxNotification({
|
|
386
|
+
});
|
|
387
|
+
await store.insertInboxNotification({
|
|
939
388
|
id: crypto.randomUUID(),
|
|
940
|
-
actor:
|
|
389
|
+
actor: buildAgentUri(webId),
|
|
941
390
|
object: approvalUri,
|
|
942
391
|
createdAt: now,
|
|
943
|
-
}));
|
|
392
|
+
}).catch(() => undefined);
|
|
944
393
|
return normalizeApprovalSummary({
|
|
945
394
|
id: approvalId,
|
|
946
|
-
approvalUri,
|
|
947
395
|
session: sessionUri,
|
|
948
|
-
toolCallId
|
|
949
|
-
toolName: request
|
|
950
|
-
target:
|
|
951
|
-
action: request
|
|
952
|
-
risk: request
|
|
396
|
+
toolCallId,
|
|
397
|
+
toolName: buildToolName(options.request),
|
|
398
|
+
target: sessionUri,
|
|
399
|
+
action: buildActionUri(options.request),
|
|
400
|
+
risk: buildRisk(options.request),
|
|
953
401
|
status: 'pending',
|
|
954
|
-
assignedTo,
|
|
955
|
-
|
|
956
|
-
...(approvalOptions ? { approvalOptions } : {}),
|
|
957
|
-
policyVersion,
|
|
402
|
+
assignedTo: webId,
|
|
403
|
+
policyVersion: REMOTE_APPROVAL_POLICY_VERSION,
|
|
958
404
|
createdAt: now,
|
|
959
|
-
|
|
960
|
-
|
|
405
|
+
}, [{
|
|
406
|
+
id: crypto.randomUUID(),
|
|
407
|
+
action: 'approval_requested',
|
|
408
|
+
actor: buildAgentUri(webId),
|
|
409
|
+
actorRole: 'secretary',
|
|
410
|
+
onBehalfOf: webId,
|
|
411
|
+
session: sessionUri,
|
|
412
|
+
toolCallId,
|
|
413
|
+
approval: approvalUri,
|
|
414
|
+
context: JSON.stringify(requestContext),
|
|
415
|
+
policyVersion: REMOTE_APPROVAL_POLICY_VERSION,
|
|
416
|
+
createdAt: now,
|
|
417
|
+
}]);
|
|
961
418
|
});
|
|
962
419
|
}
|
|
963
420
|
export async function waitForRemoteWatchApproval(options) {
|
|
@@ -967,13 +424,10 @@ export async function waitForRemoteWatchApproval(options) {
|
|
|
967
424
|
if (options.signal?.aborted) {
|
|
968
425
|
throw createAbortError();
|
|
969
426
|
}
|
|
970
|
-
const
|
|
971
|
-
|
|
972
|
-
approvalUri: options.approvalUri,
|
|
973
|
-
});
|
|
427
|
+
const approvals = await store.listApprovals();
|
|
428
|
+
const row = approvals.find((entry) => entry.id === options.approvalId);
|
|
974
429
|
if (!row) {
|
|
975
|
-
|
|
976
|
-
continue;
|
|
430
|
+
throw new Error(`Remote approval disappeared before resolution: ${options.approvalId}`);
|
|
977
431
|
}
|
|
978
432
|
const decision = decisionFromApprovalRow(row);
|
|
979
433
|
if (decision) {
|
|
@@ -987,20 +441,17 @@ export async function requestRemoteWatchApproval(options) {
|
|
|
987
441
|
const activeRuntime = options.runtime ?? await createDefaultRuntime();
|
|
988
442
|
const delegated = await withRemoteApprovalStore(activeRuntime, async ({ store, webId }) => {
|
|
989
443
|
const grants = await store.listGrants();
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
request: options.request,
|
|
999
|
-
}),
|
|
1000
|
-
});
|
|
444
|
+
const requestAction = buildActionUri(options.request);
|
|
445
|
+
const requestTarget = buildThreadUri(webId, options.record.id);
|
|
446
|
+
const requestRisk = buildRisk(options.request);
|
|
447
|
+
return grants.some((grant) => (grant.effect === 'allow'
|
|
448
|
+
&& grant.action === requestAction
|
|
449
|
+
&& grant.target === requestTarget
|
|
450
|
+
&& riskScore(typeof grant.riskCeiling === 'string' ? grant.riskCeiling : undefined) >= riskScore(requestRisk)
|
|
451
|
+
&& !grant.revokedAt));
|
|
1001
452
|
});
|
|
1002
453
|
if (delegated) {
|
|
1003
|
-
return
|
|
454
|
+
return 'accept_for_session';
|
|
1004
455
|
}
|
|
1005
456
|
const summary = await createRemoteWatchApproval({
|
|
1006
457
|
record: options.record,
|
|
@@ -1009,62 +460,6 @@ export async function requestRemoteWatchApproval(options) {
|
|
|
1009
460
|
});
|
|
1010
461
|
return waitForRemoteWatchApproval({
|
|
1011
462
|
approvalId: summary.id,
|
|
1012
|
-
approvalUri: summary.approvalUri,
|
|
1013
|
-
pollMs: options.pollMs,
|
|
1014
|
-
signal: options.signal,
|
|
1015
|
-
runtime: activeRuntime,
|
|
1016
|
-
});
|
|
1017
|
-
}
|
|
1018
|
-
export async function resolveExistingRemoteWatchGrant(options) {
|
|
1019
|
-
const activeRuntime = options.runtime ?? await createDefaultRuntime();
|
|
1020
|
-
return withRemoteApprovalStore(activeRuntime, async ({ store, webId }) => {
|
|
1021
|
-
const grants = await store.listGrants();
|
|
1022
|
-
return resolveSemanticGrantDecision({
|
|
1023
|
-
runtime: activeRuntime,
|
|
1024
|
-
grants,
|
|
1025
|
-
record: options.record,
|
|
1026
|
-
request: options.request,
|
|
1027
|
-
requestContext: buildWatchGrantRequestContext({
|
|
1028
|
-
webId,
|
|
1029
|
-
record: options.record,
|
|
1030
|
-
request: options.request,
|
|
1031
|
-
}),
|
|
1032
|
-
});
|
|
1033
|
-
});
|
|
1034
|
-
}
|
|
1035
|
-
export async function requestRemoteApproval(options) {
|
|
1036
|
-
const activeRuntime = options.runtime ?? await createDefaultRuntime();
|
|
1037
|
-
const delegated = await withRemoteApprovalStore(activeRuntime, async ({ store, webId, stored }) => {
|
|
1038
|
-
const subject = typeof options.subject === 'function'
|
|
1039
|
-
? options.subject({ webId, stored })
|
|
1040
|
-
: options.subject;
|
|
1041
|
-
const request = typeof options.request === 'function'
|
|
1042
|
-
? options.request({ webId, stored, sessionUri: subject.sessionUri })
|
|
1043
|
-
: options.request;
|
|
1044
|
-
const grants = await store.listGrants();
|
|
1045
|
-
const requestContext = buildGenericGrantRequestContext({ subject, request });
|
|
1046
|
-
return resolveSemanticGrantDecision({
|
|
1047
|
-
runtime: activeRuntime,
|
|
1048
|
-
grants,
|
|
1049
|
-
request: {
|
|
1050
|
-
...request,
|
|
1051
|
-
session: subject.sessionUri,
|
|
1052
|
-
target: requestContext.target,
|
|
1053
|
-
},
|
|
1054
|
-
requestContext,
|
|
1055
|
-
});
|
|
1056
|
-
});
|
|
1057
|
-
if (delegated) {
|
|
1058
|
-
return delegated;
|
|
1059
|
-
}
|
|
1060
|
-
const summary = await createRemoteApproval({
|
|
1061
|
-
subject: options.subject,
|
|
1062
|
-
request: options.request,
|
|
1063
|
-
runtime: activeRuntime,
|
|
1064
|
-
});
|
|
1065
|
-
return waitForRemoteWatchApproval({
|
|
1066
|
-
approvalId: summary.id,
|
|
1067
|
-
approvalUri: summary.approvalUri,
|
|
1068
463
|
pollMs: options.pollMs,
|
|
1069
464
|
signal: options.signal,
|
|
1070
465
|
runtime: activeRuntime,
|
|
@@ -1074,9 +469,12 @@ export async function listRemoteWatchApprovals(options = {}) {
|
|
|
1074
469
|
const activeRuntime = options.runtime ?? await createDefaultRuntime();
|
|
1075
470
|
const requestedStatus = options.status ?? 'pending';
|
|
1076
471
|
return withRemoteApprovalStore(activeRuntime, async ({ store, webId }) => {
|
|
1077
|
-
const approvals = await
|
|
472
|
+
const [approvals, audits] = await Promise.all([
|
|
473
|
+
store.listApprovals(),
|
|
474
|
+
store.listAudits(),
|
|
475
|
+
]);
|
|
1078
476
|
return approvals
|
|
1079
|
-
.map((row) => normalizeApprovalSummary(row))
|
|
477
|
+
.map((row) => normalizeApprovalSummary(row, audits))
|
|
1080
478
|
.filter((summary) => !summary.assignedTo || summary.assignedTo === webId)
|
|
1081
479
|
.filter((summary) => requestedStatus === 'all' || summary.status === requestedStatus)
|
|
1082
480
|
.sort((left, right) => right.createdAt.localeCompare(left.createdAt));
|
|
@@ -1085,124 +483,96 @@ export async function listRemoteWatchApprovals(options = {}) {
|
|
|
1085
483
|
export async function resolveRemoteWatchApproval(options) {
|
|
1086
484
|
const activeRuntime = options.runtime ?? await createDefaultRuntime();
|
|
1087
485
|
return withRemoteApprovalStore(activeRuntime, async ({ store, webId }) => {
|
|
1088
|
-
const
|
|
1089
|
-
|
|
1090
|
-
approvalUri: options.approvalUri,
|
|
1091
|
-
});
|
|
486
|
+
const approvals = await store.listApprovals();
|
|
487
|
+
const row = approvals.find((entry) => entry.id === options.approvalId);
|
|
1092
488
|
if (!row) {
|
|
1093
489
|
throw new Error(`Remote approval not found: ${options.approvalId}`);
|
|
1094
490
|
}
|
|
1095
491
|
if (row.status !== 'pending') {
|
|
1096
|
-
|
|
492
|
+
const audits = await store.listAudits();
|
|
493
|
+
return normalizeApprovalSummary(row, audits);
|
|
1097
494
|
}
|
|
1098
495
|
const now = activeRuntime.now();
|
|
1099
|
-
const
|
|
1100
|
-
const approvalUri = buildApprovalUriForDate(row.session, row.id, approvalCreatedAt);
|
|
496
|
+
const approvalUri = buildApprovalUri(row.session, row.id);
|
|
1101
497
|
const nextStatus = options.decision === 'accept' || options.decision === 'accept_for_session'
|
|
1102
498
|
? 'approved'
|
|
1103
499
|
: 'rejected';
|
|
1104
|
-
const decisionRole = options.decisionRole ?? 'human';
|
|
1105
500
|
await store.updateApproval(row.id, {
|
|
1106
501
|
status: nextStatus,
|
|
1107
502
|
decisionBy: webId,
|
|
1108
|
-
decisionRole,
|
|
503
|
+
decisionRole: 'human',
|
|
1109
504
|
onBehalfOf: webId,
|
|
1110
505
|
reason: encodeDecisionReason(options.decision, options.note),
|
|
1111
506
|
resolvedAt: now,
|
|
1112
507
|
});
|
|
1113
|
-
await
|
|
508
|
+
await store.insertAudit({
|
|
1114
509
|
id: crypto.randomUUID(),
|
|
1115
510
|
action: nextStatus === 'approved' ? 'approval_approved' : 'approval_rejected',
|
|
1116
511
|
actor: webId,
|
|
1117
|
-
actorRole:
|
|
512
|
+
actorRole: 'human',
|
|
1118
513
|
onBehalfOf: webId,
|
|
1119
514
|
session: row.session,
|
|
1120
|
-
entry: approvalUri,
|
|
1121
515
|
toolCallId: row.toolCallId,
|
|
1122
|
-
toolName: row.toolName,
|
|
1123
516
|
approval: approvalUri,
|
|
517
|
+
context: JSON.stringify({
|
|
518
|
+
decision: options.decision,
|
|
519
|
+
...(options.note?.trim() ? { note: options.note.trim() } : {}),
|
|
520
|
+
}),
|
|
1124
521
|
policyVersion: REMOTE_APPROVAL_POLICY_VERSION,
|
|
1125
522
|
createdAt: now,
|
|
1126
|
-
})
|
|
523
|
+
});
|
|
1127
524
|
if (options.decision === 'accept_for_session') {
|
|
1128
525
|
const grantId = crypto.randomUUID();
|
|
1129
|
-
const body = grantWikiBodyFromApproval(row, options.grantWikiBody);
|
|
1130
526
|
await store.insertGrant({
|
|
1131
527
|
id: grantId,
|
|
1132
528
|
target: row.target,
|
|
1133
529
|
action: row.action,
|
|
1134
|
-
title: grantWikiTitleFromApproval(row, options.grantWikiTitle),
|
|
1135
|
-
summary: grantWikiSummaryFromApproval(row, options.grantWikiSummary),
|
|
1136
|
-
body,
|
|
1137
|
-
schema: buildGrantSchemaUri(webId),
|
|
1138
|
-
pageKind: 'autonomy-grant',
|
|
1139
|
-
wikiStatus: 'active',
|
|
1140
|
-
tags: grantWikiTagsFromApproval(row, options.grantWikiTags),
|
|
1141
|
-
source: 'approval',
|
|
1142
|
-
sourceHash: grantSourceHash(row),
|
|
1143
|
-
compiledAt: now,
|
|
1144
|
-
compiledFrom: [approvalUri],
|
|
1145
|
-
related: [row.session],
|
|
1146
530
|
effect: 'allow',
|
|
1147
531
|
riskCeiling: row.risk,
|
|
1148
|
-
policy: grantIndexTextFromWikiBody(body),
|
|
1149
|
-
context: grantContextFromApproval(row),
|
|
1150
532
|
decisionBy: webId,
|
|
1151
|
-
decisionRole,
|
|
533
|
+
decisionRole: 'human',
|
|
1152
534
|
onBehalfOf: webId,
|
|
1153
535
|
createdAt: now,
|
|
1154
536
|
});
|
|
1155
|
-
await
|
|
537
|
+
await store.insertInboxNotification({
|
|
1156
538
|
id: crypto.randomUUID(),
|
|
1157
539
|
actor: webId,
|
|
1158
540
|
object: buildGrantUri(row.session, grantId),
|
|
1159
541
|
createdAt: now,
|
|
1160
|
-
}));
|
|
542
|
+
}).catch(() => undefined);
|
|
1161
543
|
}
|
|
1162
|
-
await
|
|
544
|
+
await store.insertInboxNotification({
|
|
1163
545
|
id: crypto.randomUUID(),
|
|
1164
546
|
actor: webId,
|
|
1165
547
|
object: approvalUri,
|
|
1166
548
|
createdAt: now,
|
|
1167
|
-
}));
|
|
549
|
+
}).catch(() => undefined);
|
|
1168
550
|
const nextRow = {
|
|
1169
551
|
...row,
|
|
1170
552
|
status: nextStatus,
|
|
1171
553
|
decisionBy: webId,
|
|
1172
|
-
decisionRole,
|
|
554
|
+
decisionRole: 'human',
|
|
1173
555
|
onBehalfOf: webId,
|
|
1174
556
|
reason: encodeDecisionReason(options.decision, options.note),
|
|
1175
557
|
resolvedAt: now,
|
|
1176
558
|
};
|
|
1177
|
-
|
|
559
|
+
const audits = await store.listAudits();
|
|
560
|
+
return normalizeApprovalSummary(nextRow, audits);
|
|
1178
561
|
});
|
|
1179
562
|
}
|
|
1180
|
-
async function readRemoteApprovalRow(store, options) {
|
|
1181
|
-
if (store.findApproval) {
|
|
1182
|
-
const row = await store.findApproval(options.approvalId, {
|
|
1183
|
-
resourceUri: options.approvalUri,
|
|
1184
|
-
});
|
|
1185
|
-
if (row || options.approvalUri) {
|
|
1186
|
-
return row;
|
|
1187
|
-
}
|
|
1188
|
-
}
|
|
1189
|
-
const approvals = await store.listApprovals();
|
|
1190
|
-
return approvals.find((entry) => entry.id === options.approvalId) ?? null;
|
|
1191
|
-
}
|
|
1192
563
|
export const __podApprovalInternal = {
|
|
1193
564
|
createAbortError,
|
|
1194
|
-
createDefaultRuntime,
|
|
1195
565
|
buildActionUri,
|
|
566
|
+
buildRequestAuditContext,
|
|
1196
567
|
buildRisk,
|
|
1197
568
|
buildToolName,
|
|
1198
|
-
createNativeRemoteApprovalStore,
|
|
1199
569
|
extractToolCallId,
|
|
1200
570
|
decisionFromApprovalRow,
|
|
1201
571
|
encodeDecisionReason,
|
|
1202
572
|
formatSummaryHeadline,
|
|
1203
|
-
readRemoteApprovalRow,
|
|
1204
573
|
isRemoteApprovalAbortError,
|
|
1205
574
|
normalizeApprovalSummary,
|
|
1206
575
|
parseDecisionReason,
|
|
576
|
+
parseRequestAuditContext,
|
|
1207
577
|
};
|
|
1208
578
|
//# sourceMappingURL=pod-approval.js.map
|