better-dsh 0.2.3-e → 0.2.3-g
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/docs/50_test-reports/2026-09-13-preact-ui-shell/345/256/236/346/265/213/346/212/245/345/221/212.md +1 -1
- package/docs/50_test-reports/2026-09-14-4999-skill/346/270/205/345/215/225/344/270/216lsp-gate/345/256/236/346/265/213/346/212/245/345/221/212.md +54 -0
- package/docs/specs/agent/spec.md +54 -0
- package/docs/specs/ast/spec.md +34 -0
- package/docs/specs/compaction-recall/spec.md +46 -0
- package/docs/specs/ctx/spec.md +107 -0
- package/docs/specs/dsh/spec.md +47 -0
- package/docs/specs/dvc/spec.md +87 -0
- package/docs/specs/escalation-guidance/spec.md +44 -0
- package/docs/specs/fs-scheme-resolution/spec.md +37 -0
- package/docs/specs/hash-edit/spec.md +41 -0
- package/docs/specs/http-read/spec.md +73 -0
- package/docs/specs/kernel-provisioning/spec.md +53 -0
- package/docs/specs/lsp/spec.md +121 -0
- package/docs/specs/mobile-layout/spec.md +108 -0
- package/docs/specs/model-failover/spec.md +20 -0
- package/docs/specs/preact-ui-shell/spec.md +22 -0
- package/docs/specs/repl-dispatch-resilience/spec.md +21 -0
- package/docs/specs/skill/spec.md +58 -0
- package/docs/specs/tool-surface/spec.md +222 -0
- package/docs/specs/url-schema/spec.md +148 -0
- package/docs/specs/web-trust-fence/spec.md +43 -0
- package/dsh-docs/AGENTS.md +75 -0
- package/dsh-docs/agent-lifecycle.md +84 -0
- package/dsh-docs/agent-lifecycle.zh.md +86 -0
- package/dsh-docs/api-gateway.md +164 -0
- package/dsh-docs/api-gateway.zh.md +164 -0
- package/dsh-docs/architecture.md +150 -0
- package/dsh-docs/architecture.zh.md +154 -0
- package/dsh-docs/capability-seams.md +543 -0
- package/dsh-docs/capability-seams.zh.md +545 -0
- package/dsh-docs/config-catalog.md +3473 -0
- package/dsh-docs/config-catalog.zh.md +3474 -0
- package/dsh-docs/cookbook/adding-a-package.md +117 -0
- package/dsh-docs/cookbook/adding-a-package.zh.md +119 -0
- package/dsh-docs/cookbook/adding-a-remote-api.md +197 -0
- package/dsh-docs/cookbook/adding-a-remote-api.zh.md +197 -0
- package/dsh-docs/cookbook/adding-a-settings-card.md +102 -0
- package/dsh-docs/cookbook/adding-a-settings-card.zh.md +102 -0
- package/dsh-docs/cookbook/adding-a-tool.md +101 -0
- package/dsh-docs/cookbook/adding-a-tool.zh.md +103 -0
- package/dsh-docs/cookbook/adding-a-vendored-package.md +59 -0
- package/dsh-docs/cookbook/adding-a-vendored-package.zh.md +59 -0
- package/dsh-docs/cookbook/adding-an-llm-adapter.md +43 -0
- package/dsh-docs/cookbook/adding-an-llm-adapter.zh.md +43 -0
- package/dsh-docs/cookbook/extension-cookbook.md +132 -0
- package/dsh-docs/cookbook/extension-cookbook.zh.md +136 -0
- package/dsh-docs/cookbook/maintaining-dsh-code-review.md +64 -0
- package/dsh-docs/cookbook/maintaining-dsh-code-review.zh.md +64 -0
- package/dsh-docs/cookbook/responding-to-pr-review-on-a-stack.md +32 -0
- package/dsh-docs/cookbook/responding-to-pr-review-on-a-stack.zh.md +32 -0
- package/dsh-docs/cordis-api/context.md +364 -0
- package/dsh-docs/cordis-api/context.zh.md +366 -0
- package/dsh-docs/cordis-api/events.md +207 -0
- package/dsh-docs/cordis-api/events.zh.md +209 -0
- package/dsh-docs/cordis-api/fiber.md +375 -0
- package/dsh-docs/cordis-api/fiber.zh.md +377 -0
- package/dsh-docs/cordis-api/inherited.md +39 -0
- package/dsh-docs/cordis-api/registry.md +152 -0
- package/dsh-docs/cordis-api/registry.zh.md +154 -0
- package/dsh-docs/cordis-api/service.md +102 -0
- package/dsh-docs/cordis-api/service.zh.md +104 -0
- package/dsh-docs/cordis-primer.md +45 -0
- package/dsh-docs/cordis-primer.zh.md +51 -0
- package/dsh-docs/cordis-tutorial/01-first-plugin.md +95 -0
- package/dsh-docs/cordis-tutorial/01-first-plugin.zh.md +95 -0
- package/dsh-docs/cordis-tutorial/02-lifecycle-and-effects.md +98 -0
- package/dsh-docs/cordis-tutorial/02-lifecycle-and-effects.zh.md +98 -0
- package/dsh-docs/cordis-tutorial/03-services.md +98 -0
- package/dsh-docs/cordis-tutorial/03-services.zh.md +98 -0
- package/dsh-docs/cordis-tutorial/04-events.md +144 -0
- package/dsh-docs/cordis-tutorial/04-events.zh.md +144 -0
- package/dsh-docs/cordis-tutorial/05-config.md +84 -0
- package/dsh-docs/cordis-tutorial/05-config.zh.md +84 -0
- package/dsh-docs/cordis-tutorial/06-composition-and-hmr.md +113 -0
- package/dsh-docs/cordis-tutorial/06-composition-and-hmr.zh.md +113 -0
- package/dsh-docs/cordis-tutorial/07-into-the-harness.md +108 -0
- package/dsh-docs/cordis-tutorial/07-into-the-harness.zh.md +108 -0
- package/dsh-docs/cordis-tutorial/index.md +60 -0
- package/dsh-docs/cordis-tutorial/index.zh.md +62 -0
- package/dsh-docs/deepseek-llm-api-wire-extensions.md +163 -0
- package/dsh-docs/deepseek-llm-api-wire-extensions.zh.md +163 -0
- package/dsh-docs/defensive-patterns.md +33 -0
- package/dsh-docs/defensive-patterns.zh.md +35 -0
- package/dsh-docs/development.md +167 -0
- package/dsh-docs/development.zh.md +173 -0
- package/dsh-docs/event-producer-consumer.md +86 -0
- package/dsh-docs/event-producer-consumer.zh.md +88 -0
- package/dsh-docs/glossary.md +45 -0
- package/dsh-docs/glossary.zh.md +45 -0
- package/dsh-docs/graph-atlas.md +22 -0
- package/dsh-docs/graph-atlas.zh.md +24 -0
- package/dsh-docs/i18n/README.md +60 -0
- package/dsh-docs/i18n/README.zh.md +62 -0
- package/dsh-docs/i18n/style-samples.md +87 -0
- package/dsh-docs/i18n/terminology.md +214 -0
- package/dsh-docs/i18n/translation-prompt.md +263 -0
- package/dsh-docs/i18n/translation-rules.md +69 -0
- package/dsh-docs/i18n/translation-rules.zh.md +69 -0
- package/dsh-docs/module-graph.md +1411 -0
- package/dsh-docs/module-graph.zh.md +1413 -0
- package/dsh-docs/persistence-catalog.md +1075 -0
- package/dsh-docs/persistence-catalog.zh.md +1077 -0
- package/dsh-docs/postmortem/0001-acp-default-export-drops-inject.md +113 -0
- package/dsh-docs/postmortem/0001-acp-default-export-drops-inject.zh.md +113 -0
- package/dsh-docs/postmortem/0002-js-expression-disabled-filesystem-tools.md +47 -0
- package/dsh-docs/postmortem/0002-js-expression-disabled-filesystem-tools.zh.md +47 -0
- package/dsh-docs/postmortem/0003-web-agent-gui-feedback-loop.md +53 -0
- package/dsh-docs/postmortem/0003-web-agent-gui-feedback-loop.zh.md +53 -0
- package/dsh-docs/postmortem/0004-landlock-partial-notice-misclassified-child-failures.md +55 -0
- package/dsh-docs/postmortem/0004-landlock-partial-notice-misclassified-child-failures.zh.md +55 -0
- package/dsh-docs/postmortem/README.md +18 -0
- package/dsh-docs/postmortem/README.zh.md +18 -0
- package/dsh-docs/rescope.md +53 -0
- package/dsh-docs/rescope.zh.md +53 -0
- package/dsh-docs/subsystems/README.md +61 -0
- package/dsh-docs/subsystems/README.zh.md +61 -0
- package/dsh-docs/subsystems/agent-team.md +207 -0
- package/dsh-docs/subsystems/agent-team.zh.md +207 -0
- package/dsh-docs/subsystems/approval.md +170 -0
- package/dsh-docs/subsystems/approval.zh.md +170 -0
- package/dsh-docs/subsystems/attachment.md +351 -0
- package/dsh-docs/subsystems/attachment.zh.md +351 -0
- package/dsh-docs/subsystems/client-modules.md +168 -0
- package/dsh-docs/subsystems/client-modules.zh.md +168 -0
- package/dsh-docs/subsystems/code-runtime.md +195 -0
- package/dsh-docs/subsystems/code-runtime.zh.md +195 -0
- package/dsh-docs/subsystems/commands.md +219 -0
- package/dsh-docs/subsystems/commands.zh.md +219 -0
- package/dsh-docs/subsystems/compaction.md +238 -0
- package/dsh-docs/subsystems/compaction.zh.md +238 -0
- package/dsh-docs/subsystems/conversation.md +258 -0
- package/dsh-docs/subsystems/conversation.zh.md +258 -0
- package/dsh-docs/subsystems/core.md +1209 -0
- package/dsh-docs/subsystems/core.zh.md +1219 -0
- package/dsh-docs/subsystems/credentials.md +329 -0
- package/dsh-docs/subsystems/credentials.zh.md +329 -0
- package/dsh-docs/subsystems/extensions.md +382 -0
- package/dsh-docs/subsystems/extensions.zh.md +382 -0
- package/dsh-docs/subsystems/feedback.md +266 -0
- package/dsh-docs/subsystems/feedback.zh.md +266 -0
- package/dsh-docs/subsystems/filesystem.md +505 -0
- package/dsh-docs/subsystems/filesystem.zh.md +505 -0
- package/dsh-docs/subsystems/goal.md +277 -0
- package/dsh-docs/subsystems/goal.zh.md +277 -0
- package/dsh-docs/subsystems/invariants.md +88 -0
- package/dsh-docs/subsystems/invariants.zh.md +88 -0
- package/dsh-docs/subsystems/jobs.md +290 -0
- package/dsh-docs/subsystems/jobs.zh.md +290 -0
- package/dsh-docs/subsystems/llm-streaming.md +1080 -0
- package/dsh-docs/subsystems/llm-streaming.zh.md +1086 -0
- package/dsh-docs/subsystems/lsp.md +202 -0
- package/dsh-docs/subsystems/lsp.zh.md +202 -0
- package/dsh-docs/subsystems/permission-presets.md +131 -0
- package/dsh-docs/subsystems/permission-presets.zh.md +131 -0
- package/dsh-docs/subsystems/persistence.md +395 -0
- package/dsh-docs/subsystems/persistence.zh.md +395 -0
- package/dsh-docs/subsystems/plan.md +87 -0
- package/dsh-docs/subsystems/plan.zh.md +87 -0
- package/dsh-docs/subsystems/sandbox.md +220 -0
- package/dsh-docs/subsystems/sandbox.zh.md +220 -0
- package/dsh-docs/subsystems/schedule.md +192 -0
- package/dsh-docs/subsystems/schedule.zh.md +192 -0
- package/dsh-docs/subsystems/scope.md +59 -0
- package/dsh-docs/subsystems/scope.zh.md +59 -0
- package/dsh-docs/subsystems/session-projection.md +354 -0
- package/dsh-docs/subsystems/session-projection.zh.md +354 -0
- package/dsh-docs/subsystems/session-query.md +509 -0
- package/dsh-docs/subsystems/session-query.zh.md +509 -0
- package/dsh-docs/subsystems/session-reference.md +219 -0
- package/dsh-docs/subsystems/session-reference.zh.md +219 -0
- package/dsh-docs/subsystems/session-telemetry.md +194 -0
- package/dsh-docs/subsystems/session-telemetry.zh.md +194 -0
- package/dsh-docs/subsystems/session-title.md +204 -0
- package/dsh-docs/subsystems/session-title.zh.md +204 -0
- package/dsh-docs/subsystems/session.md +1155 -0
- package/dsh-docs/subsystems/session.zh.md +1159 -0
- package/dsh-docs/subsystems/settings.md +405 -0
- package/dsh-docs/subsystems/settings.zh.md +405 -0
- package/dsh-docs/subsystems/shell.md +303 -0
- package/dsh-docs/subsystems/shell.zh.md +303 -0
- package/dsh-docs/subsystems/skills.md +354 -0
- package/dsh-docs/subsystems/skills.zh.md +354 -0
- package/dsh-docs/subsystems/slots.md +175 -0
- package/dsh-docs/subsystems/slots.zh.md +175 -0
- package/dsh-docs/subsystems/spill.md +117 -0
- package/dsh-docs/subsystems/spill.zh.md +117 -0
- package/dsh-docs/subsystems/storage.md +260 -0
- package/dsh-docs/subsystems/storage.zh.md +260 -0
- package/dsh-docs/subsystems/subagent.md +766 -0
- package/dsh-docs/subsystems/subagent.zh.md +770 -0
- package/dsh-docs/subsystems/subprocess.md +324 -0
- package/dsh-docs/subsystems/subprocess.zh.md +324 -0
- package/dsh-docs/subsystems/system-prompt.md +220 -0
- package/dsh-docs/subsystems/system-prompt.zh.md +220 -0
- package/dsh-docs/subsystems/terminal.md +184 -0
- package/dsh-docs/subsystems/terminal.zh.md +184 -0
- package/dsh-docs/subsystems/todo.md +32 -0
- package/dsh-docs/subsystems/todo.zh.md +32 -0
- package/dsh-docs/subsystems/token-meter.md +105 -0
- package/dsh-docs/subsystems/token-meter.zh.md +105 -0
- package/dsh-docs/subsystems/tools.md +720 -0
- package/dsh-docs/subsystems/tools.zh.md +720 -0
- package/dsh-docs/subsystems/typert.md +343 -0
- package/dsh-docs/subsystems/typert.zh.md +343 -0
- package/dsh-docs/subsystems/user-questions.md +178 -0
- package/dsh-docs/subsystems/user-questions.zh.md +178 -0
- package/dsh-docs/subsystems/web-client.md +95 -0
- package/dsh-docs/subsystems/web-client.zh.md +95 -0
- package/dsh-docs/subsystems/web-server.md +154 -0
- package/dsh-docs/subsystems/web-server.zh.md +154 -0
- package/dsh-docs/subsystems/web.md +206 -0
- package/dsh-docs/subsystems/web.zh.md +206 -0
- package/dsh-docs/subsystems/webhook.md +70 -0
- package/dsh-docs/subsystems/webhook.zh.md +70 -0
- package/dsh-docs/subsystems/workflow.md +278 -0
- package/dsh-docs/subsystems/workflow.zh.md +278 -0
- package/dsh-docs/subsystems/workspace.md +321 -0
- package/dsh-docs/subsystems/workspace.zh.md +321 -0
- package/dsh-docs/testing.md +54 -0
- package/dsh-docs/testing.zh.md +54 -0
- package/dsh-docs/tool-catalog.md +2225 -0
- package/dsh-docs/tool-catalog.zh.md +2233 -0
- package/dsh-docs/tool-execution-pipeline.md +62 -0
- package/dsh-docs/tool-execution-pipeline.zh.md +64 -0
- package/dsh-docs/user/develop/basic/config.md +106 -0
- package/dsh-docs/user/develop/basic/config.zh.md +106 -0
- package/dsh-docs/user/develop/basic/index.md +144 -0
- package/dsh-docs/user/develop/basic/index.zh.md +144 -0
- package/dsh-docs/user/develop/basic/publish.md +183 -0
- package/dsh-docs/user/develop/basic/publish.zh.md +183 -0
- package/dsh-docs/user/develop/basic/tool.md +52 -0
- package/dsh-docs/user/develop/basic/tool.zh.md +52 -0
- package/dsh-docs/user/develop/framework/events.md +143 -0
- package/dsh-docs/user/develop/framework/events.zh.md +143 -0
- package/dsh-docs/user/develop/framework/index.md +137 -0
- package/dsh-docs/user/develop/framework/index.zh.md +137 -0
- package/dsh-docs/user/develop/framework/service.md +148 -0
- package/dsh-docs/user/develop/framework/service.zh.md +150 -0
- package/dsh-docs/user/develop/practice/dynamic-cordis.md +15 -0
- package/dsh-docs/user/develop/practice/dynamic-cordis.zh.md +15 -0
- package/dsh-docs/user/develop/practice/index.md +155 -0
- package/dsh-docs/user/develop/practice/index.zh.md +155 -0
- package/dsh-docs/user/develop/practice/llm-adapter.md +189 -0
- package/dsh-docs/user/develop/practice/llm-adapter.zh.md +189 -0
- package/dsh-docs/user/guide/github-review.md +102 -0
- package/dsh-docs/user/guide/github-review.zh.md +102 -0
- package/dsh-docs/user/guide/index.md +30 -0
- package/dsh-docs/user/guide/index.zh.md +30 -0
- package/dsh-docs/user/guide/mcp-memory.md +101 -0
- package/dsh-docs/user/guide/mcp-memory.zh.md +101 -0
- package/dsh-docs/user/guide/network-proxy.md +85 -0
- package/dsh-docs/user/guide/network-proxy.zh.md +85 -0
- package/dsh-docs/user/guide/providers.md +190 -0
- package/dsh-docs/user/guide/providers.zh.md +190 -0
- package/dsh-docs/user/guide/python-sdk.md +150 -0
- package/dsh-docs/user/guide/python-sdk.zh.md +150 -0
- package/dsh-docs/user/guide/schedule.md +21 -0
- package/dsh-docs/user/guide/schedule.zh.md +21 -0
- package/dsh-docs/user/index.md +11 -0
- package/dsh-docs/user/index.zh.md +11 -0
- package/dsh-docs/web-styling.md +29 -0
- package/dsh-docs/web-styling.zh.md +29 -0
- package/lib/client/index.js +268 -38
- package/lib/fs-aware/sandbox-plugin.js +1 -1
- package/lib/index.js +1112 -1261
- package/lib/lsp-server-registry-B8DNonhS.js +3 -0
- package/lib/lsp-server-registry-BexQagaK.js +943 -0
- package/lib/{wrap-DC8O3SYz.js → wrap-JFjcWwZf.js} +42 -16
- package/package.json +2 -1
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# 事件系统
|
|
2
|
+
|
|
3
|
+
[English](events.md) | 中文
|
|
4
|
+
|
|
5
|
+
事件是 Cordis 插件间通信的核心机制。Harness 大量使用事件来实现松耦合的扩展点。
|
|
6
|
+
|
|
7
|
+
## 基本用法
|
|
8
|
+
|
|
9
|
+
### 监听事件
|
|
10
|
+
|
|
11
|
+
```ts ignore-check
|
|
12
|
+
ctx.on('event-name', (payload) => {
|
|
13
|
+
// Handle the event.
|
|
14
|
+
})
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
### 触发事件
|
|
18
|
+
|
|
19
|
+
```ts ignore-check
|
|
20
|
+
ctx.emit('event-name', payload)
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## 事件模式
|
|
24
|
+
|
|
25
|
+
Cordis 提供多种事件模式,适用于不同的交互契约:
|
|
26
|
+
|
|
27
|
+
### emit — 广播
|
|
28
|
+
|
|
29
|
+
所有监听器同步执行,返回值会被忽略:
|
|
30
|
+
|
|
31
|
+
```ts ignore-check
|
|
32
|
+
// Emit
|
|
33
|
+
ctx.emit('my-plugin/ready', { id: 'worker-1' })
|
|
34
|
+
|
|
35
|
+
// Listen
|
|
36
|
+
ctx.on('my-plugin/ready', ({ id }) => {
|
|
37
|
+
console.log(`${id} is ready`)
|
|
38
|
+
})
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### bail — 短路
|
|
42
|
+
|
|
43
|
+
监听器按顺序运行,第一个不是 `null`、`false` 或 `undefined` 的返回值会成为最终结果:
|
|
44
|
+
|
|
45
|
+
```ts ignore-check
|
|
46
|
+
// Dispatch
|
|
47
|
+
const result = ctx.bail('some-check', input)
|
|
48
|
+
|
|
49
|
+
// Listen: a returned value stops later listeners.
|
|
50
|
+
ctx.on('some-check', (input) => {
|
|
51
|
+
if (shouldBlock(input)) return 'blocked'
|
|
52
|
+
// Return null, false, or undefined to continue to the next listener.
|
|
53
|
+
})
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### serial — 顺序执行
|
|
57
|
+
|
|
58
|
+
监听器按注册顺序依次执行,并等待异步结果;第一个不是 `null`、`false` 或 `undefined` 的返回值会终止后续执行:
|
|
59
|
+
|
|
60
|
+
```ts ignore-check
|
|
61
|
+
await ctx.serial('setup-phase', context)
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### waterfall(瀑布式事件)— 流水线
|
|
65
|
+
|
|
66
|
+
每个监听器可以包装下游返回值,形成处理链。**必须调用 `next()` 传递给下游**,不调用即会短路流水线:
|
|
67
|
+
|
|
68
|
+
```ts ignore-check
|
|
69
|
+
// Dispatch
|
|
70
|
+
const output = await ctx.waterfall('my-plugin/transform', input, async () => input)
|
|
71
|
+
|
|
72
|
+
// Listen: next() is mandatory.
|
|
73
|
+
ctx.on('my-plugin/transform', async (_input, next) => {
|
|
74
|
+
const downstream = await next()
|
|
75
|
+
return downstream.trim()
|
|
76
|
+
})
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
::: warning
|
|
80
|
+
waterfall 监听器**必须调用 `next()`**。不调用 `next` 会短路整个流水线,这是故意为之的设计——用于实现拦截/网关逻辑。
|
|
81
|
+
:::
|
|
82
|
+
|
|
83
|
+
## 类型安全的事件
|
|
84
|
+
|
|
85
|
+
Harness 使用 TypeScript 声明合并来为事件提供类型安全:
|
|
86
|
+
|
|
87
|
+
```ts
|
|
88
|
+
import '@deepseek-ai/cordis'
|
|
89
|
+
|
|
90
|
+
declare module '@deepseek-ai/cordis' {
|
|
91
|
+
interface Events {
|
|
92
|
+
'my-plugin/ready': (payload: { id: string }) => void
|
|
93
|
+
'my-plugin/check': (input: string) => boolean | undefined
|
|
94
|
+
'my-plugin/transform': (input: string, next: () => Promise<string>) => Promise<string>
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// ctx.on('my-plugin/ready', ...) and ctx.emit('my-plugin/ready', ...)
|
|
99
|
+
// are now inferred correctly.
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Cordis 事件与会话记录
|
|
103
|
+
|
|
104
|
+
Harness 的 Cordis 事件遵循 `namespace/action` 命名,例如 `agent/pre-step`、`agent/request`、`agent/request-error`、`tools/result` 和 `session/event`。完整签名与触发模式见[子系统页面](../../../subsystems/core.zh.md)上生成的 `cordis-surface` 区块。
|
|
105
|
+
|
|
106
|
+
`turn/*`、`step/*`、`tool/call`、`tool/result` 和 `compaction/*` 是持久化的会话事件类型,不是同名 Cordis 事件。需要观察它们时,监听 `session/event` 并检查 `event.type`。
|
|
107
|
+
|
|
108
|
+
## 事件监听器也是效果
|
|
109
|
+
|
|
110
|
+
通过 `ctx.on()` 注册的监听器会在插件卸载时自动移除:
|
|
111
|
+
|
|
112
|
+
```ts ignore-check
|
|
113
|
+
export function apply(ctx: Context) {
|
|
114
|
+
// This listener is removed when the plugin disposes.
|
|
115
|
+
ctx.on('tools/result', handler)
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## 示例:日志插件
|
|
120
|
+
|
|
121
|
+
这个插件记录工具调用和工具结果:
|
|
122
|
+
|
|
123
|
+
```ts
|
|
124
|
+
import type { Context } from '@deepseek-ai/cordis'
|
|
125
|
+
import '@deepseek-ai/dsh-tools'
|
|
126
|
+
|
|
127
|
+
export const name = 'tool-logger'
|
|
128
|
+
|
|
129
|
+
export function apply(ctx: Context) {
|
|
130
|
+
ctx.on('tools/result', (exec, result) => {
|
|
131
|
+
console.log(`[tool] ${exec.name}(${JSON.stringify(exec.arguments)})`)
|
|
132
|
+
const text = result.content
|
|
133
|
+
.map(block => block.type === 'text' ? block.text : '')
|
|
134
|
+
.join('')
|
|
135
|
+
console.log(`[tool result] ${text.slice(0, 100)}`)
|
|
136
|
+
})
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## 下一步
|
|
141
|
+
|
|
142
|
+
- [能力分层](../practice/index.zh.md) — 了解能力接口中的事件
|
|
143
|
+
- [LLM(大语言模型)适配器](../practice/llm-adapter.zh.md) — 实现一个完整的 LLM 后端
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# Plugins and lifecycle
|
|
2
|
+
|
|
3
|
+
English | [中文](index.zh.md)
|
|
4
|
+
|
|
5
|
+
This page describes the Cordis plugin model and lifecycle state machine.
|
|
6
|
+
|
|
7
|
+
## Fiber state machine
|
|
8
|
+
|
|
9
|
+
Every loaded plugin owns a **Fiber** scope with the following states:
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
PENDING → LOADING → ACTIVE
|
|
13
|
+
↘ FAILED
|
|
14
|
+
ACTIVE → UNLOADING → DISPOSED
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
| State | Meaning |
|
|
18
|
+
|------|------|
|
|
19
|
+
| PENDING | Declared, but required dependencies are not ready |
|
|
20
|
+
| LOADING | Dependencies are ready and `apply` is running |
|
|
21
|
+
| ACTIVE | The plugin is running |
|
|
22
|
+
| FAILED | `apply` threw an error |
|
|
23
|
+
| UNLOADING | The plugin is unloading and disposing resources |
|
|
24
|
+
| DISPOSED | The plugin is fully unloaded |
|
|
25
|
+
|
|
26
|
+
## Dependency-driven loading
|
|
27
|
+
|
|
28
|
+
A plugin with `inject` waits for every required service before loading:
|
|
29
|
+
|
|
30
|
+
```ts ignore-check
|
|
31
|
+
export const inject = ['tools', 'llm']
|
|
32
|
+
|
|
33
|
+
export function apply(ctx: Context) {
|
|
34
|
+
// ctx.tools and ctx.llm are ready here.
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
If a required service disappears, for example during provider replacement, the plugin unloads automatically (ACTIVE → DISPOSED) and loads again when the service returns.
|
|
39
|
+
|
|
40
|
+
## Automatic cleanup
|
|
41
|
+
|
|
42
|
+
Every registration made through `ctx` is undone when the plugin unloads:
|
|
43
|
+
|
|
44
|
+
```ts ignore-check
|
|
45
|
+
export function apply(ctx: Context) {
|
|
46
|
+
// Event listener: removed automatically on unload.
|
|
47
|
+
ctx.on('some-event', handler)
|
|
48
|
+
|
|
49
|
+
// Custom resource: the returned disposer runs on unload.
|
|
50
|
+
ctx.effect(() => {
|
|
51
|
+
const connection = createConnection()
|
|
52
|
+
return () => connection.close()
|
|
53
|
+
})
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
The framework tracks and disposes all of these operations:
|
|
58
|
+
- `ctx.on(event, handler)` — event listener
|
|
59
|
+
- `ctx.tools.register(tool)` — tool registration
|
|
60
|
+
- `ctx.llm.registerAdapter(names, adapter)` — LLM adapter registration
|
|
61
|
+
- `ctx.effect(() => cleanup)` — custom resource
|
|
62
|
+
|
|
63
|
+
During unload, disposer invocation starts in reverse registration order, but multiple async disposers run concurrently and have no serial completion guarantee. Put order-dependent cleanup in one disposer returned from a single `ctx.effect()` and await its steps serially there.
|
|
64
|
+
|
|
65
|
+
## Nested contexts
|
|
66
|
+
|
|
67
|
+
`ctx.plugin()` creates a child Fiber that inherits the parent context but has an independent lifecycle:
|
|
68
|
+
|
|
69
|
+
```ts ignore-check
|
|
70
|
+
export function apply(ctx: Context) {
|
|
71
|
+
// Register a child plugin.
|
|
72
|
+
ctx.plugin(childPlugin)
|
|
73
|
+
|
|
74
|
+
// The child has its own Fiber and unloads with its parent.
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Dispose semantics
|
|
79
|
+
|
|
80
|
+
To stop a plugin instance early:
|
|
81
|
+
|
|
82
|
+
```ts
|
|
83
|
+
import type { Context } from '@deepseek-ai/cordis'
|
|
84
|
+
|
|
85
|
+
declare const ctx: Context
|
|
86
|
+
declare function myPlugin(ctx: Context): void
|
|
87
|
+
|
|
88
|
+
const fiber = ctx.plugin(myPlugin)
|
|
89
|
+
|
|
90
|
+
// Dispose it manually later.
|
|
91
|
+
await fiber.dispose()
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
`dispose` guarantees:
|
|
95
|
+
1. All registrations owned by the plugin are removed.
|
|
96
|
+
2. Child plugins are recursively unloaded.
|
|
97
|
+
3. The returned promise resolves after all asynchronous cleanup finishes.
|
|
98
|
+
|
|
99
|
+
## Hot replacement (HMR)
|
|
100
|
+
|
|
101
|
+
With `@deepseek-ai/cordis-plugin-hmr` loaded from `cordis.yml`, editing a plugin source file triggers:
|
|
102
|
+
|
|
103
|
+
1. Unload the old plugin and clean up its registrations.
|
|
104
|
+
2. Load the new code.
|
|
105
|
+
3. Run the new `apply`.
|
|
106
|
+
|
|
107
|
+
Because plugin registrations clean themselves up, hot replacement does not retain registrations from the old instance.
|
|
108
|
+
|
|
109
|
+
## Example lifecycle
|
|
110
|
+
|
|
111
|
+
```ts ignore-check
|
|
112
|
+
export function apply(ctx: Context) {
|
|
113
|
+
console.log('plugin loading')
|
|
114
|
+
|
|
115
|
+
ctx.effect(() => {
|
|
116
|
+
console.log('effect registered')
|
|
117
|
+
return () => console.log('effect cleaned up')
|
|
118
|
+
})
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Loading prints:
|
|
123
|
+
```
|
|
124
|
+
plugin loading
|
|
125
|
+
effect registered
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Unloading prints:
|
|
129
|
+
```
|
|
130
|
+
effect cleaned up
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## Next steps
|
|
134
|
+
|
|
135
|
+
- [Services and dependencies](./service.md) — expose a capability to other plugins
|
|
136
|
+
- [Event system](./events.md) — communicate between plugins
|
|
137
|
+
- [Cordis tutorial](../../../cordis-tutorial/index.md) — the same lifecycle, services, and events built step by step against the Cordis runtime
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# 插件与生命周期
|
|
2
|
+
|
|
3
|
+
[English](index.md) | 中文
|
|
4
|
+
|
|
5
|
+
本页介绍 Cordis 插件模型和生命周期状态机。
|
|
6
|
+
|
|
7
|
+
## Fiber 状态机
|
|
8
|
+
|
|
9
|
+
每个被加载的插件都拥有一个 **Fiber** 作用域,其状态如下:
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
PENDING → LOADING → ACTIVE
|
|
13
|
+
↘ FAILED
|
|
14
|
+
ACTIVE → UNLOADING → DISPOSED
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
| 状态 | 含义 |
|
|
18
|
+
|------|------|
|
|
19
|
+
| PENDING | 已声明,但所需依赖未就绪 |
|
|
20
|
+
| LOADING | 依赖就绪,正在执行 `apply` |
|
|
21
|
+
| ACTIVE | 插件运行中 |
|
|
22
|
+
| FAILED | `apply` 抛出异常 |
|
|
23
|
+
| UNLOADING | 插件正在卸载并释放资源 |
|
|
24
|
+
| DISPOSED | 已完全卸载 |
|
|
25
|
+
|
|
26
|
+
## 依赖驱动的加载
|
|
27
|
+
|
|
28
|
+
声明了 `inject` 的插件会等待所有必需服务就绪:
|
|
29
|
+
|
|
30
|
+
```ts ignore-check
|
|
31
|
+
export const inject = ['tools', 'llm']
|
|
32
|
+
|
|
33
|
+
export function apply(ctx: Context) {
|
|
34
|
+
// ctx.tools and ctx.llm are ready here.
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
如果依赖的服务消失(例如提供方被替换时),插件会被自动卸载(ACTIVE → DISPOSED),待服务恢复后重新加载。
|
|
39
|
+
|
|
40
|
+
## 自动清理机制
|
|
41
|
+
|
|
42
|
+
通过 `ctx` 做的任何注册,在插件卸载时都会自动撤销:
|
|
43
|
+
|
|
44
|
+
```ts ignore-check
|
|
45
|
+
export function apply(ctx: Context) {
|
|
46
|
+
// Event listener: removed automatically on unload.
|
|
47
|
+
ctx.on('some-event', handler)
|
|
48
|
+
|
|
49
|
+
// Custom resource: the returned disposer runs on unload.
|
|
50
|
+
ctx.effect(() => {
|
|
51
|
+
const connection = createConnection()
|
|
52
|
+
return () => connection.close()
|
|
53
|
+
})
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
以下操作都会被自动追踪和清理:
|
|
58
|
+
- `ctx.on(event, handler)` — 事件监听
|
|
59
|
+
- `ctx.tools.register(tool)` — 工具注册
|
|
60
|
+
- `ctx.llm.registerAdapter(names, adapter)` — LLM(大语言模型)适配器注册
|
|
61
|
+
- `ctx.effect(() => cleanup)` — 自定义资源
|
|
62
|
+
|
|
63
|
+
插件卸载时,处置器按注册顺序的逆序开始调用,但多个异步处置器会并发执行,不保证逐个完成。存在顺序依赖的清理步骤必须放进同一个 `ctx.effect()` 返回的处置器中,由该处置器负责串行等待。
|
|
64
|
+
|
|
65
|
+
## 嵌套上下文
|
|
66
|
+
|
|
67
|
+
`ctx.plugin()` 创建子 Fiber,它继承父上下文但有独立的生命周期:
|
|
68
|
+
|
|
69
|
+
```ts ignore-check
|
|
70
|
+
export function apply(ctx: Context) {
|
|
71
|
+
// Register a child plugin.
|
|
72
|
+
ctx.plugin(childPlugin)
|
|
73
|
+
|
|
74
|
+
// The child has its own Fiber and unloads with its parent.
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## dispose(资源释放)语义
|
|
79
|
+
|
|
80
|
+
当你需要提前终止一个插件实例:
|
|
81
|
+
|
|
82
|
+
```ts
|
|
83
|
+
import type { Context } from '@deepseek-ai/cordis'
|
|
84
|
+
|
|
85
|
+
declare const ctx: Context
|
|
86
|
+
declare function myPlugin(ctx: Context): void
|
|
87
|
+
|
|
88
|
+
const fiber = ctx.plugin(myPlugin)
|
|
89
|
+
|
|
90
|
+
// Dispose it manually later.
|
|
91
|
+
await fiber.dispose()
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
`dispose` 保证:
|
|
95
|
+
1. 该插件拥有的所有注册均被移除
|
|
96
|
+
2. 它的子插件也被递归卸载
|
|
97
|
+
3. 返回的 Promise 会在所有异步清理完成后兑现
|
|
98
|
+
|
|
99
|
+
## HMR(热模块替换)
|
|
100
|
+
|
|
101
|
+
通过 `cordis.yml` 加载 `@deepseek-ai/cordis-plugin-hmr` 后,修改插件源文件会触发:
|
|
102
|
+
|
|
103
|
+
1. 卸载旧插件(清理所有注册)
|
|
104
|
+
2. 重新加载新代码
|
|
105
|
+
3. 执行新的 `apply`
|
|
106
|
+
|
|
107
|
+
因为插件注册会被自动清理,所以热替换不会保留旧实例的注册。
|
|
108
|
+
|
|
109
|
+
## 生命周期示例
|
|
110
|
+
|
|
111
|
+
```ts ignore-check
|
|
112
|
+
export function apply(ctx: Context) {
|
|
113
|
+
console.log('plugin loading')
|
|
114
|
+
|
|
115
|
+
ctx.effect(() => {
|
|
116
|
+
console.log('effect registered')
|
|
117
|
+
return () => console.log('effect cleaned up')
|
|
118
|
+
})
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
加载时输出:
|
|
123
|
+
```
|
|
124
|
+
plugin loading
|
|
125
|
+
effect registered
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
卸载时输出:
|
|
129
|
+
```
|
|
130
|
+
effect cleaned up
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## 下一步
|
|
134
|
+
|
|
135
|
+
- [服务与依赖](./service.zh.md) — 让插件向其他插件提供能力
|
|
136
|
+
- [事件系统](./events.zh.md) — 在插件之间通信
|
|
137
|
+
- [Cordis 框架教程](../../../cordis-tutorial/index.zh.md) — 在 Cordis 运行时上逐步搭出同一套生命周期、服务与事件
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# Services and dependencies
|
|
2
|
+
|
|
3
|
+
English | [中文](service.zh.md)
|
|
4
|
+
|
|
5
|
+
A service is a capability one plugin exposes to other plugins. `inject` declares the services a plugin requires.
|
|
6
|
+
|
|
7
|
+
## What is a service?
|
|
8
|
+
|
|
9
|
+
In Harness, `tools`, `llm`, and `agents` are services. Each is a named capability mounted on `ctx`:
|
|
10
|
+
|
|
11
|
+
```ts ignore-check
|
|
12
|
+
ctx.tools // ToolRuntime service
|
|
13
|
+
ctx.llm // LLM service
|
|
14
|
+
ctx.agents // Agent service
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Any plugin can provide a service for other plugins to consume.
|
|
18
|
+
|
|
19
|
+
## Consume a service
|
|
20
|
+
|
|
21
|
+
Declare `inject` to use an existing service:
|
|
22
|
+
|
|
23
|
+
```ts ignore-check
|
|
24
|
+
export const inject = ['tools']
|
|
25
|
+
|
|
26
|
+
export function apply(ctx: Context) {
|
|
27
|
+
// ctx.tools exists and is ready here.
|
|
28
|
+
ctx.tools.register(/* ... */)
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
When `apply` runs, every service declared by `inject` is ready. If a service is not ready, the plugin waits instead of running.
|
|
33
|
+
|
|
34
|
+
## Provide a service
|
|
35
|
+
|
|
36
|
+
### Extend Service
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
import { Service, type Context } from '@deepseek-ai/cordis'
|
|
40
|
+
|
|
41
|
+
export default class MetricsService extends Service {
|
|
42
|
+
static inject = ['llm'] // A service may depend on other services.
|
|
43
|
+
|
|
44
|
+
constructor(ctx: Context) {
|
|
45
|
+
super(ctx, 'metrics') // 'metrics' is the service name.
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Public service method.
|
|
49
|
+
record(event: string, value: number) {
|
|
50
|
+
// ...
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
After loading this plugin, consumers access the service as `ctx.metrics`:
|
|
56
|
+
|
|
57
|
+
```ts ignore-check
|
|
58
|
+
export const inject = ['metrics']
|
|
59
|
+
|
|
60
|
+
export function apply(ctx: Context) {
|
|
61
|
+
ctx.metrics.record('tool_call', 1)
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Declare its type
|
|
66
|
+
|
|
67
|
+
Use TypeScript declaration merging to type `ctx.metrics`:
|
|
68
|
+
|
|
69
|
+
```ts
|
|
70
|
+
import { Service, type Context } from '@deepseek-ai/cordis'
|
|
71
|
+
|
|
72
|
+
declare module '@deepseek-ai/cordis' {
|
|
73
|
+
interface Context {
|
|
74
|
+
metrics: MetricsService
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export default class MetricsService extends Service {
|
|
79
|
+
constructor(ctx: Context) {
|
|
80
|
+
super(ctx, 'metrics')
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
record(event: string, value: number) { /* ... */ }
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Dependency behavior
|
|
88
|
+
|
|
89
|
+
### Required and optional dependencies
|
|
90
|
+
|
|
91
|
+
```ts ignore-check
|
|
92
|
+
// Required: the plugin does not load while the service is absent.
|
|
93
|
+
export const inject = ['tools']
|
|
94
|
+
|
|
95
|
+
// Optional: omit inject and query with ctx.get() at the use site.
|
|
96
|
+
export function apply(ctx: Context) {
|
|
97
|
+
const metrics = ctx.get('metrics')
|
|
98
|
+
metrics?.record('plugin_loaded', 1)
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### When a service disappears
|
|
103
|
+
|
|
104
|
+
If a required service disappears while the application is running, for example because its provider unloads:
|
|
105
|
+
|
|
106
|
+
1. Dependent plugins dispose automatically.
|
|
107
|
+
2. They load again when the service returns.
|
|
108
|
+
|
|
109
|
+
This prevents a plugin from calling a service that no longer exists.
|
|
110
|
+
|
|
111
|
+
## Service isolation
|
|
112
|
+
|
|
113
|
+
`cordis.yml` can isolate services so separate plugin groups see separate instances of the same service:
|
|
114
|
+
|
|
115
|
+
```yaml
|
|
116
|
+
- id: group-a
|
|
117
|
+
name: '@deepseek-ai/cordis-plugin-group'
|
|
118
|
+
group: true
|
|
119
|
+
isolate:
|
|
120
|
+
shell: true
|
|
121
|
+
config:
|
|
122
|
+
- name: '@deepseek-ai/dsh-bash-local'
|
|
123
|
+
config:
|
|
124
|
+
timeoutMs: 5000
|
|
125
|
+
- name: './src/plugin-a.ts'
|
|
126
|
+
|
|
127
|
+
- id: group-b
|
|
128
|
+
name: '@deepseek-ai/cordis-plugin-group'
|
|
129
|
+
group: true
|
|
130
|
+
isolate:
|
|
131
|
+
shell: true
|
|
132
|
+
config:
|
|
133
|
+
- name: '@deepseek-ai/dsh-bash-local'
|
|
134
|
+
config:
|
|
135
|
+
timeoutMs: 60000
|
|
136
|
+
- name: './src/plugin-b.ts'
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
`plugin-a` and `plugin-b` each see the Bash instance in their own group, with no cross-group effect.
|
|
140
|
+
|
|
141
|
+
## Built-in Harness services
|
|
142
|
+
|
|
143
|
+
The repository generates the service names, public methods, and source locations into each service's [subsystem page](../../../subsystems/core.md). Use those generated regions and the service's TypeScript interface while developing a plugin; do not maintain a second static list.
|
|
144
|
+
|
|
145
|
+
## Next steps
|
|
146
|
+
|
|
147
|
+
- [Event system](./events.md) — communicate between plugins without tight coupling
|
|
148
|
+
- [Capability layering](../practice/index.md) — use services as capability interfaces
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
# 服务与依赖
|
|
2
|
+
|
|
3
|
+
[English](service.md) | 中文
|
|
4
|
+
|
|
5
|
+
服务是一个插件向其他插件公开的能力。inject 声明插件需要哪些服务。
|
|
6
|
+
|
|
7
|
+
## 什么是服务
|
|
8
|
+
|
|
9
|
+
在 Harness 中,`tools`、`llm`、`agents` 都是服务。服务是挂载在 `ctx` 上的命名能力:
|
|
10
|
+
|
|
11
|
+
```ts ignore-check
|
|
12
|
+
ctx.tools // ToolRuntime service
|
|
13
|
+
ctx.llm // LLM service
|
|
14
|
+
ctx.agents // Agent service
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
任何插件都可以提供服务,供其他插件使用。
|
|
18
|
+
|
|
19
|
+
## 使用服务
|
|
20
|
+
|
|
21
|
+
声明 `inject` 来使用已有服务:
|
|
22
|
+
|
|
23
|
+
```ts ignore-check
|
|
24
|
+
export const inject = ['tools']
|
|
25
|
+
|
|
26
|
+
export function apply(ctx: Context) {
|
|
27
|
+
// ctx.tools exists and is ready here.
|
|
28
|
+
ctx.tools.register(/* ... */)
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
框架保证:在 `apply` 执行时,`inject` 声明的服务已经全部就绪。如果服务还没准备好,你的插件会等着,不会执行。
|
|
33
|
+
|
|
34
|
+
## 提供服务
|
|
35
|
+
|
|
36
|
+
### 使用 Service 基类
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
import { Service, type Context } from '@deepseek-ai/cordis'
|
|
40
|
+
|
|
41
|
+
export default class MetricsService extends Service {
|
|
42
|
+
static inject = ['llm'] // A service may depend on other services.
|
|
43
|
+
|
|
44
|
+
constructor(ctx: Context) {
|
|
45
|
+
super(ctx, 'metrics') // 'metrics' is the service name.
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Public service method.
|
|
49
|
+
record(event: string, value: number) {
|
|
50
|
+
// ...
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
加载这个插件后,消费方就可以通过 `ctx.metrics` 访问它:
|
|
56
|
+
|
|
57
|
+
```ts ignore-check
|
|
58
|
+
export const inject = ['metrics']
|
|
59
|
+
|
|
60
|
+
export function apply(ctx: Context) {
|
|
61
|
+
ctx.metrics.record('tool_call', 1)
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### 类型声明
|
|
66
|
+
|
|
67
|
+
使用 TypeScript 声明合并让 `ctx.metrics` 有正确类型:
|
|
68
|
+
|
|
69
|
+
```ts
|
|
70
|
+
import { Service, type Context } from '@deepseek-ai/cordis'
|
|
71
|
+
|
|
72
|
+
declare module '@deepseek-ai/cordis' {
|
|
73
|
+
interface Context {
|
|
74
|
+
metrics: MetricsService
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export default class MetricsService extends Service {
|
|
79
|
+
constructor(ctx: Context) {
|
|
80
|
+
super(ctx, 'metrics')
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
record(event: string, value: number) { /* ... */ }
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## 依赖的行为
|
|
88
|
+
|
|
89
|
+
### 必需依赖与可选依赖
|
|
90
|
+
|
|
91
|
+
```ts ignore-check
|
|
92
|
+
// Required: the plugin does not load while the service is absent.
|
|
93
|
+
export const inject = ['tools']
|
|
94
|
+
|
|
95
|
+
// Optional: omit inject and query with ctx.get() at the use site.
|
|
96
|
+
export function apply(ctx: Context) {
|
|
97
|
+
const metrics = ctx.get('metrics')
|
|
98
|
+
metrics?.record('plugin_loaded', 1)
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### 服务消失时的行为
|
|
103
|
+
|
|
104
|
+
如果应用运行期间某项必需服务消失(例如其提供方卸载):
|
|
105
|
+
|
|
106
|
+
1. 依赖它的插件会自动 dispose(资源释放)
|
|
107
|
+
2. 当服务重新出现时,插件自动重新加载
|
|
108
|
+
|
|
109
|
+
这可以防止插件调用已不存在的服务。
|
|
110
|
+
|
|
111
|
+
<a id="service-isolation"></a>
|
|
112
|
+
|
|
113
|
+
## 服务隔离
|
|
114
|
+
|
|
115
|
+
`cordis.yml` 支持服务隔离——同一个服务可以有多个实例,不同插件组看到不同实例:
|
|
116
|
+
|
|
117
|
+
```yaml
|
|
118
|
+
- id: group-a
|
|
119
|
+
name: '@deepseek-ai/cordis-plugin-group'
|
|
120
|
+
group: true
|
|
121
|
+
isolate:
|
|
122
|
+
shell: true
|
|
123
|
+
config:
|
|
124
|
+
- name: '@deepseek-ai/dsh-bash-local'
|
|
125
|
+
config:
|
|
126
|
+
timeoutMs: 5000
|
|
127
|
+
- name: './src/plugin-a.ts'
|
|
128
|
+
|
|
129
|
+
- id: group-b
|
|
130
|
+
name: '@deepseek-ai/cordis-plugin-group'
|
|
131
|
+
group: true
|
|
132
|
+
isolate:
|
|
133
|
+
shell: true
|
|
134
|
+
config:
|
|
135
|
+
- name: '@deepseek-ai/dsh-bash-local'
|
|
136
|
+
config:
|
|
137
|
+
timeoutMs: 60000
|
|
138
|
+
- name: './src/plugin-b.ts'
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
`plugin-a` 和 `plugin-b` 各自看到自己组内的 Bash 实例,互不影响。
|
|
142
|
+
|
|
143
|
+
## Harness 内置服务
|
|
144
|
+
|
|
145
|
+
服务名、公开方法和源码位置由仓库自动生成到各服务的[子系统页面](../../../subsystems/core.zh.md)。开发插件时应以这些生成区块和服务的 TypeScript 接口为准,不要维护另一份静态清单。
|
|
146
|
+
|
|
147
|
+
## 下一步
|
|
148
|
+
|
|
149
|
+
- [事件系统](./events.zh.md) — 插件间松耦合通信
|
|
150
|
+
- [能力分层](../practice/index.zh.md) — 将服务用作能力接口
|