lark-coding-assistant 0.2.1 → 0.2.3

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.
@@ -5,7 +5,7 @@ import { createServer } from "net";
5
5
  // src/platform/process.ts
6
6
  import { execFile, spawn } from "child_process";
7
7
  function runFile(file, args, options = {}) {
8
- return new Promise((resolve, reject) => {
8
+ return new Promise((resolve2, reject) => {
9
9
  execFile(
10
10
  file,
11
11
  [...args],
@@ -20,13 +20,13 @@ function runFile(file, args, options = {}) {
20
20
  reject(Object.assign(error, { stdout, stderr }));
21
21
  return;
22
22
  }
23
- resolve({ stdout, stderr });
23
+ resolve2({ stdout, stderr });
24
24
  }
25
25
  );
26
26
  });
27
27
  }
28
28
  function runFileWithInput(file, args, input) {
29
- return new Promise((resolve, reject) => {
29
+ return new Promise((resolve2, reject) => {
30
30
  const child = spawn(file, [...args], { stdio: ["pipe", "pipe", "pipe"] });
31
31
  let stdout = "";
32
32
  let stderr = "";
@@ -36,7 +36,7 @@ function runFileWithInput(file, args, input) {
36
36
  child.stderr.on("data", (chunk) => stderr += chunk);
37
37
  child.once("error", reject);
38
38
  child.once("exit", (code, signal) => {
39
- if (code === 0) resolve({ stdout, stderr });
39
+ if (code === 0) resolve2({ stdout, stderr });
40
40
  else reject(new Error(`${file} exited with ${code ?? signal}: ${stderr.trim()}`));
41
41
  });
42
42
  child.stdin.end(input, "utf8");
@@ -100,6 +100,81 @@ function normalizeAgentId(value) {
100
100
  return AGENT_IDS.includes(value) ? value : void 0;
101
101
  }
102
102
 
103
+ // src/workspace/path.ts
104
+ import { constants } from "fs";
105
+ import { access, stat } from "fs/promises";
106
+ import { homedir } from "os";
107
+ import { isAbsolute, normalize, resolve } from "path";
108
+
109
+ // src/core/errors.ts
110
+ var AppError = class extends Error {
111
+ code;
112
+ context;
113
+ constructor(code, message, context = {}, options = {}) {
114
+ super(message, options);
115
+ this.name = "AppError";
116
+ this.code = code;
117
+ this.context = context;
118
+ }
119
+ };
120
+ function isAppError(error) {
121
+ return error instanceof AppError;
122
+ }
123
+ function errorMessage(error) {
124
+ return error instanceof Error ? error.message : String(error);
125
+ }
126
+ function serializeAppError(error) {
127
+ if (!isAppError(error)) return { error: errorMessage(error) };
128
+ const errorContext = Object.fromEntries(
129
+ Object.entries(error.context).filter(([, value]) => value !== void 0)
130
+ );
131
+ return {
132
+ error: error.message,
133
+ errorCode: error.code,
134
+ ...Object.keys(errorContext).length > 0 ? { errorContext } : {}
135
+ };
136
+ }
137
+ function systemErrorCode(error) {
138
+ return error instanceof Error && "code" in error && typeof error.code === "string" ? error.code : void 0;
139
+ }
140
+
141
+ // src/workspace/path.ts
142
+ function normalizeWorkspacePath(input, home = homedir()) {
143
+ const value = input.trim();
144
+ if (!value) throw invalidCwd(input, "\u5DE5\u4F5C\u76EE\u5F55\u4E0D\u80FD\u4E3A\u7A7A");
145
+ let expanded = value;
146
+ if (value === "~") expanded = home;
147
+ else if (value.startsWith("~/")) expanded = resolve(home, value.slice(2));
148
+ else if (value.startsWith("~")) throw invalidCwd(input, "\u4E0D\u652F\u6301 ~other-user\uFF0C\u8BF7\u4F7F\u7528 ~ \u6216 ~/\u76EE\u5F55");
149
+ if (!isAbsolute(expanded)) throw invalidCwd(input, "\u5DE5\u4F5C\u76EE\u5F55\u5FC5\u987B\u662F\u7EDD\u5BF9\u8DEF\u5F84\uFF0C\u6216\u4F7F\u7528 ~/\u76EE\u5F55");
150
+ return normalize(expanded);
151
+ }
152
+ async function validateWorkspaceDirectory(input, home = homedir()) {
153
+ const cwd = normalizeWorkspacePath(input, home);
154
+ let info;
155
+ try {
156
+ info = await stat(cwd);
157
+ } catch (error) {
158
+ const reason = systemErrorCode(error) === "EACCES" ? `\u65E0\u6743\u8BBF\u95EE\u5DE5\u4F5C\u76EE\u5F55\uFF1A${cwd}` : `\u5DE5\u4F5C\u76EE\u5F55\u4E0D\u5B58\u5728\u6216\u4E0D\u53EF\u7528\uFF1A${cwd}`;
159
+ throw invalidCwd(cwd, reason, error);
160
+ }
161
+ if (!info.isDirectory()) throw invalidCwd(cwd, `\u5DE5\u4F5C\u76EE\u5F55\u4E0D\u662F\u6587\u4EF6\u5939\uFF1A${cwd}`);
162
+ try {
163
+ await access(cwd, constants.R_OK | constants.X_OK);
164
+ } catch (error) {
165
+ throw invalidCwd(cwd, `\u65E0\u6743\u8FDB\u5165\u5DE5\u4F5C\u76EE\u5F55\uFF1A${cwd}`, error);
166
+ }
167
+ return cwd;
168
+ }
169
+ function normalizeWorkspaceRoots(values, home = homedir()) {
170
+ const unique = /* @__PURE__ */ new Set();
171
+ for (const value of values) unique.add(normalizeWorkspacePath(value, home));
172
+ return [...unique];
173
+ }
174
+ function invalidCwd(cwd, reason, cause) {
175
+ return new AppError("INVALID_CWD", reason, { cwd, reason }, cause === void 0 ? {} : { cause });
176
+ }
177
+
103
178
  // src/core/store.ts
104
179
  var AppStore = class {
105
180
  constructor(paths2) {
@@ -129,11 +204,15 @@ var AppStore = class {
129
204
  traex: binaries.traex ?? binaries["trae-cli"] ?? "trae-cli",
130
205
  claude: binaries.claude ?? binaries["claude-code"] ?? "claude"
131
206
  },
132
- pollIntervalMs: config.pollIntervalMs
207
+ pollIntervalMs: config.pollIntervalMs,
208
+ workspaceRoots: normalizeWorkspaceRoots(config.workspaceRoots ?? [])
133
209
  };
134
210
  }
135
211
  saveConfig(config) {
136
- return writeJsonAtomic(this.paths.config, config);
212
+ return writeJsonAtomic(this.paths.config, {
213
+ ...config,
214
+ workspaceRoots: normalizeWorkspaceRoots(config.workspaceRoots)
215
+ });
137
216
  }
138
217
  loadSecrets() {
139
218
  return readJson(this.paths.secrets);
@@ -148,12 +227,33 @@ var AppStore = class {
148
227
  const agent = normalizeAgentId(session.agent);
149
228
  return agent ? [[id, { ...session, agent }]] : [];
150
229
  }));
151
- return { ...state, sessions };
230
+ const recentWorkspaces = await normalizeRecentWorkspaces(state.recentWorkspaces ?? []);
231
+ return { ...state, sessions, recentWorkspaces };
152
232
  }
153
233
  saveState(state) {
154
234
  return writeJsonAtomic(this.paths.state, state);
155
235
  }
156
236
  };
237
+ async function normalizeRecentWorkspaces(values) {
238
+ const unique = /* @__PURE__ */ new Set();
239
+ const result2 = [];
240
+ const ordered = [...values].sort((left, right) => right.lastUsedAt - left.lastUsedAt);
241
+ for (const value of ordered) {
242
+ if (result2.length >= 30) break;
243
+ if (!Number.isFinite(value.lastUsedAt)) continue;
244
+ let cwd;
245
+ try {
246
+ cwd = normalizeWorkspacePath(value.cwd);
247
+ await validateWorkspaceDirectory(cwd);
248
+ } catch {
249
+ continue;
250
+ }
251
+ if (unique.has(cwd)) continue;
252
+ unique.add(cwd);
253
+ result2.push({ cwd, lastUsedAt: value.lastUsedAt });
254
+ }
255
+ return result2;
256
+ }
157
257
  function createBindCode() {
158
258
  return randomBytes(16).toString("base64url");
159
259
  }
@@ -170,41 +270,7 @@ function verifyBindCode(code, encoded) {
170
270
  return actual.length === expected.length && timingSafeEqual(actual, expected);
171
271
  }
172
272
 
173
- // src/core/errors.ts
174
- var AppError = class extends Error {
175
- code;
176
- context;
177
- constructor(code, message, context = {}, options = {}) {
178
- super(message, options);
179
- this.name = "AppError";
180
- this.code = code;
181
- this.context = context;
182
- }
183
- };
184
- function isAppError(error) {
185
- return error instanceof AppError;
186
- }
187
- function errorMessage(error) {
188
- return error instanceof Error ? error.message : String(error);
189
- }
190
- function serializeAppError(error) {
191
- if (!isAppError(error)) return { error: errorMessage(error) };
192
- const errorContext = Object.fromEntries(
193
- Object.entries(error.context).filter(([, value]) => value !== void 0)
194
- );
195
- return {
196
- error: error.message,
197
- errorCode: error.code,
198
- ...Object.keys(errorContext).length > 0 ? { errorContext } : {}
199
- };
200
- }
201
- function systemErrorCode(error) {
202
- return error instanceof Error && "code" in error && typeof error.code === "string" ? error.code : void 0;
203
- }
204
-
205
273
  // src/session/start-request.ts
206
- import { stat } from "fs/promises";
207
- import { isAbsolute } from "path";
208
274
  async function validateStartSessionRequest(request) {
209
275
  if (!validSessionId(request.sessionId)) {
210
276
  throw new AppError(
@@ -213,21 +279,13 @@ async function validateStartSessionRequest(request) {
213
279
  { sessionId: request.sessionId }
214
280
  );
215
281
  }
216
- if (!isAbsolute(request.cwd)) {
217
- throw new AppError("INVALID_CWD", "working directory must be absolute", { cwd: request.cwd });
218
- }
219
- const info = await stat(request.cwd).catch((error) => {
220
- throw new AppError("INVALID_CWD", `working directory is unavailable: ${request.cwd}`, { cwd: request.cwd }, { cause: error });
221
- });
222
- if (!info.isDirectory()) {
223
- throw new AppError("INVALID_CWD", `working directory is not a directory: ${request.cwd}`, { cwd: request.cwd });
224
- }
282
+ const cwd = await validateWorkspaceDirectory(request.cwd);
225
283
  if (request.resume?.mode === "session" && !request.resume.sessionId.trim()) {
226
284
  throw new AppError("INVALID_RESUME", "resume session id must not be empty", {
227
285
  reason: "\u6062\u590D\u5386\u53F2\u4F1A\u8BDD\u65F6\u5FC5\u987B\u63D0\u4F9B session ID"
228
286
  });
229
287
  }
230
- return request;
288
+ return { ...request, cwd };
231
289
  }
232
290
  function validSessionId(value) {
233
291
  return /^[a-zA-Z0-9_-]{1,40}$/.test(value);
@@ -277,6 +335,15 @@ var SessionReconciler = class {
277
335
  }
278
336
  continue;
279
337
  }
338
+ if (result2.status === "dead") {
339
+ await this.tmux.killSession(result2.pane.sessionName).catch((error) => this.log(
340
+ `failed to clean dead tmux session ${result2.pane.sessionName}: ${errorMessage2(error)}`
341
+ ));
342
+ delete sessions[id];
343
+ this.misses.delete(id);
344
+ changed = true;
345
+ continue;
346
+ }
280
347
  const misses = (this.misses.get(id) ?? 0) + 1;
281
348
  this.misses.set(id, misses);
282
349
  if (misses < this.missingThreshold) continue;
@@ -310,9 +377,18 @@ var SessionReconciler = class {
310
377
  }
311
378
  let changed = false;
312
379
  const seen = /* @__PURE__ */ new Set();
380
+ const liveSessionNames = new Set(panes.filter((pane) => !pane.dead).map((pane) => pane.sessionName));
313
381
  for (const pane of panes) {
314
- if (seen.has(pane.sessionName) || pane.dead) continue;
382
+ if (seen.has(pane.sessionName)) continue;
315
383
  seen.add(pane.sessionName);
384
+ if (pane.dead) {
385
+ if (!liveSessionNames.has(pane.sessionName)) {
386
+ await this.tmux.killSession(pane.sessionName).catch((error) => this.log(
387
+ `failed to clean orphaned dead tmux session ${pane.sessionName}: ${errorMessage2(error)}`
388
+ ));
389
+ }
390
+ continue;
391
+ }
316
392
  const registered = Object.values(sessions).find((session) => session.sessionName === pane.sessionName);
317
393
  if (registered) {
318
394
  if (registered.paneId !== pane.paneId) {
@@ -397,10 +473,10 @@ function errorMessage2(error) {
397
473
 
398
474
  // src/session/native-session.ts
399
475
  import { readdir, readFile as readFile2 } from "fs/promises";
400
- import { homedir } from "os";
476
+ import { homedir as homedir2 } from "os";
401
477
  import { join } from "path";
402
478
  var UUID = "[0-9a-fA-F-]{32,36}";
403
- async function resolveNativeAgentSessionId(agent, pid, home = homedir()) {
479
+ async function resolveNativeAgentSessionId(agent, pid, home = homedir2()) {
404
480
  if (!Number.isInteger(pid) || pid <= 0) return void 0;
405
481
  if (agent === "traex") {
406
482
  const peer = await resolveTraexPeer(pid, home);
@@ -486,7 +562,7 @@ var TmuxController = class {
486
562
  throw new AppError(
487
563
  "SESSION_EXISTS",
488
564
  `tmux session already exists: ${options.sessionName}`,
489
- { sessionId: displaySessionId(options.sessionName) }
565
+ { sessionId: displaySessionId(options.sessionName), source: "tmux" }
490
566
  );
491
567
  }
492
568
  const environment = Object.entries(options.env ?? {}).map(([key, value]) => {
@@ -692,7 +768,7 @@ function parsePane(line) {
692
768
  function tmuxTargetMissing(error) {
693
769
  const message = error instanceof Error ? error.message : String(error);
694
770
  const stderr = error && typeof error === "object" && "stderr" in error ? String(error.stderr) : "";
695
- return /can't find (?:pane|session|window)|no such (?:pane|session|window)|(?:pane|session|window) not found/i.test(`${message}
771
+ return /can't find (?:pane|session|window)|no such (?:pane|session|window)|(?:pane|session|window) not found|no server running/i.test(`${message}
696
772
  ${stderr}`);
697
773
  }
698
774
 
@@ -1207,6 +1283,8 @@ var ActionSigner = class {
1207
1283
  value.interactionKind ?? null,
1208
1284
  value.sessionId ?? null,
1209
1285
  value.manualMode ?? null,
1286
+ value.snapshotId ?? null,
1287
+ value.page ?? null,
1210
1288
  value.agent,
1211
1289
  value.action,
1212
1290
  value.paneId,
@@ -1230,7 +1308,12 @@ function withoutSignature(value) {
1230
1308
  function isSignedAction(value) {
1231
1309
  if (!value || typeof value !== "object") return false;
1232
1310
  const item = value;
1233
- return item.v === 1 && (item.kind === "choice" || item.kind === "stop" || item.kind === "session" || item.kind === "session-stop" || item.kind === "session-create" || item.kind === "session-start-error" || item.kind === "startup-conflict" || item.kind === "resume-picker" || item.kind === "manual") && (item.kind !== "choice" || item.interactionKind === "approval" || item.interactionKind === "question" || item.interactionKind === "choice") && (item.kind !== "manual" || typeof item.sessionId === "string" && (item.manualMode === "explicit" || item.manualMode === "fallback")) && (item.kind !== "session-stop" && item.kind !== "session-start-error" && item.kind !== "resume-picker" && item.kind !== "startup-conflict" || typeof item.sessionId === "string") && typeof item.agent === "string" && isAgentId(item.agent) && typeof item.action === "string" && typeof item.paneId === "string" && typeof item.fingerprint === "string" && typeof item.chatId === "string" && typeof item.nonce === "string" && typeof item.expiresAt === "number" && typeof item.sig === "string";
1311
+ return item.v === 1 && (item.kind === "choice" || item.kind === "stop" || item.kind === "session" || item.kind === "session-stop" || item.kind === "session-create" || item.kind === "session-start-error" || item.kind === "startup-conflict" || item.kind === "resume-picker" || item.kind === "manual") && (item.kind !== "choice" || item.interactionKind === "approval" || item.interactionKind === "question" || item.interactionKind === "choice") && (item.kind !== "manual" || typeof item.sessionId === "string" && (item.manualMode === "explicit" || item.manualMode === "fallback")) && (item.snapshotId === void 0 || typeof item.snapshotId === "string") && (item.page === void 0 || typeof item.page === "number" && Number.isInteger(item.page) && item.page >= 0) && (item.kind !== "session-stop" && item.kind !== "session-start-error" && item.kind !== "resume-picker" && item.kind !== "startup-conflict" || typeof item.sessionId === "string") && typeof item.agent === "string" && isAgentId(item.agent) && typeof item.action === "string" && typeof item.paneId === "string" && typeof item.fingerprint === "string" && typeof item.chatId === "string" && typeof item.nonce === "string" && typeof item.expiresAt === "number" && typeof item.sig === "string";
1312
+ }
1313
+
1314
+ // src/workspace/session-create.ts
1315
+ function emptySessionCreateDraft() {
1316
+ return { agent: "codex", resumeMode: "new" };
1234
1317
  }
1235
1318
 
1236
1319
  // src/lark/cards.ts
@@ -1614,10 +1697,33 @@ var SESSION_CREATE_SUBMIT_ACTION = "session_create_submit";
1614
1697
  var SESSION_CREATE_NAME_FIELD = "session_name";
1615
1698
  var SESSION_CREATE_AGENT_FIELD = "session_agent";
1616
1699
  var SESSION_CREATE_CWD_FIELD = "session_cwd";
1700
+ var SESSION_CREATE_PROJECT_FIELD = "session_project";
1617
1701
  var SESSION_CREATE_RESUME_FIELD = "session_resume";
1618
- function sessionCreateCard() {
1702
+ var SESSION_CREATE_MANUAL_VALUE = "__manual__";
1703
+ function sessionCreateCard(view2 = {
1704
+ mode: "manual",
1705
+ page: 0,
1706
+ pageCount: 1,
1707
+ candidates: [],
1708
+ partial: false,
1709
+ warnings: [],
1710
+ draft: emptySessionCreateDraft()
1711
+ }) {
1712
+ const directoryElements = view2.mode === "projects" ? projectDirectoryFields(view2) : [{
1713
+ tag: "input",
1714
+ name: SESSION_CREATE_CWD_FIELD,
1715
+ default_value: view2.draft.manualCwd,
1716
+ placeholder: { tag: "plain_text", content: "~/workspace/project \u6216 /absolute/path" },
1717
+ label: { tag: "plain_text", content: "\u9879\u76EE\u76EE\u5F55" }
1718
+ }];
1719
+ const noticeLines = [
1720
+ ...view2.warnings.map((warning) => `\u26A0\uFE0F ${escapeMarkdown(warning)}`),
1721
+ ...view2.partial ? ["\u26A0\uFE0F \u90E8\u5206\u76EE\u5F55\u672A\u52A0\u8F7D\uFF1B\u53EF\u9009\u62E9\u5DF2\u663E\u793A\u9879\u76EE\u6216\u624B\u52A8\u586B\u5199\u8DEF\u5F84\u3002"] : []
1722
+ ];
1723
+ const notices = noticeLines.length > 0 ? [{ tag: "markdown", content: noticeLines.join("\n") }] : [];
1619
1724
  return cardElements("\u65B0\u5EFA Coding Session", [
1620
1725
  { tag: "markdown", content: "\u5728\u672C\u673A\u53D7\u7BA1 tmux \u4E2D\u542F\u52A8\u4E00\u4E2A\u65B0\u4F1A\u8BDD\uFF1B\u521B\u5EFA\u6210\u529F\u540E\u4F1A\u81EA\u52A8\u8FDE\u63A5\u3002" },
1726
+ ...notices,
1621
1727
  {
1622
1728
  tag: "form",
1623
1729
  name: "session_create_form",
@@ -1628,6 +1734,7 @@ function sessionCreateCard() {
1628
1734
  tag: "input",
1629
1735
  name: SESSION_CREATE_NAME_FIELD,
1630
1736
  required: true,
1737
+ default_value: view2.draft.sessionId,
1631
1738
  placeholder: { tag: "plain_text", content: "Session \u540D\u79F0\uFF0C\u4F8B\u5982 helix" },
1632
1739
  label: { tag: "plain_text", content: "Session \u540D\u79F0" }
1633
1740
  },
@@ -1636,24 +1743,18 @@ function sessionCreateCard() {
1636
1743
  name: SESSION_CREATE_AGENT_FIELD,
1637
1744
  required: true,
1638
1745
  placeholder: { tag: "plain_text", content: "\u9009\u62E9 Agent" },
1639
- initial_option: "codex",
1746
+ initial_option: view2.draft.agent,
1640
1747
  options: listAgentAdapters().map((adapter) => ({
1641
1748
  text: { tag: "plain_text", content: adapter.displayName },
1642
1749
  value: adapter.id
1643
1750
  }))
1644
1751
  },
1645
- {
1646
- tag: "input",
1647
- name: SESSION_CREATE_CWD_FIELD,
1648
- required: true,
1649
- placeholder: { tag: "plain_text", content: "/absolute/path/to/project" },
1650
- label: { tag: "plain_text", content: "\u5DE5\u4F5C\u76EE\u5F55\uFF08\u5FC5\u987B\u4E3A\u7EDD\u5BF9\u8DEF\u5F84\uFF09" }
1651
- },
1752
+ ...directoryElements,
1652
1753
  {
1653
1754
  tag: "select_static",
1654
1755
  name: SESSION_CREATE_RESUME_FIELD,
1655
1756
  placeholder: { tag: "plain_text", content: "\u9009\u62E9\u542F\u52A8\u65B9\u5F0F" },
1656
- initial_option: "new",
1757
+ initial_option: view2.draft.resumeMode,
1657
1758
  options: [
1658
1759
  { text: { tag: "plain_text", content: "\u65B0\u4F1A\u8BDD" }, value: "new" },
1659
1760
  { text: { tag: "plain_text", content: "\u6253\u5F00\u539F\u751F Resume Picker" }, value: "picker" }
@@ -1672,6 +1773,26 @@ function sessionCreateCard() {
1672
1773
  }
1673
1774
  ]);
1674
1775
  }
1776
+ function projectDirectoryFields(view2) {
1777
+ const options = [...view2.candidates.map((candidate) => ({
1778
+ text: { tag: "plain_text", content: candidate.label },
1779
+ value: candidate.cwd
1780
+ })), { text: { tag: "plain_text", content: "\u624B\u52A8\u586B\u5199\u5176\u4ED6\u8DEF\u5F84\u2026" }, value: SESSION_CREATE_MANUAL_VALUE }];
1781
+ return [{
1782
+ tag: "select_static",
1783
+ name: SESSION_CREATE_PROJECT_FIELD,
1784
+ placeholder: { tag: "plain_text", content: "\u9009\u62E9\u9879\u76EE\u76EE\u5F55" },
1785
+ initial_option: view2.draft.projectCwd,
1786
+ required: true,
1787
+ options
1788
+ }, {
1789
+ tag: "input",
1790
+ name: SESSION_CREATE_CWD_FIELD,
1791
+ default_value: view2.draft.manualCwd,
1792
+ placeholder: { tag: "plain_text", content: "\u9009\u62E9\u201C\u624B\u52A8\u586B\u5199\u5176\u4ED6\u8DEF\u5F84\u2026\u201D\u65F6\u586B\u5199" },
1793
+ label: { tag: "plain_text", content: "\u5176\u4ED6\u8DEF\u5F84\uFF08\u53EF\u9009\uFF09" }
1794
+ }];
1795
+ }
1675
1796
  function sessionCreateResultCard(success, content, session) {
1676
1797
  const details = session ? `
1677
1798
 
@@ -2038,10 +2159,10 @@ var LarkGateway = class {
2038
2159
  const result2 = await this.handler.onAction(event, action);
2039
2160
  if (result2.type === "error") return { toast: result2 };
2040
2161
  if (result2.type === "session-create-form") {
2041
- this.rememberSessionCreateFormAction(event.messageId, event.chatId);
2162
+ this.rememberSessionCreateFormActions(event.messageId, event.chatId, result2.view);
2042
2163
  return {
2043
2164
  toast: { type: "success", content: result2.content },
2044
- card: { type: "raw", data: sessionCreateCard() }
2165
+ card: { type: "raw", data: sessionCreateCard(result2.view) }
2045
2166
  };
2046
2167
  }
2047
2168
  if (result2.type === "manual") {
@@ -2112,6 +2233,8 @@ var LarkGateway = class {
2112
2233
  signer;
2113
2234
  formActions = /* @__PURE__ */ new Map();
2114
2235
  formActionsInFlight = /* @__PURE__ */ new Set();
2236
+ processingReactions = /* @__PURE__ */ new Map();
2237
+ processingGenerations = /* @__PURE__ */ new Map();
2115
2238
  async handleDeferredCardAction(event, action) {
2116
2239
  let initialResumePicker;
2117
2240
  try {
@@ -2128,7 +2251,7 @@ var LarkGateway = class {
2128
2251
  this.formActions.delete(event.messageId);
2129
2252
  return;
2130
2253
  }
2131
- await this.channel.send(event.chatId, { text: `\u5361\u7247\u64CD\u4F5C\u672A\u5B8C\u6210\uFF1A${result2.content}` });
2254
+ await this.sendMessage(event.chatId, { text: `\u5361\u7247\u64CD\u4F5C\u672A\u5B8C\u6210\uFF1A${result2.content}` });
2132
2255
  return;
2133
2256
  }
2134
2257
  if (result2.type === "session-created") {
@@ -2158,7 +2281,7 @@ var LarkGateway = class {
2158
2281
  }
2159
2282
  if (result2.type === "session-picker") {
2160
2283
  if (action.kind === "session-start-error") {
2161
- await this.channel.send(event.chatId, { card: sessionPickerCard(
2284
+ await this.sendMessage(event.chatId, { card: sessionPickerCard(
2162
2285
  event.chatId,
2163
2286
  result2.sessions,
2164
2287
  result2.activeSessionId,
@@ -2177,13 +2300,18 @@ var LarkGateway = class {
2177
2300
  return;
2178
2301
  }
2179
2302
  if (result2.type === "session-create-form") {
2180
- const sent = await this.channel.send(event.chatId, { card: sessionCreateCard() });
2181
- this.rememberSessionCreateFormAction(sent.messageId, event.chatId);
2303
+ if (action.kind === "session-create" && action.action !== "open") {
2304
+ await this.updateCardAfterAction(event.messageId, sessionCreateCard(result2.view));
2305
+ this.rememberSessionCreateFormActions(event.messageId, event.chatId, result2.view);
2306
+ return;
2307
+ }
2308
+ const sent = await this.sendMessage(event.chatId, { card: sessionCreateCard(result2.view) });
2309
+ this.rememberSessionCreateFormActions(sent.messageId, event.chatId, result2.view);
2182
2310
  const sourceCard = action.kind === "session-create" && result2.sessions ? sessionPickerCard(event.chatId, result2.sessions, result2.activeSessionId, this.signer, void 0, false) : sessionCreateOpenedCard();
2183
2311
  await this.updateCardAfterAction(event.messageId, sourceCard).catch(async (error) => {
2184
2312
  const detail = cardErrorDetail(error);
2185
2313
  console.error(`[lca] failed to retire session-create source card: message=${event.messageId} detail=${detail}`);
2186
- await this.channel.send(event.chatId, {
2314
+ await this.sendMessage(event.chatId, {
2187
2315
  text: "\u65B0\u5EFA\u8868\u5355\u5DF2\u53D1\u9001\uFF0C\u4F46\u539F\u5361\u7247\u72B6\u6001\u66F4\u65B0\u5931\u8D25\uFF1B\u8BF7\u4F7F\u7528\u6700\u65B0\u7684\u201C\u65B0\u5EFA Coding Session\u201D\u5361\u7247\u7EE7\u7EED\u64CD\u4F5C\u3002"
2188
2316
  }).catch(() => void 0);
2189
2317
  });
@@ -2216,7 +2344,7 @@ var LarkGateway = class {
2216
2344
  }
2217
2345
  if (action.kind === "session-create") this.formActions.delete(event.messageId);
2218
2346
  console.error(`[lca] deferred card action failed: kind=${action.kind} action=${action.action} message=${event.messageId} detail=${detail}`);
2219
- await this.channel.send(event.chatId, {
2347
+ await this.sendMessage(event.chatId, {
2220
2348
  text: action.kind === "session-create" ? `\u65B0\u5EFA Session \u8868\u5355\u6253\u5F00\u5931\u8D25\uFF1A${detail}\u3002\u8BF7\u91CD\u65B0\u53D1\u9001 /sessions\uFF0C\u6216\u76F4\u63A5\u4F7F\u7528 /start \u547D\u4EE4\u3002` : `\u5361\u7247\u64CD\u4F5C\u540C\u6B65\u5931\u8D25\uFF1A${detail}`
2221
2349
  }).catch(() => void 0);
2222
2350
  }
@@ -2225,7 +2353,7 @@ var LarkGateway = class {
2225
2353
  const retryDelays = [0, 300, 800, 1600];
2226
2354
  let lastError;
2227
2355
  for (const delay of retryDelays) {
2228
- if (delay > 0) await new Promise((resolve) => setTimeout(resolve, delay));
2356
+ if (delay > 0) await new Promise((resolve2) => setTimeout(resolve2, delay));
2229
2357
  try {
2230
2358
  await this.channel.updateCard(messageId, card2);
2231
2359
  return;
@@ -2242,7 +2370,7 @@ var LarkGateway = class {
2242
2370
  } catch (error) {
2243
2371
  const detail = cardErrorDetail(error);
2244
2372
  console.error(`[lca] failed to mark expired card: message=${event.messageId} detail=${detail}`);
2245
- await this.channel.send(event.chatId, {
2373
+ await this.sendMessage(event.chatId, {
2246
2374
  text: "\u5361\u7247\u6216\u6309\u94AE\u5DF2\u5931\u6548\uFF0C\u8BF7\u91CD\u65B0\u53D1\u9001\u5BF9\u5E94\u547D\u4EE4\u83B7\u53D6\u6700\u65B0\u5361\u7247\uFF1BSession \u76F8\u5173\u64CD\u4F5C\u53EF\u53D1\u9001 /sessions\u3002"
2247
2375
  }).catch(() => void 0);
2248
2376
  }
@@ -2250,17 +2378,37 @@ var LarkGateway = class {
2250
2378
  connect() {
2251
2379
  return this.channel.connect();
2252
2380
  }
2253
- disconnect() {
2254
- return this.channel.disconnect();
2381
+ async disconnect() {
2382
+ const chats = /* @__PURE__ */ new Set([...this.processingGenerations.keys(), ...this.processingReactions.keys()]);
2383
+ await Promise.all([...chats].map((chatId) => this.clearProcessing(chatId)));
2384
+ await this.channel.disconnect();
2385
+ }
2386
+ async startProcessing(message) {
2387
+ await this.clearProcessing(message.chatId);
2388
+ const generation = this.processingGenerations.get(message.chatId) ?? 0;
2389
+ try {
2390
+ const reactionId = await this.channel.addReaction(message.messageId, "Typing");
2391
+ if (this.processingGenerations.get(message.chatId) !== generation) {
2392
+ await this.removeProcessingReaction(message.messageId, reactionId);
2393
+ return;
2394
+ }
2395
+ const timer = setTimeout(() => {
2396
+ void this.clearProcessing(message.chatId, generation);
2397
+ }, 10 * 6e4);
2398
+ timer.unref?.();
2399
+ this.processingReactions.set(message.chatId, { messageId: message.messageId, reactionId, timer, generation });
2400
+ } catch (error) {
2401
+ console.error(`[lca] failed to add processing reaction: chat=${message.chatId} message=${message.messageId} detail=${cardErrorDetail(error)}`);
2402
+ }
2255
2403
  }
2256
2404
  sendText(chatId, text) {
2257
- return this.channel.send(chatId, { text });
2405
+ return this.sendMessage(chatId, { text });
2258
2406
  }
2259
2407
  sendMarkdown(chatId, markdown) {
2260
- return this.channel.send(chatId, { markdown });
2408
+ return this.sendMessage(chatId, { markdown });
2261
2409
  }
2262
2410
  async sendChoice(chatId, paneId, screen, agent) {
2263
- const result2 = await this.channel.send(chatId, { card: choiceCard(chatId, paneId, screen, this.signer, agent) });
2411
+ const result2 = await this.sendMessage(chatId, { card: choiceCard(chatId, paneId, screen, this.signer, agent) });
2264
2412
  this.rememberFormActions(result2.messageId, chatId, paneId, screen, agent);
2265
2413
  return result2;
2266
2414
  }
@@ -2273,7 +2421,7 @@ var LarkGateway = class {
2273
2421
  this.formActions.delete(messageId);
2274
2422
  }
2275
2423
  async sendManual(chatId, view2) {
2276
- const result2 = await this.channel.send(chatId, { card: manualControlCard(chatId, view2, this.signer) });
2424
+ const result2 = await this.sendMessage(chatId, { card: manualControlCard(chatId, view2, this.signer) });
2277
2425
  this.rememberManualFormActions(result2.messageId, chatId, view2);
2278
2426
  return result2;
2279
2427
  }
@@ -2296,18 +2444,19 @@ var LarkGateway = class {
2296
2444
  [MANUAL_SUBMIT_ACTION, this.signer.sign({ ...common, action: MANUAL_SUBMIT_ACTION }, 10 * 6e4)]
2297
2445
  ]));
2298
2446
  }
2299
- rememberSessionCreateFormAction(messageId, chatId) {
2300
- this.formActions.set(messageId, /* @__PURE__ */ new Map([[
2301
- SESSION_CREATE_SUBMIT_ACTION,
2302
- this.signer.sign({
2303
- kind: "session-create",
2304
- agent: "codex",
2305
- action: "submit",
2306
- paneId: "",
2307
- fingerprint: "create",
2308
- chatId
2309
- }, 10 * 6e4)
2310
- ]]));
2447
+ rememberSessionCreateFormActions(messageId, chatId, view2) {
2448
+ const actions = /* @__PURE__ */ new Map();
2449
+ actions.set(SESSION_CREATE_SUBMIT_ACTION, this.signer.sign({
2450
+ kind: "session-create",
2451
+ agent: view2.draft.agent,
2452
+ action: "submit",
2453
+ paneId: "",
2454
+ fingerprint: view2.mode,
2455
+ chatId,
2456
+ snapshotId: view2.snapshotId,
2457
+ page: view2.page
2458
+ }, 10 * 6e4));
2459
+ this.formActions.set(messageId, actions);
2311
2460
  }
2312
2461
  rememberFormActions(messageId, chatId, paneId, screen, agent) {
2313
2462
  if (screen.interaction?.semantics?.activation !== "toggle") {
@@ -2332,27 +2481,51 @@ var LarkGateway = class {
2332
2481
  }
2333
2482
  }
2334
2483
  sendStatus(chatId, status) {
2335
- return this.channel.send(chatId, { card: statusCard(status) });
2484
+ return this.sendMessage(chatId, { card: statusCard(status) });
2336
2485
  }
2337
2486
  sendSessionPicker(chatId, sessions, activeSessionId) {
2338
- return this.channel.send(chatId, { card: sessionPickerCard(chatId, sessions, activeSessionId, this.signer) });
2487
+ return this.sendMessage(chatId, { card: sessionPickerCard(chatId, sessions, activeSessionId, this.signer) });
2339
2488
  }
2340
2489
  sendResumePicker(chatId, session, picker) {
2341
- return this.channel.send(chatId, { card: resumePickerCard(chatId, session, picker, this.signer) });
2490
+ return this.sendMessage(chatId, { card: resumePickerCard(chatId, session, picker, this.signer) });
2342
2491
  }
2343
2492
  sendStartupConflict(chatId, request, owner) {
2344
- return this.channel.send(chatId, { card: startupConflictCard(chatId, requestSession(request), owner, this.signer) });
2493
+ return this.sendMessage(chatId, { card: startupConflictCard(chatId, requestSession(request), owner, this.signer) });
2345
2494
  }
2346
- async sendSessionCreate(chatId) {
2347
- const result2 = await this.channel.send(chatId, { card: sessionCreateCard() });
2348
- this.rememberSessionCreateFormAction(result2.messageId, chatId);
2495
+ async sendSessionCreate(chatId, view2) {
2496
+ const result2 = await this.sendMessage(chatId, { card: sessionCreateCard(view2) });
2497
+ this.rememberSessionCreateFormActions(result2.messageId, chatId, view2);
2349
2498
  return result2;
2350
2499
  }
2351
2500
  sendSessionStartupFailure(chatId, failure) {
2352
- return this.channel.send(chatId, { card: sessionStartupFailureCard(chatId, failure, this.signer) });
2501
+ return this.sendMessage(chatId, { card: sessionStartupFailureCard(chatId, failure, this.signer) });
2353
2502
  }
2354
2503
  sendStopConfirmation(chatId, paneId, fingerprint, agent) {
2355
- return this.channel.send(chatId, { card: stopCard(chatId, paneId, fingerprint, this.signer, agent) });
2504
+ return this.sendMessage(chatId, { card: stopCard(chatId, paneId, fingerprint, this.signer, agent) });
2505
+ }
2506
+ async sendMessage(chatId, input) {
2507
+ await this.clearProcessing(chatId);
2508
+ return this.channel.send(chatId, input);
2509
+ }
2510
+ async clearProcessing(chatId, expectedGeneration) {
2511
+ const currentGeneration = this.processingGenerations.get(chatId) ?? 0;
2512
+ if (expectedGeneration !== void 0 && currentGeneration !== expectedGeneration) return;
2513
+ this.processingGenerations.set(chatId, currentGeneration + 1);
2514
+ const pending = this.processingReactions.get(chatId);
2515
+ if (!pending) return;
2516
+ this.processingReactions.delete(chatId);
2517
+ clearTimeout(pending.timer);
2518
+ await this.removeProcessingReaction(pending.messageId, pending.reactionId);
2519
+ }
2520
+ async removeProcessingReaction(messageId, reactionId) {
2521
+ try {
2522
+ await this.channel.removeReaction(messageId, reactionId);
2523
+ } catch (error) {
2524
+ console.error(`[lca] failed to remove processing reaction by id: message=${messageId} detail=${cardErrorDetail(error)}`);
2525
+ await this.channel.removeReactionByEmoji(messageId, "Typing").catch((fallbackError) => {
2526
+ console.error(`[lca] failed to remove processing reaction by emoji: message=${messageId} detail=${cardErrorDetail(fallbackError)}`);
2527
+ });
2528
+ }
2356
2529
  }
2357
2530
  };
2358
2531
  function requestSession(request) {
@@ -2594,6 +2767,152 @@ function redactSecrets(value) {
2594
2767
  ).replace(/([?&](?:api[_-]?key|access[_-]?token|token|secret|signature)=)[^&#\s]+/gi, "$1[REDACTED]");
2595
2768
  }
2596
2769
 
2770
+ // src/workspace/discovery.ts
2771
+ import { lstat, readdir as readdir2, stat as stat2 } from "fs/promises";
2772
+ import { homedir as homedir3 } from "os";
2773
+ import { basename, dirname as dirname2, join as join2, relative } from "path";
2774
+ async function discoverWorkspaces(options) {
2775
+ const maxDepth = options.maxDepth ?? 1;
2776
+ const maxDirectories = options.maxDirectories ?? 500;
2777
+ const timeBudgetMs = options.timeBudgetMs ?? 2e3;
2778
+ const now = options.now ?? Date.now;
2779
+ const home = options.home ?? homedir3();
2780
+ const startedAt = now();
2781
+ const warnings = [];
2782
+ const byPath = /* @__PURE__ */ new Map();
2783
+ let visitedDirectories = 0;
2784
+ let partial = false;
2785
+ const budgetAvailable = () => {
2786
+ const available = visitedDirectories < maxDirectories && now() - startedAt <= timeBudgetMs;
2787
+ if (!available) partial = true;
2788
+ return available;
2789
+ };
2790
+ const add = async (input, source) => {
2791
+ if (!budgetAvailable()) return;
2792
+ let cwd;
2793
+ try {
2794
+ cwd = normalizeWorkspacePath(input, home);
2795
+ } catch {
2796
+ return;
2797
+ }
2798
+ if (byPath.has(cwd)) return;
2799
+ visitedDirectories += 1;
2800
+ try {
2801
+ const info = await lstat(cwd);
2802
+ if (!info.isDirectory() || info.isSymbolicLink()) return;
2803
+ byPath.set(cwd, { cwd, label: workspaceLabel(cwd, home), source, git: await hasGitEntry(cwd) });
2804
+ } catch {
2805
+ if (source === "configured") warnings.push(`\u65E0\u6CD5\u8BFB\u53D6 workspace\uFF1A${cwd}`);
2806
+ }
2807
+ };
2808
+ for (const session of options.activeSessions) await add(session.cwd, "active");
2809
+ for (const recent of [...options.recentWorkspaces].sort((a, b) => b.lastUsedAt - a.lastUsedAt)) {
2810
+ await add(recent.cwd, "recent");
2811
+ }
2812
+ for (const rootInput of options.workspaceRoots) {
2813
+ if (!budgetAvailable()) break;
2814
+ let root;
2815
+ try {
2816
+ root = normalizeWorkspacePath(rootInput, home);
2817
+ const info = await lstat(root);
2818
+ if (!info.isDirectory() || info.isSymbolicLink()) throw new Error("not a directory");
2819
+ } catch {
2820
+ warnings.push(`\u65E0\u6CD5\u8BFB\u53D6 workspace\uFF1A${rootInput}`);
2821
+ continue;
2822
+ }
2823
+ await add(root, "configured");
2824
+ let frontier = [root];
2825
+ for (let depth = 1; depth <= maxDepth && frontier.length > 0 && budgetAvailable(); depth += 1) {
2826
+ const next = [];
2827
+ for (const parent of frontier) {
2828
+ if (!budgetAvailable()) break;
2829
+ let entries;
2830
+ try {
2831
+ entries = await readdir2(parent, { withFileTypes: true });
2832
+ } catch {
2833
+ warnings.push(`\u65E0\u6CD5\u8BFB\u53D6\u76EE\u5F55\uFF1A${parent}`);
2834
+ continue;
2835
+ }
2836
+ for (const entry of entries) {
2837
+ if (!budgetAvailable()) break;
2838
+ if (!entry.isDirectory() || entry.isSymbolicLink() || entry.name.startsWith(".")) continue;
2839
+ const child = join2(parent, entry.name);
2840
+ await add(child, "configured");
2841
+ if (depth < maxDepth) next.push(child);
2842
+ }
2843
+ }
2844
+ frontier = next;
2845
+ }
2846
+ }
2847
+ const priority = { active: 0, recent: 1, configured: 2 };
2848
+ const candidates = [...byPath.values()].sort(
2849
+ (left, right) => priority[left.source] - priority[right.source] || Number(right.git) - Number(left.git) || left.label.localeCompare(right.label, "zh-CN")
2850
+ );
2851
+ return { candidates, partial, warnings: [...new Set(warnings)], visitedDirectories };
2852
+ }
2853
+ function workspaceLabel(cwd, home = homedir3()) {
2854
+ const parent = dirname2(cwd);
2855
+ const rel = relative(home, parent);
2856
+ const displayParent = rel === "" ? "~" : !rel.startsWith("..") ? `~/${rel}` : parent;
2857
+ return `${basename(cwd) || cwd} \xB7 ${displayParent}`;
2858
+ }
2859
+ async function hasGitEntry(cwd) {
2860
+ return stat2(join2(cwd, ".git")).then(() => true).catch(() => false);
2861
+ }
2862
+
2863
+ // src/workspace/recent.ts
2864
+ function rememberRecentWorkspace(existing, cwd, now = Date.now(), limit = 30) {
2865
+ const normalized = normalizeWorkspacePath(cwd);
2866
+ return [
2867
+ { cwd: normalized, lastUsedAt: now },
2868
+ ...(existing ?? []).filter((item) => normalizeWorkspacePath(item.cwd) !== normalized)
2869
+ ].slice(0, limit);
2870
+ }
2871
+
2872
+ // src/workspace/snapshot.ts
2873
+ import { randomBytes as randomBytes4 } from "crypto";
2874
+ var WorkspaceSnapshotStore = class {
2875
+ constructor(ttlMs = 10 * 6e4, capacity = 64, now = Date.now, createId = () => randomBytes4(12).toString("base64url")) {
2876
+ this.ttlMs = ttlMs;
2877
+ this.capacity = capacity;
2878
+ this.now = now;
2879
+ this.createId = createId;
2880
+ }
2881
+ ttlMs;
2882
+ capacity;
2883
+ now;
2884
+ createId;
2885
+ values = /* @__PURE__ */ new Map();
2886
+ create(input) {
2887
+ const now = this.now();
2888
+ this.prune(now);
2889
+ const snapshot = {
2890
+ ...input,
2891
+ id: this.createId(),
2892
+ createdAt: now,
2893
+ expiresAt: now + this.ttlMs
2894
+ };
2895
+ this.values.set(snapshot.id, snapshot);
2896
+ while (this.values.size > this.capacity) {
2897
+ const oldest = this.values.keys().next().value;
2898
+ if (!oldest) break;
2899
+ this.values.delete(oldest);
2900
+ }
2901
+ return snapshot;
2902
+ }
2903
+ get(id, chatId, ownerOpenId) {
2904
+ const now = this.now();
2905
+ this.prune(now);
2906
+ const value = this.values.get(id);
2907
+ return value?.chatId === chatId && value.ownerOpenId === ownerOpenId ? value : void 0;
2908
+ }
2909
+ prune(now) {
2910
+ for (const [id, value] of this.values) {
2911
+ if (value.expiresAt <= now) this.values.delete(id);
2912
+ }
2913
+ }
2914
+ };
2915
+
2597
2916
  // src/daemon/server.ts
2598
2917
  var AssistantDaemon = class {
2599
2918
  constructor(store, paths2, gatewayFactory = (config, secrets, handler) => new LarkGateway(config, secrets, handler), sessionName = "lark-coding-assistant", stopHookCommand = "lark-coding-assistant-hook", completionQuietMs = 2500, appVersion = "dev") {
@@ -2635,6 +2954,7 @@ var AssistantDaemon = class {
2635
2954
  pendingAgentSessionClaims = /* @__PURE__ */ new Map();
2636
2955
  pendingResumePickers = /* @__PURE__ */ new Map();
2637
2956
  pendingStartupConflicts = /* @__PURE__ */ new Map();
2957
+ workspaceSnapshots = new WorkspaceSnapshotStore();
2638
2958
  pendingInteractionInput;
2639
2959
  attachAttempts = /* @__PURE__ */ new Map();
2640
2960
  server = createServer((socket) => this.handleSocket(socket));
@@ -2661,9 +2981,9 @@ var AssistantDaemon = class {
2661
2981
  try {
2662
2982
  await this.reconcileSessions(true);
2663
2983
  await rm(this.paths.socket, { force: true });
2664
- await new Promise((resolve, reject) => {
2984
+ await new Promise((resolve2, reject) => {
2665
2985
  this.server.once("error", reject);
2666
- this.server.listen(this.paths.socket, () => resolve());
2986
+ this.server.listen(this.paths.socket, () => resolve2());
2667
2987
  });
2668
2988
  await chmod3(this.paths.socket, 384);
2669
2989
  this.gateway = this.gatewayFactory(config, secrets, {
@@ -2686,7 +3006,7 @@ var AssistantDaemon = class {
2686
3006
  if (this.timer) clearTimeout(this.timer);
2687
3007
  await this.pollInFlight?.catch(() => void 0);
2688
3008
  await this.gateway?.disconnect().catch(() => void 0);
2689
- await new Promise((resolve) => this.server.close(() => resolve()));
3009
+ await new Promise((resolve2) => this.server.close(() => resolve2()));
2690
3010
  await this.releaseRuntimeFiles();
2691
3011
  }
2692
3012
  async acquireRuntimeFiles() {
@@ -2773,7 +3093,8 @@ var AssistantDaemon = class {
2773
3093
  }
2774
3094
  async startSession(sessionId, cwd, agentId, resume) {
2775
3095
  try {
2776
- await validateStartSessionRequest({ sessionId, cwd, agent: agentId, resume });
3096
+ const validated = await validateStartSessionRequest({ sessionId, cwd, agent: agentId, resume });
3097
+ cwd = validated.cwd;
2777
3098
  } catch (error) {
2778
3099
  return fail(error);
2779
3100
  }
@@ -2901,6 +3222,7 @@ var AssistantDaemon = class {
2901
3222
  await this.tmux.preserveOnExit(session.sessionName, false).catch((error) => this.log(
2902
3223
  `failed to disable startup preservation for ${sessionId}: ${errorMessage3(error)}`
2903
3224
  ));
3225
+ await this.rememberSessionWorkspace(session.cwd);
2904
3226
  }
2905
3227
  await this.log(
2906
3228
  `session created: session=${session.id} agent=${session.agent} pane=${session.paneId} active=${this.state.activeSessionId === session.id}`
@@ -2985,7 +3307,7 @@ var AssistantDaemon = class {
2985
3307
  if (message.senderId !== this.state.ownerOpenId || message.chatId !== this.state.boundChatId) return;
2986
3308
  if (text === "/start") {
2987
3309
  try {
2988
- await this.gateway?.sendSessionCreate(message.chatId);
3310
+ await this.gateway?.sendSessionCreate(message.chatId, await this.createSessionWorkspaceView(message.chatId));
2989
3311
  } catch (error) {
2990
3312
  await this.log(`session create card failed: ${errorMessage3(error)}`);
2991
3313
  await this.gateway?.sendText(message.chatId, "\u65B0\u5EFA Session \u8868\u5355\u53D1\u9001\u5931\u8D25\u3002\u8BF7\u4F7F\u7528 /start <name> --agent <agent> --cwd <\u7EDD\u5BF9\u8DEF\u5F84>\u3002");
@@ -3178,6 +3500,7 @@ ${escapeFence2(output).slice(-6800)}
3178
3500
  await this.gateway?.sendText(message.chatId, `\u5DF2\u5411 ${getAgentAdapter(session.agent).displayName} \u63D0\u4EA4\u8865\u5145\u5185\u5BB9\u3002`);
3179
3501
  return;
3180
3502
  }
3503
+ await this.gateway?.startProcessing(message);
3181
3504
  await this.poll();
3182
3505
  if (this.shouldQueueMessage()) {
3183
3506
  if (this.pendingMessages.length >= 100) {
@@ -3249,7 +3572,11 @@ ${escapeFence2(output).slice(-6800)}
3249
3572
  }
3250
3573
  if (action.kind === "session-stop") return this.handleSessionStopAction(action);
3251
3574
  if (action.kind === "session-start-error") {
3252
- if (action.action === "create") return { type: "session-create-form", content: "\u8BF7\u586B\u5199\u542F\u52A8\u4FE1\u606F\u3002" };
3575
+ if (action.action === "create") return {
3576
+ type: "session-create-form",
3577
+ content: "\u8BF7\u586B\u5199\u542F\u52A8\u4FE1\u606F\u3002",
3578
+ view: await this.createSessionWorkspaceView(event.chatId)
3579
+ };
3253
3580
  if (action.action === "sessions") {
3254
3581
  await this.reconcileSessions(true);
3255
3582
  return this.sessionPickerActionResult("\u5DF2\u53D1\u9001\u6700\u65B0 Sessions\u3002");
@@ -3263,15 +3590,53 @@ ${escapeFence2(output).slice(-6800)}
3263
3590
  return {
3264
3591
  type: "session-create-form",
3265
3592
  content: "\u8BF7\u586B\u5199\u542F\u52A8\u4FE1\u606F\u3002",
3593
+ view: await this.createSessionWorkspaceView(event.chatId),
3266
3594
  sessions: Object.values(this.state.sessions ?? {}),
3267
3595
  activeSessionId: this.state.activeSessionId
3268
3596
  };
3269
3597
  }
3270
- if (action.action !== "submit" || !event.action.formValue) {
3598
+ const snapshot = action.snapshotId ? this.workspaceSnapshots.get(action.snapshotId, event.chatId, event.operator.openId) : void 0;
3599
+ if (!snapshot && action.snapshotId) {
3600
+ return {
3601
+ type: "session-create-form",
3602
+ content: "\u9879\u76EE\u76EE\u5F55\u5217\u8868\u5DF2\u8FC7\u671F\uFF0C\u5DF2\u91CD\u65B0\u52A0\u8F7D\u3002",
3603
+ view: await this.createSessionWorkspaceView(event.chatId)
3604
+ };
3605
+ }
3606
+ if (action.action !== "submit") return { type: "error", content: "\u65E0\u6CD5\u8BC6\u522B\u65B0\u5EFA Session \u64CD\u4F5C\u3002" };
3607
+ if (!event.action.formValue) {
3271
3608
  return { type: "error", content: "\u65E0\u6CD5\u8BC6\u522B\u65B0\u5EFA Session \u8868\u5355\uFF0C\u8BF7\u91CD\u65B0\u53D1\u9001 /sessions\u3002" };
3272
3609
  }
3273
- const request = startRequestFromForm(event.action.formValue);
3274
- if (!request.ok) return { type: "error", content: request.error };
3610
+ const submittedResumeMode = formString(event.action.formValue[SESSION_CREATE_RESUME_FIELD]) || "new";
3611
+ if (submittedResumeMode === "last") {
3612
+ return { type: "error", content: "\u98DE\u4E66\u5DF2\u4E0D\u518D\u652F\u6301\u201C\u6062\u590D\u4E0A\u6B21\u4F1A\u8BDD\u201D\uFF0C\u8BF7\u91CD\u65B0\u6253\u5F00\u8868\u5355\u5E76\u4F7F\u7528 Resume Picker\u3002" };
3613
+ }
3614
+ if (submittedResumeMode !== "new" && submittedResumeMode !== "picker") {
3615
+ return { type: "error", content: "\u65E0\u6CD5\u8BC6\u522B\u542F\u52A8\u65B9\u5F0F\uFF0C\u8BF7\u91CD\u65B0\u6253\u5F00\u65B0\u5EFA Session \u8868\u5355\u3002" };
3616
+ }
3617
+ const submittedAgent = formString(event.action.formValue[SESSION_CREATE_AGENT_FIELD]);
3618
+ if (!normalizeAgentId(submittedAgent)) {
3619
+ return { type: "error", content: "\u8BF7\u9009\u62E9\u6709\u6548\u7684 Agent\uFF1Acodex\u3001traex \u6216 claude\u3002" };
3620
+ }
3621
+ const draft = sessionCreateDraftFromForm(event.action.formValue);
3622
+ const submittedProject = formString(event.action.formValue[SESSION_CREATE_PROJECT_FIELD]);
3623
+ const manualForm = action.fingerprint === "manual";
3624
+ const selectedProject = submittedProject !== SESSION_CREATE_MANUAL_VALUE ? submittedProject : "";
3625
+ if (selectedProject && !manualForm && (!snapshot || !snapshot.candidates.some((candidate) => candidate.cwd === selectedProject))) {
3626
+ return {
3627
+ type: "session-create-form",
3628
+ content: "\u9879\u76EE\u76EE\u5F55\u5217\u8868\u5DF2\u53D8\u5316\uFF0C\u5DF2\u91CD\u65B0\u52A0\u8F7D\u3002",
3629
+ view: await this.createSessionWorkspaceView(event.chatId)
3630
+ };
3631
+ }
3632
+ const request = startRequestFromDraft(draft);
3633
+ if (!request.ok) {
3634
+ return {
3635
+ type: "session-create-form",
3636
+ content: request.error,
3637
+ view: this.sessionCreateRetryView(snapshot, draft)
3638
+ };
3639
+ }
3275
3640
  const result3 = await this.startRemoteSession(request.value);
3276
3641
  if (!result3.ok) return larkStartupError(result3.error, request.value);
3277
3642
  if (result3.state === "picker") {
@@ -3476,6 +3841,7 @@ ${escapeFence2(output).slice(-6800)}
3476
3841
  await this.tmux.preserveOnExit(session.sessionName, false).catch(() => void 0);
3477
3842
  const selected = await this.useSession(sessionId);
3478
3843
  if (!selected.ok) return { type: "error", content: remoteError(selected) };
3844
+ await this.rememberSessionWorkspace(session.cwd);
3479
3845
  return { type: "session-created", content: remoteStartSuccess(session), session };
3480
3846
  }
3481
3847
  async readResumePicker(session) {
@@ -3496,7 +3862,7 @@ ${escapeFence2(output).slice(-6800)}
3496
3862
  while (Date.now() < deadline) {
3497
3863
  latest = await this.readResumePicker(session);
3498
3864
  if (latest && (!previousFingerprint || latest.fingerprint !== previousFingerprint)) return latest;
3499
- await new Promise((resolve) => setTimeout(resolve, 100));
3865
+ await new Promise((resolve2) => setTimeout(resolve2, 100));
3500
3866
  }
3501
3867
  return latest;
3502
3868
  }
@@ -3509,7 +3875,7 @@ ${escapeFence2(output).slice(-6800)}
3509
3875
  let previousFingerprint;
3510
3876
  const deadline = Date.now() + 900;
3511
3877
  do {
3512
- await new Promise((resolve) => setTimeout(resolve, 120));
3878
+ await new Promise((resolve2) => setTimeout(resolve2, 120));
3513
3879
  await this.poll();
3514
3880
  const fingerprint = this.screen?.fingerprint;
3515
3881
  if (fingerprint && fingerprint === previousFingerprint) return;
@@ -3537,7 +3903,7 @@ ${escapeFence2(output).slice(-6800)}
3537
3903
  }
3538
3904
  try {
3539
3905
  await execute(session);
3540
- await new Promise((resolve) => setTimeout(resolve, 120));
3906
+ await new Promise((resolve2) => setTimeout(resolve2, 120));
3541
3907
  await this.poll();
3542
3908
  const output = this.screen ? tailScreen(this.screen.normalized, 60) : "\u65E0\u6CD5\u8BFB\u53D6\u6700\u65B0\u7EC8\u7AEF\u753B\u9762\u3002";
3543
3909
  await this.gateway?.sendMarkdown(chatId, `**\u624B\u52A8\u64CD\u4F5C\uFF1A${operation}**
@@ -3588,7 +3954,7 @@ ${escapeFence2(output).slice(-6500)}
3588
3954
  };
3589
3955
  }
3590
3956
  if (target?.role === "custom-input" || target?.role === "chat") {
3591
- await new Promise((resolve) => setTimeout(resolve, 100));
3957
+ await new Promise((resolve2) => setTimeout(resolve2, 100));
3592
3958
  await this.poll();
3593
3959
  if (!before?.interactionId) return { type: "error", content: "\u65E0\u6CD5\u786E\u8BA4\u5F53\u524D\u4EA4\u4E92\uFF0C\u8BF7\u7528 /tail \u68C0\u67E5\u3002" };
3594
3960
  this.pendingInteractionInput = {
@@ -3721,7 +4087,7 @@ ${escapeFence2(output).slice(-6500)}
3721
4087
  if (focusedIndex === targetIndex) return { ok: true };
3722
4088
  const direction = targetIndex > focusedIndex ? "Down" : "Up";
3723
4089
  await this.tmux.sendKey(paneId, direction);
3724
- await new Promise((resolve) => setTimeout(resolve, 40));
4090
+ await new Promise((resolve2) => setTimeout(resolve2, 40));
3725
4091
  await this.poll();
3726
4092
  const nextFocused = this.screen?.actions.findIndex(({ focused }) => focused) ?? -1;
3727
4093
  if (nextFocused === focusedIndex) return { ok: false, error: "terminal focus did not move as expected" };
@@ -3731,7 +4097,7 @@ ${escapeFence2(output).slice(-6500)}
3731
4097
  async waitForInteractionChange(interactionId) {
3732
4098
  const deadline = Date.now() + 1500;
3733
4099
  while (Date.now() < deadline) {
3734
- await new Promise((resolve) => setTimeout(resolve, 75));
4100
+ await new Promise((resolve2) => setTimeout(resolve2, 75));
3735
4101
  await this.poll();
3736
4102
  const current = this.screen?.interaction;
3737
4103
  if (!current || current.interactionId !== interactionId) return;
@@ -3740,7 +4106,7 @@ ${escapeFence2(output).slice(-6500)}
3740
4106
  async waitForControlMarker(interactionId, controlId, marker) {
3741
4107
  const deadline = Date.now() + 1500;
3742
4108
  while (Date.now() < deadline) {
3743
- await new Promise((resolve) => setTimeout(resolve, 75));
4109
+ await new Promise((resolve2) => setTimeout(resolve2, 75));
3744
4110
  await this.poll();
3745
4111
  const current = this.screen?.interaction;
3746
4112
  const control = this.screen?.actions.find(({ id }) => id === controlId);
@@ -3751,7 +4117,7 @@ ${escapeFence2(output).slice(-6500)}
3751
4117
  const expected = input.trim();
3752
4118
  const deadline = Date.now() + 1500;
3753
4119
  while (Date.now() < deadline) {
3754
- await new Promise((resolve) => setTimeout(resolve, 75));
4120
+ await new Promise((resolve2) => setTimeout(resolve2, 75));
3755
4121
  await this.poll();
3756
4122
  const control = this.screen?.actions.find(({ id }) => id === controlId);
3757
4123
  if (this.screen?.interaction?.interactionId === interactionId && control?.inputValue && (control.inputValue === expected || expected.startsWith(control.inputValue))) return;
@@ -3796,7 +4162,7 @@ ${escapeFence2(output).slice(-6500)}
3796
4162
  if (!navigation.ok) return navigation;
3797
4163
  if (desired.editor) {
3798
4164
  if (desired.editor.openKey) await this.tmux.sendKey(paneId, desired.editor.openKey);
3799
- await new Promise((resolve) => setTimeout(resolve, 100));
4165
+ await new Promise((resolve2) => setTimeout(resolve2, 100));
3800
4166
  await this.tmux.sendKey(paneId, "C-u");
3801
4167
  await this.tmux.sendKey(paneId, "C-k");
3802
4168
  await this.tmux.sendText(paneId, desired.input, false);
@@ -4078,7 +4444,7 @@ ${escapeFence2(output).slice(-6500)}
4078
4444
  source: "startup-discovery"
4079
4445
  });
4080
4446
  }
4081
- await new Promise((resolve) => setTimeout(resolve, 100));
4447
+ await new Promise((resolve2) => setTimeout(resolve2, 100));
4082
4448
  }
4083
4449
  const session = this.state.sessions?.[sessionId];
4084
4450
  if (!session) return fail(new AppError("SESSION_NOT_FOUND", `session disappeared during startup: ${sessionId}`, { sessionId }));
@@ -4109,7 +4475,7 @@ ${escapeFence2(output).slice(-6500)}
4109
4475
  while (Date.now() < deadline) {
4110
4476
  const pane = await this.tmux.inspect(session.paneId);
4111
4477
  if (!pane || pane.dead) return fail(await this.startupExitedError(session, pane));
4112
- await new Promise((resolve) => setTimeout(resolve, 80));
4478
+ await new Promise((resolve2) => setTimeout(resolve2, 80));
4113
4479
  }
4114
4480
  return { ok: true };
4115
4481
  }
@@ -4277,6 +4643,67 @@ ${output}`
4277
4643
  }
4278
4644
  return { ok: true, state: "ready", session };
4279
4645
  }
4646
+ async createSessionWorkspaceView(chatId, draft = emptySessionCreateDraft()) {
4647
+ await this.reconcileSessions(true);
4648
+ const latestConfig = await this.store.loadConfig();
4649
+ const discovered = await discoverWorkspaces({
4650
+ workspaceRoots: latestConfig?.workspaceRoots ?? this.config.workspaceRoots,
4651
+ activeSessions: Object.values(this.state.sessions ?? {}),
4652
+ recentWorkspaces: this.state.recentWorkspaces ?? []
4653
+ });
4654
+ for (const warning of discovered.warnings) await this.log(`workspace discovery warning: ${warning}`);
4655
+ const snapshot = this.workspaceSnapshots.create({
4656
+ chatId,
4657
+ ownerOpenId: this.state.ownerOpenId ?? "",
4658
+ candidates: discovered.candidates,
4659
+ warnings: discovered.warnings,
4660
+ partial: discovered.partial
4661
+ });
4662
+ return discovered.candidates.length > 0 ? this.sessionWorkspaceView(snapshot, draft) : this.manualSessionCreateView(snapshot, draft);
4663
+ }
4664
+ sessionWorkspaceView(snapshot, draft) {
4665
+ const visibleCandidates = snapshot.candidates.slice(0, 99);
4666
+ return {
4667
+ mode: "projects",
4668
+ snapshotId: snapshot.id,
4669
+ page: 0,
4670
+ pageCount: 1,
4671
+ hasProjectCandidates: snapshot.candidates.length > 0,
4672
+ candidates: visibleCandidates,
4673
+ warnings: snapshot.warnings,
4674
+ partial: snapshot.partial || visibleCandidates.length < snapshot.candidates.length,
4675
+ draft
4676
+ };
4677
+ }
4678
+ sessionCreateRetryView(snapshot, draft) {
4679
+ return snapshot && snapshot.candidates.length > 0 ? this.sessionWorkspaceView(snapshot, draft) : this.manualSessionCreateView(snapshot, draft);
4680
+ }
4681
+ manualSessionCreateView(snapshot, draft, requestedPage = 0) {
4682
+ const pageCount = Math.max(1, Math.ceil((snapshot?.candidates.length ?? 0) / 20));
4683
+ const page = Math.max(0, Math.min(pageCount - 1, requestedPage));
4684
+ const noCandidates = snapshot && snapshot.candidates.length === 0 ? ["\u5C1A\u672A\u53D1\u73B0\u53EF\u9009\u9879\u76EE\uFF1B\u8BF7\u624B\u52A8\u586B\u5199\u8DEF\u5F84\uFF0C\u6216\u5728\u672C\u673A\u8FD0\u884C lca workspace add ~/workspace\u3002"] : [];
4685
+ return {
4686
+ mode: "manual",
4687
+ snapshotId: snapshot?.id,
4688
+ page,
4689
+ hasProjectCandidates: (snapshot?.candidates.length ?? 0) > 0,
4690
+ pageCount,
4691
+ candidates: [],
4692
+ warnings: [...snapshot?.warnings ?? [], ...noCandidates],
4693
+ partial: snapshot?.partial ?? false,
4694
+ draft: { ...draft, manualCwd: draft.manualCwd ?? (draft.cwd !== SESSION_CREATE_MANUAL_VALUE ? draft.cwd : void 0) }
4695
+ };
4696
+ }
4697
+ async rememberSessionWorkspace(cwd) {
4698
+ this.state = {
4699
+ ...this.state,
4700
+ recentWorkspaces: rememberRecentWorkspace(this.state.recentWorkspaces, cwd),
4701
+ updatedAt: Date.now()
4702
+ };
4703
+ await this.store.saveState(this.state).catch((error) => this.log(
4704
+ `failed to persist recent workspace: cwd=${cwd} error=${errorMessage3(error)}`
4705
+ ));
4706
+ }
4280
4707
  async log(message) {
4281
4708
  await appendFile(this.paths.logFile, `${(/* @__PURE__ */ new Date()).toISOString()} ${message}
4282
4709
  `, { mode: 384 });
@@ -4314,24 +4741,29 @@ function remember(values, value, limit) {
4314
4741
  values.delete(oldest);
4315
4742
  }
4316
4743
  }
4317
- function startRequestFromForm(values) {
4744
+ function sessionCreateDraftFromForm(values) {
4318
4745
  const sessionId = formString(values[SESSION_CREATE_NAME_FIELD]);
4319
4746
  const agentValue = formString(values[SESSION_CREATE_AGENT_FIELD]);
4320
- const cwd = formString(values[SESSION_CREATE_CWD_FIELD]);
4747
+ const projectCwd = formString(values[SESSION_CREATE_PROJECT_FIELD]);
4748
+ const manualCwd = formString(values[SESSION_CREATE_CWD_FIELD]);
4321
4749
  const resumeMode = formString(values[SESSION_CREATE_RESUME_FIELD]) || "new";
4750
+ const cwd = projectCwd === SESSION_CREATE_MANUAL_VALUE ? manualCwd : projectCwd || manualCwd;
4751
+ return {
4752
+ sessionId: sessionId || void 0,
4753
+ agent: normalizeAgentId(agentValue) ?? "codex",
4754
+ resumeMode: resumeMode === "picker" ? "picker" : "new",
4755
+ cwd: cwd || void 0,
4756
+ projectCwd: projectCwd || void 0,
4757
+ manualCwd: manualCwd || void 0
4758
+ };
4759
+ }
4760
+ function startRequestFromDraft(draft) {
4761
+ const sessionId = draft.sessionId?.trim() ?? "";
4322
4762
  if (!sessionId) return { ok: false, error: "\u8BF7\u586B\u5199 Session \u540D\u79F0\u3002" };
4323
- const agent = normalizeAgentId(agentValue);
4324
- if (!agent) return { ok: false, error: "\u8BF7\u9009\u62E9\u6709\u6548\u7684 Agent\uFF1Acodex\u3001traex \u6216 claude\u3002" };
4325
- if (!cwd) return { ok: false, error: "\u8BF7\u586B\u5199\u7EDD\u5BF9\u5DE5\u4F5C\u76EE\u5F55\u3002" };
4763
+ if (!draft.cwd || draft.cwd === SESSION_CREATE_MANUAL_VALUE) return { ok: false, error: "\u8BF7\u9009\u62E9\u6216\u586B\u5199\u9879\u76EE\u76EE\u5F55\u3002" };
4326
4764
  let resume;
4327
- if (resumeMode === "last") {
4328
- return { ok: false, error: "\u98DE\u4E66\u5DF2\u4E0D\u518D\u652F\u6301\u201C\u6062\u590D\u4E0A\u6B21\u4F1A\u8BDD\u201D\uFF0C\u8BF7\u91CD\u65B0\u6253\u5F00\u8868\u5355\u5E76\u4F7F\u7528 Resume Picker\u3002" };
4329
- }
4330
- if (resumeMode === "picker") resume = { mode: "picker" };
4331
- else if (resumeMode !== "new") {
4332
- return { ok: false, error: "\u65E0\u6CD5\u8BC6\u522B\u542F\u52A8\u65B9\u5F0F\uFF0C\u8BF7\u91CD\u65B0\u6253\u5F00\u65B0\u5EFA Session \u8868\u5355\u3002" };
4333
- }
4334
- return { ok: true, value: { sessionId, agent, cwd, resume } };
4765
+ if (draft.resumeMode === "picker") resume = { mode: "picker" };
4766
+ return { ok: true, value: { sessionId, agent: draft.agent, cwd: draft.cwd, resume } };
4335
4767
  }
4336
4768
  function formString(value) {
4337
4769
  if (typeof value === "string") return value.trim();
@@ -4353,7 +4785,7 @@ function remoteError(result2) {
4353
4785
  const sessionId = typeof context.sessionId === "string" ? context.sessionId : "\u8BE5\u540D\u79F0";
4354
4786
  switch (result2.errorCode) {
4355
4787
  case "SESSION_EXISTS":
4356
- return `\u65E0\u6CD5\u542F\u52A8 session\u300C${sessionId}\u300D\uFF1A\u8BE5 session \u5DF2\u5728\u8FD0\u884C\u3002\u8BF7\u6362\u4E00\u4E2A\u540D\u79F0\uFF0C\u6216\u7528 /sessions \u8FDE\u63A5\u73B0\u6709 session\u3002`;
4788
+ return context.source === "tmux" ? `\u65E0\u6CD5\u542F\u52A8 session\u300C${sessionId}\u300D\uFF1A\u68C0\u6D4B\u5230\u540C\u540D tmux \u4F1A\u8BDD\uFF0C\u4F46\u5B83\u672A\u767B\u8BB0\u4E3A\u53EF\u8FDE\u63A5\u7684 LCA session\u3002\u8BF7\u6362\u4E00\u4E2A\u540D\u79F0\uFF0C\u6216\u5728\u672C\u673A\u68C0\u67E5 tmux \u4F1A\u8BDD\u3002` : `\u65E0\u6CD5\u542F\u52A8 session\u300C${sessionId}\u300D\uFF1A\u8BE5 session \u5DF2\u5728\u8FD0\u884C\u3002\u8BF7\u6362\u4E00\u4E2A\u540D\u79F0\uFF0C\u6216\u7528 /sessions \u8FDE\u63A5\u73B0\u6709 session\u3002`;
4357
4789
  case "AGENT_SESSION_IN_USE": {
4358
4790
  const ownerSessionId = typeof context.ownerSessionId === "string" ? context.ownerSessionId : "\u73B0\u6709 session";
4359
4791
  return `\u65E0\u6CD5\u542F\u52A8 session\u300C${sessionId}\u300D\uFF1A\u8BE5 Agent \u539F\u751F session \u5DF2\u7531 LCA session\u300C${ownerSessionId}\u300D\u8FDE\u63A5\u3002\u8BF7\u7528 /sessions \u8FDE\u63A5\u73B0\u6709 session\u3002`;
@@ -4429,20 +4861,20 @@ function manualTimestamp() {
4429
4861
  }
4430
4862
 
4431
4863
  // src/core/paths.ts
4432
- import { homedir as homedir2 } from "os";
4433
- import { join as join2 } from "path";
4864
+ import { homedir as homedir4 } from "os";
4865
+ import { join as join3 } from "path";
4434
4866
  function resolveAppPaths(root = process.env.LARK_CODING_ASSISTANT_HOME) {
4435
- const base = root || join2(homedir2(), ".lark-coding-assistant");
4867
+ const base = root || join3(homedir4(), ".lark-coding-assistant");
4436
4868
  return {
4437
4869
  root: base,
4438
- config: join2(base, "config.json"),
4439
- secrets: join2(base, "secrets.json"),
4440
- state: join2(base, "state.json"),
4441
- logsDir: join2(base, "logs"),
4442
- logFile: join2(base, "logs", "assistant.log"),
4443
- runtimeDir: join2(base, "runtime"),
4444
- socket: join2(base, "runtime", "daemon.sock"),
4445
- pid: join2(base, "runtime", "daemon.pid")
4870
+ config: join3(base, "config.json"),
4871
+ secrets: join3(base, "secrets.json"),
4872
+ state: join3(base, "state.json"),
4873
+ logsDir: join3(base, "logs"),
4874
+ logFile: join3(base, "logs", "assistant.log"),
4875
+ runtimeDir: join3(base, "runtime"),
4876
+ socket: join3(base, "runtime", "daemon.sock"),
4877
+ pid: join3(base, "runtime", "daemon.pid")
4446
4878
  };
4447
4879
  }
4448
4880