@sleep2agi/agent-network 2.3.0-preview.11 → 2.3.0-preview.110

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 (116) hide show
  1. package/README.md +35 -324
  2. package/dist/bin/anet.cjs +101 -0
  3. package/dist/bin/cli.d.ts +1 -0
  4. package/dist/bin/cli.js +20 -1
  5. package/dist/bin/goal-wake-log-render.d.ts +31 -0
  6. package/dist/src/active-network-task.d.ts +28 -0
  7. package/dist/src/batch-workdir.d.ts +9 -0
  8. package/dist/src/bootstrap-password-db.d.ts +13 -0
  9. package/dist/src/channel-attachments.d.ts +29 -0
  10. package/dist/src/channel-meta.d.ts +10 -0
  11. package/dist/src/channel-task-trace.d.ts +14 -0
  12. package/dist/src/claude-vendor-env.d.ts +28 -0
  13. package/dist/src/cli-args.d.ts +8 -0
  14. package/dist/src/cli-version-display.d.ts +16 -0
  15. package/dist/src/client-task-trace.d.ts +9 -0
  16. package/dist/src/client.js +1 -1
  17. package/dist/src/codex-auth-fingerprint.d.ts +211 -0
  18. package/dist/src/codex-copresence-preflight.d.ts +65 -0
  19. package/dist/src/codex-copresence-profile.d.ts +125 -0
  20. package/dist/src/codex-copresence-recovery.d.ts +43 -0
  21. package/dist/src/codex-copresence-thread.d.ts +13 -0
  22. package/dist/src/codex-lifecycle-account.d.ts +104 -0
  23. package/dist/src/codex-lifecycle-canary.d.ts +20 -0
  24. package/dist/src/codex-lifecycle-facts.d.ts +53 -0
  25. package/dist/src/codex-lifecycle-fork.d.ts +91 -0
  26. package/dist/src/codex-lifecycle-preflight.d.ts +77 -0
  27. package/dist/src/codex-lifecycle-receipt.d.ts +44 -0
  28. package/dist/src/codex-lifecycle-restart.d.ts +86 -0
  29. package/dist/src/codex-model-default.d.ts +6 -0
  30. package/dist/src/codex-tui-client-health.d.ts +17 -0
  31. package/dist/src/commhub-response.d.ts +1 -0
  32. package/dist/src/controlled-upload.d.ts +83 -0
  33. package/dist/src/copresence-deps.d.ts +22 -0
  34. package/dist/src/copresence-identity.d.ts +353 -0
  35. package/dist/src/copresence-startup-diagnosis.d.ts +17 -0
  36. package/dist/src/daemon-anet-bin.d.ts +32 -0
  37. package/dist/src/daemon-capability-display.d.ts +110 -0
  38. package/dist/src/daemon-capability-display.js +21 -0
  39. package/dist/src/daemon-runtime-path-preflight.d.ts +22 -0
  40. package/dist/src/daemon-runtime-staleness.d.ts +13 -0
  41. package/dist/src/dashboard-managed-process.d.ts +35 -0
  42. package/dist/src/display-width.d.ts +27 -0
  43. package/dist/src/doctor-node-count.d.ts +28 -0
  44. package/dist/src/environ-alias.d.ts +14 -0
  45. package/dist/src/grok-attach-client.d.ts +129 -0
  46. package/dist/src/grok-build-drift.d.ts +15 -0
  47. package/dist/src/grok-copresence-disclosure.d.ts +11 -0
  48. package/dist/src/grok-copresence-orchestration.d.ts +75 -0
  49. package/dist/src/grok-copresence-profile.d.ts +65 -0
  50. package/dist/src/host-telemetry.d.ts +14 -0
  51. package/dist/src/hub-health-timeout.d.ts +4 -0
  52. package/dist/src/hub-time-display.d.ts +20 -0
  53. package/dist/src/hub-version-skew.d.ts +19 -0
  54. package/dist/src/im/correlation-store.d.ts +34 -0
  55. package/dist/src/im/feishu/adapter.d.ts +80 -0
  56. package/dist/src/im/feishu/bridge.d.ts +78 -14
  57. package/dist/src/im/feishu/config.d.ts +31 -0
  58. package/dist/src/im/feishu/hub-upload.d.ts +88 -0
  59. package/dist/src/im/feishu/outbound-route.d.ts +62 -0
  60. package/dist/src/im/feishu/worker-lifecycle.d.ts +13 -0
  61. package/dist/src/im/feishu/worker.js +187 -144
  62. package/dist/src/im/types.d.ts +30 -1
  63. package/dist/src/inbox-drain.d.ts +20 -0
  64. package/dist/src/local-process-state.d.ts +12 -0
  65. package/dist/src/locale-diagnostic.d.ts +12 -0
  66. package/dist/src/ls-columns.d.ts +15 -0
  67. package/dist/src/mock-llm.d.ts +12 -0
  68. package/dist/src/node-activity-log.d.ts +4 -0
  69. package/dist/src/node-not-found.d.ts +1 -0
  70. package/dist/src/node-running-guard.d.ts +9 -0
  71. package/dist/src/node-server-payload.d.ts +66 -0
  72. package/dist/src/node-server.js +7 -1
  73. package/dist/src/normalize-runtime.d.ts +29 -2
  74. package/dist/src/offline-age.d.ts +29 -0
  75. package/dist/src/one-line-cell.d.ts +20 -0
  76. package/dist/src/opencode-agent-node-pair.d.ts +25 -0
  77. package/dist/src/opencode-auth-login.d.ts +43 -0
  78. package/dist/src/opencode-launch-env.d.ts +13 -0
  79. package/dist/src/opencode-owner-mode.d.ts +6 -0
  80. package/dist/src/opencode-package-binary.d.ts +26 -0
  81. package/dist/src/opencode-pin.d.ts +44 -0
  82. package/dist/src/opencode-preset.d.ts +73 -0
  83. package/dist/src/opencode-runtime-binding.d.ts +30 -0
  84. package/dist/src/opencode-safe-root.d.ts +36 -0
  85. package/dist/src/opencode-smoke-env.d.ts +1 -0
  86. package/dist/src/outbound-attachments.d.ts +60 -0
  87. package/dist/src/outbound-tool-names.d.ts +1 -0
  88. package/dist/src/owned-roots.d.ts +58 -0
  89. package/dist/src/owner-env-file.d.ts +2 -0
  90. package/dist/src/package-mode-preflight.d.ts +41 -0
  91. package/dist/src/posix-codex-copresence.d.ts +8 -0
  92. package/dist/src/posix-modes.d.ts +36 -0
  93. package/dist/src/primary-network.d.ts +23 -0
  94. package/dist/src/private-state.d.ts +10 -0
  95. package/dist/src/process-telemetry.d.ts +12 -0
  96. package/dist/src/profile-serialize.d.ts +1 -0
  97. package/dist/src/project-key.d.ts +1 -0
  98. package/dist/src/reply-local-links.d.ts +47 -0
  99. package/dist/src/reply-originator.d.ts +20 -0
  100. package/dist/src/resume-runtime-infer.d.ts +18 -0
  101. package/dist/src/secret-shell-guidance.d.ts +3 -0
  102. package/dist/src/session-status-class.d.ts +48 -0
  103. package/dist/src/sibling-agent-node.d.ts +23 -0
  104. package/dist/src/stale-socket.d.ts +118 -0
  105. package/dist/src/subcommand-redirect.d.ts +26 -0
  106. package/dist/src/task-trace.d.ts +33 -0
  107. package/dist/src/tmux-attach.d.ts +8 -0
  108. package/dist/src/tmux-capability.d.ts +58 -0
  109. package/dist/src/tmux-exact-target.d.ts +34 -0
  110. package/dist/src/tmux-pane-prompt.d.ts +22 -0
  111. package/dist/src/token-cli.d.ts +13 -0
  112. package/dist/src/tool-allowlist.d.ts +28 -0
  113. package/dist/src/unsafe-package-path-reason.d.ts +17 -0
  114. package/dist/src/win-launcher.d.ts +20 -0
  115. package/dist/src/windows-codex-copresence.d.ts +60 -0
  116. package/package.json +10 -5
@@ -0,0 +1,21 @@
1
+ var J=Object.freeze({anet_bin_identity:{explain:"该路径不是 anet 的包内 bin(若设过 ANET_BIN_ABS,先 unset 再重装)",command:"npm i -g @sleep2agi/agent-network"},anet_bin_source:{explain:"pin 没有可信来源。在该机器上写信任根 —— 两段都要 sudo,少一个 && 就会断链",command:`sudo install -d -m 755 /etc/anet-daemon && printf 'ANET_BIN_ABS=%s
2
+ ' "$(command -v anet)" | sudo tee /etc/anet-daemon/path.conf`},anet_bin_permission:{explain:"该二进制 group/other 可写。一行就能修",command:'chmod go-w "$(command -v anet)"'},anet_bin_shape:{explain:"pin 的路径形态不合法(相对路径,或路径里含软链)。取它的 realpath 重新写进 path.conf",command:'readlink -f "$(command -v anet)"'},anet_bin_unknown:{explain:"daemon 没能给出类别(例如安装后二进制被换过、校验和不符)。只能看该机器的 daemon 日志取原文",command:null},anet_bin_pin_unresolved:{explain:"旧版 daemon(agent-node 2.5.0-preview.40 及更早)只报这一个笼统原因。升级该机器的 agent-node 并重启 daemon 后才能拿到具体类别",command:null}});function G(c){if(!Number.isFinite(c)||c<0)return"?";if(c<1000)return`${Math.round(c)}ms 前`;let j=Math.floor(c/1000);if(j<60)return`${j}s 前`;let k=Math.floor(j/60);if(k<60)return`${k}m 前`;let u=Math.floor(k/60);if(u<24)return`${u}h 前`;return`${Math.floor(u/24)}d 前`}function P(c){if(typeof c==="number"&&Number.isFinite(c))return c;if(typeof c==="string"){let j=Date.parse(c);if(Number.isFinite(j))return j}return}function Q(c,j){if(typeof c.can_create_nodes!=="boolean")return{kind:"never-reported",line:`创建能力:未知 —— 这台 daemon 没报过这一格。
3
+ `+" (agent-node 版本早于 preview.55;升级后必须重启 daemon 才生效)"};let k=P(c.last_seen_at),u=c.create_capability_observed_ms_ago,q=k!==void 0&&typeof u==="number"&&Number.isFinite(u)?Math.max(0,j-k)+u:void 0;if(c.can_create_nodes){if(q===void 0)return{kind:"ready-age-unknown",line:`创建能力:可用
4
+ `+" ⚠ 不知道是什么时候测的 —— 该版本开机只算一次。重启它,或升级。"};return{kind:"ready",ageMs:q,line:`创建能力:可用(${G(q)}测)`}}let C=c.create_nodes_blocked_reason||"anet_bin_unknown",z=J[C]??{explain:`未知原因代码 ${C} —— 本 CLI 可能比那台机器的 anet 旧,升级本机 anet 或看它的 daemon 日志`,command:null},K=`完整原文只在那台机器的 daemon 日志里。
5
+ (它带真实机器路径,按设计不上报)`,H=[` 原因:${z.explain}`,z.command?` 修法(可整行粘贴):${z.command}`:" 修法:没有单条命令能修,见上。",` ${K}`].join(`
6
+ `);if(q===void 0)return{kind:"blocked-age-unknown",line:`创建能力:**不可用**(${C})
7
+ ⚠ 不知道是什么时候测的 —— 该 daemon 版本开机只算一次,之后不再重测。
8
+ ${H}`,fix:z};return{kind:"blocked",ageMs:q,line:`创建能力:**不可用**(${C},${G(q)}测)
9
+ ${H}`,fix:z}}function T(c){switch(c.why){case"no-hub":return`创建能力:查不到 —— 本机还没配 hub 地址。
10
+ `+" 跑 anet init,或这次带上 --hub <url>"+`
11
+ (本地清单不需要网络,上面那些信息仍然有效)`;case"unauthorized":return`创建能力:查不到 —— hub 拒绝了本机的身份(HTTP ${c.status})。
12
+ `+" hub 是通的,要修的是**凭据**不是网络:跑 anet login"+`
13
+ (本地清单不需要网络,上面那些信息仍然有效)`;case"http":return`创建能力:查不到 —— hub 返回 HTTP ${c.status}。
14
+ `+" hub 在,但这个接口没给出结果;看 hub 日志"+`
15
+ (本地清单不需要网络,上面那些信息仍然有效)`;case"bad-body":return`创建能力:查不到 —— hub 的应答读不懂。
16
+ `+" 多半是 hub 比本机 CLI 旧,升级 hub 或降本机 anet"+`
17
+ (本地清单不需要网络,上面那些信息仍然有效)`;case"unreachable":return"创建能力:查不到 —— 连不上 hub"+(c.detail?`(${c.detail})`:"")+"。"+`
18
+ (本地清单不需要网络,上面那些信息仍然有效)`}}function U(c){if(c?.error!=="daemon_cannot_create_nodes")return null;let j=c.blocked_reason||"anet_bin_unknown",k=J[j]??{explain:`未知原因代码 ${j} —— 本端可能比那台机器的 anet 旧,升级本端或看它的 daemon 日志`,command:null},u="完整原文(含真实路径)只在那台机器的 daemon 日志里 —— 它带机器路径,按设计不上报。",D=[`原因:${k.explain}`,k.command?`修法(可整行粘贴):${k.command}`:"修法:没有单条命令能修,见上。",u].join(`
19
+ `),q=c.capability_observed_ms_ago;if(c.capability_age==="known"&&typeof q==="number"&&Number.isFinite(q)&&q>=0)return{kind:"blocked",ageMs:q,line:`这台 daemon 报告它现在**建不了节点**(${j},${G(q)}测)
20
+ ${D}`,fix:k};let z=c.capability_age==="unknown_no_heartbeat_time"?"**它报了这个判断,但 hub 这边没有它的心跳时间**,所以算不出是多久以前的 —— 先看这台 daemon 是否还在线,别急着升级它":"**不知道是多久以前测的** —— 该 daemon 版本在开机时算一次就不再重测,这个结论可能早就过期了。重启或升级它";return{kind:"blocked-age-unknown",line:`这台 daemon 报告它现在**建不了节点**(${j});${z}
21
+ ${D}`,fix:k}}export{G as formatAge,T as describeFetchFailure,U as describeCreateRejection,Q as describeCapability};
@@ -0,0 +1,22 @@
1
+ /** 一个 runtime 依赖的外部二进制,以及它**有据可查**的安装位置。 */
2
+ export type RuntimeBinaryHint = {
3
+ /** 哪些 runtime 名需要这个二进制 */
4
+ readonly runtimes: readonly string[];
5
+ /** 在 PATH 上查的裸名 */
6
+ readonly binary: string;
7
+ /** 官方安装位置(相对 $HOME) */
8
+ readonly homeRelativeDir: string;
9
+ /** 🔴 这一条的出处 —— 没有出处就不许进这张表 */
10
+ readonly evidence: string;
11
+ };
12
+ export declare const RUNTIME_BINARY_HINTS: readonly RuntimeBinaryHint[];
13
+ export type PathPreflightInput = {
14
+ /** 这台 daemon 声称支持的 runtime */
15
+ readonly runtimes: readonly string[];
16
+ /** 二进制在 daemon 的 PATH 上能不能找到 */
17
+ readonly resolvesOnPath: (binary: string) => boolean;
18
+ /** 二进制在那个已知位置存不存在 */
19
+ readonly existsInHomeDir: (homeRelativeDir: string, binary: string) => boolean;
20
+ };
21
+ /** 返回要打给用户的警告。**没问题时返回空数组** —— 空是常态,不是失败。 */
22
+ export declare function daemonPathWarnings(input: PathPreflightInput): string[];
@@ -0,0 +1,13 @@
1
+ /**
2
+ * daemon 的 `runtimes_supported` 是**写配置那一刻**的支持集快照,之后不会自愈。
3
+ *
4
+ * 🔴 这不是假想的:#1298 (2026-08-28) 把新建 daemon 的默认清单从 3 个放开到
5
+ * `SUPPORTED_RUNTIME_NAMES` 全体,但**它只改了写入路径**。在那之前 init 过的
6
+ * daemon,配置里仍然只有那 3 个,而且没有任何东西会说出这件事 ——
7
+ * 用户在客户端「选服务器」里看到某台机器少几个 runtime,
8
+ * 既不知道为什么,也不知道该做什么。
9
+ *
10
+ * 判据从**分发处**取(`SUPPORTED_RUNTIME_NAMES`),不在这里手写一份清单:
11
+ * 抄一份就意味着它会再漂一次(同 #1728 的教训)。
12
+ */
13
+ export declare function describeStaleRuntimeSupport(declared: readonly string[] | undefined | null, supported: readonly string[], nameHint?: string): string | null;
@@ -0,0 +1,35 @@
1
+ export type DashboardLaunchSource = "npx" | "global";
2
+ export interface DashboardLaunchRecord {
3
+ schema: 1;
4
+ port: number;
5
+ listener_pid: number;
6
+ listener_birth: string;
7
+ source: DashboardLaunchSource;
8
+ source_key: string;
9
+ recorded_at: string;
10
+ }
11
+ export type DashboardListenerDecision = {
12
+ action: "start";
13
+ } | {
14
+ action: "already_running";
15
+ pid: number;
16
+ } | {
17
+ action: "terminate_owned_stale";
18
+ pid: number;
19
+ reason: "unhealthy" | "version_changed";
20
+ } | {
21
+ action: "refuse";
22
+ reason: string;
23
+ };
24
+ export declare function isDashboardProcessCommand(command: string): boolean;
25
+ export declare function decideDashboardListener(input: {
26
+ port: number;
27
+ listenerPids: number[];
28
+ record: DashboardLaunchRecord | null;
29
+ listenerBirth: string | null;
30
+ listenerCommand: string | null;
31
+ desiredSource: DashboardLaunchSource;
32
+ desiredSourceKey: string;
33
+ healthy: boolean;
34
+ }): DashboardListenerDecision;
35
+ export declare function parseDashboardLaunchRecord(value: unknown): DashboardLaunchRecord | null;
@@ -0,0 +1,27 @@
1
+ /**
2
+ * 终端里的**显示列宽** —— CJK 字符占 2 列,而 `String.length` / `padEnd` 只数码元。
3
+ *
4
+ * 🔴 这份知识仓里本来就有,但它只活在**一个测试文件**里
5
+ * (`daemon-capability-display.test.ts` 的 `displayWidth`,2026-08-30 由 macOS 真机
6
+ * 上「合成首行约 99 列、80 列终端折行且折点落在句子中间」催生)。
7
+ * 生产代码没有它,于是所有用 `padEnd` 对齐的表在中文别名下都是歪的。
8
+ *
9
+ * 实测(2026-08-31,`anet node ls` 对着一支全中文别名的军团):
10
+ *
11
+ * NAME RUNTIME STATUS
12
+ * ──────────────────── ────────────── ────────
13
+ * 通信工程马 claude-code-cli idle ← RUNTIME 列起点
14
+ * 通信IM马 claude-code-cli idle ← 不一样
15
+ * 通信SDK牛 codex-sdk idle ← 又不一样
16
+ *
17
+ * `通信工程马`.length 是 5,`padEnd(20)` 补 15 个空格 —— 但它渲染出来是 10 列,
18
+ * 于是这一行实际占 25 列而不是 20。**别名里 CJK 越多,歪得越远。**
19
+ */
20
+ export declare function displayWidth(s: unknown): number;
21
+ /**
22
+ * 按**显示列**右补空格。已经不短于目标宽度时原样返回(与 `padEnd` 一致,不截断)。
23
+ *
24
+ * 🔴 不截断是有意的:截断会引出「截到一半的宽字符」这个新问题,
25
+ * 而调用方要的只是对齐。**一个修对齐的改动不该顺手引入截断语义。**
26
+ */
27
+ export declare function padDisplayEnd(s: unknown, width: number): string;
@@ -0,0 +1,28 @@
1
+ /**
2
+ * `anet doctor` 原先对「本机配置了几个节点」的判定是:
3
+ *
4
+ * check("Nodes configured", ids.length > 0, `${ids.length} node(s)`);
5
+ *
6
+ * 于是 **0 个节点 → ❌**。而一个刚装好、还没建节点的人跑 `anet doctor`,
7
+ * 看到的是:
8
+ *
9
+ * ❌ Nodes configured — 0 node(s)
10
+ * Result: 9 ok, 3 warnings, 2 errors
11
+ *
12
+ * 🔴 「还没有节点」是**全新安装的预期状态**,不是故障。把它报成 error,
13
+ * 等于在新用户的第一次诊断里制造一个假警报 —— 而这条命令存在的意义
14
+ * 正是告诉他「你这台机器现在好不好」。
15
+ *
16
+ * 🔴 但也不能反过来直接判成「一切正常」:同一个 0 对一个**本来有节点**的人
17
+ * 意味着配置目录不见了。doctor 手上只有一个数字,**分不出这两种现实**。
18
+ * 所以这里既不报错也不报好 —— 把两种都说出来,让读的人自己对号,
19
+ * 并给出各自的下一步。
20
+ */
21
+ export type NodeCountLine = {
22
+ ok: true;
23
+ detail: string;
24
+ } | {
25
+ ok: false;
26
+ info: string;
27
+ };
28
+ export declare function nodeCountLine(count: number): NodeCountLine;
@@ -0,0 +1,14 @@
1
+ /** Parse the environ blob (NUL-separated key=val) and return the
2
+ * COMMHUB_ALIAS value if present, else null. Exported for testing. */
3
+ export declare function parseEnvironAlias(environBlob: string): string | null;
4
+ /** Read /proc/<pid>/environ and return the COMMHUB_ALIAS value.
5
+ * Any file-read error (not-a-Linux, permission denied, race with
6
+ * process exit) returns null — caller must not fail-closed on that
7
+ * (the process is stale/gone/inaccessible, not our problem). */
8
+ export declare function readEnvironAlias(pid: number): string | null;
9
+ /** Scan /proc for pids whose COMMHUB_ALIAS env matches any of the
10
+ * target aliases. Linux-only (procfs); returns null on non-Linux or
11
+ * when /proc is unreadable — caller MUST fail-closed on null per
12
+ * the same #180 R2 fail-closed contract as findNodeProcessesByAlias.
13
+ * Self-pid + init (PID 1) are excluded. */
14
+ export declare function findEnvironAliasMatches(aliases: Iterable<string>, selfPid: number): number[] | null;
@@ -0,0 +1,129 @@
1
+ import type { Duplex } from "node:stream";
2
+ export declare const GROK_ATTACH_PROTOCOL = "anet-grok-copresence-attach";
3
+ export declare const GROK_ATTACH_PROTOCOL_VERSION = 1;
4
+ export declare const GROK_ATTACH_DEFAULT_MAX_FRAME_BYTES: number;
5
+ export declare const GROK_ATTACH_DEFAULT_MAX_BUFFER_BYTES: number;
6
+ export declare const GROK_ATTACH_DEFAULT_HANDSHAKE_TIMEOUT_MS = 5000;
7
+ export declare const GROK_ATTACH_DEFAULT_CLOSE_TIMEOUT_MS = 1000;
8
+ export type GrokAttachJsonValue = null | boolean | number | string | GrokAttachJsonValue[] | {
9
+ [key: string]: GrokAttachJsonValue;
10
+ };
11
+ export interface GrokAttachHelloFrame {
12
+ type: "hello";
13
+ protocol: typeof GROK_ATTACH_PROTOCOL;
14
+ version: typeof GROK_ATTACH_PROTOCOL_VERSION;
15
+ alias: string;
16
+ sessionId: string;
17
+ [key: string]: unknown;
18
+ }
19
+ export interface GrokAttachOutputFrame {
20
+ type: "output";
21
+ data: string;
22
+ encoding: "base64";
23
+ }
24
+ export interface GrokAttachStatusFrame {
25
+ type: "status";
26
+ status: GrokAttachJsonValue;
27
+ }
28
+ export interface GrokAttachErrorFrame {
29
+ type: "error";
30
+ code: string;
31
+ message: string;
32
+ fatal: boolean;
33
+ }
34
+ export interface GrokAttachDetachFrame {
35
+ type: "detach";
36
+ }
37
+ export type GrokAttachServerFrame = GrokAttachHelloFrame | GrokAttachOutputFrame | GrokAttachStatusFrame | GrokAttachErrorFrame | GrokAttachDetachFrame;
38
+ export interface GrokAttachInputFrame {
39
+ type: "input";
40
+ data: string;
41
+ encoding: "base64";
42
+ }
43
+ export interface GrokAttachResizeFrame {
44
+ type: "resize";
45
+ cols: number;
46
+ rows: number;
47
+ }
48
+ /** Asks the node to switch models (issue #879); allowed on control connections. */
49
+ export interface GrokAttachSetModelFrame {
50
+ type: "set-model";
51
+ model: string;
52
+ }
53
+ export type GrokAttachClientFrame = GrokAttachInputFrame | GrokAttachResizeFrame | GrokAttachSetModelFrame | GrokAttachDetachFrame;
54
+ export interface GrokAttachInputSource {
55
+ on(event: "data", listener: (chunk: unknown) => void): unknown;
56
+ on(event: "end", listener: () => void): unknown;
57
+ off?(event: "data" | "end", listener: (...args: any[]) => void): unknown;
58
+ removeListener?(event: "data" | "end", listener: (...args: any[]) => void): unknown;
59
+ pause?(): unknown;
60
+ resume?(): unknown;
61
+ }
62
+ export interface GrokAttachOutputSink {
63
+ write(chunk: Uint8Array): boolean | void;
64
+ columns?: number;
65
+ rows?: number;
66
+ once?(event: "drain", listener: () => void): unknown;
67
+ }
68
+ export interface GrokAttachSignalSource {
69
+ on(event: "SIGWINCH", listener: () => void): unknown;
70
+ off?(event: "SIGWINCH", listener: () => void): unknown;
71
+ removeListener?(event: "SIGWINCH", listener: () => void): unknown;
72
+ }
73
+ export interface GrokAttachSocketStat {
74
+ uid: number;
75
+ isSocket(): boolean;
76
+ isSymbolicLink(): boolean;
77
+ }
78
+ export interface GrokAttachDependencies {
79
+ lstat?: (socketPath: string) => Promise<GrokAttachSocketStat>;
80
+ getuid?: () => number | undefined;
81
+ connect?: (socketPath: string) => Duplex;
82
+ }
83
+ export interface GrokAttachClientOptions {
84
+ socketPath: string;
85
+ input: GrokAttachInputSource;
86
+ output: GrokAttachOutputSink;
87
+ signalSource?: GrokAttachSignalSource;
88
+ terminalSize?: () => {
89
+ cols: number | undefined;
90
+ rows: number | undefined;
91
+ };
92
+ maxFrameBytes?: number;
93
+ maxBufferBytes?: number;
94
+ handshakeTimeoutMs?: number;
95
+ closeTimeoutMs?: number;
96
+ detachOnInputEnd?: boolean;
97
+ onHello?: (frame: GrokAttachHelloFrame) => void;
98
+ onStatus?: (frame: GrokAttachStatusFrame) => void;
99
+ onError?: (error: Error, frame?: GrokAttachErrorFrame) => void;
100
+ onDetach?: (frame: GrokAttachDetachFrame) => void;
101
+ dependencies?: GrokAttachDependencies;
102
+ }
103
+ export type GrokAttachCloseReason = "local-detach" | "input-end" | "remote-detach" | "socket-close" | "socket-error" | "protocol-error";
104
+ export interface GrokAttachCloseInfo {
105
+ reason: GrokAttachCloseReason;
106
+ error?: Error;
107
+ }
108
+ export interface GrokAttachSession {
109
+ readonly socketPath: string;
110
+ readonly closed: Promise<GrokAttachCloseInfo>;
111
+ detach(): void;
112
+ resize(cols?: number, rows?: number): void;
113
+ /**
114
+ * Ask the node to switch models (issue #879).
115
+ *
116
+ * The outcome does not come back from this call — it arrives as a `status`
117
+ * frame carrying `modelSwitch`, because the node may accept, refuse
118
+ * (busy / unchanged / invalid), or fail while re-spawning. Callers read it
119
+ * through `onStatus`.
120
+ */
121
+ setModel(model: string): void;
122
+ }
123
+ export declare class GrokAttachRemoteError extends Error {
124
+ readonly code: string;
125
+ readonly fatal: boolean;
126
+ constructor(frame: GrokAttachErrorFrame);
127
+ }
128
+ export declare function validateGrokAttachSocket(socketPath: string, dependencies?: GrokAttachDependencies): Promise<void>;
129
+ export declare function connectGrokAttach(options: GrokAttachClientOptions): Promise<GrokAttachSession>;
@@ -0,0 +1,15 @@
1
+ /** 从一段日志文本里取出**最后一次**出现的 grok 版本串。
2
+ * 🔴 取最后一次,不是第一次:日志按时间追加,最后一次才是这个进程最近一次
3
+ * 启动时用的那个。取第一次会在日志轮转/多次重启后指向历史。 */
4
+ export declare function parseGrokBuildFromLog(text: string): string | undefined;
5
+ /** 从 `grok --version` 的输出里取版本串(同一形状)。 */
6
+ export declare function parseGrokBuildFromVersionOutput(out: string): string | undefined;
7
+ export type GrokBuildDriftKind = "match" | "drift" | "unknown-current" | "unknown-started";
8
+ export interface GrokBuildDrift {
9
+ readonly kind: GrokBuildDriftKind;
10
+ readonly started?: string;
11
+ readonly current?: string;
12
+ /** 一句人读的话。四种 kind 四句不同的话 —— 含糊化等于把这一格的功能删掉。 */
13
+ readonly line: string;
14
+ }
15
+ export declare function describeGrokBuildDrift(started: string | undefined, current: string | undefined): GrokBuildDrift;
@@ -0,0 +1,11 @@
1
+ export type GrokCopresenceSessionDisclosure = "configured" | "new" | "resume";
2
+ export type GrokCopresenceDisclosure = {
3
+ profile: "commhub-only" | "x-search" | "repo-read" | "invalid";
4
+ lines: readonly string[];
5
+ };
6
+ /**
7
+ * Describe only the exact tool profiles accepted by the pinned Grok TUI
8
+ * runtime. This is deliberately exact: a near-miss must never be presented as
9
+ * either reviewed capability set.
10
+ */
11
+ export declare function grokCopresenceDisclosure(tools: unknown, session?: GrokCopresenceSessionDisclosure): GrokCopresenceDisclosure;
@@ -0,0 +1,75 @@
1
+ /** tmux session names. The attachable TUI owns the bare alias so that
2
+ * `tmux attach -t '=<alias>'` lands a human on the TUI, matching the codex
3
+ * lane exactly — an operator should not have to remember which runtime a node
4
+ * uses in order to know where to attach. */
5
+ export declare function grokCopresenceSessions(displayName: string): {
6
+ node: string;
7
+ tui: string;
8
+ };
9
+ export type GrokAttachSocketState = "missing" | "not_a_socket" | "ready";
10
+ /** Readiness is "the attach socket exists AND is a socket", never "the child
11
+ * process started". A grok leader that dies during startup leaves either no
12
+ * socket at all or a stale regular file from an aborted run; both used to read
13
+ * as ready because nothing looked. */
14
+ export declare function grokAttachSocketState(entry: {
15
+ isSocket(): boolean;
16
+ } | null | undefined): GrokAttachSocketState;
17
+ /** Set on the node child so that `anet node start` inside the tmux session does
18
+ * not re-enter this orchestration and fork bomb. The codex lane guards on the
19
+ * same variable (cli.ts: `process.env.ANET_COPRESENCE_BRIDGE !== "1"`); using a
20
+ * second name would leave two guards that must be kept in sync by hand. */
21
+ export declare const GROK_COPRESENCE_CHILD_ENV = "ANET_COPRESENCE_BRIDGE";
22
+ export interface GrokCopresenceDiagnosis {
23
+ readonly ok: boolean;
24
+ readonly lines: readonly string[];
25
+ /** #1768 —— 非阻断提示:平台能跑,但 agent-node 的能力表说它少了内核层保证。
26
+ * ok=true 时也可能非空;调用方打印它,不退出。 */
27
+ readonly notices: readonly string[];
28
+ }
29
+ /** One block naming every reason this node cannot run the shared TUI, not one
30
+ * exit per reason. A node that is both the wrong runtime AND missing its
31
+ * attach socket should learn both in one run. */
32
+ /** Platforms whose PTY / IPC / isolation primitives the grok co-presence lane
33
+ * has actually been validated on. agent-node refuses anywhere else, and it does
34
+ * so only after the node has been created and started once:
35
+ *
36
+ * [agent-node] grok-build-cli co-presence 无法在 darwin 上运行,缺少:
37
+ * 平台 darwin 尚未验证过共存所需的 PTY / IPC / 隔离原语
38
+ *
39
+ * Learned the expensive way on a Mac mini: install anet, install agent-node,
40
+ * create the node, start it — and only then find out the platform is refused.
41
+ * A one-command launcher that lets someone get that far has not saved them
42
+ * anything, so say it in the same breath as every other gap. */
43
+ export declare const GROK_COPRESENCE_PLATFORMS: readonly NodeJS.Platform[];
44
+ /** 能跑但没有内核层强制的平台 —— agent-node 启动时逐条打印 reducedGuarantees,这里只提前说一句。 */
45
+ export declare const GROK_COPRESENCE_REDUCED_GUARANTEE_PLATFORMS: readonly NodeJS.Platform[];
46
+ export declare function diagnoseGrokCopresence(input: {
47
+ runtime: string;
48
+ displayName: string;
49
+ grokCopresence?: boolean;
50
+ grokAttachSocket?: string;
51
+ platform?: NodeJS.Platform;
52
+ }): GrokCopresenceDiagnosis;
53
+ /** True when a plain `anet node start <name>` (no flag) should bring the shared
54
+ * TUI up by itself.
55
+ *
56
+ * 🔴 Deliberately NOT keyed on `grokCopresence`. That field is set to true at
57
+ * CREATE time for every grok-build-cli node, so reading it here would silently
58
+ * change what `anet node start` does for every grok node that already exists —
59
+ * they would stop running in the foreground and fork into tmux, unasked. Caught
60
+ * exactly that way: a start without the flag entered the orchestration.
61
+ *
62
+ * The codex lane looks the same but is not: `codexCopresence` is written only
63
+ * when the operator passes the flag. So grok needs its own record of the
64
+ * operator's choice, and `grokCopresenceAuto` is it. */
65
+ export declare function grokCopresenceRequested(flagPassed: boolean, profile: {
66
+ runtime?: unknown;
67
+ grokCopresence?: unknown;
68
+ grokCopresenceAuto?: unknown;
69
+ }): boolean;
70
+ /** Record the operator's opt-in so the NEXT start needs no flag — and only then.
71
+ * Never infer it from `grokCopresence`, which create sets by default. */
72
+ export declare function shouldPersistGrokCopresence(flagPassed: boolean, profile: {
73
+ runtime?: unknown;
74
+ grokCopresenceAuto?: unknown;
75
+ }): boolean;
@@ -0,0 +1,65 @@
1
+ export declare const GROK_UNIX_SOCKET_PATH_MAX_BYTES = 100;
2
+ export declare const GROK_COPRESENCE_CAPABILITY_MARKER = "ANET_CAPABILITY_GROK_COPRESENCE_V2";
3
+ export declare const GROK_PREVIEW_RESOLVER_INHERITED_ENV_KEYS: readonly ["PATH", "TMPDIR", "TMP", "TEMP", "LANG", "LC_ALL", "LC_CTYPE", "TZ"];
4
+ export declare const GROK_AGENT_NODE_INHERITED_ENV_KEYS: readonly ["PATH", "HOME", "TMPDIR", "TMP", "TEMP", "LANG", "LC_ALL", "LC_CTYPE", "TZ", "SHELL", "USER", "LOGNAME", "TERM", "COLORTERM", "NO_COLOR"];
5
+ export declare const GROK_AGENT_NODE_OPTIONAL_ENV_KEYS: readonly ["GROK_BINARY", "GROK_HOME", "FLOCK_BINARY", "SETPRIV_BINARY", "UNSHARE_BINARY", "GROK_CLI_TIMEOUT_MS", "GROK_HANDSHAKE_TIMEOUT_MS", "LOG_LEVEL", "ANET_GOAL_TICK_MS", "COMMHUB_MAX_GOALS_PER_NODE"];
6
+ /** Exact environment for the long-lived agent-node parent of the Grok TUI. */
7
+ export declare function buildGrokAgentNodeEnv(parentEnv: NodeJS.ProcessEnv): Record<string, string>;
8
+ export declare function grokPreviewResolverConfigPaths(home: string): {
9
+ directory: string;
10
+ userConfig: string;
11
+ globalConfig: string;
12
+ };
13
+ /**
14
+ * npm rejects loading the same file as both user and global config. Prepare
15
+ * two distinct, empty, owner-only files without following a final symlink so
16
+ * the resolver cannot inherit a user's ordinary npmrc credentials.
17
+ */
18
+ export declare function prepareGrokPreviewResolverConfigs(home: string): void;
19
+ /** Exact environment for the short-lived npm resolver and capability probe. */
20
+ export declare function buildGrokPreviewResolverEnv(parentEnv: NodeJS.ProcessEnv, home: string): Record<string, string>;
21
+ /** Old headless-only agent-node builds already advertised grok-build-cli. */
22
+ export declare function agentNodeHelpSupportsGrokCopresence(help: string): boolean;
23
+ export interface GrokCopresenceProfileFields {
24
+ grokCopresence: boolean;
25
+ grokLeaderSocket?: string;
26
+ grokAttachSocket?: string;
27
+ }
28
+ export interface GrokSocketPathOptions {
29
+ cwd?: string;
30
+ home?: string;
31
+ xdgRuntimeDir?: string;
32
+ uid?: number;
33
+ platform?: NodeJS.Platform;
34
+ }
35
+ /**
36
+ * Allocate deterministic Unix socket paths without creating anything.
37
+ *
38
+ * Grok's workspace sandbox does not admit an otherwise owner-controlled
39
+ * XDG_RUNTIME_DIR such as /run/user/<uid>. Keep the primary sockets under the
40
+ * node's owner-bound state home, which the runtime already admits, and use a
41
+ * short private tmp path only when the Unix socket length limit requires it.
42
+ * The runtime owns directory creation and permissions; `anet node create`
43
+ * only persists the identity of the two sockets.
44
+ */
45
+ export declare function grokCopresenceSocketPaths(nodeId: string, options?: GrokSocketPathOptions): {
46
+ leaderSocket: string;
47
+ attachSocket: string;
48
+ };
49
+ export type GrokAttachTarget = {
50
+ ok: true;
51
+ socketPath: string;
52
+ } | {
53
+ ok: false;
54
+ reason: "not_grok_build_cli" | "headless" | "missing_attach_socket";
55
+ };
56
+ /**
57
+ * `anet grok attach` eligibility. The CLI is the only human join path;
58
+ * this is the shipped decision so tests can drive it without a TTY.
59
+ */
60
+ export declare function resolveGrokAttachTarget(input: {
61
+ runtime: string;
62
+ grokCopresence?: unknown;
63
+ grokAttachSocket?: unknown;
64
+ }): GrokAttachTarget;
65
+ export declare function grokBuildCliCreationFields(runtime: string, nodeId: string, headless?: boolean, options?: GrokSocketPathOptions): GrokCopresenceProfileFields | Record<string, never>;
@@ -0,0 +1,14 @@
1
+ export interface HostTelemetry {
2
+ hostname: string;
3
+ ip: string | null;
4
+ cpu_load_1min: number | null;
5
+ cpu_cores: number | null;
6
+ mem_total_gb: number | null;
7
+ mem_used_gb: number | null;
8
+ mem_avail_gb: number | null;
9
+ disk_total_gb: number | null;
10
+ disk_used_gb: number | null;
11
+ disk_avail_gb: number | null;
12
+ }
13
+ export declare function getHostTelemetry(): HostTelemetry;
14
+ export declare function _clearHostTelemetryCache(): void;
@@ -0,0 +1,4 @@
1
+ export declare const LOOPBACK_HUB_HEALTH_TIMEOUT_MS = 2000;
2
+ export declare const REMOTE_HUB_HEALTH_TIMEOUT_MS = 10000;
3
+ export declare const HUB_HEALTH_TIMEOUT_ENV = "ANET_HUB_HEALTH_TIMEOUT_MS";
4
+ export declare function hubHealthTimeoutMs(hub: string, env?: NodeJS.ProcessEnv): number;
@@ -0,0 +1,20 @@
1
+ /**
2
+ * 把 hub 的 TEXT 时间戳显示给人看。
3
+ *
4
+ * 🔴 原先是**原样打印**:
5
+ *
6
+ * updated: 2026-08-31 00:20:11
7
+ *
8
+ * 那是 UTC,**没有任何时区标记**。同一时刻本地是 `08:22:41 CST` ——
9
+ * 于是一个 **2 分钟前**刚心跳过的节点,在 UTC+8 的用户眼里像是 **8 小时前**的。
10
+ *
11
+ * 这不是假想:2026-08-31 我自己就这么读错过一次,据此差点报出「舰队心跳陈旧
12
+ * 8.3 小时」。**CLI 在主动制造这个误读。**
13
+ *
14
+ * 所以显示两样东西:
15
+ * - **相对时长**(人真正想知道的那个)
16
+ * - 绝对值,并**显式标 UTC**(便于和日志、issue 里的时间对齐)
17
+ */
18
+ export declare function formatHubTime(raw: unknown, nowMs?: number): string;
19
+ /** 人读的时长。负数(时钟偏差/未来时间)如实说,不折成 0。 */
20
+ export declare function formatAgo(deltaMs: number): string;
@@ -0,0 +1,19 @@
1
+ /**
2
+ * #1595 —— `anet doctor` 原先只印 hub 自报的版本:
3
+ *
4
+ * ✅ CommHub reachable (http://127.0.0.1:9200 v0.9.0-preview.38)
5
+ *
6
+ * 读的人**看不到自己这台 CLI 钉的是哪个**。实测(2026-08-31):生产 hub 在
7
+ * `.38`,而 `anet hub start` 的 `PINNED_SERVER_VERSION` 是 `.44` —— 两个数
8
+ * 差 6 个版本,而屏幕上只有一个。今天有一整条排查(agent→用户消息为什么
9
+ * 验不了端到端)就卡在这个差上:`.44` 之前 `send_desktop_message` 是
10
+ * fire-and-forget 且仍返回 `ok:true`,所以在 `.38` 上拿到的 `ok:true`
11
+ * 不是「用户看到了」的证据。
12
+ *
13
+ * 🔴 **只并排摆出两个数,不判断谁对、不给阈值、不发警告。**
14
+ * hub 比 CLI 的 pin 老或新都可能完全合理 —— 连的是别人运维的 hub、
15
+ * 本机 hub 还没重启、或者故意钉在旧版。给一个猜出来的「应该一致」
16
+ * 判据,会在这些正常情况下变成误报,而一个会误报的检查第一周就会被关掉。
17
+ * 让读的人自己看见差在哪,是这一格能诚实做到的全部。
18
+ */
19
+ export declare function formatHubVersionDetail(hubUrl: string, hubVersion: unknown, pinnedVersion: string): string;
@@ -0,0 +1,34 @@
1
+ import type { IMCorrelationStore, IMTaskCorrelation } from "./types";
2
+ export interface JsonIMCorrelationStoreOptions {
3
+ now?: () => number;
4
+ /** How long idempotency keys remain valid. Defaults to 24h. */
5
+ seenTtlMs?: number;
6
+ /** How long terminal correlations remain queryable. Defaults to 24h. */
7
+ terminalTtlMs?: number;
8
+ }
9
+ /**
10
+ * Small durable store for RFC-020 §2.9④ / §4.4 correlation state.
11
+ *
12
+ * The first gateway PR needs this as a standalone data structure before the
13
+ * bridge is rewired: idempotency keys survive process restart, and task replies
14
+ * can be routed back to their originating IM conversation.
15
+ */
16
+ export declare class JsonIMCorrelationStore implements IMCorrelationStore {
17
+ private readonly path;
18
+ private readonly now;
19
+ private readonly seenTtlMs;
20
+ private readonly terminalTtlMs;
21
+ constructor(path: string, options?: JsonIMCorrelationStoreOptions);
22
+ hasSeen(idempotencyKey: string): Promise<string | null>;
23
+ recordSeen(idempotencyKey: string, taskId: string): Promise<void>;
24
+ getCorrelation(taskId: string): Promise<IMTaskCorrelation | null>;
25
+ putCorrelation(taskId: string, correlation: IMTaskCorrelation): Promise<void>;
26
+ updateStatus(taskId: string, status: IMTaskCorrelation["status"]): Promise<void>;
27
+ gc(now: number): Promise<{
28
+ removed: number;
29
+ }>;
30
+ private isExpired;
31
+ private load;
32
+ private save;
33
+ }
34
+ export declare function createJsonIMCorrelationStore(path: string, options?: JsonIMCorrelationStoreOptions): IMCorrelationStore;