larkway 0.3.7 → 0.3.9
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 +2 -6
- package/README.zh.md +2 -6
- package/dist/cli/index.js +6163 -7278
- package/dist/main.js +249 -85
- package/dist/web/public/app.js +264 -1674
- package/dist/web/public/index.html +5 -37
- package/dist/web/public/style.css +63 -511
- package/package.json +1 -1
package/dist/main.js
CHANGED
|
@@ -107360,7 +107360,6 @@ var require_main = __commonJS({
|
|
|
107360
107360
|
});
|
|
107361
107361
|
|
|
107362
107362
|
// src/main.ts
|
|
107363
|
-
import { execSync } from "node:child_process";
|
|
107364
107363
|
import { mkdirSync } from "node:fs";
|
|
107365
107364
|
import path13 from "node:path";
|
|
107366
107365
|
|
|
@@ -111527,20 +111526,6 @@ var ChatEntry = external_exports.object({
|
|
|
111527
111526
|
purpose: external_exports.enum(["production", "test", "staging"]).default("production"),
|
|
111528
111527
|
description: external_exports.string().optional()
|
|
111529
111528
|
});
|
|
111530
|
-
var CentralConfig = external_exports.object({
|
|
111531
|
-
/**
|
|
111532
|
-
* Git URL or local path of the central config repo (where `bots/` lives).
|
|
111533
|
-
* e.g. "git@gitlab.company.com:ops/larkway-bots.git" or a bare repo path.
|
|
111534
|
-
*/
|
|
111535
|
-
repo: external_exports.string().min(1, "centralConfig.repo is required (git url or path)"),
|
|
111536
|
-
/** Branch to track on the central repo. @default "main" */
|
|
111537
|
-
branch: external_exports.string().min(1).default("main"),
|
|
111538
|
-
/**
|
|
111539
|
-
* Path INSIDE the central repo that holds the bot files (<id>.yaml +
|
|
111540
|
-
* <id>.memory.md). @default "bots"
|
|
111541
|
-
*/
|
|
111542
|
-
path: external_exports.string().min(1).default("bots")
|
|
111543
|
-
});
|
|
111544
111529
|
var ConfigJson = external_exports.object({
|
|
111545
111530
|
conventions: ConventionsConfig,
|
|
111546
111531
|
permissions: PermissionsConfig,
|
|
@@ -111548,12 +111533,7 @@ var ConfigJson = external_exports.object({
|
|
|
111548
111533
|
* List of allowed chats with labels. If empty/missing, bridge falls back
|
|
111549
111534
|
* to the legacy `LARK_ALLOWED_CHAT_IDS` env var (deprecated).
|
|
111550
111535
|
*/
|
|
111551
|
-
chats: external_exports.array(ChatEntry).default([])
|
|
111552
|
-
/**
|
|
111553
|
-
* Central config repo (V2.2 §7 A.2). Optional — when present, `larkway sync`
|
|
111554
|
-
* pulls bots/ from it. Absent = local self-management.
|
|
111555
|
-
*/
|
|
111556
|
-
centralConfig: CentralConfig.optional()
|
|
111536
|
+
chats: external_exports.array(ChatEntry).default([])
|
|
111557
111537
|
});
|
|
111558
111538
|
var DEFAULT_CONFIG_PATH = path2.join(larkwayHome(), "config.json");
|
|
111559
111539
|
function getLanIp() {
|
|
@@ -111577,8 +111557,7 @@ function defaultConfigJson() {
|
|
|
111577
111557
|
portRangeEnd: 3050
|
|
111578
111558
|
},
|
|
111579
111559
|
permissions: { allowExtra: [] },
|
|
111580
|
-
chats: []
|
|
111581
|
-
centralConfig: void 0
|
|
111560
|
+
chats: []
|
|
111582
111561
|
};
|
|
111583
111562
|
}
|
|
111584
111563
|
async function loadConfigJson(configPath = DEFAULT_CONFIG_PATH) {
|
|
@@ -112549,9 +112528,11 @@ ${recent.join("\n")}` : recent.join("\n");
|
|
|
112549
112528
|
});
|
|
112550
112529
|
elements.push({ tag: "hr" });
|
|
112551
112530
|
}
|
|
112531
|
+
const mentionPrefix = atMentionMarkdown(opts.mentionOpenId);
|
|
112552
112532
|
const cleanBody = opts.bodyText ? stripLeakedToolMarkup(opts.bodyText) : "";
|
|
112553
|
-
|
|
112554
|
-
|
|
112533
|
+
const bodyWithMention = [mentionPrefix, cleanBody].filter(Boolean).join("\n\n");
|
|
112534
|
+
if (bodyWithMention) {
|
|
112535
|
+
const chunks = chunkMarkdown(bodyWithMention);
|
|
112555
112536
|
for (let i = 0; i < chunks.length; i++) {
|
|
112556
112537
|
elements.push({
|
|
112557
112538
|
tag: "markdown",
|
|
@@ -112593,6 +112574,11 @@ ${chunks[i]}`
|
|
|
112593
112574
|
};
|
|
112594
112575
|
return JSON.stringify(card);
|
|
112595
112576
|
}
|
|
112577
|
+
function atMentionMarkdown(userId) {
|
|
112578
|
+
if (!userId) return "";
|
|
112579
|
+
if (!/^[A-Za-z0-9_:-]+$/.test(userId)) return "";
|
|
112580
|
+
return `<at id=${userId}></at>`;
|
|
112581
|
+
}
|
|
112596
112582
|
var CardHandleImpl = class {
|
|
112597
112583
|
messageId;
|
|
112598
112584
|
/** Transport for the leaf PATCH network call (default = lark-cli). */
|
|
@@ -112644,6 +112630,7 @@ var CardHandleImpl = class {
|
|
|
112644
112630
|
showToolSummary: this.showToolSummary,
|
|
112645
112631
|
status: cardStatus,
|
|
112646
112632
|
failureReason: opts.failureReason,
|
|
112633
|
+
mentionOpenId: opts.mentionOpenId,
|
|
112647
112634
|
// V2 dynamic-choice buttons — agent-declared, only on finalize.
|
|
112648
112635
|
choices: opts.choices,
|
|
112649
112636
|
choicePrompt: opts.choicePrompt,
|
|
@@ -113373,6 +113360,33 @@ function renderTurnTakingHint(limit) {
|
|
|
113373
113360
|
"</turn-taking>"
|
|
113374
113361
|
];
|
|
113375
113362
|
}
|
|
113363
|
+
function renderRuntimeWarningsBlock(warnings) {
|
|
113364
|
+
if (warnings.length === 0) return [];
|
|
113365
|
+
const hasMissingLarkCli = warnings.some((warning) => warning.command === "lark-cli");
|
|
113366
|
+
return [
|
|
113367
|
+
"<runtime-warnings>",
|
|
113368
|
+
"Bridge \u68C0\u6D4B\u5230\u4EE5\u4E0B\u672C\u673A\u80FD\u529B\u6682\u4E0D\u53EF\u7528\u3002\u8FD9\u662F\u63D0\u793A,\u4E0D\u662F\u5F3A\u5236\u505C\u6B62\u6761\u4EF6:",
|
|
113369
|
+
...warnings.map((warning) => {
|
|
113370
|
+
const name = warning.command ? `${warning.label} (${warning.command})` : warning.label;
|
|
113371
|
+
const reason = warning.reason ? `: ${warning.reason}` : "";
|
|
113372
|
+
const installHint = warning.installHint ? ` Fix hint: ${warning.installHint}` : "";
|
|
113373
|
+
return `- ${name}${reason}${installHint}`;
|
|
113374
|
+
}),
|
|
113375
|
+
"",
|
|
113376
|
+
"\u5904\u7406\u539F\u5219:",
|
|
113377
|
+
"- \u80FD\u4EC5\u51ED\u5F53\u524D\u6D88\u606F\u7EE7\u7EED\u7684\u4EFB\u52A1,\u7EE7\u7EED\u5904\u7406,\u4E0D\u8981\u56E0\u4E3A warning \u76F4\u63A5\u62D2\u7EDD\u3002",
|
|
113378
|
+
"- \u53EA\u6709\u5F53\u4EFB\u52A1\u786E\u5B9E\u9700\u8981\u7F3A\u5931\u80FD\u529B\u65F6,\u518D\u5728 last_message \u91CC\u7528\u4EA7\u54C1\u5316\u8BED\u8A00\u544A\u8BC9\u7528\u6237\u7F3A\u4EC0\u4E48\u3001\u4F1A\u5F71\u54CD\u4EC0\u4E48\u3001\u5982\u4F55\u7EE7\u7EED\u3002",
|
|
113379
|
+
...hasMissingLarkCli ? [
|
|
113380
|
+
"- \u5BF9\u7F3A\u5C11 lark-cli \u7684\u60C5\u51B5:\u4E0D\u8981\u989D\u5916 @ \u7528\u6237;\u5728\u5361\u7247\u91CC\u8F7B\u91CF\u8BF4\u660E\u5F53\u524D\u65E0\u6CD5\u81EA\u52A8\u8BFB\u53D6\u98DE\u4E66\u8BDD\u9898\u5386\u53F2\u3001\u9644\u4EF6\u6216\u6587\u6863\u5373\u53EF\u3002",
|
|
113381
|
+
'- \u5982\u679C\u5F53\u524D\u4EFB\u52A1\u9700\u8981\u8FD9\u4E9B\u4E0A\u4E0B\u6587,\u7528 choices \u95EE\u662F\u5426\u5141\u8BB8\u5B89\u88C5\u6700\u65B0\u7248\u98DE\u4E66 CLI\u3002\u5EFA\u8BAE: `choice_prompt: "\u8BFB\u53D6\u98DE\u4E66\u5386\u53F2\u9700\u8981\u672C\u673A\u5B89\u88C5\u6700\u65B0\u7248\u98DE\u4E66 CLI,\u662F\u5426\u5141\u8BB8\u6211\u5C1D\u8BD5\u5B89\u88C5?"`, `choices: [{label:"\u5141\u8BB8\u5B89\u88C5", value:"\u5141\u8BB8\u5B89\u88C5 lark-cli"}, {label:"\u5148\u4E0D\u5B89\u88C5", value:"\u5148\u4E0D\u5B89\u88C5 lark-cli,\u6211\u4F1A\u628A\u8981\u5904\u7406\u7684\u5185\u5BB9\u8D34\u5230\u8BDD\u9898\u91CC"}]`\u3002',
|
|
113382
|
+
"- \u7528\u6237\u660E\u786E\u9009\u62E9/\u56DE\u590D\u5141\u8BB8\u5B89\u88C5\u540E,\u518D\u5C1D\u8BD5\u5B89\u88C5;\u4E0D\u8981\u5728\u672A\u786E\u8BA4\u524D\u6539\u5BBF\u4E3B\u673A\u5168\u5C40\u73AF\u5883\u3002",
|
|
113383
|
+
"- \u63A8\u8350\u5B89\u88C5\u547D\u4EE4: `npx -y @larksuite/cli@latest install`,\u7136\u540E\u8FD0\u884C `lark-cli --version` \u9A8C\u8BC1\u3002",
|
|
113384
|
+
'- \u5982\u9047 npm \u5168\u5C40\u76EE\u5F55\u6743\u9650\u9519\u8BEF(EACCES/permission denied),\u4F7F\u7528\u7528\u6237\u7EA7 prefix \u540E\u91CD\u8BD5: `mkdir -p ~/.npm-global && npm config set prefix "$HOME/.npm-global" && export PATH="$HOME/.npm-global/bin:$PATH" && npx -y @larksuite/cli@latest install`\u3002\u4E0D\u8981\u9ED8\u8BA4\u8981\u6C42 sudo\u3002',
|
|
113385
|
+
"- \u5B89\u88C5\u6210\u529F\u540E,\u5982\u679C\u672C\u8F6E\u9700\u8981\u7ACB\u5373\u8BFB\u53D6\u98DE\u4E66\u4E0A\u4E0B\u6587,\u53EF\u5728\u5F53\u524D shell \u4E2D\u5E26\u4E0A\u4FEE\u590D\u540E\u7684 PATH \u7EE7\u7EED\u5C1D\u8BD5;\u82E5\u9700\u8981 bridge \u540E\u7EED\u8F6E\u6B21\u7A33\u5B9A\u4F7F\u7528,\u8BF7\u63D0\u793A owner \u91CD\u542F Larkway\u3002"
|
|
113386
|
+
] : [],
|
|
113387
|
+
"</runtime-warnings>"
|
|
113388
|
+
];
|
|
113389
|
+
}
|
|
113376
113390
|
function renderWorkspaceBlock(primarySlug, primaryCachePath, defaultBranch, extraRepos) {
|
|
113377
113391
|
const lines = [
|
|
113378
113392
|
"<workspace>",
|
|
@@ -113443,7 +113457,17 @@ function sceneFacts(parsed, isNewThread) {
|
|
|
113443
113457
|
};
|
|
113444
113458
|
}
|
|
113445
113459
|
function renderPrompt(input) {
|
|
113446
|
-
const {
|
|
113460
|
+
const {
|
|
113461
|
+
parsed,
|
|
113462
|
+
isNewThread,
|
|
113463
|
+
conventions,
|
|
113464
|
+
peers,
|
|
113465
|
+
turn_taking_limit,
|
|
113466
|
+
agentMemory,
|
|
113467
|
+
extraRepoPaths,
|
|
113468
|
+
larkCliProfile,
|
|
113469
|
+
runtimeWarnings = []
|
|
113470
|
+
} = input;
|
|
113447
113471
|
const backend = input.backend ?? "claude";
|
|
113448
113472
|
const profileFlag = larkCliProfile ? ` --profile ${larkCliProfile}` : "";
|
|
113449
113473
|
const attachmentKeys = parsed.attachments.map((a) => a.fileKey);
|
|
@@ -113456,6 +113480,7 @@ function renderPrompt(input) {
|
|
|
113456
113480
|
const stateContract = renderStateContract(conventions.stateFilePath);
|
|
113457
113481
|
const peersBlock = peers && peers.length > 0 ? renderPeersBlock(peers) : [];
|
|
113458
113482
|
const turnTakingBlock = turn_taking_limit && turn_taking_limit > 0 ? renderTurnTakingHint(turn_taking_limit) : [];
|
|
113483
|
+
const runtimeWarningsBlock = renderRuntimeWarningsBlock(runtimeWarnings);
|
|
113459
113484
|
const extraRepos = extraRepoPaths ?? conventions.extraRepoPaths ?? [];
|
|
113460
113485
|
const workspaceBlock = isAgentWorkspace ? renderAgentWorkspaceBlock(conventions, extraRepos) : hasRepo ? renderWorkspaceBlock(
|
|
113461
113486
|
conventions.defaultProjectSlug ?? "repo",
|
|
@@ -113511,6 +113536,7 @@ function renderPrompt(input) {
|
|
|
113511
113536
|
"",
|
|
113512
113537
|
...agentMemoryBlock,
|
|
113513
113538
|
...skillIntroNew,
|
|
113539
|
+
...runtimeWarningsBlock.length > 0 ? [...runtimeWarningsBlock, ""] : [],
|
|
113514
113540
|
"<thread-context>",
|
|
113515
113541
|
`thread_id: ${parsed.threadId}`,
|
|
113516
113542
|
`message_id: ${parsed.messageId}`,
|
|
@@ -113555,7 +113581,7 @@ function renderPrompt(input) {
|
|
|
113555
113581
|
` lark-cli docs +get <doc-url>${profileFlag}`,
|
|
113556
113582
|
"- \u53D6\u672C\u6761\u6D88\u606F\u7684\u9644\u4EF6/\u5185\u8054\u56FE(post \u5185\u8054\u56FE\u4E0D\u5728\u4E0A\u9762 attachments/images \u91CC)\u3001\u62C9\u8BDD\u9898\u5386\u53F2:",
|
|
113557
113583
|
attachmentHelpLine,
|
|
113558
|
-
"- glab /
|
|
113584
|
+
"- glab / gh / git API",
|
|
113559
113585
|
"- pnpm / npm",
|
|
113560
113586
|
"",
|
|
113561
113587
|
"**\u91CD\u8981**:`thread_id` \u5C31\u662F\u8BDD\u9898\u9996\u697C\u7684 message_id\u3002\u5982\u679C\u5F53\u524D\u6D88\u606F attachments/feishu_doc_links \u4E3A\u7A7A,\u8BF4\u660E\u8FD0\u8425\u628A\u7D20\u6750\u653E\u5728\u9996\u697C,**\u5148\u62C9\u9996\u697C\u770B\u8FD0\u8425\u539F\u59CB\u9700\u6C42**,\u518D\u51B3\u5B9A\u4E0B\u4E00\u6B65\u3002",
|
|
@@ -113578,6 +113604,7 @@ function renderPrompt(input) {
|
|
|
113578
113604
|
return [
|
|
113579
113605
|
...agentMemoryBlock,
|
|
113580
113606
|
...skillIntroCont,
|
|
113607
|
+
...runtimeWarningsBlock.length > 0 ? [...runtimeWarningsBlock, ""] : [],
|
|
113581
113608
|
"<thread-context>",
|
|
113582
113609
|
`thread_id: ${parsed.threadId}`,
|
|
113583
113610
|
`message_id: ${parsed.messageId}`,
|
|
@@ -113850,7 +113877,7 @@ function renderPermissionsRequest(input) {
|
|
|
113850
113877
|
"## Repo Pointers",
|
|
113851
113878
|
"",
|
|
113852
113879
|
...repoLines,
|
|
113853
|
-
input.bot.gitlab_token_env ? `-
|
|
113880
|
+
input.bot.gitlab_token_env ? `- Git token env name available after human setup: ${input.bot.gitlab_token_env}` : "- Git token env name: pending human confirmation",
|
|
113854
113881
|
"",
|
|
113855
113882
|
"## Human Gate",
|
|
113856
113883
|
"",
|
|
@@ -113876,11 +113903,11 @@ function defaultWorkspacePermissionItems(input) {
|
|
|
113876
113903
|
}
|
|
113877
113904
|
if (input.repos && input.repos.length > 0) {
|
|
113878
113905
|
for (const repo of input.repos) {
|
|
113879
|
-
capabilities.push(`
|
|
113906
|
+
capabilities.push(`Git repo pointer: ${repo.slug} (${repo.branch ?? "master"})`);
|
|
113880
113907
|
}
|
|
113881
113908
|
}
|
|
113882
113909
|
if (input.bot.gitlab_token_env) {
|
|
113883
|
-
capabilities.push(`
|
|
113910
|
+
capabilities.push(`Git token env name: ${input.bot.gitlab_token_env}`);
|
|
113884
113911
|
}
|
|
113885
113912
|
capabilities.push("Local shell inside the Agent Workspace for task execution and verification");
|
|
113886
113913
|
return permissionItemsFromCapabilities(capabilities);
|
|
@@ -113936,10 +113963,10 @@ function renderDefaultPermissionGrantLines(input) {
|
|
|
113936
113963
|
lines.push(`- type=read Feishu chat allowlist: ${chats.join(", ")} source=saved-agent-config`);
|
|
113937
113964
|
}
|
|
113938
113965
|
for (const repo of input.repos ?? []) {
|
|
113939
|
-
lines.push(`- type=read
|
|
113966
|
+
lines.push(`- type=read Git repo pointer: ${repo.slug} (${repo.branch ?? "master"}) source=saved-agent-config`);
|
|
113940
113967
|
}
|
|
113941
113968
|
if (input.bot.gitlab_token_env) {
|
|
113942
|
-
lines.push(`- type=write
|
|
113969
|
+
lines.push(`- type=write Git write/MR env=${input.bot.gitlab_token_env} source=saved-agent-config`);
|
|
113943
113970
|
}
|
|
113944
113971
|
const humanGates = input.humanGates && input.humanGates.length > 0 ? input.humanGates : ["Deploy/restart and production-impact actions require explicit human confirmation."];
|
|
113945
113972
|
for (const gate of humanGates) {
|
|
@@ -114461,6 +114488,11 @@ var BridgeHandler = class {
|
|
|
114461
114488
|
constructor(deps) {
|
|
114462
114489
|
this.deps = deps;
|
|
114463
114490
|
}
|
|
114491
|
+
runtimeWarnings() {
|
|
114492
|
+
return (this.deps.runtimeRequirements ?? []).filter(
|
|
114493
|
+
(req) => !req.ok && (req.severity === "required" || req.kind === "secret")
|
|
114494
|
+
);
|
|
114495
|
+
}
|
|
114464
114496
|
/**
|
|
114465
114497
|
* Enter the main loop: for-await over client.events(), per-thread concurrent dispatch.
|
|
114466
114498
|
*
|
|
@@ -114596,7 +114628,7 @@ var BridgeHandler = class {
|
|
|
114596
114628
|
bot: {
|
|
114597
114629
|
name: this.deps.botConfig?.name ?? "Larkway Agent",
|
|
114598
114630
|
description: this.deps.botConfig?.description ?? "Local agent served through Larkway.",
|
|
114599
|
-
gitlab_token_env: this.deps.botConfig?.gitlab_token_env
|
|
114631
|
+
gitlab_token_env: this.deps.botConfig?.git_token_env ?? this.deps.botConfig?.gitlab_token_env
|
|
114600
114632
|
},
|
|
114601
114633
|
agentMemory: this.deps.agentMemory,
|
|
114602
114634
|
repos: [
|
|
@@ -114762,7 +114794,8 @@ var BridgeHandler = class {
|
|
|
114762
114794
|
botName: this.deps.botConfig?.name,
|
|
114763
114795
|
backend: this.deps.botConfig?.backend,
|
|
114764
114796
|
agentMemory: this.deps.agentMemory,
|
|
114765
|
-
larkCliProfile: this.deps.larkCliProfile
|
|
114797
|
+
larkCliProfile: this.deps.larkCliProfile,
|
|
114798
|
+
runtimeWarnings: this.runtimeWarnings()
|
|
114766
114799
|
});
|
|
114767
114800
|
const backend = this.deps.botConfig?.backend ?? "claude";
|
|
114768
114801
|
const permissionMode = this.deps.permissionMode ?? (isAgentWorkspace ? "acceptEdits" : "bypassPermissions");
|
|
@@ -117872,6 +117905,14 @@ var GitIdentitySchema = external_exports.object({
|
|
|
117872
117905
|
name: external_exports.string().min(1),
|
|
117873
117906
|
email: external_exports.string().email()
|
|
117874
117907
|
});
|
|
117908
|
+
var GitCloneUrlSchema = external_exports.string().min(1).refine(
|
|
117909
|
+
(value) => {
|
|
117910
|
+
if (/^https?:\/\/\S+$/i.test(value)) return true;
|
|
117911
|
+
if (/^ssh:\/\/\S+$/i.test(value)) return true;
|
|
117912
|
+
return /^[A-Za-z0-9_.-]+@[^:\s]+:[^ \t\r\n]+$/.test(value);
|
|
117913
|
+
},
|
|
117914
|
+
"url must be an http(s), ssh://, or scp-like Git clone URL"
|
|
117915
|
+
);
|
|
117875
117916
|
var BotConfigSchema = external_exports.object({
|
|
117876
117917
|
/** Unique identifier, kebab-case. Used as key in sessionStore. */
|
|
117877
117918
|
id: external_exports.string().regex(/^[a-z0-9]+(-[a-z0-9]+)*$/, "id must be kebab-case"),
|
|
@@ -117946,7 +117987,7 @@ var BotConfigSchema = external_exports.object({
|
|
|
117946
117987
|
* Agent workspace runtime: URL is a pointer only; the Agent decides clone
|
|
117947
117988
|
* timing and destination.
|
|
117948
117989
|
*/
|
|
117949
|
-
url:
|
|
117990
|
+
url: GitCloneUrlSchema.optional()
|
|
117950
117991
|
})
|
|
117951
117992
|
).default([]),
|
|
117952
117993
|
/**
|
|
@@ -117977,13 +118018,23 @@ var BotConfigSchema = external_exports.object({
|
|
|
117977
118018
|
*/
|
|
117978
118019
|
lark_cli_profile: external_exports.string().min(1).optional(),
|
|
117979
118020
|
/**
|
|
117980
|
-
* Env-var *name* (not value) that holds this bot's
|
|
118021
|
+
* Env-var *name* (not value) that holds this bot's Git PAT.
|
|
117981
118022
|
* Read from process.env at startup and injected as GITLAB_TOKEN into the
|
|
117982
|
-
*
|
|
117983
|
-
* When absent, the
|
|
118023
|
+
* agent subprocess env, so MRs/git ops use the bot's own account.
|
|
118024
|
+
* When absent, the subprocess inherits process.env.GITLAB_TOKEN
|
|
117984
118025
|
* (V1 single-bot behavior — one global token).
|
|
118026
|
+
*
|
|
118027
|
+
* Preferred field name; `gitlab_token_env` below is the backward-compat alias.
|
|
118028
|
+
*/
|
|
118029
|
+
git_token_env: external_exports.string().min(1).optional(),
|
|
118030
|
+
// preferred: generic git PAT env-var name
|
|
118031
|
+
/**
|
|
118032
|
+
* @deprecated Backward-compat alias for `git_token_env`. If both are present,
|
|
118033
|
+
* main.ts prefers `git_token_env`. Old bot yamls with this field continue to
|
|
118034
|
+
* work without changes.
|
|
117985
118035
|
*/
|
|
117986
118036
|
gitlab_token_env: external_exports.string().min(1).optional(),
|
|
118037
|
+
// compat alias (legacy)
|
|
117987
118038
|
/**
|
|
117988
118039
|
* L2 Agent Memory (职能定义) — filename relative to the bots/ directory,
|
|
117989
118040
|
* pointing at this bot's `*.memory.md`. Loaded at startup and injected into
|
|
@@ -118294,12 +118345,12 @@ var SIGKILL_GRACE_MS = 5e3;
|
|
|
118294
118345
|
function buildEnv(botGitIdentity, gitlabToken) {
|
|
118295
118346
|
const env = { ...process.env };
|
|
118296
118347
|
delete env["ANTHROPIC_API_KEY"];
|
|
118297
|
-
|
|
118298
|
-
|
|
118299
|
-
|
|
118300
|
-
|
|
118301
|
-
|
|
118302
|
-
|
|
118348
|
+
if (botGitIdentity) {
|
|
118349
|
+
env["GIT_AUTHOR_NAME"] = botGitIdentity.name;
|
|
118350
|
+
env["GIT_AUTHOR_EMAIL"] = botGitIdentity.email;
|
|
118351
|
+
env["GIT_COMMITTER_NAME"] = botGitIdentity.name;
|
|
118352
|
+
env["GIT_COMMITTER_EMAIL"] = botGitIdentity.email;
|
|
118353
|
+
}
|
|
118303
118354
|
if (gitlabToken !== void 0) {
|
|
118304
118355
|
env["GITLAB_TOKEN"] = gitlabToken;
|
|
118305
118356
|
}
|
|
@@ -118608,12 +118659,12 @@ function buildCodexEnv(botGitIdentity, gitlabToken) {
|
|
|
118608
118659
|
const env = { ...process.env };
|
|
118609
118660
|
delete env["OPENAI_API_KEY"];
|
|
118610
118661
|
delete env["ANTHROPIC_API_KEY"];
|
|
118611
|
-
|
|
118612
|
-
|
|
118613
|
-
|
|
118614
|
-
|
|
118615
|
-
|
|
118616
|
-
|
|
118662
|
+
if (botGitIdentity) {
|
|
118663
|
+
env["GIT_AUTHOR_NAME"] = botGitIdentity.name;
|
|
118664
|
+
env["GIT_AUTHOR_EMAIL"] = botGitIdentity.email;
|
|
118665
|
+
env["GIT_COMMITTER_NAME"] = botGitIdentity.name;
|
|
118666
|
+
env["GIT_COMMITTER_EMAIL"] = botGitIdentity.email;
|
|
118667
|
+
}
|
|
118617
118668
|
if (gitlabToken !== void 0) {
|
|
118618
118669
|
env["GITLAB_TOKEN"] = gitlabToken;
|
|
118619
118670
|
}
|
|
@@ -118999,33 +119050,144 @@ function checkGrantMatchesBot(normalizedGrantText, bot) {
|
|
|
118999
119050
|
return { ok: true };
|
|
119000
119051
|
}
|
|
119001
119052
|
|
|
119002
|
-
// src/
|
|
119003
|
-
|
|
119004
|
-
|
|
119005
|
-
|
|
119053
|
+
// src/runtimeRequirements.ts
|
|
119054
|
+
import { execFileSync } from "node:child_process";
|
|
119055
|
+
function checkCli(command) {
|
|
119056
|
+
try {
|
|
119057
|
+
execFileSync("which", [command], { stdio: ["pipe", "pipe", "pipe"] });
|
|
119058
|
+
} catch {
|
|
119059
|
+
return { ok: false };
|
|
119060
|
+
}
|
|
119006
119061
|
try {
|
|
119007
|
-
const
|
|
119008
|
-
|
|
119009
|
-
const version = execSync(`${name} --version`, {
|
|
119010
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
119011
|
-
}).toString().trim().split("\n")[0] ?? "";
|
|
119012
|
-
return { ok: true, version };
|
|
119062
|
+
const version = execFileSync(command, ["--version"], { stdio: ["pipe", "pipe", "pipe"] }).toString().trim().split("\n")[0];
|
|
119063
|
+
return { ok: true, ...version ? { version } : {} };
|
|
119013
119064
|
} catch {
|
|
119014
|
-
return { ok:
|
|
119065
|
+
return { ok: true };
|
|
119015
119066
|
}
|
|
119016
119067
|
}
|
|
119017
|
-
function
|
|
119018
|
-
|
|
119019
|
-
return Array.from(/* @__PURE__ */ new Set(["lark-cli", ...backendBins, "glab"]));
|
|
119068
|
+
function repoLooksGitLab(urlOrSlug) {
|
|
119069
|
+
return /(^|[./:@-])gitlab([./:@-]|$)/i.test(urlOrSlug);
|
|
119020
119070
|
}
|
|
119071
|
+
function botUsesGitLab(bot) {
|
|
119072
|
+
return bot.repos.some((repo) => repoLooksGitLab(repo.url ?? repo.slug));
|
|
119073
|
+
}
|
|
119074
|
+
function addCliRequirement(requirements, input) {
|
|
119075
|
+
const existing = requirements.get(`cli:${input.command}`);
|
|
119076
|
+
if (existing) {
|
|
119077
|
+
existing.botIds = Array.from(/* @__PURE__ */ new Set([...existing.botIds, ...input.botIds])).sort();
|
|
119078
|
+
if (existing.severity === "optional" && input.severity === "required") {
|
|
119079
|
+
existing.severity = "required";
|
|
119080
|
+
}
|
|
119081
|
+
return;
|
|
119082
|
+
}
|
|
119083
|
+
const probe = checkCli(input.command);
|
|
119084
|
+
requirements.set(`cli:${input.command}`, {
|
|
119085
|
+
id: `cli:${input.command}`,
|
|
119086
|
+
label: input.label ?? input.command,
|
|
119087
|
+
command: input.command,
|
|
119088
|
+
kind: "cli",
|
|
119089
|
+
severity: input.severity,
|
|
119090
|
+
ok: probe.ok,
|
|
119091
|
+
...probe.version ? { version: probe.version } : {},
|
|
119092
|
+
reason: input.reason,
|
|
119093
|
+
...input.installHint ? { installHint: input.installHint } : {},
|
|
119094
|
+
botIds: Array.from(new Set(input.botIds)).sort()
|
|
119095
|
+
});
|
|
119096
|
+
}
|
|
119097
|
+
function addSecretRequirement(requirements, input) {
|
|
119098
|
+
const id = `secret:${input.envName}`;
|
|
119099
|
+
const existing = requirements.get(id);
|
|
119100
|
+
if (existing) {
|
|
119101
|
+
existing.botIds = Array.from(/* @__PURE__ */ new Set([...existing.botIds, ...input.botIds])).sort();
|
|
119102
|
+
return;
|
|
119103
|
+
}
|
|
119104
|
+
const value = process.env[input.envName];
|
|
119105
|
+
requirements.set(id, {
|
|
119106
|
+
id,
|
|
119107
|
+
label: input.envName,
|
|
119108
|
+
kind: "secret",
|
|
119109
|
+
severity: input.severity ?? "required",
|
|
119110
|
+
ok: value != null && value !== "",
|
|
119111
|
+
reason: input.reason,
|
|
119112
|
+
installHint: `Set ${input.envName} in ~/.larkway/.env and restart Larkway.`,
|
|
119113
|
+
botIds: Array.from(new Set(input.botIds)).sort()
|
|
119114
|
+
});
|
|
119115
|
+
}
|
|
119116
|
+
function runtimeRequirementsForBots(bots) {
|
|
119117
|
+
const requirements = /* @__PURE__ */ new Map();
|
|
119118
|
+
if (bots.length > 0) {
|
|
119119
|
+
addCliRequirement(requirements, {
|
|
119120
|
+
command: "lark-cli",
|
|
119121
|
+
label: "Feishu CLI",
|
|
119122
|
+
severity: "required",
|
|
119123
|
+
botIds: bots.map((bot) => bot.id),
|
|
119124
|
+
reason: "Required for agents to read Feishu topic history, attachments, docs, and other context.",
|
|
119125
|
+
installHint: "Install and configure lark-cli, then restart Larkway."
|
|
119126
|
+
});
|
|
119127
|
+
}
|
|
119128
|
+
for (const bot of bots) {
|
|
119129
|
+
addCliRequirement(requirements, {
|
|
119130
|
+
command: bot.backend ?? "claude",
|
|
119131
|
+
severity: "required",
|
|
119132
|
+
botIds: [bot.id],
|
|
119133
|
+
reason: `Required to run this bot's ${bot.backend ?? "claude"} backend.`
|
|
119134
|
+
});
|
|
119135
|
+
if (bot.runtime === "agent_workspace" && bot.repos.length > 0) {
|
|
119136
|
+
const tokenEnvName = bot.git_token_env ?? bot.gitlab_token_env;
|
|
119137
|
+
if (tokenEnvName) {
|
|
119138
|
+
addSecretRequirement(requirements, {
|
|
119139
|
+
envName: tokenEnvName,
|
|
119140
|
+
botIds: [bot.id],
|
|
119141
|
+
severity: "optional",
|
|
119142
|
+
reason: "Optional Git identity material. When absent, agents use the host's normal Git auth."
|
|
119143
|
+
});
|
|
119144
|
+
} else {
|
|
119145
|
+
requirements.set(`secret:missing-git-token:${bot.id}`, {
|
|
119146
|
+
id: `secret:missing-git-token:${bot.id}`,
|
|
119147
|
+
label: "Git access token env",
|
|
119148
|
+
kind: "secret",
|
|
119149
|
+
severity: "optional",
|
|
119150
|
+
ok: false,
|
|
119151
|
+
reason: `Bot "${bot.id}" has repo pointers but no git_token_env. It will use the host's normal Git auth.`,
|
|
119152
|
+
installHint: "Add a Git access token only when you want this agent to use a specific GitHub/GitLab identity.",
|
|
119153
|
+
botIds: [bot.id]
|
|
119154
|
+
});
|
|
119155
|
+
}
|
|
119156
|
+
}
|
|
119157
|
+
if (botUsesGitLab(bot)) {
|
|
119158
|
+
addCliRequirement(requirements, {
|
|
119159
|
+
command: "glab",
|
|
119160
|
+
label: "GitLab CLI",
|
|
119161
|
+
severity: "optional",
|
|
119162
|
+
botIds: [bot.id],
|
|
119163
|
+
reason: "Only needed when an agent uses GitLab-specific actions such as MRs, pipelines, or releases.",
|
|
119164
|
+
installHint: "Install glab only for bots that need GitLab-specific workflow commands."
|
|
119165
|
+
});
|
|
119166
|
+
}
|
|
119167
|
+
}
|
|
119168
|
+
return [...requirements.values()].sort((a, b) => {
|
|
119169
|
+
if (a.severity !== b.severity) return a.severity === "required" ? -1 : 1;
|
|
119170
|
+
if (a.kind !== b.kind) return a.kind === "cli" ? -1 : 1;
|
|
119171
|
+
return a.label.localeCompare(b.label);
|
|
119172
|
+
});
|
|
119173
|
+
}
|
|
119174
|
+
|
|
119175
|
+
// src/main.ts
|
|
119176
|
+
var STATUS_WRITE_INTERVAL_MS = 3e4;
|
|
119177
|
+
var VERSION = resolveLarkwayVersion(import.meta.url);
|
|
119021
119178
|
function printExternalCliProbe(bots) {
|
|
119022
|
-
console.log("
|
|
119023
|
-
for (const
|
|
119024
|
-
const
|
|
119025
|
-
|
|
119026
|
-
|
|
119179
|
+
console.log("Runtime requirements:");
|
|
119180
|
+
for (const req of runtimeRequirementsForBots(bots)) {
|
|
119181
|
+
const icon = req.ok ? "\u2713" : "\u2717";
|
|
119182
|
+
const tag = req.severity === "optional" ? "optional" : "required";
|
|
119183
|
+
const target = req.command ?? req.label;
|
|
119184
|
+
const botScope = req.botIds.length > 0 ? ` [${req.botIds.join(", ")}]` : "";
|
|
119185
|
+
if (req.ok) {
|
|
119186
|
+
console.log(` ${target.padEnd(14)} ${icon} ${req.version ?? tag}${botScope}`);
|
|
119027
119187
|
} else {
|
|
119028
|
-
|
|
119188
|
+
const message = req.kind === "secret" ? req.reason : `not found \u2014 ${req.reason}`;
|
|
119189
|
+
console.warn(` ${target.padEnd(14)} ${icon} (${tag}) ${message}${botScope}`);
|
|
119190
|
+
if (req.installHint) console.warn(` ${"".padEnd(14)} ${req.installHint}`);
|
|
119029
119191
|
}
|
|
119030
119192
|
}
|
|
119031
119193
|
}
|
|
@@ -119057,19 +119219,18 @@ async function runV2Mode({
|
|
|
119057
119219
|
continue;
|
|
119058
119220
|
}
|
|
119059
119221
|
if (bot.runtime === "agent_workspace") {
|
|
119060
|
-
|
|
119061
|
-
|
|
119062
|
-
|
|
119222
|
+
const _agentTokenEnvName = bot.git_token_env ?? bot.gitlab_token_env;
|
|
119223
|
+
if (bot.repos.length > 0 && !_agentTokenEnvName) {
|
|
119224
|
+
console.warn(
|
|
119225
|
+
`[larkway] bot "${bot.id}": runtime=agent_workspace has repo pointers but no git_token_env. Starting anyway; the agent will use the host's normal Git identity/auth.`
|
|
119063
119226
|
);
|
|
119064
|
-
continue;
|
|
119065
119227
|
}
|
|
119066
|
-
if (
|
|
119067
|
-
const
|
|
119068
|
-
if (
|
|
119069
|
-
console.
|
|
119070
|
-
`[larkway]
|
|
119228
|
+
if (_agentTokenEnvName) {
|
|
119229
|
+
const agentToken = process.env[_agentTokenEnvName];
|
|
119230
|
+
if (agentToken == null || agentToken === "") {
|
|
119231
|
+
console.warn(
|
|
119232
|
+
`[larkway] bot "${bot.id}": runtime=agent_workspace declares token env "${_agentTokenEnvName}", but that env var is unset/empty. Starting anyway; the agent will use the host's normal Git identity/auth.`
|
|
119071
119233
|
);
|
|
119072
|
-
continue;
|
|
119073
119234
|
}
|
|
119074
119235
|
}
|
|
119075
119236
|
const permissionGate = await checkWorkspacePermissionGrant(resolveAgentWorkspacePath(bot.id), bot);
|
|
@@ -119096,10 +119257,11 @@ async function runV2Mode({
|
|
|
119096
119257
|
const instances = [];
|
|
119097
119258
|
for (const bot of healthyBots) {
|
|
119098
119259
|
const appSecret = process.env[bot.app_secret_env];
|
|
119099
|
-
const
|
|
119100
|
-
|
|
119260
|
+
const tokenEnvName = bot.git_token_env ?? bot.gitlab_token_env;
|
|
119261
|
+
const gitlabToken = tokenEnvName != null ? process.env[tokenEnvName] : void 0;
|
|
119262
|
+
if (tokenEnvName != null && (gitlabToken == null || gitlabToken === "")) {
|
|
119101
119263
|
console.warn(
|
|
119102
|
-
`[larkway] bot "${bot.id}" declares
|
|
119264
|
+
`[larkway] bot "${bot.id}" declares token env "${tokenEnvName}" but that env var is unset/empty \u2014 leaving the host Git auth environment unchanged.`
|
|
119103
119265
|
);
|
|
119104
119266
|
}
|
|
119105
119267
|
const larkCliProfile = deriveLarkCliProfile(bot.lark_cli_profile, bot.app_id);
|
|
@@ -119168,9 +119330,9 @@ async function runV2Mode({
|
|
|
119168
119330
|
portRangeStart: configJson.conventions.portRangeStart,
|
|
119169
119331
|
portRangeEnd: configJson.conventions.portRangeEnd,
|
|
119170
119332
|
readOnly: bot.read_only,
|
|
119171
|
-
gitlabTokenEnvName:
|
|
119333
|
+
gitlabTokenEnvName: tokenEnvName
|
|
119172
119334
|
};
|
|
119173
|
-
const effectiveGitlabToken =
|
|
119335
|
+
const effectiveGitlabToken = gitlabToken;
|
|
119174
119336
|
const resolvedPeers = bot.peers.flatMap((peerId) => {
|
|
119175
119337
|
const peer = bots.find((b) => b.id === peerId);
|
|
119176
119338
|
if (!peer) return [];
|
|
@@ -119191,11 +119353,13 @@ async function runV2Mode({
|
|
|
119191
119353
|
git_identity: bot.git_identity,
|
|
119192
119354
|
backend: bot.backend,
|
|
119193
119355
|
runtime: bot.runtime,
|
|
119356
|
+
git_token_env: bot.git_token_env,
|
|
119194
119357
|
gitlab_token_env: bot.gitlab_token_env
|
|
119195
119358
|
},
|
|
119196
119359
|
gitlabToken: effectiveGitlabToken,
|
|
119197
119360
|
agentMemory: bot.agent_memory,
|
|
119198
119361
|
larkCliProfile,
|
|
119362
|
+
runtimeRequirements: runtimeRequirementsForBots([bot]),
|
|
119199
119363
|
recordRuntimeEvent: async (patch) => {
|
|
119200
119364
|
await upsertRuntimeEvent(larkwayHome(), bot.id, patch);
|
|
119201
119365
|
}
|