@capyclaw/foundation 0.1.2 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -25,7 +25,10 @@ export const { name, inject, apply } = createPlugin({
25
25
 
26
26
  ## 状态
27
27
 
28
- - 已发布 `0.1.1`,并登记进 `capyclaw-registry.json`。
28
+ - 已发布 `0.1.3`,并登记进 `capyclaw-registry.json`。
29
+ - 分层:应用插件 → ABI → 内核 → **Host 适配器** → agent 运行时。内核不认识 dsh;
30
+ `lib/host/dsh.mjs` 是唯一知道 dsh 服务名的地方,`lib/host/memory.mjs` 是可移植性基线。
31
+ 规范见 `docs/HOST-ADAPTER.md`。
29
32
  - ABI v1,六个接口适配器全部可用:`skill-provider` / `tool-provider` / `runtime-driver` /
30
33
  `http-router` / `ui-panel` / `lifecycle-hook`,另有逃生舱 `raw@1`。
31
34
  - 能力表按 `ctx` 实际服务探测(见 `lib/kernel.mjs` 的 `detectCapabilities`),不做空头承诺。
package/abi/loader.mjs CHANGED
@@ -23,6 +23,8 @@ import { satisfies } from './version.mjs'
23
23
  * }} options
24
24
  */
25
25
  export function createLoader({ adapters = {}, capabilities = {}, log = null, abiMajor = ABI_MAJOR } = {}) {
26
+ /** 能力表可以是对象,也可以是函数(宿主服务晚到时可现查)。 */
27
+ const capabilityMap = () => (typeof capabilities === 'function' ? capabilities() : capabilities)
26
28
  /** @type {Map<string, { spec: object, entries: object[], handle: object }>} */
27
29
  const plugins = new Map()
28
30
  /** 唯一性占用:key -> 插件名 */
@@ -45,7 +47,7 @@ export function createLoader({ adapters = {}, capabilities = {}, log = null, abi
45
47
  const missing = []
46
48
  for (const requirement of spec.requires) {
47
49
  if (!requirement.name.startsWith('os.')) continue // 跨插件能力由 suite 组合期校验,运行时只报告
48
- const version = capabilities[requirement.name]
50
+ const version = capabilityMap()[requirement.name]
49
51
  if (!version) {
50
52
  missing.push({ name: requirement.name, range: requirement.range, reason: '未提供' })
51
53
  } else if (!satisfies(version, requirement.range)) {
@@ -196,7 +198,7 @@ export function createLoader({ adapters = {}, capabilities = {}, log = null, abi
196
198
 
197
199
  /** 当前可用的能力版本:内核承诺优先,其次插件提供。 */
198
200
  function capability(name) {
199
- return capabilities[name] || provided.get(name)?.version || null
201
+ return capabilityMap()[name] || provided.get(name)?.version || null
200
202
  }
201
203
 
202
204
  /** 未解析的跨插件能力(运行时只报告,组合期才是硬闸)。 */
@@ -205,7 +207,7 @@ export function createLoader({ adapters = {}, capabilities = {}, log = null, abi
205
207
  for (const { spec } of plugins.values()) {
206
208
  for (const requirement of spec.requires) {
207
209
  if (requirement.name.startsWith('os.')) continue
208
- if (provided.has(requirement.name) || capabilities[requirement.name]) continue
210
+ if (provided.has(requirement.name) || capabilityMap()[requirement.name]) continue
209
211
  out.push({ plugin: spec.name, capability: requirement.name, range: requirement.range })
210
212
  }
211
213
  }
@@ -214,7 +216,7 @@ export function createLoader({ adapters = {}, capabilities = {}, log = null, abi
214
216
 
215
217
  /** 内核状态快照:给 os.report() 与健康检查用。 */
216
218
  function report() {
217
- const capabilityView = { ...capabilities }
219
+ const capabilityView = { ...capabilityMap() }
218
220
  for (const [name, info] of provided) capabilityView[name] = info.version
219
221
  return {
220
222
  abiVersion: abiMajor,
@@ -8,16 +8,17 @@ import { AbiError, ERROR_CODES } from '../../abi/errors.mjs'
8
8
 
9
9
  export const kind = 'http-router@1'
10
10
 
11
- export function createHttpRouterAdapter({ ctx, net, log }) {
11
+ export function createHttpRouterAdapter({ host, net, log }) {
12
12
  return {
13
13
  kind,
14
14
  compile(impl, { spec, config }) {
15
- if (!ctx?.serviceHost?.createApp) {
15
+ const serviceHost = host.http()
16
+ if (!serviceHost?.createApp) {
16
17
  throw new AbiError(ERROR_CODES.E_CAP_MISSING,
17
18
  'ctx.serviceHost 不可用:无法创建子 app', { plugin: spec.name, capability: 'os.net' })
18
19
  }
19
20
 
20
- const app = ctx.serviceHost.createApp({ id: spec.name, title: spec.name })
21
+ const app = serviceHost.createApp({ id: spec.name, title: spec.name })
21
22
  try {
22
23
  impl.routes(app, { spec, config, log })
23
24
  } catch (error) {
@@ -10,13 +10,14 @@ import { AbiError, ERROR_CODES } from '../../abi/errors.mjs'
10
10
 
11
11
  export const kind = 'runtime-driver@1'
12
12
 
13
- export function createRuntimeDriverAdapter({ ctx, config, log }) {
13
+ export function createRuntimeDriverAdapter({ host, config, log }) {
14
14
  const defaultPort = config.driverPort ?? 0
15
15
 
16
16
  return {
17
17
  kind,
18
18
  compile(impl, mountContext) {
19
- if (!ctx?.serviceBroker?.start || !ctx?.serviceBroker?.stop) {
19
+ const broker = host.proc()
20
+ if (!broker?.start || !broker?.stop) {
20
21
  throw new AbiError(ERROR_CODES.E_CAP_MISSING,
21
22
  'ctx.serviceBroker 不可用:无法拉起长驻进程',
22
23
  { plugin: mountContext.spec.name, capability: 'os.proc' })
@@ -56,7 +57,7 @@ export function createRuntimeDriverAdapter({ ctx, config, log }) {
56
57
  return {
57
58
  serviceSpec,
58
59
  async start() {
59
- const result = await ctx.serviceBroker.start(serviceSpec)
60
+ const result = await broker.start(serviceSpec)
60
61
  const failure = (result?.errors || []).find((item) => item.id === impl.id)
61
62
  if (failure) {
62
63
  throw new AbiError(ERROR_CODES.E_ADAPTER_THREW,
@@ -68,7 +69,7 @@ export function createRuntimeDriverAdapter({ ctx, config, log }) {
68
69
  if (stopped) return
69
70
  stopped = true
70
71
  try {
71
- ctx.serviceBroker.stop(impl.id)
72
+ broker.stop(impl.id)
72
73
  } catch (error) {
73
74
  log?.warn?.(`[foundation] 停止 ${impl.id} 失败: ${error.message}`)
74
75
  }
@@ -13,11 +13,11 @@ import { createSkillProvider } from '../skill/scan.mjs'
13
13
 
14
14
  export const kind = 'skill-provider@1'
15
15
 
16
- export function createSkillAdapter({ ctx, log }) {
16
+ export function createSkillAdapter({ host, log }) {
17
17
  return {
18
18
  kind,
19
19
  compile(impl, { spec, context }) {
20
- const skills = ctx?.skills
20
+ const skills = host.skills()
21
21
  if (!skills || typeof skills.registerProvider !== 'function') {
22
22
  throw new AbiError(ERROR_CODES.E_CAP_MISSING,
23
23
  'ctx.skills.registerProvider 不可用:skills 服务未加载', { plugin: spec.name })
@@ -10,11 +10,12 @@ import { AbiError, ERROR_CODES } from '../../abi/errors.mjs'
10
10
 
11
11
  export const kind = 'tool-provider@1'
12
12
 
13
- export function createToolProviderAdapter({ ctx, log }) {
13
+ export function createToolProviderAdapter({ host, log }) {
14
14
  return {
15
15
  kind,
16
16
  compile(impl, { spec, config }) {
17
- if (!ctx?.toolHost?.registerWithDsh) {
17
+ const tools = host.tools()
18
+ if (!tools?.register) {
18
19
  throw new AbiError(ERROR_CODES.E_CAP_MISSING,
19
20
  'ctx.toolHost 不可用:无法注册工具面', { plugin: spec.name, capability: 'os.tools' })
20
21
  }
@@ -31,7 +32,7 @@ export function createToolProviderAdapter({ ctx, log }) {
31
32
  'provide() 必须返回 { TOOLS, TOOL_IMPL } 形状的注册表', { plugin: spec.name })
32
33
  }
33
34
 
34
- const dispose = ctx.toolHost.registerWithDsh(ctx, registry, { namespace: impl.namespace })
35
+ const dispose = tools.register(registry, { namespace: impl.namespace })
35
36
  log?.debug?.(`[foundation] ${spec.name} 注册 ${registry.TOOLS.length} 个工具(${impl.namespace})`)
36
37
  return {
37
38
  registry,
@@ -9,11 +9,13 @@ import { AbiError, ERROR_CODES } from '../../abi/errors.mjs'
9
9
 
10
10
  export const kind = 'ui-panel@1'
11
11
 
12
- export function createUiPanelAdapter({ ctx, net, log }) {
12
+ export function createUiPanelAdapter({ host, net, log }) {
13
13
  return {
14
14
  kind,
15
15
  compile(impl, { spec, config, context }) {
16
- if (!ctx?.serviceHost?.createApp || !ctx?.editorHost?.createEditorHost) {
16
+ const serviceHost = host.http()
17
+ const editorHost = host.ui()
18
+ if (!serviceHost?.createApp || !editorHost?.createEditorHost) {
17
19
  throw new AbiError(ERROR_CODES.E_CAP_MISSING,
18
20
  'ctx.editorHost / ctx.serviceHost 不可用:无法挂静态面板',
19
21
  { plugin: spec.name, capability: 'os.ui' })
@@ -24,14 +26,14 @@ export function createUiPanelAdapter({ ctx, net, log }) {
24
26
  }
25
27
 
26
28
  const dir = fileURLToPath(new URL(impl.root, context.baseUrl))
27
- const editor = ctx.editorHost.createEditorHost({
29
+ const editor = editorHost.createEditorHost({
28
30
  id: spec.name,
29
31
  dir,
30
32
  basePath: impl.mount,
31
33
  index: impl.index,
32
34
  })
33
35
 
34
- const app = ctx.serviceHost.createApp({ id: spec.name, title: spec.name })
36
+ const app = serviceHost.createApp({ id: spec.name, title: spec.name })
35
37
  for (const route of editor.routes()) app.use(route)
36
38
 
37
39
  let unmount
@@ -0,0 +1,21 @@
1
+ /**
2
+ * 服务探测:Cordis 的 ctx 是 Proxy,读取未在 `inject` 声明的服务会直接抛
3
+ * `cannot get property "x" without inject`。内核需要在 apply 时探测「哪些服务在场」,
4
+ * 因此统一走 `ctx.get(name)`(Cordis 官方 API:读取服务但不要求 inject,未提供返回 undefined)。
5
+ *
6
+ * 纯对象 ctx(测试、宿主)没有 get(),退化为直接属性访问;两种情况都不抛。
7
+ */
8
+ export function getService(ctx, name) {
9
+ if (!ctx) return undefined
10
+ if (typeof ctx.get === 'function') {
11
+ try {
12
+ const service = ctx.get(name)
13
+ if (service !== undefined) return service
14
+ } catch { /* 继续尝试属性访问 */ }
15
+ }
16
+ try {
17
+ return ctx[name]
18
+ } catch {
19
+ return undefined
20
+ }
21
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * 宿主能力表:内核承诺给应用插件的 os.* 能力。
3
+ *
4
+ * BASE 是内核自身就能提供的;其余取决于宿主实际接入了哪些服务。
5
+ * 这里是唯一允许出现宿主服务名(skills / serviceHost / toolHost / serviceBroker /
6
+ * editorHost)的地方——它们属于 dsh 适配层,不属于 ABI。
7
+ */
8
+ import { getService } from '../ctx-service.mjs'
9
+
10
+ export const BASE_CAPABILITIES = Object.freeze({
11
+ 'os.events': '1.0.0',
12
+ 'os.log': '1.0.0',
13
+ 'os.caps': '1.0.0',
14
+ })
15
+
16
+ /** dsh 宿主:按 ctx 里实际存在的服务生成能力表。 */
17
+ export function detectDshCapabilities(ctx) {
18
+ const capabilities = { ...BASE_CAPABILITIES }
19
+ if (getService(ctx, 'skills')?.registerProvider) capabilities['os.skills'] = '1.0.0'
20
+ const serviceHost = getService(ctx, 'serviceHost')
21
+ if (serviceHost?.createSharedHost && serviceHost?.createApp) capabilities['os.net'] = '1.0.0'
22
+ if (getService(ctx, 'toolHost')?.registerWithDsh) capabilities['os.tools'] = '1.0.0'
23
+ if (getService(ctx, 'serviceBroker')?.start) capabilities['os.proc'] = '1.0.0'
24
+ if (getService(ctx, 'editorHost')?.createEditorHost) capabilities['os.ui'] = '1.0.0'
25
+ return capabilities
26
+ }
@@ -0,0 +1,41 @@
1
+ /**
2
+ * dsh 宿主适配器:把 Cordis ctx 翻译成内核认识的 Host 接口。
3
+ *
4
+ * 这是整个 foundation 里**唯一**知道 dsh 服务名的地方。换一个 agent 运行时,
5
+ * 只需要照着 Host 接口写一个新的适配器(lib/host/<runtime>.mjs),
6
+ * 内核、ABI 与应用插件都不改。
7
+ *
8
+ * Host 接口(v1):
9
+ * kind 宿主标识
10
+ * capabilities() 返回 { 'os.xxx': '1.0.0' },用于 mount 前的能力校验
11
+ * log() 返回 { debug/info/warn/error } 或 undefined
12
+ * skills() { registerProvider(factory) => dispose }
13
+ * tools() { registerWithDsh(ctx, registry, { namespace }) => dispose }
14
+ * http() { createApp({id}), createSharedHost({id}) }
15
+ * proc() { start(serviceSpec) => Promise<{errors?}>, stop(id) }
16
+ * ui() { createEditorHost({ id, dir, basePath, index }) }
17
+ */
18
+ import { getService } from '../ctx-service.mjs'
19
+ import { detectDshCapabilities } from './caps.mjs'
20
+
21
+ export const HOST_VERSION = '1.0.0'
22
+
23
+ export function createDshHost(ctx) {
24
+ return {
25
+ kind: 'dsh',
26
+ version: HOST_VERSION,
27
+ capabilities: () => detectDshCapabilities(ctx),
28
+ log: () => getService(ctx, 'logger'),
29
+ skills: () => getService(ctx, 'skills'),
30
+ tools: () => {
31
+ const toolHost = getService(ctx, 'toolHost')
32
+ if (!toolHost?.registerWithDsh) return null
33
+ return {
34
+ register: (registry, options) => toolHost.registerWithDsh(ctx, registry, options),
35
+ }
36
+ },
37
+ http: () => getService(ctx, 'serviceHost'),
38
+ proc: () => getService(ctx, 'serviceBroker'),
39
+ ui: () => getService(ctx, 'editorHost'),
40
+ }
41
+ }
@@ -0,0 +1,116 @@
1
+ /**
2
+ * 内存宿主:Host 接口的参考实现,不依赖任何 agent 运行时。
3
+ *
4
+ * 用途有两个:
5
+ * 1. 一致性测试的基线——应用插件在这里跑通,说明它没有偷偷依赖 dsh;
6
+ * 2. 新宿主(别的 agent 系统)的模板——照这个接口实现即可,应用插件零改动。
7
+ *
8
+ * 它不承担真实网络/进程语义,只保证 Host 契约的形状与生命周期。
9
+ */
10
+ import { BASE_CAPABILITIES } from './caps.mjs'
11
+
12
+ const NOOP_LOG = { debug() {}, info() {}, warn() {}, error() {} }
13
+
14
+ export function createMemoryHost(options = {}) {
15
+ const state = {
16
+ providers: [],
17
+ tools: [],
18
+ apps: [],
19
+ mounts: new Map(),
20
+ services: new Map(),
21
+ }
22
+ const log = options.log ?? NOOP_LOG
23
+
24
+ function http() {
25
+ return {
26
+ createApp({ id, title } = {}) {
27
+ const routes = []
28
+ const app = {
29
+ id,
30
+ title: title || id,
31
+ routes,
32
+ use(handler) {
33
+ routes.push(handler)
34
+ return () => {
35
+ const index = routes.indexOf(handler)
36
+ if (index >= 0) routes.splice(index, 1)
37
+ }
38
+ },
39
+ async dispatch() {},
40
+ }
41
+ state.apps.push(app)
42
+ return app
43
+ },
44
+ createSharedHost({ id } = {}) {
45
+ return {
46
+ id,
47
+ mount(prefix, child) {
48
+ if (state.mounts.has(prefix)) throw new Error(`前缀已挂载: ${prefix}`)
49
+ state.mounts.set(prefix, child.id)
50
+ return () => state.mounts.delete(prefix)
51
+ },
52
+ listen: () => Promise.resolve({ port: 0, url: `memory://${id || 'shared'}` }),
53
+ close: async () => {},
54
+ children: () => [...state.mounts.keys()],
55
+ }
56
+ },
57
+ }
58
+ }
59
+
60
+ return {
61
+ kind: 'memory',
62
+ version: '1.0.0',
63
+ state,
64
+ capabilities: () => ({
65
+ ...BASE_CAPABILITIES,
66
+ 'os.skills': '1.0.0',
67
+ 'os.net': '1.0.0',
68
+ 'os.tools': '1.0.0',
69
+ 'os.proc': '1.0.0',
70
+ 'os.ui': '1.0.0',
71
+ }),
72
+ log: () => log,
73
+ skills: () => ({
74
+ registerProvider(factory) {
75
+ const provider = factory()
76
+ state.providers.push(provider)
77
+ return () => {
78
+ const index = state.providers.indexOf(provider)
79
+ if (index >= 0) state.providers.splice(index, 1)
80
+ }
81
+ },
82
+ }),
83
+ tools: () => ({
84
+ register(registry, options2 = {}) {
85
+ const entry = { registry, namespace: options2.namespace || '' }
86
+ state.tools.push(entry)
87
+ return () => {
88
+ const index = state.tools.indexOf(entry)
89
+ if (index >= 0) state.tools.splice(index, 1)
90
+ }
91
+ },
92
+ }),
93
+ http,
94
+ proc: () => ({
95
+ async start(serviceSpec) {
96
+ state.services.set(serviceSpec.id, serviceSpec)
97
+ return { services: [{ id: serviceSpec.id }], errors: [] }
98
+ },
99
+ stop(id) {
100
+ state.services.delete(id)
101
+ },
102
+ }),
103
+ ui: () => ({
104
+ createEditorHost({ id, dir, basePath, index }) {
105
+ return {
106
+ id,
107
+ dir,
108
+ basePath,
109
+ index,
110
+ routes: () => [async () => false],
111
+ status: () => ({ connected: false, lease: null }),
112
+ }
113
+ },
114
+ }),
115
+ }
116
+ }
package/lib/kernel.mjs CHANGED
@@ -1,13 +1,17 @@
1
1
  /**
2
2
  * 内核装配:把 ABI 加载器、适配器与 os 子系统接到一起。
3
3
  *
4
- * 能力表是**探测出来**的,不是写死的:ctx 里没有 skills 服务,就不承诺 os.skills,
5
- * 依赖它的插件在 mount 阶段拿到 E_CAP_MISSING,而不是运行到一半才炸。
4
+ * 分层:应用插件 → ABI → 内核 → **Host 适配器** → agent 运行时(dsh)。
5
+ * 内核不认识 dsh:它只调用 Host 接口(skills/tools/http/proc/ui/log/capabilities)。
6
+ * 换宿主 = 写一个新的 lib/host/<runtime>.mjs,ABI 与应用插件零改动。
6
7
  *
7
- * 惰性:os.net 只在第一次挂路由时创建共享宿主并 listen;纯 skill 插件不占端口。
8
+ * 能力表是**每次 mount 时现查**的:宿主服务晚于 foundation 激活也能被看到。
9
+ * 惰性:os.net 只在第一次挂路由时创建共享宿主并 listen。
8
10
  */
9
11
  import { ABI_MAJOR } from '../abi/kinds.mjs'
10
12
  import { createLoader } from '../abi/loader.mjs'
13
+ import { BASE_CAPABILITIES, detectDshCapabilities } from './host/caps.mjs'
14
+ import { createDshHost } from './host/dsh.mjs'
11
15
  import { createSkillAdapter } from './adapters/skill-provider.mjs'
12
16
  import { createToolProviderAdapter } from './adapters/tool-provider.mjs'
13
17
  import { createRuntimeDriverAdapter } from './adapters/runtime-driver.mjs'
@@ -18,54 +22,57 @@ import { createEvents } from './subsystems/events.mjs'
18
22
  import { createLogger } from './subsystems/log.mjs'
19
23
  import { createNet } from './subsystems/net.mjs'
20
24
 
21
- /** 内核自身就能提供的能力(不依赖任何 dsh 服务)。 */
22
- export const BASE_CAPABILITIES = Object.freeze({
23
- 'os.events': '1.0.0',
24
- 'os.log': '1.0.0',
25
- 'os.caps': '1.0.0',
26
- })
25
+ export { BASE_CAPABILITIES }
27
26
 
28
- /** 按 ctx 里实际存在的服务生成能力表。 */
27
+ /** 兼容导出:测试与旧调用方按 ctx 探测 dsh 能力。 */
29
28
  export function detectCapabilities(ctx) {
30
- const capabilities = { ...BASE_CAPABILITIES }
31
- if (ctx?.skills?.registerProvider) capabilities['os.skills'] = '1.0.0'
32
- if (ctx?.serviceHost?.createSharedHost && ctx?.serviceHost?.createApp) capabilities['os.net'] = '1.0.0'
33
- if (ctx?.toolHost?.registerWithDsh) capabilities['os.tools'] = '1.0.0'
34
- if (ctx?.serviceBroker?.start) capabilities['os.proc'] = '1.0.0'
35
- if (ctx?.editorHost?.createEditorHost) capabilities['os.ui'] = '1.0.0'
36
- return capabilities
29
+ return detectDshCapabilities(ctx)
37
30
  }
38
31
 
39
32
  export const KERNEL_VERSION = '0.1.0'
40
33
  export const ABI_VERSION = ABI_MAJOR
41
34
 
42
- export function createKernel(ctx, config = {}) {
43
- const log = createLogger(ctx, config)
35
+ /**
36
+ * @param {object} ctx dsh Cordis ctx(自定义宿主时可为 null)
37
+ * @param {object} config 内核配置
38
+ * @param {{ host?: object }} [options] 显式传入 Host 适配器(测试/其他运行时)
39
+ */
40
+ export function createKernel(ctx, config = {}, options = {}) {
41
+ const host = options.host ?? createDshHost(ctx)
42
+ const log = createLogger(host, config)
44
43
  const events = createEvents({
45
44
  onError: (error, topic) => log.error(`事件订阅者抛错 (${topic}): ${error.message}`),
46
45
  })
47
46
  const hooks = createHookRegistry()
48
- const net = createNet(ctx, config)
49
- const capabilities = detectCapabilities(ctx)
47
+ const net = createNet(host, config)
50
48
 
51
49
  const adapters = {
52
- 'skill-provider@1': createSkillAdapter({ ctx, log }),
53
- 'tool-provider@1': createToolProviderAdapter({ ctx, log }),
54
- 'runtime-driver@1': createRuntimeDriverAdapter({ ctx, config, log }),
55
- 'http-router@1': createHttpRouterAdapter({ ctx, net, log }),
56
- 'ui-panel@1': createUiPanelAdapter({ ctx, net, log }),
50
+ 'skill-provider@1': createSkillAdapter({ host, log }),
51
+ 'tool-provider@1': createToolProviderAdapter({ host, log }),
52
+ 'runtime-driver@1': createRuntimeDriverAdapter({ host, config, log }),
53
+ 'http-router@1': createHttpRouterAdapter({ host, net, log }),
54
+ 'ui-panel@1': createUiPanelAdapter({ host, net, log }),
57
55
  'lifecycle-hook@1': createLifecycleAdapter({ hooks, log }),
58
56
  }
59
57
 
60
- const loader = createLoader({ adapters, capabilities, log })
58
+ /* 能力表按需现查:宿主服务可能晚于 foundation 激活。 */
59
+ const loader = createLoader({ adapters, capabilities: () => host.capabilities(), log })
61
60
  const startedAt = Date.now()
62
61
 
63
62
  const os = {
64
63
  version: KERNEL_VERSION,
65
64
  abi: ABI_VERSION,
65
+ host: host.kind,
66
+ hostVersion: host.version || '1.0.0',
66
67
  mount: (spec, mountConfig = {}, meta = {}) => loader.mount(spec, mountConfig, meta),
67
68
  report: () => ({
68
- kernel: { version: KERNEL_VERSION, abi: ABI_VERSION, uptimeMs: Date.now() - startedAt },
69
+ kernel: {
70
+ version: KERNEL_VERSION,
71
+ abi: ABI_VERSION,
72
+ host: host.kind,
73
+ hostVersion: host.version || '1.0.0',
74
+ uptimeMs: Date.now() - startedAt,
75
+ },
69
76
  ...loader.report(),
70
77
  }),
71
78
  events,
@@ -75,7 +82,7 @@ export function createKernel(ctx, config = {}) {
75
82
 
76
83
  /** 能力检查与自报。 */
77
84
  caps: {
78
- list: () => Object.keys(capabilities),
85
+ list: () => Object.keys(host.capabilities()),
79
86
  has: (name) => loader.capability(name) !== null,
80
87
  version: (name) => loader.capability(name),
81
88
  report: () => loader.report(),
@@ -2,9 +2,11 @@
2
2
  * 结构化日志:优先复用 dsh 的 ctx.logger,没有就退回 console。
3
3
  *
4
4
  * 插件不自己拼前缀,一律通过 log.child({ plugin }) 拿作用域。
5
+ * ctx 是 Cordis Proxy,未 inject 的服务直接读会抛错,这里用 getService 探测。
5
6
  */
6
- const LEVELS = ['debug', 'info', 'warn', 'error']
7
+ import { getService } from '../ctx-service.mjs'
7
8
 
9
+ const LEVELS = ['debug', 'info', 'warn', 'error']
8
10
  function createScoped(underlying, scope) {
9
11
  const prefix = scope ? `[${scope}]` : ''
10
12
  const scoped = {}
@@ -21,7 +23,7 @@ function createScoped(underlying, scope) {
21
23
  return scoped
22
24
  }
23
25
 
24
- export function createLogger(ctx, config = {}) {
25
- const underlying = ctx?.logger
26
+ export function createLogger(host, config = {}) {
27
+ const underlying = typeof host?.log === 'function' ? host.log() : getService(host, 'logger')
26
28
  return createScoped(underlying, config.scope || 'foundation')
27
29
  }
@@ -6,23 +6,23 @@
6
6
  */
7
7
  import { AbiError, ERROR_CODES } from '../../abi/errors.mjs'
8
8
 
9
- export function createNet(ctx, config = {}) {
9
+ export function createNet(host, config = {}) {
10
10
  const http = config.http || {}
11
- let host = null
11
+ let sharedHost = null
12
12
  let listening = null
13
13
  let closed = false
14
14
 
15
15
  function ensure() {
16
- const serviceHost = ctx?.serviceHost
16
+ const serviceHost = host.http()
17
17
  if (!serviceHost?.createSharedHost) {
18
18
  throw new AbiError(ERROR_CODES.E_CAP_MISSING,
19
19
  'ctx.serviceHost 不可用:无法挂载 HTTP 路由', { capability: 'os.net' })
20
20
  }
21
- if (!host) {
22
- host = serviceHost.createSharedHost({ id: http.hostId || 'foundation-shared' })
23
- listening = Promise.resolve(host.listen({ port: http.port ?? 0 }))
21
+ if (!sharedHost) {
22
+ sharedHost = serviceHost.createSharedHost({ id: http.hostId || 'foundation-shared' })
23
+ listening = Promise.resolve(sharedHost.listen({ port: http.port ?? 0 }))
24
24
  }
25
- return host
25
+ return sharedHost
26
26
  }
27
27
 
28
28
  return {
@@ -30,7 +30,7 @@ export function createNet(ctx, config = {}) {
30
30
  mount(prefix, child) {
31
31
  return ensure().mount(prefix, child)
32
32
  },
33
- children: () => (host ? host.children() : []),
33
+ children: () => (sharedHost ? sharedHost.children() : []),
34
34
  ready() {
35
35
  ensure()
36
36
  return listening
@@ -38,12 +38,12 @@ export function createNet(ctx, config = {}) {
38
38
  async dispose() {
39
39
  if (closed) return
40
40
  closed = true
41
- if (host) {
41
+ if (sharedHost) {
42
42
  try {
43
- await host.close()
43
+ await sharedHost.close()
44
44
  } catch { /* 已关闭 */ }
45
45
  }
46
- host = null
46
+ sharedHost = null
47
47
  },
48
48
  }
49
49
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capyclaw/foundation",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "dsh 插件基石层:接口 ABI、插件加载器与内核服务 ctx.os。应用插件只声明接口,不写装配代码。",
5
5
  "type": "module",
6
6
  "main": "./lib/host.mjs",