dsh-lark-bot 0.19.2 → 0.19.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -1
- package/dist/cli.js +362 -54
- package/dist/cli.js.map +1 -1
- package/dist/plugin.d.ts +2 -0
- package/dist/plugin.js +188 -30
- package/dist/plugin.js.map +1 -1
- package/dist/skill.d.ts +26 -0
- package/dist/skill.js +94 -0
- package/dist/skill.js.map +1 -0
- package/package.json +5 -1
package/dist/plugin.d.ts
CHANGED
|
@@ -1321,9 +1321,11 @@ interface GuardianUpdateState {
|
|
|
1321
1321
|
route: GuardianUpdateRoute;
|
|
1322
1322
|
startedAt: string;
|
|
1323
1323
|
finishedAt?: string;
|
|
1324
|
+
errorCode?: GuardianUpdateErrorCode;
|
|
1324
1325
|
error?: string;
|
|
1325
1326
|
delivered?: boolean;
|
|
1326
1327
|
}
|
|
1328
|
+
type GuardianUpdateErrorCode = 'filesystem-access' | 'registry-unavailable' | 'bootstrap-unavailable' | 'upgrade-failed';
|
|
1327
1329
|
interface GuardianUpdateHandoffOptions {
|
|
1328
1330
|
file: string;
|
|
1329
1331
|
packageName: string;
|
package/dist/plugin.js
CHANGED
|
@@ -864,9 +864,9 @@ async function writeFileAtomic(target, data, options = {}) {
|
|
|
864
864
|
}
|
|
865
865
|
}
|
|
866
866
|
async function rmSilently(path) {
|
|
867
|
-
const { rm:
|
|
867
|
+
const { rm: rm11 } = await import("fs/promises");
|
|
868
868
|
try {
|
|
869
|
-
await
|
|
869
|
+
await rm11(path, { force: true });
|
|
870
870
|
} catch {
|
|
871
871
|
}
|
|
872
872
|
}
|
|
@@ -2538,6 +2538,18 @@ function patchYamlFor(options) {
|
|
|
2538
2538
|
"",
|
|
2539
2539
|
"- id: hmr",
|
|
2540
2540
|
" disabled: true",
|
|
2541
|
+
"",
|
|
2542
|
+
// Model-invocable channel skill: expose the dsh-lark-bot operations guide
|
|
2543
|
+
// to THIS agent session's skill catalog — the one the `skill` tool reads.
|
|
2544
|
+
// The bridge engine registers the same skill on its own cordis context
|
|
2545
|
+
// (plugin.ts), but that context is never the one the model reads. This row
|
|
2546
|
+
// ensures the skill also lands on the agent runtime's context so that
|
|
2547
|
+
// `skill("dsh-lark-bot")` resolves in a live SDK session. It is kept
|
|
2548
|
+
// outside the bridgeTools gate because it needs no callback endpoint and
|
|
2549
|
+
// is also useful to the guardian's core-only safe profile.
|
|
2550
|
+
"- insert:",
|
|
2551
|
+
" - id: lark-skill",
|
|
2552
|
+
` name: '${own.name}/skill'`,
|
|
2541
2553
|
""
|
|
2542
2554
|
];
|
|
2543
2555
|
if (bridgeTools) {
|
|
@@ -4561,7 +4573,11 @@ function processElements(state, locale, maxTools) {
|
|
|
4561
4573
|
function thinkingPanel(state, locale, maxTools) {
|
|
4562
4574
|
return {
|
|
4563
4575
|
tag: "collapsible_panel",
|
|
4564
|
-
|
|
4576
|
+
// Default to collapsed: the top-level compatibility snapshot already shows
|
|
4577
|
+
// the latest status and current tools, so keep the detailed per-tool list
|
|
4578
|
+
// collapsed to avoid two overlapping expanded surfaces. (Both sections are
|
|
4579
|
+
// retained for now.)
|
|
4580
|
+
expanded: false,
|
|
4565
4581
|
header: {
|
|
4566
4582
|
title: { tag: "plain_text", content: `\u2699\uFE0F ${locale === "zh_cn" ? "\u6267\u884C\u8FC7\u7A0B" : "Execution"} \xB7 ${summaryText(state, locale)}` },
|
|
4567
4583
|
icon: { tag: "standard_icon", token: "down-small-ccm_outlined" },
|
|
@@ -5247,6 +5263,15 @@ async function runAttempt(input, cwd, workspaceCwd, sessionId, resuming, replyOp
|
|
|
5247
5263
|
log.warn("run-flow", "card-update-failed", { scope: input.scope, error });
|
|
5248
5264
|
}
|
|
5249
5265
|
};
|
|
5266
|
+
const unregisterReanchor = input.runCardAnchors?.register(input.chatId, async () => {
|
|
5267
|
+
if (state.terminal !== "running") return;
|
|
5268
|
+
try {
|
|
5269
|
+
await controller.update(renderer(state, density, Date.now()));
|
|
5270
|
+
if (typeof controller.reanchor === "function") await controller.reanchor();
|
|
5271
|
+
} catch (error) {
|
|
5272
|
+
log.warn("run-flow", "card-reanchor-failed", { scope: input.scope, error });
|
|
5273
|
+
}
|
|
5274
|
+
});
|
|
5250
5275
|
const showResumeRecovery = async (error) => {
|
|
5251
5276
|
if (classifySessionError(errorMessage(error)) === void 0) throw error;
|
|
5252
5277
|
resumeFailure = errorMessage(error);
|
|
@@ -5420,6 +5445,7 @@ async function runAttempt(input, cwd, workspaceCwd, sessionId, resuming, replyOp
|
|
|
5420
5445
|
unsubscribeQuestion?.();
|
|
5421
5446
|
unsubscribePlan?.();
|
|
5422
5447
|
unsubscribeApproval?.();
|
|
5448
|
+
unregisterReanchor?.();
|
|
5423
5449
|
}
|
|
5424
5450
|
};
|
|
5425
5451
|
let producerStarted = false;
|
|
@@ -9203,6 +9229,41 @@ var ResilientCardStreamController = class {
|
|
|
9203
9229
|
});
|
|
9204
9230
|
}
|
|
9205
9231
|
}
|
|
9232
|
+
/**
|
|
9233
|
+
* Recall the current card and re-create it as the newest top-level message in
|
|
9234
|
+
* the chat, rebinding the controller to the fresh message id. Because Feishu
|
|
9235
|
+
* cannot reorder an existing message, this is the only way to keep an
|
|
9236
|
+
* in-progress process card visible at the tail while interim agent bubbles are
|
|
9237
|
+
* appended below. Best-effort: if the recall fails the card is left where it
|
|
9238
|
+
* is (no duplicate is created); if the controller is closed or failed the id
|
|
9239
|
+
* is left unchanged.
|
|
9240
|
+
*/
|
|
9241
|
+
async reanchor() {
|
|
9242
|
+
if (this.closed || this.failed) return this.messageId;
|
|
9243
|
+
if (this.inFlight !== void 0) await this.inFlight;
|
|
9244
|
+
if (this.timer !== void 0) {
|
|
9245
|
+
clearTimeout(this.timer);
|
|
9246
|
+
this.timer = void 0;
|
|
9247
|
+
}
|
|
9248
|
+
const card = this.latest;
|
|
9249
|
+
if (card === void 0) return this.messageId;
|
|
9250
|
+
try {
|
|
9251
|
+
await this.channel.recallMessage(this.messageId);
|
|
9252
|
+
} catch (error) {
|
|
9253
|
+
log.warn("lark-card-stream", "reanchor-recall-failed", {
|
|
9254
|
+
messageId: this.messageId,
|
|
9255
|
+
error: error instanceof Error ? error.message : String(error)
|
|
9256
|
+
});
|
|
9257
|
+
return this.messageId;
|
|
9258
|
+
}
|
|
9259
|
+
const sent = await this.channel.send(this.chatId, { card }, {});
|
|
9260
|
+
if (!sent.messageId) throw new Error("Feishu card re-anchor returned no message_id");
|
|
9261
|
+
this.messageId = sent.messageId;
|
|
9262
|
+
this.latest = card;
|
|
9263
|
+
this.dirty = false;
|
|
9264
|
+
this.failed = false;
|
|
9265
|
+
return this.messageId;
|
|
9266
|
+
}
|
|
9206
9267
|
};
|
|
9207
9268
|
function adaptLarkChannel(channel) {
|
|
9208
9269
|
const base = {
|
|
@@ -11446,6 +11507,63 @@ function wizardContextFor(event, deps, channel, scope) {
|
|
|
11446
11507
|
};
|
|
11447
11508
|
}
|
|
11448
11509
|
|
|
11510
|
+
// src/bridge/run-card-anchors.ts
|
|
11511
|
+
var RunCardAnchors = class {
|
|
11512
|
+
byChatId = /* @__PURE__ */ new Map();
|
|
11513
|
+
/**
|
|
11514
|
+
* Register a re-anchor callback for a chat. Returns a disposer; safe to call
|
|
11515
|
+
* more than once (idempotent).
|
|
11516
|
+
*/
|
|
11517
|
+
register(chatId, reanchor) {
|
|
11518
|
+
const set = this.byChatId.get(chatId) ?? /* @__PURE__ */ new Set();
|
|
11519
|
+
set.add(reanchor);
|
|
11520
|
+
this.byChatId.set(chatId, set);
|
|
11521
|
+
let active = true;
|
|
11522
|
+
return () => {
|
|
11523
|
+
if (!active) return;
|
|
11524
|
+
active = false;
|
|
11525
|
+
set.delete(reanchor);
|
|
11526
|
+
if (set.size === 0) this.byChatId.delete(chatId);
|
|
11527
|
+
};
|
|
11528
|
+
}
|
|
11529
|
+
/** Notify that a new bubble was delivered to the chat. */
|
|
11530
|
+
async bubbleSent(chatId) {
|
|
11531
|
+
const set = this.byChatId.get(chatId);
|
|
11532
|
+
if (!set || set.size === 0) return;
|
|
11533
|
+
await Promise.allSettled([...set].map((reanchor) => reanchor()));
|
|
11534
|
+
}
|
|
11535
|
+
};
|
|
11536
|
+
function attachRunCardAnchors(channel, anchors) {
|
|
11537
|
+
const notify = (chatId) => {
|
|
11538
|
+
void anchors.bubbleSent(chatId).catch((error) => {
|
|
11539
|
+
log.warn("run-card-anchors", "bubble-sent-failed", {
|
|
11540
|
+
chatId,
|
|
11541
|
+
error: error instanceof Error ? error.message : String(error)
|
|
11542
|
+
});
|
|
11543
|
+
});
|
|
11544
|
+
};
|
|
11545
|
+
return {
|
|
11546
|
+
...channel,
|
|
11547
|
+
async sendMarkdown(chatId, markdown2, options) {
|
|
11548
|
+
const result = await channel.sendMarkdown(chatId, markdown2, options);
|
|
11549
|
+
notify(chatId);
|
|
11550
|
+
return result;
|
|
11551
|
+
},
|
|
11552
|
+
async sendCard(chatId, card, options) {
|
|
11553
|
+
if (typeof channel.sendCard !== "function") return void 0;
|
|
11554
|
+
const result = await channel.sendCard(chatId, card, options);
|
|
11555
|
+
notify(chatId);
|
|
11556
|
+
return result;
|
|
11557
|
+
},
|
|
11558
|
+
async sendFile(chatId, fileName, content2, options) {
|
|
11559
|
+
if (typeof channel.sendFile !== "function") return void 0;
|
|
11560
|
+
const result = await channel.sendFile(chatId, fileName, content2, options);
|
|
11561
|
+
notify(chatId);
|
|
11562
|
+
return result;
|
|
11563
|
+
}
|
|
11564
|
+
};
|
|
11565
|
+
}
|
|
11566
|
+
|
|
11449
11567
|
// src/bridge/scope-directory.ts
|
|
11450
11568
|
import { readFile as readFile11 } from "fs/promises";
|
|
11451
11569
|
var ScopeDirectory = class {
|
|
@@ -15008,14 +15126,14 @@ function matchProfileProcess(cmdline, dshProfile) {
|
|
|
15008
15126
|
if (/(?:^|\s)plugin(?:\s|$)/.test(cmdline)) return false;
|
|
15009
15127
|
return hasProfileFlag(cmdline, dshProfile) && looksLikeDshProcess(cmdline);
|
|
15010
15128
|
}
|
|
15011
|
-
async function listProcesses() {
|
|
15012
|
-
if (
|
|
15013
|
-
return listProcessesWindows();
|
|
15129
|
+
async function listProcesses(platform = process.platform, run = captureOutput) {
|
|
15130
|
+
if (platform === "win32") {
|
|
15131
|
+
return listProcessesWindows(run);
|
|
15014
15132
|
}
|
|
15015
|
-
return listProcessesPosix();
|
|
15133
|
+
return listProcessesPosix(run);
|
|
15016
15134
|
}
|
|
15017
|
-
async function listProcessesPosix() {
|
|
15018
|
-
const { stdout } = await
|
|
15135
|
+
async function listProcessesPosix(run) {
|
|
15136
|
+
const { stdout } = await run("ps", ["-axo", "pid=,args="], 1e4);
|
|
15019
15137
|
const result = [];
|
|
15020
15138
|
for (const line of stdout.split("\n")) {
|
|
15021
15139
|
const match = /^\s*(\d+)\s+(.+)$/.exec(line);
|
|
@@ -15025,33 +15143,46 @@ async function listProcessesPosix() {
|
|
|
15025
15143
|
}
|
|
15026
15144
|
return result;
|
|
15027
15145
|
}
|
|
15028
|
-
async function listProcessesWindows() {
|
|
15029
|
-
const { stdout } = await
|
|
15146
|
+
async function listProcessesWindows(run) {
|
|
15147
|
+
const { stdout } = await run(
|
|
15030
15148
|
"powershell.exe",
|
|
15031
15149
|
[
|
|
15032
15150
|
"-NoProfile",
|
|
15033
15151
|
"-NonInteractive",
|
|
15034
15152
|
"-Command",
|
|
15035
|
-
"Get-CimInstance Win32_Process | Select-Object ProcessId, CommandLine | ConvertTo-
|
|
15153
|
+
"Get-CimInstance Win32_Process | Select-Object ProcessId, CommandLine | ConvertTo-Json -Compress"
|
|
15036
15154
|
],
|
|
15037
15155
|
1e4
|
|
15038
15156
|
);
|
|
15039
|
-
|
|
15040
|
-
|
|
15041
|
-
const
|
|
15042
|
-
|
|
15043
|
-
|
|
15044
|
-
|
|
15045
|
-
|
|
15157
|
+
try {
|
|
15158
|
+
const parsed = JSON.parse(stdout);
|
|
15159
|
+
const rows = Array.isArray(parsed) ? parsed : [parsed];
|
|
15160
|
+
return rows.flatMap((row) => {
|
|
15161
|
+
if (typeof row !== "object" || row === null) return [];
|
|
15162
|
+
const record = row;
|
|
15163
|
+
const pid = Number(record.ProcessId);
|
|
15164
|
+
const cmdline = record.CommandLine;
|
|
15165
|
+
return Number.isInteger(pid) && pid > 0 && typeof cmdline === "string" ? [{ pid, cmdline }] : [];
|
|
15166
|
+
});
|
|
15167
|
+
} catch {
|
|
15168
|
+
return [];
|
|
15046
15169
|
}
|
|
15047
|
-
return result;
|
|
15048
15170
|
}
|
|
15049
|
-
async function captureOutput(command, args, timeoutMs = 3e4) {
|
|
15171
|
+
async function captureOutput(command, args, timeoutMs = 3e4, options = {}) {
|
|
15050
15172
|
return new Promise((resolve6) => {
|
|
15051
|
-
const
|
|
15052
|
-
|
|
15053
|
-
|
|
15054
|
-
|
|
15173
|
+
const previousUmask = options.umask !== void 0 && process.platform !== "win32" ? process.umask(options.umask) : void 0;
|
|
15174
|
+
const child = (() => {
|
|
15175
|
+
try {
|
|
15176
|
+
return spawn5(command, [...args], {
|
|
15177
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
15178
|
+
windowsHide: true,
|
|
15179
|
+
...options.cwd ? { cwd: options.cwd } : {},
|
|
15180
|
+
...options.env ? { env: options.env } : {}
|
|
15181
|
+
});
|
|
15182
|
+
} finally {
|
|
15183
|
+
if (previousUmask !== void 0) process.umask(previousUmask);
|
|
15184
|
+
}
|
|
15185
|
+
})();
|
|
15055
15186
|
let stdout = "";
|
|
15056
15187
|
let stderr = "";
|
|
15057
15188
|
child.stdout?.on("data", (chunk) => {
|
|
@@ -16374,11 +16505,35 @@ function buildSecretHandler(deps) {
|
|
|
16374
16505
|
init_own_package();
|
|
16375
16506
|
|
|
16376
16507
|
// src/guardian/update-handoff.ts
|
|
16377
|
-
import { randomUUID as randomUUID13 } from "crypto";
|
|
16508
|
+
import { createHash as createHash3, randomUUID as randomUUID13 } from "crypto";
|
|
16378
16509
|
import { spawn as spawn7 } from "child_process";
|
|
16379
|
-
import { mkdir as mkdir20, readFile as readFile28 } from "fs/promises";
|
|
16510
|
+
import { chmod, mkdir as mkdir20, readFile as readFile28, rm as rm10 } from "fs/promises";
|
|
16380
16511
|
import { dirname as dirname15, join as join23 } from "path";
|
|
16381
16512
|
init_own_package();
|
|
16513
|
+
function guardianUpdateFailureHint(code) {
|
|
16514
|
+
switch (code) {
|
|
16515
|
+
case "filesystem-access":
|
|
16516
|
+
return {
|
|
16517
|
+
zh: "\u66F4\u65B0\u5DE5\u4F5C\u76EE\u5F55\u4E0D\u53EF\u8BBF\u95EE\uFF1B\u8BF7\u53D1\u9001 `/doctor` \u68C0\u67E5\u6587\u4EF6\u6743\u9650\u540E\u91CD\u8BD5\u3002",
|
|
16518
|
+
en: "The private update workspace is inaccessible. Send `/doctor` to check file permissions, then retry."
|
|
16519
|
+
};
|
|
16520
|
+
case "registry-unavailable":
|
|
16521
|
+
return {
|
|
16522
|
+
zh: "\u65E0\u6CD5\u8FDE\u63A5 npm \u6B63\u5F0F\u6E90\uFF1B\u8BF7\u68C0\u67E5\u7F51\u7EDC\u540E\u91CD\u8BD5\u3002",
|
|
16523
|
+
en: "The npm registry is unreachable. Check the network, then retry."
|
|
16524
|
+
};
|
|
16525
|
+
case "bootstrap-unavailable":
|
|
16526
|
+
return {
|
|
16527
|
+
zh: "\u65E0\u6CD5\u542F\u52A8 npm/npx\uFF1B\u8BF7\u53D1\u9001 `/doctor` \u68C0\u67E5 Node.js \u4E0E npm\u3002",
|
|
16528
|
+
en: "npm/npx could not start. Send `/doctor` to check Node.js and npm."
|
|
16529
|
+
};
|
|
16530
|
+
default:
|
|
16531
|
+
return {
|
|
16532
|
+
zh: "\u5347\u7EA7\u547D\u4EE4\u672A\u5B8C\u6210\uFF1B\u8BF7\u53D1\u9001 `/doctor` \u68C0\u67E5\u540E\u91CD\u8BD5\u3002",
|
|
16533
|
+
en: "The upgrade command did not complete. Send `/doctor`, then retry."
|
|
16534
|
+
};
|
|
16535
|
+
}
|
|
16536
|
+
}
|
|
16382
16537
|
async function loadState(file) {
|
|
16383
16538
|
try {
|
|
16384
16539
|
const parsed = JSON.parse(await readFile28(file, "utf8"));
|
|
@@ -16734,6 +16889,7 @@ async function startBridgeEngine(options) {
|
|
|
16734
16889
|
const questions = new QuestionRegistry();
|
|
16735
16890
|
const plans = new PlanApprovalRegistry();
|
|
16736
16891
|
const densityStore = new DensityStore();
|
|
16892
|
+
const runCardAnchors = new RunCardAnchors();
|
|
16737
16893
|
const models = new ModelStore();
|
|
16738
16894
|
const wizardStore = new WizardStore();
|
|
16739
16895
|
const dshConfig = new DshProviderManager({ env: process.env });
|
|
@@ -16964,6 +17120,7 @@ async function startBridgeEngine(options) {
|
|
|
16964
17120
|
plans,
|
|
16965
17121
|
densityStore,
|
|
16966
17122
|
channel: streaming,
|
|
17123
|
+
runCardAnchors,
|
|
16967
17124
|
defaultWorkspace,
|
|
16968
17125
|
replyTo: first.messageId,
|
|
16969
17126
|
deliverFinalReply: (replyScope, chatId, markdown2, options2) => replyDispatcher.deliver(replyScope, chatId, markdown2, options2),
|
|
@@ -17166,17 +17323,18 @@ async function startBridgeEngine(options) {
|
|
|
17166
17323
|
error: "channel does not expose getBotIdentity"
|
|
17167
17324
|
});
|
|
17168
17325
|
}
|
|
17169
|
-
streaming = adaptLarkChannel(bridge.channel);
|
|
17326
|
+
streaming = attachRunCardAnchors(adaptLarkChannel(bridge.channel), runCardAnchors);
|
|
17170
17327
|
larkChannel = bridge.channel;
|
|
17171
17328
|
const deliverUpdateResult = async () => {
|
|
17172
17329
|
await updateHandoff.deliverResult(async (state) => {
|
|
17173
17330
|
if (!streaming) throw new Error("channel is not ready");
|
|
17331
|
+
const failureHint = guardianUpdateFailureHint(state.errorCode);
|
|
17174
17332
|
const markdown2 = state.status === "succeeded" ? bilingualMarkdown(
|
|
17175
17333
|
`\u2705 dsh-lark-bot \u5DF2\u66F4\u65B0\u5230 \`${state.targetVersion}\`\uFF0C\u673A\u5668\u4EBA\u5DF2\u5B8C\u6210\u91CD\u8F7D\u3002`,
|
|
17176
17334
|
`\u2705 dsh-lark-bot was updated to \`${state.targetVersion}\` and reloaded.`
|
|
17177
17335
|
) : bilingualMarkdown(
|
|
17178
|
-
`\u26A0\uFE0F dsh-lark-bot \u66F4\u65B0\u5230 \`${state.targetVersion}\` \u5931\u8D25\u3002
|
|
17179
|
-
`\u26A0\uFE0F Failed to update dsh-lark-bot to \`${state.targetVersion}\`.
|
|
17336
|
+
`\u26A0\uFE0F dsh-lark-bot \u66F4\u65B0\u5230 \`${state.targetVersion}\` \u5931\u8D25\u3002${failureHint.zh}`,
|
|
17337
|
+
`\u26A0\uFE0F Failed to update dsh-lark-bot to \`${state.targetVersion}\`. ${failureHint.en}`
|
|
17180
17338
|
);
|
|
17181
17339
|
await streaming.sendMarkdown(state.route.chatId, markdown2, {
|
|
17182
17340
|
...state.route.threadId ? { threadId: state.route.threadId } : {}
|