lark-remote 0.1.2 → 0.1.4

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 (50) hide show
  1. package/CHANGELOG.md +65 -0
  2. package/README.en.md +2 -5
  3. package/README.md +1 -4
  4. package/dist/bridge/index.d.ts +5 -1
  5. package/dist/bridge/index.js +12 -2
  6. package/dist/card/card-budget.js +1 -2
  7. package/dist/card/run-renderer.js +1 -0
  8. package/dist/card/run-state.d.ts +4 -0
  9. package/dist/config/codex-config.d.ts +3 -5
  10. package/dist/config/codex-config.js +7 -7
  11. package/dist/config/dir.d.ts +1 -0
  12. package/dist/config/dir.js +4 -0
  13. package/dist/config/index.d.ts +0 -10
  14. package/dist/config/index.js +0 -11
  15. package/dist/connector/index.js +28 -0
  16. package/dist/index.js +74 -21
  17. package/dist/router/config/codex.js +8 -12
  18. package/dist/router/config/index.d.ts +10 -0
  19. package/dist/router/config/index.js +17 -0
  20. package/dist/router/index.d.ts +40 -1
  21. package/dist/router/index.js +514 -48
  22. package/dist/router/utils.d.ts +3 -0
  23. package/dist/router/utils.js +15 -3
  24. package/dist/runner/claude/runner.d.ts +0 -1
  25. package/dist/runner/claude/runner.js +1 -1
  26. package/dist/runner/codex/runner.d.ts +0 -2
  27. package/dist/runner/codex/runner.js +1 -1
  28. package/dist/runner/common/spawning-runner.d.ts +0 -1
  29. package/dist/runner/common/spawning-runner.js +2 -4
  30. package/dist/runner/kimi/runner.d.ts +0 -1
  31. package/dist/runner/kimi/runner.js +1 -1
  32. package/dist/runner/opencode/runner.d.ts +0 -2
  33. package/dist/runner/opencode/runner.js +1 -1
  34. package/dist/runner/pi/runner.d.ts +0 -1
  35. package/dist/runner/pi/runner.js +1 -1
  36. package/dist/runner/probe.d.ts +45 -0
  37. package/dist/runner/probe.js +87 -0
  38. package/dist/runner/types.d.ts +3 -0
  39. package/dist/session/claude/sessions.d.ts +1 -1
  40. package/dist/session/claude/sessions.js +77 -31
  41. package/dist/session/codex/rollout-reader.js +9 -0
  42. package/dist/session/kimi/sessions.d.ts +3 -1
  43. package/dist/session/kimi/sessions.js +9 -3
  44. package/dist/session/opencode/sessions.d.ts +0 -2
  45. package/dist/session/opencode/sessions.js +1 -1
  46. package/dist/startup-contact.d.ts +4 -2
  47. package/dist/startup-contact.js +7 -4
  48. package/dist/workspace/index.d.ts +19 -1
  49. package/dist/workspace/index.js +51 -5
  50. package/package.json +39 -4
@@ -15,8 +15,6 @@
15
15
  */
16
16
  import type { AgentSession, AgentSessionReader, SessionContent } from '../../runner/index.js';
17
17
  interface OpencodeSessionReaderOptions {
18
- /** Path to opencode binary. Default: 'opencode' (in PATH). */
19
- binary?: string;
20
18
  /** Cache TTL for session list (ms). Default: 10_000. */
21
19
  cacheTtlMs?: number;
22
20
  /**
@@ -28,7 +28,7 @@ export class OpencodeSessionReader {
28
28
  /** Cache keyed by cwd - each cwd has its own cache entry */
29
29
  listCache = new Map();
30
30
  constructor(opts = {}) {
31
- this.binary = opts.binary ?? 'opencode';
31
+ this.binary = 'opencode';
32
32
  this.cacheTtlMs = opts.cacheTtlMs ?? 10_000;
33
33
  this.captureExportOverride = opts.captureExport;
34
34
  }
@@ -13,6 +13,8 @@ export declare class StartupContactStore {
13
13
  save(contact: StartupContact): void;
14
14
  getContact(): StartupContact | undefined;
15
15
  }
16
- export declare function formatStartupHello(now?: Date, pid?: number): string;
17
- export declare function sendStartupHello(connector: StartupHelloChannel, store: StartupContactStore): Promise<void>;
16
+ export declare function formatStartupHello(now?: Date, pid?: number, dev?: boolean): string;
17
+ export declare function sendStartupHello(connector: StartupHelloChannel, store: StartupContactStore, opts?: {
18
+ dev?: boolean;
19
+ }): Promise<void>;
18
20
  export {};
@@ -21,7 +21,7 @@ export class StartupContactStore {
21
21
  return contact.chatId || contact.userId ? contact : undefined;
22
22
  }
23
23
  }
24
- export function formatStartupHello(now = new Date(), pid = process.pid) {
24
+ export function formatStartupHello(now = new Date(), pid = process.pid, dev = false) {
25
25
  const startedAt = new Intl.DateTimeFormat('zh-CN', {
26
26
  year: 'numeric',
27
27
  month: '2-digit',
@@ -31,9 +31,10 @@ export function formatStartupHello(now = new Date(), pid = process.pid) {
31
31
  second: '2-digit',
32
32
  hour12: false,
33
33
  }).format(now);
34
- return `lark-remote 已启动\n启动时间:${startedAt}\n进程号:${pid}`;
34
+ const modeTag = dev ? ' 🔧 dev' : '';
35
+ return `lark-remote${modeTag} 已启动\n启动时间:${startedAt}\n进程号:${pid}`;
35
36
  }
36
- export async function sendStartupHello(connector, store) {
37
+ export async function sendStartupHello(connector, store, opts) {
37
38
  const contact = store.getContact();
38
39
  const recipient = contact?.chatId ?? contact?.userId;
39
40
  if (!recipient) {
@@ -41,7 +42,9 @@ export async function sendStartupHello(connector, store) {
41
42
  return;
42
43
  }
43
44
  try {
44
- await connector.sendWithRetry(recipient, { text: formatStartupHello() });
45
+ await connector.sendWithRetry(recipient, {
46
+ text: formatStartupHello(undefined, undefined, opts?.dev),
47
+ });
45
48
  getLogger().info('[startup] hello message sent');
46
49
  }
47
50
  catch (err) {
@@ -1,9 +1,18 @@
1
+ /** Public view of a workspace entry returned by list(). */
2
+ export interface WorkspaceEntry {
3
+ name: string;
4
+ path: string;
5
+ lastUsedAt: number;
6
+ }
1
7
  /**
2
8
  * Persistent named-alias store for workspace directories.
3
9
  *
4
10
  * Atomic write (§9.10): writes a temp file then `fs.rename` so a crash
5
11
  * mid-write cannot leave a truncated JSON file. On load, a corrupt file
6
12
  * is treated as empty (with a warning) so the bridge can still start.
13
+ *
14
+ * Data migration: old format `{ alias: "path" }` is normalized to
15
+ * `{ alias: { path, lastUsedAt: 0 } }` on first load and persisted.
7
16
  */
8
17
  export declare class WorkspaceStore {
9
18
  private filePath;
@@ -15,5 +24,14 @@ export declare class WorkspaceStore {
15
24
  get(name: string): string | undefined;
16
25
  has(name: string): boolean;
17
26
  remove(name: string): void;
18
- list(): [string, string][];
27
+ /**
28
+ * Update lastUsedAt to now for the given alias and persist.
29
+ * No-op if the alias does not exist.
30
+ */
31
+ touch(name: string): void;
32
+ /**
33
+ * List all workspace entries in insertion order.
34
+ * Sorting is the caller's responsibility (view concern).
35
+ */
36
+ list(): WorkspaceEntry[];
19
37
  }
@@ -2,12 +2,16 @@ import path from 'node:path';
2
2
  import os from 'node:os';
3
3
  import { atomicWriteJson } from '../persistence/atomic-write.js';
4
4
  import { loadJsonFile } from '../persistence/load-json-file.js';
5
+ import { getLogger } from '../logger/index.js';
5
6
  /**
6
7
  * Persistent named-alias store for workspace directories.
7
8
  *
8
9
  * Atomic write (§9.10): writes a temp file then `fs.rename` so a crash
9
10
  * mid-write cannot leave a truncated JSON file. On load, a corrupt file
10
11
  * is treated as empty (with a warning) so the bridge can still start.
12
+ *
13
+ * Data migration: old format `{ alias: "path" }` is normalized to
14
+ * `{ alias: { path, lastUsedAt: 0 } }` on first load and persisted.
11
15
  */
12
16
  export class WorkspaceStore {
13
17
  filePath;
@@ -21,9 +25,32 @@ export class WorkspaceStore {
21
25
  const parsed = loadJsonFile(this.filePath, undefined);
22
26
  if (!parsed)
23
27
  return;
28
+ let needsMigration = false;
24
29
  for (const [k, v] of Object.entries(parsed)) {
25
- if (typeof v === 'string')
26
- this.data.set(k, v);
30
+ if (typeof v === 'string') {
31
+ // Old format: string value → { path, lastUsedAt: 0 }
32
+ this.data.set(k, { path: v, lastUsedAt: 0 });
33
+ needsMigration = true;
34
+ }
35
+ else if (typeof v === 'object' &&
36
+ v !== null &&
37
+ typeof v.path === 'string') {
38
+ // New format: extract path and lastUsedAt (default 0 if missing)
39
+ const obj = v;
40
+ const lastUsedAt = typeof obj.lastUsedAt === 'number' ? obj.lastUsedAt : 0;
41
+ if (typeof obj.lastUsedAt !== 'number') {
42
+ if (obj.lastUsedAt !== undefined) {
43
+ getLogger().warn(`[workspace] "${k}" has invalid lastUsedAt (${JSON.stringify(obj.lastUsedAt)}), resetting to 0`);
44
+ }
45
+ needsMigration = true;
46
+ }
47
+ this.data.set(k, { path: obj.path, lastUsedAt });
48
+ }
49
+ // Non-string, non-object entries are silently skipped (existing behavior)
50
+ }
51
+ // Write back immediately if migration occurred
52
+ if (needsMigration) {
53
+ this.persist();
27
54
  }
28
55
  }
29
56
  persist() {
@@ -33,11 +60,11 @@ export class WorkspaceStore {
33
60
  atomicWriteJson(this.filePath, obj);
34
61
  }
35
62
  save(name, dirPath) {
36
- this.data.set(name, dirPath);
63
+ this.data.set(name, { path: dirPath, lastUsedAt: 0 });
37
64
  this.persist();
38
65
  }
39
66
  get(name) {
40
- return this.data.get(name);
67
+ return this.data.get(name)?.path;
41
68
  }
42
69
  has(name) {
43
70
  return this.data.has(name);
@@ -46,8 +73,27 @@ export class WorkspaceStore {
46
73
  this.data.delete(name);
47
74
  this.persist();
48
75
  }
76
+ /**
77
+ * Update lastUsedAt to now for the given alias and persist.
78
+ * No-op if the alias does not exist.
79
+ */
80
+ touch(name) {
81
+ const entry = this.data.get(name);
82
+ if (!entry)
83
+ return;
84
+ entry.lastUsedAt = Date.now();
85
+ this.persist();
86
+ }
87
+ /**
88
+ * List all workspace entries in insertion order.
89
+ * Sorting is the caller's responsibility (view concern).
90
+ */
49
91
  list() {
50
- return [...this.data.entries()];
92
+ return [...this.data.entries()].map(([name, data]) => ({
93
+ name,
94
+ path: data.path,
95
+ lastUsedAt: data.lastUsedAt,
96
+ }));
51
97
  }
52
98
  }
53
99
  //# sourceMappingURL=index.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lark-remote",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "Feishu (Lark) private-chat bridge to local coding agents — Claude Code / Codex / opencode / pi / kimi, with live-streaming CardKit 2.0 cards",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -18,13 +18,13 @@
18
18
  "!dist/**/*.js.map",
19
19
  "!dist/**/*.d.ts.map",
20
20
  "!dist/config/codex-bundled-test-helpers.*",
21
- "!dist/test-helpers.*",
22
21
  "README.md",
23
22
  "README.en.md",
24
23
  "LICENSE",
25
24
  "CHANGELOG.md"
26
25
  ],
27
26
  "scripts": {
27
+ "prepare": "husky",
28
28
  "dev": "bun src/index.ts",
29
29
  "build": "rm -rf dist && tsc && chmod +x dist/index.js dist/cli.js",
30
30
  "typecheck": "tsc --noEmit",
@@ -34,12 +34,35 @@
34
34
  "format:check": "prettier --check 'src/**/*.ts' 'tests/**/*.ts'",
35
35
  "test": "vitest run",
36
36
  "test:watch": "vitest",
37
+ "test:bridge": "vitest run --project infra-bridge",
38
+ "test:card": "vitest run --project infra-card",
39
+ "test:connector": "vitest run --project infra-connector",
40
+ "test:router": "vitest run --project infra-router",
41
+ "test:session": "vitest run --project infra-session",
42
+ "test:runner": "vitest run --project infra-runner",
43
+ "test:config": "vitest run --project infra-config",
44
+ "test:order": "vitest run --project infra-order",
45
+ "test:misc": "vitest run --project infra-misc",
46
+ "test:claude": "vitest run --project agent-claude",
47
+ "test:codex": "vitest run --project agent-codex",
48
+ "test:opencode": "vitest run --project agent-opencode",
49
+ "test:pi": "vitest run --project agent-pi",
50
+ "test:kimi": "vitest run --project agent-kimi",
51
+ "test:bash": "vitest run --project agent-bash",
52
+ "test:infra": "vitest run --project 'infra-*'",
53
+ "test:agents": "vitest run --project 'agent-*'",
54
+ "test:codex-full": "vitest run --project agent-codex --project infra-runner --project infra-session",
55
+ "test:claude-full": "vitest run --project agent-claude --project infra-runner --project infra-session",
56
+ "test:opencode-full": "vitest run --project agent-opencode --project infra-runner --project infra-session",
57
+ "test:pi-full": "vitest run --project agent-pi --project infra-runner --project infra-session",
58
+ "test:kimi-full": "vitest run --project agent-kimi --project infra-runner --project infra-session",
59
+ "test:live": "vitest run --project live",
37
60
  "prepublishOnly": "tsc --noEmit && vitest run && rm -rf dist && tsc && chmod +x dist/index.js dist/cli.js",
38
61
  "prepack": "node -e \"const fs=require('fs');if(!fs.existsSync('dist/cli.js')){console.error('dist/ missing — run bun run build first');process.exit(1)}\""
39
62
  },
40
63
  "publishConfig": {
41
64
  "access": "public",
42
- "provenance": false
65
+ "provenance": true
43
66
  },
44
67
  "keywords": [
45
68
  "feishu",
@@ -85,9 +108,21 @@
85
108
  "eslint": "^10.7.0",
86
109
  "eslint-config-prettier": "^10.1.8",
87
110
  "eslint-plugin-prettier": "^5.5.6",
111
+ "husky": "^9.1.7",
112
+ "lint-staged": "^17.3.0",
88
113
  "prettier": "^3.9.5",
89
114
  "typescript": "^6.0.0",
90
115
  "typescript-eslint": "^8.63.0",
91
- "vitest": "^4.1.9"
116
+ "vitest": "^4.1.10"
117
+ },
118
+ "lint-staged": {
119
+ "src/**/*.ts": [
120
+ "eslint --fix",
121
+ "prettier --write"
122
+ ],
123
+ "tests/**/*.ts": [
124
+ "eslint --fix",
125
+ "prettier --write"
126
+ ]
92
127
  }
93
128
  }