@capyclaw/foundation 0.1.2 → 0.1.3
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/lib/adapters/http-router.mjs +4 -2
- package/lib/adapters/runtime-driver.mjs +5 -3
- package/lib/adapters/skill-provider.mjs +2 -1
- package/lib/adapters/tool-provider.mjs +4 -2
- package/lib/adapters/ui-panel.mjs +6 -3
- package/lib/ctx-service.mjs +21 -0
- package/lib/kernel.mjs +7 -5
- package/lib/subsystems/log.mjs +4 -1
- package/lib/subsystems/net.mjs +2 -1
- package/package.json +1 -1
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* 应用侧不接触 server、不选端口。
|
|
6
6
|
*/
|
|
7
7
|
import { AbiError, ERROR_CODES } from '../../abi/errors.mjs'
|
|
8
|
+
import { getService } from '../ctx-service.mjs'
|
|
8
9
|
|
|
9
10
|
export const kind = 'http-router@1'
|
|
10
11
|
|
|
@@ -12,12 +13,13 @@ export function createHttpRouterAdapter({ ctx, net, log }) {
|
|
|
12
13
|
return {
|
|
13
14
|
kind,
|
|
14
15
|
compile(impl, { spec, config }) {
|
|
15
|
-
|
|
16
|
+
const serviceHost = getService(ctx, 'serviceHost')
|
|
17
|
+
if (!serviceHost?.createApp) {
|
|
16
18
|
throw new AbiError(ERROR_CODES.E_CAP_MISSING,
|
|
17
19
|
'ctx.serviceHost 不可用:无法创建子 app', { plugin: spec.name, capability: 'os.net' })
|
|
18
20
|
}
|
|
19
21
|
|
|
20
|
-
const app =
|
|
22
|
+
const app = serviceHost.createApp({ id: spec.name, title: spec.name })
|
|
21
23
|
try {
|
|
22
24
|
impl.routes(app, { spec, config, log })
|
|
23
25
|
} catch (error) {
|
|
@@ -7,6 +7,7 @@
|
|
|
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'
|
|
10
11
|
|
|
11
12
|
export const kind = 'runtime-driver@1'
|
|
12
13
|
|
|
@@ -16,7 +17,8 @@ export function createRuntimeDriverAdapter({ ctx, config, log }) {
|
|
|
16
17
|
return {
|
|
17
18
|
kind,
|
|
18
19
|
compile(impl, mountContext) {
|
|
19
|
-
|
|
20
|
+
const broker = getService(ctx, 'serviceBroker')
|
|
21
|
+
if (!broker?.start || !broker?.stop) {
|
|
20
22
|
throw new AbiError(ERROR_CODES.E_CAP_MISSING,
|
|
21
23
|
'ctx.serviceBroker 不可用:无法拉起长驻进程',
|
|
22
24
|
{ plugin: mountContext.spec.name, capability: 'os.proc' })
|
|
@@ -56,7 +58,7 @@ export function createRuntimeDriverAdapter({ ctx, config, log }) {
|
|
|
56
58
|
return {
|
|
57
59
|
serviceSpec,
|
|
58
60
|
async start() {
|
|
59
|
-
const result = await
|
|
61
|
+
const result = await broker.start(serviceSpec)
|
|
60
62
|
const failure = (result?.errors || []).find((item) => item.id === impl.id)
|
|
61
63
|
if (failure) {
|
|
62
64
|
throw new AbiError(ERROR_CODES.E_ADAPTER_THREW,
|
|
@@ -68,7 +70,7 @@ export function createRuntimeDriverAdapter({ ctx, config, log }) {
|
|
|
68
70
|
if (stopped) return
|
|
69
71
|
stopped = true
|
|
70
72
|
try {
|
|
71
|
-
|
|
73
|
+
broker.stop(impl.id)
|
|
72
74
|
} catch (error) {
|
|
73
75
|
log?.warn?.(`[foundation] 停止 ${impl.id} 失败: ${error.message}`)
|
|
74
76
|
}
|
|
@@ -9,6 +9,7 @@ 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'
|
|
12
13
|
import { createSkillProvider } from '../skill/scan.mjs'
|
|
13
14
|
|
|
14
15
|
export const kind = 'skill-provider@1'
|
|
@@ -17,7 +18,7 @@ export function createSkillAdapter({ ctx, log }) {
|
|
|
17
18
|
return {
|
|
18
19
|
kind,
|
|
19
20
|
compile(impl, { spec, context }) {
|
|
20
|
-
const skills = ctx
|
|
21
|
+
const skills = getService(ctx, 'skills')
|
|
21
22
|
if (!skills || typeof skills.registerProvider !== 'function') {
|
|
22
23
|
throw new AbiError(ERROR_CODES.E_CAP_MISSING,
|
|
23
24
|
'ctx.skills.registerProvider 不可用:skills 服务未加载', { plugin: spec.name })
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* 工具实现不经过 foundation,foundation 只决定「什么时候挂上去、挂到哪个命名空间」。
|
|
8
8
|
*/
|
|
9
9
|
import { AbiError, ERROR_CODES } from '../../abi/errors.mjs'
|
|
10
|
+
import { getService } from '../ctx-service.mjs'
|
|
10
11
|
|
|
11
12
|
export const kind = 'tool-provider@1'
|
|
12
13
|
|
|
@@ -14,7 +15,8 @@ export function createToolProviderAdapter({ ctx, log }) {
|
|
|
14
15
|
return {
|
|
15
16
|
kind,
|
|
16
17
|
compile(impl, { spec, config }) {
|
|
17
|
-
|
|
18
|
+
const toolHost = getService(ctx, 'toolHost')
|
|
19
|
+
if (!toolHost?.registerWithDsh) {
|
|
18
20
|
throw new AbiError(ERROR_CODES.E_CAP_MISSING,
|
|
19
21
|
'ctx.toolHost 不可用:无法注册工具面', { plugin: spec.name, capability: 'os.tools' })
|
|
20
22
|
}
|
|
@@ -31,7 +33,7 @@ export function createToolProviderAdapter({ ctx, log }) {
|
|
|
31
33
|
'provide() 必须返回 { TOOLS, TOOL_IMPL } 形状的注册表', { plugin: spec.name })
|
|
32
34
|
}
|
|
33
35
|
|
|
34
|
-
const dispose =
|
|
36
|
+
const dispose = toolHost.registerWithDsh(ctx, registry, { namespace: impl.namespace })
|
|
35
37
|
log?.debug?.(`[foundation] ${spec.name} 注册 ${registry.TOOLS.length} 个工具(${impl.namespace})`)
|
|
36
38
|
return {
|
|
37
39
|
registry,
|
|
@@ -6,6 +6,7 @@
|
|
|
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'
|
|
9
10
|
|
|
10
11
|
export const kind = 'ui-panel@1'
|
|
11
12
|
|
|
@@ -13,7 +14,9 @@ export function createUiPanelAdapter({ ctx, net, log }) {
|
|
|
13
14
|
return {
|
|
14
15
|
kind,
|
|
15
16
|
compile(impl, { spec, config, context }) {
|
|
16
|
-
|
|
17
|
+
const serviceHost = getService(ctx, 'serviceHost')
|
|
18
|
+
const editorHost = getService(ctx, 'editorHost')
|
|
19
|
+
if (!serviceHost?.createApp || !editorHost?.createEditorHost) {
|
|
17
20
|
throw new AbiError(ERROR_CODES.E_CAP_MISSING,
|
|
18
21
|
'ctx.editorHost / ctx.serviceHost 不可用:无法挂静态面板',
|
|
19
22
|
{ plugin: spec.name, capability: 'os.ui' })
|
|
@@ -24,14 +27,14 @@ export function createUiPanelAdapter({ ctx, net, log }) {
|
|
|
24
27
|
}
|
|
25
28
|
|
|
26
29
|
const dir = fileURLToPath(new URL(impl.root, context.baseUrl))
|
|
27
|
-
const editor =
|
|
30
|
+
const editor = editorHost.createEditorHost({
|
|
28
31
|
id: spec.name,
|
|
29
32
|
dir,
|
|
30
33
|
basePath: impl.mount,
|
|
31
34
|
index: impl.index,
|
|
32
35
|
})
|
|
33
36
|
|
|
34
|
-
const app =
|
|
37
|
+
const app = serviceHost.createApp({ id: spec.name, title: spec.name })
|
|
35
38
|
for (const route of editor.routes()) app.use(route)
|
|
36
39
|
|
|
37
40
|
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
|
+
}
|
package/lib/kernel.mjs
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { ABI_MAJOR } from '../abi/kinds.mjs'
|
|
10
10
|
import { createLoader } from '../abi/loader.mjs'
|
|
11
|
+
import { getService } from './ctx-service.mjs'
|
|
11
12
|
import { createSkillAdapter } from './adapters/skill-provider.mjs'
|
|
12
13
|
import { createToolProviderAdapter } from './adapters/tool-provider.mjs'
|
|
13
14
|
import { createRuntimeDriverAdapter } from './adapters/runtime-driver.mjs'
|
|
@@ -28,11 +29,12 @@ export const BASE_CAPABILITIES = Object.freeze({
|
|
|
28
29
|
/** 按 ctx 里实际存在的服务生成能力表。 */
|
|
29
30
|
export function detectCapabilities(ctx) {
|
|
30
31
|
const capabilities = { ...BASE_CAPABILITIES }
|
|
31
|
-
if (ctx
|
|
32
|
-
|
|
33
|
-
if (
|
|
34
|
-
if (ctx?.
|
|
35
|
-
if (ctx?.
|
|
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'
|
|
36
38
|
return capabilities
|
|
37
39
|
}
|
|
38
40
|
|
package/lib/subsystems/log.mjs
CHANGED
|
@@ -2,7 +2,10 @@
|
|
|
2
2
|
* 结构化日志:优先复用 dsh 的 ctx.logger,没有就退回 console。
|
|
3
3
|
*
|
|
4
4
|
* 插件不自己拼前缀,一律通过 log.child({ plugin }) 拿作用域。
|
|
5
|
+
* ctx 是 Cordis Proxy,未 inject 的服务直接读会抛错,这里用 getService 探测。
|
|
5
6
|
*/
|
|
7
|
+
import { getService } from '../ctx-service.mjs'
|
|
8
|
+
|
|
6
9
|
const LEVELS = ['debug', 'info', 'warn', 'error']
|
|
7
10
|
|
|
8
11
|
function createScoped(underlying, scope) {
|
|
@@ -22,6 +25,6 @@ function createScoped(underlying, scope) {
|
|
|
22
25
|
}
|
|
23
26
|
|
|
24
27
|
export function createLogger(ctx, config = {}) {
|
|
25
|
-
const underlying = ctx
|
|
28
|
+
const underlying = getService(ctx, 'logger')
|
|
26
29
|
return createScoped(underlying, config.scope || 'foundation')
|
|
27
30
|
}
|
package/lib/subsystems/net.mjs
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* 只注册 skill 的插件不会因为装了 foundation 就占一个端口。
|
|
6
6
|
*/
|
|
7
7
|
import { AbiError, ERROR_CODES } from '../../abi/errors.mjs'
|
|
8
|
+
import { getService } from '../ctx-service.mjs'
|
|
8
9
|
|
|
9
10
|
export function createNet(ctx, config = {}) {
|
|
10
11
|
const http = config.http || {}
|
|
@@ -13,7 +14,7 @@ export function createNet(ctx, config = {}) {
|
|
|
13
14
|
let closed = false
|
|
14
15
|
|
|
15
16
|
function ensure() {
|
|
16
|
-
const serviceHost = ctx
|
|
17
|
+
const serviceHost = getService(ctx, 'serviceHost')
|
|
17
18
|
if (!serviceHost?.createSharedHost) {
|
|
18
19
|
throw new AbiError(ERROR_CODES.E_CAP_MISSING,
|
|
19
20
|
'ctx.serviceHost 不可用:无法挂载 HTTP 路由', { capability: 'os.net' })
|