larkway 0.3.31 → 0.3.32
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/dist/main.js +136 -4
- package/package.json +1 -1
package/dist/main.js
CHANGED
|
@@ -118306,6 +118306,84 @@ function renderPrompt(input) {
|
|
|
118306
118306
|
].join("\n");
|
|
118307
118307
|
}
|
|
118308
118308
|
|
|
118309
|
+
// src/lark/rosterResolver.ts
|
|
118310
|
+
import { execFile as execFile2 } from "node:child_process";
|
|
118311
|
+
function parseBotRoster(stdout) {
|
|
118312
|
+
const roster = /* @__PURE__ */ new Map();
|
|
118313
|
+
let json2;
|
|
118314
|
+
try {
|
|
118315
|
+
json2 = JSON.parse(stdout);
|
|
118316
|
+
} catch {
|
|
118317
|
+
return roster;
|
|
118318
|
+
}
|
|
118319
|
+
const items = json2?.data?.items;
|
|
118320
|
+
if (!Array.isArray(items)) return roster;
|
|
118321
|
+
for (const item of items) {
|
|
118322
|
+
if (typeof item !== "object" || item === null) continue;
|
|
118323
|
+
const record = item;
|
|
118324
|
+
const name = record["bot_name"];
|
|
118325
|
+
const id = record["bot_id"];
|
|
118326
|
+
if (typeof name === "string" && name && typeof id === "string" && id) {
|
|
118327
|
+
roster.set(name, id);
|
|
118328
|
+
}
|
|
118329
|
+
}
|
|
118330
|
+
return roster;
|
|
118331
|
+
}
|
|
118332
|
+
function remapPeersToLiveRoster(peers, liveRoster) {
|
|
118333
|
+
const remapped = [];
|
|
118334
|
+
const unresolved = [];
|
|
118335
|
+
const out = peers.map((peer) => {
|
|
118336
|
+
const liveId = liveRoster.get(peer.name);
|
|
118337
|
+
if (!liveId) {
|
|
118338
|
+
unresolved.push(peer.name);
|
|
118339
|
+
return peer;
|
|
118340
|
+
}
|
|
118341
|
+
if (liveId !== peer.id) {
|
|
118342
|
+
remapped.push(peer.name);
|
|
118343
|
+
return { ...peer, id: liveId };
|
|
118344
|
+
}
|
|
118345
|
+
return peer;
|
|
118346
|
+
});
|
|
118347
|
+
return { peers: out, remapped, unresolved };
|
|
118348
|
+
}
|
|
118349
|
+
async function resolveChatBotRoster(chatId, opts = {}) {
|
|
118350
|
+
const cli = opts.larkCliPath ?? "lark-cli";
|
|
118351
|
+
const args = ["im", "chat.members", "bots", "--chat-id", chatId, "--as", "bot"];
|
|
118352
|
+
if (opts.profile) args.push("--profile", opts.profile);
|
|
118353
|
+
const exec = opts.exec ?? defaultExec(opts.timeoutMs ?? 1e4);
|
|
118354
|
+
try {
|
|
118355
|
+
const stdout = await exec(cli, args);
|
|
118356
|
+
const roster = parseBotRoster(stdout);
|
|
118357
|
+
return roster.size > 0 ? roster : null;
|
|
118358
|
+
} catch {
|
|
118359
|
+
return null;
|
|
118360
|
+
}
|
|
118361
|
+
}
|
|
118362
|
+
function defaultExec(timeoutMs) {
|
|
118363
|
+
return (cmd, args) => new Promise((resolve2, reject) => {
|
|
118364
|
+
execFile2(cmd, args, { timeout: timeoutMs, maxBuffer: 1024 * 1024 }, (err, stdout) => {
|
|
118365
|
+
if (err) reject(err);
|
|
118366
|
+
else resolve2(stdout);
|
|
118367
|
+
});
|
|
118368
|
+
});
|
|
118369
|
+
}
|
|
118370
|
+
function createCachedRosterResolver(opts) {
|
|
118371
|
+
const ttlMs = opts.ttlMs ?? 5 * 60 * 1e3;
|
|
118372
|
+
const now = opts.now ?? (() => Date.now());
|
|
118373
|
+
const cache = /* @__PURE__ */ new Map();
|
|
118374
|
+
return async (chatId) => {
|
|
118375
|
+
const hit = cache.get(chatId);
|
|
118376
|
+
if (hit && now() - hit.at < ttlMs) return hit.roster;
|
|
118377
|
+
const roster = await resolveChatBotRoster(chatId, {
|
|
118378
|
+
profile: opts.profile,
|
|
118379
|
+
larkCliPath: opts.larkCliPath,
|
|
118380
|
+
exec: opts.exec
|
|
118381
|
+
});
|
|
118382
|
+
cache.set(chatId, { roster, at: now() });
|
|
118383
|
+
return roster;
|
|
118384
|
+
};
|
|
118385
|
+
}
|
|
118386
|
+
|
|
118309
118387
|
// src/agent/runner.ts
|
|
118310
118388
|
var _registry = /* @__PURE__ */ new Map();
|
|
118311
118389
|
function registerRunner(backend, factory) {
|
|
@@ -120132,7 +120210,7 @@ async function ensureNodeModules(worktreePath) {
|
|
|
120132
120210
|
if (await pathExists(modulesMarker)) return;
|
|
120133
120211
|
const start = Date.now();
|
|
120134
120212
|
try {
|
|
120135
|
-
await
|
|
120213
|
+
await execFile3(
|
|
120136
120214
|
"pnpm",
|
|
120137
120215
|
["install", "--offline", "--frozen-lockfile"],
|
|
120138
120216
|
{ cwd: monorepDir, timeoutMs: 18e4 }
|
|
@@ -120146,7 +120224,7 @@ async function ensureNodeModules(worktreePath) {
|
|
|
120146
120224
|
`[bridge.handler] --offline install failed (will fall back to network): ${offlineErr.message.slice(0, 200)}`
|
|
120147
120225
|
);
|
|
120148
120226
|
}
|
|
120149
|
-
await
|
|
120227
|
+
await execFile3(
|
|
120150
120228
|
"pnpm",
|
|
120151
120229
|
["install", "--frozen-lockfile"],
|
|
120152
120230
|
{ cwd: monorepDir, timeoutMs: 6e5 }
|
|
@@ -120155,7 +120233,7 @@ async function ensureNodeModules(worktreePath) {
|
|
|
120155
120233
|
`[bridge.handler] pnpm install (network) ok (${((Date.now() - start) / 1e3).toFixed(1)}s) in ${monorepDir}`
|
|
120156
120234
|
);
|
|
120157
120235
|
}
|
|
120158
|
-
function
|
|
120236
|
+
function execFile3(cmd, args, opts) {
|
|
120159
120237
|
return new Promise((resolve2, reject) => {
|
|
120160
120238
|
const child = child_process.spawn(cmd, args, {
|
|
120161
120239
|
cwd: opts.cwd,
|
|
@@ -120223,9 +120301,30 @@ async function createOnlyPostFallback(opts) {
|
|
|
120223
120301
|
var BridgeHandler = class {
|
|
120224
120302
|
deps;
|
|
120225
120303
|
closed = false;
|
|
120304
|
+
/**
|
|
120305
|
+
* In-flight per-turn completion promises. run() dispatches handleOne
|
|
120306
|
+
* fire-and-forget (thread-serialized), so run() returning does NOT mean the
|
|
120307
|
+
* turn finished. Each entry resolves only when its handleOne AND all trailing
|
|
120308
|
+
* work (terminal render + ledger writes, incl. the failure-path fallback that
|
|
120309
|
+
* runs after the early settle) have completed. {@link whenAllTurnsSettled}
|
|
120310
|
+
* awaits these — the real turn boundary tests must sync assertions/cleanup on.
|
|
120311
|
+
*/
|
|
120312
|
+
inFlightTurns = /* @__PURE__ */ new Set();
|
|
120226
120313
|
constructor(deps) {
|
|
120227
120314
|
this.deps = deps;
|
|
120228
120315
|
}
|
|
120316
|
+
/**
|
|
120317
|
+
* Resolves when every dispatched turn has fully completed (handleOne returned
|
|
120318
|
+
* and its trailing render/ledger I/O drained). Test-facing sync point; a bridge
|
|
120319
|
+
* that keeps receiving events would keep finding work, so callers use this
|
|
120320
|
+
* after the event source is exhausted (e.g. right after `await run()` in a
|
|
120321
|
+
* single-message test).
|
|
120322
|
+
*/
|
|
120323
|
+
async whenAllTurnsSettled() {
|
|
120324
|
+
while (this.inFlightTurns.size > 0) {
|
|
120325
|
+
await Promise.all([...this.inFlightTurns]);
|
|
120326
|
+
}
|
|
120327
|
+
}
|
|
120229
120328
|
runtimeWarnings() {
|
|
120230
120329
|
return (this.deps.runtimeRequirements ?? []).filter(
|
|
120231
120330
|
(req) => !req.ok && (req.severity === "required" || req.kind === "secret")
|
|
@@ -120276,7 +120375,9 @@ var BridgeHandler = class {
|
|
|
120276
120375
|
}).finally(() => {
|
|
120277
120376
|
release();
|
|
120278
120377
|
if (threadQueues.get(key) === next) threadQueues.delete(key);
|
|
120378
|
+
this.inFlightTurns.delete(next);
|
|
120279
120379
|
});
|
|
120380
|
+
this.inFlightTurns.add(next);
|
|
120280
120381
|
threadQueues.set(key, next);
|
|
120281
120382
|
}
|
|
120282
120383
|
}
|
|
@@ -120659,6 +120760,34 @@ var BridgeHandler = class {
|
|
|
120659
120760
|
while (true) {
|
|
120660
120761
|
attempt++;
|
|
120661
120762
|
const currentIsNewThread = currentExisting === void 0;
|
|
120763
|
+
let effectivePeers = this.deps.peers;
|
|
120764
|
+
if (effectivePeers?.length && this.deps.resolveLiveRoster) {
|
|
120765
|
+
try {
|
|
120766
|
+
const liveRoster = await this.deps.resolveLiveRoster(parsed.chatId);
|
|
120767
|
+
if (liveRoster) {
|
|
120768
|
+
const { peers: remappedPeers, remapped, unresolved } = remapPeersToLiveRoster(
|
|
120769
|
+
effectivePeers,
|
|
120770
|
+
liveRoster
|
|
120771
|
+
);
|
|
120772
|
+
effectivePeers = remappedPeers;
|
|
120773
|
+
if (remapped.length > 0 || unresolved.length > 0) {
|
|
120774
|
+
await recordEvent({
|
|
120775
|
+
status: "running",
|
|
120776
|
+
appendPath: "peer roster",
|
|
120777
|
+
reason: `live-roster resolve: remapped [${remapped.join(", ") || "none"}] to same-app-scope open_id; unresolved (kept static config id, may not be deliverable) [${unresolved.join(", ") || "none"}].`
|
|
120778
|
+
});
|
|
120779
|
+
}
|
|
120780
|
+
} else {
|
|
120781
|
+
await recordEvent({
|
|
120782
|
+
status: "running",
|
|
120783
|
+
appendPath: "peer roster",
|
|
120784
|
+
reason: "live-roster resolve returned nothing; kept static <peer-bots> open_ids (may be cross-app-scope / undeliverable)."
|
|
120785
|
+
});
|
|
120786
|
+
}
|
|
120787
|
+
} catch (err) {
|
|
120788
|
+
console.warn("[bridge.handler] live-roster resolve failed (using static peers):", err);
|
|
120789
|
+
}
|
|
120790
|
+
}
|
|
120662
120791
|
const prompt = renderPrompt({
|
|
120663
120792
|
parsed,
|
|
120664
120793
|
isNewThread: currentIsNewThread,
|
|
@@ -120680,7 +120809,7 @@ var BridgeHandler = class {
|
|
|
120680
120809
|
readOnly: conventions.readOnly,
|
|
120681
120810
|
gitlabTokenEnvName: conventions.gitlabTokenEnvName
|
|
120682
120811
|
},
|
|
120683
|
-
peers:
|
|
120812
|
+
peers: effectivePeers,
|
|
120684
120813
|
turn_taking_limit: this.deps.botConfig?.turn_taking_limit,
|
|
120685
120814
|
botName: this.deps.botConfig?.name,
|
|
120686
120815
|
backend: this.deps.botConfig?.backend,
|
|
@@ -126297,6 +126426,9 @@ async function runV2Mode({
|
|
|
126297
126426
|
gitlabToken: effectiveGitlabToken,
|
|
126298
126427
|
agentMemory: bot.agent_memory,
|
|
126299
126428
|
larkCliProfile,
|
|
126429
|
+
// PRB-6/§11.3: resolve peer @ targets to same-app-scope open_ids from the
|
|
126430
|
+
// live chat roster (per-chat cached), only when this bot actually has peers.
|
|
126431
|
+
resolveLiveRoster: resolvedPeers.length > 0 ? createCachedRosterResolver({ profile: larkCliProfile }) : void 0,
|
|
126300
126432
|
runtimeRequirements: runtimeRequirementsForBots([bot]),
|
|
126301
126433
|
recordRuntimeEvent: async (patch) => {
|
|
126302
126434
|
await upsertRuntimeEvent(larkwayHome(), bot.id, patch);
|