@superdoc/sdk 2.10.0-next.7 → 2.10.0-next.9
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 +45 -0
- package/dist/action-primitives/doc-index.cjs +6 -4
- package/dist/action-primitives/doc-index.js +6 -4
- package/dist/action-primitives/engine.cjs +6 -2
- package/dist/action-primitives/engine.js +6 -2
- package/dist/action-primitives/receipt.d.ts +4 -0
- package/dist/action-primitives/tools/structure-insert.d.ts +1 -1
- package/dist/agent/actions.cjs +203 -223
- package/dist/agent/actions.d.ts +5 -0
- package/dist/agent/actions.js +203 -223
- package/dist/agent/catalog.cjs +15 -0
- package/dist/agent/catalog.js +15 -0
- package/dist/agent/doc-snapshot.cjs +22 -46
- package/dist/agent/doc-snapshot.js +22 -46
- package/dist/agent/execution-context.cjs +385 -0
- package/dist/agent/execution-context.d.ts +97 -0
- package/dist/agent/execution-context.js +376 -0
- package/dist/agent/runtime.cjs +116 -158
- package/dist/agent/runtime.d.ts +3 -0
- package/dist/agent/runtime.js +117 -159
- package/dist/agent/v2-preset-compat.cjs +5 -1
- package/dist/agent/v2-preset-compat.js +4 -1
- package/dist/embedded-tools.generated.cjs +5 -5
- package/dist/embedded-tools.generated.js +5 -5
- package/dist/generated/client.cjs +2 -0
- package/dist/generated/client.d.ts +47 -0
- package/dist/generated/client.js +2 -0
- package/dist/generated/contract.cjs +1605 -1297
- package/dist/generated/contract.js +1605 -1297
- package/dist/index.cjs +11 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.js +11 -0
- package/dist/presets/core.cjs +1 -1
- package/dist/presets/core.js +1 -1
- package/dist/runtime/document-evidence.cjs +40 -0
- package/dist/runtime/document-evidence.d.ts +13 -0
- package/dist/runtime/document-evidence.js +30 -0
- package/dist/runtime/host.cjs +30 -2
- package/dist/runtime/host.d.ts +2 -0
- package/dist/runtime/host.js +30 -2
- package/dist/runtime/process.cjs +6 -0
- package/dist/runtime/process.d.ts +2 -0
- package/dist/runtime/process.js +6 -0
- package/dist/runtime/sdk-version.generated.cjs +1 -1
- package/dist/runtime/sdk-version.generated.d.ts +1 -1
- package/dist/runtime/sdk-version.generated.js +1 -1
- package/package.json +10 -9
- package/tools/catalog.json +35 -0
- package/tools/tools-policy.json +1 -1
- package/tools/tools.anthropic.json +35 -0
- package/tools/tools.generic.json +35 -0
- package/tools/tools.openai.json +35 -0
- package/tools/tools.vercel.json +35 -0
package/dist/agent/runtime.cjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
var executionContext = require('./execution-context.cjs');
|
|
3
4
|
var contract = require('../generated/contract.cjs');
|
|
4
5
|
var errors = require('../runtime/errors.cjs');
|
|
5
6
|
var ir = require('./ir.cjs');
|
|
@@ -336,172 +337,150 @@ async function trySaveReopen(doc, checks) {
|
|
|
336
337
|
function verificationNeedsSaveReopen(checks) {
|
|
337
338
|
return checks.some((check) => check.kind === 'document-saves-cleanly' || check.kind === 'save-reopen-text-contains');
|
|
338
339
|
}
|
|
340
|
+
function revisionOnlyParagraphStep(plan) {
|
|
341
|
+
if (plan.atomic || plan.preconditions || plan.postconditions || plan.expectedDiff || plan.steps.length !== 2) {
|
|
342
|
+
return undefined;
|
|
343
|
+
}
|
|
344
|
+
const [apply, verify] = plan.steps;
|
|
345
|
+
if (apply?.kind !== 'apply' ||
|
|
346
|
+
apply.operationId !== 'doc.create.paragraph' ||
|
|
347
|
+
apply.atomic ||
|
|
348
|
+
verify?.kind !== 'verify' ||
|
|
349
|
+
verify.saveReopen ||
|
|
350
|
+
verify.checks.length === 0 ||
|
|
351
|
+
!verify.checks.every((check) => check.kind === 'revision-changed' || check.kind === 'revision-unchanged')) {
|
|
352
|
+
return undefined;
|
|
353
|
+
}
|
|
354
|
+
// Only literal body inputs are eligible. Binding tokens, story targets and
|
|
355
|
+
// additional operation options must retain the complete evidence path.
|
|
356
|
+
if (Object.keys(apply.args).some((key) => key !== 'text' && key !== 'at'))
|
|
357
|
+
return undefined;
|
|
358
|
+
if (apply.args.text !== undefined && typeof apply.args.text !== 'string')
|
|
359
|
+
return undefined;
|
|
360
|
+
const at = apply.args.at;
|
|
361
|
+
if (at !== undefined) {
|
|
362
|
+
if (!isRecord(at))
|
|
363
|
+
return undefined;
|
|
364
|
+
if (at.kind === 'documentStart' || at.kind === 'documentEnd') {
|
|
365
|
+
if (Object.keys(at).some((key) => key !== 'kind'))
|
|
366
|
+
return undefined;
|
|
367
|
+
}
|
|
368
|
+
else if (at.kind === 'before' || at.kind === 'after') {
|
|
369
|
+
if (Object.keys(at).some((key) => key !== 'kind' && key !== 'target') ||
|
|
370
|
+
!isRecord(at.target) ||
|
|
371
|
+
at.target.kind !== 'block' ||
|
|
372
|
+
typeof at.target.nodeId !== 'string' ||
|
|
373
|
+
typeof at.target.nodeType !== 'string' ||
|
|
374
|
+
Object.keys(at.target).some((key) => !['kind', 'nodeId', 'nodeType'].includes(key))) {
|
|
375
|
+
return undefined;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
else
|
|
379
|
+
return undefined;
|
|
380
|
+
}
|
|
381
|
+
return apply;
|
|
382
|
+
}
|
|
339
383
|
async function agentApply(doc, args) {
|
|
340
384
|
const plan = args.plan;
|
|
341
385
|
const validation = ir.validatePlan(plan);
|
|
342
|
-
if (!validation.ok)
|
|
343
|
-
return {
|
|
344
|
-
status: 'failed',
|
|
345
|
-
intent: plan.intent,
|
|
346
|
-
preSnapshot: { revision: 'unknown', counts: emptyCounts() },
|
|
347
|
-
selectedTargets: [],
|
|
348
|
-
executedOperations: [],
|
|
349
|
-
verification: [],
|
|
350
|
-
errors: validation.errors.map((e) => ({ code: e.code, message: e.message })),
|
|
351
|
-
};
|
|
352
|
-
}
|
|
353
|
-
let preSnapshot;
|
|
354
|
-
try {
|
|
355
|
-
preSnapshot = await docSnapshot.buildMutationSnapshot(doc);
|
|
356
|
-
}
|
|
357
|
-
catch (error) {
|
|
358
|
-
if (!(error instanceof docSnapshot.MutationSnapshotError))
|
|
359
|
-
throw error;
|
|
386
|
+
if (!validation.ok)
|
|
360
387
|
return {
|
|
361
388
|
status: 'failed',
|
|
362
389
|
intent: plan.intent,
|
|
363
|
-
preSnapshot: { revision: 'unknown', counts: emptyCounts() },
|
|
364
390
|
selectedTargets: [],
|
|
365
391
|
executedOperations: [],
|
|
366
392
|
verification: [],
|
|
367
|
-
errors:
|
|
393
|
+
errors: validation.errors.map((error) => ({ code: error.code, message: error.message })),
|
|
368
394
|
};
|
|
369
|
-
|
|
370
|
-
const
|
|
371
|
-
|
|
395
|
+
const verifyStep = plan.steps.find((step) => step.kind === 'verify');
|
|
396
|
+
const checks = verifyStep?.kind === 'verify' ? verifyStep.checks : [];
|
|
397
|
+
let written = false;
|
|
398
|
+
const baselineAfterWrite = plan.steps.some((step) => {
|
|
399
|
+
if (step.kind === 'apply')
|
|
400
|
+
written = true;
|
|
401
|
+
return written && step.kind === 'select' && step.selector.kind !== 'ref';
|
|
402
|
+
});
|
|
403
|
+
const context = new executionContext.ExecutionContext(doc, {
|
|
404
|
+
evidence: args.evidence,
|
|
405
|
+
revisionOnly: !!revisionOnlyParagraphStep(plan),
|
|
406
|
+
checks,
|
|
407
|
+
selectors: plan.steps.flatMap((step) => step.kind === 'select' && step.selector.kind !== 'ref' ? [step.selector] : []),
|
|
408
|
+
completeReason: plan.atomic ||
|
|
409
|
+
plan.preconditions ||
|
|
410
|
+
plan.postconditions ||
|
|
411
|
+
plan.expectedDiff ||
|
|
412
|
+
plan.steps.some((step) => step.kind === 'inspect') ||
|
|
413
|
+
plan.steps.filter((step) => step.kind === 'apply').length > 1
|
|
414
|
+
? 'plan requires complete baseline evidence'
|
|
415
|
+
: baselineAfterWrite
|
|
416
|
+
? 'plan selects baseline targets after a write'
|
|
417
|
+
: planTouchesRiskyDomain(plan)
|
|
418
|
+
? 'plan requires document-wide save/reopen evidence'
|
|
419
|
+
: plan.steps.some((step) => step.kind === 'apply' &&
|
|
420
|
+
['doc.history.undo', 'doc.history.redo', 'doc.plan.execute'].includes(step.operationId))
|
|
421
|
+
? 'operation requires complete execution evidence'
|
|
422
|
+
: undefined,
|
|
423
|
+
});
|
|
372
424
|
const bindings = new Map();
|
|
373
425
|
try {
|
|
426
|
+
const pre = await context.start();
|
|
374
427
|
for (const step of plan.steps) {
|
|
375
428
|
if (step.kind === 'select') {
|
|
376
|
-
const matched =
|
|
377
|
-
|
|
429
|
+
const matched = pre.complete
|
|
430
|
+
? resolveSelectorWithBindings(pre.complete, step.selector, bindings)
|
|
431
|
+
: step.selector.kind === 'ref'
|
|
432
|
+
? extractBoundNodeIds(resolveBindingRef(bindings, step.selector.ref))
|
|
433
|
+
: await pre.select(step.selector);
|
|
434
|
+
if (step.requireUnique && matched.length !== 1)
|
|
378
435
|
throw new docSnapshot.AmbiguousSelectorError(`Selector did not resolve uniquely (matched ${matched.length}).`, matched.map((nodeId) => ({ nodeId, description: nodeId })));
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
if (step.bind) {
|
|
436
|
+
context.selectedTargets.push({ selector: step.selector, matched });
|
|
437
|
+
if (step.bind)
|
|
382
438
|
bindings.set(step.bind, matched.length === 1 ? matched[0] : [...matched]);
|
|
383
|
-
}
|
|
384
|
-
continue;
|
|
385
439
|
}
|
|
386
|
-
if (step.kind === 'inspect') {
|
|
387
|
-
const
|
|
388
|
-
const method = resolveDocMethod(
|
|
389
|
-
const result = await method(
|
|
390
|
-
|
|
440
|
+
else if (step.kind === 'inspect' || step.kind === 'apply') {
|
|
441
|
+
const input = ensureClean(resolveBindingTokens(step.args, bindings));
|
|
442
|
+
const method = resolveDocMethod(context.document, step.operationId);
|
|
443
|
+
const result = await method(step.kind === 'apply' && step.changeMode && operationCatalog.getOperationCatalogEntry(step.operationId)?.supportsChangeMode
|
|
444
|
+
? { ...input, changeMode: step.changeMode }
|
|
445
|
+
: input);
|
|
446
|
+
if (step.kind === 'inspect' && step.bind)
|
|
391
447
|
bindings.set(step.bind, result);
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
if (step.kind === 'apply') {
|
|
395
|
-
ensureKnownOperation(step.operationId);
|
|
396
|
-
const applyArgs = ensureClean(resolveBindingTokens(step.args, bindings));
|
|
397
|
-
const method = resolveDocMethod(doc, step.operationId);
|
|
398
|
-
const argsWithMode = step.changeMode != null && operationCatalog.getOperationCatalogEntry(step.operationId)?.supportsChangeMode
|
|
399
|
-
? { ...applyArgs, changeMode: step.changeMode }
|
|
400
|
-
: applyArgs;
|
|
401
|
-
const result = await method(argsWithMode);
|
|
402
|
-
executedOperations.push({ operationId: step.operationId, rationale: step.rationale, result });
|
|
448
|
+
if (step.kind === 'apply' && context.executedOperations.length)
|
|
449
|
+
context.executedOperations.at(-1).rationale = step.rationale;
|
|
403
450
|
}
|
|
404
451
|
}
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
{
|
|
417
|
-
code: err.code,
|
|
418
|
-
message: err.message,
|
|
419
|
-
},
|
|
420
|
-
],
|
|
421
|
-
};
|
|
422
|
-
}
|
|
423
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
424
|
-
return {
|
|
425
|
-
status: 'failed',
|
|
426
|
-
intent: plan.intent,
|
|
427
|
-
preSnapshot: { revision: preSnapshot.revision, counts: preSnapshot.counts },
|
|
428
|
-
selectedTargets,
|
|
429
|
-
executedOperations,
|
|
430
|
-
verification: [],
|
|
431
|
-
errors: [{ code: 'APPLY_FAILED', message }],
|
|
432
|
-
};
|
|
433
|
-
}
|
|
434
|
-
let postSnapshot;
|
|
435
|
-
try {
|
|
436
|
-
postSnapshot = await docSnapshot.buildMutationSnapshot(doc);
|
|
452
|
+
const post = await context.finish();
|
|
453
|
+
const saveReopen = (verifyStep?.kind === 'verify' && (verifyStep.saveReopen || verificationNeedsSaveReopen(checks))) ||
|
|
454
|
+
planTouchesRiskyDomain(plan)
|
|
455
|
+
? await trySaveReopen(doc, checks)
|
|
456
|
+
: undefined;
|
|
457
|
+
const verification = pre.complete && post.complete
|
|
458
|
+
? computeDeltaChecks(pre.complete, post.complete, checks, saveReopen)
|
|
459
|
+
: await executionContext.evaluateFactChecks(pre, post, checks);
|
|
460
|
+
if (!post.complete && checks.some((check) => !['revision-changed', 'revision-unchanged'].includes(check.kind)))
|
|
461
|
+
await post.fence();
|
|
462
|
+
return context.receipt(plan.intent, verification, { saveReopen });
|
|
437
463
|
}
|
|
438
464
|
catch (error) {
|
|
439
|
-
|
|
440
|
-
throw error;
|
|
441
|
-
return {
|
|
442
|
-
status: 'failed',
|
|
443
|
-
intent: plan.intent,
|
|
444
|
-
preSnapshot: { revision: preSnapshot.revision, counts: preSnapshot.counts },
|
|
445
|
-
selectedTargets,
|
|
446
|
-
executedOperations,
|
|
447
|
-
verification: [],
|
|
448
|
-
errors: [{ code: error.code, message: error.message, recovery: { kind: 'reinspect' } }],
|
|
449
|
-
};
|
|
450
|
-
}
|
|
451
|
-
const verifyStep = plan.steps.find((s) => s.kind === 'verify');
|
|
452
|
-
let saveReopen;
|
|
453
|
-
const shouldSaveReopen = (verifyStep?.kind === 'verify' && (verifyStep.saveReopen || verificationNeedsSaveReopen(verifyStep.checks))) ||
|
|
454
|
-
planTouchesRiskyDomain(plan);
|
|
455
|
-
if (shouldSaveReopen) {
|
|
456
|
-
saveReopen = await trySaveReopen(doc, verifyStep?.kind === 'verify' ? verifyStep.checks : []);
|
|
465
|
+
return context.failure(plan.intent, error);
|
|
457
466
|
}
|
|
458
|
-
const verification = verifyStep?.kind === 'verify' ? computeDeltaChecks(preSnapshot, postSnapshot, verifyStep.checks, saveReopen) : [];
|
|
459
|
-
const allVerified = verification.every((v) => v.passed);
|
|
460
|
-
return {
|
|
461
|
-
status: allVerified ? 'ok' : 'failed',
|
|
462
|
-
intent: plan.intent,
|
|
463
|
-
preSnapshot: { revision: preSnapshot.revision, counts: preSnapshot.counts },
|
|
464
|
-
postSnapshot: { revision: postSnapshot.revision, counts: postSnapshot.counts },
|
|
465
|
-
selectedTargets,
|
|
466
|
-
executedOperations,
|
|
467
|
-
verification,
|
|
468
|
-
saveReopen,
|
|
469
|
-
};
|
|
470
467
|
}
|
|
471
468
|
async function agentVerify(doc, args) {
|
|
472
|
-
|
|
469
|
+
const context = new executionContext.ExecutionContext(doc, { evidence: args.evidence, checks: args.checks });
|
|
473
470
|
try {
|
|
474
|
-
|
|
471
|
+
const current = await context.start();
|
|
472
|
+
context.post = current;
|
|
473
|
+
const saveReopen = args.saveReopen || verificationNeedsSaveReopen(args.checks) ? await trySaveReopen(doc, args.checks) : undefined;
|
|
474
|
+
const verification = current.complete
|
|
475
|
+
? computeCurrentChecks(current.complete, args.checks, saveReopen)
|
|
476
|
+
: await executionContext.evaluateFactChecks(undefined, current, args.checks);
|
|
477
|
+
if (!current.complete)
|
|
478
|
+
await current.fence();
|
|
479
|
+
return context.receipt('verify', verification, { saveReopen });
|
|
475
480
|
}
|
|
476
481
|
catch (error) {
|
|
477
|
-
|
|
478
|
-
throw error;
|
|
479
|
-
return {
|
|
480
|
-
status: 'failed',
|
|
481
|
-
intent: 'verify',
|
|
482
|
-
preSnapshot: { revision: 'unknown', counts: emptyCounts() },
|
|
483
|
-
selectedTargets: [],
|
|
484
|
-
executedOperations: [],
|
|
485
|
-
verification: [],
|
|
486
|
-
errors: [{ code: error.code, message: error.message, recovery: { kind: 'retry' } }],
|
|
487
|
-
};
|
|
488
|
-
}
|
|
489
|
-
let saveReopen;
|
|
490
|
-
if (args.saveReopen || verificationNeedsSaveReopen(args.checks)) {
|
|
491
|
-
saveReopen = await trySaveReopen(doc, args.checks);
|
|
482
|
+
return context.failure('verify', error);
|
|
492
483
|
}
|
|
493
|
-
const verification = computeCurrentChecks(snapshot, args.checks, saveReopen);
|
|
494
|
-
const allPassed = verification.every((v) => v.passed);
|
|
495
|
-
return {
|
|
496
|
-
status: allPassed ? 'ok' : 'failed',
|
|
497
|
-
intent: 'verify',
|
|
498
|
-
preSnapshot: { revision: snapshot.revision, counts: snapshot.counts },
|
|
499
|
-
postSnapshot: { revision: snapshot.revision, counts: snapshot.counts },
|
|
500
|
-
selectedTargets: [],
|
|
501
|
-
executedOperations: [],
|
|
502
|
-
verification,
|
|
503
|
-
saveReopen,
|
|
504
|
-
};
|
|
505
484
|
}
|
|
506
485
|
/**
|
|
507
486
|
* Controlled escape hatch — dispatches a single generated operation by id.
|
|
@@ -529,27 +508,6 @@ async function agentOperation(doc, args) {
|
|
|
529
508
|
const method = resolveDocMethod(doc, args.operationId);
|
|
530
509
|
return method(callArgs);
|
|
531
510
|
}
|
|
532
|
-
function emptyCounts() {
|
|
533
|
-
return {
|
|
534
|
-
blocks: 0,
|
|
535
|
-
paragraphs: 0,
|
|
536
|
-
headings: 0,
|
|
537
|
-
tables: 0,
|
|
538
|
-
lists: 0,
|
|
539
|
-
images: 0,
|
|
540
|
-
comments: 0,
|
|
541
|
-
trackedChanges: 0,
|
|
542
|
-
sections: 0,
|
|
543
|
-
fields: 0,
|
|
544
|
-
hyperlinks: 0,
|
|
545
|
-
bookmarks: 0,
|
|
546
|
-
contentControls: 0,
|
|
547
|
-
permissionRanges: 0,
|
|
548
|
-
styles: 0,
|
|
549
|
-
headers: 0,
|
|
550
|
-
footers: 0,
|
|
551
|
-
};
|
|
552
|
-
}
|
|
553
511
|
|
|
554
512
|
exports.agentApply = agentApply;
|
|
555
513
|
exports.agentInspect = agentInspect;
|
package/dist/agent/runtime.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type EvidencePolicy } from './execution-context.js';
|
|
1
2
|
/**
|
|
2
3
|
* Clean agent runtime.
|
|
3
4
|
*
|
|
@@ -43,9 +44,11 @@ export type AgentInspectArgs = {
|
|
|
43
44
|
blockRunsLimit?: number;
|
|
44
45
|
};
|
|
45
46
|
export type AgentApplyArgs = {
|
|
47
|
+
evidence?: EvidencePolicy;
|
|
46
48
|
plan: AgentPlan;
|
|
47
49
|
};
|
|
48
50
|
export type AgentVerifyArgs = {
|
|
51
|
+
evidence?: EvidencePolicy;
|
|
49
52
|
checks: readonly AgentVerificationCheck[];
|
|
50
53
|
saveReopen?: boolean;
|
|
51
54
|
};
|
package/dist/agent/runtime.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
import { ExecutionContext, evaluateFactChecks } from './execution-context.js';
|
|
1
2
|
import { CONTRACT } from '../generated/contract.js';
|
|
2
3
|
import { SuperDocCliError } from '../runtime/errors.js';
|
|
3
4
|
import { validatePlan } from './ir.js';
|
|
4
|
-
import { buildDocumentSnapshot, buildMutationSnapshot, resolveSnapshotSelector, AmbiguousSelectorError,
|
|
5
|
+
import { buildDocumentSnapshot, buildMutationSnapshot, resolveSnapshotSelector, AmbiguousSelectorError, } from './doc-snapshot.js';
|
|
5
6
|
import { getOperationCatalogEntry } from './operation-catalog.js';
|
|
6
7
|
const RESERVED_ARG_KEYS = new Set(['sessionId', 'doc']);
|
|
7
8
|
function ensureKnownOperation(operationId) {
|
|
@@ -333,172 +334,150 @@ async function trySaveReopen(doc, checks) {
|
|
|
333
334
|
function verificationNeedsSaveReopen(checks) {
|
|
334
335
|
return checks.some((check) => check.kind === 'document-saves-cleanly' || check.kind === 'save-reopen-text-contains');
|
|
335
336
|
}
|
|
337
|
+
function revisionOnlyParagraphStep(plan) {
|
|
338
|
+
if (plan.atomic || plan.preconditions || plan.postconditions || plan.expectedDiff || plan.steps.length !== 2) {
|
|
339
|
+
return undefined;
|
|
340
|
+
}
|
|
341
|
+
const [apply, verify] = plan.steps;
|
|
342
|
+
if (apply?.kind !== 'apply' ||
|
|
343
|
+
apply.operationId !== 'doc.create.paragraph' ||
|
|
344
|
+
apply.atomic ||
|
|
345
|
+
verify?.kind !== 'verify' ||
|
|
346
|
+
verify.saveReopen ||
|
|
347
|
+
verify.checks.length === 0 ||
|
|
348
|
+
!verify.checks.every((check) => check.kind === 'revision-changed' || check.kind === 'revision-unchanged')) {
|
|
349
|
+
return undefined;
|
|
350
|
+
}
|
|
351
|
+
// Only literal body inputs are eligible. Binding tokens, story targets and
|
|
352
|
+
// additional operation options must retain the complete evidence path.
|
|
353
|
+
if (Object.keys(apply.args).some((key) => key !== 'text' && key !== 'at'))
|
|
354
|
+
return undefined;
|
|
355
|
+
if (apply.args.text !== undefined && typeof apply.args.text !== 'string')
|
|
356
|
+
return undefined;
|
|
357
|
+
const at = apply.args.at;
|
|
358
|
+
if (at !== undefined) {
|
|
359
|
+
if (!isRecord(at))
|
|
360
|
+
return undefined;
|
|
361
|
+
if (at.kind === 'documentStart' || at.kind === 'documentEnd') {
|
|
362
|
+
if (Object.keys(at).some((key) => key !== 'kind'))
|
|
363
|
+
return undefined;
|
|
364
|
+
}
|
|
365
|
+
else if (at.kind === 'before' || at.kind === 'after') {
|
|
366
|
+
if (Object.keys(at).some((key) => key !== 'kind' && key !== 'target') ||
|
|
367
|
+
!isRecord(at.target) ||
|
|
368
|
+
at.target.kind !== 'block' ||
|
|
369
|
+
typeof at.target.nodeId !== 'string' ||
|
|
370
|
+
typeof at.target.nodeType !== 'string' ||
|
|
371
|
+
Object.keys(at.target).some((key) => !['kind', 'nodeId', 'nodeType'].includes(key))) {
|
|
372
|
+
return undefined;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
else
|
|
376
|
+
return undefined;
|
|
377
|
+
}
|
|
378
|
+
return apply;
|
|
379
|
+
}
|
|
336
380
|
export async function agentApply(doc, args) {
|
|
337
381
|
const plan = args.plan;
|
|
338
382
|
const validation = validatePlan(plan);
|
|
339
|
-
if (!validation.ok)
|
|
340
|
-
return {
|
|
341
|
-
status: 'failed',
|
|
342
|
-
intent: plan.intent,
|
|
343
|
-
preSnapshot: { revision: 'unknown', counts: emptyCounts() },
|
|
344
|
-
selectedTargets: [],
|
|
345
|
-
executedOperations: [],
|
|
346
|
-
verification: [],
|
|
347
|
-
errors: validation.errors.map((e) => ({ code: e.code, message: e.message })),
|
|
348
|
-
};
|
|
349
|
-
}
|
|
350
|
-
let preSnapshot;
|
|
351
|
-
try {
|
|
352
|
-
preSnapshot = await buildMutationSnapshot(doc);
|
|
353
|
-
}
|
|
354
|
-
catch (error) {
|
|
355
|
-
if (!(error instanceof MutationSnapshotError))
|
|
356
|
-
throw error;
|
|
383
|
+
if (!validation.ok)
|
|
357
384
|
return {
|
|
358
385
|
status: 'failed',
|
|
359
386
|
intent: plan.intent,
|
|
360
|
-
preSnapshot: { revision: 'unknown', counts: emptyCounts() },
|
|
361
387
|
selectedTargets: [],
|
|
362
388
|
executedOperations: [],
|
|
363
389
|
verification: [],
|
|
364
|
-
errors:
|
|
390
|
+
errors: validation.errors.map((error) => ({ code: error.code, message: error.message })),
|
|
365
391
|
};
|
|
366
|
-
|
|
367
|
-
const
|
|
368
|
-
|
|
392
|
+
const verifyStep = plan.steps.find((step) => step.kind === 'verify');
|
|
393
|
+
const checks = verifyStep?.kind === 'verify' ? verifyStep.checks : [];
|
|
394
|
+
let written = false;
|
|
395
|
+
const baselineAfterWrite = plan.steps.some((step) => {
|
|
396
|
+
if (step.kind === 'apply')
|
|
397
|
+
written = true;
|
|
398
|
+
return written && step.kind === 'select' && step.selector.kind !== 'ref';
|
|
399
|
+
});
|
|
400
|
+
const context = new ExecutionContext(doc, {
|
|
401
|
+
evidence: args.evidence,
|
|
402
|
+
revisionOnly: !!revisionOnlyParagraphStep(plan),
|
|
403
|
+
checks,
|
|
404
|
+
selectors: plan.steps.flatMap((step) => step.kind === 'select' && step.selector.kind !== 'ref' ? [step.selector] : []),
|
|
405
|
+
completeReason: plan.atomic ||
|
|
406
|
+
plan.preconditions ||
|
|
407
|
+
plan.postconditions ||
|
|
408
|
+
plan.expectedDiff ||
|
|
409
|
+
plan.steps.some((step) => step.kind === 'inspect') ||
|
|
410
|
+
plan.steps.filter((step) => step.kind === 'apply').length > 1
|
|
411
|
+
? 'plan requires complete baseline evidence'
|
|
412
|
+
: baselineAfterWrite
|
|
413
|
+
? 'plan selects baseline targets after a write'
|
|
414
|
+
: planTouchesRiskyDomain(plan)
|
|
415
|
+
? 'plan requires document-wide save/reopen evidence'
|
|
416
|
+
: plan.steps.some((step) => step.kind === 'apply' &&
|
|
417
|
+
['doc.history.undo', 'doc.history.redo', 'doc.plan.execute'].includes(step.operationId))
|
|
418
|
+
? 'operation requires complete execution evidence'
|
|
419
|
+
: undefined,
|
|
420
|
+
});
|
|
369
421
|
const bindings = new Map();
|
|
370
422
|
try {
|
|
423
|
+
const pre = await context.start();
|
|
371
424
|
for (const step of plan.steps) {
|
|
372
425
|
if (step.kind === 'select') {
|
|
373
|
-
const matched =
|
|
374
|
-
|
|
426
|
+
const matched = pre.complete
|
|
427
|
+
? resolveSelectorWithBindings(pre.complete, step.selector, bindings)
|
|
428
|
+
: step.selector.kind === 'ref'
|
|
429
|
+
? extractBoundNodeIds(resolveBindingRef(bindings, step.selector.ref))
|
|
430
|
+
: await pre.select(step.selector);
|
|
431
|
+
if (step.requireUnique && matched.length !== 1)
|
|
375
432
|
throw new AmbiguousSelectorError(`Selector did not resolve uniquely (matched ${matched.length}).`, matched.map((nodeId) => ({ nodeId, description: nodeId })));
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
if (step.bind) {
|
|
433
|
+
context.selectedTargets.push({ selector: step.selector, matched });
|
|
434
|
+
if (step.bind)
|
|
379
435
|
bindings.set(step.bind, matched.length === 1 ? matched[0] : [...matched]);
|
|
380
|
-
}
|
|
381
|
-
continue;
|
|
382
436
|
}
|
|
383
|
-
if (step.kind === 'inspect') {
|
|
384
|
-
const
|
|
385
|
-
const method = resolveDocMethod(
|
|
386
|
-
const result = await method(
|
|
387
|
-
|
|
437
|
+
else if (step.kind === 'inspect' || step.kind === 'apply') {
|
|
438
|
+
const input = ensureClean(resolveBindingTokens(step.args, bindings));
|
|
439
|
+
const method = resolveDocMethod(context.document, step.operationId);
|
|
440
|
+
const result = await method(step.kind === 'apply' && step.changeMode && getOperationCatalogEntry(step.operationId)?.supportsChangeMode
|
|
441
|
+
? { ...input, changeMode: step.changeMode }
|
|
442
|
+
: input);
|
|
443
|
+
if (step.kind === 'inspect' && step.bind)
|
|
388
444
|
bindings.set(step.bind, result);
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
if (step.kind === 'apply') {
|
|
392
|
-
ensureKnownOperation(step.operationId);
|
|
393
|
-
const applyArgs = ensureClean(resolveBindingTokens(step.args, bindings));
|
|
394
|
-
const method = resolveDocMethod(doc, step.operationId);
|
|
395
|
-
const argsWithMode = step.changeMode != null && getOperationCatalogEntry(step.operationId)?.supportsChangeMode
|
|
396
|
-
? { ...applyArgs, changeMode: step.changeMode }
|
|
397
|
-
: applyArgs;
|
|
398
|
-
const result = await method(argsWithMode);
|
|
399
|
-
executedOperations.push({ operationId: step.operationId, rationale: step.rationale, result });
|
|
445
|
+
if (step.kind === 'apply' && context.executedOperations.length)
|
|
446
|
+
context.executedOperations.at(-1).rationale = step.rationale;
|
|
400
447
|
}
|
|
401
448
|
}
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
{
|
|
414
|
-
code: err.code,
|
|
415
|
-
message: err.message,
|
|
416
|
-
},
|
|
417
|
-
],
|
|
418
|
-
};
|
|
419
|
-
}
|
|
420
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
421
|
-
return {
|
|
422
|
-
status: 'failed',
|
|
423
|
-
intent: plan.intent,
|
|
424
|
-
preSnapshot: { revision: preSnapshot.revision, counts: preSnapshot.counts },
|
|
425
|
-
selectedTargets,
|
|
426
|
-
executedOperations,
|
|
427
|
-
verification: [],
|
|
428
|
-
errors: [{ code: 'APPLY_FAILED', message }],
|
|
429
|
-
};
|
|
430
|
-
}
|
|
431
|
-
let postSnapshot;
|
|
432
|
-
try {
|
|
433
|
-
postSnapshot = await buildMutationSnapshot(doc);
|
|
449
|
+
const post = await context.finish();
|
|
450
|
+
const saveReopen = (verifyStep?.kind === 'verify' && (verifyStep.saveReopen || verificationNeedsSaveReopen(checks))) ||
|
|
451
|
+
planTouchesRiskyDomain(plan)
|
|
452
|
+
? await trySaveReopen(doc, checks)
|
|
453
|
+
: undefined;
|
|
454
|
+
const verification = pre.complete && post.complete
|
|
455
|
+
? computeDeltaChecks(pre.complete, post.complete, checks, saveReopen)
|
|
456
|
+
: await evaluateFactChecks(pre, post, checks);
|
|
457
|
+
if (!post.complete && checks.some((check) => !['revision-changed', 'revision-unchanged'].includes(check.kind)))
|
|
458
|
+
await post.fence();
|
|
459
|
+
return context.receipt(plan.intent, verification, { saveReopen });
|
|
434
460
|
}
|
|
435
461
|
catch (error) {
|
|
436
|
-
|
|
437
|
-
throw error;
|
|
438
|
-
return {
|
|
439
|
-
status: 'failed',
|
|
440
|
-
intent: plan.intent,
|
|
441
|
-
preSnapshot: { revision: preSnapshot.revision, counts: preSnapshot.counts },
|
|
442
|
-
selectedTargets,
|
|
443
|
-
executedOperations,
|
|
444
|
-
verification: [],
|
|
445
|
-
errors: [{ code: error.code, message: error.message, recovery: { kind: 'reinspect' } }],
|
|
446
|
-
};
|
|
447
|
-
}
|
|
448
|
-
const verifyStep = plan.steps.find((s) => s.kind === 'verify');
|
|
449
|
-
let saveReopen;
|
|
450
|
-
const shouldSaveReopen = (verifyStep?.kind === 'verify' && (verifyStep.saveReopen || verificationNeedsSaveReopen(verifyStep.checks))) ||
|
|
451
|
-
planTouchesRiskyDomain(plan);
|
|
452
|
-
if (shouldSaveReopen) {
|
|
453
|
-
saveReopen = await trySaveReopen(doc, verifyStep?.kind === 'verify' ? verifyStep.checks : []);
|
|
462
|
+
return context.failure(plan.intent, error);
|
|
454
463
|
}
|
|
455
|
-
const verification = verifyStep?.kind === 'verify' ? computeDeltaChecks(preSnapshot, postSnapshot, verifyStep.checks, saveReopen) : [];
|
|
456
|
-
const allVerified = verification.every((v) => v.passed);
|
|
457
|
-
return {
|
|
458
|
-
status: allVerified ? 'ok' : 'failed',
|
|
459
|
-
intent: plan.intent,
|
|
460
|
-
preSnapshot: { revision: preSnapshot.revision, counts: preSnapshot.counts },
|
|
461
|
-
postSnapshot: { revision: postSnapshot.revision, counts: postSnapshot.counts },
|
|
462
|
-
selectedTargets,
|
|
463
|
-
executedOperations,
|
|
464
|
-
verification,
|
|
465
|
-
saveReopen,
|
|
466
|
-
};
|
|
467
464
|
}
|
|
468
465
|
export async function agentVerify(doc, args) {
|
|
469
|
-
|
|
466
|
+
const context = new ExecutionContext(doc, { evidence: args.evidence, checks: args.checks });
|
|
470
467
|
try {
|
|
471
|
-
|
|
468
|
+
const current = await context.start();
|
|
469
|
+
context.post = current;
|
|
470
|
+
const saveReopen = args.saveReopen || verificationNeedsSaveReopen(args.checks) ? await trySaveReopen(doc, args.checks) : undefined;
|
|
471
|
+
const verification = current.complete
|
|
472
|
+
? computeCurrentChecks(current.complete, args.checks, saveReopen)
|
|
473
|
+
: await evaluateFactChecks(undefined, current, args.checks);
|
|
474
|
+
if (!current.complete)
|
|
475
|
+
await current.fence();
|
|
476
|
+
return context.receipt('verify', verification, { saveReopen });
|
|
472
477
|
}
|
|
473
478
|
catch (error) {
|
|
474
|
-
|
|
475
|
-
throw error;
|
|
476
|
-
return {
|
|
477
|
-
status: 'failed',
|
|
478
|
-
intent: 'verify',
|
|
479
|
-
preSnapshot: { revision: 'unknown', counts: emptyCounts() },
|
|
480
|
-
selectedTargets: [],
|
|
481
|
-
executedOperations: [],
|
|
482
|
-
verification: [],
|
|
483
|
-
errors: [{ code: error.code, message: error.message, recovery: { kind: 'retry' } }],
|
|
484
|
-
};
|
|
485
|
-
}
|
|
486
|
-
let saveReopen;
|
|
487
|
-
if (args.saveReopen || verificationNeedsSaveReopen(args.checks)) {
|
|
488
|
-
saveReopen = await trySaveReopen(doc, args.checks);
|
|
479
|
+
return context.failure('verify', error);
|
|
489
480
|
}
|
|
490
|
-
const verification = computeCurrentChecks(snapshot, args.checks, saveReopen);
|
|
491
|
-
const allPassed = verification.every((v) => v.passed);
|
|
492
|
-
return {
|
|
493
|
-
status: allPassed ? 'ok' : 'failed',
|
|
494
|
-
intent: 'verify',
|
|
495
|
-
preSnapshot: { revision: snapshot.revision, counts: snapshot.counts },
|
|
496
|
-
postSnapshot: { revision: snapshot.revision, counts: snapshot.counts },
|
|
497
|
-
selectedTargets: [],
|
|
498
|
-
executedOperations: [],
|
|
499
|
-
verification,
|
|
500
|
-
saveReopen,
|
|
501
|
-
};
|
|
502
481
|
}
|
|
503
482
|
/**
|
|
504
483
|
* Controlled escape hatch — dispatches a single generated operation by id.
|
|
@@ -526,24 +505,3 @@ export async function agentOperation(doc, args) {
|
|
|
526
505
|
const method = resolveDocMethod(doc, args.operationId);
|
|
527
506
|
return method(callArgs);
|
|
528
507
|
}
|
|
529
|
-
function emptyCounts() {
|
|
530
|
-
return {
|
|
531
|
-
blocks: 0,
|
|
532
|
-
paragraphs: 0,
|
|
533
|
-
headings: 0,
|
|
534
|
-
tables: 0,
|
|
535
|
-
lists: 0,
|
|
536
|
-
images: 0,
|
|
537
|
-
comments: 0,
|
|
538
|
-
trackedChanges: 0,
|
|
539
|
-
sections: 0,
|
|
540
|
-
fields: 0,
|
|
541
|
-
hyperlinks: 0,
|
|
542
|
-
bookmarks: 0,
|
|
543
|
-
contentControls: 0,
|
|
544
|
-
permissionRanges: 0,
|
|
545
|
-
styles: 0,
|
|
546
|
-
headers: 0,
|
|
547
|
-
footers: 0,
|
|
548
|
-
};
|
|
549
|
-
}
|