@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.
Files changed (49) hide show
  1. package/dist/index.js +225 -51
  2. package/dist/index.js.map +1 -1
  3. package/dist/lib/ai-command.js +2 -2
  4. package/dist/lib/ai-command.js.map +1 -1
  5. package/dist/lib/chat-api.js +146 -15
  6. package/dist/lib/chat-api.js.map +1 -1
  7. package/dist/lib/credentials-store.js +6 -0
  8. package/dist/lib/credentials-store.js.map +1 -1
  9. package/dist/lib/default-model.js +1 -0
  10. package/dist/lib/default-model.js.map +1 -1
  11. package/dist/lib/login-command.js +16 -1
  12. package/dist/lib/login-command.js.map +1 -1
  13. package/dist/lib/models.js +2 -2
  14. package/dist/lib/models.js.map +1 -1
  15. package/dist/lib/node-warning-filter.js +34 -0
  16. package/dist/lib/node-warning-filter.js.map +1 -0
  17. package/dist/lib/oidc-auth.js +78 -13
  18. package/dist/lib/oidc-auth.js.map +1 -1
  19. package/dist/lib/pi-adapter/auth.js +12 -5
  20. package/dist/lib/pi-adapter/auth.js.map +1 -1
  21. package/dist/lib/pi-adapter/branding.js +553 -75
  22. package/dist/lib/pi-adapter/branding.js.map +1 -1
  23. package/dist/lib/pi-adapter/interactive.js +117 -4
  24. package/dist/lib/pi-adapter/interactive.js.map +1 -1
  25. package/dist/lib/pi-adapter/pod-approval.js +121 -0
  26. package/dist/lib/pi-adapter/pod-approval.js.map +1 -0
  27. package/dist/lib/pi-adapter/pod-mirror-mapping.js +189 -0
  28. package/dist/lib/pi-adapter/pod-mirror-mapping.js.map +1 -0
  29. package/dist/lib/pi-adapter/pod-mirror.js +390 -0
  30. package/dist/lib/pi-adapter/pod-mirror.js.map +1 -0
  31. package/dist/lib/pi-adapter/pod-native.js +422 -0
  32. package/dist/lib/pi-adapter/pod-native.js.map +1 -0
  33. package/dist/lib/pi-adapter/runtime.js +116 -21
  34. package/dist/lib/pi-adapter/runtime.js.map +1 -1
  35. package/dist/lib/pi-adapter/session.js +613 -0
  36. package/dist/lib/pi-adapter/session.js.map +1 -0
  37. package/dist/lib/pi-adapter/stream.js +74 -1
  38. package/dist/lib/pi-adapter/stream.js.map +1 -1
  39. package/dist/lib/profile-identity.js +5 -5
  40. package/dist/lib/profile-identity.js.map +1 -1
  41. package/dist/lib/watch/pod-ai.js +2 -1
  42. package/dist/lib/watch/pod-ai.js.map +1 -1
  43. package/dist/lib/watch/pod-approval.js +372 -98
  44. package/dist/lib/watch/pod-approval.js.map +1 -1
  45. package/dist/lib/watch/pod-persistence.js +2 -1
  46. package/dist/lib/watch/pod-persistence.js.map +1 -1
  47. package/package.json +3 -7
  48. package/dist/generated/version.js +0 -3
  49. 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 || !backend || !sessionId) {
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 client credentials auth in `~/.linx`.';
255
+ return 'LinX remote approval requires a valid LinX login in `~/.linx`.';
253
256
  }
254
257
  async function createDefaultRuntime() {
255
- const [credentialsStore, solidAuth, models] = await Promise.all([
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('../models.js', import.meta.url).href),
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
- 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);
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 (!clientCredentials) {
333
- throw new Error(unsupportedRemoteApprovalAuthMessage());
334
- }
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.');
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
- try {
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(session),
344
- webId,
322
+ store: runtime.createStore(stored.webId, fetcher),
323
+ webId: stored.webId,
345
324
  stored,
346
325
  });
347
326
  }
348
- finally {
349
- await session.logout().catch(() => undefined);
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 withRemoteApprovalStore(activeRuntime, async ({ store, webId }) => {
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 = buildThreadUri(webId, options.record.id);
587
+ const sessionUri = subject.sessionUri;
358
588
  const approvalUri = buildApprovalUri(webId, approvalId);
359
- const toolCallId = extractToolCallId(options.request);
360
- const requestContext = buildRequestAuditContext(options.record, options.request);
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: buildToolName(options.request),
366
- target: sessionUri,
367
- action: buildActionUri(options.request),
368
- risk: buildRisk(options.request),
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: webId,
371
- policyVersion: REMOTE_APPROVAL_POLICY_VERSION,
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: buildAgentUri(webId),
617
+ actor: subject.actorUri,
378
618
  actorRole: 'secretary',
379
- onBehalfOf: webId,
619
+ onBehalfOf,
380
620
  session: sessionUri,
381
- toolCallId,
621
+ toolCallId: request.toolCallId,
382
622
  approval: approvalUri,
383
623
  context: JSON.stringify(requestContext),
384
- policyVersion: REMOTE_APPROVAL_POLICY_VERSION,
624
+ policyVersion,
385
625
  createdAt: now,
386
626
  });
387
627
  await store.insertInboxNotification({
388
628
  id: crypto.randomUUID(),
389
- actor: buildAgentUri(webId),
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: buildToolName(options.request),
398
- target: sessionUri,
399
- action: buildActionUri(options.request),
400
- risk: buildRisk(options.request),
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: webId,
403
- policyVersion: REMOTE_APPROVAL_POLICY_VERSION,
642
+ assignedTo,
643
+ policyVersion,
404
644
  createdAt: now,
405
645
  }, [{
406
646
  id: crypto.randomUUID(),
407
647
  action: 'approval_requested',
408
- actor: buildAgentUri(webId),
648
+ actor: subject.actorUri,
409
649
  actorRole: 'secretary',
410
- onBehalfOf: webId,
650
+ onBehalfOf,
411
651
  session: sessionUri,
412
- toolCallId,
652
+ toolCallId: request.toolCallId,
413
653
  approval: approvalUri,
414
654
  context: JSON.stringify(requestContext),
415
- policyVersion: REMOTE_APPROVAL_POLICY_VERSION,
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,