@kooka/agent-sdk 0.1.3 → 0.1.5

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.
Files changed (45) hide show
  1. package/README.md +10 -10
  2. package/dist/agent/agent.d.ts +7 -1
  3. package/dist/agent/agent.d.ts.map +1 -1
  4. package/dist/agent/agent.js +257 -19
  5. package/dist/agent/agent.js.map +1 -1
  6. package/dist/agent/prompts.d.ts +1 -1
  7. package/dist/agent/prompts.d.ts.map +1 -1
  8. package/dist/agent/prompts.js +2 -0
  9. package/dist/agent/prompts.js.map +1 -1
  10. package/dist/agent/reminders.d.ts +5 -1
  11. package/dist/agent/reminders.d.ts.map +1 -1
  12. package/dist/agent/reminders.js +14 -3
  13. package/dist/agent/reminders.js.map +1 -1
  14. package/dist/index.d.ts +6 -0
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +6 -0
  17. package/dist/index.js.map +1 -1
  18. package/dist/persistence/sessionSnapshot.d.ts +8 -0
  19. package/dist/persistence/sessionSnapshot.d.ts.map +1 -1
  20. package/dist/persistence/sessionSnapshot.js +16 -0
  21. package/dist/persistence/sessionSnapshot.js.map +1 -1
  22. package/dist/plugins/pluginManager.d.ts.map +1 -1
  23. package/dist/plugins/pluginManager.js +19 -3
  24. package/dist/plugins/pluginManager.js.map +1 -1
  25. package/dist/tools/agentBrowser.js +15 -15
  26. package/dist/tools/agentBrowser.js.map +1 -1
  27. package/dist/tools/builtin/bash.d.ts.map +1 -1
  28. package/dist/tools/builtin/bash.js +3 -2
  29. package/dist/tools/builtin/bash.js.map +1 -1
  30. package/dist/tools/builtin/index.d.ts.map +1 -1
  31. package/dist/tools/builtin/index.js +2 -0
  32. package/dist/tools/builtin/index.js.map +1 -1
  33. package/dist/tools/builtin/task.d.ts +4 -0
  34. package/dist/tools/builtin/task.d.ts.map +1 -0
  35. package/dist/tools/builtin/task.js +47 -0
  36. package/dist/tools/builtin/task.js.map +1 -0
  37. package/dist/tools/builtin/workspace.d.ts.map +1 -1
  38. package/dist/tools/builtin/workspace.js +42 -3
  39. package/dist/tools/builtin/workspace.js.map +1 -1
  40. package/dist/tools/registry.d.ts.map +1 -1
  41. package/dist/tools/registry.js +19 -1
  42. package/dist/tools/registry.js.map +1 -1
  43. package/dist/types.d.ts +5 -0
  44. package/dist/types.d.ts.map +1 -1
  45. package/package.json +2 -2
package/README.md CHANGED
@@ -111,11 +111,11 @@ import { createLingyunAgent, type ToolDefinition } from '@kooka/agent-sdk';
111
111
  const { agent, registry } = createLingyunAgent({ /* ... */ });
112
112
 
113
113
  const timeTool: ToolDefinition = {
114
- id: 'time.now',
115
- name: 'time.now',
114
+ id: 'time_now',
115
+ name: 'time_now',
116
116
  description: 'Get the current time as an ISO string.',
117
117
  parameters: { type: 'object', properties: {} },
118
- execution: { type: 'function', handler: 'time.now' },
118
+ execution: { type: 'function', handler: 'time_now' },
119
119
  metadata: { readOnly: true },
120
120
  };
121
121
 
@@ -162,7 +162,7 @@ Useful event types:
162
162
 
163
163
  - `assistant_token`: user-facing assistant text (with `<think>` and tool-call markers removed)
164
164
  - `thought_token`: model “thinking” tokens (only if the provider emits them)
165
- - `notice`: user-facing notices from the runtime (e.g. unknown `$skill-name`)
165
+ - `notice`: user-facing notices from the runtime (e.g. subagent model fallback warnings)
166
166
  - `tool_call` / `tool_result` / `tool_blocked`: tool lifecycle
167
167
  - `compaction_start` / `compaction_end`: context overflow mitigation
168
168
 
@@ -188,7 +188,7 @@ If a user message includes `$<skill-name>`, LingYun:
188
188
  1. Looks up the skill by `name:` in discovered `SKILL.md` files
189
189
  2. Injects the skill body as a synthetic `<skill>...</skill>` user message before calling the model
190
190
 
191
- Unknown skills are ignored and emitted as `notice` events (`callbacks.onNotice`).
191
+ Unknown skills are ignored.
192
192
 
193
193
  Configure discovery/injection via `tools.builtinOptions.skills`:
194
194
 
@@ -233,7 +233,7 @@ const { registry } = createLingyunAgent({
233
233
 
234
234
  registry.registerTool(
235
235
  {
236
- id: 'demo.echo',
236
+ id: 'demo_echo',
237
237
  name: 'Echo',
238
238
  description: 'Echo back the message argument',
239
239
  parameters: {
@@ -241,7 +241,7 @@ registry.registerTool(
241
241
  properties: { message: { type: 'string' } },
242
242
  required: ['message'],
243
243
  },
244
- execution: { type: 'function', handler: 'demo.echo' },
244
+ execution: { type: 'function', handler: 'demo_echo' },
245
245
  metadata: { requiresApproval: false, permission: 'read', readOnly: true },
246
246
  },
247
247
  async (args) => ({ success: true, data: `Echo: ${String(args.message ?? '')}` })
@@ -276,9 +276,9 @@ registerAgentBrowserTools(registry, {
276
276
  ```
277
277
 
278
278
  Tools:
279
- - `browser.startSession` / `browser.closeSession`
280
- - `browser.snapshot` (read-only; returns accessibility tree with refs like `@e2`)
281
- - `browser.run` (requires approval; runs click/fill/type/wait/get/screenshot/pdf/trace actions)
279
+ - `browser_start_session` / `browser_close_session`
280
+ - `browser_snapshot` (read-only; returns accessibility tree with refs like `@e2`)
281
+ - `browser_run` (requires approval; runs click/fill/type/wait/get/screenshot/pdf/trace actions)
282
282
 
283
283
  Security defaults:
284
284
  - HTTPS-only and blocks private hosts / IPs by default
@@ -6,11 +6,15 @@ export declare class LingyunSession {
6
6
  history: AgentHistoryMessage[];
7
7
  pendingPlan?: string;
8
8
  sessionId?: string;
9
+ parentSessionId?: string;
10
+ subagentType?: string;
11
+ modelId?: string;
12
+ mentionedSkills: string[];
9
13
  fileHandles?: {
10
14
  nextId: number;
11
15
  byId: Record<string, string>;
12
16
  };
13
- constructor(init?: Partial<Pick<LingyunSession, 'history' | 'pendingPlan' | 'sessionId' | 'fileHandles'>>);
17
+ constructor(init?: Partial<Pick<LingyunSession, 'history' | 'pendingPlan' | 'sessionId' | 'parentSessionId' | 'subagentType' | 'modelId' | 'mentionedSkills' | 'fileHandles'>>);
14
18
  getHistory(): AgentHistoryMessage[];
15
19
  }
16
20
  export type LingyunAgentRuntimeOptions = {
@@ -39,6 +43,8 @@ export declare class LingyunAgent {
39
43
  private readonly modelLimits?;
40
44
  private readonly compactionConfig;
41
45
  private registeredPluginTools;
46
+ private readonly taskSessions;
47
+ private readonly maxTaskSessions;
42
48
  constructor(llm: LLMProvider, config: AgentConfig, registry: ToolRegistry, runtime?: LingyunAgentRuntimeOptions);
43
49
  updateConfig(config: Partial<AgentConfig>): void;
44
50
  setAllowExternalPaths(allow: boolean): void;
@@ -1 +1 @@
1
- {"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../../src/agent/agent.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../../src/agent/agent.ts"],"names":[],"mappings":"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"}
@@ -1,7 +1,7 @@
1
1
  import * as path from 'path';
2
2
  import { convertToModelMessages, extractReasoningMiddleware, jsonSchema, streamText, tool as aiTool, wrapLanguageModel, } from 'ai';
3
3
  import { combineAbortSignals } from '../abort.js';
4
- import { COMPACTION_AUTO_CONTINUE_TEXT, COMPACTION_MARKER_TEXT, COMPACTION_PROMPT_TEXT, COMPACTION_SYSTEM_PROMPT, createAssistantHistoryMessage, createHistoryForCompactionPrompt, createHistoryForModel, createUserHistoryMessage, evaluatePermission, evaluateShellCommand, extractSkillMentions, extractUsageTokens, finalizeStreamingParts, findExternalPathReferencesInShellCommand, getEffectiveHistory, getMessageText, getReservedOutputTokens, isOverflow as isContextOverflow, isPathInsideWorkspace, markPreviousAssistantToolOutputs, redactFsPathForPrompt, renderSkillsSectionForPrompt, selectSkillsForText, setDynamicToolError, setDynamicToolOutput, upsertDynamicToolCall, } from '@kooka/core';
4
+ import { COMPACTION_AUTO_CONTINUE_TEXT, COMPACTION_MARKER_TEXT, COMPACTION_PROMPT_TEXT, COMPACTION_SYSTEM_PROMPT, createAssistantHistoryMessage, applyAssistantReplayForPrompt, applyCopilotImageInputPattern, applyOpenAICompatibleReasoningField, createHistoryForCompactionPrompt, createHistoryForModel, createUserHistoryMessage, evaluatePermission, evaluateShellCommand, extractSkillMentions, extractUsageTokens, finalizeStreamingParts, findExternalPathReferencesInShellCommand, getEffectiveHistory, getMessageText, getReservedOutputTokens, isOverflow as isContextOverflow, isPathInsideWorkspace, listBuiltinSubagents, normalizeSessionId, markPreviousAssistantToolOutputs, redactFsPathForPrompt, renderSkillsSectionForPrompt, requireString, resolveBuiltinSubagent, selectSkillsForText, setDynamicToolError, setDynamicToolOutput, upsertDynamicToolCall, } from '@kooka/core';
5
5
  import { PluginManager } from '../plugins/pluginManager.js';
6
6
  import { insertModeReminders } from './reminders.js';
7
7
  import { DEFAULT_SYSTEM_PROMPT } from './prompts.js';
@@ -107,6 +107,10 @@ export class LingyunSession {
107
107
  history = [];
108
108
  pendingPlan;
109
109
  sessionId;
110
+ parentSessionId;
111
+ subagentType;
112
+ modelId;
113
+ mentionedSkills = [];
110
114
  fileHandles;
111
115
  constructor(init) {
112
116
  if (init?.history)
@@ -115,6 +119,14 @@ export class LingyunSession {
115
119
  this.pendingPlan = init.pendingPlan;
116
120
  if (init?.sessionId)
117
121
  this.sessionId = init.sessionId;
122
+ if (init?.parentSessionId)
123
+ this.parentSessionId = init.parentSessionId;
124
+ if (init?.subagentType)
125
+ this.subagentType = init.subagentType;
126
+ if (init?.modelId)
127
+ this.modelId = init.modelId;
128
+ if (init?.mentionedSkills)
129
+ this.mentionedSkills = [...init.mentionedSkills];
118
130
  if (init?.fileHandles)
119
131
  this.fileHandles = init.fileHandles;
120
132
  }
@@ -134,6 +146,8 @@ export class LingyunAgent {
134
146
  modelLimits;
135
147
  compactionConfig;
136
148
  registeredPluginTools = new Set();
149
+ taskSessions = new Map();
150
+ maxTaskSessions = 50;
137
151
  constructor(llm, config, registry, runtime) {
138
152
  this.llm = llm;
139
153
  this.config = config;
@@ -335,11 +349,16 @@ export class LingyunAgent {
335
349
  async toModelMessages(session, tools, modelId) {
336
350
  const effective = getEffectiveHistory(session.history);
337
351
  const prepared = createHistoryForModel(effective);
338
- const reminded = insertModeReminders(prepared, this.getMode());
352
+ const reminded = insertModeReminders(prepared, this.getMode(), { allowExternalPaths: this.allowExternalPaths });
339
353
  const withoutIds = reminded.map(({ id: _id, ...rest }) => rest);
340
354
  const messagesOutput = await this.plugins.trigger('experimental.chat.messages.transform', { sessionId: session.sessionId ?? this.config.sessionId, mode: this.getMode(), modelId }, { messages: [...withoutIds] });
341
355
  const messages = Array.isArray(messagesOutput.messages) ? messagesOutput.messages : withoutIds;
342
- return convertToModelMessages(messages, { tools: tools });
356
+ const replayed = this.llm.id === 'openaiCompatible'
357
+ ? applyAssistantReplayForPrompt(messages)
358
+ : messages;
359
+ const converted = await convertToModelMessages(replayed, { tools: tools });
360
+ const withReasoning = this.llm.id === 'openaiCompatible' ? applyOpenAICompatibleReasoningField(converted) : converted;
361
+ return this.llm.id === 'copilot' ? applyCopilotImageInputPattern(withReasoning) : withReasoning;
343
362
  }
344
363
  createToolContext(signal, session, callbacks) {
345
364
  return {
@@ -504,6 +523,213 @@ export class LingyunAgent {
504
523
  for (const def of tools) {
505
524
  const toolName = def.id;
506
525
  toolNameToDefinition.set(toolName, def);
526
+ if (toolName === 'task') {
527
+ out[toolName] = aiTool({
528
+ id: toolName,
529
+ description: def.description,
530
+ inputSchema: jsonSchema(def.parameters),
531
+ execute: async (args, options) => {
532
+ const resolvedArgs = args ?? {};
533
+ if (session.parentSessionId || session.subagentType) {
534
+ return {
535
+ success: false,
536
+ error: 'Subagents cannot spawn other subagents via task.',
537
+ metadata: { errorType: 'task_recursion_denied' },
538
+ };
539
+ }
540
+ const parentMode = this.getMode();
541
+ const descriptionResult = requireString(resolvedArgs, 'description');
542
+ if ('error' in descriptionResult)
543
+ return { success: false, error: descriptionResult.error };
544
+ const promptResult = requireString(resolvedArgs, 'prompt');
545
+ if ('error' in promptResult)
546
+ return { success: false, error: promptResult.error };
547
+ const typeResult = requireString(resolvedArgs, 'subagent_type');
548
+ if ('error' in typeResult)
549
+ return { success: false, error: typeResult.error };
550
+ const subagentTypeRaw = typeResult.value.trim();
551
+ const subagent = resolveBuiltinSubagent(subagentTypeRaw);
552
+ if (!subagent) {
553
+ const names = listBuiltinSubagents().map((a) => a.name).join(', ');
554
+ return {
555
+ success: false,
556
+ error: `Unknown subagent_type: ${subagentTypeRaw}. Available: ${names || '(none)'}`,
557
+ metadata: { errorType: 'unknown_subagent_type', subagentType: subagentTypeRaw },
558
+ };
559
+ }
560
+ if (parentMode === 'plan' && subagent.name !== 'explore') {
561
+ return {
562
+ success: false,
563
+ error: 'Only the explore subagent is allowed in Plan mode.',
564
+ metadata: { errorType: 'subagent_denied_in_plan', subagentType: subagent.name },
565
+ };
566
+ }
567
+ const sessionIdRaw = typeof resolvedArgs.session_id === 'string' && resolvedArgs.session_id.trim()
568
+ ? String(resolvedArgs.session_id).trim()
569
+ : '';
570
+ const parentSessionId = session.sessionId ?? this.config.sessionId;
571
+ const requestedSessionId = normalizeSessionId(sessionIdRaw) || '';
572
+ const childSessionId = requestedSessionId || crypto.randomUUID();
573
+ const existing = this.taskSessions.get(childSessionId);
574
+ const childSession = existing ??
575
+ new LingyunSession({
576
+ sessionId: childSessionId,
577
+ parentSessionId,
578
+ subagentType: subagent.name,
579
+ });
580
+ if (!existing) {
581
+ this.taskSessions.set(childSessionId, childSession);
582
+ while (this.taskSessions.size > this.maxTaskSessions) {
583
+ const oldestKey = this.taskSessions.keys().next().value;
584
+ if (!oldestKey)
585
+ break;
586
+ if (oldestKey === childSessionId)
587
+ break;
588
+ this.taskSessions.delete(oldestKey);
589
+ }
590
+ }
591
+ else {
592
+ childSession.parentSessionId = parentSessionId;
593
+ childSession.subagentType = subagent.name;
594
+ // Refresh LRU order.
595
+ this.taskSessions.delete(childSessionId);
596
+ this.taskSessions.set(childSessionId, childSession);
597
+ }
598
+ const parentModelId = this.config.model;
599
+ if (!parentModelId) {
600
+ return {
601
+ success: false,
602
+ error: 'No model configured. Set AgentConfig.model.',
603
+ metadata: { errorType: 'missing_model' },
604
+ };
605
+ }
606
+ const configuredSubagentModel = typeof this.config.subagentModel === 'string' ? this.config.subagentModel.trim() : '';
607
+ const desiredChildModelId = childSession.modelId || configuredSubagentModel || parentModelId;
608
+ let childModelId = parentModelId;
609
+ let childModelWarning;
610
+ if (desiredChildModelId !== parentModelId) {
611
+ try {
612
+ await this.llm.getModel(desiredChildModelId);
613
+ childModelId = desiredChildModelId;
614
+ }
615
+ catch (error) {
616
+ childModelWarning =
617
+ `Subagent model "${desiredChildModelId}" is unavailable; ` +
618
+ `using parent model "${parentModelId}".`;
619
+ callbacks?.onNotice?.({ level: 'warning', message: childModelWarning });
620
+ callbacks?.onDebug?.(`[Task] subagent model fallback requested=${desiredChildModelId} using=${parentModelId} error=${error instanceof Error ? error.message : String(error)}`);
621
+ childModelId = parentModelId;
622
+ }
623
+ }
624
+ childSession.modelId = childModelId;
625
+ const basePrompt = this.config.systemPrompt || DEFAULT_SYSTEM_PROMPT;
626
+ const subagentConfig = {
627
+ model: childModelId,
628
+ mode: 'build',
629
+ temperature: this.config.temperature,
630
+ maxRetries: this.config.maxRetries,
631
+ maxOutputTokens: this.config.maxOutputTokens,
632
+ autoApprove: this.config.autoApprove,
633
+ toolFilter: subagent.toolFilter?.length ? subagent.toolFilter : undefined,
634
+ systemPrompt: `${basePrompt}\n\n${subagent.prompt}`,
635
+ sessionId: childSessionId,
636
+ };
637
+ const subagentRunner = new LingyunAgent(this.llm, subagentConfig, this.registry, {
638
+ plugins: this.plugins,
639
+ workspaceRoot: this.workspaceRoot,
640
+ allowExternalPaths: this.allowExternalPaths,
641
+ skills: this.skillsConfig,
642
+ modelLimits: this.modelLimits,
643
+ compaction: this.compactionConfig,
644
+ });
645
+ const toolSummary = new Map();
646
+ const childCallbacks = {
647
+ onRequestApproval: callbacks?.onRequestApproval,
648
+ onToolCall: (tool, definition) => {
649
+ toolSummary.set(tool.id, { id: tool.id, tool: definition.id, status: 'running' });
650
+ },
651
+ onToolResult: (tool, result) => {
652
+ const prev = toolSummary.get(tool.id);
653
+ const nextStatus = result.success ? 'success' : 'error';
654
+ toolSummary.set(tool.id, {
655
+ id: tool.id,
656
+ tool: prev?.tool ?? tool.function.name,
657
+ status: nextStatus,
658
+ });
659
+ },
660
+ };
661
+ try {
662
+ const run = subagentRunner.run({
663
+ session: childSession,
664
+ input: promptResult.value,
665
+ callbacks: childCallbacks,
666
+ signal: options.abortSignal,
667
+ });
668
+ const drain = (async () => {
669
+ for await (const _event of run.events) {
670
+ // drain
671
+ }
672
+ })();
673
+ const done = await run.done;
674
+ await drain;
675
+ const text = done.text || '';
676
+ const outputText = text.trimEnd() +
677
+ '\n\n' +
678
+ ['<task_metadata>', `session_id: ${childSessionId}`, '</task_metadata>'].join('\n');
679
+ const summary = [...toolSummary.values()].sort((a, b) => a.id.localeCompare(b.id));
680
+ return {
681
+ success: true,
682
+ data: {
683
+ session_id: childSessionId,
684
+ subagent_type: subagent.name,
685
+ text,
686
+ },
687
+ metadata: {
688
+ title: descriptionResult.value,
689
+ outputText,
690
+ task: {
691
+ description: descriptionResult.value,
692
+ subagent_type: subagent.name,
693
+ session_id: childSessionId,
694
+ parent_session_id: parentSessionId,
695
+ summary,
696
+ model_id: childModelId,
697
+ ...(childModelWarning
698
+ ? { model_warning: childModelWarning, requested_model_id: desiredChildModelId }
699
+ : {}),
700
+ },
701
+ childSession: {
702
+ sessionId: childSessionId,
703
+ parentSessionId,
704
+ subagentType: subagent.name,
705
+ modelId: childModelId,
706
+ history: childSession.getHistory(),
707
+ pendingPlan: childSession.pendingPlan,
708
+ fileHandles: childSession.fileHandles,
709
+ },
710
+ },
711
+ };
712
+ }
713
+ catch (error) {
714
+ return {
715
+ success: false,
716
+ error: error instanceof Error ? error.message : String(error),
717
+ metadata: { errorType: 'task_subagent_failed' },
718
+ };
719
+ }
720
+ finally {
721
+ // Avoid leaking subagent's temporary skill injection messages to later turns.
722
+ childSession.history = childSession.history.filter((msg) => !(msg.role === 'user' && msg.metadata?.skill));
723
+ }
724
+ },
725
+ toModelOutput: async (options) => {
726
+ const output = options.output;
727
+ const content = await this.formatToolResult(output, def.name);
728
+ return { type: 'text', value: content };
729
+ },
730
+ });
731
+ continue;
732
+ }
507
733
  out[toolName] = aiTool({
508
734
  id: toolName,
509
735
  description: def.description,
@@ -768,7 +994,10 @@ export class LingyunAgent {
768
994
  const prepared = createHistoryForCompactionPrompt(effective, this.compactionConfig);
769
995
  const withoutIds = prepared.map(({ id: _id, ...rest }) => rest);
770
996
  const compactionUser = createUserHistoryMessage(promptText, { synthetic: true });
771
- const compactionModelMessages = await convertToModelMessages([...withoutIds, compactionUser], { tools: {} });
997
+ const convertedCompactionModelMessages = await convertToModelMessages([...withoutIds, compactionUser], { tools: {} });
998
+ const compactionModelMessages = this.llm.id === 'copilot'
999
+ ? applyCopilotImageInputPattern(convertedCompactionModelMessages)
1000
+ : convertedCompactionModelMessages;
772
1001
  const stream = streamText({
773
1002
  model: compactionModel,
774
1003
  system: COMPACTION_SYSTEM_PROMPT,
@@ -929,7 +1158,16 @@ export class LingyunAgent {
929
1158
  const toolCallId = String(part.toolCallId);
930
1159
  const def = toolNameToDefinition.get(toolName);
931
1160
  const toolLabel = def?.name || toolName;
932
- const output = await this.pruneToolResultForHistory(part.output, toolLabel);
1161
+ const rawOutput = part.output;
1162
+ let output = await this.pruneToolResultForHistory(rawOutput, toolLabel);
1163
+ const isTaskTool = def?.id === 'task' || toolName === 'task';
1164
+ if (isTaskTool && output.metadata && typeof output.metadata === 'object') {
1165
+ // Do not persist child session snapshots inside the parent session history.
1166
+ const meta = { ...output.metadata };
1167
+ delete meta.childSession;
1168
+ delete meta.task;
1169
+ output = { ...output, metadata: meta };
1170
+ }
933
1171
  setDynamicToolOutput(assistantMessage, {
934
1172
  toolName,
935
1173
  toolCallId,
@@ -937,7 +1175,12 @@ export class LingyunAgent {
937
1175
  output,
938
1176
  });
939
1177
  const tc = toToolCall(toolCallId, toolName, part.input);
940
- callbacksSafe?.onToolResult?.(tc, output);
1178
+ if (isTaskTool && rawOutput && typeof rawOutput === 'object' && typeof rawOutput.success === 'boolean') {
1179
+ callbacksSafe?.onToolResult?.(tc, rawOutput);
1180
+ }
1181
+ else {
1182
+ callbacksSafe?.onToolResult?.(tc, output);
1183
+ }
941
1184
  callbacksSafe?.onStatusChange?.({ type: 'running', message: '' });
942
1185
  break;
943
1186
  }
@@ -992,6 +1235,7 @@ export class LingyunAgent {
992
1235
  assistantMessage.metadata = {
993
1236
  mode: this.getMode(),
994
1237
  finishReason: streamFinishReason,
1238
+ replay: { text: attemptText, reasoning: attemptReasoning },
995
1239
  ...(tokens ? { tokens } : {}),
996
1240
  };
997
1241
  const cleanedText = stripToolBlocks(stripThinkBlocks(attemptText)).trim();
@@ -1004,6 +1248,9 @@ export class LingyunAgent {
1004
1248
  if (finalText) {
1005
1249
  assistantMessage.parts.unshift({ type: 'text', text: finalText, state: 'streaming' });
1006
1250
  }
1251
+ if (attemptReasoning.trim()) {
1252
+ assistantMessage.parts.unshift({ type: 'reasoning', text: attemptReasoning, state: 'streaming' });
1253
+ }
1007
1254
  finalizeStreamingParts(assistantMessage);
1008
1255
  session.history.push(assistantMessage);
1009
1256
  const lastAssistantText = getMessageText(assistantMessage).trim();
@@ -1048,19 +1295,7 @@ export class LingyunAgent {
1048
1295
  allowExternalPaths: this.allowExternalPaths,
1049
1296
  signal,
1050
1297
  });
1051
- const { selected, unknown } = selectSkillsForText(text, index);
1052
- if (unknown.length > 0) {
1053
- const availableSample = index.skills
1054
- .map((s) => s.name)
1055
- .slice(0, 20);
1056
- const availableLabel = availableSample.length > 0
1057
- ? ` Available: ${availableSample.map((n) => `$${n}`).join(', ')}${index.skills.length > availableSample.length ? ', ...' : ''}`
1058
- : '';
1059
- callbacks?.onNotice?.({
1060
- level: 'warning',
1061
- message: `Unknown skills: ${unknown.map((n) => `$${n}`).join(', ')}.${availableLabel}`,
1062
- });
1063
- }
1298
+ const { selected } = selectSkillsForText(text, index);
1064
1299
  if (selected.length === 0)
1065
1300
  return;
1066
1301
  const maxSkills = this.skillsConfig.maxInjectSkills;
@@ -1080,6 +1315,9 @@ export class LingyunAgent {
1080
1315
  for (const skill of selectedForInject) {
1081
1316
  if (signal?.aborted)
1082
1317
  break;
1318
+ if (!session.mentionedSkills.includes(skill.name)) {
1319
+ session.mentionedSkills.push(skill.name);
1320
+ }
1083
1321
  let body;
1084
1322
  try {
1085
1323
  body = (await loadSkillFile(skill)).content;