@lazyingart/agent-web 0.1.40

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.
Files changed (42) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +438 -0
  3. package/docs/architecture.md +503 -0
  4. package/package.json +43 -0
  5. package/src/aginti-adapter.js +602 -0
  6. package/src/chat-context.js +1020 -0
  7. package/src/chat-migrations.js +947 -0
  8. package/src/chat-store.js +3308 -0
  9. package/src/cli.js +134 -0
  10. package/src/cloud-server.js +2043 -0
  11. package/src/contracts.js +103 -0
  12. package/src/deterministic-context-summarizer.js +254 -0
  13. package/src/direct-chat-capability-limits.js +66 -0
  14. package/src/direct-chat-contract.js +3 -0
  15. package/src/errors.js +50 -0
  16. package/src/http-contract.js +592 -0
  17. package/src/index.js +88 -0
  18. package/src/localllm-connector.js +667 -0
  19. package/src/migrations.js +231 -0
  20. package/src/operator-health.js +184 -0
  21. package/src/password-verifier.js +131 -0
  22. package/src/service-config.js +547 -0
  23. package/src/service.js +408 -0
  24. package/src/sqlite-health.js +83 -0
  25. package/src/storage-path.js +130 -0
  26. package/src/store.js +914 -0
  27. package/src/validation.js +181 -0
  28. package/src/vision-attachment.js +404 -0
  29. package/src/web/aginti-client.js +552 -0
  30. package/src/web/aginti-protocol.js +1146 -0
  31. package/src/web/asset-map.js +462 -0
  32. package/src/web/browser-app.js +6491 -0
  33. package/src/web/cloud-session-client.js +427 -0
  34. package/src/web/direct-chat-client.js +1482 -0
  35. package/src/web/index.js +10 -0
  36. package/src/web/presentation-state.js +107 -0
  37. package/src/web/pwa-assets.js +854 -0
  38. package/src/web/pwa-update-handoff-store.js +179 -0
  39. package/src/web/safe-rendering.js +836 -0
  40. package/src/web/vision-image-client.js +546 -0
  41. package/src/web/vision-image-sanitizer.js +168 -0
  42. package/src/web/web-release.js +28 -0
@@ -0,0 +1,103 @@
1
+ import { ControlPlaneError, ValidationError } from './errors.js';
2
+ import { nowIso } from './validation.js';
3
+
4
+ export const CONTRACT_VERSION = 1;
5
+ export const COMPONENT_ID = 'lazying-agent-web';
6
+ export const COMPONENT_ROLE = 'cloud-presentation-control-plane';
7
+
8
+ const DEFAULT_CLOCK = () => new Date();
9
+
10
+ function deepFreeze(value) {
11
+ if (value && typeof value === 'object' && !Object.isFrozen(value)) {
12
+ for (const child of Object.values(value)) deepFreeze(child);
13
+ Object.freeze(value);
14
+ }
15
+ return value;
16
+ }
17
+
18
+ export function createCapabilityContract({ clock = DEFAULT_CLOCK } = {}) {
19
+ if (typeof clock !== 'function') throw new ValidationError('clock must be a function.');
20
+ return deepFreeze({
21
+ contractVersion: CONTRACT_VERSION,
22
+ generatedAt: nowIso(clock),
23
+ component: {
24
+ id: COMPONENT_ID,
25
+ role: COMPONENT_ROLE,
26
+ standalone: true
27
+ },
28
+ authorities: {
29
+ agent: 'aginti',
30
+ inference: 'localllm',
31
+ transport: 'lazyedge',
32
+ presentation: COMPONENT_ID
33
+ },
34
+ capabilities: {
35
+ accounts: { owner: COMPONENT_ID, available: true },
36
+ browserSessions: {
37
+ owner: COMPONENT_ID,
38
+ available: true,
39
+ rawTokensStored: false,
40
+ maximumPerAccount: 32,
41
+ expiredRowsPruned: true
42
+ },
43
+ csrfBinding: { owner: COMPONENT_ID, available: true, rawTokensStored: false },
44
+ threadPresentationIndex: {
45
+ owner: COMPONENT_ID,
46
+ available: true,
47
+ authoritative: false
48
+ },
49
+ resumableDeliveryCursor: {
50
+ owner: COMPONENT_ID,
51
+ available: true,
52
+ authoritative: false,
53
+ monotonic: true,
54
+ idempotencyReceipts: false
55
+ },
56
+ safeRendering: { owner: COMPONENT_ID, available: false },
57
+ agentThreads: { owner: 'aginti', availableThroughThisComponent: false },
58
+ agentRuns: { owner: 'aginti', availableThroughThisComponent: false },
59
+ contextAndCompaction: { owner: 'aginti', availableThroughThisComponent: false },
60
+ plansAndTools: { owner: 'aginti', availableThroughThisComponent: false },
61
+ artifacts: { owner: 'aginti', availableThroughThisComponent: false },
62
+ inference: { owner: 'localllm', availableThroughThisComponent: false },
63
+ edgeTransport: { owner: 'lazyedge', availableThroughThisComponent: false }
64
+ }
65
+ });
66
+ }
67
+
68
+ export function createHealthContract({ store, clock = DEFAULT_CLOCK } = {}) {
69
+ if (!store || typeof store.healthCheck !== 'function') {
70
+ throw new ValidationError('store must provide healthCheck().');
71
+ }
72
+ if (typeof clock !== 'function') throw new ValidationError('clock must be a function.');
73
+ const checkedAt = nowIso(clock);
74
+ try {
75
+ const storage = store.healthCheck();
76
+ return deepFreeze({
77
+ contractVersion: CONTRACT_VERSION,
78
+ checkedAt,
79
+ status: 'ready',
80
+ component: { id: COMPONENT_ID, role: COMPONENT_ROLE },
81
+ storage,
82
+ dependencies: {
83
+ aginti: 'not_probed',
84
+ localllm: 'not_probed',
85
+ lazyedge: 'not_probed'
86
+ }
87
+ });
88
+ } catch (error) {
89
+ const code = error instanceof ControlPlaneError ? error.code : 'storage_unavailable';
90
+ return deepFreeze({
91
+ contractVersion: CONTRACT_VERSION,
92
+ checkedAt,
93
+ status: 'unavailable',
94
+ component: { id: COMPONENT_ID, role: COMPONENT_ROLE },
95
+ storage: { ready: false, code },
96
+ dependencies: {
97
+ aginti: 'not_probed',
98
+ localllm: 'not_probed',
99
+ lazyedge: 'not_probed'
100
+ }
101
+ });
102
+ }
103
+ }
@@ -0,0 +1,254 @@
1
+ import { createHash } from 'node:crypto';
2
+
3
+ const COMPACTION_SCHEMA = 'lazying.direct-chat.local-compaction.v1';
4
+ const HASH_PATTERN = /^[a-f0-9]{64}$/u;
5
+ const MAX_RAW_MESSAGES = 2_000;
6
+ const MAX_RAW_BYTES = 8 * 1024 * 1024;
7
+
8
+ function exactRecord(value, required, optional, label) {
9
+ if (value === null || typeof value !== 'object' || Array.isArray(value)
10
+ || Object.getPrototypeOf(value) !== Object.prototype) {
11
+ throw new TypeError(`${label} must be a plain object`);
12
+ }
13
+ const descriptors = Object.getOwnPropertyDescriptors(value);
14
+ const allowed = new Set([...required, ...optional]);
15
+ for (const key of Reflect.ownKeys(descriptors)) {
16
+ const descriptor = descriptors[key];
17
+ if (typeof key !== 'string' || !allowed.has(key) || !descriptor.enumerable
18
+ || !Object.hasOwn(descriptor, 'value')) {
19
+ throw new TypeError(`${label} contains an unsupported field or accessor`);
20
+ }
21
+ }
22
+ for (const key of required) {
23
+ if (!Object.hasOwn(descriptors, key)) throw new TypeError(`${label}.${key} is required`);
24
+ }
25
+ return Object.freeze(Object.fromEntries(
26
+ Reflect.ownKeys(descriptors).map((key) => [key, descriptors[key].value])
27
+ ));
28
+ }
29
+
30
+ function denseArray(value, label) {
31
+ if (!Array.isArray(value) || Object.getPrototypeOf(value) !== Array.prototype
32
+ || value.length > MAX_RAW_MESSAGES) {
33
+ throw new TypeError(`${label} must be a bounded plain array`);
34
+ }
35
+ const descriptors = Object.getOwnPropertyDescriptors(value);
36
+ for (const key of Reflect.ownKeys(descriptors)) {
37
+ if (key === 'length') continue;
38
+ const descriptor = descriptors[key];
39
+ if (typeof key !== 'string' || !/^(0|[1-9]\d*)$/u.test(key)
40
+ || Number(key) >= value.length || !descriptor.enumerable
41
+ || !Object.hasOwn(descriptor, 'value')) {
42
+ throw new TypeError(`${label} must contain only dense data entries`);
43
+ }
44
+ }
45
+ for (let index = 0; index < value.length; index += 1) {
46
+ if (!Object.hasOwn(descriptors, String(index))) throw new TypeError(`${label} must be dense`);
47
+ }
48
+ return value;
49
+ }
50
+
51
+ function scalarText(value, label, maximumBytes) {
52
+ if (typeof value !== 'string' || value.includes('\u0000')) {
53
+ throw new TypeError(`${label} must be bounded text`);
54
+ }
55
+ for (let index = 0; index < value.length; index += 1) {
56
+ const code = value.charCodeAt(index);
57
+ if (code >= 0xd800 && code <= 0xdbff) {
58
+ const next = value.charCodeAt(index + 1);
59
+ if (!(next >= 0xdc00 && next <= 0xdfff)) throw new TypeError(`${label} has invalid Unicode`);
60
+ index += 1;
61
+ } else if (code >= 0xdc00 && code <= 0xdfff) {
62
+ throw new TypeError(`${label} has invalid Unicode`);
63
+ }
64
+ }
65
+ const bytes = Buffer.byteLength(value, 'utf8');
66
+ if (bytes > maximumBytes) throw new TypeError(`${label} exceeds its byte bound`);
67
+ return Object.freeze({ value, bytes });
68
+ }
69
+
70
+ function positiveInteger(value, label, maximum) {
71
+ if (!Number.isSafeInteger(value) || value < 1 || value > maximum) {
72
+ throw new TypeError(`${label} is invalid`);
73
+ }
74
+ return value;
75
+ }
76
+
77
+ function truncateUtf8(value, maximumBytes) {
78
+ if (maximumBytes < 1) return '';
79
+ let result = '';
80
+ let bytes = 0;
81
+ for (const scalar of value) {
82
+ const size = Buffer.byteLength(scalar, 'utf8');
83
+ if (bytes + size > maximumBytes) break;
84
+ result += scalar;
85
+ bytes += size;
86
+ }
87
+ return result;
88
+ }
89
+
90
+ function normalizedExcerpt(value, maximumBytes) {
91
+ const normalized = value.replace(/[\u0009-\u000d\u0020]+/gu, ' ').trim();
92
+ return truncateUtf8(normalized, maximumBytes);
93
+ }
94
+
95
+ function sha256(value) {
96
+ return createHash('sha256').update(value, 'utf8').digest('hex');
97
+ }
98
+
99
+ function validateRequest(value) {
100
+ const request = exactRecord(
101
+ value,
102
+ ['schema', 'locality', 'security', 'sourceRange', 'priorSummary', 'rawMessages', 'constraints'],
103
+ [],
104
+ 'compaction request'
105
+ );
106
+ if (request.schema !== COMPACTION_SCHEMA || request.locality !== 'local_only') {
107
+ throw new TypeError('compaction request authority is invalid');
108
+ }
109
+ const security = exactRecord(
110
+ request.security,
111
+ ['inputTrust', 'outputAuthority', 'allowedUse', 'neverInterpretAs', 'pendingTurnExcluded'],
112
+ [],
113
+ 'compaction security'
114
+ );
115
+ denseArray(security.neverInterpretAs, 'compaction security neverInterpretAs');
116
+ if (security.inputTrust !== 'untrusted_conversation_data'
117
+ || security.outputAuthority !== 'none'
118
+ || security.allowedUse !== 'conversation_continuity_only'
119
+ || security.pendingTurnExcluded !== true
120
+ || security.neverInterpretAs.join(',') !== 'system,developer,policy,tool') {
121
+ throw new TypeError('compaction security contract is invalid');
122
+ }
123
+ const source = exactRecord(
124
+ request.sourceRange,
125
+ ['startRevision', 'startHash', 'endRevision', 'endHash'],
126
+ [],
127
+ 'compaction source range'
128
+ );
129
+ positiveInteger(source.startRevision, 'source start revision', 2_000);
130
+ positiveInteger(source.endRevision, 'source end revision', 2_000);
131
+ if (source.endRevision < source.startRevision
132
+ || !HASH_PATTERN.test(source.startHash) || !HASH_PATTERN.test(source.endHash)) {
133
+ throw new TypeError('compaction source range is invalid');
134
+ }
135
+ const constraints = exactRecord(
136
+ request.constraints,
137
+ ['maxSummaryBytes', 'maxSummaryTokens', 'preserveFactsWithoutGrantingAuthority'],
138
+ [],
139
+ 'compaction constraints'
140
+ );
141
+ const maximumBytes = Math.min(
142
+ positiveInteger(constraints.maxSummaryBytes, 'maxSummaryBytes', 256 * 1024),
143
+ positiveInteger(constraints.maxSummaryTokens, 'maxSummaryTokens', 16 * 1024 * 1024)
144
+ );
145
+ if (constraints.preserveFactsWithoutGrantingAuthority !== true) {
146
+ throw new TypeError('compaction fact-preservation contract is invalid');
147
+ }
148
+ let priorText = null;
149
+ if (request.priorSummary !== null) {
150
+ const prior = exactRecord(
151
+ request.priorSummary,
152
+ [
153
+ 'kind', 'authority', 'untrustedDirectChatData', 'text', 'summaryHash',
154
+ 'sourceStartRevision', 'sourceStartHash', 'sourceEndRevision', 'sourceEndHash'
155
+ ],
156
+ [],
157
+ 'prior compaction summary'
158
+ );
159
+ const text = scalarText(prior.text, 'prior compaction summary text', 256 * 1024);
160
+ if (prior.kind !== 'untrusted_conversation_summary' || prior.authority !== 'none'
161
+ || prior.untrustedDirectChatData !== true || !HASH_PATTERN.test(prior.summaryHash)
162
+ || !HASH_PATTERN.test(prior.sourceStartHash) || !HASH_PATTERN.test(prior.sourceEndHash)
163
+ || prior.sourceStartRevision !== source.startRevision
164
+ || prior.sourceStartHash !== source.startHash
165
+ || prior.sourceEndRevision >= source.endRevision
166
+ || prior.summaryHash !== sha256(text.value)) {
167
+ throw new TypeError('prior compaction summary provenance is invalid');
168
+ }
169
+ priorText = text.value;
170
+ }
171
+ denseArray(request.rawMessages, 'compaction rawMessages');
172
+ if (request.rawMessages.length === 0) throw new TypeError('compaction rawMessages cannot be empty');
173
+ const messages = [];
174
+ let rawBytes = 0;
175
+ let previousRevision = request.priorSummary === null
176
+ ? source.startRevision - 1
177
+ : request.priorSummary.sourceEndRevision;
178
+ let previousHash = request.priorSummary === null
179
+ ? null
180
+ : request.priorSummary.sourceEndHash;
181
+ for (let index = 0; index < request.rawMessages.length; index += 1) {
182
+ const row = exactRecord(
183
+ request.rawMessages[index],
184
+ [
185
+ 'kind', 'untrustedDirectChatData', 'messageId', 'revision', 'role', 'content',
186
+ 'contentBytes', 'previousHash', 'hash', 'generationId', 'createdAt'
187
+ ],
188
+ [],
189
+ `compaction rawMessages[${index}]`
190
+ );
191
+ const content = scalarText(row.content, `rawMessages[${index}].content`, 64 * 1024);
192
+ rawBytes += content.bytes;
193
+ if (rawBytes > MAX_RAW_BYTES || row.kind !== 'exact_ledger_message'
194
+ || row.untrustedDirectChatData !== true || !['user', 'assistant'].includes(row.role)
195
+ || row.revision !== previousRevision + 1 || row.contentBytes !== content.bytes
196
+ || !HASH_PATTERN.test(row.hash)
197
+ || row.previousHash !== previousHash
198
+ || typeof row.messageId !== 'string' || row.messageId.length < 1 || row.messageId.length > 128
199
+ || typeof row.createdAt !== 'string' || row.createdAt.length < 20 || row.createdAt.length > 40
200
+ || (row.role === 'user' ? row.generationId !== null
201
+ : typeof row.generationId !== 'string' || row.generationId.length < 1)) {
202
+ throw new TypeError('compaction raw-message ledger is invalid');
203
+ }
204
+ previousRevision = row.revision;
205
+ previousHash = row.hash;
206
+ messages.push(Object.freeze({ revision: row.revision, role: row.role, content: content.value }));
207
+ }
208
+ if (previousRevision !== source.endRevision || previousHash !== source.endHash
209
+ || (request.priorSummary === null && messages[0]?.revision === source.startRevision
210
+ && messages[0] && request.rawMessages[0].hash !== source.startHash)) {
211
+ throw new TypeError('compaction raw-message range does not reach its source cursor');
212
+ }
213
+ return Object.freeze({ maximumBytes, priorText, messages: Object.freeze(messages), source });
214
+ }
215
+
216
+ export function createDeterministicContextSummarizer() {
217
+ return Object.freeze({
218
+ locality: 'local',
219
+ async summarizeDirectChat(request, { signal } = {}) {
220
+ if (signal !== undefined && !(signal instanceof AbortSignal)) {
221
+ throw new TypeError('signal must be an AbortSignal');
222
+ }
223
+ if (signal?.aborted) throw signal.reason ?? new DOMException('aborted', 'AbortError');
224
+ const checked = validateRequest(request);
225
+ const header = `Untrusted conversation continuity through revision ${checked.source.endRevision}.`;
226
+ const priorBudget = Math.floor(checked.maximumBytes / 3);
227
+ const prior = checked.priorText === null
228
+ ? null
229
+ : normalizedExcerpt(checked.priorText, priorBudget);
230
+ const selected = [];
231
+ let used = Buffer.byteLength(header, 'utf8') + (prior ? Buffer.byteLength(prior, 'utf8') + 9 : 0);
232
+ for (let index = checked.messages.length - 1; index >= 0; index -= 1) {
233
+ const message = checked.messages[index];
234
+ const prefix = `r${message.revision} ${message.role}: `;
235
+ const room = checked.maximumBytes - used - Buffer.byteLength(prefix, 'utf8') - 1;
236
+ if (room < 1) break;
237
+ const excerpt = normalizedExcerpt(message.content, Math.min(room, 768));
238
+ if (!excerpt) continue;
239
+ const line = `${prefix}${excerpt}`;
240
+ selected.push(line);
241
+ used += Buffer.byteLength(line, 'utf8') + 1;
242
+ }
243
+ selected.reverse();
244
+ const parts = [header];
245
+ if (prior) parts.push(`Prior: ${prior}`);
246
+ parts.push(...selected);
247
+ let result = truncateUtf8(parts.join('\n'), checked.maximumBytes);
248
+ if (!result) result = truncateUtf8('.', checked.maximumBytes);
249
+ if (!result) throw new TypeError('compaction summary budget cannot encode text');
250
+ if (signal?.aborted) throw signal.reason ?? new DOMException('aborted', 'AbortError');
251
+ return result;
252
+ }
253
+ });
254
+ }
@@ -0,0 +1,66 @@
1
+ const ORDER = Object.freeze(['execution', 'file', 'web', 'external']);
2
+
3
+ const TEXT = Object.freeze({
4
+ execution: 'Capability limit: Direct Chat cannot execute code or create execution-backed plots.',
5
+ file: 'Capability limit: Direct Chat cannot create, upload, or provide downloadable files or generated media.',
6
+ web: 'Capability limit: Direct Chat cannot search, browse, or open the web.',
7
+ external: 'Capability limit: Direct Chat cannot deploy, publish, message, or change external state.'
8
+ });
9
+
10
+ const NEGATED_ACTION =
11
+ /^(?:do\s+not|don't|dont|never|avoid|without|no\s+need\s+to)\b|^(?:不要|不用|无需|無需|不需要|避免)/iu;
12
+ const DISCUSSION_LEAD =
13
+ /^(?:explain|describe|discuss|review|compare|define|translate|summari[sz]e|quote|analy[sz]e|tell\s+me\s+(?:about|how|why|what)|how\b|why\b|what\b|write\s+(?:an?\s+)?(?:tutorial|explanation|guide|example|article)|(?:解释|解釋|描述|讨论|討論|说明|說明|为什么|為什麼|如何|什么是|什麼是))/iu;
14
+ const EXECUTION_ACTION =
15
+ /^(?:run|execute)\b[^.!?;\r\n]{0,180}\b(?:python|code|script|program|command|test|calculation)\b|^(?:plot|visuali[sz]e)\b|^(?:make|create|generate|draw|show|render|produce)\b[^.!?;\r\n]{0,160}\b(?:plot|chart|graph)\b|^(?:运行|運行|执行|執行).{0,80}(?:代码|代碼|脚本|腳本|python)|^(?:画图|畫圖|绘图|繪圖|生成图表|生成圖表)/iu;
16
+ const FILE_ACTION =
17
+ /^(?:make|create|generate|produce|prepare|compile|typeset|render|export|save|download|upload|provide|return|send|give|share)\b[^.!?;\r\n]{0,220}(?:\b(?:files?|attachments?|downloads?|archives?|pdf|latex|tex|documents?|spreadsheets?|presentations?|images?|photos?|illustrations?|audio|voice|videos?)\b|\.(?:pdf|tex|csv|json|md|docx?|xlsx?|pptx?|zip|tar|gz|py|js|ts|html|svg|png|jpe?g|webp|mp3|wav|m4a|mp4)\b)|^(?:创建|建立|生成|制作|製作|编译|編譯|导出|導出|下载|下載|上传|上傳).{0,140}(?:文件|文档|文檔|pdf|latex|tex|图片|圖片|音频|音頻|视频|視頻)/iu;
18
+ const WEB_ACTION =
19
+ /^(?:search|browse|google|visit|fetch|open|read|look\s+up|find)\b[^.!?;\r\n]{0,180}(?:\b(?:web|internet|online|website|site|url)\b|https?:\/\/|www\.)|^(?:搜索|搜尋|浏览|瀏覽|打开|打開|查找|查詢).{0,120}(?:网络|網絡|互联网|互聯網|网站|網站|网页|網頁)/iu;
20
+ const EXTERNAL_ACTION =
21
+ /^(?:deploy|publish|push|upload|email|post|submit)\b|^send\b[^.!?;\r\n]{0,160}\b(?:email|notification)\b|^send\b[^.!?;\r\n]{0,160}\bto\s+(?!(?:me|us|here|this\s+chat)\b)\S|^(?:change|update|delete|remove)\b[^.!?;\r\n]{0,160}\b(?:account|website|site|server|deployment|repository|repo|setting|record|remote)\b|^(?:部署|发布|發布|推送|上传|上傳|发送|發送|删除|刪除|修改).{0,120}(?:网站|網站|服务器|伺服器|仓库|倉庫|账号|帳號|设置|設定|邮件|郵件|消息)/iu;
22
+
23
+ function actionText(value) {
24
+ let text = String(value || '').trim();
25
+ text = text.replace(/^(?:please|kindly)\s+/iu, '');
26
+ text = text.replace(/^(?:can|could|would|will)\s+you\s+(?:(?:please|kindly)\s+)?/iu, '');
27
+ text = text.replace(/^i(?:'d|\s+would)?\s+(?:like|want|need)\s+(?:you\s+)?to\s+/iu, '');
28
+ text = text.replace(/^let(?:'s|\s+us)\s+/iu, '');
29
+ text = text.replace(/^(?:请你?|請你?|麻烦你?|麻煩你?)[ \t]*/u, '');
30
+ return text;
31
+ }
32
+
33
+ function clauses(value) {
34
+ const unquoted = String(value || '')
35
+ .normalize('NFKC')
36
+ .replace(/```[^\r\n]*\r?\n?[\s\S]*?```/gu, ' ')
37
+ .replace(/~~~[^\r\n]*\r?\n?[\s\S]*?~~~/gu, ' ')
38
+ .replace(/`[^`\r\n]*`/gu, ' ')
39
+ .replace(/[“”]([^“”\r\n]*)[“”]/gu, ' ')
40
+ .replace(/"([^"\r\n]*)"/gu, ' ')
41
+ .replace(/[‘’]([^‘’\r\n]*)[‘’]/gu, ' ');
42
+ return unquoted
43
+ .split(/(?:[!?。!?;;\r\n]+|\.(?=\s|$))/u)
44
+ .flatMap((clause) => clause.split(/(?:,\s*)?\b(?:and\s+then|then|but)\b\s+(?=(?:(?:please|kindly)\s+)?(?:do\s+not|don't|dont|never|avoid|run|execute|plot|visuali[sz]e|make|create|generate|draw|show|render|produce|prepare|compile|typeset|export|save|download|upload|provide|return|send|give|share|search|browse|google|visit|fetch|open|read|look\s+up|find|deploy|publish|push|email|post|submit|change|update|delete|remove|explain|describe|discuss|summari[sz]e)\b)/giu))
45
+ .map(actionText)
46
+ .filter(Boolean);
47
+ }
48
+
49
+ export function directChatCapabilityCategories(value) {
50
+ const requested = new Set();
51
+ for (const clause of clauses(value)) {
52
+ if (NEGATED_ACTION.test(clause) || DISCUSSION_LEAD.test(clause)) continue;
53
+ if (EXECUTION_ACTION.test(clause)) requested.add('execution');
54
+ if (FILE_ACTION.test(clause)) requested.add('file');
55
+ const suppliedTextTarget = /\b(?:in|from)\s+(?:the\s+)?(?:supplied|provided|attached|this|given)\s+(?:text|content|document)\b/iu.test(clause);
56
+ if (!suppliedTextTarget && WEB_ACTION.test(clause)) requested.add('web');
57
+ if (EXTERNAL_ACTION.test(clause)) requested.add('external');
58
+ }
59
+ return Object.freeze(ORDER.filter((category) => requested.has(category)));
60
+ }
61
+
62
+ export function directChatCapabilityNotice(value) {
63
+ const categories = directChatCapabilityCategories(value);
64
+ if (categories.length === 0) return '';
65
+ return `${categories.map((category) => TEXT[category]).join('\n')}\n\nI will still complete every supported text or current supplied-image part below.\n\n`;
66
+ }
@@ -0,0 +1,3 @@
1
+ // One shared boundary for the coordinator and the LocalLLM connector. A
2
+ // labeled summary consumes one entry just like an exact ledger message.
3
+ export const DIRECT_CHAT_CONTEXT_ENTRY_LIMIT = 256;
package/src/errors.js ADDED
@@ -0,0 +1,50 @@
1
+ export class ControlPlaneError extends Error {
2
+ constructor(message, { code = 'control_plane_error', cause } = {}) {
3
+ super(message, { cause });
4
+ this.name = new.target.name;
5
+ this.code = code;
6
+ }
7
+ }
8
+
9
+ export class ValidationError extends ControlPlaneError {
10
+ constructor(message, options = {}) {
11
+ super(message, { ...options, code: 'invalid_input' });
12
+ }
13
+ }
14
+
15
+ export class NotFoundError extends ControlPlaneError {
16
+ constructor(message = 'The requested resource does not exist.', options = {}) {
17
+ super(message, { ...options, code: 'not_found' });
18
+ }
19
+ }
20
+
21
+ export class ConflictError extends ControlPlaneError {
22
+ constructor(message = 'The requested mutation conflicts with existing state.', options = {}) {
23
+ super(message, { ...options, code: 'conflict' });
24
+ }
25
+ }
26
+
27
+ export class IdempotencyConflictError extends ControlPlaneError {
28
+ constructor(message = 'The idempotency key was already used for a different request.', options = {}) {
29
+ super(message, { ...options, code: 'idempotency_conflict' });
30
+ }
31
+ }
32
+
33
+ export class StorageSecurityError extends ControlPlaneError {
34
+ constructor(message, options = {}) {
35
+ super(message, { ...options, code: 'storage_security_error' });
36
+ }
37
+ }
38
+
39
+ export class StorageCorruptionError extends ControlPlaneError {
40
+ constructor(message = 'The control-plane database failed integrity validation.', options = {}) {
41
+ super(message, { ...options, code: 'storage_corruption' });
42
+ }
43
+ }
44
+
45
+ export class UnsupportedSchemaError extends ControlPlaneError {
46
+ constructor(message = 'The control-plane database schema is newer than this software.', options = {}) {
47
+ super(message, { ...options, code: 'unsupported_schema' });
48
+ }
49
+ }
50
+