@wu529778790/open-im 1.10.7-beta.0 → 1.10.7-beta.2

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/README.md CHANGED
@@ -38,6 +38,10 @@ Config: **`~/.open-im/config.json`**
38
38
 
39
39
  After `start`, the CLI prints the dashboard URL (default **`http://127.0.0.1:39282`**).
40
40
 
41
+ ## Git co-authors
42
+
43
+ `Co-authored-by` is appended by default on AI-driven commits. **Disable:** set **`OPEN_IM_GIT_COAUTHOR=0`** in the environment and restart the bridge.
44
+
41
45
  ## Web dashboard
42
46
 
43
47
  `open-im start` and `open-im dashboard` serve the built-in SPA and **`/api/*`** on **`OPEN_IM_WEB_PORT`** (default **39282**). Open **`http://127.0.0.1:39282`** in a browser (same origin as the API). Override the displayed URL with **`OPEN_IM_PUBLIC_WEB_URL`** if behind a proxy.
package/README.zh-CN.md CHANGED
@@ -38,6 +38,10 @@ npx @wu529778790/open-im start
38
38
 
39
39
  `start` 后会提示控制台地址(默认 **`http://127.0.0.1:39282`**)。
40
40
 
41
+ ## Git 共同作者
42
+
43
+ 默认在 AI 发起的提交里追加 `Co-authored-by`。**关闭**:设置环境变量 **`OPEN_IM_GIT_COAUTHOR=0`** 并重启桥接。
44
+
41
45
  ## Web 控制台
42
46
 
43
47
  `open-im start` / `open-im dashboard` 在 **`OPEN_IM_WEB_PORT`**(默认 **39282**)提供内置页面与 **`/api/*`**。浏览器打开 **`http://127.0.0.1:39282`** 即可(与 API 同源)。反向代理时可设 **`OPEN_IM_PUBLIC_WEB_URL`**。
@@ -17,6 +17,11 @@ export declare const WEB_CONFIG_PORT = 39282;
17
17
  export declare function getDefaultLocalDashboardUrl(): string;
18
18
  export declare function getPublicWebDashboardUrl(): string;
19
19
  export declare const IMAGE_DIR: string;
20
+ /**
21
+ * Co-authored-by 使用的固定提交者地址(开箱即用,形如 GitHub noreply)。
22
+ * 若希望贡献图关联到具体用户,请将该地址加入对应 GitHub 账号的已验证联系方式,或 fork 后自行修改常量。
23
+ */
24
+ export declare const DEFAULT_OPEN_IM_COAUTHOR_ADDR = "529778790@qq.com";
20
25
  export declare const TERMINAL_ONLY_COMMANDS: Set<string>;
21
26
  /** CardKit 流式更新节流:80ms(约 12 次/秒,cardElement.content 专为打字机设计,支持更高频率) */
22
27
  export declare const CARDKIT_THROTTLE_MS = 80;
package/dist/constants.js CHANGED
@@ -29,6 +29,11 @@ export function getPublicWebDashboardUrl() {
29
29
  return raw.replace(/\/$/, "");
30
30
  }
31
31
  export const IMAGE_DIR = join(tmpdir(), "open-im-images");
32
+ /**
33
+ * Co-authored-by 使用的固定提交者地址(开箱即用,形如 GitHub noreply)。
34
+ * 若希望贡献图关联到具体用户,请将该地址加入对应 GitHub 账号的已验证联系方式,或 fork 后自行修改常量。
35
+ */
36
+ export const DEFAULT_OPEN_IM_COAUTHOR_ADDR = "529778790@qq.com";
32
37
  export const TERMINAL_ONLY_COMMANDS = new Set([
33
38
  "/context",
34
39
  "/rewind",
@@ -0,0 +1,5 @@
1
+ /**
2
+ * 桥接启动时调用:在未占用 global core.hooksPath 时自动指向 ~/.open-im/git-hooks。
3
+ * 若用户已设置其它 hooksPath,仅打日志,不覆盖。
4
+ */
5
+ export declare function ensureOpenImGlobalPrepareCommitHook(): void;
@@ -0,0 +1,86 @@
1
+ import { chmodSync, copyFileSync, existsSync, mkdirSync } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import { dirname, join, resolve as pathResolve } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import { execFileSync } from "node:child_process";
6
+ import { APP_HOME } from "./constants.js";
7
+ import { createLogger } from "./logger.js";
8
+ const __dirname = dirname(fileURLToPath(import.meta.url));
9
+ const log = createLogger("GitHook");
10
+ /** 避免每次启动在满足「已占用 hooksPath」时重复打 WARN */
11
+ let warnedForeignHooksPath = false;
12
+ function packageRoot() {
13
+ return join(__dirname, "..");
14
+ }
15
+ function bundledPrepareCommitMsg() {
16
+ return join(packageRoot(), "scripts", "git-hooks", "prepare-commit-msg");
17
+ }
18
+ function targetHooksDir() {
19
+ return join(APP_HOME, "git-hooks");
20
+ }
21
+ function targetPrepareCommitMsg() {
22
+ return join(targetHooksDir(), "prepare-commit-msg");
23
+ }
24
+ function gitTry(args) {
25
+ try {
26
+ return execFileSync("git", args, { encoding: "utf-8" }).trim();
27
+ }
28
+ catch {
29
+ return undefined;
30
+ }
31
+ }
32
+ /** 将包内钩子复制到 ~/.open-im/git-hooks(失败则返回 false) */
33
+ function materializeOpenImHookScript() {
34
+ const src = bundledPrepareCommitMsg();
35
+ if (!existsSync(src)) {
36
+ log.debug(`Bundled prepare-commit-msg not found at ${src}, skip`);
37
+ return false;
38
+ }
39
+ try {
40
+ mkdirSync(targetHooksDir(), { recursive: true });
41
+ copyFileSync(src, targetPrepareCommitMsg());
42
+ }
43
+ catch (err) {
44
+ log.warn(`Could not copy prepare-commit-msg: ${err instanceof Error ? err.message : String(err)}`);
45
+ return false;
46
+ }
47
+ if (process.platform !== "win32") {
48
+ try {
49
+ chmodSync(targetPrepareCommitMsg(), 0o755);
50
+ }
51
+ catch {
52
+ /* ignore */
53
+ }
54
+ }
55
+ return true;
56
+ }
57
+ /**
58
+ * 桥接启动时调用:在未占用 global core.hooksPath 时自动指向 ~/.open-im/git-hooks。
59
+ * 若用户已设置其它 hooksPath,仅打日志,不覆盖。
60
+ */
61
+ export function ensureOpenImGlobalPrepareCommitHook() {
62
+ if (!materializeOpenImHookScript())
63
+ return;
64
+ const hooksPath = targetHooksDir();
65
+ const existing = gitTry(["config", "--global", "--get", "core.hooksPath"]);
66
+ if (!existing) {
67
+ try {
68
+ execFileSync("git", ["config", "--global", "core.hooksPath", hooksPath]);
69
+ log.info(`Auto-set git config --global core.hooksPath → ${hooksPath}`);
70
+ }
71
+ catch (err) {
72
+ log.warn(`Could not set global core.hooksPath (is git installed?): ${err instanceof Error ? err.message : String(err)}`);
73
+ }
74
+ return;
75
+ }
76
+ const a = pathResolve(existing.replace(/^~(?=$|[/\\])/, homedir()));
77
+ const b = pathResolve(hooksPath);
78
+ if (a === b)
79
+ return;
80
+ if (!warnedForeignHooksPath) {
81
+ warnedForeignHooksPath = true;
82
+ log.warn(`Global core.hooksPath is "${existing}" (not open-im's dir); skipping auto wire. ` +
83
+ `Merge scripts/git-hooks/prepare-commit-msg into that hooks directory, ` +
84
+ `or set OPEN_IM_GIT_COAUTHOR_NO_AUTO_HOOK=1 to silence this warning.`);
85
+ }
86
+ }
package/dist/index.js CHANGED
@@ -31,6 +31,7 @@ import { initLogger, createLogger, closeLogger, emitStructuredEvent, shutdownLog
31
31
  import { APP_HOME, SHUTDOWN_PORT } from "./constants.js";
32
32
  import { createRequire } from "node:module";
33
33
  import { escapePathForMarkdown } from "./shared/utils.js";
34
+ import { applyOpenImGitCoauthorToProcessEnv } from "./shared/git-coauthor.js";
34
35
  const require = createRequire(import.meta.url);
35
36
  const { version: APP_VERSION } = require("../package.json");
36
37
  const log = createLogger("Main");
@@ -157,6 +158,7 @@ export async function main() {
157
158
  logLevel: config.logLevel,
158
159
  telemetry: config.telemetry,
159
160
  });
161
+ applyOpenImGitCoauthorToProcessEnv();
160
162
  }
161
163
  catch (err) {
162
164
  if (err instanceof Error &&
@@ -172,6 +174,7 @@ export async function main() {
172
174
  logLevel: config.logLevel,
173
175
  telemetry: config.telemetry,
174
176
  });
177
+ applyOpenImGitCoauthorToProcessEnv();
175
178
  }
176
179
  else {
177
180
  throw err;
@@ -211,9 +214,8 @@ export async function main() {
211
214
  }
212
215
  log.info(`启用平台: ${config.enabledPlatforms.join(", ")}`);
213
216
  const sessionManager = new SessionManager(startupCwd, config.claudeWorkDir);
214
- // CLI 工具(Codex/CodeBuddy)的 session 是进程级别的,服务重启后一定无效。
215
- // 启动时仅清除 CLI 工具自己的 sessionId,保留 Claude 的持久上下文。
216
- sessionManager.clearAllCliSessionIds();
217
+ // Codex/CodeBuddy sessionId 持久化在 sessions.json;重启后沿用以便 --resume 续聊。
218
+ // CLI 判定会话无效,ai-task 会通过 onSessionInvalid 清理并提示重试。
217
219
  // Track active platform handles and successfully initialized platforms
218
220
  const activeHandles = new Map();
219
221
  const successfulPlatforms = [];
@@ -25,11 +25,6 @@ export declare class SessionManager {
25
25
  hasUserSession(userId: string): boolean;
26
26
  getConvId(userId: string): string;
27
27
  setWorkDir(userId: string, workDir: string): Promise<string>;
28
- /**
29
- * 服务启动时调用:清除所有用户的 CLI sessionId。
30
- * Codex/CodeBuddy 的 session 是进程级别的,服务重启后旧 session 一定无效。
31
- */
32
- clearAllCliSessionIds(): void;
33
28
  newSession(userId: string): boolean;
34
29
  clearActiveToolSession(userId: string, toolId: ToolId): boolean;
35
30
  listConvHistory(userId: string): ConvHistoryEntry[];
@@ -136,41 +136,6 @@ export class SessionManager {
136
136
  log.info(`WorkDir changed for user ${userId}: ${realPath}, oldConvId=${oldConvId}`);
137
137
  return realPath;
138
138
  }
139
- /**
140
- * 服务启动时调用:清除所有用户的 CLI sessionId。
141
- * Codex/CodeBuddy 的 session 是进程级别的,服务重启后旧 session 一定无效。
142
- */
143
- clearAllCliSessionIds() {
144
- let changed = false;
145
- for (const [, s] of this.sessions) {
146
- for (const toolId of ['codex', 'codebuddy']) {
147
- if (this.getToolSessionId(s, toolId) !== undefined) {
148
- this.clearToolSessionId(s, toolId);
149
- changed = true;
150
- }
151
- }
152
- if (s.threads) {
153
- for (const t of Object.values(s.threads)) {
154
- for (const toolId of ['codex', 'codebuddy']) {
155
- if (t.sessionIds?.[toolId] !== undefined) {
156
- delete t.sessionIds[toolId];
157
- changed = true;
158
- }
159
- }
160
- }
161
- }
162
- }
163
- for (const key of [...this.convSessionMap.keys()]) {
164
- if (key.endsWith(':codex') || key.endsWith(':codebuddy')) {
165
- this.convSessionMap.delete(key);
166
- changed = true;
167
- }
168
- }
169
- if (changed) {
170
- this.flushSync();
171
- log.info('Cleared CLI session IDs for codex/codebuddy on startup');
172
- }
173
- }
174
139
  newSession(userId) {
175
140
  const s = this.sessions.get(userId);
176
141
  if (s) {
@@ -0,0 +1,16 @@
1
+ /**
2
+ * GitHub 会识别 commit message 中的 Co-authored-by 并在提交页展示共同作者。
3
+ *
4
+ * **默认开启**:无需也不应手动设置 `OPEN_IM_GIT_COAUTHOR_LINE`,桥接启动时会自动写入该变量;
5
+ * 仅当 `OPEN_IM_GIT_COAUTHOR=0|false|no` 时才会清空。地址来自 {@link DEFAULT_OPEN_IM_COAUTHOR_ADDR}。
6
+ *
7
+ * - OPEN_IM_GIT_COAUTHOR=0|false|no — 关闭共同作者(不写 OPEN_IM_GIT_COAUTHOR_LINE)
8
+ * - OPEN_IM_GIT_COAUTHOR_NAME — 显示名,默认 open-im
9
+ * - OPEN_IM_GIT_COAUTHOR_NO_AUTO_HOOK=1 — 不自动改 git global core.hooksPath(需自行装钩子)
10
+ */
11
+ export declare function resolveOpenImGitCoauthorLine(): string | undefined;
12
+ /**
13
+ * 默认开启:设置 `process.env.OPEN_IM_GIT_COAUTHOR_LINE` 供子进程与 git 钩子使用;
14
+ * AI 发起的 commit 会由 prepare-commit-msg 追加该行(除非已显式关闭共同作者)。
15
+ */
16
+ export declare function applyOpenImGitCoauthorToProcessEnv(): void;
@@ -0,0 +1,47 @@
1
+ import { DEFAULT_OPEN_IM_COAUTHOR_ADDR } from "../constants.js";
2
+ import { createLogger } from "../logger.js";
3
+ import { ensureOpenImGlobalPrepareCommitHook } from "../git-hook.js";
4
+ const log = createLogger("GitCoauthor");
5
+ const DISABLE_VALUES = new Set(["0", "false", "no"]);
6
+ /**
7
+ * GitHub 会识别 commit message 中的 Co-authored-by 并在提交页展示共同作者。
8
+ *
9
+ * **默认开启**:无需也不应手动设置 `OPEN_IM_GIT_COAUTHOR_LINE`,桥接启动时会自动写入该变量;
10
+ * 仅当 `OPEN_IM_GIT_COAUTHOR=0|false|no` 时才会清空。地址来自 {@link DEFAULT_OPEN_IM_COAUTHOR_ADDR}。
11
+ *
12
+ * - OPEN_IM_GIT_COAUTHOR=0|false|no — 关闭共同作者(不写 OPEN_IM_GIT_COAUTHOR_LINE)
13
+ * - OPEN_IM_GIT_COAUTHOR_NAME — 显示名,默认 open-im
14
+ * - OPEN_IM_GIT_COAUTHOR_NO_AUTO_HOOK=1 — 不自动改 git global core.hooksPath(需自行装钩子)
15
+ */
16
+ export function resolveOpenImGitCoauthorLine() {
17
+ const flag = process.env.OPEN_IM_GIT_COAUTHOR?.trim().toLowerCase();
18
+ if (flag && DISABLE_VALUES.has(flag))
19
+ return undefined;
20
+ const addr = DEFAULT_OPEN_IM_COAUTHOR_ADDR;
21
+ if (!addr.trim())
22
+ return undefined;
23
+ const rawName = process.env.OPEN_IM_GIT_COAUTHOR_NAME?.trim() || "open-im";
24
+ const name = rawName.replace(/[<>]/g, "").trim() || "open-im";
25
+ return `Co-authored-by: ${name} <${addr}>`;
26
+ }
27
+ /**
28
+ * 默认开启:设置 `process.env.OPEN_IM_GIT_COAUTHOR_LINE` 供子进程与 git 钩子使用;
29
+ * AI 发起的 commit 会由 prepare-commit-msg 追加该行(除非已显式关闭共同作者)。
30
+ */
31
+ export function applyOpenImGitCoauthorToProcessEnv() {
32
+ const line = resolveOpenImGitCoauthorLine();
33
+ if (line) {
34
+ process.env.OPEN_IM_GIT_COAUTHOR_LINE = line;
35
+ if (!isTruthyEnv(process.env.OPEN_IM_GIT_COAUTHOR_NO_AUTO_HOOK)) {
36
+ ensureOpenImGlobalPrepareCommitHook();
37
+ }
38
+ log.info("Git co-author enabled; AI git commits will append OPEN_IM_GIT_COAUTHOR_LINE when the hook runs.");
39
+ }
40
+ else {
41
+ delete process.env.OPEN_IM_GIT_COAUTHOR_LINE;
42
+ }
43
+ }
44
+ function isTruthyEnv(v) {
45
+ const t = v?.trim().toLowerCase();
46
+ return t === "1" || t === "true" || t === "yes";
47
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wu529778790/open-im",
3
- "version": "1.10.7-beta.0",
3
+ "version": "1.10.7-beta.2",
4
4
  "description": "Multi-platform IM bridge for AI CLI tools (Claude, Codex, CodeBuddy)",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -10,6 +10,7 @@
10
10
  "files": [
11
11
  "dist",
12
12
  "web/dist",
13
+ "scripts/git-hooks",
13
14
  "README.md",
14
15
  "LICENSE"
15
16
  ],
@@ -0,0 +1,18 @@
1
+ #!/bin/sh
2
+ # open-im: 在 git commit 时追加 Co-authored-by。
3
+ # OPEN_IM_GIT_COAUTHOR_LINE:由桥接进程默认注入(共同作者默认开启),无需在 shell 里手动 export。
4
+ # 若为空则跳过(例如未启动桥接、或已设 OPEN_IM_GIT_COAUTHOR=0 后重启)。
5
+ # 安装:由桥接启动时自动复制并配置 core.hooksPath(若尚未占用)
6
+
7
+ COMMIT_MSG_FILE=$1
8
+
9
+ if [ -z "$OPEN_IM_GIT_COAUTHOR_LINE" ] || [ -z "$COMMIT_MSG_FILE" ] || [ ! -f "$COMMIT_MSG_FILE" ]; then
10
+ exit 0
11
+ fi
12
+
13
+ if grep -qF "$OPEN_IM_GIT_COAUTHOR_LINE" "$COMMIT_MSG_FILE" 2>/dev/null; then
14
+ exit 0
15
+ fi
16
+
17
+ printf '\n%s\n' "$OPEN_IM_GIT_COAUTHOR_LINE" >>"$COMMIT_MSG_FILE"
18
+ exit 0