larkin 0.2.70

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.
Files changed (144) hide show
  1. package/CONTRIBUTING.md +48 -0
  2. package/LICENSE +201 -0
  3. package/README.md +122 -0
  4. package/SECURITY.md +14 -0
  5. package/artifacts/release/THIRD_PARTY_NOTICES.txt +5488 -0
  6. package/dist/agent/agent-cli-capabilities.cjs +43 -0
  7. package/dist/agent/agent-cli-capabilities.mjs +35 -0
  8. package/dist/agent/agent-state-store.cjs +961 -0
  9. package/dist/agent/agent-state-store.mjs +923 -0
  10. package/dist/agent/agent-transport.cjs +201 -0
  11. package/dist/agent/agent-transport.mjs +194 -0
  12. package/dist/agent/config-cli-contract.cjs +19 -0
  13. package/dist/agent/config-cli-contract.mjs +16 -0
  14. package/dist/agent/context-prompt.cjs +159 -0
  15. package/dist/agent/context-prompt.mjs +155 -0
  16. package/dist/agent/freshness-gate.mjs +24 -0
  17. package/dist/agent/history-projection.cjs +72 -0
  18. package/dist/agent/history-projection.mjs +65 -0
  19. package/dist/agent/host-reminder-orchestrator.cjs +289 -0
  20. package/dist/agent/host-reminder-orchestrator.mjs +252 -0
  21. package/dist/agent/inbox-projection.cjs +92 -0
  22. package/dist/agent/inbox-projection.mjs +85 -0
  23. package/dist/agent/interaction-state-machine.cjs +710 -0
  24. package/dist/agent/interaction-state-machine.mjs +705 -0
  25. package/dist/agent/reminder-routes.cjs +192 -0
  26. package/dist/agent/reminder-routes.mjs +156 -0
  27. package/dist/agent/reminder-store.cjs +220 -0
  28. package/dist/agent/reminder-store.mjs +205 -0
  29. package/dist/agent/transport-business-context.cjs +486 -0
  30. package/dist/agent/transport-business-context.mjs +446 -0
  31. package/dist/agent/transport-shell.cjs +45 -0
  32. package/dist/agent/transport-shell.mjs +39 -0
  33. package/dist/app/agent-cli.mjs +596 -0
  34. package/dist/app/agent-config.mjs +633 -0
  35. package/dist/app/agent-lark-cli-workspace.cjs +95 -0
  36. package/dist/app/agent-lark-cli-workspace.mjs +85 -0
  37. package/dist/app/agent-readiness.cjs +42 -0
  38. package/dist/app/agent-readiness.mjs +38 -0
  39. package/dist/app/binary-entry.mjs +86 -0
  40. package/dist/app/cli.mjs +176 -0
  41. package/dist/app/dashboard.mjs +205 -0
  42. package/dist/app/internal-command.cjs +57 -0
  43. package/dist/app/internal-command.mjs +48 -0
  44. package/dist/app/lark-channel-secret.mjs +22 -0
  45. package/dist/app/lark-cli.mjs +887 -0
  46. package/dist/app/lark.mjs +45 -0
  47. package/dist/app/local-control.mjs +728 -0
  48. package/dist/app/official-lark-cli.cjs +138 -0
  49. package/dist/app/official-lark-cli.mjs +128 -0
  50. package/dist/app/pi-auth-cli.mjs +67 -0
  51. package/dist/app/run.mjs +364 -0
  52. package/dist/app/runtime-agent-config.mjs +416 -0
  53. package/dist/app/runtime-model-directory.mjs +78 -0
  54. package/dist/app/runtime-process.mjs +113 -0
  55. package/dist/app/session-cli.mjs +83 -0
  56. package/dist/app/setup-dashboard.mjs +26 -0
  57. package/dist/app/setup.mjs +166 -0
  58. package/dist/app/telemetry.mjs +83 -0
  59. package/dist/dashboard/dashboard-assets.mjs +21 -0
  60. package/dist/dashboard/dashboard-avatar.mjs +128 -0
  61. package/dist/dashboard/dashboard-config-controller.mjs +525 -0
  62. package/dist/dashboard/dashboard-lifecycle.cjs +97 -0
  63. package/dist/dashboard/dashboard-lifecycle.mjs +88 -0
  64. package/dist/dashboard/dashboard-template.mjs +29 -0
  65. package/dist/dashboard/dashboard-view-model.mjs +556 -0
  66. package/dist/dashboard/dashboard-workspace.mjs +104 -0
  67. package/dist/dashboard/web/assets/dashboard.css +3 -0
  68. package/dist/dashboard/web/assets/dashboard.js +49 -0
  69. package/dist/feishu/channel-transport.cjs +151 -0
  70. package/dist/feishu/channel-transport.mjs +148 -0
  71. package/dist/feishu/document-comment.cjs +120 -0
  72. package/dist/feishu/document-comment.mjs +109 -0
  73. package/dist/feishu/host-business-state.cjs +477 -0
  74. package/dist/feishu/host-business-state.mjs +462 -0
  75. package/dist/feishu/host-channel-business.cjs +161 -0
  76. package/dist/feishu/host-channel-business.mjs +157 -0
  77. package/dist/feishu/host-processing-eye.cjs +185 -0
  78. package/dist/feishu/host-processing-eye.mjs +181 -0
  79. package/dist/feishu/host-shell.cjs +1425 -0
  80. package/dist/feishu/host-shell.mjs +1385 -0
  81. package/dist/feishu/im-freshness-adapter.mjs +89 -0
  82. package/dist/feishu/interaction-orchestrator.cjs +279 -0
  83. package/dist/feishu/interaction-orchestrator.mjs +242 -0
  84. package/dist/feishu/lark-passthrough.mjs +70 -0
  85. package/dist/feishu/message-policy.cjs +111 -0
  86. package/dist/feishu/message-policy.mjs +98 -0
  87. package/dist/feishu/outbound-transport.cjs +221 -0
  88. package/dist/feishu/outbound-transport.mjs +215 -0
  89. package/dist/platform/build-info.cjs +25 -0
  90. package/dist/platform/build-info.mjs +18 -0
  91. package/dist/platform/callback-capability.cjs +301 -0
  92. package/dist/platform/callback-capability.mjs +252 -0
  93. package/dist/platform/check-bun-version.cjs +12 -0
  94. package/dist/platform/config.cjs +751 -0
  95. package/dist/platform/config.mjs +694 -0
  96. package/dist/platform/process-inspect.cjs +89 -0
  97. package/dist/platform/process-state.cjs +237 -0
  98. package/dist/platform/process-state.mjs +223 -0
  99. package/dist/platform/release-artifacts.mjs +61 -0
  100. package/dist/platform/root-layout.cjs +111 -0
  101. package/dist/platform/root-layout.mjs +72 -0
  102. package/dist/platform/telemetry-config.cjs +64 -0
  103. package/dist/platform/telemetry-config.mjs +56 -0
  104. package/dist/platform/telemetry-spool.cjs +661 -0
  105. package/dist/platform/telemetry-spool.mjs +653 -0
  106. package/dist/platform/telemetry-tracing.cjs +602 -0
  107. package/dist/platform/telemetry-tracing.mjs +596 -0
  108. package/dist/platform/telemetry-uploader.cjs +150 -0
  109. package/dist/platform/telemetry-uploader.mjs +146 -0
  110. package/dist/platform/workspace-service.cjs +469 -0
  111. package/dist/platform/workspace-service.mjs +430 -0
  112. package/dist/runtime/builtin-pi-assets.mjs +33 -0
  113. package/dist/runtime/claude-model-catalog.cjs +102 -0
  114. package/dist/runtime/claude-model-catalog.mjs +99 -0
  115. package/dist/runtime/codex-model-catalog.cjs +108 -0
  116. package/dist/runtime/codex-model-catalog.mjs +105 -0
  117. package/dist/runtime/pi-bash-timeout-extension.mjs +60 -0
  118. package/dist/runtime/pi-bash-timeout-injection.mjs +60 -0
  119. package/dist/runtime/pi-bash-timeout.bundle.js +43 -0
  120. package/dist/runtime/pi-model-catalog.cjs +116 -0
  121. package/dist/runtime/pi-model-catalog.mjs +75 -0
  122. package/dist/runtime/pi-official-auth.mjs +480 -0
  123. package/dist/runtime/pi-provider-config.cjs +252 -0
  124. package/dist/runtime/pi-provider-config.mjs +239 -0
  125. package/dist/runtime/pi-rpc-client.cjs +255 -0
  126. package/dist/runtime/pi-rpc-client.mjs +251 -0
  127. package/dist/runtime/pi-subagent-injection.mjs +129 -0
  128. package/dist/runtime/pi-subagents.bundle.js +8990 -0
  129. package/dist/runtime/runtime-adapters.mjs +1019 -0
  130. package/dist/runtime/runtime-contracts.cjs +2 -0
  131. package/dist/runtime/runtime-contracts.mjs +1 -0
  132. package/dist/runtime/runtime-host.cjs +930 -0
  133. package/dist/runtime/runtime-host.mjs +922 -0
  134. package/dist/runtime/runtime-model-catalog.cjs +43 -0
  135. package/dist/runtime/runtime-model-catalog.mjs +40 -0
  136. package/dist/runtime/runtime-readiness.cjs +177 -0
  137. package/dist/runtime/runtime-readiness.mjs +133 -0
  138. package/dist/setup/bot-register.mjs +655 -0
  139. package/dist/setup/grant-scopes.mjs +133 -0
  140. package/dist/setup/run-credential-preflight.mjs +89 -0
  141. package/dist/setup/setup-agent-choice.mjs +172 -0
  142. package/dist/setup/setup-bind.mjs +337 -0
  143. package/dist/setup/setup-binding.mjs +52 -0
  144. package/package.json +112 -0
@@ -0,0 +1,95 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.larkChannelSourceConfigPath = larkChannelSourceConfigPath;
7
+ exports.larkChannelWorkspaceConfigPath = larkChannelWorkspaceConfigPath;
8
+ exports.managedLarkCliEnv = managedLarkCliEnv;
9
+ exports.managedOfficialLarkCli = managedOfficialLarkCli;
10
+ exports.assertAgentWorkspaceBound = assertAgentWorkspaceBound;
11
+ const node_fs_1 = __importDefault(require("node:fs"));
12
+ const node_path_1 = __importDefault(require("node:path"));
13
+ const official_lark_cli_js_1 = require("./official-lark-cli.cjs");
14
+ function larkChannelSourceConfigPath(agent) {
15
+ if (!agent.stateDir)
16
+ throw new Error(`Agent ${agent.agentId} 缺少 stateDir,无法建立 lark-channel binding`);
17
+ return node_path_1.default.join(node_path_1.default.resolve(agent.stateDir), "lark-channel-source", "config.json");
18
+ }
19
+ function larkChannelWorkspaceConfigPath(agent) {
20
+ if (!agent.larkConfigDir)
21
+ throw new Error(`Agent ${agent.agentId} 缺少 larkConfigDir,无法建立 lark-channel binding`);
22
+ return node_path_1.default.join(node_path_1.default.resolve(agent.larkConfigDir), "lark-channel", "config.json");
23
+ }
24
+ function managedLarkCliEnv(agent, env) {
25
+ if (!agent.larkConfigDir)
26
+ throw new Error(`Agent ${agent.agentId} 缺少 larkConfigDir,拒绝回退 local workspace`);
27
+ const isolated = { ...env };
28
+ for (const key of ["OPENCLAW_CLI", "OPENCLAW_HOME", "OPENCLAW_STATE_DIR", "OPENCLAW_CONFIG_PATH", "OPENCLAW_SERVICE_MARKER",
29
+ "OPENCLAW_SERVICE_VERSION", "OPENCLAW_GATEWAY_PORT", "OPENCLAW_SHELL", "HERMES_HOME", "HERMES_QUIET", "HERMES_EXEC_ASK",
30
+ "HERMES_GATEWAY_TOKEN", "HERMES_SESSION_KEY"])
31
+ delete isolated[key];
32
+ return {
33
+ ...isolated,
34
+ LARK_CHANNEL: "1",
35
+ LARK_CHANNEL_CONFIG: larkChannelSourceConfigPath(agent),
36
+ LARKSUITE_CLI_CONFIG_DIR: node_path_1.default.resolve(agent.larkConfigDir),
37
+ };
38
+ }
39
+ function managedOfficialLarkCli(agent, env, resolve = official_lark_cli_js_1.resolveOfficialLarkCli) {
40
+ assertAgentWorkspaceBound(agent);
41
+ const managedEnv = managedLarkCliEnv(agent, env);
42
+ return { command: resolve({ env: managedEnv }), env: managedEnv };
43
+ }
44
+ function readPrivateJson(file, label) {
45
+ const directory = node_fs_1.default.lstatSync(node_path_1.default.dirname(file));
46
+ if (!directory.isDirectory() || directory.isSymbolicLink() || (directory.mode & 0o777) !== 0o700
47
+ || (typeof process.getuid === "function" && directory.uid !== process.getuid()))
48
+ throw new Error(`${label} 目录不安全`);
49
+ const fd = node_fs_1.default.openSync(file, node_fs_1.default.constants.O_RDONLY | (node_fs_1.default.constants.O_NOFOLLOW || 0));
50
+ try {
51
+ const stat = node_fs_1.default.fstatSync(fd);
52
+ if (!stat.isFile() || (stat.mode & 0o777) !== 0o600
53
+ || (typeof process.getuid === "function" && stat.uid !== process.getuid()))
54
+ throw new Error(`${label} 文件不安全`);
55
+ return JSON.parse(node_fs_1.default.readFileSync(fd, "utf8"));
56
+ }
57
+ finally {
58
+ node_fs_1.default.closeSync(fd);
59
+ }
60
+ }
61
+ function assertAgentWorkspaceBound(agent) {
62
+ if (!agent.feishuAppId)
63
+ throw new Error(`Agent ${agent.agentId} 缺少 feishuAppId,拒绝回退 local workspace`);
64
+ let source;
65
+ let workspace;
66
+ try {
67
+ source = readPrivateJson(larkChannelSourceConfigPath(agent), "lark-channel source projection");
68
+ workspace = readPrivateJson(larkChannelWorkspaceConfigPath(agent), "lark-channel workspace");
69
+ }
70
+ catch (error) {
71
+ if (error.code === "ENOENT")
72
+ throw new Error(`Agent ${agent.agentId} lark-channel workspace 未绑定;运行 larkin setup`);
73
+ throw error;
74
+ }
75
+ const sourceApp = source.accounts?.app;
76
+ const provider = source.secrets?.providers?.["larkin-bot-credential"];
77
+ if (sourceApp?.id !== agent.feishuAppId || sourceApp?.secret?.source !== "exec"
78
+ || sourceApp?.secret?.provider !== "larkin-bot-credential" || sourceApp?.secret?.id !== agent.feishuAppId
79
+ || provider?.source !== "exec" || typeof provider.command !== "string" || !node_path_1.default.isAbsolute(provider.command)
80
+ || !Array.isArray(provider.args) || provider.env?.LARKIN_AGENT_ID !== agent.agentId
81
+ || provider.env?.LARKIN_SECRET_PROVIDER_CONTEXT !== "bind") {
82
+ throw new Error(`Agent ${agent.agentId} lark-channel source projection 错配`);
83
+ }
84
+ if (agent.credentialRevision && source.credentialRevision !== agent.credentialRevision) {
85
+ throw new Error(`Agent ${agent.agentId} lark-channel credential revision 已过期;运行 larkin setup`);
86
+ }
87
+ const apps = workspace.apps;
88
+ const app = Array.isArray(apps) && apps.length === 1 ? apps[0] : null;
89
+ if (!app || app.appId !== agent.feishuAppId || app.defaultAs !== "bot" || app.strictMode !== "bot"
90
+ || app.appSecret?.source !== "keychain" || app.appSecret?.id !== `appsecret:${agent.feishuAppId}`
91
+ || (Array.isArray(app.users) ? app.users.length !== 0
92
+ : app.users && typeof app.users === "object" ? Object.keys(app.users).length !== 0 : app.users !== undefined && app.users !== null)) {
93
+ throw new Error(`Agent ${agent.agentId} lark-channel workspace identity/bot-only policy 错配;运行 larkin setup`);
94
+ }
95
+ }
@@ -0,0 +1,85 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { resolveOfficialLarkCli } from "./official-lark-cli.mjs";
4
+ export function larkChannelSourceConfigPath(agent) {
5
+ if (!agent.stateDir)
6
+ throw new Error(`Agent ${agent.agentId} 缺少 stateDir,无法建立 lark-channel binding`);
7
+ return path.join(path.resolve(agent.stateDir), "lark-channel-source", "config.json");
8
+ }
9
+ export function larkChannelWorkspaceConfigPath(agent) {
10
+ if (!agent.larkConfigDir)
11
+ throw new Error(`Agent ${agent.agentId} 缺少 larkConfigDir,无法建立 lark-channel binding`);
12
+ return path.join(path.resolve(agent.larkConfigDir), "lark-channel", "config.json");
13
+ }
14
+ export function managedLarkCliEnv(agent, env) {
15
+ if (!agent.larkConfigDir)
16
+ throw new Error(`Agent ${agent.agentId} 缺少 larkConfigDir,拒绝回退 local workspace`);
17
+ const isolated = { ...env };
18
+ for (const key of ["OPENCLAW_CLI", "OPENCLAW_HOME", "OPENCLAW_STATE_DIR", "OPENCLAW_CONFIG_PATH", "OPENCLAW_SERVICE_MARKER",
19
+ "OPENCLAW_SERVICE_VERSION", "OPENCLAW_GATEWAY_PORT", "OPENCLAW_SHELL", "HERMES_HOME", "HERMES_QUIET", "HERMES_EXEC_ASK",
20
+ "HERMES_GATEWAY_TOKEN", "HERMES_SESSION_KEY"])
21
+ delete isolated[key];
22
+ return {
23
+ ...isolated,
24
+ LARK_CHANNEL: "1",
25
+ LARK_CHANNEL_CONFIG: larkChannelSourceConfigPath(agent),
26
+ LARKSUITE_CLI_CONFIG_DIR: path.resolve(agent.larkConfigDir),
27
+ };
28
+ }
29
+ export function managedOfficialLarkCli(agent, env, resolve = resolveOfficialLarkCli) {
30
+ assertAgentWorkspaceBound(agent);
31
+ const managedEnv = managedLarkCliEnv(agent, env);
32
+ return { command: resolve({ env: managedEnv }), env: managedEnv };
33
+ }
34
+ function readPrivateJson(file, label) {
35
+ const directory = fs.lstatSync(path.dirname(file));
36
+ if (!directory.isDirectory() || directory.isSymbolicLink() || (directory.mode & 0o777) !== 0o700
37
+ || (typeof process.getuid === "function" && directory.uid !== process.getuid()))
38
+ throw new Error(`${label} 目录不安全`);
39
+ const fd = fs.openSync(file, fs.constants.O_RDONLY | (fs.constants.O_NOFOLLOW || 0));
40
+ try {
41
+ const stat = fs.fstatSync(fd);
42
+ if (!stat.isFile() || (stat.mode & 0o777) !== 0o600
43
+ || (typeof process.getuid === "function" && stat.uid !== process.getuid()))
44
+ throw new Error(`${label} 文件不安全`);
45
+ return JSON.parse(fs.readFileSync(fd, "utf8"));
46
+ }
47
+ finally {
48
+ fs.closeSync(fd);
49
+ }
50
+ }
51
+ export function assertAgentWorkspaceBound(agent) {
52
+ if (!agent.feishuAppId)
53
+ throw new Error(`Agent ${agent.agentId} 缺少 feishuAppId,拒绝回退 local workspace`);
54
+ let source;
55
+ let workspace;
56
+ try {
57
+ source = readPrivateJson(larkChannelSourceConfigPath(agent), "lark-channel source projection");
58
+ workspace = readPrivateJson(larkChannelWorkspaceConfigPath(agent), "lark-channel workspace");
59
+ }
60
+ catch (error) {
61
+ if (error.code === "ENOENT")
62
+ throw new Error(`Agent ${agent.agentId} lark-channel workspace 未绑定;运行 larkin setup`);
63
+ throw error;
64
+ }
65
+ const sourceApp = source.accounts?.app;
66
+ const provider = source.secrets?.providers?.["larkin-bot-credential"];
67
+ if (sourceApp?.id !== agent.feishuAppId || sourceApp?.secret?.source !== "exec"
68
+ || sourceApp?.secret?.provider !== "larkin-bot-credential" || sourceApp?.secret?.id !== agent.feishuAppId
69
+ || provider?.source !== "exec" || typeof provider.command !== "string" || !path.isAbsolute(provider.command)
70
+ || !Array.isArray(provider.args) || provider.env?.LARKIN_AGENT_ID !== agent.agentId
71
+ || provider.env?.LARKIN_SECRET_PROVIDER_CONTEXT !== "bind") {
72
+ throw new Error(`Agent ${agent.agentId} lark-channel source projection 错配`);
73
+ }
74
+ if (agent.credentialRevision && source.credentialRevision !== agent.credentialRevision) {
75
+ throw new Error(`Agent ${agent.agentId} lark-channel credential revision 已过期;运行 larkin setup`);
76
+ }
77
+ const apps = workspace.apps;
78
+ const app = Array.isArray(apps) && apps.length === 1 ? apps[0] : null;
79
+ if (!app || app.appId !== agent.feishuAppId || app.defaultAs !== "bot" || app.strictMode !== "bot"
80
+ || app.appSecret?.source !== "keychain" || app.appSecret?.id !== `appsecret:${agent.feishuAppId}`
81
+ || (Array.isArray(app.users) ? app.users.length !== 0
82
+ : app.users && typeof app.users === "object" ? Object.keys(app.users).length !== 0 : app.users !== undefined && app.users !== null)) {
83
+ throw new Error(`Agent ${agent.agentId} lark-channel workspace identity/bot-only policy 错配;运行 larkin setup`);
84
+ }
85
+ }
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isChannelReconnecting = isChannelReconnecting;
4
+ exports.projectAgentReadiness = projectAgentReadiness;
5
+ const process_state_js_1 = require("../platform/process-state.cjs");
6
+ function timestamp(value) {
7
+ const parsed = Date.parse(String(value || ""));
8
+ return Number.isFinite(parsed) ? parsed : null;
9
+ }
10
+ function isChannelReconnecting(status) {
11
+ const reconnectingAt = timestamp(status?.reconnectingAt);
12
+ const connectedAt = timestamp(status?.connectedAt);
13
+ const reconnectedAt = timestamp(status?.reconnectedAt);
14
+ const latestConnectedAt = Math.max(connectedAt ?? Number.NEGATIVE_INFINITY, reconnectedAt ?? Number.NEGATIVE_INFINITY);
15
+ return reconnectingAt !== null && reconnectingAt > latestConnectedAt;
16
+ }
17
+ function projectAgentReadiness(input) {
18
+ const status = input.status ?? null;
19
+ const daemonOwned = (0, process_state_js_1.daemonHasAgent)(input.daemon, input.agentId);
20
+ const daemonStartedAt = timestamp(input.daemon.startedAt);
21
+ const connectedAt = timestamp(status?.connectedAt);
22
+ const channelConnected = daemonOwned
23
+ && status?.connectedVia === "channel"
24
+ && daemonStartedAt !== null
25
+ && connectedAt !== null
26
+ && connectedAt >= daemonStartedAt;
27
+ const readiness = {
28
+ daemon_owned: daemonOwned,
29
+ runtime_ready: status?.runtimeReadiness?.state === "ready",
30
+ channel_connected: channelConnected,
31
+ channel_not_reconnecting: !isChannelReconnecting(status),
32
+ };
33
+ return {
34
+ ready: Object.values(readiness).every(Boolean),
35
+ readiness,
36
+ channel: {
37
+ connected_at: connectedAt === null ? null : status?.connectedAt ?? null,
38
+ connected_via: status?.connectedVia || null,
39
+ inbound_verified_at: timestamp(status?.inboundVerifiedAt) === null ? null : status?.inboundVerifiedAt ?? null,
40
+ },
41
+ };
42
+ }
@@ -0,0 +1,38 @@
1
+ import { daemonHasAgent } from "../platform/process-state.mjs";
2
+ function timestamp(value) {
3
+ const parsed = Date.parse(String(value || ""));
4
+ return Number.isFinite(parsed) ? parsed : null;
5
+ }
6
+ export function isChannelReconnecting(status) {
7
+ const reconnectingAt = timestamp(status?.reconnectingAt);
8
+ const connectedAt = timestamp(status?.connectedAt);
9
+ const reconnectedAt = timestamp(status?.reconnectedAt);
10
+ const latestConnectedAt = Math.max(connectedAt ?? Number.NEGATIVE_INFINITY, reconnectedAt ?? Number.NEGATIVE_INFINITY);
11
+ return reconnectingAt !== null && reconnectingAt > latestConnectedAt;
12
+ }
13
+ export function projectAgentReadiness(input) {
14
+ const status = input.status ?? null;
15
+ const daemonOwned = daemonHasAgent(input.daemon, input.agentId);
16
+ const daemonStartedAt = timestamp(input.daemon.startedAt);
17
+ const connectedAt = timestamp(status?.connectedAt);
18
+ const channelConnected = daemonOwned
19
+ && status?.connectedVia === "channel"
20
+ && daemonStartedAt !== null
21
+ && connectedAt !== null
22
+ && connectedAt >= daemonStartedAt;
23
+ const readiness = {
24
+ daemon_owned: daemonOwned,
25
+ runtime_ready: status?.runtimeReadiness?.state === "ready",
26
+ channel_connected: channelConnected,
27
+ channel_not_reconnecting: !isChannelReconnecting(status),
28
+ };
29
+ return {
30
+ ready: Object.values(readiness).every(Boolean),
31
+ readiness,
32
+ channel: {
33
+ connected_at: connectedAt === null ? null : status?.connectedAt ?? null,
34
+ connected_via: status?.connectedVia || null,
35
+ inbound_verified_at: timestamp(status?.inboundVerifiedAt) === null ? null : status?.inboundVerifiedAt ?? null,
36
+ },
37
+ };
38
+ }
@@ -0,0 +1,86 @@
1
+ #!/usr/bin/env bun
2
+ import { INTERNAL_COMMAND_MARKER, INTERNAL_MODES } from "./internal-command.mjs";
3
+ function normalizeArgv(mode, rest) {
4
+ process.env.LARKIN_INTERNAL_DISPATCH = "1";
5
+ process.argv = [process.execPath, `${INTERNAL_COMMAND_MARKER} ${mode}`, ...rest];
6
+ }
7
+ async function dispatchInternal(mode, rest) {
8
+ normalizeArgv(mode, rest);
9
+ switch (mode) {
10
+ case "run":
11
+ await (await import("./run.mjs")).main();
12
+ return;
13
+ case "setup":
14
+ await (await import("./setup.mjs")).main();
15
+ return;
16
+ case "runtime-process":
17
+ await (await import("./runtime-process.mjs")).main();
18
+ return;
19
+ case "agent-cli":
20
+ await (await import("./agent-cli.mjs")).main(rest, process.env);
21
+ return;
22
+ case "lark-cli":
23
+ process.exitCode = await (await import("./lark-cli.mjs")).runLarkCliProcess(rest, process.env);
24
+ return;
25
+ case "runtime-model-directory":
26
+ await (await import("./runtime-model-directory.mjs")).main();
27
+ return;
28
+ case "agent-config":
29
+ await import("./agent-config.mjs");
30
+ return;
31
+ case "session-cli":
32
+ await (await import("./session-cli.mjs")).main(rest, process.env);
33
+ return;
34
+ case "lark":
35
+ await import("./lark.mjs");
36
+ return;
37
+ case "dashboard":
38
+ await import("./dashboard.mjs");
39
+ return;
40
+ case "bot-register":
41
+ await (await import("../setup/bot-register.mjs")).main();
42
+ return;
43
+ case "setup-bind":
44
+ await (await import("../setup/setup-bind.mjs")).main();
45
+ return;
46
+ case "grant-scopes":
47
+ await (await import("../setup/grant-scopes.mjs")).main();
48
+ return;
49
+ case "lark-channel-secret":
50
+ await (await import("./lark-channel-secret.mjs")).main(process.env);
51
+ return;
52
+ case "pi-rpc": {
53
+ const { prepareBuiltinPiPackageAssets } = await import("../runtime/builtin-pi-assets.mjs");
54
+ prepareBuiltinPiPackageAssets();
55
+ await import("@earendil-works/pi-coding-agent/rpc-entry");
56
+ return;
57
+ }
58
+ case "pi-auth":
59
+ await (await import("./pi-auth-cli.mjs")).main(rest, process.env);
60
+ return;
61
+ case "telemetry":
62
+ await (await import("./telemetry.mjs")).main(rest, process.env);
63
+ return;
64
+ }
65
+ }
66
+ export async function main() {
67
+ const standalone = process.env.LARKIN_STANDALONE === "1";
68
+ if (!standalone && process.argv[1])
69
+ process.env.LARKIN_BINARY_ENTRY_PATH = process.argv[1];
70
+ const argv = process.argv.slice(standalone ? 1 : 2);
71
+ if (argv[0] === INTERNAL_COMMAND_MARKER) {
72
+ const mode = argv[1];
73
+ if (!mode || !INTERNAL_MODES.includes(mode))
74
+ throw new Error("invalid internal command");
75
+ await dispatchInternal(mode, argv.slice(2));
76
+ return;
77
+ }
78
+ process.argv = [process.execPath, "larkin", ...argv];
79
+ await import("./cli.mjs");
80
+ }
81
+ if (typeof LARKIN_WRAPPED_ENTRY === "undefined" || !LARKIN_WRAPPED_ENTRY) {
82
+ main().catch((error) => {
83
+ process.stderr.write(`larkin: ${error instanceof Error ? error.message : String(error)}\n`);
84
+ process.exitCode = 1;
85
+ });
86
+ }
@@ -0,0 +1,176 @@
1
+ #!/usr/bin/env bun
2
+ // Public larkin CLI router. This file deliberately owns only the outer shell:
3
+ // command selection, read-only help, child spawning, and signal/exit forwarding.
4
+ import "../platform/check-bun-version.cjs";
5
+ import { spawn } from "node:child_process";
6
+ import path from "node:path";
7
+ import { fileURLToPath } from "node:url";
8
+ import { CONFIG_CLI_USAGE } from "../agent/config-cli-contract.mjs";
9
+ import { internalCommandSpec } from "./internal-command.mjs";
10
+ import { packageVersion } from "../platform/build-info.mjs";
11
+ const command = process.argv[2] || "help";
12
+ let rest = process.argv.slice(3);
13
+ const PACKAGE_VERSION = packageVersion(path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../.."));
14
+ const removedCommandMessages = {
15
+ init: "首次配置或重新授权请使用 larkin setup。",
16
+ "bot:connect": "首次配置或重新授权请使用 larkin setup。",
17
+ build: "源码构建命令已移除;仓库维护者请使用 bun run build。",
18
+ dashboard: "dashboard 已由 larkin start 自动启动和监控。",
19
+ };
20
+ if (removedCommandMessages[command]) {
21
+ console.error(`larkin: ${command} 已移除;${removedCommandMessages[command]}`);
22
+ process.exit(1);
23
+ }
24
+ const routes = {
25
+ start: ["run"],
26
+ setup: ["setup"],
27
+ status: ["agent-config", "agents"],
28
+ agents: ["agent-config", "agents"],
29
+ model: ["agent-config", "model"],
30
+ runtime: ["agent-config", "runtime"],
31
+ effort: ["agent-config", "effort"],
32
+ chats: ["agent-config", "chats"],
33
+ config: ["agent-config", "config"],
34
+ session: ["session-cli"],
35
+ "pi-auth": ["pi-auth"],
36
+ telemetry: ["telemetry"],
37
+ };
38
+ const runtimeAgentAuthority = typeof process.env.LARKIN_AGENT_ID === "string"
39
+ && process.env.LARKIN_AGENT_ID.trim().length > 0;
40
+ const runtimeAgentCommand = runtimeAgentAuthority
41
+ && ["inbox", "reminder", "interaction", "profile", "config"].includes(command);
42
+ if (runtimeAgentCommand)
43
+ routes[command] = ["agent-cli", command];
44
+ if (runtimeAgentAuthority && command === "comment")
45
+ routes.comment = ["lark-cli", "comment"];
46
+ // Help stays in the outer CLI so it never loads configuration, inspects a process,
47
+ // or starts a foreground service merely to print usage.
48
+ const commandHelp = {
49
+ start: `Usage: larkin start [--agent <App ID> | --agents <App ID,...>]
50
+ Start one foreground supervisor for the daemon and local dashboard, or reuse it.`,
51
+ setup: `Usage: larkin setup [--runtime <runtime>] [--comment-subscription <none|application>] [--no-start]
52
+ Run the interactive setup to create or connect a bot and configure its Agent.`,
53
+ status: `Usage: larkin status [--json]
54
+ Show Agent configuration, bot identity, credentials, and connection status. Use --json for readiness automation.`,
55
+ agents: `Usage: larkin agents [--json]
56
+ List every configured Agent and its current local status. Use --json for daemon/Runtime/channel readiness.`,
57
+ model: `Usage: larkin model [<model>] [--agent <App ID>]
58
+ Show or change an Agent model.`,
59
+ runtime: `Usage: larkin runtime [<runtime>] [--agent <App ID>] [--model <model>]
60
+ Show or change an Agent runtime.`,
61
+ effort: `Usage: larkin effort [<level>|clear|default] [--agent <App ID>]
62
+ Show or change an Agent reasoning effort; clear/default restores the Runtime default.`,
63
+ chats: `Usage: larkin chats [--agent <App ID>]
64
+ larkin chats (free|strict) <oc_id> [--agent <App ID>]
65
+ List known chats or configure mention requirements.`,
66
+ config: `Usage: ${CONFIG_CLI_USAGE.join("\n ")}
67
+ Inspect and safely update user-facing Larkin configuration.
68
+
69
+ Values:
70
+ default / clear Do not persist a model override / clear effort to Runtime default
71
+ inherit Clear an Agent or chat mention override
72
+
73
+ Examples:
74
+ larkin config show --agent cli_x --chat oc_x --json
75
+ larkin config runtime pi --agent cli_x --model default
76
+ larkin config mention chat oc_x free --agent cli_x
77
+
78
+ Credentials, internal paths, serverId, activeAgent, and raw config are never exposed here.`,
79
+ session: `Usage: larkin session reset --agent <App ID> --json [--wait-ready <seconds>]
80
+ Atomically replace one idle, zero-backlog Agent Runtime session through authenticated local control.`,
81
+ "pi-auth": `Usage: larkin pi-auth status [--agent <App ID>] [--json]
82
+ larkin pi-auth logout <provider> [--agent <App ID>]
83
+ Show non-sensitive official Pi credential metadata or remove one target provider credential.`,
84
+ comment: `Usage: larkin comment reply --message-id <doc_comment_message_id> --text '<reply>' --json
85
+ Reply once, as the Runtime-bound Bot, to the exact cloud-document comment locator supplied by canonical Inbox.`,
86
+ telemetry: `Usage: larkin telemetry <status|export|import|flush>
87
+ Inspect the durable local trace queue, move an offline bundle, or upload queued OTLP traces.`,
88
+ };
89
+ if (command === "--version" || command === "-V") {
90
+ console.log(`larkin ${PACKAGE_VERSION}`);
91
+ process.exit(0);
92
+ }
93
+ if (command === "help" && rest[0] && commandHelp[rest[0]]) {
94
+ console.log(commandHelp[rest[0]]);
95
+ process.exit(0);
96
+ }
97
+ if (!runtimeAgentCommand && routes[command] && rest.some((argument) => argument === "--help" || argument === "-h")) {
98
+ console.log(commandHelp[command]);
99
+ process.exit(0);
100
+ }
101
+ if (!runtimeAgentCommand && command === "config" && ["runtime", "model", "effort"].includes(rest[0] || "")) {
102
+ routes.config = ["agent-config", rest[0]];
103
+ rest = rest.slice(1);
104
+ }
105
+ // At a user terminal, unregistered non-flag commands keep the legacy lark-cli passthrough.
106
+ // Inside an Agent Runtime, unknown Feishu command groups enter the same Larkin-owned
107
+ // identity and freshness AOP before delegation to the verified global official CLI.
108
+ const wantsHelp = command === "help" || command === "--help" || command === "-h"
109
+ || (!runtimeAgentAuthority && command.startsWith("-"));
110
+ if (!routes[command] && !wantsHelp) {
111
+ routes[command] = runtimeAgentAuthority ? ["lark-cli", command] : ["lark", command];
112
+ }
113
+ if (!routes[command]) {
114
+ console.log(`larkin — Run persistent-personality agents on Feishu
115
+
116
+ Usage: larkin <command>
117
+ setup Create or connect a bot, configure its Agent, start it, and open the dashboard
118
+ start Start and supervise configured Agents plus the local dashboard
119
+ status Show Agent configuration, bot identity, credentials, and connection status
120
+ agents List all Agents, including runtime, model, bot identity, and credential status
121
+ model [<id>] Show or change an Agent model, with workspace validation
122
+ runtime [<id>] Show or change an Agent runtime; changing it resets the model to its default
123
+ effort [<level>] Show or set the reasoning effort; use clear to restore the default
124
+ chats List known chats; use free/strict <oc_id> to configure mention requirements
125
+ config Inspect effective config/source, edit mention inheritance, or explicitly apply runtime changes
126
+ session reset Replace one idle, zero-backlog Agent Runtime session for a fresh scenario
127
+ pi-auth Show non-sensitive built-in Pi auth status or logout one provider
128
+ comment reply Reply to the exact cloud-document comment bound by a polled Inbox message
129
+ telemetry Inspect, export, import, or flush the durable OpenTelemetry trace queue
130
+ <lark-cli 命令组> im/docs/wiki/drive 等 lark-cli 命令原样转发,机器人身份已锁定(如 larkin im +chat-list)
131
+ Getting started:
132
+ First-time setup: larkin setup
133
+ Daily startup: larkin start
134
+ Health check: larkin status
135
+
136
+ Process persistence is managed externally. larkin start stays in the foreground and supervises the daemon and dashboard.`);
137
+ process.exit(command === "help" || command === "--help" || command === "-h" ? 0 : 1);
138
+ }
139
+ const [mode, ...presetArguments] = routes[command];
140
+ const childSpec = internalCommandSpec(mode, [...presetArguments, ...rest]);
141
+ const child = spawn(childSpec.command, childSpec.args, { stdio: "inherit" });
142
+ // Package-manager bin shims add a wrapper process. Forward terminal signals so the actual command
143
+ // does not become an orphan that keeps the machine lock or Feishu connection.
144
+ const signalForwarders = new Map();
145
+ for (const signal of ["SIGINT", "SIGTERM"]) {
146
+ const forward = () => {
147
+ if (child.exitCode === null && child.signalCode === null)
148
+ child.kill(signal);
149
+ };
150
+ signalForwarders.set(signal, forward);
151
+ process.on(signal, forward);
152
+ }
153
+ const stopForwardingSignals = () => {
154
+ for (const [signal, forward] of signalForwarders)
155
+ process.off(signal, forward);
156
+ };
157
+ child.on("error", (error) => {
158
+ stopForwardingSignals();
159
+ console.error(`larkin ${command} 启动失败: ${error.message}`);
160
+ process.exit(1);
161
+ });
162
+ child.on("exit", (code, signal) => {
163
+ stopForwardingSignals();
164
+ if (code !== null)
165
+ process.exit(code);
166
+ if (!signal)
167
+ process.exit(1);
168
+ const fallback = signal === "SIGINT" ? 130 : signal === "SIGTERM" ? 143 : signal === "SIGKILL" ? 137 : 1;
169
+ try {
170
+ process.kill(process.pid, signal);
171
+ }
172
+ catch {
173
+ process.exit(fallback);
174
+ }
175
+ setImmediate(() => process.exit(fallback));
176
+ });