@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.
Files changed (200) hide show
  1. package/bin/bolloon-cli.cjs +165 -0
  2. package/bin/bolloon-daemon.sh +207 -0
  3. package/bin/bolloon.cmd +11 -0
  4. package/dist/agents/constraint-layer.js +10 -15
  5. package/dist/agents/pi-sdk.js +433 -106
  6. package/dist/agents/protocol.js +82 -1
  7. package/dist/agents/subagent-manager.js +2 -2
  8. package/dist/agents/workflow-engine.js +15 -20
  9. package/dist/agents/workflow-pivot-loop.js +541 -0
  10. package/dist/bollharness/src/index.js +5 -0
  11. package/dist/bollharness/src/scripts/checks/check_adr_plan_numbering.js +6 -0
  12. package/dist/bollharness/src/scripts/checks/check_api_types.js +45 -0
  13. package/dist/bollharness/src/scripts/checks/check_artifact_link.js +146 -0
  14. package/dist/bollharness/src/scripts/checks/check_bridge_deps.js +6 -0
  15. package/dist/bollharness/src/scripts/checks/check_bugfix_binding.js +6 -0
  16. package/dist/bollharness/src/scripts/checks/check_bugfix_binding_ci.js +6 -0
  17. package/dist/bollharness/src/scripts/checks/check_doc_file_references.js +6 -0
  18. package/dist/bollharness/src/scripts/checks/check_doc_freshness.js +135 -0
  19. package/dist/bollharness/src/scripts/checks/check_doc_links.js +31 -0
  20. package/dist/bollharness/src/scripts/checks/check_file_existence_claims.js +6 -0
  21. package/dist/bollharness/src/scripts/checks/check_fragment_integrity.js +34 -0
  22. package/dist/bollharness/src/scripts/checks/check_hook_installed.js +63 -0
  23. package/dist/bollharness/src/scripts/checks/check_issue_closure.js +41 -0
  24. package/dist/bollharness/src/scripts/checks/check_mcp_parity.js +6 -0
  25. package/dist/bollharness/src/scripts/checks/check_security.js +48 -0
  26. package/dist/bollharness/src/scripts/checks/check_skill_parity.js +6 -0
  27. package/dist/bollharness/src/scripts/checks/check_versions.js +6 -0
  28. package/dist/bollharness/src/scripts/checks/finding.js +13 -0
  29. package/dist/bollharness/src/scripts/checks/next_decision_number.js +20 -0
  30. package/dist/bollharness/src/scripts/checks/regenerate_magic_docs.js +6 -0
  31. package/dist/bollharness/src/scripts/ci/detect_rebaseline_triggers.js +8 -0
  32. package/dist/bollharness/src/scripts/ci/scan_subprocess_cfg.js +8 -0
  33. package/dist/bollharness/src/scripts/ci/scan_verify_artifacts.js +8 -0
  34. package/dist/bollharness/src/scripts/ci/scan_yaml_schema.js +8 -0
  35. package/dist/bollharness/src/scripts/context_router.js +67 -0
  36. package/dist/bollharness/src/scripts/deploy-guard.js +157 -0
  37. package/dist/bollharness/src/scripts/guard-feedback.js +192 -0
  38. package/dist/bollharness/src/scripts/guard_router.js +158 -0
  39. package/dist/bollharness/src/scripts/hooks/_hook_output.js +6 -0
  40. package/dist/bollharness/src/scripts/hooks/auto-python3.js +6 -0
  41. package/dist/bollharness/src/scripts/hooks/deploy-progress-on-session-end.js +6 -0
  42. package/dist/bollharness/src/scripts/hooks/failure-analyzer.js +6 -0
  43. package/dist/bollharness/src/scripts/hooks/gate-judgment-inject.js +92 -0
  44. package/dist/bollharness/src/scripts/hooks/gate-transition-judgment.js +63 -0
  45. package/dist/bollharness/src/scripts/hooks/inbox-ack.js +6 -0
  46. package/dist/bollharness/src/scripts/hooks/inbox-inject-on-start.js +6 -0
  47. package/dist/bollharness/src/scripts/hooks/inbox-validate.js +6 -0
  48. package/dist/bollharness/src/scripts/hooks/inbox-write-ledger.js +6 -0
  49. package/dist/bollharness/src/scripts/hooks/initializer-agent.js +6 -0
  50. package/dist/bollharness/src/scripts/hooks/loop-detection.js +73 -0
  51. package/dist/bollharness/src/scripts/hooks/owner-guard.js +6 -0
  52. package/dist/bollharness/src/scripts/hooks/precompact.js +6 -0
  53. package/dist/bollharness/src/scripts/hooks/review-agent-gatekeeper.js +6 -0
  54. package/dist/bollharness/src/scripts/hooks/risk-tracker.js +108 -0
  55. package/dist/bollharness/src/scripts/hooks/sanitize-on-read.js +6 -0
  56. package/dist/bollharness/src/scripts/hooks/session-reflection.js +7 -0
  57. package/dist/bollharness/src/scripts/hooks/session-start-magic-docs.js +7 -0
  58. package/dist/bollharness/src/scripts/hooks/session-start-reset-risk.js +7 -0
  59. package/dist/bollharness/src/scripts/hooks/session-start-toolkit-reminder.js +7 -0
  60. package/dist/bollharness/src/scripts/hooks/stop-evaluator.js +157 -0
  61. package/dist/bollharness/src/scripts/hooks/tool-call-counter.js +6 -0
  62. package/dist/bollharness/src/scripts/hooks/trace-analyzer.js +10 -0
  63. package/dist/bollharness/src/scripts/install/install-trust-token.js +7 -0
  64. package/dist/bollharness/src/scripts/install/multi_project_registry.js +9 -0
  65. package/dist/bollharness/src/scripts/install/phase2_auto.js +21 -0
  66. package/dist/bollharness/src/scripts/install/pre_commit_installer.js +6 -0
  67. package/dist/bollharness/src/scripts/install/tier_selector.js +7 -0
  68. package/dist/bollharness/src/scripts/install/transcript_miner.js +7 -0
  69. package/dist/bollharness/src/scripts/lib/claim_patterns.js +10 -0
  70. package/dist/bollharness/src/scripts/lib/sanitize_patterns.js +12 -0
  71. package/dist/bollharness/src/scripts/sanitize.js +6 -0
  72. package/dist/bollharness-integration/channel-judgment-engine.js +530 -0
  73. package/dist/bollharness-integration/context-chain-router.js +383 -0
  74. package/dist/bollharness-integration/context-router-judgment.js +13 -21
  75. package/dist/bollharness-integration/context-router.js +22 -64
  76. package/dist/bollharness-integration/gate-state-machine.js +14 -19
  77. package/dist/bollharness-integration/gate-transition-hooks.js +16 -61
  78. package/dist/bollharness-integration/guard-checker.js +21 -68
  79. package/dist/bollharness-integration/index.js +14 -124
  80. package/dist/bollharness-integration/integration.js +13 -20
  81. package/dist/bollharness-integration/llm-judgment-engine.js +569 -0
  82. package/dist/bollharness-integration/skill-adapter.js +18 -64
  83. package/dist/cli-entry.js +261 -0
  84. package/dist/constraint-runtime/src/commands.js +17 -7
  85. package/dist/constraint-runtime/src/constraint/budget.js +1 -6
  86. package/dist/constraint-runtime/src/constraint/permission.js +1 -6
  87. package/dist/constraint-runtime/src/models.js +1 -3
  88. package/dist/constraint-runtime/src/tools.js +17 -7
  89. package/dist/constraints/index.js +1 -7
  90. package/dist/documents/reader.js +8 -49
  91. package/dist/heartbeat/DaemonManager.js +242 -0
  92. package/dist/heartbeat/HealthMonitor.js +285 -0
  93. package/dist/heartbeat/StartupVerifier.js +205 -0
  94. package/dist/heartbeat/Watchdog.js +168 -0
  95. package/dist/heartbeat/index.js +84 -0
  96. package/dist/heartbeat/types.js +5 -0
  97. package/dist/index.js +381 -28
  98. package/dist/llm/config-store.js +31 -57
  99. package/dist/llm/llm-judgment-client.js +389 -0
  100. package/dist/llm/pi-ai.js +9 -52
  101. package/dist/network/agent-network.js +46 -90
  102. package/dist/network/hybrid-messenger.js +125 -0
  103. package/dist/network/iroh-bootstrap.js +38 -0
  104. package/dist/network/iroh-discovery.js +145 -0
  105. package/dist/network/iroh-integration.js +9 -16
  106. package/dist/network/iroh-transport.js +10 -48
  107. package/dist/network/p2p.js +23 -62
  108. package/dist/network/storage/adapters/json-adapter.js +4 -42
  109. package/dist/network/storage/index.js +147 -0
  110. package/dist/network/storage/types.js +14 -0
  111. package/dist/pi-ecosystem/index.js +233 -0
  112. package/dist/pi-ecosystem-colony/index.js +29 -90
  113. package/dist/pi-ecosystem-goals/index.js +20 -74
  114. package/dist/pi-ecosystem-judgment/decision.js +29 -47
  115. package/dist/pi-ecosystem-judgment/distillation.js +16 -29
  116. package/dist/pi-ecosystem-judgment/human-value-store.js +13 -60
  117. package/dist/pi-ecosystem-judgment/index.js +21 -74
  118. package/dist/pi-ecosystem-judgment/value-injection.js +26 -72
  119. package/dist/pi-ecosystem-mcp/index.js +24 -78
  120. package/dist/pi-ecosystem-subagents/index.js +20 -69
  121. package/dist/social/ant-colony/AdaptiveHeartbeat.js +3 -8
  122. package/dist/social/ant-colony/PheromoneEngine.js +11 -49
  123. package/dist/social/ant-colony/index.js +6 -0
  124. package/dist/social/ant-colony/types.js +4 -8
  125. package/dist/social/channels/ChannelManager.js +8 -46
  126. package/dist/social/channels/DiapChannelBridge.js +9 -47
  127. package/dist/social/channels/InterestMatcher.js +2 -7
  128. package/dist/social/channels/channel-agent-session.js +309 -0
  129. package/dist/social/channels/channel-heartbeat-agent.js +494 -0
  130. package/dist/social/channels/diap-doc-parser.js +204 -0
  131. package/dist/social/channels/harness-workflow-integrator.js +446 -0
  132. package/dist/social/channels/index.js +9 -0
  133. package/dist/social/channels/types.js +3 -7
  134. package/dist/social/global-shared-context.js +6 -47
  135. package/dist/social/heartbeat.js +29 -72
  136. package/dist/social/persona/enhanced-persona.js +299 -0
  137. package/dist/web/client.js +302 -136
  138. package/dist/web/components/p2p/index.js +159 -9
  139. package/dist/web/components/p2p/p2p-connection.js +136 -0
  140. package/dist/web/components/p2p/p2p-manager.js +24 -0
  141. package/dist/web/components/p2p/p2p-store-memory.js +1 -1
  142. package/dist/web/components/p2p/types.js +7 -0
  143. package/dist/web/index.html +5 -0
  144. package/dist/web/style.css +118 -0
  145. package/package.json +12 -6
  146. package/scripts/build-cli.js +206 -0
  147. package/scripts/postinstall.js +153 -0
  148. package/src/agents/pi-sdk.ts +347 -28
  149. package/src/agents/protocol.ts +95 -1
  150. package/src/agents/workflow-pivot-loop.ts +674 -0
  151. package/src/bollharness/CLAUDE.md +73 -0
  152. package/src/bollharness/README.md +143 -0
  153. package/src/bollharness/README.zh-CN.md +131 -0
  154. package/src/bollharness/reference/boll-reference/scripts/hooks/stop-evaluator.md +57 -0
  155. package/src/bollharness/scripts/context-fragments/artifact-linkage.md +14 -0
  156. package/src/bollharness/scripts/context-fragments/auth-consumers.md +17 -0
  157. package/src/bollharness/scripts/context-fragments/bridge-constitution.md +13 -0
  158. package/src/bollharness/scripts/context-fragments/catalyst-distributed.md +18 -0
  159. package/src/bollharness/scripts/context-fragments/closure-checklist.md +13 -0
  160. package/src/bollharness/scripts/context-fragments/contract-consumers.md +15 -0
  161. package/src/bollharness/scripts/context-fragments/db-shared-structures.md +15 -0
  162. package/src/bollharness/scripts/context-fragments/fixed-three-layers.md +19 -0
  163. package/src/bollharness/scripts/context-fragments/general-dev-principles.md +11 -0
  164. package/src/bollharness/scripts/context-fragments/issue-first.md +8 -0
  165. package/src/bollharness/scripts/context-fragments/mcp-parity.md +16 -0
  166. package/src/bollharness/scripts/context-fragments/pi-agent-operations.md +108 -0
  167. package/src/bollharness/scripts/context-fragments/protocol-consumers.md +15 -0
  168. package/src/bollharness/scripts/context-fragments/run-events-consumers.md +15 -0
  169. package/src/bollharness/scripts/context-fragments/scene-fidelity.md +13 -0
  170. package/src/bollharness/scripts/context-fragments/truth-source-hierarchy.md +15 -0
  171. package/src/bollharness/scripts/context-fragments/two-language.md +15 -0
  172. package/src/bollharness/scripts/context-fragments/version-sources.md +14 -0
  173. package/src/bollharness/scripts/hooks/stop-evaluator.md +83 -0
  174. package/src/bollharness/templates/scaffold/CLAUDE.md +89 -0
  175. package/src/cli-entry.ts +304 -0
  176. package/src/heartbeat/DaemonManager.ts +283 -0
  177. package/src/heartbeat/HealthMonitor.ts +316 -0
  178. package/src/heartbeat/StartupVerifier.ts +223 -0
  179. package/src/heartbeat/Watchdog.ts +198 -0
  180. package/src/heartbeat/index.ts +108 -0
  181. package/src/heartbeat/types.ts +82 -0
  182. package/src/llm/config-store.ts +23 -5
  183. package/src/network/iroh-transport.ts +3 -3
  184. package/src/web/client.js +302 -136
  185. package/src/web/components/p2p/P2PModal.tsx +91 -3
  186. package/src/web/components/p2p/index.ts +171 -9
  187. package/src/web/components/p2p/p2p-connection.ts +153 -1
  188. package/src/web/components/p2p/p2p-manager.ts +39 -1
  189. package/src/web/components/p2p/p2p-store-memory.ts +1 -1
  190. package/src/web/components/p2p/p2p-tools.ts +315 -0
  191. package/src/web/components/p2p/types.ts +58 -0
  192. package/src/web/design.md +99 -0
  193. package/src/web/index.html +5 -0
  194. package/src/web/server.ts +353 -36
  195. package/src/web/style.css +118 -0
  196. package/tsconfig.cli.json +16 -0
  197. package/tsconfig.electron.json +1 -1
  198. package/tsconfig.json +1 -2
  199. package/dist/web/server.js +0 -1647
  200. 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,6 @@
1
+ export function run(repoRoot, mode = "full") {
2
+ const findings = [];
3
+ if (mode !== "full" && mode !== "ci")
4
+ return findings;
5
+ return findings;
6
+ }
@@ -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
+ }