@undefineds.co/linx 0.2.3 → 0.2.12
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/index.js +225 -51
- package/dist/index.js.map +1 -1
- package/dist/lib/ai-command.js +2 -2
- package/dist/lib/ai-command.js.map +1 -1
- package/dist/lib/chat-api.js +146 -15
- package/dist/lib/chat-api.js.map +1 -1
- package/dist/lib/credentials-store.js +6 -0
- package/dist/lib/credentials-store.js.map +1 -1
- package/dist/lib/default-model.js +1 -0
- package/dist/lib/default-model.js.map +1 -1
- package/dist/lib/login-command.js +16 -1
- package/dist/lib/login-command.js.map +1 -1
- package/dist/lib/models.js +2 -2
- package/dist/lib/models.js.map +1 -1
- package/dist/lib/node-warning-filter.js +34 -0
- package/dist/lib/node-warning-filter.js.map +1 -0
- package/dist/lib/oidc-auth.js +78 -13
- package/dist/lib/oidc-auth.js.map +1 -1
- package/dist/lib/pi-adapter/auth.js +12 -5
- package/dist/lib/pi-adapter/auth.js.map +1 -1
- package/dist/lib/pi-adapter/branding.js +553 -75
- package/dist/lib/pi-adapter/branding.js.map +1 -1
- package/dist/lib/pi-adapter/interactive.js +117 -4
- package/dist/lib/pi-adapter/interactive.js.map +1 -1
- package/dist/lib/pi-adapter/pod-approval.js +121 -0
- package/dist/lib/pi-adapter/pod-approval.js.map +1 -0
- package/dist/lib/pi-adapter/pod-mirror-mapping.js +189 -0
- package/dist/lib/pi-adapter/pod-mirror-mapping.js.map +1 -0
- package/dist/lib/pi-adapter/pod-mirror.js +390 -0
- package/dist/lib/pi-adapter/pod-mirror.js.map +1 -0
- package/dist/lib/pi-adapter/pod-native.js +422 -0
- package/dist/lib/pi-adapter/pod-native.js.map +1 -0
- package/dist/lib/pi-adapter/runtime.js +116 -21
- package/dist/lib/pi-adapter/runtime.js.map +1 -1
- package/dist/lib/pi-adapter/session.js +613 -0
- package/dist/lib/pi-adapter/session.js.map +1 -0
- package/dist/lib/pi-adapter/stream.js +74 -1
- package/dist/lib/pi-adapter/stream.js.map +1 -1
- package/dist/lib/profile-identity.js +5 -5
- package/dist/lib/profile-identity.js.map +1 -1
- package/dist/lib/watch/pod-ai.js +2 -1
- package/dist/lib/watch/pod-ai.js.map +1 -1
- package/dist/lib/watch/pod-approval.js +372 -98
- package/dist/lib/watch/pod-approval.js.map +1 -1
- package/dist/lib/watch/pod-persistence.js +2 -1
- package/dist/lib/watch/pod-persistence.js.map +1 -1
- package/package.json +3 -7
- package/dist/generated/version.js +0 -3
- package/dist/generated/version.js.map +0 -1
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { setTimeout as delay } from 'node:timers/promises';
|
|
2
|
+
import { getClientCredentialId, getClientCredentialKey } from '../credentials-store.js';
|
|
3
|
+
import { AS_ACTOR, AS_ANNOUNCE, AS_OBJECT, DCT_CREATED, ODRL_ACTION, ODRL_POLICY, ODRL_TARGET, RDF_TYPE, UDFS_ACTION, UDFS_ACTOR, UDFS_ACTOR_ROLE, UDFS_APPROVAL_REQUEST, UDFS_ASSIGNED_TO, UDFS_AUDIT_ENTRY, UDFS_AUTONOMY_GRANT, UDFS_CONTEXT, UDFS_DECISION_BY, UDFS_DECISION_ROLE, UDFS_EFFECT, UDFS_ON_BEHALF_OF, UDFS_POLICY_VERSION, UDFS_REASON, UDFS_RESOLVED_AT, UDFS_REVOKED_AT, UDFS_RISK, UDFS_RISK_CEILING, UDFS_SESSION, UDFS_STATUS, UDFS_TOOL_CALL_ID, UDFS_TOOL_NAME, buildApprovalResourceUrl, buildAuditResourceUrl, buildGrantResourceUrl, buildInboxResourceUrl, firstIri, firstLiteral, iri, listTurtleResources, literal, parseManagedTurtleBlocks, readTurtleResource, subjectIdFromResourceUrl, upsertManagedTurtleBlock, } from '../pi-adapter/pod-native.js';
|
|
2
4
|
const WATCH_CHAT_ID = 'linx-watch';
|
|
3
5
|
const WATCH_AGENT_ID = 'linx-watch-assistant';
|
|
4
6
|
const REMOTE_APPROVAL_POLICY_VERSION = 'linx-watch-remote-approval/v1';
|
|
@@ -162,16 +164,17 @@ function parseRequestAuditContext(value) {
|
|
|
162
164
|
}
|
|
163
165
|
const kind = normalizeString(parsed.kind);
|
|
164
166
|
const message = normalizeString(parsed.message);
|
|
165
|
-
const backend = normalizeString(parsed.backend);
|
|
166
167
|
const sessionId = normalizeString(parsed.sessionId);
|
|
167
|
-
if (!kind || !message || !
|
|
168
|
+
if (!kind || !message || !sessionId) {
|
|
168
169
|
return null;
|
|
169
170
|
}
|
|
170
171
|
return {
|
|
171
172
|
kind: kind,
|
|
172
173
|
message,
|
|
173
|
-
backend: backend,
|
|
174
174
|
sessionId,
|
|
175
|
+
...(normalizeString(parsed.backend) ? { backend: normalizeString(parsed.backend) } : {}),
|
|
176
|
+
...(normalizeString(parsed.runtime) ? { runtime: normalizeString(parsed.runtime) } : {}),
|
|
177
|
+
...(normalizeString(parsed.toolName) ? { toolName: normalizeString(parsed.toolName) } : {}),
|
|
175
178
|
...(normalizeString(parsed.command) ? { command: normalizeString(parsed.command) } : {}),
|
|
176
179
|
...(normalizeString(parsed.cwd) ? { cwd: normalizeString(parsed.cwd) } : {}),
|
|
177
180
|
};
|
|
@@ -249,71 +252,36 @@ function missingRemoteApprovalCredentialsMessage() {
|
|
|
249
252
|
return 'LinX remote approval requires `linx login` first.';
|
|
250
253
|
}
|
|
251
254
|
function unsupportedRemoteApprovalAuthMessage() {
|
|
252
|
-
return 'LinX remote approval requires
|
|
255
|
+
return 'LinX remote approval requires a valid LinX login in `~/.linx`.';
|
|
253
256
|
}
|
|
254
257
|
async function createDefaultRuntime() {
|
|
255
|
-
const [credentialsStore, solidAuth,
|
|
258
|
+
const [credentialsStore, solidAuth, oidcAuth] = await Promise.all([
|
|
256
259
|
dynamicImport(new URL('../credentials-store.js', import.meta.url).href),
|
|
257
260
|
dynamicImport(new URL('../solid-auth.js', import.meta.url).href),
|
|
258
|
-
dynamicImport(new URL('../
|
|
261
|
+
dynamicImport(new URL('../oidc-auth.js', import.meta.url).href),
|
|
259
262
|
]);
|
|
260
263
|
return {
|
|
261
264
|
loadCredentials: credentialsStore.loadCredentials,
|
|
262
265
|
getClientCredentials: credentialsStore.getClientCredentials,
|
|
266
|
+
getOidcAccessToken: oidcAuth.getOidcAccessToken,
|
|
263
267
|
authenticate: solidAuth.authenticate,
|
|
264
|
-
|
|
265
|
-
const
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
models.grantTable,
|
|
280
|
-
models.inboxNotificationTable,
|
|
281
|
-
]).catch(() => undefined);
|
|
268
|
+
authenticatedFetch(url, token, init) {
|
|
269
|
+
const headers = new Headers(init?.headers);
|
|
270
|
+
headers.set('Authorization', `Bearer ${token}`);
|
|
271
|
+
return fetch(url, { ...init, headers });
|
|
272
|
+
},
|
|
273
|
+
createStore(sessionOrWebId, fetcher) {
|
|
274
|
+
const webId = typeof sessionOrWebId === 'string' ? sessionOrWebId : sessionOrWebId.info.webId;
|
|
275
|
+
if (!webId) {
|
|
276
|
+
throw new Error('Remote approval authentication succeeded without a WebID.');
|
|
277
|
+
}
|
|
278
|
+
const activeFetcher = fetcher ?? (typeof sessionOrWebId === 'string'
|
|
279
|
+
? undefined
|
|
280
|
+
: ((url, init) => sessionOrWebId.fetch(url, init)));
|
|
281
|
+
if (!activeFetcher) {
|
|
282
|
+
throw new Error('Remote approval authentication succeeded without a Pod fetcher.');
|
|
282
283
|
}
|
|
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
|
-
};
|
|
284
|
+
return createNativeRemoteApprovalStore(webId, activeFetcher);
|
|
317
285
|
},
|
|
318
286
|
sleep(ms) {
|
|
319
287
|
return delay(ms);
|
|
@@ -329,90 +297,362 @@ async function withRemoteApprovalStore(runtime, fn) {
|
|
|
329
297
|
throw new Error(missingRemoteApprovalCredentialsMessage());
|
|
330
298
|
}
|
|
331
299
|
const clientCredentials = runtime.getClientCredentials(stored);
|
|
332
|
-
if (
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
300
|
+
if (clientCredentials) {
|
|
301
|
+
const { session } = await runtime.authenticate(getClientCredentialId(clientCredentials), getClientCredentialKey(clientCredentials), stored.url);
|
|
302
|
+
const webId = session.info.webId ?? stored.webId;
|
|
303
|
+
if (!webId) {
|
|
304
|
+
await session.logout().catch(() => undefined);
|
|
305
|
+
throw new Error('Remote approval authentication succeeded without a WebID.');
|
|
306
|
+
}
|
|
307
|
+
try {
|
|
308
|
+
return await fn({
|
|
309
|
+
store: runtime.createStore(session),
|
|
310
|
+
webId,
|
|
311
|
+
stored,
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
finally {
|
|
315
|
+
await session.logout().catch(() => undefined);
|
|
316
|
+
}
|
|
340
317
|
}
|
|
341
|
-
|
|
318
|
+
const accessToken = await runtime.getOidcAccessToken?.(stored);
|
|
319
|
+
if (accessToken && stored.webId && runtime.authenticatedFetch) {
|
|
320
|
+
const fetcher = (url, init) => runtime.authenticatedFetch(url, accessToken, init);
|
|
342
321
|
return await fn({
|
|
343
|
-
store: runtime.createStore(
|
|
344
|
-
webId,
|
|
322
|
+
store: runtime.createStore(stored.webId, fetcher),
|
|
323
|
+
webId: stored.webId,
|
|
345
324
|
stored,
|
|
346
325
|
});
|
|
347
326
|
}
|
|
348
|
-
|
|
349
|
-
|
|
327
|
+
throw new Error(unsupportedRemoteApprovalAuthMessage());
|
|
328
|
+
}
|
|
329
|
+
function createNativeRemoteApprovalStore(webId, fetcher) {
|
|
330
|
+
return {
|
|
331
|
+
listApprovals: () => listApprovalRows(webId, fetcher),
|
|
332
|
+
insertApproval: (row) => writeApprovalRow(webId, fetcher, row),
|
|
333
|
+
async updateApproval(id, patch) {
|
|
334
|
+
const existing = (await listApprovalRows(webId, fetcher)).find((row) => row.id === id);
|
|
335
|
+
if (!existing) {
|
|
336
|
+
throw new Error(`Remote approval not found: ${id}`);
|
|
337
|
+
}
|
|
338
|
+
await writeApprovalRow(webId, fetcher, { ...existing, ...patch });
|
|
339
|
+
},
|
|
340
|
+
listAudits: () => listAuditRows(webId, fetcher),
|
|
341
|
+
insertAudit: (row) => writeAuditRow(webId, fetcher, row),
|
|
342
|
+
listGrants: () => listGrantRows(webId, fetcher),
|
|
343
|
+
insertGrant: (row) => writeGrantRow(webId, fetcher, row),
|
|
344
|
+
insertInboxNotification: (row) => writeInboxNotificationRow(webId, fetcher, row),
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
async function listApprovalRows(webId, fetcher) {
|
|
348
|
+
const urls = await listTurtleResources(fetcher, `${getPodBaseUrl(webId)}/.data/approvals/`);
|
|
349
|
+
const rows = [];
|
|
350
|
+
for (const url of urls.filter((entry) => entry.endsWith('.ttl'))) {
|
|
351
|
+
const turtle = await readTurtleResource(fetcher, url).catch(() => null);
|
|
352
|
+
if (!turtle)
|
|
353
|
+
continue;
|
|
354
|
+
const predicates = parseManagedTurtleBlocks(turtle, url).get(url);
|
|
355
|
+
if (!predicates)
|
|
356
|
+
continue;
|
|
357
|
+
const row = approvalRowFromPredicates(url, predicates);
|
|
358
|
+
if (row)
|
|
359
|
+
rows.push(row);
|
|
360
|
+
}
|
|
361
|
+
return rows;
|
|
362
|
+
}
|
|
363
|
+
async function writeApprovalRow(webId, fetcher, row) {
|
|
364
|
+
const url = buildApprovalResourceUrl(webId, row.id);
|
|
365
|
+
await upsertManagedTurtleBlock(fetcher, url, {
|
|
366
|
+
subject: url,
|
|
367
|
+
triples: [
|
|
368
|
+
{ predicate: RDF_TYPE, object: iri(UDFS_APPROVAL_REQUEST) },
|
|
369
|
+
{ predicate: UDFS_SESSION, object: iri(row.session) },
|
|
370
|
+
{ predicate: UDFS_TOOL_CALL_ID, object: literal(row.toolCallId) },
|
|
371
|
+
{ predicate: UDFS_TOOL_NAME, object: literal(row.toolName) },
|
|
372
|
+
{ predicate: ODRL_TARGET, object: iri(row.target) },
|
|
373
|
+
{ predicate: ODRL_ACTION, object: iri(row.action) },
|
|
374
|
+
{ predicate: UDFS_RISK, object: literal(row.risk) },
|
|
375
|
+
{ predicate: UDFS_STATUS, object: literal(row.status) },
|
|
376
|
+
...(row.assignedTo ? [{ predicate: UDFS_ASSIGNED_TO, object: iri(row.assignedTo) }] : []),
|
|
377
|
+
...(row.decisionBy ? [{ predicate: UDFS_DECISION_BY, object: iri(row.decisionBy) }] : []),
|
|
378
|
+
...(row.decisionRole ? [{ predicate: UDFS_DECISION_ROLE, object: literal(row.decisionRole) }] : []),
|
|
379
|
+
...(row.onBehalfOf ? [{ predicate: UDFS_ON_BEHALF_OF, object: iri(row.onBehalfOf) }] : []),
|
|
380
|
+
...(row.reason ? [{ predicate: UDFS_REASON, object: literal(row.reason) }] : []),
|
|
381
|
+
...(row.policyVersion ? [{ predicate: UDFS_POLICY_VERSION, object: literal(row.policyVersion) }] : []),
|
|
382
|
+
{ predicate: DCT_CREATED, object: literal(toIsoString(row.createdAt, new Date().toISOString())) },
|
|
383
|
+
...(row.resolvedAt ? [{ predicate: UDFS_RESOLVED_AT, object: literal(toIsoString(row.resolvedAt, new Date().toISOString())) }] : []),
|
|
384
|
+
],
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
async function listAuditRows(webId, fetcher) {
|
|
388
|
+
const urls = await listTurtleResources(fetcher, `${getPodBaseUrl(webId)}/.data/audit/`);
|
|
389
|
+
const rows = [];
|
|
390
|
+
for (const url of urls.filter((entry) => entry.endsWith('.ttl'))) {
|
|
391
|
+
const turtle = await readTurtleResource(fetcher, url).catch(() => null);
|
|
392
|
+
if (!turtle)
|
|
393
|
+
continue;
|
|
394
|
+
const predicates = parseManagedTurtleBlocks(turtle, url).get(url);
|
|
395
|
+
if (!predicates)
|
|
396
|
+
continue;
|
|
397
|
+
const row = auditRowFromPredicates(url, predicates);
|
|
398
|
+
if (row)
|
|
399
|
+
rows.push(row);
|
|
400
|
+
}
|
|
401
|
+
return rows;
|
|
402
|
+
}
|
|
403
|
+
async function writeAuditRow(webId, fetcher, row) {
|
|
404
|
+
const url = buildAuditResourceUrl(webId, row.id);
|
|
405
|
+
await upsertManagedTurtleBlock(fetcher, url, {
|
|
406
|
+
subject: url,
|
|
407
|
+
triples: [
|
|
408
|
+
{ predicate: RDF_TYPE, object: iri(UDFS_AUDIT_ENTRY) },
|
|
409
|
+
{ predicate: UDFS_ACTION, object: literal(row.action) },
|
|
410
|
+
{ predicate: UDFS_ACTOR, object: iri(row.actor) },
|
|
411
|
+
{ predicate: UDFS_ACTOR_ROLE, object: literal(row.actorRole) },
|
|
412
|
+
...(row.onBehalfOf ? [{ predicate: UDFS_ON_BEHALF_OF, object: iri(row.onBehalfOf) }] : []),
|
|
413
|
+
...(row.session ? [{ predicate: UDFS_SESSION, object: iri(row.session) }] : []),
|
|
414
|
+
...(row.toolCallId ? [{ predicate: UDFS_TOOL_CALL_ID, object: literal(row.toolCallId) }] : []),
|
|
415
|
+
...(row.approval ? [{ predicate: 'https://undefineds.co/ns#approval', object: iri(row.approval) }] : []),
|
|
416
|
+
...(row.context ? [{ predicate: UDFS_CONTEXT, object: literal(row.context) }] : []),
|
|
417
|
+
...(row.policyVersion ? [{ predicate: UDFS_POLICY_VERSION, object: literal(row.policyVersion) }] : []),
|
|
418
|
+
{ predicate: DCT_CREATED, object: literal(toIsoString(row.createdAt, new Date().toISOString())) },
|
|
419
|
+
],
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
async function listGrantRows(webId, fetcher) {
|
|
423
|
+
const urls = await listTurtleResources(fetcher, `${getPodBaseUrl(webId)}/settings/autonomy/grants/`);
|
|
424
|
+
const rows = [];
|
|
425
|
+
for (const url of urls.filter((entry) => entry.endsWith('.ttl'))) {
|
|
426
|
+
const turtle = await readTurtleResource(fetcher, url).catch(() => null);
|
|
427
|
+
if (!turtle)
|
|
428
|
+
continue;
|
|
429
|
+
const predicates = parseManagedTurtleBlocks(turtle, url).get(url);
|
|
430
|
+
if (!predicates)
|
|
431
|
+
continue;
|
|
432
|
+
const row = grantRowFromPredicates(url, predicates);
|
|
433
|
+
if (row)
|
|
434
|
+
rows.push(row);
|
|
435
|
+
}
|
|
436
|
+
return rows;
|
|
437
|
+
}
|
|
438
|
+
async function writeGrantRow(webId, fetcher, row) {
|
|
439
|
+
const id = normalizeString(row.id) ?? crypto.randomUUID();
|
|
440
|
+
const url = buildGrantResourceUrl(webId, id);
|
|
441
|
+
const target = normalizeString(row.target);
|
|
442
|
+
const action = normalizeString(row.action);
|
|
443
|
+
const effect = normalizeString(row.effect);
|
|
444
|
+
const decisionBy = normalizeString(row.decisionBy);
|
|
445
|
+
const decisionRole = normalizeString(row.decisionRole);
|
|
446
|
+
if (!target || !action || !effect || !decisionBy || !decisionRole) {
|
|
447
|
+
throw new Error(`Invalid remote approval grant row: ${id}`);
|
|
448
|
+
}
|
|
449
|
+
await upsertManagedTurtleBlock(fetcher, url, {
|
|
450
|
+
subject: url,
|
|
451
|
+
triples: [
|
|
452
|
+
{ predicate: RDF_TYPE, object: iri(ODRL_POLICY) },
|
|
453
|
+
{ predicate: RDF_TYPE, object: iri(UDFS_AUTONOMY_GRANT) },
|
|
454
|
+
{ predicate: ODRL_TARGET, object: iri(target) },
|
|
455
|
+
{ predicate: ODRL_ACTION, object: iri(action) },
|
|
456
|
+
{ predicate: UDFS_EFFECT, object: literal(effect) },
|
|
457
|
+
...(normalizeString(row.riskCeiling) ? [{ predicate: UDFS_RISK_CEILING, object: literal(normalizeString(row.riskCeiling)) }] : []),
|
|
458
|
+
{ predicate: UDFS_DECISION_BY, object: iri(decisionBy) },
|
|
459
|
+
{ predicate: UDFS_DECISION_ROLE, object: literal(decisionRole) },
|
|
460
|
+
...(normalizeString(row.onBehalfOf) ? [{ predicate: UDFS_ON_BEHALF_OF, object: iri(normalizeString(row.onBehalfOf)) }] : []),
|
|
461
|
+
{ predicate: DCT_CREATED, object: literal(toIsoString(row.createdAt, new Date().toISOString())) },
|
|
462
|
+
...(normalizeString(row.revokedAt) ? [{ predicate: UDFS_REVOKED_AT, object: literal(normalizeString(row.revokedAt)) }] : []),
|
|
463
|
+
],
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
async function writeInboxNotificationRow(webId, fetcher, row) {
|
|
467
|
+
const url = buildInboxResourceUrl(webId, row.id);
|
|
468
|
+
await upsertManagedTurtleBlock(fetcher, url, {
|
|
469
|
+
subject: url,
|
|
470
|
+
triples: [
|
|
471
|
+
{ predicate: RDF_TYPE, object: iri(AS_ANNOUNCE) },
|
|
472
|
+
...(row.actor ? [{ predicate: AS_ACTOR, object: iri(row.actor) }] : []),
|
|
473
|
+
{ predicate: AS_OBJECT, object: iri(row.object) },
|
|
474
|
+
{ predicate: DCT_CREATED, object: literal(toIsoString(row.createdAt, new Date().toISOString())) },
|
|
475
|
+
],
|
|
476
|
+
});
|
|
477
|
+
}
|
|
478
|
+
function approvalRowFromPredicates(url, predicates) {
|
|
479
|
+
const session = firstIri(predicates, UDFS_SESSION);
|
|
480
|
+
const toolCallId = firstLiteral(predicates, UDFS_TOOL_CALL_ID);
|
|
481
|
+
const toolName = firstLiteral(predicates, UDFS_TOOL_NAME);
|
|
482
|
+
const target = firstIri(predicates, ODRL_TARGET);
|
|
483
|
+
const action = firstIri(predicates, ODRL_ACTION);
|
|
484
|
+
const risk = firstLiteral(predicates, UDFS_RISK);
|
|
485
|
+
const status = firstLiteral(predicates, UDFS_STATUS);
|
|
486
|
+
const createdAt = firstLiteral(predicates, DCT_CREATED);
|
|
487
|
+
if (!session || !toolCallId || !toolName || !target || !action || !risk || !status || !createdAt) {
|
|
488
|
+
return null;
|
|
489
|
+
}
|
|
490
|
+
return {
|
|
491
|
+
id: subjectIdFromResourceUrl(url),
|
|
492
|
+
session,
|
|
493
|
+
toolCallId,
|
|
494
|
+
toolName,
|
|
495
|
+
target,
|
|
496
|
+
action,
|
|
497
|
+
risk,
|
|
498
|
+
status,
|
|
499
|
+
assignedTo: firstIri(predicates, UDFS_ASSIGNED_TO),
|
|
500
|
+
decisionBy: firstIri(predicates, UDFS_DECISION_BY),
|
|
501
|
+
decisionRole: firstLiteral(predicates, UDFS_DECISION_ROLE),
|
|
502
|
+
onBehalfOf: firstIri(predicates, UDFS_ON_BEHALF_OF),
|
|
503
|
+
reason: firstLiteral(predicates, UDFS_REASON),
|
|
504
|
+
policyVersion: firstLiteral(predicates, UDFS_POLICY_VERSION),
|
|
505
|
+
createdAt,
|
|
506
|
+
resolvedAt: firstLiteral(predicates, UDFS_RESOLVED_AT),
|
|
507
|
+
};
|
|
508
|
+
}
|
|
509
|
+
function auditRowFromPredicates(url, predicates) {
|
|
510
|
+
const action = firstLiteral(predicates, UDFS_ACTION);
|
|
511
|
+
const actor = firstIri(predicates, UDFS_ACTOR);
|
|
512
|
+
const actorRole = firstLiteral(predicates, UDFS_ACTOR_ROLE);
|
|
513
|
+
const createdAt = firstLiteral(predicates, DCT_CREATED);
|
|
514
|
+
if (!action || !actor || !actorRole || !createdAt) {
|
|
515
|
+
return null;
|
|
516
|
+
}
|
|
517
|
+
return {
|
|
518
|
+
id: subjectIdFromResourceUrl(url),
|
|
519
|
+
action,
|
|
520
|
+
actor,
|
|
521
|
+
actorRole,
|
|
522
|
+
onBehalfOf: firstIri(predicates, UDFS_ON_BEHALF_OF),
|
|
523
|
+
session: firstIri(predicates, UDFS_SESSION),
|
|
524
|
+
toolCallId: firstLiteral(predicates, UDFS_TOOL_CALL_ID),
|
|
525
|
+
approval: firstIri(predicates, 'https://undefineds.co/ns#approval'),
|
|
526
|
+
context: firstLiteral(predicates, UDFS_CONTEXT),
|
|
527
|
+
policyVersion: firstLiteral(predicates, UDFS_POLICY_VERSION),
|
|
528
|
+
createdAt,
|
|
529
|
+
};
|
|
530
|
+
}
|
|
531
|
+
function grantRowFromPredicates(url, predicates) {
|
|
532
|
+
const target = firstIri(predicates, ODRL_TARGET);
|
|
533
|
+
const action = firstIri(predicates, ODRL_ACTION);
|
|
534
|
+
const effect = firstLiteral(predicates, UDFS_EFFECT);
|
|
535
|
+
const decisionBy = firstIri(predicates, UDFS_DECISION_BY);
|
|
536
|
+
const decisionRole = firstLiteral(predicates, UDFS_DECISION_ROLE);
|
|
537
|
+
const createdAt = firstLiteral(predicates, DCT_CREATED);
|
|
538
|
+
if (!target || !action || !effect || !decisionBy || !decisionRole || !createdAt) {
|
|
539
|
+
return null;
|
|
350
540
|
}
|
|
541
|
+
return {
|
|
542
|
+
id: subjectIdFromResourceUrl(url),
|
|
543
|
+
target,
|
|
544
|
+
action,
|
|
545
|
+
effect,
|
|
546
|
+
riskCeiling: firstLiteral(predicates, UDFS_RISK_CEILING),
|
|
547
|
+
decisionBy,
|
|
548
|
+
decisionRole,
|
|
549
|
+
onBehalfOf: firstIri(predicates, UDFS_ON_BEHALF_OF),
|
|
550
|
+
createdAt,
|
|
551
|
+
revokedAt: firstLiteral(predicates, UDFS_REVOKED_AT),
|
|
552
|
+
};
|
|
351
553
|
}
|
|
352
554
|
export async function createRemoteWatchApproval(options) {
|
|
353
555
|
const activeRuntime = options.runtime ?? await createDefaultRuntime();
|
|
354
|
-
return
|
|
556
|
+
return createRemoteApproval({
|
|
557
|
+
subject: ({ webId }) => ({
|
|
558
|
+
sessionUri: buildThreadUri(webId, options.record.id),
|
|
559
|
+
actorUri: buildAgentUri(webId),
|
|
560
|
+
policyVersion: REMOTE_APPROVAL_POLICY_VERSION,
|
|
561
|
+
}),
|
|
562
|
+
request: ({ sessionUri }) => ({
|
|
563
|
+
kind: options.request.kind,
|
|
564
|
+
message: buildRequestMessage(options.request),
|
|
565
|
+
toolCallId: extractToolCallId(options.request),
|
|
566
|
+
toolName: buildToolName(options.request),
|
|
567
|
+
action: buildActionUri(options.request),
|
|
568
|
+
risk: buildRisk(options.request),
|
|
569
|
+
...(options.request.kind === 'command-approval' && options.request.command ? { command: options.request.command } : {}),
|
|
570
|
+
...(options.request.kind === 'command-approval' && options.request.cwd ? { cwd: options.request.cwd } : {}),
|
|
571
|
+
context: buildRequestAuditContext(options.record, options.request),
|
|
572
|
+
}),
|
|
573
|
+
runtime: activeRuntime,
|
|
574
|
+
});
|
|
575
|
+
}
|
|
576
|
+
export async function createRemoteApproval(options) {
|
|
577
|
+
const activeRuntime = options.runtime ?? await createDefaultRuntime();
|
|
578
|
+
return withRemoteApprovalStore(activeRuntime, async ({ store, webId, stored }) => {
|
|
579
|
+
const subject = typeof options.subject === 'function'
|
|
580
|
+
? options.subject({ webId, stored })
|
|
581
|
+
: options.subject;
|
|
582
|
+
const request = typeof options.request === 'function'
|
|
583
|
+
? options.request({ webId, stored, sessionUri: subject.sessionUri })
|
|
584
|
+
: options.request;
|
|
355
585
|
const approvalId = crypto.randomUUID();
|
|
356
586
|
const now = activeRuntime.now();
|
|
357
|
-
const sessionUri =
|
|
587
|
+
const sessionUri = subject.sessionUri;
|
|
358
588
|
const approvalUri = buildApprovalUri(webId, approvalId);
|
|
359
|
-
const
|
|
360
|
-
const
|
|
589
|
+
const targetUri = subject.targetUri ?? sessionUri;
|
|
590
|
+
const assignedTo = subject.assignedTo ?? webId;
|
|
591
|
+
const onBehalfOf = subject.onBehalfOf ?? webId;
|
|
592
|
+
const policyVersion = subject.policyVersion ?? REMOTE_APPROVAL_POLICY_VERSION;
|
|
593
|
+
const requestContext = request.context ?? {
|
|
594
|
+
kind: request.kind,
|
|
595
|
+
message: request.message,
|
|
596
|
+
sessionId: extractSessionId(sessionUri),
|
|
597
|
+
toolName: request.toolName,
|
|
598
|
+
...(request.command ? { command: request.command } : {}),
|
|
599
|
+
...(request.cwd ? { cwd: request.cwd } : {}),
|
|
600
|
+
};
|
|
361
601
|
await store.insertApproval({
|
|
362
602
|
id: approvalId,
|
|
363
603
|
session: sessionUri,
|
|
364
|
-
toolCallId,
|
|
365
|
-
toolName:
|
|
366
|
-
target:
|
|
367
|
-
action:
|
|
368
|
-
risk:
|
|
604
|
+
toolCallId: request.toolCallId,
|
|
605
|
+
toolName: request.toolName,
|
|
606
|
+
target: targetUri,
|
|
607
|
+
action: request.action,
|
|
608
|
+
risk: request.risk,
|
|
369
609
|
status: 'pending',
|
|
370
|
-
assignedTo
|
|
371
|
-
policyVersion
|
|
610
|
+
assignedTo,
|
|
611
|
+
policyVersion,
|
|
372
612
|
createdAt: now,
|
|
373
613
|
});
|
|
374
614
|
await store.insertAudit({
|
|
375
615
|
id: crypto.randomUUID(),
|
|
376
616
|
action: 'approval_requested',
|
|
377
|
-
actor:
|
|
617
|
+
actor: subject.actorUri,
|
|
378
618
|
actorRole: 'secretary',
|
|
379
|
-
onBehalfOf
|
|
619
|
+
onBehalfOf,
|
|
380
620
|
session: sessionUri,
|
|
381
|
-
toolCallId,
|
|
621
|
+
toolCallId: request.toolCallId,
|
|
382
622
|
approval: approvalUri,
|
|
383
623
|
context: JSON.stringify(requestContext),
|
|
384
|
-
policyVersion
|
|
624
|
+
policyVersion,
|
|
385
625
|
createdAt: now,
|
|
386
626
|
});
|
|
387
627
|
await store.insertInboxNotification({
|
|
388
628
|
id: crypto.randomUUID(),
|
|
389
|
-
actor:
|
|
629
|
+
actor: subject.actorUri,
|
|
390
630
|
object: approvalUri,
|
|
391
631
|
createdAt: now,
|
|
392
632
|
}).catch(() => undefined);
|
|
393
633
|
return normalizeApprovalSummary({
|
|
394
634
|
id: approvalId,
|
|
395
635
|
session: sessionUri,
|
|
396
|
-
toolCallId,
|
|
397
|
-
toolName:
|
|
398
|
-
target:
|
|
399
|
-
action:
|
|
400
|
-
risk:
|
|
636
|
+
toolCallId: request.toolCallId,
|
|
637
|
+
toolName: request.toolName,
|
|
638
|
+
target: targetUri,
|
|
639
|
+
action: request.action,
|
|
640
|
+
risk: request.risk,
|
|
401
641
|
status: 'pending',
|
|
402
|
-
assignedTo
|
|
403
|
-
policyVersion
|
|
642
|
+
assignedTo,
|
|
643
|
+
policyVersion,
|
|
404
644
|
createdAt: now,
|
|
405
645
|
}, [{
|
|
406
646
|
id: crypto.randomUUID(),
|
|
407
647
|
action: 'approval_requested',
|
|
408
|
-
actor:
|
|
648
|
+
actor: subject.actorUri,
|
|
409
649
|
actorRole: 'secretary',
|
|
410
|
-
onBehalfOf
|
|
650
|
+
onBehalfOf,
|
|
411
651
|
session: sessionUri,
|
|
412
|
-
toolCallId,
|
|
652
|
+
toolCallId: request.toolCallId,
|
|
413
653
|
approval: approvalUri,
|
|
414
654
|
context: JSON.stringify(requestContext),
|
|
415
|
-
policyVersion
|
|
655
|
+
policyVersion,
|
|
416
656
|
createdAt: now,
|
|
417
657
|
}]);
|
|
418
658
|
});
|
|
@@ -465,6 +705,38 @@ export async function requestRemoteWatchApproval(options) {
|
|
|
465
705
|
runtime: activeRuntime,
|
|
466
706
|
});
|
|
467
707
|
}
|
|
708
|
+
export async function requestRemoteApproval(options) {
|
|
709
|
+
const activeRuntime = options.runtime ?? await createDefaultRuntime();
|
|
710
|
+
const delegated = await withRemoteApprovalStore(activeRuntime, async ({ store, webId, stored }) => {
|
|
711
|
+
const subject = typeof options.subject === 'function'
|
|
712
|
+
? options.subject({ webId, stored })
|
|
713
|
+
: options.subject;
|
|
714
|
+
const request = typeof options.request === 'function'
|
|
715
|
+
? options.request({ webId, stored, sessionUri: subject.sessionUri })
|
|
716
|
+
: options.request;
|
|
717
|
+
const grants = await store.listGrants();
|
|
718
|
+
const requestTarget = subject.targetUri ?? subject.sessionUri;
|
|
719
|
+
return grants.some((grant) => (grant.effect === 'allow'
|
|
720
|
+
&& grant.action === request.action
|
|
721
|
+
&& grant.target === requestTarget
|
|
722
|
+
&& riskScore(typeof grant.riskCeiling === 'string' ? grant.riskCeiling : undefined) >= riskScore(request.risk)
|
|
723
|
+
&& !grant.revokedAt));
|
|
724
|
+
});
|
|
725
|
+
if (delegated) {
|
|
726
|
+
return 'accept_for_session';
|
|
727
|
+
}
|
|
728
|
+
const summary = await createRemoteApproval({
|
|
729
|
+
subject: options.subject,
|
|
730
|
+
request: options.request,
|
|
731
|
+
runtime: activeRuntime,
|
|
732
|
+
});
|
|
733
|
+
return waitForRemoteWatchApproval({
|
|
734
|
+
approvalId: summary.id,
|
|
735
|
+
pollMs: options.pollMs,
|
|
736
|
+
signal: options.signal,
|
|
737
|
+
runtime: activeRuntime,
|
|
738
|
+
});
|
|
739
|
+
}
|
|
468
740
|
export async function listRemoteWatchApprovals(options = {}) {
|
|
469
741
|
const activeRuntime = options.runtime ?? await createDefaultRuntime();
|
|
470
742
|
const requestedStatus = options.status ?? 'pending';
|
|
@@ -562,10 +834,12 @@ export async function resolveRemoteWatchApproval(options) {
|
|
|
562
834
|
}
|
|
563
835
|
export const __podApprovalInternal = {
|
|
564
836
|
createAbortError,
|
|
837
|
+
createDefaultRuntime,
|
|
565
838
|
buildActionUri,
|
|
566
839
|
buildRequestAuditContext,
|
|
567
840
|
buildRisk,
|
|
568
841
|
buildToolName,
|
|
842
|
+
createNativeRemoteApprovalStore,
|
|
569
843
|
extractToolCallId,
|
|
570
844
|
decisionFromApprovalRow,
|
|
571
845
|
encodeDecisionReason,
|