@superdoc/sdk 2.9.1-next.2 → 2.10.0-next.10
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 +384 -373
- package/dist/agent/actions.d.ts +5 -0
- package/dist/agent/actions.js +385 -374
- package/dist/agent/catalog.cjs +15 -0
- package/dist/agent/catalog.js +15 -0
- package/dist/agent/doc-snapshot.cjs +205 -86
- package/dist/agent/doc-snapshot.d.ts +11 -0
- package/dist/agent/doc-snapshot.js +203 -86
- 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 +123 -117
- package/dist/agent/runtime.d.ts +3 -0
- package/dist/agent/runtime.js +124 -118
- 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 +85 -0
- package/dist/generated/client.js +2 -0
- package/dist/generated/contract.cjs +1796 -1297
- package/dist/generated/contract.js +1797 -1297
- package/dist/index.cjs +23 -0
- package/dist/index.d.ts +7 -2
- package/dist/index.js +23 -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/document-rpc.cjs +27 -0
- package/dist/runtime/document-rpc.d.ts +2 -0
- package/dist/runtime/document-rpc.js +25 -0
- package/dist/runtime/host.cjs +65 -4
- package/dist/runtime/host.d.ts +3 -0
- package/dist/runtime/host.js +66 -5
- package/dist/runtime/process.cjs +38 -0
- package/dist/runtime/process.d.ts +16 -0
- package/dist/runtime/process.js +38 -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 -8
- package/tools/catalog.json +89 -0
- package/tools/tools-policy.json +1 -1
- package/tools/tools.anthropic.json +89 -0
- package/tools/tools.generic.json +89 -0
- package/tools/tools.openai.json +89 -0
- package/tools/tools.vercel.json +89 -0
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
import { getDocumentRevisionReader, supportsDocumentFacts, forwardDocumentEvidence, getMutationRevision, } from '../runtime/document-evidence.js';
|
|
2
|
+
import { SuperDocCliError } from '../runtime/errors.js';
|
|
3
|
+
import { buildDocumentSnapshot, buildMutationSnapshot, resolveSnapshotSelector, } from './doc-snapshot.js';
|
|
4
|
+
import { getOperationCatalogEntry } from './operation-catalog.js';
|
|
5
|
+
export function supportsLocalSelector(selector) {
|
|
6
|
+
return (selector.kind === 'nodeId' ||
|
|
7
|
+
selector.kind === 'textSearch' ||
|
|
8
|
+
selector.kind === 'placement' ||
|
|
9
|
+
(selector.kind === 'ordinal' &&
|
|
10
|
+
['blockOrdinal', 'headingOrdinal', 'tableOrdinal'].includes(selector.ordinalKind)) ||
|
|
11
|
+
(selector.kind === 'relative' && supportsLocalSelector(selector.target)));
|
|
12
|
+
}
|
|
13
|
+
function localCheck(check) {
|
|
14
|
+
return (['revision-changed', 'revision-unchanged', 'block-text-contains', 'block-text-equals', 'table-shape'].includes(check.kind) ||
|
|
15
|
+
(check.kind === 'block-count-delta' && ['paragraph', 'heading', 'listItem', 'table'].includes(check.nodeType)));
|
|
16
|
+
}
|
|
17
|
+
export async function readPublicRevision(doc) {
|
|
18
|
+
const revision = await getDocumentRevisionReader(doc)?.();
|
|
19
|
+
if (revision !== undefined)
|
|
20
|
+
return revision;
|
|
21
|
+
const page = await doc.blocks.list({ offset: Number.MAX_SAFE_INTEGER, limit: 1 });
|
|
22
|
+
if (!page.revision || page.revision === 'unknown')
|
|
23
|
+
throw new SuperDocCliError('Required document revision is unavailable.', { code: 'EVIDENCE_UNAVAILABLE' });
|
|
24
|
+
return page.revision;
|
|
25
|
+
}
|
|
26
|
+
export class DocumentFacts {
|
|
27
|
+
doc;
|
|
28
|
+
revision;
|
|
29
|
+
complete;
|
|
30
|
+
summaryCounts;
|
|
31
|
+
blocks = new Map();
|
|
32
|
+
counts = new Map();
|
|
33
|
+
selections = new Map();
|
|
34
|
+
constructor(doc, revision, complete, summaryCounts = complete?.counts) {
|
|
35
|
+
this.doc = doc;
|
|
36
|
+
this.revision = revision;
|
|
37
|
+
this.complete = complete;
|
|
38
|
+
this.summaryCounts = summaryCounts;
|
|
39
|
+
}
|
|
40
|
+
get summary() {
|
|
41
|
+
return { revision: this.revision, ...(this.summaryCounts ? { counts: this.summaryCounts } : {}) };
|
|
42
|
+
}
|
|
43
|
+
async fence() {
|
|
44
|
+
if ((await readPublicRevision(this.doc)) !== this.revision)
|
|
45
|
+
throw new SuperDocCliError('Document changed while acquiring execution evidence.', { code: 'REVISION_CONFLICT' });
|
|
46
|
+
}
|
|
47
|
+
async page(input) {
|
|
48
|
+
const result = await this.doc.blocks.list({ ...input, includeText: true });
|
|
49
|
+
if (result.revision !== this.revision)
|
|
50
|
+
throw new SuperDocCliError('Document changed while resolving a target.', { code: 'REVISION_CONFLICT' });
|
|
51
|
+
const blocks = result.blocks.map((block) => ({
|
|
52
|
+
...block,
|
|
53
|
+
ordinal: block.ordinal + 1,
|
|
54
|
+
text: block.text ?? '',
|
|
55
|
+
textPreview: block.textPreview ?? null,
|
|
56
|
+
styleId: block.styleId ?? null,
|
|
57
|
+
}));
|
|
58
|
+
for (const block of blocks)
|
|
59
|
+
this.blocks.set(block.nodeId, block);
|
|
60
|
+
return blocks;
|
|
61
|
+
}
|
|
62
|
+
async count(nodeType) {
|
|
63
|
+
const key = nodeType ?? '*';
|
|
64
|
+
if (this.counts.has(key))
|
|
65
|
+
return this.counts.get(key);
|
|
66
|
+
if (this.complete)
|
|
67
|
+
return nodeType
|
|
68
|
+
? this.complete.blocks.filter((block) => block.nodeType === nodeType).length
|
|
69
|
+
: this.complete.blocks.length;
|
|
70
|
+
const result = await this.doc.blocks.list({
|
|
71
|
+
offset: Number.MAX_SAFE_INTEGER,
|
|
72
|
+
limit: 1,
|
|
73
|
+
...(nodeType ? { nodeTypes: [nodeType] } : {}),
|
|
74
|
+
});
|
|
75
|
+
if (result.revision !== this.revision || !Number.isSafeInteger(result.total))
|
|
76
|
+
throw new SuperDocCliError('Required count is unavailable at the execution revision.', {
|
|
77
|
+
code: 'EVIDENCE_UNAVAILABLE',
|
|
78
|
+
});
|
|
79
|
+
this.counts.set(key, result.total);
|
|
80
|
+
return result.total;
|
|
81
|
+
}
|
|
82
|
+
async block(nodeId) {
|
|
83
|
+
if (this.complete)
|
|
84
|
+
return this.complete.blocks.find((block) => block.nodeId === nodeId);
|
|
85
|
+
if (!this.blocks.has(nodeId))
|
|
86
|
+
await this.page({ nodeIds: [nodeId], limit: 1 });
|
|
87
|
+
return this.blocks.get(nodeId);
|
|
88
|
+
}
|
|
89
|
+
async select(selector) {
|
|
90
|
+
if (this.complete)
|
|
91
|
+
return resolveSnapshotSelector(this.complete, selector);
|
|
92
|
+
const key = JSON.stringify(selector);
|
|
93
|
+
if (this.selections.has(key))
|
|
94
|
+
return this.selections.get(key);
|
|
95
|
+
let blocks = [];
|
|
96
|
+
if (selector.kind === 'nodeId') {
|
|
97
|
+
const block = await this.block(selector.nodeId);
|
|
98
|
+
if (block)
|
|
99
|
+
blocks = [block];
|
|
100
|
+
}
|
|
101
|
+
else if (selector.kind === 'textSearch') {
|
|
102
|
+
const terms = selector.terms.filter((term) => term.trim());
|
|
103
|
+
if (terms.length && selector.nodeTypes?.length !== 0)
|
|
104
|
+
blocks = await this.page({
|
|
105
|
+
textSearch: { terms: [...terms], match: selector.match, caseSensitive: selector.caseSensitive },
|
|
106
|
+
nodeTypes: [...(selector.nodeTypes ?? ['paragraph', 'heading', 'listItem'])],
|
|
107
|
+
offset: (selector.occurrence ?? 1) - 1,
|
|
108
|
+
limit: 1,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
else if (selector.kind === 'placement') {
|
|
112
|
+
const offset = selector.at === 'document_start' ? 0 : (await this.count()) - 1;
|
|
113
|
+
if (offset >= 0)
|
|
114
|
+
blocks = await this.page({ offset, limit: 1 });
|
|
115
|
+
}
|
|
116
|
+
else if (selector.kind === 'ordinal') {
|
|
117
|
+
blocks = await this.page({
|
|
118
|
+
offset: selector.value - 1,
|
|
119
|
+
limit: 1,
|
|
120
|
+
...(selector.ordinalKind === 'headingOrdinal'
|
|
121
|
+
? { nodeTypes: ['heading'] }
|
|
122
|
+
: selector.ordinalKind === 'tableOrdinal'
|
|
123
|
+
? { nodeTypes: ['table'] }
|
|
124
|
+
: {}),
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
else if (selector.kind === 'relative') {
|
|
128
|
+
for (const id of await this.select(selector.target)) {
|
|
129
|
+
const target = await this.block(id);
|
|
130
|
+
const offset = target ? target.ordinal - 1 + (selector.position === 'before' ? -1 : 1) : -1;
|
|
131
|
+
if (offset >= 0)
|
|
132
|
+
blocks.push(...(await this.page({ offset, limit: 1 })));
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
else
|
|
136
|
+
throw new SuperDocCliError('Selector requires complete evidence.', { code: 'EVIDENCE_UNAVAILABLE' });
|
|
137
|
+
const ids = [...new Set(blocks.map((block) => block.nodeId))];
|
|
138
|
+
this.selections.set(key, ids);
|
|
139
|
+
return ids;
|
|
140
|
+
}
|
|
141
|
+
async target(selector) {
|
|
142
|
+
const ids = await this.select(selector);
|
|
143
|
+
if (this.complete && selector.kind === 'tableCell' && ids.length === 1) {
|
|
144
|
+
const cell = this.complete.tables.flatMap((table) => table.cells).find((cell) => cell.nodeId === ids[0]);
|
|
145
|
+
if (cell?.nodeId)
|
|
146
|
+
return {
|
|
147
|
+
nodeId: cell.nodeId,
|
|
148
|
+
nodeType: 'paragraph',
|
|
149
|
+
text: cell.text,
|
|
150
|
+
textPreview: cell.text,
|
|
151
|
+
styleId: null,
|
|
152
|
+
ordinal: 0,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
return ids.length === 1 ? this.block(ids[0]) : undefined;
|
|
156
|
+
}
|
|
157
|
+
async tableShape(nodeId) {
|
|
158
|
+
if (this.complete)
|
|
159
|
+
return this.complete.tables.find((table) => table.nodeId === nodeId);
|
|
160
|
+
return this.doc.tables.get({ nodeId });
|
|
161
|
+
}
|
|
162
|
+
async completeIndex(reason, acquire) {
|
|
163
|
+
const value = await acquire();
|
|
164
|
+
await this.fence();
|
|
165
|
+
return { value, coverage: 'complete', reason };
|
|
166
|
+
}
|
|
167
|
+
async prepare(checks) {
|
|
168
|
+
for (const check of checks)
|
|
169
|
+
if (check.kind === 'block-count-delta')
|
|
170
|
+
await this.count(check.nodeType);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
export class ExecutionContext {
|
|
174
|
+
source;
|
|
175
|
+
requirements;
|
|
176
|
+
document;
|
|
177
|
+
executedOperations = [];
|
|
178
|
+
selectedTargets = [];
|
|
179
|
+
pre;
|
|
180
|
+
post;
|
|
181
|
+
fallbackReason;
|
|
182
|
+
currentRevision;
|
|
183
|
+
legacyRevisionOnly = false;
|
|
184
|
+
constructor(source, requirements = {}) {
|
|
185
|
+
this.source = source;
|
|
186
|
+
this.requirements = requirements;
|
|
187
|
+
if (requirements.evidence !== undefined && !['required', 'full'].includes(requirements.evidence))
|
|
188
|
+
throw new SuperDocCliError('evidence must be required or full.', { code: 'INVALID_ARGUMENT' });
|
|
189
|
+
const wrap = (value, path) => new Proxy(value, {
|
|
190
|
+
get: (target, property, receiver) => {
|
|
191
|
+
const member = Reflect.get(target, property, receiver);
|
|
192
|
+
if (typeof property !== 'string')
|
|
193
|
+
return member;
|
|
194
|
+
const memberPath = [...path, property];
|
|
195
|
+
const operationId = `doc.${memberPath.join('.')}`;
|
|
196
|
+
if (typeof member === 'function' && getOperationCatalogEntry(operationId)?.isMutating) {
|
|
197
|
+
return async (args = {}, options) => {
|
|
198
|
+
if (operationId === 'doc.format.apply' &&
|
|
199
|
+
supportsDocumentFacts(this.source) &&
|
|
200
|
+
typeof args.blockId === 'string') {
|
|
201
|
+
const { blockId, start, end, ...rest } = args;
|
|
202
|
+
args = {
|
|
203
|
+
...rest,
|
|
204
|
+
target: {
|
|
205
|
+
kind: 'selection',
|
|
206
|
+
start: { kind: 'text', blockId, offset: start },
|
|
207
|
+
end: { kind: 'text', blockId, offset: end },
|
|
208
|
+
},
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
if (this.legacyRevisionOnly && this.executedOperations.length)
|
|
212
|
+
throw new SuperDocCliError('This host cannot bind revisions across multiple writes.', {
|
|
213
|
+
code: 'EVIDENCE_UNAVAILABLE',
|
|
214
|
+
});
|
|
215
|
+
const guarded = (supportsDocumentFacts(this.source) || this.legacyRevisionOnly) &&
|
|
216
|
+
!['doc.history.undo', 'doc.history.redo', 'doc.plan.execute'].includes(operationId);
|
|
217
|
+
const revision = guarded ? (this.currentRevision ?? (await readPublicRevision(this.source))) : undefined;
|
|
218
|
+
const result = await Reflect.apply(member, target, guarded
|
|
219
|
+
? [
|
|
220
|
+
{ ...args, expectedRevision: args.expectedRevision ?? revision },
|
|
221
|
+
{ ...options, expectedRevision: options?.expectedRevision ?? revision },
|
|
222
|
+
]
|
|
223
|
+
: [args, options]);
|
|
224
|
+
this.executedOperations.push({ operationId, result });
|
|
225
|
+
if (result?.success === false)
|
|
226
|
+
throw new SuperDocCliError('Document operation did not apply.', {
|
|
227
|
+
code: result.failure?.code ?? 'APPLY_FAILED',
|
|
228
|
+
details: result,
|
|
229
|
+
});
|
|
230
|
+
if (supportsDocumentFacts(this.source)) {
|
|
231
|
+
const applied = getMutationRevision(result);
|
|
232
|
+
if (!applied ||
|
|
233
|
+
applied.before !== this.currentRevision ||
|
|
234
|
+
!applied.after ||
|
|
235
|
+
applied.after === 'unknown')
|
|
236
|
+
throw new SuperDocCliError('Committed operation revision is unavailable or changed.', {
|
|
237
|
+
code: 'EVIDENCE_UNAVAILABLE',
|
|
238
|
+
});
|
|
239
|
+
this.currentRevision = applied.after;
|
|
240
|
+
}
|
|
241
|
+
return result;
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
if (member && typeof member === 'object')
|
|
245
|
+
return wrap(member, memberPath);
|
|
246
|
+
return member;
|
|
247
|
+
},
|
|
248
|
+
});
|
|
249
|
+
this.document = wrap(source, []);
|
|
250
|
+
forwardDocumentEvidence(source, this.document);
|
|
251
|
+
}
|
|
252
|
+
async start() {
|
|
253
|
+
const { evidence, selectors = [], checks = [], completeReason } = this.requirements;
|
|
254
|
+
const compactRevision = this.requirements.revisionOnly && evidence !== 'full' && !completeReason
|
|
255
|
+
? await getDocumentRevisionReader(this.source)?.()
|
|
256
|
+
: undefined;
|
|
257
|
+
this.legacyRevisionOnly = compactRevision !== undefined && !supportsDocumentFacts(this.source);
|
|
258
|
+
this.fallbackReason =
|
|
259
|
+
completeReason ??
|
|
260
|
+
(!supportsDocumentFacts(this.source) && !this.legacyRevisionOnly
|
|
261
|
+
? 'host lacks typed execution facts'
|
|
262
|
+
: selectors.some((selector) => !supportsLocalSelector(selector))
|
|
263
|
+
? 'selector requires complete index'
|
|
264
|
+
: checks.some((check) => !localCheck(check))
|
|
265
|
+
? 'verification requires complete evidence'
|
|
266
|
+
: undefined);
|
|
267
|
+
const complete = evidence === 'full' || this.fallbackReason ? await buildMutationSnapshot(this.source) : undefined;
|
|
268
|
+
if (complete && (complete.revision === 'unknown' || complete.diagnostics.some((item) => item.section === 'info')))
|
|
269
|
+
throw new SuperDocCliError('Required complete evidence is unavailable.', { code: 'EVIDENCE_UNAVAILABLE' });
|
|
270
|
+
this.pre = new DocumentFacts(this.source, complete?.revision ?? compactRevision ?? (await readPublicRevision(this.source)), complete);
|
|
271
|
+
this.currentRevision = this.pre.revision;
|
|
272
|
+
await this.pre.prepare(checks);
|
|
273
|
+
return this.pre;
|
|
274
|
+
}
|
|
275
|
+
async finishRevision() {
|
|
276
|
+
if (this.requirements.evidence === 'full')
|
|
277
|
+
return this.finish();
|
|
278
|
+
const summary = this.pre?.complete ? await buildDocumentSnapshot(this.source, { countsOnly: true }) : undefined;
|
|
279
|
+
if (summary && (summary.revision === 'unknown' || summary.diagnostics.some((item) => item.section === 'info')))
|
|
280
|
+
throw new SuperDocCliError('Required summary is unavailable.', { code: 'EVIDENCE_UNAVAILABLE' });
|
|
281
|
+
const revision = summary?.revision ?? (await readPublicRevision(this.source));
|
|
282
|
+
this.assertExecutionRevision(revision);
|
|
283
|
+
this.post = new DocumentFacts(this.source, revision, undefined, summary?.counts);
|
|
284
|
+
this.currentRevision = this.post.revision;
|
|
285
|
+
return this.post;
|
|
286
|
+
}
|
|
287
|
+
async finish() {
|
|
288
|
+
const complete = this.pre?.complete ? await buildMutationSnapshot(this.source) : undefined;
|
|
289
|
+
if (complete && (complete.revision === 'unknown' || complete.diagnostics.some((item) => item.section === 'info')))
|
|
290
|
+
throw new SuperDocCliError('Required complete evidence is unavailable.', { code: 'EVIDENCE_UNAVAILABLE' });
|
|
291
|
+
const revision = complete?.revision ?? (await readPublicRevision(this.source));
|
|
292
|
+
this.assertExecutionRevision(revision);
|
|
293
|
+
this.post = new DocumentFacts(this.source, revision, complete);
|
|
294
|
+
this.currentRevision = this.post.revision;
|
|
295
|
+
return this.post;
|
|
296
|
+
}
|
|
297
|
+
assertExecutionRevision(revision) {
|
|
298
|
+
if (supportsDocumentFacts(this.source) && this.currentRevision !== revision)
|
|
299
|
+
throw new SuperDocCliError('Document changed outside this execution.', { code: 'REVISION_CONFLICT' });
|
|
300
|
+
}
|
|
301
|
+
receipt(intent, verification, extra = {}) {
|
|
302
|
+
return {
|
|
303
|
+
status: verification.every((check) => check.passed) ? 'ok' : 'failed',
|
|
304
|
+
intent,
|
|
305
|
+
preSnapshot: this.pre?.summary,
|
|
306
|
+
postSnapshot: this.post?.summary,
|
|
307
|
+
selectedTargets: this.selectedTargets,
|
|
308
|
+
executedOperations: this.executedOperations,
|
|
309
|
+
verification,
|
|
310
|
+
...(this.fallbackReason ? { evidenceFallback: this.fallbackReason } : {}),
|
|
311
|
+
...extra,
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
failure(intent, error) {
|
|
315
|
+
const err = error;
|
|
316
|
+
return this.receipt(intent, [], {
|
|
317
|
+
status: err?.code === 'AMBIGUOUS_SELECTOR' ? 'aborted' : 'failed',
|
|
318
|
+
errors: [
|
|
319
|
+
{ code: err?.code ?? 'ACTION_FAILED', message: err?.message ?? String(error), recovery: { kind: 'reinspect' } },
|
|
320
|
+
],
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
export async function evaluateFactChecks(pre, post, checks) {
|
|
325
|
+
const results = [];
|
|
326
|
+
for (const check of checks) {
|
|
327
|
+
if ([
|
|
328
|
+
'revision-changed',
|
|
329
|
+
'revision-unchanged',
|
|
330
|
+
'block-count-delta',
|
|
331
|
+
'comment-count-delta',
|
|
332
|
+
'tracked-change-count-delta',
|
|
333
|
+
].includes(check.kind) &&
|
|
334
|
+
!pre) {
|
|
335
|
+
results.push({ check, passed: false, detail: `${check.kind} requires a baseline snapshot` });
|
|
336
|
+
continue;
|
|
337
|
+
}
|
|
338
|
+
if (check.kind === 'revision-changed' || check.kind === 'revision-unchanged')
|
|
339
|
+
results.push({
|
|
340
|
+
check,
|
|
341
|
+
passed: check.kind === 'revision-changed' ? pre.revision !== post.revision : pre.revision === post.revision,
|
|
342
|
+
detail: `pre=${pre.revision} post=${post.revision}`,
|
|
343
|
+
});
|
|
344
|
+
else if (check.kind === 'block-count-delta') {
|
|
345
|
+
const before = await pre.count(check.nodeType), after = await post.count(check.nodeType);
|
|
346
|
+
results.push({
|
|
347
|
+
check,
|
|
348
|
+
passed: after - before === check.delta,
|
|
349
|
+
detail: `pre=${before} post=${after} delta=${after - before}`,
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
else if (check.kind === 'block-text-contains' || check.kind === 'block-text-equals') {
|
|
353
|
+
const block = await post.block(check.nodeId);
|
|
354
|
+
results.push({
|
|
355
|
+
check,
|
|
356
|
+
passed: !!block && (check.kind === 'block-text-equals' ? block.text === check.text : block.text.includes(check.text)),
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
else if (check.kind === 'table-shape') {
|
|
360
|
+
const table = await post.tableShape(check.nodeId);
|
|
361
|
+
results.push({ check, passed: !!table && table.rows === check.rows && table.columns === check.columns });
|
|
362
|
+
}
|
|
363
|
+
else if (check.kind === 'comment-count-delta' || check.kind === 'tracked-change-count-delta') {
|
|
364
|
+
const key = check.kind === 'comment-count-delta' ? 'comments' : 'trackedChanges';
|
|
365
|
+
const before = pre?.complete?.counts[key], after = post.complete?.counts[key];
|
|
366
|
+
results.push({
|
|
367
|
+
check,
|
|
368
|
+
passed: before !== undefined && after !== undefined && after - before === check.delta,
|
|
369
|
+
detail: `pre=${before} post=${after}`,
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
else
|
|
373
|
+
results.push({ check, passed: false, detail: 'Check requires domain-specific evidence.' });
|
|
374
|
+
}
|
|
375
|
+
return results;
|
|
376
|
+
}
|
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');
|
|
@@ -227,8 +228,8 @@ function computeDeltaChecks(pre, post, checks, saveReopen) {
|
|
|
227
228
|
else if (check.kind === 'comment-count-delta') {
|
|
228
229
|
results.push({
|
|
229
230
|
check,
|
|
230
|
-
passed: post.comments
|
|
231
|
-
detail: `pre=${pre.comments
|
|
231
|
+
passed: post.counts.comments - pre.counts.comments === check.delta,
|
|
232
|
+
detail: `pre=${pre.counts.comments} post=${post.counts.comments}`,
|
|
232
233
|
});
|
|
233
234
|
}
|
|
234
235
|
else if (check.kind === 'tracked-change-count-delta') {
|
|
@@ -313,7 +314,7 @@ async function trySaveReopen(doc, checks) {
|
|
|
313
314
|
await saveAny.call(doc, {});
|
|
314
315
|
// Rebuild a fresh snapshot after save. Host-level true reopen still needs
|
|
315
316
|
// a new document handle, which this runtime cannot force on its own.
|
|
316
|
-
const fresh = await docSnapshot.
|
|
317
|
+
const fresh = await docSnapshot.buildMutationSnapshot(doc);
|
|
317
318
|
for (const check of checks) {
|
|
318
319
|
if (check.kind === 'save-reopen-text-contains') {
|
|
319
320
|
const found = fresh.blocks.some((b) => b.text.includes(check.text));
|
|
@@ -336,124 +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)
|
|
386
|
+
if (!validation.ok)
|
|
343
387
|
return {
|
|
344
388
|
status: 'failed',
|
|
345
389
|
intent: plan.intent,
|
|
346
|
-
preSnapshot: { revision: 'unknown', counts: emptyCounts() },
|
|
347
390
|
selectedTargets: [],
|
|
348
391
|
executedOperations: [],
|
|
349
392
|
verification: [],
|
|
350
|
-
errors: validation.errors.map((
|
|
393
|
+
errors: validation.errors.map((error) => ({ code: error.code, message: error.message })),
|
|
351
394
|
};
|
|
352
|
-
|
|
353
|
-
const
|
|
354
|
-
|
|
355
|
-
const
|
|
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
|
+
});
|
|
356
424
|
const bindings = new Map();
|
|
357
425
|
try {
|
|
426
|
+
const pre = await context.start();
|
|
358
427
|
for (const step of plan.steps) {
|
|
359
428
|
if (step.kind === 'select') {
|
|
360
|
-
const matched =
|
|
361
|
-
|
|
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)
|
|
362
435
|
throw new docSnapshot.AmbiguousSelectorError(`Selector did not resolve uniquely (matched ${matched.length}).`, matched.map((nodeId) => ({ nodeId, description: nodeId })));
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
if (step.bind) {
|
|
436
|
+
context.selectedTargets.push({ selector: step.selector, matched });
|
|
437
|
+
if (step.bind)
|
|
366
438
|
bindings.set(step.bind, matched.length === 1 ? matched[0] : [...matched]);
|
|
367
|
-
}
|
|
368
|
-
continue;
|
|
369
439
|
}
|
|
370
|
-
if (step.kind === 'inspect') {
|
|
371
|
-
const
|
|
372
|
-
const method = resolveDocMethod(
|
|
373
|
-
const result = await method(
|
|
374
|
-
|
|
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)
|
|
375
447
|
bindings.set(step.bind, result);
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
if (step.kind === 'apply') {
|
|
379
|
-
ensureKnownOperation(step.operationId);
|
|
380
|
-
const applyArgs = ensureClean(resolveBindingTokens(step.args, bindings));
|
|
381
|
-
const method = resolveDocMethod(doc, step.operationId);
|
|
382
|
-
const argsWithMode = step.changeMode != null && operationCatalog.getOperationCatalogEntry(step.operationId)?.supportsChangeMode
|
|
383
|
-
? { ...applyArgs, changeMode: step.changeMode }
|
|
384
|
-
: applyArgs;
|
|
385
|
-
const result = await method(argsWithMode);
|
|
386
|
-
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;
|
|
387
450
|
}
|
|
388
451
|
}
|
|
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 });
|
|
389
463
|
}
|
|
390
|
-
catch (
|
|
391
|
-
|
|
392
|
-
return {
|
|
393
|
-
status: 'aborted',
|
|
394
|
-
intent: plan.intent,
|
|
395
|
-
preSnapshot: { revision: preSnapshot.revision, counts: preSnapshot.counts },
|
|
396
|
-
selectedTargets,
|
|
397
|
-
executedOperations,
|
|
398
|
-
verification: [],
|
|
399
|
-
errors: [
|
|
400
|
-
{
|
|
401
|
-
code: err.code,
|
|
402
|
-
message: err.message,
|
|
403
|
-
},
|
|
404
|
-
],
|
|
405
|
-
};
|
|
406
|
-
}
|
|
407
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
408
|
-
return {
|
|
409
|
-
status: 'failed',
|
|
410
|
-
intent: plan.intent,
|
|
411
|
-
preSnapshot: { revision: preSnapshot.revision, counts: preSnapshot.counts },
|
|
412
|
-
selectedTargets,
|
|
413
|
-
executedOperations,
|
|
414
|
-
verification: [],
|
|
415
|
-
errors: [{ code: 'APPLY_FAILED', message }],
|
|
416
|
-
};
|
|
464
|
+
catch (error) {
|
|
465
|
+
return context.failure(plan.intent, error);
|
|
417
466
|
}
|
|
418
|
-
const postSnapshot = await docSnapshot.buildDocumentSnapshot(doc);
|
|
419
|
-
const verifyStep = plan.steps.find((s) => s.kind === 'verify');
|
|
420
|
-
let saveReopen;
|
|
421
|
-
const shouldSaveReopen = (verifyStep?.kind === 'verify' && (verifyStep.saveReopen || verificationNeedsSaveReopen(verifyStep.checks))) ||
|
|
422
|
-
planTouchesRiskyDomain(plan);
|
|
423
|
-
if (shouldSaveReopen) {
|
|
424
|
-
saveReopen = await trySaveReopen(doc, verifyStep?.kind === 'verify' ? verifyStep.checks : []);
|
|
425
|
-
}
|
|
426
|
-
const verification = verifyStep?.kind === 'verify' ? computeDeltaChecks(preSnapshot, postSnapshot, verifyStep.checks, saveReopen) : [];
|
|
427
|
-
const allVerified = verification.every((v) => v.passed);
|
|
428
|
-
return {
|
|
429
|
-
status: allVerified ? 'ok' : 'failed',
|
|
430
|
-
intent: plan.intent,
|
|
431
|
-
preSnapshot: { revision: preSnapshot.revision, counts: preSnapshot.counts },
|
|
432
|
-
postSnapshot: { revision: postSnapshot.revision, counts: postSnapshot.counts },
|
|
433
|
-
selectedTargets,
|
|
434
|
-
executedOperations,
|
|
435
|
-
verification,
|
|
436
|
-
saveReopen,
|
|
437
|
-
};
|
|
438
467
|
}
|
|
439
468
|
async function agentVerify(doc, args) {
|
|
440
|
-
const
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
469
|
+
const context = new executionContext.ExecutionContext(doc, { evidence: args.evidence, checks: args.checks });
|
|
470
|
+
try {
|
|
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 });
|
|
480
|
+
}
|
|
481
|
+
catch (error) {
|
|
482
|
+
return context.failure('verify', error);
|
|
444
483
|
}
|
|
445
|
-
const verification = computeCurrentChecks(snapshot, args.checks, saveReopen);
|
|
446
|
-
const allPassed = verification.every((v) => v.passed);
|
|
447
|
-
return {
|
|
448
|
-
status: allPassed ? 'ok' : 'failed',
|
|
449
|
-
intent: 'verify',
|
|
450
|
-
preSnapshot: { revision: snapshot.revision, counts: snapshot.counts },
|
|
451
|
-
postSnapshot: { revision: snapshot.revision, counts: snapshot.counts },
|
|
452
|
-
selectedTargets: [],
|
|
453
|
-
executedOperations: [],
|
|
454
|
-
verification,
|
|
455
|
-
saveReopen,
|
|
456
|
-
};
|
|
457
484
|
}
|
|
458
485
|
/**
|
|
459
486
|
* Controlled escape hatch — dispatches a single generated operation by id.
|
|
@@ -481,27 +508,6 @@ async function agentOperation(doc, args) {
|
|
|
481
508
|
const method = resolveDocMethod(doc, args.operationId);
|
|
482
509
|
return method(callArgs);
|
|
483
510
|
}
|
|
484
|
-
function emptyCounts() {
|
|
485
|
-
return {
|
|
486
|
-
blocks: 0,
|
|
487
|
-
paragraphs: 0,
|
|
488
|
-
headings: 0,
|
|
489
|
-
tables: 0,
|
|
490
|
-
lists: 0,
|
|
491
|
-
images: 0,
|
|
492
|
-
comments: 0,
|
|
493
|
-
trackedChanges: 0,
|
|
494
|
-
sections: 0,
|
|
495
|
-
fields: 0,
|
|
496
|
-
hyperlinks: 0,
|
|
497
|
-
bookmarks: 0,
|
|
498
|
-
contentControls: 0,
|
|
499
|
-
permissionRanges: 0,
|
|
500
|
-
styles: 0,
|
|
501
|
-
headers: 0,
|
|
502
|
-
footers: 0,
|
|
503
|
-
};
|
|
504
|
-
}
|
|
505
511
|
|
|
506
512
|
exports.agentApply = agentApply;
|
|
507
513
|
exports.agentInspect = agentInspect;
|