@modelzen/feishu-codex-bridge 0.2.1-win → 0.2.2-win
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/cli.js +399 -106
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -2013,8 +2013,8 @@ function card(elements, opts = {}) {
|
|
|
2013
2013
|
if (opts.streaming) {
|
|
2014
2014
|
config.streaming_mode = true;
|
|
2015
2015
|
config.streaming_config = {
|
|
2016
|
-
print_frequency_ms: { default:
|
|
2017
|
-
print_step: { default:
|
|
2016
|
+
print_frequency_ms: { default: 25 },
|
|
2017
|
+
print_step: { default: 6 },
|
|
2018
2018
|
print_strategy: "fast"
|
|
2019
2019
|
};
|
|
2020
2020
|
}
|
|
@@ -2036,6 +2036,9 @@ function card(elements, opts = {}) {
|
|
|
2036
2036
|
function md(content) {
|
|
2037
2037
|
return { tag: "markdown", content };
|
|
2038
2038
|
}
|
|
2039
|
+
function mdStream(content, elementId) {
|
|
2040
|
+
return { tag: "markdown", element_id: elementId, content };
|
|
2041
|
+
}
|
|
2039
2042
|
function image(imgKey, alt = "") {
|
|
2040
2043
|
return {
|
|
2041
2044
|
tag: "img",
|
|
@@ -2585,6 +2588,7 @@ function escapeInline2(s) {
|
|
|
2585
2588
|
var RC = {
|
|
2586
2589
|
stop: "run.stop"
|
|
2587
2590
|
};
|
|
2591
|
+
var ANSWER_EID = "answer";
|
|
2588
2592
|
var REASONING_MAX = 1500;
|
|
2589
2593
|
var COLLAPSE_TOOL_THRESHOLD = 3;
|
|
2590
2594
|
var PROCESS_BODY_BUDGET = 22e3;
|
|
@@ -2598,14 +2602,19 @@ function renderRunning(state, rc) {
|
|
|
2598
2602
|
const elements = [];
|
|
2599
2603
|
const reasoning = reasoningContent(state);
|
|
2600
2604
|
if (reasoning) elements.push(reasoningPanel(reasoning, state.reasoningActive));
|
|
2601
|
-
const
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2605
|
+
const showTools = rc.showTools !== false;
|
|
2606
|
+
const tools = [];
|
|
2607
|
+
const textParts = [];
|
|
2608
|
+
for (const b of state.blocks) {
|
|
2609
|
+
if (b.kind === "tool") {
|
|
2610
|
+
if (showTools) tools.push(b.tool);
|
|
2611
|
+
} else if (b.content.trim()) {
|
|
2612
|
+
textParts.push(b.content);
|
|
2607
2613
|
}
|
|
2608
2614
|
}
|
|
2615
|
+
if (tools.length > 0) elements.push(...renderToolGroup(tools, false));
|
|
2616
|
+
const answer = textParts.join("\n\n");
|
|
2617
|
+
if (answer) elements.push(mdStream(answer, ANSWER_EID));
|
|
2609
2618
|
if (state.footer) elements.push(footerStatus(state.footer));
|
|
2610
2619
|
if (rc.cardKey) elements.push(actions([button("\u23F9 \u7EC8\u6B62", { a: RC.stop, m: rc.cardKey }, "danger")]));
|
|
2611
2620
|
return elements;
|
|
@@ -2752,16 +2761,105 @@ function truncate4(s, n) {
|
|
|
2752
2761
|
}
|
|
2753
2762
|
|
|
2754
2763
|
// src/card/run-card-stream.ts
|
|
2755
|
-
var STREAM_THROTTLE_MS =
|
|
2764
|
+
var STREAM_THROTTLE_MS = 150;
|
|
2756
2765
|
var RunCardStream = class {
|
|
2757
2766
|
cardId = "";
|
|
2758
2767
|
_messageId = "";
|
|
2759
2768
|
seq = 0;
|
|
2760
2769
|
lastPush = 0;
|
|
2761
2770
|
lastContent = "";
|
|
2771
|
+
// Per-turn push counters — surfaced via stats() for the stream.timing log.
|
|
2772
|
+
pushCount = 0;
|
|
2773
|
+
cardPushes = 0;
|
|
2774
|
+
// whole-card card.update (structure)
|
|
2775
|
+
elPushes = 0;
|
|
2776
|
+
// element cardElement.content (answer typewriter)
|
|
2777
|
+
totalRttMs = 0;
|
|
2778
|
+
maxRttMs = 0;
|
|
2779
|
+
// Coalesced streaming. The consume loop records the latest {card, answerEid} in
|
|
2780
|
+
// `pending`; a single pump() drains it — NON-BLOCKING, so consuming codex events
|
|
2781
|
+
// never stalls on a round-trip (awaiting each push serially drained the backlog
|
|
2782
|
+
// at one event per ~RTT → "已回完、飞书还在慢慢打字"). The pump pushes only the most
|
|
2783
|
+
// recent snapshot per round-trip: when only the answer text grew it streams that
|
|
2784
|
+
// via cardElement.content (typewriter), otherwise it whole-card updates.
|
|
2785
|
+
pending = null;
|
|
2786
|
+
pumpChannel = null;
|
|
2787
|
+
pumpPromise = null;
|
|
2788
|
+
// Baselines for the pump's route decision (structure unchanged + answer grew?).
|
|
2789
|
+
lastStructureSig = "";
|
|
2790
|
+
lastAnswerText = "";
|
|
2762
2791
|
get messageId() {
|
|
2763
2792
|
return this._messageId;
|
|
2764
2793
|
}
|
|
2794
|
+
/** Push counts (whole-card vs element) + round-trip stats for this card. */
|
|
2795
|
+
stats() {
|
|
2796
|
+
return {
|
|
2797
|
+
pushCount: this.pushCount,
|
|
2798
|
+
cardPushes: this.cardPushes,
|
|
2799
|
+
elPushes: this.elPushes,
|
|
2800
|
+
totalRttMs: this.totalRttMs,
|
|
2801
|
+
maxRttMs: this.maxRttMs
|
|
2802
|
+
};
|
|
2803
|
+
}
|
|
2804
|
+
/**
|
|
2805
|
+
* Record the latest card and ensure the pump is running. Returns immediately;
|
|
2806
|
+
* calls that arrive while a push is in flight collapse into a single push of
|
|
2807
|
+
* the most recent card once that round-trip completes. Use this from the
|
|
2808
|
+
* event-consume loop instead of awaiting {@link streamCard} per event.
|
|
2809
|
+
*/
|
|
2810
|
+
streamCoalesced(channel, fullCard, answerEid) {
|
|
2811
|
+
this.pending = { card: fullCard, answerEid };
|
|
2812
|
+
this.pumpChannel = channel;
|
|
2813
|
+
if (!this.pumpPromise) this.pumpPromise = this.pump();
|
|
2814
|
+
}
|
|
2815
|
+
/** Await any in-flight coalesced push so the final streaming frame lands (and
|
|
2816
|
+
* `seq` stays ordered) before the terminal update. Call after the loop ends. */
|
|
2817
|
+
async drain() {
|
|
2818
|
+
if (this.pumpPromise) await this.pumpPromise;
|
|
2819
|
+
}
|
|
2820
|
+
async pump() {
|
|
2821
|
+
try {
|
|
2822
|
+
while (this.pending && this.pumpChannel) {
|
|
2823
|
+
const { card: card2, answerEid } = this.pending;
|
|
2824
|
+
this.pending = null;
|
|
2825
|
+
const t0 = Date.now();
|
|
2826
|
+
const answer = answerEid ? answerContent(card2, answerEid) : null;
|
|
2827
|
+
const sig = structureSig(card2, answerEid);
|
|
2828
|
+
if (answerEid && answer !== null && sig === this.lastStructureSig && answer !== this.lastAnswerText && answer.startsWith(this.lastAnswerText)) {
|
|
2829
|
+
await this.streamElement(this.pumpChannel, answerEid, answer);
|
|
2830
|
+
this.lastAnswerText = answer;
|
|
2831
|
+
} else {
|
|
2832
|
+
await this.streamCard(this.pumpChannel, card2, true);
|
|
2833
|
+
this.lastStructureSig = sig;
|
|
2834
|
+
this.lastAnswerText = answer ?? "";
|
|
2835
|
+
}
|
|
2836
|
+
const gap = STREAM_THROTTLE_MS - (Date.now() - t0);
|
|
2837
|
+
if (this.pending && gap > 0) await new Promise((r) => setTimeout(r, gap));
|
|
2838
|
+
}
|
|
2839
|
+
} finally {
|
|
2840
|
+
this.pumpPromise = null;
|
|
2841
|
+
}
|
|
2842
|
+
}
|
|
2843
|
+
/** Element-level streaming push (cardkit.v1.cardElement.content): the answer
|
|
2844
|
+
* element's accumulated full text. Feishu diffs the prefix and types the delta
|
|
2845
|
+
* per the card's streaming_config. Needs streaming_mode on the card. */
|
|
2846
|
+
async streamElement(channel, elementId, content) {
|
|
2847
|
+
if (!this.cardId) return;
|
|
2848
|
+
const t0 = Date.now();
|
|
2849
|
+
try {
|
|
2850
|
+
await channel.rawClient.cardkit.v1.cardElement.content({
|
|
2851
|
+
path: { card_id: this.cardId, element_id: elementId },
|
|
2852
|
+
data: { content, sequence: ++this.seq, uuid: `e_${this.cardId}_${this.seq}` }
|
|
2853
|
+
});
|
|
2854
|
+
const rtt = Date.now() - t0;
|
|
2855
|
+
this.pushCount++;
|
|
2856
|
+
this.elPushes++;
|
|
2857
|
+
this.totalRttMs += rtt;
|
|
2858
|
+
if (rtt > this.maxRttMs) this.maxRttMs = rtt;
|
|
2859
|
+
} catch (err) {
|
|
2860
|
+
log.fail("card", err, { phase: "run-stream-el", cardId: this.cardId, seq: this.seq });
|
|
2861
|
+
}
|
|
2862
|
+
}
|
|
2765
2863
|
/** Create the entity from the initial (running) card and send a message
|
|
2766
2864
|
* referencing it by card_id. Returns the carrier message id. */
|
|
2767
2865
|
async create(channel, chatId, initialCard, opts) {
|
|
@@ -2803,11 +2901,17 @@ var RunCardStream = class {
|
|
|
2803
2901
|
if (!force && now - this.lastPush < STREAM_THROTTLE_MS) return;
|
|
2804
2902
|
this.lastPush = now;
|
|
2805
2903
|
this.lastContent = data;
|
|
2904
|
+
const t0 = Date.now();
|
|
2806
2905
|
try {
|
|
2807
2906
|
await channel.rawClient.cardkit.v1.card.update({
|
|
2808
2907
|
path: { card_id: this.cardId },
|
|
2809
2908
|
data: { card: { type: "card_json", data }, sequence: ++this.seq, uuid: `s_${this.cardId}_${this.seq}` }
|
|
2810
2909
|
});
|
|
2910
|
+
const rtt = Date.now() - t0;
|
|
2911
|
+
this.pushCount++;
|
|
2912
|
+
this.cardPushes++;
|
|
2913
|
+
this.totalRttMs += rtt;
|
|
2914
|
+
if (rtt > this.maxRttMs) this.maxRttMs = rtt;
|
|
2811
2915
|
} catch (err) {
|
|
2812
2916
|
log.fail("card", err, { phase: "run-stream", cardId: this.cardId, seq: this.seq });
|
|
2813
2917
|
}
|
|
@@ -2837,6 +2941,21 @@ var RunCardStream = class {
|
|
|
2837
2941
|
}
|
|
2838
2942
|
}
|
|
2839
2943
|
};
|
|
2944
|
+
function answerContent(card2, eid) {
|
|
2945
|
+
const els = card2.body?.elements;
|
|
2946
|
+
if (!Array.isArray(els)) return null;
|
|
2947
|
+
for (const el of els) {
|
|
2948
|
+
if (el && el.element_id === eid) return typeof el.content === "string" ? el.content : "";
|
|
2949
|
+
}
|
|
2950
|
+
return null;
|
|
2951
|
+
}
|
|
2952
|
+
function structureSig(card2, eid) {
|
|
2953
|
+
const body = card2.body;
|
|
2954
|
+
const els = body?.elements;
|
|
2955
|
+
if (!eid || !Array.isArray(els)) return JSON.stringify(card2);
|
|
2956
|
+
const blanked = els.map((el) => el && el.element_id === eid ? { ...el, content: "" } : el);
|
|
2957
|
+
return JSON.stringify({ ...card2, body: { ...body, elements: blanked } });
|
|
2958
|
+
}
|
|
2840
2959
|
|
|
2841
2960
|
// src/card/outbound-images.ts
|
|
2842
2961
|
import { readFile as readFile5, stat as stat2 } from "fs/promises";
|
|
@@ -3438,8 +3557,8 @@ function buildGroupSettingsCard(project) {
|
|
|
3438
3557
|
|
|
3439
3558
|
// src/service/update.ts
|
|
3440
3559
|
import { execFile, spawn as spawn4 } from "child_process";
|
|
3441
|
-
import { existsSync as
|
|
3442
|
-
import { dirname as
|
|
3560
|
+
import { existsSync as existsSync7, readFileSync as readFileSync2 } from "fs";
|
|
3561
|
+
import { dirname as dirname10, join as join11, resolve as resolve5 } from "path";
|
|
3443
3562
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
3444
3563
|
import { promisify } from "util";
|
|
3445
3564
|
|
|
@@ -3448,12 +3567,14 @@ import { spawn as spawn3, spawnSync } from "child_process";
|
|
|
3448
3567
|
import { existsSync as existsSync4 } from "fs";
|
|
3449
3568
|
import { mkdir as mkdir6, rm as rm2, writeFile as writeFile5 } from "fs/promises";
|
|
3450
3569
|
import { homedir as homedir3, userInfo as userInfo2 } from "os";
|
|
3451
|
-
import { dirname as
|
|
3452
|
-
import { fileURLToPath as
|
|
3570
|
+
import { dirname as dirname7, join as join8, resolve as resolve4 } from "path";
|
|
3571
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
3453
3572
|
|
|
3454
3573
|
// src/service/common.ts
|
|
3455
|
-
import {
|
|
3456
|
-
import {
|
|
3574
|
+
import { createReadStream, statSync } from "fs";
|
|
3575
|
+
import { appendFile, mkdir as mkdir5, readFile as readFile7 } from "fs/promises";
|
|
3576
|
+
import { dirname as dirname6, join as join7, resolve as resolve3 } from "path";
|
|
3577
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
3457
3578
|
function serviceStdoutPath() {
|
|
3458
3579
|
return join7(paths.appDir, "service.log");
|
|
3459
3580
|
}
|
|
@@ -3465,22 +3586,76 @@ async function ensureLogFiles() {
|
|
|
3465
3586
|
await appendFile(serviceStdoutPath(), "");
|
|
3466
3587
|
await appendFile(serviceStderrPath(), "");
|
|
3467
3588
|
}
|
|
3589
|
+
function resolveCliBinPath() {
|
|
3590
|
+
const distDir = dirname6(fileURLToPath2(import.meta.url));
|
|
3591
|
+
return resolve3(distDir, "..", "bin", "feishu-codex-bridge.mjs");
|
|
3592
|
+
}
|
|
3593
|
+
async function tailServiceLogs(follow) {
|
|
3594
|
+
await ensureLogFiles();
|
|
3595
|
+
const files = [serviceStdoutPath(), serviceStderrPath()];
|
|
3596
|
+
for (const f of files) {
|
|
3597
|
+
const tail = await lastLines(f, 100);
|
|
3598
|
+
if (tail) process.stdout.write(`
|
|
3599
|
+
===== ${f} =====
|
|
3600
|
+
${tail}
|
|
3601
|
+
`);
|
|
3602
|
+
}
|
|
3603
|
+
if (!follow) return;
|
|
3604
|
+
const offsets = new Map(files.map((f) => [f, fileSize(f)]));
|
|
3605
|
+
await new Promise((resolvePromise) => {
|
|
3606
|
+
const onSigint = () => {
|
|
3607
|
+
clearInterval(timer);
|
|
3608
|
+
process.off("SIGINT", onSigint);
|
|
3609
|
+
resolvePromise();
|
|
3610
|
+
};
|
|
3611
|
+
process.on("SIGINT", onSigint);
|
|
3612
|
+
const timer = setInterval(() => {
|
|
3613
|
+
for (const f of files) {
|
|
3614
|
+
const size = fileSize(f);
|
|
3615
|
+
const from = offsets.get(f) ?? 0;
|
|
3616
|
+
if (size > from) {
|
|
3617
|
+
offsets.set(f, size);
|
|
3618
|
+
createReadStream(f, { start: from, end: size - 1, encoding: "utf8" }).pipe(process.stdout, {
|
|
3619
|
+
end: false
|
|
3620
|
+
});
|
|
3621
|
+
} else if (size < from) {
|
|
3622
|
+
offsets.set(f, size);
|
|
3623
|
+
}
|
|
3624
|
+
}
|
|
3625
|
+
}, 700);
|
|
3626
|
+
});
|
|
3627
|
+
}
|
|
3628
|
+
function fileSize(file) {
|
|
3629
|
+
try {
|
|
3630
|
+
return statSync(file).size;
|
|
3631
|
+
} catch {
|
|
3632
|
+
return 0;
|
|
3633
|
+
}
|
|
3634
|
+
}
|
|
3635
|
+
async function lastLines(file, n) {
|
|
3636
|
+
try {
|
|
3637
|
+
const text = await readFile7(file, "utf8");
|
|
3638
|
+
return text.split("\n").slice(-n - 1).join("\n").trimEnd();
|
|
3639
|
+
} catch {
|
|
3640
|
+
return "";
|
|
3641
|
+
}
|
|
3642
|
+
}
|
|
3468
3643
|
|
|
3469
3644
|
// src/service/launchd.ts
|
|
3470
3645
|
var LAUNCHD_LABEL = "ai.feishu-codex-bridge.bot";
|
|
3471
3646
|
function launchAgentPlistPath() {
|
|
3472
3647
|
return join8(homedir3(), "Library", "LaunchAgents", `${LAUNCHD_LABEL}.plist`);
|
|
3473
3648
|
}
|
|
3474
|
-
function
|
|
3475
|
-
const distDir =
|
|
3476
|
-
return
|
|
3649
|
+
function resolveCliBinPath2() {
|
|
3650
|
+
const distDir = dirname7(fileURLToPath3(import.meta.url));
|
|
3651
|
+
return resolve4(distDir, "..", "bin", "feishu-codex-bridge.mjs");
|
|
3477
3652
|
}
|
|
3478
3653
|
function escapeXml(value) {
|
|
3479
3654
|
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
3480
3655
|
}
|
|
3481
3656
|
function buildPlist() {
|
|
3482
3657
|
const nodePath = process.execPath;
|
|
3483
|
-
const cliBinPath =
|
|
3658
|
+
const cliBinPath = resolveCliBinPath2();
|
|
3484
3659
|
const pathEnv = process.env.PATH ?? "/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin:/usr/sbin:/sbin";
|
|
3485
3660
|
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
3486
3661
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
@@ -3513,7 +3688,7 @@ function buildPlist() {
|
|
|
3513
3688
|
}
|
|
3514
3689
|
async function installLaunchd() {
|
|
3515
3690
|
const plistPath = launchAgentPlistPath();
|
|
3516
|
-
await mkdir6(
|
|
3691
|
+
await mkdir6(dirname7(plistPath), { recursive: true });
|
|
3517
3692
|
await ensureLogFiles();
|
|
3518
3693
|
await writeFile5(plistPath, buildPlist(), "utf8");
|
|
3519
3694
|
if (isLoaded()) {
|
|
@@ -3619,21 +3794,16 @@ function launchctlError(command, result) {
|
|
|
3619
3794
|
|
|
3620
3795
|
// src/service/schtasks.ts
|
|
3621
3796
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
3622
|
-
import {
|
|
3623
|
-
import { mkdir as mkdir7,
|
|
3624
|
-
import { dirname as
|
|
3625
|
-
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
3797
|
+
import { existsSync as existsSync5 } from "fs";
|
|
3798
|
+
import { mkdir as mkdir7, rm as rm3, writeFile as writeFile6 } from "fs/promises";
|
|
3799
|
+
import { dirname as dirname8, join as join9 } from "path";
|
|
3626
3800
|
var WINDOWS_TASK_NAME = "feishu-codex-bridge";
|
|
3627
3801
|
function launcherCmdPath() {
|
|
3628
3802
|
return join9(paths.appDir, "service-launcher.cmd");
|
|
3629
3803
|
}
|
|
3630
|
-
function resolveCliBinPath2() {
|
|
3631
|
-
const distDir = dirname7(fileURLToPath3(import.meta.url));
|
|
3632
|
-
return resolve4(distDir, "..", "bin", "feishu-codex-bridge.mjs");
|
|
3633
|
-
}
|
|
3634
3804
|
function buildLauncherCmd() {
|
|
3635
3805
|
const nodePath = process.execPath;
|
|
3636
|
-
const cliBinPath =
|
|
3806
|
+
const cliBinPath = resolveCliBinPath();
|
|
3637
3807
|
const pathEnv = process.env.PATH ?? "";
|
|
3638
3808
|
return [
|
|
3639
3809
|
"@echo off",
|
|
@@ -3657,7 +3827,7 @@ function schtasksError(command, r) {
|
|
|
3657
3827
|
}
|
|
3658
3828
|
async function writeLauncherCmd() {
|
|
3659
3829
|
const cmdPath = launcherCmdPath();
|
|
3660
|
-
await mkdir7(
|
|
3830
|
+
await mkdir7(dirname8(cmdPath), { recursive: true });
|
|
3661
3831
|
await ensureLogFiles();
|
|
3662
3832
|
await writeFile6(cmdPath, buildLauncherCmd(), "utf8");
|
|
3663
3833
|
}
|
|
@@ -3735,56 +3905,124 @@ async function waitUntilStopped(timeoutMs = 5e3) {
|
|
|
3735
3905
|
}
|
|
3736
3906
|
return false;
|
|
3737
3907
|
}
|
|
3738
|
-
|
|
3908
|
+
|
|
3909
|
+
// src/service/systemd.ts
|
|
3910
|
+
import { spawnSync as spawnSync3 } from "child_process";
|
|
3911
|
+
import { existsSync as existsSync6 } from "fs";
|
|
3912
|
+
import { mkdir as mkdir8, rm as rm4, writeFile as writeFile7 } from "fs/promises";
|
|
3913
|
+
import { homedir as homedir4 } from "os";
|
|
3914
|
+
import { dirname as dirname9, join as join10 } from "path";
|
|
3915
|
+
var SYSTEMD_UNIT_NAME = "feishu-codex-bridge.service";
|
|
3916
|
+
function systemdUnitPath() {
|
|
3917
|
+
const base = process.env.XDG_CONFIG_HOME ?? join10(homedir4(), ".config");
|
|
3918
|
+
return join10(base, "systemd", "user", SYSTEMD_UNIT_NAME);
|
|
3919
|
+
}
|
|
3920
|
+
function buildUnit() {
|
|
3921
|
+
const esc = (s) => s.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
3922
|
+
const nodePath = process.execPath;
|
|
3923
|
+
const cliBinPath = resolveCliBinPath();
|
|
3924
|
+
const pathEnv = process.env.PATH ?? "";
|
|
3925
|
+
return `[Unit]
|
|
3926
|
+
Description=feishu-codex-bridge bot
|
|
3927
|
+
After=network-online.target
|
|
3928
|
+
Wants=network-online.target
|
|
3929
|
+
|
|
3930
|
+
[Service]
|
|
3931
|
+
Type=simple
|
|
3932
|
+
ExecStart="${esc(nodePath)}" "${esc(cliBinPath)}" run
|
|
3933
|
+
Restart=always
|
|
3934
|
+
RestartSec=5
|
|
3935
|
+
StandardOutput=append:${serviceStdoutPath()}
|
|
3936
|
+
StandardError=append:${serviceStderrPath()}
|
|
3937
|
+
Environment="PATH=${esc(pathEnv)}"
|
|
3938
|
+
|
|
3939
|
+
[Install]
|
|
3940
|
+
WantedBy=default.target
|
|
3941
|
+
`;
|
|
3942
|
+
}
|
|
3943
|
+
function runSystemctl(args) {
|
|
3944
|
+
const r = spawnSync3("systemctl", ["--user", ...args], { encoding: "utf8" });
|
|
3945
|
+
return {
|
|
3946
|
+
ok: r.status === 0,
|
|
3947
|
+
status: r.status,
|
|
3948
|
+
stdout: r.stdout ?? "",
|
|
3949
|
+
stderr: (r.error ? `${r.error.message}
|
|
3950
|
+
` : "") + (r.stderr ?? "")
|
|
3951
|
+
};
|
|
3952
|
+
}
|
|
3953
|
+
function systemctlError(command, r) {
|
|
3954
|
+
const out = [r.stderr.trim(), r.stdout.trim()].filter(Boolean).join("\n");
|
|
3955
|
+
return new Error(`${command} \u5931\u8D25\uFF08exit ${r.status ?? "unknown"}\uFF09${out ? `\uFF1A${out}` : ""}`);
|
|
3956
|
+
}
|
|
3957
|
+
function systemdAvailable() {
|
|
3958
|
+
const r = spawnSync3("systemctl", ["--user", "is-system-running"], { encoding: "utf8" });
|
|
3959
|
+
if (r.error) return false;
|
|
3960
|
+
const out = `${r.stdout ?? ""}${r.stderr ?? ""}`;
|
|
3961
|
+
return !/not been booted with systemd|Failed to connect to (the )?bus|Failed to (connect|get) D-?Bus/i.test(out);
|
|
3962
|
+
}
|
|
3963
|
+
function ensureSystemdOrThrow() {
|
|
3964
|
+
if (systemdAvailable()) return;
|
|
3965
|
+
throw new Error(
|
|
3966
|
+
"\u672A\u68C0\u6D4B\u5230\u53EF\u7528\u7684\u7528\u6237\u7EA7 systemd\u3002WSL \u9700\u5728 /etc/wsl.conf \u5199\u5165 `[boot]\\nsystemd=true` \u540E\u6267\u884C `wsl --shutdown` \u91CD\u542F\uFF1B\u6216\u76F4\u63A5\u7528 `feishu-codex-bridge run` \u524D\u53F0\u8FD0\u884C\uFF08\u65E0\u9700\u540E\u53F0\u670D\u52A1\uFF09\u3002"
|
|
3967
|
+
);
|
|
3968
|
+
}
|
|
3969
|
+
async function installSystemd() {
|
|
3970
|
+
ensureSystemdOrThrow();
|
|
3971
|
+
const unitPath = systemdUnitPath();
|
|
3972
|
+
await mkdir8(dirname9(unitPath), { recursive: true });
|
|
3739
3973
|
await ensureLogFiles();
|
|
3740
|
-
|
|
3741
|
-
|
|
3742
|
-
|
|
3743
|
-
|
|
3744
|
-
|
|
3745
|
-
|
|
3746
|
-
|
|
3747
|
-
|
|
3748
|
-
if (
|
|
3749
|
-
|
|
3750
|
-
|
|
3751
|
-
|
|
3752
|
-
|
|
3753
|
-
|
|
3754
|
-
|
|
3755
|
-
|
|
3756
|
-
|
|
3757
|
-
|
|
3758
|
-
|
|
3759
|
-
|
|
3760
|
-
|
|
3761
|
-
|
|
3762
|
-
|
|
3763
|
-
|
|
3764
|
-
|
|
3765
|
-
|
|
3766
|
-
|
|
3767
|
-
|
|
3768
|
-
|
|
3769
|
-
|
|
3770
|
-
|
|
3771
|
-
|
|
3974
|
+
await writeFile7(unitPath, buildUnit(), "utf8");
|
|
3975
|
+
const reload = runSystemctl(["daemon-reload"]);
|
|
3976
|
+
if (!reload.ok) throw systemctlError("systemctl --user daemon-reload", reload);
|
|
3977
|
+
const enable = runSystemctl(["enable", "--now", SYSTEMD_UNIT_NAME]);
|
|
3978
|
+
if (!enable.ok) throw systemctlError("systemctl --user enable --now", enable);
|
|
3979
|
+
return statusSystemd();
|
|
3980
|
+
}
|
|
3981
|
+
async function uninstallSystemd() {
|
|
3982
|
+
if (systemdAvailable() && unitExists()) {
|
|
3983
|
+
runSystemctl(["disable", "--now", SYSTEMD_UNIT_NAME]);
|
|
3984
|
+
}
|
|
3985
|
+
await rm4(systemdUnitPath(), { force: true });
|
|
3986
|
+
if (systemdAvailable()) runSystemctl(["daemon-reload"]);
|
|
3987
|
+
}
|
|
3988
|
+
async function restartSystemd() {
|
|
3989
|
+
ensureSystemdOrThrow();
|
|
3990
|
+
if (!unitExists()) {
|
|
3991
|
+
throw new Error(`systemd unit \u672A\u5B89\u88C5\uFF1A${systemdUnitPath()}\uFF08\u5148\u8FD0\u884C \`feishu-codex-bridge start\`\uFF09`);
|
|
3992
|
+
}
|
|
3993
|
+
const restart = runSystemctl(["restart", SYSTEMD_UNIT_NAME]);
|
|
3994
|
+
if (!restart.ok) throw systemctlError("systemctl --user restart", restart);
|
|
3995
|
+
return statusSystemd();
|
|
3996
|
+
}
|
|
3997
|
+
function statusSystemd() {
|
|
3998
|
+
const installed = unitExists();
|
|
3999
|
+
const raw = installed && systemdAvailable() ? describeService() : "";
|
|
4000
|
+
return {
|
|
4001
|
+
platformName: "systemd (Linux user)",
|
|
4002
|
+
installed,
|
|
4003
|
+
running: systemdActive(),
|
|
4004
|
+
servicePath: systemdUnitPath(),
|
|
4005
|
+
stdoutPath: serviceStdoutPath(),
|
|
4006
|
+
stderrPath: serviceStderrPath(),
|
|
4007
|
+
pid: raw.match(/Main PID:\s*(\d+)/)?.[1],
|
|
4008
|
+
// On an inactive unit the "Process: <pid> ExecStart=... status=<n>" line
|
|
4009
|
+
// carries the last exit code.
|
|
4010
|
+
lastExit: raw.match(/Process:\s+\d+\s+ExecStart=.*status=(\d+)/)?.[1],
|
|
4011
|
+
raw
|
|
4012
|
+
};
|
|
3772
4013
|
}
|
|
3773
|
-
function
|
|
3774
|
-
|
|
3775
|
-
return statSync(file).size;
|
|
3776
|
-
} catch {
|
|
3777
|
-
return 0;
|
|
3778
|
-
}
|
|
4014
|
+
function unitExists() {
|
|
4015
|
+
return existsSync6(systemdUnitPath());
|
|
3779
4016
|
}
|
|
3780
|
-
|
|
3781
|
-
|
|
3782
|
-
|
|
3783
|
-
|
|
3784
|
-
|
|
3785
|
-
|
|
3786
|
-
|
|
3787
|
-
|
|
4017
|
+
function systemdActive() {
|
|
4018
|
+
const r = spawnSync3("systemctl", ["--user", "is-active", SYSTEMD_UNIT_NAME], {
|
|
4019
|
+
stdio: ["ignore", "ignore", "ignore"]
|
|
4020
|
+
});
|
|
4021
|
+
return r.status === 0;
|
|
4022
|
+
}
|
|
4023
|
+
function describeService() {
|
|
4024
|
+
const r = runSystemctl(["status", SYSTEMD_UNIT_NAME, "--no-pager"]);
|
|
4025
|
+
return r.stdout || r.stderr || "";
|
|
3788
4026
|
}
|
|
3789
4027
|
|
|
3790
4028
|
// src/service/adapter.ts
|
|
@@ -3804,17 +4042,27 @@ function getServiceAdapter() {
|
|
|
3804
4042
|
uninstall: uninstallSchtask,
|
|
3805
4043
|
status: async () => statusSchtask(),
|
|
3806
4044
|
restart: restartSchtask,
|
|
3807
|
-
logs:
|
|
4045
|
+
logs: tailServiceLogs
|
|
4046
|
+
};
|
|
4047
|
+
}
|
|
4048
|
+
if (process.platform === "linux") {
|
|
4049
|
+
return {
|
|
4050
|
+
install: installSystemd,
|
|
4051
|
+
uninstall: uninstallSystemd,
|
|
4052
|
+
status: async () => statusSystemd(),
|
|
4053
|
+
restart: restartSystemd,
|
|
4054
|
+
logs: tailServiceLogs
|
|
3808
4055
|
};
|
|
3809
4056
|
}
|
|
3810
4057
|
throw new Error(
|
|
3811
|
-
"service\uFF1A\u5F53\u524D\u5E73\u53F0\u6682\u4E0D\u652F\u6301\u540E\u53F0\u670D\u52A1\uFF08\u4EC5 macOS launchd / Windows \u8BA1\u5212\u4EFB\u52A1\uFF09\u3002\u8BF7\u7528 `feishu-codex-bridge run` \u524D\u53F0\u8FD0\u884C
|
|
4058
|
+
"service\uFF1A\u5F53\u524D\u5E73\u53F0\u6682\u4E0D\u652F\u6301\u540E\u53F0\u670D\u52A1\uFF08\u4EC5 macOS launchd / Windows \u8BA1\u5212\u4EFB\u52A1 / Linux systemd\uFF09\u3002\u8BF7\u7528 `feishu-codex-bridge run` \u524D\u53F0\u8FD0\u884C\u3002"
|
|
3812
4059
|
);
|
|
3813
4060
|
}
|
|
3814
4061
|
function isServiceRunning() {
|
|
3815
4062
|
try {
|
|
3816
4063
|
if (process.platform === "darwin") return isLoaded();
|
|
3817
4064
|
if (process.platform === "win32") return schtaskRunning();
|
|
4065
|
+
if (process.platform === "linux") return systemdActive();
|
|
3818
4066
|
} catch {
|
|
3819
4067
|
}
|
|
3820
4068
|
return false;
|
|
@@ -3824,11 +4072,11 @@ function isServiceRunning() {
|
|
|
3824
4072
|
var execFileP = promisify(execFile);
|
|
3825
4073
|
var NPM = process.platform === "win32" ? "npm.cmd" : "npm";
|
|
3826
4074
|
function pkgRoot() {
|
|
3827
|
-
return resolve5(
|
|
4075
|
+
return resolve5(dirname10(fileURLToPath4(import.meta.url)), "..");
|
|
3828
4076
|
}
|
|
3829
4077
|
function pkgJson() {
|
|
3830
4078
|
try {
|
|
3831
|
-
return JSON.parse(readFileSync2(
|
|
4079
|
+
return JSON.parse(readFileSync2(join11(pkgRoot(), "package.json"), "utf8"));
|
|
3832
4080
|
} catch {
|
|
3833
4081
|
return {};
|
|
3834
4082
|
}
|
|
@@ -3840,7 +4088,7 @@ function packageName() {
|
|
|
3840
4088
|
return pkgJson().name ?? "@modelzen/feishu-codex-bridge";
|
|
3841
4089
|
}
|
|
3842
4090
|
function isDevSource() {
|
|
3843
|
-
return
|
|
4091
|
+
return existsSync7(join11(pkgRoot(), ".git"));
|
|
3844
4092
|
}
|
|
3845
4093
|
function isNewer(a, b) {
|
|
3846
4094
|
const pa = a.split(".").map((n) => Number.parseInt(n, 10) || 0);
|
|
@@ -3886,9 +4134,9 @@ async function restartDaemon() {
|
|
|
3886
4134
|
}
|
|
3887
4135
|
|
|
3888
4136
|
// src/project/lifecycle.ts
|
|
3889
|
-
import { mkdir as
|
|
3890
|
-
import { existsSync as
|
|
3891
|
-
import { isAbsolute as isAbsolute2, join as
|
|
4137
|
+
import { mkdir as mkdir9 } from "fs/promises";
|
|
4138
|
+
import { existsSync as existsSync8 } from "fs";
|
|
4139
|
+
import { isAbsolute as isAbsolute2, join as join12, resolve as resolve6 } from "path";
|
|
3892
4140
|
|
|
3893
4141
|
// src/project/git-info.ts
|
|
3894
4142
|
import { execFile as execFile2 } from "child_process";
|
|
@@ -4024,11 +4272,11 @@ async function onboardGroup(channel, project) {
|
|
|
4024
4272
|
async function resolveCwd(name, existingPath) {
|
|
4025
4273
|
if (existingPath) {
|
|
4026
4274
|
const cwd2 = isAbsolute2(existingPath) ? existingPath : resolve6(existingPath);
|
|
4027
|
-
if (!
|
|
4275
|
+
if (!existsSync8(cwd2)) throw new Error(`\u6587\u4EF6\u5939\u4E0D\u5B58\u5728\uFF1A${cwd2}`);
|
|
4028
4276
|
return { cwd: cwd2, blank: false };
|
|
4029
4277
|
}
|
|
4030
|
-
const cwd =
|
|
4031
|
-
await
|
|
4278
|
+
const cwd = join12(paths.projectsRootDir, name);
|
|
4279
|
+
await mkdir9(cwd, { recursive: true });
|
|
4032
4280
|
return { cwd, blank: true };
|
|
4033
4281
|
}
|
|
4034
4282
|
async function createProject(channel, input2) {
|
|
@@ -4095,8 +4343,8 @@ async function leaveChat(channel, chatId) {
|
|
|
4095
4343
|
}
|
|
4096
4344
|
|
|
4097
4345
|
// src/bot/session-store.ts
|
|
4098
|
-
import { mkdir as
|
|
4099
|
-
import { dirname as
|
|
4346
|
+
import { mkdir as mkdir10, readFile as readFile8, rename as rename5, writeFile as writeFile8 } from "fs/promises";
|
|
4347
|
+
import { dirname as dirname11 } from "path";
|
|
4100
4348
|
var FILE_VERSION3 = 1;
|
|
4101
4349
|
async function read2() {
|
|
4102
4350
|
try {
|
|
@@ -4109,10 +4357,10 @@ async function read2() {
|
|
|
4109
4357
|
}
|
|
4110
4358
|
}
|
|
4111
4359
|
async function write2(sessions) {
|
|
4112
|
-
await
|
|
4360
|
+
await mkdir10(dirname11(paths.sessionsFile), { recursive: true });
|
|
4113
4361
|
const tmp = `${paths.sessionsFile}.tmp-${process.pid}`;
|
|
4114
4362
|
const body = { version: FILE_VERSION3, sessions };
|
|
4115
|
-
await
|
|
4363
|
+
await writeFile8(tmp, `${JSON.stringify(body, null, 2)}
|
|
4116
4364
|
`, "utf8");
|
|
4117
4365
|
await rename5(tmp, paths.sessionsFile);
|
|
4118
4366
|
}
|
|
@@ -4156,8 +4404,8 @@ async function handleDmConsole(channel, cfg, msg) {
|
|
|
4156
4404
|
}
|
|
4157
4405
|
|
|
4158
4406
|
// src/bot/media.ts
|
|
4159
|
-
import { mkdir as
|
|
4160
|
-
import { join as
|
|
4407
|
+
import { mkdir as mkdir11, readdir as readdir2, rm as rm5, stat as stat3 } from "fs/promises";
|
|
4408
|
+
import { join as join13 } from "path";
|
|
4161
4409
|
var MAX_IMAGES2 = 9;
|
|
4162
4410
|
var MEDIA_TTL_MS = 60 * 6e4;
|
|
4163
4411
|
var EXT_BY_CONTENT_TYPE = {
|
|
@@ -4186,7 +4434,7 @@ async function collectInboundImages(channel, msg) {
|
|
|
4186
4434
|
if (refs.length === 0) return [];
|
|
4187
4435
|
await pruneOldMedia();
|
|
4188
4436
|
try {
|
|
4189
|
-
await
|
|
4437
|
+
await mkdir11(paths.mediaDir, { recursive: true });
|
|
4190
4438
|
} catch {
|
|
4191
4439
|
}
|
|
4192
4440
|
const out = [];
|
|
@@ -4262,7 +4510,7 @@ async function downloadOne(channel, ref, index) {
|
|
|
4262
4510
|
params: { type: "image" }
|
|
4263
4511
|
});
|
|
4264
4512
|
const ext = extFromHeaders(res.headers);
|
|
4265
|
-
const file =
|
|
4513
|
+
const file = join13(paths.mediaDir, `${safeName(ref.fileKey)}-${index}.${ext}`);
|
|
4266
4514
|
await res.writeFile(file);
|
|
4267
4515
|
return file;
|
|
4268
4516
|
} catch (err) {
|
|
@@ -4296,10 +4544,10 @@ async function pruneOldMedia() {
|
|
|
4296
4544
|
}
|
|
4297
4545
|
const cutoff = Date.now() - MEDIA_TTL_MS;
|
|
4298
4546
|
for (const name of entries) {
|
|
4299
|
-
const file =
|
|
4547
|
+
const file = join13(paths.mediaDir, name);
|
|
4300
4548
|
try {
|
|
4301
4549
|
const st = await stat3(file);
|
|
4302
|
-
if (st.mtimeMs < cutoff) await
|
|
4550
|
+
if (st.mtimeMs < cutoff) await rm5(file, { force: true });
|
|
4303
4551
|
} catch {
|
|
4304
4552
|
}
|
|
4305
4553
|
}
|
|
@@ -5348,11 +5596,29 @@ ${tail}` }, { replyTo: evt.messageId }).catch(() => void 0);
|
|
|
5348
5596
|
},
|
|
5349
5597
|
stopSignal
|
|
5350
5598
|
);
|
|
5599
|
+
const tStart = Date.now();
|
|
5600
|
+
let firstEvAt = 0;
|
|
5601
|
+
let firstTextAt = 0;
|
|
5602
|
+
let lastEvAt = tStart;
|
|
5603
|
+
let evCount = 0;
|
|
5604
|
+
let textChars = 0;
|
|
5351
5605
|
for await (const ev of guarded) {
|
|
5606
|
+
const tEv = Date.now();
|
|
5607
|
+
if (!firstEvAt) firstEvAt = tEv;
|
|
5608
|
+
const et = ev.type;
|
|
5609
|
+
if (et === "text_delta") {
|
|
5610
|
+
if (!firstTextAt) firstTextAt = tEv;
|
|
5611
|
+
const d = ev.delta;
|
|
5612
|
+
if (typeof d === "string") textChars += d.length;
|
|
5613
|
+
}
|
|
5614
|
+
lastEvAt = tEv;
|
|
5615
|
+
evCount++;
|
|
5352
5616
|
render.apply(ev);
|
|
5353
5617
|
rc.rs = render.snapshot();
|
|
5354
|
-
|
|
5618
|
+
stream2.streamCoalesced(channel, buildRunCard(rc), ANSWER_EID);
|
|
5355
5619
|
}
|
|
5620
|
+
const doneAt = Date.now();
|
|
5621
|
+
await stream2.drain();
|
|
5356
5622
|
state.interrupt = void 0;
|
|
5357
5623
|
const killed = interrupted || timedOut;
|
|
5358
5624
|
if (timedOut) render.timeout(Math.max(1, Math.round(idleMs / 6e4)));
|
|
@@ -5373,6 +5639,25 @@ ${tail}` }, { replyTo: evt.messageId }).catch(() => void 0);
|
|
|
5373
5639
|
rc.images = await uploadOutboundImages(channel, imgSources, opts.cwd ?? fallbackCwd);
|
|
5374
5640
|
}
|
|
5375
5641
|
await stream2.updateCard(channel, buildRunCard(rc));
|
|
5642
|
+
{
|
|
5643
|
+
const terminalAt = Date.now();
|
|
5644
|
+
const st = stream2.stats();
|
|
5645
|
+
log.info("stream", "timing", {
|
|
5646
|
+
firstEv: firstEvAt ? firstEvAt - tStart : -1,
|
|
5647
|
+
firstText: firstTextAt ? firstTextAt - tStart : -1,
|
|
5648
|
+
lastEv: lastEvAt - tStart,
|
|
5649
|
+
done: doneAt - tStart,
|
|
5650
|
+
terminal: terminalAt - tStart,
|
|
5651
|
+
doneToTerminal: terminalAt - doneAt,
|
|
5652
|
+
events: evCount,
|
|
5653
|
+
textChars,
|
|
5654
|
+
pushes: st.pushCount,
|
|
5655
|
+
cardPushes: st.cardPushes,
|
|
5656
|
+
elPushes: st.elPushes,
|
|
5657
|
+
rttAvg: st.pushCount ? Math.round(st.totalRttMs / st.pushCount) : 0,
|
|
5658
|
+
rttMax: st.maxRttMs
|
|
5659
|
+
});
|
|
5660
|
+
}
|
|
5376
5661
|
runsByCard.delete(cardMsgId);
|
|
5377
5662
|
promoteCard(finalMsgId, rc);
|
|
5378
5663
|
for (const fence of fences) {
|
|
@@ -5634,7 +5919,7 @@ async function startBridge(opts) {
|
|
|
5634
5919
|
|
|
5635
5920
|
// src/core/single-instance.ts
|
|
5636
5921
|
import { mkdirSync as mkdirSync2, readFileSync as readFileSync3, unlinkSync, writeFileSync } from "fs";
|
|
5637
|
-
import { dirname as
|
|
5922
|
+
import { dirname as dirname12 } from "path";
|
|
5638
5923
|
var BridgeAlreadyRunningError = class extends Error {
|
|
5639
5924
|
constructor(pid) {
|
|
5640
5925
|
super(
|
|
@@ -5663,7 +5948,7 @@ function acquireSingleInstanceLock(appId) {
|
|
|
5663
5948
|
} catch (err) {
|
|
5664
5949
|
if (err instanceof BridgeAlreadyRunningError) throw err;
|
|
5665
5950
|
}
|
|
5666
|
-
mkdirSync2(
|
|
5951
|
+
mkdirSync2(dirname12(file), { recursive: true });
|
|
5667
5952
|
const record = { pid: process.pid, appId, startedAt: Date.now() };
|
|
5668
5953
|
writeFileSync(file, `${JSON.stringify(record)}
|
|
5669
5954
|
`, "utf8");
|
|
@@ -5732,9 +6017,7 @@ async function runStart() {
|
|
|
5732
6017
|
return;
|
|
5733
6018
|
}
|
|
5734
6019
|
const status = await getServiceAdapter().install();
|
|
5735
|
-
console.log(
|
|
5736
|
-
process.platform === "win32" ? "\u2713 \u540E\u53F0\u670D\u52A1\u5DF2\u5B89\u88C5\u5E76\u542F\u52A8\uFF08\u767B\u5F55\u81EA\u542F\uFF1B\u6CE8\u610F\uFF1AWindows \u8BA1\u5212\u4EFB\u52A1\u65E0\u5D29\u6E83\u81EA\u52A8\u62C9\u8D77\uFF09\u3002" : "\u2713 \u540E\u53F0\u670D\u52A1\u5DF2\u5B89\u88C5\u5E76\u542F\u52A8\uFF08\u5F00\u673A\u81EA\u542F\u3001\u5D29\u6E83\u81EA\u52A8\u62C9\u8D77\uFF09\u3002"
|
|
5737
|
-
);
|
|
6020
|
+
console.log(installedNote());
|
|
5738
6021
|
printStatus(status);
|
|
5739
6022
|
}
|
|
5740
6023
|
async function runStop() {
|
|
@@ -5752,6 +6035,16 @@ async function runStatus() {
|
|
|
5752
6035
|
async function runLogs(follow) {
|
|
5753
6036
|
await getServiceAdapter().logs(follow);
|
|
5754
6037
|
}
|
|
6038
|
+
function installedNote() {
|
|
6039
|
+
switch (process.platform) {
|
|
6040
|
+
case "win32":
|
|
6041
|
+
return "\u2713 \u540E\u53F0\u670D\u52A1\u5DF2\u5B89\u88C5\u5E76\u542F\u52A8\uFF08\u767B\u5F55\u81EA\u542F\uFF1B\u6CE8\u610F\uFF1AWindows \u8BA1\u5212\u4EFB\u52A1\u65E0\u5D29\u6E83\u81EA\u52A8\u62C9\u8D77\uFF09\u3002";
|
|
6042
|
+
case "linux":
|
|
6043
|
+
return "\u2713 \u540E\u53F0\u670D\u52A1\u5DF2\u5B89\u88C5\u5E76\u542F\u52A8\uFF08\u767B\u5F55\u81EA\u542F\u3001\u5D29\u6E83\u81EA\u52A8\u62C9\u8D77\uFF09\u3002\n \u63D0\u793A\uFF1A\u6CE8\u9500\u540E\u4ECD\u4FDD\u6301\u8FD0\u884C\u9700\u6267\u884C\u4E00\u6B21 `loginctl enable-linger $USER`\u3002";
|
|
6044
|
+
default:
|
|
6045
|
+
return "\u2713 \u540E\u53F0\u670D\u52A1\u5DF2\u5B89\u88C5\u5E76\u542F\u52A8\uFF08\u5F00\u673A\u81EA\u542F\u3001\u5D29\u6E83\u81EA\u52A8\u62C9\u8D77\uFF09\u3002";
|
|
6046
|
+
}
|
|
6047
|
+
}
|
|
5755
6048
|
function printStatus(status) {
|
|
5756
6049
|
console.log(`service: ${status.platformName}`);
|
|
5757
6050
|
console.log(`path: ${status.servicePath}`);
|
|
@@ -5817,7 +6110,7 @@ async function runUpdate(opts = {}) {
|
|
|
5817
6110
|
}
|
|
5818
6111
|
|
|
5819
6112
|
// src/cli/commands/bot.ts
|
|
5820
|
-
import { rm as
|
|
6113
|
+
import { rm as rm6 } from "fs/promises";
|
|
5821
6114
|
async function runBotInit(name) {
|
|
5822
6115
|
if (!ensureCodex()) {
|
|
5823
6116
|
process.exitCode = 1;
|
|
@@ -5872,7 +6165,7 @@ async function runBotRm(name) {
|
|
|
5872
6165
|
}
|
|
5873
6166
|
const after = await removeBot(bot2.appId);
|
|
5874
6167
|
await removeSecret(secretKeyForApp(bot2.appId));
|
|
5875
|
-
await
|
|
6168
|
+
await rm6(botDir(bot2.appId), { recursive: true, force: true });
|
|
5876
6169
|
console.log(`\u2713 \u5DF2\u79FB\u9664\u673A\u5668\u4EBA\u300C${bot2.name}\u300D(${bot2.appId})\uFF1A\u6CE8\u518C\u8868 + \u5BC6\u94A5 + \u72B6\u6001\u76EE\u5F55(projects/sessions)\u3002`);
|
|
5877
6170
|
if (after.bots.length === 0) {
|
|
5878
6171
|
console.log(" \u5DF2\u65E0\u4EFB\u4F55\u673A\u5668\u4EBA\uFF0C`bot init` \u91CD\u65B0\u521B\u5EFA\u3002");
|
package/package.json
CHANGED