@yeaft/webchat-agent 0.1.926 → 0.1.928

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": "0.1.926",
3
+ "version": "0.1.928",
4
4
  "description": "Remote agent for Yeaft WebChat — connects worker machines to the central server",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -193,7 +193,21 @@ export class AdapterRouter extends LLMAdapter {
193
193
  */
194
194
  constructor({ providers }) {
195
195
  super();
196
- this.#providers = providers;
196
+ this.#providers = [];
197
+ this.#modelToProvider = new Map();
198
+ this.#adapterCache = new Map();
199
+ this.refreshProviders(providers);
200
+ }
201
+
202
+ /**
203
+ * Replace the provider/model index after config.json changes. Existing
204
+ * provider adapters are dropped because credentials, baseUrl, protocol,
205
+ * or model ownership may have changed along with the list.
206
+ *
207
+ * @param {object[]} providers
208
+ */
209
+ refreshProviders(providers) {
210
+ this.#providers = Array.isArray(providers) ? providers : [];
197
211
  this.#modelToProvider = new Map();
198
212
  this.#adapterCache = new Map();
199
213
 
@@ -201,7 +215,7 @@ export class AdapterRouter extends LLMAdapter {
201
215
  // model id appears in multiple providers. Each model entry may declare
202
216
  // its own `protocol`; we keep the normalized entry so #effectiveProtocol
203
217
  // can consult it later without re-parsing.
204
- for (const provider of providers) {
218
+ for (const provider of this.#providers) {
205
219
  if (!Array.isArray(provider.models)) continue;
206
220
  for (const raw of provider.models) {
207
221
  const entry = normalizeModelEntry(raw);
@@ -179,7 +179,8 @@ export default defineTool({
179
179
  description: `Create a sub-agent to work on an independent task in parallel.
180
180
 
181
181
  Sub-agents run in their own context and can be given a concrete mission
182
- with an expected_output schema and a budget (max_tokens/max_turns/wall_time_ms).
182
+ with an optional expected_output schema. Optional budget limits
183
+ (max_tokens/max_turns/wall_time_ms) act as safety cutoffs only when supplied.
183
184
  Pick a preset persona to pre-wire a tool subset and model tier:
184
185
  - explorer : fast, read-only scout (Read/Grep/Glob/ListDir)
185
186
  - implementer: builder with full work tools (primary model)
@@ -188,8 +189,8 @@ Pick a preset persona to pre-wire a tool subset and model tier:
188
189
 
189
190
  Guidelines:
190
191
  - Give a clear, focused mission — what "done" looks like
191
- - Use expected_output to pin the structure you want back
192
- - Always set a budget for unbounded missions
192
+ - Use expected_output when the return shape matters
193
+ - Add a budget only when you need an explicit safety cutoff
193
194
  - Use PromptAgent to communicate, WaitAgent to collect results, CloseAgent to finalize`,
194
195
  parameters: {
195
196
  type: 'object',
@@ -218,11 +219,11 @@ Guidelines:
218
219
  budget: {
219
220
  type: 'object',
220
221
  properties: {
221
- max_tokens: { type: 'number' },
222
- max_turns: { type: 'number' },
223
- wall_time_ms: { type: 'number' },
222
+ max_tokens: { type: 'number', description: 'Optional token ceiling; no default limit is applied' },
223
+ max_turns: { type: 'number', description: 'Optional turn ceiling; no default limit is applied' },
224
+ wall_time_ms: { type: 'number', description: 'Optional elapsed-time ceiling in milliseconds; no default limit is applied' },
224
225
  },
225
- description: 'Budget limits; exceeding any returns { status: "budget_exceeded", partial_output, reason }',
226
+ description: 'Optional safety limits; no max_tokens/max_turns/wall_time_ms defaults are applied. Exceeding an explicit limit returns { status: "budget_exceeded", partial_output, reason }',
226
227
  },
227
228
  cwd: {
228
229
  type: 'string',
@@ -10,7 +10,9 @@ export default defineTool({
10
10
  description: `Wait for a sub-agent to complete its task and retrieve the result.
11
11
 
12
12
  Returns the agent's final result or current status if still running.
13
- Use after sending a task to an agent via PromptAgent.`,
13
+ Use after sending a task to an agent via PromptAgent.
14
+
15
+ The default wait is 30000ms. Callers may request up to 300000ms (5 minutes).`,
14
16
  parameters: {
15
17
  type: 'object',
16
18
  properties: {
@@ -20,16 +22,22 @@ Use after sending a task to an agent via PromptAgent.`,
20
22
  },
21
23
  timeout_ms: {
22
24
  type: 'number',
23
- description: 'Maximum time to wait in milliseconds (default: 30000)',
25
+ minimum: 0,
26
+ maximum: 300000,
27
+ description: 'Maximum time to wait in milliseconds (default: 30000, max: 300000 / 5 minutes)',
24
28
  },
25
29
  },
26
30
  required: ['agent_id'],
27
31
  },
32
+ timeoutMs: 305000,
28
33
  isConcurrencySafe: () => true,
29
34
  isReadOnly: () => true,
30
35
  async execute(input, ctx) {
31
36
  const { agent_id, timeout_ms = 30000 } = input;
32
37
  if (!agent_id) return JSON.stringify({ error: 'agent_id is required' });
38
+ if (typeof timeout_ms !== 'number' || !Number.isFinite(timeout_ms) || timeout_ms < 0 || timeout_ms > 300000) {
39
+ return JSON.stringify({ error: 'timeout_ms must be a number between 0 and 300000' });
40
+ }
33
41
 
34
42
  const agents = getAgentRegistry();
35
43
  const agent = agents.get(agent_id);
@@ -23,6 +23,7 @@ import { existsSync } from 'node:fs';
23
23
  import { randomUUID } from 'node:crypto';
24
24
  import { Engine } from './engine.js';
25
25
  import { loadSession } from './session.js';
26
+ import { loadConfig } from './config.js';
26
27
  import { sendToServer } from '../connection/buffer.js';
27
28
  import ctx from '../context.js';
28
29
  import { hydrateYeaftStatusFromSession } from './status-cache.js';
@@ -69,6 +70,26 @@ let session = null;
69
70
 
70
71
  let threadClassifier = defaultClassifyThread;
71
72
 
73
+ function refreshLiveSessionConfig() {
74
+ if (!session) return;
75
+ try {
76
+ const freshConfig = loadConfig({ dir: session.yeaftDir || ctx.CONFIG?.yeaftDir });
77
+ const freshModels = Array.isArray(freshConfig.availableModels) ? freshConfig.availableModels : [];
78
+ session.config.availableModels = freshModels;
79
+ if (freshConfig.model && !freshModels.some(m => m?.id === session.config.model)) {
80
+ session.config.model = freshConfig.model;
81
+ }
82
+ if (freshConfig.providers) {
83
+ session.config.providers = freshConfig.providers;
84
+ if (typeof session.adapter?.refreshProviders === 'function') {
85
+ session.adapter.refreshProviders(freshConfig.providers);
86
+ }
87
+ }
88
+ } catch (err) {
89
+ console.warn('[Yeaft] refresh live session config failed:', err?.message || err);
90
+ }
91
+ }
92
+
72
93
  /** Test-only: replace the lightweight VP thread classifier. */
73
94
  export function __testSetThreadClassifier(fn) {
74
95
  threadClassifier = typeof fn === 'function' ? fn : defaultClassifyThread;
@@ -3728,6 +3749,7 @@ export function handleYeaftModeSwitch(_msg) {
3728
3749
  /** Handle model switch from the web UI. */
3729
3750
  export function handleYeaftModelSwitch(msg) {
3730
3751
  if (!session || !msg.model) return;
3752
+ refreshLiveSessionConfig();
3731
3753
 
3732
3754
  const available = session.config.availableModels || [];
3733
3755
  const found = available.some(m => m.id === msg.model);
@@ -3774,6 +3796,7 @@ export async function handleYeaftLoadHistory(msg) {
3774
3796
  installYeaftRuntimeBridge(session);
3775
3797
 
3776
3798
  yeaftConversationId = `yeaft-${Date.now()}`;
3799
+ refreshLiveSessionConfig();
3777
3800
  hydrateYeaftStatusFromSession(session, { reason: 'history_load', emitEvent: true });
3778
3801
 
3779
3802
  // Per-group history hydrates lazily via getOrCreateSessionHistory.
@@ -3782,7 +3805,12 @@ export async function handleYeaftLoadHistory(msg) {
3782
3805
  // it doesn't (legacy callers), do nothing — the per-group lazy
3783
3806
  // hydration handles it.
3784
3807
  if (sessionId) setGroupHistory(sessionId, hydrateGroupHistory(sessionId));
3785
- } else if (sessionId) {
3808
+ } else {
3809
+ refreshLiveSessionConfig();
3810
+ hydrateYeaftStatusFromSession(session, { reason: 'history_load', emitEvent: true });
3811
+ }
3812
+
3813
+ if (sessionId) {
3786
3814
  // Re-entering an existing session with a (possibly new) group filter:
3787
3815
  // re-seed THIS group's history from disk so it doesn't carry stale
3788
3816
  // in-memory state into the next turn's context.