lark-remote 0.1.6 → 0.1.7

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 (188) hide show
  1. package/CHANGELOG.md +23 -1
  2. package/README.en.md +9 -1
  3. package/README.md +14 -2
  4. package/dist/bridge/approval-coordinator.d.ts +57 -34
  5. package/dist/bridge/approval-coordinator.js +191 -40
  6. package/dist/bridge/inbound-media.d.ts +62 -0
  7. package/dist/bridge/inbound-media.js +282 -0
  8. package/dist/bridge/index.d.ts +78 -7
  9. package/dist/bridge/index.js +202 -79
  10. package/dist/bridge/queue-manager.d.ts +2 -1
  11. package/dist/bridge/queue-manager.js +1 -0
  12. package/dist/card/approval-render.js +152 -36
  13. package/dist/card/bash-card-session.d.ts +2 -24
  14. package/dist/card/bash-card-session.js +6 -73
  15. package/dist/card/bash-renderer.js +2 -3
  16. package/dist/card/card-budget.js +29 -75
  17. package/dist/card/card-session.d.ts +48 -0
  18. package/dist/card/card-session.js +86 -0
  19. package/dist/card/card-shared.d.ts +1 -12
  20. package/dist/card/card-shared.js +1 -26
  21. package/dist/card/run-card-session.d.ts +2 -55
  22. package/dist/card/run-card-session.js +6 -104
  23. package/dist/card/run-renderer.js +99 -232
  24. package/dist/card/run-state.d.ts +13 -0
  25. package/dist/card/run-state.js +25 -16
  26. package/dist/card/text-truncate.d.ts +13 -12
  27. package/dist/card/text-truncate.js +13 -63
  28. package/dist/common/fs.d.ts +8 -0
  29. package/dist/common/fs.js +17 -0
  30. package/dist/common/guards.d.ts +0 -20
  31. package/dist/common/guards.js +0 -43
  32. package/dist/common/sleep.d.ts +2 -0
  33. package/dist/common/sleep.js +5 -0
  34. package/dist/common/truncate.d.ts +39 -0
  35. package/dist/common/truncate.js +92 -0
  36. package/dist/config/codex-config.d.ts +1 -1
  37. package/dist/config/codex-config.js +1 -1
  38. package/dist/config/index.d.ts +88 -23
  39. package/dist/config/index.js +105 -34
  40. package/dist/connector/index.d.ts +69 -2
  41. package/dist/connector/index.js +107 -1
  42. package/dist/index.js +123 -105
  43. package/dist/instance-lock.js +4 -12
  44. package/dist/order/alias-resolve.d.ts +13 -0
  45. package/dist/order/alias-resolve.js +22 -0
  46. package/dist/order/alias-store.d.ts +38 -0
  47. package/dist/order/alias-store.js +106 -0
  48. package/dist/persistence/atomic-write.d.ts +8 -2
  49. package/dist/persistence/atomic-write.js +23 -7
  50. package/dist/restart.js +2 -1
  51. package/dist/router/card-helpers.d.ts +63 -0
  52. package/dist/router/card-helpers.js +146 -0
  53. package/dist/router/config/claude.js +25 -1
  54. package/dist/router/config/codex.js +29 -50
  55. package/dist/router/config/common/model-patch.d.ts +22 -0
  56. package/dist/router/config/common/model-patch.js +26 -0
  57. package/dist/router/config/common/render.js +5 -4
  58. package/dist/router/config/index.d.ts +12 -0
  59. package/dist/router/config/index.js +25 -1
  60. package/dist/router/config/kimi.d.ts +4 -1
  61. package/dist/router/config/kimi.js +27 -0
  62. package/dist/router/config/opencode.js +18 -5
  63. package/dist/router/config/pi.js +4 -5
  64. package/dist/router/config/types.d.ts +11 -2
  65. package/dist/router/index.d.ts +56 -21
  66. package/dist/router/index.js +373 -548
  67. package/dist/router/utils.d.ts +2 -0
  68. package/dist/router/utils.js +4 -0
  69. package/dist/runner/agent-choices-common.d.ts +16 -0
  70. package/dist/runner/agent-choices-common.js +31 -0
  71. package/dist/runner/claude/runner.d.ts +47 -10
  72. package/dist/runner/claude/runner.js +117 -46
  73. package/dist/runner/claude/session.d.ts +166 -0
  74. package/dist/runner/claude/session.js +749 -0
  75. package/dist/runner/codex/app-server/protocol-types.d.ts +31 -39
  76. package/dist/runner/codex/app-server/protocol-types.js +1 -12
  77. package/dist/runner/codex/app-server/runner.d.ts +42 -59
  78. package/dist/runner/codex/app-server/runner.js +160 -337
  79. package/dist/runner/codex/app-server/translator.d.ts +1 -21
  80. package/dist/runner/codex/app-server/translator.js +0 -18
  81. package/dist/runner/codex/index.d.ts +0 -2
  82. package/dist/runner/codex/index.js +0 -2
  83. package/dist/runner/common/acp/protocol-helpers.d.ts +14 -0
  84. package/dist/runner/common/acp/protocol-helpers.js +21 -0
  85. package/dist/runner/common/acp/protocol-types.d.ts +238 -0
  86. package/dist/runner/common/acp/protocol-types.js +75 -0
  87. package/dist/runner/common/connection-based-runner.d.ts +111 -0
  88. package/dist/runner/common/connection-based-runner.js +261 -0
  89. package/dist/runner/{codex/app-server → common/jsonrpc}/client.d.ts +14 -7
  90. package/dist/runner/{codex/app-server → common/jsonrpc}/client.js +27 -18
  91. package/dist/runner/common/jsonrpc/connection-manager.d.ts +125 -0
  92. package/dist/runner/{codex/app-server → common/jsonrpc}/connection-manager.js +63 -28
  93. package/dist/runner/{codex/app-server → common/jsonrpc}/transport.d.ts +5 -4
  94. package/dist/runner/{codex/app-server → common/jsonrpc}/transport.js +18 -19
  95. package/dist/runner/common/jsonrpc/types.d.ts +32 -0
  96. package/dist/runner/common/jsonrpc/types.js +9 -0
  97. package/dist/runner/common/process-stopper.d.ts +2 -1
  98. package/dist/runner/common/process-stopper.js +1 -0
  99. package/dist/runner/common/runner-utils.d.ts +6 -13
  100. package/dist/runner/common/runner-utils.js +6 -47
  101. package/dist/runner/common/spawning-runner.d.ts +24 -70
  102. package/dist/runner/common/spawning-runner.js +98 -152
  103. package/dist/runner/index.d.ts +4 -4
  104. package/dist/runner/index.js +4 -4
  105. package/dist/runner/kimi/acp/runner.d.ts +106 -0
  106. package/dist/runner/kimi/acp/runner.js +448 -0
  107. package/dist/runner/kimi/acp/translator.d.ts +124 -0
  108. package/dist/runner/kimi/acp/translator.js +336 -0
  109. package/dist/runner/kimi/index.d.ts +1 -1
  110. package/dist/runner/kimi/index.js +1 -1
  111. package/dist/runner/opencode/acp/index.d.ts +1 -0
  112. package/dist/runner/opencode/acp/index.js +2 -0
  113. package/dist/runner/opencode/acp/runner.d.ts +121 -0
  114. package/dist/runner/opencode/acp/runner.js +429 -0
  115. package/dist/runner/opencode/acp/translator.d.ts +119 -0
  116. package/dist/runner/opencode/acp/translator.js +345 -0
  117. package/dist/runner/opencode/index.d.ts +1 -1
  118. package/dist/runner/opencode/index.js +1 -1
  119. package/dist/runner/pi/index.d.ts +1 -1
  120. package/dist/runner/pi/index.js +1 -1
  121. package/dist/runner/pi/rpc/client.d.ts +52 -0
  122. package/dist/runner/pi/rpc/client.js +114 -0
  123. package/dist/runner/pi/rpc/index.d.ts +4 -0
  124. package/dist/runner/pi/rpc/index.js +4 -0
  125. package/dist/runner/pi/rpc/protocol-types.d.ts +127 -0
  126. package/dist/runner/pi/rpc/protocol-types.js +21 -0
  127. package/dist/runner/pi/rpc/runner.d.ts +80 -0
  128. package/dist/runner/pi/rpc/runner.js +199 -0
  129. package/dist/runner/pi/rpc/translator.d.ts +55 -0
  130. package/dist/runner/pi/rpc/translator.js +258 -0
  131. package/dist/runner/registry.d.ts +7 -8
  132. package/dist/runner/registry.js +3 -4
  133. package/dist/runner/resolve-agent-choices.js +18 -59
  134. package/dist/runner/sync-agent-choices.js +16 -67
  135. package/dist/runner/types.d.ts +43 -13
  136. package/dist/session/claude/session-index.js +2 -3
  137. package/dist/session/claude/session-reader.js +3 -6
  138. package/dist/session/claude/sessions.js +6 -13
  139. package/dist/session/codex/rollout-reader.d.ts +2 -2
  140. package/dist/session/codex/rollout-reader.js +14 -17
  141. package/dist/session/codex/sessions.d.ts +1 -1
  142. package/dist/session/codex/sessions.js +2 -2
  143. package/dist/session/common/content-blocks.d.ts +7 -0
  144. package/dist/session/common/content-blocks.js +13 -9
  145. package/dist/session/common/jsonl.d.ts +8 -25
  146. package/dist/session/common/jsonl.js +126 -261
  147. package/dist/session/common/pagination.d.ts +28 -0
  148. package/dist/session/common/pagination.js +33 -0
  149. package/dist/session/kimi/sessions.d.ts +34 -1
  150. package/dist/session/kimi/sessions.js +208 -98
  151. package/dist/session/opencode/sessions.d.ts +17 -3
  152. package/dist/session/opencode/sessions.js +29 -29
  153. package/dist/session/pi/sessions.d.ts +5 -1
  154. package/dist/session/pi/sessions.js +12 -16
  155. package/dist/session/session-store.d.ts +11 -1
  156. package/dist/session/session-store.js +75 -178
  157. package/dist/update/index.d.ts +2 -2
  158. package/dist/update/index.js +2 -2
  159. package/dist/update/install.d.ts +20 -2
  160. package/dist/update/install.js +51 -7
  161. package/package.json +1 -1
  162. package/dist/runner/codex/app-server/connection-manager.d.ts +0 -56
  163. package/dist/runner/codex/argv.d.ts +0 -33
  164. package/dist/runner/codex/argv.js +0 -40
  165. package/dist/runner/codex/factory.d.ts +0 -29
  166. package/dist/runner/codex/factory.js +0 -39
  167. package/dist/runner/codex/jsonl.d.ts +0 -37
  168. package/dist/runner/codex/jsonl.js +0 -221
  169. package/dist/runner/codex/runner.d.ts +0 -66
  170. package/dist/runner/codex/runner.js +0 -129
  171. package/dist/runner/common/exec-translator.d.ts +0 -73
  172. package/dist/runner/common/exec-translator.js +0 -105
  173. package/dist/runner/kimi/jsonl.d.ts +0 -28
  174. package/dist/runner/kimi/jsonl.js +0 -143
  175. package/dist/runner/kimi/runner.d.ts +0 -49
  176. package/dist/runner/kimi/runner.js +0 -105
  177. package/dist/runner/opencode/argv.d.ts +0 -32
  178. package/dist/runner/opencode/argv.js +0 -35
  179. package/dist/runner/opencode/jsonl.d.ts +0 -68
  180. package/dist/runner/opencode/jsonl.js +0 -295
  181. package/dist/runner/opencode/runner.d.ts +0 -61
  182. package/dist/runner/opencode/runner.js +0 -93
  183. package/dist/runner/pi/jsonl.d.ts +0 -128
  184. package/dist/runner/pi/jsonl.js +0 -249
  185. package/dist/runner/pi/runner.d.ts +0 -36
  186. package/dist/runner/pi/runner.js +0 -76
  187. package/dist/session/common/catch-up.d.ts +0 -12
  188. package/dist/session/common/catch-up.js +0 -31
@@ -4,8 +4,8 @@ import os from 'node:os';
4
4
  import { findJsonlLine, scanJsonlLines, readJsonlLinesFromOffset } from '../common/jsonl.js';
5
5
  import { extractContentBlocks } from '../common/content-blocks.js';
6
6
  import { UsageAccumulator } from '../common/usage-accumulator.js';
7
- import { truncate } from '../../card/card-shared.js';
8
7
  import { STALE_MS } from '../common/constants.js';
8
+ import { capEvents, paginate } from '../common/pagination.js';
9
9
  function defaultPiDir() {
10
10
  return path.join(os.homedir(), '.pi', 'agent');
11
11
  }
@@ -24,7 +24,7 @@ const PI_LIST_TTL_MS = 5000;
24
24
  const PI_LIST_MAX_ENTRIES = 32;
25
25
  const piListCache = new Map();
26
26
  /** Pi-specific field-name mapping for content block extraction. */
27
- const PI_MAPPING = {
27
+ export const PI_MAPPING = {
28
28
  toolUseType: 'toolCall',
29
29
  toolResultType: 'toolResult',
30
30
  toolInputField: 'arguments',
@@ -187,9 +187,10 @@ function piScalarScan(filePath) {
187
187
  return {
188
188
  tailOffset,
189
189
  usage: buildPiUsage(acc),
190
- displayTitle: lastUserText
191
- ? truncate(lastUserText, 200, { normalizeWhitespace: true })
192
- : undefined,
190
+ // Whitespace-normalized single-line display title (no length truncation —
191
+ // the consumer truncates). Normalization collapses the skill-injection
192
+ // newlines so the title stays on one line.
193
+ displayTitle: lastUserText ? lastUserText.replace(/\s+/g, ' ').trim() : undefined,
193
194
  };
194
195
  }
195
196
  /**
@@ -273,19 +274,19 @@ function extractPiEventsFromTail(tailLines) {
273
274
  */
274
275
  export class PiSessionReader {
275
276
  piDir;
277
+ /** 会话目录(public:测试可重定向到 fixture 目录,替代 as any)。 */
276
278
  sessionsDir;
277
279
  constructor(opts = {}) {
278
280
  this.piDir = opts.piDir ?? defaultPiDir();
279
281
  this.sessionsDir = path.join(this.piDir, 'sessions');
280
282
  }
281
283
  listSessions(cwd, opts) {
282
- const limit = opts?.limit ?? 20;
283
- const offset = Math.max(0, opts?.offset ?? 0);
284
284
  // SCAN: filesystem is the source of truth for pi sessions.
285
285
  const scanned = this.listSessionsByScan(cwd);
286
+ const { items, total } = paginate(scanned, opts ?? {});
286
287
  return {
287
- sessions: scanned.slice(offset, offset + limit),
288
- total: scanned.length,
288
+ sessions: items,
289
+ total,
289
290
  };
290
291
  }
291
292
  getNewestSession(cwd) {
@@ -322,12 +323,7 @@ export class PiSessionReader {
322
323
  // Apply maxEvents cap: keep the LAST N events (most recent), matching
323
324
  // CodexSessionReader's slice(-maxEvents). maxEvents limits the catch-up
324
325
  // tail so auto-resume cards don't load the entire session.
325
- // P2-7: maxEvents <= 0 returns [] (guards `slice(-0)` full-array trap).
326
- const cappedEvents = opts?.maxEvents !== undefined
327
- ? opts.maxEvents <= 0
328
- ? []
329
- : events.slice(-opts.maxEvents)
330
- : events;
326
+ const cappedEvents = capEvents(events, opts?.maxEvents);
331
327
  return {
332
328
  events: cappedEvents,
333
329
  usage: scan.usage,
@@ -376,7 +372,7 @@ export class PiSessionReader {
376
372
  const sessionId = stem.includes('_') ? stem.slice(stem.lastIndexOf('_') + 1) : stem;
377
373
  sessions.push({
378
374
  sessionId,
379
- summary: truncate(summarizePiSession(full), 60, { normalizeWhitespace: true }),
375
+ summary: summarizePiSession(full),
380
376
  mtime: st.mtimeMs,
381
377
  });
382
378
  }
@@ -48,6 +48,16 @@ export declare class SessionStore {
48
48
  /** 用户意图移动了全部 agent 的 session 指针(/cd、/ws use 的 setCwd)。 */
49
49
  private bumpUserEpoch;
50
50
  constructor(persistPath?: string, defaultAgent?: AgentKind);
51
+ /** A fresh, empty session entry (default factory). */
52
+ private static emptyEntry;
53
+ /**
54
+ * Clone the user's entry, apply `mutate` to it, and store the result.
55
+ * Creates a fresh empty entry when the user has none. All the per-field
56
+ * setters (clearSessionId / setPreviousSessionId / setArrivalSessionId /
57
+ * setCwd / setSessionId / setSessionIdAndCwd / setSessionIdAndSessionCwd)
58
+ * use this to avoid each re-spelling the "clone entry, tweak one Map" dance.
59
+ */
60
+ private mutateEntry;
51
61
  get(userId: string): SessionEntry | undefined;
52
62
  set(userId: string, entry: SessionEntry): void;
53
63
  delete(userId: string): void;
@@ -79,7 +89,7 @@ export declare class SessionStore {
79
89
  getSessionCwd(userId: string, agent?: AgentKind): string | undefined;
80
90
  /** Set sessionId and sessionCwd for a specific agent, without touching cwd */
81
91
  setSessionIdAndSessionCwd(userId: string, agent: AgentKind, sessionId: string, sessionCwd: string): void;
82
- /** Persisted format: only cwd per user (sessionId is process-scoped) */
92
+ /** Persisted format: cwd + sessions + previous/arrival session maps. */
83
93
  private load;
84
94
  private autoPersist;
85
95
  }
@@ -53,6 +53,37 @@ export class SessionStore {
53
53
  if (persistPath)
54
54
  this.load();
55
55
  }
56
+ /** A fresh, empty session entry (default factory). */
57
+ static emptyEntry(cwd = '') {
58
+ return {
59
+ sessions: new Map(),
60
+ previousSessions: new Map(),
61
+ sessionCwds: new Map(),
62
+ arrivalSessions: new Map(),
63
+ cwd,
64
+ };
65
+ }
66
+ /**
67
+ * Clone the user's entry, apply `mutate` to it, and store the result.
68
+ * Creates a fresh empty entry when the user has none. All the per-field
69
+ * setters (clearSessionId / setPreviousSessionId / setArrivalSessionId /
70
+ * setCwd / setSessionId / setSessionIdAndCwd / setSessionIdAndSessionCwd)
71
+ * use this to avoid each re-spelling the "clone entry, tweak one Map" dance.
72
+ */
73
+ mutateEntry(userId, mutate) {
74
+ const current = this.sessions.get(userId);
75
+ const base = current
76
+ ? {
77
+ sessions: new Map(current.sessions),
78
+ previousSessions: new Map(current.previousSessions),
79
+ sessionCwds: new Map(current.sessionCwds),
80
+ arrivalSessions: new Map(current.arrivalSessions),
81
+ cwd: current.cwd,
82
+ }
83
+ : SessionStore.emptyEntry();
84
+ this.sessions.set(userId, mutate(base));
85
+ this.autoPersist();
86
+ }
56
87
  get(userId) {
57
88
  return this.sessions.get(userId);
58
89
  }
@@ -69,23 +100,13 @@ export class SessionStore {
69
100
  // 无条件 bump:空→空也必须可检测(事故场景正是 run 以 (none) 启动,
70
101
  // /new 后 store 仍为空,值比较会误判「没变」)。entry 不存在同样 bump。
71
102
  this.bumpSessionEpoch(userId, agent);
72
- const entry = this.sessions.get(userId);
73
- if (entry) {
74
- const newSessions = new Map(entry.sessions);
75
- newSessions.set(agent, '');
76
- const newSessionCwds = new Map(entry.sessionCwds);
103
+ this.mutateEntry(userId, (entry) => {
104
+ entry.sessions.set(agent, '');
77
105
  if (opts?.clearSessionCwd) {
78
- newSessionCwds.set(agent, '');
106
+ entry.sessionCwds.set(agent, '');
79
107
  }
80
- this.sessions.set(userId, {
81
- sessions: newSessions,
82
- previousSessions: entry.previousSessions,
83
- sessionCwds: newSessionCwds,
84
- arrivalSessions: entry.arrivalSessions,
85
- cwd: entry.cwd,
86
- });
87
- }
88
- this.autoPersist();
108
+ return entry;
109
+ });
89
110
  }
90
111
  /** Get previous sessionId for a specific agent (stored when switching away) */
91
112
  getPreviousSessionId(userId, agent) {
@@ -99,46 +120,17 @@ export class SessionStore {
99
120
  }
100
121
  /** Set previous sessionId for a specific agent (for potential restore when switching back) */
101
122
  setPreviousSessionId(userId, agent, sessionId) {
102
- const entry = this.sessions.get(userId);
103
- if (entry) {
104
- const newPrevious = new Map(entry.previousSessions);
105
- newPrevious.set(agent, sessionId);
106
- this.sessions.set(userId, {
107
- sessions: entry.sessions,
108
- previousSessions: newPrevious,
109
- sessionCwds: entry.sessionCwds,
110
- arrivalSessions: entry.arrivalSessions,
111
- cwd: entry.cwd,
112
- });
113
- }
114
- else {
115
- const newPrevious = new Map();
116
- newPrevious.set(agent, sessionId);
117
- this.sessions.set(userId, {
118
- sessions: new Map(),
119
- previousSessions: newPrevious,
120
- sessionCwds: new Map(),
121
- arrivalSessions: new Map(),
122
- cwd: '',
123
- });
124
- }
125
- this.autoPersist();
123
+ this.mutateEntry(userId, (entry) => {
124
+ entry.previousSessions.set(agent, sessionId);
125
+ return entry;
126
+ });
126
127
  }
127
128
  /** Clear previous sessionId for a specific agent */
128
129
  clearPreviousSessionId(userId, agent) {
129
- const entry = this.sessions.get(userId);
130
- if (entry) {
131
- const newPrevious = new Map(entry.previousSessions);
132
- newPrevious.delete(agent);
133
- this.sessions.set(userId, {
134
- sessions: entry.sessions,
135
- previousSessions: newPrevious,
136
- sessionCwds: entry.sessionCwds,
137
- arrivalSessions: entry.arrivalSessions,
138
- cwd: entry.cwd,
139
- });
140
- }
141
- this.autoPersist();
130
+ this.mutateEntry(userId, (entry) => {
131
+ entry.previousSessions.delete(agent);
132
+ return entry;
133
+ });
142
134
  }
143
135
  /** Get the arrival baseline sessionId for a specific agent ('' and missing → undefined) */
144
136
  getArrivalSessionId(userId, agent) {
@@ -152,60 +144,26 @@ export class SessionStore {
152
144
  }
153
145
  /** Set the arrival baseline for a specific agent ('' explicitly clears the arrival baseline) */
154
146
  setArrivalSessionId(userId, agent, sessionId) {
155
- const entry = this.sessions.get(userId);
156
- if (entry) {
157
- const newArrival = new Map(entry.arrivalSessions);
158
- newArrival.set(agent, sessionId);
159
- this.sessions.set(userId, {
160
- sessions: entry.sessions,
161
- previousSessions: entry.previousSessions,
162
- sessionCwds: entry.sessionCwds,
163
- arrivalSessions: newArrival,
164
- cwd: entry.cwd,
165
- });
166
- }
167
- else {
168
- const newArrival = new Map();
169
- newArrival.set(agent, sessionId);
170
- this.sessions.set(userId, {
171
- sessions: new Map(),
172
- previousSessions: new Map(),
173
- sessionCwds: new Map(),
174
- arrivalSessions: newArrival,
175
- cwd: '',
176
- });
177
- }
178
- this.autoPersist();
147
+ this.mutateEntry(userId, (entry) => {
148
+ entry.arrivalSessions.set(agent, sessionId);
149
+ return entry;
150
+ });
179
151
  }
180
152
  /** Set cwd for a user, clearing all sessionIds (§9.1) */
181
153
  setCwd(userId, cwd) {
182
154
  // §9.1 setCwd 清全部 agent 的 sessionId → user 级 bump,覆盖 sessions
183
155
  // map 里还没有 key 的 agent(run 刚启动、首个 init 未写回时 /cd 的边界)。
184
156
  this.bumpUserEpoch(userId);
185
- const entry = this.sessions.get(userId);
186
- if (entry) {
187
- const newSessions = new Map(entry.sessions);
188
- for (const key of newSessions.keys()) {
189
- newSessions.set(key, '');
157
+ this.mutateEntry(userId, (entry) => {
158
+ for (const key of entry.sessions.keys()) {
159
+ entry.sessions.set(key, '');
190
160
  }
191
- this.sessions.set(userId, {
192
- sessions: newSessions,
193
- previousSessions: new Map(),
194
- sessionCwds: new Map(),
195
- arrivalSessions: new Map(),
196
- cwd,
197
- });
198
- }
199
- else {
200
- this.sessions.set(userId, {
201
- sessions: new Map(),
202
- previousSessions: new Map(),
203
- sessionCwds: new Map(),
204
- arrivalSessions: new Map(),
205
- cwd,
206
- });
207
- }
208
- this.autoPersist();
161
+ entry.previousSessions = new Map();
162
+ entry.sessionCwds = new Map();
163
+ entry.arrivalSessions = new Map();
164
+ entry.cwd = cwd;
165
+ return entry;
166
+ });
209
167
  }
210
168
  /** Get cwd for a user, or undefined if no session */
211
169
  getCwd(userId) {
@@ -224,61 +182,23 @@ export class SessionStore {
224
182
  /** Set sessionId for a specific agent, optionally with cwd */
225
183
  setSessionId(userId, agent, sessionId, cwd) {
226
184
  this.bumpSessionEpoch(userId, agent);
227
- const entry = this.sessions.get(userId);
228
- if (entry) {
229
- const newSessions = new Map(entry.sessions);
230
- newSessions.set(agent, sessionId);
231
- this.sessions.set(userId, {
232
- sessions: newSessions,
233
- previousSessions: entry.previousSessions,
234
- sessionCwds: entry.sessionCwds,
235
- arrivalSessions: entry.arrivalSessions,
236
- cwd: cwd ?? entry.cwd,
237
- });
238
- }
239
- else {
240
- const newSessions = new Map();
241
- newSessions.set(agent, sessionId);
242
- this.sessions.set(userId, {
243
- sessions: newSessions,
244
- previousSessions: new Map(),
245
- sessionCwds: new Map(),
246
- arrivalSessions: new Map(),
247
- cwd: cwd ?? '',
248
- });
249
- }
250
- this.autoPersist();
185
+ this.mutateEntry(userId, (entry) => {
186
+ entry.sessions.set(agent, sessionId);
187
+ if (cwd !== undefined)
188
+ entry.cwd = cwd;
189
+ return entry;
190
+ });
251
191
  }
252
192
  /** Set sessionId for a specific agent and cwd together */
253
193
  setSessionIdAndCwd(userId, agent, sessionId, cwd, sessionCwd) {
254
- const entry = this.sessions.get(userId);
255
- if (entry) {
256
- const newSessions = new Map(entry.sessions);
257
- newSessions.set(agent, sessionId);
258
- const newSessionCwds = new Map(entry.sessionCwds);
194
+ this.mutateEntry(userId, (entry) => {
195
+ entry.sessions.set(agent, sessionId);
259
196
  if (sessionCwd !== undefined) {
260
- newSessionCwds.set(agent, sessionCwd);
197
+ entry.sessionCwds.set(agent, sessionCwd);
261
198
  }
262
- this.sessions.set(userId, {
263
- sessions: newSessions,
264
- previousSessions: entry.previousSessions,
265
- sessionCwds: newSessionCwds,
266
- arrivalSessions: entry.arrivalSessions,
267
- cwd,
268
- });
269
- }
270
- else {
271
- const newSessions = new Map();
272
- newSessions.set(agent, sessionId);
273
- this.sessions.set(userId, {
274
- sessions: newSessions,
275
- previousSessions: new Map(),
276
- sessionCwds: new Map(),
277
- arrivalSessions: new Map(),
278
- cwd,
279
- });
280
- }
281
- this.autoPersist();
199
+ entry.cwd = cwd;
200
+ return entry;
201
+ });
282
202
  }
283
203
  /** Get sessionCwd for a specific agent, or undefined if not set */
284
204
  getSessionCwd(userId, agent = 'claude') {
@@ -292,37 +212,14 @@ export class SessionStore {
292
212
  }
293
213
  /** Set sessionId and sessionCwd for a specific agent, without touching cwd */
294
214
  setSessionIdAndSessionCwd(userId, agent, sessionId, sessionCwd) {
295
- const entry = this.sessions.get(userId);
296
- if (entry) {
297
- const newSessions = new Map(entry.sessions);
298
- newSessions.set(agent, sessionId);
299
- const newSessionCwds = new Map(entry.sessionCwds);
300
- newSessionCwds.set(agent, sessionCwd);
301
- this.sessions.set(userId, {
302
- sessions: newSessions,
303
- previousSessions: entry.previousSessions,
304
- sessionCwds: newSessionCwds,
305
- arrivalSessions: entry.arrivalSessions,
306
- cwd: entry.cwd,
307
- });
308
- }
309
- else {
310
- const newSessions = new Map();
311
- newSessions.set(agent, sessionId);
312
- const newSessionCwds = new Map();
313
- newSessionCwds.set(agent, sessionCwd);
314
- this.sessions.set(userId, {
315
- sessions: newSessions,
316
- previousSessions: new Map(),
317
- sessionCwds: newSessionCwds,
318
- arrivalSessions: new Map(),
319
- cwd: '',
320
- });
321
- }
322
- this.autoPersist();
215
+ this.mutateEntry(userId, (entry) => {
216
+ entry.sessions.set(agent, sessionId);
217
+ entry.sessionCwds.set(agent, sessionCwd);
218
+ return entry;
219
+ });
323
220
  }
324
221
  // -- Persistence helpers --
325
- /** Persisted format: only cwd per user (sessionId is process-scoped) */
222
+ /** Persisted format: cwd + sessions + previous/arrival session maps. */
326
223
  load() {
327
224
  if (!this.persistPath)
328
225
  return;
@@ -5,6 +5,6 @@
5
5
  * package installation (detect package manager + global install),
6
6
  * and the combined workflow for the /update command and --update CLI flag.
7
7
  */
8
- export { isNewer, checkLatestVersion, type VersionCheckResult, type UpdateCache, CACHE_TTL_MS, } from './version-check.js';
9
- export { detectPackageManager, runInstallLatest, type PackageManager, type InstallResult, } from './install.js';
8
+ export { isNewer, checkLatestVersion, type VersionCheckResult, type UpdateCache, } from './version-check.js';
9
+ export { runInstallLatest, type PackageManager, type InstallResult } from './install.js';
10
10
  export { formatUpdateHint } from './startup-hint.js';
@@ -5,7 +5,7 @@
5
5
  * package installation (detect package manager + global install),
6
6
  * and the combined workflow for the /update command and --update CLI flag.
7
7
  */
8
- export { isNewer, checkLatestVersion, CACHE_TTL_MS, } from './version-check.js';
9
- export { detectPackageManager, runInstallLatest, } from './install.js';
8
+ export { isNewer, checkLatestVersion, } from './version-check.js';
9
+ export { runInstallLatest } from './install.js';
10
10
  export { formatUpdateHint } from './startup-hint.js';
11
11
  //# sourceMappingURL=index.js.map
@@ -8,14 +8,32 @@ export interface InstallResult {
8
8
  /** Type signature matching execFile's callback style. */
9
9
  type ExecFileCallback = (error: Error | null, stdout: string | Buffer, stderr: string | Buffer) => void;
10
10
  type ExecFileFn = (file: string, args: readonly string[], options: object, callback: ExecFileCallback) => void;
11
+ /**
12
+ * Infer the installing package manager from the real path of the running
13
+ * script (e.g. `.../node_modules/lark-remote/dist/index.js`).
14
+ *
15
+ * Layout markers (check order matters — bun/pnpm paths also contain
16
+ * `/node_modules/lark-remote/`):
17
+ * - bun: `~/.bun/install/global/node_modules/lark-remote/...`
18
+ * - pnpm: `<global>/node_modules/.pnpm/lark-remote@<ver>/node_modules/lark-remote/...`
19
+ * - npm: `<prefix>/lib/node_modules/lark-remote/...` (POSIX) or
20
+ * `<prefix>/node_modules/lark-remote/...` (Windows)
21
+ *
22
+ * Returns null when the path has no marker (e.g. running from a source
23
+ * checkout in dev mode).
24
+ */
25
+ export declare function inferPackageManagerFromPath(scriptPath: string): PackageManager | null;
11
26
  /**
12
27
  * Detect which package manager to use for global install.
13
28
  *
14
29
  * Priority:
15
30
  * 1. Environment variable override (LARK_REMOTE_MANAGED_BY)
16
- * 2. `which` availability: npm → bun → pnpm
31
+ * 2. Install location of the running script (upgrades must target the same
32
+ * copy that is actually running — a different PM would install a second
33
+ * copy and leave the running one stale)
34
+ * 3. `which` availability: npm → bun → pnpm
17
35
  */
18
- export declare function detectPackageManager(): PackageManager | null;
36
+ export declare function detectPackageManager(scriptPath?: string): PackageManager | null;
19
37
  /**
20
38
  * Run the global install command to upgrade lark-remote to the latest version.
21
39
  *
@@ -1,18 +1,60 @@
1
1
  import { execFile, execFileSync } from 'node:child_process';
2
+ import fs from 'node:fs';
2
3
  import { getLogger } from '../logger/index.js';
4
+ /**
5
+ * Infer the installing package manager from the real path of the running
6
+ * script (e.g. `.../node_modules/lark-remote/dist/index.js`).
7
+ *
8
+ * Layout markers (check order matters — bun/pnpm paths also contain
9
+ * `/node_modules/lark-remote/`):
10
+ * - bun: `~/.bun/install/global/node_modules/lark-remote/...`
11
+ * - pnpm: `<global>/node_modules/.pnpm/lark-remote@<ver>/node_modules/lark-remote/...`
12
+ * - npm: `<prefix>/lib/node_modules/lark-remote/...` (POSIX) or
13
+ * `<prefix>/node_modules/lark-remote/...` (Windows)
14
+ *
15
+ * Returns null when the path has no marker (e.g. running from a source
16
+ * checkout in dev mode).
17
+ */
18
+ export function inferPackageManagerFromPath(scriptPath) {
19
+ const p = scriptPath.replace(/\\/g, '/');
20
+ if (p.includes('/.bun/install/global/'))
21
+ return 'bun';
22
+ if (p.includes('/.pnpm/lark-remote@'))
23
+ return 'pnpm';
24
+ if (p.includes('/node_modules/lark-remote/'))
25
+ return 'npm';
26
+ return null;
27
+ }
3
28
  /**
4
29
  * Detect which package manager to use for global install.
5
30
  *
6
31
  * Priority:
7
32
  * 1. Environment variable override (LARK_REMOTE_MANAGED_BY)
8
- * 2. `which` availability: npm → bun → pnpm
33
+ * 2. Install location of the running script (upgrades must target the same
34
+ * copy that is actually running — a different PM would install a second
35
+ * copy and leave the running one stale)
36
+ * 3. `which` availability: npm → bun → pnpm
9
37
  */
10
- export function detectPackageManager() {
38
+ export function detectPackageManager(scriptPath) {
11
39
  // 1. Env override (highest priority)
12
40
  const env = process.env.LARK_REMOTE_MANAGED_BY;
13
41
  if (env === 'npm' || env === 'bun' || env === 'pnpm')
14
42
  return env;
15
- // 2. which availability detection
43
+ // 2. Infer from where this process was loaded from
44
+ const raw = scriptPath ?? process.argv[1];
45
+ if (raw) {
46
+ let resolved = raw;
47
+ try {
48
+ resolved = fs.realpathSync(raw);
49
+ }
50
+ catch {
51
+ // Keep raw path (e.g. synthetic path in tests)
52
+ }
53
+ const inferred = inferPackageManagerFromPath(resolved);
54
+ if (inferred)
55
+ return inferred;
56
+ }
57
+ // 3. which availability fallback
16
58
  const candidates = ['npm', 'bun', 'pnpm'];
17
59
  for (const cmd of candidates) {
18
60
  try {
@@ -73,10 +115,12 @@ export function runInstallLatest(opts) {
73
115
  logger.error(`[update] install failed: ${msg}`);
74
116
  // Detect common errors and give actionable advice
75
117
  if (msg.includes('EACCES') || msg.includes('permission')) {
76
- resolve({
77
- success: false,
78
- error: `权限不足,尝试 sudo ${cmd} ${args.join(' ')}`,
79
- });
118
+ // bun/pnpm install into the user dir — sudo is wrong advice there
119
+ // (it would install into root's global dir instead).
120
+ const hint = pm === 'npm'
121
+ ? `权限不足,尝试 sudo ${cmd} ${args.join(' ')}`
122
+ : `权限不足,请检查 ${cmd} 全局安装目录的写权限`;
123
+ resolve({ success: false, error: hint });
80
124
  return;
81
125
  }
82
126
  resolve({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lark-remote",
3
- "version": "0.1.6",
3
+ "version": "0.1.7",
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",
@@ -1,56 +0,0 @@
1
- /**
2
- * ConnectionManager: manages Codex App Server connections per workspace.
3
- *
4
- * Each workspace gets its own connection (transport + client). Connections are
5
- * created on demand via `acquire()`, cached for reuse, and released after an
6
- * idle timeout (30 minutes default).
7
- */
8
- import { CodexAppServerClient } from './client.js';
9
- export interface ConnectionManagerOptions {
10
- /** Path to the codex binary. */
11
- binary: string;
12
- /** Args to spawn the app server with. Defaults to `['app-server', '--stdio']`. */
13
- args?: string[];
14
- /** Environment variables to pass to the binary. */
15
- env?: Record<string, string | undefined>;
16
- /** Request timeout in milliseconds. */
17
- requestTimeoutMs?: number;
18
- /** Idle TTL in milliseconds. */
19
- idleTtlMs?: number;
20
- }
21
- export declare class ConnectionManager {
22
- private slots;
23
- private readonly binary;
24
- private readonly args;
25
- private readonly env;
26
- private readonly requestTimeoutMs;
27
- private readonly idleTtlMs;
28
- /** Callback when a connection is lost — cleared from slot map. */
29
- onConnectionLost?: (workspace: string) => void;
30
- constructor(opts: ConnectionManagerOptions);
31
- /**
32
- * Acquire a connection for the given workspace.
33
- * Creates a new connection if one does not exist or the previous one was lost.
34
- * Serializes creation so concurrent calls share the same connection.
35
- */
36
- acquire(workspace: string): Promise<CodexAppServerClient>;
37
- /**
38
- * Release (dispose) a connection for the given workspace.
39
- */
40
- release(workspace: string): Promise<void>;
41
- /**
42
- * Dispose all connections.
43
- */
44
- disposeAll(): Promise<void>;
45
- /**
46
- * Notify that the workspace is active — disarm the idle timer.
47
- */
48
- notifyActivity(workspace: string): void;
49
- /**
50
- * Notify that the workspace is idle — arm the idle timer.
51
- */
52
- notifyIdle(workspace: string): void;
53
- private createClient;
54
- private clearIdleTimer;
55
- private disposeSlot;
56
- }
@@ -1,33 +0,0 @@
1
- /**
2
- * Builds `codex exec` command-line arguments.
3
- *
4
- * Key design decisions:
5
- * - `approval_policy="never"` eliminates the approval handshake (root cause ①).
6
- * - Prompt goes via stdin (`-` flag) to avoid argv escaping issues.
7
- * - `--skip-git-repo-check`: lark-remote's cwd may not be a git repo.
8
- * - User's `~/.codex/config.toml` and project rules are preserved
9
- * (no `--ignore-user-config` / `--ignore-rules`).
10
- */
11
- interface BuildCodexExecArgsInput {
12
- /** Working directory for the codex process. */
13
- cwd: string;
14
- /** Model override (maps to `-m`). Omitted uses codex config.toml default. */
15
- model?: string;
16
- /** Model provider override (maps to `-c model_provider="xxx"`).
17
- * Omitted uses codex config.toml default. */
18
- modelProvider?: string;
19
- /** Thread ID for session resumption (maps to `exec resume --json <threadId> -`). */
20
- threadId?: string;
21
- /** Reasoning effort level (maps to `-c model_reasoning_effort="xxx"`). */
22
- reasoningEffort?: string;
23
- }
24
- /**
25
- * Construct the argv array for `codex exec --json`.
26
- *
27
- * New session:
28
- * `codex exec --json --sandbox danger-full-access -c approval_policy="never" ... -`
29
- * Resume session:
30
- * `codex exec --sandbox danger-full-access ... resume --json <threadId> -`
31
- */
32
- export declare function buildCodexExecArgs(input: BuildCodexExecArgsInput): string[];
33
- export {};