@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.
Files changed (165) hide show
  1. package/assets/hopping-skill/SKILL.md +221 -0
  2. package/dist/approval/manager.d.ts +60 -0
  3. package/dist/approval/manager.d.ts.map +1 -0
  4. package/dist/approval/manager.js +101 -0
  5. package/dist/approval/manager.js.map +1 -0
  6. package/dist/approval/session-memory.d.ts +37 -0
  7. package/dist/approval/session-memory.d.ts.map +1 -0
  8. package/dist/approval/session-memory.js +63 -0
  9. package/dist/approval/session-memory.js.map +1 -0
  10. package/dist/cli/config-writer.d.ts +57 -0
  11. package/dist/cli/config-writer.d.ts.map +1 -0
  12. package/dist/cli/config-writer.js +318 -0
  13. package/dist/cli/config-writer.js.map +1 -0
  14. package/dist/cli/index.d.ts +3 -0
  15. package/dist/cli/index.d.ts.map +1 -0
  16. package/dist/cli/index.js +82 -0
  17. package/dist/cli/index.js.map +1 -0
  18. package/dist/cli/path-resolver.d.ts +48 -0
  19. package/dist/cli/path-resolver.d.ts.map +1 -0
  20. package/dist/cli/path-resolver.js +212 -0
  21. package/dist/cli/path-resolver.js.map +1 -0
  22. package/dist/cli/setup.d.ts +10 -0
  23. package/dist/cli/setup.d.ts.map +1 -0
  24. package/dist/cli/setup.js +268 -0
  25. package/dist/cli/setup.js.map +1 -0
  26. package/dist/cloud/connector.d.ts +74 -0
  27. package/dist/cloud/connector.d.ts.map +1 -0
  28. package/dist/cloud/connector.js +524 -0
  29. package/dist/cloud/connector.js.map +1 -0
  30. package/dist/cloud/index.d.ts +3 -0
  31. package/dist/cloud/index.d.ts.map +1 -0
  32. package/dist/cloud/index.js +6 -0
  33. package/dist/cloud/index.js.map +1 -0
  34. package/dist/config/manager.d.ts +76 -0
  35. package/dist/config/manager.d.ts.map +1 -0
  36. package/dist/config/manager.js +296 -0
  37. package/dist/config/manager.js.map +1 -0
  38. package/dist/dev-mode.d.ts +30 -0
  39. package/dist/dev-mode.d.ts.map +1 -0
  40. package/dist/dev-mode.js +53 -0
  41. package/dist/dev-mode.js.map +1 -0
  42. package/dist/index.d.ts +10 -0
  43. package/dist/index.d.ts.map +1 -0
  44. package/dist/index.js +354 -0
  45. package/dist/index.js.map +1 -0
  46. package/dist/ipc/index.d.ts +12 -0
  47. package/dist/ipc/index.d.ts.map +1 -0
  48. package/dist/ipc/index.js +15 -0
  49. package/dist/ipc/index.js.map +1 -0
  50. package/dist/ipc/watcher.d.ts +226 -0
  51. package/dist/ipc/watcher.d.ts.map +1 -0
  52. package/dist/ipc/watcher.js +745 -0
  53. package/dist/ipc/watcher.js.map +1 -0
  54. package/dist/local/approval-dialog.d.ts +30 -0
  55. package/dist/local/approval-dialog.d.ts.map +1 -0
  56. package/dist/local/approval-dialog.js +214 -0
  57. package/dist/local/approval-dialog.js.map +1 -0
  58. package/dist/local/index.d.ts +8 -0
  59. package/dist/local/index.d.ts.map +1 -0
  60. package/dist/local/index.js +13 -0
  61. package/dist/local/index.js.map +1 -0
  62. package/dist/local/local-approval.d.ts +55 -0
  63. package/dist/local/local-approval.d.ts.map +1 -0
  64. package/dist/local/local-approval.js +125 -0
  65. package/dist/local/local-approval.js.map +1 -0
  66. package/dist/local/notifier.d.ts +19 -0
  67. package/dist/local/notifier.d.ts.map +1 -0
  68. package/dist/local/notifier.js +110 -0
  69. package/dist/local/notifier.js.map +1 -0
  70. package/dist/local/sanitize.d.ts +20 -0
  71. package/dist/local/sanitize.d.ts.map +1 -0
  72. package/dist/local/sanitize.js +28 -0
  73. package/dist/local/sanitize.js.map +1 -0
  74. package/dist/mcp/file-extractor.d.ts +11 -0
  75. package/dist/mcp/file-extractor.d.ts.map +1 -0
  76. package/dist/mcp/file-extractor.js +74 -0
  77. package/dist/mcp/file-extractor.js.map +1 -0
  78. package/dist/mcp/risk-level.d.ts +44 -0
  79. package/dist/mcp/risk-level.d.ts.map +1 -0
  80. package/dist/mcp/risk-level.js +127 -0
  81. package/dist/mcp/risk-level.js.map +1 -0
  82. package/dist/mcp/schemas.d.ts +83 -0
  83. package/dist/mcp/schemas.d.ts.map +1 -0
  84. package/dist/mcp/schemas.js +84 -0
  85. package/dist/mcp/schemas.js.map +1 -0
  86. package/dist/mcp/summary.d.ts +11 -0
  87. package/dist/mcp/summary.d.ts.map +1 -0
  88. package/dist/mcp/summary.js +150 -0
  89. package/dist/mcp/summary.js.map +1 -0
  90. package/dist/mcp/tools.d.ts +45 -0
  91. package/dist/mcp/tools.d.ts.map +1 -0
  92. package/dist/mcp/tools.js +1217 -0
  93. package/dist/mcp/tools.js.map +1 -0
  94. package/dist/pairing/auto-pairing.d.ts +37 -0
  95. package/dist/pairing/auto-pairing.d.ts.map +1 -0
  96. package/dist/pairing/auto-pairing.js +144 -0
  97. package/dist/pairing/auto-pairing.js.map +1 -0
  98. package/dist/pairing/binding-poller.d.ts +26 -0
  99. package/dist/pairing/binding-poller.d.ts.map +1 -0
  100. package/dist/pairing/binding-poller.js +108 -0
  101. package/dist/pairing/binding-poller.js.map +1 -0
  102. package/dist/pairing/pairing-server.d.ts +14 -0
  103. package/dist/pairing/pairing-server.d.ts.map +1 -0
  104. package/dist/pairing/pairing-server.js +277 -0
  105. package/dist/pairing/pairing-server.js.map +1 -0
  106. package/dist/pairing/qr-display.d.ts +14 -0
  107. package/dist/pairing/qr-display.d.ts.map +1 -0
  108. package/dist/pairing/qr-display.js +40 -0
  109. package/dist/pairing/qr-display.js.map +1 -0
  110. package/dist/policy/engine.d.ts +31 -0
  111. package/dist/policy/engine.d.ts.map +1 -0
  112. package/dist/policy/engine.js +187 -0
  113. package/dist/policy/engine.js.map +1 -0
  114. package/dist/policy/store.d.ts +26 -0
  115. package/dist/policy/store.d.ts.map +1 -0
  116. package/dist/policy/store.js +70 -0
  117. package/dist/policy/store.js.map +1 -0
  118. package/dist/policy/system-policies.d.ts +15 -0
  119. package/dist/policy/system-policies.d.ts.map +1 -0
  120. package/dist/policy/system-policies.js +265 -0
  121. package/dist/policy/system-policies.js.map +1 -0
  122. package/dist/policy/tool-mapping.d.ts +45 -0
  123. package/dist/policy/tool-mapping.d.ts.map +1 -0
  124. package/dist/policy/tool-mapping.js +88 -0
  125. package/dist/policy/tool-mapping.js.map +1 -0
  126. package/dist/policy/tool-registry.json +85 -0
  127. package/dist/store/db.d.ts +17 -0
  128. package/dist/store/db.d.ts.map +1 -0
  129. package/dist/store/db.js +193 -0
  130. package/dist/store/db.js.map +1 -0
  131. package/dist/store/index.d.ts +4 -0
  132. package/dist/store/index.d.ts.map +1 -0
  133. package/dist/store/index.js +7 -0
  134. package/dist/store/index.js.map +1 -0
  135. package/dist/store/metadata.d.ts +31 -0
  136. package/dist/store/metadata.d.ts.map +1 -0
  137. package/dist/store/metadata.js +178 -0
  138. package/dist/store/metadata.js.map +1 -0
  139. package/dist/store/operations.d.ts +26 -0
  140. package/dist/store/operations.d.ts.map +1 -0
  141. package/dist/store/operations.js +171 -0
  142. package/dist/store/operations.js.map +1 -0
  143. package/dist/utils/json.d.ts +7 -0
  144. package/dist/utils/json.d.ts.map +1 -0
  145. package/dist/utils/json.js +33 -0
  146. package/dist/utils/json.js.map +1 -0
  147. package/dist/utils/logger.d.ts +13 -0
  148. package/dist/utils/logger.d.ts.map +1 -0
  149. package/dist/utils/logger.js +58 -0
  150. package/dist/utils/logger.js.map +1 -0
  151. package/dist/utils/open-browser.d.ts +8 -0
  152. package/dist/utils/open-browser.d.ts.map +1 -0
  153. package/dist/utils/open-browser.js +38 -0
  154. package/dist/utils/open-browser.js.map +1 -0
  155. package/node_modules/@hopping/shared/dist/types.d.ts +649 -0
  156. package/node_modules/@hopping/shared/dist/types.js +48 -0
  157. package/node_modules/@hopping/shared/dist/types.js.map +1 -0
  158. package/node_modules/@hopping/shared/package.json +14 -0
  159. package/node_modules/@hopping/shared/tsconfig.json +16 -0
  160. package/node_modules/@hopping/shared/types.d.ts +650 -0
  161. package/node_modules/@hopping/shared/types.d.ts.map +1 -0
  162. package/node_modules/@hopping/shared/types.js +48 -0
  163. package/node_modules/@hopping/shared/types.js.map +1 -0
  164. package/node_modules/@hopping/shared/types.ts +895 -0
  165. package/package.json +52 -0
@@ -0,0 +1,895 @@
1
+ // ============================================
2
+ // HopPing 共享型別定義
3
+ // 跨 Hub / Backend / Mobile / Hooks 共用
4
+ // ============================================
5
+
6
+ // ============================================
7
+ // Section 1:通用 Enums / Literal Unions
8
+ // ============================================
9
+
10
+ /** 風險等級(Cloud ApprovalSummary.riskLevel + Hub operations.risk_level) */
11
+ export type RiskLevel = 'low' | 'medium' | 'high' | 'critical';
12
+
13
+ /** 審核決策(Cloud ApprovalSummary.decision) */
14
+ export type ApprovalDecision = 'approved' | 'denied' | 'conditional' | 'timeout' | 'pending';
15
+
16
+ /** 超時行為子集(toApprovalDecision() 的輸出集合,不含 conditional) */
17
+ export type TimeoutDecision = 'approved' | 'denied' | 'timeout' | 'pending';
18
+
19
+ /** Agent 連線狀態(Cloud Agent.status) */
20
+ export type AgentStatus = 'online' | 'offline' | 'error';
21
+
22
+ // ============================================
23
+ // Section 2:Agent Types
24
+ // ============================================
25
+
26
+ export type AgentType = 'claude_code' | 'cursor' | 'generic_mcp';
27
+
28
+ export type AgentCapability =
29
+ | 'shell_execution'
30
+ | 'file_operations'
31
+ | 'mcp_tools'
32
+ | 'composer_mode'
33
+ | 'web_scraping'
34
+ | 'code_generation';
35
+
36
+ /** @deprecated 請改用 AgentStatus */
37
+ export type AgentConnectionStatus = AgentStatus;
38
+
39
+ export interface AgentInfo {
40
+ id: string;
41
+ type: AgentType;
42
+ name: string;
43
+ version: string;
44
+ capabilities: AgentCapability[];
45
+ connectedAt: string;
46
+ status: AgentStatus;
47
+ }
48
+
49
+ /**
50
+ * 根據 clientInfo.name 識別 Agent 類型
51
+ */
52
+ export function identifyAgentType(clientName: string): AgentType {
53
+ const normalized = clientName.toLowerCase();
54
+
55
+ const mapping: Record<string, AgentType> = {
56
+ cursor: 'cursor',
57
+ 'cursor-agent': 'cursor',
58
+ 'claude-code': 'claude_code',
59
+ claudecode: 'claude_code',
60
+ 'claude code': 'claude_code',
61
+ };
62
+
63
+ if (mapping[normalized]) {
64
+ return mapping[normalized];
65
+ }
66
+
67
+ for (const [key, type] of Object.entries(mapping)) {
68
+ if (normalized.includes(key)) {
69
+ return type;
70
+ }
71
+ }
72
+
73
+ return 'generic_mcp';
74
+ }
75
+
76
+ // ============================================
77
+ // Section 3:Policy Types
78
+ // ============================================
79
+
80
+ export type PolicyAction = 'WARN' | 'ASK' | 'ALLOW';
81
+
82
+ export type PolicyType =
83
+ | 'command_filter'
84
+ | 'file_protection'
85
+ | 'cost_limit'
86
+ | 'time_restriction'
87
+ | 'network_filter'
88
+ | 'custom';
89
+
90
+ export type PolicyConditionField =
91
+ | 'command'
92
+ | 'command_args'
93
+ | 'file_path'
94
+ | 'file_extension'
95
+ | 'file_action'
96
+ | 'sql_statement'
97
+ | 'api_endpoint'
98
+ | 'token_cost'
99
+ | 'session_cost'
100
+ | 'time_of_day'
101
+ | 'agent_type'
102
+ | 'action_type'
103
+ | 'target'
104
+ | 'tool_name';
105
+
106
+ export type PolicyConditionOperator =
107
+ | 'equals'
108
+ | 'not_equals'
109
+ | 'contains'
110
+ | 'not_contains'
111
+ | 'matches_regex'
112
+ | 'greater_than'
113
+ | 'less_than'
114
+ | 'in_list'
115
+ | 'not_in_list';
116
+
117
+ export interface PolicyCondition {
118
+ field: PolicyConditionField;
119
+ operator: PolicyConditionOperator;
120
+ value: string | number | string[];
121
+ caseSensitive?: boolean;
122
+ }
123
+
124
+ export interface Policy {
125
+ id: string;
126
+ name: string;
127
+ description: string;
128
+ type: PolicyType;
129
+ enabled: boolean;
130
+ priority: number;
131
+ isSystem: boolean;
132
+ conditions: PolicyCondition[];
133
+ action: PolicyAction;
134
+ scope: {
135
+ agentTypes?: AgentType[];
136
+ agentIds?: string[];
137
+ };
138
+ }
139
+
140
+ export interface TriggeredPolicy {
141
+ policyId: string;
142
+ policyName: string;
143
+ action: PolicyAction;
144
+ matchedConditions: MatchedCondition[];
145
+ }
146
+
147
+ export interface MatchedCondition {
148
+ field: string;
149
+ operator: string;
150
+ expected: unknown;
151
+ actual: unknown;
152
+ }
153
+
154
+ export interface PolicyEvaluationResult {
155
+ requestId: string;
156
+ finalAction: PolicyAction;
157
+ triggeredPolicies: TriggeredPolicy[];
158
+ evaluationTimeMs: number;
159
+ evaluatedAt: string;
160
+ }
161
+
162
+ export interface EvaluationContext {
163
+ requestId: string;
164
+ agentId: string;
165
+ agentType: AgentType;
166
+ operation: {
167
+ type: string;
168
+ target?: string;
169
+ toolName?: string;
170
+ command?: string;
171
+ filePath?: string;
172
+ sqlStatement?: string;
173
+ apiEndpoint?: string;
174
+ };
175
+ session: {
176
+ costUsd: number;
177
+ durationSeconds: number;
178
+ approvalCount: number;
179
+ };
180
+ timestamp: string;
181
+ }
182
+
183
+ // ============================================
184
+ // Section 3b:Rich Permission 基礎型別(PermissionPromptPayload 的依賴型別)
185
+ // ============================================
186
+
187
+ /** Prompt 互動類型(預設 'approval') */
188
+ export type PromptType = 'approval' | 'native_permission' | 'question';
189
+
190
+ /** 回應格式類型(預設 'binary') */
191
+ export type ResponseType = 'binary' | 'choice' | 'freetext';
192
+
193
+ /** hopping.ask 支援的回應格式(排除 binary,ask 不支援二元回應) */
194
+ export type AskResponseType = Exclude<ResponseType, 'binary'>;
195
+
196
+ /** 選擇題選項 */
197
+ export interface PromptOption {
198
+ id: string;
199
+ label: string;
200
+ description?: string;
201
+ /** 若為 true,Mobile 選中後展開 textarea 讓用戶輸入自訂文字。
202
+ * 回應時 answer = option.id,customText = 用戶輸入內容。
203
+ * Agent 應優先讀取 customText(若有值)作為實際指令。 */
204
+ isOther?: boolean;
205
+ }
206
+
207
+ /** 被審核工具的上下文(描述「要執行什麼」,與 taskContext 的「在做什麼」語意不同) */
208
+ export interface ToolContext {
209
+ /** 工具的人類可讀描述(如 "執行 Shell 指令") */
210
+ description?: string;
211
+ /** 工具的完整參數(型別與 HoppingCheckInput.toolInput 一致;
212
+ * 結構化工具參數屬 Tool Registry 層,不在此處定義) */
213
+ parameters?: Record<string, unknown>;
214
+ }
215
+
216
+ // ============================================
217
+ // Section 4:WebSocket 事件型別(Socket.IO 型別安全)
218
+ // Hub ↔ Cloud ↔ Mobile 全 9 事件
219
+ // ============================================
220
+
221
+ // --- Hub → Cloud → Mobile ---
222
+
223
+ export interface PermissionPromptPayload {
224
+ id: string; // approval ID
225
+ agentId: string;
226
+ toolName: string;
227
+ riskLevel: RiskLevel;
228
+ summary: string; // 簡短摘要(顯示在通知/卡片)
229
+ policyAction: PolicyAction; // WARN | ASK
230
+ sessionId?: string;
231
+ timestamp: string;
232
+ /** Hub 對此審核實際使用的超時時間(ms)。Cloud 用此值排程自己的超時清理 job(delay = timeoutMs + buffer)。
233
+ * 舊版 Hub 不包含此欄位時,Cloud 使用 300,000ms (5 min) 作為 fallback。 */
234
+ timeoutMs?: number;
235
+ /** Hub 配置的超時行為(TimeoutDecision — 不含 conditional)。Cloud 用此值決定排程策略:
236
+ * 'pending' → 使用 PENDING_SAFETY_NET_MS(12h)延長排程;其他值 → 使用 timeoutMs + buffer。
237
+ * undefined → 向下相容,使用預設排程(same as 'timeout')。 */
238
+ onTimeout?: TimeoutDecision;
239
+ /** 任務上下文(truncated 200 chars each,不含 userPrompt) */
240
+ taskContext?: {
241
+ taskDescription?: string;
242
+ reasoning?: string;
243
+ };
244
+ /** Prompt 互動類型(預設 'approval';舊版 Hub 不送此欄位時 Mobile 使用 'approval') */
245
+ promptType?: PromptType;
246
+ /** 回應格式(預設 'binary';舊版 Hub 不送此欄位時 Mobile 使用 'binary') */
247
+ responseType?: ResponseType;
248
+ /** 選擇題選項(responseType='choice' 時有效) */
249
+ options?: PromptOption[];
250
+ /** 自由輸入提示文字(responseType='freetext' 時有效) */
251
+ placeholder?: string;
252
+ /** 被審核工具的上下文(工具描述 + 完整參數,Mobile 用於顯示更豐富的審核資訊) */
253
+ toolContext?: ToolContext;
254
+ }
255
+
256
+ export interface AgentStatusPayload {
257
+ agentId: string;
258
+ status: AgentStatus;
259
+ agentName?: string;
260
+ agentType?: AgentType;
261
+ timestamp: string;
262
+ agentMode?: AgentMode; // YOLO 模式時為 'yolo',一般為 'normal' 或省略
263
+ agentModeChangedAt?: string; // ISO 8601 UTC — Hub 進入 YOLO 模式的時間戳,切回 normal 時省略
264
+ }
265
+
266
+ export type ProgressMessageType =
267
+ | 'progress'
268
+ | 'command_ack'
269
+ | 'status_report'
270
+ | 'operation_report'
271
+ | 'task_completed'
272
+ | 'yolo_observation';
273
+
274
+ export interface ProgressUpdatePayload {
275
+ agentId: string;
276
+ sessionId: string;
277
+ message: string;
278
+ progress?: number; // 0–100
279
+ timestamp: string;
280
+ messageType?: ProgressMessageType;
281
+ // YOLO 觀測欄位(全部 optional,向下相容)
282
+ toolName?: string; // YOLO: 被 auto-approved 的工具名
283
+ riskLevel?: RiskLevel; // YOLO: Policy Engine 評估的風險等級
284
+ decision?: string; // YOLO: 'auto_approved'
285
+ filePaths?: string[]; // YOLO: 受影響的檔案路徑
286
+ }
287
+
288
+ // --- Mobile → Cloud → Hub ---
289
+
290
+ export interface PermissionResponsePayload {
291
+ id: string; // 對應 approval ID
292
+ decision: ApprovalDecision;
293
+ reason?: string;
294
+ respondedBy?: string; // user ID
295
+ timestamp?: string; // Mobile 回應時間(Cloud 可用於 decidedAt)
296
+ /** 用戶選擇的選項 ID(responseType='choice' 時) */
297
+ selectedOption?: string;
298
+ /** 用戶輸入的自由文字(responseType='freetext' 時) */
299
+ freetext?: string;
300
+ /** 當選擇 isOther=true 選項時,用戶輸入的自訂文字(選擇題 + 自訂輸入的組合回應) */
301
+ customText?: string;
302
+ }
303
+
304
+ export interface DetailRequestPayload {
305
+ operationId: string;
306
+ }
307
+
308
+ /** YOLO session 操作歷史項目(Hub → Cloud → Mobile,透過 stats_response.timeline 傳遞) */
309
+ export interface SessionTimelineItem {
310
+ operationId: string;
311
+ toolName: string;
312
+ riskLevel: RiskLevel | null;
313
+ decision: string | null;
314
+ durationMs: number | null;
315
+ source: string | null;
316
+ createdAt: string;
317
+ }
318
+
319
+ export interface StatsRequestPayload {
320
+ agentId: string;
321
+ period?: 'day' | 'week' | 'month';
322
+ /** JS getTimezoneOffset() semantics: minutes west of UTC. UTC+8 = -480, UTC-7 = 420. Defaults to 0 (UTC). */
323
+ timezoneOffset?: number;
324
+ /** 查詢類型:'stats'(預設)或 'session_timeline'(YOLO 歷史補齊) */
325
+ type?: 'stats' | 'session_timeline';
326
+ /** session_timeline 專用:最多回傳筆數(預設 100) */
327
+ limit?: number;
328
+ /** session_timeline 專用:指定 session(不傳則由 Hub 查最近操作) */
329
+ sessionId?: string;
330
+ }
331
+
332
+ export interface CommandPayload {
333
+ agentId: string;
334
+ command: string;
335
+ args?: Record<string, unknown>;
336
+ }
337
+
338
+ export interface StatsRequestBatchPayload {
339
+ agentIds: string[];
340
+ period?: 'day' | 'week' | 'month';
341
+ /** JS getTimezoneOffset() semantics: minutes west of UTC. UTC+8 = -480, UTC-7 = 420. Defaults to 0 (UTC). */
342
+ timezoneOffset?: number;
343
+ }
344
+
345
+ export interface StatsResponseBatchPayload {
346
+ results: StatsResponsePayload[];
347
+ period: 'day' | 'week' | 'month';
348
+ }
349
+
350
+ // --- Hub → Cloud → Mobile(回應)---
351
+
352
+ /** detail_response 錯誤碼 — Gateway 透傳層產生 */
353
+ export const DETAIL_RESPONSE_ERRORS = {
354
+ INVALID_OPERATION_ID: 'INVALID_OPERATION_ID',
355
+ NOT_FOUND: 'NOT_FOUND',
356
+ FORBIDDEN: 'FORBIDDEN',
357
+ HUB_OFFLINE: 'HUB_OFFLINE',
358
+ INTERNAL_ERROR: 'INTERNAL_ERROR',
359
+ } as const;
360
+
361
+ export type DetailResponseError =
362
+ (typeof DETAIL_RESPONSE_ERRORS)[keyof typeof DETAIL_RESPONSE_ERRORS];
363
+
364
+ export interface DetailResponsePayload {
365
+ operationId: string;
366
+ toolName: string;
367
+ toolParams?: Record<string, unknown>;
368
+ filePaths?: string[];
369
+ riskLevel?: RiskLevel;
370
+ decision?: ApprovalDecision;
371
+ createdAt: string;
372
+ decidedAt?: string;
373
+ /** 操作元資料(來自 Hub SQLite metadata 表,透過 detail_request 透傳) */
374
+ metadata?: {
375
+ agentType?: string;
376
+ sessionId?: string;
377
+ durationMs?: number | null;
378
+ contextTags?: Record<string, unknown> | null;
379
+ source?: OperationSource;
380
+ resultStatus?: ResultStatus;
381
+ toolResultSummary?: string;
382
+ };
383
+ /** Gateway 錯誤碼 — 存在時表示無法取得詳情 */
384
+ error?: DetailResponseError;
385
+ }
386
+
387
+ /** stats_response 錯誤碼 — Gateway 透傳層產生 */
388
+ export const STATS_RESPONSE_ERRORS = {
389
+ INVALID_AGENT_ID: 'INVALID_AGENT_ID',
390
+ AGENT_NOT_FOUND: 'AGENT_NOT_FOUND',
391
+ HUB_OFFLINE: 'HUB_OFFLINE',
392
+ TIMEOUT: 'TIMEOUT',
393
+ INTERNAL_ERROR: 'INTERNAL_ERROR',
394
+ } as const;
395
+
396
+ export type StatsResponseError = (typeof STATS_RESPONSE_ERRORS)[keyof typeof STATS_RESPONSE_ERRORS];
397
+
398
+ export interface StatsResponsePayload {
399
+ agentId: string;
400
+ period: 'day' | 'week' | 'month';
401
+ totalOperations: number;
402
+ approvedCount: number;
403
+ deniedCount: number;
404
+ autoApprovedCount: number;
405
+ topTools: Array<{ toolName: string; count: number }>;
406
+ /** Gateway 錯誤碼 — 存在時表示無法取得統計資料 */
407
+ error?: StatsResponseError;
408
+ /** YOLO session 操作歷史(type=session_timeline 時填充) */
409
+ timeline?: SessionTimelineItem[];
410
+ }
411
+
412
+ // --- Socket.IO Typed Events ---
413
+
414
+ export interface ServerToClientEvents {
415
+ permission_prompt: (payload: PermissionPromptPayload) => void;
416
+ agent_status: (payload: AgentStatusPayload) => void;
417
+ progress_update: (payload: ProgressUpdatePayload) => void;
418
+ detail_response: (payload: DetailResponsePayload) => void;
419
+ stats_response: (payload: StatsResponsePayload) => void;
420
+ stats_response_batch: (payload: StatsResponseBatchPayload) => void;
421
+ }
422
+
423
+ export interface ClientToServerEvents {
424
+ permission_response: (payload: PermissionResponsePayload) => void;
425
+ detail_request: (payload: DetailRequestPayload) => void;
426
+ stats_request: (payload: StatsRequestPayload) => void;
427
+ stats_request_batch: (payload: StatsRequestBatchPayload) => void;
428
+ command: (payload: CommandPayload) => void;
429
+ }
430
+
431
+ // Hub → Cloud(同 ServerToClient,Cloud 透傳)
432
+ export type HubToCloudEvents = ServerToClientEvents;
433
+
434
+ // Cloud → Hub(同 ClientToServer,Cloud 透傳)
435
+ export type CloudToHubEvents = ClientToServerEvents;
436
+
437
+ // ============================================
438
+ // Section 5:MCP Tool 型別
439
+ // ============================================
440
+
441
+ export type HoppingToolName =
442
+ | 'hopping.check'
443
+ | 'hopping.report'
444
+ | 'hopping.status'
445
+ | 'hopping.pair'
446
+ | 'hopping.respond'
447
+ | 'hopping.ask'
448
+ | 'hopping.mode';
449
+
450
+ // hopping.check — Agent 呼叫審核
451
+ export interface HoppingCheckInput {
452
+ toolName: string; // 被檢查的工具名
453
+ toolInput: Record<string, unknown>; // 被檢查工具的參數
454
+ sessionId?: string;
455
+ cwd?: string;
456
+ context?: {
457
+ recentFiles?: string[];
458
+ branch?: string;
459
+ project?: string;
460
+ /** Agent 正在執行的任務描述(由 Skill 引導提供) */
461
+ taskDescription?: string;
462
+ /** Agent 為什麼需要此操作的推理(由 Skill 引導提供) */
463
+ reasoning?: string;
464
+ /** 用戶的原始 prompt(敏感資料,僅存本地 Hub SQLite,不送 Cloud) */
465
+ userPrompt?: string;
466
+ };
467
+ }
468
+
469
+ export interface HoppingCheckOutput {
470
+ approvalId: string;
471
+ decision: ApprovalDecision;
472
+ riskLevel: RiskLevel;
473
+ policyResult: PolicyEvaluationResult;
474
+ message: string; // human-readable 結果說明
475
+ waitedForRemote: boolean; // 是否等了手機回覆
476
+ }
477
+
478
+ // hopping.report — Agent 自報操作(無 Hook 的 Agent 用)
479
+ export interface HoppingReportInput {
480
+ toolName: string;
481
+ toolParams?: Record<string, unknown>;
482
+ result?: string;
483
+ duration?: number; // ms
484
+ success?: boolean;
485
+ filePaths?: string[];
486
+ sessionId?: string; // Agent session ID(覆蓋 deps.agentId fallback)
487
+ agentType?: string; // Agent 類型(覆蓋 deps.agentType)
488
+ context?: {
489
+ branch?: string;
490
+ project?: string;
491
+ cwd?: string;
492
+ taskCompleted?: boolean;
493
+ };
494
+ }
495
+
496
+ export interface HoppingReportOutput {
497
+ recorded: boolean;
498
+ operationId: string;
499
+ }
500
+
501
+ // hopping.status — 查詢 Hub 狀態
502
+ export interface HoppingStatusInput {
503
+ query?: 'sessions' | 'pending' | 'stats' | 'all';
504
+ agentId?: string;
505
+ }
506
+
507
+ export interface HoppingStatusOutput {
508
+ activeSessions: number;
509
+ pendingApprovals: number;
510
+ todayOperations: number;
511
+ hubUptime: number; // seconds
512
+ connectedToCloud: boolean;
513
+ /** S4-HUB-DONTASK-001: Don't-ask-again tools currently in session memory */
514
+ sessionMemory?: Array<{ toolName: string; riskLevel: RiskLevel; addedAt: number }>;
515
+ }
516
+
517
+ // hopping.respond — Skill 驅動的本地審核(S2-HUB-APR-002)
518
+ export interface HoppingRespondInput {
519
+ approvalId: string;
520
+ decision: 'approved' | 'denied';
521
+ reason?: string;
522
+ }
523
+
524
+ export interface HoppingRespondOutput {
525
+ success: boolean;
526
+ decision?: ApprovalDecision;
527
+ source?: 'local' | 'remote';
528
+ error?: string;
529
+ }
530
+
531
+ // hopping.ask — Agent 向用戶發送非審核類問題(選擇題或自由填寫)
532
+ export interface HoppingAskInput {
533
+ question: string;
534
+ responseType: AskResponseType;
535
+ options?: PromptOption[];
536
+ placeholder?: string;
537
+ context?: {
538
+ taskDescription?: string;
539
+ reasoning?: string;
540
+ };
541
+ }
542
+
543
+ /** hopping.ask 回應狀態 */
544
+ export type AskStatus = 'ask_locally' | 'answered' | 'timeout';
545
+
546
+ export interface HoppingAskOutput {
547
+ questionId: string;
548
+ /** 回應狀態:ask_locally(local 模式立即回傳)、answered(away 模式收到回答)、timeout(away 模式超時) */
549
+ status: AskStatus;
550
+ /** responseType='choice' 時為選中的 option.id;responseType='freetext' 時為用戶輸入文字 */
551
+ answer: string;
552
+ /** 當用戶選擇 isOther=true 選項時,用戶輸入的自訂文字。
553
+ * Agent 應優先讀取 customText(若有值)作為實際指令,answer 為選項 id。 */
554
+ customText?: string;
555
+ respondedBy: 'mobile' | 'local';
556
+ message: string;
557
+ }
558
+
559
+ // hopping.mode — Local/Away 模式切換
560
+ export type AskMode = 'local' | 'away';
561
+
562
+ export interface HoppingModeInput {
563
+ mode: AskMode;
564
+ }
565
+
566
+ export interface HoppingModeOutput {
567
+ mode: AskMode;
568
+ message: string;
569
+ }
570
+
571
+ /** hopping.ask 的 timeout 設定(~/.hopping/config.json askConfig section) */
572
+ export interface AskConfig {
573
+ timeoutEnabled: boolean;
574
+ timeoutMs: number;
575
+ }
576
+
577
+ // hopping.pair — Agent 呼叫配對流程
578
+ export interface HoppingPairInput {
579
+ pairingCode: string;
580
+ cloudUrl?: string; // 覆蓋預設 Cloud URL
581
+ }
582
+
583
+ export interface HoppingPairOutput {
584
+ paired: boolean;
585
+ agentId?: string;
586
+ agentName?: string;
587
+ connectedToCloud: boolean;
588
+ message: string;
589
+ }
590
+
591
+ // ============================================
592
+ // Section 6:Hub SQLite 記錄型別
593
+ // ============================================
594
+
595
+ /** operations 表對應 */
596
+ export interface OperationRecord {
597
+ id: string;
598
+ approvalId: string | null;
599
+ toolName: string;
600
+ toolParams: string | null; // JSON string
601
+ filePaths: string | null; // JSON array string
602
+ riskLevel: RiskLevel | null;
603
+ decision: ApprovalDecision | null;
604
+ createdAt: string; // ISO 8601
605
+ decidedAt: string | null;
606
+ /** Claude Code tool_use_id — pre/post 關聯鍵(v3 新增) */
607
+ toolUseId: string | null;
608
+ /** Prompt 互動類型(v4 新增) */
609
+ promptType?: PromptType | null;
610
+ /** 回應格式類型(v4 新增) */
611
+ responseType?: ResponseType | null;
612
+ /** Rich response JSON:{ selectedOption?, freetext?, customText? }(v4 新增) */
613
+ responseData?: string | null;
614
+ }
615
+
616
+ /** metadata.source 的值域 */
617
+ export type OperationSource = 'check' | 'report' | 'hook';
618
+
619
+ /** metadata.result_status 的值域 */
620
+ export type ResultStatus = 'success' | 'failure' | 'unknown';
621
+
622
+ /** metadata 表對應 */
623
+ export interface OperationMetadataRecord {
624
+ id: number;
625
+ operationId: string;
626
+ agentType: string;
627
+ sessionId: string;
628
+ durationMs: number | null;
629
+ tokenUsage: number | null;
630
+ contextTags: string | null; // JSON string
631
+ source: OperationSource | null;
632
+ resultStatus: ResultStatus | null;
633
+ toolResultSummary: string | null; // 截斷結果摘要(max 500 chars)
634
+ createdAt: string;
635
+ }
636
+
637
+ // ============================================
638
+ // Section 7:API 共享 DTO
639
+ // ============================================
640
+
641
+ export interface PaginatedResponse<T> {
642
+ data: T[];
643
+ meta: {
644
+ total: number;
645
+ page: number;
646
+ limit: number;
647
+ totalPages: number;
648
+ timestamp: string;
649
+ };
650
+ }
651
+
652
+ export interface PaginationQuery {
653
+ page?: number;
654
+ limit?: number;
655
+ sortBy?: string;
656
+ sortOrder?: 'asc' | 'desc';
657
+ }
658
+
659
+ /** User 公開資訊(不含 OAuth 敏感欄位) */
660
+ export interface UserDto {
661
+ id: string;
662
+ email: string;
663
+ name: string | null;
664
+ avatarUrl: string | null;
665
+ createdAt: string;
666
+ }
667
+
668
+ /** Agent 公開資訊(不含 token) */
669
+ export interface AgentDto {
670
+ id: string;
671
+ name: string;
672
+ agentType: AgentType;
673
+ status: AgentStatus;
674
+ createdAt: string;
675
+ updatedAt: string;
676
+ }
677
+
678
+ /** Approval 摘要 DTO */
679
+ export interface ApprovalSummaryDto {
680
+ id: string;
681
+ agentId: string;
682
+ toolName: string;
683
+ riskLevel: RiskLevel;
684
+ summary: string;
685
+ decision: ApprovalDecision | null;
686
+ reason: string | null;
687
+ policyAction: PolicyAction | null;
688
+ sessionId: string | null;
689
+ taskDescription: string | null;
690
+ reasoning: string | null;
691
+ createdAt: string;
692
+ decidedAt: string | null;
693
+ promptType: PromptType | null;
694
+ responseType: ResponseType | null;
695
+ selectedOption: string | null;
696
+ freetext: string | null;
697
+ customText: string | null;
698
+ // S4-BE-RPERM-002: 冷啟動資料補齊
699
+ options: PromptOption[] | null;
700
+ placeholder: string | null;
701
+ timeoutMs: number | null;
702
+ toolDescription: string | null;
703
+ }
704
+
705
+ // ============================================
706
+ // Section 8:Session Types
707
+ // ============================================
708
+
709
+ export type SessionStatus = 'active' | 'idle' | 'disconnected' | 'closed';
710
+
711
+ export interface SessionMessage {
712
+ id: string;
713
+ sessionId: string;
714
+ sender: 'user' | 'agent' | 'system';
715
+ content: string;
716
+ status?: 'pending' | 'executing' | 'awaiting_approval' | 'done' | 'error';
717
+ timestamp: string;
718
+ metadata?: {
719
+ tokensUsed?: number;
720
+ operationType?: string;
721
+ approvalId?: string;
722
+ };
723
+ }
724
+
725
+ export interface Session {
726
+ id: string;
727
+ agentId: string;
728
+ agentType: AgentType;
729
+ agentName: string;
730
+ status: SessionStatus;
731
+ createdAt: string;
732
+ lastActivity: string;
733
+ messages: SessionMessage[];
734
+ metadata: {
735
+ totalTokens: number;
736
+ totalApprovals: number;
737
+ totalRejections: number;
738
+ costUsd: number;
739
+ };
740
+ }
741
+
742
+ // ============================================
743
+ // Section 9:Dev Mode(雞生蛋解法)
744
+ // ============================================
745
+
746
+ export type DevMode = 'isolated' | 'allowlist' | 'full' | 'yolo';
747
+
748
+ /** Agent 操作模式('normal' = 一般審核模式,'yolo' = 觀測模式自動放行) */
749
+ export type AgentMode = 'normal' | 'yolo';
750
+
751
+ // ============================================
752
+ // Section 9b:Hub 本地設定(~/.hopping/config.json)
753
+ // ============================================
754
+
755
+ /** Hub 本地持久化設定(配對後寫入,token 讀取優先順序:env > 此檔 > 無) */
756
+ export interface HubLocalConfig {
757
+ agentId: string;
758
+ token: string;
759
+ cloudUrl: string;
760
+ pairedAt: string; // ISO 8601
761
+ }
762
+
763
+ // ============================================
764
+ // Section 10:Hook ↔ Hub IPC(檔案交換 + 共享 SQLite)
765
+ // ============================================
766
+
767
+ /** 審核請求:Hook 寫入 ~/.hopping/pending/{id}.json */
768
+ export interface IpcPendingRequest {
769
+ id: string;
770
+ toolName: string;
771
+ toolInput: Record<string, unknown>;
772
+ sessionId?: string;
773
+ cwd?: string;
774
+ /** Claude Code 提供的 tool_use_id,用於追蹤同一次工具呼叫的 pre/post 事件 */
775
+ toolUseId?: string;
776
+ timestamp: string;
777
+ }
778
+
779
+ /** 審核結果:Hub 寫入 ~/.hopping/resolved/{id}.json */
780
+ export interface IpcResolvedResponse {
781
+ id: string;
782
+ approved: boolean;
783
+ reason?: string;
784
+ timestamp: string;
785
+ }
786
+
787
+ /** 觀測記錄:post-tool-use Hook 直接寫入 Hub SQLite */
788
+ export interface IpcOperationRecord {
789
+ id: string;
790
+ toolName: string;
791
+ toolParams?: string;
792
+ filePaths?: string;
793
+ riskLevel?: RiskLevel;
794
+ decision?: ApprovalDecision;
795
+ createdAt: string;
796
+ }
797
+
798
+ // ============================================
799
+ // Section 11:WebSocket Connection(連線控制)
800
+ // ============================================
801
+
802
+ export interface ConnectedMessage {
803
+ type: 'connected';
804
+ message: string;
805
+ timestamp: string;
806
+ }
807
+
808
+ export interface PingMessage {
809
+ type: 'ping';
810
+ }
811
+
812
+ export interface PongMessage {
813
+ type: 'pong';
814
+ }
815
+
816
+ // ============================================
817
+ // Section 12:Auth Types
818
+ // ============================================
819
+
820
+ export type OAuthProvider = 'github' | 'google';
821
+
822
+ /** Token Exchange request (Mobile → Backend) */
823
+ export interface TokenExchangeRequest {
824
+ provider: OAuthProvider;
825
+ code: string;
826
+ codeVerifier: string;
827
+ redirectUri: string;
828
+ }
829
+
830
+ /** Auth token response (Backend → Mobile) — includes refresh token */
831
+ export interface AuthTokenResponse {
832
+ accessToken: string;
833
+ refreshToken: string;
834
+ tokenType: 'Bearer';
835
+ expiresIn: number;
836
+ user: {
837
+ id: string;
838
+ email: string;
839
+ name: string | null;
840
+ avatarUrl: string | null;
841
+ };
842
+ }
843
+
844
+ /** Refresh token request (Mobile → Backend) */
845
+ export interface RefreshTokenRequest {
846
+ refreshToken: string;
847
+ }
848
+
849
+ /** Refresh token response (Backend → Mobile) — no user object */
850
+ export interface RefreshTokenResponse {
851
+ accessToken: string;
852
+ refreshToken: string;
853
+ tokenType: 'Bearer';
854
+ expiresIn: number;
855
+ }
856
+
857
+ /** JWT payload stored in token */
858
+ export interface JwtTokenPayload {
859
+ sub: string; // user ID
860
+ email: string;
861
+ provider: OAuthProvider;
862
+ iat: number; // issued at
863
+ exp: number; // expiry
864
+ }
865
+
866
+ // ============================================
867
+ // Section 13:Deduplication
868
+ // ============================================
869
+
870
+ export interface DeduplicationEntry {
871
+ operationId: string;
872
+ result: 'approved' | 'rejected';
873
+ timestamp: number;
874
+ }
875
+
876
+ // ============================================
877
+ // Section 14:Local Approval(S3-HUB-LOCAL-001)
878
+ // ============================================
879
+
880
+ /** 本地審核通道設定(~/.hopping/config.json localApproval section) */
881
+ export interface LocalApprovalConfig {
882
+ enabled: boolean; // default: true — 本地審核通道開關
883
+ dialogEnabled: boolean; // default: true — 原生對話框開關(false = 只顯示通知)
884
+ soundEnabled: boolean; // default: true — 通知音效
885
+ raceMode: boolean; // default: true — Cloud 連線時也啟動本地通道(雙通道競速)
886
+ }
887
+
888
+ /** 本地審核決策來源 */
889
+ export type LocalApprovalSource = 'local_dialog' | 'local_respond' | 'timeout';
890
+
891
+ /** LocalApprovalChannel.requestApproval() 回傳結果 */
892
+ export interface LocalApprovalResult {
893
+ decision: 'approved' | 'denied' | 'timeout';
894
+ source: LocalApprovalSource;
895
+ }