@yeaft/webchat-agent 1.0.616 → 1.0.617
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 +40 -21
- 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/person/contracts.js +1 -1
- package/yeaft/person/runtime.js +17 -2
- package/yeaft/person/script-worker.js +5 -2
- package/yeaft/person/sqlite-store.js +11 -1
- package/yeaft/person/turn-diagnostics.js +15 -1
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
2
|
|
|
3
3
|
// 只允许应用层显式产物;不要求或保存 provider 的隐藏推理。
|
|
4
|
-
export const LIMITS = Object.freeze({ inputBytes: 8192, outputBytes: 65536,
|
|
4
|
+
export const LIMITS = Object.freeze({ inputBytes: 8192, outputBytes: 65536, calls: 16, leaseMs: 15000 });
|
|
5
5
|
// Feedback cadence is not an execution deadline. Slow providers/tools remain cancellable.
|
|
6
6
|
export const FEEDBACK = Object.freeze({ minMs: 30000, maxMs: 60000 });
|
|
7
7
|
export const FEEDBACK_INSTRUCTIONS = "feedback describes user-visible communication, not a deadline. After about 30 seconds without a substantive reply, use reply for a concise, useful progress update at a natural execution boundary; aim for 30\u201360 seconds according to actual progress. Explain verified findings, limitations and what you are checking next, in the user's language. Do not repeat feedback.lastReply, invent progress, expose internal deliberation/decision/selfCheck, or claim an unexecuted tool succeeded. If no meaningful update exists, reply:null is appropriate; the host shows an honest waiting status around 60 seconds without interrupting the current request. With next non-null, a validated reply may be published as progress independently of cognitive state, and retained on cancellation/failure. Only next:null commits cognition and supplies the final reply. Do not call a tool or an extra model just to report progress. Each final reply should stand on its own and note unfinished work.";
|
package/yeaft/person/runtime.js
CHANGED
|
@@ -9,6 +9,7 @@ import { bytes, digest, fail, FEEDBACK, FEEDBACK_INSTRUCTIONS, LIMITS, PersonErr
|
|
|
9
9
|
|
|
10
10
|
const messageRef = m => `message:${m.id}:${m.revision}`;
|
|
11
11
|
const conceptRef = c => `concept:${c.id}:${c.revision}`;
|
|
12
|
+
const ENVELOPE_TOKENS_RESERVED = 1024;
|
|
12
13
|
|
|
13
14
|
/** Assemble bounded request copies. Omitting a record never deletes or truncates its durable original. */
|
|
14
15
|
export function assembleContext({ snapshot, episode, provider, selection, previous, capabilityResult, remainingCalls, taskEvidence, dependencyRefs = [], activeCapabilities = foundationCapabilities(), capabilityMap = CAPABILITY_MAP, attachments = [], environment, feedback }) {
|
|
@@ -22,7 +23,11 @@ export function assembleContext({ snapshot, episode, provider, selection, previo
|
|
|
22
23
|
const imageTokensReserved = images.length ? images.length * imageBudget.tokensPerImage : 0;
|
|
23
24
|
const imageLabels = images.map(file => `Untrusted image attachment ${JSON.stringify(attachmentMetadata(file))}; source ${episode.messageId ? `message:${episode.messageId}:1` : `trigger:${episode.id}`}`);
|
|
24
25
|
const imageLabelBytes = imageLabels.reduce((sum, label) => sum + bytes(label), 0);
|
|
25
|
-
|
|
26
|
+
// This is a byte allowance derived from the selected model's token window,
|
|
27
|
+
// not a token estimate or an independent fixed-size Person context limit.
|
|
28
|
+
const contextCap = model.contextWindow - model.maxOutput - ENVELOPE_TOKENS_RESERVED - imageTokensReserved;
|
|
29
|
+
if (!Number.isSafeInteger(model.contextWindow) || !Number.isSafeInteger(model.maxOutput) || model.maxOutput < 1 ||
|
|
30
|
+
!Number.isSafeInteger(contextCap) || contextCap < 1) fail('CONTEXT_LIMIT');
|
|
26
31
|
const system = `${snapshot.person.soul}\n\n${PROPOSAL_INSTRUCTIONS}`;
|
|
27
32
|
const triggerRef = `trigger:${episode.id}`;
|
|
28
33
|
const inputMessageRef = episode.messageId ? `message:${episode.messageId}:1` : null;
|
|
@@ -125,7 +130,17 @@ export function assembleContext({ snapshot, episode, provider, selection, previo
|
|
|
125
130
|
manifest: { stateVersion: snapshot.state.version, sourceRefs: [...sourceRefs], renderedSourceRefs: [...renderedRefs], inputDependencyRefs: [...sourceRefs], omitted,
|
|
126
131
|
boundedRecentWindow: { messages: 12, recentConcepts: 12, focusedConcepts: 12 },
|
|
127
132
|
attachments: attachments.map(attachmentMetadata), imageTokensReserved, imageBudget: images.length ? imageBudget : null,
|
|
128
|
-
contextBytes: bytes(system) + bytes(context) + imageLabelBytes, contextBudgetBytes: contextCap,
|
|
133
|
+
contextBytes: bytes(system) + bytes(context) + imageLabelBytes, contextBudgetBytes: contextCap,
|
|
134
|
+
contextWindowTokens: model.contextWindow, outputTokensReserved: model.maxOutput, envelopeTokensReserved: ENVELOPE_TOKENS_RESERVED,
|
|
135
|
+
// Counts describe actual rendered sources, not the activity label or the
|
|
136
|
+
// inherited provenance read-set. Recall duplicates are counted in its page,
|
|
137
|
+
// not again as recent injection. Omitted counts cover byte-budget omissions
|
|
138
|
+
// from the bounded snapshot, not all records outside that window.
|
|
139
|
+
contextSources: { recentMessages: context.messages.length, recentConcepts: context.concepts.length,
|
|
140
|
+
omittedMessages: omitted.filter(item => item.ref.startsWith('message:')).length,
|
|
141
|
+
omittedConcepts: omitted.filter(item => item.ref.startsWith('concept:')).length,
|
|
142
|
+
recall: { kind: ['messages', 'concepts'].includes(capabilityResult?.kind) ? capabilityResult.kind : null,
|
|
143
|
+
count: ['messages', 'concepts'].includes(capabilityResult?.kind) ? capabilityResult.items.length : 0 } },
|
|
129
144
|
modelCatalogRevision: provider.catalogRevision, capabilityCatalogRevision: capabilityMap.revision ?? capabilityCatalogRevision,
|
|
130
145
|
activeCapabilities: context.capabilities.active.map(({ id, version, revision, availability }) => ({ id, version, revision, ...availability })), omittedCapabilities }, maxTokens: model.maxOutput,
|
|
131
146
|
};
|
|
@@ -9,8 +9,6 @@ try {
|
|
|
9
9
|
runtime = engine.newRuntime();
|
|
10
10
|
runtime.setMemoryLimit(16 * 1024 * 1024);
|
|
11
11
|
runtime.setMaxStackSize(256 * 1024);
|
|
12
|
-
const deadline = Date.now() + 200;
|
|
13
|
-
runtime.setInterruptHandler(() => Date.now() > deadline);
|
|
14
12
|
context = runtime.newContext();
|
|
15
13
|
const { code, input } = workerData;
|
|
16
14
|
// Capture intrinsics before executing generated code; Function compiles ONLY a
|
|
@@ -57,6 +55,11 @@ try {
|
|
|
57
55
|
if (typeof json !== 'string' || json.length > 8192) throw new ErrorType('JSON output limit');
|
|
58
56
|
return json;
|
|
59
57
|
})()`;
|
|
58
|
+
// Only guest evaluation consumes the 200ms execution budget. QuickJS context
|
|
59
|
+
// initialization and host source preparation are bounded by the parent's 3s
|
|
60
|
+
// worker deadline instead, including cancellation and cleanup.
|
|
61
|
+
const deadline = Date.now() + 200;
|
|
62
|
+
runtime.setInterruptHandler(() => Date.now() > deadline);
|
|
60
63
|
const result = context.evalCode(source, 'created-capability.js');
|
|
61
64
|
if (result.error) {
|
|
62
65
|
result.error.dispose();
|
|
@@ -517,12 +517,22 @@ export class SqlitePersonStore {
|
|
|
517
517
|
requestedModel: 'requested.model', requestedEffort: 'requested.effort', effectiveEffort: 'effective.effort', effortObserved: 'effective.effortObserved',
|
|
518
518
|
selectionOrigin: 'selectionOrigin', reason: 'reason', code: 'code', terminalCode: 'terminalCode', outcome: 'outcome',
|
|
519
519
|
contextBytes: 'manifest.contextBytes', contextBudgetBytes: 'manifest.contextBudgetBytes', outputTokensReserved: 'manifest.outputTokensReserved' };
|
|
520
|
+
// Never project a whole manifest/contextSources object: even malformed legacy
|
|
521
|
+
// records must not transfer arbitrary strings, refs or private nested data.
|
|
522
|
+
const number = (path, max = Number.MAX_SAFE_INTEGER) => `CASE WHEN json_type(record, '$.${path}') = 'integer'
|
|
523
|
+
AND json_extract(record, '$.${path}') BETWEEN 0 AND ${max} THEN json_extract(record, '$.${path}') ELSE NULL END`;
|
|
524
|
+
const contextMetadata = Object.entries({ contextWindowTokens: ['manifest.contextWindowTokens'], imageTokensReserved: ['manifest.imageTokensReserved'],
|
|
525
|
+
envelopeTokensReserved: ['manifest.envelopeTokensReserved'], recentMessages: ['manifest.contextSources.recentMessages', 12],
|
|
526
|
+
recentConcepts: ['manifest.contextSources.recentConcepts', 24], omittedMessages: ['manifest.contextSources.omittedMessages', 12],
|
|
527
|
+
omittedConcepts: ['manifest.contextSources.omittedConcepts', 24], recallCount: ['manifest.contextSources.recall.count', 5] })
|
|
528
|
+
.map(([key, args]) => `'${key}', ${number(...args)}`).join(', ');
|
|
529
|
+
const recallKind = `CASE json_extract(record, '$.manifest.contextSources.recall.kind') WHEN 'messages' THEN 'messages' WHEN 'concepts' THEN 'concepts' ELSE NULL END`;
|
|
520
530
|
const usage = `json_object(${TOKEN_FIELDS.map(key => `'${key}', json_extract(record, '$.output.usage.${key}')`).join(', ')},
|
|
521
531
|
'accountingVersion', json_extract(record, '$.output.usage.accountingVersion'),
|
|
522
532
|
'accountingIncomplete', json(CASE json_extract(record, '$.output.usage.accountingIncomplete') WHEN 1 THEN 'true' ELSE 'false' END),
|
|
523
533
|
'cacheTokensAreIncludedInInput', json(CASE json_extract(record, '$.output.usage.cacheTokensAreIncludedInInput') WHEN 1 THEN 'true' WHEN 0 THEN 'false' ELSE 'null' END))`;
|
|
524
534
|
const metadata = `${project(fields).slice(0, -1)}, 'capabilityId', COALESCE(json_extract(record, '$.capability.id'), json_extract(record, '$.capabilityId')),
|
|
525
|
-
'usage', ${usage})`;
|
|
535
|
+
${contextMetadata}, 'recallKind', ${recallKind}, 'usage', ${usage})`;
|
|
526
536
|
const items = episodes.slice(0, limit).map(episode => {
|
|
527
537
|
const events = this.sql(`SELECT ${metadata} AS metadata FROM traces WHERE ${SCOPE}
|
|
528
538
|
AND json_extract(record, '$.episodeId') = ? AND json_extract(record, '$.kind') IN
|
|
@@ -4,6 +4,18 @@ export const TOKEN_FIELDS = Object.freeze(['inputTokens', 'outputTokens', 'reaso
|
|
|
4
4
|
const numeric = value => Number.isSafeInteger(value) && value >= 0;
|
|
5
5
|
const token = value => numeric(value) ? value : null;
|
|
6
6
|
const string = value => typeof value === 'string' ? value : null;
|
|
7
|
+
const count = (value, max) => numeric(value) && value <= max ? value : null;
|
|
8
|
+
|
|
9
|
+
/** Bounded scalar counts only; missing legacy statistics remain unknown. */
|
|
10
|
+
function contextSources(event) {
|
|
11
|
+
const recentMessages = count(event.recentMessages, 12), recentConcepts = count(event.recentConcepts, 24);
|
|
12
|
+
const omittedMessages = count(event.omittedMessages, 12), omittedConcepts = count(event.omittedConcepts, 24);
|
|
13
|
+
const recallCount = count(event.recallCount, 5);
|
|
14
|
+
const kind = ['messages', 'concepts'].includes(event.recallKind) ? event.recallKind : null;
|
|
15
|
+
if ([recentMessages, recentConcepts, omittedMessages, omittedConcepts, recallCount].every(value => value === null) && kind === null) return null;
|
|
16
|
+
return { recentMessages, recentConcepts, omittedMessages, omittedConcepts,
|
|
17
|
+
recall: recallCount === null && kind === null ? null : { kind, count: recallCount } };
|
|
18
|
+
}
|
|
7
19
|
|
|
8
20
|
/** Turn cursor is an exclusive numeric episode watermark, never a trace offset. */
|
|
9
21
|
export function turnsPage(payload = {}) {
|
|
@@ -62,7 +74,9 @@ export function turnView(episode, events) {
|
|
|
62
74
|
// It does not expose the response model: do not label configured intent as observed reality.
|
|
63
75
|
dispatched: { model: string(event.requestedModel) }, effective: { model: null, effort: null },
|
|
64
76
|
selectionOrigin: string(event.selectionOrigin), reason: string(event.reason), createdAt: event.createdAt, endedAt: null, code: null,
|
|
65
|
-
contextBytes: token(event.contextBytes), contextBudgetBytes: token(event.contextBudgetBytes),
|
|
77
|
+
contextBytes: token(event.contextBytes), contextBudgetBytes: token(event.contextBudgetBytes), contextWindowTokens: token(event.contextWindowTokens),
|
|
78
|
+
outputTokensReserved: token(event.outputTokensReserved), imageTokensReserved: token(event.imageTokensReserved), envelopeTokensReserved: token(event.envelopeTokensReserved),
|
|
79
|
+
contextSources: contextSources(event),
|
|
66
80
|
usage: callUsage(null, false) });
|
|
67
81
|
continue;
|
|
68
82
|
}
|