@mininglamp-oss/octo-runtime 1.1.0 → 1.1.1

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 (127) hide show
  1. package/README.md +4 -2
  2. package/apps/cli/README.md +3 -1
  3. package/apps/cli/dist/build-info.json +2 -2
  4. package/apps/cli/dist/extras.d.ts +4 -2
  5. package/apps/cli/dist/extras.js +20 -21
  6. package/apps/cli/dist/help.js +3 -0
  7. package/apps/cli/dist/logs.d.ts +10 -0
  8. package/apps/cli/dist/logs.js +186 -0
  9. package/apps/cli/dist/main.d.ts +1 -0
  10. package/apps/cli/dist/main.js +94 -19
  11. package/apps/cli/dist/{default_adapters.json → manifest.json} +5 -0
  12. package/apps/cli/dist/module-manifest.d.ts +3 -0
  13. package/apps/cli/dist/module-manifest.js +11 -0
  14. package/apps/cli/dist/openclaw.js +30 -6
  15. package/apps/cli/dist/output.js +3 -0
  16. package/apps/cli/dist/playboard.js +10 -8
  17. package/apps/cli/dist/status-output.d.ts +10 -0
  18. package/apps/cli/dist/status-output.js +65 -0
  19. package/apps/cli/dist/worker.d.ts +1 -0
  20. package/apps/cli/dist/worker.js +18 -0
  21. package/dist/public/host.d.ts +1 -0
  22. package/node_modules/@mininglamp-oss/octo-runtime-acp-client/package.json +3 -3
  23. package/node_modules/@mininglamp-oss/octo-runtime-adapter-acp/package.json +4 -4
  24. package/node_modules/@mininglamp-oss/octo-runtime-adapter-antigravity/package.json +3 -3
  25. package/node_modules/@mininglamp-oss/octo-runtime-adapter-claude-code/package.json +3 -3
  26. package/node_modules/@mininglamp-oss/octo-runtime-adapter-codebuddy/package.json +4 -4
  27. package/node_modules/@mininglamp-oss/octo-runtime-adapter-codex/package.json +3 -3
  28. package/node_modules/@mininglamp-oss/octo-runtime-adapter-hermes/package.json +2 -2
  29. package/node_modules/@mininglamp-oss/octo-runtime-adapter-kimi-code/package.json +2 -2
  30. package/node_modules/@mininglamp-oss/octo-runtime-adapter-mcode/package.json +2 -2
  31. package/node_modules/@mininglamp-oss/octo-runtime-adapter-openclaw/package.json +3 -3
  32. package/node_modules/@mininglamp-oss/octo-runtime-adapter-opencode/package.json +4 -4
  33. package/node_modules/@mininglamp-oss/octo-runtime-adapter-qodercli/package.json +2 -2
  34. package/node_modules/@mininglamp-oss/octo-runtime-adapter-qoderclicn/package.json +2 -2
  35. package/node_modules/@mininglamp-oss/octo-runtime-adapter-sdk/dist/index.d.ts +1 -0
  36. package/node_modules/@mininglamp-oss/octo-runtime-adapter-sdk/dist/index.js +1 -0
  37. package/node_modules/@mininglamp-oss/octo-runtime-adapter-sdk/dist/module-manifest.d.ts +33 -0
  38. package/node_modules/@mininglamp-oss/octo-runtime-adapter-sdk/dist/module-manifest.js +118 -0
  39. package/node_modules/@mininglamp-oss/octo-runtime-adapter-sdk/dist/runtime-log-worker.js +7 -5
  40. package/node_modules/@mininglamp-oss/octo-runtime-adapter-sdk/dist/runtime-log.d.ts +3 -0
  41. package/node_modules/@mininglamp-oss/octo-runtime-adapter-sdk/dist/runtime-log.js +45 -10
  42. package/node_modules/@mininglamp-oss/octo-runtime-adapter-sdk/dist/service-status.d.ts +11 -0
  43. package/node_modules/@mininglamp-oss/octo-runtime-adapter-sdk/dist/service-status.js +70 -0
  44. package/node_modules/@mininglamp-oss/octo-runtime-adapter-sdk/package.json +5 -3
  45. package/node_modules/@mininglamp-oss/octo-runtime-adapter-zcode-gui/package.json +3 -3
  46. package/node_modules/@mininglamp-oss/octo-runtime-cli/dist/build-info.json +2 -2
  47. package/node_modules/@mininglamp-oss/octo-runtime-cli/dist/extras.d.ts +4 -2
  48. package/node_modules/@mininglamp-oss/octo-runtime-cli/dist/extras.js +20 -21
  49. package/node_modules/@mininglamp-oss/octo-runtime-cli/dist/help.js +3 -0
  50. package/node_modules/@mininglamp-oss/octo-runtime-cli/dist/logs.d.ts +10 -0
  51. package/node_modules/@mininglamp-oss/octo-runtime-cli/dist/logs.js +186 -0
  52. package/node_modules/@mininglamp-oss/octo-runtime-cli/dist/main.d.ts +1 -0
  53. package/node_modules/@mininglamp-oss/octo-runtime-cli/dist/main.js +94 -19
  54. package/node_modules/@mininglamp-oss/octo-runtime-cli/dist/{default_adapters.json → manifest.json} +5 -0
  55. package/node_modules/@mininglamp-oss/octo-runtime-cli/dist/module-manifest.d.ts +3 -0
  56. package/node_modules/@mininglamp-oss/octo-runtime-cli/dist/module-manifest.js +11 -0
  57. package/node_modules/@mininglamp-oss/octo-runtime-cli/dist/openclaw.js +30 -6
  58. package/node_modules/@mininglamp-oss/octo-runtime-cli/dist/output.js +3 -0
  59. package/node_modules/@mininglamp-oss/octo-runtime-cli/dist/playboard.js +10 -8
  60. package/node_modules/@mininglamp-oss/octo-runtime-cli/dist/status-output.d.ts +10 -0
  61. package/node_modules/@mininglamp-oss/octo-runtime-cli/dist/status-output.js +65 -0
  62. package/node_modules/@mininglamp-oss/octo-runtime-cli/dist/worker.d.ts +1 -0
  63. package/node_modules/@mininglamp-oss/octo-runtime-cli/dist/worker.js +18 -0
  64. package/node_modules/@mininglamp-oss/octo-runtime-cli/package.json +22 -9
  65. package/node_modules/@mininglamp-oss/octo-runtime-cross-service/package.json +2 -2
  66. package/node_modules/@mininglamp-oss/octo-runtime-executor-core/dist/model-input.d.ts +1 -1
  67. package/node_modules/@mininglamp-oss/octo-runtime-executor-core/dist/model-input.js +5 -5
  68. package/node_modules/@mininglamp-oss/octo-runtime-executor-core/package.json +3 -3
  69. package/node_modules/@mininglamp-oss/octo-runtime-executor-kernel/dist/heartbeat.d.ts +16 -0
  70. package/node_modules/@mininglamp-oss/octo-runtime-executor-kernel/dist/heartbeat.js +59 -0
  71. package/node_modules/@mininglamp-oss/octo-runtime-executor-kernel/dist/invocation-manifest.d.ts +1 -0
  72. package/node_modules/@mininglamp-oss/octo-runtime-executor-kernel/dist/invocation-manifest.js +4 -1
  73. package/node_modules/@mininglamp-oss/octo-runtime-executor-kernel/dist/manifest-adapters.d.ts +6 -0
  74. package/node_modules/@mininglamp-oss/octo-runtime-executor-kernel/dist/manifest-adapters.js +88 -0
  75. package/node_modules/@mininglamp-oss/octo-runtime-executor-kernel/dist/module-specifier.d.ts +1 -2
  76. package/node_modules/@mininglamp-oss/octo-runtime-executor-kernel/dist/module-specifier.js +2 -9
  77. package/node_modules/@mininglamp-oss/octo-runtime-executor-kernel/dist/process.d.ts +7 -2
  78. package/node_modules/@mininglamp-oss/octo-runtime-executor-kernel/dist/process.js +138 -9
  79. package/node_modules/@mininglamp-oss/octo-runtime-executor-kernel/dist/runner.js +3 -92
  80. package/node_modules/@mininglamp-oss/octo-runtime-executor-kernel/dist/serve.js +36 -12
  81. package/node_modules/@mininglamp-oss/octo-runtime-executor-kernel/dist/server.d.ts +4 -1
  82. package/node_modules/@mininglamp-oss/octo-runtime-executor-kernel/dist/server.js +14 -6
  83. package/node_modules/@mininglamp-oss/octo-runtime-executor-kernel/dist/service.js +2 -1
  84. package/node_modules/@mininglamp-oss/octo-runtime-executor-kernel/dist/supervisor.d.ts +2 -2
  85. package/node_modules/@mininglamp-oss/octo-runtime-executor-kernel/dist/supervisor.js +57 -34
  86. package/node_modules/@mininglamp-oss/octo-runtime-executor-kernel/package.json +8 -16
  87. package/node_modules/@mininglamp-oss/octo-runtime-extra-channel-gateway/config-schema.json +2 -2
  88. package/node_modules/@mininglamp-oss/octo-runtime-extra-channel-gateway/dist/application/im-channel.js +16 -17
  89. package/node_modules/@mininglamp-oss/octo-runtime-extra-channel-gateway/package.json +6 -6
  90. package/node_modules/@mininglamp-oss/octo-runtime-extra-channel-wecom/config-schema.json +2 -2
  91. package/node_modules/@mininglamp-oss/octo-runtime-extra-channel-wecom/package.json +5 -5
  92. package/node_modules/@mininglamp-oss/octo-runtime-extra-webui/config-schema.json +2 -2
  93. package/node_modules/@mininglamp-oss/octo-runtime-extra-webui/dist/index.js +1 -1
  94. package/node_modules/@mininglamp-oss/octo-runtime-extra-webui/dist/store.d.ts +4 -1
  95. package/node_modules/@mininglamp-oss/octo-runtime-extra-webui/dist/store.js +54 -4
  96. package/node_modules/@mininglamp-oss/octo-runtime-extra-webui/package.json +2 -2
  97. package/node_modules/@mininglamp-oss/octo-runtime-host-sdk/dist/extra-config.d.ts +8 -1
  98. package/node_modules/@mininglamp-oss/octo-runtime-host-sdk/dist/extra-config.js +27 -12
  99. package/node_modules/@mininglamp-oss/octo-runtime-host-sdk/dist/extra-service.js +39 -2
  100. package/node_modules/@mininglamp-oss/octo-runtime-host-sdk/dist/index.d.ts +1 -0
  101. package/node_modules/@mininglamp-oss/octo-runtime-host-sdk/package.json +4 -4
  102. package/node_modules/@mininglamp-oss/octo-runtime-protocol/package.json +1 -1
  103. package/node_modules/import-meta-resolve/index.d.ts +19 -0
  104. package/node_modules/import-meta-resolve/index.d.ts.map +1 -0
  105. package/node_modules/import-meta-resolve/index.js +47 -0
  106. package/node_modules/import-meta-resolve/lib/errors.d.ts +24 -0
  107. package/node_modules/import-meta-resolve/lib/errors.d.ts.map +1 -0
  108. package/node_modules/import-meta-resolve/lib/errors.js +504 -0
  109. package/node_modules/import-meta-resolve/lib/get-format.d.ts +13 -0
  110. package/node_modules/import-meta-resolve/lib/get-format.d.ts.map +1 -0
  111. package/node_modules/import-meta-resolve/lib/get-format.js +159 -0
  112. package/node_modules/import-meta-resolve/lib/package-json-reader.d.ts +31 -0
  113. package/node_modules/import-meta-resolve/lib/package-json-reader.d.ts.map +1 -0
  114. package/node_modules/import-meta-resolve/lib/package-json-reader.js +177 -0
  115. package/node_modules/import-meta-resolve/lib/resolve.d.ts +29 -0
  116. package/node_modules/import-meta-resolve/lib/resolve.d.ts.map +1 -0
  117. package/node_modules/import-meta-resolve/lib/resolve.js +1301 -0
  118. package/node_modules/import-meta-resolve/lib/utils.d.ts +6 -0
  119. package/node_modules/import-meta-resolve/lib/utils.d.ts.map +1 -0
  120. package/node_modules/import-meta-resolve/lib/utils.js +47 -0
  121. package/node_modules/import-meta-resolve/license +74 -0
  122. package/node_modules/import-meta-resolve/package.json +78 -0
  123. package/node_modules/import-meta-resolve/readme.md +252 -0
  124. package/package.json +28 -26
  125. package/packages/octo-runtime-extra-channel-gateway/config-schema.json +2 -2
  126. package/packages/octo-runtime-extra-channel-wecom/config-schema.json +2 -2
  127. package/packages/octo-runtime-extra-webui/config-schema.json +2 -2
@@ -1,6 +1,7 @@
1
- /** 宿主可选的结构化日志;只接受执行元数据,磁盘写入隔离在有界 Worker。 */
1
+ /** Optional metadata logging; bounded queues isolate filesystem writes in a dedicated worker. */
2
2
  import { isAbsolute } from "node:path";
3
3
  import { Worker } from "node:worker_threads";
4
+ import { extraStatusRecord } from "./service-status.js";
4
5
  export const noopRuntimeLog = () => { };
5
6
  const strings = new Set([
6
7
  "run_id",
@@ -30,11 +31,15 @@ export function assertRuntimeLogging(options) {
30
31
  if (options !== undefined &&
31
32
  (!options || typeof options.directory !== "string" || !isAbsolute(options.directory)))
32
33
  throw new TypeError("LOG_DIRECTORY_MUST_BE_ABSOLUTE");
34
+ if (options?.statusOutput !== undefined && typeof options.statusOutput !== "function")
35
+ throw new TypeError("LOG_STATUS_OUTPUT_INVALID");
33
36
  }
34
37
  /** F31:.octospec/tasks/gateway-august-feature-assessment/assessment.md */
35
38
  export class RuntimeLogging {
36
39
  #worker;
37
40
  #pending = 0;
41
+ #statusPending = 0;
42
+ #statusOutput;
38
43
  #closed = false;
39
44
  #closing;
40
45
  #done;
@@ -42,27 +47,31 @@ export class RuntimeLogging {
42
47
  assertRuntimeLogging(options);
43
48
  if (!options)
44
49
  return;
50
+ this.#statusOutput = options.statusOutput;
45
51
  try {
46
52
  this.#worker = new Worker(new URL("./runtime-log-worker.js", import.meta.url), {
47
- workerData: options
53
+ workerData: { directory: options.directory }
48
54
  });
49
55
  }
50
56
  catch {
51
- this.#closed = true;
52
57
  return;
53
58
  }
54
59
  this.#worker.on("message", (message) => {
55
- if (message === "written")
56
- this.#pending = Math.max(0, this.#pending - 1);
60
+ if (typeof message === "object") {
61
+ if (message.written)
62
+ this.#statusPending = Math.max(0, this.#statusPending - 1);
63
+ else
64
+ this.#pending = Math.max(0, this.#pending - 1);
65
+ }
57
66
  if (message === "closed")
58
67
  this.#done?.();
59
68
  });
60
69
  this.#worker.on("error", () => {
61
- this.#closed = true;
70
+ this.#worker = undefined;
62
71
  this.#done?.();
63
72
  });
64
73
  this.#worker.on("exit", () => {
65
- this.#closed = true;
74
+ this.#worker = undefined;
66
75
  this.#done?.();
67
76
  });
68
77
  this.#worker.unref();
@@ -74,13 +83,39 @@ export class RuntimeLogging {
74
83
  return (level, event, data) => this.#write("extra", name, level, event, data);
75
84
  }
76
85
  #write(source, extra, level, event, data) {
77
- if (!this.#worker || this.#closed || this.#pending >= 128)
86
+ if (this.#closed)
87
+ return;
88
+ if (extra) {
89
+ try {
90
+ const status = extraStatusRecord(extra, level, event, data);
91
+ if (status) {
92
+ // Sink and mirror fail independently; neither failure may affect service recovery.
93
+ try {
94
+ const result = this.#statusOutput?.(status);
95
+ // TypeScript permits async functions in void sinks; observe rejection without waiting.
96
+ if (result && typeof result.then === "function")
97
+ void Promise.resolve(result).catch(() => undefined);
98
+ }
99
+ catch {
100
+ /* A host sink failure must not suppress the independent file mirror. */
101
+ }
102
+ if (this.#worker && this.#statusPending < 128) {
103
+ this.#worker.postMessage({ extra, status: true, line: `${JSON.stringify(status)}\n` });
104
+ this.#statusPending++;
105
+ }
106
+ }
107
+ }
108
+ catch {
109
+ /* Untrusted diagnostic getters must not escape into service execution. */
110
+ }
111
+ }
112
+ if (!this.#worker || this.#pending >= 128)
78
113
  return;
79
114
  try {
80
115
  if (!["info", "warn", "error"].includes(level) || !/^[a-z][a-z0-9_.-]{0,63}$/.test(event))
81
116
  return;
82
117
  const safe = {};
83
- // 不做任意对象脱敏或正文截断:白名单之外的字段直接不读取、不序列化。
118
+ // Ignore unknown fields entirely instead of serializing or truncating arbitrary bodies.
84
119
  for (const key of strings) {
85
120
  const value = data?.[key];
86
121
  if (typeof value === "string" &&
@@ -105,7 +140,7 @@ export class RuntimeLogging {
105
140
  this.#pending++;
106
141
  }
107
142
  catch {
108
- /* 日志不能中断执行,包括恶意 getter 或已退出的 Worker。 */
143
+ /* Diagnostics cannot interrupt execution, including getter errors or an exited worker. */
109
144
  }
110
145
  }
111
146
  close() {
@@ -0,0 +1,11 @@
1
+ import type { RuntimeLogLevel } from "./runtime-log.js";
2
+ export interface ExtraStatusRecord {
3
+ time: string;
4
+ level: RuntimeLogLevel;
5
+ source: "extra";
6
+ extra_type: string;
7
+ event: string;
8
+ data: Record<string, string | number>;
9
+ }
10
+ /** Rebuild status records from a separate allowlist, including when reading persisted files. */
11
+ export declare function extraStatusRecord(extra: string, level: RuntimeLogLevel, event: string, data?: Readonly<Record<string, unknown>>, time?: string): ExtraStatusRecord | undefined;
@@ -0,0 +1,70 @@
1
+ const events = new Set([
2
+ "service.initializing",
3
+ "service.ready",
4
+ "service.closing",
5
+ "service.closed",
6
+ "service.failed",
7
+ "service.cleanup_incomplete",
8
+ "service.output_dropped",
9
+ "kernel.available",
10
+ "kernel.unavailable",
11
+ "channel.connecting",
12
+ "channel.connected",
13
+ "channel.disconnected",
14
+ "channel.connection_failed",
15
+ "channel.retry_started",
16
+ "channel.retry_scheduled",
17
+ "channel.stopped",
18
+ "channel.diagnostic"
19
+ ]);
20
+ const states = new Set([
21
+ "created",
22
+ "initializing",
23
+ "ready",
24
+ "closing",
25
+ "closed",
26
+ "failed",
27
+ "connecting",
28
+ "online",
29
+ "retrying",
30
+ "stopped",
31
+ "available",
32
+ "unavailable"
33
+ ]);
34
+ /** Rebuild status records from a separate allowlist, including when reading persisted files. */
35
+ export function extraStatusRecord(extra, level, event, data, time = new Date().toISOString()) {
36
+ if (!/^[a-z][a-z0-9-]{0,63}$/.test(extra) ||
37
+ !["info", "warn", "error"].includes(level) ||
38
+ !events.has(event))
39
+ return;
40
+ if (!/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/.test(time) ||
41
+ !Number.isFinite(Date.parse(time)))
42
+ return;
43
+ if (event === "channel.diagnostic" && data?.code !== "IM_DISPOSE_FAILED")
44
+ return;
45
+ const safe = {};
46
+ for (const key of ["state", "code", "endpoint_id"]) {
47
+ const value = data?.[key];
48
+ if (typeof value !== "string")
49
+ continue;
50
+ if (key === "state"
51
+ ? states.has(value)
52
+ : key === "endpoint_id"
53
+ ? /^[a-f0-9]{64}$/.test(value)
54
+ : /^[A-Z][A-Z0-9_]{0,95}$/.test(value))
55
+ safe[key] = value;
56
+ }
57
+ for (const key of ["attempt", "delay_ms", "dropped_count"]) {
58
+ const value = data?.[key];
59
+ if (typeof value === "number" && Number.isSafeInteger(value) && value >= 0)
60
+ safe[key] = value;
61
+ }
62
+ return Object.freeze({
63
+ time,
64
+ level,
65
+ source: "extra",
66
+ extra_type: extra,
67
+ event,
68
+ data: Object.freeze(safe)
69
+ });
70
+ }
@@ -1,14 +1,16 @@
1
1
  {
2
2
  "name": "@mininglamp-oss/octo-runtime-adapter-sdk",
3
- "version": "1.1.0",
3
+ "version": "1.1.1",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "exports": {
7
7
  ".": "./dist/index.js",
8
- "./process-cleanup": "./dist/process-cleanup.js"
8
+ "./process-cleanup": "./dist/process-cleanup.js",
9
+ "./module-manifest": "./dist/module-manifest.js"
9
10
  },
10
11
  "dependencies": {
11
- "@mininglamp-oss/octo-runtime-protocol": "1.1.0"
12
+ "@mininglamp-oss/octo-runtime-protocol": "1.1.1",
13
+ "import-meta-resolve": "4.2.0"
12
14
  },
13
15
  "types": "./dist/index.d.ts",
14
16
  "files": [
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@mininglamp-oss/octo-runtime-adapter-zcode-gui",
3
- "version": "1.1.0",
3
+ "version": "1.1.1",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "exports": "./dist/index.js",
7
7
  "dependencies": {
8
- "@mininglamp-oss/octo-runtime-protocol": "1.1.0",
9
- "@mininglamp-oss/octo-runtime-adapter-sdk": "1.1.0"
8
+ "@mininglamp-oss/octo-runtime-protocol": "1.1.1",
9
+ "@mininglamp-oss/octo-runtime-adapter-sdk": "1.1.1"
10
10
  },
11
11
  "types": "./dist/index.d.ts",
12
12
  "files": [
@@ -1,5 +1,5 @@
1
1
  {
2
- "commit": "d064cfe78f6818d3c451ecf803a280245f4b0496",
2
+ "commit": "20c377136288a55356300bd3d4a755d0f19a23ee",
3
3
  "dirty": false,
4
- "buildTime": "2026-10-21T06:46:27.212Z"
4
+ "buildTime": "2026-10-22T07:38:35.134Z"
5
5
  }
@@ -1,6 +1,8 @@
1
- import { type ExtraDescriptor, type StoredExtra } from "@mininglamp-oss/octo-runtime-host-sdk";
1
+ import { type ExtraRegistration, type StoredExtra } from "@mininglamp-oss/octo-runtime-host-sdk";
2
2
  import { type RuntimePaths } from "./state.js";
3
+ import { type ModuleManifest } from "@mininglamp-oss/octo-runtime-adapter-sdk/module-manifest";
3
4
  /** Generic full replacement: no platform-specific CLI fields or validation. */
4
5
  export declare function setExtra(paths: RuntimePaths, name: string, input: NodeJS.ReadableStream): Promise<unknown>;
5
- export declare function readExtra(paths: RuntimePaths, name: string): Promise<ExtraDescriptor>;
6
+ /** CLI 将模块目录与业务配置装配;仅 load 时解析/import,禁用项及未启动项不执行模块。 */
7
+ export declare function readExtra(paths: RuntimePaths, name: string, manifest: ModuleManifest): Promise<ExtraRegistration>;
6
8
  export declare function getExtra(paths: RuntimePaths, name: string): Promise<StoredExtra>;
@@ -1,6 +1,7 @@
1
1
  import { join } from "node:path";
2
- import { assertExtraName, assertStoredExtra, readExtraDirectory } from "@mininglamp-oss/octo-runtime-host-sdk";
2
+ import { assertExtraName, assertStoredExtra, readStoredExtra } from "@mininglamp-oss/octo-runtime-host-sdk";
3
3
  import { withSetupLock, writeJsonAtomic } from "./state.js";
4
+ import { resolveModuleSpecifier } from "@mininglamp-oss/octo-runtime-adapter-sdk/module-manifest";
4
5
  /** Generic full replacement: no platform-specific CLI fields or validation. */
5
6
  export async function setExtra(paths, name, input) {
6
7
  assertExtraName(name);
@@ -29,33 +30,31 @@ export async function setExtra(paths, name, input) {
29
30
  return { saved: true, name, file, reload_required: true };
30
31
  });
31
32
  }
32
- export async function readExtra(paths, name) {
33
+ /** CLI 将模块目录与业务配置装配;仅 load 时解析/import,禁用项及未启动项不执行模块。 */
34
+ export async function readExtra(paths, name, manifest) {
33
35
  assertExtraName(name);
34
- const entries = await readExtraDirectory(join(paths.home, "extras"));
35
- const entry = entries.find((item) => item.name === name);
36
+ const stored = await getExtra(paths, name);
37
+ const entry = manifest.extras.find((item) => item.name === name);
36
38
  if (!entry)
37
- throw new Error("EXTRA_NOT_REGISTERED");
38
- // Invalid stored declarations carry a controlled read error through their loader.
39
- if (!entry.module)
40
- await entry.load();
39
+ throw new Error("EXTRA_MANIFEST_DECLARATION_REQUIRED");
40
+ if (stored.module !== undefined && stored.module !== entry.module)
41
+ throw new Error("EXTRA_MODULE_MIGRATION_REQUIRED");
41
42
  return {
42
43
  name,
43
44
  module: entry.module,
44
- config: entry.config,
45
- ...(entry.enabled === undefined ? {} : { enabled: entry.enabled })
45
+ config: stored.config,
46
+ enabled: stored.enabled,
47
+ load: () => import(resolveModuleSpecifier(entry.module, entry.sourceManifest))
46
48
  };
47
49
  }
48
50
  export async function getExtra(paths, name) {
49
51
  assertExtraName(name);
50
- const entries = await readExtraDirectory(join(paths.home, "extras"));
51
- const entry = entries.find((item) => item.name === name);
52
- if (!entry)
53
- throw new Error("EXTRA_NOT_REGISTERED");
54
- if (!entry.module)
55
- await entry.load();
56
- return {
57
- module: entry.module,
58
- ...(entry.enabled === undefined ? {} : { enabled: entry.enabled }),
59
- config: entry.config
60
- };
52
+ try {
53
+ return await readStoredExtra(join(paths.home, "extras"), name);
54
+ }
55
+ catch (error) {
56
+ if (error.code === "ENOENT")
57
+ throw new Error("EXTRA_NOT_REGISTERED", { cause: error });
58
+ throw error;
59
+ }
61
60
  }
@@ -63,9 +63,11 @@ Commands:
63
63
  stop Stop this service, preserving its autostart setting
64
64
  status Show native service, IPC and component status
65
65
  run Run this component in the foreground
66
+ ${component === "executor-kernel" ? "" : " logs Tail service status logs (no conversation content)\n"}
66
67
 
67
68
  Flags: --home <path> --profile <name> --manifest <path> --json --help
68
69
  Install: --autostart=true|false
70
+ ${component === "executor-kernel" ? "" : "Logs: --lines <1..10000> (default 200) --follow --json (NDJSON)\n"}
69
71
  Kernel and Extras have independent services and autostart settings.
70
72
  An Extra may start before Kernel and connect when Kernel becomes available.`;
71
73
  return `Octo Runtime CLI (${cliVersion})
@@ -75,6 +77,7 @@ Manage the local Agent runtime and independent services.
75
77
  Usage:
76
78
  octo-runtime executor-kernel uninstall|install|start|restart|stop|status|run
77
79
  octo-runtime <extra-name> uninstall|install|start|restart|stop|status|run
80
+ octo-runtime <extra-name> logs [--lines 200] [--follow] [--json]
78
81
  octo-runtime status [--json]
79
82
  octo-runtime adapter list
80
83
  octo-runtime adapter probe <type>
@@ -0,0 +1,10 @@
1
+ export declare function showExtraLogs(options: {
2
+ home: string;
3
+ extra: string;
4
+ lines: number;
5
+ follow: boolean;
6
+ json: boolean;
7
+ output: (line: string, signal?: AbortSignal) => void | Promise<void>;
8
+ errorOutput: (line: string) => void;
9
+ signal?: AbortSignal;
10
+ }): Promise<void>;
@@ -0,0 +1,186 @@
1
+ import { constants } from "node:fs";
2
+ import { lstat, open, opendir } from "node:fs/promises";
3
+ import { join } from "node:path";
4
+ import { setTimeout as delay } from "node:timers/promises";
5
+ import { extraStatusRecord } from "@mininglamp-oss/octo-runtime-adapter-sdk";
6
+ const MAX_READ = 4 * 1024 * 1024;
7
+ const MAX_LINE = 4096;
8
+ function record(line, extra) {
9
+ if (Buffer.byteLength(line) > MAX_LINE)
10
+ return;
11
+ try {
12
+ const value = JSON.parse(line);
13
+ if (value.source !== "extra" || value.extra_type !== extra || typeof value.time !== "string")
14
+ return;
15
+ return extraStatusRecord(extra, value.level, value.event, value.data, value.time);
16
+ }
17
+ catch {
18
+ return;
19
+ }
20
+ }
21
+ async function files(home, extra) {
22
+ const directory = join(home, "logs", "extras");
23
+ for (const path of [join(home, "logs"), directory]) {
24
+ try {
25
+ const stat = await lstat(path);
26
+ if (!stat.isDirectory() || stat.isSymbolicLink())
27
+ throw new Error("LOG_DIRECTORY_NOT_REGULAR");
28
+ }
29
+ catch (error) {
30
+ if (error.code === "ENOENT")
31
+ return [];
32
+ throw error;
33
+ }
34
+ }
35
+ const pattern = new RegExp(`^${extra}-status-\\d{8}T\\d{2}\\.log$`);
36
+ // Keep only three candidates while scanning, without loading an unbounded directory listing.
37
+ const selected = [];
38
+ let count = 0;
39
+ for await (const entry of await opendir(directory)) {
40
+ if (++count > 10000)
41
+ throw new Error("LOG_SCAN_LIMIT");
42
+ if (!pattern.test(entry.name) || !entry.isFile() || entry.isSymbolicLink())
43
+ continue;
44
+ selected.push(join(directory, entry.name));
45
+ selected.sort();
46
+ if (selected.length > 3)
47
+ selected.shift();
48
+ }
49
+ return selected;
50
+ }
51
+ async function read(path, start, maximum) {
52
+ const handle = await open(path, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));
53
+ try {
54
+ const stat = await handle.stat();
55
+ if (!stat.isFile() || stat.nlink !== 1)
56
+ throw new Error("LOG_NOT_REGULAR_FILE");
57
+ const offset = start === undefined ? Math.max(0, stat.size - maximum) : Math.min(start, stat.size);
58
+ const buffer = Buffer.alloc(Math.min(maximum, stat.size - offset));
59
+ const { bytesRead } = await handle.read(buffer, 0, buffer.length, offset);
60
+ return {
61
+ text: buffer.subarray(0, bytesRead).toString("utf8"),
62
+ offset,
63
+ end: offset + bytesRead,
64
+ identity: `${stat.dev}:${stat.ino}`,
65
+ size: stat.size
66
+ };
67
+ }
68
+ finally {
69
+ await handle.close();
70
+ }
71
+ }
72
+ export async function showExtraLogs(options) {
73
+ if (!/^[a-z][a-z0-9-]{0,63}$/.test(options.extra) ||
74
+ !Number.isInteger(options.lines) ||
75
+ options.lines < 1 ||
76
+ options.lines > 10000)
77
+ throw new Error("INVALID_ARGUMENTS");
78
+ const controller = new AbortController();
79
+ const stop = () => controller.abort();
80
+ if (options.follow) {
81
+ process.on("SIGINT", stop);
82
+ process.on("SIGTERM", stop);
83
+ options.signal?.addEventListener("abort", stop, { once: true });
84
+ if (options.signal?.aborted)
85
+ stop();
86
+ }
87
+ const cursors = new Map();
88
+ const emit = async (value) => {
89
+ const text = options.json
90
+ ? JSON.stringify(value)
91
+ : `${value.time} ${value.level.toUpperCase()} ${value.extra_type} ${value.event}${Object.keys(value.data).length ? ` ${JSON.stringify(value.data)}` : ""}`;
92
+ await options.output(text, controller.signal);
93
+ };
94
+ try {
95
+ const selected = await files(options.home, options.extra);
96
+ let remaining = MAX_READ;
97
+ const chunks = [];
98
+ // Spend the shared byte budget newest-first, then emit complete records in file order.
99
+ for (const path of [...selected].reverse()) {
100
+ try {
101
+ const value = await read(path, undefined, remaining);
102
+ remaining -= Buffer.byteLength(value.text);
103
+ const lines = value.text.split("\n");
104
+ if (value.offset > 0)
105
+ lines.shift();
106
+ const pending = lines.pop() ?? "";
107
+ cursors.set(path, {
108
+ offset: value.end,
109
+ identity: value.identity,
110
+ pending: pending.length <= MAX_LINE ? pending : "",
111
+ skipping: pending.length > MAX_LINE
112
+ });
113
+ chunks.unshift(lines
114
+ .map((line) => record(line, options.extra))
115
+ .filter((value) => !!value));
116
+ }
117
+ catch (error) {
118
+ if (error.code !== "ENOENT")
119
+ throw error;
120
+ }
121
+ }
122
+ const tail = chunks.flat().slice(-options.lines);
123
+ for (const value of tail)
124
+ await emit(value);
125
+ if (!tail.length)
126
+ options.errorOutput(`${options.extra}: no service status logs`);
127
+ if (!options.follow)
128
+ return;
129
+ while (!controller.signal.aborted) {
130
+ await delay(500, undefined, { signal: controller.signal });
131
+ const current = await files(options.home, options.extra);
132
+ for (const path of cursors.keys())
133
+ if (!current.includes(path))
134
+ cursors.delete(path);
135
+ let budget = MAX_READ;
136
+ for (const path of current) {
137
+ if (controller.signal.aborted || budget <= 0)
138
+ break;
139
+ try {
140
+ // Inode/size changes reset the cursor; preserve partial records across appends.
141
+ let cursor = cursors.get(path);
142
+ let value = await read(path, cursor?.offset ?? 0, budget);
143
+ if (!cursor || cursor.identity !== value.identity || cursor.offset > value.size) {
144
+ cursor = { offset: 0, identity: value.identity, pending: "", skipping: false };
145
+ if (value.offset !== 0)
146
+ value = await read(path, 0, budget);
147
+ }
148
+ budget -= Buffer.byteLength(value.text);
149
+ const lines = (cursor.pending + value.text).split("\n");
150
+ if (cursor.skipping) {
151
+ if (lines.length === 1) {
152
+ cursors.set(path, { ...cursor, offset: value.end });
153
+ continue;
154
+ }
155
+ lines.shift();
156
+ }
157
+ const pending = lines.pop() ?? "";
158
+ for (const line of lines) {
159
+ const value = record(line, options.extra);
160
+ if (value)
161
+ await emit(value);
162
+ }
163
+ cursors.set(path, {
164
+ offset: value.end,
165
+ identity: value.identity,
166
+ pending: pending.length <= MAX_LINE ? pending : "",
167
+ skipping: pending.length > MAX_LINE
168
+ });
169
+ }
170
+ catch (error) {
171
+ if (error.code !== "ENOENT")
172
+ throw error;
173
+ }
174
+ }
175
+ }
176
+ }
177
+ catch (error) {
178
+ if (!controller.signal.aborted)
179
+ throw error;
180
+ }
181
+ finally {
182
+ process.off("SIGINT", stop);
183
+ process.off("SIGTERM", stop);
184
+ options.signal?.removeEventListener("abort", stop);
185
+ }
186
+ }
@@ -7,5 +7,6 @@ export interface CliDependencies extends ServiceControlOptions {
7
7
  sleep?: (ms: number) => Promise<void>;
8
8
  /** CLI 入口选择前台组件的退出策略;仅确认命令类型,不表示服务 ready。 */
9
9
  onComponentRun?: () => void;
10
+ logsSignal?: AbortSignal;
10
11
  }
11
12
  export declare function runCli(args: string[], dependencies?: CliDependencies): Promise<number>;