feat: align self-iteration builtin tool with shared runtime and inspector patterns (#14827)

This commit is contained in:
AmAzing-
2026-05-16 13:52:08 +08:00
committed by GitHub
parent 8c4fbf4a81
commit 7b61b9526f
17 changed files with 654 additions and 163 deletions
+8
View File
@@ -237,6 +237,14 @@
"builtins.lobe-page-agent.apiName.updateNode": "Update node",
"builtins.lobe-page-agent.apiName.wrapNodes": "Wrap nodes",
"builtins.lobe-page-agent.title": "Page",
"builtins.lobe-self-feedback-intent.apiName.declareSelfFeedbackIntent": "Record improvement idea",
"builtins.lobe-self-feedback-intent.inspector.gap.proposal": "Suggest improvement",
"builtins.lobe-self-feedback-intent.inspector.memory.write": "Record preference",
"builtins.lobe-self-feedback-intent.inspector.rejected": "Not recorded",
"builtins.lobe-self-feedback-intent.inspector.skill.consolidate": "Organize methods",
"builtins.lobe-self-feedback-intent.inspector.skill.create": "New method found",
"builtins.lobe-self-feedback-intent.inspector.skill.refine": "Improve method",
"builtins.lobe-self-feedback-intent.title": "Improvement Ideas",
"builtins.lobe-skill-store.apiName.importFromMarket": "Import from Market",
"builtins.lobe-skill-store.apiName.importSkill": "Import Skill",
"builtins.lobe-skill-store.apiName.searchSkill": "Search Skills",
+8
View File
@@ -237,6 +237,14 @@
"builtins.lobe-page-agent.apiName.updateNode": "更新节点",
"builtins.lobe-page-agent.apiName.wrapNodes": "包装节点",
"builtins.lobe-page-agent.title": "文档",
"builtins.lobe-self-feedback-intent.apiName.declareSelfFeedbackIntent": "记录改进想法",
"builtins.lobe-self-feedback-intent.inspector.gap.proposal": "提出改进建议",
"builtins.lobe-self-feedback-intent.inspector.memory.write": "记下偏好",
"builtins.lobe-self-feedback-intent.inspector.rejected": "未记录",
"builtins.lobe-self-feedback-intent.inspector.skill.consolidate": "整理方法",
"builtins.lobe-self-feedback-intent.inspector.skill.create": "发现新方法",
"builtins.lobe-self-feedback-intent.inspector.skill.refine": "改进方法",
"builtins.lobe-self-feedback-intent.title": "改进想法",
"builtins.lobe-skill-store.apiName.importFromMarket": "从市场导入",
"builtins.lobe-skill-store.apiName.importSkill": "导入技能",
"builtins.lobe-skill-store.apiName.searchSkill": "搜索技能",
@@ -4,11 +4,20 @@
"private": true,
"type": "module",
"exports": {
".": "./src/index.ts"
".": "./src/index.ts",
"./client": "./src/client/index.ts",
"./executionRuntime": "./src/ExecutionRuntime/index.ts"
},
"main": "./src/index.ts",
"devDependencies": {
"@lobechat/context-engine": "workspace:*",
"@lobechat/types": "workspace:*"
},
"peerDependencies": {
"@lobehub/ui": "^5",
"antd-style": "*",
"lucide-react": "*",
"react": "*",
"react-i18next": "*"
}
}
@@ -0,0 +1,89 @@
import type { BuiltinServerRuntimeOutput } from '@lobechat/types';
import type {
DeclareSelfFeedbackIntentContext,
DeclareSelfFeedbackIntentInput,
DeclareSelfFeedbackIntentPayload,
DeclareSelfFeedbackIntentResult,
DeclareSelfFeedbackIntentState,
} from '../types';
export interface SelfFeedbackIntentRuntimeService {
declareIntent: (
input: DeclareSelfFeedbackIntentInput,
) => Promise<DeclareSelfFeedbackIntentResult>;
}
export interface SelfFeedbackIntentExecutionRuntimeOptions {
service: SelfFeedbackIntentRuntimeService;
}
const REQUIRED_CONTEXT_KEYS = ['agentId', 'userId', 'topicId'];
const createJsonOutput = (
state: DeclareSelfFeedbackIntentState,
success: boolean,
): BuiltinServerRuntimeOutput => ({
content: JSON.stringify(state),
state,
success,
});
export class SelfFeedbackIntentExecutionRuntime {
private service: SelfFeedbackIntentRuntimeService;
constructor(options: SelfFeedbackIntentExecutionRuntimeOptions) {
this.service = options.service;
}
declareSelfFeedbackIntent = async (
input: DeclareSelfFeedbackIntentPayload,
context: DeclareSelfFeedbackIntentContext = {},
): Promise<BuiltinServerRuntimeOutput> => {
const { agentId, operationId, toolCallId, topicId, userId } = context;
if (!agentId || !userId || !topicId) {
return createJsonOutput(
{
accepted: false,
reason: 'missing_context',
required: REQUIRED_CONTEXT_KEYS,
},
false,
);
}
try {
const result = await this.service.declareIntent({
agentId,
input,
topicId,
userId,
...(operationId ? { operationId } : {}),
...(toolCallId ? { toolCallId } : {}),
});
return createJsonOutput(
{
accepted: result.accepted,
reason: result.reason ?? null,
sourceId: result.sourceId ?? null,
strength: result.strength,
},
true,
);
} catch (e) {
const message = e instanceof Error ? e.message : 'Unknown self-feedback intent error';
return {
content: `declareSelfFeedbackIntent with error detail: ${message}`,
error: { message },
state: {
accepted: false,
reason: 'runtime_error',
} satisfies DeclareSelfFeedbackIntentState,
success: false,
};
}
};
}
@@ -0,0 +1,116 @@
'use client';
import type { BuiltinInspectorProps } from '@lobechat/types';
import { Icon } from '@lobehub/ui';
import { createStaticStyles, cx } from 'antd-style';
import { CheckCircle2, CircleAlert } from 'lucide-react';
import { memo } from 'react';
import { useTranslation } from 'react-i18next';
import { highlightTextStyles, inspectorTextStyles, shinyTextStyles } from '@/styles';
import type {
DeclareSelfFeedbackIntentParams,
DeclareSelfFeedbackIntentState,
} from '../../../types';
const getIntentLabelKey = (data?: Partial<DeclareSelfFeedbackIntentParams>) => {
if (data?.kind === 'memory' && data.action === 'write') {
return 'builtins.lobe-self-feedback-intent.inspector.memory.write';
}
if (data?.kind === 'skill' && data.action === 'create') {
return 'builtins.lobe-self-feedback-intent.inspector.skill.create';
}
if (data?.kind === 'skill' && data.action === 'refine') {
return 'builtins.lobe-self-feedback-intent.inspector.skill.refine';
}
if (data?.kind === 'skill' && data.action === 'consolidate') {
return 'builtins.lobe-self-feedback-intent.inspector.skill.consolidate';
}
if (data?.kind === 'gap' && data.action === 'proposal') {
return 'builtins.lobe-self-feedback-intent.inspector.gap.proposal';
}
return 'builtins.lobe-self-feedback-intent.apiName.declareSelfFeedbackIntent';
};
const styles = createStaticStyles(({ css, cssVar }) => ({
iconAccepted: css`
flex-shrink: 0;
color: ${cssVar.colorSuccess};
`,
iconRejected: css`
flex-shrink: 0;
color: ${cssVar.colorWarning};
`,
meta: css`
flex-shrink: 0;
font-size: 12px;
color: ${cssVar.colorTextTertiary};
`,
summary: css`
overflow: hidden;
min-width: 0;
max-width: 320px;
text-overflow: ellipsis;
white-space: nowrap;
`,
}));
export const DeclareSelfFeedbackIntentInspector = memo<
BuiltinInspectorProps<DeclareSelfFeedbackIntentParams, DeclareSelfFeedbackIntentState>
>(({ args, partialArgs, isArgumentsStreaming, isLoading, pluginState }) => {
const { t } = useTranslation('plugin');
const data = args ?? partialArgs;
const summary = data?.summary;
const hasContext = Boolean(summary || data?.kind || data?.action);
const title = t(getIntentLabelKey(data));
if (isArgumentsStreaming && !hasContext) {
return (
<div className={cx(inspectorTextStyles.root, shinyTextStyles.shinyText)}>
<span>{title}</span>
</div>
);
}
const isSettled = !isArgumentsStreaming && !isLoading && !!pluginState;
return (
<div
style={{ flexWrap: 'wrap', gap: 4 }}
className={cx(
inspectorTextStyles.root,
(isArgumentsStreaming || isLoading) && shinyTextStyles.shinyText,
)}
>
<span>{title}</span>
{summary && (
<span className={cx(highlightTextStyles.primary, styles.summary)}>{summary}</span>
)}
{isSettled &&
pluginState &&
(pluginState.accepted ? (
<Icon className={styles.iconAccepted} icon={CheckCircle2} size={14} />
) : (
<>
<Icon className={styles.iconRejected} icon={CircleAlert} size={14} />
<span className={styles.meta}>
{t('builtins.lobe-self-feedback-intent.inspector.rejected')}
</span>
</>
))}
</div>
);
});
DeclareSelfFeedbackIntentInspector.displayName = 'DeclareSelfFeedbackIntentInspector';
export default DeclareSelfFeedbackIntentInspector;
@@ -0,0 +1,11 @@
import type { BuiltinInspector } from '@lobechat/types';
import { SelfFeedbackIntentApiName } from '../../types';
import { DeclareSelfFeedbackIntentInspector } from './DeclareSelfFeedbackIntent';
export const SelfFeedbackIntentInspectors: Record<string, BuiltinInspector> = {
[SelfFeedbackIntentApiName.declareSelfFeedbackIntent]:
DeclareSelfFeedbackIntentInspector as BuiltinInspector,
};
export { DeclareSelfFeedbackIntentInspector } from './DeclareSelfFeedbackIntent';
@@ -0,0 +1,4 @@
export { DeclareSelfFeedbackIntentInspector, SelfFeedbackIntentInspectors } from './Inspector';
export { selfFeedbackIntentManifest } from '../manifest';
export * from '../types';
@@ -1,11 +1,12 @@
import type { LobeToolManifest, OperationToolSet, ToolSource } from '@lobechat/context-engine';
import { describe, expect, it } from 'vitest';
import { describe, expect, it, vi } from 'vitest';
import {
injectSelfFeedbackIntentTool,
SELF_FEEDBACK_INTENT_API_NAME,
SELF_FEEDBACK_INTENT_IDENTIFIER,
SELF_FEEDBACK_INTENT_TOOL_NAME,
SelfFeedbackIntentExecutionRuntime,
selfFeedbackIntentManifest,
shouldExposeSelfFeedbackIntentTool,
} from './index';
@@ -103,6 +104,7 @@ describe('selfFeedbackIntentTool', () => {
'skillId',
]);
expect(api.description).toContain('does not mutate memory or skills');
expect(properties.evidenceRefs.items.properties.summary).toBeDefined();
expect(api.parameters.required).toEqual([
'action',
'kind',
@@ -110,6 +112,93 @@ describe('selfFeedbackIntentTool', () => {
'summary',
'reason',
]);
expect(selfFeedbackIntentManifest.systemRole).toContain('<aggressive_usage_policy>');
});
});
describe('SelfFeedbackIntentExecutionRuntime', () => {
/**
* @example
* The package runtime delegates declaration emission to an injected service and persists state.
*/
it('delegates declarations to the injected service', async () => {
const service = {
declareIntent: vi.fn().mockResolvedValue({
accepted: true,
sourceId: 'self-feedback-intent:user-1:agent-1:topic:topic-1:tool-call-1',
strength: 'strong' as const,
}),
};
const runtime = new SelfFeedbackIntentExecutionRuntime({ service });
const result = await runtime.declareSelfFeedbackIntent(
{
action: 'refine',
confidence: 0.91,
evidenceRefs: [{ id: 'msg-1', type: 'message' }],
kind: 'skill',
reason: 'The release workflow correction should become reusable.',
summary: 'Refine the release workflow skill.',
},
{
agentId: 'agent-1',
toolCallId: 'tool-call-1',
topicId: 'topic-1',
userId: 'user-1',
},
);
expect(result.success).toBe(true);
expect(result.state).toEqual({
accepted: true,
reason: null,
sourceId: 'self-feedback-intent:user-1:agent-1:topic:topic-1:tool-call-1',
strength: 'strong',
});
expect(service.declareIntent).toHaveBeenCalledWith({
agentId: 'agent-1',
input: {
action: 'refine',
confidence: 0.91,
evidenceRefs: [{ id: 'msg-1', type: 'message' }],
kind: 'skill',
reason: 'The release workflow correction should become reusable.',
summary: 'Refine the release workflow skill.',
},
toolCallId: 'tool-call-1',
topicId: 'topic-1',
userId: 'user-1',
});
});
/**
* @example
* Missing runtime identity context returns a tool failure before crossing service boundaries.
*/
it('returns missing context failure without calling the service', async () => {
const service = {
declareIntent: vi.fn(),
};
const runtime = new SelfFeedbackIntentExecutionRuntime({ service });
const result = await runtime.declareSelfFeedbackIntent(
{
action: 'proposal',
confidence: 0.6,
kind: 'gap',
reason: 'The inspector is missing.',
summary: 'Add a self-feedback inspector.',
},
{ agentId: 'agent-1', userId: 'user-1' },
);
expect(result.success).toBe(false);
expect(JSON.parse(result.content)).toEqual({
accepted: false,
reason: 'missing_context',
required: ['agentId', 'userId', 'topicId'],
});
expect(service.declareIntent).not.toHaveBeenCalled();
});
});
@@ -1,12 +1,32 @@
export * from './ExecutionRuntime';
export {
injectSelfFeedbackIntentTool,
type SelfFeedbackIntentToolSetParts,
shouldExposeSelfFeedbackIntentTool,
} from './inject';
export { selfFeedbackIntentManifest } from './manifest';
export { systemPrompt } from './systemRole';
export {
type DeclareSelfFeedbackIntentContext,
type DeclareSelfFeedbackIntentInput,
type DeclareSelfFeedbackIntentParams,
type DeclareSelfFeedbackIntentPayload,
type DeclareSelfFeedbackIntentRejectionReason,
type DeclareSelfFeedbackIntentResult,
type DeclareSelfFeedbackIntentState,
type DeclareSelfFeedbackIntentStateReason,
SELF_FEEDBACK_INTENT_API_NAME,
SELF_FEEDBACK_INTENT_ACTIONS,
SELF_FEEDBACK_INTENT_EVIDENCE_REF_TYPES,
SELF_FEEDBACK_INTENT_IDENTIFIER,
SELF_FEEDBACK_INTENT_KINDS,
SELF_FEEDBACK_INTENT_TOOL_NAME,
type ShouldExposeSelfFeedbackIntentToolOptions,
SelfFeedbackIntentApiName,
type SelfFeedbackIntentApiNameType,
type SelfFeedbackIntentAction,
type SelfFeedbackIntentEvidenceRef,
type SelfFeedbackIntentEvidenceRefType,
type SelfFeedbackIntentKind,
type SelfFeedbackIntentStrength,
} from './types';
@@ -1,6 +1,13 @@
import type { BuiltinToolManifest } from '@lobechat/types';
import { SELF_FEEDBACK_INTENT_API_NAME, SELF_FEEDBACK_INTENT_IDENTIFIER } from './types';
import { systemPrompt } from './systemRole';
import {
SELF_FEEDBACK_INTENT_ACTIONS,
SELF_FEEDBACK_INTENT_EVIDENCE_REF_TYPES,
SELF_FEEDBACK_INTENT_IDENTIFIER,
SELF_FEEDBACK_INTENT_KINDS,
SelfFeedbackIntentApiName,
} from './types';
/**
* Self-iteration intent builtin tool manifest.
@@ -19,44 +26,54 @@ export const selfFeedbackIntentManifest = {
api: [
{
description:
'Declare advisory self-feedback intent for future review. This only records intent and does not mutate memory or skills.',
name: SELF_FEEDBACK_INTENT_API_NAME,
'Declare advisory self-feedback intent for future review whenever the running agent finds a concrete, reusable improvement opportunity. Use this proactively for memory, skill, or system gap feedback; it only records intent and does not mutate memory or skills.',
name: SelfFeedbackIntentApiName.declareSelfFeedbackIntent,
parameters: {
additionalProperties: false,
properties: {
action: {
description: 'Self-iteration action the agent believes may be useful.',
enum: ['write', 'create', 'refine', 'consolidate', 'proposal'],
description:
'Self-iteration action the agent believes may be useful. Use write for memory, create/refine/consolidate for skills, and proposal for system or workflow gaps.',
enum: [...SELF_FEEDBACK_INTENT_ACTIONS],
type: 'string',
},
kind: {
description: 'Self-iteration target category for the declaration.',
enum: ['memory', 'skill', 'gap'],
description:
'Self-iteration target category: memory for durable user signals, skill for reusable procedures or capabilities, gap for product/runtime/tooling/policy feedback.',
enum: [...SELF_FEEDBACK_INTENT_KINDS],
type: 'string',
},
confidence: {
description: 'Agent confidence from 0 to 1.',
description:
'Agent confidence from 0 to 1 that this declaration is worth downstream review. Prefer >=0.75 for well-grounded evidence and 0.45-0.74 for plausible but review-needed feedback.',
maximum: 1,
minimum: 0,
type: 'number',
},
summary: {
description: 'Short summary of the self-feedback intent.',
description:
'Short, actionable summary of the self-feedback intent. Name the target and desired improvement.',
type: 'string',
},
reason: {
description: 'Rationale for why this self-feedback intent may be useful.',
description:
'Rationale explaining the triggering evidence, why it matters, and the expected future benefit.',
type: 'string',
},
evidenceRefs: {
description: 'Optional references that justify the declaration.',
description:
'Optional stable references that justify the declaration. Prefer concrete message, tool_call, operation, topic, receipt, task, agent_document, or memory refs.',
items: {
additionalProperties: false,
properties: {
id: { description: 'Stable evidence identifier.', type: 'string' },
summary: {
description: 'Optional short note explaining why this evidence matters.',
type: 'string',
},
type: {
description: 'Evidence object type.',
enum: ['message', 'tool_call', 'receipt', 'document', 'custom'],
enum: [...SELF_FEEDBACK_INTENT_EVIDENCE_REF_TYPES],
type: 'string',
},
},
@@ -79,13 +96,13 @@ export const selfFeedbackIntentManifest = {
},
},
],
executors: ['server'],
identifier: SELF_FEEDBACK_INTENT_IDENTIFIER,
meta: {
description:
'Let a running agent declare advisory self-feedback intent without mutating memory or skills directly.',
'Let a running agent proactively declare advisory self-feedback intent without mutating memory or skills directly.',
title: 'Self Feedback Intent',
},
systemRole:
'Declare advisory self-feedback intent only when future self-review may improve memory or skills. This tool records intent and must not claim that it directly mutates resources.',
systemRole: systemPrompt,
type: 'builtin',
} as const satisfies BuiltinToolManifest;
@@ -0,0 +1,46 @@
export const systemPrompt = `You have access to the Self Feedback Intent tool. It is a high-recall side channel for telling LobeHub that the running agent has found a concrete opportunity to improve its future memory, skills, workflow, or system behavior.
<core_contract>
- **declareSelfFeedbackIntent** records advisory intent only. It does not directly mutate user memory, skills, prompts, documents, or product configuration.
- Downstream self-iteration reviewers own validation, dedupe, approval, and any eventual write/refine/create/consolidate action.
- Use this tool to make the system aware of what the agent thinks should be reviewed later, even when the current user task should continue normally.
</core_contract>
<aggressive_usage_policy>
- Be proactive. Declare self-feedback intent whenever a specific, reusable improvement is visible and can be grounded in the current run.
- Prefer declaring a concise intent over silently losing a useful learning signal. The downstream pipeline can reject weak, duplicate, or unsafe declarations.
- Emit at most the strongest 1-3 declarations per operation or topic. Do not spam vague reflections, stylistic preferences, or duplicate signals.
- Use confidence >= 0.75 when you have concrete evidence refs and a clear future benefit. Use 0.45-0.74 for plausible but review-needed improvements. Avoid calls below 0.45 unless the gap is operationally important.
</aggressive_usage_policy>
<when_to_call>
Call **declareSelfFeedbackIntent** when any of these happen:
- The user corrects the agent, asks "remember next time", points out a repeated miss, or gives feedback that should improve future behavior.
- The agent discovers a reusable workflow, checklist, prompt pattern, tool-use strategy, or coding/review heuristic that should become or refine a skill.
- The agent sees an outdated, incomplete, duplicated, or missing memory signal that should be reviewed before future conversations.
- A tool, runtime, inspector, prompt, policy, or routing behavior caused friction and a concrete system gap should be reviewed.
- A task succeeds only after a non-obvious fix, workaround, or diagnosis that future agents should reuse.
</when_to_call>
<action_kind_mapping>
- **kind=memory + action=write**: durable user preference, identity/context/experience signal, or stale/missing memory worth review.
- **kind=skill + action=create**: a reusable procedure or capability does not exist yet.
- **kind=skill + action=refine**: an existing skill should be sharpened, corrected, made more aggressive, or expanded with examples.
- **kind=skill + action=consolidate**: multiple overlapping skills or procedures should be merged.
- **kind=gap + action=proposal**: product/runtime/tooling/policy gaps, missing UI, weak inspector, poor evidence capture, or unsupported automation ideas.
</action_kind_mapping>
<argument_rules>
- **summary**: one short, actionable sentence. Name the target and desired improvement.
- **reason**: include the triggering evidence, why it matters, and the expected future benefit.
- **confidence**: calibrated probability that this declaration is worth downstream review, not certainty that a mutation should happen.
- **evidenceRefs**: include stable ids when available. Prefer message, tool_call, operation, topic, receipt, task, agent_document, or memory refs over generic prose.
- **memoryId** and **skillId**: include only when you know the exact existing target. Do not invent ids.
</argument_rules>
<boundaries>
- Do not use this tool as a user-facing answer, apology, or progress update.
- Do not declare secrets, credentials, private keys, or sensitive personal data as self-feedback.
- Do not claim that the declaration saved memory or updated a skill. Say only that the intent was declared when you mention it internally.
- If a direct user request conflicts with self-iteration, satisfy the user request first and only declare concise feedback if it will not distract from the task.
</boundaries>`;
@@ -7,6 +7,151 @@ export const SELF_FEEDBACK_INTENT_API_NAME = 'declareSelfFeedbackIntent';
/** LLM-visible tool name generated from identifier and API name. */
export const SELF_FEEDBACK_INTENT_TOOL_NAME = `${SELF_FEEDBACK_INTENT_IDENTIFIER}____${SELF_FEEDBACK_INTENT_API_NAME}`;
/** Stable API name map used by manifests, runtimes, and inspectors. */
export const SelfFeedbackIntentApiName = {
declareSelfFeedbackIntent: SELF_FEEDBACK_INTENT_API_NAME,
} as const;
export type SelfFeedbackIntentApiNameType =
(typeof SelfFeedbackIntentApiName)[keyof typeof SelfFeedbackIntentApiName];
export const SELF_FEEDBACK_INTENT_ACTIONS = [
'write',
'create',
'refine',
'consolidate',
'proposal',
] as const;
export const SELF_FEEDBACK_INTENT_KINDS = ['memory', 'skill', 'gap'] as const;
export const SELF_FEEDBACK_INTENT_EVIDENCE_REF_TYPES = [
'topic',
'message',
'operation',
'source',
'receipt',
'tool_call',
'task',
'agent_document',
'memory',
] as const;
/** Actions that an agent may declare as self-feedback intent. */
export type SelfFeedbackIntentAction = (typeof SELF_FEEDBACK_INTENT_ACTIONS)[number];
/** Self-feedback target categories accepted from agent-declared intent. */
export type SelfFeedbackIntentKind = (typeof SELF_FEEDBACK_INTENT_KINDS)[number];
/** Evidence reference type accepted by downstream self-iteration handlers. */
export type SelfFeedbackIntentEvidenceRefType =
(typeof SELF_FEEDBACK_INTENT_EVIDENCE_REF_TYPES)[number];
/** Evidence strength assigned to one accepted or rejected declaration. */
export type SelfFeedbackIntentStrength = 'strong' | 'weak';
/** Optional reference that grounds one self-feedback declaration. */
export interface SelfFeedbackIntentEvidenceRef {
/** Stable evidence identifier in its source domain. */
id: string;
/** Optional short note explaining why this evidence matters. */
summary?: string;
/** Evidence object type. */
type: SelfFeedbackIntentEvidenceRefType;
}
/** Input payload declared by the running agent through the self-feedback intent tool. */
export interface DeclareSelfFeedbackIntentPayload {
/** Self-feedback action the agent believes may be useful. */
action: SelfFeedbackIntentAction;
/** Agent confidence from 0 to 1. */
confidence: number;
/** Evidence references that justify the declaration. */
evidenceRefs?: SelfFeedbackIntentEvidenceRef[];
/** Target category for the declaration. */
kind: SelfFeedbackIntentKind;
/** Existing memory id when the declaration targets a known memory. */
memoryId?: string;
/** Human-readable rationale from the agent. */
reason: string;
/** Existing skill id when the declaration targets a known skill. */
skillId?: string;
/** Short declaration summary for downstream review. */
summary: string;
}
export type DeclareSelfFeedbackIntentParams = DeclareSelfFeedbackIntentPayload;
/** Runtime context required to emit one self-feedback declaration. */
export interface DeclareSelfFeedbackIntentContext {
/** Stable agent id associated with the running agent. */
agentId?: string;
/** Runtime operation id when the declaration belongs to a narrower operation scope. */
operationId?: string;
/** Caller-provided tool-call id. */
toolCallId?: string;
/** Current topic id for stable source ids and topic fallback scope. */
topicId?: string;
/** Stable user id associated with the running agent. */
userId?: string;
}
/** Input used by a runtime service to declare one self-feedback source event. */
export interface DeclareSelfFeedbackIntentInput {
/** Stable agent id associated with the running agent. */
agentId: string;
/** Agent-declared self-feedback intent payload. */
input: DeclareSelfFeedbackIntentPayload;
/** Runtime operation id when the declaration belongs to a narrower operation scope. */
operationId?: string;
/** Caller-provided tool-call id. */
toolCallId?: string;
/** Current topic id for stable source ids and topic fallback scope. */
topicId: string;
/** Stable user id associated with the running agent. */
userId: string;
}
export type DeclareSelfFeedbackIntentRejectionReason =
| 'enqueue_gate_rejected'
| 'intent_gate_rejected'
| 'invalid_action'
| 'invalid_confidence'
| 'invalid_kind'
| 'rate_limited';
/** Result returned after one declaration attempt. */
export interface DeclareSelfFeedbackIntentResult {
/** Whether the declaration was accepted and emitted to the enqueue boundary. */
accepted: boolean;
/** Optional rejection reason when no source was enqueued. */
reason?: DeclareSelfFeedbackIntentRejectionReason;
/** Stable source id built for accepted declarations when available. */
sourceId?: string;
/** Evidence strength assigned from confidence and evidence presence. */
strength: SelfFeedbackIntentStrength;
}
export type DeclareSelfFeedbackIntentStateReason =
| DeclareSelfFeedbackIntentRejectionReason
| 'missing_context'
| 'runtime_error'
| null;
/** State persisted for inspector display after one self-feedback declaration. */
export interface DeclareSelfFeedbackIntentState {
/** Whether the declaration crossed the Agent Signal enqueue boundary. */
accepted: boolean;
/** Missing context keys when the runtime cannot emit the declaration. */
required?: string[];
/** Rejection or runtime reason. */
reason: DeclareSelfFeedbackIntentStateReason;
/** Stable source id for accepted declarations. */
sourceId?: null | string;
/** Evidence strength assigned by the declaration service. */
strength?: SelfFeedbackIntentStrength;
}
/** Gate input used to decide whether the declaration tool may be exposed. */
export interface ShouldExposeSelfFeedbackIntentToolOptions {
/** Agent-level self-iteration chat config gate. */
+9 -1
View File
@@ -42,6 +42,10 @@ import {
import { MemoryInspectors, MemoryManifest } from '@lobechat/builtin-tool-memory/client';
import { MessageInspectors, MessageManifest } from '@lobechat/builtin-tool-message/client';
import { PageAgentInspectors, PageAgentManifest } from '@lobechat/builtin-tool-page-agent/client';
import {
SelfFeedbackIntentInspectors,
selfFeedbackIntentManifest,
} from '@lobechat/builtin-tool-self-iteration/client';
import {
SkillStoreInspectors,
SkillStoreManifest,
@@ -57,7 +61,7 @@ import {
WebOnboardingManifest,
} from '@lobechat/builtin-tool-web-onboarding/client';
import { createRunCommandInspector } from '@lobechat/shared-tool-ui/inspectors';
import { type BuiltinInspector } from '@lobechat/types';
import type { BuiltinInspector } from '@lobechat/types';
import { CodexInspectors } from './codex';
import { GithubIdentifier, GithubInspectors } from './github';
@@ -93,6 +97,10 @@ const BuiltinToolInspectors: Record<string, Record<string, BuiltinInspector>> =
[MessageManifest.identifier]: MessageInspectors as Record<string, BuiltinInspector>,
[PageAgentManifest.identifier]: PageAgentInspectors as Record<string, BuiltinInspector>,
[LobeActivatorManifest.identifier]: LobeActivatorInspectors as Record<string, BuiltinInspector>,
[selfFeedbackIntentManifest.identifier]: SelfFeedbackIntentInspectors as Record<
string,
BuiltinInspector
>,
[SkillStoreManifest.identifier]: SkillStoreInspectors as Record<string, BuiltinInspector>,
[SkillsManifest.identifier]: SkillsInspectors as Record<string, BuiltinInspector>,
[TaskManifest.identifier]: TaskInspectors as Record<string, BuiltinInspector>,
+25 -1
View File
@@ -1,8 +1,32 @@
import { type ChildProcess, spawn } from 'node:child_process';
import dotenv from 'dotenv';
import dotenvExpand from 'dotenv-expand';
import net from 'node:net';
dotenv.config();
const env = process.env.NODE_ENV || 'development';
const shellEnv = Object.entries(process.env).reduce<Record<string, string>>(
(acc, [key, value]) => {
if (typeof value === 'string') acc[key] = value;
return acc;
},
{},
);
const dotenvEnv: Record<string, string> = {};
const dotenvResult = dotenv.config({
override: true,
path: ['.env', `.env.${env}`, `.env.${env}.local`],
processEnv: dotenvEnv,
});
if (dotenvResult.parsed) {
const expanded = dotenvExpand.expand({
parsed: dotenvResult.parsed,
processEnv: { ...dotenvEnv, ...shellEnv },
});
Object.assign(process.env, expanded.parsed, shellEnv);
}
const NEXT_HOST = 'localhost';
+8
View File
@@ -290,6 +290,14 @@ export default {
'builtins.lobe-task.runTasks.failedCount': '{{count}} failed',
'builtins.lobe-task.runTasks.more': '+{{count}} more',
'builtins.lobe-task.title': 'Task Tools',
'builtins.lobe-self-feedback-intent.apiName.declareSelfFeedbackIntent': 'Record improvement idea',
'builtins.lobe-self-feedback-intent.inspector.gap.proposal': 'Suggest improvement',
'builtins.lobe-self-feedback-intent.inspector.memory.write': 'Record preference',
'builtins.lobe-self-feedback-intent.inspector.rejected': 'Not recorded',
'builtins.lobe-self-feedback-intent.inspector.skill.consolidate': 'Organize methods',
'builtins.lobe-self-feedback-intent.inspector.skill.create': 'New method found',
'builtins.lobe-self-feedback-intent.inspector.skill.refine': 'Improve method',
'builtins.lobe-self-feedback-intent.title': 'Improvement Ideas',
'builtins.lobe-user-memory.apiName.addContextMemory': 'Add context memory',
'builtins.lobe-user-memory.apiName.addExperienceMemory': 'Add experience memory',
'builtins.lobe-user-memory.apiName.addIdentityMemory': 'Add identity memory',
@@ -1,79 +1,36 @@
import { AGENT_SIGNAL_SOURCE_TYPES } from '@lobechat/agent-signal/source';
import type {
DeclareSelfFeedbackIntentInput,
DeclareSelfFeedbackIntentPayload,
DeclareSelfFeedbackIntentResult,
SelfFeedbackIntentAction,
SelfFeedbackIntentKind,
SelfFeedbackIntentStrength,
} from '@lobechat/builtin-tool-self-iteration';
import {
SELF_FEEDBACK_INTENT_ACTIONS,
SELF_FEEDBACK_INTENT_KINDS,
} from '@lobechat/builtin-tool-self-iteration';
import type { AgentSignalSourceEventInput } from '@/server/services/agentSignal/emitter';
import type { EvidenceRef } from './selfIteration/types';
import { buildSelfFeedbackIntentSourceId } from './selfIteration/types';
export type {
DeclareSelfFeedbackIntentInput,
DeclareSelfFeedbackIntentPayload,
DeclareSelfFeedbackIntentResult,
SelfFeedbackIntentAction,
SelfFeedbackIntentKind,
SelfFeedbackIntentStrength,
} from '@lobechat/builtin-tool-self-iteration';
type MaybePromise<TValue> = TValue | Promise<TValue>;
/** Actions that an agent may declare as self-feedback intent. */
export type SelfFeedbackIntentAction = 'write' | 'create' | 'refine' | 'consolidate' | 'proposal';
/** Self-feedback target categories accepted from agent-declared intent. */
export type SelfFeedbackIntentKind = 'memory' | 'skill' | 'gap';
/** Evidence strength assigned to one accepted or rejected declaration. */
export type SelfFeedbackIntentStrength = 'strong' | 'weak';
/** Source event input emitted by the self-feedback intent declaration service. */
export type SelfFeedbackIntentSourceEventInput =
AgentSignalSourceEventInput<'agent.self_feedback_intent.declared'>;
/** Input payload declared by the running agent through the self-feedback intent tool. */
export interface DeclareSelfFeedbackIntentPayload {
/** Self-feedback action the agent believes may be useful. */
action: SelfFeedbackIntentAction;
/** Agent confidence from 0 to 1. */
confidence: number;
/** Evidence references that justify the declaration. */
evidenceRefs?: EvidenceRef[];
/** Target category for the declaration. */
kind: SelfFeedbackIntentKind;
/** Existing memory id when the declaration targets a known memory. */
memoryId?: string;
/** Human-readable rationale from the agent. */
reason: string;
/** Existing skill id when the declaration targets a known skill. */
skillId?: string;
/** Short declaration summary for downstream review. */
summary: string;
}
/** Input used to declare one agent-facing self-feedback intent source event. */
export interface DeclareSelfFeedbackIntentInput {
/** Stable agent id associated with the running agent. */
agentId: string;
/** Agent-declared self-feedback intent payload. */
input: DeclareSelfFeedbackIntentPayload;
/** Runtime operation id when the declaration belongs to a narrower operation scope. */
operationId?: string;
/** Caller-provided tool-call id. When omitted, the injected id generator is used. */
toolCallId?: string;
/** Current topic id for stable source ids and topic fallback scope. */
topicId: string;
/** Stable user id associated with the running agent. */
userId: string;
}
/** Result returned after one declaration attempt. */
export interface DeclareSelfFeedbackIntentResult {
/** Whether the declaration was accepted and emitted to the enqueue boundary. */
accepted: boolean;
/** Optional rejection reason when no source was enqueued. */
reason?:
| 'enqueue_gate_rejected'
| 'intent_gate_rejected'
| 'invalid_action'
| 'invalid_confidence'
| 'invalid_kind'
| 'rate_limited';
/** Stable source id built for accepted declarations when available. */
sourceId?: string;
/** Evidence strength assigned from confidence and evidence presence. */
strength: SelfFeedbackIntentStrength;
}
/** Dependencies used by the pure self-feedback intent declaration service. */
export interface SelfFeedbackIntentServiceDependencies {
/**
@@ -119,14 +76,8 @@ export interface SelfFeedbackIntentService {
const DECLARATION_LIMIT_PER_SCOPE = 3;
const STRONG_CONFIDENCE_THRESHOLD = 0.75;
const validActions = new Set<SelfFeedbackIntentAction>([
'write',
'create',
'refine',
'consolidate',
'proposal',
]);
const validKinds = new Set<SelfFeedbackIntentKind>(['memory', 'skill', 'gap']);
const validActions = new Set<SelfFeedbackIntentAction>(SELF_FEEDBACK_INTENT_ACTIONS);
const validKinds = new Set<SelfFeedbackIntentKind>(SELF_FEEDBACK_INTENT_KINDS);
const getStrength = (input: DeclareSelfFeedbackIntentPayload): SelfFeedbackIntentStrength => {
if (!input.evidenceRefs?.length || input.confidence < STRONG_CONFIDENCE_THRESHOLD) {
@@ -1,28 +1,12 @@
import { SELF_FEEDBACK_INTENT_IDENTIFIER } from '@lobechat/builtin-tool-self-iteration';
import { SelfFeedbackIntentExecutionRuntime } from '@lobechat/builtin-tool-self-iteration/executionRuntime';
import { nanoid } from '@lobechat/utils';
import { enqueueAgentSignalSourceEvent } from '@/server/services/agentSignal';
import type { DeclareSelfFeedbackIntentPayload } from '@/server/services/agentSignal/services/selfFeedbackIntent';
import { createSelfFeedbackIntentService } from '@/server/services/agentSignal/services/selfFeedbackIntent';
import type { ToolExecutionContext, ToolExecutionResult } from '../types';
import type { ServerRuntimeRegistration } from './types';
type SelfFeedbackIntentToolResultContent = {
accepted: boolean;
reason: null | string;
sourceId: null | string;
strength: 'strong' | 'weak';
};
const createJsonResult = (
content: SelfFeedbackIntentToolResultContent,
success: boolean,
): ToolExecutionResult => ({
content: JSON.stringify(content),
success,
});
const sharedSelfFeedbackIntentService = createSelfFeedbackIntentService({
enqueueSource: (sourceEvent) =>
enqueueAgentSignalSourceEvent(sourceEvent, {
@@ -32,72 +16,26 @@ const sharedSelfFeedbackIntentService = createSelfFeedbackIntentService({
nextToolCallId: () => nanoid(),
});
/**
* Server runtime for advisory self-feedback intent declarations.
*
* Use when:
* - A running agent calls declareSelfFeedbackIntent
* - The server should enqueue Agent Signal source events without mutating resources directly
*
* Expects:
* - Tool execution context includes `agentId`, `userId`, and `topicId`
* - `operationId` and `toolCallId` are used when present for stable source identity
*
* Returns:
* - JSON tool content with accepted status, source id, strength, and rejection reason
*/
class SelfFeedbackIntentRuntime {
declareSelfFeedbackIntent = async (
input: DeclareSelfFeedbackIntentPayload,
context: ToolExecutionContext,
): Promise<ToolExecutionResult> => {
const { agentId, operationId, toolCallId, topicId, userId } = context;
if (!agentId || !userId || !topicId) {
return {
content: JSON.stringify({
accepted: false,
reason: 'missing_context',
required: ['agentId', 'userId', 'topicId'],
}),
success: false,
};
}
const result = await sharedSelfFeedbackIntentService.declareIntent({
agentId,
input,
operationId,
toolCallId,
topicId,
userId,
});
return createJsonResult(
{
accepted: result.accepted,
reason: result.reason ?? null,
sourceId: result.sourceId ?? null,
strength: result.strength,
},
true,
);
};
}
const runtime = new SelfFeedbackIntentExecutionRuntime({
service: sharedSelfFeedbackIntentService,
});
/**
* Registers the self-feedback intent builtin server runtime.
*
* Use when:
* - A running agent calls declareSelfFeedbackIntent
* - The server should enqueue Agent Signal source events without mutating resources directly
* - BuiltinToolsExecutor needs to resolve the injected declaration tool
*
* Expects:
* - Per-call method validation handles required runtime context
* - The package ExecutionRuntime validates per-call `agentId`, `userId`, and `topicId`
* - The shared service preserves fast-loop declaration rate limits
*
* Returns:
* - A lightweight runtime instance for the current execution
* - A shared runtime instance backed by the server Agent Signal enqueue boundary
*/
export const selfFeedbackIntentRuntime: ServerRuntimeRegistration = {
factory: () => new SelfFeedbackIntentRuntime(),
factory: () => runtime,
identifier: SELF_FEEDBACK_INTENT_IDENTIFIER,
};