@hwj123weijian/pi-feishu 0.11.0 → 0.11.1
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/package.json +1 -1
- package/src/extension.ts +64 -39
package/package.json
CHANGED
package/src/extension.ts
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import { resolve as resolvePath } from "node:path";
|
|
1
3
|
import type {
|
|
2
4
|
ExtensionAPI,
|
|
3
5
|
ExtensionCommandContext,
|
|
4
6
|
ExtensionContext,
|
|
5
|
-
ProjectTrustEventResult,
|
|
6
7
|
ProjectTrustHandler,
|
|
7
8
|
} from "@earendil-works/pi-coding-agent";
|
|
9
|
+
import { Type } from "typebox";
|
|
10
|
+
import type { AgentBridge, FeishuStatus, PiModelInfo, PiRuntime, PiRuntimeSnapshot } from "./contracts.js";
|
|
8
11
|
|
|
9
12
|
/** switchSession/withSession 回调里的新会话上下文;Pi 未从包根导出该类型名,从签名提取。 */
|
|
10
13
|
type SwitchCallback = NonNullable<
|
|
@@ -14,9 +17,6 @@ type SwitchCallback = NonNullable<
|
|
|
14
17
|
: never
|
|
15
18
|
: never
|
|
16
19
|
>;
|
|
17
|
-
type WithSessionCallback = (ctx: SwitchCallback) => Promise<void>;
|
|
18
|
-
import { Type } from "typebox";
|
|
19
|
-
import type { AgentBridge, FeishuStatus, PiModelInfo, PiRuntime, PiRuntimeSnapshot } from "./contracts.js";
|
|
20
20
|
import { FeishuController } from "./controller.js";
|
|
21
21
|
import { CredentialError, FileCredentialStore } from "./credentials.js";
|
|
22
22
|
import { SdkFeishuGateway, validateSdkCredentials } from "./gateway.js";
|
|
@@ -207,9 +207,17 @@ async function sendToChatSession(state: SharedFeishuState, chatId: string, promp
|
|
|
207
207
|
try {
|
|
208
208
|
const sessionFile = state.controller.getChatSessionFile(chatId);
|
|
209
209
|
if (sessionFile) {
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
)
|
|
210
|
+
// Pi 的 TUI switchSession 不透传 cwdOverride,改为把 cwd 写进会话头:
|
|
211
|
+
// SessionManager.open 切换时从 header 读取 cwd,runtime 随之在新目录重建。
|
|
212
|
+
if (directory && !(await anchorSessionHeaderCwd(sessionFile, directory))) {
|
|
213
|
+
throw new Error(`无法把群会话锚定到 ${directory}(会话文件不可写或格式未知)。`);
|
|
214
|
+
}
|
|
215
|
+
const result = await context.switchSession(sessionFile, {
|
|
216
|
+
withSession: (nextContext) => {
|
|
217
|
+
if (directory) assertAnchoredCwd(nextContext, directory);
|
|
218
|
+
return sendAndTrackGroupSession(state, chatId, sessionFile, nextContext, prompt);
|
|
219
|
+
},
|
|
220
|
+
});
|
|
213
221
|
if (result.cancelled) throw new Error("切换到飞书群绑定的 Pi 会话已取消。");
|
|
214
222
|
return;
|
|
215
223
|
}
|
|
@@ -219,11 +227,18 @@ async function sendToChatSession(state: SharedFeishuState, chatId: string, promp
|
|
|
219
227
|
state.latestCommandContext = nextContext;
|
|
220
228
|
const currentFile = nextContext.sessionManager.getSessionFile();
|
|
221
229
|
if (currentFile) await state.controller.setChatSessionFile(chatId, currentFile);
|
|
222
|
-
if (directory) {
|
|
223
|
-
// 新会话默认继承当前 cwd
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
230
|
+
if (currentFile && directory) {
|
|
231
|
+
// 新会话默认继承当前 cwd:把会话头 cwd 改写为绑定目录后重新切换一次,
|
|
232
|
+
// runtime 会以新 cwd 重建,然后才投递消息。
|
|
233
|
+
if (!(await anchorSessionHeaderCwd(currentFile, directory))) {
|
|
234
|
+
throw new Error(`无法把群会话锚定到 ${directory}(会话文件不可写或格式未知)。`);
|
|
235
|
+
}
|
|
236
|
+
const anchored = await nextContext.switchSession(currentFile, {
|
|
237
|
+
withSession: (finalContext) => {
|
|
238
|
+
state.latestCommandContext = finalContext;
|
|
239
|
+
assertAnchoredCwd(finalContext, directory);
|
|
240
|
+
return finalContext.sendUserMessage(prompt);
|
|
241
|
+
},
|
|
227
242
|
});
|
|
228
243
|
if (anchored.cancelled) throw new Error("锚定群会话工作目录已取消。");
|
|
229
244
|
return;
|
|
@@ -238,36 +253,46 @@ async function sendToChatSession(state: SharedFeishuState, chatId: string, promp
|
|
|
238
253
|
}
|
|
239
254
|
|
|
240
255
|
/**
|
|
241
|
-
*
|
|
242
|
-
*
|
|
243
|
-
*
|
|
244
|
-
*
|
|
245
|
-
* cwd 生效——若未来 Pi 改为丢弃该参数,会得到可操作的错误而不是静默串目录。
|
|
256
|
+
* 把会话文件头(JSONL 首行的 session header)里的 cwd 改写为绑定目录。
|
|
257
|
+
* 群会话空闲时文件不被任何 runtime 持有,重写首行是安全的;下次
|
|
258
|
+
* SessionManager.open 会以新 cwd 重建 runtime(工具、bash 都在新目录执行)。
|
|
259
|
+
* 返回 false 表示文件不可读/格式未知,调用方给出可操作错误。
|
|
246
260
|
*/
|
|
247
|
-
async function
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
if (!directory) {
|
|
254
|
-
return context.switchSession(sessionFile, { withSession });
|
|
261
|
+
export async function anchorSessionHeaderCwd(sessionFile: string, directory: string): Promise<boolean> {
|
|
262
|
+
let raw: string;
|
|
263
|
+
try {
|
|
264
|
+
raw = await readFile(sessionFile, "utf8");
|
|
265
|
+
} catch {
|
|
266
|
+
return false;
|
|
255
267
|
}
|
|
256
|
-
const
|
|
257
|
-
const
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
268
|
+
const newlineIndex = raw.indexOf("\n");
|
|
269
|
+
const firstLine = newlineIndex === -1 ? raw : raw.slice(0, newlineIndex);
|
|
270
|
+
let header: Record<string, unknown>;
|
|
271
|
+
try {
|
|
272
|
+
header = JSON.parse(firstLine) as Record<string, unknown>;
|
|
273
|
+
} catch {
|
|
274
|
+
return false;
|
|
275
|
+
}
|
|
276
|
+
if (header.type !== "session" || typeof header.cwd !== "string") return false;
|
|
277
|
+
if (resolvePath(header.cwd) === resolvePath(directory)) return true;
|
|
278
|
+
header.cwd = directory;
|
|
279
|
+
const updatedFirstLine = JSON.stringify(header);
|
|
280
|
+
const updated = newlineIndex === -1 ? updatedFirstLine : updatedFirstLine + raw.slice(newlineIndex);
|
|
281
|
+
try {
|
|
282
|
+
await writeFile(sessionFile, updated, "utf8");
|
|
283
|
+
} catch {
|
|
284
|
+
return false;
|
|
285
|
+
}
|
|
286
|
+
return true;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function assertAnchoredCwd(context: ExtensionContext, directory: string): void {
|
|
290
|
+
const effective = safeCwd(context);
|
|
291
|
+
if (effective && resolvePath(effective) !== resolvePath(directory)) {
|
|
292
|
+
throw new Error(
|
|
293
|
+
`群会话未能切换到绑定目录(当前:${effective})。请重启本地 Pi 后重试,或在本地执行 /feishu status 刷新会话控制。`,
|
|
294
|
+
);
|
|
269
295
|
}
|
|
270
|
-
return result;
|
|
271
296
|
}
|
|
272
297
|
|
|
273
298
|
function safeCwd(context: ExtensionContext): string | undefined {
|