@yeaft/webchat-agent 1.0.513 → 1.0.514

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.
@@ -0,0 +1,79 @@
1
+ /** Parent-owned, in-place child controls; never resumes a terminal or reporting child. */
2
+ import { defineTool } from './types.js';
3
+ import { getAgentRegistry, agentBelongsToCaller } from './agent.js';
4
+ import { isTerminalAgentStatus } from '../sub-agent/status.js';
5
+ import { validateBudget } from '../sub-agent/execution-control.js';
6
+ import { validateToolGrants } from '../sub-agent/tool-access.js';
7
+ import { diagnoseAgentLiveness } from '../sub-agent/liveness.js';
8
+
9
+ export default defineTool({
10
+ name: 'UpdateAgent',
11
+ description: {
12
+ en: 'Adjust a live child in place after inspecting its progress: absolute lifetime time/tool/LLM budgets and explicit extra tool grants. Does not queue a prompt, reset usage, or revive a terminal/reporting child. Give evidence and the remaining task in reason; do not extend stalled or repeating work blindly. Bash permits arbitrary shell and writes, not a read-only sandbox; grant only necessary parent tools and isolate writable workspaces. Already dispatched work is not undone by revocation.',
13
+ zh: '检查进展后原地调整活跃子 Agent:累计时间/工具/LLM 上限及额外工具授权。不排队提示、不清零用量、不复活终止或收尾中的任务。reason 说明已有证据和剩余工作,勿盲目给停滞/重复任务扩额。Bash 可执行任意 Shell 和写入,并非只读沙箱;只授予必要的父级工具,写任务隔离 workspace。撤销不撤回已执行操作。',
14
+ },
15
+ parameters: {
16
+ type: 'object',
17
+ properties: {
18
+ agent_id: { type: 'string' },
19
+ reason: { type: 'string', description: { en: 'Observed progress, remaining work and why this adjustment is necessary', zh: '实际进展、剩余工作及调整原因' } },
20
+ budget: {
21
+ type: 'object',
22
+ properties: {
23
+ max_tokens: { type: 'number', minimum: 1 },
24
+ max_turns: { type: 'integer', minimum: 1 },
25
+ max_tool_calls: { type: 'integer', minimum: 1 },
26
+ max_llm_calls: { type: 'integer', minimum: 1 },
27
+ wall_time_ms: { type: 'integer', minimum: 1 },
28
+ },
29
+ description: { en: 'Partial absolute lifetime ceilings, not added quota. Unspecified limits stay unchanged. LLM cap excludes one reserved reporting request; time is measured from original spawn.', zh: '部分累计上限,不是增加的额度;未指定项不变。LLM 上限之外保留一次报告请求;时间从最初启动计算。' },
30
+ },
31
+ allow_tools: {
32
+ type: 'array', items: { type: 'string' }, maxItems: 32,
33
+ description: { en: 'Replace extra persona grants with these canonical parent tool names (e.g. Bash, FileEdit); [] revokes extras, omission leaves grants unchanged', zh: '替换 persona 额外授权(如 Bash、FileEdit);[] 撤销额外授权,省略则不变' },
34
+ },
35
+ },
36
+ required: ['agent_id', 'reason'],
37
+ },
38
+ isReadOnly: () => false,
39
+ isConcurrencySafe: () => false,
40
+ mayMutateWorkspaceAfterReturn: () => true,
41
+ async execute(input, ctx) {
42
+ const fail = error => JSON.stringify({ error, next_steps: 'Inspect the current agent state and correct the request; do not respawn or repeat blindly.' });
43
+ const agent = getAgentRegistry().get(input.agent_id);
44
+ if (!agent || !agentBelongsToCaller(agent, ctx)) return fail(`Agent not found: ${input.agent_id}`);
45
+ if (isTerminalAgentStatus(agent.status) || agent.budgetReportStarted || agent.budgetStopReason
46
+ || agent.abortController?.signal.aborted) return fail('Agent is terminal, stopping or already reporting; it cannot be extended');
47
+ if (typeof input.reason !== 'string' || !input.reason.trim() || input.reason.length > 2000) return fail('reason must contain 1..2000 characters of evidence and remaining work');
48
+ if (input.budget === undefined && input.allow_tools === undefined) return fail('budget or allow_tools is required');
49
+ if (input.budget !== undefined) {
50
+ const error = validateBudget(input.budget);
51
+ if (error) return fail(error);
52
+ }
53
+ const parentRegistry = ctx?.parentEngineDeps?.parentToolRegistry;
54
+ const grants = input.allow_tools === undefined ? null : validateToolGrants(input.allow_tools, parentRegistry);
55
+ if (grants && !grants.ok) return fail(grants.error);
56
+ const previousBudget = { ...agent.budget };
57
+ const previousTools = [...(agent.allowTools || [])];
58
+ // All validation precedes mutation. Original usage/deadline origin are retained.
59
+ agent.budget = { ...agent.budget, ...input.budget };
60
+ if (grants) agent.allowTools = grants.tools;
61
+ agent.controlRevision = (agent.controlRevision || 0) + 1;
62
+ if (agent.execution && agent.execution.toolCalls < agent.budget.max_tool_calls * 0.75) agent.execution.warning = null;
63
+ if (!agent.budgetReportStarted) {
64
+ agent.toolBudgetReason = null;
65
+ agent.executionBudgetReason = null;
66
+ }
67
+ agent.refreshToolPolicy?.();
68
+ agent.rearmWallTimeWatchdog?.();
69
+ const event = { type: 'sub_agent_control_updated', at: Date.now(), reason: input.reason.trim(),
70
+ previousBudget, budget: { ...agent.budget }, previousTools, allowTools: [...(agent.allowTools || [])] };
71
+ agent.diagnostics ||= [];
72
+ agent.diagnostics.push(event);
73
+ if (agent.diagnostics.length > 100) agent.diagnostics.shift();
74
+ try { agent.outputLog?.write(event); } catch { /* diagnostics must not fail the applied update */ }
75
+ return JSON.stringify({ success: true, agentId: agent.id, status: agent.status,
76
+ budget: agent.budget, allow_tools: agent.allowTools || [], liveness: diagnoseAgentLiveness(agent),
77
+ next_steps: 'Adjustment applied without restarting work. Continue the parent task; use PromptAgent only if new guidance is needed, then collect its reply. Revocation does not cancel already dispatched work.' });
78
+ },
79
+ });
@@ -86,6 +86,7 @@ import {
86
86
  import {
87
87
  SessionCrudError,
88
88
  createSessionFromSpec,
89
+ copySession,
89
90
  renameSession,
90
91
  updateSessionAnnouncement,
91
92
  archiveSession,
@@ -3699,6 +3700,28 @@ export function handleYeaftCreateSession(msg) {
3699
3700
  }
3700
3701
  }
3701
3702
 
3703
+ export function handleYeaftCopySession(msg) {
3704
+ const requestId = msg && msg.requestId;
3705
+ const sessionId = msg && (msg.sessionId || msg.groupId);
3706
+ try {
3707
+ const yeaftDir = ctx.CONFIG?.yeaftDir;
3708
+ const source = decorateSessionsWithRuntimeState(snapshotSessions(yeaftDir))
3709
+ .find(session => session?.id === sessionId);
3710
+ if (!source) throw new SessionCrudError('not_found', sessionId);
3711
+ if (source.running) throw new SessionCrudError('session_running', sessionId, 'Cannot copy a running Session');
3712
+ const session = copySession(yeaftDir, sessionId, {
3713
+ ...configuredVpPaths(),
3714
+ name: msg && msg.name,
3715
+ });
3716
+ recordAgentSessionCreated();
3717
+ session.config = loadSessionConfig(yeaftDir, session.id);
3718
+ sendSessionCrudResult({ op: 'copy', requestId, ok: true, session });
3719
+ sendSessionSnapshotBroadcast();
3720
+ } catch (err) {
3721
+ sendSessionCrudResult({ op: 'copy', requestId, ok: false, error: sessionErrorPayload(err) });
3722
+ }
3723
+ }
3724
+
3702
3725
  /**
3703
3726
  * `yeaft_scan_workdir_sessions` — compatibility endpoint for the retired
3704
3727
  * workdir Session scan flow. Session data now lives under the user-level