@bolloon/bolloon-agent 0.1.1 → 0.1.3
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/bin/bolloon-cli.cjs +165 -0
- package/bin/bolloon-daemon.sh +207 -0
- package/bin/bolloon.cmd +11 -0
- package/dist/agents/constraint-layer.js +10 -15
- package/dist/agents/pi-sdk.js +433 -106
- package/dist/agents/protocol.js +82 -1
- package/dist/agents/subagent-manager.js +2 -2
- package/dist/agents/workflow-engine.js +15 -20
- package/dist/agents/workflow-pivot-loop.js +541 -0
- package/dist/bollharness/src/index.js +5 -0
- package/dist/bollharness/src/scripts/checks/check_adr_plan_numbering.js +6 -0
- package/dist/bollharness/src/scripts/checks/check_api_types.js +45 -0
- package/dist/bollharness/src/scripts/checks/check_artifact_link.js +146 -0
- package/dist/bollharness/src/scripts/checks/check_bridge_deps.js +6 -0
- package/dist/bollharness/src/scripts/checks/check_bugfix_binding.js +6 -0
- package/dist/bollharness/src/scripts/checks/check_bugfix_binding_ci.js +6 -0
- package/dist/bollharness/src/scripts/checks/check_doc_file_references.js +6 -0
- package/dist/bollharness/src/scripts/checks/check_doc_freshness.js +135 -0
- package/dist/bollharness/src/scripts/checks/check_doc_links.js +31 -0
- package/dist/bollharness/src/scripts/checks/check_file_existence_claims.js +6 -0
- package/dist/bollharness/src/scripts/checks/check_fragment_integrity.js +34 -0
- package/dist/bollharness/src/scripts/checks/check_hook_installed.js +63 -0
- package/dist/bollharness/src/scripts/checks/check_issue_closure.js +41 -0
- package/dist/bollharness/src/scripts/checks/check_mcp_parity.js +6 -0
- package/dist/bollharness/src/scripts/checks/check_security.js +48 -0
- package/dist/bollharness/src/scripts/checks/check_skill_parity.js +6 -0
- package/dist/bollharness/src/scripts/checks/check_versions.js +6 -0
- package/dist/bollharness/src/scripts/checks/finding.js +13 -0
- package/dist/bollharness/src/scripts/checks/next_decision_number.js +20 -0
- package/dist/bollharness/src/scripts/checks/regenerate_magic_docs.js +6 -0
- package/dist/bollharness/src/scripts/ci/detect_rebaseline_triggers.js +8 -0
- package/dist/bollharness/src/scripts/ci/scan_subprocess_cfg.js +8 -0
- package/dist/bollharness/src/scripts/ci/scan_verify_artifacts.js +8 -0
- package/dist/bollharness/src/scripts/ci/scan_yaml_schema.js +8 -0
- package/dist/bollharness/src/scripts/context_router.js +67 -0
- package/dist/bollharness/src/scripts/deploy-guard.js +157 -0
- package/dist/bollharness/src/scripts/guard-feedback.js +192 -0
- package/dist/bollharness/src/scripts/guard_router.js +158 -0
- package/dist/bollharness/src/scripts/hooks/_hook_output.js +6 -0
- package/dist/bollharness/src/scripts/hooks/auto-python3.js +6 -0
- package/dist/bollharness/src/scripts/hooks/deploy-progress-on-session-end.js +6 -0
- package/dist/bollharness/src/scripts/hooks/failure-analyzer.js +6 -0
- package/dist/bollharness/src/scripts/hooks/gate-judgment-inject.js +92 -0
- package/dist/bollharness/src/scripts/hooks/gate-transition-judgment.js +63 -0
- package/dist/bollharness/src/scripts/hooks/inbox-ack.js +6 -0
- package/dist/bollharness/src/scripts/hooks/inbox-inject-on-start.js +6 -0
- package/dist/bollharness/src/scripts/hooks/inbox-validate.js +6 -0
- package/dist/bollharness/src/scripts/hooks/inbox-write-ledger.js +6 -0
- package/dist/bollharness/src/scripts/hooks/initializer-agent.js +6 -0
- package/dist/bollharness/src/scripts/hooks/loop-detection.js +73 -0
- package/dist/bollharness/src/scripts/hooks/owner-guard.js +6 -0
- package/dist/bollharness/src/scripts/hooks/precompact.js +6 -0
- package/dist/bollharness/src/scripts/hooks/review-agent-gatekeeper.js +6 -0
- package/dist/bollharness/src/scripts/hooks/risk-tracker.js +108 -0
- package/dist/bollharness/src/scripts/hooks/sanitize-on-read.js +6 -0
- package/dist/bollharness/src/scripts/hooks/session-reflection.js +7 -0
- package/dist/bollharness/src/scripts/hooks/session-start-magic-docs.js +7 -0
- package/dist/bollharness/src/scripts/hooks/session-start-reset-risk.js +7 -0
- package/dist/bollharness/src/scripts/hooks/session-start-toolkit-reminder.js +7 -0
- package/dist/bollharness/src/scripts/hooks/stop-evaluator.js +157 -0
- package/dist/bollharness/src/scripts/hooks/tool-call-counter.js +6 -0
- package/dist/bollharness/src/scripts/hooks/trace-analyzer.js +10 -0
- package/dist/bollharness/src/scripts/install/install-trust-token.js +7 -0
- package/dist/bollharness/src/scripts/install/multi_project_registry.js +9 -0
- package/dist/bollharness/src/scripts/install/phase2_auto.js +21 -0
- package/dist/bollharness/src/scripts/install/pre_commit_installer.js +6 -0
- package/dist/bollharness/src/scripts/install/tier_selector.js +7 -0
- package/dist/bollharness/src/scripts/install/transcript_miner.js +7 -0
- package/dist/bollharness/src/scripts/lib/claim_patterns.js +10 -0
- package/dist/bollharness/src/scripts/lib/sanitize_patterns.js +12 -0
- package/dist/bollharness/src/scripts/sanitize.js +6 -0
- package/dist/bollharness-integration/channel-judgment-engine.js +530 -0
- package/dist/bollharness-integration/context-chain-router.js +383 -0
- package/dist/bollharness-integration/context-router-judgment.js +13 -21
- package/dist/bollharness-integration/context-router.js +22 -64
- package/dist/bollharness-integration/gate-state-machine.js +14 -19
- package/dist/bollharness-integration/gate-transition-hooks.js +16 -61
- package/dist/bollharness-integration/guard-checker.js +21 -68
- package/dist/bollharness-integration/index.js +14 -124
- package/dist/bollharness-integration/integration.js +13 -20
- package/dist/bollharness-integration/llm-judgment-engine.js +569 -0
- package/dist/bollharness-integration/skill-adapter.js +18 -64
- package/dist/cli-entry.js +261 -0
- package/dist/constraint-runtime/src/commands.js +17 -7
- package/dist/constraint-runtime/src/constraint/budget.js +1 -6
- package/dist/constraint-runtime/src/constraint/permission.js +1 -6
- package/dist/constraint-runtime/src/models.js +1 -3
- package/dist/constraint-runtime/src/tools.js +17 -7
- package/dist/constraints/index.js +1 -7
- package/dist/documents/reader.js +8 -49
- package/dist/heartbeat/DaemonManager.js +242 -0
- package/dist/heartbeat/HealthMonitor.js +285 -0
- package/dist/heartbeat/StartupVerifier.js +205 -0
- package/dist/heartbeat/Watchdog.js +168 -0
- package/dist/heartbeat/index.js +84 -0
- package/dist/heartbeat/types.js +5 -0
- package/dist/index.js +381 -28
- package/dist/llm/config-store.js +31 -57
- package/dist/llm/llm-judgment-client.js +389 -0
- package/dist/llm/pi-ai.js +9 -52
- package/dist/network/agent-network.js +46 -90
- package/dist/network/hybrid-messenger.js +125 -0
- package/dist/network/iroh-bootstrap.js +38 -0
- package/dist/network/iroh-discovery.js +145 -0
- package/dist/network/iroh-integration.js +9 -16
- package/dist/network/iroh-transport.js +10 -48
- package/dist/network/p2p.js +23 -62
- package/dist/network/storage/adapters/json-adapter.js +4 -42
- package/dist/network/storage/index.js +147 -0
- package/dist/network/storage/types.js +14 -0
- package/dist/pi-ecosystem/index.js +233 -0
- package/dist/pi-ecosystem-colony/index.js +29 -90
- package/dist/pi-ecosystem-goals/index.js +20 -74
- package/dist/pi-ecosystem-judgment/decision.js +29 -47
- package/dist/pi-ecosystem-judgment/distillation.js +16 -29
- package/dist/pi-ecosystem-judgment/human-value-store.js +13 -60
- package/dist/pi-ecosystem-judgment/index.js +21 -74
- package/dist/pi-ecosystem-judgment/value-injection.js +26 -72
- package/dist/pi-ecosystem-mcp/index.js +24 -78
- package/dist/pi-ecosystem-subagents/index.js +20 -69
- package/dist/social/ant-colony/AdaptiveHeartbeat.js +3 -8
- package/dist/social/ant-colony/PheromoneEngine.js +11 -49
- package/dist/social/ant-colony/index.js +6 -0
- package/dist/social/ant-colony/types.js +4 -8
- package/dist/social/channels/ChannelManager.js +8 -46
- package/dist/social/channels/DiapChannelBridge.js +9 -47
- package/dist/social/channels/InterestMatcher.js +2 -7
- package/dist/social/channels/channel-agent-session.js +309 -0
- package/dist/social/channels/channel-heartbeat-agent.js +494 -0
- package/dist/social/channels/diap-doc-parser.js +204 -0
- package/dist/social/channels/harness-workflow-integrator.js +446 -0
- package/dist/social/channels/index.js +9 -0
- package/dist/social/channels/types.js +3 -7
- package/dist/social/global-shared-context.js +6 -47
- package/dist/social/heartbeat.js +29 -72
- package/dist/social/persona/enhanced-persona.js +299 -0
- package/dist/web/client.js +302 -136
- package/dist/web/components/p2p/index.js +159 -9
- package/dist/web/components/p2p/p2p-connection.js +136 -0
- package/dist/web/components/p2p/p2p-manager.js +24 -0
- package/dist/web/components/p2p/p2p-store-memory.js +1 -1
- package/dist/web/components/p2p/types.js +7 -0
- package/dist/web/index.html +5 -0
- package/dist/web/style.css +118 -0
- package/package.json +12 -6
- package/scripts/build-cli.js +206 -0
- package/scripts/postinstall.js +153 -0
- package/src/agents/pi-sdk.ts +347 -28
- package/src/agents/protocol.ts +95 -1
- package/src/agents/workflow-pivot-loop.ts +674 -0
- package/src/bollharness/CLAUDE.md +73 -0
- package/src/bollharness/README.md +143 -0
- package/src/bollharness/README.zh-CN.md +131 -0
- package/src/bollharness/reference/boll-reference/scripts/hooks/stop-evaluator.md +57 -0
- package/src/bollharness/scripts/context-fragments/artifact-linkage.md +14 -0
- package/src/bollharness/scripts/context-fragments/auth-consumers.md +17 -0
- package/src/bollharness/scripts/context-fragments/bridge-constitution.md +13 -0
- package/src/bollharness/scripts/context-fragments/catalyst-distributed.md +18 -0
- package/src/bollharness/scripts/context-fragments/closure-checklist.md +13 -0
- package/src/bollharness/scripts/context-fragments/contract-consumers.md +15 -0
- package/src/bollharness/scripts/context-fragments/db-shared-structures.md +15 -0
- package/src/bollharness/scripts/context-fragments/fixed-three-layers.md +19 -0
- package/src/bollharness/scripts/context-fragments/general-dev-principles.md +11 -0
- package/src/bollharness/scripts/context-fragments/issue-first.md +8 -0
- package/src/bollharness/scripts/context-fragments/mcp-parity.md +16 -0
- package/src/bollharness/scripts/context-fragments/pi-agent-operations.md +108 -0
- package/src/bollharness/scripts/context-fragments/protocol-consumers.md +15 -0
- package/src/bollharness/scripts/context-fragments/run-events-consumers.md +15 -0
- package/src/bollharness/scripts/context-fragments/scene-fidelity.md +13 -0
- package/src/bollharness/scripts/context-fragments/truth-source-hierarchy.md +15 -0
- package/src/bollharness/scripts/context-fragments/two-language.md +15 -0
- package/src/bollharness/scripts/context-fragments/version-sources.md +14 -0
- package/src/bollharness/scripts/hooks/stop-evaluator.md +83 -0
- package/src/bollharness/templates/scaffold/CLAUDE.md +89 -0
- package/src/cli-entry.ts +304 -0
- package/src/heartbeat/DaemonManager.ts +283 -0
- package/src/heartbeat/HealthMonitor.ts +316 -0
- package/src/heartbeat/StartupVerifier.ts +223 -0
- package/src/heartbeat/Watchdog.ts +198 -0
- package/src/heartbeat/index.ts +108 -0
- package/src/heartbeat/types.ts +82 -0
- package/src/llm/config-store.ts +23 -5
- package/src/network/iroh-transport.ts +3 -3
- package/src/web/client.js +302 -136
- package/src/web/components/p2p/P2PModal.tsx +91 -3
- package/src/web/components/p2p/index.ts +171 -9
- package/src/web/components/p2p/p2p-connection.ts +153 -1
- package/src/web/components/p2p/p2p-manager.ts +39 -1
- package/src/web/components/p2p/p2p-store-memory.ts +1 -1
- package/src/web/components/p2p/p2p-tools.ts +315 -0
- package/src/web/components/p2p/types.ts +58 -0
- package/src/web/design.md +99 -0
- package/src/web/index.html +5 -0
- package/src/web/server.ts +353 -36
- package/src/web/style.css +118 -0
- package/tsconfig.cli.json +16 -0
- package/tsconfig.electron.json +1 -1
- package/tsconfig.json +1 -2
- package/dist/web/server.js +0 -1647
- package/dist/web/server.js.map +0 -1
package/src/agents/pi-sdk.ts
CHANGED
|
@@ -12,6 +12,7 @@ import { p2pNetwork } from '../network/p2p.js';
|
|
|
12
12
|
import { ConstraintLayer, WorkflowContext } from './constraint-layer.js';
|
|
13
13
|
import { WorkflowEngine, WorkflowStep, StepResult, Workflow } from './workflow-engine.js';
|
|
14
14
|
import { DeepThinkingEngine, AgentCoordinator, type ThinkResult, type AgentResult } from '@bolloon/constraint-runtime';
|
|
15
|
+
import { WorkflowPivotLoop, createDefaultPivotConfig, type PivotLoopConfig, type LoopResult } from './workflow-pivot-loop.js';
|
|
15
16
|
import {
|
|
16
17
|
DiscoveredAgentsManager,
|
|
17
18
|
SocialHeartbeat,
|
|
@@ -42,6 +43,8 @@ export interface AgentSessionConfig {
|
|
|
42
43
|
cwd: string;
|
|
43
44
|
peerId?: string;
|
|
44
45
|
identityDoc?: IdentityDoc;
|
|
46
|
+
usePivotLoop?: boolean;
|
|
47
|
+
pivotLoopConfig?: PivotLoopConfig;
|
|
45
48
|
}
|
|
46
49
|
|
|
47
50
|
export interface IdentityDoc {
|
|
@@ -487,6 +490,7 @@ export interface HeartbeatConfig {
|
|
|
487
490
|
export interface AgentSession {
|
|
488
491
|
prompt(input: string): Promise<string>;
|
|
489
492
|
promptStream(input: string, onStream: StreamCallback): Promise<string>;
|
|
493
|
+
promptWithPivotLoop(input: string, config?: PivotLoopConfig): Promise<LoopResult>;
|
|
490
494
|
suggestRename(messages: { type: string; content: string }[]): Promise<string | null>;
|
|
491
495
|
readDocument(filePath: string): Promise<string>;
|
|
492
496
|
summarizeDocument(filePath: string, context?: string): Promise<{
|
|
@@ -546,11 +550,15 @@ class PiAgentSession implements AgentSession {
|
|
|
546
550
|
private messageHistory: Message[] = [];
|
|
547
551
|
private tools: Map<string, Tool> = new Map();
|
|
548
552
|
private skillRegistry: SkillRegistry = new SkillRegistry();
|
|
549
|
-
private readonly MAX_REACT_ITERATIONS =
|
|
553
|
+
private readonly MAX_REACT_ITERATIONS = 100;
|
|
554
|
+
private readonly MAX_REFINE_ATTEMPTS = 3;
|
|
555
|
+
private readonly QUALITY_THRESHOLD = 0.6;
|
|
550
556
|
private thinkingEngine = new DeepThinkingEngine(3);
|
|
551
557
|
private coordinator = new AgentCoordinator(3);
|
|
552
558
|
private harness: any = null;
|
|
553
559
|
private harnessEnabled = false;
|
|
560
|
+
private usePivotLoop: boolean = false;
|
|
561
|
+
private pivotLoopConfig?: PivotLoopConfig;
|
|
554
562
|
|
|
555
563
|
constructor(config: AgentSessionConfig) {
|
|
556
564
|
this.cwd = config.cwd;
|
|
@@ -561,6 +569,8 @@ class PiAgentSession implements AgentSession {
|
|
|
561
569
|
this.workflowEngine = new WorkflowEngine(this.constraintLayer);
|
|
562
570
|
this.sessionManager = new PiSessionManager(this.identity.did, this.cwd);
|
|
563
571
|
this.agentsManager = new DiscoveredAgentsManager();
|
|
572
|
+
this.usePivotLoop = config.usePivotLoop ?? false;
|
|
573
|
+
this.pivotLoopConfig = config.pivotLoopConfig;
|
|
564
574
|
this.initSession();
|
|
565
575
|
this.registerTools();
|
|
566
576
|
this.initHarness();
|
|
@@ -739,6 +749,58 @@ class PiAgentSession implements AgentSession {
|
|
|
739
749
|
};
|
|
740
750
|
}
|
|
741
751
|
});
|
|
752
|
+
|
|
753
|
+
// 添加文件列表工具
|
|
754
|
+
this.tools.set('list_files', {
|
|
755
|
+
name: 'list_files',
|
|
756
|
+
description: '列出目录中的文件',
|
|
757
|
+
parameters: { path: '目录路径(可选,默认为当前目录)' },
|
|
758
|
+
execute: async (args) => {
|
|
759
|
+
try {
|
|
760
|
+
const fs = await import('fs');
|
|
761
|
+
const path = args.path || this.cwd;
|
|
762
|
+
const files = fs.readdirSync(path);
|
|
763
|
+
return {
|
|
764
|
+
success: true,
|
|
765
|
+
output: `📁 目录 ${path} 中的文件 (${files.length} 个):\n${files.slice(0, 20).map(f => ` - ${f}`).join('\n')}${files.length > 20 ? '\n ...' : ''}`
|
|
766
|
+
};
|
|
767
|
+
} catch (e) {
|
|
768
|
+
return { success: false, error: String(e) };
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
});
|
|
772
|
+
|
|
773
|
+
// 添加目录读取工具(更完整的实现)
|
|
774
|
+
this.tools.set('read_directory', {
|
|
775
|
+
name: 'read_directory',
|
|
776
|
+
description: '读取目录内容,返回文件列表和目录结构',
|
|
777
|
+
parameters: { path: '目录路径(可选,默认为当前目录)' },
|
|
778
|
+
execute: async (args) => {
|
|
779
|
+
try {
|
|
780
|
+
const fs = await import('fs');
|
|
781
|
+
const pathModule = await import('path');
|
|
782
|
+
const targetPath = args.path || this.cwd;
|
|
783
|
+
const items = fs.readdirSync(targetPath);
|
|
784
|
+
const result: string[] = [];
|
|
785
|
+
for (const item of items.slice(0, 30)) {
|
|
786
|
+
const fullPath = pathModule.join(targetPath, item);
|
|
787
|
+
try {
|
|
788
|
+
const stat = fs.statSync(fullPath);
|
|
789
|
+
const type = stat.isDirectory() ? '📁' : '📄';
|
|
790
|
+
result.push(`${type} ${item}${stat.isDirectory() ? '/' : ''}`);
|
|
791
|
+
} catch {
|
|
792
|
+
result.push(`📄 ${item}`);
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
return {
|
|
796
|
+
success: true,
|
|
797
|
+
output: `📂 ${targetPath} (${items.length} 项):\n${result.join('\n')}${items.length > 30 ? '\n... 还有更多文件' : ''}`
|
|
798
|
+
};
|
|
799
|
+
} catch (e) {
|
|
800
|
+
return { success: false, error: `无法读取目录: ${String(e)}` };
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
});
|
|
742
804
|
}
|
|
743
805
|
|
|
744
806
|
private getToolDefinitions(): string {
|
|
@@ -803,7 +865,7 @@ class PiAgentSession implements AgentSession {
|
|
|
803
865
|
content: input
|
|
804
866
|
});
|
|
805
867
|
|
|
806
|
-
onStream({ type: 'thinking', content: '🤔
|
|
868
|
+
onStream({ type: 'thinking', content: '🤔 开始思考...' });
|
|
807
869
|
|
|
808
870
|
if (!this.minimaxAvailable) {
|
|
809
871
|
const response = await this.handleFallback(input);
|
|
@@ -812,22 +874,113 @@ class PiAgentSession implements AgentSession {
|
|
|
812
874
|
return response;
|
|
813
875
|
}
|
|
814
876
|
|
|
815
|
-
const result = await this.runReActLoop();
|
|
877
|
+
const result = await this.runReActLoop(onStream);
|
|
816
878
|
onStream({ type: 'done', content: '' });
|
|
817
879
|
return result;
|
|
818
880
|
}
|
|
819
881
|
|
|
820
|
-
|
|
882
|
+
async promptWithPivotLoop(input: string, config?: PivotLoopConfig): Promise<LoopResult> {
|
|
883
|
+
if (!this.minimaxAvailable) {
|
|
884
|
+
const response = await this.handleFallback(input);
|
|
885
|
+
return {
|
|
886
|
+
success: false,
|
|
887
|
+
response,
|
|
888
|
+
iterations: 0,
|
|
889
|
+
toolCalls: 0,
|
|
890
|
+
qualityScore: 0,
|
|
891
|
+
exitReason: 'error',
|
|
892
|
+
state: {
|
|
893
|
+
iteration: 0,
|
|
894
|
+
totalTokens: 0,
|
|
895
|
+
toolCallsCount: 0,
|
|
896
|
+
consecutiveNoProgress: 0,
|
|
897
|
+
qualityScores: [],
|
|
898
|
+
pendingToolUses: [],
|
|
899
|
+
lastMeaningfulWork: 0
|
|
900
|
+
}
|
|
901
|
+
};
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
const llm = getMinimax();
|
|
905
|
+
const loopConfig = config || this.pivotLoopConfig || createDefaultPivotConfig();
|
|
906
|
+
const loop = new WorkflowPivotLoop(loopConfig);
|
|
907
|
+
|
|
908
|
+
for (const tool of this.tools.values()) {
|
|
909
|
+
loop.registerTool(tool);
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
const personaSection = this.persona ? `
|
|
913
|
+
角色描述: ${this.persona.description || '无'}
|
|
914
|
+
性格特点: ${this.persona.personality || '无'}
|
|
915
|
+
问候语: ${this.persona.greeting || '无'}
|
|
916
|
+
` : '';
|
|
917
|
+
|
|
918
|
+
const systemPrompt = `你是 ${this.identity.name},基于ReAct (Reasoning + Acting)模式工作。${personaSection}
|
|
919
|
+
当前工作目录: ${this.cwd}
|
|
920
|
+
当前身份: ${this.identity.name} (${this.identity.did})
|
|
921
|
+
|
|
922
|
+
${this.getToolDefinitions()}
|
|
923
|
+
|
|
924
|
+
工作模式:
|
|
925
|
+
1. 理解用户自然语言请求
|
|
926
|
+
2. 分析需要哪些工具来完成
|
|
927
|
+
3. 按顺序调用工具并观察结果
|
|
928
|
+
4. 根据观察结果决定下一步
|
|
929
|
+
5. 最终给出完整回答
|
|
930
|
+
|
|
931
|
+
重要:
|
|
932
|
+
- 每次只调用一个工具
|
|
933
|
+
- 仔细分析工具返回结果
|
|
934
|
+
- 当任务完成时,必须在回答末尾添加 <final gen> 标记表示结束
|
|
935
|
+
- 如果需要更多信息,继续调用工具`;
|
|
936
|
+
|
|
937
|
+
const result = await loop.execute(input, llm, systemPrompt);
|
|
938
|
+
|
|
939
|
+
this.messageHistory.push({ role: 'user', content: input });
|
|
940
|
+
if (result.response) {
|
|
941
|
+
this.messageHistory.push({ role: 'assistant', content: result.response });
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
return result;
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
private async runReActLoop(onStream?: StreamCallback): Promise<string> {
|
|
821
948
|
const llm = getMinimax();
|
|
822
949
|
let iteration = 0;
|
|
823
950
|
let finalResponse = '';
|
|
951
|
+
let lastQualityScore = 0;
|
|
952
|
+
let refineAttempts = 0;
|
|
953
|
+
let consecutiveErrors = 0;
|
|
954
|
+
const MAX_CONSECUTIVE_ERRORS = 3;
|
|
955
|
+
|
|
956
|
+
// 发送循环开始的事件
|
|
957
|
+
if (onStream) {
|
|
958
|
+
onStream({ type: 'status', content: '🔄 开始 ReAct 循环...', tool: 'system' });
|
|
959
|
+
}
|
|
824
960
|
|
|
825
961
|
while (iteration < this.MAX_REACT_ITERATIONS) {
|
|
826
962
|
iteration++;
|
|
827
963
|
|
|
964
|
+
// 调试日志:显示每次循环开始
|
|
965
|
+
console.log(`[PiAgent] 循环 ${iteration}/${this.MAX_REACT_ITERATIONS} 开始`);
|
|
966
|
+
if (onStream) {
|
|
967
|
+
onStream({ type: 'status', content: `🔄 循环 ${iteration}/${this.MAX_REACT_ITERATIONS}`, tool: 'loop' });
|
|
968
|
+
}
|
|
969
|
+
|
|
828
970
|
const context = this.buildContext();
|
|
829
971
|
const toolDefs = this.getToolDefinitions();
|
|
830
972
|
|
|
973
|
+
// 动态构建 refine 上下文
|
|
974
|
+
let refineContext = '';
|
|
975
|
+
if (refineAttempts > 0 && lastQualityScore < this.QUALITY_THRESHOLD) {
|
|
976
|
+
refineContext = `\n【改进提示】上轮结果质量分 ${(lastQualityScore * 10).toFixed(1)}/10,请改进回答。`;
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
// 连续错误时的额外提示
|
|
980
|
+
if (consecutiveErrors > 0) {
|
|
981
|
+
refineContext += `\n【错误提示】上轮发生 ${consecutiveErrors} 次错误,请重新分析问题或换一种方式处理。`;
|
|
982
|
+
}
|
|
983
|
+
|
|
831
984
|
const personaSection = this.persona ? `
|
|
832
985
|
角色描述: ${this.persona.description || '无'}
|
|
833
986
|
性格特点: ${this.persona.personality || '无'}
|
|
@@ -837,6 +990,7 @@ class PiAgentSession implements AgentSession {
|
|
|
837
990
|
const systemPrompt = `你是 ${this.identity.name},基于ReAct (Reasoning + Acting)模式工作。${personaSection}
|
|
838
991
|
当前工作目录: ${this.cwd}
|
|
839
992
|
当前身份: ${this.identity.name} (${this.identity.did})
|
|
993
|
+
${refineContext}
|
|
840
994
|
|
|
841
995
|
${toolDefs}
|
|
842
996
|
|
|
@@ -850,13 +1004,30 @@ ${toolDefs}
|
|
|
850
1004
|
重要:
|
|
851
1005
|
- 每次只调用一个工具
|
|
852
1006
|
- 仔细分析工具返回结果
|
|
853
|
-
-
|
|
1007
|
+
- 当任务完成时,必须在回答末尾添加 <final gen> 标记表示结束
|
|
854
1008
|
- 如果需要更多信息,继续调用工具`;
|
|
855
1009
|
|
|
856
1010
|
const response = await llm.chat(context, systemPrompt);
|
|
857
1011
|
const reply = response.reply.trim();
|
|
858
1012
|
|
|
1013
|
+
console.log(`[PiAgent] LLM 回复长度: ${reply.length}, 内容预览: "${reply.substring(0, 80)}..."`);
|
|
1014
|
+
|
|
1015
|
+
// 通知前端:收到 LLM 回复
|
|
1016
|
+
if (onStream) {
|
|
1017
|
+
onStream({ type: 'token', content: reply.substring(0, 100) });
|
|
1018
|
+
}
|
|
1019
|
+
|
|
859
1020
|
if (this.isFinalResponse(reply)) {
|
|
1021
|
+
// 检查质量分数
|
|
1022
|
+
lastQualityScore = this.estimateResponseQuality(reply);
|
|
1023
|
+
|
|
1024
|
+
// 如果质量太低且还有改进机会,进入改进循环
|
|
1025
|
+
if (lastQualityScore < this.QUALITY_THRESHOLD && refineAttempts < this.MAX_REFINE_ATTEMPTS) {
|
|
1026
|
+
refineAttempts++;
|
|
1027
|
+
console.log(`[PiAgent] 质量评分 ${(lastQualityScore * 10).toFixed(1)}/10 < ${(this.QUALITY_THRESHOLD * 10).toFixed(1)}/10,自动改进中 (${refineAttempts}/${this.MAX_REFINE_ATTEMPTS})`);
|
|
1028
|
+
continue;
|
|
1029
|
+
}
|
|
1030
|
+
|
|
860
1031
|
finalResponse = this.extractFinalAnswer(reply);
|
|
861
1032
|
break;
|
|
862
1033
|
}
|
|
@@ -869,30 +1040,132 @@ ${toolDefs}
|
|
|
869
1040
|
toolCall
|
|
870
1041
|
});
|
|
871
1042
|
|
|
1043
|
+
// 通知前端:检测到工具调用
|
|
1044
|
+
if (onStream) {
|
|
1045
|
+
onStream({ type: 'tool', content: `🔧 调用工具: ${toolCall.name}`, tool: toolCall.name });
|
|
1046
|
+
if (toolCall.args && Object.keys(toolCall.args).length > 0) {
|
|
1047
|
+
onStream({ type: 'status', content: `📋 参数: ${JSON.stringify(toolCall.args)}`, tool: toolCall.name });
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
|
|
872
1051
|
const tool = this.tools.get(toolCall.name);
|
|
873
1052
|
if (!tool) {
|
|
1053
|
+
consecutiveErrors++;
|
|
874
1054
|
const errorResult: ToolResult = { success: false, error: `未知工具: ${toolCall.name}` };
|
|
875
1055
|
this.messageHistory.push({ role: 'tool', content: JSON.stringify(errorResult), toolResult: errorResult });
|
|
876
1056
|
this.logToHarness(toolCall.name, toolCall.args, errorResult);
|
|
1057
|
+
console.warn(`[PiAgent] 未知工具: ${toolCall.name},跳过并继续`);
|
|
877
1058
|
continue;
|
|
878
1059
|
}
|
|
879
1060
|
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
1061
|
+
try {
|
|
1062
|
+
const result = await tool.execute(toolCall.args);
|
|
1063
|
+
console.log(`[PiAgent] 工具 ${toolCall.name} 执行完成: success=${result.success}`);
|
|
1064
|
+
this.messageHistory.push({ role: 'tool', content: JSON.stringify(result), toolResult: result });
|
|
1065
|
+
this.logToHarness(toolCall.name, toolCall.args, result);
|
|
1066
|
+
|
|
1067
|
+
// 通知前端工具执行结果
|
|
1068
|
+
if (onStream) {
|
|
1069
|
+
if (result.success) {
|
|
1070
|
+
onStream({ type: 'status', content: `✅ ${toolCall.name} 执行成功`, tool: toolCall.name });
|
|
1071
|
+
if (result.output) {
|
|
1072
|
+
const outputPreview = result.output.substring(0, 200);
|
|
1073
|
+
onStream({ type: 'tool', content: `📤 结果: ${outputPreview}${result.output.length > 200 ? '...' : ''}`, tool: toolCall.name });
|
|
1074
|
+
}
|
|
1075
|
+
} else {
|
|
1076
|
+
onStream({ type: 'error', content: `❌ ${toolCall.name} 执行失败: ${result.error}`, tool: toolCall.name });
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
883
1079
|
|
|
884
|
-
|
|
885
|
-
|
|
1080
|
+
if (result.success) {
|
|
1081
|
+
consecutiveErrors = 0; // 重置连续错误计数
|
|
1082
|
+
|
|
1083
|
+
// 检查工具执行质量
|
|
1084
|
+
lastQualityScore = this.estimateToolResultQuality(result);
|
|
1085
|
+
if (lastQualityScore < this.QUALITY_THRESHOLD && refineAttempts < this.MAX_REFINE_ATTEMPTS) {
|
|
1086
|
+
refineAttempts++;
|
|
1087
|
+
console.log(`[PiAgent] 工具结果质量低,自动重试 (${refineAttempts}/${this.MAX_REFINE_ATTEMPTS})`);
|
|
1088
|
+
} else {
|
|
1089
|
+
console.log(`[PiAgent] 工具执行成功,质量评分: ${(lastQualityScore * 10).toFixed(1)}/10`);
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
// 工具执行成功后,继续循环获取下一个 LLM 响应
|
|
1093
|
+
if (onStream) {
|
|
1094
|
+
onStream({ type: 'status', content: `🔄 工具执行完成,继续循环...`, tool: 'loop' });
|
|
1095
|
+
}
|
|
1096
|
+
// 不 break,继续下一次循环
|
|
1097
|
+
} else {
|
|
1098
|
+
consecutiveErrors++;
|
|
1099
|
+
console.warn(`[PiAgent] 工具执行失败 (${consecutiveErrors}/${MAX_CONSECUTIVE_ERRORS}): ${result.error}`);
|
|
1100
|
+
|
|
1101
|
+
// 连续错误达到上限,尝试换一种方式
|
|
1102
|
+
if (consecutiveErrors >= MAX_CONSECUTIVE_ERRORS) {
|
|
1103
|
+
console.log(`[PiAgent] 连续 ${MAX_CONSECUTIVE_ERRORS} 次错误,尝试换一种方式处理`);
|
|
1104
|
+
// 添加错误上下文,让 LLM 换一种方式
|
|
1105
|
+
this.messageHistory.push({
|
|
1106
|
+
role: 'system',
|
|
1107
|
+
content: `[注意] 前面的工具调用连续失败。请尝试其他工具或换一种方式完成用户请求。`
|
|
1108
|
+
});
|
|
1109
|
+
consecutiveErrors = 0; // 重置以继续尝试
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
} catch (execError) {
|
|
1113
|
+
consecutiveErrors++;
|
|
1114
|
+
const errorResult: ToolResult = { success: false, error: String(execError) };
|
|
1115
|
+
this.messageHistory.push({ role: 'tool', content: JSON.stringify(errorResult), toolResult: errorResult });
|
|
1116
|
+
this.logToHarness(toolCall.name, toolCall.args, errorResult);
|
|
1117
|
+
console.error(`[PiAgent] 工具执行异常: ${execError}`);
|
|
886
1118
|
}
|
|
887
1119
|
} else {
|
|
888
|
-
|
|
1120
|
+
// LLM 返回的不是 tool call 格式
|
|
1121
|
+
this.messageHistory.push({
|
|
1122
|
+
role: 'assistant',
|
|
1123
|
+
content: reply
|
|
1124
|
+
});
|
|
1125
|
+
|
|
1126
|
+
// 通知前端收到非工具调用回复
|
|
1127
|
+
if (onStream) {
|
|
1128
|
+
onStream({ type: 'token', content: reply.substring(0, 150) });
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
// 检查是否需要继续循环处理
|
|
1132
|
+
// 更严格的判断:只有当回复明确表示需要更多信息时才继续
|
|
1133
|
+
const containsToolCallIntent = reply.includes('调用工具') || reply.includes('tool(') ||
|
|
1134
|
+
reply.includes('使用工具') || reply.includes('需要获取') || reply.includes('需要查看');
|
|
1135
|
+
const hasError = ['不存在', '找不到', '无法找到', 'not found', 'does not exist',
|
|
1136
|
+
'错误', 'error', '失败', 'failed'].some(k => reply.includes(k));
|
|
1137
|
+
const isTooShort = reply.length < 50 && reply.length > 0;
|
|
1138
|
+
const hasQuestion = reply.includes('?') && (reply.includes('怎么') || reply.includes('如何') || reply.includes('什么'));
|
|
1139
|
+
|
|
1140
|
+
const needsMoreWork = hasError || containsToolCallIntent || isTooShort || hasQuestion;
|
|
1141
|
+
|
|
1142
|
+
if (needsMoreWork && iteration < this.MAX_REACT_ITERATIONS) {
|
|
1143
|
+
console.log(`[PiAgent] 继续循环处理 (${iteration}/${this.MAX_REACT_ITERATIONS}): needsMoreWork=${needsMoreWork}, hasError=${hasError}, containsToolCallIntent=${containsToolCallIntent}`);
|
|
1144
|
+
if (onStream) {
|
|
1145
|
+
onStream({ type: 'status', content: `🔄 继续处理,循环 ${iteration}...`, tool: 'loop' });
|
|
1146
|
+
}
|
|
1147
|
+
continue;
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
// 否则把这个当作可能的最终回答
|
|
889
1151
|
finalResponse = reply;
|
|
1152
|
+
if (onStream) {
|
|
1153
|
+
onStream({ type: 'status', content: `📝 提取最终回答,长度 ${reply.length}`, tool: 'system' });
|
|
1154
|
+
}
|
|
890
1155
|
break;
|
|
891
1156
|
}
|
|
892
1157
|
}
|
|
893
1158
|
|
|
894
1159
|
if (!finalResponse) {
|
|
895
1160
|
finalResponse = '任务处理超时,请尝试更具体的请求。';
|
|
1161
|
+
if (onStream) {
|
|
1162
|
+
onStream({ type: 'error', content: '⚠️ 任务处理超时', tool: 'system' });
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
// 通知前端循环完成
|
|
1167
|
+
if (onStream) {
|
|
1168
|
+
onStream({ type: 'status', content: `✅ 处理完成,共 ${iteration - 1} 次循环`, tool: 'system' });
|
|
896
1169
|
}
|
|
897
1170
|
|
|
898
1171
|
const now = new Date().toISOString();
|
|
@@ -975,30 +1248,23 @@ Workspace root folder: ${this.cwd}
|
|
|
975
1248
|
}
|
|
976
1249
|
|
|
977
1250
|
private isFinalResponse(content: string): boolean {
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
// 更保守的判断:只有明确标记最终回答,且回复较短时才认为是最终回复
|
|
981
|
-
if (content.includes('✅') && content.length < 200 && finalMarkers.some(m => lower.includes(m))) {
|
|
982
|
-
return true;
|
|
983
|
-
}
|
|
984
|
-
return finalMarkers.some(m => lower.includes(m));
|
|
1251
|
+
// 只有明确输出 <final gen> 才认为是最终回答
|
|
1252
|
+
return content.includes('<final gen>');
|
|
985
1253
|
}
|
|
986
1254
|
|
|
987
1255
|
private extractFinalAnswer(content: string): string {
|
|
988
|
-
//
|
|
1256
|
+
// 提取 <final gen> 后的内容作为最终回答
|
|
1257
|
+
const marker = '<final gen>';
|
|
1258
|
+
const markerIndex = content.indexOf(marker);
|
|
1259
|
+
if (markerIndex !== -1) {
|
|
1260
|
+
content = content.substring(markerIndex + marker.length).trim();
|
|
1261
|
+
}
|
|
1262
|
+
// 移除任何 tool call 标记
|
|
989
1263
|
let cleaned = content
|
|
990
1264
|
.replace(/调用工具[::]\s*\w+\s*\([^)]*\)/g, '')
|
|
991
1265
|
.replace(/使用工具[::]\s*\w+\s*\([^)]*\)/g, '')
|
|
992
1266
|
.replace(/tool[_\w]*[::]\s*\w+\s*\([^)]*\)/gi, '')
|
|
993
1267
|
.trim();
|
|
994
|
-
|
|
995
|
-
const lines = cleaned.split('\n');
|
|
996
|
-
const answerStart = lines.findIndex(l =>
|
|
997
|
-
['最终回答', '完成', '答案如下', '结果是', 'final', 'answer:'].some(m => l.toLowerCase().includes(m))
|
|
998
|
-
);
|
|
999
|
-
if (answerStart >= 0) {
|
|
1000
|
-
return lines.slice(answerStart + 1).join('\n').trim();
|
|
1001
|
-
}
|
|
1002
1268
|
return cleaned;
|
|
1003
1269
|
}
|
|
1004
1270
|
|
|
@@ -1033,6 +1299,34 @@ Workspace root folder: ${this.cwd}
|
|
|
1033
1299
|
return null;
|
|
1034
1300
|
}
|
|
1035
1301
|
|
|
1302
|
+
private estimateResponseQuality(response: string): number {
|
|
1303
|
+
let score = 0.5;
|
|
1304
|
+
if (response.length > 50) score += 0.1;
|
|
1305
|
+
if (response.length > 200) score += 0.1;
|
|
1306
|
+
if (response.length < 20) score -= 0.3;
|
|
1307
|
+
if (response.includes('\n')) score += 0.1;
|
|
1308
|
+
if (response.includes('-') || response.includes('•')) score += 0.05;
|
|
1309
|
+
if (response.includes('```')) score += 0.1;
|
|
1310
|
+
const conclusionWords = ['完成', '结果', '总结', '所以', '因此', '答案', '推荐'];
|
|
1311
|
+
if (conclusionWords.some(w => response.includes(w))) score += 0.1;
|
|
1312
|
+
if (response.includes('调用工具') || response.includes('tool(')) score -= 0.2;
|
|
1313
|
+
return Math.max(0, Math.min(1, score));
|
|
1314
|
+
}
|
|
1315
|
+
|
|
1316
|
+
private estimateToolResultQuality(result: ToolResult): number {
|
|
1317
|
+
let score = 0.5;
|
|
1318
|
+
if (!result.success) return 0.2;
|
|
1319
|
+
if (result.output) {
|
|
1320
|
+
score += 0.2;
|
|
1321
|
+
if (result.output.length > 50) score += 0.1;
|
|
1322
|
+
if (result.output.length < 10) score -= 0.1;
|
|
1323
|
+
if (result.output.includes('❌') || result.output.includes('error')) score -= 0.2;
|
|
1324
|
+
if (result.output.includes('✅') || result.output.includes('success')) score += 0.1;
|
|
1325
|
+
}
|
|
1326
|
+
if (result.error) score -= 0.3;
|
|
1327
|
+
return Math.max(0, Math.min(1, score));
|
|
1328
|
+
}
|
|
1329
|
+
|
|
1036
1330
|
private async handleFallback(input: string): Promise<string> {
|
|
1037
1331
|
const lowerInput = input.toLowerCase();
|
|
1038
1332
|
const parts = input.trim().split(/\s+/);
|
|
@@ -1551,9 +1845,34 @@ ${this.extractOperationsFromRef(operationsRef)}
|
|
|
1551
1845
|
let sessionInstance: AgentSession | null = null;
|
|
1552
1846
|
let lastIdentityDid: string | null = null;
|
|
1553
1847
|
|
|
1554
|
-
|
|
1848
|
+
// 独立的 session 实例缓存(用于多 session 支持)
|
|
1849
|
+
const independentSessions: Map<string, AgentSession> = new Map();
|
|
1850
|
+
|
|
1851
|
+
export async function createAgentSession(config: AgentSessionConfig, forceNew?: boolean): Promise<AgentSession> {
|
|
1555
1852
|
const incomingDid = config.identityDoc?.did;
|
|
1556
1853
|
|
|
1854
|
+
// 如果有独立的 peerId (包含 :),使用它作为 key
|
|
1855
|
+
if (config.peerId && config.peerId.includes(':')) {
|
|
1856
|
+
const key = config.peerId;
|
|
1857
|
+
if (!forceNew && independentSessions.has(key)) {
|
|
1858
|
+
console.log(`[createAgentSession] 找到现有独立 session, key=${key}`);
|
|
1859
|
+
return independentSessions.get(key)!;
|
|
1860
|
+
}
|
|
1861
|
+
const session = new PiAgentSession(config);
|
|
1862
|
+
independentSessions.set(key, session);
|
|
1863
|
+
console.log(`[createAgentSession] 创建独立 session, key=${key}, DID=${incomingDid}`);
|
|
1864
|
+
return session;
|
|
1865
|
+
}
|
|
1866
|
+
|
|
1867
|
+
// 如果指定了 forceNew 但没有 peerId,生成带时间戳的 key
|
|
1868
|
+
if (forceNew) {
|
|
1869
|
+
const key = `force:${Date.now()}`;
|
|
1870
|
+
const session = new PiAgentSession(config);
|
|
1871
|
+
independentSessions.set(key, session);
|
|
1872
|
+
console.log(`[createAgentSession] 创建强制新 session, key=${key}`);
|
|
1873
|
+
return session;
|
|
1874
|
+
}
|
|
1875
|
+
|
|
1557
1876
|
// 如果有新的 DID,强制重建 session
|
|
1558
1877
|
if (sessionInstance && lastIdentityDid && incomingDid && lastIdentityDid !== incomingDid) {
|
|
1559
1878
|
console.log(`[createAgentSession] DID 变化 ${lastIdentityDid} -> ${incomingDid},重建 session`);
|
package/src/agents/protocol.ts
CHANGED
|
@@ -268,11 +268,105 @@ export class AgentProtocol {
|
|
|
268
268
|
};
|
|
269
269
|
|
|
270
270
|
const peers = p2pNetwork.getPeers();
|
|
271
|
+
const failedPeers: string[] = [];
|
|
272
|
+
|
|
271
273
|
for (const peer of peers) {
|
|
272
274
|
if (peer !== fromPeer) {
|
|
273
|
-
|
|
275
|
+
try {
|
|
276
|
+
await p2pNetwork.sendMessage(peer, 'report', JSON.stringify(reportMsg));
|
|
277
|
+
console.log(`[${this.identityName}] 汇报已发送至 ${peer}`);
|
|
278
|
+
} catch (sendError) {
|
|
279
|
+
console.warn(`[${this.identityName}] 发送汇报至 ${peer} 失败: ${sendError}`);
|
|
280
|
+
failedPeers.push(peer);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// 重试失败的发送
|
|
286
|
+
if (failedPeers.length > 0) {
|
|
287
|
+
console.log(`[${this.identityName}] 尝试重新发送汇报至 ${failedPeers.length} 个失败节点`);
|
|
288
|
+
await this.retryFailedReports(reportMsg, failedPeers, 2);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
private async retryFailedReports(
|
|
293
|
+
reportMsg: AgentMessage,
|
|
294
|
+
failedPeers: string[],
|
|
295
|
+
maxRetries: number
|
|
296
|
+
): Promise<void> {
|
|
297
|
+
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
298
|
+
if (failedPeers.length === 0) break;
|
|
299
|
+
|
|
300
|
+
console.log(`[${this.identityName}] 重试第 ${attempt}/${maxRetries} 次`);
|
|
301
|
+
await this.sleep(1000 * attempt); // 指数退避
|
|
302
|
+
|
|
303
|
+
const stillFailed: string[] = [];
|
|
304
|
+
for (const peer of failedPeers) {
|
|
305
|
+
try {
|
|
306
|
+
await p2pNetwork.sendMessage(peer, 'report', JSON.stringify(reportMsg));
|
|
307
|
+
console.log(`[${this.identityName}] 重试成功: ${peer}`);
|
|
308
|
+
} catch {
|
|
309
|
+
stillFailed.push(peer);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
failedPeers = stillFailed;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
if (failedPeers.length > 0) {
|
|
316
|
+
console.log(`[${this.identityName}] 最终仍有 ${failedPeers.length} 个节点发送失败,将加入待重试队列`);
|
|
317
|
+
this.queueFailedReports(reportMsg, failedPeers);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
private failedReportsQueue: Array<{ msg: AgentMessage; peers: string[]; timestamp: number }> = [];
|
|
322
|
+
|
|
323
|
+
private queueFailedReports(msg: AgentMessage, peers: string[]): void {
|
|
324
|
+
this.failedReportsQueue.push({
|
|
325
|
+
msg,
|
|
326
|
+
peers,
|
|
327
|
+
timestamp: Date.now()
|
|
328
|
+
});
|
|
329
|
+
// 限制队列大小
|
|
330
|
+
if (this.failedReportsQueue.length > 50) {
|
|
331
|
+
this.failedReportsQueue = this.failedReportsQueue.slice(-50);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
async processFailedReportsQueue(): Promise<void> {
|
|
336
|
+
if (this.failedReportsQueue.length === 0) return;
|
|
337
|
+
|
|
338
|
+
console.log(`[${this.identityName}] 处理待重试汇报队列 (${this.failedReportsQueue.length} 条)`);
|
|
339
|
+
const processed: number[] = [];
|
|
340
|
+
|
|
341
|
+
for (let i = 0; i < this.failedReportsQueue.length; i++) {
|
|
342
|
+
const item = this.failedReportsQueue[i];
|
|
343
|
+
const stillFailed: string[] = [];
|
|
344
|
+
|
|
345
|
+
for (const peer of item.peers) {
|
|
346
|
+
try {
|
|
347
|
+
await p2pNetwork.sendMessage(peer, 'report', JSON.stringify(item.msg));
|
|
348
|
+
console.log(`[${this.identityName}] 队列重试成功: ${peer}`);
|
|
349
|
+
} catch {
|
|
350
|
+
stillFailed.push(peer);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
if (stillFailed.length === 0) {
|
|
355
|
+
processed.push(i);
|
|
356
|
+
} else {
|
|
357
|
+
item.peers = stillFailed;
|
|
358
|
+
item.timestamp = Date.now();
|
|
274
359
|
}
|
|
275
360
|
}
|
|
361
|
+
|
|
362
|
+
// 移除已成功的
|
|
363
|
+
for (let i = processed.length - 1; i >= 0; i--) {
|
|
364
|
+
this.failedReportsQueue.splice(processed[i], 1);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
private sleep(ms: number): Promise<void> {
|
|
369
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
276
370
|
}
|
|
277
371
|
|
|
278
372
|
async submitImprovements(taskId: string, improvements: string): Promise<void> {
|