@things-factory/ai-assistant 10.1.90 → 10.1.97
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/client/components/assistant-chat.ts +31 -3
- package/client/components/assistant-dock.ts +9 -1
- package/client/components/entry-review.ts +56 -0
- package/client/components/workspace-view.ts +428 -0
- package/client/pages/assistant-workspace-page.ts +237 -0
- package/client/route.ts +9 -0
- package/client/utils/entry-proposal.ts +20 -5
- package/client/utils/workspace.ts +89 -0
- package/dist-client/components/assistant-chat.d.ts +2 -0
- package/dist-client/components/assistant-chat.js +32 -3
- package/dist-client/components/assistant-chat.js.map +1 -1
- package/dist-client/components/assistant-dock.d.ts +1 -0
- package/dist-client/components/assistant-dock.js +9 -1
- package/dist-client/components/assistant-dock.js.map +1 -1
- package/dist-client/components/entry-review.d.ts +5 -0
- package/dist-client/components/entry-review.js +55 -0
- package/dist-client/components/entry-review.js.map +1 -1
- package/dist-client/components/workspace-view.d.ts +19 -0
- package/dist-client/components/workspace-view.js +416 -0
- package/dist-client/components/workspace-view.js.map +1 -0
- package/dist-client/pages/assistant-workspace-page.d.ts +27 -0
- package/dist-client/pages/assistant-workspace-page.js +263 -0
- package/dist-client/pages/assistant-workspace-page.js.map +1 -0
- package/dist-client/route.d.ts +1 -0
- package/dist-client/route.js +10 -0
- package/dist-client/route.js.map +1 -0
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-client/utils/entry-proposal.d.ts +5 -0
- package/dist-client/utils/entry-proposal.js +16 -5
- package/dist-client/utils/entry-proposal.js.map +1 -1
- package/dist-client/utils/workspace.d.ts +40 -0
- package/dist-client/utils/workspace.js +68 -0
- package/dist-client/utils/workspace.js.map +1 -0
- package/dist-server/service/assistant-mode/resolve.d.ts +2 -2
- package/dist-server/service/assistant-mode/resolve.js +5 -3
- package/dist-server/service/assistant-mode/resolve.js.map +1 -1
- package/dist-server/service/data-entry/entry-apply.d.ts +3 -3
- package/dist-server/service/data-entry/entry-apply.js +55 -2
- package/dist-server/service/data-entry/entry-apply.js.map +1 -1
- package/dist-server/service/data-entry/entry-resolver.js +3 -7
- package/dist-server/service/data-entry/entry-resolver.js.map +1 -1
- package/dist-server/service/data-entry/entry-target.d.ts +51 -0
- package/dist-server/service/data-entry/entry-target.js +77 -34
- package/dist-server/service/data-entry/entry-target.js.map +1 -1
- package/dist-server/service/data-entry/entry-tools.d.ts +7 -1
- package/dist-server/service/data-entry/entry-tools.js +73 -3
- package/dist-server/service/data-entry/entry-tools.js.map +1 -1
- package/dist-server/service/data-entry/entry-update.d.ts +99 -0
- package/dist-server/service/data-entry/entry-update.js +196 -0
- package/dist-server/service/data-entry/entry-update.js.map +1 -0
- package/dist-server/service/index.d.ts +2 -0
- package/dist-server/service/index.js +3 -0
- package/dist-server/service/index.js.map +1 -1
- package/dist-server/service/workspace/workspace-blocks.d.ts +24 -0
- package/dist-server/service/workspace/workspace-blocks.js +118 -0
- package/dist-server/service/workspace/workspace-blocks.js.map +1 -0
- package/dist-server/service/workspace/workspace-tools.d.ts +24 -0
- package/dist-server/service/workspace/workspace-tools.js +65 -0
- package/dist-server/service/workspace/workspace-tools.js.map +1 -0
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/server/service/assistant-mode/resolve.test.ts +2 -1
- package/server/service/assistant-mode/resolve.ts +5 -3
- package/server/service/data-entry/data-entry-update.test.ts +192 -0
- package/server/service/data-entry/data-entry.test.ts +2 -2
- package/server/service/data-entry/entry-apply.ts +63 -6
- package/server/service/data-entry/entry-resolver.ts +4 -9
- package/server/service/data-entry/entry-target.ts +97 -31
- package/server/service/data-entry/entry-tools.ts +77 -3
- package/server/service/data-entry/entry-update.ts +209 -0
- package/server/service/index.ts +3 -0
- package/server/service/workspace/workspace-blocks.ts +127 -0
- package/server/service/workspace/workspace-tools.ts +66 -0
- package/server/service/workspace/workspace.test.ts +58 -0
- package/test/entry-proposal.test.ts +15 -2
- package/test/workspace.test.ts +54 -0
- package/things-factory.config.js +6 -1
- package/translations/en.json +36 -1
- package/translations/ja.json +36 -1
- package/translations/ko.json +36 -1
- package/translations/ms.json +36 -1
- package/translations/zh.json +36 -1
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { type EntryField, type EntryIssue, type EntryRecord, type EntryTarget } from './entry-target.js';
|
|
2
|
+
/** Runs one of the application's queries or mutations as the person. */
|
|
3
|
+
export type GraphqlRunner = (document: string, variables: Record<string, unknown>) => Promise<{
|
|
4
|
+
data?: any;
|
|
5
|
+
errors?: readonly {
|
|
6
|
+
message: string;
|
|
7
|
+
}[];
|
|
8
|
+
}>;
|
|
9
|
+
/** How the record of a change was picked - said on the card, so the person sees the ground of it. */
|
|
10
|
+
export type ChangeVia = 'current' | 'selected' | 'id';
|
|
11
|
+
export interface ChangeRow {
|
|
12
|
+
index: number;
|
|
13
|
+
id: string;
|
|
14
|
+
via: ChangeVia;
|
|
15
|
+
label?: string;
|
|
16
|
+
/** What the record holds now, for the fields that change - read by the server. */
|
|
17
|
+
before: Record<string, unknown>;
|
|
18
|
+
values: Record<string, unknown>;
|
|
19
|
+
issues: EntryIssue[];
|
|
20
|
+
}
|
|
21
|
+
export declare const MAX_CHANGES = 100;
|
|
22
|
+
/** The fields a target lets change. */
|
|
23
|
+
export declare function changeableFields(target: EntryTarget): EntryField[];
|
|
24
|
+
/**
|
|
25
|
+
* The records the changes name. `current` is the record on screen, `selected` every row selected there -
|
|
26
|
+
* both from the surface's host context, which is **not trusted**: an id from it is only a candidate, read
|
|
27
|
+
* through the application before anything is shown. Any other ref is an id, from the find tool.
|
|
28
|
+
*/
|
|
29
|
+
export declare function resolveChangeRefs(targetKey: string, changes: unknown, host: Record<string, any> | undefined): {
|
|
30
|
+
picked: {
|
|
31
|
+
id: string;
|
|
32
|
+
via: ChangeVia;
|
|
33
|
+
values: Record<string, unknown>;
|
|
34
|
+
}[];
|
|
35
|
+
unresolved: string[];
|
|
36
|
+
};
|
|
37
|
+
/** Reads the records by id through the application's read query, as the person. */
|
|
38
|
+
export declare function readRecords(target: EntryTarget, ids: string[], run: GraphqlRunner): Promise<Map<string, EntryRecord>>;
|
|
39
|
+
/** The change rows against what the records hold now. */
|
|
40
|
+
export declare function changeRows(target: EntryTarget, picked: {
|
|
41
|
+
id: string;
|
|
42
|
+
via: ChangeVia;
|
|
43
|
+
values: Record<string, unknown>;
|
|
44
|
+
}[], records: Map<string, EntryRecord>): ChangeRow[];
|
|
45
|
+
export declare function proposeChanges(target: EntryTarget, args: any, context: any, run: GraphqlRunner): Promise<{
|
|
46
|
+
rejected: boolean;
|
|
47
|
+
error: string;
|
|
48
|
+
message: string;
|
|
49
|
+
} | {
|
|
50
|
+
message: string;
|
|
51
|
+
reason?: any;
|
|
52
|
+
proposed: boolean;
|
|
53
|
+
kind: string;
|
|
54
|
+
op: string;
|
|
55
|
+
target: string;
|
|
56
|
+
labelKey: string;
|
|
57
|
+
label: string;
|
|
58
|
+
icon: string;
|
|
59
|
+
columns: {
|
|
60
|
+
labelKey?: string;
|
|
61
|
+
name: string;
|
|
62
|
+
type: import("./entry-target.js").EntryFieldType;
|
|
63
|
+
}[];
|
|
64
|
+
rows: ChangeRow[];
|
|
65
|
+
counts: {
|
|
66
|
+
rows: number;
|
|
67
|
+
ready: number;
|
|
68
|
+
blocked: number;
|
|
69
|
+
cut: number;
|
|
70
|
+
};
|
|
71
|
+
rejected?: undefined;
|
|
72
|
+
error?: undefined;
|
|
73
|
+
}>;
|
|
74
|
+
export declare const MAX_CANDIDATES = 20;
|
|
75
|
+
/** Candidates for what the person described - the model lists them and asks when there is more than one. */
|
|
76
|
+
export declare function findRecords(target: EntryTarget, args: any, run: GraphqlRunner): Promise<{
|
|
77
|
+
rejected: boolean;
|
|
78
|
+
error: string;
|
|
79
|
+
message: string;
|
|
80
|
+
total?: undefined;
|
|
81
|
+
candidates?: undefined;
|
|
82
|
+
} | {
|
|
83
|
+
total: number;
|
|
84
|
+
candidates: {
|
|
85
|
+
record: EntryRecord;
|
|
86
|
+
label?: string;
|
|
87
|
+
id: string;
|
|
88
|
+
}[];
|
|
89
|
+
message: string;
|
|
90
|
+
rejected?: undefined;
|
|
91
|
+
error?: undefined;
|
|
92
|
+
}>;
|
|
93
|
+
/**
|
|
94
|
+
* The rows of a pressed 수정안, against what the records hold now. A row whose field moved since the card was
|
|
95
|
+
* made is kept out as `changed-since` - it is not overwritten unseen.
|
|
96
|
+
*/
|
|
97
|
+
export declare function stillAsProposed(target: EntryTarget, rows: ChangeRow[], now: Map<string, EntryRecord>): (ChangeRow & {
|
|
98
|
+
now?: EntryRecord;
|
|
99
|
+
})[];
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MAX_CANDIDATES = exports.MAX_CHANGES = void 0;
|
|
4
|
+
exports.changeableFields = changeableFields;
|
|
5
|
+
exports.resolveChangeRefs = resolveChangeRefs;
|
|
6
|
+
exports.readRecords = readRecords;
|
|
7
|
+
exports.changeRows = changeRows;
|
|
8
|
+
exports.proposeChanges = proposeChanges;
|
|
9
|
+
exports.findRecords = findRecords;
|
|
10
|
+
exports.stillAsProposed = stillAsProposed;
|
|
11
|
+
/*
|
|
12
|
+
* Changing records that exist - the 수정안 (operato-application/design/ai/data-entry.md §5-1).
|
|
13
|
+
*
|
|
14
|
+
* The most expensive mistake is changing the wrong record, so which record is meant is settled here, by the
|
|
15
|
+
* server, never by the model's memory. A record is picked by the screen (the one being looked at, the rows
|
|
16
|
+
* selected) or by an id the find tool handed back; whichever way, it is read again through the application's
|
|
17
|
+
* read query as the person, and a record they cannot see is not found. The card shows what is there now and
|
|
18
|
+
* what it becomes; pressing reads it once more, and a field that moved in between keeps its row out.
|
|
19
|
+
*
|
|
20
|
+
* Pure but for the GraphQL runner, which comes in as an argument - the rules are tested without a server.
|
|
21
|
+
*/
|
|
22
|
+
const entry_target_js_1 = require("./entry-target.js");
|
|
23
|
+
exports.MAX_CHANGES = 100;
|
|
24
|
+
const blockedChange = (row) => row.issues.some(issue => issue.blocking);
|
|
25
|
+
/** The fields a target lets change. */
|
|
26
|
+
function changeableFields(target) {
|
|
27
|
+
const names = target.update?.fields;
|
|
28
|
+
return names ? target.fields.filter(field => names.includes(field.name)) : target.fields;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* The records the changes name. `current` is the record on screen, `selected` every row selected there -
|
|
32
|
+
* both from the surface's host context, which is **not trusted**: an id from it is only a candidate, read
|
|
33
|
+
* through the application before anything is shown. Any other ref is an id, from the find tool.
|
|
34
|
+
*/
|
|
35
|
+
function resolveChangeRefs(targetKey, changes, host) {
|
|
36
|
+
const picked = [];
|
|
37
|
+
const unresolved = [];
|
|
38
|
+
const mine = (one) => !one?.target || one.target === targetKey;
|
|
39
|
+
for (const change of Array.isArray(changes) ? changes : []) {
|
|
40
|
+
const values = change?.values && typeof change.values === 'object' ? change.values : {};
|
|
41
|
+
const ref = typeof change?.ref === 'string' ? change.ref.trim() : '';
|
|
42
|
+
if (ref === 'current') {
|
|
43
|
+
const id = mine(host?.record) ? host?.record?.id : undefined;
|
|
44
|
+
if (typeof id === 'string' && id)
|
|
45
|
+
picked.push({ id, via: 'current', values });
|
|
46
|
+
else
|
|
47
|
+
unresolved.push('current');
|
|
48
|
+
}
|
|
49
|
+
else if (ref === 'selected') {
|
|
50
|
+
const ids = mine(host?.selection) && Array.isArray(host?.selection?.ids) ? host.selection.ids.filter((id) => typeof id === 'string' && id) : [];
|
|
51
|
+
if (ids.length)
|
|
52
|
+
for (const id of ids)
|
|
53
|
+
picked.push({ id, via: 'selected', values });
|
|
54
|
+
else
|
|
55
|
+
unresolved.push('selected');
|
|
56
|
+
}
|
|
57
|
+
else if (ref) {
|
|
58
|
+
picked.push({ id: ref, via: 'id', values });
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
unresolved.push('no-ref');
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return { picked: picked.slice(0, exports.MAX_CHANGES), unresolved };
|
|
65
|
+
}
|
|
66
|
+
/** Reads the records by id through the application's read query, as the person. */
|
|
67
|
+
async function readRecords(target, ids, run) {
|
|
68
|
+
const update = target.update;
|
|
69
|
+
const answer = await run(update.read.query, update.read.variables([...new Set(ids)]));
|
|
70
|
+
if (answer.errors?.length)
|
|
71
|
+
throw new Error(answer.errors.map(one => one.message).join('; '));
|
|
72
|
+
return new Map((update.read.recordsOf(answer.data) ?? []).filter(record => record && typeof record.id === 'string').map(record => [record.id, record]));
|
|
73
|
+
}
|
|
74
|
+
/** The change rows against what the records hold now. */
|
|
75
|
+
function changeRows(target, picked, records) {
|
|
76
|
+
const fields = new Map(changeableFields(target).map(field => [field.name, field]));
|
|
77
|
+
return picked.map((one, index) => {
|
|
78
|
+
const record = records.get(one.id);
|
|
79
|
+
const issues = [];
|
|
80
|
+
const values = {};
|
|
81
|
+
const before = {};
|
|
82
|
+
for (const [name, value] of Object.entries(one.values)) {
|
|
83
|
+
const field = fields.get(name);
|
|
84
|
+
if (!field) {
|
|
85
|
+
issues.push({ field: name, code: 'not-changeable', blocking: false });
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
const { read, code } = value === null || value === '' ? { read: null, code: undefined } : (0, entry_target_js_1.readFieldValue)(field, value);
|
|
89
|
+
if (read === undefined) {
|
|
90
|
+
values[name] = value;
|
|
91
|
+
issues.push({ field: name, code: code, params: code === 'not-in-list' ? { values: field.values } : undefined, blocking: true });
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
if (read === null && field.required)
|
|
95
|
+
issues.push({ field: name, code: 'required', blocking: true });
|
|
96
|
+
values[name] = read;
|
|
97
|
+
}
|
|
98
|
+
if (record)
|
|
99
|
+
before[name] = record[name] ?? null;
|
|
100
|
+
}
|
|
101
|
+
if (!record)
|
|
102
|
+
issues.push({ code: 'record-not-found', blocking: true });
|
|
103
|
+
else if (Object.keys(values).length && Object.keys(values).every(name => (0, entry_target_js_1.sameFieldValue)(fields.get(name), before[name], values[name])))
|
|
104
|
+
issues.push({ code: 'no-change', blocking: true });
|
|
105
|
+
if (!Object.keys(values).length && !issues.some(issue => issue.blocking))
|
|
106
|
+
issues.push({ code: 'nothing-to-change', blocking: true });
|
|
107
|
+
return { index, id: one.id, via: one.via, ...(record && target.update?.labelOf ? { label: target.update.labelOf(record) } : {}), before, values, issues };
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
/** What the card needs besides the rows - the fields that change, in the target's order. */
|
|
111
|
+
function changedColumns(target, rows) {
|
|
112
|
+
const used = new Set(rows.flatMap(row => Object.keys(row.values)));
|
|
113
|
+
return changeableFields(target)
|
|
114
|
+
.filter(field => used.has(field.name))
|
|
115
|
+
.map(field => ({ name: field.name, type: field.type, ...(field.labelKey ? { labelKey: field.labelKey } : {}) }));
|
|
116
|
+
}
|
|
117
|
+
async function proposeChanges(target, args, context, run) {
|
|
118
|
+
const { picked, unresolved } = resolveChangeRefs(target.key, args?.changes, context?.host);
|
|
119
|
+
if (!picked.length) {
|
|
120
|
+
return {
|
|
121
|
+
rejected: true,
|
|
122
|
+
error: unresolved.length ? `unresolved-${unresolved[0]}` : 'no-changes',
|
|
123
|
+
message: unresolved[0] === 'current' || unresolved[0] === 'selected'
|
|
124
|
+
? `the screen names no ${unresolved[0] === 'current' ? 'record' : 'selected rows'} of this kind. Ask the person which record, or call ${(0, entry_target_js_1.findToolName)(target.key)}.`
|
|
125
|
+
: `name each record: ref "current", "selected", or an id from ${(0, entry_target_js_1.findToolName)(target.key)}. Never guess an id.`
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
let records;
|
|
129
|
+
try {
|
|
130
|
+
records = await readRecords(target, picked.map(one => one.id), run);
|
|
131
|
+
}
|
|
132
|
+
catch (error) {
|
|
133
|
+
return { rejected: true, error: 'read-failed', message: `could not read the records: ${error?.message ?? error}` };
|
|
134
|
+
}
|
|
135
|
+
const rows = changeRows(target, picked, records);
|
|
136
|
+
const blocked = rows.filter(blockedChange).length;
|
|
137
|
+
return {
|
|
138
|
+
proposed: true,
|
|
139
|
+
kind: 'entry',
|
|
140
|
+
op: 'update',
|
|
141
|
+
target: target.key,
|
|
142
|
+
labelKey: target.labelKey,
|
|
143
|
+
label: `${rows.length} ${target.key} changes`,
|
|
144
|
+
icon: 'edit_note',
|
|
145
|
+
columns: changedColumns(target, rows),
|
|
146
|
+
rows,
|
|
147
|
+
counts: { rows: rows.length, ready: rows.length - blocked, blocked, cut: 0 },
|
|
148
|
+
...(typeof args?.note === 'string' && args.note.trim() ? { reason: args.note.trim().slice(0, 300) } : {}),
|
|
149
|
+
message: `proposed ${rows.length} changes (${rows.length - blocked} ready, ${blocked} blocked). Nothing changes until the person presses the card. ` +
|
|
150
|
+
'Tell them which records are not found or would not change.'
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
exports.MAX_CANDIDATES = 20;
|
|
154
|
+
/** Candidates for what the person described - the model lists them and asks when there is more than one. */
|
|
155
|
+
async function findRecords(target, args, run) {
|
|
156
|
+
const find = target.update?.find;
|
|
157
|
+
if (!find)
|
|
158
|
+
return { rejected: true, error: 'no-find', message: 'this record cannot be searched.' };
|
|
159
|
+
const criteria = {};
|
|
160
|
+
for (const field of find.criteria) {
|
|
161
|
+
const value = args?.[field.name];
|
|
162
|
+
if (value === undefined || value === null || value === '')
|
|
163
|
+
continue;
|
|
164
|
+
const { read } = (0, entry_target_js_1.readFieldValue)(field, value);
|
|
165
|
+
if (read !== undefined)
|
|
166
|
+
criteria[field.name] = read;
|
|
167
|
+
}
|
|
168
|
+
const answer = await run(find.query, find.variables(criteria));
|
|
169
|
+
if (answer.errors?.length)
|
|
170
|
+
return { rejected: true, error: 'find-failed', message: answer.errors.map(one => one.message).join('; ') };
|
|
171
|
+
const found = (find.recordsOf(answer.data) ?? []).filter(record => record && typeof record.id === 'string');
|
|
172
|
+
return {
|
|
173
|
+
total: found.length,
|
|
174
|
+
candidates: found.slice(0, exports.MAX_CANDIDATES).map(record => ({ id: record.id, ...(target.update?.labelOf ? { label: target.update.labelOf(record) } : {}), record })),
|
|
175
|
+
message: found.length === 1
|
|
176
|
+
? `one record matches; pass its id to ${(0, entry_target_js_1.changeToolName)(target.key)}.`
|
|
177
|
+
: found.length
|
|
178
|
+
? `${found.length} records match${found.length > exports.MAX_CANDIDATES ? ` (first ${exports.MAX_CANDIDATES} shown)` : ''}. List them to the person and ask which - never pick one yourself.`
|
|
179
|
+
: 'no record matches. Tell the person and ask for more to go on.'
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* The rows of a pressed 수정안, against what the records hold now. A row whose field moved since the card was
|
|
184
|
+
* made is kept out as `changed-since` - it is not overwritten unseen.
|
|
185
|
+
*/
|
|
186
|
+
function stillAsProposed(target, rows, now) {
|
|
187
|
+
const fields = new Map(target.fields.map(field => [field.name, field]));
|
|
188
|
+
return rows.map(row => {
|
|
189
|
+
const record = now.get(row.id);
|
|
190
|
+
if (!record)
|
|
191
|
+
return { ...row, issues: [...row.issues, { code: 'record-not-found', blocking: true }] };
|
|
192
|
+
const moved = Object.keys(row.values).find(name => fields.get(name) && !(0, entry_target_js_1.sameFieldValue)(fields.get(name), record[name] ?? null, row.before[name]));
|
|
193
|
+
return moved ? { ...row, now: record, issues: [...row.issues, { field: moved, code: 'changed-since', blocking: true }] } : { ...row, now: record };
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
//# sourceMappingURL=entry-update.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entry-update.js","sourceRoot":"","sources":["../../../server/service/data-entry/entry-update.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Changing records that exist - the 수정안 (operato-application/design/ai/data-entry.md §5-1).\n *\n * The most expensive mistake is changing the wrong record, so which record is meant is settled here, by the\n * server, never by the model's memory. A record is picked by the screen (the one being looked at, the rows\n * selected) or by an id the find tool handed back; whichever way, it is read again through the application's\n * read query as the person, and a record they cannot see is not found. The card shows what is there now and\n * what it becomes; pressing reads it once more, and a field that moved in between keeps its row out.\n *\n * Pure but for the GraphQL runner, which comes in as an argument - the rules are tested without a server.\n */\nimport {\n changeToolName,\n findToolName,\n readFieldValue,\n sameFieldValue,\n type EntryField,\n type EntryIssue,\n type EntryRecord,\n type EntryTarget\n} from './entry-target.js'\n\n/** Runs one of the application's queries or mutations as the person. */\nexport type GraphqlRunner = (document: string, variables: Record<string, unknown>) => Promise<{ data?: any; errors?: readonly { message: string }[] }>\n\n/** How the record of a change was picked - said on the card, so the person sees the ground of it. */\nexport type ChangeVia = 'current' | 'selected' | 'id'\n\nexport interface ChangeRow {\n index: number\n id: string\n via: ChangeVia\n label?: string\n /** What the record holds now, for the fields that change - read by the server. */\n before: Record<string, unknown>\n values: Record<string, unknown>\n issues: EntryIssue[]\n}\n\nexport const MAX_CHANGES = 100\n\nconst blockedChange = (row: { issues: EntryIssue[] }) => row.issues.some(issue => issue.blocking)\n\n/** The fields a target lets change. */\nexport function changeableFields(target: EntryTarget): EntryField[] {\n const names = target.update?.fields\n return names ? target.fields.filter(field => names.includes(field.name)) : target.fields\n}\n\n/**\n * The records the changes name. `current` is the record on screen, `selected` every row selected there -\n * both from the surface's host context, which is **not trusted**: an id from it is only a candidate, read\n * through the application before anything is shown. Any other ref is an id, from the find tool.\n */\nexport function resolveChangeRefs(\n targetKey: string,\n changes: unknown,\n host: Record<string, any> | undefined\n): { picked: { id: string; via: ChangeVia; values: Record<string, unknown> }[]; unresolved: string[] } {\n const picked: { id: string; via: ChangeVia; values: Record<string, unknown> }[] = []\n const unresolved: string[] = []\n const mine = (one: any) => !one?.target || one.target === targetKey\n for (const change of Array.isArray(changes) ? changes : []) {\n const values = change?.values && typeof change.values === 'object' ? change.values : {}\n const ref = typeof change?.ref === 'string' ? change.ref.trim() : ''\n if (ref === 'current') {\n const id = mine(host?.record) ? host?.record?.id : undefined\n if (typeof id === 'string' && id) picked.push({ id, via: 'current', values })\n else unresolved.push('current')\n } else if (ref === 'selected') {\n const ids = mine(host?.selection) && Array.isArray(host?.selection?.ids) ? host!.selection.ids.filter((id: unknown) => typeof id === 'string' && id) : []\n if (ids.length) for (const id of ids) picked.push({ id, via: 'selected', values })\n else unresolved.push('selected')\n } else if (ref) {\n picked.push({ id: ref, via: 'id', values })\n } else {\n unresolved.push('no-ref')\n }\n }\n return { picked: picked.slice(0, MAX_CHANGES), unresolved }\n}\n\n/** Reads the records by id through the application's read query, as the person. */\nexport async function readRecords(target: EntryTarget, ids: string[], run: GraphqlRunner): Promise<Map<string, EntryRecord>> {\n const update = target.update!\n const answer = await run(update.read.query, update.read.variables([...new Set(ids)]))\n if (answer.errors?.length) throw new Error(answer.errors.map(one => one.message).join('; '))\n return new Map((update.read.recordsOf(answer.data) ?? []).filter(record => record && typeof record.id === 'string').map(record => [record.id, record]))\n}\n\n/** The change rows against what the records hold now. */\nexport function changeRows(target: EntryTarget, picked: { id: string; via: ChangeVia; values: Record<string, unknown> }[], records: Map<string, EntryRecord>): ChangeRow[] {\n const fields = new Map(changeableFields(target).map(field => [field.name, field]))\n return picked.map((one, index): ChangeRow => {\n const record = records.get(one.id)\n const issues: EntryIssue[] = []\n const values: Record<string, unknown> = {}\n const before: Record<string, unknown> = {}\n for (const [name, value] of Object.entries(one.values)) {\n const field = fields.get(name)\n if (!field) {\n issues.push({ field: name, code: 'not-changeable', blocking: false })\n continue\n }\n const { read, code } = value === null || value === '' ? { read: null, code: undefined } : readFieldValue(field, value)\n if (read === undefined) {\n values[name] = value\n issues.push({ field: name, code: code!, params: code === 'not-in-list' ? { values: field.values } : undefined, blocking: true })\n } else {\n if (read === null && field.required) issues.push({ field: name, code: 'required', blocking: true })\n values[name] = read\n }\n if (record) before[name] = record[name] ?? null\n }\n if (!record) issues.push({ code: 'record-not-found', blocking: true })\n else if (Object.keys(values).length && Object.keys(values).every(name => sameFieldValue(fields.get(name)!, before[name], values[name])))\n issues.push({ code: 'no-change', blocking: true })\n if (!Object.keys(values).length && !issues.some(issue => issue.blocking)) issues.push({ code: 'nothing-to-change', blocking: true })\n return { index, id: one.id, via: one.via, ...(record && target.update?.labelOf ? { label: target.update.labelOf(record) } : {}), before, values, issues }\n })\n}\n\n/** What the card needs besides the rows - the fields that change, in the target's order. */\nfunction changedColumns(target: EntryTarget, rows: ChangeRow[]) {\n const used = new Set(rows.flatMap(row => Object.keys(row.values)))\n return changeableFields(target)\n .filter(field => used.has(field.name))\n .map(field => ({ name: field.name, type: field.type, ...(field.labelKey ? { labelKey: field.labelKey } : {}) }))\n}\n\nexport async function proposeChanges(target: EntryTarget, args: any, context: any, run: GraphqlRunner) {\n const { picked, unresolved } = resolveChangeRefs(target.key, args?.changes, context?.host)\n if (!picked.length) {\n return {\n rejected: true,\n error: unresolved.length ? `unresolved-${unresolved[0]}` : 'no-changes',\n message:\n unresolved[0] === 'current' || unresolved[0] === 'selected'\n ? `the screen names no ${unresolved[0] === 'current' ? 'record' : 'selected rows'} of this kind. Ask the person which record, or call ${findToolName(target.key)}.`\n : `name each record: ref \"current\", \"selected\", or an id from ${findToolName(target.key)}. Never guess an id.`\n }\n }\n let records: Map<string, EntryRecord>\n try {\n records = await readRecords(target, picked.map(one => one.id), run)\n } catch (error: any) {\n return { rejected: true, error: 'read-failed', message: `could not read the records: ${error?.message ?? error}` }\n }\n const rows = changeRows(target, picked, records)\n const blocked = rows.filter(blockedChange).length\n return {\n proposed: true,\n kind: 'entry',\n op: 'update',\n target: target.key,\n labelKey: target.labelKey,\n label: `${rows.length} ${target.key} changes`,\n icon: 'edit_note',\n columns: changedColumns(target, rows),\n rows,\n counts: { rows: rows.length, ready: rows.length - blocked, blocked, cut: 0 },\n ...(typeof args?.note === 'string' && args.note.trim() ? { reason: args.note.trim().slice(0, 300) } : {}),\n message:\n `proposed ${rows.length} changes (${rows.length - blocked} ready, ${blocked} blocked). Nothing changes until the person presses the card. ` +\n 'Tell them which records are not found or would not change.'\n }\n}\n\nexport const MAX_CANDIDATES = 20\n\n/** Candidates for what the person described - the model lists them and asks when there is more than one. */\nexport async function findRecords(target: EntryTarget, args: any, run: GraphqlRunner) {\n const find = target.update?.find\n if (!find) return { rejected: true, error: 'no-find', message: 'this record cannot be searched.' }\n const criteria: Record<string, unknown> = {}\n for (const field of find.criteria) {\n const value = args?.[field.name]\n if (value === undefined || value === null || value === '') continue\n const { read } = readFieldValue(field, value)\n if (read !== undefined) criteria[field.name] = read\n }\n const answer = await run(find.query, find.variables(criteria))\n if (answer.errors?.length) return { rejected: true, error: 'find-failed', message: answer.errors.map(one => one.message).join('; ') }\n const found = (find.recordsOf(answer.data) ?? []).filter(record => record && typeof record.id === 'string')\n return {\n total: found.length,\n candidates: found.slice(0, MAX_CANDIDATES).map(record => ({ id: record.id, ...(target.update?.labelOf ? { label: target.update.labelOf(record) } : {}), record })),\n message:\n found.length === 1\n ? `one record matches; pass its id to ${changeToolName(target.key)}.`\n : found.length\n ? `${found.length} records match${found.length > MAX_CANDIDATES ? ` (first ${MAX_CANDIDATES} shown)` : ''}. List them to the person and ask which - never pick one yourself.`\n : 'no record matches. Tell the person and ask for more to go on.'\n }\n}\n\n/**\n * The rows of a pressed 수정안, against what the records hold now. A row whose field moved since the card was\n * made is kept out as `changed-since` - it is not overwritten unseen.\n */\nexport function stillAsProposed(target: EntryTarget, rows: ChangeRow[], now: Map<string, EntryRecord>): (ChangeRow & { now?: EntryRecord })[] {\n const fields = new Map(target.fields.map(field => [field.name, field]))\n return rows.map(row => {\n const record = now.get(row.id)\n if (!record) return { ...row, issues: [...row.issues, { code: 'record-not-found', blocking: true }] }\n const moved = Object.keys(row.values).find(name => fields.get(name) && !sameFieldValue(fields.get(name)!, record[name] ?? null, row.before[name]))\n return moved ? { ...row, now: record, issues: [...row.issues, { field: moved, code: 'changed-since', blocking: true }] } : { ...row, now: record }\n })\n}\n"]}
|
|
@@ -8,6 +8,7 @@ import './chat-session/attachment-tools.js';
|
|
|
8
8
|
import './chat-session/session-purge.js';
|
|
9
9
|
import './chat-session/attachment-route.js';
|
|
10
10
|
import { EntryProposalResolver } from './data-entry/entry-resolver.js';
|
|
11
|
+
import './workspace/workspace-tools.js';
|
|
11
12
|
export * from './types.js';
|
|
12
13
|
export * from './chat-session-resolver.js';
|
|
13
14
|
export * from './assistant-chat-resolver.js';
|
|
@@ -17,6 +18,7 @@ export * from './assistant-capability-resolver.js';
|
|
|
17
18
|
export * from './tool-access.js';
|
|
18
19
|
export * from './data-entry/entry-target.js';
|
|
19
20
|
export { registerEntryTarget, entryCategoryOf } from './data-entry/entry-tools.js';
|
|
21
|
+
export * from './workspace/workspace-blocks.js';
|
|
20
22
|
export * from './grounding-sources.js';
|
|
21
23
|
export * from './chat-session/session-anchor.js';
|
|
22
24
|
export * from './chat-session/session-visibility.js';
|
|
@@ -15,6 +15,7 @@ require("./chat-session/attachment-tools.js");
|
|
|
15
15
|
require("./chat-session/session-purge.js");
|
|
16
16
|
require("./chat-session/attachment-route.js");
|
|
17
17
|
const entry_resolver_js_1 = require("./data-entry/entry-resolver.js");
|
|
18
|
+
require("./workspace/workspace-tools.js");
|
|
18
19
|
tslib_1.__exportStar(require("./types.js"), exports);
|
|
19
20
|
tslib_1.__exportStar(require("./chat-session-resolver.js"), exports);
|
|
20
21
|
tslib_1.__exportStar(require("./assistant-chat-resolver.js"), exports);
|
|
@@ -33,6 +34,8 @@ tslib_1.__exportStar(require("./data-entry/entry-target.js"), exports);
|
|
|
33
34
|
var entry_tools_js_1 = require("./data-entry/entry-tools.js");
|
|
34
35
|
Object.defineProperty(exports, "registerEntryTarget", { enumerable: true, get: function () { return entry_tools_js_1.registerEntryTarget; } });
|
|
35
36
|
Object.defineProperty(exports, "entryCategoryOf", { enumerable: true, get: function () { return entry_tools_js_1.entryCategoryOf; } });
|
|
37
|
+
/* The AI 작업판 - large results on a page of their own (design/ai/workspace.md). */
|
|
38
|
+
tslib_1.__exportStar(require("./workspace/workspace-blocks.js"), exports);
|
|
36
39
|
/* 무엇이 근거인가 — 도구 결과 밖의 셋(프롬프트 · 대화 · 화면 스냅숏). */
|
|
37
40
|
tslib_1.__exportStar(require("./grounding-sources.js"), exports);
|
|
38
41
|
/* 앵커 순수 로직 — 호스트가 앵커 종류를 참조한다(리졸버는 데코레이터 때문에 무겁다). */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../server/service/index.ts"],"names":[],"mappings":";;;;AAAA,yEAAgE;AAChE,6EAAoE;AACpE,4FAAmF;AACnF,yFAAgF;AAChF,sGAA+F;AAC/F,8FAAqF;AACrF,sDAAyE;AACzE,sDAAyE;AACzE,kEAAgG;AAChG,8CAA2C;AAC3C,2CAAwC;AACxC,8CAA2C;AAC3C,sEAAsE;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../server/service/index.ts"],"names":[],"mappings":";;;;AAAA,yEAAgE;AAChE,6EAAoE;AACpE,4FAAmF;AACnF,yFAAgF;AAChF,sGAA+F;AAC/F,8FAAqF;AACrF,sDAAyE;AACzE,sDAAyE;AACzE,kEAAgG;AAChG,8CAA2C;AAC3C,2CAAwC;AACxC,8CAA2C;AAC3C,sEAAsE;AACtE,0CAAuC;AAEvC,qDAA0B;AAC1B,qEAA0C;AAC1C,uEAA4C;AAC5C,oGAAoG;AACpG,oEAAyC;AACzC,iEAA2D;AAAlD,0HAAA,iBAAiB,OAAA;AAC1B,6EAAkD;AAClD;;;GAGG;AACH,2DAAgC;AAChC,+GAA+G;AAC/G,uEAA4C;AAC5C,8DAAkF;AAAzE,qHAAA,mBAAmB,OAAA;AAAE,iHAAA,eAAe,OAAA;AAC7C,iFAAiF;AACjF,0EAA+C;AAC/C,gDAAgD;AAChD,iEAAsC;AACtC,sDAAsD;AACtD,2EAAgD;AAChD,gHAAgH;AAChH,+EAAoD;AACpD,kEAAuC;AACvC,8DAA8D;AAC9D,0EAA+C;AAC/C,8EAAmD;AACnD,0EAA+C;AAC/C,qFAA0D;AAC1D,uFAA4D;AAC5D,wEAA6C;AAC7C,4EAAiD;AACjD,yEAA8C;AAC9C,wDAAwD;AACxD,iFAAsD;AACtD,iFAAsD;AACtD,mFAAwD;AACxD,4FAAiE;AACjE,2DAA2D;AAC3D,uFAA4D;AAC5D,iFAAsD;AACtD,kEAAuC;AACvC,8EAAmD;AAEtC,QAAA,QAAQ,GAAG,CAAC,GAAG,mBAAmB,EAAE,GAAG,mBAAmB,EAAE,GAAG,mBAA8B,CAAC,CAAA;AAE9F,QAAA,MAAM,GAAG;IACpB,eAAe,EAAE;QACf,8CAAmB;QACnB,sDAAsD;QACtD,kDAAqB;QACrB,uDAAuD;QACvD,kDAAqB;QACrB,wDAAwD;QACxD,8DAA2B;QAC3B,sDAAuB;QACvB,qDAAqD;QACrD,gEAA6B;QAC7B,mDAAmD;QACnD,yCAAqB;KACtB;CACF,CAAA","sourcesContent":["import { ChatSessionResolver } from './chat-session-resolver.js'\nimport { AssistantChatResolver } from './assistant-chat-resolver.js'\nimport { AssistantModeResolver } from './assistant-mode/assistant-mode-resolver.js'\nimport { AssistantCapabilityResolver } from './assistant-capability-resolver.js'\nimport { MySessionActivitySubscription } from './chat-session/session-activity-subscription.js'\nimport { ChatMessageSubscription } from './chat-message/chat-message-subscription.js'\nimport { entities as ChatSessionEntities } from './chat-session/index.js'\nimport { entities as ChatMessageEntities } from './chat-message/index.js'\nimport { entities as ChatSessionParticipantEntities } from './chat-session-participant/index.js'\nimport './chat-session/attachment-tools.js'\nimport './chat-session/session-purge.js'\nimport './chat-session/attachment-route.js'\nimport { EntryProposalResolver } from './data-entry/entry-resolver.js'\nimport './workspace/workspace-tools.js'\n\nexport * from './types.js'\nexport * from './chat-session-resolver.js'\nexport * from './assistant-chat-resolver.js'\n/* The mode registry — each app server declares what its conversations are (ADR-0061 decision 7). */\nexport * from './assistant-mode/index.js'\nexport { requestTranslator } from './request-translator.js'\nexport * from './assistant-capability-resolver.js'\n/*\n * 대화 각본 하네스는 **런타임 export 에서 뺀다**(ADR-0070 A15 결정 ④) — 시험 도구가 서버 번들에\n * 실린다. 시험은 경로로 가져온다: `…/service/testing/conversation-script.js`.\n */\nexport * from './tool-access.js'\n/* Data entry from material - an app registers what it can enter; a mode names it (design/ai/data-entry.md). */\nexport * from './data-entry/entry-target.js'\nexport { registerEntryTarget, entryCategoryOf } from './data-entry/entry-tools.js'\n/* The AI 작업판 - large results on a page of their own (design/ai/workspace.md). */\nexport * from './workspace/workspace-blocks.js'\n/* 무엇이 근거인가 — 도구 결과 밖의 셋(프롬프트 · 대화 · 화면 스냅숏). */\nexport * from './grounding-sources.js'\n/* 앵커 순수 로직 — 호스트가 앵커 종류를 참조한다(리졸버는 데코레이터 때문에 무겁다). */\nexport * from './chat-session/session-anchor.js'\n/* Who may read a conversation — one rule, also asked by doors outside this package (board-ai's revertPatch). */\nexport * from './chat-session/session-visibility.js'\nexport * from './chat-session/index.js'\n/* 순수 로직들 — 호스트(board-ai 등)가 같은 규칙을 쓰라고 공개한다. 베끼면 한쪽만 고쳐진다. */\nexport * from './chat-session/session-inbox.js'\nexport * from './chat-session/attachment-policy.js'\nexport * from './chat-session/session-purge.js'\nexport * from './chat-session/session-activity-publish.js'\nexport * from './chat-session/session-activity-broadcast.js'\nexport * from './chat-message/llm-history.js'\nexport * from './chat-message/history-summary.js'\nexport * from './chat-message/fold-history.js'\n/* 상한 밖으로 밀려난 앞부분을 요약으로 접는 orchestration — 중립 문이 부른다. */\nexport * from './chat-message/fold-session-history.js'\nexport * from './chat-message/chat-message-publish.js'\nexport * from './chat-message/chat-message-broadcast.js'\nexport * from './chat-session-participant/invite-notification.js'\n/* 멘션이 사람을 불러들이는 자리 — 사람의 말을 저장하는 문만 부른다(ADR-0071 결정 1). */\nexport * from './chat-session-participant/mention-invite.js'\nexport * from './chat-session-participant/presence.js'\nexport * from './chat-message/index.js'\nexport * from './chat-session-participant/index.js'\n\nexport const entities = [...ChatSessionEntities, ...ChatMessageEntities, ...ChatSessionParticipantEntities]\n\nexport const schema = {\n resolverClasses: [\n ChatSessionResolver,\n /* 문서를 갖지 않은 표면의 대화 — board 를 안 싣는 제품이 물어볼 수 있는 자리. */\n AssistantChatResolver,\n /* 화면이 자기 대화가 무엇을 주는지 묻는 자리 — 문구는 서버가 요청 로케일로 옮겨 낸다. */\n AssistantModeResolver,\n /* 「이 배포에 AI 가 있나 · 무엇이 선언 없이 등록됐나」 — 창구 하나(표준 A7-a). */\n AssistantCapabilityResolver,\n ChatMessageSubscription,\n /* 내가 참여한 모든 세션의 활동 — 창을 닫아 둔 표면이 배지·목록을 갱신할 수 있게. */\n MySessionActivitySubscription,\n /* 사람이 누른 입력 제안을 쓰는 자리 — 행마다 앱의 코드로, 그 사람의 권한으로. */\n EntryProposalResolver\n ]\n}\n"]}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export declare const WORKSPACE_BLOCK_KINDS: readonly ["heading", "text", "metrics", "table", "chart", "steps"];
|
|
2
|
+
export type WorkspaceBlockKind = (typeof WORKSPACE_BLOCK_KINDS)[number];
|
|
3
|
+
export declare const MAX_BLOCKS = 40;
|
|
4
|
+
export declare const MAX_TABLE_ROWS = 1000;
|
|
5
|
+
export declare const MAX_SERIES = 8;
|
|
6
|
+
export interface WorkspaceBlock {
|
|
7
|
+
kind: WorkspaceBlockKind | 'unreadable';
|
|
8
|
+
[key: string]: unknown;
|
|
9
|
+
}
|
|
10
|
+
export interface Workspace {
|
|
11
|
+
name: string;
|
|
12
|
+
title: string;
|
|
13
|
+
summary?: string;
|
|
14
|
+
blocks: WorkspaceBlock[];
|
|
15
|
+
}
|
|
16
|
+
/** A short key of the workspace - the same name in one conversation is the same workspace, a new version. */
|
|
17
|
+
export declare function workspaceName(name: unknown, title: string): string;
|
|
18
|
+
/** The workspace as the screen reads it. */
|
|
19
|
+
export declare function normalizeWorkspace(args: any): {
|
|
20
|
+
workspace?: Workspace;
|
|
21
|
+
error?: string;
|
|
22
|
+
};
|
|
23
|
+
/** One line of what is in it - for the card in the conversation and for the model. */
|
|
24
|
+
export declare function workspaceContents(workspace: Workspace): Record<string, number>;
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* The AI 작업판 - what a workspace is made of (operato-application/design/ai/workspace.md §3).
|
|
4
|
+
*
|
|
5
|
+
* The model does not write HTML. A workspace is a list of blocks of known kinds; the screen draws each, so
|
|
6
|
+
* theme, language and accessibility are the framework's. A block that does not fit is not dropped: it is
|
|
7
|
+
* kept as a fact in its place ("a block that cannot be drawn"), so the person sees that something was meant
|
|
8
|
+
* to be there. Pure - tested without a server.
|
|
9
|
+
*/
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.MAX_SERIES = exports.MAX_TABLE_ROWS = exports.MAX_BLOCKS = exports.WORKSPACE_BLOCK_KINDS = void 0;
|
|
12
|
+
exports.workspaceName = workspaceName;
|
|
13
|
+
exports.normalizeWorkspace = normalizeWorkspace;
|
|
14
|
+
exports.workspaceContents = workspaceContents;
|
|
15
|
+
exports.WORKSPACE_BLOCK_KINDS = ['heading', 'text', 'metrics', 'table', 'chart', 'steps'];
|
|
16
|
+
exports.MAX_BLOCKS = 40;
|
|
17
|
+
exports.MAX_TABLE_ROWS = 1000;
|
|
18
|
+
exports.MAX_SERIES = 8;
|
|
19
|
+
const MAX_TEXT = 20_000;
|
|
20
|
+
const text = (value, max = 500) => (typeof value === 'string' && value.trim() ? value.trim().slice(0, max) : undefined);
|
|
21
|
+
const scalar = (value) => value === null || value === undefined ? null : typeof value === 'number' || typeof value === 'boolean' ? value : String(value).slice(0, 500);
|
|
22
|
+
/** A short key of the workspace - the same name in one conversation is the same workspace, a new version. */
|
|
23
|
+
function workspaceName(name, title) {
|
|
24
|
+
const given = typeof name === 'string' ? name.trim().toLowerCase() : '';
|
|
25
|
+
if (/^[a-z0-9][a-z0-9-]{0,63}$/.test(given))
|
|
26
|
+
return given;
|
|
27
|
+
const slug = (given || title)
|
|
28
|
+
.toLowerCase()
|
|
29
|
+
.replace(/[^a-z0-9가-힣]+/g, '-')
|
|
30
|
+
.replace(/^-+|-+$/g, '')
|
|
31
|
+
.slice(0, 64);
|
|
32
|
+
return slug || 'workspace';
|
|
33
|
+
}
|
|
34
|
+
function unreadable(kind, reason) {
|
|
35
|
+
return { kind: 'unreadable', was: typeof kind === 'string' ? kind.slice(0, 40) : null, reason };
|
|
36
|
+
}
|
|
37
|
+
function block(raw) {
|
|
38
|
+
const kind = raw?.kind;
|
|
39
|
+
switch (kind) {
|
|
40
|
+
case 'heading': {
|
|
41
|
+
const value = text(raw.text, 200);
|
|
42
|
+
if (!value)
|
|
43
|
+
return unreadable(kind, 'no-text');
|
|
44
|
+
const level = [1, 2, 3].includes(raw.level) ? raw.level : 2;
|
|
45
|
+
return { kind, text: value, level };
|
|
46
|
+
}
|
|
47
|
+
case 'text': {
|
|
48
|
+
const value = text(raw.markdown ?? raw.text, MAX_TEXT);
|
|
49
|
+
return value ? { kind, markdown: value } : unreadable(kind, 'no-text');
|
|
50
|
+
}
|
|
51
|
+
case 'metrics': {
|
|
52
|
+
const items = (Array.isArray(raw.items) ? raw.items : [])
|
|
53
|
+
.filter((one) => text(one?.label) && one?.value !== undefined && one?.value !== null)
|
|
54
|
+
.slice(0, 12)
|
|
55
|
+
.map((one) => ({ label: text(one.label, 80), value: scalar(one.value), ...(text(one.unit, 20) ? { unit: text(one.unit, 20) } : {}), ...(text(one.note, 200) ? { note: text(one.note, 200) } : {}) }));
|
|
56
|
+
return items.length ? { kind, items } : unreadable(kind, 'no-items');
|
|
57
|
+
}
|
|
58
|
+
case 'table': {
|
|
59
|
+
const columns = (Array.isArray(raw.columns) ? raw.columns : [])
|
|
60
|
+
.filter((one) => text(one?.name, 80))
|
|
61
|
+
.slice(0, 30)
|
|
62
|
+
.map((one) => ({ name: text(one.name, 80), ...(text(one.label, 80) ? { label: text(one.label, 80) } : {}), type: ['number', 'date', 'string'].includes(one.type) ? one.type : 'string' }));
|
|
63
|
+
if (!columns.length)
|
|
64
|
+
return unreadable(kind, 'no-columns');
|
|
65
|
+
const given = Array.isArray(raw.rows) ? raw.rows : [];
|
|
66
|
+
/* A row is an object by column name, or an array in column order - models write both. */
|
|
67
|
+
const rows = given.slice(0, exports.MAX_TABLE_ROWS).map((row) => Object.fromEntries(columns.map((column, i) => [column.name, scalar(Array.isArray(row) ? row[i] : row?.[column.name])])));
|
|
68
|
+
return { kind, columns, rows, ...(text(raw.caption, 300) ? { caption: text(raw.caption, 300) } : {}), ...(given.length > exports.MAX_TABLE_ROWS ? { cut: given.length - exports.MAX_TABLE_ROWS } : {}) };
|
|
69
|
+
}
|
|
70
|
+
case 'chart': {
|
|
71
|
+
const x = (Array.isArray(raw.x) ? raw.x : []).slice(0, 200).map((one) => String(one ?? '').slice(0, 60));
|
|
72
|
+
const series = (Array.isArray(raw.series) ? raw.series : [])
|
|
73
|
+
.filter((one) => text(one?.name, 60) && Array.isArray(one?.values))
|
|
74
|
+
.map((one) => ({ name: text(one.name, 60), values: x.map((_, i) => (typeof one.values[i] === 'number' && Number.isFinite(one.values[i]) ? one.values[i] : null)) }));
|
|
75
|
+
if (!x.length || !series.length)
|
|
76
|
+
return unreadable(kind, 'no-series');
|
|
77
|
+
/* Never a ninth hue: past eight, the first seven stay and the rest fold into one "other". */
|
|
78
|
+
const shown = series.length > exports.MAX_SERIES
|
|
79
|
+
? [
|
|
80
|
+
...series.slice(0, exports.MAX_SERIES - 1),
|
|
81
|
+
{ name: 'other', other: true, values: x.map((_, i) => series.slice(exports.MAX_SERIES - 1).reduce((acc, one) => acc + (one.values[i] ?? 0), 0)) }
|
|
82
|
+
]
|
|
83
|
+
: series;
|
|
84
|
+
return { kind, chart: raw.chart === 'line' || raw.type === 'line' || raw.shape === 'line' ? 'line' : 'bar', x, series: shown, ...(text(raw.unit, 20) ? { unit: text(raw.unit, 20) } : {}), ...(text(raw.caption, 300) ? { caption: text(raw.caption, 300) } : {}) };
|
|
85
|
+
}
|
|
86
|
+
case 'steps': {
|
|
87
|
+
const items = (Array.isArray(raw.items) ? raw.items : [])
|
|
88
|
+
.filter((one) => text(one?.label, 200))
|
|
89
|
+
.slice(0, 50)
|
|
90
|
+
.map((one) => ({ label: text(one.label, 200), state: ['done', 'doing', 'todo', 'failed'].includes(one.state) ? one.state : 'todo', ...(text(one.note, 300) ? { note: text(one.note, 300) } : {}) }));
|
|
91
|
+
return items.length ? { kind, items } : unreadable(kind, 'no-items');
|
|
92
|
+
}
|
|
93
|
+
default:
|
|
94
|
+
return unreadable(kind, 'unknown-kind');
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
/** The workspace as the screen reads it. */
|
|
98
|
+
function normalizeWorkspace(args) {
|
|
99
|
+
const title = text(args?.title, 200);
|
|
100
|
+
if (!title)
|
|
101
|
+
return { error: 'no-title' };
|
|
102
|
+
const given = Array.isArray(args?.blocks) ? args.blocks : [];
|
|
103
|
+
if (!given.length)
|
|
104
|
+
return { error: 'no-blocks' };
|
|
105
|
+
const blocks = given.slice(0, exports.MAX_BLOCKS).map(block);
|
|
106
|
+
if (given.length > exports.MAX_BLOCKS)
|
|
107
|
+
blocks.push({ kind: 'unreadable', was: null, reason: 'too-many-blocks', cut: given.length - exports.MAX_BLOCKS });
|
|
108
|
+
return { workspace: { name: workspaceName(args?.name, title), title, ...(text(args?.summary, 500) ? { summary: text(args.summary, 500) } : {}), blocks } };
|
|
109
|
+
}
|
|
110
|
+
/** One line of what is in it - for the card in the conversation and for the model. */
|
|
111
|
+
function workspaceContents(workspace) {
|
|
112
|
+
const counts = {};
|
|
113
|
+
for (const one of workspace.blocks)
|
|
114
|
+
if (['table', 'chart', 'metrics', 'steps'].includes(one.kind))
|
|
115
|
+
counts[one.kind] = (counts[one.kind] ?? 0) + 1;
|
|
116
|
+
return counts;
|
|
117
|
+
}
|
|
118
|
+
//# sourceMappingURL=workspace-blocks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workspace-blocks.js","sourceRoot":"","sources":["../../../server/service/workspace/workspace-blocks.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * The AI 작업판 - what a workspace is made of (operato-application/design/ai/workspace.md §3).\n *\n * The model does not write HTML. A workspace is a list of blocks of known kinds; the screen draws each, so\n * theme, language and accessibility are the framework's. A block that does not fit is not dropped: it is\n * kept as a fact in its place (\"a block that cannot be drawn\"), so the person sees that something was meant\n * to be there. Pure - tested without a server.\n */\n\nexport const WORKSPACE_BLOCK_KINDS = ['heading', 'text', 'metrics', 'table', 'chart', 'steps'] as const\nexport type WorkspaceBlockKind = (typeof WORKSPACE_BLOCK_KINDS)[number]\n\nexport const MAX_BLOCKS = 40\nexport const MAX_TABLE_ROWS = 1000\nexport const MAX_SERIES = 8\nconst MAX_TEXT = 20_000\n\nexport interface WorkspaceBlock {\n kind: WorkspaceBlockKind | 'unreadable'\n [key: string]: unknown\n}\n\nexport interface Workspace {\n name: string\n title: string\n summary?: string\n blocks: WorkspaceBlock[]\n}\n\nconst text = (value: unknown, max = 500): string | undefined => (typeof value === 'string' && value.trim() ? value.trim().slice(0, max) : undefined)\nconst scalar = (value: unknown): string | number | boolean | null =>\n value === null || value === undefined ? null : typeof value === 'number' || typeof value === 'boolean' ? value : String(value).slice(0, 500)\n\n/** A short key of the workspace - the same name in one conversation is the same workspace, a new version. */\nexport function workspaceName(name: unknown, title: string): string {\n const given = typeof name === 'string' ? name.trim().toLowerCase() : ''\n if (/^[a-z0-9][a-z0-9-]{0,63}$/.test(given)) return given\n const slug = (given || title)\n .toLowerCase()\n .replace(/[^a-z0-9가-힣]+/g, '-')\n .replace(/^-+|-+$/g, '')\n .slice(0, 64)\n return slug || 'workspace'\n}\n\nfunction unreadable(kind: unknown, reason: string): WorkspaceBlock {\n return { kind: 'unreadable', was: typeof kind === 'string' ? kind.slice(0, 40) : null, reason }\n}\n\nfunction block(raw: any): WorkspaceBlock {\n const kind = raw?.kind\n switch (kind) {\n case 'heading': {\n const value = text(raw.text, 200)\n if (!value) return unreadable(kind, 'no-text')\n const level = [1, 2, 3].includes(raw.level) ? raw.level : 2\n return { kind, text: value, level }\n }\n case 'text': {\n const value = text(raw.markdown ?? raw.text, MAX_TEXT)\n return value ? { kind, markdown: value } : unreadable(kind, 'no-text')\n }\n case 'metrics': {\n const items = (Array.isArray(raw.items) ? raw.items : [])\n .filter((one: any) => text(one?.label) && one?.value !== undefined && one?.value !== null)\n .slice(0, 12)\n .map((one: any) => ({ label: text(one.label, 80), value: scalar(one.value), ...(text(one.unit, 20) ? { unit: text(one.unit, 20) } : {}), ...(text(one.note, 200) ? { note: text(one.note, 200) } : {}) }))\n return items.length ? { kind, items } : unreadable(kind, 'no-items')\n }\n case 'table': {\n const columns = (Array.isArray(raw.columns) ? raw.columns : [])\n .filter((one: any) => text(one?.name, 80))\n .slice(0, 30)\n .map((one: any) => ({ name: text(one.name, 80)!, ...(text(one.label, 80) ? { label: text(one.label, 80) } : {}), type: ['number', 'date', 'string'].includes(one.type) ? one.type : 'string' }))\n if (!columns.length) return unreadable(kind, 'no-columns')\n const given = Array.isArray(raw.rows) ? raw.rows : []\n /* A row is an object by column name, or an array in column order - models write both. */\n const rows = given.slice(0, MAX_TABLE_ROWS).map((row: any) =>\n Object.fromEntries(columns.map((column: any, i: number) => [column.name, scalar(Array.isArray(row) ? row[i] : row?.[column.name])]))\n )\n return { kind, columns, rows, ...(text(raw.caption, 300) ? { caption: text(raw.caption, 300) } : {}), ...(given.length > MAX_TABLE_ROWS ? { cut: given.length - MAX_TABLE_ROWS } : {}) }\n }\n case 'chart': {\n const x = (Array.isArray(raw.x) ? raw.x : []).slice(0, 200).map((one: unknown) => String(one ?? '').slice(0, 60))\n const series = (Array.isArray(raw.series) ? raw.series : [])\n .filter((one: any) => text(one?.name, 60) && Array.isArray(one?.values))\n .map((one: any) => ({ name: text(one.name, 60)!, values: x.map((_: unknown, i: number) => (typeof one.values[i] === 'number' && Number.isFinite(one.values[i]) ? one.values[i] : null)) }))\n if (!x.length || !series.length) return unreadable(kind, 'no-series')\n /* Never a ninth hue: past eight, the first seven stay and the rest fold into one \"other\". */\n const shown =\n series.length > MAX_SERIES\n ? [\n ...series.slice(0, MAX_SERIES - 1),\n { name: 'other', other: true, values: x.map((_: unknown, i: number) => series.slice(MAX_SERIES - 1).reduce((acc: number, one: any) => acc + (one.values[i] ?? 0), 0)) }\n ]\n : series\n return { kind, chart: raw.chart === 'line' || raw.type === 'line' || raw.shape === 'line' ? 'line' : 'bar', x, series: shown, ...(text(raw.unit, 20) ? { unit: text(raw.unit, 20) } : {}), ...(text(raw.caption, 300) ? { caption: text(raw.caption, 300) } : {}) }\n }\n case 'steps': {\n const items = (Array.isArray(raw.items) ? raw.items : [])\n .filter((one: any) => text(one?.label, 200))\n .slice(0, 50)\n .map((one: any) => ({ label: text(one.label, 200), state: ['done', 'doing', 'todo', 'failed'].includes(one.state) ? one.state : 'todo', ...(text(one.note, 300) ? { note: text(one.note, 300) } : {}) }))\n return items.length ? { kind, items } : unreadable(kind, 'no-items')\n }\n default:\n return unreadable(kind, 'unknown-kind')\n }\n}\n\n/** The workspace as the screen reads it. */\nexport function normalizeWorkspace(args: any): { workspace?: Workspace; error?: string } {\n const title = text(args?.title, 200)\n if (!title) return { error: 'no-title' }\n const given = Array.isArray(args?.blocks) ? args.blocks : []\n if (!given.length) return { error: 'no-blocks' }\n const blocks = given.slice(0, MAX_BLOCKS).map(block)\n if (given.length > MAX_BLOCKS) blocks.push({ kind: 'unreadable', was: null, reason: 'too-many-blocks', cut: given.length - MAX_BLOCKS })\n return { workspace: { name: workspaceName(args?.name, title), title, ...(text(args?.summary, 500) ? { summary: text(args.summary, 500) } : {}), blocks } }\n}\n\n/** One line of what is in it - for the card in the conversation and for the model. */\nexport function workspaceContents(workspace: Workspace): Record<string, number> {\n const counts: Record<string, number> = {}\n for (const one of workspace.blocks) if (['table', 'chart', 'metrics', 'steps'].includes(one.kind)) counts[one.kind] = (counts[one.kind] ?? 0) + 1\n return counts\n}\n"]}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { type ToolCategory } from '@things-factory/ai-client-base';
|
|
2
|
+
export declare const WORKSPACE_CATEGORY = "assistant-workspace";
|
|
3
|
+
export declare function showInWorkspace(args: any): {
|
|
4
|
+
rejected: boolean;
|
|
5
|
+
error: string;
|
|
6
|
+
message: string;
|
|
7
|
+
proposed?: undefined;
|
|
8
|
+
kind?: undefined;
|
|
9
|
+
icon?: undefined;
|
|
10
|
+
label?: undefined;
|
|
11
|
+
workspace?: undefined;
|
|
12
|
+
contents?: undefined;
|
|
13
|
+
} | {
|
|
14
|
+
proposed: boolean;
|
|
15
|
+
kind: string;
|
|
16
|
+
icon: string;
|
|
17
|
+
label: string;
|
|
18
|
+
workspace: import("./workspace-blocks.js").Workspace;
|
|
19
|
+
contents: Record<string, number>;
|
|
20
|
+
message: string;
|
|
21
|
+
rejected?: undefined;
|
|
22
|
+
error?: undefined;
|
|
23
|
+
};
|
|
24
|
+
export declare const workspaceCategory: ToolCategory;
|