@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.
Files changed (119) hide show
  1. package/README.md +6 -3
  2. package/dist/generated/version.js +3 -0
  3. package/dist/generated/version.js.map +1 -0
  4. package/dist/index.js +139 -259
  5. package/dist/index.js.map +1 -1
  6. package/dist/lib/account-api.js +1 -1
  7. package/dist/lib/account-api.js.map +1 -1
  8. package/dist/lib/account-session.js +1 -1
  9. package/dist/lib/account-session.js.map +1 -1
  10. package/dist/lib/ai-command.js +105 -56
  11. package/dist/lib/ai-command.js.map +1 -1
  12. package/dist/lib/chat-api.js +19 -177
  13. package/dist/lib/chat-api.js.map +1 -1
  14. package/dist/lib/codex-plugin/bridge.js.map +1 -1
  15. package/dist/lib/codex-plugin/codex-native-proxy.js.map +1 -1
  16. package/dist/lib/codex-plugin/index.js.map +1 -1
  17. package/dist/lib/codex-plugin/runner.js.map +1 -1
  18. package/dist/lib/credentials-store.js +1 -7
  19. package/dist/lib/credentials-store.js.map +1 -1
  20. package/dist/lib/default-model.js +0 -1
  21. package/dist/lib/default-model.js.map +1 -1
  22. package/dist/lib/login-command.js +2 -17
  23. package/dist/lib/login-command.js.map +1 -1
  24. package/dist/lib/models.js +27 -2
  25. package/dist/lib/models.js.map +1 -1
  26. package/dist/lib/oidc-auth.js +13 -78
  27. package/dist/lib/oidc-auth.js.map +1 -1
  28. package/dist/lib/oidc-session-storage.js.map +1 -1
  29. package/dist/lib/pi-adapter/auth.js +5 -12
  30. package/dist/lib/pi-adapter/auth.js.map +1 -1
  31. package/dist/lib/pi-adapter/branding.js +76 -554
  32. package/dist/lib/pi-adapter/branding.js.map +1 -1
  33. package/dist/lib/pi-adapter/index.js.map +1 -1
  34. package/dist/lib/pi-adapter/interactive.js +4 -154
  35. package/dist/lib/pi-adapter/interactive.js.map +1 -1
  36. package/dist/lib/pi-adapter/runtime.js +25 -140
  37. package/dist/lib/pi-adapter/runtime.js.map +1 -1
  38. package/dist/lib/pi-adapter/stream.js +4 -154
  39. package/dist/lib/pi-adapter/stream.js.map +1 -1
  40. package/dist/lib/pi-adapter/theme.js.map +1 -1
  41. package/dist/lib/pod-chat-store.js +1 -1
  42. package/dist/lib/pod-chat-store.js.map +1 -1
  43. package/dist/lib/profile-identity.js +60 -16
  44. package/dist/lib/profile-identity.js.map +1 -1
  45. package/dist/lib/prompt.js.map +1 -1
  46. package/dist/lib/runtime-target.js +1 -1
  47. package/dist/lib/runtime-target.js.map +1 -1
  48. package/dist/lib/solid-auth.js.map +1 -1
  49. package/dist/lib/thread-utils.js.map +1 -1
  50. package/dist/lib/watch/archive.js +1 -1
  51. package/dist/lib/watch/archive.js.map +1 -1
  52. package/dist/lib/watch/auth.js +1 -1
  53. package/dist/lib/watch/auth.js.map +1 -1
  54. package/dist/lib/watch/codex-composer.js.map +1 -1
  55. package/dist/lib/watch/codex-footer.js.map +1 -1
  56. package/dist/lib/watch/codex-overlay.js.map +1 -1
  57. package/dist/lib/watch/codex-request-form.js +1 -1
  58. package/dist/lib/watch/codex-request-form.js.map +1 -1
  59. package/dist/lib/watch/codex-request-input.js.map +1 -1
  60. package/dist/lib/watch/display.js.map +1 -1
  61. package/dist/lib/watch/format.js.map +1 -1
  62. package/dist/lib/watch/hooks/claude.js +0 -4
  63. package/dist/lib/watch/hooks/claude.js.map +1 -1
  64. package/dist/lib/watch/hooks/codebuddy.js +0 -4
  65. package/dist/lib/watch/hooks/codebuddy.js.map +1 -1
  66. package/dist/lib/watch/hooks/codex.js +0 -4
  67. package/dist/lib/watch/hooks/codex.js.map +1 -1
  68. package/dist/lib/watch/hooks/index.js.map +1 -1
  69. package/dist/lib/watch/hooks/shared.js +1 -0
  70. package/dist/lib/watch/hooks/shared.js.map +1 -1
  71. package/dist/lib/watch/index.js.map +1 -1
  72. package/dist/lib/watch/pod-ai.js +37 -29
  73. package/dist/lib/watch/pod-ai.js.map +1 -1
  74. package/dist/lib/watch/pod-approval.js +216 -846
  75. package/dist/lib/watch/pod-approval.js.map +1 -1
  76. package/dist/lib/watch/pod-persistence.js +78 -184
  77. package/dist/lib/watch/pod-persistence.js.map +1 -1
  78. package/dist/lib/watch/runner.js +38 -243
  79. package/dist/lib/watch/runner.js.map +1 -1
  80. package/dist/lib/watch/types.js.map +1 -1
  81. package/dist/skills/drizzle-solid/SKILL.md +340 -0
  82. package/dist/skills/pod-storage/SKILL.md +60 -0
  83. package/dist/skills/solid-modeling/SKILL.md +274 -0
  84. package/dist/skills/xpod-componentsjs/SKILL.md +284 -0
  85. package/dist/watch-cli.js +34 -8
  86. package/dist/watch-cli.js.map +1 -1
  87. package/package.json +9 -3
  88. package/vendor/client/dist/client/index.d.ts +118 -0
  89. package/vendor/client/dist/client/index.js +260 -0
  90. package/vendor/client/dist/index.d.ts +1 -0
  91. package/vendor/client/dist/index.js +1 -0
  92. package/vendor/client/dist/watch/index.d.ts +226 -0
  93. package/vendor/client/dist/watch/index.js +1114 -0
  94. package/vendor/client/package.json +9 -0
  95. package/dist/lib/node-warning-filter.js +0 -34
  96. package/dist/lib/node-warning-filter.js.map +0 -1
  97. package/dist/lib/pi-adapter/pod-approval.js +0 -8
  98. package/dist/lib/pi-adapter/pod-approval.js.map +0 -1
  99. package/dist/lib/pi-adapter/pod-mirror-mapping.js +0 -189
  100. package/dist/lib/pi-adapter/pod-mirror-mapping.js.map +0 -1
  101. package/dist/lib/pi-adapter/pod-mirror.js +0 -334
  102. package/dist/lib/pi-adapter/pod-mirror.js.map +0 -1
  103. package/dist/lib/pi-adapter/pod-native.js +0 -478
  104. package/dist/lib/pi-adapter/pod-native.js.map +0 -1
  105. package/dist/lib/pi-adapter/session.js +0 -727
  106. package/dist/lib/pi-adapter/session.js.map +0 -1
  107. package/dist/lib/pod-data-session.js +0 -110
  108. package/dist/lib/pod-data-session.js.map +0 -1
  109. package/dist/lib/watch/secretary.js +0 -238
  110. package/dist/lib/watch/secretary.js.map +0 -1
  111. package/vendor/agent-runtime/dist/acp.d.ts +0 -27
  112. package/vendor/agent-runtime/dist/acp.js +0 -86
  113. package/vendor/agent-runtime/dist/companion-model.d.ts +0 -7
  114. package/vendor/agent-runtime/dist/companion-model.js +0 -12
  115. package/vendor/agent-runtime/dist/index.d.ts +0 -3
  116. package/vendor/agent-runtime/dist/index.js +0 -3
  117. package/vendor/agent-runtime/dist/turn-controller.d.ts +0 -69
  118. package/vendor/agent-runtime/dist/turn-controller.js +0 -129
  119. package/vendor/agent-runtime/package.json +0 -11
@@ -1,25 +1,17 @@
1
1
  import { setTimeout as delay } from 'node:timers/promises';
2
- import { getDefaultPodDataSession } from '../pod-data-session.js';
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 buildWatchChatId(record) {
49
- return `${WATCH_CHAT_ID_PREFIX}-${record.backend}`;
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 buildApprovalResourceUrl(webIdOrUri, approvalId);
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 buildGrantResourceUrl(webIdOrUri, grantId);
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 safeJsonStringify({
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
- async function warnOnly(runtime, task) {
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 undefined;
156
+ return null;
300
157
  }
301
158
  try {
302
159
  const parsed = JSON.parse(value);
303
- if (!Array.isArray(parsed)) {
304
- return undefined;
160
+ if (!isRecord(parsed)) {
161
+ return null;
305
162
  }
306
- const options = parsed
307
- .map((option) => {
308
- if (!isRecord(option)) {
309
- return null;
310
- }
311
- const optionId = normalizeString(option.optionId);
312
- const label = normalizeString(option.label);
313
- if (!optionId || !label) {
314
- return null;
315
- }
316
- const kind = normalizeString(option.kind);
317
- const description = normalizeString(option.description);
318
- return {
319
- optionId,
320
- label,
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 undefined;
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 normalizeApprovalSummary(row) {
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: formatApprovalMessage(row),
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
- getPodDataSession: getDefaultPodDataSession,
429
- createStore(webId, fetcher) {
430
- return createNativeRemoteApprovalStore(webId, fetcher);
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 client = await getRemoteApprovalClient(runtime);
443
- if (!client) {
327
+ const stored = runtime.loadCredentials();
328
+ if (!stored) {
444
329
  throw new Error(missingRemoteApprovalCredentialsMessage());
445
330
  }
446
- return await fn({
447
- store: client.store,
448
- webId: client.session.webId,
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
- return (await listApprovalRows(webId, fetcher)).find((row) => row.id === id) ?? null;
508
- }
509
- async function readApprovalRowFromResource(fetcher, resourceUri) {
510
- const turtle = await readTurtleResource(fetcher, documentUrlFromResourceUri(resourceUri));
511
- if (!turtle) {
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
- return null;
524
- }
525
- async function listApprovalRows(webId, fetcher) {
526
- const urls = [
527
- ...recentApprovalDocumentUrls(webId),
528
- ...await listTurtleResources(fetcher, `${getPodBaseUrl(webId)}/.data/approvals/`).catch(() => []),
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
- const resolver = options.runtime.resolveGrantCoverage ?? resolveWatchGrantCoverage;
825
- for (const grant of candidates) {
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 createRemoteApproval({
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 = subject.sessionUri;
898
- const approvalUri = buildApprovalUriForDate(webId, approvalId, now);
899
- const targetUri = subject.targetUri ?? sessionUri;
900
- const assignedTo = subject.assignedTo ?? webId;
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: request.toolCallId,
911
- toolName: request.toolName,
912
- target: targetUri,
913
- action: request.action,
914
- risk: request.risk,
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
- context,
918
- ...(approvalOptions ? { approvalOptions } : {}),
919
- policyVersion,
370
+ assignedTo: webId,
371
+ policyVersion: REMOTE_APPROVAL_POLICY_VERSION,
920
372
  createdAt: now,
921
- ...(expiresAt ? { expiresAt } : {}),
922
373
  });
923
- const requestAudit = {
374
+ await store.insertAudit({
924
375
  id: crypto.randomUUID(),
925
376
  action: 'approval_requested',
926
- actor: subject.actorUri,
377
+ actor: buildAgentUri(webId),
927
378
  actorRole: 'secretary',
928
- onBehalfOf,
379
+ onBehalfOf: webId,
929
380
  session: sessionUri,
930
- entry: requestEntry,
931
- toolCallId: request.toolCallId,
932
- toolName: request.toolName,
381
+ toolCallId,
933
382
  approval: approvalUri,
934
- policyVersion,
383
+ context: JSON.stringify(requestContext),
384
+ policyVersion: REMOTE_APPROVAL_POLICY_VERSION,
935
385
  createdAt: now,
936
- };
937
- await warnOnly(activeRuntime, () => store.insertAudit(requestAudit));
938
- await warnOnly(activeRuntime, () => store.insertInboxNotification({
386
+ });
387
+ await store.insertInboxNotification({
939
388
  id: crypto.randomUUID(),
940
- actor: subject.actorUri,
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: request.toolCallId,
949
- toolName: request.toolName,
950
- target: targetUri,
951
- action: request.action,
952
- risk: request.risk,
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
- context,
956
- ...(approvalOptions ? { approvalOptions } : {}),
957
- policyVersion,
402
+ assignedTo: webId,
403
+ policyVersion: REMOTE_APPROVAL_POLICY_VERSION,
958
404
  createdAt: now,
959
- ...(expiresAt ? { expiresAt } : {}),
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 row = await readRemoteApprovalRow(store, {
971
- approvalId: options.approvalId,
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
- await activeRuntime.sleep(options.pollMs ?? DEFAULT_REMOTE_APPROVAL_POLL_MS);
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
- return resolveSemanticGrantDecision({
991
- runtime: activeRuntime,
992
- grants,
993
- record: options.record,
994
- request: options.request,
995
- requestContext: buildWatchGrantRequestContext({
996
- webId,
997
- record: options.record,
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 delegated;
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 store.listApprovals();
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 row = await readRemoteApprovalRow(store, {
1089
- approvalId: options.approvalId,
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
- return normalizeApprovalSummary(row);
492
+ const audits = await store.listAudits();
493
+ return normalizeApprovalSummary(row, audits);
1097
494
  }
1098
495
  const now = activeRuntime.now();
1099
- const approvalCreatedAt = new Date(toIsoString(row.createdAt, now.toISOString()));
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 warnOnly(activeRuntime, () => store.insertAudit({
508
+ await store.insertAudit({
1114
509
  id: crypto.randomUUID(),
1115
510
  action: nextStatus === 'approved' ? 'approval_approved' : 'approval_rejected',
1116
511
  actor: webId,
1117
- actorRole: decisionRole,
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 warnOnly(activeRuntime, () => store.insertInboxNotification({
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 warnOnly(activeRuntime, () => store.insertInboxNotification({
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
- return normalizeApprovalSummary(nextRow);
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