@yeaft/webchat-agent 1.0.607 → 1.0.608
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/version.json +1 -1
- package/local-runtime/web/app.bundle.js +2 -2
- package/local-runtime/web/app.bundle.js.gz +0 -0
- package/local-runtime/web/index.html +1 -1
- package/package.json +1 -2
- package/yeaft/person/bridge.js +3 -7
- package/yeaft/person/capabilities.js +1 -1
- package/yeaft/person/contracts.js +1 -1
- package/yeaft/person/runtime.js +2 -2
- package/yeaft/person/service.js +10 -15
- package/yeaft/person/sqlite-storage.js +24 -0
- package/yeaft/person/sqlite-store.js +4 -4
- package/yeaft/person/task-host.js +1 -1
- package/yeaft/person/repository.js +0 -516
- package/yeaft/person/storage.js +0 -36
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yeaft/webchat-agent",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.608",
|
|
4
4
|
"description": "Remote worker agent for Yeaft Web Code Agent — connects the native Yeaft engine, CLI providers, and workbench tools",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -72,7 +72,6 @@
|
|
|
72
72
|
"dotenv": "^16.3.1",
|
|
73
73
|
"express": "^4.22.2",
|
|
74
74
|
"jsonwebtoken": "^9.0.2",
|
|
75
|
-
"mongodb": "^6.21.0",
|
|
76
75
|
"multer": "^2.2.0",
|
|
77
76
|
"nodemailer": "^9.0.3",
|
|
78
77
|
"puppeteer-core": "24.41.0",
|
package/yeaft/person/bridge.js
CHANGED
|
@@ -2,8 +2,8 @@ import { createHash } from 'node:crypto';
|
|
|
2
2
|
import ctx from '../../context.js';
|
|
3
3
|
import { sendToServer } from '../../connection/buffer.js';
|
|
4
4
|
|
|
5
|
-
/** Lazy Person transport.
|
|
6
|
-
*
|
|
5
|
+
/** Lazy Person transport. SQLite storage belongs to this Agent instance;
|
|
6
|
+
* browser messages cannot choose a storage authority or instance directory.
|
|
7
7
|
*/
|
|
8
8
|
export function createPersonBridge({
|
|
9
9
|
context = ctx, send = sendToServer, env = process.env,
|
|
@@ -32,7 +32,6 @@ export function createPersonBridge({
|
|
|
32
32
|
await send({ ...response, ok: false, errorCode: 'invalid_request', error: 'Digital person request requires authenticated ownership' });
|
|
33
33
|
return true;
|
|
34
34
|
}
|
|
35
|
-
const uri = env.YEAFT_PERSON_MONGODB_URI;
|
|
36
35
|
const agentId = context.agentId || context.AGENT_ID;
|
|
37
36
|
if (!agentId || !context.CONFIG?.yeaftDir) throw new Error('identity');
|
|
38
37
|
// Server+Agent is a storage boundary; a re-registration must not reuse the old runtime.
|
|
@@ -45,10 +44,7 @@ export function createPersonBridge({
|
|
|
45
44
|
identity = namespace;
|
|
46
45
|
if (!servicePromise) {
|
|
47
46
|
servicePromise = Promise.resolve().then(() => createService({
|
|
48
|
-
uri,
|
|
49
|
-
storage: env.YEAFT_PERSON_STORAGE,
|
|
50
47
|
embedding: { enabled: env.YEAFT_PERSON_EMBEDDING !== 'off', allowDownload: env.YEAFT_PERSON_EMBEDDING_DOWNLOAD !== '0' },
|
|
51
|
-
dbName: env.YEAFT_PERSON_MONGODB_DB || 'yeaft_person',
|
|
52
48
|
namespace,
|
|
53
49
|
yeaftDir: context.CONFIG.yeaftDir,
|
|
54
50
|
workDir: context.CONFIG.workDir,
|
|
@@ -78,7 +74,7 @@ export function createPersonBridge({
|
|
|
78
74
|
TASK_CONTROL_UNAVAILABLE: 'Task control could not be confirmed; refresh before retrying',
|
|
79
75
|
STALE: 'Digital person state changed; refresh and try again',
|
|
80
76
|
IDEMPOTENCY_CONFLICT: 'This message identifier already belongs to a different request',
|
|
81
|
-
STORAGE_MISMATCH: '
|
|
77
|
+
STORAGE_MISMATCH: 'Existing digital person storage is not a valid SQLite binding; check instance storage before retrying',
|
|
82
78
|
UNSUPPORTED: 'This capability is not available in this digital person version',
|
|
83
79
|
};
|
|
84
80
|
const known = Object.hasOwn(safeErrors, error?.code);
|
|
@@ -83,7 +83,7 @@ export function inspectCapabilities(created, { cursor, limit }, nativeToolIds =
|
|
|
83
83
|
contract.evidence = item.evidence;
|
|
84
84
|
entries.push(item);
|
|
85
85
|
}
|
|
86
|
-
// IDs are ASCII identifiers. Binary ordering matches SQLite
|
|
86
|
+
// IDs are ASCII identifiers. Binary ordering matches SQLite.
|
|
87
87
|
const matches = entries.filter(entry => cursor === null || entry.id > cursor)
|
|
88
88
|
.sort((a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0);
|
|
89
89
|
return inspectionPage(matches, limit, 'id');
|
|
@@ -5,7 +5,7 @@ export const LIMITS = Object.freeze({ inputBytes: 8192, outputBytes: 65536, cont
|
|
|
5
5
|
const ERRORS = {
|
|
6
6
|
INVALID_REQUEST: 'Invalid digital person request.', NOT_CONFIGURED: 'Digital person storage configuration is missing.',
|
|
7
7
|
STORAGE_UNAVAILABLE: 'Digital person storage is unavailable. Check instance storage and database configuration.',
|
|
8
|
-
STORAGE_MISMATCH: '
|
|
8
|
+
STORAGE_MISMATCH: 'Existing digital person storage is not a valid SQLite binding.',
|
|
9
9
|
NOT_OPEN: 'Open your digital person first.', BUSY: 'The digital person is already active.',
|
|
10
10
|
IDEMPOTENCY_CONFLICT: 'This clientMessageId was already used for a different request.',
|
|
11
11
|
STALE: 'The activity no longer owns the current person revision.', INVALID_PROPOSAL: 'The model returned an invalid cognitive proposal.',
|
package/yeaft/person/runtime.js
CHANGED
|
@@ -147,8 +147,8 @@ export class PersonRuntime {
|
|
|
147
147
|
this.running = new Map(); this.pendingCancellations = new Map(); this.workerId = randomUUID(); this.closed = false;
|
|
148
148
|
}
|
|
149
149
|
tasks() {
|
|
150
|
-
// Construction/status/open do not touch task storage.
|
|
151
|
-
//
|
|
150
|
+
// Construction/status/open do not touch task storage. An instance root is
|
|
151
|
+
// required before creating the durable task host.
|
|
152
152
|
if (!this.toolOptions.yeaftDir) return null;
|
|
153
153
|
return this.taskHost ??= new PersonTaskHost({ ...this.toolOptions, namespace: this.namespace });
|
|
154
154
|
}
|
package/yeaft/person/service.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { MongoPersonRepository } from './repository.js';
|
|
2
1
|
import { SqlitePersonRepository } from './sqlite-repository.js';
|
|
3
2
|
import { LocalPersonMemory } from './local-memory.js';
|
|
4
|
-
import {
|
|
3
|
+
import { ensurePersonStorage } from './sqlite-storage.js';
|
|
5
4
|
import { PersonRuntime } from './runtime.js';
|
|
6
5
|
import { allowedNativeToolIds } from './native-tools.js';
|
|
7
6
|
import { loadConfig } from '../config.js';
|
|
@@ -12,35 +11,31 @@ import { inspectRequest, personName, searchRequest } from './inspection.js';
|
|
|
12
11
|
|
|
13
12
|
/**
|
|
14
13
|
* One durable Person per (namespace, authenticated ownerId). The transport MUST
|
|
15
|
-
* supply authenticated ownerId, never accept it from a browser payload.
|
|
14
|
+
* supply authenticated ownerId, never accept it from a browser payload. Provider
|
|
16
15
|
* secrets stay inside this Agent. Constructor and status never initiate model calls.
|
|
17
|
-
*
|
|
18
|
-
*
|
|
16
|
+
* SQLite storage and native provider configuration belong to yeaftDir only
|
|
17
|
+
* (no Session initialization or alternate storage authority).
|
|
19
18
|
*/
|
|
20
19
|
export function createPersonService(options = {}) {
|
|
21
|
-
const {
|
|
20
|
+
const { namespace = 'default', yeaftDir, workDir, config, adapter, allowedModels } = options;
|
|
22
21
|
identifier(namespace);
|
|
23
|
-
|
|
24
|
-
if (uri != null && typeof uri !== 'string') fail('INVALID_REQUEST');
|
|
25
|
-
const { storage, configured } = selectPersonStorage({ ...options, yeaftDir });
|
|
22
|
+
const configured = typeof yeaftDir === 'string' && Boolean(yeaftDir.trim());
|
|
26
23
|
const calls = options.maxCalls ?? LIMITS.calls;
|
|
27
24
|
const timeoutMs = options.timeoutMs ?? LIMITS.timeoutMs;
|
|
28
25
|
const leaseMs = options.leaseMs ?? LIMITS.leaseMs;
|
|
29
26
|
if (!Number.isInteger(calls) || calls < 1 || calls > 32 || !Number.isInteger(timeoutMs) || timeoutMs < 100 || timeoutMs > 300000 ||
|
|
30
27
|
!Number.isInteger(leaseMs) || leaseMs < 300 || leaseMs > 60000) fail('INVALID_REQUEST');
|
|
31
28
|
if (allowedModels != null && (!Array.isArray(allowedModels) || !allowedModels.length || allowedModels.some(m => typeof m !== 'string'))) fail('INVALID_REQUEST');
|
|
32
|
-
const repository =
|
|
33
|
-
? new MongoPersonRepository({ uri, dbName, namespace, MongoClient, leaseMs })
|
|
34
|
-
: new SqlitePersonRepository({ yeaftDir, namespace, leaseMs });
|
|
29
|
+
const repository = configured ? new SqlitePersonRepository({ yeaftDir, namespace, leaseMs }) : null;
|
|
35
30
|
const initialize = repository?.init.bind(repository);
|
|
36
31
|
let binding;
|
|
37
32
|
if (repository) repository.init = async () => {
|
|
38
|
-
if (!binding) binding =
|
|
33
|
+
if (!binding) binding = ensurePersonStorage(yeaftDir, namespace).catch(error => { binding = null; throw error; });
|
|
39
34
|
await binding;
|
|
40
35
|
return initialize();
|
|
41
36
|
};
|
|
42
37
|
const literalRecall = repository?.recall.bind(repository);
|
|
43
|
-
const memory = repository
|
|
38
|
+
const memory = repository ? new LocalPersonMemory({
|
|
44
39
|
repository, literalRecall, yeaftDir, namespace, embedding: options.embedding,
|
|
45
40
|
}) : null;
|
|
46
41
|
if (memory) repository.recall = (ownerId, args = {}, { signal } = {}) => args.query?.trim()
|
|
@@ -58,7 +53,7 @@ export function createPersonService(options = {}) {
|
|
|
58
53
|
object(payload, []);
|
|
59
54
|
let agentDefaultModel = null;
|
|
60
55
|
try { agentDefaultModel = resolveAgentDefaultModel(config ?? loadConfig({ dir: yeaftDir })); } catch { /* Invalid/missing native config. */ }
|
|
61
|
-
const status = { renameSupported: true, defaultModelSupported: true, configured, storage, modelReady: false,
|
|
56
|
+
const status = { renameSupported: true, defaultModelSupported: true, configured, storage: 'sqlite', modelReady: false,
|
|
62
57
|
defaultModel: null, agentDefaultModel, modelCandidates: [], effectiveModelCandidates: [], effectiveDefaultModel: null };
|
|
63
58
|
if (!configured) return { ...status, reason: 'Digital person storage configuration is missing.', storageReady: false };
|
|
64
59
|
try { await repository.init(); }
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { mkdir, readFile, writeFile, link, unlink } from 'node:fs/promises';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { randomUUID } from 'node:crypto';
|
|
4
|
+
import { digest, fail } from './contracts.js';
|
|
5
|
+
|
|
6
|
+
/** Preserve existing instance bindings without silently creating a new authority.
|
|
7
|
+
* SQLite is the only supported storage; this marker is not a backend selector.
|
|
8
|
+
*/
|
|
9
|
+
export async function ensurePersonStorage(yeaftDir, namespace) {
|
|
10
|
+
const directory = join(yeaftDir, 'person');
|
|
11
|
+
await mkdir(directory, { recursive: true, mode: 0o700 });
|
|
12
|
+
const path = join(directory, `storage-${digest(namespace)}.json`);
|
|
13
|
+
const temporary = `${path}.${randomUUID()}.tmp`;
|
|
14
|
+
await writeFile(temporary, JSON.stringify({ version: 1, storage: 'sqlite' }), { flag: 'wx', mode: 0o600 });
|
|
15
|
+
try {
|
|
16
|
+
// A fully written inode is linked atomically; competing openers never read partial JSON.
|
|
17
|
+
await link(temporary, path);
|
|
18
|
+
} catch (error) {
|
|
19
|
+
if (error.code !== 'EEXIST') throw error;
|
|
20
|
+
let binding;
|
|
21
|
+
try { binding = JSON.parse(await readFile(path, 'utf8')); } catch { fail('STORAGE_MISMATCH'); }
|
|
22
|
+
if (!binding || binding.version !== 1 || binding.storage !== 'sqlite') fail('STORAGE_MISMATCH');
|
|
23
|
+
} finally { await unlink(temporary).catch(() => {}); }
|
|
24
|
+
}
|
|
@@ -448,9 +448,9 @@ export class SqlitePersonStore {
|
|
|
448
448
|
list(ownerId, collection, { cursor = null, limit = 20 } = {}, filter = {}) {
|
|
449
449
|
if (!['messages', 'traces'].includes(collection)) fail('INVALID_REQUEST');
|
|
450
450
|
this.getPerson(ownerId); boundedLimit(limit);
|
|
451
|
-
if (Object.keys(filter).some(k => k !== '
|
|
452
|
-
|
|
453
|
-
|
|
451
|
+
if (Object.keys(filter).some(k => k !== 'query')) fail('INVALID_REQUEST');
|
|
452
|
+
const query = filter.query ?? '';
|
|
453
|
+
text(query, LIMITS.inputBytes, true);
|
|
454
454
|
if (query && collection !== 'messages') fail('INVALID_REQUEST');
|
|
455
455
|
let clause = '', params = [];
|
|
456
456
|
if (cursor != null) { clause += ' AND seq < ?'; params.push(sequence(cursor)); }
|
|
@@ -460,7 +460,7 @@ export class SqlitePersonStore {
|
|
|
460
460
|
}
|
|
461
461
|
recall(ownerId, { kind = 'messages', query = '', cursor = null, limit = 5 } = {}) {
|
|
462
462
|
memoryKind(kind); text(query, LIMITS.inputBytes, true); boundedLimit(limit);
|
|
463
|
-
if (kind === 'messages') return this.list(ownerId, kind, { cursor, limit },
|
|
463
|
+
if (kind === 'messages') return this.list(ownerId, kind, { cursor, limit }, { query });
|
|
464
464
|
this.getPerson(ownerId);
|
|
465
465
|
if (cursor != null) text(cursor, 128);
|
|
466
466
|
const docs = this.rows('concepts', this.scope(ownerId), ' AND (? IS NULL OR id > ?) AND instr(statement, ?) > 0 ORDER BY id ASC LIMIT ?', [cursor, cursor, query.toLowerCase(), limit + 1]);
|
|
@@ -61,7 +61,7 @@ function privatePath(root, path) {
|
|
|
61
61
|
// expose native command fallbacks; redact common sensitive forms before capping.
|
|
62
62
|
function summary(value, maxBytes = 512) {
|
|
63
63
|
const clean = String(value ?? '')
|
|
64
|
-
.replace(
|
|
64
|
+
.replace(/\b[a-z][a-z\d+.-]*:\/\/\S+/gi, '[url]')
|
|
65
65
|
.replace(/(?:bearer\s+|(?:api[_-]?key|password|secret|token)\s*[:=]\s*)\S+/gi, '[redacted]')
|
|
66
66
|
.replace(/\b(?:sk-[\w-]+|gh[pousr]_[\w]+|github_pat_[\w]+)\b/g, '[redacted]')
|
|
67
67
|
.replace(/(?:\/[\w.@~+-]+)+(?:\/[^\s]*)?|(?:~|\.\.?|[A-Za-z]:)[\\/][^\s]+/g, '[path]')
|