@kelloggt/dsh-program-agents 0.0.0-stage → 0.1.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 (222) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +113 -2
  3. package/cordis.patch.yml +5 -0
  4. package/dist/app/agents.d.ts +50 -0
  5. package/dist/app/agents.js +70 -0
  6. package/dist/app/agents.js.map +1 -0
  7. package/dist/app/conversations.d.ts +8 -0
  8. package/dist/app/conversations.js +26 -0
  9. package/dist/app/conversations.js.map +1 -0
  10. package/dist/app/endpoints.d.ts +36 -0
  11. package/dist/app/endpoints.js +162 -0
  12. package/dist/app/endpoints.js.map +1 -0
  13. package/dist/app/inbox.d.ts +61 -0
  14. package/dist/app/inbox.js +253 -0
  15. package/dist/app/inbox.js.map +1 -0
  16. package/dist/app/index.d.ts +70 -0
  17. package/dist/app/index.js +147 -0
  18. package/dist/app/index.js.map +1 -0
  19. package/dist/app/mailbox.d.ts +63 -0
  20. package/dist/app/mailbox.js +173 -0
  21. package/dist/app/mailbox.js.map +1 -0
  22. package/dist/app/models.d.ts +32 -0
  23. package/dist/app/models.js +13 -0
  24. package/dist/app/models.js.map +1 -0
  25. package/dist/app/server.d.ts +36 -0
  26. package/dist/app/server.js +233 -0
  27. package/dist/app/server.js.map +1 -0
  28. package/dist/app/stream-server.d.ts +9 -0
  29. package/dist/app/stream-server.js +77 -0
  30. package/dist/app/stream-server.js.map +1 -0
  31. package/dist/app/stream.d.ts +4 -0
  32. package/dist/app/stream.js +93 -0
  33. package/dist/app/stream.js.map +1 -0
  34. package/dist/app/tools.d.ts +16 -0
  35. package/dist/app/tools.js +24 -0
  36. package/dist/app/tools.js.map +1 -0
  37. package/dist/client/controller.d.ts +21 -0
  38. package/dist/client/controller.js +140 -0
  39. package/dist/client/controller.js.map +1 -0
  40. package/dist/client/index.d.ts +22 -0
  41. package/dist/client/index.js +14 -0
  42. package/dist/client/index.js.map +1 -0
  43. package/dist/client/model.d.ts +66 -0
  44. package/dist/client/model.js +147 -0
  45. package/dist/client/model.js.map +1 -0
  46. package/dist/client/models.d.ts +11 -0
  47. package/dist/client/models.js +27 -0
  48. package/dist/client/models.js.map +1 -0
  49. package/dist/client/transport.d.ts +64 -0
  50. package/dist/client/transport.js +141 -0
  51. package/dist/client/transport.js.map +1 -0
  52. package/dist/client/view.d.ts +19 -0
  53. package/dist/client/view.js +69 -0
  54. package/dist/client/view.js.map +1 -0
  55. package/dist/client.js +673 -0
  56. package/dist/conversations.d.ts +92 -0
  57. package/dist/conversations.js +6 -0
  58. package/dist/conversations.js.map +1 -0
  59. package/dist/endpoints.d.ts +48 -0
  60. package/dist/endpoints.js +43 -0
  61. package/dist/endpoints.js.map +1 -0
  62. package/dist/examples/dashboard.js +42 -0
  63. package/dist/examples/dynamic-server.js +27 -0
  64. package/dist/examples/inbox-worker.js +22 -0
  65. package/dist/examples/model-slots.js +19 -0
  66. package/dist/examples/standalone-supervisor.js +20 -0
  67. package/dist/host/adapter.d.ts +195 -0
  68. package/dist/host/adapter.js +630 -0
  69. package/dist/host/adapter.js.map +1 -0
  70. package/dist/host/agents.d.ts +35 -0
  71. package/dist/host/agents.js +222 -0
  72. package/dist/host/agents.js.map +1 -0
  73. package/dist/host/audit.d.ts +14 -0
  74. package/dist/host/audit.js +39 -0
  75. package/dist/host/audit.js.map +1 -0
  76. package/dist/host/catalog.d.ts +53 -0
  77. package/dist/host/catalog.js +116 -0
  78. package/dist/host/catalog.js.map +1 -0
  79. package/dist/host/contracts.d.ts +38 -0
  80. package/dist/host/contracts.js +2 -0
  81. package/dist/host/contracts.js.map +1 -0
  82. package/dist/host/conversations.d.ts +41 -0
  83. package/dist/host/conversations.js +324 -0
  84. package/dist/host/conversations.js.map +1 -0
  85. package/dist/host/events.d.ts +12 -0
  86. package/dist/host/events.js +106 -0
  87. package/dist/host/events.js.map +1 -0
  88. package/dist/host/http.d.ts +14 -0
  89. package/dist/host/http.js +30 -0
  90. package/dist/host/http.js.map +1 -0
  91. package/dist/host/index.d.ts +23 -0
  92. package/dist/host/index.js +84 -0
  93. package/dist/host/index.js.map +1 -0
  94. package/dist/host/installed-v10.js +19797 -0
  95. package/dist/host/installed-v11.js +19797 -0
  96. package/dist/host/installed-v12.js +19797 -0
  97. package/dist/host/installed-v13.js +19797 -0
  98. package/dist/host/installed-v2.js +19797 -0
  99. package/dist/host/installed-v3.js +19797 -0
  100. package/dist/host/installed-v4.js +19797 -0
  101. package/dist/host/installed-v5.js +19797 -0
  102. package/dist/host/installed-v6.js +19797 -0
  103. package/dist/host/installed-v7.js +19797 -0
  104. package/dist/host/installed-v8.js +19797 -0
  105. package/dist/host/installed-v9.js +19797 -0
  106. package/dist/host/mcp.d.ts +11 -0
  107. package/dist/host/mcp.js +42 -0
  108. package/dist/host/mcp.js.map +1 -0
  109. package/dist/host/models.d.ts +37 -0
  110. package/dist/host/models.js +141 -0
  111. package/dist/host/models.js.map +1 -0
  112. package/dist/host/operations.d.ts +22 -0
  113. package/dist/host/operations.js +72 -0
  114. package/dist/host/operations.js.map +1 -0
  115. package/dist/host/plugin.js +19797 -0
  116. package/dist/host/policies.d.ts +36 -0
  117. package/dist/host/policies.js +73 -0
  118. package/dist/host/policies.js.map +1 -0
  119. package/dist/host/requests.d.ts +57 -0
  120. package/dist/host/requests.js +353 -0
  121. package/dist/host/requests.js.map +1 -0
  122. package/dist/host/tool-client.d.ts +19 -0
  123. package/dist/host/tool-client.js +62 -0
  124. package/dist/host/tool-client.js.map +1 -0
  125. package/dist/host/validation.d.ts +5 -0
  126. package/dist/host/validation.js +42 -0
  127. package/dist/host/validation.js.map +1 -0
  128. package/dist/mcp.d.ts +7 -0
  129. package/dist/mcp.js +2 -0
  130. package/dist/mcp.js.map +1 -0
  131. package/dist/models.d.ts +34 -0
  132. package/dist/models.js +53 -0
  133. package/dist/models.js.map +1 -0
  134. package/dist/monitoring/error-buffer.d.ts +89 -0
  135. package/dist/monitoring/error-buffer.js +355 -0
  136. package/dist/monitoring/error-buffer.js.map +1 -0
  137. package/dist/monitoring/fatal.d.ts +6 -0
  138. package/dist/monitoring/fatal.js +32 -0
  139. package/dist/monitoring/fatal.js.map +1 -0
  140. package/dist/monitoring/index.d.ts +7 -0
  141. package/dist/monitoring/index.js +8 -0
  142. package/dist/monitoring/index.js.map +1 -0
  143. package/dist/monitoring/logger.d.ts +45 -0
  144. package/dist/monitoring/logger.js +174 -0
  145. package/dist/monitoring/logger.js.map +1 -0
  146. package/dist/monitoring/schemas.d.ts +34 -0
  147. package/dist/monitoring/schemas.js +132 -0
  148. package/dist/monitoring/schemas.js.map +1 -0
  149. package/dist/monitoring/standard.d.ts +51 -0
  150. package/dist/monitoring/standard.js +140 -0
  151. package/dist/monitoring/standard.js.map +1 -0
  152. package/dist/monitoring/tailer.d.ts +41 -0
  153. package/dist/monitoring/tailer.js +210 -0
  154. package/dist/monitoring/tailer.js.map +1 -0
  155. package/dist/requests.d.ts +92 -0
  156. package/dist/requests.js +120 -0
  157. package/dist/requests.js.map +1 -0
  158. package/dist/runtime/bootstrap.d.ts +1 -0
  159. package/dist/runtime/bootstrap.js +25 -0
  160. package/dist/runtime/bootstrap.js.map +1 -0
  161. package/dist/runtime/definitions.d.ts +7 -0
  162. package/dist/runtime/definitions.js +138 -0
  163. package/dist/runtime/definitions.js.map +1 -0
  164. package/dist/runtime/index.d.ts +4 -0
  165. package/dist/runtime/index.js +4 -0
  166. package/dist/runtime/index.js.map +1 -0
  167. package/dist/runtime/log-file.d.ts +24 -0
  168. package/dist/runtime/log-file.js +124 -0
  169. package/dist/runtime/log-file.js.map +1 -0
  170. package/dist/runtime/protocol.d.ts +60 -0
  171. package/dist/runtime/protocol.js +155 -0
  172. package/dist/runtime/protocol.js.map +1 -0
  173. package/dist/runtime/registry.d.ts +8 -0
  174. package/dist/runtime/registry.js +38 -0
  175. package/dist/runtime/registry.js.map +1 -0
  176. package/dist/runtime/supervisor.d.ts +74 -0
  177. package/dist/runtime/supervisor.js +881 -0
  178. package/dist/runtime/supervisor.js.map +1 -0
  179. package/dist/sdk/connection.d.ts +33 -0
  180. package/dist/sdk/connection.js +265 -0
  181. package/dist/sdk/connection.js.map +1 -0
  182. package/dist/sdk/index.d.ts +8 -0
  183. package/dist/sdk/index.js +2 -0
  184. package/dist/sdk/index.js.map +1 -0
  185. package/dist/supervision.d.ts +22 -0
  186. package/dist/supervision.js +32 -0
  187. package/dist/supervision.js.map +1 -0
  188. package/dist/types.d.ts +170 -0
  189. package/dist/types.js +2 -0
  190. package/dist/types.js.map +1 -0
  191. package/docs/installation.md +35 -0
  192. package/docs/log-format.md +102 -0
  193. package/docs/publishing.md +45 -0
  194. package/docs/supervision.md +73 -0
  195. package/examples/dashboard.ts +31 -0
  196. package/examples/dsh-web-local.patch.yml +15 -0
  197. package/examples/dynamic-server.ts +13 -0
  198. package/examples/inbox-worker.ts +17 -0
  199. package/examples/model-slots.ts +12 -0
  200. package/examples/standalone-supervisor.ts +14 -0
  201. package/examples/watch-server.ts +12 -0
  202. package/package.json +131 -3
  203. package/schemas/dsh.http.request.v1.schema.json +14 -0
  204. package/schemas/dsh.program.error-batch.v1.schema.json +24 -0
  205. package/schemas/dsh.program.error.v1.schema.json +8 -0
  206. package/schemas/standard-log-record.v1.schema.json +26 -0
  207. package/skills/program-agents/SKILL.md +124 -0
  208. package/skills/program-agents/chat-sidebar.md +23 -0
  209. package/skills/program-agents/endpoints.md +92 -0
  210. package/skills/program-agents/inboxes.md +120 -0
  211. package/skills/program-agents/log-format.md +102 -0
  212. package/skills/program-agents/models.md +59 -0
  213. package/skills/program-agents/references/chat-sidebar/AgentSidebar.tsx +181 -0
  214. package/skills/program-agents/references/chat-sidebar/README.md +181 -0
  215. package/skills/program-agents/references/chat-sidebar/agent-sidebar.css +37 -0
  216. package/skills/program-agents/references/chat-sidebar/backend.ts +184 -0
  217. package/skills/program-agents/references/chat-sidebar/protocol.ts +214 -0
  218. package/skills/program-agents/schemas/dsh.http.request.v1.schema.json +14 -0
  219. package/skills/program-agents/schemas/dsh.program.error-batch.v1.schema.json +24 -0
  220. package/skills/program-agents/schemas/dsh.program.error.v1.schema.json +8 -0
  221. package/skills/program-agents/schemas/standard-log-record.v1.schema.json +26 -0
  222. package/skills/program-agents/ui-patterns.md +39 -0
package/package.json CHANGED
@@ -1,6 +1,134 @@
1
1
  {
2
2
  "name": "@kelloggt/dsh-program-agents",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.1.0",
4
+ "description": "Supervised TypeScript/JavaScript programs for DeepSeek Harness",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "homepage": "https://github.com/tkellogg/dsh-program-agents#readme",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/tkellogg/dsh-program-agents.git"
11
+ },
12
+ "bugs": {
13
+ "url": "https://github.com/tkellogg/dsh-program-agents/issues"
14
+ },
15
+ "keywords": [
16
+ "deepseek-harness",
17
+ "dsh-plugin",
18
+ "program-agents"
19
+ ],
20
+ "publishConfig": {
21
+ "access": "public"
22
+ },
23
+ "engines": {
24
+ "node": ">=22.18.0"
25
+ },
26
+ "main": "./dist/host/index.js",
27
+ "types": "./dist/host/index.d.ts",
28
+ "exports": {
29
+ ".": {
30
+ "types": "./dist/host/index.d.ts",
31
+ "default": "./dist/host/index.js"
32
+ },
33
+ "./endpoints": {
34
+ "types": "./dist/endpoints.d.ts",
35
+ "default": "./dist/endpoints.js"
36
+ },
37
+ "./app": {
38
+ "types": "./dist/app/index.d.ts",
39
+ "default": "./dist/app/index.js"
40
+ },
41
+ "./monitoring": {
42
+ "types": "./dist/monitoring/index.d.ts",
43
+ "default": "./dist/monitoring/index.js"
44
+ },
45
+ "./supervision": {
46
+ "types": "./dist/supervision.d.ts",
47
+ "default": "./dist/supervision.js"
48
+ },
49
+ "./sdk": {
50
+ "types": "./dist/sdk/index.d.ts",
51
+ "default": "./dist/sdk/index.js"
52
+ },
53
+ "./runtime": {
54
+ "types": "./dist/runtime/index.d.ts",
55
+ "default": "./dist/runtime/index.js"
56
+ },
57
+ "./client": "./dist/client.js",
58
+ "./cordis.patch.yml": "./cordis.patch.yml",
59
+ "./package.json": "./package.json"
60
+ },
61
+ "files": [
62
+ "dist",
63
+ "examples",
64
+ "README.md",
65
+ "LICENSE",
66
+ "cordis.patch.yml",
67
+ "docs/installation.md",
68
+ "docs/log-format.md",
69
+ "docs/publishing.md",
70
+ "docs/supervision.md",
71
+ "skills",
72
+ "schemas"
73
+ ],
74
+ "dependencies": {
75
+ "@deepseek-ai/schemastery": "3.18.5",
76
+ "@modelcontextprotocol/core": "2.2.0",
77
+ "ajv": "^8.20.0",
78
+ "zod": "^4.6.7"
79
+ },
80
+ "peerDependencies": {
81
+ "@deepseek-ai/cordis": "~4.0.5",
82
+ "@deepseek-ai/dsh-agent": "0.2.1-rc.2",
83
+ "@deepseek-ai/dsh-api-session-controller": "0.2.1-rc.2",
84
+ "@deepseek-ai/dsh-llm": "0.2.1-rc.2",
85
+ "@deepseek-ai/dsh-session": "0.2.1-rc.2",
86
+ "@deepseek-ai/dsh-tools": "0.2.1-rc.2",
87
+ "@deepseek-ai/dsh-typert-protocol": "0.2.1-rc.2"
88
+ },
89
+ "devDependencies": {
90
+ "@deepseek-ai/cordis": "~4.0.5",
91
+ "@deepseek-ai/dsh-agent": "0.2.1-rc.2",
92
+ "@deepseek-ai/dsh-api-session-controller": "0.2.1-rc.2",
93
+ "@deepseek-ai/dsh-llm": "0.2.1-rc.2",
94
+ "@deepseek-ai/dsh-session": "0.2.1-rc.2",
95
+ "@deepseek-ai/dsh-tools": "0.2.1-rc.2",
96
+ "@deepseek-ai/dsh-typert-protocol": "0.2.1-rc.2",
97
+ "@types/node": "^22.18.0",
98
+ "@types/react": "^18.3.0",
99
+ "@types/react-dom": "^18.3.0",
100
+ "esbuild": "^0.25.0",
101
+ "hono": "^4.13.13",
102
+ "linkedom": "^0.18.0",
103
+ "react": "^18.3.0",
104
+ "react-dom": "^18.3.0",
105
+ "tsx": "^4.20.0",
106
+ "typescript": "^5.9.0"
107
+ },
108
+ "dsh": {
109
+ "bundle": {
110
+ "patch": "./cordis.patch.yml"
111
+ },
112
+ "client": {
113
+ "platform": "web",
114
+ "inject": [
115
+ "@deepseek-ai/dsh-client-connection",
116
+ "@deepseek-ai/dsh-client-ui-conversation",
117
+ "@deepseek-ai/dsh-client-ui-renderer"
118
+ ]
119
+ }
120
+ },
121
+ "scripts": {
122
+ "build": "tsc -p tsconfig.json && node scripts/build-client.mjs",
123
+ "typecheck": "tsc --noEmit -p tsconfig.json",
124
+ "typecheck:references": "tsc --noEmit -p tsconfig.references.json",
125
+ "test": "tsx --test tests/*.test.ts",
126
+ "check": "pnpm build && pnpm typecheck:references && pnpm test",
127
+ "example": "tsx examples/standalone-supervisor.ts",
128
+ "release:pack": "node scripts/release.mjs pack",
129
+ "release:inspect": "node scripts/release.mjs inspect",
130
+ "release:login": "pnpm login",
131
+ "release:publish:dry-run": "node scripts/release.mjs publish --dry-run --no-git-checks",
132
+ "release:publish": "node scripts/release.mjs publish"
133
+ }
6
134
  }
@@ -0,0 +1,14 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://dsh.dev/schemas/dsh.http.request.v1.schema.json",
4
+ "title": "dsh.http.request.v1 required data payload",
5
+ "type": "object",
6
+ "required": ["method", "path", "status"],
7
+ "properties": {
8
+ "method": {"type": "string", "minLength": 1, "maxLength": 32},
9
+ "path": {"type": "string"},
10
+ "status": {"type": "integer", "minimum": 100, "maximum": 599},
11
+ "duration_ms": {"type": "number", "minimum": 0}
12
+ },
13
+ "additionalProperties": true
14
+ }
@@ -0,0 +1,24 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://dsh.dev/schemas/dsh.program.error-batch.v1.schema.json",
4
+ "title": "dsh.program.error-batch.v1 required data payload",
5
+ "type": "object", "required": ["key", "groups", "count", "range", "dropped"], "additionalProperties": true,
6
+ "properties": {
7
+ "key": {"type": "string"}, "count": {"type": "integer", "minimum": 1}, "dropped": {"type": "integer", "minimum": 0},
8
+ "range": {"type": "object", "required": ["first", "last"], "properties": {"first": {"type": "string"}, "last": {"type": "string"}}, "additionalProperties": false},
9
+ "groups": {
10
+ "type": "array", "minItems": 1, "maxItems": 20,
11
+ "items": {
12
+ "type": "object", "required": ["fingerprint", "count", "sample", "range"], "additionalProperties": true,
13
+ "properties": {
14
+ "fingerprint": {"type": "string"}, "count": {"type": "integer", "minimum": 1}, "sample": {"type": "object", "description": "Redacted representative standard record; large payloads/stacks/extensions may be omitted."},
15
+ "range": {"type": "object", "required": ["first", "last"], "properties": {"first": {"type": "string"}, "last": {"type": "string"}}, "additionalProperties": false},
16
+ "source": {
17
+ "type": "object", "additionalProperties": false,
18
+ "properties": {"path": {"type": "string"}, "offset": {"type": "integer", "minimum": 0}, "run": {"oneOf": [{"type": "string"}, {"type": "object", "required": ["id", "generation"], "properties": {"id": {"type": "string"}, "generation": {"type": "integer", "minimum": 0}}, "additionalProperties": false}]}}
19
+ }
20
+ }
21
+ }
22
+ }
23
+ }
24
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://dsh.dev/schemas/dsh.program.error.v1.schema.json",
4
+ "title": "dsh.program.error.v1 optional data payload",
5
+ "description": "Payload schema only, not the full record. data may be omitted. Additional application/error metadata is allowed; errors under any other log_type are still monitored by ERROR level.",
6
+ "type": "object",
7
+ "additionalProperties": true
8
+ }
@@ -0,0 +1,26 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://dsh.dev/schemas/standard-log-record.v1.schema.json",
4
+ "title": "DSH extensible standard JSONL record v1",
5
+ "description": "One UTF-8 JSON object per line; at most 65536 bytes, JSON depth at most 32. Runtime rejects non-finite/lossy JSON. Unknown versioned log types are accepted by this base contract.",
6
+ "type": "object",
7
+ "required": ["timestamp", "level", "log_type", "message"],
8
+ "properties": {
9
+ "timestamp": {"type": "string", "format": "date-time", "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(\\.[0-9]{1,9})?(Z|[+-][0-9]{2}:[0-9]{2})$"},
10
+ "level": {"enum": ["DEBUG", "INFO", "WARNING", "ERROR"]},
11
+ "log_type": {"type": "string", "maxLength": 256, "pattern": "^[a-z][a-z0-9_-]*(\\.[a-z][a-z0-9_-]*)+\\.v[1-9][0-9]*$"},
12
+ "message": {"type": "string"},
13
+ "operation_id": {"type": "string"},
14
+ "event_id": {"type": "string"},
15
+ "fatal": {"type": "boolean", "description": "Advisory fatal/unhandled metadata; ERROR level determines monitoring independently."},
16
+ "error": {
17
+ "type": "object", "required": ["name", "message"], "additionalProperties": true,
18
+ "properties": {
19
+ "name": {"type": "string"}, "message": {"type": "string"}, "stack": {"type": "string"},
20
+ "code": {"type": ["string", "number"]}, "fingerprint": {"type": "string"}
21
+ }
22
+ },
23
+ "data": {}
24
+ },
25
+ "additionalProperties": true
26
+ }
@@ -0,0 +1,124 @@
1
+ ---
2
+ name: program-agents
3
+ description: Program agents are regular scripts & apps that are monitored & maintained by agents (you). Use this skill to learn when & how to use them or manage them. Good uses include polling for jobs, files, web services, builing dashboards, giving more visibility, gathering user input in novel new ways, etc.
4
+ ---
5
+
6
+ # Program agents
7
+
8
+ A program agent is ordinary trusted code supervised by the current DSH parent Agent. It is **not an autonomous LLM loop, RLM, REPL or security sandbox**. Prefer one program per cohesive service/watch task. Launch through the program tools, not a detached bash job: that gives this exact parent ownership, process-group stop/restart, logs and the pinned composer strip. The user can Start/Stop/Restart it without seeing internal IPC.
9
+
10
+ A program agent can have a web UI, but do not have to. It is recommended to build a UI so that the user can participate. They
11
+ can also send and recieve messages to and from regular DSH agents, and the parent DSH agent can edit & restart the program agent
12
+ as needed.
13
+
14
+ Uses for program agents (these are suggestions, don't limit yourself to them, build on top of them):
15
+
16
+ 1. **Polling** — have a program agent frequently poll a web resource (or open a websocket/SSE), and when the resource is ready, forward a message to the parent DSH agent. e.g. Connect to Slack, poll for Github issue updates, alert on file changes, poll for job completion, etc.
17
+ 2. **Dashboards** — Humans get easily overloaded by text, and it often helps to UI that's dynamically changing. This is a great way to communicate status to the user, or explain data that's otherwise difficult to understand. It's important to keep dashboards up-to-date. Dynamically changing data is usually appreciated, but confirm this with the user.
18
+ 3. **Apps** — Agents are great for highly complex use cases. Sometimes the user just needs a very simple and repeatable use case. For these, build an app with a regular backend; maybe a CRUD app. Wire in messaging to agents to handle errors or handle bulk user input.
19
+ 4. **Optimization loops** — setup a loop to optimize code or business processes. Setup a subagent to the program agent to handle making changes, and create a dashboard to show progress to the user and let the user participate more in optimization.
20
+
21
+ If you're deciding what to build, please read [ui-patterns](ui-patterns.md).
22
+
23
+ Program agents are fundamentally a way to use agents in a more high leverage way. Focus LLMs on the control plane instead of the
24
+ data plane. Another way to think about it is, "personal SaaS where the agent is the Ops team & development team combined".
25
+
26
+ ## Error handling & Logging
27
+ Fail fast on unexpected, unrecoverable failures; do not deliberately leave expected validation or operational errors unhandled. Initialize `connectProgram({logger:{logsPath:'./logs',stdout:true,private:true}})` and explicitly register `installGlobalErrorHandlers(dsh.logger)`. That handler writes a standard fatal ERROR, attempts a bounded flush, then exits nonzero—it does not swallow the failure or keep a corrupted process alive. The host, not each application error handler, durably buffers/deduplicates standard ERROR records and routes them to the configured supervisor. Do not also send the same error manually.
28
+
29
+ Use DEBUG for detailed diagnostics/metrics, INFO for normal milestones (roughly one completion record per web request), WARNING for recoverable/degraded conditions, and ERROR for failed operations that warrant supervisor attention. ERROR does **not** necessarily mean the process must exit; `fatal:true` denotes fatal errors. Use the structured logger rather than ad-hoc text. [Log schemas and conventions](log-format.md) define the wire format.
30
+
31
+ You (the agent) should know:
32
+ 1. About any unhandled error, so you can learn about new edge cases and handle them. Don't assume you know the edge cases, learn about them from actual usage.
33
+ 2. What the user was doing. Capture enough in the logs to figure out what the user was doing when things broke. Also carefully track interactions with external services. The usual — anything that can be unpredictable should be tracked carefully. Keep metrics, counts, latencies, etc. Generally keep these as DEBUG messages.
34
+ 3. What external services were doing. Keep metrics, counts, latencies, etc. Generally keep these as DEBUG messages.
35
+
36
+ Use an [operation ID](log-format.md) to correlate events across web requests, tool calls and messages; retain the same ID as `correlationId` for related Agent requests. Configure the logger's single active file as `./logs/app-log.jsonl` (or a registered logsPath); it rotates on size/UTC-day boundaries with bounded retention. JSONL records must have a stable versioned `log_type`, timestamp, level and message. Event IDs deduplicate stdout/file observations. Never log credentials, raw Cookie/Authorization headers, request bodies or sensitive personal data by default; key redaction does not make arbitrary free text or stack traces safe. It's wise to setup some sort of tooling to read logs and dashboard out latencies and operational events in a form that agents can make sense of (scripts are typically fine, dashboard UIs if the user needs it). In some cases it may be appropriate to export to DuckDB.
37
+
38
+ ### Responding to errors
39
+ Read [inboxes.md](inboxes.md) to understand the techical details. The simplest way to respond to errors is to log an ERROR line, but you can also use the client-side library. Also, unhandled errors should bubble up to the agent, so throwing an exception is a perfectly adequate response.
40
+
41
+ When writing code, you may come across a situation which is strange and might need further diagnosis. Write code that explicitly sends a message to an agent.
42
+
43
+ ## Who to send a message to?
44
+
45
+ 1. **Parent agent** — Generally the right choice. However, it does clog the agent's inbox so don't do this if the user also needs to interact with the parent agent. Designate a child!
46
+ 2. **Designated child agent** — Use the [client library](inboxes.md) to send a message to a child agent. You'll create a name/ID for this agent such that all traffic goes to the same agent every time.
47
+ 3. **New child agent** — Alternately, instead of designating a single agent to handle all operational issues, you may consider launching off multiple agents to handle incidents. This may be important if latency is key, as a new agent won't ever have to wait for another message to finish.
48
+
49
+
50
+ ## Integration references
51
+
52
+ - Read [endpoints.md](endpoints.md) before publishing app URLs: persist endpoint/path recipes, bind and report an OS-assigned port through `connectProgram().endpoints.listen`, and let the harness/browser supply reachable hostnames. Includes restart collisions, remote/Tailscale access, bind policy and security caveats.
53
+ - Read [inboxes.md](inboxes.md) for the app-side library: managed `inbox.receive(options, async handler)` automatically renews and settles claims; `agents.ask` validates one accepted typed response; `agents.send` and `ask` can create named/fresh child Agents; `tools.listTools/callTool` mirror the official MCP client tool shape. Tool access is limited to existing DSH tools registered through the normal DSH tool API and connected MCP tools. ERROR logs automatically reach the configured supervisor; semantic app workflows can contact it for intelligence as well.
54
+ - Read [models.md](models.md) for named model slots with purpose descriptions, user-editable Models widgets in the DSH composer strip, durable overrides and explicit native model calls. Slot preferences never change the parent/global model.
55
+ - Read [chat-sidebar.md](chat-sidebar.md) for native DSH/subagent conversations with history/event streaming and a copyable React sidebar + Hono backend. Append bounded, allowlisted page context on each user turn; never rewrite stable system/history prefixes to inject UI state.
56
+ - Read [log-format.md](log-format.md) for the versioned JSONL standard, extensible `log_type` schemas, structured logger, buffering, redaction and rotation.
57
+ - `program_register` accepts optional `supervision` (restart policy/budget/backoff, startAtStartup, supervisor route, error buffering) and explicit `access` grants. Defaults do not auto-restart/start. Startup runs only for existing, unarchived parent Sessions.
58
+
59
+ ## Recommended stack
60
+
61
+ - **TypeScript**, Node22.18+ (Node26 on this Mac), ESM; pnpm with a committed lockfile.
62
+ - **Hono + @hono/node-server** for a small typed HTTP app/API; **Zod** for external data validation. Use harness-supplied listener settings and prefer OS-assigned dynamic ports with [endpoint recipes](endpoints.md), not a hardcoded hostname/port. Bind local apps to `127.0.0.1` by default; remote exposure requires deliberate harness configuration. Never silently kill another service holding a fixed port.
63
+ - **Vite + React** for a genuinely interactive UI; use plain server-rendered HTML for simple dashboards. Keep the frontend/API on a consistent origin or explicitly document dev proxying. Publish a UI URL the user can actually open.
64
+ - **SQLite** for transactional application state (Node's `node:sqlite` when appropriate, migrations, WAL, parameterized queries). It is the default for mutable single-user apps/queues/idempotency keys. Do not use a JSON file as a concurrent database.
65
+ - **DuckDB** for analytics, columnar/CSV/Parquet exploration and batch reporting, not the default hot transactional service database. Prefer the supported Node `@duckdb/node-api` binding if needed. Avoid multiple uncoordinated writers and do not introduce PostgreSQL/Docker for a tiny local app without a reason.
66
+ - **tsx watch** for the Node/TypeScript server (`pnpm exec tsx watch src/server.ts`), Vite's dev server for frontend reload. Node `--watch` is another good option for erasable TS or built JS. Keep the watcher as the supervised foreground process so all descendants stay in its process group. Ignore generated DB/log paths to avoid reload loops. If the entire watcher exits, it is restarted only when the program's explicit supervision policy opts in; restart budget/backoff applies.
67
+
68
+ Do not change this Mac's model budgets, subscription credentials, running DSH server or unrelated workstation configuration to run an app.
69
+
70
+ ## Shared launch description
71
+
72
+ Register a program with:
73
+
74
+ - `name`: short user-facing app/task name, not a UUID.
75
+ - `description`: one sentence about what it does.
76
+ - `command`: **argv array**, executable then arguments; no implicit shell. Use an absolute executable path if PATH ambiguity matters. Use `['pnpm','run','dev']`, not a shell string with `cd`, `&`, redirects or interpolated user data. A program is trusted code; review before launch.
77
+ - `cwd`: app's working directory (relative paths resolve from the current parent working directory).
78
+ - `endpoints`: named listeners, usually `{"web":{"port":"dynamic"}}`. The app must consume harness settings and report its actual bound port; `connectProgram().endpoints.listen('web', server)` does both. Do not persist a previous run's evaluated port or browser hostname.
79
+ - `urls`: prefer `{id,title,type:'ui'|'api'|'docs',endpoint:'web',path:'/api',docs?}` for app-owned listeners. An endpoint/path recipe is mutually exclusive with literal `url`; absolute HTTP(S) URLs and informational documentation paths remain supported. Usually0–1 UI URL. `ui` is for the user, `api` for agents/tools, `docs` for discovering usage. **An API should also have documentation:** a docs URL, a docs resource on the same endpoint, or an absolute local README/OpenAPI path. Filesystem docs are informational paths for file tools, not file:// web links. Never put auth tokens in URLs. See [endpoints.md](endpoints.md).
80
+ - `logsPath`: optional existing log file or directory owned by the app. If omitted the supervisor captures bounded stdout/stderr to an actual private file and returns its path in the run. Use file tools or `tail` to inspect; do not paste log floods into chat.
81
+
82
+ Example tool argument:
83
+
84
+ ```json
85
+ {
86
+ "name": "Inventory",
87
+ "description": "Local inventory editor and stock-level API.",
88
+ "command": ["pnpm", "run", "dev"],
89
+ "cwd": "/absolute/path/inventory",
90
+ "endpoints": {"web":{"port":"dynamic"}},
91
+ "urls": [
92
+ {"id":"app","title":"Open inventory","type":"ui","endpoint":"web","path":"/"},
93
+ {"id":"api","title":"Inventory API","type":"api","endpoint":"web","path":"/api","docs":"/absolute/path/inventory/docs/api.md"},
94
+ {"id":"docs","title":"API docs","type":"docs","url":"/absolute/path/inventory/docs/api.md"}
95
+ ]
96
+ }
97
+ ```
98
+
99
+ For this example, bind the app's server with `await connectProgram().endpoints.listen('web', server)`; registration metadata alone cannot change a hardcoded listener. The harness supplies bind interface/protocol and defaults public links to the viewing browser's hostname. Dynamic ports are selected afresh at bind time; remote/Tailscale access still needs reachable interfaces and ACLs. Do not silently widen exposure or assume the DSH page's HTTPS gives the app TLS. Read [endpoints.md](endpoints.md) for the full contract.
100
+
101
+ Call `program_register`; its returned `program` is the opaque launch key. Then `program_start({program})` or let the user press Start. Registering does not execute code. Re-register the same name to update the description/launch command; restart when the command itself changes. Ordinary source edits should be picked up by the watcher; don't keep asking the user to restart.
102
+
103
+ Launch descriptions survive DSH/plugin restart on this machine; running execution state does not. They are scoped to the parent Session. The UI's Start button dispatches the exact live parent's public `program_start` tool under its policy, not an unrelated user shell. Current parent must be live; never manufacture a different parent Agent just to launch a child.
104
+
105
+ ## Supervise with minimal user overhead
106
+
107
+ 1. Create the app, install only needed dependencies, add a reproducible `dev` command and brief README/API docs.
108
+ 2. Register metadata and a watch/reload command. Run migrations/backups explicitly before side-effectful changes.
109
+ 3. Start it. `program_start` returns on process connection/spawn, **not application readiness**. Endpoint links stay unavailable until the app binds and reports its actual port. Verify UI/API health using the current binding and inspect logs; don't claim a working app from a spawned PID alone. Browser-facing hostnames may differ from server-local health addresses—see [endpoints.md](endpoints.md).
110
+ 4. `program_list` returns descriptions and current runs, typed links, generation and log paths. Stop/restart controls require exact id+generation; stale controls must not mutate newer runs.
111
+ 5. Use UI URL for the user; use API URL+docs for automation. Prefer typed structured API calls to scraping a dashboard. Never automatically fetch arbitrary linked resources under DSH's origin.
112
+ 6. If the SDK program notifies its parent, handle it as background context and continue work. Do not ask the user to relay messages, open a message pane or acknowledge IPC. Notify the user only for a genuine decision, permission, completion or actionable problem.
113
+ 7. Before retrying external writes after crash/cancel, check external state. Effects may have unknown outcomes; implement SQLite transactions/idempotency keys or equivalent application deduplication. No exactly-once/replay promise.
114
+ 8. Stop disposable smoke tests. Leave a useful app running when that is the user's intent and give its URL without unnecessary diagnostics.
115
+
116
+ ## Optional SDK
117
+
118
+ Ordinary foreground commands need no special entrypoint. Use `@kelloggt/dsh-program-agents/app` for supervisor messaging, MCP-shaped tool calls, typed Agent requests and managed durable inboxes in watched apps; see inboxes.md. For explicit model/tool calls, parent escalation, dynamic URLs/status or legacy in-process inbox control, a configured trusted TypeScript entry can use `@kelloggt/dsh-program-agents/sdk` and export `default async function main(ctx)`. Named capabilities and exact tool routes require grants; registering an arbitrary command does not grant tools. Model calls use existing native adapter routes/credentials and optional user-controlled slots; creating a conversational child with a selected model also obeys native subagent model-selection policy. API keys remain in the broker. The process is still trusted OS-user code, not isolated from files/network.
119
+
120
+ `ctx.parent.send(json)` automatically routes to the configured supervisor (parent by default; bounded/rate-limited). It is for exceptional agent coordination, not a user messaging interface. Passive status/log changes never wake the LLM. `ctx.resources.publish({id,title,type,url,docs?})` updates typed resources; stopped generations lose live links. Docs files stay informational. Use a watch child/SDK lifecycle deliberately: killing an SDK entry on each edit can break its IPC; ordinary command watch mode is the simplest app workflow.
121
+
122
+ ## Limits and operations
123
+
124
+ POSIX/macOS/Linux only. The supervisor controls a process group and will refuse overlapping restart if old closure cannot be proven. It is not an OS sandbox; don't run untrusted code. Capture logs are bounded/rolling, not an audit archive; use your own retention for production data. Metadata SSE carries only compact state/links, not tokens/chat/log floods. The folded composer strip stays with the parent and updates automatically; there is no Refresh or user message form.
@@ -0,0 +1,23 @@
1
+ # App-owned assistant sidebar (copyable reference)
2
+
3
+ Use this when a supervised web app needs a small chat sidebar that understands the page the human is looking at. This is an **app reference**, not a modification to the DSH composer or full conversation feed. It uses a real continuable native DSH conversation; do not implement a second model/tool loop or send ordinary chat through the supervisor inbox.
4
+
5
+ Copy the five files in [the reference directory](references/chat-sidebar/README.md) into your app:
6
+
7
+ - [AgentSidebar.tsx](references/chat-sidebar/AgentSidebar.tsx): React UI; opens only on human interaction, sends once, follows independently, reconnects with a fresh native snapshot.
8
+ - [backend.ts](references/chat-sidebar/backend.ts): Hono routes; lazily opens one app-owned conversation and proxies only that handle.
9
+ - [protocol.ts](references/chat-sidebar/protocol.ts): browser-pure native-frame reducer, compact snapshot reconstruction, bounded fetch-SSE reader.
10
+ - [agent-sidebar.css](references/chat-sidebar/agent-sidebar.css): scoped, responsive styling.
11
+ - [README.md](references/chat-sidebar/README.md): installation, typed app integration, route contract, security and lifecycle limitations.
12
+
13
+ ## Non-negotiable boundaries
14
+
15
+ 1. `connectProgram()` belongs **only on the backend**. Never ship bridge URL/token/environment to the frontend. The browser gets only same-origin, app-scoped routes, not arbitrary conversation/session IDs, tools, models, or bridge calls.
16
+ 2. Supply an explicit backend `sanitizePageState(input)` allowlist. Share selected IDs, visible filters, and small summaries—not the whole store, DOM, cookies, credentials, or hidden records. Raw requests are capped at 32 KiB; page state at 16 KiB. Plain JSON is data, not HTML or trusted instructions.
17
+ 3. Call `getPageContext()` only when the human sends a new message. Context is appended to that new user turn in the native SDK's explicit untrusted-data wrapper. Never rewrite the system prompt or previous history on page changes; never send page state from render/effect/polling loops.
18
+ 4. Keep the persona and model stable at conversation creation. Provider prompt caching is best-effort, not a guarantee; changing a route invalidates caches in the ordinary way. A model slot is not a selector on the parent composer. Existing conversations keep their creation-time route: a new route/persona requires a deliberately different conversation name.
19
+ 5. Follow `handle.events({ signal, maxMessages: 20 })` independently of send/ask. The native stream starts with a replacement snapshot; native assistant chunks are ephemeral and settle to durable events. Do not invent token event names, append duplicate baseline rows, or render reasoning by default.
20
+ 6. An accepted receipt means native admission, not successful tool execution. A dropped send response has an **unknown outcome**. Preserve its request ID, reconnect, and inspect history before deciding whether to resend. Native request IDs are correlation, not an exactly-once or automatic-deduplication promise.
21
+ 7. Enforce exact app Origin on writes, exact configured host, no wildcard CORS, and real app authentication where needed. Same-origin browser GETs normally omit Origin; the reference permits only the documented GET-only `Sec-Fetch-Site: same-origin` plus non-simple custom-header exception. Localhost cookies are not port-isolated; see the security section in the reference README.
22
+
23
+ Read [the complete integration instructions](references/chat-sidebar/README.md) before copying or publishing routes. This reference does not install dependencies, launch a server, change a running DSH instance, or provide a production authentication system.
@@ -0,0 +1,92 @@
1
+ # Runtime endpoint recipes and direct remote access
2
+
3
+ A URL has two lifetimes: a **persisted recipe** and a **current listener address**. Never save an evaluated dynamic port or last browser hostname as the launch definition. No stored JavaScript, interpolation, eval, or shell expansion is involved.
4
+
5
+ ## Registration
6
+
7
+ ```json
8
+ {
9
+ "name":"Inventory",
10
+ "description":"Local inventory app and API",
11
+ "command":["pnpm","run","dev"],
12
+ "cwd":"/absolute/path/inventory",
13
+ "endpoints":{"web":{"port":"dynamic"}},
14
+ "urls":[
15
+ {"id":"ui","title":"Open inventory","type":"ui","endpoint":"web","path":"/"},
16
+ {"id":"api","title":"Inventory API","type":"api","endpoint":"web","path":"/api","docs":"/absolute/path/inventory/docs/api.md"},
17
+ {"id":"docs","title":"App docs","type":"docs","endpoint":"web","path":"/docs"}
18
+ ]
19
+ }
20
+ ```
21
+
22
+ `endpoints` is a map of at most16 named listeners. `port` is `"dynamic"` or an explicit integer1–65535. Recipe URLs use `endpoint` and optional `path` (default `/`), mutually exclusive with legacy `url`. Multiple UI/API/docs paths may share a listener. Paths include query/hash if useful but cannot change authority or carry raw controls/backslashes. Filesystem documentation remains informational; no `file://` links. Legacy absolute HTTP(S) URLs continue unchanged, including deliberately external links: the harness never globally rewrites `127.0.0.1` inside arbitrary strings.
23
+
24
+ Registration persists the recipe only. A fresh process generation starts with requested port0 for dynamic listeners, no bound port and unavailable links. A listener's actual port becomes available only after the app binds and reports it. Stop/crash/restart clears binding/link availability. The strip receives pushed metadata and resolves links again; it never needs a manual Refresh.
25
+
26
+ ## Bind with the app helper
27
+
28
+ ```ts
29
+ import {createServer} from 'node:http';
30
+ import {connectProgram} from '@kelloggt/dsh-program-agents/app';
31
+
32
+ const app=connectProgram();
33
+ const server=createServer((req,res)=>res.end('Hello'));
34
+ const listener=await app.endpoints.listen('web',server);
35
+ console.log('Local health address:', listener.localUrl);
36
+ // On orderly shutdown:
37
+ await listener.close();
38
+ ```
39
+
40
+ The helper asks the authenticated host for this generation's binding policy, calls `server.listen({host,port})`, waits for listening, reads `server.address().port`, then publishes it. For dynamic ports the OS selects and holds port0 atomically—no probe/free/rebind race. It does not guess a free port, reserve it briefly, or promise the previous port survives a restart. If the old port is occupied, the next dynamic launch uses an available one. Fixed-port collisions fail normally and never advertise an unbound endpoint. There is no automatic retry of app side effects.
41
+
42
+ `listen` takes a Node net/http-compatible Server, so Hono's Node handler can use `createServer(getRequestListener(app.fetch))`; use the same pattern with a framework's server adapter. For separate frontend/backend listeners, register distinct endpoint names. Prefer a single-origin app when possible so asset URLs and relative API requests follow the browser's actual host/port. Configure Vite/framework host/origin settings deliberately; DSH does not rewrite arbitrary HTML, asset paths or redirects.
43
+
44
+ For framework-owned listeners:
45
+
46
+ ```ts
47
+ const policy=(await app.endpoints.list()).find(x=>x.id==='web');
48
+ if(!policy) throw new Error('web endpoint is not registered');
49
+ // Bind using policy.bindHost and policy.requestedPort (0 for dynamic), then:
50
+ await app.endpoints.publish('web',{port:actualBoundPort});
51
+ // On close (port condition prevents an old worker clearing a replacement):
52
+ await app.endpoints.unpublish('web',{port:actualBoundPort});
53
+ ```
54
+
55
+ Per-run policy is also in `DSH_PROGRAM_ENDPOINTS_JSON` for non-JS launchers; the app bridge has list/publish/unpublish. This environment is ephemeral harness input, not something to copy back into the saved definition. Legacy SDK entrypoints can use `/app` alongside `/sdk`; ordinary watched commands need no special entrypoint. A reload worker reads `requestedPort`, not a previously reported `port`. Worker replacement can report a fresh port in the same generation. Publication is not a health-check assertion beyond the helper's successful local bind; use actual health endpoints.
56
+
57
+ ## Harness host policy
58
+
59
+ Plugin config, not registration or a request Host header, controls exposure:
60
+
61
+ ```yaml
62
+ networking:
63
+ bindHost: 127.0.0.1
64
+ publicHost: browser
65
+ protocol: http
66
+ ```
67
+
68
+ Defaults are local-only bind, browser-host links, HTTP. `publicHost: browser` means render using **this browser's `location.hostname`**, excluding DSH's port. This matters with two viewers: local DSH gets `http://127.0.0.1:<app-port>/api`; a Tailscale viewer gets `http://devbox.tailnet.ts.net:<app-port>/api`. Browser hostname is evaluated when constructing each link, not persisted, and never controls where a server listens. An explicit public DNS/IP hostname overrides the browser default. IPv6 is supported/bracketed appropriately. Agent/server health checks use a server-local address, not a guessed remote-browser authority; returned binding metadata exposes bindHost+actual port separately from the public recipe.
69
+
70
+ ### Tailscale direct-port mode
71
+
72
+ Configure `bindHost` to the server's **Tailscale interface address** to expose only that interface, or another deliberately reachable bind address. The user/harness owns that setting. Don't silently change a local app to `0.0.0.0`: that can expose it on Wi-Fi/LAN too. A Tailscale-only bind may not accept `127.0.0.1`; if DSH is viewed locally through loopback, configure an explicit Tailscale `publicHost` or view DSH through its Tailscale name. `publicHost: browser` is correct only when the browser's hostname reaches the app's configured interface.
73
+
74
+ Tailscale ACL/firewall policy must permit the app's selected ports. Dynamic ports aren't automatically tunneled or opened. The current implementation uses **own ports**, as selected by the user—not a DSH same-origin reverse proxy. Tailscale provides the private network but is not per-app authorization: add app auth for sensitive services or other authorized tailnet users.
75
+
76
+ `protocol` describes the app listener, not the DSH page. Opening DSH over HTTPS does not magically give a plain Node HTTP listener TLS; default links remain HTTP. Set HTTPS only when the app itself serves HTTPS with valid certificates at that hostname/port. Tailscale transport encryption is separate from browser HTTPS. This direct mode does not implement Tailscale Serve mappings, HTTPS termination, SSH forwarding of arbitrary dynamic ports, or one-port proxy support. A browser following a link navigates in a separate tab; DSH never fetches/embeds app content with its credentials.
77
+
78
+ ## Lifecycle and security
79
+
80
+ - Different ports are different browser origins, **but cookies are not port-scoped**. A browser may send matching hostname/path cookies to an app on another port. HttpOnly prevents JavaScript reads, not a receiving HTTP server from seeing Cookie headers. These are trusted OS-user programs, not a hostile-code isolation boundary. Do not log/forward Cookie or Authorization headers; use a separate app hostname and appropriately scoped DSH cookies if cookie isolation is required. No reverse proxy is added here.
81
+ - Host metadata has structured `address:{endpoint,host,protocol,port,path}` only after a report; recipe `url` is empty, never an old port string. The browser validates shape/host/path again. Unbound, stale, stopped, pending-action, malformed links are disabled.
82
+ - Bridge credential selects exact live owner/run/generation. A stale generation cannot report or unpublish a newer listener. Program input cannot set bindHost/publicHost/protocol through report methods. Trust boundary remains trusted OS-user code, not a network sandbox; custom code could still ignore instructions and bind differently.
83
+ - Configured recipe resource IDs cannot be overwritten via legacy `resources.publish`; report/unpublish the endpoint instead. Arbitrary external static resources remain supported.
84
+ - Runtime does not probe/fetch advertised URLs or auto-kill another service holding a fixed port. Do not adopt another process's listener after EADDRINUSE.
85
+ - OS-assigned ports solve collision/restart allocation for integrated apps. A generic black-box command hardcoded to a port must be adapted to consume harness configuration and report readiness; metadata alone cannot change its listening socket.
86
+ - No URLs, browser hostnames or evaluated port bindings are saved back to launch recipes on exit. App databases and idempotency remain separate from endpoint lifecycle.
87
+
88
+ ## Example and validation
89
+
90
+ `examples/dynamic-server.ts` (built `dist/examples/dynamic-server.js`) is an ordinary command using the helper. Register endpoint web dynamic and UI `/`, API `/api/status`, docs `/docs` if actually implemented (the example only serves UI and API).
91
+
92
+ Tests cover actual bind0 and publication, restart with previous port occupied by another process, no advertising before bind, fixed-port collision, bridge failures/cancellation/close cleanup, stale generations, per-browser local/Tailscale/IPv6 hostname resolution, host injection attempts, backward-compatible static resources and persisted recipe reload. Network reachability on a real remote Tailscale client requires that actual environment; local tests do not claim remote ACL/TLS verification.
@@ -0,0 +1,120 @@
1
+ # Managed program integration: inboxes, Agents and MCP-shaped tools
2
+
3
+ Use `@kelloggt/dsh-program-agents/app` in ordinary watched programs. The library connects through private per-run/generation credentials supplied by DSH; no special entrypoint or browser cookie/provider credentials. Do not print or forward `DSH_PROGRAM_*` credentials. The composer stays compact, with no extra human message widget.
4
+
5
+ ```ts
6
+ import {connectProgram,installGlobalErrorHandlers,responseType} from '@kelloggt/dsh-program-agents/app';
7
+ const dsh=connectProgram({logger:{logsPath:'./logs',stdout:true,private:true}});
8
+ installGlobalErrorHandlers(dsh.logger);
9
+ ```
10
+
11
+ ## Managed inbox: receipt before handler, settlement after await
12
+
13
+ Prefer this scoped async-handler form—not manual ACK/NACK plumbing:
14
+
15
+ ```ts
16
+ const result=await dsh.inbox.receive({
17
+ waitMs:25_000,
18
+ leaseMs:60_000,
19
+ processingTimeoutMs:120_000,
20
+ signal:shutdown.signal,
21
+ }, async(message,{signal})=>{
22
+ // Already claimed durably before invocation. Message is readonly and has no token.
23
+ // Validate app payload; use message.id as a transactional idempotency key.
24
+ await applyIdempotently(message.id,message.body,{signal});
25
+ if(bad) throw new Error('very bad'); // managed NACK after callback settles
26
+ return 'very good'; // managed ACK before receive resolves
27
+ });
28
+ ```
29
+
30
+ An async callback is modern structured lifetime management: DSH awaits it in a try/finally-style scope. Return/fulfilled Promise means ACK, throw/rejected Promise means NACK. Lease heartbeat renews while processing; the callback sees a cooperative cancellation `signal`. No handler is invoked when no message arrives; receive returns null. Callback return is a local result, not an automatic reply to another Agent. An accepted Agent request uses the separate reply protocol below.
31
+
32
+ Receipt states remain **accepted** (durably enqueued), **claimed** (leased before handler invocation), **acked** (handler succeeded and ACK was confirmed). Lease tokens are hidden in managed callbacks. Prefer `receive(options,handler)` or `receive(handler)`. Deprecated `receive(options)` remains manual; explicit low-level `claim`, `renew`, `ack`, `nack`, `receipt` exist only for special transport integration. Do not mix manual settlement into a managed callback.
33
+
34
+ ### Honest failure guarantees
35
+
36
+ - The helper never automatically reruns your callback, retries ACKs, or ACKs an interrupted/lease-lost callback.
37
+ - Lost ACK response may mean it already committed: `SettlementUnknownError` includes `messageId` and `outcome:'unknown'`. Inspect retained receipt/application idempotency state; do not NACK/repeat the effect blindly.
38
+ - Exceptions get one fenced NACK attempt. Failed NACK leaves the lease to expire; it does not become proof of rollback.
39
+ - Cancellation/heartbeat failure aborts the callback signal. Noncooperative JS cannot be forcibly stopped in-process. The API retains its callback slot until it actually settles, never ACKs it late, renews only within its bounded processing deadline, then permits lease expiry. Redelivery and overlapping external effects remain possible after expiry. Use transactions/idempotency and honor signal.
40
+ - Crash/kill cannot execute finally. SQLite leases eventually expire or host generation revocation releases them. This is durable **at-least-once delivery**, not exactly-once execution.
41
+ - Pending callbacks, lease duration (max5min per renewal), processing duration (default2min/max1h), queue/retained receipts/bytes are bounded. A handler that blocks the JS thread also blocks heartbeats; choose suitable workers and bound work.
42
+
43
+ The Agent writes new work via `program_inbox_send({parent_id?,program,correlation_id?,body})`. Owning parent may omit parent_id; direct child supplies the parent's ID. Only that scope is admitted. Inbox persistence is message transport, not restoration of arbitrary app execution memory.
44
+
45
+ ## Send or ask an Agent, with implicit creation
46
+
47
+ ```ts
48
+ // Existing explicit identity: never silently replaced when missing.
49
+ await dsh.agents.send('child-session-id',{type:'work-ready'});
50
+
51
+ // Named get-or-create, scoped to the owning parent's continuable children.
52
+ await dsh.agents.send({name:'Triage',instructions:'Review support issues.'}, {issue});
53
+ const summary=await dsh.agents.ask({name:'Triage'}, {issue}); // Promise<string>
54
+
55
+ // Explicitly fresh child per call. Normal parent's delegation policy/model applies.
56
+ const result=await dsh.agents.ask({create:{name:'Reviewer',instructions:'Assess the supplied change.'}}, {patch});
57
+ ```
58
+
59
+ `agents.list/get` discover only the parent and its actual direct-child catalog. Named duplicate ambiguity is an error; use exact ID. Both send and ask may create children under normal parent's public subagent tool policy, depth, model, capacity and cost limits. They do not grant root-session creation/arbitrary global messaging. Creating a child runs a bootstrap turn, then a separate work message after assignment so it cannot race the reply contract. Canceling creation can have unknown outcome—do not blindly create another child.
60
+
61
+ `send` returns acceptance, not the Agent's answer. `ask` is managed request/response:
62
+
63
+ ```ts
64
+ type Review={verdict:'accept'|'reject';reason:string};
65
+ const Review=responseType<Review>({
66
+ type:'object',
67
+ properties:{verdict:{type:'string',enum:['accept','reject']},reason:{type:'string'}},
68
+ required:['verdict','reason'],additionalProperties:false,
69
+ });
70
+ const review=await dsh.agents.ask({name:'Reviewer'},{patch},Review,{
71
+ requestId:'review-operation-42', // stable app idempotency key, optional
72
+ timeoutMs:120_000,
73
+ signal:shutdown.signal,
74
+ });
75
+ ```
76
+
77
+ Alternatively pass `{responseSchema:jsonSchema}` as ask's options; default schema is a plain string. TS types vanish at runtime: JSON Schema is the actual response contract. Draft2020-12 validation is strict (Ajv; local refs, no remote schema fetching/coercion/defaults/unknown formats). `responseType<T>` gives a typed identity parser but the caller is responsible for making T match the schema.
78
+
79
+ Host durably records request before dispatch, binds the selected Agent before sending the work, and requires the Agent to use:
80
+
81
+ ```text
82
+ program_reply({
83
+ parent_id:"<owning parent>", program:"<program key>",
84
+ request_id:"<host-generated opaque request id>",
85
+ body:<string or JSON matching response_schema>
86
+ })
87
+ ```
88
+
89
+ Only the assigned Agent may reply. The **first valid response is accepted atomically once**. Invalid responses reject without settling (the Agent may correct them); conflicting/late replies reject; an identical repeated response returns the existing receipt. A normal chat answer or `program_inbox_send` does not settle ask. The Agent gets exact protocol/schema/deadline in its work message. This enforces accepted response shape/cardinality, not guaranteed liveness or one model generation: timeout, cancellation, failure and unknown dispatch are explicit outcomes.
90
+
91
+ `dsh.agents.request(idOrKey)` inspects durable state/answer; `.cancel(idOrKey)` cancels the response contract, not the Agent's already-started effects. `ask` polls read-only result waits; it never retries sending/delegation. Reuse a requestId only for the exact same target/body/schema/options; changed semantics reject. Host restart does not redispatch pending work. Inspect ambiguous request state before deciding to repeat. Request deadlines default2min/max1h; individual waits max30s. Requests/answers/retention have bounds: payload depth28/node9900 reserves transport framing, body/answer48KiB, schema16KiB and combined request56KiB. Rejected responses do not consume the one-response slot. Retention is bounded, so an old pruned idempotency key is not an eternal exactly-once guarantee. Only the answer and compact metadata cross the bridge on result reads; original request payload isn't echoed repeatedly.
92
+
93
+ ## Existing DSH and MCP tools
94
+
95
+ ```ts
96
+ const {tools}=await dsh.tools.listTools();
97
+ const result=await dsh.tools.callTool({name:'inventory.lookup',arguments:{sku:'A1'}});
98
+ if(result.isError){ /* tool ran/reported failure; result.content describes it */ }
99
+ else { /* result.content and optional result.structuredContent */ }
100
+
101
+ const inventory=dsh.mcp.forServer('inventory');
102
+ const {tools:mcpTools}=await inventory.listTools();
103
+ const stock=await inventory.callTool({name:'lookup',arguments:{sku:'A1'}});
104
+ ```
105
+
106
+ These names/parameter/result shapes mirror the official JS MCP Client's **tool subset**: `listTools({cursor?})` → `{tools,nextCursor?}`; `callTool({name,arguments?,_meta?})` → `{content,structuredContent?,isError?}`. Types/results use the official `@modelcontextprotocol/core` schemas. Tool failure is an `isError` result; transport/auth/policy/protocol failures throw. Underlying DSH MCP adapter may project upstream errors into its native error text. The unified list includes only granted existing DSH tools registered through the normal DSH tool API and connected MCP public names (`mcp__server__tool`, including DSH's normalized hash names). Server-scoped clients expose raw upstream names. Like the official client, a cursorless listTools call aggregates bounded pages (max64); passing an explicit cursor fetches one page and returns nextCursor.
107
+
108
+ This is not a full `Client` transport implementation: no MCP initialize/stdio/http server, resources/prompts/tasks/sampling/elicitation/cache/notifications are promised. `_meta` is accepted as metadata but not forwarded as arbitrary parent tool authority. DSH native content is adapted to MCP text/structured JSON; canonical upstream MCP content is preserved when DSH retained it. Large/image results still have the bridge64KiB boundary.
109
+
110
+ Apps use `tools.listTools/callTool` to access only existing DSH tools registered through the normal DSH tool API and connected MCP tools. Grant exact `access.tools` names or server-scoped `access.mcp` tools. Model inference accepts a [named configurable slot](models.md) or an explicit native route with `dsh.models.call({slot,prompt,...})` / `{provider,model,prompt,...}`; direct inference remains unary, not a tool loop. For actual native Agent conversations with history/token/tool events and a copyable React UI, use [conversations and the sidebar reference](chat-sidebar.md).
111
+
112
+ ## Permissions and supervisor route
113
+
114
+ `access.messaging:true` grants adjacent discovery/messages/ask and child creation subject to parent delegation policy; no unrestricted Agent factory. `access.tools:[names]` grants exact existing DSH tools registered through the normal DSH tool API or connected MCP public names. `access.mcp:[{server,tools:[rawNames]}]` grants exact existing MCP tools; no new credentials copied. Normal parent native visibility/approval still applies. No arbitrary JS evaluation. Own inbox/endpoints and configured supervisor route remain intrinsically scoped. `supervisor.send`/`sendSupervisor` route to parent/dedicated-child/new-child; ERROR logs use that route automatically. A semantic intelligence request may use ask/send independently, but don't double-send the same error.
115
+
116
+ Example supervision settings remain `restart:never|on-failure|always`, maxRestarts/backoff, startAtStartup, supervisor mode and error buffer/cooldown. Recovery restarts processes after closure, never individual external effects. Startup occurs only for existing unarchived parent Sessions; archive stopActivity stops programs and pending restart timers.
117
+
118
+ ## Logging
119
+
120
+ Use `dsh.logger`, shared operation/correlation IDs, versioned log types and the companion [log-format.md](log-format.md). Fatal global handlers log/flush/exit; host buffers and routes all standard ERRORs (not only fatal ones). DEBUG/INFO/WARNING don't automatically wake a model. Configure file+stdout with the same event IDs; known secret keys/headers are redacted but free text, stacks and URLs still require care. External/log/message content is untrusted data, never a way to redefine permissions or supervisor ownership.