sessix-server 0.3.8 → 0.4.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/dist/index.js +579 -48
- package/dist/server.js +573 -42
- package/package.json +1 -1
package/dist/server.js
CHANGED
|
@@ -307,12 +307,12 @@ function t(key, params) {
|
|
|
307
307
|
}
|
|
308
308
|
|
|
309
309
|
// src/server.ts
|
|
310
|
-
var
|
|
311
|
-
var
|
|
312
|
-
var
|
|
313
|
-
var
|
|
314
|
-
var
|
|
315
|
-
var
|
|
310
|
+
var import_uuid7 = require("uuid");
|
|
311
|
+
var import_promises5 = require("fs/promises");
|
|
312
|
+
var import_node_os8 = require("os");
|
|
313
|
+
var import_node_path7 = require("path");
|
|
314
|
+
var import_node_child_process10 = require("child_process");
|
|
315
|
+
var import_node_util2 = require("util");
|
|
316
316
|
|
|
317
317
|
// src/providers/ProcessProvider.ts
|
|
318
318
|
var import_child_process = require("child_process");
|
|
@@ -2528,6 +2528,8 @@ var ApprovalProxy = class _ApprovalProxy {
|
|
|
2528
2528
|
pendingApprovals = /* @__PURE__ */ new Map();
|
|
2529
2529
|
/** 审批请求回调(通知外部推送到手机) */
|
|
2530
2530
|
approvalRequestCallbacks = [];
|
|
2531
|
+
/** 审批 resolve 回调(任何来源的 resolve 都会触发,用于 WS 广播清理 */
|
|
2532
|
+
approvalResolvedCallbacks = [];
|
|
2531
2533
|
/** YOLO 模式状态:sessionId -> enabled */
|
|
2532
2534
|
yoloSessions = /* @__PURE__ */ new Map();
|
|
2533
2535
|
/** 内存缓存:已被"始终允许"的工具名(避免每次读 settings.json) */
|
|
@@ -2565,6 +2567,30 @@ var ApprovalProxy = class _ApprovalProxy {
|
|
|
2565
2567
|
onApprovalRequest(callback) {
|
|
2566
2568
|
this.approvalRequestCallbacks.push(callback);
|
|
2567
2569
|
}
|
|
2570
|
+
/**
|
|
2571
|
+
* 注册审批 resolve 回调
|
|
2572
|
+
*
|
|
2573
|
+
* 任何来源的 resolve 都会触发:
|
|
2574
|
+
* - resolveApproval(手机端 approve/reject)
|
|
2575
|
+
* - 5 分钟超时自动 allow
|
|
2576
|
+
* - clearPendingForSession(会话被 kill)
|
|
2577
|
+
* - approveAll(手机全断时自动 allow)
|
|
2578
|
+
*
|
|
2579
|
+
* 用于向所有客户端广播 approval_resolved,清理可能残留的审批 UI。
|
|
2580
|
+
*/
|
|
2581
|
+
onApprovalResolved(callback) {
|
|
2582
|
+
this.approvalResolvedCallbacks.push(callback);
|
|
2583
|
+
}
|
|
2584
|
+
/** 通知所有 resolve 回调(内部调用) */
|
|
2585
|
+
notifyApprovalResolved(requestId, decision) {
|
|
2586
|
+
for (const callback of this.approvalResolvedCallbacks) {
|
|
2587
|
+
try {
|
|
2588
|
+
callback(requestId, decision);
|
|
2589
|
+
} catch (err) {
|
|
2590
|
+
console.error("[ApprovalProxy] Approval resolved callback error:", err);
|
|
2591
|
+
}
|
|
2592
|
+
}
|
|
2593
|
+
}
|
|
2568
2594
|
/** 设置状态信息提供者(用于 /health 端点) */
|
|
2569
2595
|
setStatusInfoProvider(provider) {
|
|
2570
2596
|
this.statusInfoProvider = provider;
|
|
@@ -2598,6 +2624,7 @@ var ApprovalProxy = class _ApprovalProxy {
|
|
|
2598
2624
|
pending.resolve(decision);
|
|
2599
2625
|
this.pendingApprovals.delete(requestId);
|
|
2600
2626
|
console.log(`[ApprovalProxy] ${t("approval.requestProcessed", { id: requestId })}: ${decision.decision}`);
|
|
2627
|
+
this.notifyApprovalResolved(requestId, decision);
|
|
2601
2628
|
return true;
|
|
2602
2629
|
}
|
|
2603
2630
|
/** 获取当前待处理的审批数量 */
|
|
@@ -2613,9 +2640,11 @@ var ApprovalProxy = class _ApprovalProxy {
|
|
|
2613
2640
|
for (const [requestId, pending] of this.pendingApprovals) {
|
|
2614
2641
|
if (pending.request.sessionId === sessionId) {
|
|
2615
2642
|
clearTimeout(pending.timer);
|
|
2616
|
-
|
|
2643
|
+
const decision = { decision: "allow" };
|
|
2644
|
+
pending.resolve(decision);
|
|
2617
2645
|
this.pendingApprovals.delete(requestId);
|
|
2618
2646
|
console.log(`[ApprovalProxy] Session ${sessionId} killed, auto-allowed pending approval ${requestId}`);
|
|
2647
|
+
this.notifyApprovalResolved(requestId, decision);
|
|
2619
2648
|
}
|
|
2620
2649
|
}
|
|
2621
2650
|
}
|
|
@@ -2703,9 +2732,11 @@ var ApprovalProxy = class _ApprovalProxy {
|
|
|
2703
2732
|
const entries = Array.from(this.pendingApprovals.entries());
|
|
2704
2733
|
for (const [requestId, pending] of entries) {
|
|
2705
2734
|
clearTimeout(pending.timer);
|
|
2706
|
-
|
|
2735
|
+
const decision = { decision: "allow" };
|
|
2736
|
+
pending.resolve(decision);
|
|
2707
2737
|
this.pendingApprovals.delete(requestId);
|
|
2708
2738
|
console.log(`[ApprovalProxy] ${t("approval.autoAllowed", { id: requestId, reason: reason ? `\uFF08${reason}\uFF09` : "" })}`);
|
|
2739
|
+
this.notifyApprovalResolved(requestId, decision);
|
|
2709
2740
|
}
|
|
2710
2741
|
}
|
|
2711
2742
|
/** 优雅关闭 HTTP 服务 */
|
|
@@ -2797,7 +2828,9 @@ var ApprovalProxy = class _ApprovalProxy {
|
|
|
2797
2828
|
const timer = setTimeout(() => {
|
|
2798
2829
|
console.log(`[ApprovalProxy] ${t("approval.timeout", { id: requestId })}`);
|
|
2799
2830
|
this.pendingApprovals.delete(requestId);
|
|
2800
|
-
|
|
2831
|
+
const autoDecision = { decision: "allow" };
|
|
2832
|
+
resolve(autoDecision);
|
|
2833
|
+
this.notifyApprovalResolved(requestId, autoDecision);
|
|
2801
2834
|
}, 325e3);
|
|
2802
2835
|
this.pendingApprovals.set(requestId, { resolve, timer, request: approvalRequest });
|
|
2803
2836
|
});
|
|
@@ -3098,9 +3131,26 @@ process.stdin.on('end', async () => {
|
|
|
3098
3131
|
signal: AbortSignal.timeout(320000),
|
|
3099
3132
|
})
|
|
3100
3133
|
const data = await res.json()
|
|
3101
|
-
|
|
3134
|
+
const decision = data.decision === 'deny' ? 'deny' : 'allow'
|
|
3135
|
+
const output = {
|
|
3136
|
+
hookSpecificOutput: {
|
|
3137
|
+
hookEventName: 'PreToolUse',
|
|
3138
|
+
permissionDecision: decision,
|
|
3139
|
+
},
|
|
3140
|
+
}
|
|
3141
|
+
if (decision === 'deny' && data.reason) {
|
|
3142
|
+
output.hookSpecificOutput.permissionDecisionReason = String(data.reason)
|
|
3143
|
+
}
|
|
3144
|
+
process.stdout.write(JSON.stringify(output))
|
|
3145
|
+
process.exit(0)
|
|
3102
3146
|
} catch {
|
|
3103
|
-
// Sessix \u670D\u52A1\u5668\u4E0D\u53EF\u7528\uFF0C\u9ED8\u8BA4\u653E\u884C
|
|
3147
|
+
// Sessix \u670D\u52A1\u5668\u4E0D\u53EF\u7528\uFF0C\u9ED8\u8BA4\u653E\u884C\uFF08\u8F93\u51FA\u663E\u5F0F allow\uFF0C\u907F\u514D\u843D\u5230\u7EC8\u7AEF\u63D0\u793A\uFF09
|
|
3148
|
+
process.stdout.write(JSON.stringify({
|
|
3149
|
+
hookSpecificOutput: {
|
|
3150
|
+
hookEventName: 'PreToolUse',
|
|
3151
|
+
permissionDecision: 'allow',
|
|
3152
|
+
},
|
|
3153
|
+
}))
|
|
3104
3154
|
process.exit(0)
|
|
3105
3155
|
}
|
|
3106
3156
|
})
|
|
@@ -3143,15 +3193,19 @@ var HookInstaller = class {
|
|
|
3143
3193
|
console.log("[HookInstaller] Hook uninstalled");
|
|
3144
3194
|
}
|
|
3145
3195
|
/**
|
|
3146
|
-
* 检查 hook
|
|
3147
|
-
*
|
|
3196
|
+
* 检查 hook 是否已安装且为最新版本
|
|
3197
|
+
*
|
|
3198
|
+
* 必须同时满足:
|
|
3199
|
+
* 1. 两个脚本文件都存在
|
|
3200
|
+
* 2. settings.json 中有 Sessix hook 配置
|
|
3201
|
+
* 3. approval-hook.js 包含最新的 permissionDecision 输出协议
|
|
3202
|
+
* (旧版仅用 exit code,会导致 ExitPlanMode 等工具卡住)
|
|
3148
3203
|
*/
|
|
3149
3204
|
async isInstalled() {
|
|
3150
|
-
let
|
|
3205
|
+
let approvalScriptContent = "";
|
|
3151
3206
|
let permissionScriptExists = false;
|
|
3152
3207
|
try {
|
|
3153
|
-
await (0, import_promises2.
|
|
3154
|
-
approvalScriptExists = true;
|
|
3208
|
+
approvalScriptContent = await (0, import_promises2.readFile)(HOOK_SCRIPT_PATH, "utf-8");
|
|
3155
3209
|
} catch {
|
|
3156
3210
|
}
|
|
3157
3211
|
try {
|
|
@@ -3159,9 +3213,10 @@ var HookInstaller = class {
|
|
|
3159
3213
|
permissionScriptExists = true;
|
|
3160
3214
|
} catch {
|
|
3161
3215
|
}
|
|
3216
|
+
const isLatestVersion = approvalScriptContent.includes("permissionDecision");
|
|
3162
3217
|
const settings = await this.readClaudeSettings();
|
|
3163
3218
|
const configExists = this.hasHookConfig(settings);
|
|
3164
|
-
return
|
|
3219
|
+
return isLatestVersion && permissionScriptExists && configExists;
|
|
3165
3220
|
}
|
|
3166
3221
|
// ============================================
|
|
3167
3222
|
// 内部方法
|
|
@@ -4335,7 +4390,7 @@ var AuthManager = class extends import_events3.EventEmitter {
|
|
|
4335
4390
|
};
|
|
4336
4391
|
|
|
4337
4392
|
// src/server.ts
|
|
4338
|
-
var
|
|
4393
|
+
var import_promises6 = require("fs/promises");
|
|
4339
4394
|
|
|
4340
4395
|
// src/terminal/TerminalExecutor.ts
|
|
4341
4396
|
var import_node_child_process7 = require("child_process");
|
|
@@ -4424,8 +4479,422 @@ var TerminalExecutor = class {
|
|
|
4424
4479
|
}
|
|
4425
4480
|
};
|
|
4426
4481
|
|
|
4427
|
-
// src/
|
|
4482
|
+
// src/xcode/XcodeBuildExecutor.ts
|
|
4428
4483
|
var import_node_child_process8 = require("child_process");
|
|
4484
|
+
var import_node_util = require("util");
|
|
4485
|
+
var import_promises4 = require("fs/promises");
|
|
4486
|
+
var import_node_path6 = require("path");
|
|
4487
|
+
var import_node_os7 = require("os");
|
|
4488
|
+
var import_uuid6 = require("uuid");
|
|
4489
|
+
var execAsync = (0, import_node_util.promisify)(import_node_child_process8.exec);
|
|
4490
|
+
var BUILD_TIMEOUT_MS = 30 * 60 * 1e3;
|
|
4491
|
+
var INSTALL_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
4492
|
+
var CONFIG_FILE = (0, import_node_path6.join)((0, import_node_os7.homedir)(), ".sessix", "xcode-config.json");
|
|
4493
|
+
var SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
4494
|
+
"node_modules",
|
|
4495
|
+
".git",
|
|
4496
|
+
"DerivedData",
|
|
4497
|
+
"Pods",
|
|
4498
|
+
".build",
|
|
4499
|
+
"build",
|
|
4500
|
+
"dist",
|
|
4501
|
+
"__pycache__",
|
|
4502
|
+
".next",
|
|
4503
|
+
"vendor",
|
|
4504
|
+
".expo",
|
|
4505
|
+
"android",
|
|
4506
|
+
".gradle",
|
|
4507
|
+
"Carthage",
|
|
4508
|
+
"xcarchive"
|
|
4509
|
+
]);
|
|
4510
|
+
var MAX_SCAN_DEPTH = 4;
|
|
4511
|
+
var XcodeBuildExecutor = class {
|
|
4512
|
+
builds = /* @__PURE__ */ new Map();
|
|
4513
|
+
installs = /* @__PURE__ */ new Map();
|
|
4514
|
+
eventCallbacks = [];
|
|
4515
|
+
configCache = null;
|
|
4516
|
+
onEvent(callback) {
|
|
4517
|
+
this.eventCallbacks.push(callback);
|
|
4518
|
+
return () => {
|
|
4519
|
+
const idx = this.eventCallbacks.indexOf(callback);
|
|
4520
|
+
if (idx !== -1) this.eventCallbacks.splice(idx, 1);
|
|
4521
|
+
};
|
|
4522
|
+
}
|
|
4523
|
+
emit(event) {
|
|
4524
|
+
for (const cb of this.eventCallbacks) {
|
|
4525
|
+
try {
|
|
4526
|
+
cb(event);
|
|
4527
|
+
} catch (err) {
|
|
4528
|
+
console.error("[XcodeBuildExecutor] Event callback error:", err);
|
|
4529
|
+
}
|
|
4530
|
+
}
|
|
4531
|
+
}
|
|
4532
|
+
// ============================================
|
|
4533
|
+
// 配置持久化
|
|
4534
|
+
// ============================================
|
|
4535
|
+
async loadConfigs() {
|
|
4536
|
+
if (this.configCache) return this.configCache;
|
|
4537
|
+
try {
|
|
4538
|
+
const raw = await (0, import_promises4.readFile)(CONFIG_FILE, "utf8");
|
|
4539
|
+
this.configCache = JSON.parse(raw);
|
|
4540
|
+
} catch {
|
|
4541
|
+
this.configCache = {};
|
|
4542
|
+
}
|
|
4543
|
+
return this.configCache;
|
|
4544
|
+
}
|
|
4545
|
+
async writeConfigs(store) {
|
|
4546
|
+
await (0, import_promises4.mkdir)((0, import_node_path6.join)((0, import_node_os7.homedir)(), ".sessix"), { recursive: true });
|
|
4547
|
+
await (0, import_promises4.writeFile)(CONFIG_FILE, JSON.stringify(store, null, 2), "utf8");
|
|
4548
|
+
this.configCache = store;
|
|
4549
|
+
}
|
|
4550
|
+
async getSavedConfig(projectPath) {
|
|
4551
|
+
const store = await this.loadConfigs();
|
|
4552
|
+
return store[projectPath];
|
|
4553
|
+
}
|
|
4554
|
+
async saveConfig(projectPath, config) {
|
|
4555
|
+
const store = await this.loadConfigs();
|
|
4556
|
+
store[projectPath] = config;
|
|
4557
|
+
await this.writeConfigs(store);
|
|
4558
|
+
}
|
|
4559
|
+
// ============================================
|
|
4560
|
+
// 递归扫描 Xcode 工程
|
|
4561
|
+
// ============================================
|
|
4562
|
+
async findAllContainers(projectPath) {
|
|
4563
|
+
const results = [];
|
|
4564
|
+
await this.scanDir(projectPath, projectPath, 0, results);
|
|
4565
|
+
results.sort((a, b) => a.path.split("/").length - b.path.split("/").length);
|
|
4566
|
+
return results;
|
|
4567
|
+
}
|
|
4568
|
+
async scanDir(rootPath, currentPath, depth, results) {
|
|
4569
|
+
if (depth > MAX_SCAN_DEPTH) return;
|
|
4570
|
+
let entries;
|
|
4571
|
+
try {
|
|
4572
|
+
entries = await (0, import_promises4.readdir)(currentPath);
|
|
4573
|
+
} catch {
|
|
4574
|
+
return;
|
|
4575
|
+
}
|
|
4576
|
+
let foundWorkspace;
|
|
4577
|
+
let foundProject;
|
|
4578
|
+
for (const name of entries) {
|
|
4579
|
+
if (name.endsWith(".xcworkspace") && !name.endsWith("project.xcworkspace")) {
|
|
4580
|
+
foundWorkspace = foundWorkspace ?? name;
|
|
4581
|
+
} else if (name.endsWith(".xcodeproj")) {
|
|
4582
|
+
foundProject = foundProject ?? name;
|
|
4583
|
+
}
|
|
4584
|
+
}
|
|
4585
|
+
if (foundWorkspace || foundProject) {
|
|
4586
|
+
const relDir = currentPath === rootPath ? "" : currentPath.slice(rootPath.length + 1);
|
|
4587
|
+
const containerName = foundWorkspace ? foundWorkspace.replace(/\.xcworkspace$/, "") : foundProject.replace(/\.xcodeproj$/, "");
|
|
4588
|
+
results.push({
|
|
4589
|
+
path: relDir,
|
|
4590
|
+
name: containerName,
|
|
4591
|
+
workspace: foundWorkspace ? relDir ? `${relDir}/${foundWorkspace}` : foundWorkspace : void 0,
|
|
4592
|
+
project: foundProject ? relDir ? `${relDir}/${foundProject}` : foundProject : void 0
|
|
4593
|
+
});
|
|
4594
|
+
}
|
|
4595
|
+
for (const name of entries) {
|
|
4596
|
+
if (SKIP_DIRS.has(name)) continue;
|
|
4597
|
+
if (name.startsWith(".")) continue;
|
|
4598
|
+
if (name.endsWith(".xcodeproj") || name.endsWith(".xcworkspace")) continue;
|
|
4599
|
+
const childPath = (0, import_node_path6.join)(currentPath, name);
|
|
4600
|
+
await this.scanDir(rootPath, childPath, depth + 1, results);
|
|
4601
|
+
}
|
|
4602
|
+
}
|
|
4603
|
+
// ============================================
|
|
4604
|
+
// 探测 schemes(针对指定 container)
|
|
4605
|
+
// ============================================
|
|
4606
|
+
async detect(projectPath) {
|
|
4607
|
+
if (process.platform !== "darwin") {
|
|
4608
|
+
return { available: false, error: "Xcode \u6784\u5EFA\u53EA\u652F\u6301 macOS", containers: [], schemes: [] };
|
|
4609
|
+
}
|
|
4610
|
+
const containers = await this.findAllContainers(projectPath);
|
|
4611
|
+
if (containers.length === 0) {
|
|
4612
|
+
return { available: false, error: "\u672A\u627E\u5230 .xcworkspace \u6216 .xcodeproj", containers: [], schemes: [] };
|
|
4613
|
+
}
|
|
4614
|
+
const firstContainer = containers[0];
|
|
4615
|
+
const schemes = await this.getSchemesForContainer(projectPath, firstContainer);
|
|
4616
|
+
const saved = await this.getSavedConfig(projectPath);
|
|
4617
|
+
return {
|
|
4618
|
+
available: true,
|
|
4619
|
+
containers,
|
|
4620
|
+
schemes,
|
|
4621
|
+
saved
|
|
4622
|
+
};
|
|
4623
|
+
}
|
|
4624
|
+
async getSchemesForContainer(projectPath, container) {
|
|
4625
|
+
const args = container.workspace ? ["-workspace", container.workspace, "-list", "-json"] : ["-project", container.project, "-list", "-json"];
|
|
4626
|
+
try {
|
|
4627
|
+
const { stdout } = await execAsync(
|
|
4628
|
+
`xcodebuild ${args.map(shellQuote).join(" ")}`,
|
|
4629
|
+
{ cwd: projectPath, timeout: 3e4, maxBuffer: 4 * 1024 * 1024 }
|
|
4630
|
+
);
|
|
4631
|
+
const parsed = JSON.parse(stdout);
|
|
4632
|
+
return parsed.workspace?.schemes ?? parsed.project?.schemes ?? [];
|
|
4633
|
+
} catch {
|
|
4634
|
+
return [];
|
|
4635
|
+
}
|
|
4636
|
+
}
|
|
4637
|
+
// ============================================
|
|
4638
|
+
// 列举 destinations
|
|
4639
|
+
// ============================================
|
|
4640
|
+
async listDestinations(projectPath, scheme, container) {
|
|
4641
|
+
if (process.platform !== "darwin") return [];
|
|
4642
|
+
const args = [
|
|
4643
|
+
...container.workspace ? ["-workspace", container.workspace] : ["-project", container.project],
|
|
4644
|
+
"-scheme",
|
|
4645
|
+
scheme,
|
|
4646
|
+
"-showdestinations"
|
|
4647
|
+
];
|
|
4648
|
+
try {
|
|
4649
|
+
const { stdout, stderr } = await execAsync(
|
|
4650
|
+
`xcodebuild ${args.map(shellQuote).join(" ")}`,
|
|
4651
|
+
{ cwd: projectPath, timeout: 6e4, maxBuffer: 4 * 1024 * 1024 }
|
|
4652
|
+
);
|
|
4653
|
+
return parseDestinations(stdout + "\n" + stderr);
|
|
4654
|
+
} catch (err) {
|
|
4655
|
+
const e = err;
|
|
4656
|
+
const parsed = parseDestinations(`${e.stdout ?? ""}
|
|
4657
|
+
${e.stderr ?? ""}`);
|
|
4658
|
+
if (parsed.length > 0) return parsed;
|
|
4659
|
+
throw err;
|
|
4660
|
+
}
|
|
4661
|
+
}
|
|
4662
|
+
// ============================================
|
|
4663
|
+
// 构建
|
|
4664
|
+
// ============================================
|
|
4665
|
+
async build(sessionId, projectPath, override) {
|
|
4666
|
+
if (process.platform !== "darwin") {
|
|
4667
|
+
this.emitBuildError(sessionId, "", "Xcode \u6784\u5EFA\u4EC5\u652F\u6301 macOS\n");
|
|
4668
|
+
return null;
|
|
4669
|
+
}
|
|
4670
|
+
const config = override ?? await this.getSavedConfig(projectPath);
|
|
4671
|
+
if (!config) {
|
|
4672
|
+
this.emitBuildError(sessionId, "", "\u672A\u914D\u7F6E Xcode \u6784\u5EFA\u53C2\u6570\uFF0C\u8BF7\u5148\u9009\u62E9 scheme \u4E0E\u76EE\u6807\u8BBE\u5907\n");
|
|
4673
|
+
return null;
|
|
4674
|
+
}
|
|
4675
|
+
if (override) await this.saveConfig(projectPath, override);
|
|
4676
|
+
const buildId = (0, import_uuid6.v4)();
|
|
4677
|
+
const args = buildArgs(config);
|
|
4678
|
+
const proc = (0, import_node_child_process8.spawn)("xcodebuild", args, {
|
|
4679
|
+
cwd: projectPath,
|
|
4680
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
4681
|
+
env: { ...process.env, NSUnbufferedIO: "YES" }
|
|
4682
|
+
});
|
|
4683
|
+
this.builds.set(buildId, proc);
|
|
4684
|
+
this.emit({
|
|
4685
|
+
type: "xcode_build_output",
|
|
4686
|
+
sessionId,
|
|
4687
|
+
buildId,
|
|
4688
|
+
stream: "stdout",
|
|
4689
|
+
data: `$ xcodebuild ${args.map((a) => a.includes(" ") ? `"${a}"` : a).join(" ")}
|
|
4690
|
+
cwd: ${projectPath}
|
|
4691
|
+
destination: ${config.destinationName}
|
|
4692
|
+
|
|
4693
|
+
`
|
|
4694
|
+
});
|
|
4695
|
+
proc.stdout?.on("data", (chunk) => {
|
|
4696
|
+
this.emit({ type: "xcode_build_output", sessionId, buildId, stream: "stdout", data: chunk.toString() });
|
|
4697
|
+
});
|
|
4698
|
+
proc.stderr?.on("data", (chunk) => {
|
|
4699
|
+
this.emit({ type: "xcode_build_output", sessionId, buildId, stream: "stderr", data: chunk.toString() });
|
|
4700
|
+
});
|
|
4701
|
+
proc.on("error", (err) => {
|
|
4702
|
+
this.emit({ type: "xcode_build_output", sessionId, buildId, stream: "stderr", data: `[spawn error] ${err.message}
|
|
4703
|
+
` });
|
|
4704
|
+
});
|
|
4705
|
+
const timer = setTimeout(() => {
|
|
4706
|
+
if (this.builds.has(buildId)) killProcessCrossPlatform(proc);
|
|
4707
|
+
}, BUILD_TIMEOUT_MS);
|
|
4708
|
+
proc.on("exit", (code, signal) => {
|
|
4709
|
+
clearTimeout(timer);
|
|
4710
|
+
this.builds.delete(buildId);
|
|
4711
|
+
this.emit({ type: "xcode_build_exit", sessionId, buildId, code, signal });
|
|
4712
|
+
});
|
|
4713
|
+
console.log(`[XcodeBuildExecutor] build ${buildId} scheme=${config.scheme} dest=${config.destinationName}`);
|
|
4714
|
+
return buildId;
|
|
4715
|
+
}
|
|
4716
|
+
killBuild(buildId) {
|
|
4717
|
+
const proc = this.builds.get(buildId);
|
|
4718
|
+
if (proc) {
|
|
4719
|
+
killProcessCrossPlatform(proc);
|
|
4720
|
+
console.log(`[XcodeBuildExecutor] kill build ${buildId}`);
|
|
4721
|
+
}
|
|
4722
|
+
}
|
|
4723
|
+
emitBuildError(sessionId, buildId, msg) {
|
|
4724
|
+
this.emit({ type: "xcode_build_output", sessionId, buildId, stream: "stderr", data: msg });
|
|
4725
|
+
this.emit({ type: "xcode_build_exit", sessionId, buildId, code: 1, signal: null });
|
|
4726
|
+
}
|
|
4727
|
+
// ============================================
|
|
4728
|
+
// 安装
|
|
4729
|
+
// ============================================
|
|
4730
|
+
async install(sessionId, projectPath) {
|
|
4731
|
+
if (process.platform !== "darwin") {
|
|
4732
|
+
this.emitInstallError(sessionId, "", "Xcode \u5B89\u88C5\u4EC5\u652F\u6301 macOS\n");
|
|
4733
|
+
return null;
|
|
4734
|
+
}
|
|
4735
|
+
const config = await this.getSavedConfig(projectPath);
|
|
4736
|
+
if (!config) {
|
|
4737
|
+
this.emitInstallError(sessionId, "", "\u672A\u627E\u5230\u6784\u5EFA\u914D\u7F6E\uFF0C\u8BF7\u5148\u6784\u5EFA\u4E00\u6B21\n");
|
|
4738
|
+
return null;
|
|
4739
|
+
}
|
|
4740
|
+
const installId = (0, import_uuid6.v4)();
|
|
4741
|
+
let appPath;
|
|
4742
|
+
try {
|
|
4743
|
+
appPath = await this.getAppPath(projectPath, config);
|
|
4744
|
+
} catch (err) {
|
|
4745
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
4746
|
+
this.emitInstallError(sessionId, installId, `\u65E0\u6CD5\u5B9A\u4F4D\u6784\u5EFA\u4EA7\u7269: ${msg}
|
|
4747
|
+
`);
|
|
4748
|
+
return null;
|
|
4749
|
+
}
|
|
4750
|
+
const { destinationKind, destinationId, destinationName } = config;
|
|
4751
|
+
let installCmd;
|
|
4752
|
+
if (destinationKind === "simulator") {
|
|
4753
|
+
installCmd = ["xcrun", "simctl", "install", destinationId, appPath];
|
|
4754
|
+
} else if (destinationKind === "device") {
|
|
4755
|
+
installCmd = ["xcrun", "devicectl", "device", "install", "app", "--device-id", destinationId, appPath];
|
|
4756
|
+
} else if (destinationKind === "mac") {
|
|
4757
|
+
installCmd = ["open", appPath];
|
|
4758
|
+
} else {
|
|
4759
|
+
this.emitInstallError(sessionId, installId, `\u672A\u77E5\u76EE\u6807\u7C7B\u578B\uFF0C\u65E0\u6CD5\u81EA\u52A8\u5B89\u88C5
|
|
4760
|
+
`);
|
|
4761
|
+
return null;
|
|
4762
|
+
}
|
|
4763
|
+
this.emit({
|
|
4764
|
+
type: "xcode_install_output",
|
|
4765
|
+
sessionId,
|
|
4766
|
+
installId,
|
|
4767
|
+
stream: "stdout",
|
|
4768
|
+
data: `$ ${installCmd.join(" ")}
|
|
4769
|
+
destination: ${destinationName}
|
|
4770
|
+
app: ${appPath}
|
|
4771
|
+
|
|
4772
|
+
`
|
|
4773
|
+
});
|
|
4774
|
+
const proc = (0, import_node_child_process8.spawn)(installCmd[0], installCmd.slice(1), {
|
|
4775
|
+
cwd: projectPath,
|
|
4776
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
4777
|
+
});
|
|
4778
|
+
this.installs.set(installId, proc);
|
|
4779
|
+
proc.stdout?.on("data", (chunk) => {
|
|
4780
|
+
this.emit({ type: "xcode_install_output", sessionId, installId, stream: "stdout", data: chunk.toString() });
|
|
4781
|
+
});
|
|
4782
|
+
proc.stderr?.on("data", (chunk) => {
|
|
4783
|
+
this.emit({ type: "xcode_install_output", sessionId, installId, stream: "stderr", data: chunk.toString() });
|
|
4784
|
+
});
|
|
4785
|
+
proc.on("error", (err) => {
|
|
4786
|
+
this.emit({ type: "xcode_install_output", sessionId, installId, stream: "stderr", data: `[spawn error] ${err.message}
|
|
4787
|
+
` });
|
|
4788
|
+
});
|
|
4789
|
+
const timer = setTimeout(() => {
|
|
4790
|
+
if (this.installs.has(installId)) killProcessCrossPlatform(proc);
|
|
4791
|
+
}, INSTALL_TIMEOUT_MS);
|
|
4792
|
+
proc.on("exit", (code, signal) => {
|
|
4793
|
+
clearTimeout(timer);
|
|
4794
|
+
this.installs.delete(installId);
|
|
4795
|
+
this.emit({ type: "xcode_install_exit", sessionId, installId, code, signal });
|
|
4796
|
+
});
|
|
4797
|
+
console.log(`[XcodeBuildExecutor] install ${installId} dest=${destinationName} kind=${destinationKind}`);
|
|
4798
|
+
return installId;
|
|
4799
|
+
}
|
|
4800
|
+
killInstall(installId) {
|
|
4801
|
+
const proc = this.installs.get(installId);
|
|
4802
|
+
if (proc) {
|
|
4803
|
+
killProcessCrossPlatform(proc);
|
|
4804
|
+
console.log(`[XcodeBuildExecutor] kill install ${installId}`);
|
|
4805
|
+
}
|
|
4806
|
+
}
|
|
4807
|
+
emitInstallError(sessionId, installId, msg) {
|
|
4808
|
+
this.emit({ type: "xcode_install_output", sessionId, installId, stream: "stderr", data: msg });
|
|
4809
|
+
this.emit({ type: "xcode_install_exit", sessionId, installId, code: 1, signal: null });
|
|
4810
|
+
}
|
|
4811
|
+
/** 通过 xcodebuild -showBuildSettings 定位 .app 路径 */
|
|
4812
|
+
async getAppPath(projectPath, config) {
|
|
4813
|
+
const args = [
|
|
4814
|
+
...buildArgs(config).filter((a) => a !== "build"),
|
|
4815
|
+
"-showBuildSettings"
|
|
4816
|
+
];
|
|
4817
|
+
const { stdout } = await execAsync(
|
|
4818
|
+
`xcodebuild ${args.map(shellQuote).join(" ")}`,
|
|
4819
|
+
{ cwd: projectPath, timeout: 3e4, maxBuffer: 4 * 1024 * 1024 }
|
|
4820
|
+
);
|
|
4821
|
+
const builtDir = extractBuildSetting(stdout, "BUILT_PRODUCTS_DIR");
|
|
4822
|
+
const productName = extractBuildSetting(stdout, "FULL_PRODUCT_NAME");
|
|
4823
|
+
if (!builtDir || !productName) {
|
|
4824
|
+
throw new Error("\u65E0\u6CD5\u4ECE -showBuildSettings \u4E2D\u8BFB\u53D6 BUILT_PRODUCTS_DIR / FULL_PRODUCT_NAME");
|
|
4825
|
+
}
|
|
4826
|
+
return (0, import_node_path6.join)(builtDir, productName);
|
|
4827
|
+
}
|
|
4828
|
+
// ============================================
|
|
4829
|
+
// 清理
|
|
4830
|
+
// ============================================
|
|
4831
|
+
destroy() {
|
|
4832
|
+
for (const [id, proc] of this.builds) {
|
|
4833
|
+
killProcessCrossPlatform(proc);
|
|
4834
|
+
console.log(`[XcodeBuildExecutor] cleanup build ${id}`);
|
|
4835
|
+
}
|
|
4836
|
+
for (const [id, proc] of this.installs) {
|
|
4837
|
+
killProcessCrossPlatform(proc);
|
|
4838
|
+
console.log(`[XcodeBuildExecutor] cleanup install ${id}`);
|
|
4839
|
+
}
|
|
4840
|
+
this.builds.clear();
|
|
4841
|
+
this.installs.clear();
|
|
4842
|
+
this.eventCallbacks.length = 0;
|
|
4843
|
+
}
|
|
4844
|
+
};
|
|
4845
|
+
function shellQuote(s) {
|
|
4846
|
+
return `'${s.replace(/'/g, "'\\''")}'`;
|
|
4847
|
+
}
|
|
4848
|
+
function buildArgs(config) {
|
|
4849
|
+
const container = config.workspace ? ["-workspace", config.workspace] : config.project ? ["-project", config.project] : [];
|
|
4850
|
+
return [
|
|
4851
|
+
...container,
|
|
4852
|
+
"-scheme",
|
|
4853
|
+
config.scheme,
|
|
4854
|
+
"-destination",
|
|
4855
|
+
`id=${config.destinationId}`,
|
|
4856
|
+
"-configuration",
|
|
4857
|
+
config.configuration ?? "Debug",
|
|
4858
|
+
"build"
|
|
4859
|
+
];
|
|
4860
|
+
}
|
|
4861
|
+
function extractBuildSetting(output, key) {
|
|
4862
|
+
const match = new RegExp(`^\\s*${key}\\s*=\\s*(.+)$`, "m").exec(output);
|
|
4863
|
+
return match?.[1]?.trim();
|
|
4864
|
+
}
|
|
4865
|
+
function parseDestinations(text) {
|
|
4866
|
+
const results = [];
|
|
4867
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4868
|
+
const lineRegex = /\{\s*([^{}]+)\s*\}/g;
|
|
4869
|
+
let match;
|
|
4870
|
+
while ((match = lineRegex.exec(text)) !== null) {
|
|
4871
|
+
const fields = {};
|
|
4872
|
+
for (const part of match[1].split(",")) {
|
|
4873
|
+
const colon = part.indexOf(":");
|
|
4874
|
+
if (colon === -1) continue;
|
|
4875
|
+
const key = part.slice(0, colon).trim();
|
|
4876
|
+
const value = part.slice(colon + 1).trim();
|
|
4877
|
+
if (key) fields[key] = value;
|
|
4878
|
+
}
|
|
4879
|
+
const { id, name, platform: platform2 } = fields;
|
|
4880
|
+
if (!id || !name || !platform2) continue;
|
|
4881
|
+
if (seen.has(id)) continue;
|
|
4882
|
+
seen.add(id);
|
|
4883
|
+
let kind = "unknown";
|
|
4884
|
+
if (platform2.includes("Simulator")) kind = "simulator";
|
|
4885
|
+
else if (platform2 === "iOS" || platform2 === "watchOS" || platform2 === "tvOS" || platform2 === "visionOS") kind = "device";
|
|
4886
|
+
else if (platform2 === "macOS") kind = "mac";
|
|
4887
|
+
results.push({ id, name, platform: platform2, os: fields.OS, kind });
|
|
4888
|
+
}
|
|
4889
|
+
results.sort((a, b) => kindOrder(a.kind) - kindOrder(b.kind));
|
|
4890
|
+
return results;
|
|
4891
|
+
}
|
|
4892
|
+
function kindOrder(k) {
|
|
4893
|
+
return k === "device" ? 0 : k === "simulator" ? 1 : k === "mac" ? 2 : 3;
|
|
4894
|
+
}
|
|
4895
|
+
|
|
4896
|
+
// src/utils/cliCapabilities.ts
|
|
4897
|
+
var import_node_child_process9 = require("child_process");
|
|
4429
4898
|
var DEFAULT_CAPABILITIES = {
|
|
4430
4899
|
effortLevels: ["low", "medium", "high", "xhigh", "max"]
|
|
4431
4900
|
};
|
|
@@ -4453,7 +4922,7 @@ async function parseCliCapabilities() {
|
|
|
4453
4922
|
}
|
|
4454
4923
|
function runCli(path2, args) {
|
|
4455
4924
|
return new Promise((resolve) => {
|
|
4456
|
-
(0,
|
|
4925
|
+
(0, import_node_child_process9.execFile)(path2, args, { timeout: 5e3 }, (err, stdout) => {
|
|
4457
4926
|
if (err) {
|
|
4458
4927
|
console.warn(`[CliCapabilities] Failed to run ${path2} ${args.join(" ")}:`, err.message);
|
|
4459
4928
|
resolve(null);
|
|
@@ -4467,11 +4936,11 @@ function runCli(path2, args) {
|
|
|
4467
4936
|
// src/server.ts
|
|
4468
4937
|
var WS_PORT = 3745;
|
|
4469
4938
|
var HTTP_PORT = 3746;
|
|
4470
|
-
var
|
|
4939
|
+
var execAsync2 = (0, import_node_util2.promisify)(import_node_child_process10.exec);
|
|
4471
4940
|
async function killPortProcess(port) {
|
|
4472
4941
|
try {
|
|
4473
4942
|
if (isWindows) {
|
|
4474
|
-
const { stdout } = await
|
|
4943
|
+
const { stdout } = await execAsync2(
|
|
4475
4944
|
`netstat -ano | findstr :${port} | findstr LISTENING`
|
|
4476
4945
|
);
|
|
4477
4946
|
const pids = /* @__PURE__ */ new Set();
|
|
@@ -4481,14 +4950,14 @@ async function killPortProcess(port) {
|
|
|
4481
4950
|
if (pid && /^\d+$/.test(pid) && pid !== "0") pids.add(pid);
|
|
4482
4951
|
}
|
|
4483
4952
|
for (const pid of pids) {
|
|
4484
|
-
await
|
|
4953
|
+
await execAsync2(`taskkill /PID ${pid} /F`).catch(() => {
|
|
4485
4954
|
});
|
|
4486
4955
|
}
|
|
4487
4956
|
} else {
|
|
4488
|
-
const { stdout } = await
|
|
4957
|
+
const { stdout } = await execAsync2(`lsof -ti :${port}`);
|
|
4489
4958
|
const pids = stdout.trim().split("\n").filter((p) => p && /^\d+$/.test(p));
|
|
4490
4959
|
if (pids.length > 0) {
|
|
4491
|
-
await
|
|
4960
|
+
await execAsync2(`kill -9 ${pids.join(" ")}`);
|
|
4492
4961
|
}
|
|
4493
4962
|
}
|
|
4494
4963
|
await new Promise((resolve) => setTimeout(resolve, 600));
|
|
@@ -4509,8 +4978,8 @@ async function createWithRetry(label, port, factory) {
|
|
|
4509
4978
|
}
|
|
4510
4979
|
}
|
|
4511
4980
|
async function start(opts = {}) {
|
|
4512
|
-
const configDir = (0,
|
|
4513
|
-
const tokenFile = (0,
|
|
4981
|
+
const configDir = (0, import_node_path7.join)((0, import_node_os8.homedir)(), ".sessix");
|
|
4982
|
+
const tokenFile = (0, import_node_path7.join)(configDir, "token");
|
|
4514
4983
|
let token;
|
|
4515
4984
|
if (opts.token !== void 0) {
|
|
4516
4985
|
token = opts.token;
|
|
@@ -4520,17 +4989,23 @@ async function start(opts = {}) {
|
|
|
4520
4989
|
token = envToken;
|
|
4521
4990
|
} else {
|
|
4522
4991
|
try {
|
|
4523
|
-
token = (await (0,
|
|
4992
|
+
token = (await (0, import_promises5.readFile)(tokenFile, "utf8")).trim();
|
|
4524
4993
|
} catch {
|
|
4525
|
-
token = (0,
|
|
4526
|
-
await (0,
|
|
4527
|
-
await (0,
|
|
4994
|
+
token = (0, import_uuid7.v4)();
|
|
4995
|
+
await (0, import_promises5.mkdir)(configDir, { recursive: true });
|
|
4996
|
+
await (0, import_promises5.writeFile)(tokenFile, token, "utf8");
|
|
4528
4997
|
}
|
|
4529
4998
|
}
|
|
4530
4999
|
}
|
|
4531
5000
|
const providerFactory = new ProviderFactory();
|
|
4532
5001
|
const sessionManager = new SessionManager(providerFactory);
|
|
4533
5002
|
const terminalExecutor = new TerminalExecutor();
|
|
5003
|
+
const xcodeBuildExecutor = new XcodeBuildExecutor();
|
|
5004
|
+
const approvalProxy = await createWithRetry(
|
|
5005
|
+
"ApprovalProxy",
|
|
5006
|
+
HTTP_PORT,
|
|
5007
|
+
() => ApprovalProxy.create({ port: HTTP_PORT, token })
|
|
5008
|
+
);
|
|
4534
5009
|
const wsBridge = await createWithRetry(
|
|
4535
5010
|
"WsBridge",
|
|
4536
5011
|
WS_PORT,
|
|
@@ -4561,15 +5036,10 @@ async function start(opts = {}) {
|
|
|
4561
5036
|
const sessionFileWatcher = new SessionFileWatcher((event) => {
|
|
4562
5037
|
wsBridge.broadcast(event);
|
|
4563
5038
|
});
|
|
4564
|
-
const approvalProxy = await createWithRetry(
|
|
4565
|
-
"ApprovalProxy",
|
|
4566
|
-
HTTP_PORT,
|
|
4567
|
-
() => ApprovalProxy.create({ port: HTTP_PORT, token })
|
|
4568
|
-
);
|
|
4569
5039
|
let mdnsService = null;
|
|
4570
5040
|
const pairingManager = new PairingManager({
|
|
4571
5041
|
token,
|
|
4572
|
-
serverName: (0,
|
|
5042
|
+
serverName: (0, import_node_os8.hostname)(),
|
|
4573
5043
|
version: "0.2.0",
|
|
4574
5044
|
onStateChange: (state) => mdnsService?.updatePairingState(state)
|
|
4575
5045
|
});
|
|
@@ -4623,7 +5093,7 @@ async function start(opts = {}) {
|
|
|
4623
5093
|
try {
|
|
4624
5094
|
switch (event.type) {
|
|
4625
5095
|
case "create_session": {
|
|
4626
|
-
await (0,
|
|
5096
|
+
await (0, import_promises5.mkdir)(event.projectPath, { recursive: true });
|
|
4627
5097
|
const resumeId = event.resumeSessionId ?? event.newSessionId;
|
|
4628
5098
|
if (resumeId) sessionFileWatcher.unwatch(resumeId);
|
|
4629
5099
|
await sessionManager.createSession(
|
|
@@ -4811,7 +5281,7 @@ async function start(opts = {}) {
|
|
|
4811
5281
|
if (!isStreaming) {
|
|
4812
5282
|
const filePath = getSessionFilePath(event.projectPath, event.sessionId);
|
|
4813
5283
|
try {
|
|
4814
|
-
const fileStat = await (0,
|
|
5284
|
+
const fileStat = await (0, import_promises6.stat)(filePath);
|
|
4815
5285
|
sessionFileWatcher.watch(event.sessionId, filePath, fileStat.size);
|
|
4816
5286
|
} catch {
|
|
4817
5287
|
}
|
|
@@ -4920,6 +5390,56 @@ async function start(opts = {}) {
|
|
|
4920
5390
|
wsBridge.send(ws, { type: "agent_list", agents });
|
|
4921
5391
|
break;
|
|
4922
5392
|
}
|
|
5393
|
+
case "xcode_detect": {
|
|
5394
|
+
const info = await xcodeBuildExecutor.detect(event.projectPath);
|
|
5395
|
+
wsBridge.send(ws, { type: "xcode_info", sessionId: event.sessionId, info });
|
|
5396
|
+
break;
|
|
5397
|
+
}
|
|
5398
|
+
case "xcode_list_schemes": {
|
|
5399
|
+
const schemes = await xcodeBuildExecutor.getSchemesForContainer(event.projectPath, event.container);
|
|
5400
|
+
wsBridge.send(ws, {
|
|
5401
|
+
type: "xcode_info",
|
|
5402
|
+
sessionId: event.sessionId,
|
|
5403
|
+
info: {
|
|
5404
|
+
available: schemes.length > 0,
|
|
5405
|
+
containers: [event.container],
|
|
5406
|
+
schemes
|
|
5407
|
+
}
|
|
5408
|
+
});
|
|
5409
|
+
break;
|
|
5410
|
+
}
|
|
5411
|
+
case "xcode_list_destinations": {
|
|
5412
|
+
try {
|
|
5413
|
+
const destinations = await xcodeBuildExecutor.listDestinations(event.projectPath, event.scheme, event.container);
|
|
5414
|
+
wsBridge.send(ws, { type: "xcode_destinations", sessionId: event.sessionId, scheme: event.scheme, destinations });
|
|
5415
|
+
} catch (err) {
|
|
5416
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
5417
|
+
wsBridge.send(ws, {
|
|
5418
|
+
type: "xcode_destinations",
|
|
5419
|
+
sessionId: event.sessionId,
|
|
5420
|
+
scheme: event.scheme,
|
|
5421
|
+
destinations: [],
|
|
5422
|
+
error: message.split("\n")[0]
|
|
5423
|
+
});
|
|
5424
|
+
}
|
|
5425
|
+
break;
|
|
5426
|
+
}
|
|
5427
|
+
case "xcode_save_config": {
|
|
5428
|
+
await xcodeBuildExecutor.saveConfig(event.projectPath, event.config);
|
|
5429
|
+
break;
|
|
5430
|
+
}
|
|
5431
|
+
case "xcode_build": {
|
|
5432
|
+
await xcodeBuildExecutor.build(event.sessionId, event.projectPath, event.config);
|
|
5433
|
+
break;
|
|
5434
|
+
}
|
|
5435
|
+
case "xcode_build_kill": {
|
|
5436
|
+
xcodeBuildExecutor.killBuild(event.buildId);
|
|
5437
|
+
break;
|
|
5438
|
+
}
|
|
5439
|
+
case "xcode_install": {
|
|
5440
|
+
await xcodeBuildExecutor.install(event.sessionId, event.projectPath);
|
|
5441
|
+
break;
|
|
5442
|
+
}
|
|
4923
5443
|
default: {
|
|
4924
5444
|
wsBridge.send(ws, {
|
|
4925
5445
|
type: "error",
|
|
@@ -4955,11 +5475,21 @@ async function start(opts = {}) {
|
|
|
4955
5475
|
terminalExecutor.onEvent((event) => {
|
|
4956
5476
|
wsBridge.broadcast(event);
|
|
4957
5477
|
});
|
|
5478
|
+
xcodeBuildExecutor.onEvent((event) => {
|
|
5479
|
+
wsBridge.broadcast(event);
|
|
5480
|
+
});
|
|
4958
5481
|
wsBridge.onDisconnect(() => {
|
|
4959
5482
|
if (wsBridge.getConnectionCount() === 0 && approvalProxy.getPendingCount() > 0) {
|
|
4960
5483
|
approvalProxy.approveAll(t("server.phoneDisconnected"));
|
|
4961
5484
|
}
|
|
4962
5485
|
});
|
|
5486
|
+
approvalProxy.onApprovalResolved((requestId, decision) => {
|
|
5487
|
+
wsBridge.broadcast({
|
|
5488
|
+
type: "approval_resolved",
|
|
5489
|
+
requestId,
|
|
5490
|
+
decision: decision.decision
|
|
5491
|
+
});
|
|
5492
|
+
});
|
|
4963
5493
|
approvalProxy.onApprovalRequest((request) => {
|
|
4964
5494
|
wsBridge.broadcast({ type: "approval_request", request });
|
|
4965
5495
|
setTimeout(() => {
|
|
@@ -5055,6 +5585,7 @@ async function start(opts = {}) {
|
|
|
5055
5585
|
await attempt(() => approvalProxy.close(), "ApprovalProxy");
|
|
5056
5586
|
await attempt(() => sessionManager.destroy(), "SessionManager");
|
|
5057
5587
|
await attempt(() => terminalExecutor.destroy(), "TerminalExecutor");
|
|
5588
|
+
await attempt(() => xcodeBuildExecutor.destroy(), "XcodeBuildExecutor");
|
|
5058
5589
|
await attempt(() => notificationService.destroy(), "NotificationService");
|
|
5059
5590
|
await attempt(() => sessionFileWatcher.destroy(), "SessionFileWatcher");
|
|
5060
5591
|
if (errors.length > 0) {
|
|
@@ -5083,9 +5614,9 @@ async function start(opts = {}) {
|
|
|
5083
5614
|
openPairing: (duration) => pairingManager.open(duration),
|
|
5084
5615
|
closePairing: () => pairingManager.close(),
|
|
5085
5616
|
regenerateToken: async () => {
|
|
5086
|
-
const newToken = (0,
|
|
5087
|
-
await (0,
|
|
5088
|
-
await (0,
|
|
5617
|
+
const newToken = (0, import_uuid7.v4)();
|
|
5618
|
+
await (0, import_promises5.mkdir)(configDir, { recursive: true });
|
|
5619
|
+
await (0, import_promises5.writeFile)(tokenFile, newToken, "utf8");
|
|
5089
5620
|
instance.token = newToken;
|
|
5090
5621
|
wsBridge.updateToken(newToken);
|
|
5091
5622
|
approvalProxy.updateToken(newToken);
|