@manudota/artist-mcp 2.1.1-staging.97 → 2.2.0
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/agent-pack/.artist/policies/PATCH.md +63 -0
- package/agent-pack/registry.json +1 -1
- package/dist/api.d.ts +1 -0
- package/dist/onenote-patch.d.ts +152 -25
- package/dist/onenote-patch.js +976 -87
- package/dist/server.js +116 -25
- package/package.json +1 -1
|
@@ -76,6 +76,26 @@ may have edited since.
|
|
|
76
76
|
- Where a decision genuinely lands in two places, that is two fragments, each
|
|
77
77
|
named for where it goes. Not one merged block covering both.
|
|
78
78
|
|
|
79
|
+
### A table is the exception, and only because OneNote makes it one
|
|
80
|
+
|
|
81
|
+
OneNote supports no change to a single row or cell. The tool can replace a whole
|
|
82
|
+
table and nothing smaller, so where the decision lands in a table the smallest
|
|
83
|
+
fragment it can *apply* is the entire table.
|
|
84
|
+
|
|
85
|
+
That does not loosen anything above; it moves the care somewhere else. **Every
|
|
86
|
+
cell that is not changing has to be carried across exactly as it reads now** —
|
|
87
|
+
copy them from the rows the preview lists rather than retyping them from memory,
|
|
88
|
+
because a cell left out of the markup is not a cell left alone, it is a value
|
|
89
|
+
destroyed. `UNKNOWN` stays `UNKNOWN` in those words, and a column the musician
|
|
90
|
+
never fills stays empty.
|
|
91
|
+
|
|
92
|
+
Say what the change is, not what the mechanism is. "Changing Honorar to 1450" is
|
|
93
|
+
the decision; that the whole table is rewritten to do it is worth one sentence,
|
|
94
|
+
because they are approving the loss of everything in it if the markup is wrong.
|
|
95
|
+
|
|
96
|
+
A patch to be pasted is unaffected: they place a row themselves, so it stays one
|
|
97
|
+
row.
|
|
98
|
+
|
|
79
99
|
A whole page is a fragment only when the whole page is what they settled — the
|
|
80
100
|
consolidated page in `policy:divergence`, once every conflicting field has been
|
|
81
101
|
decided, which goes somewhere new rather than over either original. Wanting a
|
|
@@ -175,6 +195,49 @@ Owner is the only cell they settled. Due is `UNKNOWN` because no date was given
|
|
|
175
195
|
is `UNKNOWN` because "not started" is a reasonable guess and still a guess. The
|
|
176
196
|
page says nothing about any of this until they paste it.
|
|
177
197
|
|
|
198
|
+
## Table markup
|
|
199
|
+
|
|
200
|
+
A table cannot be edited a cell at a time — OneNote supports no update to a row
|
|
201
|
+
or a cell, so changing one value rewrites the whole table. Every cell that is
|
|
202
|
+
not changing has to be carried across exactly as it reads.
|
|
203
|
+
|
|
204
|
+
Formatting the original carried and the replacement leaves out is now carried
|
|
205
|
+
across for you, and the preview says which parts it is keeping. Do not rely on
|
|
206
|
+
that silently: it covers the table's own borders and its cells' borders, and it
|
|
207
|
+
stops the moment the markup specifies any styling of its own — style one cell
|
|
208
|
+
and you have taken responsibility for all of them.
|
|
209
|
+
|
|
210
|
+
So write the house shape explicitly whenever the table is a new one, and let
|
|
211
|
+
inheritance cover the ones you are editing in place. The house shape, which is
|
|
212
|
+
what the artist's own pages already look like:
|
|
213
|
+
|
|
214
|
+
- The table itself: `<table border="1" style="border-collapse:collapse">`. The
|
|
215
|
+
`border` attribute is the part OneNote acts on — a CSS `border:1px solid` in
|
|
216
|
+
the style alone comes back as `border:0px`.
|
|
217
|
+
- Every cell: `<td style="border:1px solid #A3A3A3;padding:4px">`, with the
|
|
218
|
+
content in a `<p>`.
|
|
219
|
+
- The header row, where the table has one, additionally carries
|
|
220
|
+
`background-color:#EFEFEF` on each cell and wraps its text in `<b>`. A roster
|
|
221
|
+
of musicians, a milestones table, a Field/Value block — the first row names the
|
|
222
|
+
columns and is shaded so it reads as a header rather than as data.
|
|
223
|
+
- A free-text section is a single-cell table and has **no** header row. Shading
|
|
224
|
+
its only cell would make the whole section look like a heading.
|
|
225
|
+
- Column widths are not set. OneNote sizes them, and a width guessed from a
|
|
226
|
+
preview is worse than none.
|
|
227
|
+
|
|
228
|
+
Send this through the `html` parameter, never `text` — a table put through
|
|
229
|
+
`text` arrives entity-escaped and lands on the page as literal angle brackets.
|
|
230
|
+
|
|
231
|
+
Where a page needs several changes, give them as `changes` in one call. They are
|
|
232
|
+
previewed together, confirmed together and written together, and if any one of
|
|
233
|
+
them cannot be applied then none of them is. Changing one thing at a time makes
|
|
234
|
+
the tool re-read the page between every change.
|
|
235
|
+
|
|
236
|
+
Reading the page back afterwards is still worth doing on anything structural. It
|
|
237
|
+
is no longer a way of telling a display artifact from a real mangling — the
|
|
238
|
+
preview shows paragraph breaks and table rows as they are — but it is the only
|
|
239
|
+
thing that confirms what the page now holds.
|
|
240
|
+
|
|
178
241
|
## Boundaries
|
|
179
242
|
|
|
180
243
|
- Never claim a page was changed unless the tool reported changing it. A patch
|
package/agent-pack/registry.json
CHANGED
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"name": "Patch",
|
|
48
48
|
"description": "Once the musician has decided something, hand them the smallest fragment that records it, and say which part of the page it goes under.",
|
|
49
49
|
"file": ".artist/policies/PATCH.md",
|
|
50
|
-
"sha256": "
|
|
50
|
+
"sha256": "47029fd651ab2458ecc4b60dd73b16decf3f5c5506f20f4f61a71ca69f3f38e0"
|
|
51
51
|
},
|
|
52
52
|
{
|
|
53
53
|
"id": "project-type:concert",
|
package/dist/api.d.ts
CHANGED
package/dist/onenote-patch.d.ts
CHANGED
|
@@ -66,8 +66,10 @@ export declare const extractElement: (html: string, attr: "id" | "data-id", valu
|
|
|
66
66
|
export declare const preImage: (html: string, generatedId: string) => string;
|
|
67
67
|
export type PatchCommand = {
|
|
68
68
|
target: string;
|
|
69
|
-
action: 'append' | 'replace';
|
|
69
|
+
action: 'append' | 'replace' | 'insert';
|
|
70
70
|
content: string;
|
|
71
|
+
/** Only an insert has one. Graph ignores it elsewhere rather than refusing. */
|
|
72
|
+
position?: 'before' | 'after';
|
|
71
73
|
};
|
|
72
74
|
/**
|
|
73
75
|
* Append to a container.
|
|
@@ -86,6 +88,20 @@ export declare const appendCommand: (target: string, contentHtml: string) => Pat
|
|
|
86
88
|
* confusingly or hits something other than what was meant.
|
|
87
89
|
*/
|
|
88
90
|
export declare const replaceCommand: (generatedId: string, contentHtml: string) => PatchCommand;
|
|
91
|
+
/**
|
|
92
|
+
* Put a new element beside an existing one.
|
|
93
|
+
*
|
|
94
|
+
* Graph lists `insert` for a `table` with a `position` of `before` or `after`,
|
|
95
|
+
* and this is the only way a new block reaches the middle of a page: `append`
|
|
96
|
+
* can only reach the end of the body, so without a sibling insert a section
|
|
97
|
+
* added to a template lands after everything, which for these pages is after
|
|
98
|
+
* the signature block rather than where it belongs.
|
|
99
|
+
*
|
|
100
|
+
* The anchor is named by its generated id for the same reason a replace is —
|
|
101
|
+
* `data-id` targets resolve for `append` and not for this, and the failure is
|
|
102
|
+
* a `20134` that reads as though the element could not be edited.
|
|
103
|
+
*/
|
|
104
|
+
export declare const insertCommand: (generatedId: string, position: "before" | "after", contentHtml: string) => PatchCommand;
|
|
89
105
|
/**
|
|
90
106
|
* Plain text as a single paragraph, which is what replaces a paragraph.
|
|
91
107
|
*
|
|
@@ -95,14 +111,71 @@ export declare const replaceCommand: (generatedId: string, contentHtml: string)
|
|
|
95
111
|
* decided rather than smuggled in through the content string.
|
|
96
112
|
*/
|
|
97
113
|
export declare const paragraph: (text: string) => string;
|
|
114
|
+
/**
|
|
115
|
+
* Well-formed, allowed, and balanced — or nothing is sent.
|
|
116
|
+
*
|
|
117
|
+
* Returns the markup unchanged on success. It is deliberately not a normaliser:
|
|
118
|
+
* what goes to Graph is byte-for-byte what was hashed into the confirmation
|
|
119
|
+
* token and shown in the preview, so there is no room for a rewrite between
|
|
120
|
+
* the musician's yes and the write.
|
|
121
|
+
*/
|
|
122
|
+
export declare const validateFragment: (html: string) => string;
|
|
123
|
+
/**
|
|
124
|
+
* The tag of the single element a fragment consists of, or undefined if it is
|
|
125
|
+
* not exactly one element.
|
|
126
|
+
*
|
|
127
|
+
* A replace must yield an element of the kind the target supports: a table
|
|
128
|
+
* replaced by two tables, or by a table and a stray paragraph, is not the
|
|
129
|
+
* change that was previewed as one table.
|
|
130
|
+
*/
|
|
131
|
+
export declare const rootTag: (html: string) => string | undefined;
|
|
132
|
+
export type EditablePart = {
|
|
133
|
+
element_id: string;
|
|
134
|
+
/** A table is replaced whole and with HTML; everything else takes plain text. */
|
|
135
|
+
kind: 'text' | 'table';
|
|
136
|
+
/** As the musician reads it: a table as its rows, anything else as its text. */
|
|
137
|
+
text: string;
|
|
138
|
+
/** The table this sits in, if it does. Null for a top-level element. */
|
|
139
|
+
inside_table: string | null;
|
|
140
|
+
};
|
|
141
|
+
/** One change. A draft carries a list of these, even when the list has one entry. */
|
|
142
|
+
export type EditChange = {
|
|
143
|
+
action: 'append' | 'replace' | 'insert';
|
|
144
|
+
/** The generated id of the element to replace, or to sit beside. Absent for an append. */
|
|
145
|
+
element_id: string | null;
|
|
146
|
+
/** Plain text, escaped on the way out. Null when the change is markup. */
|
|
147
|
+
text: string | null;
|
|
148
|
+
/** Validated markup. Null when the change is plain text. */
|
|
149
|
+
html: string | null;
|
|
150
|
+
/** Which side of the anchor an insert lands on. Null otherwise. */
|
|
151
|
+
position: 'before' | 'after' | null;
|
|
152
|
+
};
|
|
98
153
|
export type EditDraft = {
|
|
99
154
|
page_id: string;
|
|
100
|
-
|
|
101
|
-
/** The generated id of the element to replace. Absent for an append. */
|
|
102
|
-
element_id: string | null;
|
|
103
|
-
text: string;
|
|
155
|
+
changes: EditChange[];
|
|
104
156
|
source_page: string | null;
|
|
105
157
|
};
|
|
158
|
+
export declare const inherit: (targetHtml: string, contentHtml: string) => {
|
|
159
|
+
html: string;
|
|
160
|
+
notes: string[];
|
|
161
|
+
};
|
|
162
|
+
/**
|
|
163
|
+
* A page and the changes to make to it, whether that is one or several.
|
|
164
|
+
*
|
|
165
|
+
* Several ride in one `PATCH`, which is what Graph's array of change objects is
|
|
166
|
+
* for and which this used to send one command at a time. The reason to batch is
|
|
167
|
+
* not only the round trips: generated ids move after *every* write, so eight
|
|
168
|
+
* changes applied one by one means eight reads, and every one of them is a
|
|
169
|
+
* chance for the page to have moved under the sequence. One command array
|
|
170
|
+
* resolves every target against a single read.
|
|
171
|
+
*
|
|
172
|
+
* That was verified rather than assumed, and so was the thing that makes it
|
|
173
|
+
* safe to approve as one step: a batch containing one unresolvable target
|
|
174
|
+
* applies **nothing** — `400`, and the page untouched. See
|
|
175
|
+
* scripts/spike-onenote-batch.mjs. A batch that half-applied would be a page in
|
|
176
|
+
* a state nobody previewed and nobody agreed to, and this capability would not
|
|
177
|
+
* be worth having.
|
|
178
|
+
*/
|
|
106
179
|
export declare const editFrom: (params: Record<string, unknown>) => EditDraft;
|
|
107
180
|
/**
|
|
108
181
|
* The token a preview returns and an edit must carry back.
|
|
@@ -117,7 +190,60 @@ export declare const editFrom: (params: Record<string, unknown>) => EditDraft;
|
|
|
117
190
|
* Hashing the pre-image makes that case fail closed: the edit is refused unless
|
|
118
191
|
* the element still holds exactly what the musician was shown.
|
|
119
192
|
*/
|
|
120
|
-
export declare const editToken: (draft: EditDraft,
|
|
193
|
+
export declare const editToken: (draft: EditDraft, preImages: string[]) => Promise<string>;
|
|
194
|
+
/** A table as its cell text, row by row. */
|
|
195
|
+
export declare const tableRows: (tableHtml: string) => string[][];
|
|
196
|
+
/**
|
|
197
|
+
* A table as something a musician can read and say yes or no to.
|
|
198
|
+
*
|
|
199
|
+
* This is the difference between a whole-table replace being previewable and
|
|
200
|
+
* not. The thing a replace destroys is a block of markup hundreds of characters
|
|
201
|
+
* wide; shown raw it is not a preview, it is a wall that gets approved unread —
|
|
202
|
+
* and an approval nobody could read is exactly the failure the confirmation
|
|
203
|
+
* step exists to prevent. So the table is shown as its rows.
|
|
204
|
+
*
|
|
205
|
+
* **Nothing is ever cut.** This originally clipped a cell at forty characters
|
|
206
|
+
* with an ellipsis, on the reasoning that the shape of the table is what a
|
|
207
|
+
* reader checks. That was wrong in the case these pages are mostly made of: a
|
|
208
|
+
* single-cell table holding a paragraph of free text, where the whole change
|
|
209
|
+
* lives past the fortieth character. Both halves of the preview then rendered
|
|
210
|
+
* to the same truncated line, and the preview asserted in effect that a
|
|
211
|
+
* destructive change was a no-op. Found in use, not by any test here — the test
|
|
212
|
+
* asserted the ellipsis was present, which is how a defect gets a green tick.
|
|
213
|
+
*
|
|
214
|
+
* So a long cell wraps within its column instead. A wrapped preview is longer
|
|
215
|
+
* to read; a truncated one cannot be read at all.
|
|
216
|
+
*
|
|
217
|
+
* `marks` optionally prefixes each row, from `markRows`. The mark sits outside
|
|
218
|
+
* the table so the columns still line up under it.
|
|
219
|
+
*/
|
|
220
|
+
export declare const renderTable: (tableHtml: string, marks?: string[]) => string;
|
|
221
|
+
export declare const renderRows: (rows: string[][], marks?: string[]) => string;
|
|
222
|
+
/**
|
|
223
|
+
* Which rows a replace actually changes, matched up rather than compared by
|
|
224
|
+
* position.
|
|
225
|
+
*
|
|
226
|
+
* A row inserted near the top shifts every row under it, and comparing by index
|
|
227
|
+
* would then mark the whole table as changed — which tells a reader as little
|
|
228
|
+
* as marking none of it. So the two row lists are matched by their longest
|
|
229
|
+
* common subsequence, and only what is genuinely new, gone or altered is
|
|
230
|
+
* marked.
|
|
231
|
+
*/
|
|
232
|
+
export declare const markRows: (before: string[][], after: string[][]) => {
|
|
233
|
+
before: string[];
|
|
234
|
+
after: string[];
|
|
235
|
+
};
|
|
236
|
+
/**
|
|
237
|
+
* A fragment of several elements, each rendered as what it is.
|
|
238
|
+
*
|
|
239
|
+
* A replacement may now be a paragraph, a heading and a table at once, and
|
|
240
|
+
* running the whole thing through `asText` turned the table back into a column
|
|
241
|
+
* of loose lines — the same flattening `copy-onenote-page.mjs` refuses for the
|
|
242
|
+
* same reason. The reader has to be able to see that a table is arriving.
|
|
243
|
+
*/
|
|
244
|
+
export declare const renderFragment: (html: string) => string;
|
|
245
|
+
/** Whether a generated id names a table, which decides what may be done to it. */
|
|
246
|
+
export declare const isTableId: (id: string) => boolean;
|
|
121
247
|
/**
|
|
122
248
|
* What the page currently says, element by element, so a caller can name the
|
|
123
249
|
* part to change without guessing an id.
|
|
@@ -127,13 +253,12 @@ export declare const editToken: (draft: EditDraft, preImageHtml: string) => Prom
|
|
|
127
253
|
* quoted back in a later session — Microsoft says they "might change after a
|
|
128
254
|
* page update", and that was observed rather than merely read.
|
|
129
255
|
*/
|
|
130
|
-
export declare const readEditableParts: (token: string, pageId: string
|
|
256
|
+
export declare const readEditableParts: (token: string, pageId: string, { full }?: {
|
|
257
|
+
full?: boolean;
|
|
258
|
+
}) => Promise<{
|
|
131
259
|
html: string;
|
|
132
260
|
etag: string | null;
|
|
133
|
-
parts:
|
|
134
|
-
element_id: string;
|
|
135
|
-
text: string;
|
|
136
|
-
}[];
|
|
261
|
+
parts: EditablePart[];
|
|
137
262
|
}>;
|
|
138
263
|
/**
|
|
139
264
|
* Show the change that would be made, and hand back the token making it needs.
|
|
@@ -143,33 +268,35 @@ export declare const readEditableParts: (token: string, pageId: string) => Promi
|
|
|
143
268
|
export declare function previewEdit(token: string, params: Record<string, unknown>): Promise<{
|
|
144
269
|
preview: null;
|
|
145
270
|
page_id: string;
|
|
146
|
-
parts:
|
|
147
|
-
|
|
148
|
-
text: string;
|
|
149
|
-
}[];
|
|
271
|
+
parts: EditablePart[];
|
|
272
|
+
abbreviated: boolean;
|
|
150
273
|
confirmation_token: null;
|
|
151
274
|
note: string;
|
|
152
275
|
} | {
|
|
153
276
|
preview: string;
|
|
154
277
|
page_id: string;
|
|
155
|
-
parts:
|
|
156
|
-
|
|
157
|
-
text: string;
|
|
158
|
-
}[];
|
|
278
|
+
parts: EditablePart[];
|
|
279
|
+
abbreviated: boolean;
|
|
159
280
|
confirmation_token: string;
|
|
160
281
|
note: string;
|
|
161
282
|
}>;
|
|
162
283
|
/**
|
|
163
|
-
* Apply the change, having shown it.
|
|
284
|
+
* Apply the change, or the batch of them, having shown it.
|
|
164
285
|
*
|
|
165
286
|
* The page is read again here rather than trusted from the preview, and that is
|
|
166
|
-
* not caution: generated ids move after any page update, so an id that was
|
|
167
|
-
*
|
|
168
|
-
*
|
|
287
|
+
* not caution: generated ids move after any page update, so an id that was right
|
|
288
|
+
* at preview may name something else by now. Re-reading also produces the ETag
|
|
289
|
+
* that goes out as `If-Match`, which is what makes the musician typing in
|
|
169
290
|
* OneNote between the two calls a refusal rather than a silent overwrite.
|
|
170
291
|
*
|
|
171
|
-
* The confirmation token is checked against
|
|
172
|
-
*
|
|
292
|
+
* The confirmation token is checked against every change *and* every pre-image,
|
|
293
|
+
* so a batch fails closed if any element it would overwrite has changed since
|
|
294
|
+
* the preview — not merely the one that moved.
|
|
295
|
+
*
|
|
296
|
+
* All the commands go out in one `PATCH`, which is what Graph's array is for.
|
|
297
|
+
* A batch containing an unresolvable target applies nothing at all, verified
|
|
298
|
+
* against a real page, so there is no state in which half a confirmed batch is
|
|
299
|
+
* on the page.
|
|
173
300
|
*/
|
|
174
301
|
export declare function applyEdit(token: string, params: Record<string, unknown>, record: RecordWrite): Promise<{
|
|
175
302
|
changed: boolean;
|