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,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cell tools — general (non-block) read/write of arbitrary cells by
|
|
3
|
+
* (sheet, row, col). The build/edit tools are block-shaped; these cover
|
|
4
|
+
* ordinary spreadsheet work: "what's in B2:D10", "put 5 in A1", "clear this
|
|
5
|
+
* range". Watson addresses cells by zero-based row/col; results echo A1 refs
|
|
6
|
+
* so the model can talk about them naturally.
|
|
7
|
+
*/
|
|
8
|
+
import type { Tool } from '../tool.js';
|
|
9
|
+
/** A cell value in a form the model reads directly (null = empty). */
|
|
10
|
+
type ScalarValue = string | number | boolean | null;
|
|
11
|
+
interface RangeInput {
|
|
12
|
+
sheetIdx: number;
|
|
13
|
+
startRow: number;
|
|
14
|
+
startCol: number;
|
|
15
|
+
endRow: number;
|
|
16
|
+
endCol: number;
|
|
17
|
+
}
|
|
18
|
+
interface CellReadout {
|
|
19
|
+
ref: string;
|
|
20
|
+
value: ScalarValue;
|
|
21
|
+
/** Present only when the cell holds a formula. */
|
|
22
|
+
formula?: string;
|
|
23
|
+
}
|
|
24
|
+
export declare const getCells: Tool<RangeInput, {
|
|
25
|
+
range: string;
|
|
26
|
+
cells: CellReadout[];
|
|
27
|
+
}>;
|
|
28
|
+
interface CellWrite {
|
|
29
|
+
row: number;
|
|
30
|
+
col: number;
|
|
31
|
+
content: string | number | boolean | null;
|
|
32
|
+
}
|
|
33
|
+
interface SetCellsInput {
|
|
34
|
+
sheetIdx: number;
|
|
35
|
+
cells: ReadonlyArray<CellWrite>;
|
|
36
|
+
}
|
|
37
|
+
export declare const setCells: Tool<SetCellsInput, {
|
|
38
|
+
written: number;
|
|
39
|
+
}>;
|
|
40
|
+
export declare const clearCells: Tool<RangeInput, {
|
|
41
|
+
cleared: number;
|
|
42
|
+
}>;
|
|
43
|
+
interface FillInput {
|
|
44
|
+
sheetIdx: number;
|
|
45
|
+
srcStartRow: number;
|
|
46
|
+
srcStartCol: number;
|
|
47
|
+
srcEndRow: number;
|
|
48
|
+
srcEndCol: number;
|
|
49
|
+
dstStartRow: number;
|
|
50
|
+
dstStartCol: number;
|
|
51
|
+
dstEndRow: number;
|
|
52
|
+
dstEndCol: number;
|
|
53
|
+
}
|
|
54
|
+
export declare const fillCells: Tool<FillInput, {
|
|
55
|
+
filled: number;
|
|
56
|
+
}>;
|
|
57
|
+
export declare const CELL_TOOLS: Tool[];
|
|
58
|
+
export {};
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cell tools — general (non-block) read/write of arbitrary cells by
|
|
3
|
+
* (sheet, row, col). The build/edit tools are block-shaped; these cover
|
|
4
|
+
* ordinary spreadsheet work: "what's in B2:D10", "put 5 in A1", "clear this
|
|
5
|
+
* range". Watson addresses cells by zero-based row/col; results echo A1 refs
|
|
6
|
+
* so the model can talk about them naturally.
|
|
7
|
+
*/
|
|
8
|
+
import { isErrorMessage } from 'logisheets-web/pure';
|
|
9
|
+
function asClient(ctx) {
|
|
10
|
+
return ctx.workbook;
|
|
11
|
+
}
|
|
12
|
+
async function commit(client, payloads, label) {
|
|
13
|
+
const tx = { payloads, undoable: true, temp: false };
|
|
14
|
+
const result = await client.handleTransaction({ transaction: tx });
|
|
15
|
+
if (isErrorMessage(result))
|
|
16
|
+
throw new Error(`${label}: ${result.msg}`);
|
|
17
|
+
if (result.status.type === 'err')
|
|
18
|
+
throw new Error(`${label}: status code ${result.status.value}`);
|
|
19
|
+
}
|
|
20
|
+
function valueToScalar(v) {
|
|
21
|
+
if (v === 'empty')
|
|
22
|
+
return null;
|
|
23
|
+
switch (v.type) {
|
|
24
|
+
case 'number':
|
|
25
|
+
return v.value;
|
|
26
|
+
case 'str':
|
|
27
|
+
return v.value;
|
|
28
|
+
case 'bool':
|
|
29
|
+
return v.value;
|
|
30
|
+
case 'error':
|
|
31
|
+
return `#${v.value}`;
|
|
32
|
+
default:
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
function colToA1(col) {
|
|
37
|
+
let c = col + 1;
|
|
38
|
+
let s = '';
|
|
39
|
+
while (c > 0) {
|
|
40
|
+
const m = (c - 1) % 26;
|
|
41
|
+
s = String.fromCharCode(65 + m) + s;
|
|
42
|
+
c = (c - m - 1) / 26;
|
|
43
|
+
}
|
|
44
|
+
return s;
|
|
45
|
+
}
|
|
46
|
+
function a1(row, col) {
|
|
47
|
+
return `${colToA1(col)}${row + 1}`;
|
|
48
|
+
}
|
|
49
|
+
/** cap on how many cells one call may read/clear, to keep tool calls bounded. */
|
|
50
|
+
const MAX_RANGE = 500;
|
|
51
|
+
/** cap on how many cells one set_cells call may write. */
|
|
52
|
+
const MAX_WRITE = 200;
|
|
53
|
+
const RANGE_SCHEMA = {
|
|
54
|
+
sheetIdx: { type: 'integer', description: 'Zero-based sheet index.' },
|
|
55
|
+
startRow: { type: 'integer', description: 'Zero-based first row.' },
|
|
56
|
+
startCol: { type: 'integer', description: 'Zero-based first column.' },
|
|
57
|
+
endRow: { type: 'integer', description: 'Zero-based last row (inclusive).' },
|
|
58
|
+
endCol: { type: 'integer', description: 'Zero-based last column (inclusive).' },
|
|
59
|
+
};
|
|
60
|
+
function normalizeRange(i) {
|
|
61
|
+
return {
|
|
62
|
+
sheetIdx: i.sheetIdx,
|
|
63
|
+
startRow: Math.min(i.startRow, i.endRow),
|
|
64
|
+
startCol: Math.min(i.startCol, i.endCol),
|
|
65
|
+
endRow: Math.max(i.startRow, i.endRow),
|
|
66
|
+
endCol: Math.max(i.startCol, i.endCol),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
export const getCells = {
|
|
70
|
+
namespace: 'cell',
|
|
71
|
+
name: 'get_cells',
|
|
72
|
+
description: [
|
|
73
|
+
'Read the values (and formulas) in a rectangular range of cells, addressed by zero-based (sheetIdx, startRow, startCol, endRow, endCol).',
|
|
74
|
+
'Returns only the non-empty cells, each with its A1 ref, value, and formula (if any). Use this for ordinary "what is in these cells" questions; for a single computed result you can also use build eval_formula.',
|
|
75
|
+
`Reads at most ${MAX_RANGE} cells per call — narrow the range if it is bigger.`,
|
|
76
|
+
].join('\n'),
|
|
77
|
+
mutates: false,
|
|
78
|
+
confirmation: 'never',
|
|
79
|
+
inputSchema: { properties: RANGE_SCHEMA, required: Object.keys(RANGE_SCHEMA) },
|
|
80
|
+
handler: async (input, ctx) => {
|
|
81
|
+
const r = normalizeRange(input);
|
|
82
|
+
const count = (r.endRow - r.startRow + 1) * (r.endCol - r.startCol + 1);
|
|
83
|
+
if (count > MAX_RANGE)
|
|
84
|
+
throw new Error(`range covers ${count} cells (max ${MAX_RANGE}); narrow it`);
|
|
85
|
+
const client = asClient(ctx);
|
|
86
|
+
// getCells (not getCellInfos — the latter is unimplemented in the
|
|
87
|
+
// engine worker) returns the range's CellInfo[] row-major.
|
|
88
|
+
const res = await client.getCells({
|
|
89
|
+
sheetIdx: r.sheetIdx,
|
|
90
|
+
startRow: r.startRow,
|
|
91
|
+
startCol: r.startCol,
|
|
92
|
+
endRow: r.endRow,
|
|
93
|
+
endCol: r.endCol,
|
|
94
|
+
});
|
|
95
|
+
if (isErrorMessage(res))
|
|
96
|
+
throw new Error(`get_cells: ${res.msg}`);
|
|
97
|
+
const infos = res;
|
|
98
|
+
const width = r.endCol - r.startCol + 1;
|
|
99
|
+
const cells = [];
|
|
100
|
+
infos.forEach((info, i) => {
|
|
101
|
+
const value = valueToScalar(info.value);
|
|
102
|
+
const formula = info.formula || undefined;
|
|
103
|
+
if (value === null && !formula)
|
|
104
|
+
return; // skip empties → compact
|
|
105
|
+
const row = r.startRow + Math.floor(i / width);
|
|
106
|
+
const col = r.startCol + (i % width);
|
|
107
|
+
const out = { ref: a1(row, col), value };
|
|
108
|
+
if (formula)
|
|
109
|
+
out.formula = formula;
|
|
110
|
+
cells.push(out);
|
|
111
|
+
});
|
|
112
|
+
return {
|
|
113
|
+
data: { range: `${a1(r.startRow, r.startCol)}:${a1(r.endRow, r.endCol)}`, cells },
|
|
114
|
+
display: `Read ${cells.length} non-empty cell(s) in ${a1(r.startRow, r.startCol)}:${a1(r.endRow, r.endCol)}`,
|
|
115
|
+
};
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
function contentToString(v) {
|
|
119
|
+
if (v === null || v === undefined)
|
|
120
|
+
return '';
|
|
121
|
+
if (typeof v === 'string')
|
|
122
|
+
return v;
|
|
123
|
+
return String(v);
|
|
124
|
+
}
|
|
125
|
+
export const setCells = {
|
|
126
|
+
namespace: 'cell',
|
|
127
|
+
name: 'set_cells',
|
|
128
|
+
description: [
|
|
129
|
+
'Write one or more arbitrary cells on a sheet in a single atomic transaction (one undo step). Each cell is addressed by zero-based row/col.',
|
|
130
|
+
"Content is a literal (string / number / boolean) or a formula prefixed with '='. null or '' clears the cell.",
|
|
131
|
+
'For cells that belong to a block, prefer edit set_block_cells (it respects the schema); use this for plain, non-block cells.',
|
|
132
|
+
`At most ${MAX_WRITE} cells per call.`,
|
|
133
|
+
].join('\n'),
|
|
134
|
+
mutates: true,
|
|
135
|
+
confirmation: 'always',
|
|
136
|
+
inputSchema: {
|
|
137
|
+
properties: {
|
|
138
|
+
sheetIdx: { type: 'integer', description: 'Zero-based sheet index.' },
|
|
139
|
+
cells: {
|
|
140
|
+
type: 'array',
|
|
141
|
+
minItems: 1,
|
|
142
|
+
items: {
|
|
143
|
+
type: 'object',
|
|
144
|
+
properties: {
|
|
145
|
+
row: { type: 'integer', description: 'Zero-based row.' },
|
|
146
|
+
col: { type: 'integer', description: 'Zero-based column.' },
|
|
147
|
+
content: {
|
|
148
|
+
type: ['string', 'number', 'boolean', 'null'],
|
|
149
|
+
description: "Literal value, or a formula starting with '='. null clears.",
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
required: ['row', 'col', 'content'],
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
},
|
|
156
|
+
required: ['sheetIdx', 'cells'],
|
|
157
|
+
},
|
|
158
|
+
handler: async (input, ctx) => {
|
|
159
|
+
if (input.cells.length > MAX_WRITE)
|
|
160
|
+
throw new Error(`${input.cells.length} cells (max ${MAX_WRITE}) — split into batches`);
|
|
161
|
+
const payloads = input.cells.map((c) => ({
|
|
162
|
+
type: 'cellInput',
|
|
163
|
+
value: {
|
|
164
|
+
sheetIdx: input.sheetIdx,
|
|
165
|
+
row: c.row,
|
|
166
|
+
col: c.col,
|
|
167
|
+
content: contentToString(c.content),
|
|
168
|
+
},
|
|
169
|
+
}));
|
|
170
|
+
await commit(asClient(ctx), payloads, 'set_cells');
|
|
171
|
+
return {
|
|
172
|
+
data: { written: input.cells.length },
|
|
173
|
+
display: `Wrote ${input.cells.length} cell(s)`,
|
|
174
|
+
};
|
|
175
|
+
},
|
|
176
|
+
};
|
|
177
|
+
// ---------------------------------------------------------------------------
|
|
178
|
+
// clear_cells — clear the values in a rectangular range
|
|
179
|
+
// ---------------------------------------------------------------------------
|
|
180
|
+
export const clearCells = {
|
|
181
|
+
namespace: 'cell',
|
|
182
|
+
name: 'clear_cells',
|
|
183
|
+
description: [
|
|
184
|
+
'Clear the contents of every cell in a rectangular range, addressed by zero-based (sheetIdx, startRow, startCol, endRow, endCol). One undo step.',
|
|
185
|
+
`Clears at most ${MAX_RANGE} cells per call.`,
|
|
186
|
+
].join('\n'),
|
|
187
|
+
mutates: true,
|
|
188
|
+
confirmation: 'always',
|
|
189
|
+
inputSchema: { properties: RANGE_SCHEMA, required: Object.keys(RANGE_SCHEMA) },
|
|
190
|
+
handler: async (input, ctx) => {
|
|
191
|
+
const r = normalizeRange(input);
|
|
192
|
+
const count = (r.endRow - r.startRow + 1) * (r.endCol - r.startCol + 1);
|
|
193
|
+
if (count > MAX_RANGE)
|
|
194
|
+
throw new Error(`range covers ${count} cells (max ${MAX_RANGE}); narrow it`);
|
|
195
|
+
const payloads = [];
|
|
196
|
+
for (let row = r.startRow; row <= r.endRow; row++)
|
|
197
|
+
for (let col = r.startCol; col <= r.endCol; col++)
|
|
198
|
+
payloads.push({
|
|
199
|
+
type: 'cellClear',
|
|
200
|
+
value: { sheetIdx: r.sheetIdx, row, col },
|
|
201
|
+
});
|
|
202
|
+
await commit(asClient(ctx), payloads, 'clear_cells');
|
|
203
|
+
return {
|
|
204
|
+
data: { cleared: count },
|
|
205
|
+
display: `Cleared ${a1(r.startRow, r.startCol)}:${a1(r.endRow, r.endCol)}`,
|
|
206
|
+
};
|
|
207
|
+
},
|
|
208
|
+
};
|
|
209
|
+
export const fillCells = {
|
|
210
|
+
namespace: 'cell',
|
|
211
|
+
name: 'fill',
|
|
212
|
+
description: [
|
|
213
|
+
'Autofill a destination range from a source range — the "drag the fill handle" operation. Continues number/date series, copies values, and shifts relative formula references. All coordinates zero-based.',
|
|
214
|
+
'The destination should extend the source (e.g. src A1:A1, dst A2:A10 to fill down from A1; or src A1:B1, dst A1:B10 to fill that pair down).',
|
|
215
|
+
].join('\n'),
|
|
216
|
+
mutates: true,
|
|
217
|
+
confirmation: 'always',
|
|
218
|
+
inputSchema: {
|
|
219
|
+
properties: {
|
|
220
|
+
sheetIdx: { type: 'integer' },
|
|
221
|
+
srcStartRow: { type: 'integer' },
|
|
222
|
+
srcStartCol: { type: 'integer' },
|
|
223
|
+
srcEndRow: { type: 'integer' },
|
|
224
|
+
srcEndCol: { type: 'integer' },
|
|
225
|
+
dstStartRow: { type: 'integer' },
|
|
226
|
+
dstStartCol: { type: 'integer' },
|
|
227
|
+
dstEndRow: { type: 'integer' },
|
|
228
|
+
dstEndCol: { type: 'integer' },
|
|
229
|
+
},
|
|
230
|
+
required: [
|
|
231
|
+
'sheetIdx',
|
|
232
|
+
'srcStartRow',
|
|
233
|
+
'srcStartCol',
|
|
234
|
+
'srcEndRow',
|
|
235
|
+
'srcEndCol',
|
|
236
|
+
'dstStartRow',
|
|
237
|
+
'dstStartCol',
|
|
238
|
+
'dstEndRow',
|
|
239
|
+
'dstEndCol',
|
|
240
|
+
],
|
|
241
|
+
},
|
|
242
|
+
handler: async (input, ctx) => {
|
|
243
|
+
const client = asClient(ctx);
|
|
244
|
+
// The engine predicts the filled cells (series continuation, relative
|
|
245
|
+
// formula shifting), then we commit them as ordinary cell inputs.
|
|
246
|
+
const predicted = await client.predictFill(input);
|
|
247
|
+
if (isErrorMessage(predicted))
|
|
248
|
+
throw new Error(`fill: ${predicted.msg}`);
|
|
249
|
+
const inputs = predicted;
|
|
250
|
+
const payloads = inputs.map((ci) => ({
|
|
251
|
+
type: 'cellInput',
|
|
252
|
+
value: ci,
|
|
253
|
+
}));
|
|
254
|
+
await commit(client, payloads, 'fill');
|
|
255
|
+
return { data: { filled: payloads.length }, display: `Filled ${payloads.length} cell(s)` };
|
|
256
|
+
},
|
|
257
|
+
};
|
|
258
|
+
export const CELL_TOOLS = [
|
|
259
|
+
getCells,
|
|
260
|
+
setCells,
|
|
261
|
+
clearCells,
|
|
262
|
+
fillCells,
|
|
263
|
+
];
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Comment tools — threaded cell comments. A comment is a thread anchored at a
|
|
3
|
+
* cell, with one or more notes (a root note + replies). Watson can list, add,
|
|
4
|
+
* reply, edit, resolve, and delete.
|
|
5
|
+
*
|
|
6
|
+
* Authorship: comments Watson creates are attributed to "Watson (AI)" by
|
|
7
|
+
* default (honest — the assistant added them). The user can override the name
|
|
8
|
+
* per call (e.g. "add a comment as Alice"). There is no global "current user"
|
|
9
|
+
* in the open-source app, so we don't invent one.
|
|
10
|
+
*/
|
|
11
|
+
import type { Tool } from '../tool.js';
|
|
12
|
+
export declare const listComments: Tool<{
|
|
13
|
+
sheetIdx: number;
|
|
14
|
+
}, unknown>;
|
|
15
|
+
export declare const addComment: Tool<{
|
|
16
|
+
sheetIdx: number;
|
|
17
|
+
row: number;
|
|
18
|
+
col: number;
|
|
19
|
+
content: string;
|
|
20
|
+
authorName?: string;
|
|
21
|
+
}, {
|
|
22
|
+
commentId: string;
|
|
23
|
+
cell: string;
|
|
24
|
+
}>;
|
|
25
|
+
export declare const replyComment: Tool<{
|
|
26
|
+
sheetIdx: number;
|
|
27
|
+
parentId: string;
|
|
28
|
+
content: string;
|
|
29
|
+
authorName?: string;
|
|
30
|
+
}, {
|
|
31
|
+
commentId: string;
|
|
32
|
+
cell: string;
|
|
33
|
+
}>;
|
|
34
|
+
export declare const editComment: Tool<{
|
|
35
|
+
sheetIdx: number;
|
|
36
|
+
commentId: string;
|
|
37
|
+
content: string;
|
|
38
|
+
}, {
|
|
39
|
+
ok: true;
|
|
40
|
+
}>;
|
|
41
|
+
export declare const resolveComment: Tool<{
|
|
42
|
+
sheetIdx: number;
|
|
43
|
+
commentId: string;
|
|
44
|
+
resolved: boolean;
|
|
45
|
+
}, {
|
|
46
|
+
ok: true;
|
|
47
|
+
}>;
|
|
48
|
+
export declare const deleteComment: Tool<{
|
|
49
|
+
sheetIdx: number;
|
|
50
|
+
commentId: string;
|
|
51
|
+
}, {
|
|
52
|
+
ok: true;
|
|
53
|
+
}>;
|
|
54
|
+
export declare const COMMENT_TOOLS: Tool[];
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Comment tools — threaded cell comments. A comment is a thread anchored at a
|
|
3
|
+
* cell, with one or more notes (a root note + replies). Watson can list, add,
|
|
4
|
+
* reply, edit, resolve, and delete.
|
|
5
|
+
*
|
|
6
|
+
* Authorship: comments Watson creates are attributed to "Watson (AI)" by
|
|
7
|
+
* default (honest — the assistant added them). The user can override the name
|
|
8
|
+
* per call (e.g. "add a comment as Alice"). There is no global "current user"
|
|
9
|
+
* in the open-source app, so we don't invent one.
|
|
10
|
+
*/
|
|
11
|
+
import { isErrorMessage } from 'logisheets-web/pure';
|
|
12
|
+
function asClient(ctx) {
|
|
13
|
+
return ctx.workbook;
|
|
14
|
+
}
|
|
15
|
+
async function commit(client, payload, label) {
|
|
16
|
+
const tx = { payloads: [payload], undoable: true, temp: false };
|
|
17
|
+
const r = await client.handleTransaction({ transaction: tx });
|
|
18
|
+
if (isErrorMessage(r))
|
|
19
|
+
throw new Error(`${label}: ${r.msg}`);
|
|
20
|
+
if (r.status.type === 'err')
|
|
21
|
+
throw new Error(`${label}: status code ${r.status.value}`);
|
|
22
|
+
}
|
|
23
|
+
const DEFAULT_AUTHOR = 'Watson (AI)';
|
|
24
|
+
function newGuid() {
|
|
25
|
+
var _a, _b, _c;
|
|
26
|
+
const g = globalThis;
|
|
27
|
+
const uuid = (_c = (_b = (_a = g.crypto) === null || _a === void 0 ? void 0 : _a.randomUUID) === null || _b === void 0 ? void 0 : _b.call(_a)) !== null && _c !== void 0 ? _c : `${Math.random().toString(36).slice(2)}${Date.now().toString(36)}`;
|
|
28
|
+
return `{${uuid}}`;
|
|
29
|
+
}
|
|
30
|
+
function nowIso() {
|
|
31
|
+
return new Date().toISOString();
|
|
32
|
+
}
|
|
33
|
+
function colToA1(col) {
|
|
34
|
+
let c = col + 1;
|
|
35
|
+
let s = '';
|
|
36
|
+
while (c > 0) {
|
|
37
|
+
const m = (c - 1) % 26;
|
|
38
|
+
s = String.fromCharCode(65 + m) + s;
|
|
39
|
+
c = (c - m - 1) / 26;
|
|
40
|
+
}
|
|
41
|
+
return s;
|
|
42
|
+
}
|
|
43
|
+
function a1(row, col) {
|
|
44
|
+
return `${colToA1(col)}${row + 1}`;
|
|
45
|
+
}
|
|
46
|
+
async function readComments(client, sheetIdx) {
|
|
47
|
+
const res = await client.getComments({ sheetIdx });
|
|
48
|
+
if (isErrorMessage(res))
|
|
49
|
+
throw new Error(`comments: ${res.msg}`);
|
|
50
|
+
return res;
|
|
51
|
+
}
|
|
52
|
+
// ---------------------------------------------------------------------------
|
|
53
|
+
export const listComments = {
|
|
54
|
+
namespace: 'comment',
|
|
55
|
+
name: 'list_comments',
|
|
56
|
+
description: 'List the comment threads on a sheet. Each thread is anchored at a cell (A1 ref) and holds one or more notes (root + replies), each with its id, author, text, timestamp, and resolved flag.',
|
|
57
|
+
mutates: false,
|
|
58
|
+
confirmation: 'never',
|
|
59
|
+
inputSchema: {
|
|
60
|
+
properties: { sheetIdx: { type: 'integer', description: 'Zero-based sheet index.' } },
|
|
61
|
+
required: ['sheetIdx'],
|
|
62
|
+
},
|
|
63
|
+
handler: async (input, ctx) => {
|
|
64
|
+
const comments = await readComments(asClient(ctx), input.sheetIdx);
|
|
65
|
+
const threads = comments.map((c) => ({
|
|
66
|
+
cell: a1(c.row, c.col),
|
|
67
|
+
row: c.row,
|
|
68
|
+
col: c.col,
|
|
69
|
+
notes: c.notes.map((n) => {
|
|
70
|
+
var _a, _b;
|
|
71
|
+
return ({
|
|
72
|
+
id: n.id,
|
|
73
|
+
author: (_b = (_a = n.author) === null || _a === void 0 ? void 0 : _a.displayName) !== null && _b !== void 0 ? _b : '',
|
|
74
|
+
content: n.content,
|
|
75
|
+
dt: n.dt,
|
|
76
|
+
resolved: n.resolved,
|
|
77
|
+
parentId: n.parentId,
|
|
78
|
+
});
|
|
79
|
+
}),
|
|
80
|
+
}));
|
|
81
|
+
return { data: { threads }, display: `${threads.length} comment thread(s)` };
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
export const addComment = {
|
|
85
|
+
namespace: 'comment',
|
|
86
|
+
name: 'add_comment',
|
|
87
|
+
description: 'Start a new comment thread on a cell (zero-based row/col). Attributed to "Watson (AI)" unless you pass authorName.',
|
|
88
|
+
mutates: true,
|
|
89
|
+
confirmation: 'always',
|
|
90
|
+
inputSchema: {
|
|
91
|
+
properties: {
|
|
92
|
+
sheetIdx: { type: 'integer' },
|
|
93
|
+
row: { type: 'integer' },
|
|
94
|
+
col: { type: 'integer' },
|
|
95
|
+
content: { type: 'string' },
|
|
96
|
+
authorName: { type: 'string', description: 'Override the comment author name.' },
|
|
97
|
+
},
|
|
98
|
+
required: ['sheetIdx', 'row', 'col', 'content'],
|
|
99
|
+
},
|
|
100
|
+
handler: async (input, ctx) => {
|
|
101
|
+
const commentId = newGuid();
|
|
102
|
+
await commit(asClient(ctx), {
|
|
103
|
+
type: 'addComment',
|
|
104
|
+
value: {
|
|
105
|
+
sheetIdx: input.sheetIdx,
|
|
106
|
+
row: input.row,
|
|
107
|
+
col: input.col,
|
|
108
|
+
commentId,
|
|
109
|
+
author: { displayName: input.authorName || DEFAULT_AUTHOR },
|
|
110
|
+
dt: nowIso(),
|
|
111
|
+
content: input.content,
|
|
112
|
+
mentions: [],
|
|
113
|
+
},
|
|
114
|
+
}, 'add_comment');
|
|
115
|
+
return {
|
|
116
|
+
data: { commentId, cell: a1(input.row, input.col) },
|
|
117
|
+
display: `Commented on ${a1(input.row, input.col)}`,
|
|
118
|
+
};
|
|
119
|
+
},
|
|
120
|
+
};
|
|
121
|
+
export const replyComment = {
|
|
122
|
+
namespace: 'comment',
|
|
123
|
+
name: 'reply_comment',
|
|
124
|
+
description: 'Reply to an existing comment thread. Pass the parentId of any note in the thread (from list_comments).',
|
|
125
|
+
mutates: true,
|
|
126
|
+
confirmation: 'always',
|
|
127
|
+
inputSchema: {
|
|
128
|
+
properties: {
|
|
129
|
+
sheetIdx: { type: 'integer' },
|
|
130
|
+
parentId: { type: 'string', description: 'Id of a note in the target thread.' },
|
|
131
|
+
content: { type: 'string' },
|
|
132
|
+
authorName: { type: 'string' },
|
|
133
|
+
},
|
|
134
|
+
required: ['sheetIdx', 'parentId', 'content'],
|
|
135
|
+
},
|
|
136
|
+
handler: async (input, ctx) => {
|
|
137
|
+
const client = asClient(ctx);
|
|
138
|
+
const comments = await readComments(client, input.sheetIdx);
|
|
139
|
+
const thread = comments.find((c) => c.notes.some((n) => n.id === input.parentId));
|
|
140
|
+
if (!thread)
|
|
141
|
+
throw new Error(`reply_comment: no thread contains "${input.parentId}"`);
|
|
142
|
+
const commentId = newGuid();
|
|
143
|
+
await commit(client, {
|
|
144
|
+
type: 'addComment',
|
|
145
|
+
value: {
|
|
146
|
+
sheetIdx: input.sheetIdx,
|
|
147
|
+
row: thread.row,
|
|
148
|
+
col: thread.col,
|
|
149
|
+
commentId,
|
|
150
|
+
parentId: input.parentId,
|
|
151
|
+
author: { displayName: input.authorName || DEFAULT_AUTHOR },
|
|
152
|
+
dt: nowIso(),
|
|
153
|
+
content: input.content,
|
|
154
|
+
mentions: [],
|
|
155
|
+
},
|
|
156
|
+
}, 'reply_comment');
|
|
157
|
+
return { data: { commentId, cell: a1(thread.row, thread.col) }, display: 'Replied' };
|
|
158
|
+
},
|
|
159
|
+
};
|
|
160
|
+
export const editComment = {
|
|
161
|
+
namespace: 'comment',
|
|
162
|
+
name: 'edit_comment',
|
|
163
|
+
description: 'Change the text of an existing comment note (by its id).',
|
|
164
|
+
mutates: true,
|
|
165
|
+
confirmation: 'always',
|
|
166
|
+
inputSchema: {
|
|
167
|
+
properties: {
|
|
168
|
+
sheetIdx: { type: 'integer' },
|
|
169
|
+
commentId: { type: 'string' },
|
|
170
|
+
content: { type: 'string' },
|
|
171
|
+
},
|
|
172
|
+
required: ['sheetIdx', 'commentId', 'content'],
|
|
173
|
+
},
|
|
174
|
+
handler: async (input, ctx) => {
|
|
175
|
+
await commit(asClient(ctx), {
|
|
176
|
+
type: 'editComment',
|
|
177
|
+
value: {
|
|
178
|
+
sheetIdx: input.sheetIdx,
|
|
179
|
+
commentId: input.commentId,
|
|
180
|
+
content: input.content,
|
|
181
|
+
mentions: [],
|
|
182
|
+
},
|
|
183
|
+
}, 'edit_comment');
|
|
184
|
+
return { data: { ok: true }, display: 'Edited' };
|
|
185
|
+
},
|
|
186
|
+
};
|
|
187
|
+
export const resolveComment = {
|
|
188
|
+
namespace: 'comment',
|
|
189
|
+
name: 'resolve_comment',
|
|
190
|
+
description: 'Mark a comment thread resolved (or unresolved) by a note id.',
|
|
191
|
+
mutates: true,
|
|
192
|
+
confirmation: 'never',
|
|
193
|
+
inputSchema: {
|
|
194
|
+
properties: {
|
|
195
|
+
sheetIdx: { type: 'integer' },
|
|
196
|
+
commentId: { type: 'string' },
|
|
197
|
+
resolved: { type: 'boolean' },
|
|
198
|
+
},
|
|
199
|
+
required: ['sheetIdx', 'commentId', 'resolved'],
|
|
200
|
+
},
|
|
201
|
+
handler: async (input, ctx) => {
|
|
202
|
+
await commit(asClient(ctx), { type: 'resolveComment', value: input }, 'resolve_comment');
|
|
203
|
+
return {
|
|
204
|
+
data: { ok: true },
|
|
205
|
+
display: input.resolved ? 'Resolved' : 'Reopened',
|
|
206
|
+
};
|
|
207
|
+
},
|
|
208
|
+
};
|
|
209
|
+
export const deleteComment = {
|
|
210
|
+
namespace: 'comment',
|
|
211
|
+
name: 'delete_comment',
|
|
212
|
+
description: 'Delete a comment note by its id. Deleting a thread\'s root note removes the whole thread.',
|
|
213
|
+
mutates: true,
|
|
214
|
+
confirmation: 'destructive',
|
|
215
|
+
inputSchema: {
|
|
216
|
+
properties: {
|
|
217
|
+
sheetIdx: { type: 'integer' },
|
|
218
|
+
commentId: { type: 'string' },
|
|
219
|
+
},
|
|
220
|
+
required: ['sheetIdx', 'commentId'],
|
|
221
|
+
},
|
|
222
|
+
handler: async (input, ctx) => {
|
|
223
|
+
await commit(asClient(ctx), { type: 'deleteComment', value: input }, 'delete_comment');
|
|
224
|
+
return { data: { ok: true }, display: 'Deleted' };
|
|
225
|
+
},
|
|
226
|
+
};
|
|
227
|
+
export const COMMENT_TOOLS = [
|
|
228
|
+
listComments,
|
|
229
|
+
addComment,
|
|
230
|
+
replyComment,
|
|
231
|
+
editComment,
|
|
232
|
+
resolveComment,
|
|
233
|
+
deleteComment,
|
|
234
|
+
];
|
|
@@ -0,0 +1,22 @@
|
|
|
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 type { Tool } from '../tool.js';
|
|
22
|
+
export declare const CRAFT_INTERACTION_TOOLS: Tool[];
|