logisheets-logician 1.11.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/README.md +89 -0
- package/dist/agent/loop.d.ts +101 -0
- package/dist/agent/loop.js +260 -0
- package/dist/conversation.d.ts +105 -0
- package/dist/conversation.js +10 -0
- package/dist/craft-interactions-api.d.ts +81 -0
- package/dist/craft-interactions-api.js +25 -0
- package/dist/craft-interactions-core.d.ts +14 -0
- package/dist/craft-interactions-core.js +33 -0
- package/dist/crafts/manifest.d.ts +39 -0
- package/dist/crafts/manifest.js +10 -0
- package/dist/crafts/skill-tools.d.ts +33 -0
- package/dist/crafts/skill-tools.js +146 -0
- package/dist/crafts/store.d.ts +47 -0
- package/dist/crafts/store.js +69 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.js +22 -0
- package/dist/index.node.js +5065 -0
- package/dist/projection.d.ts +129 -0
- package/dist/projection.js +249 -0
- package/dist/storage.d.ts +99 -0
- package/dist/storage.js +223 -0
- package/dist/tool.d.ts +137 -0
- package/dist/tool.js +57 -0
- package/dist/tools/block-ops.d.ts +30 -0
- package/dist/tools/block-ops.js +97 -0
- package/dist/tools/builder.d.ts +206 -0
- package/dist/tools/builder.js +1502 -0
- package/dist/tools/cells.d.ts +58 -0
- package/dist/tools/cells.js +263 -0
- package/dist/tools/comments.d.ts +54 -0
- package/dist/tools/comments.js +234 -0
- package/dist/tools/craft-interactions.d.ts +22 -0
- package/dist/tools/craft-interactions.js +454 -0
- package/dist/tools/edit.d.ts +55 -0
- package/dist/tools/edit.js +356 -0
- package/dist/tools/format.d.ts +58 -0
- package/dist/tools/format.js +208 -0
- package/dist/tools/history.d.ts +13 -0
- package/dist/tools/history.js +39 -0
- package/dist/tools/inspect.d.ts +93 -0
- package/dist/tools/inspect.js +488 -0
- package/dist/tools/links.d.ts +30 -0
- package/dist/tools/links.js +122 -0
- package/dist/tools/structure.d.ts +62 -0
- package/dist/tools/structure.js +174 -0
- package/dist/tools/taxonomy.d.ts +32 -0
- package/dist/tools/taxonomy.js +93 -0
- package/package.json +53 -0
|
@@ -0,0 +1,454 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Craft-interaction tools — let Watson BUILD craft-style cell-overlay
|
|
3
|
+
* widgets onto blocks, the way factory-simulator does, and READ back what
|
|
4
|
+
* the end user selected.
|
|
5
|
+
*
|
|
6
|
+
* Model (mirrors `injectCraftInteractionAPIs` in the host): a craft
|
|
7
|
+
* *registers* an overlay group onto specific block cells, then the end
|
|
8
|
+
* user operates it by clicking (choosing a radio option, dragging a
|
|
9
|
+
* slider, splitting a percentage). The craft cannot set selections
|
|
10
|
+
* programmatically — so these tools cover registration + clearing +
|
|
11
|
+
* reading results, not "operate".
|
|
12
|
+
*
|
|
13
|
+
* Addressing: cells are named by (block ref name, field name, row key) —
|
|
14
|
+
* the same altitude as `edit__set_block_cells`. Each tool resolves those
|
|
15
|
+
* to block-relative (row, col) offsets via the block schema before
|
|
16
|
+
* calling the host. The LLM never deals with raw coordinates.
|
|
17
|
+
*
|
|
18
|
+
* When the host has no craft-interaction surface (headless CLI), the
|
|
19
|
+
* tools return a structured "not available" result instead of throwing.
|
|
20
|
+
*/
|
|
21
|
+
import { isErrorMessage } from 'logisheets-web/pure';
|
|
22
|
+
function asClient(ctx) {
|
|
23
|
+
return ctx.workbook;
|
|
24
|
+
}
|
|
25
|
+
/** Resolve the craft surface or produce a "not available" result. */
|
|
26
|
+
function requireApi(ctx) {
|
|
27
|
+
const api = ctx.craftInteractions;
|
|
28
|
+
if (!api) {
|
|
29
|
+
return {
|
|
30
|
+
unavailable: {
|
|
31
|
+
data: { available: false },
|
|
32
|
+
display: 'Craft interactions are not available in this host.',
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
return { api };
|
|
37
|
+
}
|
|
38
|
+
/** Fetch all blocks once and index by schema ref name. */
|
|
39
|
+
async function loadBlocksByName(client) {
|
|
40
|
+
var _a;
|
|
41
|
+
const all = await client.getAllBlocks({});
|
|
42
|
+
if (isErrorMessage(all)) {
|
|
43
|
+
throw new Error(`getAllBlocks failed: ${all.msg}`);
|
|
44
|
+
}
|
|
45
|
+
const byName = new Map();
|
|
46
|
+
for (const b of all) {
|
|
47
|
+
if ((_a = b.schema) === null || _a === void 0 ? void 0 : _a.name) {
|
|
48
|
+
byName.set(b.schema.name, {
|
|
49
|
+
block: b,
|
|
50
|
+
sheetIdx: b.sheetIdx,
|
|
51
|
+
blockId: b.blockId,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return byName;
|
|
56
|
+
}
|
|
57
|
+
/** Resolve (field, row_key) → block-relative (row, col) via the schema. */
|
|
58
|
+
function resolveCell(rb, blockRef, rowKey, field) {
|
|
59
|
+
const schema = rb.block.schema;
|
|
60
|
+
if (!schema) {
|
|
61
|
+
throw new Error(`block "${blockRef}" has no schema`);
|
|
62
|
+
}
|
|
63
|
+
const rowEntry = schema.keys.find((k) => k.key === rowKey);
|
|
64
|
+
if (!rowEntry) {
|
|
65
|
+
throw new Error(`no row with key "${rowKey}" in block "${blockRef}" (keys: ${schema.keys
|
|
66
|
+
.map((k) => k.key)
|
|
67
|
+
.join(', ')})`);
|
|
68
|
+
}
|
|
69
|
+
const fieldEntry = schema.fields.find((f) => f.field === field);
|
|
70
|
+
if (!fieldEntry) {
|
|
71
|
+
throw new Error(`no field "${field}" in block "${blockRef}" (fields: ${schema.fields
|
|
72
|
+
.map((f) => f.field)
|
|
73
|
+
.join(', ')})`);
|
|
74
|
+
}
|
|
75
|
+
return {
|
|
76
|
+
sheetIdx: rb.sheetIdx,
|
|
77
|
+
blockId: rb.blockId,
|
|
78
|
+
row: rowEntry.idx,
|
|
79
|
+
col: fieldEntry.idx,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
// ---------------------------------------------------------------------------
|
|
83
|
+
// Shared JSON Schema fragments
|
|
84
|
+
// ---------------------------------------------------------------------------
|
|
85
|
+
const GROUP_ID_SCHEMA = {
|
|
86
|
+
type: 'string',
|
|
87
|
+
description: 'A craft-chosen id for this overlay group. Cells sharing a group_id interact together (one radio choice, one percent split, one point pool).',
|
|
88
|
+
};
|
|
89
|
+
const CELL_REF_SCHEMA = {
|
|
90
|
+
type: 'object',
|
|
91
|
+
description: 'Addresses a block cell by field name and row key.',
|
|
92
|
+
properties: {
|
|
93
|
+
row_key: {
|
|
94
|
+
type: 'string',
|
|
95
|
+
description: "Value of the block's key column.",
|
|
96
|
+
},
|
|
97
|
+
field: { type: 'string' },
|
|
98
|
+
},
|
|
99
|
+
required: ['row_key', 'field'],
|
|
100
|
+
};
|
|
101
|
+
const VALUED_CELL_REF_SCHEMA = {
|
|
102
|
+
type: 'object',
|
|
103
|
+
properties: {
|
|
104
|
+
row_key: { type: 'string' },
|
|
105
|
+
field: { type: 'string' },
|
|
106
|
+
value: {
|
|
107
|
+
type: 'string',
|
|
108
|
+
description: 'The option value this cell represents when chosen (radio/multi-select).',
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
required: ['row_key', 'field', 'value'],
|
|
112
|
+
};
|
|
113
|
+
const registerRadioGroup = {
|
|
114
|
+
namespace: 'craft',
|
|
115
|
+
name: 'register_radio_group',
|
|
116
|
+
description: [
|
|
117
|
+
'Bind a single-choice radio overlay across a set of block cells. Each option is a cell; clicking one selects it and deselects the others in the group. Re-registering the same group_id replaces it.',
|
|
118
|
+
].join('\n'),
|
|
119
|
+
mutates: true,
|
|
120
|
+
confirmation: 'never',
|
|
121
|
+
inputSchema: {
|
|
122
|
+
properties: {
|
|
123
|
+
group_id: GROUP_ID_SCHEMA,
|
|
124
|
+
block: { type: 'string', description: 'Block ref name.' },
|
|
125
|
+
options: {
|
|
126
|
+
type: 'array',
|
|
127
|
+
items: VALUED_CELL_REF_SCHEMA,
|
|
128
|
+
minItems: 1,
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
required: ['group_id', 'block', 'options'],
|
|
132
|
+
},
|
|
133
|
+
handler: async (input, ctx) => {
|
|
134
|
+
const r = requireApi(ctx);
|
|
135
|
+
if ('unavailable' in r)
|
|
136
|
+
return r.unavailable;
|
|
137
|
+
const { api } = r;
|
|
138
|
+
const blocks = await loadBlocksByName(asClient(ctx));
|
|
139
|
+
const rb = blocks.get(input.block);
|
|
140
|
+
if (!rb)
|
|
141
|
+
throw new Error(`no block with ref name "${input.block}"`);
|
|
142
|
+
// Resolve all cells first; only mutate host state if all succeed.
|
|
143
|
+
const resolved = input.options.map((o) => ({
|
|
144
|
+
...resolveCell(rb, input.block, o.row_key, o.field),
|
|
145
|
+
value: o.value,
|
|
146
|
+
}));
|
|
147
|
+
api.clearRadios(input.group_id);
|
|
148
|
+
for (const c of resolved) {
|
|
149
|
+
api.registerRadio({ groupId: input.group_id, ...c });
|
|
150
|
+
}
|
|
151
|
+
return {
|
|
152
|
+
data: { group_id: input.group_id, options: resolved.length },
|
|
153
|
+
display: `Registered radio group "${input.group_id}" (${resolved.length} options).`,
|
|
154
|
+
};
|
|
155
|
+
},
|
|
156
|
+
};
|
|
157
|
+
const registerMultiSelectGroup = {
|
|
158
|
+
namespace: 'craft',
|
|
159
|
+
name: 'register_multi_select_group',
|
|
160
|
+
description: [
|
|
161
|
+
'Bind a k-of-n multi-select overlay across block cells. The user can select up to `max` of them. Re-registering the same group_id replaces it.',
|
|
162
|
+
].join('\n'),
|
|
163
|
+
mutates: true,
|
|
164
|
+
confirmation: 'never',
|
|
165
|
+
inputSchema: {
|
|
166
|
+
properties: {
|
|
167
|
+
group_id: GROUP_ID_SCHEMA,
|
|
168
|
+
block: { type: 'string', description: 'Block ref name.' },
|
|
169
|
+
max: {
|
|
170
|
+
type: 'integer',
|
|
171
|
+
minimum: 1,
|
|
172
|
+
description: 'Maximum number of cells the user may select.',
|
|
173
|
+
},
|
|
174
|
+
options: {
|
|
175
|
+
type: 'array',
|
|
176
|
+
items: VALUED_CELL_REF_SCHEMA,
|
|
177
|
+
minItems: 1,
|
|
178
|
+
},
|
|
179
|
+
},
|
|
180
|
+
required: ['group_id', 'block', 'max', 'options'],
|
|
181
|
+
},
|
|
182
|
+
handler: async (input, ctx) => {
|
|
183
|
+
const r = requireApi(ctx);
|
|
184
|
+
if ('unavailable' in r)
|
|
185
|
+
return r.unavailable;
|
|
186
|
+
const { api } = r;
|
|
187
|
+
const blocks = await loadBlocksByName(asClient(ctx));
|
|
188
|
+
const rb = blocks.get(input.block);
|
|
189
|
+
if (!rb)
|
|
190
|
+
throw new Error(`no block with ref name "${input.block}"`);
|
|
191
|
+
const resolved = input.options.map((o) => ({
|
|
192
|
+
...resolveCell(rb, input.block, o.row_key, o.field),
|
|
193
|
+
value: o.value,
|
|
194
|
+
}));
|
|
195
|
+
api.clearMultiSelects(input.group_id);
|
|
196
|
+
for (const c of resolved) {
|
|
197
|
+
api.registerMultiSelect({ groupId: input.group_id, ...c });
|
|
198
|
+
}
|
|
199
|
+
api.setMultiSelectMax(input.group_id, input.max);
|
|
200
|
+
return {
|
|
201
|
+
data: {
|
|
202
|
+
group_id: input.group_id,
|
|
203
|
+
max: input.max,
|
|
204
|
+
options: resolved.length,
|
|
205
|
+
},
|
|
206
|
+
display: `Registered multi-select "${input.group_id}" (${resolved.length} options, max ${input.max}).`,
|
|
207
|
+
};
|
|
208
|
+
},
|
|
209
|
+
};
|
|
210
|
+
const registerPointAllocator = {
|
|
211
|
+
namespace: 'craft',
|
|
212
|
+
name: 'register_point_allocator',
|
|
213
|
+
description: [
|
|
214
|
+
'Bind a point-allocator overlay: a pool of `pool_total` points the user distributes across the given cells (+/- on each cell). Re-registering the same group_id replaces it.',
|
|
215
|
+
].join('\n'),
|
|
216
|
+
mutates: true,
|
|
217
|
+
confirmation: 'never',
|
|
218
|
+
inputSchema: {
|
|
219
|
+
properties: {
|
|
220
|
+
group_id: GROUP_ID_SCHEMA,
|
|
221
|
+
block: { type: 'string', description: 'Block ref name.' },
|
|
222
|
+
pool_total: { type: 'integer', minimum: 0 },
|
|
223
|
+
cells: { type: 'array', items: CELL_REF_SCHEMA, minItems: 1 },
|
|
224
|
+
},
|
|
225
|
+
required: ['group_id', 'block', 'pool_total', 'cells'],
|
|
226
|
+
},
|
|
227
|
+
handler: async (input, ctx) => {
|
|
228
|
+
const r = requireApi(ctx);
|
|
229
|
+
if ('unavailable' in r)
|
|
230
|
+
return r.unavailable;
|
|
231
|
+
const { api } = r;
|
|
232
|
+
const blocks = await loadBlocksByName(asClient(ctx));
|
|
233
|
+
const rb = blocks.get(input.block);
|
|
234
|
+
if (!rb)
|
|
235
|
+
throw new Error(`no block with ref name "${input.block}"`);
|
|
236
|
+
const resolved = input.cells.map((c) => resolveCell(rb, input.block, c.row_key, c.field));
|
|
237
|
+
api.clearPointAllocators(input.group_id);
|
|
238
|
+
for (const c of resolved) {
|
|
239
|
+
api.registerPointAllocator({ groupId: input.group_id, ...c });
|
|
240
|
+
}
|
|
241
|
+
api.setPointPool(input.group_id, input.pool_total);
|
|
242
|
+
return {
|
|
243
|
+
data: {
|
|
244
|
+
group_id: input.group_id,
|
|
245
|
+
pool_total: input.pool_total,
|
|
246
|
+
cells: resolved.length,
|
|
247
|
+
},
|
|
248
|
+
display: `Registered point allocator "${input.group_id}" (${resolved.length} cells, pool ${input.pool_total}).`,
|
|
249
|
+
};
|
|
250
|
+
},
|
|
251
|
+
};
|
|
252
|
+
const registerPercentAllocator = {
|
|
253
|
+
namespace: 'craft',
|
|
254
|
+
name: 'register_percent_allocator',
|
|
255
|
+
description: [
|
|
256
|
+
'Bind a percent-allocator overlay across cells that must sum to 100%. The user clicks +/- to shift percentage between them; values are written to the cells. Typical use: a two-supplier split (factory-simulator does exactly this). Re-registering the same group_id replaces it.',
|
|
257
|
+
].join('\n'),
|
|
258
|
+
mutates: true,
|
|
259
|
+
confirmation: 'never',
|
|
260
|
+
inputSchema: {
|
|
261
|
+
properties: {
|
|
262
|
+
group_id: GROUP_ID_SCHEMA,
|
|
263
|
+
block: { type: 'string', description: 'Block ref name.' },
|
|
264
|
+
cells: {
|
|
265
|
+
type: 'array',
|
|
266
|
+
items: CELL_REF_SCHEMA,
|
|
267
|
+
minItems: 2,
|
|
268
|
+
description: 'The cells whose values must sum to 100%.',
|
|
269
|
+
},
|
|
270
|
+
},
|
|
271
|
+
required: ['group_id', 'block', 'cells'],
|
|
272
|
+
},
|
|
273
|
+
handler: async (input, ctx) => {
|
|
274
|
+
const r = requireApi(ctx);
|
|
275
|
+
if ('unavailable' in r)
|
|
276
|
+
return r.unavailable;
|
|
277
|
+
const { api } = r;
|
|
278
|
+
const blocks = await loadBlocksByName(asClient(ctx));
|
|
279
|
+
const rb = blocks.get(input.block);
|
|
280
|
+
if (!rb)
|
|
281
|
+
throw new Error(`no block with ref name "${input.block}"`);
|
|
282
|
+
const resolved = input.cells.map((c) => resolveCell(rb, input.block, c.row_key, c.field));
|
|
283
|
+
api.clearPercentAllocators(input.group_id);
|
|
284
|
+
for (const c of resolved) {
|
|
285
|
+
api.registerPercentAllocator({ groupId: input.group_id, ...c });
|
|
286
|
+
}
|
|
287
|
+
return {
|
|
288
|
+
data: { group_id: input.group_id, cells: resolved.length },
|
|
289
|
+
display: `Registered percent allocator "${input.group_id}" (${resolved.length} cells).`,
|
|
290
|
+
};
|
|
291
|
+
},
|
|
292
|
+
};
|
|
293
|
+
const registerNumberSlider = {
|
|
294
|
+
namespace: 'craft',
|
|
295
|
+
name: 'register_number_slider',
|
|
296
|
+
description: [
|
|
297
|
+
'Bind a number-slider overlay onto a single block cell. The user scrolls/types to set a value within [min,max]; the value is written to the cell. Re-registering the same group_id replaces it.',
|
|
298
|
+
].join('\n'),
|
|
299
|
+
mutates: true,
|
|
300
|
+
confirmation: 'never',
|
|
301
|
+
inputSchema: {
|
|
302
|
+
properties: {
|
|
303
|
+
group_id: GROUP_ID_SCHEMA,
|
|
304
|
+
block: { type: 'string', description: 'Block ref name.' },
|
|
305
|
+
cell: CELL_REF_SCHEMA,
|
|
306
|
+
min: { type: 'number' },
|
|
307
|
+
max: { type: 'number' },
|
|
308
|
+
step: { type: 'number', description: 'Defaults to 1.' },
|
|
309
|
+
initial_value: { type: 'number' },
|
|
310
|
+
},
|
|
311
|
+
required: ['group_id', 'block', 'cell', 'min', 'max'],
|
|
312
|
+
},
|
|
313
|
+
handler: async (input, ctx) => {
|
|
314
|
+
const r = requireApi(ctx);
|
|
315
|
+
if ('unavailable' in r)
|
|
316
|
+
return r.unavailable;
|
|
317
|
+
const { api } = r;
|
|
318
|
+
if (input.min > input.max) {
|
|
319
|
+
throw new Error(`min (${input.min}) must be <= max (${input.max})`);
|
|
320
|
+
}
|
|
321
|
+
const blocks = await loadBlocksByName(asClient(ctx));
|
|
322
|
+
const rb = blocks.get(input.block);
|
|
323
|
+
if (!rb)
|
|
324
|
+
throw new Error(`no block with ref name "${input.block}"`);
|
|
325
|
+
const c = resolveCell(rb, input.block, input.cell.row_key, input.cell.field);
|
|
326
|
+
api.clearNumberSliders(input.group_id);
|
|
327
|
+
api.registerNumberSlider({
|
|
328
|
+
groupId: input.group_id,
|
|
329
|
+
...c,
|
|
330
|
+
min: input.min,
|
|
331
|
+
max: input.max,
|
|
332
|
+
step: input.step,
|
|
333
|
+
initialValue: input.initial_value,
|
|
334
|
+
});
|
|
335
|
+
return {
|
|
336
|
+
data: { group_id: input.group_id, min: input.min, max: input.max },
|
|
337
|
+
display: `Registered number slider "${input.group_id}" on ${input.block}[${input.cell.row_key}].${input.cell.field} ([${input.min}, ${input.max}]).`,
|
|
338
|
+
};
|
|
339
|
+
},
|
|
340
|
+
};
|
|
341
|
+
const clearInteraction = {
|
|
342
|
+
namespace: 'craft',
|
|
343
|
+
name: 'clear_interaction',
|
|
344
|
+
description: 'Remove a single overlay group of a given kind. A group_id is required (no bulk wipe) to avoid clobbering unrelated overlays.',
|
|
345
|
+
mutates: true,
|
|
346
|
+
confirmation: 'always',
|
|
347
|
+
inputSchema: {
|
|
348
|
+
properties: {
|
|
349
|
+
kind: {
|
|
350
|
+
type: 'string',
|
|
351
|
+
enum: [
|
|
352
|
+
'radio',
|
|
353
|
+
'multi_select',
|
|
354
|
+
'point',
|
|
355
|
+
'percent',
|
|
356
|
+
'number_slider',
|
|
357
|
+
],
|
|
358
|
+
},
|
|
359
|
+
group_id: GROUP_ID_SCHEMA,
|
|
360
|
+
},
|
|
361
|
+
required: ['kind', 'group_id'],
|
|
362
|
+
},
|
|
363
|
+
handler: async (input, ctx) => {
|
|
364
|
+
const r = requireApi(ctx);
|
|
365
|
+
if ('unavailable' in r)
|
|
366
|
+
return r.unavailable;
|
|
367
|
+
const { api } = r;
|
|
368
|
+
switch (input.kind) {
|
|
369
|
+
case 'radio':
|
|
370
|
+
api.clearRadios(input.group_id);
|
|
371
|
+
break;
|
|
372
|
+
case 'multi_select':
|
|
373
|
+
api.clearMultiSelects(input.group_id);
|
|
374
|
+
break;
|
|
375
|
+
case 'point':
|
|
376
|
+
api.clearPointAllocators(input.group_id);
|
|
377
|
+
break;
|
|
378
|
+
case 'percent':
|
|
379
|
+
api.clearPercentAllocators(input.group_id);
|
|
380
|
+
break;
|
|
381
|
+
case 'number_slider':
|
|
382
|
+
api.clearNumberSliders(input.group_id);
|
|
383
|
+
break;
|
|
384
|
+
}
|
|
385
|
+
return {
|
|
386
|
+
data: { kind: input.kind, group_id: input.group_id, cleared: true },
|
|
387
|
+
display: `Cleared ${input.kind} group "${input.group_id}".`,
|
|
388
|
+
};
|
|
389
|
+
},
|
|
390
|
+
};
|
|
391
|
+
const readSelection = {
|
|
392
|
+
namespace: 'craft',
|
|
393
|
+
name: 'read_selection',
|
|
394
|
+
description: [
|
|
395
|
+
'Read what the user has selected/allocated in an overlay group.',
|
|
396
|
+
' - radio: the chosen value (or null).',
|
|
397
|
+
' - multi_select: the chosen values.',
|
|
398
|
+
' - point: the pool (total/used/remaining) and per-cell allocations.',
|
|
399
|
+
'Percent and number-slider values live in the cells themselves — read those with the normal cell/inspect tools.',
|
|
400
|
+
].join('\n'),
|
|
401
|
+
mutates: false,
|
|
402
|
+
confirmation: 'never',
|
|
403
|
+
cost: 'cheap',
|
|
404
|
+
inputSchema: {
|
|
405
|
+
properties: {
|
|
406
|
+
kind: { type: 'string', enum: ['radio', 'multi_select', 'point'] },
|
|
407
|
+
group_id: GROUP_ID_SCHEMA,
|
|
408
|
+
},
|
|
409
|
+
required: ['kind', 'group_id'],
|
|
410
|
+
},
|
|
411
|
+
handler: async (input, ctx) => {
|
|
412
|
+
var _a;
|
|
413
|
+
const r = requireApi(ctx);
|
|
414
|
+
if ('unavailable' in r)
|
|
415
|
+
return r.unavailable;
|
|
416
|
+
const { api } = r;
|
|
417
|
+
switch (input.kind) {
|
|
418
|
+
case 'radio': {
|
|
419
|
+
const selected = (_a = api.getRadioSelection(input.group_id)) !== null && _a !== void 0 ? _a : null;
|
|
420
|
+
return {
|
|
421
|
+
data: { group_id: input.group_id, selected },
|
|
422
|
+
display: `radio "${input.group_id}" = ${selected !== null && selected !== void 0 ? selected : '(none)'}`,
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
case 'multi_select': {
|
|
426
|
+
const selected = api.getMultiSelectSelections(input.group_id);
|
|
427
|
+
return {
|
|
428
|
+
data: { group_id: input.group_id, selected },
|
|
429
|
+
display: `multi-select "${input.group_id}" = [${selected.join(', ')}]`,
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
case 'point': {
|
|
433
|
+
const pool = api.getPointPool(input.group_id);
|
|
434
|
+
const allocations = api.getPointAllocations(input.group_id);
|
|
435
|
+
return {
|
|
436
|
+
data: { group_id: input.group_id, pool, allocations },
|
|
437
|
+
display: `points "${input.group_id}": ${pool.used}/${pool.total} used`,
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
},
|
|
442
|
+
};
|
|
443
|
+
// ---------------------------------------------------------------------------
|
|
444
|
+
// Bundle
|
|
445
|
+
// ---------------------------------------------------------------------------
|
|
446
|
+
export const CRAFT_INTERACTION_TOOLS = [
|
|
447
|
+
registerRadioGroup,
|
|
448
|
+
registerMultiSelectGroup,
|
|
449
|
+
registerPointAllocator,
|
|
450
|
+
registerPercentAllocator,
|
|
451
|
+
registerNumberSlider,
|
|
452
|
+
clearInteraction,
|
|
453
|
+
readSelection,
|
|
454
|
+
];
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Edit tools — modify data in an existing workbook without changing
|
|
3
|
+
* structure. Use builder tools for create_sheet / create_block /
|
|
4
|
+
* set_field_rule; use these for "change this value", "fill these in",
|
|
5
|
+
* "wipe this block". Preview is a dry-run wrapper around a batch of
|
|
6
|
+
* edits, leveraging the workbook's temp-transaction machinery.
|
|
7
|
+
*/
|
|
8
|
+
import type { Tool } from '../tool.js';
|
|
9
|
+
export type CellValue = string | number | boolean | null;
|
|
10
|
+
export interface BlockCellChange {
|
|
11
|
+
block: string;
|
|
12
|
+
row_key: string;
|
|
13
|
+
field: string;
|
|
14
|
+
value: CellValue;
|
|
15
|
+
}
|
|
16
|
+
interface SetBlockCellsInput {
|
|
17
|
+
changes: ReadonlyArray<BlockCellChange>;
|
|
18
|
+
}
|
|
19
|
+
interface SetBlockCellsOutput {
|
|
20
|
+
/** Number of changes successfully translated into BlockInput
|
|
21
|
+
* payloads (== changes.length when the call returns; rejections
|
|
22
|
+
* surface as thrown errors before commit). */
|
|
23
|
+
applied: number;
|
|
24
|
+
}
|
|
25
|
+
export declare const setBlockCells: Tool<SetBlockCellsInput, SetBlockCellsOutput>;
|
|
26
|
+
interface ClearBlockInput {
|
|
27
|
+
block: string;
|
|
28
|
+
/** Don\'t actually clear — just report how many rows would be removed. */
|
|
29
|
+
dry_run?: boolean;
|
|
30
|
+
}
|
|
31
|
+
export declare const clearBlock: Tool<ClearBlockInput, {
|
|
32
|
+
rows_cleared: number;
|
|
33
|
+
}>;
|
|
34
|
+
interface PreviewChangesInput {
|
|
35
|
+
changes: ReadonlyArray<BlockCellChange>;
|
|
36
|
+
}
|
|
37
|
+
interface PreviewDiffEntry {
|
|
38
|
+
/** Block ref name if the cell falls inside a block, else null. */
|
|
39
|
+
block: string | null;
|
|
40
|
+
row_key: string | null;
|
|
41
|
+
field: string | null;
|
|
42
|
+
sheet_idx: number;
|
|
43
|
+
row: number;
|
|
44
|
+
col: number;
|
|
45
|
+
before: CellValue;
|
|
46
|
+
after: CellValue;
|
|
47
|
+
}
|
|
48
|
+
interface PreviewChangesOutput {
|
|
49
|
+
/** Every cell whose value changes — both directly written cells and
|
|
50
|
+
* cascaded recalculations (formula deps, value_formula fields). */
|
|
51
|
+
diff: PreviewDiffEntry[];
|
|
52
|
+
}
|
|
53
|
+
export declare const previewChanges: Tool<PreviewChangesInput, PreviewChangesOutput>;
|
|
54
|
+
export declare const EDIT_TOOLS: Tool[];
|
|
55
|
+
export {};
|