@yeaft/webchat-agent 1.0.592 → 1.0.594
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/local-runtime/server/handlers/client-person.js +34 -6
- package/local-runtime/version.json +1 -1
- package/local-runtime/web/app.bundle.js +177 -177
- package/local-runtime/web/app.bundle.js.gz +0 -0
- package/local-runtime/web/index.html +2 -2
- package/local-runtime/web/style.bundle.css +1 -1
- package/local-runtime/web/style.bundle.css.gz +0 -0
- package/package.json +1 -1
- package/yeaft/llm/openai-responses.js +1 -1
- package/yeaft/person/attachments.js +58 -0
- package/yeaft/person/bridge.js +5 -0
- package/yeaft/person/contracts.js +12 -0
- package/yeaft/person/provider.js +59 -7
- package/yeaft/person/repository.js +34 -10
- package/yeaft/person/runtime.js +42 -11
- package/yeaft/person/service.js +36 -10
- package/yeaft/person/sqlite-repository.js +2 -0
- package/yeaft/person/sqlite-schema.js +7 -0
- package/yeaft/person/sqlite-store.js +29 -10
|
@@ -3,9 +3,10 @@ import { mkdirSync } from 'node:fs';
|
|
|
3
3
|
import { dirname } from 'node:path';
|
|
4
4
|
import { randomUUID } from 'node:crypto';
|
|
5
5
|
import { isMainThread } from 'node:worker_threads';
|
|
6
|
-
import { bytes, digest, fail, identifier, LIMITS, PersonError, text } from './contracts.js';
|
|
6
|
+
import { admissionReceipt, bytes, digest, fail, identifier, LIMITS, PersonError, text } from './contracts.js';
|
|
7
7
|
import { CREATED_CAPABILITY_LIMITS, createdCapabilityRecord, validateCreatedCapability } from './created-capability-contract.js';
|
|
8
8
|
import { SCHEMA, TABLES } from './sqlite-schema.js';
|
|
9
|
+
import { attachmentMetadata, attachmentRequestHash, validateFiles } from './attachments.js';
|
|
9
10
|
import { capabilityExperienceView, recordCapabilityExperience } from './capability-experience.js';
|
|
10
11
|
|
|
11
12
|
const SCOPE = 'namespace = ? AND ownerId = ? AND personId = ?';
|
|
@@ -45,7 +46,7 @@ const publicOutput = (output, failed) => {
|
|
|
45
46
|
return failed ? { text: output.text, retainedBytes, observedBytes, complete: false, accepted: false, availability: 'captured', usage, stopReason }
|
|
46
47
|
: { text: output.text, bytes: retainedBytes, complete: true, usage, stopReason };
|
|
47
48
|
};
|
|
48
|
-
const READS = new Set(['getPerson', 'context', 'recall', 'list', 'searchChanges', 'resolveMemories', 'createdCapabilities']);
|
|
49
|
+
const READS = new Set(['receipt', 'getPerson', 'context', 'recall', 'list', 'searchChanges', 'resolveMemories', 'createdCapabilities', 'episodeAttachments']);
|
|
49
50
|
const WRITES = new Set(['open', 'recover', 'admit', 'heartbeat', 'append', 'startCall', 'finalizeCall', 'commit', 'finish', 'cancel', 'settings', 'snapshot', 'saveCreatedCapability']);
|
|
50
51
|
const memoryKind = kind => { if (!['messages', 'concepts'].includes(kind)) fail('INVALID_REQUEST'); return kind; };
|
|
51
52
|
const boundedLimit = (limit, max = 100) => { if (!Number.isSafeInteger(limit) || limit < 1 || limit > max) fail('INVALID_REQUEST'); return limit; };
|
|
@@ -190,11 +191,17 @@ export class SqlitePersonStore {
|
|
|
190
191
|
}
|
|
191
192
|
return expired;
|
|
192
193
|
}
|
|
193
|
-
|
|
194
|
+
receipt(ownerId, clientMessageId, requestHash) {
|
|
195
|
+
identifier(clientMessageId);
|
|
196
|
+
const episode = this.one('episodes', this.scope(ownerId), ' AND clientMessageId = ?', [clientMessageId]);
|
|
197
|
+
return admissionReceipt(episode, clientMessageId, requestHash);
|
|
198
|
+
}
|
|
199
|
+
admit(ownerId, { kind, text: input, clientMessageId, workerId, budget, files = [] }) {
|
|
194
200
|
if (!['send', 'think', 'dream'].includes(kind)) fail('INVALID_REQUEST');
|
|
195
|
-
|
|
201
|
+
const validated = validateFiles(files, input, kind), attachments = validated.map(attachmentMetadata);
|
|
202
|
+
identifier(clientMessageId); text(workerId, 256);
|
|
196
203
|
this.recover(ownerId);
|
|
197
|
-
const scope = this.scope(ownerId), requestHash =
|
|
204
|
+
const scope = this.scope(ownerId), requestHash = attachmentRequestHash(kind, input, validated);
|
|
198
205
|
const existing = this.one('episodes', scope, ' AND clientMessageId = ?', [clientMessageId]);
|
|
199
206
|
if (existing) {
|
|
200
207
|
if (existing.requestHash !== requestHash) fail('IDEMPOTENCY_CONFLICT');
|
|
@@ -203,21 +210,33 @@ export class SqlitePersonStore {
|
|
|
203
210
|
const p = this.getPerson(ownerId);
|
|
204
211
|
if (p.activeEpisodeId) fail('BUSY');
|
|
205
212
|
const id = randomUUID();
|
|
213
|
+
const hasMessage = kind === 'send' || (kind === 'think' && attachments.length > 0);
|
|
206
214
|
p.activeEpisodeId = id; p.leaseOwner = workerId; p.leaseUntil = new Date(this.now.getTime() + this.leaseMs);
|
|
207
|
-
p.epoch++; p.writeSerial++; p.inputWatermark++; if (
|
|
215
|
+
p.epoch++; p.writeSerial++; p.inputWatermark++; if (hasMessage) p.messageSeq++;
|
|
208
216
|
this.put('persons', p);
|
|
209
217
|
let messageId = null;
|
|
210
|
-
|
|
218
|
+
for (const file of validated) this.put('attachments', this.doc(scope, file));
|
|
219
|
+
if (hasMessage) {
|
|
211
220
|
messageId = randomUUID();
|
|
212
|
-
const message = this.doc(scope, { id: messageId, revision: 1, seq: p.messageSeq, episodeId: id, role: 'user', text: input, createdAt: this.now, clientMessageId });
|
|
221
|
+
const message = this.doc(scope, { id: messageId, revision: 1, seq: p.messageSeq, episodeId: id, role: 'user', text: input, attachments, createdAt: this.now, clientMessageId });
|
|
213
222
|
this.put('messages', message, true); this.journal(scope, 'messages', message);
|
|
214
223
|
}
|
|
215
|
-
const episode = this.doc(scope, { id, clientMessageId, requestHash, kind, text: input, messageId, status: 'running', workerId, epoch: p.epoch,
|
|
224
|
+
const episode = this.doc(scope, { id, clientMessageId, requestHash, kind, text: input, messageId, attachments, modelCandidates: [...(p.settings.modelCandidates ?? [])], status: 'running', workerId, epoch: p.epoch,
|
|
216
225
|
baseStateVersion: p.stateVersion, inputWatermark: p.inputWatermark, controlVersion: p.controlVersion, budget, createdAt: this.now });
|
|
217
226
|
this.put('episodes', episode, true);
|
|
218
|
-
this.trace(ownerId, id, 'accepted', { trigger: { kind, text: input, messageId }, baseStateVersion: p.stateVersion, budget });
|
|
227
|
+
this.trace(ownerId, id, 'accepted', { trigger: { kind, text: input, messageId, attachments }, baseStateVersion: p.stateVersion, budget });
|
|
219
228
|
return { episodeId: id, duplicate: false, status: 'running', episode };
|
|
220
229
|
}
|
|
230
|
+
episodeAttachments(episode) {
|
|
231
|
+
this.own(episode);
|
|
232
|
+
const scope = this.episodeScope(episode);
|
|
233
|
+
const stored = this.one('episodes', scope, ' AND id = ?', [episode.id]);
|
|
234
|
+
return (stored.attachments ?? []).map(ref => {
|
|
235
|
+
const file = this.one('attachments', scope, ' AND id = ?', [ref.id]);
|
|
236
|
+
if (!file || file.sha256 !== ref.sha256) fail('STORAGE_UNAVAILABLE');
|
|
237
|
+
return publicDoc(file);
|
|
238
|
+
});
|
|
239
|
+
}
|
|
221
240
|
episodeScope(episode) {
|
|
222
241
|
const scope = this.scope(episode.ownerId);
|
|
223
242
|
if (episode.namespace !== scope.namespace || episode.personId !== scope.personId) fail('STALE');
|