agentworkshop 0.5.5 → 0.5.7
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/.AgentWorkShop/prompts/compaction-hints.md +9 -0
- package/.AgentWorkShop/prompts/host-tools.json +695 -630
- package/.AgentWorkShop/prompts/peer-reply-cross-channel.md +10 -0
- package/.AgentWorkShop/prompts/system-manual.md +6 -1
- package/.output/.AgentWorkShop/prompts/compaction-hints.md +9 -0
- package/.output/.AgentWorkShop/prompts/host-tools.json +695 -630
- package/.output/.AgentWorkShop/prompts/peer-reply-cross-channel.md +10 -0
- package/.output/.AgentWorkShop/prompts/system-manual.md +6 -1
- package/.output/nitro.json +1 -1
- package/.output/public/_nuxt/builds/latest.json +1 -1
- package/.output/public/_nuxt/builds/meta/02278199-eeea-4f96-882a-94c5408c6cb6.json +1 -0
- package/.output/server/chunks/_/nitro.mjs +2188 -1324
- package/.output/server/chunks/_/nitro.mjs.map +1 -1
- package/.output/server/chunks/routes/api/workshop/agent-tools/invoke.post.mjs +5 -2
- package/.output/server/chunks/routes/api/workshop/agent-tools/invoke.post.mjs.map +1 -1
- package/.output/server/chunks/routes/api/workshop/channels/_id/index3.post.mjs +25 -3
- package/.output/server/chunks/routes/api/workshop/channels/_id/index3.post.mjs.map +1 -1
- package/.output/server/package.json +1 -1
- package/package.json +1 -1
- package/scripts/_dbg-cross-channel-e2e.mjs +157 -0
- package/scripts/_dbg-cross-team-visibility-e2e.mjs +129 -0
- package/scripts/test-context-governor.ts +176 -0
- package/scripts/test-memory-brief-chronicle.ts +180 -0
- package/scripts/test-memory-dynamic.ts +3 -1
- package/scripts/test-memory-rrf-mmr.ts +114 -0
- package/server/api/workshop/agent-tools/invoke.post.ts +8 -1
- package/server/api/workshop/channels/[id]/messages/index.post.ts +26 -1
- package/server/api/workshop/ws.ts +1 -0
- package/server/data/daqs.json +35 -13
- package/server/data/dcw-lines.json +14 -0
- package/server/data/dcw-products.json +14 -0
- package/server/data/dcw-recipes.json +28 -0
- package/server/data/dcw-rollback.json +122 -1
- package/server/data/dcw-runs.json +54 -36
- package/server/data/dcw-writes.json +60 -0
- package/server/data/dcws.json +42 -0
- package/server/data/line-runs.json +32 -22
- package/server/services/workshop/agents/adapters/omp-rpc-client.ts +65 -0
- package/server/services/workshop/agents/agent-interface.ts +44 -1
- package/server/services/workshop/agents/mock-agent.ts +12 -0
- package/server/services/workshop/agents/omp-agent.ts +304 -8
- package/server/services/workshop/db/database.ts +16 -1
- package/server/services/workshop/db/memory.repo.ts +40 -2
- package/server/services/workshop/prompts/loader.ts +6 -5
- package/server/services/workshop/runtime/agent-runtime.ts +52 -6
- package/server/services/workshop/runtime/channel-runtime.ts +14 -6
- package/server/services/workshop/runtime/manager.ts +314 -3
- package/server/services/workshop/runtime/memory.ts +313 -78
- package/server/services/workshop/types/task.ts +14 -0
- package/.output/public/_nuxt/builds/meta/f2f649c5-fd3b-4507-a3a1-8b4a194de72d.json +0 -1
|
@@ -28,10 +28,11 @@ import type {
|
|
|
28
28
|
SupervisionSnapshot,
|
|
29
29
|
} from './agent-interface'
|
|
30
30
|
import type { A2AArtifact, A2AMessage, Part } from '../types/a2a'
|
|
31
|
-
import type { WorkspaceTask } from '../types/task'
|
|
31
|
+
import type { AgentContextStats, WorkspaceTask } from '../types/task'
|
|
32
32
|
import {
|
|
33
33
|
OmpRpcClient,
|
|
34
34
|
type AgentSessionEvent,
|
|
35
|
+
type CompactionResult,
|
|
35
36
|
type RpcHostToolDefinition,
|
|
36
37
|
type HostToolCallRequest,
|
|
37
38
|
} from './adapters/omp-rpc-client'
|
|
@@ -52,6 +53,7 @@ import {
|
|
|
52
53
|
import { toolDaqQuery, toolDcwControl, toolDcwJudge, toolDcwJournal, toolDcwRollback, toolMyIndustrialNodes } from './industrial-tools'
|
|
53
54
|
import { buildIndustrialContext, industrialLoopGuide } from './industrial-context'
|
|
54
55
|
import { extractTaskMode } from '../runtime/execution-mode'
|
|
56
|
+
import { envNum } from '../runtime/memory'
|
|
55
57
|
|
|
56
58
|
const log = createLogger('workshop.omp')
|
|
57
59
|
|
|
@@ -217,6 +219,194 @@ export class OmpRpcAgentImpl implements AgentInterface {
|
|
|
217
219
|
*/
|
|
218
220
|
private replyContext: { fromId: string, messageId: string } | null = null
|
|
219
221
|
|
|
222
|
+
// ===== 上下文治理(70% 无中断压缩环)=====
|
|
223
|
+
/** 压缩进行中(平台 gate 与 omp 原生压缩共用互斥位) */
|
|
224
|
+
private compacting = false
|
|
225
|
+
private lastCompactAt = 0
|
|
226
|
+
/** omp 不支持 compact 命令(旧版)→ 永久停用平台压缩;原生 auto-compaction 兜底,harvest 照常 */
|
|
227
|
+
private compactLegacy = false
|
|
228
|
+
/** 模型上下文窗口(get_state/get_session_stats 探测;null = 未知,percent 不可算) */
|
|
229
|
+
private contextWindow: number | null = null
|
|
230
|
+
private sessionId: string | null = null
|
|
231
|
+
/** harvest 双路去重(compact 响应与 compaction_end 事件可能双达) */
|
|
232
|
+
private lastHarvestKey = ''
|
|
233
|
+
private lastHarvestAt = 0
|
|
234
|
+
|
|
235
|
+
/** 总开关(AW_OMP_COMPACT_ENABLED=0 显式关闭;envNum 拒 0,故单独解析) */
|
|
236
|
+
private compactEnabled(): boolean {
|
|
237
|
+
const v = Number(process.env.AW_OMP_COMPACT_ENABLED)
|
|
238
|
+
return !(Number.isFinite(v) && v === 0)
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
private compactThreshold(): number {
|
|
242
|
+
return Math.min(1, envNum('AW_OMP_COMPACT_THRESHOLD', 0.7))
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
private compactMinIntervalMs(): number {
|
|
246
|
+
return envNum('AW_OMP_COMPACT_MIN_INTERVAL_MS', 300_000)
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
private compactWaitMs(): number {
|
|
250
|
+
return envNum('AW_OMP_COMPACT_WAIT_MS', 120_000)
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/** 被动上下文用量快照(无探测 RPC;getStatus 透出用) */
|
|
254
|
+
getContextStats(): AgentContextStats | null {
|
|
255
|
+
const usage = this.client?.getContextUsage() ?? null
|
|
256
|
+
const window = this.contextWindow ?? usage?.contextWindow ?? null
|
|
257
|
+
if (!usage && window === null) return null
|
|
258
|
+
const usedTokens = usage?.tokens ?? 0
|
|
259
|
+
return {
|
|
260
|
+
usedTokens,
|
|
261
|
+
contextWindow: window,
|
|
262
|
+
percent: window && window > 0 ? Math.min(1, usedTokens / window) : null,
|
|
263
|
+
compacting: this.compacting,
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* 上下文用量探测:get_session_stats 权威值;不可用退化为被动 usage + 已探测窗口。
|
|
269
|
+
* percent 归一化到 0-1(omp 可能返回 0-100)。
|
|
270
|
+
*/
|
|
271
|
+
private async probeUsage(client: OmpRpcClient): Promise<{ tokens: number, contextWindow: number | null, percent: number | null } | null> {
|
|
272
|
+
try {
|
|
273
|
+
const resp = await client.send({ type: 'get_session_stats' })
|
|
274
|
+
const cu = ((resp.data ?? {}) as Record<string, unknown>).contextUsage as
|
|
275
|
+
| { tokens?: number, contextWindow?: number, percent?: number }
|
|
276
|
+
| undefined
|
|
277
|
+
if (cu && typeof cu.tokens === 'number' && cu.tokens > 0) {
|
|
278
|
+
if (typeof cu.contextWindow === 'number' && cu.contextWindow > 0) {
|
|
279
|
+
this.contextWindow = cu.contextWindow
|
|
280
|
+
client.setContextWindow(cu.contextWindow)
|
|
281
|
+
}
|
|
282
|
+
const window = this.contextWindow ?? cu.contextWindow ?? null
|
|
283
|
+
const rawPercent = typeof cu.percent === 'number' ? (cu.percent > 1 ? cu.percent / 100 : cu.percent) : null
|
|
284
|
+
const percent = rawPercent ?? (window && window > 0 ? Math.min(1, cu.tokens / window) : null)
|
|
285
|
+
return { tokens: cu.tokens, contextWindow: window, percent }
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
catch { /* get_session_stats 不可用 → 被动跟踪 */ }
|
|
289
|
+
return client.getContextUsage()
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* 上下文治理门控:三条 prompt 路径(worker/peer/supervise)在回合间隙统一调用;
|
|
294
|
+
* post-settle 经 onTurnSettled 复用同一守卫。任何失败路径都放行——压缩晚一轮
|
|
295
|
+
* 永远好过中断作业。三重防线:仅回合间隙发起 → get_state 复查 isStreaming/
|
|
296
|
+
* isCompacting → compacting 互斥位 + 最小间隔防振荡。
|
|
297
|
+
*/
|
|
298
|
+
private async contextGate(reason: string): Promise<void> {
|
|
299
|
+
if (!this.compactEnabled() || this.compactLegacy || this.compacting) return
|
|
300
|
+
const client = this.client
|
|
301
|
+
if (!client || !client.alive) return
|
|
302
|
+
try {
|
|
303
|
+
const usage = await this.probeUsage(client)
|
|
304
|
+
if (!usage || usage.percent === null || usage.percent < this.compactThreshold()) return
|
|
305
|
+
if (Date.now() - this.lastCompactAt < this.compactMinIntervalMs()) return
|
|
306
|
+
// 回合间隙硬校验:流式中/压缩中绝不发起(不中断执行的硬约束)
|
|
307
|
+
const st = await client.send({ type: 'get_state' })
|
|
308
|
+
const s = (st.data ?? {}) as Record<string, unknown>
|
|
309
|
+
if (s.isStreaming === true || s.isCompacting === true) return
|
|
310
|
+
if (typeof s.sessionId === 'string' && s.sessionId) this.sessionId = s.sessionId
|
|
311
|
+
this.compacting = true
|
|
312
|
+
this.lastCompactAt = Date.now()
|
|
313
|
+
try {
|
|
314
|
+
const result = await this.runCompaction(client)
|
|
315
|
+
if (result?.summary) {
|
|
316
|
+
await this.harvestCompaction(
|
|
317
|
+
result.summary,
|
|
318
|
+
result.tokensBefore,
|
|
319
|
+
result.tokensAfter ?? result.estimatedTokensAfter,
|
|
320
|
+
reason,
|
|
321
|
+
)
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
finally {
|
|
325
|
+
this.compacting = false
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
catch (err) {
|
|
329
|
+
const msg = err instanceof Error ? err.message : String(err)
|
|
330
|
+
if (/compact/i.test(msg) && /unknown|unsupported|invalid|无效|未知|不支持/i.test(msg)) this.compactLegacy = true
|
|
331
|
+
log.warn(`[OmpRpcAgent:${this.selfAgentId}] contextGate(${reason}) 失败,放行: ${msg}`)
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* 发起 compact 并等待 compaction_end(响应内含 result 则立即收口;
|
|
337
|
+
* 超时放行——压缩在 harness 内后台继续,isCompacting 防后续双发)。
|
|
338
|
+
*/
|
|
339
|
+
private runCompaction(client: OmpRpcClient): Promise<CompactionResult | null> {
|
|
340
|
+
return new Promise<CompactionResult | null>((resolve) => {
|
|
341
|
+
let settled = false
|
|
342
|
+
const finish = (r: CompactionResult | null): void => {
|
|
343
|
+
if (settled) return
|
|
344
|
+
settled = true
|
|
345
|
+
off()
|
|
346
|
+
clearTimeout(timer)
|
|
347
|
+
resolve(r)
|
|
348
|
+
}
|
|
349
|
+
const off = client.onEvent((event) => {
|
|
350
|
+
if (event.type === 'compaction_end') {
|
|
351
|
+
finish((event as { result?: CompactionResult }).result ?? null)
|
|
352
|
+
}
|
|
353
|
+
else if (event.type === '__process_exit__') {
|
|
354
|
+
finish(null)
|
|
355
|
+
}
|
|
356
|
+
})
|
|
357
|
+
const timer = setTimeout(() => {
|
|
358
|
+
log.warn(`[OmpRpcAgent:${this.selfAgentId}] compact 等待超时(${this.compactWaitMs()}ms)→ 放行(压缩后台继续)`)
|
|
359
|
+
finish(null)
|
|
360
|
+
}, this.compactWaitMs())
|
|
361
|
+
void client.send({ type: 'compact', customInstructions: this.compactionHints() })
|
|
362
|
+
.then((resp) => {
|
|
363
|
+
const result = (resp.data as { result?: CompactionResult } | undefined)?.result
|
|
364
|
+
if (result?.summary) finish(result)
|
|
365
|
+
// 响应无 result:等 compaction_end 事件(协议权威语义)
|
|
366
|
+
})
|
|
367
|
+
.catch((err) => {
|
|
368
|
+
const msg = err instanceof Error ? err.message : String(err)
|
|
369
|
+
if (/compact/i.test(msg)) this.compactLegacy = true
|
|
370
|
+
log.warn(`[OmpRpcAgent:${this.selfAgentId}] compact 发起失败: ${msg}`)
|
|
371
|
+
finish(null)
|
|
372
|
+
})
|
|
373
|
+
})
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/** 压缩摘要指令(外置 prompts;缺失时省略,omp 用默认策略) */
|
|
377
|
+
private compactionHints(): string | undefined {
|
|
378
|
+
try {
|
|
379
|
+
return renderPrompt('compaction-hints')
|
|
380
|
+
}
|
|
381
|
+
catch {
|
|
382
|
+
return undefined
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* 压缩摘要 harvest(三路统一:平台主动 compact / omp 阈值自动 / overflow):
|
|
388
|
+
* 经 workspace.recordSessionMemory 落库为本人 episodic-session 记忆。
|
|
389
|
+
* 双路去重:同摘要 60s 内只入库一次。
|
|
390
|
+
*/
|
|
391
|
+
private async harvestCompaction(summary: string, tokensBefore?: number, tokensAfter?: number, reason?: string): Promise<void> {
|
|
392
|
+
const key = summary.slice(0, 120)
|
|
393
|
+
if (key === this.lastHarvestKey && Date.now() - this.lastHarvestAt < 60_000) return
|
|
394
|
+
this.lastHarvestKey = key
|
|
395
|
+
this.lastHarvestAt = Date.now()
|
|
396
|
+
try {
|
|
397
|
+
await this.workspace?.recordSessionMemory?.({ summary, tokensBefore, tokensAfter, reason })
|
|
398
|
+
log.info(`[OmpRpcAgent:${this.selfAgentId}] 压缩摘要已入记忆(${summary.length} 字,reason=${reason ?? 'auto'})`)
|
|
399
|
+
}
|
|
400
|
+
catch (err) {
|
|
401
|
+
log.warn(`[OmpRpcAgent:${this.selfAgentId}] 压缩摘要入库失败(不影响会话):`, err instanceof Error ? err.message : err)
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/** 回合落定钩子(AgentRuntime 在信箱空时调用):后台压缩检查,自守卫不抛错 */
|
|
406
|
+
async onTurnSettled(): Promise<void> {
|
|
407
|
+
await this.contextGate('post-settle')
|
|
408
|
+
}
|
|
409
|
+
|
|
220
410
|
constructor(config: Record<string, unknown> = {}) {
|
|
221
411
|
this.config = config as OmpAgentConfig
|
|
222
412
|
// factory 注入的 agent 身份(无需等待 init())
|
|
@@ -449,6 +639,9 @@ export class OmpRpcAgentImpl implements AgentInterface {
|
|
|
449
639
|
if (!this.client) return []
|
|
450
640
|
if (this.supervising) return [] // 上一轮 supervise 未收口:跳过本拍(节流即正确)
|
|
451
641
|
|
|
642
|
+
// 上下文门控(≥70% 先压缩再调度;回合间隙发起,失败放行)
|
|
643
|
+
await this.contextGate('supervise')
|
|
644
|
+
|
|
452
645
|
const prompt = await this.buildSupervisePrompt(snapshot, ctx.memory)
|
|
453
646
|
// 150s 上界:supervise 持 lead.execLock 期间信箱消费停顿(更久会拖垮 worker 回执处理);
|
|
454
647
|
// supervise 是一次真实 LLM 回合:omp 冷启动(插件/MCP 加载 30~90s)+ 慢 provider
|
|
@@ -589,9 +782,14 @@ export class OmpRpcAgentImpl implements AgentInterface {
|
|
|
589
782
|
const msgText = partsToText(msg.parts)
|
|
590
783
|
const requireReply = msg.metadata?.['x-aw-require-reply'] === 'true'
|
|
591
784
|
const isReply = typeof msg.metadata?.['x-aw-in-reply-to'] === 'string'
|
|
785
|
+
// 跨 Channel 来信:发送方是其他 channel 的 lead,回执必须走 send_cross_channel_message
|
|
786
|
+
const crossChannel = msg.metadata?.['x-aw-cross-channel'] === 'true'
|
|
787
|
+
const fromChannel = typeof msg.metadata?.['x-aw-from-channel'] === 'string'
|
|
788
|
+
? String(msg.metadata['x-aw-from-channel'])
|
|
789
|
+
: ''
|
|
592
790
|
// 回执自动关联仅对真实 agent 发送者生效(人类无 agentId 可回投;
|
|
593
791
|
// 对人类的回复经事件流/时间线可见)
|
|
594
|
-
this.replyContext = requireReply && request.fromAgentId
|
|
792
|
+
this.replyContext = requireReply && request.fromAgentId && !crossChannel
|
|
595
793
|
? { fromId: request.fromAgentId, messageId: msg.messageId }
|
|
596
794
|
: null
|
|
597
795
|
|
|
@@ -599,11 +797,13 @@ export class OmpRpcAgentImpl implements AgentInterface {
|
|
|
599
797
|
? `You are "${this.agentName}", the LEAD coordinator of a multi-agent team (Channel: ${this.channelId}). A team member sent you a direct message.`
|
|
600
798
|
: `You are "${this.agentName}", a worker agent in a multi-agent team (Channel: ${this.channelId}).`
|
|
601
799
|
|
|
602
|
-
//
|
|
603
|
-
const respondBlock =
|
|
604
|
-
fromId,
|
|
605
|
-
|
|
606
|
-
|
|
800
|
+
// 消息应答指令:必复/可选/跨 Channel 三态 + lead 名册提示(全部外置 prompts/)
|
|
801
|
+
const respondBlock = crossChannel
|
|
802
|
+
? renderPrompt('peer-reply-cross-channel', { fromId, messageId: msg.messageId, fromChannel })
|
|
803
|
+
: renderPrompt(requireReply ? 'peer-reply-required' : 'peer-reply-optional', {
|
|
804
|
+
fromId,
|
|
805
|
+
messageId: msg.messageId,
|
|
806
|
+
})
|
|
607
807
|
const peerBody = renderPrompt('peer-message', {
|
|
608
808
|
fromId,
|
|
609
809
|
messageId: msg.messageId,
|
|
@@ -647,6 +847,8 @@ export class OmpRpcAgentImpl implements AgentInterface {
|
|
|
647
847
|
if (!client) {
|
|
648
848
|
throw new Error('omp 客户端未就绪')
|
|
649
849
|
}
|
|
850
|
+
// 上下文门控(≥70% 时在回合间隙先压缩再发 prompt;等待有界,失败放行)
|
|
851
|
+
await this.contextGate('pre-prompt')
|
|
650
852
|
const queue: AgentEvent[] = []
|
|
651
853
|
let resolveWait: (() => void) | null = null
|
|
652
854
|
let isDone = false
|
|
@@ -929,6 +1131,29 @@ export class OmpRpcAgentImpl implements AgentInterface {
|
|
|
929
1131
|
error: { code: 'OMP_PROCESS_EXIT', message: 'omp 子进程意外退出' },
|
|
930
1132
|
}]
|
|
931
1133
|
|
|
1134
|
+
// ===== 上下文治理事件(回合中到达的原生阈值/overflow 压缩;不污染事件流)=====
|
|
1135
|
+
case 'compaction_start':
|
|
1136
|
+
this.compacting = true
|
|
1137
|
+
return []
|
|
1138
|
+
|
|
1139
|
+
case 'compaction_end': {
|
|
1140
|
+
this.compacting = false
|
|
1141
|
+
const result = (event as { result?: CompactionResult }).result
|
|
1142
|
+
if (result?.summary) {
|
|
1143
|
+
void this.harvestCompaction(
|
|
1144
|
+
result.summary,
|
|
1145
|
+
result.tokensBefore,
|
|
1146
|
+
result.tokensAfter ?? result.estimatedTokensAfter,
|
|
1147
|
+
(event as { reason?: string }).reason,
|
|
1148
|
+
)
|
|
1149
|
+
}
|
|
1150
|
+
return []
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
// 回合完全落定(无自动重试/压缩重试/排队续跑):后续压缩检查由 AgentRuntime 驱动
|
|
1154
|
+
case 'agent_settled':
|
|
1155
|
+
return []
|
|
1156
|
+
|
|
932
1157
|
case '__error__':
|
|
933
1158
|
return [{
|
|
934
1159
|
kind: 'error',
|
|
@@ -1176,7 +1401,12 @@ export class OmpRpcAgentImpl implements AgentInterface {
|
|
|
1176
1401
|
// 只配其一时也显式发送,避免 respawn 后静默回落默认模型)
|
|
1177
1402
|
if (this.config.provider || this.config.model) {
|
|
1178
1403
|
try {
|
|
1179
|
-
|
|
1404
|
+
// 未配置一侧保持 undefined(JSON 序列化丢键)→ omp 以当前已设值补全
|
|
1405
|
+
await client.send({
|
|
1406
|
+
type: 'set_model',
|
|
1407
|
+
provider: this.config.provider as string,
|
|
1408
|
+
modelId: this.config.model as string,
|
|
1409
|
+
})
|
|
1180
1410
|
}
|
|
1181
1411
|
catch {
|
|
1182
1412
|
// 模型设置失败不致命(用 omp 默认模型)
|
|
@@ -1187,11 +1417,30 @@ export class OmpRpcAgentImpl implements AgentInterface {
|
|
|
1187
1417
|
client.onHostToolCall(req => this.handleHostTool(req))
|
|
1188
1418
|
await client.send({ type: 'set_host_tools', tools: hostToolsForRole(this.agentRole) })
|
|
1189
1419
|
|
|
1420
|
+
// 上下文治理探测(feature-detect 一次;失败退化被动 usage 跟踪)+ 原生压缩兜底保持开启
|
|
1421
|
+
void this.probeContext(client).catch(() => {})
|
|
1422
|
+
void client.send({ type: 'set_auto_compaction', enabled: true }).catch(() => {})
|
|
1423
|
+
|
|
1190
1424
|
this.client = client
|
|
1191
1425
|
this.hostToolsRegistered = true
|
|
1192
1426
|
}
|
|
1193
1427
|
}
|
|
1194
1428
|
|
|
1429
|
+
/** 上下文状态探测:get_state 取 contextWindow/sessionId(一次;失败仅损失 percent 精度) */
|
|
1430
|
+
private async probeContext(client: OmpRpcClient): Promise<void> {
|
|
1431
|
+
try {
|
|
1432
|
+
const resp = await client.send({ type: 'get_state' })
|
|
1433
|
+
const data = (resp.data ?? {}) as Record<string, unknown>
|
|
1434
|
+
const model = data.model as { contextWindow?: number } | undefined
|
|
1435
|
+
if (typeof model?.contextWindow === 'number' && model.contextWindow > 0) {
|
|
1436
|
+
this.contextWindow = model.contextWindow
|
|
1437
|
+
client.setContextWindow(model.contextWindow)
|
|
1438
|
+
}
|
|
1439
|
+
if (typeof data.sessionId === 'string' && data.sessionId) this.sessionId = data.sessionId
|
|
1440
|
+
}
|
|
1441
|
+
catch { /* get_state 不可用:仅被动 usage */ }
|
|
1442
|
+
}
|
|
1443
|
+
|
|
1195
1444
|
// ===== 内部:host tool handler(workspace 桥接) =====
|
|
1196
1445
|
|
|
1197
1446
|
private async handleHostTool(req: HostToolCallRequest): Promise<{ text: string, isError?: boolean }> {
|
|
@@ -1309,6 +1558,53 @@ export class OmpRpcAgentImpl implements AgentInterface {
|
|
|
1309
1558
|
return { text: `消息 ${sent.messageId.slice(0, 8)}… 已发送给 ${toAgentId}(priority=${priority})${triggerNote}` }
|
|
1310
1559
|
}
|
|
1311
1560
|
|
|
1561
|
+
case 'send_cross_channel_message': {
|
|
1562
|
+
// 跨 Channel 通信(仅 lead):直投目标 channel 的 lead mailbox
|
|
1563
|
+
const toChannelId = req.arguments.to_channel_id as string
|
|
1564
|
+
const message = req.arguments.message as string
|
|
1565
|
+
if (!toChannelId || !message) return { text: '缺少 to_channel_id 或 message', isError: true }
|
|
1566
|
+
try {
|
|
1567
|
+
const r = await ws.sendCrossChannelMessage({
|
|
1568
|
+
toChannelId,
|
|
1569
|
+
parts: [{ text: message }],
|
|
1570
|
+
requireReply: req.arguments.require_reply === true,
|
|
1571
|
+
inReplyTo: req.arguments.in_reply_to as string | undefined,
|
|
1572
|
+
})
|
|
1573
|
+
const note = req.arguments.require_reply === true ? '(已要求对方 Leader 回复)' : ''
|
|
1574
|
+
return { text: `跨 Channel 消息 ${r.messageId.slice(0, 8)}… 已送达 channel「${r.toChannelName}」的 Leader(${r.toLeadAgentId.slice(0, 8)}…)${note}。对方将按你的信息需求处理;其回复会经 mailbox 到达你这里。` }
|
|
1575
|
+
}
|
|
1576
|
+
catch (err) {
|
|
1577
|
+
return { text: `跨 Channel 发送失败: ${err instanceof Error ? err.message : String(err)}(仅 Leader 可跨 Channel 通信)`, isError: true }
|
|
1578
|
+
}
|
|
1579
|
+
}
|
|
1580
|
+
|
|
1581
|
+
case 'list_other_teams': {
|
|
1582
|
+
// 跨团队观察面(仅 lead):其他团队的场景任务概览
|
|
1583
|
+
const teams = await ws.listOtherTeams()
|
|
1584
|
+
if (teams.length === 0) return { text: '当前没有其他团队(或均未启用)。' }
|
|
1585
|
+
const text = teams.map((t) => {
|
|
1586
|
+
const active = t.activeTasks.length > 0
|
|
1587
|
+
? t.activeTasks.map(x => `「${x.title}」(${x.state})`).join('、')
|
|
1588
|
+
: '无进行中任务'
|
|
1589
|
+
const done = t.recentCompleted.length > 0
|
|
1590
|
+
? t.recentCompleted.map(x => `「${x.title}」`).join('、')
|
|
1591
|
+
: '无'
|
|
1592
|
+
return `- ${t.name}${t.description ? `(${t.description})` : ''} · lead=${t.leadName ?? '?'}\n 进行中: ${active}\n 近期完成: ${done}\n channel_id: ${t.channelId}`
|
|
1593
|
+
}).join('\n')
|
|
1594
|
+
return { text: `其他团队概览:\n${text}\n(需要协作时用 send_cross_channel_message 向对应团队 Leader 发信;查具体知识用 search_other_teams_memory)` }
|
|
1595
|
+
}
|
|
1596
|
+
|
|
1597
|
+
case 'search_other_teams_memory': {
|
|
1598
|
+
// 跨团队共享记忆(全员只读):别的团队沉淀的结论/经验,先查再决定是否发信
|
|
1599
|
+
const query = (req.arguments.query as string | undefined) ?? ''
|
|
1600
|
+
if (!query) return { text: '缺少 query', isError: true }
|
|
1601
|
+
const rows = await ws.searchOtherTeamsMemory({ query, limit: Number(req.arguments.limit ?? 5) })
|
|
1602
|
+
if (rows.length === 0) return { text: `其他团队的共享记忆中没有命中「${query}」的内容。` }
|
|
1603
|
+
const text = rows.map(r =>
|
|
1604
|
+
`- [${r.channelName}] 「${r.title}」(${r.createdAt.slice(0, 10)}): ${r.content}`).join('\n')
|
|
1605
|
+
return { text: `其他团队共享记忆命中 ${rows.length} 条:\n${text}` }
|
|
1606
|
+
}
|
|
1607
|
+
|
|
1312
1608
|
case 'refuse_task': {
|
|
1313
1609
|
const taskId = req.arguments.task_id as string
|
|
1314
1610
|
const reason = req.arguments.reason as string
|
|
@@ -549,12 +549,27 @@ export interface TeamMemberRow {
|
|
|
549
549
|
createdAt: string
|
|
550
550
|
}
|
|
551
551
|
|
|
552
|
+
/**
|
|
553
|
+
* 记忆类别:episodic-task/episodic-peer/episodic-session/episodic-team-task(harvest 族,
|
|
554
|
+
* 参与过期+淘汰)/ semantic(知识,免衰减)/ brief/chronicle/reflection(策展层,
|
|
555
|
+
* 免向量化免维护)。TEXT 自由列(无 CHECK),联合类型仅约束调用面。
|
|
556
|
+
*/
|
|
557
|
+
export type MemoryKind
|
|
558
|
+
= | 'episodic-task'
|
|
559
|
+
| 'episodic-peer'
|
|
560
|
+
| 'episodic-session'
|
|
561
|
+
| 'episodic-team-task'
|
|
562
|
+
| 'semantic'
|
|
563
|
+
| 'brief'
|
|
564
|
+
| 'chronicle'
|
|
565
|
+
| 'reflection'
|
|
566
|
+
|
|
552
567
|
/** agent_memories 表行(content 为已 CJK 切分存储文本;agentId='__team__' 为团队共享行) */
|
|
553
568
|
export interface MemoryRow {
|
|
554
569
|
id: string
|
|
555
570
|
channelId: string
|
|
556
571
|
agentId: string
|
|
557
|
-
kind:
|
|
572
|
+
kind: MemoryKind
|
|
558
573
|
title: string
|
|
559
574
|
content: string
|
|
560
575
|
importance: number
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { randomUUID } from 'node:crypto'
|
|
8
8
|
import type { DatabaseSync, StatementSync } from 'node:sqlite'
|
|
9
|
-
import type { MemoryRow } from './database'
|
|
9
|
+
import type { MemoryKind, MemoryRow } from './database'
|
|
10
10
|
|
|
11
11
|
/** 团队共享记忆域 sentinel(单 channel 内全员可读) */
|
|
12
12
|
export const TEAM_AGENT_ID = '__team__'
|
|
@@ -14,7 +14,8 @@ export const TEAM_AGENT_ID = '__team__'
|
|
|
14
14
|
export interface MemoryUpsertInput {
|
|
15
15
|
channelId: string
|
|
16
16
|
agentId: string
|
|
17
|
-
|
|
17
|
+
/** 语义见 database.ts MemoryKind(harvest 族参与维护;semantic 知识;策展层双免) */
|
|
18
|
+
kind: MemoryKind
|
|
18
19
|
title: string
|
|
19
20
|
/** title 的 CJK 切分副本(FTS 索引用;调用方经 AgentMemory.segmentCJK 处理,V8) */
|
|
20
21
|
titleFts: string
|
|
@@ -75,6 +76,15 @@ export function createMemoryRepo(db: DatabaseSync) {
|
|
|
75
76
|
const agentIdsStmt = db.prepare(
|
|
76
77
|
`SELECT DISTINCT agent_id FROM agent_memories WHERE agent_id != ?`,
|
|
77
78
|
)
|
|
79
|
+
const channelIdsStmt = db.prepare(
|
|
80
|
+
`SELECT DISTINCT channel_id AS channelId FROM agent_memories`,
|
|
81
|
+
)
|
|
82
|
+
const getByIdStmt = db.prepare(
|
|
83
|
+
`SELECT id, channel_id AS channelId, agent_id AS agentId, kind, title, content,
|
|
84
|
+
importance, task_id AS taskId, access_count AS accessCount,
|
|
85
|
+
last_accessed_at AS lastAccessedAt, created_at AS createdAt
|
|
86
|
+
FROM agent_memories WHERE id = ?`,
|
|
87
|
+
)
|
|
78
88
|
// ===== 向量方法(P1;vec0 分区表 + mem_rowid 辅助列映射)=====
|
|
79
89
|
// 已实测事实:
|
|
80
90
|
// - vec0 分区表不支持显式 rowid 插入 → 以 +mem_rowid 辅助列映射 agent_memories.rowid
|
|
@@ -123,6 +133,24 @@ export function createMemoryRepo(db: DatabaseSync) {
|
|
|
123
133
|
return searchStmt.all(matchQuery, agentId, TEAM_AGENT_ID, limit) as unknown as Array<MemoryRow & { bm25: number }>
|
|
124
134
|
},
|
|
125
135
|
|
|
136
|
+
/**
|
|
137
|
+
* 跨团队共享域检索(hardening 后的协作扩展;只读):仅 __team__ 行,
|
|
138
|
+
* 限定目标 channel 集合(调用方保证同主/启用),按 bm25 排序。
|
|
139
|
+
* matchQuery 为 OR 连接的切分词(与 search 同规约)。
|
|
140
|
+
*/
|
|
141
|
+
searchTeamSharedAcross(matchQuery: string, channelIds: string[], limit: number): Array<MemoryRow & { bm25: number }> {
|
|
142
|
+
if (channelIds.length === 0) return []
|
|
143
|
+
const placeholders = channelIds.map(() => '?').join(', ')
|
|
144
|
+
const stmt = db.prepare(
|
|
145
|
+
`SELECT ${COLS}, f.rank AS bm25
|
|
146
|
+
FROM agent_memories_fts f
|
|
147
|
+
JOIN agent_memories m ON m.rowid = f.memory_rowid
|
|
148
|
+
WHERE agent_memories_fts MATCH ? AND m.agent_id = ? AND m.channel_id IN (${placeholders})
|
|
149
|
+
ORDER BY f.rank LIMIT ?`,
|
|
150
|
+
)
|
|
151
|
+
return stmt.all(matchQuery, TEAM_AGENT_ID, ...channelIds, limit) as unknown as Array<MemoryRow & { bm25: number }>
|
|
152
|
+
},
|
|
153
|
+
|
|
126
154
|
listRecent(agentId: string, limit: number): MemoryRow[] {
|
|
127
155
|
return listRecentStmt.all(agentId, limit) as unknown as MemoryRow[]
|
|
128
156
|
},
|
|
@@ -188,6 +216,16 @@ export function createMemoryRepo(db: DatabaseSync) {
|
|
|
188
216
|
return (agentIdsStmt.all(TEAM_AGENT_ID) as Array<{ agent_id: string }>).map(r => r.agent_id)
|
|
189
217
|
},
|
|
190
218
|
|
|
219
|
+
/** 有记忆的 channel 清单(team 域行按 channel 迭代维护用) */
|
|
220
|
+
listChannelIds(): string[] {
|
|
221
|
+
return (channelIdsStmt.all() as Array<{ channelId: string }>).map(r => r.channelId)
|
|
222
|
+
},
|
|
223
|
+
|
|
224
|
+
/** 按 id 取单行(brief 置顶注入等直取场景;不存在返回 null) */
|
|
225
|
+
getById(id: string): MemoryRow | null {
|
|
226
|
+
return (getByIdStmt.get(id) as MemoryRow | undefined) ?? null
|
|
227
|
+
},
|
|
228
|
+
|
|
191
229
|
// ===== vec:延迟建表(vecInit)/ 刷新写入(vecSet)/ 删除(vecDelete)/ 分区 kNN(vecSearch)=====
|
|
192
230
|
|
|
193
231
|
/** 首次调用按 dims 建表;同 dims 幂等 true;不同 dims 拒绝 false(禁用向量,绝不重建) */
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
import { copyFileSync, existsSync, readdirSync, readFileSync, statSync, mkdirSync } from 'node:fs'
|
|
19
19
|
import { dirname, join, resolve } from 'node:path'
|
|
20
20
|
import { fileURLToPath } from 'node:url'
|
|
21
|
-
import { awHome, findLocalConfigRoot } from '
|
|
21
|
+
import { awHome, findLocalConfigRoot } from '@/shared/config/home.mjs'
|
|
22
22
|
import { AppError } from '../../../utils/errors'
|
|
23
23
|
|
|
24
24
|
/** 模板变量值(字符串化注入) */
|
|
@@ -40,7 +40,8 @@ function packagedPromptsDir(): string | null {
|
|
|
40
40
|
return existsSync(candidate) ? candidate : null
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
/**
|
|
43
|
+
/** 首次/每次解析把包内初始 prompts 补种到 ~/.AgentWorkShop/prompts(逐文件只补缺,不覆盖)。
|
|
44
|
+
* 每次解析都执行:包升级新增的提示词文件能进入已存在的 Home(用户已定制文件不回写)。 */
|
|
44
45
|
function seedHomePrompts(homePrompts: string): void {
|
|
45
46
|
const src = packagedPromptsDir()
|
|
46
47
|
if (!src || resolve(src) === resolve(homePrompts)) return
|
|
@@ -55,7 +56,7 @@ function seedHomePrompts(homePrompts: string): void {
|
|
|
55
56
|
}
|
|
56
57
|
catch { /* 单文件失败不阻断播种 */ }
|
|
57
58
|
}
|
|
58
|
-
if (seeded > 0) console.log(`[prompts] 已向 ${homePrompts}
|
|
59
|
+
if (seeded > 0) console.log(`[prompts] 已向 ${homePrompts} 补种 ${seeded} 个提示词文件`)
|
|
59
60
|
}
|
|
60
61
|
|
|
61
62
|
/**
|
|
@@ -72,9 +73,9 @@ export function promptsDir(): string {
|
|
|
72
73
|
return base
|
|
73
74
|
}
|
|
74
75
|
|
|
75
|
-
// ~/.AgentWorkShop/prompts
|
|
76
|
+
// ~/.AgentWorkShop/prompts:默认兜底目录,解析时自动补种初始文件(只补缺,不覆盖用户定制)
|
|
76
77
|
const homePrompts = join(awHome(process.env), 'prompts')
|
|
77
|
-
|
|
78
|
+
seedHomePrompts(homePrompts)
|
|
78
79
|
|
|
79
80
|
// ② 项目内 ./.AgentWorkShop/prompts 真实存在 → 项目内优先
|
|
80
81
|
const localConfigRoot = findLocalConfigRoot(process.cwd())
|
|
@@ -17,7 +17,7 @@ import type {
|
|
|
17
17
|
SupervisionSnapshot,
|
|
18
18
|
} from '../agents/agent-interface'
|
|
19
19
|
import type { A2AMessage, A2AArtifact, Part } from '../types/a2a'
|
|
20
|
-
import type { AgentStatusView, AgentTaskQueueView, TaskState, WorkspaceTask } from '../types/task'
|
|
20
|
+
import type { AgentContextStats, AgentStatusView, AgentTaskQueueView, TaskState, WorkspaceTask } from '../types/task'
|
|
21
21
|
import { TERMINAL_TASK_STATES } from '../types/task'
|
|
22
22
|
import type { AgentMemory } from './memory'
|
|
23
23
|
import type { Mailbox } from './mailbox'
|
|
@@ -58,6 +58,8 @@ export interface ChannelBus {
|
|
|
58
58
|
currentTaskProgress?: number | null
|
|
59
59
|
queuedCount?: number
|
|
60
60
|
completedCount?: number
|
|
61
|
+
/** harness 上下文用量(omp 有;进程内 harness 缺省) */
|
|
62
|
+
context?: AgentContextStats | null
|
|
61
63
|
}): void
|
|
62
64
|
onAgentStatus(fn: (e: {
|
|
63
65
|
agentId: string
|
|
@@ -67,6 +69,7 @@ export interface ChannelBus {
|
|
|
67
69
|
currentTaskProgress?: number | null
|
|
68
70
|
queuedCount?: number
|
|
69
71
|
completedCount?: number
|
|
72
|
+
context?: AgentContextStats | null
|
|
70
73
|
}) => void): () => void
|
|
71
74
|
/** channel 内消息投递通知(route 汇流点触发;AEP a2a.message 事件源) */
|
|
72
75
|
notifyMessage(message: A2AMessage): void
|
|
@@ -205,10 +208,11 @@ export class AgentRuntime {
|
|
|
205
208
|
return this.state
|
|
206
209
|
}
|
|
207
210
|
|
|
208
|
-
/** 实时状态视图:idle/busy/stopped + 当前任务 +
|
|
211
|
+
/** 实时状态视图:idle/busy/stopped + 当前任务 + 队列 + harness 上下文用量 */
|
|
209
212
|
getStatus(): AgentStatusView {
|
|
210
213
|
const queue = this.getQueueView()
|
|
211
214
|
const current = queue.current ?? (this.currentTaskId ? this.deps.taskEngine.get(this.currentTaskId) : undefined)
|
|
215
|
+
const context = this.impl.getContextStats?.() ?? undefined
|
|
212
216
|
return {
|
|
213
217
|
agentId: this.agentId,
|
|
214
218
|
channelId: this.channelId,
|
|
@@ -221,6 +225,7 @@ export class AgentRuntime {
|
|
|
221
225
|
currentTaskProgress: current?.progress != null ? current.progress : null,
|
|
222
226
|
queuedCount: queue.queued.length,
|
|
223
227
|
completedCount: queue.completed.length,
|
|
228
|
+
...(context ? { context } : {}),
|
|
224
229
|
}
|
|
225
230
|
}
|
|
226
231
|
|
|
@@ -448,6 +453,8 @@ export class AgentRuntime {
|
|
|
448
453
|
}
|
|
449
454
|
finally {
|
|
450
455
|
if (this.superviseController === controller) this.superviseController = null
|
|
456
|
+
// 调度回合落定:lead 的 supervise 是上下文膨胀大户,同样走 post-settle 压缩检查
|
|
457
|
+
this.maybePostSettle()
|
|
451
458
|
}
|
|
452
459
|
}
|
|
453
460
|
|
|
@@ -522,10 +529,12 @@ export class AgentRuntime {
|
|
|
522
529
|
}
|
|
523
530
|
// 每次 run 新建 AbortController;abort 后事件流终止
|
|
524
531
|
this.abortController = new AbortController()
|
|
525
|
-
// 记忆召回(异常不阻塞)
|
|
532
|
+
// 记忆召回(异常不阻塞):查询=消息原文(title 首词天然显著)+ 任务关联加权
|
|
526
533
|
let memoryBlock: string | undefined
|
|
527
534
|
try {
|
|
528
|
-
memoryBlock = (await this.deps.memory?.recall(partsToText(msg.parts)
|
|
535
|
+
memoryBlock = (await this.deps.memory?.recall(partsToText(msg.parts), {
|
|
536
|
+
relatedTaskIds: this.relatedTaskIdsOf(taskId),
|
|
537
|
+
})) ?? undefined
|
|
529
538
|
}
|
|
530
539
|
catch (err) {
|
|
531
540
|
log.error(`[AgentRuntime:${this.agentId}] 记忆召回失败:`, err)
|
|
@@ -666,11 +675,13 @@ export class AgentRuntime {
|
|
|
666
675
|
this.runErrorRetries.delete(msg.messageId)
|
|
667
676
|
this.deps.mailbox.markConsumed(msg.messageId)
|
|
668
677
|
}
|
|
678
|
+
// 回合落定:信箱空 → 后台压缩检查(post-settle 路径;impl 自守卫不阻塞)
|
|
679
|
+
this.maybePostSettle()
|
|
669
680
|
}
|
|
670
681
|
}
|
|
671
682
|
|
|
672
|
-
/** 状态通知的队列上下文(
|
|
673
|
-
private queueContext(): Pick<AgentStatusView, 'currentTaskId' | 'currentTaskTitle' | 'currentTaskProgress' | 'queuedCount' | 'completedCount'> {
|
|
683
|
+
/** 状态通知的队列上下文(实时:当前任务/待执行数/已完成数/harness 上下文用量) */
|
|
684
|
+
private queueContext(): Pick<AgentStatusView, 'currentTaskId' | 'currentTaskTitle' | 'currentTaskProgress' | 'queuedCount' | 'completedCount'> & { context?: AgentContextStats | null } {
|
|
674
685
|
const status = this.getStatus()
|
|
675
686
|
return {
|
|
676
687
|
currentTaskId: status.currentTaskId,
|
|
@@ -678,9 +689,44 @@ export class AgentRuntime {
|
|
|
678
689
|
currentTaskProgress: status.currentTaskProgress,
|
|
679
690
|
queuedCount: status.queuedCount,
|
|
680
691
|
completedCount: status.completedCount,
|
|
692
|
+
context: status.context ?? null,
|
|
681
693
|
}
|
|
682
694
|
}
|
|
683
695
|
|
|
696
|
+
/**
|
|
697
|
+
* 任务关联集(自身+父+兄弟,≤20):related-task boost 数据源。
|
|
698
|
+
* 同父兄弟任务的记忆(前几个子任务做了什么)在引子中必然置顶——
|
|
699
|
+
* 纯内存 id 判断,零检索开销。
|
|
700
|
+
*/
|
|
701
|
+
private relatedTaskIdsOf(taskId: string | undefined): string[] | undefined {
|
|
702
|
+
if (!taskId) return undefined
|
|
703
|
+
const task = this.deps.taskEngine.get(taskId)
|
|
704
|
+
if (!task) return undefined
|
|
705
|
+
const ids = new Set<string>([taskId])
|
|
706
|
+
if (task.parentId) {
|
|
707
|
+
ids.add(task.parentId)
|
|
708
|
+
for (const t of this.deps.taskEngine.list(this.channelId)) {
|
|
709
|
+
if (t.parentId === task.parentId) ids.add(t.id)
|
|
710
|
+
if (ids.size >= 20) break
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
return [...ids]
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
/**
|
|
717
|
+
* post-settle 压缩检查:仅信箱无排队消息时触发(排队消息的 pre-prompt gate 兜底)。
|
|
718
|
+
* impl 自守卫(回合间隙才压缩、异常不抛出);本方法自身也不阻塞消费循环。
|
|
719
|
+
*/
|
|
720
|
+
private maybePostSettle(): void {
|
|
721
|
+
if (!this.impl.onTurnSettled) return
|
|
722
|
+
void this.deps.mailbox.peek(1)
|
|
723
|
+
.then((pending) => {
|
|
724
|
+
if (pending.length > 0) return undefined
|
|
725
|
+
return this.impl.onTurnSettled?.()
|
|
726
|
+
})
|
|
727
|
+
.catch(() => {})
|
|
728
|
+
}
|
|
729
|
+
|
|
684
730
|
private toRequest(msg: A2AMessage, memory?: string): AgentRunRequest {
|
|
685
731
|
return {
|
|
686
732
|
message: msg,
|