lark-coding-assistant 0.2.2 → 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.
- package/README.md +23 -3
- package/dist/cli.js +178 -61
- package/dist/cli.js.map +1 -1
- package/dist/daemon-entry.js +517 -132
- package/dist/daemon-entry.js.map +1 -1
- package/package.json +1 -1
package/dist/daemon-entry.js
CHANGED
|
@@ -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((
|
|
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
|
-
|
|
23
|
+
resolve2({ stdout, stderr });
|
|
24
24
|
}
|
|
25
25
|
);
|
|
26
26
|
});
|
|
27
27
|
}
|
|
28
28
|
function runFileWithInput(file, args, input) {
|
|
29
|
-
return new Promise((
|
|
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)
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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)
|
|
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 =
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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.
|
|
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") {
|
|
@@ -2179,8 +2300,13 @@ var LarkGateway = class {
|
|
|
2179
2300
|
return;
|
|
2180
2301
|
}
|
|
2181
2302
|
if (result2.type === "session-create-form") {
|
|
2182
|
-
|
|
2183
|
-
|
|
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);
|
|
2184
2310
|
const sourceCard = action.kind === "session-create" && result2.sessions ? sessionPickerCard(event.chatId, result2.sessions, result2.activeSessionId, this.signer, void 0, false) : sessionCreateOpenedCard();
|
|
2185
2311
|
await this.updateCardAfterAction(event.messageId, sourceCard).catch(async (error) => {
|
|
2186
2312
|
const detail = cardErrorDetail(error);
|
|
@@ -2227,7 +2353,7 @@ var LarkGateway = class {
|
|
|
2227
2353
|
const retryDelays = [0, 300, 800, 1600];
|
|
2228
2354
|
let lastError;
|
|
2229
2355
|
for (const delay of retryDelays) {
|
|
2230
|
-
if (delay > 0) await new Promise((
|
|
2356
|
+
if (delay > 0) await new Promise((resolve2) => setTimeout(resolve2, delay));
|
|
2231
2357
|
try {
|
|
2232
2358
|
await this.channel.updateCard(messageId, card2);
|
|
2233
2359
|
return;
|
|
@@ -2318,18 +2444,19 @@ var LarkGateway = class {
|
|
|
2318
2444
|
[MANUAL_SUBMIT_ACTION, this.signer.sign({ ...common, action: MANUAL_SUBMIT_ACTION }, 10 * 6e4)]
|
|
2319
2445
|
]));
|
|
2320
2446
|
}
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
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);
|
|
2333
2460
|
}
|
|
2334
2461
|
rememberFormActions(messageId, chatId, paneId, screen, agent) {
|
|
2335
2462
|
if (screen.interaction?.semantics?.activation !== "toggle") {
|
|
@@ -2365,9 +2492,9 @@ var LarkGateway = class {
|
|
|
2365
2492
|
sendStartupConflict(chatId, request, owner) {
|
|
2366
2493
|
return this.sendMessage(chatId, { card: startupConflictCard(chatId, requestSession(request), owner, this.signer) });
|
|
2367
2494
|
}
|
|
2368
|
-
async sendSessionCreate(chatId) {
|
|
2369
|
-
const result2 = await this.sendMessage(chatId, { card: sessionCreateCard() });
|
|
2370
|
-
this.
|
|
2495
|
+
async sendSessionCreate(chatId, view2) {
|
|
2496
|
+
const result2 = await this.sendMessage(chatId, { card: sessionCreateCard(view2) });
|
|
2497
|
+
this.rememberSessionCreateFormActions(result2.messageId, chatId, view2);
|
|
2371
2498
|
return result2;
|
|
2372
2499
|
}
|
|
2373
2500
|
sendSessionStartupFailure(chatId, failure) {
|
|
@@ -2640,6 +2767,152 @@ function redactSecrets(value) {
|
|
|
2640
2767
|
).replace(/([?&](?:api[_-]?key|access[_-]?token|token|secret|signature)=)[^&#\s]+/gi, "$1[REDACTED]");
|
|
2641
2768
|
}
|
|
2642
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
|
+
|
|
2643
2916
|
// src/daemon/server.ts
|
|
2644
2917
|
var AssistantDaemon = class {
|
|
2645
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") {
|
|
@@ -2681,6 +2954,7 @@ var AssistantDaemon = class {
|
|
|
2681
2954
|
pendingAgentSessionClaims = /* @__PURE__ */ new Map();
|
|
2682
2955
|
pendingResumePickers = /* @__PURE__ */ new Map();
|
|
2683
2956
|
pendingStartupConflicts = /* @__PURE__ */ new Map();
|
|
2957
|
+
workspaceSnapshots = new WorkspaceSnapshotStore();
|
|
2684
2958
|
pendingInteractionInput;
|
|
2685
2959
|
attachAttempts = /* @__PURE__ */ new Map();
|
|
2686
2960
|
server = createServer((socket) => this.handleSocket(socket));
|
|
@@ -2707,9 +2981,9 @@ var AssistantDaemon = class {
|
|
|
2707
2981
|
try {
|
|
2708
2982
|
await this.reconcileSessions(true);
|
|
2709
2983
|
await rm(this.paths.socket, { force: true });
|
|
2710
|
-
await new Promise((
|
|
2984
|
+
await new Promise((resolve2, reject) => {
|
|
2711
2985
|
this.server.once("error", reject);
|
|
2712
|
-
this.server.listen(this.paths.socket, () =>
|
|
2986
|
+
this.server.listen(this.paths.socket, () => resolve2());
|
|
2713
2987
|
});
|
|
2714
2988
|
await chmod3(this.paths.socket, 384);
|
|
2715
2989
|
this.gateway = this.gatewayFactory(config, secrets, {
|
|
@@ -2732,7 +3006,7 @@ var AssistantDaemon = class {
|
|
|
2732
3006
|
if (this.timer) clearTimeout(this.timer);
|
|
2733
3007
|
await this.pollInFlight?.catch(() => void 0);
|
|
2734
3008
|
await this.gateway?.disconnect().catch(() => void 0);
|
|
2735
|
-
await new Promise((
|
|
3009
|
+
await new Promise((resolve2) => this.server.close(() => resolve2()));
|
|
2736
3010
|
await this.releaseRuntimeFiles();
|
|
2737
3011
|
}
|
|
2738
3012
|
async acquireRuntimeFiles() {
|
|
@@ -2819,7 +3093,8 @@ var AssistantDaemon = class {
|
|
|
2819
3093
|
}
|
|
2820
3094
|
async startSession(sessionId, cwd, agentId, resume) {
|
|
2821
3095
|
try {
|
|
2822
|
-
await validateStartSessionRequest({ sessionId, cwd, agent: agentId, resume });
|
|
3096
|
+
const validated = await validateStartSessionRequest({ sessionId, cwd, agent: agentId, resume });
|
|
3097
|
+
cwd = validated.cwd;
|
|
2823
3098
|
} catch (error) {
|
|
2824
3099
|
return fail(error);
|
|
2825
3100
|
}
|
|
@@ -2947,6 +3222,7 @@ var AssistantDaemon = class {
|
|
|
2947
3222
|
await this.tmux.preserveOnExit(session.sessionName, false).catch((error) => this.log(
|
|
2948
3223
|
`failed to disable startup preservation for ${sessionId}: ${errorMessage3(error)}`
|
|
2949
3224
|
));
|
|
3225
|
+
await this.rememberSessionWorkspace(session.cwd);
|
|
2950
3226
|
}
|
|
2951
3227
|
await this.log(
|
|
2952
3228
|
`session created: session=${session.id} agent=${session.agent} pane=${session.paneId} active=${this.state.activeSessionId === session.id}`
|
|
@@ -3031,7 +3307,7 @@ var AssistantDaemon = class {
|
|
|
3031
3307
|
if (message.senderId !== this.state.ownerOpenId || message.chatId !== this.state.boundChatId) return;
|
|
3032
3308
|
if (text === "/start") {
|
|
3033
3309
|
try {
|
|
3034
|
-
await this.gateway?.sendSessionCreate(message.chatId);
|
|
3310
|
+
await this.gateway?.sendSessionCreate(message.chatId, await this.createSessionWorkspaceView(message.chatId));
|
|
3035
3311
|
} catch (error) {
|
|
3036
3312
|
await this.log(`session create card failed: ${errorMessage3(error)}`);
|
|
3037
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");
|
|
@@ -3296,7 +3572,11 @@ ${escapeFence2(output).slice(-6800)}
|
|
|
3296
3572
|
}
|
|
3297
3573
|
if (action.kind === "session-stop") return this.handleSessionStopAction(action);
|
|
3298
3574
|
if (action.kind === "session-start-error") {
|
|
3299
|
-
if (action.action === "create") return {
|
|
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
|
+
};
|
|
3300
3580
|
if (action.action === "sessions") {
|
|
3301
3581
|
await this.reconcileSessions(true);
|
|
3302
3582
|
return this.sessionPickerActionResult("\u5DF2\u53D1\u9001\u6700\u65B0 Sessions\u3002");
|
|
@@ -3310,15 +3590,53 @@ ${escapeFence2(output).slice(-6800)}
|
|
|
3310
3590
|
return {
|
|
3311
3591
|
type: "session-create-form",
|
|
3312
3592
|
content: "\u8BF7\u586B\u5199\u542F\u52A8\u4FE1\u606F\u3002",
|
|
3593
|
+
view: await this.createSessionWorkspaceView(event.chatId),
|
|
3313
3594
|
sessions: Object.values(this.state.sessions ?? {}),
|
|
3314
3595
|
activeSessionId: this.state.activeSessionId
|
|
3315
3596
|
};
|
|
3316
3597
|
}
|
|
3317
|
-
|
|
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) {
|
|
3318
3608
|
return { type: "error", content: "\u65E0\u6CD5\u8BC6\u522B\u65B0\u5EFA Session \u8868\u5355\uFF0C\u8BF7\u91CD\u65B0\u53D1\u9001 /sessions\u3002" };
|
|
3319
3609
|
}
|
|
3320
|
-
const
|
|
3321
|
-
if (
|
|
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
|
+
}
|
|
3322
3640
|
const result3 = await this.startRemoteSession(request.value);
|
|
3323
3641
|
if (!result3.ok) return larkStartupError(result3.error, request.value);
|
|
3324
3642
|
if (result3.state === "picker") {
|
|
@@ -3523,6 +3841,7 @@ ${escapeFence2(output).slice(-6800)}
|
|
|
3523
3841
|
await this.tmux.preserveOnExit(session.sessionName, false).catch(() => void 0);
|
|
3524
3842
|
const selected = await this.useSession(sessionId);
|
|
3525
3843
|
if (!selected.ok) return { type: "error", content: remoteError(selected) };
|
|
3844
|
+
await this.rememberSessionWorkspace(session.cwd);
|
|
3526
3845
|
return { type: "session-created", content: remoteStartSuccess(session), session };
|
|
3527
3846
|
}
|
|
3528
3847
|
async readResumePicker(session) {
|
|
@@ -3543,7 +3862,7 @@ ${escapeFence2(output).slice(-6800)}
|
|
|
3543
3862
|
while (Date.now() < deadline) {
|
|
3544
3863
|
latest = await this.readResumePicker(session);
|
|
3545
3864
|
if (latest && (!previousFingerprint || latest.fingerprint !== previousFingerprint)) return latest;
|
|
3546
|
-
await new Promise((
|
|
3865
|
+
await new Promise((resolve2) => setTimeout(resolve2, 100));
|
|
3547
3866
|
}
|
|
3548
3867
|
return latest;
|
|
3549
3868
|
}
|
|
@@ -3556,7 +3875,7 @@ ${escapeFence2(output).slice(-6800)}
|
|
|
3556
3875
|
let previousFingerprint;
|
|
3557
3876
|
const deadline = Date.now() + 900;
|
|
3558
3877
|
do {
|
|
3559
|
-
await new Promise((
|
|
3878
|
+
await new Promise((resolve2) => setTimeout(resolve2, 120));
|
|
3560
3879
|
await this.poll();
|
|
3561
3880
|
const fingerprint = this.screen?.fingerprint;
|
|
3562
3881
|
if (fingerprint && fingerprint === previousFingerprint) return;
|
|
@@ -3584,7 +3903,7 @@ ${escapeFence2(output).slice(-6800)}
|
|
|
3584
3903
|
}
|
|
3585
3904
|
try {
|
|
3586
3905
|
await execute(session);
|
|
3587
|
-
await new Promise((
|
|
3906
|
+
await new Promise((resolve2) => setTimeout(resolve2, 120));
|
|
3588
3907
|
await this.poll();
|
|
3589
3908
|
const output = this.screen ? tailScreen(this.screen.normalized, 60) : "\u65E0\u6CD5\u8BFB\u53D6\u6700\u65B0\u7EC8\u7AEF\u753B\u9762\u3002";
|
|
3590
3909
|
await this.gateway?.sendMarkdown(chatId, `**\u624B\u52A8\u64CD\u4F5C\uFF1A${operation}**
|
|
@@ -3635,7 +3954,7 @@ ${escapeFence2(output).slice(-6500)}
|
|
|
3635
3954
|
};
|
|
3636
3955
|
}
|
|
3637
3956
|
if (target?.role === "custom-input" || target?.role === "chat") {
|
|
3638
|
-
await new Promise((
|
|
3957
|
+
await new Promise((resolve2) => setTimeout(resolve2, 100));
|
|
3639
3958
|
await this.poll();
|
|
3640
3959
|
if (!before?.interactionId) return { type: "error", content: "\u65E0\u6CD5\u786E\u8BA4\u5F53\u524D\u4EA4\u4E92\uFF0C\u8BF7\u7528 /tail \u68C0\u67E5\u3002" };
|
|
3641
3960
|
this.pendingInteractionInput = {
|
|
@@ -3768,7 +4087,7 @@ ${escapeFence2(output).slice(-6500)}
|
|
|
3768
4087
|
if (focusedIndex === targetIndex) return { ok: true };
|
|
3769
4088
|
const direction = targetIndex > focusedIndex ? "Down" : "Up";
|
|
3770
4089
|
await this.tmux.sendKey(paneId, direction);
|
|
3771
|
-
await new Promise((
|
|
4090
|
+
await new Promise((resolve2) => setTimeout(resolve2, 40));
|
|
3772
4091
|
await this.poll();
|
|
3773
4092
|
const nextFocused = this.screen?.actions.findIndex(({ focused }) => focused) ?? -1;
|
|
3774
4093
|
if (nextFocused === focusedIndex) return { ok: false, error: "terminal focus did not move as expected" };
|
|
@@ -3778,7 +4097,7 @@ ${escapeFence2(output).slice(-6500)}
|
|
|
3778
4097
|
async waitForInteractionChange(interactionId) {
|
|
3779
4098
|
const deadline = Date.now() + 1500;
|
|
3780
4099
|
while (Date.now() < deadline) {
|
|
3781
|
-
await new Promise((
|
|
4100
|
+
await new Promise((resolve2) => setTimeout(resolve2, 75));
|
|
3782
4101
|
await this.poll();
|
|
3783
4102
|
const current = this.screen?.interaction;
|
|
3784
4103
|
if (!current || current.interactionId !== interactionId) return;
|
|
@@ -3787,7 +4106,7 @@ ${escapeFence2(output).slice(-6500)}
|
|
|
3787
4106
|
async waitForControlMarker(interactionId, controlId, marker) {
|
|
3788
4107
|
const deadline = Date.now() + 1500;
|
|
3789
4108
|
while (Date.now() < deadline) {
|
|
3790
|
-
await new Promise((
|
|
4109
|
+
await new Promise((resolve2) => setTimeout(resolve2, 75));
|
|
3791
4110
|
await this.poll();
|
|
3792
4111
|
const current = this.screen?.interaction;
|
|
3793
4112
|
const control = this.screen?.actions.find(({ id }) => id === controlId);
|
|
@@ -3798,7 +4117,7 @@ ${escapeFence2(output).slice(-6500)}
|
|
|
3798
4117
|
const expected = input.trim();
|
|
3799
4118
|
const deadline = Date.now() + 1500;
|
|
3800
4119
|
while (Date.now() < deadline) {
|
|
3801
|
-
await new Promise((
|
|
4120
|
+
await new Promise((resolve2) => setTimeout(resolve2, 75));
|
|
3802
4121
|
await this.poll();
|
|
3803
4122
|
const control = this.screen?.actions.find(({ id }) => id === controlId);
|
|
3804
4123
|
if (this.screen?.interaction?.interactionId === interactionId && control?.inputValue && (control.inputValue === expected || expected.startsWith(control.inputValue))) return;
|
|
@@ -3843,7 +4162,7 @@ ${escapeFence2(output).slice(-6500)}
|
|
|
3843
4162
|
if (!navigation.ok) return navigation;
|
|
3844
4163
|
if (desired.editor) {
|
|
3845
4164
|
if (desired.editor.openKey) await this.tmux.sendKey(paneId, desired.editor.openKey);
|
|
3846
|
-
await new Promise((
|
|
4165
|
+
await new Promise((resolve2) => setTimeout(resolve2, 100));
|
|
3847
4166
|
await this.tmux.sendKey(paneId, "C-u");
|
|
3848
4167
|
await this.tmux.sendKey(paneId, "C-k");
|
|
3849
4168
|
await this.tmux.sendText(paneId, desired.input, false);
|
|
@@ -4125,7 +4444,7 @@ ${escapeFence2(output).slice(-6500)}
|
|
|
4125
4444
|
source: "startup-discovery"
|
|
4126
4445
|
});
|
|
4127
4446
|
}
|
|
4128
|
-
await new Promise((
|
|
4447
|
+
await new Promise((resolve2) => setTimeout(resolve2, 100));
|
|
4129
4448
|
}
|
|
4130
4449
|
const session = this.state.sessions?.[sessionId];
|
|
4131
4450
|
if (!session) return fail(new AppError("SESSION_NOT_FOUND", `session disappeared during startup: ${sessionId}`, { sessionId }));
|
|
@@ -4156,7 +4475,7 @@ ${escapeFence2(output).slice(-6500)}
|
|
|
4156
4475
|
while (Date.now() < deadline) {
|
|
4157
4476
|
const pane = await this.tmux.inspect(session.paneId);
|
|
4158
4477
|
if (!pane || pane.dead) return fail(await this.startupExitedError(session, pane));
|
|
4159
|
-
await new Promise((
|
|
4478
|
+
await new Promise((resolve2) => setTimeout(resolve2, 80));
|
|
4160
4479
|
}
|
|
4161
4480
|
return { ok: true };
|
|
4162
4481
|
}
|
|
@@ -4324,6 +4643,67 @@ ${output}`
|
|
|
4324
4643
|
}
|
|
4325
4644
|
return { ok: true, state: "ready", session };
|
|
4326
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
|
+
}
|
|
4327
4707
|
async log(message) {
|
|
4328
4708
|
await appendFile(this.paths.logFile, `${(/* @__PURE__ */ new Date()).toISOString()} ${message}
|
|
4329
4709
|
`, { mode: 384 });
|
|
@@ -4361,24 +4741,29 @@ function remember(values, value, limit) {
|
|
|
4361
4741
|
values.delete(oldest);
|
|
4362
4742
|
}
|
|
4363
4743
|
}
|
|
4364
|
-
function
|
|
4744
|
+
function sessionCreateDraftFromForm(values) {
|
|
4365
4745
|
const sessionId = formString(values[SESSION_CREATE_NAME_FIELD]);
|
|
4366
4746
|
const agentValue = formString(values[SESSION_CREATE_AGENT_FIELD]);
|
|
4367
|
-
const
|
|
4747
|
+
const projectCwd = formString(values[SESSION_CREATE_PROJECT_FIELD]);
|
|
4748
|
+
const manualCwd = formString(values[SESSION_CREATE_CWD_FIELD]);
|
|
4368
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() ?? "";
|
|
4369
4762
|
if (!sessionId) return { ok: false, error: "\u8BF7\u586B\u5199 Session \u540D\u79F0\u3002" };
|
|
4370
|
-
|
|
4371
|
-
if (!agent) return { ok: false, error: "\u8BF7\u9009\u62E9\u6709\u6548\u7684 Agent\uFF1Acodex\u3001traex \u6216 claude\u3002" };
|
|
4372
|
-
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" };
|
|
4373
4764
|
let resume;
|
|
4374
|
-
if (resumeMode === "
|
|
4375
|
-
|
|
4376
|
-
}
|
|
4377
|
-
if (resumeMode === "picker") resume = { mode: "picker" };
|
|
4378
|
-
else if (resumeMode !== "new") {
|
|
4379
|
-
return { ok: false, error: "\u65E0\u6CD5\u8BC6\u522B\u542F\u52A8\u65B9\u5F0F\uFF0C\u8BF7\u91CD\u65B0\u6253\u5F00\u65B0\u5EFA Session \u8868\u5355\u3002" };
|
|
4380
|
-
}
|
|
4381
|
-
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 } };
|
|
4382
4767
|
}
|
|
4383
4768
|
function formString(value) {
|
|
4384
4769
|
if (typeof value === "string") return value.trim();
|
|
@@ -4400,7 +4785,7 @@ function remoteError(result2) {
|
|
|
4400
4785
|
const sessionId = typeof context.sessionId === "string" ? context.sessionId : "\u8BE5\u540D\u79F0";
|
|
4401
4786
|
switch (result2.errorCode) {
|
|
4402
4787
|
case "SESSION_EXISTS":
|
|
4403
|
-
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`;
|
|
4404
4789
|
case "AGENT_SESSION_IN_USE": {
|
|
4405
4790
|
const ownerSessionId = typeof context.ownerSessionId === "string" ? context.ownerSessionId : "\u73B0\u6709 session";
|
|
4406
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`;
|
|
@@ -4476,20 +4861,20 @@ function manualTimestamp() {
|
|
|
4476
4861
|
}
|
|
4477
4862
|
|
|
4478
4863
|
// src/core/paths.ts
|
|
4479
|
-
import { homedir as
|
|
4480
|
-
import { join as
|
|
4864
|
+
import { homedir as homedir4 } from "os";
|
|
4865
|
+
import { join as join3 } from "path";
|
|
4481
4866
|
function resolveAppPaths(root = process.env.LARK_CODING_ASSISTANT_HOME) {
|
|
4482
|
-
const base = root ||
|
|
4867
|
+
const base = root || join3(homedir4(), ".lark-coding-assistant");
|
|
4483
4868
|
return {
|
|
4484
4869
|
root: base,
|
|
4485
|
-
config:
|
|
4486
|
-
secrets:
|
|
4487
|
-
state:
|
|
4488
|
-
logsDir:
|
|
4489
|
-
logFile:
|
|
4490
|
-
runtimeDir:
|
|
4491
|
-
socket:
|
|
4492
|
-
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")
|
|
4493
4878
|
};
|
|
4494
4879
|
}
|
|
4495
4880
|
|