@undefineds.co/linx 0.2.15 → 0.2.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -6
- package/dist/index.js +50 -104
- package/dist/index.js.map +1 -1
- package/dist/lib/ai-command.js +38 -60
- package/dist/lib/ai-command.js.map +1 -1
- package/dist/lib/pi-adapter/branding.js +2 -2
- package/dist/lib/pi-adapter/branding.js.map +1 -1
- package/dist/lib/pi-adapter/pod-native.js +3 -2
- package/dist/lib/pi-adapter/pod-native.js.map +1 -1
- package/dist/lib/pi-adapter/runtime.js +2 -2
- package/dist/lib/pi-adapter/runtime.js.map +1 -1
- package/dist/lib/pod-data-session.js +43 -3
- package/dist/lib/pod-data-session.js.map +1 -1
- package/dist/lib/watch/hooks/claude.js +4 -0
- package/dist/lib/watch/hooks/claude.js.map +1 -1
- package/dist/lib/watch/hooks/codebuddy.js +4 -0
- package/dist/lib/watch/hooks/codebuddy.js.map +1 -1
- package/dist/lib/watch/hooks/codex.js +4 -0
- package/dist/lib/watch/hooks/codex.js.map +1 -1
- package/dist/lib/watch/pod-ai.js +22 -14
- package/dist/lib/watch/pod-ai.js.map +1 -1
- package/dist/lib/watch/pod-approval.js +387 -35
- package/dist/lib/watch/pod-approval.js.map +1 -1
- package/dist/lib/watch/pod-persistence.js +162 -43
- package/dist/lib/watch/pod-persistence.js.map +1 -1
- package/dist/lib/watch/runner.js +240 -38
- package/dist/lib/watch/runner.js.map +1 -1
- package/dist/lib/watch/secretary.js +238 -0
- package/dist/lib/watch/secretary.js.map +1 -0
- package/dist/watch-cli.js +8 -34
- package/dist/watch-cli.js.map +1 -1
- package/package.json +3 -2
- package/vendor/agent-runtime/dist/acp.d.ts +27 -0
- package/vendor/agent-runtime/dist/acp.js +86 -0
- package/vendor/agent-runtime/dist/companion-model.d.ts +7 -0
- package/vendor/agent-runtime/dist/companion-model.js +12 -0
- package/vendor/agent-runtime/dist/index.d.ts +3 -0
- package/vendor/agent-runtime/dist/index.js +3 -0
- package/vendor/agent-runtime/dist/turn-controller.d.ts +69 -0
- package/vendor/agent-runtime/dist/turn-controller.js +129 -0
- package/vendor/agent-runtime/package.json +11 -0
|
@@ -2,11 +2,18 @@ import { setTimeout as delay } from 'node:timers/promises';
|
|
|
2
2
|
import { getDefaultPodDataSession } from '../pod-data-session.js';
|
|
3
3
|
import { AS, ODRL, UDFS } from '@undefineds.co/models/namespaces';
|
|
4
4
|
import { ApprovalVocab, AuditVocab, GrantVocab, InboxNotificationVocab } from '@undefineds.co/models/vocab/sidecar';
|
|
5
|
+
import { resolveWatchGrantCoverage } from './secretary.js';
|
|
5
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';
|
|
6
|
-
const
|
|
7
|
+
const WATCH_CHAT_ID_PREFIX = 'linx-watch';
|
|
7
8
|
const WATCH_AGENT_ID = 'linx-watch-assistant';
|
|
8
9
|
const REMOTE_APPROVAL_POLICY_VERSION = 'linx-watch-remote-approval/v1';
|
|
9
10
|
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;
|
|
10
17
|
const remoteApprovalClientCache = new WeakMap();
|
|
11
18
|
function createAbortError() {
|
|
12
19
|
const error = new Error('The operation was aborted.');
|
|
@@ -38,8 +45,11 @@ function getPodBaseUrl(webIdOrUri) {
|
|
|
38
45
|
}
|
|
39
46
|
return webIdOrUri.replace(/\/$/, '');
|
|
40
47
|
}
|
|
41
|
-
function
|
|
42
|
-
return `${
|
|
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}`;
|
|
43
53
|
}
|
|
44
54
|
function buildApprovalUri(webIdOrUri, approvalId) {
|
|
45
55
|
return buildApprovalResourceUrl(webIdOrUri, approvalId);
|
|
@@ -53,8 +63,8 @@ function documentUrlFromResourceUri(resourceUri) {
|
|
|
53
63
|
function buildGrantUri(webIdOrUri, grantId) {
|
|
54
64
|
return buildGrantResourceUrl(webIdOrUri, grantId);
|
|
55
65
|
}
|
|
56
|
-
function
|
|
57
|
-
return `${getPodBaseUrl(webIdOrUri)}/settings/autonomy/
|
|
66
|
+
function buildGrantSchemaUri(webIdOrUri) {
|
|
67
|
+
return `${getPodBaseUrl(webIdOrUri)}/settings/autonomy/schema/grant.ttl#GrantWikiPage`;
|
|
58
68
|
}
|
|
59
69
|
function buildAgentUri(webId) {
|
|
60
70
|
return `${getPodBaseUrl(webId)}/.data/agents/${WATCH_AGENT_ID}.ttl`;
|
|
@@ -153,7 +163,12 @@ function parseDecisionReason(value) {
|
|
|
153
163
|
}
|
|
154
164
|
async function warnOnly(runtime, task) {
|
|
155
165
|
try {
|
|
156
|
-
await
|
|
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
|
+
]);
|
|
157
172
|
}
|
|
158
173
|
catch (error) {
|
|
159
174
|
if (runtime.onWarning) {
|
|
@@ -172,6 +187,168 @@ function safeJsonStringify(value) {
|
|
|
172
187
|
return JSON.stringify({ error: 'unserializable_context' });
|
|
173
188
|
}
|
|
174
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) {
|
|
298
|
+
if (typeof value !== 'string' || !value.trim()) {
|
|
299
|
+
return undefined;
|
|
300
|
+
}
|
|
301
|
+
try {
|
|
302
|
+
const parsed = JSON.parse(value);
|
|
303
|
+
if (!Array.isArray(parsed)) {
|
|
304
|
+
return undefined;
|
|
305
|
+
}
|
|
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;
|
|
327
|
+
}
|
|
328
|
+
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);
|
|
349
|
+
}
|
|
350
|
+
return undefined;
|
|
351
|
+
}
|
|
175
352
|
function extractSessionId(sessionUri) {
|
|
176
353
|
if (sessionUri.includes('#')) {
|
|
177
354
|
return sessionUri.split('#').pop() || sessionUri;
|
|
@@ -196,6 +373,7 @@ function normalizeApprovalSummary(row) {
|
|
|
196
373
|
const createdAt = toIsoString(row.createdAt, new Date(0).toISOString());
|
|
197
374
|
const sessionUri = row.session;
|
|
198
375
|
const decision = decisionFromApprovalRow(row);
|
|
376
|
+
const approvalOptions = parseApprovalOptions(row.approvalOptions);
|
|
199
377
|
return {
|
|
200
378
|
id: row.id,
|
|
201
379
|
...(normalizeString(row.approvalUri) ? { approvalUri: normalizeString(row.approvalUri) } : {}),
|
|
@@ -209,7 +387,9 @@ function normalizeApprovalSummary(row) {
|
|
|
209
387
|
...(normalizeString(row.assignedTo) ? { assignedTo: normalizeString(row.assignedTo) } : {}),
|
|
210
388
|
...(normalizeString(row.decisionBy) ? { decisionBy: normalizeString(row.decisionBy) } : {}),
|
|
211
389
|
...(decision ? { decision } : {}),
|
|
390
|
+
...(approvalOptions ? { approvalOptions } : {}),
|
|
212
391
|
createdAt,
|
|
392
|
+
...(row.expiresAt ? { expiresAt: toIsoString(row.expiresAt, createdAt) } : {}),
|
|
213
393
|
...(row.resolvedAt ? { resolvedAt: toIsoString(row.resolvedAt, createdAt) } : {}),
|
|
214
394
|
};
|
|
215
395
|
}
|
|
@@ -255,6 +435,7 @@ async function createDefaultRuntime() {
|
|
|
255
435
|
now() {
|
|
256
436
|
return new Date();
|
|
257
437
|
},
|
|
438
|
+
resolveGrantCoverage: resolveWatchGrantCoverage,
|
|
258
439
|
};
|
|
259
440
|
}
|
|
260
441
|
async function withRemoteApprovalStore(runtime, fn) {
|
|
@@ -342,13 +523,12 @@ async function readApprovalRowFromResource(fetcher, resourceUri) {
|
|
|
342
523
|
return null;
|
|
343
524
|
}
|
|
344
525
|
async function listApprovalRows(webId, fetcher) {
|
|
345
|
-
const
|
|
346
|
-
|
|
347
|
-
listTurtleResources(fetcher, `${getPodBaseUrl(webId)}/.data/approvals/`).catch(() => []),
|
|
348
|
-
]
|
|
349
|
-
const urls = [...new Set([...currentUrls, ...legacyUrls])];
|
|
526
|
+
const urls = [
|
|
527
|
+
...recentApprovalDocumentUrls(webId),
|
|
528
|
+
...await listTurtleResources(fetcher, `${getPodBaseUrl(webId)}/.data/approvals/`).catch(() => []),
|
|
529
|
+
];
|
|
350
530
|
const rows = [];
|
|
351
|
-
for (const url of urls.filter((entry) => entry.endsWith('.ttl'))) {
|
|
531
|
+
for (const url of [...new Set(urls)].filter((entry) => entry.endsWith('.ttl'))) {
|
|
352
532
|
const turtle = await readTurtleResource(fetcher, url).catch(() => null);
|
|
353
533
|
if (!turtle)
|
|
354
534
|
continue;
|
|
@@ -360,6 +540,15 @@ async function listApprovalRows(webId, fetcher) {
|
|
|
360
540
|
}
|
|
361
541
|
return rows;
|
|
362
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
|
+
}
|
|
363
552
|
async function writeApprovalRow(webId, fetcher, row) {
|
|
364
553
|
const createdAt = new Date(toIsoString(row.createdAt, new Date().toISOString()));
|
|
365
554
|
const documentUrl = buildApprovalDocumentUrl(webId, createdAt);
|
|
@@ -380,8 +569,11 @@ async function writeApprovalRow(webId, fetcher, row) {
|
|
|
380
569
|
...(row.decisionRole ? [{ predicate: ApprovalVocab.decisionRole, object: literal(row.decisionRole) }] : []),
|
|
381
570
|
...(row.onBehalfOf ? [{ predicate: ApprovalVocab.onBehalfOf, object: iri(row.onBehalfOf) }] : []),
|
|
382
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) }] : []),
|
|
383
574
|
...(row.policyVersion ? [{ predicate: ApprovalVocab.policyVersion, object: literal(row.policyVersion) }] : []),
|
|
384
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())) }] : []),
|
|
385
577
|
...(row.resolvedAt ? [{ predicate: ApprovalVocab.resolvedAt, object: literal(toIsoString(row.resolvedAt, new Date().toISOString())) }] : []),
|
|
386
578
|
],
|
|
387
579
|
});
|
|
@@ -425,7 +617,6 @@ async function writeAuditRow(webId, fetcher, row) {
|
|
|
425
617
|
}
|
|
426
618
|
async function listGrantRows(webId, fetcher) {
|
|
427
619
|
const urls = [
|
|
428
|
-
`${getPodBaseUrl(webId)}/settings/autonomy/grants.ttl`,
|
|
429
620
|
...await listTurtleResources(fetcher, `${getPodBaseUrl(webId)}/settings/autonomy/grants/`).catch(() => []),
|
|
430
621
|
];
|
|
431
622
|
const rows = [];
|
|
@@ -443,8 +634,8 @@ async function listGrantRows(webId, fetcher) {
|
|
|
443
634
|
}
|
|
444
635
|
async function writeGrantRow(webId, fetcher, row) {
|
|
445
636
|
const id = normalizeString(row.id) ?? crypto.randomUUID();
|
|
446
|
-
const documentUrl = buildGrantDocumentUrl(webId);
|
|
447
637
|
const subjectUrl = buildGrantResourceUrl(webId, id);
|
|
638
|
+
const documentUrl = subjectUrl;
|
|
448
639
|
const target = normalizeString(row.target);
|
|
449
640
|
const action = normalizeString(row.action);
|
|
450
641
|
const effect = normalizeString(row.effect);
|
|
@@ -460,8 +651,22 @@ async function writeGrantRow(webId, fetcher, row) {
|
|
|
460
651
|
{ predicate: RDF_TYPE, object: iri(UDFS.AutonomyGrant) },
|
|
461
652
|
{ predicate: GrantVocab.target, object: iri(target) },
|
|
462
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) })),
|
|
463
666
|
{ predicate: GrantVocab.effect, object: literal(effect) },
|
|
464
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)) }] : []),
|
|
465
670
|
{ predicate: GrantVocab.decisionBy, object: iri(decisionBy) },
|
|
466
671
|
{ predicate: GrantVocab.decisionRole, object: literal(decisionRole) },
|
|
467
672
|
...(normalizeString(row.onBehalfOf) ? [{ predicate: GrantVocab.onBehalfOf, object: iri(normalizeString(row.onBehalfOf)) }] : []),
|
|
@@ -508,8 +713,11 @@ function approvalRowFromPredicates(url, predicates) {
|
|
|
508
713
|
decisionRole: firstLiteral(predicates, ApprovalVocab.decisionRole),
|
|
509
714
|
onBehalfOf: firstIri(predicates, ApprovalVocab.onBehalfOf),
|
|
510
715
|
reason: firstLiteral(predicates, ApprovalVocab.reason),
|
|
716
|
+
context: firstLiteral(predicates, ApprovalVocab.context),
|
|
717
|
+
approvalOptions: firstLiteral(predicates, ApprovalVocab.approvalOptions),
|
|
511
718
|
policyVersion: firstLiteral(predicates, ApprovalVocab.policyVersion),
|
|
512
719
|
createdAt,
|
|
720
|
+
expiresAt: firstLiteral(predicates, ApprovalVocab.expiresAt),
|
|
513
721
|
resolvedAt: firstLiteral(predicates, ApprovalVocab.resolvedAt),
|
|
514
722
|
};
|
|
515
723
|
}
|
|
@@ -550,8 +758,22 @@ function grantRowFromPredicates(url, predicates) {
|
|
|
550
758
|
id: subjectIdFromResourceUrl(url),
|
|
551
759
|
target,
|
|
552
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),
|
|
553
773
|
effect,
|
|
554
774
|
riskCeiling: firstLiteral(predicates, GrantVocab.riskCeiling),
|
|
775
|
+
policy: firstLiteral(predicates, GrantVocab.policy),
|
|
776
|
+
context: firstLiteral(predicates, GrantVocab.context),
|
|
555
777
|
decisionBy,
|
|
556
778
|
decisionRole,
|
|
557
779
|
onBehalfOf: firstIri(predicates, GrantVocab.onBehalfOf),
|
|
@@ -559,11 +781,88 @@ function grantRowFromPredicates(url, predicates) {
|
|
|
559
781
|
revokedAt: firstLiteral(predicates, GrantVocab.revokedAt),
|
|
560
782
|
};
|
|
561
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;
|
|
823
|
+
}
|
|
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
|
+
}
|
|
835
|
+
}
|
|
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
|
+
}
|
|
562
861
|
export async function createRemoteWatchApproval(options) {
|
|
563
862
|
const activeRuntime = options.runtime ?? await createDefaultRuntime();
|
|
564
863
|
return createRemoteApproval({
|
|
565
864
|
subject: ({ webId }) => ({
|
|
566
|
-
sessionUri: buildThreadUri(webId, options.record
|
|
865
|
+
sessionUri: buildThreadUri(webId, options.record),
|
|
567
866
|
actorUri: buildAgentUri(webId),
|
|
568
867
|
policyVersion: REMOTE_APPROVAL_POLICY_VERSION,
|
|
569
868
|
}),
|
|
@@ -576,6 +875,9 @@ export async function createRemoteWatchApproval(options) {
|
|
|
576
875
|
risk: buildRisk(options.request),
|
|
577
876
|
...(options.request.kind === 'command-approval' && options.request.command ? { command: options.request.command } : {}),
|
|
578
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 } : {}),
|
|
579
881
|
entry: sessionUri,
|
|
580
882
|
}),
|
|
581
883
|
runtime: activeRuntime,
|
|
@@ -599,6 +901,9 @@ export async function createRemoteApproval(options) {
|
|
|
599
901
|
const onBehalfOf = subject.onBehalfOf ?? webId;
|
|
600
902
|
const policyVersion = subject.policyVersion ?? REMOTE_APPROVAL_POLICY_VERSION;
|
|
601
903
|
const requestEntry = request.entry ?? approvalUri;
|
|
904
|
+
const expiresAt = resolveApprovalExpiresAt(request, now);
|
|
905
|
+
const approvalOptions = encodeApprovalOptions(request.approvalOptions);
|
|
906
|
+
const context = compactApprovalContext(request);
|
|
602
907
|
await store.insertApproval({
|
|
603
908
|
id: approvalId,
|
|
604
909
|
session: sessionUri,
|
|
@@ -609,8 +914,11 @@ export async function createRemoteApproval(options) {
|
|
|
609
914
|
risk: request.risk,
|
|
610
915
|
status: 'pending',
|
|
611
916
|
assignedTo,
|
|
917
|
+
context,
|
|
918
|
+
...(approvalOptions ? { approvalOptions } : {}),
|
|
612
919
|
policyVersion,
|
|
613
920
|
createdAt: now,
|
|
921
|
+
...(expiresAt ? { expiresAt } : {}),
|
|
614
922
|
});
|
|
615
923
|
const requestAudit = {
|
|
616
924
|
id: crypto.randomUUID(),
|
|
@@ -644,8 +952,11 @@ export async function createRemoteApproval(options) {
|
|
|
644
952
|
risk: request.risk,
|
|
645
953
|
status: 'pending',
|
|
646
954
|
assignedTo,
|
|
955
|
+
context,
|
|
956
|
+
...(approvalOptions ? { approvalOptions } : {}),
|
|
647
957
|
policyVersion,
|
|
648
958
|
createdAt: now,
|
|
959
|
+
...(expiresAt ? { expiresAt } : {}),
|
|
649
960
|
});
|
|
650
961
|
});
|
|
651
962
|
}
|
|
@@ -676,17 +987,20 @@ export async function requestRemoteWatchApproval(options) {
|
|
|
676
987
|
const activeRuntime = options.runtime ?? await createDefaultRuntime();
|
|
677
988
|
const delegated = await withRemoteApprovalStore(activeRuntime, async ({ store, webId }) => {
|
|
678
989
|
const grants = await store.listGrants();
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
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
|
+
});
|
|
687
1001
|
});
|
|
688
1002
|
if (delegated) {
|
|
689
|
-
return
|
|
1003
|
+
return delegated;
|
|
690
1004
|
}
|
|
691
1005
|
const summary = await createRemoteWatchApproval({
|
|
692
1006
|
record: options.record,
|
|
@@ -701,6 +1015,23 @@ export async function requestRemoteWatchApproval(options) {
|
|
|
701
1015
|
runtime: activeRuntime,
|
|
702
1016
|
});
|
|
703
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
|
+
}
|
|
704
1035
|
export async function requestRemoteApproval(options) {
|
|
705
1036
|
const activeRuntime = options.runtime ?? await createDefaultRuntime();
|
|
706
1037
|
const delegated = await withRemoteApprovalStore(activeRuntime, async ({ store, webId, stored }) => {
|
|
@@ -711,15 +1042,20 @@ export async function requestRemoteApproval(options) {
|
|
|
711
1042
|
? options.request({ webId, stored, sessionUri: subject.sessionUri })
|
|
712
1043
|
: options.request;
|
|
713
1044
|
const grants = await store.listGrants();
|
|
714
|
-
const
|
|
715
|
-
return
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
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
|
+
});
|
|
720
1056
|
});
|
|
721
1057
|
if (delegated) {
|
|
722
|
-
return
|
|
1058
|
+
return delegated;
|
|
723
1059
|
}
|
|
724
1060
|
const summary = await createRemoteApproval({
|
|
725
1061
|
subject: options.subject,
|
|
@@ -765,10 +1101,11 @@ export async function resolveRemoteWatchApproval(options) {
|
|
|
765
1101
|
const nextStatus = options.decision === 'accept' || options.decision === 'accept_for_session'
|
|
766
1102
|
? 'approved'
|
|
767
1103
|
: 'rejected';
|
|
1104
|
+
const decisionRole = options.decisionRole ?? 'human';
|
|
768
1105
|
await store.updateApproval(row.id, {
|
|
769
1106
|
status: nextStatus,
|
|
770
1107
|
decisionBy: webId,
|
|
771
|
-
decisionRole
|
|
1108
|
+
decisionRole,
|
|
772
1109
|
onBehalfOf: webId,
|
|
773
1110
|
reason: encodeDecisionReason(options.decision, options.note),
|
|
774
1111
|
resolvedAt: now,
|
|
@@ -777,7 +1114,7 @@ export async function resolveRemoteWatchApproval(options) {
|
|
|
777
1114
|
id: crypto.randomUUID(),
|
|
778
1115
|
action: nextStatus === 'approved' ? 'approval_approved' : 'approval_rejected',
|
|
779
1116
|
actor: webId,
|
|
780
|
-
actorRole:
|
|
1117
|
+
actorRole: decisionRole,
|
|
781
1118
|
onBehalfOf: webId,
|
|
782
1119
|
session: row.session,
|
|
783
1120
|
entry: approvalUri,
|
|
@@ -789,14 +1126,29 @@ export async function resolveRemoteWatchApproval(options) {
|
|
|
789
1126
|
}));
|
|
790
1127
|
if (options.decision === 'accept_for_session') {
|
|
791
1128
|
const grantId = crypto.randomUUID();
|
|
1129
|
+
const body = grantWikiBodyFromApproval(row, options.grantWikiBody);
|
|
792
1130
|
await store.insertGrant({
|
|
793
1131
|
id: grantId,
|
|
794
1132
|
target: row.target,
|
|
795
1133
|
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],
|
|
796
1146
|
effect: 'allow',
|
|
797
1147
|
riskCeiling: row.risk,
|
|
1148
|
+
policy: grantIndexTextFromWikiBody(body),
|
|
1149
|
+
context: grantContextFromApproval(row),
|
|
798
1150
|
decisionBy: webId,
|
|
799
|
-
decisionRole
|
|
1151
|
+
decisionRole,
|
|
800
1152
|
onBehalfOf: webId,
|
|
801
1153
|
createdAt: now,
|
|
802
1154
|
});
|
|
@@ -817,7 +1169,7 @@ export async function resolveRemoteWatchApproval(options) {
|
|
|
817
1169
|
...row,
|
|
818
1170
|
status: nextStatus,
|
|
819
1171
|
decisionBy: webId,
|
|
820
|
-
decisionRole
|
|
1172
|
+
decisionRole,
|
|
821
1173
|
onBehalfOf: webId,
|
|
822
1174
|
reason: encodeDecisionReason(options.decision, options.note),
|
|
823
1175
|
resolvedAt: now,
|