@hopping-dev/hub 0.1.0
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/assets/hopping-skill/SKILL.md +221 -0
- package/dist/approval/manager.d.ts +60 -0
- package/dist/approval/manager.d.ts.map +1 -0
- package/dist/approval/manager.js +101 -0
- package/dist/approval/manager.js.map +1 -0
- package/dist/approval/session-memory.d.ts +37 -0
- package/dist/approval/session-memory.d.ts.map +1 -0
- package/dist/approval/session-memory.js +63 -0
- package/dist/approval/session-memory.js.map +1 -0
- package/dist/cli/config-writer.d.ts +57 -0
- package/dist/cli/config-writer.d.ts.map +1 -0
- package/dist/cli/config-writer.js +318 -0
- package/dist/cli/config-writer.js.map +1 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +82 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/path-resolver.d.ts +48 -0
- package/dist/cli/path-resolver.d.ts.map +1 -0
- package/dist/cli/path-resolver.js +212 -0
- package/dist/cli/path-resolver.js.map +1 -0
- package/dist/cli/setup.d.ts +10 -0
- package/dist/cli/setup.d.ts.map +1 -0
- package/dist/cli/setup.js +268 -0
- package/dist/cli/setup.js.map +1 -0
- package/dist/cloud/connector.d.ts +74 -0
- package/dist/cloud/connector.d.ts.map +1 -0
- package/dist/cloud/connector.js +524 -0
- package/dist/cloud/connector.js.map +1 -0
- package/dist/cloud/index.d.ts +3 -0
- package/dist/cloud/index.d.ts.map +1 -0
- package/dist/cloud/index.js +6 -0
- package/dist/cloud/index.js.map +1 -0
- package/dist/config/manager.d.ts +76 -0
- package/dist/config/manager.d.ts.map +1 -0
- package/dist/config/manager.js +296 -0
- package/dist/config/manager.js.map +1 -0
- package/dist/dev-mode.d.ts +30 -0
- package/dist/dev-mode.d.ts.map +1 -0
- package/dist/dev-mode.js +53 -0
- package/dist/dev-mode.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +354 -0
- package/dist/index.js.map +1 -0
- package/dist/ipc/index.d.ts +12 -0
- package/dist/ipc/index.d.ts.map +1 -0
- package/dist/ipc/index.js +15 -0
- package/dist/ipc/index.js.map +1 -0
- package/dist/ipc/watcher.d.ts +226 -0
- package/dist/ipc/watcher.d.ts.map +1 -0
- package/dist/ipc/watcher.js +745 -0
- package/dist/ipc/watcher.js.map +1 -0
- package/dist/local/approval-dialog.d.ts +30 -0
- package/dist/local/approval-dialog.d.ts.map +1 -0
- package/dist/local/approval-dialog.js +214 -0
- package/dist/local/approval-dialog.js.map +1 -0
- package/dist/local/index.d.ts +8 -0
- package/dist/local/index.d.ts.map +1 -0
- package/dist/local/index.js +13 -0
- package/dist/local/index.js.map +1 -0
- package/dist/local/local-approval.d.ts +55 -0
- package/dist/local/local-approval.d.ts.map +1 -0
- package/dist/local/local-approval.js +125 -0
- package/dist/local/local-approval.js.map +1 -0
- package/dist/local/notifier.d.ts +19 -0
- package/dist/local/notifier.d.ts.map +1 -0
- package/dist/local/notifier.js +110 -0
- package/dist/local/notifier.js.map +1 -0
- package/dist/local/sanitize.d.ts +20 -0
- package/dist/local/sanitize.d.ts.map +1 -0
- package/dist/local/sanitize.js +28 -0
- package/dist/local/sanitize.js.map +1 -0
- package/dist/mcp/file-extractor.d.ts +11 -0
- package/dist/mcp/file-extractor.d.ts.map +1 -0
- package/dist/mcp/file-extractor.js +74 -0
- package/dist/mcp/file-extractor.js.map +1 -0
- package/dist/mcp/risk-level.d.ts +44 -0
- package/dist/mcp/risk-level.d.ts.map +1 -0
- package/dist/mcp/risk-level.js +127 -0
- package/dist/mcp/risk-level.js.map +1 -0
- package/dist/mcp/schemas.d.ts +83 -0
- package/dist/mcp/schemas.d.ts.map +1 -0
- package/dist/mcp/schemas.js +84 -0
- package/dist/mcp/schemas.js.map +1 -0
- package/dist/mcp/summary.d.ts +11 -0
- package/dist/mcp/summary.d.ts.map +1 -0
- package/dist/mcp/summary.js +150 -0
- package/dist/mcp/summary.js.map +1 -0
- package/dist/mcp/tools.d.ts +45 -0
- package/dist/mcp/tools.d.ts.map +1 -0
- package/dist/mcp/tools.js +1217 -0
- package/dist/mcp/tools.js.map +1 -0
- package/dist/pairing/auto-pairing.d.ts +37 -0
- package/dist/pairing/auto-pairing.d.ts.map +1 -0
- package/dist/pairing/auto-pairing.js +144 -0
- package/dist/pairing/auto-pairing.js.map +1 -0
- package/dist/pairing/binding-poller.d.ts +26 -0
- package/dist/pairing/binding-poller.d.ts.map +1 -0
- package/dist/pairing/binding-poller.js +108 -0
- package/dist/pairing/binding-poller.js.map +1 -0
- package/dist/pairing/pairing-server.d.ts +14 -0
- package/dist/pairing/pairing-server.d.ts.map +1 -0
- package/dist/pairing/pairing-server.js +277 -0
- package/dist/pairing/pairing-server.js.map +1 -0
- package/dist/pairing/qr-display.d.ts +14 -0
- package/dist/pairing/qr-display.d.ts.map +1 -0
- package/dist/pairing/qr-display.js +40 -0
- package/dist/pairing/qr-display.js.map +1 -0
- package/dist/policy/engine.d.ts +31 -0
- package/dist/policy/engine.d.ts.map +1 -0
- package/dist/policy/engine.js +187 -0
- package/dist/policy/engine.js.map +1 -0
- package/dist/policy/store.d.ts +26 -0
- package/dist/policy/store.d.ts.map +1 -0
- package/dist/policy/store.js +70 -0
- package/dist/policy/store.js.map +1 -0
- package/dist/policy/system-policies.d.ts +15 -0
- package/dist/policy/system-policies.d.ts.map +1 -0
- package/dist/policy/system-policies.js +265 -0
- package/dist/policy/system-policies.js.map +1 -0
- package/dist/policy/tool-mapping.d.ts +45 -0
- package/dist/policy/tool-mapping.d.ts.map +1 -0
- package/dist/policy/tool-mapping.js +88 -0
- package/dist/policy/tool-mapping.js.map +1 -0
- package/dist/policy/tool-registry.json +85 -0
- package/dist/store/db.d.ts +17 -0
- package/dist/store/db.d.ts.map +1 -0
- package/dist/store/db.js +193 -0
- package/dist/store/db.js.map +1 -0
- package/dist/store/index.d.ts +4 -0
- package/dist/store/index.d.ts.map +1 -0
- package/dist/store/index.js +7 -0
- package/dist/store/index.js.map +1 -0
- package/dist/store/metadata.d.ts +31 -0
- package/dist/store/metadata.d.ts.map +1 -0
- package/dist/store/metadata.js +178 -0
- package/dist/store/metadata.js.map +1 -0
- package/dist/store/operations.d.ts +26 -0
- package/dist/store/operations.d.ts.map +1 -0
- package/dist/store/operations.js +171 -0
- package/dist/store/operations.js.map +1 -0
- package/dist/utils/json.d.ts +7 -0
- package/dist/utils/json.d.ts.map +1 -0
- package/dist/utils/json.js +33 -0
- package/dist/utils/json.js.map +1 -0
- package/dist/utils/logger.d.ts +13 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +58 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/open-browser.d.ts +8 -0
- package/dist/utils/open-browser.d.ts.map +1 -0
- package/dist/utils/open-browser.js +38 -0
- package/dist/utils/open-browser.js.map +1 -0
- package/node_modules/@hopping/shared/dist/types.d.ts +649 -0
- package/node_modules/@hopping/shared/dist/types.js +48 -0
- package/node_modules/@hopping/shared/dist/types.js.map +1 -0
- package/node_modules/@hopping/shared/package.json +14 -0
- package/node_modules/@hopping/shared/tsconfig.json +16 -0
- package/node_modules/@hopping/shared/types.d.ts +650 -0
- package/node_modules/@hopping/shared/types.d.ts.map +1 -0
- package/node_modules/@hopping/shared/types.js +48 -0
- package/node_modules/@hopping/shared/types.js.map +1 -0
- package/node_modules/@hopping/shared/types.ts +895 -0
- package/package.json +52 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.approvalManager = exports.policyEngine = exports.policyStore = exports.logger = void 0;
|
|
37
|
+
const os = __importStar(require("os"));
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
40
|
+
const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
41
|
+
const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
|
|
42
|
+
const shared_1 = require("@hopping/shared");
|
|
43
|
+
const logger_1 = require("./utils/logger");
|
|
44
|
+
const store_1 = require("./policy/store");
|
|
45
|
+
const engine_1 = require("./policy/engine");
|
|
46
|
+
const manager_1 = require("./approval/manager");
|
|
47
|
+
const session_memory_1 = require("./approval/session-memory");
|
|
48
|
+
const dev_mode_1 = require("./dev-mode");
|
|
49
|
+
const tools_1 = require("./mcp/tools");
|
|
50
|
+
const store_2 = require("./store");
|
|
51
|
+
const ipc_1 = require("./ipc");
|
|
52
|
+
const cloud_1 = require("./cloud");
|
|
53
|
+
const manager_2 = require("./config/manager");
|
|
54
|
+
const local_1 = require("./local");
|
|
55
|
+
const auto_pairing_1 = require("./pairing/auto-pairing");
|
|
56
|
+
// IPC 根目錄(與 pre/post-tool-use.js 保持一致)
|
|
57
|
+
// pre-tool-use.js 寫 pending/,post-tool-use.js 讀 DB,IpcWatcher 讀 pending/ 寫 resolved/
|
|
58
|
+
const IPC_DIR = process.env.HOPPING_IPC_DIR
|
|
59
|
+
? path.resolve(process.env.HOPPING_IPC_DIR)
|
|
60
|
+
: path.join(os.homedir(), '.hopping');
|
|
61
|
+
const logger = new logger_1.Logger('HopPing Hub', 'info');
|
|
62
|
+
exports.logger = logger;
|
|
63
|
+
const startTime = Date.now();
|
|
64
|
+
// 初始化核心模組
|
|
65
|
+
const policyStore = new store_1.PolicyStore(logger);
|
|
66
|
+
exports.policyStore = policyStore;
|
|
67
|
+
const policyEngine = new engine_1.PolicyEngine(policyStore, logger);
|
|
68
|
+
exports.policyEngine = policyEngine;
|
|
69
|
+
const approvalManager = new manager_1.ApprovalManager(logger);
|
|
70
|
+
exports.approvalManager = approvalManager;
|
|
71
|
+
async function main() {
|
|
72
|
+
// 初始化 SQLite DB(在 main 內避免 import 副作用)
|
|
73
|
+
let hubDb;
|
|
74
|
+
try {
|
|
75
|
+
hubDb = new store_2.HubDatabase();
|
|
76
|
+
logger.info('SQLite DB 已初始化');
|
|
77
|
+
}
|
|
78
|
+
catch (err) {
|
|
79
|
+
logger.warn('SQLite DB 初始化失敗,降級為 log-only 模式', {
|
|
80
|
+
error: err instanceof Error ? err.message : 'Unknown error',
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
// 初始化 ConfigManager + 解析 agent token
|
|
84
|
+
// 優先順序:環境變數 HOPPING_AGENT_TOKEN > 本地設定 ~/.hopping/config.json > 無
|
|
85
|
+
const configManager = new manager_2.ConfigManager();
|
|
86
|
+
const initialToken = process.env.HOPPING_AGENT_TOKEN || (await configManager.getToken()) || undefined;
|
|
87
|
+
// 初始化 LocalApprovalChannel(S3-HUB-LOCAL-001)
|
|
88
|
+
// 讀取設定後建立,預設全部開啟(enabled/dialogEnabled/soundEnabled/raceMode=true)
|
|
89
|
+
const localApprovalConfig = await configManager.getLocalApprovalConfig();
|
|
90
|
+
const localApprovalChannel = new local_1.LocalApprovalChannel({
|
|
91
|
+
approvalManager,
|
|
92
|
+
logger,
|
|
93
|
+
config: localApprovalConfig,
|
|
94
|
+
});
|
|
95
|
+
if (initialToken) {
|
|
96
|
+
if (process.env.HOPPING_AGENT_TOKEN) {
|
|
97
|
+
logger.info('Hub token 來源:環境變數 HOPPING_AGENT_TOKEN');
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
logger.info('Hub token 來源:本地設定 ~/.hopping/config.json');
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
else if (dev_mode_1.DEV_MODE === 'isolated') {
|
|
104
|
+
logger.info('Hub 無 token,isolated 模式:以 local-only 模式啟動(不觸發自動配對)');
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
logger.info('Hub 無 token,將在 Agent 連線後觸發自動配對(QR Code 顯示於 stderr)');
|
|
108
|
+
}
|
|
109
|
+
const devModeInfo = (0, dev_mode_1.getDevModeInfo)();
|
|
110
|
+
logger.info('HopPing Hub 啟動中...', {
|
|
111
|
+
devMode: devModeInfo.mode,
|
|
112
|
+
allowlist: devModeInfo.allowlist,
|
|
113
|
+
});
|
|
114
|
+
if (dev_mode_1.DEV_MODE === 'isolated') {
|
|
115
|
+
logger.info('開發模式:isolated — 不攔截任何工具');
|
|
116
|
+
}
|
|
117
|
+
else if (dev_mode_1.DEV_MODE === 'allowlist') {
|
|
118
|
+
logger.info(`開發模式:allowlist — 只攔截 [${devModeInfo.allowlist.join(', ')}]`);
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
logger.info('開發模式:full — 攔截所有工具');
|
|
122
|
+
}
|
|
123
|
+
logger.info('Policy Engine 已就緒', {
|
|
124
|
+
policyCount: policyStore.getAllPolicies().length,
|
|
125
|
+
});
|
|
126
|
+
// 初始化 MCP Server(高階 API)
|
|
127
|
+
const mcpServer = new mcp_js_1.McpServer({ name: 'hopping-hub', version: '0.1.0' }, { capabilities: { tools: {} } });
|
|
128
|
+
// Agent type/id 在 connect 後透過 client info 更新
|
|
129
|
+
let agentType = 'generic_mcp';
|
|
130
|
+
let agentId = 'unknown';
|
|
131
|
+
// IpcWatcher 在 oninitialized 後才建立(需要知道 agentType)
|
|
132
|
+
// isolated 模式不啟動 watcher(不攔截任何工具)
|
|
133
|
+
let ipcWatcher;
|
|
134
|
+
// CloudConnector 在 oninitialized 後才建立(需要知道 agentType/agentId)
|
|
135
|
+
// isolated 模式不連 Cloud(純本地開發)
|
|
136
|
+
let cloudConnector;
|
|
137
|
+
// Fix: 用於 shutdown 時中止自動配對輪詢(pollBindingStatus 支援 AbortSignal)
|
|
138
|
+
let autoPairingController;
|
|
139
|
+
// Fix: 防止 shutdown 期間 onCloudConnectorUpdate 設置新 connector(race condition)
|
|
140
|
+
let isShuttingDown = false;
|
|
141
|
+
// S4-HUB-DONTASK-001: Session 免審記憶(in-memory,Hub 重啟後清空)
|
|
142
|
+
const sessionMemory = new session_memory_1.SessionMemory();
|
|
143
|
+
const toolHandler = (0, tools_1.createToolHandler)({
|
|
144
|
+
policyEngine,
|
|
145
|
+
approvalManager,
|
|
146
|
+
logger,
|
|
147
|
+
shouldIntercept: dev_mode_1.shouldIntercept,
|
|
148
|
+
startTime,
|
|
149
|
+
store: hubDb,
|
|
150
|
+
configManager,
|
|
151
|
+
localApprovalChannel,
|
|
152
|
+
get cloudUrl() {
|
|
153
|
+
return process.env.HOPPING_CLOUD_URL || 'http://localhost:3000';
|
|
154
|
+
},
|
|
155
|
+
get agentType() {
|
|
156
|
+
return agentType;
|
|
157
|
+
},
|
|
158
|
+
get agentId() {
|
|
159
|
+
return agentId;
|
|
160
|
+
},
|
|
161
|
+
get cloudConnector() {
|
|
162
|
+
return cloudConnector;
|
|
163
|
+
},
|
|
164
|
+
onCloudConnectorUpdate: (connector) => {
|
|
165
|
+
cloudConnector = connector;
|
|
166
|
+
logger.info('CloudConnector 已更新(hopping.pair 配對)');
|
|
167
|
+
},
|
|
168
|
+
// S4-HUB-DONTASK-001: Session 免審記憶
|
|
169
|
+
sessionMemory,
|
|
170
|
+
});
|
|
171
|
+
// 使用低階 server 手動設置工具 handlers(支援原始 JSON schema 定義)
|
|
172
|
+
const server = mcpServer.server;
|
|
173
|
+
server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => ({
|
|
174
|
+
tools: (0, tools_1.getToolDefinitions)(),
|
|
175
|
+
}));
|
|
176
|
+
server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
|
|
177
|
+
const { name, arguments: args } = request.params;
|
|
178
|
+
logger.debug('Tool call received', { name });
|
|
179
|
+
return toolHandler(name, args ?? {});
|
|
180
|
+
});
|
|
181
|
+
// Set oninitialized callback BEFORE connect() to avoid race
|
|
182
|
+
server.oninitialized = () => {
|
|
183
|
+
const clientVersion = server.getClientVersion();
|
|
184
|
+
if (clientVersion?.name) {
|
|
185
|
+
agentType = (0, shared_1.identifyAgentType)(clientVersion.name);
|
|
186
|
+
agentId = clientVersion.name;
|
|
187
|
+
logger.info('Agent 已連線', { agentId, agentType });
|
|
188
|
+
}
|
|
189
|
+
// IPC Watcher 啟動時機:Agent 連線完成後
|
|
190
|
+
//
|
|
191
|
+
// 為什麼在 oninitialized 啟動?
|
|
192
|
+
// 1. 此時 agentType 已確定(識別出 claude_code / cursor 等)
|
|
193
|
+
// 2. 工具呼叫只會在 MCP 初始化完成後發生,不會有遺漏
|
|
194
|
+
// 3. 避免在 isolated 模式下不必要地啟動檔案監聽
|
|
195
|
+
//
|
|
196
|
+
// isolated 模式不啟動:pre-tool-use.js 在 isolated 模式下直接 exit(0),
|
|
197
|
+
// 不會寫入任何 pending 檔案,watcher 沒有存在的必要
|
|
198
|
+
if (dev_mode_1.DEV_MODE !== 'isolated') {
|
|
199
|
+
ipcWatcher = new ipc_1.IpcWatcher({
|
|
200
|
+
ipcDir: IPC_DIR,
|
|
201
|
+
policyEngine,
|
|
202
|
+
store: hubDb,
|
|
203
|
+
logger,
|
|
204
|
+
shouldIntercept: dev_mode_1.shouldIntercept,
|
|
205
|
+
agentType, // oninitialized 後 agentType 已更新
|
|
206
|
+
startTime,
|
|
207
|
+
// S4-HOOK-RPERM-001: Mode-Aware 新增依賴
|
|
208
|
+
getAskMode: tools_1.getAskMode,
|
|
209
|
+
approvalManager,
|
|
210
|
+
getCloudConnector: () => cloudConnector,
|
|
211
|
+
configManager,
|
|
212
|
+
getAgentId: () => cloudConnector?.cloudAgentId ?? agentId,
|
|
213
|
+
// S4-HUB-YOLO-001: YOLO 觀測模式
|
|
214
|
+
isYoloMode: dev_mode_1.isYoloMode,
|
|
215
|
+
// S4-HUB-DONTASK-001: Session 免審記憶
|
|
216
|
+
sessionMemory,
|
|
217
|
+
});
|
|
218
|
+
ipcWatcher.start();
|
|
219
|
+
logger.info('IPC Watcher 已啟動', {
|
|
220
|
+
ipcDir: IPC_DIR,
|
|
221
|
+
devMode: dev_mode_1.DEV_MODE,
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
else {
|
|
225
|
+
logger.info('IPC Watcher 未啟動(isolated 模式,不攔截工具)');
|
|
226
|
+
}
|
|
227
|
+
// CloudConnector 啟動:non-isolated 模式 + 有 token(env 或本地設定)
|
|
228
|
+
if (dev_mode_1.DEV_MODE !== 'isolated' && initialToken) {
|
|
229
|
+
const cloudUrl = process.env.HOPPING_CLOUD_URL || 'http://localhost:3000';
|
|
230
|
+
cloudConnector = new cloud_1.CloudConnector({
|
|
231
|
+
cloudUrl,
|
|
232
|
+
agentToken: initialToken,
|
|
233
|
+
logger,
|
|
234
|
+
approvalManager,
|
|
235
|
+
store: hubDb,
|
|
236
|
+
// S4-HUB-YOLO-001: YOLO 模式通知 Mobile
|
|
237
|
+
getAgentMode: () => ((0, dev_mode_1.isYoloMode)() ? 'yolo' : 'normal'),
|
|
238
|
+
// S4-HUB-DONTASK-001: Session 免審記憶
|
|
239
|
+
sessionMemory,
|
|
240
|
+
});
|
|
241
|
+
// 非同步連線,不阻擋 MCP Server
|
|
242
|
+
// 連線超時後 socket.io 會自動在背景重連
|
|
243
|
+
cloudConnector
|
|
244
|
+
.connect()
|
|
245
|
+
.then(() => {
|
|
246
|
+
if (cloudConnector?.isConnected) {
|
|
247
|
+
logger.info('Cloud Gateway 已連線', { cloudUrl });
|
|
248
|
+
}
|
|
249
|
+
else {
|
|
250
|
+
logger.warn('Cloud Gateway 初始連線超時,Hub 以本地模式運行(自動重連中)', {
|
|
251
|
+
cloudUrl,
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
})
|
|
255
|
+
.catch((err) => {
|
|
256
|
+
logger.error('Cloud Gateway 連線例外', {
|
|
257
|
+
error: err instanceof Error ? err.message : 'Unknown error',
|
|
258
|
+
});
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
else if (dev_mode_1.DEV_MODE === 'isolated') {
|
|
262
|
+
logger.info('CloudConnector 未啟動(isolated 模式)');
|
|
263
|
+
}
|
|
264
|
+
else {
|
|
265
|
+
// 無 token + non-isolated 模式 → 觸發自動配對
|
|
266
|
+
logger.info('Hub 無 token,啟動自動配對流程...');
|
|
267
|
+
const cloudUrl = process.env.HOPPING_CLOUD_URL || 'http://localhost:3000';
|
|
268
|
+
// Fix: 建立 AbortController,讓 shutdown 可中止輪詢迴圈
|
|
269
|
+
autoPairingController = new AbortController();
|
|
270
|
+
(0, auto_pairing_1.startAutoPairing)({
|
|
271
|
+
cloudUrl,
|
|
272
|
+
configManager,
|
|
273
|
+
logger,
|
|
274
|
+
approvalManager,
|
|
275
|
+
store: hubDb,
|
|
276
|
+
onCloudConnectorUpdate: (connector) => {
|
|
277
|
+
// Fix: 防止 shutdown 期間設置新 connector(race condition)
|
|
278
|
+
if (isShuttingDown)
|
|
279
|
+
return;
|
|
280
|
+
cloudConnector = connector;
|
|
281
|
+
logger.info('CloudConnector 已更新(auto-pairing)');
|
|
282
|
+
},
|
|
283
|
+
signal: autoPairingController.signal,
|
|
284
|
+
})
|
|
285
|
+
.then((result) => {
|
|
286
|
+
logger.info('Auto-pairing 完成', {
|
|
287
|
+
agentId: result.agentId,
|
|
288
|
+
agentName: result.agentName,
|
|
289
|
+
connectedToCloud: result.connectedToCloud,
|
|
290
|
+
});
|
|
291
|
+
})
|
|
292
|
+
.catch((err) => {
|
|
293
|
+
// AbortError 是正常 shutdown,不需要 error log
|
|
294
|
+
if (err instanceof Error && err.name === 'AbortError') {
|
|
295
|
+
logger.info('Auto-pairing 已被 shutdown 中止');
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
logger.error('Auto-pairing 失敗,Hub 繼續以 local-only 模式運行', {
|
|
299
|
+
error: err instanceof Error ? err.message : 'Unknown error',
|
|
300
|
+
});
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
};
|
|
304
|
+
// 啟動 stdio transport
|
|
305
|
+
const transport = new stdio_js_1.StdioServerTransport();
|
|
306
|
+
await mcpServer.connect(transport);
|
|
307
|
+
logger.info('HopPing Hub 就緒,等待 MCP 工具呼叫');
|
|
308
|
+
// 優雅關閉
|
|
309
|
+
const shutdown = async () => {
|
|
310
|
+
logger.info('Hub 正在關閉...');
|
|
311
|
+
// Fix: 設置 flag 防止 onCloudConnectorUpdate 在 shutdown 期間設置新 connector
|
|
312
|
+
isShuttingDown = true;
|
|
313
|
+
// Fix: 中止自動配對輪詢(若進行中)
|
|
314
|
+
// pollBindingStatus 的 while 迴圈會在下次 fetch/sleep 時收到 AbortError 並退出
|
|
315
|
+
if (autoPairingController) {
|
|
316
|
+
autoPairingController.abort();
|
|
317
|
+
}
|
|
318
|
+
// 先停止 IPC Watcher,等待 in-flight handler 完成後再關 DB
|
|
319
|
+
if (ipcWatcher) {
|
|
320
|
+
await ipcWatcher.stop();
|
|
321
|
+
}
|
|
322
|
+
// 斷開 Cloud 連線
|
|
323
|
+
if (cloudConnector) {
|
|
324
|
+
await cloudConnector.disconnect();
|
|
325
|
+
}
|
|
326
|
+
approvalManager.clearAll();
|
|
327
|
+
hubDb?.close();
|
|
328
|
+
await mcpServer.close();
|
|
329
|
+
process.exit(0);
|
|
330
|
+
};
|
|
331
|
+
process.on('SIGINT', () => {
|
|
332
|
+
shutdown().catch((err) => {
|
|
333
|
+
logger.error('Shutdown failed', {
|
|
334
|
+
error: err instanceof Error ? err.message : 'Unknown error',
|
|
335
|
+
});
|
|
336
|
+
process.exit(1);
|
|
337
|
+
});
|
|
338
|
+
});
|
|
339
|
+
process.on('SIGTERM', () => {
|
|
340
|
+
shutdown().catch((err) => {
|
|
341
|
+
logger.error('Shutdown failed', {
|
|
342
|
+
error: err instanceof Error ? err.message : 'Unknown error',
|
|
343
|
+
});
|
|
344
|
+
process.exit(1);
|
|
345
|
+
});
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
main().catch((error) => {
|
|
349
|
+
logger.error('Hub 啟動失敗', {
|
|
350
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
351
|
+
});
|
|
352
|
+
process.exit(1);
|
|
353
|
+
});
|
|
354
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,uCAAyB;AACzB,2CAA6B;AAC7B,oEAAoE;AACpE,wEAAiF;AACjF,iEAAmG;AACnG,4CAAoD;AAEpD,2CAAwC;AACxC,0CAA6C;AAC7C,4CAA+C;AAC/C,gDAAqD;AACrD,8DAA0D;AAC1D,yCAAmF;AACnF,uCAAgF;AAChF,mCAAsC;AACtC,+BAAmC;AACnC,mCAAyC;AACzC,8CAAiD;AACjD,mCAA+C;AAC/C,yDAA0D;AAE1D,uCAAuC;AACvC,qFAAqF;AACrF,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,eAAe;IACzC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC;IAC3C,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,UAAU,CAAC,CAAC;AAExC,MAAM,MAAM,GAAG,IAAI,eAAM,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC;AAgUxC,wBAAM;AA/Tf,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;AAE7B,UAAU;AACV,MAAM,WAAW,GAAG,IAAI,mBAAW,CAAC,MAAM,CAAC,CAAC;AA4T3B,kCAAW;AA3T5B,MAAM,YAAY,GAAG,IAAI,qBAAY,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;AA2T7B,oCAAY;AA1T1C,MAAM,eAAe,GAAG,IAAI,yBAAe,CAAC,MAAM,CAAC,CAAC;AA0TR,0CAAe;AAxT3D,KAAK,UAAU,IAAI;IACjB,uCAAuC;IACvC,IAAI,KAA8B,CAAC;IACnC,IAAI,CAAC;QACH,KAAK,GAAG,IAAI,mBAAW,EAAE,CAAC;QAC1B,MAAM,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;IAChC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,CAAC,IAAI,CAAC,iCAAiC,EAAE;YAC7C,KAAK,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,eAAe;SAC5D,CAAC,CAAC;IACL,CAAC;IAED,qCAAqC;IACrC,kEAAkE;IAClE,MAAM,aAAa,GAAG,IAAI,uBAAa,EAAE,CAAC;IAC1C,MAAM,YAAY,GAChB,OAAO,CAAC,GAAG,CAAC,mBAAmB,IAAI,CAAC,MAAM,aAAa,CAAC,QAAQ,EAAE,CAAC,IAAI,SAAS,CAAC;IAEnF,6CAA6C;IAC7C,mEAAmE;IACnE,MAAM,mBAAmB,GAAG,MAAM,aAAa,CAAC,sBAAsB,EAAE,CAAC;IACzE,MAAM,oBAAoB,GAAG,IAAI,4BAAoB,CAAC;QACpD,eAAe;QACf,MAAM;QACN,MAAM,EAAE,mBAAmB;KAC5B,CAAC,CAAC;IAEH,IAAI,YAAY,EAAE,CAAC;QACjB,IAAI,OAAO,CAAC,GAAG,CAAC,mBAAmB,EAAE,CAAC;YACpC,MAAM,CAAC,IAAI,CAAC,uCAAuC,CAAC,CAAC;QACvD,CAAC;aAAM,CAAC;YACN,MAAM,CAAC,IAAI,CAAC,0CAA0C,CAAC,CAAC;QAC1D,CAAC;IACH,CAAC;SAAM,IAAI,mBAAQ,KAAK,UAAU,EAAE,CAAC;QACnC,MAAM,CAAC,IAAI,CAAC,oDAAoD,CAAC,CAAC;IACpE,CAAC;SAAM,CAAC;QACN,MAAM,CAAC,IAAI,CAAC,oDAAoD,CAAC,CAAC;IACpE,CAAC;IAED,MAAM,WAAW,GAAG,IAAA,yBAAc,GAAE,CAAC;IAErC,MAAM,CAAC,IAAI,CAAC,oBAAoB,EAAE;QAChC,OAAO,EAAE,WAAW,CAAC,IAAI;QACzB,SAAS,EAAE,WAAW,CAAC,SAAS;KACjC,CAAC,CAAC;IAEH,IAAI,mBAAQ,KAAK,UAAU,EAAE,CAAC;QAC5B,MAAM,CAAC,IAAI,CAAC,yBAAyB,CAAC,CAAC;IACzC,CAAC;SAAM,IAAI,mBAAQ,KAAK,WAAW,EAAE,CAAC;QACpC,MAAM,CAAC,IAAI,CAAC,yBAAyB,WAAW,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC5E,CAAC;SAAM,CAAC;QACN,MAAM,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC;IACpC,CAAC;IAED,MAAM,CAAC,IAAI,CAAC,mBAAmB,EAAE;QAC/B,WAAW,EAAE,WAAW,CAAC,cAAc,EAAE,CAAC,MAAM;KACjD,CAAC,CAAC;IAEH,yBAAyB;IACzB,MAAM,SAAS,GAAG,IAAI,kBAAS,CAC7B,EAAE,IAAI,EAAE,aAAa,EAAE,OAAO,EAAE,OAAO,EAAE,EACzC,EAAE,YAAY,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE,CAChC,CAAC;IAEF,6CAA6C;IAC7C,IAAI,SAAS,GAAc,aAAa,CAAC;IACzC,IAAI,OAAO,GAAG,SAAS,CAAC;IAExB,kDAAkD;IAClD,kCAAkC;IAClC,IAAI,UAAkC,CAAC;IAEvC,8DAA8D;IAC9D,6BAA6B;IAC7B,IAAI,cAA0C,CAAC;IAE/C,+DAA+D;IAC/D,IAAI,qBAAkD,CAAC;IACvD,2EAA2E;IAC3E,IAAI,cAAc,GAAG,KAAK,CAAC;IAE3B,wDAAwD;IACxD,MAAM,aAAa,GAAG,IAAI,8BAAa,EAAE,CAAC;IAE1C,MAAM,WAAW,GAAG,IAAA,yBAAiB,EAAC;QACpC,YAAY;QACZ,eAAe;QACf,MAAM;QACN,eAAe,EAAf,0BAAe;QACf,SAAS;QACT,KAAK,EAAE,KAAK;QACZ,aAAa;QACb,oBAAoB;QACpB,IAAI,QAAQ;YACV,OAAO,OAAO,CAAC,GAAG,CAAC,iBAAiB,IAAI,uBAAuB,CAAC;QAClE,CAAC;QACD,IAAI,SAAS;YACX,OAAO,SAAS,CAAC;QACnB,CAAC;QACD,IAAI,OAAO;YACT,OAAO,OAAO,CAAC;QACjB,CAAC;QACD,IAAI,cAAc;YAChB,OAAO,cAAc,CAAC;QACxB,CAAC;QACD,sBAAsB,EAAE,CAAC,SAAyB,EAAQ,EAAE;YAC1D,cAAc,GAAG,SAAS,CAAC;YAC3B,MAAM,CAAC,IAAI,CAAC,qCAAqC,CAAC,CAAC;QACrD,CAAC;QACD,mCAAmC;QACnC,aAAa;KACd,CAAC,CAAC;IAEH,mDAAmD;IACnD,MAAM,MAAM,GAAG,SAAS,CAAC,MAAM,CAAC;IAEhC,MAAM,CAAC,iBAAiB,CAAC,iCAAsB,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC;QAC5D,KAAK,EAAE,IAAA,0BAAkB,GAAE;KAC5B,CAAC,CAAC,CAAC;IAEJ,MAAM,CAAC,iBAAiB,CAAC,gCAAqB,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE;QAChE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC;QACjD,MAAM,CAAC,KAAK,CAAC,oBAAoB,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC;QAC7C,OAAO,WAAW,CAAC,IAAI,EAAG,IAAgC,IAAI,EAAE,CAAC,CAAC;IACpE,CAAC,CAAC,CAAC;IAEH,4DAA4D;IAC5D,MAAM,CAAC,aAAa,GAAG,GAAG,EAAE;QAC1B,MAAM,aAAa,GAAG,MAAM,CAAC,gBAAgB,EAAE,CAAC;QAChD,IAAI,aAAa,EAAE,IAAI,EAAE,CAAC;YACxB,SAAS,GAAG,IAAA,0BAAiB,EAAC,aAAa,CAAC,IAAI,CAAC,CAAC;YAClD,OAAO,GAAG,aAAa,CAAC,IAAI,CAAC;YAC7B,MAAM,CAAC,IAAI,CAAC,WAAW,EAAE,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC,CAAC;QACnD,CAAC;QAED,+BAA+B;QAC/B,EAAE;QACF,yBAAyB;QACzB,oDAAoD;QACpD,kCAAkC;QAClC,kCAAkC;QAClC,EAAE;QACF,2DAA2D;QAC3D,oCAAoC;QACpC,IAAI,mBAAQ,KAAK,UAAU,EAAE,CAAC;YAC5B,UAAU,GAAG,IAAI,gBAAU,CAAC;gBAC1B,MAAM,EAAE,OAAO;gBACf,YAAY;gBACZ,KAAK,EAAE,KAAK;gBACZ,MAAM;gBACN,eAAe,EAAf,0BAAe;gBACf,SAAS,EAAE,gCAAgC;gBAC3C,SAAS;gBACT,qCAAqC;gBACrC,UAAU,EAAV,kBAAU;gBACV,eAAe;gBACf,iBAAiB,EAAE,GAAG,EAAE,CAAC,cAAc;gBACvC,aAAa;gBACb,UAAU,EAAE,GAAG,EAAE,CAAC,cAAc,EAAE,YAAY,IAAI,OAAO;gBACzD,6BAA6B;gBAC7B,UAAU,EAAV,qBAAU;gBACV,mCAAmC;gBACnC,aAAa;aACd,CAAC,CAAC;YACH,UAAU,CAAC,KAAK,EAAE,CAAC;YAEnB,MAAM,CAAC,IAAI,CAAC,iBAAiB,EAAE;gBAC7B,MAAM,EAAE,OAAO;gBACf,OAAO,EAAE,mBAAQ;aAClB,CAAC,CAAC;QACL,CAAC;aAAM,CAAC;YACN,MAAM,CAAC,IAAI,CAAC,oCAAoC,CAAC,CAAC;QACpD,CAAC;QAED,yDAAyD;QACzD,IAAI,mBAAQ,KAAK,UAAU,IAAI,YAAY,EAAE,CAAC;YAC5C,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,iBAAiB,IAAI,uBAAuB,CAAC;YAC1E,cAAc,GAAG,IAAI,sBAAc,CAAC;gBAClC,QAAQ;gBACR,UAAU,EAAE,YAAY;gBACxB,MAAM;gBACN,eAAe;gBACf,KAAK,EAAE,KAAK;gBACZ,oCAAoC;gBACpC,YAAY,EAAE,GAAG,EAAE,CAAC,CAAC,IAAA,qBAAU,GAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC;gBACtD,mCAAmC;gBACnC,aAAa;aACd,CAAC,CAAC;YAEH,uBAAuB;YACvB,2BAA2B;YAC3B,cAAc;iBACX,OAAO,EAAE;iBACT,IAAI,CAAC,GAAG,EAAE;gBACT,IAAI,cAAc,EAAE,WAAW,EAAE,CAAC;oBAChC,MAAM,CAAC,IAAI,CAAC,mBAAmB,EAAE,EAAE,QAAQ,EAAE,CAAC,CAAC;gBACjD,CAAC;qBAAM,CAAC;oBACN,MAAM,CAAC,IAAI,CAAC,yCAAyC,EAAE;wBACrD,QAAQ;qBACT,CAAC,CAAC;gBACL,CAAC;YACH,CAAC,CAAC;iBACD,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;gBACb,MAAM,CAAC,KAAK,CAAC,oBAAoB,EAAE;oBACjC,KAAK,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,eAAe;iBAC5D,CAAC,CAAC;YACL,CAAC,CAAC,CAAC;QACP,CAAC;aAAM,IAAI,mBAAQ,KAAK,UAAU,EAAE,CAAC;YACnC,MAAM,CAAC,IAAI,CAAC,iCAAiC,CAAC,CAAC;QACjD,CAAC;aAAM,CAAC;YACN,qCAAqC;YACrC,MAAM,CAAC,IAAI,CAAC,yBAAyB,CAAC,CAAC;YACvC,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,iBAAiB,IAAI,uBAAuB,CAAC;YAE1E,6CAA6C;YAC7C,qBAAqB,GAAG,IAAI,eAAe,EAAE,CAAC;YAE9C,IAAA,+BAAgB,EAAC;gBACf,QAAQ;gBACR,aAAa;gBACb,MAAM;gBACN,eAAe;gBACf,KAAK,EAAE,KAAK;gBACZ,sBAAsB,EAAE,CAAC,SAAyB,EAAE,EAAE;oBACpD,mDAAmD;oBACnD,IAAI,cAAc;wBAAE,OAAO;oBAC3B,cAAc,GAAG,SAAS,CAAC;oBAC3B,MAAM,CAAC,IAAI,CAAC,kCAAkC,CAAC,CAAC;gBAClD,CAAC;gBACD,MAAM,EAAE,qBAAqB,CAAC,MAAM;aACrC,CAAC;iBACC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE;gBACf,MAAM,CAAC,IAAI,CAAC,iBAAiB,EAAE;oBAC7B,OAAO,EAAE,MAAM,CAAC,OAAO;oBACvB,SAAS,EAAE,MAAM,CAAC,SAAS;oBAC3B,gBAAgB,EAAE,MAAM,CAAC,gBAAgB;iBAC1C,CAAC,CAAC;YACL,CAAC,CAAC;iBACD,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;gBACb,wCAAwC;gBACxC,IAAI,GAAG,YAAY,KAAK,IAAI,GAAG,CAAC,IAAI,KAAK,YAAY,EAAE,CAAC;oBACtD,MAAM,CAAC,IAAI,CAAC,6BAA6B,CAAC,CAAC;oBAC3C,OAAO;gBACT,CAAC;gBACD,MAAM,CAAC,KAAK,CAAC,yCAAyC,EAAE;oBACtD,KAAK,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,eAAe;iBAC5D,CAAC,CAAC;YACL,CAAC,CAAC,CAAC;QACP,CAAC;IACH,CAAC,CAAC;IAEF,qBAAqB;IACrB,MAAM,SAAS,GAAG,IAAI,+BAAoB,EAAE,CAAC;IAC7C,MAAM,SAAS,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAEnC,MAAM,CAAC,IAAI,CAAC,4BAA4B,CAAC,CAAC;IAE1C,OAAO;IACP,MAAM,QAAQ,GAAG,KAAK,IAAmB,EAAE;QACzC,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;QAE3B,oEAAoE;QACpE,cAAc,GAAG,IAAI,CAAC;QAEtB,sBAAsB;QACtB,kEAAkE;QAClE,IAAI,qBAAqB,EAAE,CAAC;YAC1B,qBAAqB,CAAC,KAAK,EAAE,CAAC;QAChC,CAAC;QAED,gDAAgD;QAChD,IAAI,UAAU,EAAE,CAAC;YACf,MAAM,UAAU,CAAC,IAAI,EAAE,CAAC;QAC1B,CAAC;QAED,cAAc;QACd,IAAI,cAAc,EAAE,CAAC;YACnB,MAAM,cAAc,CAAC,UAAU,EAAE,CAAC;QACpC,CAAC;QAED,eAAe,CAAC,QAAQ,EAAE,CAAC;QAC3B,KAAK,EAAE,KAAK,EAAE,CAAC;QACf,MAAM,SAAS,CAAC,KAAK,EAAE,CAAC;QACxB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,CAAC;IAEF,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,EAAE;QACxB,QAAQ,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;YACvB,MAAM,CAAC,KAAK,CAAC,iBAAiB,EAAE;gBAC9B,KAAK,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,eAAe;aAC5D,CAAC,CAAC;YACH,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IACH,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,GAAG,EAAE;QACzB,QAAQ,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;YACvB,MAAM,CAAC,KAAK,CAAC,iBAAiB,EAAE;gBAC9B,KAAK,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,eAAe;aAC5D,CAAC,CAAC;YACH,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;IACrB,MAAM,CAAC,KAAK,CAAC,UAAU,EAAE;QACvB,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,eAAe;KAChE,CAAC,CAAC;IACH,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hub IPC 模組(barrel export)
|
|
3
|
+
*
|
|
4
|
+
* 提供 Hook ↔ Hub 的檔案交換 IPC 機制。
|
|
5
|
+
*
|
|
6
|
+
* 包含:
|
|
7
|
+
* IpcWatcher — 監聽 pending/ 目錄,處理 pre-tool-use.js 的審核請求
|
|
8
|
+
* IpcWatcherOptions — IpcWatcher 建構子選項型別
|
|
9
|
+
*/
|
|
10
|
+
export { IpcWatcher } from './watcher';
|
|
11
|
+
export type { IpcWatcherOptions } from './watcher';
|
|
12
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/ipc/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AACvC,YAAY,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.IpcWatcher = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Hub IPC 模組(barrel export)
|
|
6
|
+
*
|
|
7
|
+
* 提供 Hook ↔ Hub 的檔案交換 IPC 機制。
|
|
8
|
+
*
|
|
9
|
+
* 包含:
|
|
10
|
+
* IpcWatcher — 監聽 pending/ 目錄,處理 pre-tool-use.js 的審核請求
|
|
11
|
+
* IpcWatcherOptions — IpcWatcher 建構子選項型別
|
|
12
|
+
*/
|
|
13
|
+
var watcher_1 = require("./watcher");
|
|
14
|
+
Object.defineProperty(exports, "IpcWatcher", { enumerable: true, get: function () { return watcher_1.IpcWatcher; } });
|
|
15
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/ipc/index.ts"],"names":[],"mappings":";;;AAAA;;;;;;;;GAQG;AACH,qCAAuC;AAA9B,qGAAA,UAAU,OAAA"}
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hub IPC Watcher — Hook ↔ Hub 檔案交換橋樑
|
|
3
|
+
*
|
|
4
|
+
* === 架構背景 ===
|
|
5
|
+
*
|
|
6
|
+
* 問題:
|
|
7
|
+
* pre-tool-use.js 是 Claude Code 為每次工具呼叫啟動的獨立 Node 程序。
|
|
8
|
+
* Hub 使用 MCP stdio transport(由 Claude Code 以子程序啟動,透過 stdin/stdout 通訊)。
|
|
9
|
+
* 因此,Hook 程序無法直接連到 Hub(Hub 沒有 HTTP/WebSocket port)。
|
|
10
|
+
*
|
|
11
|
+
* 解決方案(檔案交換 IPC):
|
|
12
|
+
*
|
|
13
|
+
* pre-tool-use.js Hub(IpcWatcher)
|
|
14
|
+
* | |
|
|
15
|
+
* | 1. 寫入 pending/{id}.json |
|
|
16
|
+
* | (IpcPendingRequest JSON) |
|
|
17
|
+
* |---------------------------------------->|
|
|
18
|
+
* | | 2. chokidar 偵測到新檔案
|
|
19
|
+
* | | 3. PolicyEngine.evaluate()
|
|
20
|
+
* | | 4. 寫入 resolved/{id}.json
|
|
21
|
+
* | | (IpcResolvedResponse JSON)
|
|
22
|
+
* |<----------------------------------------|
|
|
23
|
+
* | 5. polling 讀到 resolved 檔案 |
|
|
24
|
+
* | 6. 輸出 permissionDecision 給 Claude Code|
|
|
25
|
+
*
|
|
26
|
+
* === 技術選擇 ===
|
|
27
|
+
*
|
|
28
|
+
* 為什麼用 chokidar(而不是 Node.js 原生 fs.watch)?
|
|
29
|
+
* - chokidar 跨平台更穩定(macOS 的 kqueue、Linux 的 inotify、Windows 的 ReadDirectoryChanges)
|
|
30
|
+
* - awaitWriteFinish 選項等待檔案寫入穩定後再觸發事件,避免讀到寫到一半的 JSON
|
|
31
|
+
* - Hub 是長期執行的 MCP Server,穩定性比輕量更重要
|
|
32
|
+
*
|
|
33
|
+
* 為什麼用原子寫入(.tmp + rename)?
|
|
34
|
+
* - rename 在同一 filesystem 上是原子操作(POSIX 保證)
|
|
35
|
+
* - 避免 pre-tool-use.js 讀到 Hub 寫到一半的 resolved JSON(導致 JSON.parse 失敗)
|
|
36
|
+
*
|
|
37
|
+
* === Mode-Aware 行為(S4-HOOK-RPERM-001)===
|
|
38
|
+
*
|
|
39
|
+
* IpcWatcher 依據 askMode 決定行為:
|
|
40
|
+
* - local 模式(預設):TD-007 觀測 — ALLOW/ASK/WARN 全部 approved=true
|
|
41
|
+
* - away 模式:真正攔截 — ALLOW 自動放行,ASK/WARN 送 Cloud 等手機審核
|
|
42
|
+
*
|
|
43
|
+
* 避免重複審核:
|
|
44
|
+
* - local 模式用戶在電腦前 → hopping.check 已處理審核,Hook 層不重複送 Cloud
|
|
45
|
+
* - away 模式用戶不在電腦前 → Hook 層負責攔截,hopping.check 只在 Skill 呼叫時觸發
|
|
46
|
+
*/
|
|
47
|
+
import type { AgentType, AskMode } from '@hopping/shared';
|
|
48
|
+
import type { PolicyEngine } from '../policy/engine';
|
|
49
|
+
import type { ApprovalManager } from '../approval/manager';
|
|
50
|
+
import type { CloudConnector } from '../cloud';
|
|
51
|
+
import type { ConfigManager } from '../config/manager';
|
|
52
|
+
import type { HubDatabase } from '../store';
|
|
53
|
+
import type { Logger } from '../utils/logger';
|
|
54
|
+
/** IpcWatcher 建構子選項 */
|
|
55
|
+
export interface IpcWatcherOptions {
|
|
56
|
+
/**
|
|
57
|
+
* IPC 根目錄(預設 ~/.hopping)
|
|
58
|
+
* pending/ 和 resolved/ 子目錄在此目錄下
|
|
59
|
+
*/
|
|
60
|
+
ipcDir: string;
|
|
61
|
+
/** Policy Engine 實例(決定工具呼叫是否放行) */
|
|
62
|
+
policyEngine: PolicyEngine;
|
|
63
|
+
/**
|
|
64
|
+
* Hub SQLite 資料庫(可選)
|
|
65
|
+
* 提供時,審核結果會記錄到 operations 和 metadata 表
|
|
66
|
+
* 不提供時,只進行檔案 IPC(不寫 DB)
|
|
67
|
+
*/
|
|
68
|
+
store?: HubDatabase;
|
|
69
|
+
/** Logger 實例 */
|
|
70
|
+
logger: Logger;
|
|
71
|
+
/**
|
|
72
|
+
* 判斷是否攔截此工具的函式
|
|
73
|
+
*
|
|
74
|
+
* 來自 dev-mode.ts 的 shouldIntercept():
|
|
75
|
+
* - isolated 模式 → 永遠 false(不攔截)
|
|
76
|
+
* - full 模式 → 永遠 true(全攔截)
|
|
77
|
+
* - allowlist 模式 → 只有在 allowlist 內才 true
|
|
78
|
+
*
|
|
79
|
+
* 這是「雞生蛋解法」的核心機制之一
|
|
80
|
+
*/
|
|
81
|
+
shouldIntercept: (toolName: string) => boolean;
|
|
82
|
+
/**
|
|
83
|
+
* 連線的 Agent 類型('claude_code' | 'cursor' | 'generic_mcp')
|
|
84
|
+
* 用於 PolicyEngine 評估和 SQLite metadata 記錄
|
|
85
|
+
*/
|
|
86
|
+
agentType: AgentType;
|
|
87
|
+
/**
|
|
88
|
+
* Hub 啟動時間戳(毫秒,Date.now())
|
|
89
|
+
* 用於計算 PolicyEngine 評估時的 session 持續時間
|
|
90
|
+
*/
|
|
91
|
+
startTime: number;
|
|
92
|
+
/**
|
|
93
|
+
* 取得當前 askMode 的 getter('local' | 'away')
|
|
94
|
+
* 來自 tools.ts 的 getAskMode(),每次 handlePendingFile 時呼叫
|
|
95
|
+
* 同步讀取模組變數,不存在 race condition
|
|
96
|
+
*/
|
|
97
|
+
getAskMode: () => AskMode;
|
|
98
|
+
/**
|
|
99
|
+
* ApprovalManager 實例(away 模式用)
|
|
100
|
+
* waitForApproval() 等待 Mobile 回應
|
|
101
|
+
*/
|
|
102
|
+
approvalManager: ApprovalManager;
|
|
103
|
+
/**
|
|
104
|
+
* CloudConnector getter(away 模式用)
|
|
105
|
+
* 使用 getter pattern:CloudConnector 可能在 IpcWatcher 建立後才初始化
|
|
106
|
+
* 每次 handlePendingFile 時動態取得
|
|
107
|
+
*/
|
|
108
|
+
getCloudConnector: () => CloudConnector | undefined;
|
|
109
|
+
/**
|
|
110
|
+
* ConfigManager(可選,away 模式用)
|
|
111
|
+
* 提供 approval timeout 設定
|
|
112
|
+
*/
|
|
113
|
+
configManager?: ConfigManager;
|
|
114
|
+
/**
|
|
115
|
+
* Cloud agentId getter(可選,away 模式用)
|
|
116
|
+
* sendPermissionPrompt 需要 agentId
|
|
117
|
+
*/
|
|
118
|
+
getAgentId?: () => string;
|
|
119
|
+
/**
|
|
120
|
+
* YOLO 觀測模式偵測 getter(S4-HUB-YOLO-001)
|
|
121
|
+
* 來自 dev-mode.ts 的 isYoloMode(),每次 handlePendingFile 時呼叫
|
|
122
|
+
* 使用 getter pattern 與 getAskMode 保持一致,避免 import 循環
|
|
123
|
+
*/
|
|
124
|
+
isYoloMode?: () => boolean;
|
|
125
|
+
/**
|
|
126
|
+
* S4-HUB-DONTASK-001:Session 免審記憶
|
|
127
|
+
* away 模式下,先查 session memory,命中則直接 auto-approve 不送 Cloud
|
|
128
|
+
*/
|
|
129
|
+
sessionMemory?: import('../approval/session-memory').SessionMemory;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* IPC Watcher:Hub 端的 Hook 請求處理器
|
|
133
|
+
*
|
|
134
|
+
* 在 Hub process 內執行,監聽 pending/ 目錄的新 JSON 檔案,
|
|
135
|
+
* 對每個審核請求執行 PolicyEngine 評估,並將結果寫回 resolved/ 目錄。
|
|
136
|
+
*
|
|
137
|
+
* S4-HOOK-RPERM-001: Mode-Aware
|
|
138
|
+
* - local 模式 → TD-007 觀測(auto-approve)
|
|
139
|
+
* - away 模式 → 真正攔截(ALLOW 放行,ASK/WARN 送 Cloud)
|
|
140
|
+
*
|
|
141
|
+
* 生命週期:
|
|
142
|
+
* new IpcWatcher(opts) → start() → [處理請求...] → stop()
|
|
143
|
+
*/
|
|
144
|
+
export declare class IpcWatcher {
|
|
145
|
+
private readonly opts;
|
|
146
|
+
/** pending/ 目錄完整路徑 */
|
|
147
|
+
private readonly pendingDir;
|
|
148
|
+
/** resolved/ 目錄完整路徑 */
|
|
149
|
+
private readonly resolvedDir;
|
|
150
|
+
/** chokidar FSWatcher 實例(null 表示尚未啟動或已停止) */
|
|
151
|
+
private watcher;
|
|
152
|
+
/** 目前是否在執行中(防止重複 start()) */
|
|
153
|
+
private isRunning;
|
|
154
|
+
/** 快取的 tool registry(避免每次 away-mode 審核都從磁碟讀取,隨實例生命週期管理) */
|
|
155
|
+
private cachedRegistry;
|
|
156
|
+
constructor(opts: IpcWatcherOptions);
|
|
157
|
+
/**
|
|
158
|
+
* 啟動 IPC 監聽
|
|
159
|
+
*
|
|
160
|
+
* 建立必要目錄,並開始用 chokidar 監聽 pending/ 的新增 .json 檔案。
|
|
161
|
+
* 呼叫多次不會重複啟動(有 isRunning 保護)。
|
|
162
|
+
*/
|
|
163
|
+
start(): void;
|
|
164
|
+
/**
|
|
165
|
+
* 停止 IPC 監聽
|
|
166
|
+
*
|
|
167
|
+
* 在 Hub 收到 SIGINT/SIGTERM 時呼叫(hub/src/index.ts 的 shutdown())。
|
|
168
|
+
* 停止後 Hub 不再處理新的 pending 請求,但已在進行中的請求不受影響。
|
|
169
|
+
*/
|
|
170
|
+
stop(): Promise<void>;
|
|
171
|
+
/**
|
|
172
|
+
* 處理一個新的 pending 請求檔案
|
|
173
|
+
*
|
|
174
|
+
* 完整流程:
|
|
175
|
+
* 1. 讀取並解析 JSON(IpcPendingRequest)
|
|
176
|
+
* 2. 判斷是否需要攔截(shouldIntercept)
|
|
177
|
+
* 3. 執行 PolicyEngine 評估(若需要攔截)
|
|
178
|
+
* 4. Mode-Aware 決策:
|
|
179
|
+
* - ALLOW(any mode)→ auto-approve
|
|
180
|
+
* - local + ASK/WARN → auto-approve(TD-007 觀測)
|
|
181
|
+
* - away + ASK/WARN → 送 Cloud 等手機審核
|
|
182
|
+
* 5. 原子寫入 resolved 回應(IpcResolvedResponse)
|
|
183
|
+
* 6. 寫入 SQLite 操作記錄(若 store 可用)
|
|
184
|
+
*
|
|
185
|
+
* @param filePath - pending/ 目錄中的 JSON 檔案完整路徑
|
|
186
|
+
*/
|
|
187
|
+
private handlePendingFile;
|
|
188
|
+
/**
|
|
189
|
+
* Away 模式的 Cloud 審核流程
|
|
190
|
+
*
|
|
191
|
+
* 1. Cloud connected → sendPermissionPrompt + waitForApproval
|
|
192
|
+
* 2. Cloud disconnected → auto-allow fallback(降級觀測)
|
|
193
|
+
* 3. Timeout → 依 riskLevel 決策
|
|
194
|
+
* 4. Denied → approved=false
|
|
195
|
+
*/
|
|
196
|
+
private handleAwayModeApproval;
|
|
197
|
+
/**
|
|
198
|
+
* 安全取得 ApprovalTimeoutConfig
|
|
199
|
+
*/
|
|
200
|
+
private getApprovalConfigSafe;
|
|
201
|
+
/**
|
|
202
|
+
* 原子寫入 resolved 回應檔案
|
|
203
|
+
*
|
|
204
|
+
* 步驟:
|
|
205
|
+
* 1. 寫入 {id}.json.tmp(暫存)
|
|
206
|
+
* 2. rename 為 {id}.json(原子替換)
|
|
207
|
+
*
|
|
208
|
+
* 為什麼需要原子寫入?
|
|
209
|
+
* pre-tool-use.js 每 200ms polling resolved/ 目錄,
|
|
210
|
+
* 若不用原子寫入,可能讀到寫到一半的 JSON(JSON.parse 失敗)。
|
|
211
|
+
* rename 在同一 filesystem 上是 POSIX 原子操作,不存在這個問題。
|
|
212
|
+
*
|
|
213
|
+
* @param id - IPC 請求 ID(對應 pending 檔案名,不含副檔名)
|
|
214
|
+
* @param response - 要寫入的回應資料
|
|
215
|
+
*/
|
|
216
|
+
private writeResolved;
|
|
217
|
+
/**
|
|
218
|
+
* 清理 pending 檔案
|
|
219
|
+
*/
|
|
220
|
+
private cleanupPending;
|
|
221
|
+
/**
|
|
222
|
+
* 將操作記錄寫入 SQLite operations + metadata 表
|
|
223
|
+
*/
|
|
224
|
+
private writeOperationRecord;
|
|
225
|
+
}
|
|
226
|
+
//# sourceMappingURL=watcher.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"watcher.d.ts","sourceRoot":"","sources":["../../src/ipc/watcher.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CG;AAKH,OAAO,KAAK,EAIV,SAAS,EACT,OAAO,EAOR,MAAM,iBAAiB,CAAC;AACzB,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAE3D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAC/C,OAAO,KAAK,EAAE,aAAa,EAAyB,MAAM,mBAAmB,CAAC;AAE9E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAC5C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AA2B9C,uBAAuB;AACvB,MAAM,WAAW,iBAAiB;IAChC;;;OAGG;IACH,MAAM,EAAE,MAAM,CAAC;IAEf,mCAAmC;IACnC,YAAY,EAAE,YAAY,CAAC;IAE3B;;;;OAIG;IACH,KAAK,CAAC,EAAE,WAAW,CAAC;IAEpB,gBAAgB;IAChB,MAAM,EAAE,MAAM,CAAC;IAEf;;;;;;;;;OASG;IACH,eAAe,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC;IAE/C;;;OAGG;IACH,SAAS,EAAE,SAAS,CAAC;IAErB;;;OAGG;IACH,SAAS,EAAE,MAAM,CAAC;IAMlB;;;;OAIG;IACH,UAAU,EAAE,MAAM,OAAO,CAAC;IAE1B;;;OAGG;IACH,eAAe,EAAE,eAAe,CAAC;IAEjC;;;;OAIG;IACH,iBAAiB,EAAE,MAAM,cAAc,GAAG,SAAS,CAAC;IAEpD;;;OAGG;IACH,aAAa,CAAC,EAAE,aAAa,CAAC;IAE9B;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,MAAM,CAAC;IAE1B;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,OAAO,CAAC;IAC3B;;;OAGG;IACH,aAAa,CAAC,EAAE,OAAO,4BAA4B,EAAE,aAAa,CAAC;CACpE;AAmBD;;;;;;;;;;;;GAYG;AACH,qBAAa,UAAU;IAgBT,OAAO,CAAC,QAAQ,CAAC,IAAI;IAfjC,sBAAsB;IACtB,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IAEpC,uBAAuB;IACvB,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IAErC,6CAA6C;IAC7C,OAAO,CAAC,OAAO,CAA0B;IAEzC,6BAA6B;IAC7B,OAAO,CAAC,SAAS,CAAS;IAE1B,2DAA2D;IAC3D,OAAO,CAAC,cAAc,CAA6B;gBAEtB,IAAI,EAAE,iBAAiB;IAKpD;;;;;OAKG;IACH,KAAK,IAAI,IAAI;IA0Eb;;;;;OAKG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAwB3B;;;;;;;;;;;;;;;OAeG;YACW,iBAAiB;IAuN/B;;;;;;;OAOG;YACW,sBAAsB;IA8LpC;;OAEG;YACW,qBAAqB;IASnC;;;;;;;;;;;;;;OAcG;IACH,OAAO,CAAC,aAAa;IA2BrB;;OAEG;IACH,OAAO,CAAC,cAAc;IAQtB;;OAEG;IACH,OAAO,CAAC,oBAAoB;CAwE7B"}
|