@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
|
@@ -0,0 +1,541 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WorkflowPivotLoop - Robust Agent Loop with Adaptive Iteration Control
|
|
3
|
+
*
|
|
4
|
+
* Based on the architecture pattern:
|
|
5
|
+
* 1. Loop interrupted by max iterations
|
|
6
|
+
* 2. Model decides via pending_tool_uses (empty = normal completion)
|
|
7
|
+
* 3. Conditional routing based on tool call presence
|
|
8
|
+
*
|
|
9
|
+
* Key improvements over simple ReAct:
|
|
10
|
+
* - Dynamic loop length based on task complexity
|
|
11
|
+
* - Multi-dimensional exit conditions
|
|
12
|
+
* - Consecutive invalid iteration detection
|
|
13
|
+
* - Token budget awareness
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* Analyze input to determine task complexity
|
|
17
|
+
*/
|
|
18
|
+
function analyzeTaskComplexity(input) {
|
|
19
|
+
const inputLower = input.toLowerCase();
|
|
20
|
+
const inputLength = input.length;
|
|
21
|
+
// Simple task indicators
|
|
22
|
+
const simpleIndicators = [
|
|
23
|
+
'读取', '查看', '显示', '列出', '获取', 'what is', 'show me',
|
|
24
|
+
'list', 'get', 'show', 'read', 'view', 'display'
|
|
25
|
+
];
|
|
26
|
+
// Complex task indicators
|
|
27
|
+
const complexIndicators = [
|
|
28
|
+
'分析', '比较', '改进', '优化', '重构', '实现', '设计', '创建',
|
|
29
|
+
'analyze', 'compare', 'improve', 'optimize', 'refactor', 'implement',
|
|
30
|
+
'design', 'create', 'build', 'develop'
|
|
31
|
+
];
|
|
32
|
+
// Question patterns suggest moderate complexity
|
|
33
|
+
const questionPatterns = [
|
|
34
|
+
'如何', '怎么', '为什么', '什么', 'which', 'how', 'why', 'what', '?'
|
|
35
|
+
];
|
|
36
|
+
const simpleCount = simpleIndicators.filter(i => inputLower.includes(i)).length;
|
|
37
|
+
const complexCount = complexIndicators.filter(i => inputLower.includes(i)).length;
|
|
38
|
+
const questionCount = questionPatterns.filter(p => inputLower.includes(p)).length;
|
|
39
|
+
let complexity;
|
|
40
|
+
let estimatedSteps;
|
|
41
|
+
if (complexCount > simpleCount && complexCount > 1) {
|
|
42
|
+
complexity = 'complex';
|
|
43
|
+
estimatedSteps = 5 + complexCount * 2;
|
|
44
|
+
}
|
|
45
|
+
else if (questionCount > 2 || (simpleCount > 0 && complexCount > 0)) {
|
|
46
|
+
complexity = 'moderate';
|
|
47
|
+
estimatedSteps = 3 + questionCount;
|
|
48
|
+
}
|
|
49
|
+
else if (inputLength < 50 && simpleCount > 0) {
|
|
50
|
+
complexity = 'simple';
|
|
51
|
+
estimatedSteps = 1 + simpleCount;
|
|
52
|
+
}
|
|
53
|
+
else if (complexCount > 0) {
|
|
54
|
+
complexity = 'complex';
|
|
55
|
+
estimatedSteps = 4 + complexCount;
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
complexity = 'moderate';
|
|
59
|
+
estimatedSteps = 3;
|
|
60
|
+
}
|
|
61
|
+
// Adjust based on input length (longer inputs often mean more complex tasks)
|
|
62
|
+
if (inputLength > 500 && complexity !== 'complex') {
|
|
63
|
+
complexity = 'moderate';
|
|
64
|
+
estimatedSteps = Math.max(estimatedSteps, 4);
|
|
65
|
+
}
|
|
66
|
+
// Suggested max iterations: 2-3x estimated steps for safety margin
|
|
67
|
+
const suggestedMaxIterations = Math.min(Math.max(estimatedSteps * 3, 10), 100);
|
|
68
|
+
const tokenBudget = estimatedSteps * 800; // ~800 tokens per step estimate
|
|
69
|
+
return { complexity, estimatedSteps, suggestedMaxIterations, tokenBudget };
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* WorkflowPivotLoop - Main loop controller
|
|
73
|
+
*/
|
|
74
|
+
export class WorkflowPivotLoop {
|
|
75
|
+
config;
|
|
76
|
+
state;
|
|
77
|
+
tools;
|
|
78
|
+
messageHistory;
|
|
79
|
+
streamCallback;
|
|
80
|
+
constructor(config) {
|
|
81
|
+
this.tools = new Map();
|
|
82
|
+
// Default configuration based on task complexity if not provided
|
|
83
|
+
const defaults = {
|
|
84
|
+
maxIterations: config.maxIterations || 50,
|
|
85
|
+
minIterations: config.minIterations || 2,
|
|
86
|
+
qualityThreshold: config.qualityThreshold || 0.7,
|
|
87
|
+
maxConsecutiveNoProgress: config.maxConsecutiveNoProgress || 5,
|
|
88
|
+
maxTokenBudget: config.maxTokenBudget || 50000,
|
|
89
|
+
complexity: config.complexity || 'moderate'
|
|
90
|
+
};
|
|
91
|
+
this.config = defaults;
|
|
92
|
+
this.state = this.createInitialState();
|
|
93
|
+
this.messageHistory = [];
|
|
94
|
+
}
|
|
95
|
+
createInitialState() {
|
|
96
|
+
return {
|
|
97
|
+
iteration: 0,
|
|
98
|
+
totalTokens: 0,
|
|
99
|
+
toolCallsCount: 0,
|
|
100
|
+
consecutiveNoProgress: 0,
|
|
101
|
+
qualityScores: [],
|
|
102
|
+
pendingToolUses: [],
|
|
103
|
+
lastMeaningfulWork: 0
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Register a tool for use in the loop
|
|
108
|
+
*/
|
|
109
|
+
registerTool(tool) {
|
|
110
|
+
this.tools.set(tool.name, tool);
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Register multiple tools at once
|
|
114
|
+
*/
|
|
115
|
+
registerTools(tools) {
|
|
116
|
+
for (const tool of tools) {
|
|
117
|
+
this.registerTool(tool);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Execute the pivot loop
|
|
122
|
+
*/
|
|
123
|
+
async execute(input, llm, systemPrompt, streamCallback) {
|
|
124
|
+
this.streamCallback = streamCallback;
|
|
125
|
+
this.state = this.createInitialState();
|
|
126
|
+
this.messageHistory = [{ role: 'user', content: input }];
|
|
127
|
+
// Analyze task complexity and adapt config
|
|
128
|
+
const taskProfile = analyzeTaskComplexity(input);
|
|
129
|
+
const effectiveConfig = this.adaptConfigForTask(taskProfile);
|
|
130
|
+
this.emit({
|
|
131
|
+
type: 'status',
|
|
132
|
+
content: `🔍 任务复杂度: ${taskProfile.complexity} (预估 ${taskProfile.estimatedSteps} 步)`,
|
|
133
|
+
tool: 'system'
|
|
134
|
+
});
|
|
135
|
+
this.emit({
|
|
136
|
+
type: 'status',
|
|
137
|
+
content: `⚙️ 动态配置: maxIterations=${effectiveConfig.maxIterations}, tokenBudget=${effectiveConfig.maxTokenBudget}`,
|
|
138
|
+
tool: 'system'
|
|
139
|
+
});
|
|
140
|
+
let response = '';
|
|
141
|
+
while (this.shouldContinue(effectiveConfig)) {
|
|
142
|
+
this.state.iteration++;
|
|
143
|
+
this.emit({
|
|
144
|
+
type: 'status',
|
|
145
|
+
content: `🔄 循环 ${this.state.iteration}/${effectiveConfig.maxIterations}`,
|
|
146
|
+
tool: 'loop'
|
|
147
|
+
});
|
|
148
|
+
// Build context for LLM
|
|
149
|
+
const context = this.buildContext();
|
|
150
|
+
const fullPrompt = `${systemPrompt}\n\n${context}`;
|
|
151
|
+
try {
|
|
152
|
+
// Call LLM
|
|
153
|
+
const llmResponse = await llm.chat(context, systemPrompt);
|
|
154
|
+
const reply = llmResponse.reply.trim();
|
|
155
|
+
this.emit({ type: 'token', content: reply.substring(0, 100) });
|
|
156
|
+
// Estimate token usage
|
|
157
|
+
this.state.totalTokens += this.estimateTokens(fullPrompt) + this.estimateTokens(reply);
|
|
158
|
+
// Check token budget
|
|
159
|
+
if (this.state.totalTokens > effectiveConfig.maxTokenBudget) {
|
|
160
|
+
this.emit({
|
|
161
|
+
type: 'error',
|
|
162
|
+
content: '⚠️ Token 预算超支,中断循环'
|
|
163
|
+
});
|
|
164
|
+
return this.createResult(false, response, 'token_budget_exceeded');
|
|
165
|
+
}
|
|
166
|
+
// Check if this is a final response (no tool calls)
|
|
167
|
+
const pendingTools = this.extractPendingToolUses(reply);
|
|
168
|
+
if (pendingTools.length === 0) {
|
|
169
|
+
// No pending tool uses - this is a normal completion
|
|
170
|
+
this.state.pendingToolUses = [];
|
|
171
|
+
// Evaluate quality before accepting
|
|
172
|
+
const quality = this.evaluateQuality(reply);
|
|
173
|
+
this.state.qualityScores.push(quality);
|
|
174
|
+
this.emit({
|
|
175
|
+
type: 'status',
|
|
176
|
+
content: `✅ 检测到最终回复 (质量: ${(quality * 10).toFixed(1)}/10)`,
|
|
177
|
+
tool: 'system'
|
|
178
|
+
});
|
|
179
|
+
// Check if quality threshold met
|
|
180
|
+
if (quality >= effectiveConfig.qualityThreshold) {
|
|
181
|
+
response = reply;
|
|
182
|
+
return this.createResult(true, reply, 'quality_threshold_met');
|
|
183
|
+
}
|
|
184
|
+
// Quality not met but no more tools to call
|
|
185
|
+
// Accept response if we've done minimum iterations
|
|
186
|
+
if (this.state.iteration >= effectiveConfig.minIterations) {
|
|
187
|
+
response = reply;
|
|
188
|
+
return this.createResult(true, reply, 'no_pending_tools');
|
|
189
|
+
}
|
|
190
|
+
// Too early, continue to see if we can improve
|
|
191
|
+
this.state.consecutiveNoProgress++;
|
|
192
|
+
this.emit({
|
|
193
|
+
type: 'status',
|
|
194
|
+
content: `📊 质量未达标 (${(quality * 10).toFixed(1)}/${(effectiveConfig.qualityThreshold * 10).toFixed(1)}),继续循环`,
|
|
195
|
+
tool: 'system'
|
|
196
|
+
});
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
// We have pending tool uses - execute them
|
|
200
|
+
this.state.pendingToolUses = pendingTools;
|
|
201
|
+
this.state.lastMeaningfulWork = this.state.iteration;
|
|
202
|
+
this.state.consecutiveNoProgress = 0;
|
|
203
|
+
for (const toolCall of pendingTools) {
|
|
204
|
+
this.state.toolCallsCount++;
|
|
205
|
+
const tool = this.tools.get(toolCall.name);
|
|
206
|
+
if (!tool) {
|
|
207
|
+
this.emit({
|
|
208
|
+
type: 'error',
|
|
209
|
+
content: `❌ 未知工具: ${toolCall.name}`
|
|
210
|
+
});
|
|
211
|
+
this.messageHistory.push({
|
|
212
|
+
role: 'tool',
|
|
213
|
+
content: JSON.stringify({ success: false, error: `Unknown tool: ${toolCall.name}` })
|
|
214
|
+
});
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
this.emit({
|
|
218
|
+
type: 'tool',
|
|
219
|
+
content: `🔧 执行: ${toolCall.name}`,
|
|
220
|
+
tool: toolCall.name
|
|
221
|
+
});
|
|
222
|
+
try {
|
|
223
|
+
const result = await tool.execute(toolCall.args);
|
|
224
|
+
this.emit({
|
|
225
|
+
type: result.success ? 'status' : 'error',
|
|
226
|
+
content: result.success
|
|
227
|
+
? `✅ ${toolCall.name} 成功`
|
|
228
|
+
: `❌ ${toolCall.name} 失败: ${result.error}`
|
|
229
|
+
});
|
|
230
|
+
this.messageHistory.push({
|
|
231
|
+
role: 'assistant',
|
|
232
|
+
content: reply,
|
|
233
|
+
toolCall,
|
|
234
|
+
toolResult: result
|
|
235
|
+
});
|
|
236
|
+
// Record quality from tool result
|
|
237
|
+
const toolQuality = this.evaluateToolResult(result);
|
|
238
|
+
this.state.qualityScores.push(toolQuality);
|
|
239
|
+
}
|
|
240
|
+
catch (execError) {
|
|
241
|
+
this.emit({
|
|
242
|
+
type: 'error',
|
|
243
|
+
content: `❌ 工具执行异常: ${execError}`
|
|
244
|
+
});
|
|
245
|
+
this.messageHistory.push({
|
|
246
|
+
role: 'tool',
|
|
247
|
+
content: JSON.stringify({ success: false, error: String(execError) })
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
catch (error) {
|
|
253
|
+
this.emit({
|
|
254
|
+
type: 'error',
|
|
255
|
+
content: `❌ 循环异常: ${error}`
|
|
256
|
+
});
|
|
257
|
+
return this.createResult(false, response, 'error');
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
// Loop exited - determine reason
|
|
261
|
+
const exitReason = this.determineExitReason(effectiveConfig);
|
|
262
|
+
if (!response && this.messageHistory.length > 0) {
|
|
263
|
+
const lastAssistant = this.messageHistory
|
|
264
|
+
.filter(m => m.role === 'assistant')
|
|
265
|
+
.pop();
|
|
266
|
+
response = lastAssistant?.content || '任务处理超时';
|
|
267
|
+
}
|
|
268
|
+
return this.createResult(exitReason !== 'error', response, exitReason);
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Determine if loop should continue
|
|
272
|
+
*/
|
|
273
|
+
shouldContinue(config) {
|
|
274
|
+
// Hard stop: max iterations reached
|
|
275
|
+
if (this.state.iteration >= config.maxIterations) {
|
|
276
|
+
this.emit({
|
|
277
|
+
type: 'status',
|
|
278
|
+
content: `🛑 达到最大迭代次数 ${config.maxIterations}`
|
|
279
|
+
});
|
|
280
|
+
return false;
|
|
281
|
+
}
|
|
282
|
+
// Soft stop: consecutive no progress
|
|
283
|
+
if (this.state.consecutiveNoProgress >= config.maxConsecutiveNoProgress) {
|
|
284
|
+
this.emit({
|
|
285
|
+
type: 'status',
|
|
286
|
+
content: `🛑 连续 ${config.maxConsecutiveNoProgress} 次无进展`
|
|
287
|
+
});
|
|
288
|
+
return false;
|
|
289
|
+
}
|
|
290
|
+
return true;
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Adapt configuration based on task profile
|
|
294
|
+
*/
|
|
295
|
+
adaptConfigForTask(profile) {
|
|
296
|
+
return {
|
|
297
|
+
...this.config,
|
|
298
|
+
maxIterations: Math.min(this.config.maxIterations, profile.suggestedMaxIterations),
|
|
299
|
+
maxTokenBudget: Math.min(this.config.maxTokenBudget, profile.tokenBudget),
|
|
300
|
+
complexity: profile.complexity
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Extract pending tool uses from LLM response
|
|
305
|
+
*/
|
|
306
|
+
extractPendingToolUses(content) {
|
|
307
|
+
const pending = [];
|
|
308
|
+
// Pattern 1: Chinese format "调用工具: tool_name(args)"
|
|
309
|
+
const pattern1 = /调用工具[::]\s*(\w+)\s*\(([^)]*)\)/g;
|
|
310
|
+
let match;
|
|
311
|
+
while ((match = pattern1.exec(content)) !== null) {
|
|
312
|
+
const name = match[1];
|
|
313
|
+
const argsStr = match[2];
|
|
314
|
+
const args = this.parseArgs(argsStr);
|
|
315
|
+
if (this.tools.has(name)) {
|
|
316
|
+
pending.push({ name, args });
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
// Pattern 2: tool_name(args) format
|
|
320
|
+
const pattern2 = /(\w+)\s*\(\s*([^)]*)\s*\)/g;
|
|
321
|
+
while ((match = pattern2.exec(content)) !== null) {
|
|
322
|
+
const name = match[1];
|
|
323
|
+
const argsStr = match[2];
|
|
324
|
+
// Skip if already matched or doesn't look like a tool call
|
|
325
|
+
if (pending.some(p => p.name === name))
|
|
326
|
+
continue;
|
|
327
|
+
if (!this.tools.has(name))
|
|
328
|
+
continue;
|
|
329
|
+
const args = this.parseArgs(argsStr);
|
|
330
|
+
pending.push({ name, args });
|
|
331
|
+
}
|
|
332
|
+
// Pattern 3: JSON format tool calls
|
|
333
|
+
try {
|
|
334
|
+
const jsonMatch = content.match(/\{[\s\S]*"tool_calls"[\s\S]*\}/);
|
|
335
|
+
if (jsonMatch) {
|
|
336
|
+
const parsed = JSON.parse(jsonMatch[0]);
|
|
337
|
+
if (Array.isArray(parsed.tool_calls)) {
|
|
338
|
+
for (const tc of parsed.tool_calls) {
|
|
339
|
+
if (this.tools.has(tc.name)) {
|
|
340
|
+
pending.push({ name: tc.name, args: tc.args || {} });
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
catch {
|
|
347
|
+
// JSON parsing failed, ignore
|
|
348
|
+
}
|
|
349
|
+
return pending;
|
|
350
|
+
}
|
|
351
|
+
/**
|
|
352
|
+
* Parse tool arguments from string
|
|
353
|
+
*/
|
|
354
|
+
parseArgs(argsStr) {
|
|
355
|
+
const args = {};
|
|
356
|
+
if (!argsStr || !argsStr.trim())
|
|
357
|
+
return args;
|
|
358
|
+
const pairs = argsStr.split(',').map(s => s.trim()).filter(Boolean);
|
|
359
|
+
for (const pair of pairs) {
|
|
360
|
+
const colonIdx = pair.indexOf(':');
|
|
361
|
+
if (colonIdx > 0) {
|
|
362
|
+
const key = pair.substring(0, colonIdx).trim();
|
|
363
|
+
const value = pair.substring(colonIdx + 1).trim().replace(/^['"]|['"]$/g, '');
|
|
364
|
+
args[key] = value;
|
|
365
|
+
}
|
|
366
|
+
else {
|
|
367
|
+
// No colon, try to parse as positional
|
|
368
|
+
const parts = pair.split(/\s+/);
|
|
369
|
+
if (parts.length >= 2) {
|
|
370
|
+
args[parts[0]] = parts.slice(1).join(' ');
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
return args;
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* Build context from message history
|
|
378
|
+
*/
|
|
379
|
+
buildContext() {
|
|
380
|
+
return this.messageHistory.map(m => {
|
|
381
|
+
if (m.role === 'user')
|
|
382
|
+
return `用户: ${m.content}`;
|
|
383
|
+
if (m.role === 'assistant')
|
|
384
|
+
return `助手: ${m.content}`;
|
|
385
|
+
if (m.role === 'tool' && m.toolResult) {
|
|
386
|
+
return `工具结果: ${JSON.stringify(m.toolResult)}`;
|
|
387
|
+
}
|
|
388
|
+
return '';
|
|
389
|
+
}).filter(Boolean).join('\n');
|
|
390
|
+
}
|
|
391
|
+
/**
|
|
392
|
+
* Evaluate response quality
|
|
393
|
+
*/
|
|
394
|
+
evaluateQuality(response) {
|
|
395
|
+
let score = 0.5;
|
|
396
|
+
// Length-based scoring
|
|
397
|
+
if (response.length > 100)
|
|
398
|
+
score += 0.1;
|
|
399
|
+
if (response.length > 500)
|
|
400
|
+
score += 0.1;
|
|
401
|
+
if (response.length < 30)
|
|
402
|
+
score -= 0.2;
|
|
403
|
+
// Structure indicators
|
|
404
|
+
if (response.includes('\n'))
|
|
405
|
+
score += 0.05;
|
|
406
|
+
if (response.includes('-') || response.includes('•'))
|
|
407
|
+
score += 0.05;
|
|
408
|
+
if (response.includes('```'))
|
|
409
|
+
score += 0.1;
|
|
410
|
+
// Content quality indicators
|
|
411
|
+
const conclusionWords = ['完成', '结果', '总结', '所以', '因此', '答案', '推荐', '建议'];
|
|
412
|
+
if (conclusionWords.some(w => response.includes(w)))
|
|
413
|
+
score += 0.1;
|
|
414
|
+
// Negative indicators
|
|
415
|
+
if (response.includes('调用工具') || response.includes('tool('))
|
|
416
|
+
score -= 0.15;
|
|
417
|
+
if (response.includes('??') || response.includes('未知'))
|
|
418
|
+
score -= 0.1;
|
|
419
|
+
return Math.max(0, Math.min(1, score));
|
|
420
|
+
}
|
|
421
|
+
/**
|
|
422
|
+
* Evaluate tool result quality
|
|
423
|
+
*/
|
|
424
|
+
evaluateToolResult(result) {
|
|
425
|
+
if (!result.success)
|
|
426
|
+
return 0.2;
|
|
427
|
+
let score = 0.6;
|
|
428
|
+
if (result.output) {
|
|
429
|
+
score += 0.2;
|
|
430
|
+
if (result.output.length > 100)
|
|
431
|
+
score += 0.1;
|
|
432
|
+
if (result.output.includes('error') || result.output.includes('❌'))
|
|
433
|
+
score -= 0.2;
|
|
434
|
+
if (result.output.includes('success') || result.output.includes('✅'))
|
|
435
|
+
score += 0.1;
|
|
436
|
+
}
|
|
437
|
+
if (result.error)
|
|
438
|
+
score -= 0.3;
|
|
439
|
+
return Math.max(0, Math.min(1, score));
|
|
440
|
+
}
|
|
441
|
+
/**
|
|
442
|
+
* Estimate token count (rough approximation)
|
|
443
|
+
*/
|
|
444
|
+
estimateTokens(text) {
|
|
445
|
+
// Rough estimate: ~4 characters per token for Chinese/English mix
|
|
446
|
+
return Math.ceil(text.length / 4);
|
|
447
|
+
}
|
|
448
|
+
/**
|
|
449
|
+
* Create result object
|
|
450
|
+
*/
|
|
451
|
+
createResult(success, response, exitReason) {
|
|
452
|
+
const avgQuality = this.state.qualityScores.length > 0
|
|
453
|
+
? this.state.qualityScores.reduce((a, b) => a + b, 0) / this.state.qualityScores.length
|
|
454
|
+
: 0;
|
|
455
|
+
return {
|
|
456
|
+
success,
|
|
457
|
+
response,
|
|
458
|
+
iterations: this.state.iteration,
|
|
459
|
+
toolCalls: this.state.toolCallsCount,
|
|
460
|
+
qualityScore: avgQuality,
|
|
461
|
+
exitReason,
|
|
462
|
+
state: { ...this.state }
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
/**
|
|
466
|
+
* Determine why loop exited
|
|
467
|
+
*/
|
|
468
|
+
determineExitReason(config) {
|
|
469
|
+
if (this.state.iteration >= config.maxIterations) {
|
|
470
|
+
return 'max_iterations';
|
|
471
|
+
}
|
|
472
|
+
if (this.state.consecutiveNoProgress >= config.maxConsecutiveNoProgress) {
|
|
473
|
+
return 'no_progress_exhausted';
|
|
474
|
+
}
|
|
475
|
+
if (this.state.pendingToolUses.length === 0 && this.state.iteration >= config.minIterations) {
|
|
476
|
+
return 'no_pending_tools';
|
|
477
|
+
}
|
|
478
|
+
if (this.state.totalTokens > config.maxTokenBudget) {
|
|
479
|
+
return 'token_budget_exceeded';
|
|
480
|
+
}
|
|
481
|
+
return 'max_iterations';
|
|
482
|
+
}
|
|
483
|
+
/**
|
|
484
|
+
* Emit stream event
|
|
485
|
+
*/
|
|
486
|
+
emit(event) {
|
|
487
|
+
if (this.streamCallback) {
|
|
488
|
+
this.streamCallback(event);
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
/**
|
|
492
|
+
* Get current state
|
|
493
|
+
*/
|
|
494
|
+
getState() {
|
|
495
|
+
return { ...this.state };
|
|
496
|
+
}
|
|
497
|
+
/**
|
|
498
|
+
* Reset the loop state
|
|
499
|
+
*/
|
|
500
|
+
reset() {
|
|
501
|
+
this.state = this.createInitialState();
|
|
502
|
+
this.messageHistory = [];
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
/**
|
|
506
|
+
* Factory to create a default pivot loop configuration
|
|
507
|
+
*/
|
|
508
|
+
export function createDefaultPivotConfig(complexity) {
|
|
509
|
+
const profiles = {
|
|
510
|
+
simple: {
|
|
511
|
+
maxIterations: 15,
|
|
512
|
+
minIterations: 1,
|
|
513
|
+
qualityThreshold: 0.6,
|
|
514
|
+
maxConsecutiveNoProgress: 3,
|
|
515
|
+
maxTokenBudget: 10000
|
|
516
|
+
},
|
|
517
|
+
moderate: {
|
|
518
|
+
maxIterations: 30,
|
|
519
|
+
minIterations: 2,
|
|
520
|
+
qualityThreshold: 0.7,
|
|
521
|
+
maxConsecutiveNoProgress: 5,
|
|
522
|
+
maxTokenBudget: 30000
|
|
523
|
+
},
|
|
524
|
+
complex: {
|
|
525
|
+
maxIterations: 60,
|
|
526
|
+
minIterations: 3,
|
|
527
|
+
qualityThreshold: 0.75,
|
|
528
|
+
maxConsecutiveNoProgress: 8,
|
|
529
|
+
maxTokenBudget: 60000
|
|
530
|
+
}
|
|
531
|
+
};
|
|
532
|
+
return complexity ? profiles[complexity] : profiles.moderate;
|
|
533
|
+
}
|
|
534
|
+
/**
|
|
535
|
+
* Helper to run a simple prompt through the loop
|
|
536
|
+
*/
|
|
537
|
+
export async function runPivotLoop(input, llm, tools, systemPrompt, config, streamCallback) {
|
|
538
|
+
const loop = new WorkflowPivotLoop(config || createDefaultPivotConfig());
|
|
539
|
+
loop.registerTools(tools);
|
|
540
|
+
return loop.execute(input, llm, systemPrompt, streamCallback);
|
|
541
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { createFinding } from "./scripts/checks/finding";
|
|
2
|
+
export { route, runGuards, writeSessionSignal, readAllSignals, GUARD_MAP, DEFAULT_GUARDS, CATEGORY_TO_SKILLS } from "./scripts/guard_router";
|
|
3
|
+
export { match, loadFragment, FALLBACK_FRAGMENTS, CONTEXT_MAP } from "./scripts/context_router";
|
|
4
|
+
export { main as guardFeedbackMain } from "./scripts/guard-feedback";
|
|
5
|
+
export { main as deployGuardMain } from "./scripts/deploy-guard";
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import * as fs from "fs";
|
|
2
|
+
import * as path from "path";
|
|
3
|
+
export function run(repoRoot, mode = "full") {
|
|
4
|
+
const findings = [];
|
|
5
|
+
const tsFile = path.join(repoRoot, "generated", "api-types.ts");
|
|
6
|
+
if (!fs.existsSync(tsFile)) {
|
|
7
|
+
findings.push({
|
|
8
|
+
severity: "P1",
|
|
9
|
+
message: "generated/api-types.ts does not exist — run scripts/export_api_types.py",
|
|
10
|
+
file: "generated/api-types.ts",
|
|
11
|
+
blocking: false,
|
|
12
|
+
category: "general",
|
|
13
|
+
problem_class: "unknown",
|
|
14
|
+
required_skills: [],
|
|
15
|
+
required_reads: [],
|
|
16
|
+
});
|
|
17
|
+
return findings;
|
|
18
|
+
}
|
|
19
|
+
const content = fs.readFileSync(tsFile, "utf-8");
|
|
20
|
+
const interfaceMatch = content.match(/export interface (\w+) \{/g);
|
|
21
|
+
if (!interfaceMatch) {
|
|
22
|
+
findings.push({
|
|
23
|
+
severity: "P1",
|
|
24
|
+
message: "generated/api-types.ts contains no interfaces",
|
|
25
|
+
file: "generated/api-types.ts",
|
|
26
|
+
blocking: false,
|
|
27
|
+
category: "general",
|
|
28
|
+
problem_class: "unknown",
|
|
29
|
+
required_skills: [],
|
|
30
|
+
required_reads: [],
|
|
31
|
+
});
|
|
32
|
+
return findings;
|
|
33
|
+
}
|
|
34
|
+
findings.push({
|
|
35
|
+
severity: "P2",
|
|
36
|
+
message: "API type checking requires backend model comparison (Python 3.10+ backend not available in TypeScript check)",
|
|
37
|
+
file: "generated/api-types.ts",
|
|
38
|
+
blocking: false,
|
|
39
|
+
category: "general",
|
|
40
|
+
problem_class: "unknown",
|
|
41
|
+
required_skills: [],
|
|
42
|
+
required_reads: [],
|
|
43
|
+
});
|
|
44
|
+
return findings;
|
|
45
|
+
}
|