dsh-plugin-dev-kb 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (234) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +56 -0
  3. package/cordis.patch.yml +12 -0
  4. package/kb/INDEX.md +210 -0
  5. package/kb/README.md +69 -0
  6. package/kb/extra/AGENTS.md +75 -0
  7. package/kb/extra/api-gateway.md +164 -0
  8. package/kb/extra/api-gateway.zh.md +164 -0
  9. package/kb/extra/cookbook/adding-a-vendored-package.md +59 -0
  10. package/kb/extra/cookbook/adding-a-vendored-package.zh.md +59 -0
  11. package/kb/extra/cookbook/maintaining-dsh-code-review.md +64 -0
  12. package/kb/extra/cookbook/maintaining-dsh-code-review.zh.md +64 -0
  13. package/kb/extra/cookbook/responding-to-pr-review-on-a-stack.md +32 -0
  14. package/kb/extra/cookbook/responding-to-pr-review-on-a-stack.zh.md +32 -0
  15. package/kb/extra/defensive-patterns.md +33 -0
  16. package/kb/extra/defensive-patterns.zh.md +33 -0
  17. package/kb/extra/development.md +171 -0
  18. package/kb/extra/development.zh.md +171 -0
  19. package/kb/extra/event-producer-consumer.md +76 -0
  20. package/kb/extra/event-producer-consumer.zh.md +78 -0
  21. package/kb/extra/glossary.md +45 -0
  22. package/kb/extra/glossary.zh.md +45 -0
  23. package/kb/extra/graph-atlas.md +24 -0
  24. package/kb/extra/graph-atlas.zh.md +26 -0
  25. package/kb/extra/i18n/README.md +60 -0
  26. package/kb/extra/i18n/README.zh.md +60 -0
  27. package/kb/extra/i18n/style-samples.md +87 -0
  28. package/kb/extra/i18n/terminology.md +214 -0
  29. package/kb/extra/i18n/translation-prompt.md +263 -0
  30. package/kb/extra/i18n/translation-rules.md +69 -0
  31. package/kb/extra/i18n/translation-rules.zh.md +69 -0
  32. package/kb/extra/module-graph.md +1641 -0
  33. package/kb/extra/module-graph.zh.md +1643 -0
  34. package/kb/extra/postmortem/0001-acp-default-export-drops-inject.md +113 -0
  35. package/kb/extra/postmortem/0001-acp-default-export-drops-inject.zh.md +113 -0
  36. package/kb/extra/postmortem/0002-js-expression-disabled-filesystem-tools.md +47 -0
  37. package/kb/extra/postmortem/0002-js-expression-disabled-filesystem-tools.zh.md +47 -0
  38. package/kb/extra/postmortem/0003-web-agent-gui-feedback-loop.md +53 -0
  39. package/kb/extra/postmortem/0003-web-agent-gui-feedback-loop.zh.md +53 -0
  40. package/kb/extra/postmortem/0004-landlock-partial-notice-misclassified-child-failures.md +55 -0
  41. package/kb/extra/postmortem/0004-landlock-partial-notice-misclassified-child-failures.zh.md +55 -0
  42. package/kb/extra/postmortem/README.md +18 -0
  43. package/kb/extra/postmortem/README.zh.md +18 -0
  44. package/kb/extra/rescope.md +53 -0
  45. package/kb/extra/rescope.zh.md +53 -0
  46. package/kb/extra/subsystems/attachment.md +125 -0
  47. package/kb/extra/subsystems/attachment.zh.md +125 -0
  48. package/kb/extra/subsystems/extensions.md +364 -0
  49. package/kb/extra/subsystems/extensions.zh.md +364 -0
  50. package/kb/extra/subsystems/feedback.md +266 -0
  51. package/kb/extra/subsystems/feedback.zh.md +266 -0
  52. package/kb/extra/testing.md +49 -0
  53. package/kb/extra/testing.zh.md +49 -0
  54. package/kb/extra/web-styling.md +25 -0
  55. package/kb/extra/web-styling.zh.md +25 -0
  56. package/kb/meta/search-index.json +1328 -0
  57. package/kb/meta/site-pages.txt +168 -0
  58. package/kb/meta/source.json +13 -0
  59. package/kb/meta/topics.md +75 -0
  60. package/kb/site/develop/basic/config.md +108 -0
  61. package/kb/site/develop/basic/index.md +146 -0
  62. package/kb/site/develop/basic/publish.md +185 -0
  63. package/kb/site/develop/basic/tool.md +54 -0
  64. package/kb/site/develop/cordis-tutorial/01-first-plugin.md +95 -0
  65. package/kb/site/develop/cordis-tutorial/02-lifecycle-and-effects.md +98 -0
  66. package/kb/site/develop/cordis-tutorial/03-services.md +98 -0
  67. package/kb/site/develop/cordis-tutorial/04-events.md +144 -0
  68. package/kb/site/develop/cordis-tutorial/05-config.md +84 -0
  69. package/kb/site/develop/cordis-tutorial/06-composition-and-hmr.md +113 -0
  70. package/kb/site/develop/cordis-tutorial/07-into-the-harness.md +107 -0
  71. package/kb/site/develop/cordis-tutorial/index.md +62 -0
  72. package/kb/site/develop/framework/events.md +145 -0
  73. package/kb/site/develop/framework/index.md +139 -0
  74. package/kb/site/develop/framework/service.md +152 -0
  75. package/kb/site/develop/practice/index.md +157 -0
  76. package/kb/site/develop/practice/llm-adapter.md +190 -0
  77. package/kb/site/en/develop/basic/config.md +108 -0
  78. package/kb/site/en/develop/basic/index.md +146 -0
  79. package/kb/site/en/develop/basic/publish.md +185 -0
  80. package/kb/site/en/develop/basic/tool.md +54 -0
  81. package/kb/site/en/develop/cordis-tutorial/01-first-plugin.md +95 -0
  82. package/kb/site/en/develop/cordis-tutorial/02-lifecycle-and-effects.md +98 -0
  83. package/kb/site/en/develop/cordis-tutorial/03-services.md +98 -0
  84. package/kb/site/en/develop/cordis-tutorial/04-events.md +144 -0
  85. package/kb/site/en/develop/cordis-tutorial/05-config.md +84 -0
  86. package/kb/site/en/develop/cordis-tutorial/06-composition-and-hmr.md +113 -0
  87. package/kb/site/en/develop/cordis-tutorial/07-into-the-harness.md +107 -0
  88. package/kb/site/en/develop/cordis-tutorial/index.md +60 -0
  89. package/kb/site/en/develop/framework/events.md +145 -0
  90. package/kb/site/en/develop/framework/index.md +139 -0
  91. package/kb/site/en/develop/framework/service.md +150 -0
  92. package/kb/site/en/develop/practice/index.md +157 -0
  93. package/kb/site/en/develop/practice/llm-adapter.md +190 -0
  94. package/kb/site/en/guide/providers-custom-form.png +0 -0
  95. package/kb/site/en/guide/providers-models-page.png +0 -0
  96. package/kb/site/en/guide/providers.md +100 -0
  97. package/kb/site/en/guide/python-sdk.md +106 -0
  98. package/kb/site/en/guide/quickstart.md +32 -0
  99. package/kb/site/en/index.md +8 -0
  100. package/kb/site/en/reference/agent-lifecycle.md +86 -0
  101. package/kb/site/en/reference/capability-seams.md +475 -0
  102. package/kb/site/en/reference/config-catalog.md +3155 -0
  103. package/kb/site/en/reference/cookbook/adding-a-conversation-node.md +235 -0
  104. package/kb/site/en/reference/cookbook/adding-a-package.md +120 -0
  105. package/kb/site/en/reference/cookbook/adding-a-settings-card.md +102 -0
  106. package/kb/site/en/reference/cookbook/adding-a-tool.md +96 -0
  107. package/kb/site/en/reference/cookbook/adding-an-llm-adapter.md +45 -0
  108. package/kb/site/en/reference/cookbook/extension-cookbook.md +131 -0
  109. package/kb/site/en/reference/cordis-api/context.md +368 -0
  110. package/kb/site/en/reference/cordis-api/events.md +211 -0
  111. package/kb/site/en/reference/cordis-api/fiber.md +379 -0
  112. package/kb/site/en/reference/cordis-api/inherited.md +43 -0
  113. package/kb/site/en/reference/cordis-api/registry.md +156 -0
  114. package/kb/site/en/reference/cordis-api/service.md +106 -0
  115. package/kb/site/en/reference/cordis-primer.md +46 -0
  116. package/kb/site/en/reference/index.md +131 -0
  117. package/kb/site/en/reference/persistence-catalog.md +949 -0
  118. package/kb/site/en/reference/subsystems/approval.md +173 -0
  119. package/kb/site/en/reference/subsystems/client-modules.md +121 -0
  120. package/kb/site/en/reference/subsystems/code-runtime.md +194 -0
  121. package/kb/site/en/reference/subsystems/commands.md +190 -0
  122. package/kb/site/en/reference/subsystems/compaction.md +241 -0
  123. package/kb/site/en/reference/subsystems/core.md +1073 -0
  124. package/kb/site/en/reference/subsystems/credentials.md +136 -0
  125. package/kb/site/en/reference/subsystems/filesystem.md +498 -0
  126. package/kb/site/en/reference/subsystems/goal.md +280 -0
  127. package/kb/site/en/reference/subsystems/index.md +58 -0
  128. package/kb/site/en/reference/subsystems/invariants.md +91 -0
  129. package/kb/site/en/reference/subsystems/jobs.md +293 -0
  130. package/kb/site/en/reference/subsystems/llm-streaming.md +920 -0
  131. package/kb/site/en/reference/subsystems/lsp.md +205 -0
  132. package/kb/site/en/reference/subsystems/permission-presets.md +134 -0
  133. package/kb/site/en/reference/subsystems/persistence.md +388 -0
  134. package/kb/site/en/reference/subsystems/plan.md +90 -0
  135. package/kb/site/en/reference/subsystems/sandbox.md +221 -0
  136. package/kb/site/en/reference/subsystems/schedule.md +189 -0
  137. package/kb/site/en/reference/subsystems/scope.md +62 -0
  138. package/kb/site/en/reference/subsystems/session-projection.md +265 -0
  139. package/kb/site/en/reference/subsystems/session-query.md +498 -0
  140. package/kb/site/en/reference/subsystems/session-reference.md +111 -0
  141. package/kb/site/en/reference/subsystems/session-telemetry.md +197 -0
  142. package/kb/site/en/reference/subsystems/session-title.md +207 -0
  143. package/kb/site/en/reference/subsystems/session.md +852 -0
  144. package/kb/site/en/reference/subsystems/settings.md +313 -0
  145. package/kb/site/en/reference/subsystems/shell.md +306 -0
  146. package/kb/site/en/reference/subsystems/skills.md +334 -0
  147. package/kb/site/en/reference/subsystems/spill.md +120 -0
  148. package/kb/site/en/reference/subsystems/storage.md +232 -0
  149. package/kb/site/en/reference/subsystems/subagent.md +737 -0
  150. package/kb/site/en/reference/subsystems/subprocess.md +327 -0
  151. package/kb/site/en/reference/subsystems/system-prompt.md +210 -0
  152. package/kb/site/en/reference/subsystems/terminal.md +187 -0
  153. package/kb/site/en/reference/subsystems/token-meter.md +93 -0
  154. package/kb/site/en/reference/subsystems/tools.md +723 -0
  155. package/kb/site/en/reference/subsystems/typert.md +339 -0
  156. package/kb/site/en/reference/subsystems/user-questions.md +181 -0
  157. package/kb/site/en/reference/subsystems/web-server.md +111 -0
  158. package/kb/site/en/reference/subsystems/web.md +202 -0
  159. package/kb/site/en/reference/subsystems/workflow.md +281 -0
  160. package/kb/site/en/reference/subsystems/workspace.md +231 -0
  161. package/kb/site/en/reference/tool-catalog.md +1877 -0
  162. package/kb/site/en/reference/tool-execution-pipeline.md +66 -0
  163. package/kb/site/guide/providers-custom-form.zh.png +0 -0
  164. package/kb/site/guide/providers-models-page.zh.png +0 -0
  165. package/kb/site/guide/providers.md +100 -0
  166. package/kb/site/guide/python-sdk.md +106 -0
  167. package/kb/site/guide/quickstart.md +32 -0
  168. package/kb/site/index.md +8 -0
  169. package/kb/site/reference/agent-lifecycle.md +86 -0
  170. package/kb/site/reference/capability-seams.md +475 -0
  171. package/kb/site/reference/config-catalog.md +3154 -0
  172. package/kb/site/reference/cookbook/adding-a-conversation-node.md +235 -0
  173. package/kb/site/reference/cookbook/adding-a-package.md +120 -0
  174. package/kb/site/reference/cookbook/adding-a-settings-card.md +102 -0
  175. package/kb/site/reference/cookbook/adding-a-tool.md +98 -0
  176. package/kb/site/reference/cookbook/adding-an-llm-adapter.md +45 -0
  177. package/kb/site/reference/cookbook/extension-cookbook.md +133 -0
  178. package/kb/site/reference/cordis-api/context.md +368 -0
  179. package/kb/site/reference/cordis-api/events.md +211 -0
  180. package/kb/site/reference/cordis-api/fiber.md +379 -0
  181. package/kb/site/reference/cordis-api/inherited.md +43 -0
  182. package/kb/site/reference/cordis-api/registry.md +156 -0
  183. package/kb/site/reference/cordis-api/service.md +106 -0
  184. package/kb/site/reference/cordis-primer.md +52 -0
  185. package/kb/site/reference/index.md +135 -0
  186. package/kb/site/reference/persistence-catalog.md +949 -0
  187. package/kb/site/reference/subsystems/approval.md +173 -0
  188. package/kb/site/reference/subsystems/client-modules.md +121 -0
  189. package/kb/site/reference/subsystems/code-runtime.md +194 -0
  190. package/kb/site/reference/subsystems/commands.md +190 -0
  191. package/kb/site/reference/subsystems/compaction.md +241 -0
  192. package/kb/site/reference/subsystems/core.md +1081 -0
  193. package/kb/site/reference/subsystems/credentials.md +136 -0
  194. package/kb/site/reference/subsystems/filesystem.md +498 -0
  195. package/kb/site/reference/subsystems/goal.md +280 -0
  196. package/kb/site/reference/subsystems/index.md +58 -0
  197. package/kb/site/reference/subsystems/invariants.md +91 -0
  198. package/kb/site/reference/subsystems/jobs.md +293 -0
  199. package/kb/site/reference/subsystems/llm-streaming.md +926 -0
  200. package/kb/site/reference/subsystems/lsp.md +205 -0
  201. package/kb/site/reference/subsystems/permission-presets.md +134 -0
  202. package/kb/site/reference/subsystems/persistence.md +388 -0
  203. package/kb/site/reference/subsystems/plan.md +90 -0
  204. package/kb/site/reference/subsystems/sandbox.md +221 -0
  205. package/kb/site/reference/subsystems/schedule.md +189 -0
  206. package/kb/site/reference/subsystems/scope.md +62 -0
  207. package/kb/site/reference/subsystems/session-projection.md +265 -0
  208. package/kb/site/reference/subsystems/session-query.md +498 -0
  209. package/kb/site/reference/subsystems/session-reference.md +111 -0
  210. package/kb/site/reference/subsystems/session-telemetry.md +197 -0
  211. package/kb/site/reference/subsystems/session-title.md +207 -0
  212. package/kb/site/reference/subsystems/session.md +854 -0
  213. package/kb/site/reference/subsystems/settings.md +313 -0
  214. package/kb/site/reference/subsystems/shell.md +306 -0
  215. package/kb/site/reference/subsystems/skills.md +334 -0
  216. package/kb/site/reference/subsystems/spill.md +120 -0
  217. package/kb/site/reference/subsystems/storage.md +232 -0
  218. package/kb/site/reference/subsystems/subagent.md +739 -0
  219. package/kb/site/reference/subsystems/subprocess.md +327 -0
  220. package/kb/site/reference/subsystems/system-prompt.md +210 -0
  221. package/kb/site/reference/subsystems/terminal.md +187 -0
  222. package/kb/site/reference/subsystems/token-meter.md +93 -0
  223. package/kb/site/reference/subsystems/tools.md +723 -0
  224. package/kb/site/reference/subsystems/typert.md +339 -0
  225. package/kb/site/reference/subsystems/user-questions.md +181 -0
  226. package/kb/site/reference/subsystems/web-server.md +111 -0
  227. package/kb/site/reference/subsystems/web.md +202 -0
  228. package/kb/site/reference/subsystems/workflow.md +281 -0
  229. package/kb/site/reference/subsystems/workspace.md +231 -0
  230. package/kb/site/reference/tool-catalog.md +1880 -0
  231. package/kb/site/reference/tool-execution-pipeline.md +66 -0
  232. package/package.json +40 -0
  233. package/scripts/rebuild-index.mjs +88 -0
  234. package/skills/dsh-plugin-dev-kb.md +66 -0
@@ -0,0 +1,144 @@
1
+ ---
2
+ editSource: "docs/cordis-tutorial/04-events.md"
3
+ ---
4
+
5
+ # 4. Events
6
+
7
+ Services support direct calls; **events** let a plugin announce something without knowing which plugins listen. The harness uses events for interactions such as tool results, model requests, and approval decisions.
8
+
9
+ ## Declare, emit, listen
10
+
11
+ Create `stats.ts` in `tmp/cordis-tutorial` — a service that counts things and announces each change:
12
+
13
+ ```ts
14
+ import { Service, type Context } from '@deepseek-ai/cordis'
15
+
16
+ declare module '@deepseek-ai/cordis' {
17
+ interface Context {
18
+ stats: StatsService
19
+ }
20
+ interface Events {
21
+ 'stats/report'(name: string, count: number): void
22
+ }
23
+ }
24
+
25
+ export class StatsService extends Service {
26
+ private counts = new Map<string, number>()
27
+
28
+ constructor(ctx: Context) {
29
+ super(ctx, 'stats')
30
+ }
31
+
32
+ bump(name: string) {
33
+ const next = (this.counts.get(name) ?? 0) + 1
34
+ this.counts.set(name, next)
35
+ this.ctx.emit('stats/report', name, next)
36
+ }
37
+ }
38
+
39
+ export const name = 'stats'
40
+
41
+ export function apply(ctx: Context) {
42
+ ctx.plugin(StatsService)
43
+ }
44
+ ```
45
+
46
+ The `interface Events` merge is the event-system twin of the `interface Context` merge from chapter 3: it declares the event name and its listener signature, so `ctx.emit` and `ctx.on` are fully typed. The `namespace/action` naming convention keeps the flat event namespace readable.
47
+
48
+ Create `reporter.ts`:
49
+
50
+ ```ts ignore-check
51
+ import type { Context } from '@deepseek-ai/cordis'
52
+ import type {} from './stats.ts'
53
+
54
+ export const name = 'reporter'
55
+ export const inject = ['stats']
56
+
57
+ export function apply(ctx: Context) {
58
+ ctx.on('stats/report', (name, count) => {
59
+ console.log(`[stats] ${name} -> ${count}`)
60
+ })
61
+ ctx.stats.bump('tool_call')
62
+ ctx.stats.bump('tool_call')
63
+ ctx.stats.bump('prompt')
64
+ }
65
+ ```
66
+
67
+ The `import type {} from './stats.ts'` line imports nothing at runtime; it exists so TypeScript sees the declaration merges. Compose and run:
68
+
69
+ ```yaml
70
+ - name: './stats.ts'
71
+ - name: './reporter.ts'
72
+ ```
73
+
74
+ ```
75
+ [stats] tool_call -> 1
76
+ [stats] tool_call -> 2
77
+ [stats] prompt -> 1
78
+ ```
79
+
80
+ Because `ctx.on()` is an effect, the listener disappears with the plugin — no manual `removeListener` bookkeeping, ever.
81
+
82
+ ## Dispatch modes
83
+
84
+ `emit` is one of five dispatch modes. Which one an event uses is part of its contract — it decides whether listeners can return values, run concurrently, or short-circuit each other:
85
+
86
+ | Mode | Call | Semantics |
87
+ |---|---|---|
88
+ | emit | `ctx.emit(name, ...args)` | Synchronous broadcast; returned promises and values are not awaited or collected. |
89
+ | parallel | `await ctx.parallel(name, ...args)` | All listeners run concurrently; awaited together. |
90
+ | serial | `await ctx.serial(name, ...args)` | Listeners run in order, awaited; the first non-`null`/`false`/`undefined` return wins and stops the rest. |
91
+ | bail | `ctx.bail(name, ...args)` | Synchronous version of serial. |
92
+ | waterfall | `ctx.waterfall(name, ...args, next)` | Around-middleware; see below. |
93
+
94
+ Every harness event documents its mode in the generated reference on its owning [subsystem page](../../reference/subsystems/core.md).
95
+
96
+ ## Waterfall: transform or short-circuit
97
+
98
+ Waterfall is the mode that powers interception. Each listener receives the arguments plus a `next()` continuation; it can transform what `next()` returns, or return without calling `next()` and short-circuit the rest of the chain — what the Cordis docs call the veto. Create `waterfall-demo.ts`:
99
+
100
+ ```ts
101
+ import type { Context } from '@deepseek-ai/cordis'
102
+
103
+ declare module '@deepseek-ai/cordis' {
104
+ interface Events {
105
+ 'demo/transform'(input: string, next: () => Promise<string>): Promise<string>
106
+ }
107
+ }
108
+
109
+ export const name = 'waterfall-demo'
110
+
111
+ export function apply(ctx: Context) {
112
+ // Listener 1: wrap the downstream result.
113
+ ctx.on('demo/transform', async (input, next) => {
114
+ const downstream = await next()
115
+ return downstream.toUpperCase()
116
+ })
117
+
118
+ // Listener 2: short-circuit when it owns the decision.
119
+ ctx.on('demo/transform', async (input, next) => {
120
+ if (input.includes('blocked')) return '** blocked **'
121
+ return next()
122
+ })
123
+
124
+ void (async () => {
125
+ console.log(await ctx.waterfall('demo/transform', 'hello', async () => 'hello'))
126
+ console.log(await ctx.waterfall('demo/transform', 'blocked words', async () => 'blocked words'))
127
+ })()
128
+ }
129
+ ```
130
+
131
+ Point `cordis.yml` at just this file and run:
132
+
133
+ ```
134
+ HELLO
135
+ ** BLOCKED **
136
+ ```
137
+
138
+ Walk through the second line: listener 1 runs first, calls `next()`, which invokes listener 2; listener 2 sees `blocked` and returns without calling `next()` — the innermost default (the function passed to `ctx.waterfall`) never runs — and listener 1 uppercases the replacement message on the way out.
139
+
140
+ The discipline that follows: **a waterfall listener that only observes or annotates must call `next()`**; returning without it is a deliberate short-circuit. Forgetting `next()` in a logging listener silently swallows the default behavior for everyone downstream. It is a standing rule of this repository ([waterfall semantics](../../reference/cordis-primer.md#cordis-waterfall-semantics)).
141
+
142
+ The harness uses waterfalls for decisions that cooperating plugins may wrap or answer: [`agent/request`](../../reference/subsystems/core.md#agentrequest--waterfall) lets a plugin replace the model-call config, and [`approval/request`](../../reference/subsystems/approval.md#approvalrequest--waterfall) lets a policy answer instead of the user.
143
+
144
+ Next: [Configuration](./05-config.md) — plugin options from `cordis.yml`.
@@ -0,0 +1,84 @@
1
+ ---
2
+ editSource: "docs/cordis-tutorial/05-config.md"
3
+ ---
4
+
5
+ # 5. Configuration
6
+
7
+ Each `cordis.yml` entry can carry a `config` block, and the plugin declares a schema that validates it before `apply` runs. Bad config fails the load with a precise error — the plugin never starts half-configured.
8
+
9
+ ## A configurable plugin
10
+
11
+ Create `config-demo.ts` in `tmp/cordis-tutorial`:
12
+
13
+ ```ts
14
+ import type { Context } from '@deepseek-ai/cordis'
15
+ import Schema from '@deepseek-ai/schemastery'
16
+
17
+ export const name = 'config-demo'
18
+
19
+ export interface Config {
20
+ greeting: string
21
+ targets: string[]
22
+ }
23
+
24
+ export const Config: Schema<Config> = Schema.object({
25
+ greeting: Schema.string().default('Hello'),
26
+ targets: Schema.array(String).default(['world']),
27
+ })
28
+
29
+ export function apply(ctx: Context, config: Config) {
30
+ for (const target of config.targets) {
31
+ console.log(`${config.greeting}, ${target}!`)
32
+ }
33
+ }
34
+ ```
35
+
36
+ The exported `Config` is both a TypeScript interface and a runtime schema with the same name — consumers get the type, Cordis gets the validator. This repo uses [Schemastery](https://github.com/shigma/schemastery) for schemas; Cordis itself accepts any [Standard Schema](https://standardschema.dev/) validator, so a plain object exported as `Config` will not work.
37
+
38
+ Configure it:
39
+
40
+ ```yaml
41
+ - name: './config-demo.ts'
42
+ config:
43
+ targets: ['alpha', 'beta']
44
+ ```
45
+
46
+ Run:
47
+
48
+ ```
49
+ Hello, alpha!
50
+ Hello, beta!
51
+ ```
52
+
53
+ `greeting` was omitted, so the schema default filled it in — `apply` always receives complete, validated config.
54
+
55
+ ## Fail loud
56
+
57
+ Now feed it something invalid:
58
+
59
+ ```yaml
60
+ - name: './config-demo.ts'
61
+ config:
62
+ targets: 'not-an-array'
63
+ ```
64
+
65
+ ```
66
+ ValidationError: invalid config:
67
+ - $.targets expected array but got not-an-array (at targets)
68
+ ```
69
+
70
+ The plugin's fiber goes to FAILED, and this tutorial's launcher exits with status 1 after printing the error. A plugin should also reject schema-valid config that names an unavailable resource or provider as soon as it can resolve that reference.
71
+
72
+ ## Computed config values
73
+
74
+ The loader used in this repo supports a `!!js` tag for config values that must be computed at load time:
75
+
76
+ ```yaml
77
+ - name: './config-demo.ts'
78
+ config:
79
+ greeting: !!js process.env.DEMO_GREETING ?? 'Hello'
80
+ ```
81
+
82
+ `!!js` works only inside `config` and in an entry's `disabled` field. `disabled: !!js ...` evaluates against the loader context at every mount decision (this repo's extension), so a row can gate itself on platform or environment; the other metadata (`name`, `id`, `inject`, ...) stays static, where an expression is ordinary truthy data. See [loader configuration](../../reference/cordis-primer.md#loader-configuration).
83
+
84
+ Next: [Composition and HMR](./06-composition-and-hmr.md) — treating `cordis.yml` as the application.
@@ -0,0 +1,113 @@
1
+ ---
2
+ editSource: "docs/cordis-tutorial/06-composition-and-hmr.md"
3
+ ---
4
+
5
+ # 6. Composition and HMR
6
+
7
+ Every capability built so far is a plugin, and `cordis.yml` selects the application's plugin tree. This chapter changes that composition, hot-reloads a plugin, and diagnoses a plugin that never loads.
8
+
9
+ ## Entries are more than a name
10
+
11
+ A config entry accepts metadata beyond `name` and `config`:
12
+
13
+ ```yaml
14
+ - id: greeter # stable identity for this entry
15
+ name: './greeter.ts'
16
+ - id: consumer
17
+ name: './consumer.ts'
18
+ disabled: true # keep the entry, skip mounting it
19
+ ```
20
+
21
+ `id` gives the entry a stable identity so the loader can tell an edit to an existing entry apart from a removal plus an addition. `disabled: true` unmounts a plugin without deleting its entry — flip it back and the plugin (and everything PENDING on its services) loads again.
22
+
23
+ Groups nest a sub-list of entries that load and unload as one unit, and `isolate` gives a group its own instance of a service name — two groups can each see a differently configured `shell` provider without affecting each other. The [Cordis primer](../../reference/cordis-primer.md) and the [service isolation example](../framework/service.md#service-isolation) cover the details.
24
+
25
+ ## Hot module replacement
26
+
27
+ Because unloading releases effects ([chapter 2](./02-lifecycle-and-effects.md)) and loading follows dependencies ([chapter 3](./03-services.md)), HMR can replace a running plugin by unloading and loading it. The `@deepseek-ai/cordis-plugin-hmr` plugin watches your files and does exactly that on save.
28
+
29
+ In `tmp/cordis-tutorial`, write `cordis.yml`:
30
+
31
+ ```yaml
32
+ - id: logger
33
+ name: '@deepseek-ai/cordis-plugin-logger-console'
34
+ - id: timer
35
+ name: '@deepseek-ai/cordis-plugin-timer'
36
+ - id: hmr
37
+ name: '@deepseek-ai/cordis-plugin-hmr'
38
+ config:
39
+ root: ['.']
40
+ - id: hello
41
+ name: './hello.ts'
42
+ ```
43
+
44
+ Two support plugins joined the list: HMR logs through the Cordis logger service, so without a console exporter you would not see its messages, and it `inject`s the `timer` service for debouncing — without `@deepseek-ai/cordis-plugin-timer` it sits in PENDING forever, silently. That silence is the subject of the next section.
45
+
46
+ HMR reads Node's loader internals through the Loader's native helper. Run Cordis under tsx:
47
+
48
+ ```sh
49
+ node --import tsx ../../vendor/cordis/bin.js
50
+ ```
51
+
52
+ Now edit `hello.ts` — change the log message — and save:
53
+
54
+ ```
55
+ hello from my first plugin
56
+ 2026-07-22 15:44:36 [I] hmr watching [ '.' ]
57
+ 2026-07-22 15:44:39 [I] hmr reload plugin at hello.ts
58
+ hello from my EDITED plugin
59
+ ```
60
+
61
+ The old instance unloaded (all its effects unwound), the new code loaded, `apply` ran again. Stop the process with Ctrl-C. Editing `cordis.yml` itself is also picked up: the loader diffs entries by `id` and mounts, unmounts, or reconfigures only what changed. This is why the entries above carry explicit `id`s — an entry without one gets a generated id on every read, so after any config-file edit it counts as removed-plus-added and remounts even if its own lines did not change.
62
+
63
+ ## Diagnosing a plugin that never loads
64
+
65
+ The flip side of dependency-driven loading: a plugin whose `inject` names a service nobody provides waits forever, printing nothing. No error — PENDING is a legitimate state, since the provider may be mounted later.
66
+
67
+ You can see the states directly. Every context can enumerate the plugin registry; create `diagnose.ts`:
68
+
69
+ ```ts
70
+ import { FiberState, type Context } from '@deepseek-ai/cordis'
71
+
72
+ export const name = 'diagnose'
73
+
74
+ export function apply(ctx: Context) {
75
+ setTimeout(() => {
76
+ for (const runtime of ctx.registry.values()) {
77
+ for (const fiber of runtime.fibers) {
78
+ if (fiber.state === FiberState.PENDING) {
79
+ console.log(`${fiber.name} is PENDING — a required service is missing`)
80
+ }
81
+ }
82
+ }
83
+ }, 500)
84
+ }
85
+ ```
86
+
87
+ And a plugin with an unsatisfiable dependency, `needs-timer.ts`:
88
+
89
+ ```ts
90
+ import type { Context } from '@deepseek-ai/cordis'
91
+
92
+ export const name = 'needs-timer'
93
+ export const inject = ['timer']
94
+
95
+ export function apply(ctx: Context) {
96
+ console.log('needs-timer loaded')
97
+ }
98
+ ```
99
+
100
+ ```yaml
101
+ - name: './needs-timer.ts'
102
+ - name: './diagnose.ts'
103
+ ```
104
+
105
+ Run it (plain `node --import tsx ../../vendor/cordis/bin.js`; stop with Ctrl-C):
106
+
107
+ ```
108
+ needs-timer is PENDING — a required service is missing
109
+ ```
110
+
111
+ `inject: ['timer']` has no provider. Add `- name: '@deepseek-ai/cordis-plugin-timer'` to the list and the plugin loads. When a plugin does nothing and reports nothing, inspect its fiber state. Iterating without the PENDING filter also shows the loader's own plugins (Loader, Include) as ACTIVE fibers because plugins mount the config file itself.
112
+
113
+ Next: [Into the harness](./07-into-the-harness.md) — the same patterns against real harness services.
@@ -0,0 +1,107 @@
1
+ ---
2
+ editSource: "docs/cordis-tutorial/07-into-the-harness.md"
3
+ ---
4
+
5
+ # 7. Into the harness
6
+
7
+ This chapter registers a model-callable tool with the harness's `tools` service, executes it through the harness tool pipeline, and observes the result event. It remains keyless and does not call a model.
8
+
9
+ ## A tool plugin
10
+
11
+ Create `greet-tool.ts` in `tmp/cordis-tutorial`:
12
+
13
+ ```ts
14
+ import type { Context } from '@deepseek-ai/cordis'
15
+ import { defineTool } from '@deepseek-ai/dsh-tools'
16
+ import { CallId } from '@deepseek-ai/dsh-llm'
17
+
18
+ export const name = 'greet-tool'
19
+ export const inject = ['tools']
20
+
21
+ export function apply(ctx: Context) {
22
+ ctx.tools.register(defineTool({
23
+ name: 'greet',
24
+ description: 'Greet the named person.',
25
+ parameters: {
26
+ name: { type: 'string', required: true, description: 'Who to greet' },
27
+ },
28
+ output: {
29
+ schema: { type: 'string' },
30
+ render: (_args, value) => [{ type: 'text', text: value }],
31
+ },
32
+ async execute(args) {
33
+ return `Hello, ${args.name}!`
34
+ },
35
+ }))
36
+
37
+ // Drive one call through the real execution pipeline, standing in for
38
+ // the model. CallId brands the correlation id a provider would issue.
39
+ void (async () => {
40
+ const result = await ctx.tools.execute({
41
+ callId: CallId('demo-1'),
42
+ name: 'greet',
43
+ arguments: { name: 'Cordis' },
44
+ signal: new AbortController().signal,
45
+ })
46
+ console.log('tool replied:', JSON.stringify(result.content))
47
+ })()
48
+ }
49
+ ```
50
+
51
+ Every pattern here is from the earlier chapters: `inject: ['tools']` ([chapter 3](./03-services.md)) holds the plugin until the tool registry exists; `ctx.tools.register(...)` attaches the registration disposer to the plugin ([chapter 2](./02-lifecycle-and-effects.md)), so unloading unregisters the tool. `defineTool` converts the `parameters` spec to the JSON Schema shown to the model, infers the type of `args`, and validates model-supplied arguments before `execute` runs. The tool returns the canonical value declared by `output.schema`; `output.render` separately produces the Native and durable result content.
52
+
53
+ ## An observer plugin
54
+
55
+ Create `tool-logger.ts` — a separate plugin that watches every tool call in the app through the harness's `tools/result` event:
56
+
57
+ ```ts
58
+ import type { Context } from '@deepseek-ai/cordis'
59
+ import type {} from '@deepseek-ai/dsh-tools'
60
+
61
+ export const name = 'tool-logger'
62
+ export const inject = ['tools']
63
+
64
+ export function apply(ctx: Context) {
65
+ ctx.on('tools/result', (exec, result) => {
66
+ const text = result.content
67
+ .map(block => (block.type === 'text' ? block.text : ''))
68
+ .join('')
69
+ console.log(`[tool-logger] ${exec.name} -> ${text}`)
70
+ })
71
+ }
72
+ ```
73
+
74
+ The `import type {} from '@deepseek-ai/dsh-tools'` line pulls in the package's declaration merges so `'tools/result'` and its payload are typed — the same move as chapter 4's `stats.ts` import, at package scale.
75
+
76
+ ## Compose and run
77
+
78
+ ```yaml
79
+ - name: '@deepseek-ai/dsh-system-prompt'
80
+ - name: '@deepseek-ai/dsh-tools'
81
+ - name: './tool-logger.ts'
82
+ - name: './greet-tool.ts'
83
+ ```
84
+
85
+ `@deepseek-ai/dsh-tools` injects the `systemPrompt` service because tools contribute schemas to the system prompt, so the composition lists its provider too. Without it, the tools plugin remains PENDING as described in [chapter 6](./06-composition-and-hmr.md).
86
+
87
+ ```sh
88
+ node --import tsx ../../vendor/cordis/bin.js
89
+ ```
90
+
91
+ ```
92
+ [tool-logger] greet -> Hello, Cordis!
93
+ tool replied: [{"type":"text","text":"Hello, Cordis!"}]
94
+ ```
95
+
96
+ The logger fired first: `tools/result` is emitted as part of result materialization, before `execute`'s promise resolves to the caller. Neither of your plugins knows the other exists — the registry service and the event connect them.
97
+
98
+ ## From here to a full agent
99
+
100
+ A real agent is this composition plus more plugins: an LLM adapter, the agent loop, persistence, an entry point. Compare [examples/headless-agent/cordis.yml](https://github.com/deepseek-ai/deepseek-harness/blob/master/examples/headless-agent/cordis.yml) — you can read every entry in it now. Add your `greet-tool.ts` to a copy of that file.
101
+
102
+ Where to go next:
103
+
104
+ - [Build a tool](../basic/tool.md) — more of `defineTool`, including presentation and richer schemas.
105
+ - [Three-layer capability design](../practice/index.md) — how the harness structures replaceable capabilities.
106
+ - The generated `cordis-surface` regions on the [subsystem pages](../../reference/subsystems/core.md) — everything you can inject and listen to, each on its owning page.
107
+ - [Architecture](../../reference/index.md) — the system map these plugins live in.
@@ -0,0 +1,60 @@
1
+ ---
2
+ editSource: "docs/cordis-tutorial/index.md"
3
+ ---
4
+
5
+ # Cordis tutorial
6
+
7
+ Cordis is the plugin framework underneath DeepSeek Harness: a small runtime where every capability — tools, LLM adapters, file access, the agent loop itself — is a plugin mounted into a shared context. This tutorial teaches Cordis hands-on: each chapter is a runnable example you build in a scratch directory inside this repository, ending with a plugin wired into real harness services.
8
+
9
+ The audience is agent developers. You do not need deep TypeScript experience; the [TypeScript notes](#typescript-notes) below explain the syntax that may be unfamiliar, and every chapter shows the exact commands and expected output.
10
+
11
+ If you want the condensed concept reference instead of a walkthrough, read the [Cordis primer](../../reference/cordis-primer.md). The exhaustive API reference lives in the generated `cordis-surface` regions on the [subsystem pages](../../reference/subsystems/core.md) and the [Cordis core API](../../reference/cordis-api/context.md) pages.
12
+
13
+ To write plugins for the harness itself — loaded from a `cordis.yml` and driven from the Web UI rather than the launcher below — start from [your first Harness plugin](../basic/index.md).
14
+
15
+ ## Setup
16
+
17
+ You need a clone of this repository with dependencies installed; the [development guide](https://github.com/deepseek-ai/deepseek-harness/blob/master/docs/development.md#setup-tutorial) lists the prerequisites. No API key is needed for this tutorial; every example runs keylessly.
18
+
19
+ ```sh
20
+ git clone https://github.com/deepseek-ai/deepseek-harness.git
21
+ cd deepseek-harness
22
+ pnpm install
23
+ ```
24
+
25
+ Create the scratch directory the chapters work in. `tmp/` is gitignored, so nothing you write there touches version control:
26
+
27
+ ```sh
28
+ mkdir -p tmp/cordis-tutorial
29
+ cd tmp/cordis-tutorial
30
+ ```
31
+
32
+ Every chapter runs the same command from this directory:
33
+
34
+ ```sh
35
+ node --import tsx ../../vendor/cordis/bin.js
36
+ ```
37
+
38
+ That one-file launcher (see [vendor/cordis/bin.js](https://github.com/deepseek-ai/deepseek-harness/blob/master/vendor/cordis/bin.js)) creates a root `Context`, mounts the Loader plugin, and tells it to load `./cordis.yml` from the current directory. Everything else — which plugins exist, how they are configured — comes from that YAML file, which you will write in a moment. The `--import tsx` flag lets Node run the TypeScript files the config points at without a build step.
39
+
40
+ ## Chapters
41
+
42
+ 1. [Your first plugin](./01-first-plugin.md) — a plugin is a function; the loader mounts it.
43
+ 2. [Lifecycle and effects](./02-lifecycle-and-effects.md) — Cordis-managed registrations are undone when their plugin unloads.
44
+ 3. [Services](./03-services.md) — expose a capability on `ctx` and depend on it with `inject`.
45
+ 4. [Events](./04-events.md) — typed events, broadcast dispatch, and the waterfall short-circuit.
46
+ 5. [Configuration](./05-config.md) — validated config from `cordis.yml`, failing loud on bad input.
47
+ 6. [Composition and HMR](./06-composition-and-hmr.md) — the config file as a plugin tree, hot reload, and diagnosing a plugin that never loads.
48
+ 7. [Into the harness](./07-into-the-harness.md) — register a model-callable tool against real harness services.
49
+
50
+ <a id="typescript-notes"></a>
51
+
52
+ ## TypeScript notes
53
+
54
+ The examples use three TypeScript features beyond ordinary modern JavaScript:
55
+
56
+ - **Type annotations** describe values without changing runtime behavior: `ctx: Context` says that `ctx` has the Cordis context API, `who: string` accepts text, and `string[]` means an array of strings.
57
+ - **`import type { Context } from '@deepseek-ai/cordis'`** imports only type information. It vanishes at runtime, so a plugin file that needs `Context` solely for annotations adds no runtime dependency.
58
+ - **Declaration merging** (`declare module '@deepseek-ai/cordis' { ... }`) adds your entries to interfaces that Cordis already declares — for example the type of a new `ctx.greeter` property or event name. It generates no runtime wiring; the plugin separately provides the service or emits the event. Chapter 3 shows the pattern in full.
59
+
60
+ Chapter 5 also uses an `interface` to describe a configuration object's fields and a generic type such as `Schema<Config>` to say which object fields a schema validates. You can copy those declarations as shown; the surrounding text explains what each one connects.
@@ -0,0 +1,145 @@
1
+ ---
2
+ editSource: "docs/user/develop/framework/events.md"
3
+ ---
4
+
5
+ # Event system
6
+
7
+ Events are the core communication mechanism between Cordis plugins. Harness uses them extensively for loosely coupled extension points.
8
+
9
+ ## Basic use
10
+
11
+ ### Listen for an event
12
+
13
+ ```ts ignore-check
14
+ ctx.on('event-name', (payload) => {
15
+ // Handle the event.
16
+ })
17
+ ```
18
+
19
+ ### Emit an event
20
+
21
+ ```ts ignore-check
22
+ ctx.emit('event-name', payload)
23
+ ```
24
+
25
+ ## Event modes
26
+
27
+ Cordis provides several event modes for different interaction contracts.
28
+
29
+ ### emit — broadcast
30
+
31
+ Every listener runs synchronously and return values are ignored:
32
+
33
+ ```ts ignore-check
34
+ // Emit
35
+ ctx.emit('my-plugin/ready', { id: 'worker-1' })
36
+
37
+ // Listen
38
+ ctx.on('my-plugin/ready', ({ id }) => {
39
+ console.log(`${id} is ready`)
40
+ })
41
+ ```
42
+
43
+ ### bail — short circuit
44
+
45
+ Listeners run in order; the first result other than `null`, `false`, or `undefined` becomes the final result:
46
+
47
+ ```ts ignore-check
48
+ // Dispatch
49
+ const result = ctx.bail('some-check', input)
50
+
51
+ // Listen: a returned value stops later listeners.
52
+ ctx.on('some-check', (input) => {
53
+ if (shouldBlock(input)) return 'blocked'
54
+ // Return null, false, or undefined to continue to the next listener.
55
+ })
56
+ ```
57
+
58
+ ### serial — ordered execution
59
+
60
+ Listeners run in registration order and asynchronous results are awaited. The first result other than `null`, `false`, or `undefined` stops further execution:
61
+
62
+ ```ts ignore-check
63
+ await ctx.serial('setup-phase', context)
64
+ ```
65
+
66
+ ### waterfall — pipeline
67
+
68
+ Each listener may wrap the downstream result to form a processing chain. A listener **must call `next()` to delegate downstream**; omitting the call short-circuits the pipeline:
69
+
70
+ ```ts ignore-check
71
+ // Dispatch
72
+ const output = await ctx.waterfall('my-plugin/transform', input, async () => input)
73
+
74
+ // Listen: next() is mandatory.
75
+ ctx.on('my-plugin/transform', async (_input, next) => {
76
+ const downstream = await next()
77
+ return downstream.trim()
78
+ })
79
+ ```
80
+
81
+ ::: warning
82
+ A waterfall listener **must call `next()`**. Omitting it short-circuits the pipeline by design, enabling interception and gateway behavior.
83
+ :::
84
+
85
+ ## Typed events
86
+
87
+ Harness uses TypeScript declaration merging for type-safe events:
88
+
89
+ ```ts
90
+ import '@deepseek-ai/cordis'
91
+
92
+ declare module '@deepseek-ai/cordis' {
93
+ interface Events {
94
+ 'my-plugin/ready': (payload: { id: string }) => void
95
+ 'my-plugin/check': (input: string) => boolean | undefined
96
+ 'my-plugin/transform': (input: string, next: () => Promise<string>) => Promise<string>
97
+ }
98
+ }
99
+
100
+ // ctx.on('my-plugin/ready', ...) and ctx.emit('my-plugin/ready', ...)
101
+ // are now inferred correctly.
102
+ ```
103
+
104
+ ## Cordis events and session records
105
+
106
+ Harness Cordis events use `namespace/action` names, including `agent/step`, `agent/request`, `agent/request-error`, `tools/result`, and `session/event`. The generated `cordis-surface` regions on the [subsystem pages](../../reference/subsystems/core.md) record complete signatures and modes.
107
+
108
+ `turn/*`, `step/*`, `tool/call`, `tool/result`, and `compaction/*` are durable session-event types, not same-named Cordis events. To observe them, listen to `session/event` and inspect `event.type`.
109
+
110
+ ## Event listeners are effects
111
+
112
+ A listener registered with `ctx.on()` is removed automatically when its plugin unloads:
113
+
114
+ ```ts ignore-check
115
+ export function apply(ctx: Context) {
116
+ // This listener is removed when the plugin disposes.
117
+ ctx.on('tools/result', handler)
118
+ }
119
+ ```
120
+
121
+ ## Example: logging plugin
122
+
123
+ This plugin logs tool calls and results:
124
+
125
+ ```ts
126
+ import type { Context } from '@deepseek-ai/cordis'
127
+ import '@deepseek-ai/dsh-tools'
128
+
129
+ export const name = 'tool-logger'
130
+
131
+ export function apply(ctx: Context) {
132
+ ctx.on('tools/result', (exec, result) => {
133
+ console.log(`[tool] ${exec.name}(${JSON.stringify(exec.arguments)})`)
134
+ const text = result.content
135
+ .map(block => block.type === 'text' ? block.text : '')
136
+ .join('')
137
+ console.log(`[tool result] ${text.slice(0, 100)}`)
138
+ })
139
+ }
140
+ ```
141
+
142
+ ## Next steps
143
+
144
+ - [Capability layering](../practice/index.md) — understand events within capability interfaces
145
+ - [LLM adapters](../practice/llm-adapter.md) — implement a complete LLM backend