@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yeaft/webchat-agent",
3
- "version": "1.0.616",
3
+ "version": "1.0.617",
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",
@@ -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, contextBytes: 65536, calls: 16, leaseMs: 15000 });
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.";
@@ -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
- const contextCap = Math.min(LIMITS.contextBytes, model.contextWindow - model.maxOutput - 1024 - imageTokensReserved);
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, outputTokensReserved: model.maxOutput,
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), outputTokensReserved: token(event.outputTokensReserved),
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
  }