@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/web/server.ts
CHANGED
|
@@ -51,6 +51,15 @@ interface Channel {
|
|
|
51
51
|
didDocument?: any;
|
|
52
52
|
createdAt: string;
|
|
53
53
|
updatedAt: string;
|
|
54
|
+
currentSessionId?: string;
|
|
55
|
+
sessions?: SessionSummary[];
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
interface SessionSummary {
|
|
59
|
+
id: string;
|
|
60
|
+
createdAt: string;
|
|
61
|
+
messageCount: number;
|
|
62
|
+
preview: string;
|
|
54
63
|
}
|
|
55
64
|
|
|
56
65
|
interface SessionMessage {
|
|
@@ -315,7 +324,8 @@ interface SSEClient {
|
|
|
315
324
|
}
|
|
316
325
|
|
|
317
326
|
let sseClients: Set<SSEClient> = new Set();
|
|
318
|
-
let channelSessions: Map<string, AgentSession> = new Map();
|
|
327
|
+
let channelSessions: Map<string, AgentSession> = new Map(); // key: channelId
|
|
328
|
+
let sessionMessages: Map<string, any[]> = new Map(); // key: channelId + sessionId
|
|
319
329
|
|
|
320
330
|
async function getAgentForChannel(
|
|
321
331
|
channelId: string,
|
|
@@ -323,10 +333,19 @@ async function getAgentForChannel(
|
|
|
323
333
|
channelName?: string,
|
|
324
334
|
channelDidDoc?: any
|
|
325
335
|
): Promise<AgentSession> {
|
|
326
|
-
|
|
336
|
+
// 获取当前 channel 的 currentSessionId
|
|
337
|
+
const channels = await loadChannels();
|
|
338
|
+
const channel = channels.find(c => c.id === channelId);
|
|
339
|
+
const currentSessionId = channel?.currentSessionId || 'default';
|
|
340
|
+
const sessionKey = `${channelId}:${currentSessionId}`;
|
|
341
|
+
|
|
342
|
+
console.log(`[Agent] 获取频道 ${channelId} 的 session, sessionKey = ${sessionKey}`);
|
|
343
|
+
|
|
344
|
+
const existingSession = channelSessions.get(sessionKey);
|
|
327
345
|
|
|
328
346
|
// 如果已有 session,检查是否需要更新 identity
|
|
329
347
|
if (existingSession) {
|
|
348
|
+
console.log(`[Agent] 找到现有 session: ${sessionKey}`);
|
|
330
349
|
const currentIdentity = existingSession.getIdentity();
|
|
331
350
|
|
|
332
351
|
// 如果当前 identity 没有真实 DID,或者 DID 与频道的 DID 不匹配,需要重建
|
|
@@ -362,17 +381,18 @@ async function getAgentForChannel(
|
|
|
362
381
|
ipnsName: channelDidDoc?.ipnsName
|
|
363
382
|
} : undefined;
|
|
364
383
|
|
|
384
|
+
console.log(`[Agent] 创建新 session: ${sessionKey}`);
|
|
365
385
|
const session = await createAgentSession({
|
|
366
386
|
cwd: process.cwd(),
|
|
367
|
-
peerId: `channel-${channelId}`,
|
|
387
|
+
peerId: `channel-${channelId}:${currentSessionId}`,
|
|
368
388
|
identityDoc
|
|
369
|
-
});
|
|
370
|
-
channelSessions.set(
|
|
389
|
+
}, true); // forceNew: true 强制创建新实例
|
|
390
|
+
channelSessions.set(sessionKey, session);
|
|
371
391
|
|
|
372
392
|
if (channelDid) {
|
|
373
|
-
console.log(`[Agent] 新建频道 ${channelId} session, DID = ${channelDid},
|
|
393
|
+
console.log(`[Agent] 新建频道 ${channelId} session, DID = ${channelDid}, sessionId = ${currentSessionId}`);
|
|
374
394
|
} else {
|
|
375
|
-
console.log(`[Agent] 新建频道 ${channelId} session,
|
|
395
|
+
console.log(`[Agent] 新建频道 ${channelId} session, 使用默认身份, sessionId = ${currentSessionId}`);
|
|
376
396
|
}
|
|
377
397
|
|
|
378
398
|
return session;
|
|
@@ -541,12 +561,15 @@ export async function createWebServer(port: number = 3000) {
|
|
|
541
561
|
}
|
|
542
562
|
} else if (event.type === 'status' || event.type === 'tool') {
|
|
543
563
|
broadcast({ type: 'status', tool: event.tool, content: event.content }, channelId);
|
|
544
|
-
broadcast({ type: 'workflow_step', step: event.tool || '
|
|
564
|
+
broadcast({ type: 'workflow_step', step: event.tool || '系统', content: event.content }, channelId);
|
|
565
|
+
console.log(`[SSE 广播] workflow_step: step=${event.tool}, content="${event.content?.substring(0, 80)}..."`);
|
|
545
566
|
} else if (event.type === 'error') {
|
|
546
567
|
broadcast({ type: 'error', content: event.content }, channelId);
|
|
547
568
|
}
|
|
548
569
|
};
|
|
549
570
|
|
|
571
|
+
console.log(`[消息处理] 开始处理用户消息, channelId: ${channelId}`);
|
|
572
|
+
|
|
550
573
|
// 将真实 DID 作为上下文前缀,让 AI 使用真实的 DID 而不是自己编造的
|
|
551
574
|
const contextHint = realChannelDid ? `[系统上下文] 当前频道名称: ${realChannelName}, 你的真实 DID: ${realChannelDid}\n\n` : '';
|
|
552
575
|
fullResponse = await agent.promptStream(contextHint + text, streamCallback);
|
|
@@ -672,6 +695,13 @@ app.get('/channels', async (_req, res) => {
|
|
|
672
695
|
agentId,
|
|
673
696
|
createdAt: new Date().toISOString(),
|
|
674
697
|
updatedAt: new Date().toISOString(),
|
|
698
|
+
currentSessionId: `sess_${Date.now()}`,
|
|
699
|
+
sessions: [{
|
|
700
|
+
id: `sess_${Date.now()}`,
|
|
701
|
+
createdAt: new Date().toISOString(),
|
|
702
|
+
messageCount: 0,
|
|
703
|
+
preview: ''
|
|
704
|
+
}]
|
|
675
705
|
};
|
|
676
706
|
|
|
677
707
|
console.log(`[创建频道] 先保存频道 ID: ${id}`);
|
|
@@ -714,6 +744,85 @@ app.get('/channels', async (_req, res) => {
|
|
|
714
744
|
}
|
|
715
745
|
});
|
|
716
746
|
|
|
747
|
+
// 创建新 Session(在现有 Channel 下)
|
|
748
|
+
app.post('/channels/:channelId/sessions', async (req, res) => {
|
|
749
|
+
try {
|
|
750
|
+
const { channelId } = req.params;
|
|
751
|
+
const channels = await loadChannels();
|
|
752
|
+
const channel = channels.find(c => c.id === channelId);
|
|
753
|
+
|
|
754
|
+
if (!channel) {
|
|
755
|
+
return res.status(404).json({ error: 'Channel not found' });
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
const sessionId = `sess_${Date.now()}`;
|
|
759
|
+
const session: SessionSummary = {
|
|
760
|
+
id: sessionId,
|
|
761
|
+
createdAt: new Date().toISOString(),
|
|
762
|
+
messageCount: 0,
|
|
763
|
+
preview: ''
|
|
764
|
+
};
|
|
765
|
+
|
|
766
|
+
if (!channel.sessions) {
|
|
767
|
+
channel.sessions = [];
|
|
768
|
+
}
|
|
769
|
+
channel.sessions.push(session);
|
|
770
|
+
channel.currentSessionId = sessionId;
|
|
771
|
+
channel.updatedAt = new Date().toISOString();
|
|
772
|
+
|
|
773
|
+
await saveChannels(channels);
|
|
774
|
+
await saveSession({ channelId, sessionId, messages: [], lastUpdated: new Date().toISOString() });
|
|
775
|
+
|
|
776
|
+
res.json({ session, currentSessionId: sessionId });
|
|
777
|
+
} catch (err: any) {
|
|
778
|
+
console.error('[创建Session] 错误:', err);
|
|
779
|
+
res.status(500).json({ error: err.message });
|
|
780
|
+
}
|
|
781
|
+
});
|
|
782
|
+
|
|
783
|
+
// 获取 Channel 下的所有 Sessions
|
|
784
|
+
app.get('/channels/:channelId/sessions', async (req, res) => {
|
|
785
|
+
try {
|
|
786
|
+
const { channelId } = req.params;
|
|
787
|
+
const channels = await loadChannels();
|
|
788
|
+
const channel = channels.find(c => c.id === channelId);
|
|
789
|
+
|
|
790
|
+
if (!channel) {
|
|
791
|
+
return res.status(404).json({ error: 'Channel not found' });
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
res.json({
|
|
795
|
+
sessions: channel.sessions || [],
|
|
796
|
+
currentSessionId: channel.currentSessionId
|
|
797
|
+
});
|
|
798
|
+
} catch (err: any) {
|
|
799
|
+
console.error('[获取Sessions] 错误:', err);
|
|
800
|
+
res.status(500).json({ error: err.message });
|
|
801
|
+
}
|
|
802
|
+
});
|
|
803
|
+
|
|
804
|
+
// 切换 Session
|
|
805
|
+
app.post('/channels/:channelId/sessions/:sessionId/switch', async (req, res) => {
|
|
806
|
+
try {
|
|
807
|
+
const { channelId, sessionId } = req.params;
|
|
808
|
+
const channels = await loadChannels();
|
|
809
|
+
const channel = channels.find(c => c.id === channelId);
|
|
810
|
+
|
|
811
|
+
if (!channel) {
|
|
812
|
+
return res.status(404).json({ error: 'Channel not found' });
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
channel.currentSessionId = sessionId;
|
|
816
|
+
channel.updatedAt = new Date().toISOString();
|
|
817
|
+
await saveChannels(channels);
|
|
818
|
+
|
|
819
|
+
res.json({ ok: true, currentSessionId: sessionId });
|
|
820
|
+
} catch (err: any) {
|
|
821
|
+
console.error('[切换Session] 错误:', err);
|
|
822
|
+
res.status(500).json({ error: err.message });
|
|
823
|
+
}
|
|
824
|
+
});
|
|
825
|
+
|
|
717
826
|
app.delete('/channels/:channelId', async (req, res) => {
|
|
718
827
|
try {
|
|
719
828
|
const { channelId } = req.params;
|
|
@@ -1229,18 +1338,29 @@ app.get('/channels', async (_req, res) => {
|
|
|
1229
1338
|
createdAt: new Date().toISOString()
|
|
1230
1339
|
};
|
|
1231
1340
|
|
|
1232
|
-
// 发布到 IPFS
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1341
|
+
// 发布到 IPFS(可选,如果 IPFS 不可用则跳过)
|
|
1342
|
+
let cid = '';
|
|
1343
|
+
try {
|
|
1344
|
+
const formData = new FormData();
|
|
1345
|
+
const blob = new Blob([JSON.stringify(nodeDoc)], { type: 'application/json' });
|
|
1346
|
+
formData.append('file', blob, 'node-info.json');
|
|
1347
|
+
|
|
1348
|
+
const ipfsRes = await fetch(`${IPFS_ENDPOINT}/api/v0/add`, {
|
|
1349
|
+
method: 'POST',
|
|
1350
|
+
body: formData,
|
|
1351
|
+
signal: AbortSignal.timeout(5000)
|
|
1352
|
+
});
|
|
1353
|
+
const ipfsResult = await ipfsRes.text();
|
|
1354
|
+
const cidMatch = ipfsResult.match(/"Hash":"([^"]+)"/);
|
|
1355
|
+
cid = cidMatch ? cidMatch[1] : '';
|
|
1356
|
+
console.log(`[iroh API] CID 发布成功: ${cid.substring(0, 20)}...`);
|
|
1357
|
+
} catch (ipfsErr) {
|
|
1358
|
+
console.warn('[iroh API] IPFS 不可用,跳过 CID 发布:', (ipfsErr as Error).message);
|
|
1359
|
+
// 生成一个假的 CID 用于本地测试(格式:Qm + 44个随机字符)
|
|
1360
|
+
const randomPart = Array.from({ length: 44 }, () => Math.random().toString(36)[2]).join('').substring(0, 44);
|
|
1361
|
+
cid = `Qm${randomPart}`;
|
|
1362
|
+
console.log(`[iroh API] 使用本地 CID: ${cid.substring(0, 20)}...`);
|
|
1363
|
+
}
|
|
1244
1364
|
|
|
1245
1365
|
irohNodeInfo = {
|
|
1246
1366
|
did,
|
|
@@ -1300,7 +1420,7 @@ app.get('/channels', async (_req, res) => {
|
|
|
1300
1420
|
});
|
|
1301
1421
|
});
|
|
1302
1422
|
|
|
1303
|
-
// 通过 CID 连接到其他节点
|
|
1423
|
+
// 通过 CID 或 Node ID 连接到其他节点
|
|
1304
1424
|
app.post('/api/iroh/connect', async (req, res) => {
|
|
1305
1425
|
try {
|
|
1306
1426
|
const { cid } = req.body;
|
|
@@ -1313,21 +1433,38 @@ app.get('/channels', async (_req, res) => {
|
|
|
1313
1433
|
return res.status(500).json({ error: 'iroh not initialized' });
|
|
1314
1434
|
}
|
|
1315
1435
|
|
|
1316
|
-
|
|
1436
|
+
let targetNodeId: string;
|
|
1437
|
+
let nodeName = 'Unknown';
|
|
1317
1438
|
|
|
1318
|
-
|
|
1319
|
-
const ipfsRes = await fetch(`${IPFS_ENDPOINT}/api/v0/cat?arg=${cid}`, {
|
|
1320
|
-
method: 'POST'
|
|
1321
|
-
});
|
|
1322
|
-
const content = await ipfsRes.text();
|
|
1323
|
-
const doc = JSON.parse(content);
|
|
1439
|
+
console.log(`[iroh API] 连接到: ${cid}`);
|
|
1324
1440
|
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
}
|
|
1441
|
+
// 检查是 Node ID(64字符十六进制)还是 CID
|
|
1442
|
+
const isNodeId = /^[a-f0-9]{64}$/i.test(cid);
|
|
1328
1443
|
|
|
1329
|
-
|
|
1330
|
-
|
|
1444
|
+
if (isNodeId) {
|
|
1445
|
+
// 直接使用 Node ID
|
|
1446
|
+
targetNodeId = cid;
|
|
1447
|
+
console.log(`[iroh API] 使用直接 Node ID: ${targetNodeId.substring(0, 20)}...`);
|
|
1448
|
+
} else {
|
|
1449
|
+
// 从 IPFS 获取节点信息
|
|
1450
|
+
try {
|
|
1451
|
+
const ipfsRes = await fetch(`${IPFS_ENDPOINT}/api/v0/cat?arg=${cid}`, {
|
|
1452
|
+
method: 'POST'
|
|
1453
|
+
});
|
|
1454
|
+
const content = await ipfsRes.text();
|
|
1455
|
+
const doc = JSON.parse(content);
|
|
1456
|
+
|
|
1457
|
+
if (!doc.irohNodeId) {
|
|
1458
|
+
return res.status(400).json({ error: '节点信息中不包含 irohNodeId' });
|
|
1459
|
+
}
|
|
1460
|
+
|
|
1461
|
+
targetNodeId = doc.irohNodeId;
|
|
1462
|
+
nodeName = doc.name || 'Unknown';
|
|
1463
|
+
console.log(`[iroh API] 从 IPFS 获取节点: ${targetNodeId.substring(0, 20)}...`);
|
|
1464
|
+
} catch {
|
|
1465
|
+
return res.status(400).json({ error: '无法从 CID 获取节点信息,请确认 CID 有效' });
|
|
1466
|
+
}
|
|
1467
|
+
}
|
|
1331
1468
|
|
|
1332
1469
|
// 发送连接消息
|
|
1333
1470
|
const message = JSON.stringify({
|
|
@@ -1348,7 +1485,7 @@ app.get('/channels', async (_req, res) => {
|
|
|
1348
1485
|
res.json({
|
|
1349
1486
|
ok: true,
|
|
1350
1487
|
targetNodeId,
|
|
1351
|
-
nodeName
|
|
1488
|
+
nodeName
|
|
1352
1489
|
});
|
|
1353
1490
|
} else {
|
|
1354
1491
|
console.log(`[iroh API] 连接失败(对方可能离线)`);
|
|
@@ -1780,6 +1917,181 @@ app.get('/channels', async (_req, res) => {
|
|
|
1780
1917
|
}
|
|
1781
1918
|
});
|
|
1782
1919
|
|
|
1920
|
+
// 获取持久连接列表
|
|
1921
|
+
app.get('/api/p2p/persistent-connections', async (_req, res) => {
|
|
1922
|
+
try {
|
|
1923
|
+
const sessionProvider = app.locals.sessionProvider;
|
|
1924
|
+
if (!sessionProvider) {
|
|
1925
|
+
return res.json([]);
|
|
1926
|
+
}
|
|
1927
|
+
const channels = sessionProvider.getAllChannels().filter((ch: any) => ch.peerId);
|
|
1928
|
+
res.json(
|
|
1929
|
+
channels.map((ch: any) => ({
|
|
1930
|
+
id: ch.id,
|
|
1931
|
+
peerId: ch.peerId || '',
|
|
1932
|
+
peerDid: ch.peerDid || '',
|
|
1933
|
+
peerName: ch.peerName || 'Unknown',
|
|
1934
|
+
cid: ch.cid || '',
|
|
1935
|
+
status: ch.peerId ? 'connected' : 'disconnected',
|
|
1936
|
+
lastConnectedAt: new Date(ch.updatedAt).getTime(),
|
|
1937
|
+
channelId: ch.id,
|
|
1938
|
+
isAutoConnect: false
|
|
1939
|
+
}))
|
|
1940
|
+
);
|
|
1941
|
+
} catch (err: any) {
|
|
1942
|
+
res.status(500).json({ error: err.message });
|
|
1943
|
+
}
|
|
1944
|
+
});
|
|
1945
|
+
|
|
1946
|
+
// 更新连接状态
|
|
1947
|
+
app.post('/api/p2p/connection-status', async (req, res) => {
|
|
1948
|
+
try {
|
|
1949
|
+
const { id, status, channelId } = req.body;
|
|
1950
|
+
const sessionProvider = app.locals.sessionProvider;
|
|
1951
|
+
if (sessionProvider && channelId) {
|
|
1952
|
+
await sessionProvider.setChannelInfo(channelId, {
|
|
1953
|
+
peerId: status === 'connected' ? (req.body.peerId || 'connected') : undefined
|
|
1954
|
+
});
|
|
1955
|
+
}
|
|
1956
|
+
res.json({ ok: true });
|
|
1957
|
+
} catch (err: any) {
|
|
1958
|
+
res.status(500).json({ error: err.message });
|
|
1959
|
+
}
|
|
1960
|
+
});
|
|
1961
|
+
|
|
1962
|
+
// 创建对话通道
|
|
1963
|
+
app.post('/api/p2p/create-channel', async (req, res) => {
|
|
1964
|
+
try {
|
|
1965
|
+
const { peerDid, peerName, cid, peerId } = req.body;
|
|
1966
|
+
const sessionProvider = app.locals.sessionProvider;
|
|
1967
|
+
if (!sessionProvider) {
|
|
1968
|
+
return res.status(500).json({ error: 'sessionProvider not available' });
|
|
1969
|
+
}
|
|
1970
|
+
const channel = await sessionProvider.getOrCreatePeerChannel(peerDid, peerName);
|
|
1971
|
+
await sessionProvider.setChannelInfo(channel.id, { peerId: peerId || '', cid: cid || '' });
|
|
1972
|
+
res.json({ channelId: channel.id });
|
|
1973
|
+
} catch (err: any) {
|
|
1974
|
+
res.status(500).json({ error: err.message });
|
|
1975
|
+
}
|
|
1976
|
+
});
|
|
1977
|
+
|
|
1978
|
+
// CID 解析
|
|
1979
|
+
app.post('/api/p2p/resolve-cid', async (req, res) => {
|
|
1980
|
+
try {
|
|
1981
|
+
const { cid } = req.body;
|
|
1982
|
+
const { DiapDocParser } = await import('../social/channels/diap-doc-parser.js');
|
|
1983
|
+
const parser = new DiapDocParser();
|
|
1984
|
+
const result = await parser.parseFromCID(cid);
|
|
1985
|
+
res.json(result);
|
|
1986
|
+
} catch (err: any) {
|
|
1987
|
+
res.status(500).json({ error: err.message });
|
|
1988
|
+
}
|
|
1989
|
+
});
|
|
1990
|
+
|
|
1991
|
+
// P2P 工具调用
|
|
1992
|
+
app.post('/api/p2p/tool-call', async (req, res) => {
|
|
1993
|
+
try {
|
|
1994
|
+
const { tool, targetDid, payload } = req.body;
|
|
1995
|
+
|
|
1996
|
+
let result;
|
|
1997
|
+
switch (tool) {
|
|
1998
|
+
case 'system_info':
|
|
1999
|
+
const { getLocalSystemInfo } = await import('./components/p2p/p2p-tools.js');
|
|
2000
|
+
result = getLocalSystemInfo();
|
|
2001
|
+
break;
|
|
2002
|
+
case 'file_list':
|
|
2003
|
+
const { getLocalFileList } = await import('./components/p2p/p2p-tools.js');
|
|
2004
|
+
result = getLocalFileList(payload?.path || '/');
|
|
2005
|
+
break;
|
|
2006
|
+
default:
|
|
2007
|
+
return res.status(400).json({ error: `Unknown tool: ${tool}` });
|
|
2008
|
+
}
|
|
2009
|
+
|
|
2010
|
+
res.json({ success: true, data: result });
|
|
2011
|
+
} catch (err: any) {
|
|
2012
|
+
res.status(500).json({ error: err.message });
|
|
2013
|
+
}
|
|
2014
|
+
});
|
|
2015
|
+
|
|
2016
|
+
// ==================== 24h Heartbeat System ====================
|
|
2017
|
+
|
|
2018
|
+
let healthMonitor: any = null;
|
|
2019
|
+
let watchdog: any = null;
|
|
2020
|
+
|
|
2021
|
+
// 延迟导入避免循环依赖
|
|
2022
|
+
try {
|
|
2023
|
+
const { createHealthMonitor, createWatchdog } = await import('../heartbeat/index.js');
|
|
2024
|
+
healthMonitor = createHealthMonitor();
|
|
2025
|
+
watchdog = createWatchdog();
|
|
2026
|
+
|
|
2027
|
+
console.log('[24h] Heartbeat modules loaded');
|
|
2028
|
+
} catch (err) {
|
|
2029
|
+
console.warn('[24h] Failed to load heartbeat modules:', err);
|
|
2030
|
+
}
|
|
2031
|
+
|
|
2032
|
+
// 健康检查端点
|
|
2033
|
+
app.get('/api/health', async (req, res) => {
|
|
2034
|
+
try {
|
|
2035
|
+
if (!healthMonitor) {
|
|
2036
|
+
res.status(503).json({ error: 'Health monitor not initialized' });
|
|
2037
|
+
return;
|
|
2038
|
+
}
|
|
2039
|
+
|
|
2040
|
+
const status = await healthMonitor.check();
|
|
2041
|
+
|
|
2042
|
+
// 记录心跳活跃
|
|
2043
|
+
healthMonitor.recordHeartbeat?.();
|
|
2044
|
+
watchdog?.recordActivity?.('health_check');
|
|
2045
|
+
|
|
2046
|
+
// 根据状态返回不同 HTTP 状态码
|
|
2047
|
+
const httpStatus = status.status === 'healthy' ? 200 :
|
|
2048
|
+
status.status === 'degraded' ? 200 : 503;
|
|
2049
|
+
|
|
2050
|
+
res.status(httpStatus).json(status);
|
|
2051
|
+
} catch (err: any) {
|
|
2052
|
+
res.status(500).json({ error: err.message });
|
|
2053
|
+
}
|
|
2054
|
+
});
|
|
2055
|
+
|
|
2056
|
+
// 看门狗状态
|
|
2057
|
+
app.get('/api/watchdog', async (req, res) => {
|
|
2058
|
+
try {
|
|
2059
|
+
if (!watchdog) {
|
|
2060
|
+
res.status(503).json({ error: 'Watchdog not initialized' });
|
|
2061
|
+
return;
|
|
2062
|
+
}
|
|
2063
|
+
|
|
2064
|
+
const state = watchdog.getState();
|
|
2065
|
+
res.json(state);
|
|
2066
|
+
} catch (err: any) {
|
|
2067
|
+
res.status(500).json({ error: err.message });
|
|
2068
|
+
}
|
|
2069
|
+
});
|
|
2070
|
+
|
|
2071
|
+
// 看门狗重置
|
|
2072
|
+
app.post('/api/watchdog/reset', async (req, res) => {
|
|
2073
|
+
try {
|
|
2074
|
+
if (watchdog) {
|
|
2075
|
+
watchdog.reset();
|
|
2076
|
+
}
|
|
2077
|
+
res.json({ ok: true });
|
|
2078
|
+
} catch (err: any) {
|
|
2079
|
+
res.status(500).json({ error: err.message });
|
|
2080
|
+
}
|
|
2081
|
+
});
|
|
2082
|
+
|
|
2083
|
+
// 启动看门狗监控
|
|
2084
|
+
if (watchdog) {
|
|
2085
|
+
watchdog.start();
|
|
2086
|
+
console.log('[24h] Watchdog started');
|
|
2087
|
+
}
|
|
2088
|
+
|
|
2089
|
+
// 定期健康检查(不阻塞主服务器启动)
|
|
2090
|
+
if (healthMonitor) {
|
|
2091
|
+
healthMonitor.startPeriodicCheck(60000);
|
|
2092
|
+
console.log('[24h] Health monitor periodic check started');
|
|
2093
|
+
}
|
|
2094
|
+
|
|
1783
2095
|
return new Promise<{ app: express.Express; server: typeof server }>((resolve) => {
|
|
1784
2096
|
server.listen(port, () => {
|
|
1785
2097
|
console.log(`Web 服务器启动完成: http://localhost:${port}`);
|
|
@@ -1794,12 +2106,17 @@ app.get('/channels', async (_req, res) => {
|
|
|
1794
2106
|
});
|
|
1795
2107
|
}
|
|
1796
2108
|
|
|
1797
|
-
function broadcast(data:
|
|
2109
|
+
function broadcast(data: { type: string; [key: string]: unknown }, channelId?: string) {
|
|
1798
2110
|
const envelope = { ...data, channelId };
|
|
1799
2111
|
const message = `data: ${JSON.stringify(envelope)}\n\n`;
|
|
2112
|
+
console.log(`[broadcast] type=${data.type}, channelId=${channelId}, clients=${sseClients.size}`);
|
|
1800
2113
|
for (const client of sseClients) {
|
|
1801
2114
|
if (!channelId || client.channelId === channelId) {
|
|
1802
|
-
|
|
2115
|
+
try {
|
|
2116
|
+
client.res.write(message);
|
|
2117
|
+
} catch (e: unknown) {
|
|
2118
|
+
console.error(`[broadcast] 写入失败:`, (e as Error).message);
|
|
2119
|
+
}
|
|
1803
2120
|
}
|
|
1804
2121
|
}
|
|
1805
2122
|
}
|
package/src/web/style.css
CHANGED
|
@@ -594,6 +594,21 @@ body {
|
|
|
594
594
|
border-radius: 3px;
|
|
595
595
|
}
|
|
596
596
|
|
|
597
|
+
/* Channel messages container */
|
|
598
|
+
.channel-messages {
|
|
599
|
+
display: none;
|
|
600
|
+
flex: 1;
|
|
601
|
+
overflow-y: auto;
|
|
602
|
+
padding: 24px;
|
|
603
|
+
flex-direction: column;
|
|
604
|
+
gap: 20px;
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
.channel-messages.active,
|
|
608
|
+
.channel-messages:not([style*="display: none"]) {
|
|
609
|
+
display: flex;
|
|
610
|
+
}
|
|
611
|
+
|
|
597
612
|
.message {
|
|
598
613
|
display: flex;
|
|
599
614
|
flex-direction: column;
|
|
@@ -3231,6 +3246,109 @@ body {
|
|
|
3231
3246
|
cursor: pointer;
|
|
3232
3247
|
}
|
|
3233
3248
|
|
|
3249
|
+
/* Persistent Peers Section */
|
|
3250
|
+
.persistent-peers-section {
|
|
3251
|
+
margin: 16px 0;
|
|
3252
|
+
padding: 12px;
|
|
3253
|
+
background: var(--bg-secondary);
|
|
3254
|
+
border: 1px solid var(--border);
|
|
3255
|
+
border-radius: var(--radius);
|
|
3256
|
+
}
|
|
3257
|
+
|
|
3258
|
+
.persistent-peers-section h4 {
|
|
3259
|
+
margin-bottom: 12px;
|
|
3260
|
+
font-size: 14px;
|
|
3261
|
+
color: var(--text);
|
|
3262
|
+
}
|
|
3263
|
+
|
|
3264
|
+
.persistent-peer-item {
|
|
3265
|
+
display: flex;
|
|
3266
|
+
align-items: center;
|
|
3267
|
+
gap: 12px;
|
|
3268
|
+
padding: 12px;
|
|
3269
|
+
border: 1px solid var(--border);
|
|
3270
|
+
border-radius: 8px;
|
|
3271
|
+
margin-bottom: 8px;
|
|
3272
|
+
background: var(--bg-primary);
|
|
3273
|
+
transition: var(--transition);
|
|
3274
|
+
}
|
|
3275
|
+
|
|
3276
|
+
.persistent-peer-item:last-child {
|
|
3277
|
+
margin-bottom: 0;
|
|
3278
|
+
}
|
|
3279
|
+
|
|
3280
|
+
.persistent-peer-item.connected {
|
|
3281
|
+
border-color: var(--success);
|
|
3282
|
+
background: rgba(76, 175, 80, 0.05);
|
|
3283
|
+
}
|
|
3284
|
+
|
|
3285
|
+
.persistent-peer-item.disconnected {
|
|
3286
|
+
opacity: 0.8;
|
|
3287
|
+
}
|
|
3288
|
+
|
|
3289
|
+
.persistent-peer-item.reconnecting {
|
|
3290
|
+
border-color: var(--warning);
|
|
3291
|
+
animation: pulse 2s infinite;
|
|
3292
|
+
}
|
|
3293
|
+
|
|
3294
|
+
@keyframes pulse {
|
|
3295
|
+
0%, 100% { opacity: 1; }
|
|
3296
|
+
50% { opacity: 0.6; }
|
|
3297
|
+
}
|
|
3298
|
+
|
|
3299
|
+
.peer-status-indicator {
|
|
3300
|
+
flex-shrink: 0;
|
|
3301
|
+
}
|
|
3302
|
+
|
|
3303
|
+
.peer-status-indicator .dot {
|
|
3304
|
+
display: block;
|
|
3305
|
+
width: 10px;
|
|
3306
|
+
height: 10px;
|
|
3307
|
+
border-radius: 50%;
|
|
3308
|
+
}
|
|
3309
|
+
|
|
3310
|
+
.peer-status-indicator .dot.online {
|
|
3311
|
+
background: var(--success);
|
|
3312
|
+
box-shadow: 0 0 6px var(--success);
|
|
3313
|
+
}
|
|
3314
|
+
|
|
3315
|
+
.peer-status-indicator .dot.offline {
|
|
3316
|
+
background: var(--text-muted);
|
|
3317
|
+
}
|
|
3318
|
+
|
|
3319
|
+
.auto-badge {
|
|
3320
|
+
display: inline-block;
|
|
3321
|
+
padding: 2px 6px;
|
|
3322
|
+
font-size: 10px;
|
|
3323
|
+
background: var(--accent);
|
|
3324
|
+
color: var(--bg-primary);
|
|
3325
|
+
border-radius: 4px;
|
|
3326
|
+
margin-left: 6px;
|
|
3327
|
+
}
|
|
3328
|
+
|
|
3329
|
+
.peer-info .peer-name {
|
|
3330
|
+
display: flex;
|
|
3331
|
+
align-items: center;
|
|
3332
|
+
font-weight: 500;
|
|
3333
|
+
}
|
|
3334
|
+
|
|
3335
|
+
.peer-info .peer-meta {
|
|
3336
|
+
font-size: 12px;
|
|
3337
|
+
color: var(--text-secondary);
|
|
3338
|
+
display: flex;
|
|
3339
|
+
gap: 12px;
|
|
3340
|
+
margin-top: 4px;
|
|
3341
|
+
}
|
|
3342
|
+
|
|
3343
|
+
.btn-danger {
|
|
3344
|
+
background: var(--error);
|
|
3345
|
+
color: white;
|
|
3346
|
+
}
|
|
3347
|
+
|
|
3348
|
+
.btn-danger:hover {
|
|
3349
|
+
background: #c62828;
|
|
3350
|
+
}
|
|
3351
|
+
|
|
3234
3352
|
/* Modal Small */
|
|
3235
3353
|
.modal-sm {
|
|
3236
3354
|
max-width: 300px !important;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "CommonJS",
|
|
5
|
+
"moduleResolution": "node",
|
|
6
|
+
"outDir": "./bin",
|
|
7
|
+
"rootDir": "./src",
|
|
8
|
+
"strict": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"forceConsistentCasingInFileNames": true,
|
|
12
|
+
"resolveJsonModule": true
|
|
13
|
+
},
|
|
14
|
+
"include": ["src/cli-entry.ts"],
|
|
15
|
+
"exclude": ["node_modules"]
|
|
16
|
+
}
|
package/tsconfig.electron.json
CHANGED
|
@@ -16,5 +16,5 @@
|
|
|
16
16
|
"types": ["node"]
|
|
17
17
|
},
|
|
18
18
|
"include": ["src/electron.ts", "src/electron-preload.ts"],
|
|
19
|
-
"exclude": ["node_modules", "dist", "src/constraint-runtime", "src/agents", "src/web"]
|
|
19
|
+
"exclude": ["node_modules", "dist", "src/constraint-runtime", "src/agents", "src/web", "src/bollharness"]
|
|
20
20
|
}
|
package/tsconfig.json
CHANGED
|
@@ -10,10 +10,9 @@
|
|
|
10
10
|
"skipLibCheck": true,
|
|
11
11
|
"forceConsistentCasingInFileNames": true,
|
|
12
12
|
"resolveJsonModule": true,
|
|
13
|
-
"ignoreDeprecations": "6.0",
|
|
14
13
|
"jsx": "react-jsx",
|
|
15
14
|
"jsxImportSource": "react"
|
|
16
15
|
},
|
|
17
16
|
"include": ["src/**/*"],
|
|
18
|
-
"exclude": ["node_modules"]
|
|
17
|
+
"exclude": ["node_modules", "src/bollharness/node_modules"]
|
|
19
18
|
}
|