@capyclaw/foundation 0.1.3 → 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 +4 -1
- package/abi/loader.mjs +6 -4
- package/lib/adapters/http-router.mjs +2 -3
- package/lib/adapters/runtime-driver.mjs +2 -3
- package/lib/adapters/skill-provider.mjs +2 -3
- package/lib/adapters/tool-provider.mjs +4 -5
- package/lib/adapters/ui-panel.mjs +3 -4
- package/lib/host/caps.mjs +26 -0
- package/lib/host/dsh.mjs +41 -0
- package/lib/host/memory.mjs +116 -0
- package/lib/kernel.mjs +36 -31
- package/lib/subsystems/log.mjs +2 -3
- package/lib/subsystems/net.mjs +11 -12
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -25,7 +25,10 @@ export const { name, inject, apply } = createPlugin({
|
|
|
25
25
|
|
|
26
26
|
## 状态
|
|
27
27
|
|
|
28
|
-
- 已发布 `0.1.
|
|
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 =
|
|
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
|
|
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) ||
|
|
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 = { ...
|
|
219
|
+
const capabilityView = { ...capabilityMap() }
|
|
218
220
|
for (const [name, info] of provided) capabilityView[name] = info.version
|
|
219
221
|
return {
|
|
220
222
|
abiVersion: abiMajor,
|
|
@@ -5,15 +5,14 @@
|
|
|
5
5
|
* 应用侧不接触 server、不选端口。
|
|
6
6
|
*/
|
|
7
7
|
import { AbiError, ERROR_CODES } from '../../abi/errors.mjs'
|
|
8
|
-
import { getService } from '../ctx-service.mjs'
|
|
9
8
|
|
|
10
9
|
export const kind = 'http-router@1'
|
|
11
10
|
|
|
12
|
-
export function createHttpRouterAdapter({
|
|
11
|
+
export function createHttpRouterAdapter({ host, net, log }) {
|
|
13
12
|
return {
|
|
14
13
|
kind,
|
|
15
14
|
compile(impl, { spec, config }) {
|
|
16
|
-
const serviceHost =
|
|
15
|
+
const serviceHost = host.http()
|
|
17
16
|
if (!serviceHost?.createApp) {
|
|
18
17
|
throw new AbiError(ERROR_CODES.E_CAP_MISSING,
|
|
19
18
|
'ctx.serviceHost 不可用:无法创建子 app', { plugin: spec.name, capability: 'os.net' })
|
|
@@ -7,17 +7,16 @@
|
|
|
7
7
|
import { fileURLToPath } from 'node:url'
|
|
8
8
|
|
|
9
9
|
import { AbiError, ERROR_CODES } from '../../abi/errors.mjs'
|
|
10
|
-
import { getService } from '../ctx-service.mjs'
|
|
11
10
|
|
|
12
11
|
export const kind = 'runtime-driver@1'
|
|
13
12
|
|
|
14
|
-
export function createRuntimeDriverAdapter({
|
|
13
|
+
export function createRuntimeDriverAdapter({ host, config, log }) {
|
|
15
14
|
const defaultPort = config.driverPort ?? 0
|
|
16
15
|
|
|
17
16
|
return {
|
|
18
17
|
kind,
|
|
19
18
|
compile(impl, mountContext) {
|
|
20
|
-
const broker =
|
|
19
|
+
const broker = host.proc()
|
|
21
20
|
if (!broker?.start || !broker?.stop) {
|
|
22
21
|
throw new AbiError(ERROR_CODES.E_CAP_MISSING,
|
|
23
22
|
'ctx.serviceBroker 不可用:无法拉起长驻进程',
|
|
@@ -9,16 +9,15 @@ import { basename, dirname } from 'node:path'
|
|
|
9
9
|
import { fileURLToPath } from 'node:url'
|
|
10
10
|
|
|
11
11
|
import { AbiError, ERROR_CODES } from '../../abi/errors.mjs'
|
|
12
|
-
import { getService } from '../ctx-service.mjs'
|
|
13
12
|
import { createSkillProvider } from '../skill/scan.mjs'
|
|
14
13
|
|
|
15
14
|
export const kind = 'skill-provider@1'
|
|
16
15
|
|
|
17
|
-
export function createSkillAdapter({
|
|
16
|
+
export function createSkillAdapter({ host, log }) {
|
|
18
17
|
return {
|
|
19
18
|
kind,
|
|
20
19
|
compile(impl, { spec, context }) {
|
|
21
|
-
const skills =
|
|
20
|
+
const skills = host.skills()
|
|
22
21
|
if (!skills || typeof skills.registerProvider !== 'function') {
|
|
23
22
|
throw new AbiError(ERROR_CODES.E_CAP_MISSING,
|
|
24
23
|
'ctx.skills.registerProvider 不可用:skills 服务未加载', { plugin: spec.name })
|
|
@@ -7,16 +7,15 @@
|
|
|
7
7
|
* 工具实现不经过 foundation,foundation 只决定「什么时候挂上去、挂到哪个命名空间」。
|
|
8
8
|
*/
|
|
9
9
|
import { AbiError, ERROR_CODES } from '../../abi/errors.mjs'
|
|
10
|
-
import { getService } from '../ctx-service.mjs'
|
|
11
10
|
|
|
12
11
|
export const kind = 'tool-provider@1'
|
|
13
12
|
|
|
14
|
-
export function createToolProviderAdapter({
|
|
13
|
+
export function createToolProviderAdapter({ host, log }) {
|
|
15
14
|
return {
|
|
16
15
|
kind,
|
|
17
16
|
compile(impl, { spec, config }) {
|
|
18
|
-
const
|
|
19
|
-
if (!
|
|
17
|
+
const tools = host.tools()
|
|
18
|
+
if (!tools?.register) {
|
|
20
19
|
throw new AbiError(ERROR_CODES.E_CAP_MISSING,
|
|
21
20
|
'ctx.toolHost 不可用:无法注册工具面', { plugin: spec.name, capability: 'os.tools' })
|
|
22
21
|
}
|
|
@@ -33,7 +32,7 @@ export function createToolProviderAdapter({ ctx, log }) {
|
|
|
33
32
|
'provide() 必须返回 { TOOLS, TOOL_IMPL } 形状的注册表', { plugin: spec.name })
|
|
34
33
|
}
|
|
35
34
|
|
|
36
|
-
const dispose =
|
|
35
|
+
const dispose = tools.register(registry, { namespace: impl.namespace })
|
|
37
36
|
log?.debug?.(`[foundation] ${spec.name} 注册 ${registry.TOOLS.length} 个工具(${impl.namespace})`)
|
|
38
37
|
return {
|
|
39
38
|
registry,
|
|
@@ -6,16 +6,15 @@
|
|
|
6
6
|
import { fileURLToPath } from 'node:url'
|
|
7
7
|
|
|
8
8
|
import { AbiError, ERROR_CODES } from '../../abi/errors.mjs'
|
|
9
|
-
import { getService } from '../ctx-service.mjs'
|
|
10
9
|
|
|
11
10
|
export const kind = 'ui-panel@1'
|
|
12
11
|
|
|
13
|
-
export function createUiPanelAdapter({
|
|
12
|
+
export function createUiPanelAdapter({ host, net, log }) {
|
|
14
13
|
return {
|
|
15
14
|
kind,
|
|
16
15
|
compile(impl, { spec, config, context }) {
|
|
17
|
-
const serviceHost =
|
|
18
|
-
const editorHost =
|
|
16
|
+
const serviceHost = host.http()
|
|
17
|
+
const editorHost = host.ui()
|
|
19
18
|
if (!serviceHost?.createApp || !editorHost?.createEditorHost) {
|
|
20
19
|
throw new AbiError(ERROR_CODES.E_CAP_MISSING,
|
|
21
20
|
'ctx.editorHost / ctx.serviceHost 不可用:无法挂静态面板',
|
|
@@ -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
|
+
}
|
package/lib/host/dsh.mjs
ADDED
|
@@ -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,14 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 内核装配:把 ABI 加载器、适配器与 os 子系统接到一起。
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* 分层:应用插件 → ABI → 内核 → **Host 适配器** → agent 运行时(dsh)。
|
|
5
|
+
* 内核不认识 dsh:它只调用 Host 接口(skills/tools/http/proc/ui/log/capabilities)。
|
|
6
|
+
* 换宿主 = 写一个新的 lib/host/<runtime>.mjs,ABI 与应用插件零改动。
|
|
6
7
|
*
|
|
7
|
-
*
|
|
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'
|
|
11
|
-
import {
|
|
13
|
+
import { BASE_CAPABILITIES, detectDshCapabilities } from './host/caps.mjs'
|
|
14
|
+
import { createDshHost } from './host/dsh.mjs'
|
|
12
15
|
import { createSkillAdapter } from './adapters/skill-provider.mjs'
|
|
13
16
|
import { createToolProviderAdapter } from './adapters/tool-provider.mjs'
|
|
14
17
|
import { createRuntimeDriverAdapter } from './adapters/runtime-driver.mjs'
|
|
@@ -19,55 +22,57 @@ import { createEvents } from './subsystems/events.mjs'
|
|
|
19
22
|
import { createLogger } from './subsystems/log.mjs'
|
|
20
23
|
import { createNet } from './subsystems/net.mjs'
|
|
21
24
|
|
|
22
|
-
|
|
23
|
-
export const BASE_CAPABILITIES = Object.freeze({
|
|
24
|
-
'os.events': '1.0.0',
|
|
25
|
-
'os.log': '1.0.0',
|
|
26
|
-
'os.caps': '1.0.0',
|
|
27
|
-
})
|
|
25
|
+
export { BASE_CAPABILITIES }
|
|
28
26
|
|
|
29
|
-
/**
|
|
27
|
+
/** 兼容导出:测试与旧调用方按 ctx 探测 dsh 能力。 */
|
|
30
28
|
export function detectCapabilities(ctx) {
|
|
31
|
-
|
|
32
|
-
if (getService(ctx, 'skills')?.registerProvider) capabilities['os.skills'] = '1.0.0'
|
|
33
|
-
const serviceHost = getService(ctx, 'serviceHost')
|
|
34
|
-
if (serviceHost?.createSharedHost && serviceHost?.createApp) capabilities['os.net'] = '1.0.0'
|
|
35
|
-
if (getService(ctx, 'toolHost')?.registerWithDsh) capabilities['os.tools'] = '1.0.0'
|
|
36
|
-
if (getService(ctx, 'serviceBroker')?.start) capabilities['os.proc'] = '1.0.0'
|
|
37
|
-
if (getService(ctx, 'editorHost')?.createEditorHost) capabilities['os.ui'] = '1.0.0'
|
|
38
|
-
return capabilities
|
|
29
|
+
return detectDshCapabilities(ctx)
|
|
39
30
|
}
|
|
40
31
|
|
|
41
32
|
export const KERNEL_VERSION = '0.1.0'
|
|
42
33
|
export const ABI_VERSION = ABI_MAJOR
|
|
43
34
|
|
|
44
|
-
|
|
45
|
-
|
|
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)
|
|
46
43
|
const events = createEvents({
|
|
47
44
|
onError: (error, topic) => log.error(`事件订阅者抛错 (${topic}): ${error.message}`),
|
|
48
45
|
})
|
|
49
46
|
const hooks = createHookRegistry()
|
|
50
|
-
const net = createNet(
|
|
51
|
-
const capabilities = detectCapabilities(ctx)
|
|
47
|
+
const net = createNet(host, config)
|
|
52
48
|
|
|
53
49
|
const adapters = {
|
|
54
|
-
'skill-provider@1': createSkillAdapter({
|
|
55
|
-
'tool-provider@1': createToolProviderAdapter({
|
|
56
|
-
'runtime-driver@1': createRuntimeDriverAdapter({
|
|
57
|
-
'http-router@1': createHttpRouterAdapter({
|
|
58
|
-
'ui-panel@1': createUiPanelAdapter({
|
|
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 }),
|
|
59
55
|
'lifecycle-hook@1': createLifecycleAdapter({ hooks, log }),
|
|
60
56
|
}
|
|
61
57
|
|
|
62
|
-
|
|
58
|
+
/* 能力表按需现查:宿主服务可能晚于 foundation 激活。 */
|
|
59
|
+
const loader = createLoader({ adapters, capabilities: () => host.capabilities(), log })
|
|
63
60
|
const startedAt = Date.now()
|
|
64
61
|
|
|
65
62
|
const os = {
|
|
66
63
|
version: KERNEL_VERSION,
|
|
67
64
|
abi: ABI_VERSION,
|
|
65
|
+
host: host.kind,
|
|
66
|
+
hostVersion: host.version || '1.0.0',
|
|
68
67
|
mount: (spec, mountConfig = {}, meta = {}) => loader.mount(spec, mountConfig, meta),
|
|
69
68
|
report: () => ({
|
|
70
|
-
kernel: {
|
|
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
|
+
},
|
|
71
76
|
...loader.report(),
|
|
72
77
|
}),
|
|
73
78
|
events,
|
|
@@ -77,7 +82,7 @@ export function createKernel(ctx, config = {}) {
|
|
|
77
82
|
|
|
78
83
|
/** 能力检查与自报。 */
|
|
79
84
|
caps: {
|
|
80
|
-
list: () => Object.keys(capabilities),
|
|
85
|
+
list: () => Object.keys(host.capabilities()),
|
|
81
86
|
has: (name) => loader.capability(name) !== null,
|
|
82
87
|
version: (name) => loader.capability(name),
|
|
83
88
|
report: () => loader.report(),
|
package/lib/subsystems/log.mjs
CHANGED
|
@@ -7,7 +7,6 @@
|
|
|
7
7
|
import { getService } from '../ctx-service.mjs'
|
|
8
8
|
|
|
9
9
|
const LEVELS = ['debug', 'info', 'warn', 'error']
|
|
10
|
-
|
|
11
10
|
function createScoped(underlying, scope) {
|
|
12
11
|
const prefix = scope ? `[${scope}]` : ''
|
|
13
12
|
const scoped = {}
|
|
@@ -24,7 +23,7 @@ function createScoped(underlying, scope) {
|
|
|
24
23
|
return scoped
|
|
25
24
|
}
|
|
26
25
|
|
|
27
|
-
export function createLogger(
|
|
28
|
-
const underlying = getService(
|
|
26
|
+
export function createLogger(host, config = {}) {
|
|
27
|
+
const underlying = typeof host?.log === 'function' ? host.log() : getService(host, 'logger')
|
|
29
28
|
return createScoped(underlying, config.scope || 'foundation')
|
|
30
29
|
}
|
package/lib/subsystems/net.mjs
CHANGED
|
@@ -5,25 +5,24 @@
|
|
|
5
5
|
* 只注册 skill 的插件不会因为装了 foundation 就占一个端口。
|
|
6
6
|
*/
|
|
7
7
|
import { AbiError, ERROR_CODES } from '../../abi/errors.mjs'
|
|
8
|
-
import { getService } from '../ctx-service.mjs'
|
|
9
8
|
|
|
10
|
-
export function createNet(
|
|
9
|
+
export function createNet(host, config = {}) {
|
|
11
10
|
const http = config.http || {}
|
|
12
|
-
let
|
|
11
|
+
let sharedHost = null
|
|
13
12
|
let listening = null
|
|
14
13
|
let closed = false
|
|
15
14
|
|
|
16
15
|
function ensure() {
|
|
17
|
-
const serviceHost =
|
|
16
|
+
const serviceHost = host.http()
|
|
18
17
|
if (!serviceHost?.createSharedHost) {
|
|
19
18
|
throw new AbiError(ERROR_CODES.E_CAP_MISSING,
|
|
20
19
|
'ctx.serviceHost 不可用:无法挂载 HTTP 路由', { capability: 'os.net' })
|
|
21
20
|
}
|
|
22
|
-
if (!
|
|
23
|
-
|
|
24
|
-
listening = Promise.resolve(
|
|
21
|
+
if (!sharedHost) {
|
|
22
|
+
sharedHost = serviceHost.createSharedHost({ id: http.hostId || 'foundation-shared' })
|
|
23
|
+
listening = Promise.resolve(sharedHost.listen({ port: http.port ?? 0 }))
|
|
25
24
|
}
|
|
26
|
-
return
|
|
25
|
+
return sharedHost
|
|
27
26
|
}
|
|
28
27
|
|
|
29
28
|
return {
|
|
@@ -31,7 +30,7 @@ export function createNet(ctx, config = {}) {
|
|
|
31
30
|
mount(prefix, child) {
|
|
32
31
|
return ensure().mount(prefix, child)
|
|
33
32
|
},
|
|
34
|
-
children: () => (
|
|
33
|
+
children: () => (sharedHost ? sharedHost.children() : []),
|
|
35
34
|
ready() {
|
|
36
35
|
ensure()
|
|
37
36
|
return listening
|
|
@@ -39,12 +38,12 @@ export function createNet(ctx, config = {}) {
|
|
|
39
38
|
async dispose() {
|
|
40
39
|
if (closed) return
|
|
41
40
|
closed = true
|
|
42
|
-
if (
|
|
41
|
+
if (sharedHost) {
|
|
43
42
|
try {
|
|
44
|
-
await
|
|
43
|
+
await sharedHost.close()
|
|
45
44
|
} catch { /* 已关闭 */ }
|
|
46
45
|
}
|
|
47
|
-
|
|
46
|
+
sharedHost = null
|
|
48
47
|
},
|
|
49
48
|
}
|
|
50
49
|
}
|