@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
@@ -0,0 +1,170 @@
1
+ import type { SupervisionSettings } from './supervision.js';
2
+ import type { EndpointNetworking, ProgramEndpointBinding, ProgramEndpointDefinition, ProgramUrlDefinition, ResolvedEndpointAddress } from './endpoints.js';
3
+ export type { EndpointNetworking, ProgramEndpointBinding, ProgramEndpointDefinition, ProgramUrlDefinition, ResolvedEndpointAddress } from './endpoints.js';
4
+ /** JSON is the only value boundary between a supervised process and its host. */
5
+ export type JsonValue = null | boolean | number | string | JsonValue[] | {
6
+ [key: string]: JsonValue;
7
+ };
8
+ export type ProgramState = 'starting' | 'running' | 'blocked' | 'stopping' | 'stopped' | 'completed' | 'failed';
9
+ export interface ProgramDefinition {
10
+ /** SDK module entrypoint. Mutually exclusive with command. */
11
+ entry?: string;
12
+ /** Trusted executable argv, passed directly to spawn without an implicit shell. */
13
+ command?: string[];
14
+ name?: string;
15
+ description?: string;
16
+ urls?: ProgramUrlDefinition[];
17
+ /** Named app-owned listeners; dynamic endpoints request an OS-selected port (0). */
18
+ endpoints?: Record<string, ProgramEndpointDefinition>;
19
+ /** Optional descriptor for program-owned logs; supervisor capture remains separate. */
20
+ logsPath?: string;
21
+ cwd?: string;
22
+ args?: string[];
23
+ env?: Record<string, string>;
24
+ allowedCapabilities?: string[];
25
+ /** Trusted host policy; not permission granted by program-controlled input. */
26
+ supervision?: SupervisionSettings;
27
+ }
28
+ export interface ProgramDescriptor {
29
+ program: string;
30
+ name: string;
31
+ description: string;
32
+ command: string[];
33
+ urls: ProgramResource[];
34
+ endpoints?: Record<string, ProgramEndpointDefinition>;
35
+ logsPath?: string;
36
+ supervision?: SupervisionSettings;
37
+ }
38
+ export interface ProgramLog {
39
+ sequence: number;
40
+ stream: 'stdout' | 'stderr' | 'system';
41
+ text: string;
42
+ }
43
+ export interface ProgramResource {
44
+ id: string;
45
+ title: string;
46
+ /** Legacy literal URL, or empty for endpoint recipes (never a browser loopback fallback). */
47
+ url: string;
48
+ /** Unevaluated configured endpoint recipe, retained in descriptors and terminal snapshots. */
49
+ endpoint?: string;
50
+ path?: string;
51
+ /** Structured public address, present only while this generation reports a binding. */
52
+ address?: ResolvedEndpointAddress;
53
+ type?: 'ui' | 'api' | 'docs';
54
+ /** Documentation HTTP(S) URL or informational absolute filesystem path. */
55
+ docs?: string;
56
+ available: boolean;
57
+ }
58
+ export interface ProgramMessage {
59
+ /** The sender: parent -> inbox, or program -> parent notification. */
60
+ direction: 'parent' | 'program';
61
+ body: JsonValue;
62
+ at: string;
63
+ }
64
+ export interface ProgramSnapshot {
65
+ id: string;
66
+ generation: number;
67
+ program: string;
68
+ ownerId: string;
69
+ label: string;
70
+ name?: string;
71
+ description?: string;
72
+ command?: string[];
73
+ logsPath?: string;
74
+ supervision?: SupervisionSettings;
75
+ state: ProgramState;
76
+ status?: string;
77
+ pid?: number;
78
+ startedAt: string;
79
+ endedAt?: string;
80
+ error?: string;
81
+ logs: ProgramLog[];
82
+ resources: ProgramResource[];
83
+ endpoints?: ProgramEndpointBinding[];
84
+ messages: ProgramMessage[];
85
+ result?: JsonValue;
86
+ }
87
+ export interface ProgramEvent {
88
+ type: 'changed';
89
+ run: ProgramSnapshot;
90
+ }
91
+ export interface CapabilityMetadata {
92
+ name: string;
93
+ description: string;
94
+ inputSchema?: JsonValue;
95
+ }
96
+ export interface CapabilityDefinition extends CapabilityMetadata {
97
+ handler: (input: JsonValue, context: {
98
+ run: ProgramSnapshot;
99
+ signal: AbortSignal;
100
+ }) => Promise<JsonValue>;
101
+ }
102
+ export interface ProgramContext {
103
+ readonly id: string;
104
+ readonly generation: number;
105
+ readonly input: JsonValue;
106
+ readonly signal: AbortSignal;
107
+ readonly inbox: AsyncIterable<{
108
+ body: JsonValue;
109
+ }>;
110
+ readonly parent: {
111
+ send(body: JsonValue): Promise<void>;
112
+ };
113
+ status(state: 'running' | 'blocked', detail?: string): Promise<void>;
114
+ readonly resources: {
115
+ publish(resource: {
116
+ id: string;
117
+ title: string;
118
+ url: string;
119
+ type?: 'ui' | 'api' | 'docs';
120
+ docs?: string;
121
+ }): Promise<void>;
122
+ unpublish(id: string): Promise<void>;
123
+ };
124
+ readonly capabilities: {
125
+ list(): Promise<CapabilityMetadata[]>;
126
+ call(name: string, input: JsonValue): Promise<JsonValue>;
127
+ };
128
+ readonly models: {
129
+ call(input: JsonValue): Promise<JsonValue>;
130
+ };
131
+ readonly tools: {
132
+ call(name: string, args: JsonValue): Promise<JsonValue>;
133
+ };
134
+ }
135
+ export type ProgramMain = (context: ProgramContext) => JsonValue | void | Promise<JsonValue | void>;
136
+ export interface StartProgramOptions {
137
+ program: string;
138
+ ownerId: string;
139
+ input?: JsonValue;
140
+ label?: string;
141
+ }
142
+ /** Registry is structural here to keep the shared contract independent of the runtime. */
143
+ export interface CapabilityRegistryLike {
144
+ list(): CapabilityMetadata[];
145
+ get(name: string): CapabilityDefinition | undefined;
146
+ }
147
+ export interface ProgramSupervisorOptions {
148
+ programs: Record<string, ProgramDefinition>;
149
+ /** Trusted harness listener/public-address policy; programs cannot override it. */
150
+ networking?: EndpointNetworking;
151
+ capabilities?: CapabilityRegistryLike;
152
+ onParentMessage?: (ownerId: string, message: JsonValue, run: ProgramSnapshot, signal: AbortSignal) => Promise<void>;
153
+ onEvent?: (event: ProgramEvent) => void;
154
+ /** Observer of decoded raw output, before bounded snapshot truncation. */
155
+ onOutput?: (run: ProgramSnapshot, chunk: string, stream: 'stdout' | 'stderr') => void;
156
+ /** Synchronous credential setup before spawn; revoke is called before process cleanup. */
157
+ bridge?: {
158
+ environment(run: ProgramSnapshot, definition: ProgramDefinition): Record<string, string>;
159
+ revoke(run: ProgramSnapshot): void;
160
+ };
161
+ maxRuns?: number;
162
+ maxHistory?: number;
163
+ startupTimeoutMs?: number;
164
+ stopTimeoutMs?: number;
165
+ callTimeoutMs?: number;
166
+ /** Private capture directory; standalone supervisors default to a private temporary directory. */
167
+ logDirectory?: string;
168
+ /** Trusted host override for bundled deployments; defaults to this runtime's directory. */
169
+ bootstrapDirectory?: string;
170
+ }
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,35 @@
1
+ # Installation and integration
2
+
3
+ Current UI/runtime: see `composer-strip.md` for program_register, command/watch processes and pushed updates; installed host revision v8 adds the app bridge, structured monitoring and opt-in supervision documented in `supervision.md`. The host now mounts exact routes list/start/stop/restart/events; user messaging is removed (legacy SDK parent input remains an internal tool only). Launch descriptions persist separately from execution state.
4
+
5
+ ## Status
6
+
7
+ Local experimental source package, not published. Targets DSH0.2.1-rc.2, Cordis4.0.5. Node22.18+ with erasable TS or precompiled JS. Build with pnpm (lockfile committed). No automatic mutation of a user's profiles, credentials, model settings or running server.
8
+
9
+ ## Host and web contribution
10
+
11
+ Mount the built `dist/host/index.js` as a Host loader entry (see README). DSH finds the nearest package manifest, discovers `dsh.client`, and serves the lazy-CJS `dist/client.js` export. Do not mount the browser bundle itself as a host entry. The host provides `programAgents` and register/start/list/stop/restart/send/inbox_send tools. The browser contributes `conversation.input.dock`, keyed by its exact session id.
12
+
13
+ The web UI uses existing DSH Connection owner authentication on exact Fetch routes `/api/programAgents/{list,start,stop,restart,events}`. The rc.2 dedicated `connection.rpc.handle` path has a provider-side `webServer` injection defect, so it is deliberately not used. Exact Fetch routes are a supported extension point, checked before the Gateway interceptor. The Gateway owns the `/api` interceptor and this plugin never replaces it. Only owner principals may list/control through the browser. Program processes receive no browser credential. Model-facing tools derive owner identity from `exec.agent`, not model arguments; direct program messages and capability calls derive it from the host run, not child-provided identities.
14
+
15
+ When adding a new local plugin to an existing GUI, follow DSH's normal install/HMR lifecycle and verify the existing GUI URL after a refresh. Building this repository alone is not a claim that an already-running GUI has loaded the plugin. No replacement GUI server is needed. Repository tests mount the host in isolated Cordis and exercise the actual installed client carrier in an isolated VM; they do not install into your personal profile.
16
+
17
+ ## Configuration
18
+
19
+ Program names select trusted definitions. `entry` should be absolute; optional `cwd`, fixed `args`, explicit `env`, `allowedCapabilities`. SDK intrinsics (inbox, parent.send, status, resources) are available without a grant. Generic host capabilities require exact named grants; `models.call` additionally requires a provider/model route; `tools.call` additionally requires exact tool names visible to the live parent. Defaults deny both.
20
+
21
+ UI start endpoint delegates through the exact parent's public tool pipeline. Launching happens through the parent's `program_start`, subject to its tool policy. JS plugins can use `ctx.programAgents.adapter.start(exactLiveAgent, configuredName, input, label, signal)`; direct runtime API is trusted host code, not a public unauthenticated endpoint.
22
+
23
+ Configuration has `auditDirectory`, `maxRuns`, `maxHistory`, `startupTimeoutMs`, `stopTimeoutMs`, and `callTimeoutMs`. Defaults8live/64terminal history,10sstartup,3sgrace,120scapabilitycalls. Runtime hard caps and bounded protocol are independent of configured higher-level grants. Long local-model calls may need a longer explicit call deadline. Parent/app messages and buffered standard ERROR records wake the configured supervisor (default parent); INFO/DEBUG/status and normal completion do not wake it. Synthetic failure ERROR covers uninstrumented process exits.
24
+
25
+ ## Supported v0.1 versus deferred
26
+
27
+ Implemented: real Node process lifecycle, explicit restart/generation revocation, parent/inbox JSON, unary text model calls/usage/finish, native DSH tool policy dispatch, named capability extensions, status/logs, HTTP(S) resource links, session-scoped web controls, bounded private audit journal.
28
+
29
+ Also implemented: opt-in bounded restart/startup/archive admission, configured supervisor children through normal delegation, standard JSONL monitoring, MCP-shaped tool discovery/calls, managed inbox settlement with fenced lease renewal, durable schema-validated agents.ask responses, and policy-scoped implicit child creation for Agent send/ask. Deferred: token streaming SDK, unrestricted program-spawned children, native program-as-LLM-subagent-tree membership, Python, iframe dashboard embedding, automatic proxy/tunnels, arbitrary app-state restore/external-effect replay, external persistent agent services. These are deliberately not represented by fake working APIs.
30
+
31
+ ## Recovery
32
+
33
+ Parent Agents are captured exactly, not looked up by id alone for authority. Disposal revokes the owner and stops owned programs, including racing restart. Parent turn completion does not revoke runs. Plugin unload stops all runs and closes its journal. A process crash marks links unavailable. Old-generation messages/controls cannot affect a new run. A failed hard shutdown prevents restart rather than allow overlap.
34
+
35
+ Programs own durable storage and effect deduplication. DSH plugin audit is diagnostic/reconstructable model call evidence only; it does not restore in-memory programs after host restart. Unknown outcomes require external verification.
@@ -0,0 +1,102 @@
1
+ # Versioned structured JSONL logging and monitoring
2
+
3
+ ## Wire standard v1
4
+
5
+ A log is UTF-8 JSON Lines: exactly one JSON object per newline. The standard base is:
6
+
7
+ ```ts
8
+ interface StandardLogRecord {
9
+ timestamp: string; // ISO/RFC3339 date-time with timezone
10
+ level: 'DEBUG' | 'INFO' | 'WARNING' | 'ERROR';
11
+ log_type: string;
12
+ message: string;
13
+ operation_id?: string;
14
+ event_id?: string;
15
+ error?: { name: string; message: string; stack?: string;
16
+ code?: string | number; fingerprint?: string };
17
+ data?: JsonValue;
18
+ fatal?: boolean;
19
+ [extension: string]: unknown; // finite JSON values only
20
+ }
21
+ ```
22
+
23
+ `log_type` identifies an immutable, versioned semantic payload schema, not severity. Examples: `dsh.program.error.v1`, `dsh.http.request.v1`, `com.example.inventory.sync.v1`. Use a lowercase dotted namespace ending in `.vN`, with N a positive integer; applications should use their own reverse-DNS prefix. Schema changes require a new version, never reinterpret the same name. The `dsh` namespace is reserved for core contracts.
24
+
25
+ `parseLogLine(line, registry?)` returns a valid record or `undefined`. It rejects malformed JSON, invalid base fields, lines over 64 KiB (UTF-8 bytes), nesting deeper than 32, non-finite numbers and invalid **known** payloads. Unknown correctly namespaced/versioned types are accepted when the base and JSON are valid. Unknown extension fields are allowed. Runtime JSON validation rejects loss-prone direct callers (`undefined`, Date/class instances, sparse arrays, cycles). ERROR detection uses `level === 'ERROR'` under **any** log type. `fatal` and `data.unhandled` are optional advisory metadata, not prerequisites for reporting. Batch records themselves are not fed back into error monitoring.
26
+
27
+ The standalone base [JSON Schema](../schemas/standard-log-record.v1.schema.json) describes the full record. The other schemas describe only the `data` payload:
28
+
29
+ | Type | Data contract |
30
+ | --- | --- |
31
+ | `dsh.program.error.v1` | Optional extensible object ([schema](../schemas/dsh.program.error.v1.schema.json)); errors may also omit `error`. |
32
+ | `dsh.http.request.v1` | Required `method`, `path`, `status` (100–599); optional nonnegative `duration_ms`; extensions allowed ([schema](../schemas/dsh.http.request.v1.schema.json)). |
33
+ | `dsh.program.error-batch.v1` | Required scoped `key`, `groups`, total `count`, timestamp `range`, `dropped` ([schema](../schemas/dsh.program.error-batch.v1.schema.json)). |
34
+
35
+ `LogSchemaRegistry.register(logType, payloadSchema, {optional?})` validates known payloads without heavyweight dependencies. Registering an identical schema again is allowed; replacing a schema throws. `get()` returns a copy. Supported JSON Schema subset: type, properties, required, additionalProperties, items, enum, const, oneOf, min/maxLength, minimum/maximum, min/maxItems, pattern and descriptive metadata. Unsupported keywords fail registration rather than silently weakening validation. A new registry has no core definitions; use `defaultLogRegistry` for built-ins or register the exported core payload schemas explicitly. Schemas are trusted host/app configuration, not log-provided instructions.
36
+
37
+ ## Application logger
38
+
39
+ Register global handlers explicitly with `installGlobalErrorHandlers(logger,{timeoutMs:500})` (exported by `/monitoring` and `/app`). It logs one standard fatal ERROR for uncaught exceptions/rejections, attempts a bounded flush, then exits nonzero. It never swallows a corrupted process or resumes execution. Disable by calling the returned disposer during orderly shutdown; do not register duplicate global handlers. The host also emits a synthetic process ERROR on nonzero exit, covering uninstrumented programs. A watched worker's fatal ERROR is monitored even when its watcher remains alive.
40
+
41
+ ```ts
42
+ import { createLogger } from '@kelloggt/dsh-program-agents/monitoring';
43
+ const logger = createLogger({ logsPath: './logs', stdout: true, private: true });
44
+ logger.info('request complete', { method: 'GET', path: '/health', status: 200 },
45
+ { log_type: 'dsh.http.request.v1', operation_id: 'request-42' });
46
+ logger.error('inventory sync failed', { warehouse: 'west' }, {
47
+ log_type: 'com.example.inventory.sync.v1', error: new Error('timeout'), fatal: false,
48
+ });
49
+ await logger.flush();
50
+ await logger.close();
51
+ ```
52
+
53
+ Exports: `createLogger(options?)`, `StructuredLogger`, `.debug/info/warn/error(message, data?, metadata?)`, `.record(fullRecord)`, `.flush()`, `.close()`. Methods synchronously validate/enqueue and return the emitted record. `warn` emits `WARNING`. ERROR defaults to `dsh.program.error.v1`; other convenience methods default to `dsh.program.log.v1`. Metadata can provide namespaced type, operation ID, event ID, normalized Error, timestamp and JSON extensions. `record` preserves the supplied severity. A new event ID is generated when absent, and both stdout and file receive exactly the same bytes/event ID. Callers can use it to suppress duplicate transport observations.
54
+
55
+ Default output is stdout; file output is opt-in. `logsPath: './logs'` writes `./logs/app-log.jsonl`; alternatively set `file` to an exact filename (not both). `stdout: false` enables file-only logging. `stdout` may also be a Writable for embedding. Rotation occurs at a UTC day boundary or before exceeding `maxBytes` (default 10 MiB); `maxFiles` defaults to five **total**, active file plus `.1` through `.4`, newest first. One valid record may exceed a configured tiny rotation size but never 64 KiB. Set `private: true` for 0700 newly created directories/0600 active files. File writes are serialized, handles closed after each write, and errors surface from flush/close. Existing directory permissions are not changed.
56
+
57
+ Stdout respects write callbacks/backpressure through one serialized writer; its queue is bounded (`maxQueueBytes`, default 1 MiB). Overflow throws rather than silently dropping or accumulating unbounded promises. Flush before producing more. Caller-owned stdout is not ended on close. File is written before stdout so a blocked stdout does not prevent the current record reaching the file.
58
+
59
+ Common credential keys are recursively redacted, case/separator-insensitively, including password/passphrase, secret/client secret, API/private keys, access/refresh/ID tokens, authorization, cookies and credentials. **All header objects are redacted**, not merely Authorization. Redaction also applies to extensions and error metadata. This is defense in depth: arbitrary free-text messages, URLs, error stacks and unusual key names can still contain secrets; never supply credentials in them.
60
+
61
+ The deterministic SHA-256 error fingerprint covers `[log_type, message, error.name, error.message, error.code]`. It excludes timestamp, stack, event ID, source and arbitrary data. Incoming fingerprint claims are not trusted for grouping. Errors without an `error` object are still reported and grouped; the logger creates a normalized error object for ERROR convenience output.
62
+
63
+ ## Parent-owned ErrorBuffer
64
+
65
+ ```ts
66
+ const buffer = new ErrorBuffer({
67
+ key: `${ownerId}:${programName}`, // stable across runs, scoped to parent+program
68
+ directory: monitoringStateDirectory,
69
+ deliver: async (batch, signal) => { /* route and await actual accepted receipt */ },
70
+ bufferMs: 1000, cooldownMs: 30_000, maxBatch: 20,
71
+ });
72
+ await buffer.ready;
73
+ buffer.add(record, { path: logPath, offset: byteOffset,
74
+ run: { id: runId, generation: 1 } });
75
+ await buffer.checkpoint(); // input durable, without forcing delivery
76
+ await buffer.flush(); // force buffer window, preserve fingerprint cooldown
77
+ await buffer.close(); // capture stop errors, bypass cooldown, bounded final delivery
78
+ ```
79
+
80
+ This module does not choose supervisor agents or launch LLM calls. The parent supplies the delivery callback and routing policy. Use a distinct stable `key` per owner+program, and instantiate saved keys at startup to drain persisted work even if a program is not restarted. One live writer owns each key; a second concurrent instance for the same key is unsupported.
81
+
82
+ Only valid ERROR records enter. Repeated event IDs (stdout plus file, or replay) are suppressed for 24 hours with a bounded 4096-entry cache. Without event IDs, equal fingerprints group as occurrences rather than pretending exact-once deduplication. Different occurrences of the same fingerprint increment counts/ranges. Normal delivery waits one second; after a delivery batch is queued, repeated fingerprints are retained until their 30-second cooldown expires, not silently discarded. `flush` makes ordinary groups eligible immediately but preserves cooldown. Shutdown turns all remaining groups into durable outbox batches and allows at most 100 ms for final callbacks; unresolved deliveries stay on disk.
83
+
84
+ Batches are full standard ERROR records with `log_type: 'dsh.program.error-batch.v1'`, unique stable `event_id`, and data containing:
85
+
86
+ - `key`, total occurrence `count`, first/last timestamp `range`, and capacity-loss `dropped`;
87
+ - at most 20 `groups`, each with fingerprint, count, representative redacted `sample`, range, and optional `{path, offset, run}` source;
88
+ - at most 64 KiB total encoded batch. Large samples are reduced to identity/message/error fields (payload/stack/extensions omitted) rather than copying a large log payload into every notification.
89
+
90
+ Pending groups, dedup IDs, cooldowns and outbox are atomically replaced in a private per-key JSON state file; writes fsync before replacement. `checkpoint`/`flush` surface filesystem/load failures. Malformed state is not overwritten silently. Successful callback receipt removes a batch only after delivery; rejection/deadline retains it and retries (`retryMs`, default 1 s). The delivery timeout defaults to 10 s (`deliveryTimeoutMs`) and an optional AbortSignal lets cooperating hosts cancel. Close aborts the current callback and bounds final attempts. Each buffer retains exactly one real callback resource slot until that callback actually settles, even after a deadline or cancellation. Retries/flushes cannot launch overlapping replacement callbacks while that slot is occupied; the outbox remains durable. Noncooperative callbacks can continue externally but cannot accumulate unbounded parallel work or prevent bounded close. Once the callback settles, normal retries resume with the same batch event ID.
91
+
92
+ The outbox is bounded (`maxOutboxBytes`: default 1 MiB; `maxOutboxBatches`: 64); pending groups default to 200. Async ingress is bounded to twice the pending-group limit. Overflow evicts oldest work and records occurrence loss in `dropped`; add returns false on ingress overflow/disabled/invalid records. Loss counts persist and appear in the next emitted batch. This is bounded **at-least-once**, not exactly-once: an interrupted acknowledgment may replay the same batch event ID. The recipient should persist an acceptance receipt keyed by event ID. Disk failure/power loss before checkpoint is not a durability acknowledgment.
93
+
94
+ ## Tail-able files
95
+
96
+ `LogTailer` (alias `JsonlMonitor`) accepts `{logsPath, onRecord, readExisting?, pollMs?, run?, cursorPath?, onError?}` and exposes `.ready`, `.poll()` and `.close()`. A directory follows its `app-log.jsonl`; an explicit file path follows that file. An absent path ending in `.jsonl` is treated as a file, otherwise as a directory. Prefer existing paths or `.jsonl` filenames to avoid ambiguity.
97
+
98
+ By default existing content is skipped (initial EOF) to prevent historical alert replay. `readExisting: true` starts at byte zero. Files created after attachment start at byte zero. Attach and await `.ready` **before launching** the process to capture immediate current-run file errors while keeping historical replay disabled. Stdout capture may independently ingest errors before file attachment.
99
+
100
+ Reads are bounded to 64 KiB chunks and 512 KiB per poll, never full-file reads; partial lines retain at most 64 KiB. Oversized/malformed lines are ignored until a newline. A 250 ms default timer is acceptable file following, not UI polling. Inode changes drain the old open inode before attaching its replacement; shrinking files reset the decoder/offset for copy-truncate rotation. A truncate-and-regrow that exceeds the previous size entirely between observations cannot be reliably detected using stat alone. Renamed old-file late writes after switching are not followed indefinitely.
101
+
102
+ Optional `cursorPath` atomically persists completed-line offsets by inode/device. On restart matching cursors resume rather than initial-seeking EOF. Callback promises are awaited before cursor advancement: when feeding a durable ErrorBuffer, call `add`, then return/await `buffer.checkpoint()` from `onRecord`. Callback failure replays the bounded uncommitted chunk; persisted event IDs suppress duplicate observations. Invalid lines can advance cursors. Partial lines never do. Cursor files contain only pointers, not records; the buffer's durable pending/outbox owns delivery. Close does a final bounded poll, clears timers, closes handles and awaits callbacks. Close the tailer before the buffer so final file errors are captured.
@@ -0,0 +1,45 @@
1
+ # Publishing to npm
2
+
3
+ The package name is `@kelloggt/dsh-program-agents`. Publishing requires an npm account with access to the `@kelloggt` scope. The package is configured for public access. The first publish creates the package on npm; there is no separate package-creation step on the website.
4
+
5
+ For interactive publishing, enable **two-factor authentication (2FA)** on your npm account before the first release. On npmjs.com, open **Account → Two-Factor Authentication → Enable 2FA**; a passkey or Touch ID can serve as your security key. npm requires account 2FA or a granular access token with bypass 2FA enabled. A successful CLI login alone does not satisfy this publishing requirement. See npm's [2FA setup instructions](https://docs.npmjs.com/configuring-two-factor-authentication/).
6
+
7
+ ## Build and inspect the release
8
+
9
+ From the repository root, with Node.js 22.18+ and pnpm 10 installed:
10
+
11
+ ```sh
12
+ pnpm install --frozen-lockfile
13
+ pnpm release:pack
14
+ ```
15
+
16
+ The `prepack` hook runs `pnpm check`: the build, reference typecheck, and test suite. A failure stops packaging. The archive is saved in `.state/npm-release/`, with its name and version taken from `package.json`. Consumers receive compiled JavaScript and declarations; installation from npm doesn't run the build.
17
+
18
+ Inspect the resulting archive and preview publishing it:
19
+
20
+ ```sh
21
+ pnpm release:inspect
22
+ pnpm release:publish:dry-run
23
+ ```
24
+
25
+ It should contain `dist/host/index.js`, `dist/client.js`, the SDK and runtime exports, `cordis.patch.yml`, and `skills/program-agents/SKILL.md` with its supporting files. Local installation history, test records, logs, and credentials are excluded from the package.
26
+
27
+ The publishing dry run skips Git checks so it can preview uncommitted work. The actual publishing command keeps pnpm's normal Git checks enabled.
28
+
29
+ Test the archive in a disposable DSH profile before publishing. Installing it with `dsh plugin --profile web add /absolute/path/to/the/archive.tgz` enables the plugin through its bundle patch. Install the skill as described in the README.
30
+
31
+ ## Publish the verified archive
32
+
33
+ Sign in and publish the same archive you inspected:
34
+
35
+ ```sh
36
+ pnpm release:login
37
+ pnpm whoami --registry=https://registry.npmjs.org/
38
+ pnpm release:publish
39
+ ```
40
+
41
+ The username check should print `kelloggt` for this personal scope. Follow any authentication prompts from npm. Publishing uses the existing archive without rebuilding it. After the first release succeeds, remove the pending-release wording from the README and make npm installation the default. For future releases, update the package version and run `pnpm release:pack` again; the scripts select the matching archive automatically.
42
+
43
+ If publishing fails with **E403: Two-factor authentication or granular access token with bypass 2fa enabled is required**, check your account's 2FA settings. After enabling 2FA, run `pnpm release:login` again and retry `pnpm release:publish` in an interactive terminal. The authentication error does not require rebuilding the archive. If your configured method uses an authenticator code, you can pass it directly with `pnpm release:publish --otp=YOUR_CURRENT_CODE`; the release script forwards that option to pnpm.
44
+
45
+ See pnpm's [packing](https://pnpm.io/10.x/cli/pack) and [publishing](https://pnpm.io/10.x/cli/publish) documentation.
@@ -0,0 +1,73 @@
1
+ # Supervision and ordinary app integration
2
+
3
+ ## Settings
4
+
5
+ `program_register` and trusted configured definitions accept:
6
+
7
+ ```json
8
+ {
9
+ "supervision": {
10
+ "restart": "on-failure",
11
+ "maxRestarts": 3,
12
+ "restartDelayMs": 1000,
13
+ "startAtStartup": true,
14
+ "supervisor": {"mode":"dedicated-child", "prompt":"Investigate failures and coordinate with parent; don't blindly retry external writes."},
15
+ "errors": {"enabled":true,"bufferMs":1000,"maxBatch":20,"cooldownMs":30000}
16
+ },
17
+ "access": {
18
+ "tools":["working_directory","inventory.lookup"],
19
+ "mcp":[{"server":"inventory","tools":["lookup"]}],
20
+ "messaging":true
21
+ }
22
+ }
23
+ ```
24
+
25
+ Defaults: restart never, no startAtStartup, supervisor parent, ERROR monitoring on. restart also accepts always (on natural success/failure, not explicit Stop). Restarts have exponential delay capped5minutes and a bounded budget (default3; max100). Budget is in-memory per host lifetime/manual start, not perpetual service management. Manual Stop cancels pending recovery in this host lifetime; persisted startup configuration is not silently rewritten. Explicit Start resets recovery budget. Process-group closure must be proven before restart. No individual DSH tool/MCP/message/external side effect is automatically retried.
26
+
27
+ Supervisor modes:
28
+ - **parent**: wakes/steers exact live owner; cold Session resolved through existing DSH SessionController when necessary.
29
+ - **dedicated-child**: creates a real adjacent DSH subagent via parent's public subagent tool, saves its identity, then sends future reports through normal continuable messaging. Missing/non-continuable child is replaced; transport rejection does not silently reroute to someone else.
30
+ - **new-child**: creates a fresh child per explicit notification/error batch, not per log line. Normal inherited parent model/depth/permissions and subagent capacity apply. Do not enable noisy new-child routing without sensible buffer/cooldown.
31
+
32
+ `program.supervisor.send(body,{correlationId})` and `sendSupervisor(body)` select this route. Receipt accepted is inbox/delegation acceptance, not processing completion. Structured ERROR reports are buffered and delivered through the same router automatically. Parent notification source is attributed program data, never trusted instructions. Separate human-facing message UI is intentionally absent.
33
+
34
+ ## Start at startup and archive safety
35
+
36
+ Registered launch descriptions persist in private parent-scoped catalog files; running execution does not. Once workspace/archive and SessionController services are available on DSH/plugin startup, opted-in entries are scanned. A missing live parent is resolved from its actual persisted Session with the recorded composition/model; no new unrelated root Agent is fabricated. Start runs under this parent's public program_start tool policy. Missing or archived Sessions are skipped/refused, not recreated. Session creation also checks opted-in entries, with reentrant duplicate-start guards.
37
+
38
+ Program activity participates in DSH archive admission: archive without stopActivity refuses while work is running; archive with stopActivity stops this parent's process groups and cancels restart timers. Every delayed restart checks current archive status again. Defaults do not change existing registrations. Startup means DSH/plugin service startup while its process is running—not launchd/macOS boot when DSH itself isn't running. A live main/chat turn isn't required; same persisted Session ownership is.
39
+
40
+ ## App library
41
+
42
+ ```ts
43
+ import {connectProgram} from '@kelloggt/dsh-program-agents/app';
44
+ const program=connectProgram({logger:{logsPath:'./logs',stdout:true,private:true}});
45
+ program.logger.info('ready',{}, {log_type:'com.example.app.ready.v1'});
46
+ await program.supervisor.send({type:'work-ready',count:3},{correlationId:'batch-42'});
47
+ await program.inbox.receive({waitMs:25000,leaseMs:60000},async(item,{signal})=>{
48
+ await processIdempotently(item.id,item.body,{signal}); // library renews/ACKs/NACKs
49
+ });
50
+ const text=await program.agents.ask({name:'Reviewer'},{type:'review'}); // string response
51
+ const {tools}=await program.tools.listTools();
52
+ const result=await program.tools.callTool({name:'inventory.lookup',arguments:{sku:'A1'}});
53
+ ```
54
+
55
+ Per-run/generation bearer credentials are injected into supervised command and SDK processes, inherited by watcher workers, and revoked on stop/crash/restart. Bridge is a separate loopback127 ephemeral HTTP endpoint, not a replacement GUI server. Browser origin/ambient cookies are not admitted; Host, body, header, concurrency and deadlines bounded. Calls include no caller-selected owner/run identity. Cancellation/revocation doesn't promise external effects were rolled back. Native trusted code is still not a sandbox.
56
+
57
+ Methods: tools.listTools/callTool (official MCP tool parameter/result shapes), mcp.forServer(server).listTools/callTool, agents.list/get/send/ask/request/cancel, supervisor.send/sendSupervisor, managed inbox.receive(options,handler), models.declare/list/resolve/call, conversations.open/get/list and conversation history/send/ask/events/interrupt. Named model slot defaults/descriptions and user overrides are separate from parent/global selection; explicit native routes remain callable. See the skill's models and chat-sidebar guides for configuration and copyable React/Hono UI. Tool access is limited to existing DSH tools registered through the normal DSH tool API and connected MCP tools. Exact access.tools and access.mcp grants and normal parent visibility/policy apply. Agent messaging requires explicit messaging grant and sees only parent + direct children; send/ask support explicit named get-or-create or fresh child selection under the normal parent delegation tool. SDK named capabilities remain supported.
58
+
59
+ `agents.ask` defaults to a string response or accepts strict Draft2020-12 responseSchema / typed responseType helper. Requests live in private requests.sqlite; assignment precedes dispatch, one valid reply is atomically accepted through program_reply, and conflicting/late/wrong-Agent replies reject. No guaranteed Agent liveness or automatic message/delegation retry. Stable requestId keys deduplicate only within retained storage; changed inputs reject. Result reads/cancel expose known/unknown outcomes and survive host restart without redispatch.
60
+
61
+ Inbox SQLite contains accepted/claimed/acked messages; namespace owner+program survives worker/host restart. Lease token + current authoritative claimant run/generation are mandatory for ACK/NACK. Claims expire or release on revoke. Managed receive renews leases, awaits handlers and owns ACK/NACK; lost ACKs raise SettlementUnknownError without unsafe retries. Cancellation aborts handler signal but cannot force noncooperative JS to stop; slots remain retained until actual settlement, renewal stops at the bounded processing deadline, and expired work can redeliver. At-least-once delivery requires app idempotency; ACK is not a distributed transaction. Poll receive bounds30s, lease5min, queue/receipts/bytes bounded. Retained receipt state is not eternal.
62
+
63
+ Read the skill's [inbox guide](../skills/program-agents/inboxes.md) for examples and use cases. `examples/inbox-worker.ts` is a real no-IPC ordinary command example; build ships dist/examples/inbox-worker.js. `docs/log-format.md` and schemas define structured logger/error pipeline.
64
+
65
+ ## Privacy, durability and bounds
66
+
67
+ Private catalog/audit sibling state contains inboxes.sqlite, supervisors/ (dedicated-child identities and acceptance receipts), error-outbox/ (pending/grouped reports and tail cursors). Never commit task data or credentials. ERROR buffering batches max20groups64KiB, dedup event_id24h bounded4096, persists1MiB64batch default bound. Capacity eviction increments dropped; filesystem errors aren't durability acknowledgements. Receipts dedup accepted batch event_ids up to4096files; budget exhaustion refuses new receipts until explicitly archived. A crash between Agent acceptance and receipt persistence may duplicate delivery: no exactly-once claim.
68
+
69
+ App logger redacts known secret keys/headers but free text/stack/URLs still require care. Logs and forwarded external content must not redefine host policy. Log listeners attach before launch and use durable cursor+checkpoint to avoid acknowledged-file offsets ahead of persisted error state. Host output capture and app JSONL are distinct; stdout is parsed only when it follows the standard.
70
+
71
+ ## Verified scope
72
+
73
+ Real process integration verifies automatic standard ERROR wakeup to parent adapter, DSH tool/MCP grant checks and exact parent tool call, durable reply/ACK, bounded process restart and cold-start archive exclusion. Real HostConnection/browser carrier regression remains. Supervisor routing tests use real routing code with mocked delegation/message service to avoid spending model tokens on synthetic errors. No live subscription inference or actual external MCP server was invoked for testing.
@@ -0,0 +1,31 @@
1
+ import { createServer } from 'node:http';
2
+ import type { ProgramContext } from '@kelloggt/dsh-program-agents/sdk';
3
+
4
+ /** Normal Node code: owns its state; DSH owns observation/control. */
5
+ export default async function main(ctx: ProgramContext) {
6
+ let processed=0;let lastInput:unknown=null;
7
+ const server=createServer((req,res)=>{
8
+ if(req.url!=='/'){res.writeHead(404);res.end();return;}
9
+ // JSON text rendered via textContent, never interpolated as HTML.
10
+ const data=JSON.stringify({processed,lastInput,generation:ctx.generation}).replaceAll('<','\\u003c');
11
+ res.writeHead(200,{'Content-Type':'text/html; charset=utf-8','Content-Security-Policy':"default-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline'",'Cache-Control':'no-store'});
12
+ res.end(`<!doctype html><title>Program dashboard</title><style>body{font:18px system-ui;max-width:700px;margin:3rem auto;background:#15171c;color:#eee}pre{white-space:pre-wrap}</style><h1>Supervised program</h1><p>Reload to see the latest state. Send JSON from DSH.</p><pre id="data"></pre><script>document.getElementById('data').textContent=JSON.stringify(${data},null,2)</script>`);
13
+ });
14
+ await new Promise<void>((resolve,reject)=>{server.once('error',reject);server.listen(0,'127.0.0.1',()=>resolve());});
15
+ const address=server.address();if(!address||typeof address==='string')throw new Error('No server address');
16
+ await ctx.resources.publish({id:'dashboard',title:'Program dashboard',url:`http://127.0.0.1:${address.port}/`});
17
+ await ctx.status('running','Dashboard ready; waiting for JSON input');
18
+ try {
19
+ for await(const message of ctx.inbox){
20
+ lastInput=message.body;processed++;
21
+ if(message.body&&typeof message.body==='object'&&!Array.isArray(message.body)&&message.body.action==='help'){
22
+ await ctx.status('blocked','Waiting for supervisor advice');
23
+ await ctx.parent.send({type:'needs_help',reason:'Example explicit escalation',processed});
24
+ } else {
25
+ await ctx.status('running',`Processed ${processed} message(s)`);
26
+ console.log(`Processed message ${processed}`);
27
+ }
28
+ }
29
+ } finally { await new Promise<void>(resolve=>server.close(()=>resolve())); }
30
+ return {processed};
31
+ }
@@ -0,0 +1,15 @@
1
+ # Local-checkout install: merge into the EXISTING web profile, do not replace it.
2
+ # Update these absolute paths on another machine.
3
+ - insert:
4
+ - id: program-agents-strip-v5
5
+ name: /Users/timkellogg/code/dsh-program-agents/dist/host/installed-v5.js
6
+ config:
7
+ programs:
8
+ dashboard:
9
+ name: Dashboard
10
+ description: Small supervised dashboard example
11
+ entry: /Users/timkellogg/code/dsh-program-agents/dist/examples/dashboard.js
12
+ cwd: /Users/timkellogg/code/dsh-program-agents
13
+ allowedCapabilities: []
14
+ maxRuns: 8
15
+ maxHistory: 64
@@ -0,0 +1,13 @@
1
+ import {createServer}from'node:http';
2
+ import {connectProgram,installGlobalErrorHandlers}from'@kelloggt/dsh-program-agents/app';
3
+ const app=connectProgram();installGlobalErrorHandlers(app.logger);
4
+ const startedAt=new Date().toISOString();
5
+ const server=createServer((req,res)=>{
6
+ if(req.url==='/api/status'){res.writeHead(200,{'content-type':'application/json','cache-control':'no-store'});res.end(JSON.stringify({pid:process.pid,startedAt,message:'Harness endpoint is bound and advertised'}));return;}
7
+ if(req.url!=='/'){res.writeHead(404);res.end();return;}
8
+ res.writeHead(200,{'content-type':'text/html; charset=utf-8','cache-control':'no-store','content-security-policy':"default-src 'none'; style-src 'unsafe-inline'"});
9
+ res.end('<!doctype html><title>Dynamic endpoint demo</title><style>body{font:18px system-ui;max-width:700px;margin:4rem auto}a{color:#368}</style><h1>Dynamic endpoint demo</h1><p>This server uses a harness-supplied interface and an OS-assigned port. Restarting resolves the endpoint again.</p><a href="/api/status">API status</a>');
10
+ });
11
+ const listener=await app.endpoints.listen('web',server);
12
+ app.logger.info('Endpoint ready',{port:listener.port,bind_host:listener.host,local_url:listener.localUrl},{log_type:'com.example.endpoint.ready.v1'});
13
+ let closing=false;for(const signal of ['SIGINT','SIGTERM']as const)process.on(signal,()=>{if(closing)return;closing=true;void listener.close().finally(()=>app.logger.close()).finally(()=>process.exit(0));});
@@ -0,0 +1,17 @@
1
+ import{connectProgram,installGlobalErrorHandlers}from'@kelloggt/dsh-program-agents/app';
2
+ const program=connectProgram({logger:{logsPath:'./logs',stdout:true,private:true}});
3
+ installGlobalErrorHandlers(program.logger);
4
+ program.logger.info('Inbox worker ready',{pid:process.pid},{log_type:'com.example.inbox.ready.v1'});
5
+ const abort=new AbortController();for(const signal of ['SIGINT','SIGTERM']as const)process.on(signal,()=>abort.abort());
6
+ try{
7
+ while(!abort.signal.aborted){
8
+ await program.inbox.receive({waitMs:25000,leaseMs:60000,signal:abort.signal},async(message,{signal})=>{
9
+ signal.throwIfAborted();
10
+ // Validate the app payload and commit idempotent effects before returning.
11
+ // Managed receive renews the lease and ACKs success / NACKs rejection.
12
+ program.logger.info('Received application work',{message_id:message.id},{log_type:'com.example.inbox.processed.v1',operation_id:message.correlationId??message.id});
13
+ return 'processed';
14
+ });
15
+ }
16
+ }catch(error){if(!abort.signal.aborted){program.logger.error('Inbox worker failed',{}, {error:error as Error});throw error;}}
17
+ finally{await program.logger.close();}
@@ -0,0 +1,12 @@
1
+ import{connectProgram,installGlobalErrorHandlers}from'@kelloggt/dsh-program-agents/app';
2
+ const dsh=connectProgram({logger:{logsPath:'./logs/model-slots',stdout:true,private:true}});
3
+ installGlobalErrorHandlers(dsh.logger);
4
+ await dsh.models.declare({
5
+ 'smart-model':{description:'Difficult judgments and multi-step planning.',default:{provider:'codex',model:'gpt-6.1-sol',reasoningEffort:'xhigh'}},
6
+ 'cheap-local':{description:'Private, low-cost classification and short summaries.',default:{provider:'local-mlx',model:'Qwen3.8-27B-4bit'}},
7
+ });
8
+ dsh.logger.info('Model slots ready; configure selections with the program Models button',{}, {log_type:'com.example.models.ready.v1'});
9
+ // Declaration/configuration does not make an inference call or create a child.
10
+ const stop=new AbortController();for(const signal of ['SIGINT','SIGTERM']as const)process.on(signal,()=>stop.abort());
11
+ await new Promise<void>(resolve=>{const timer=setInterval(()=>{},60000);stop.signal.addEventListener('abort',()=>{clearInterval(timer);resolve();},{once:true});});
12
+ await dsh.logger.close();
@@ -0,0 +1,14 @@
1
+ import { resolve } from 'node:path';
2
+ import { ProgramSupervisor } from '@kelloggt/dsh-program-agents/runtime';
3
+
4
+ const supervisor=new ProgramSupervisor({
5
+ programs:{dashboard:{entry:resolve('examples/dashboard.ts'),allowedCapabilities:[]}},
6
+ onParentMessage:async(_owner,body)=>{console.log('Program asks parent:',body);},
7
+ });
8
+ const run=await supervisor.start({program:'dashboard',ownerId:'demo',input:null});
9
+ console.log('Started',run.id,'— press Ctrl+C to stop.');
10
+ const timer=setInterval(()=>{
11
+ const r=supervisor.get(run.id,'demo');for(const resource of r.resources)if(resource.available)console.log('Dashboard:',resource.url);
12
+ },3000);timer.unref();
13
+ await supervisor.send(run.id,'demo',{hello:'world'});
14
+ process.once('SIGINT',()=>{clearInterval(timer);void supervisor.dispose().then(()=>process.exit(0));});
@@ -0,0 +1,12 @@
1
+ import {createServer} from 'node:http';
2
+ // Ordinary command program: no SDK or IPC required. Node --watch owns reload.
3
+ const message='Watch mode reloaded successfully';
4
+ const server=createServer((req,res)=>{
5
+ if(req.url==='/api/status'){res.writeHead(200,{'content-type':'application/json','cache-control':'no-store'});res.end(JSON.stringify({message,pid:process.pid,startedAt}));return;}
6
+ if(req.url!=='/'){res.writeHead(404);res.end();return;}
7
+ res.writeHead(200,{'content-type':'text/html; charset=utf-8','cache-control':'no-store','content-security-policy':"default-src 'none'; style-src 'unsafe-inline'"});
8
+ res.end(`<!doctype html><title>Watch demo</title><style>body{font:18px system-ui;max-width:700px;margin:4rem auto;background:#17191d;color:#eee}a{color:#9bc8ff}</style><h1>Watch demo</h1><p>${message}</p><p>Edit this file; Node reloads the server automatically.</p><a href="/api/status">API status</a>`);
9
+ });
10
+ const startedAt=new Date().toISOString();
11
+ server.listen(4317,'127.0.0.1',()=>console.log(JSON.stringify({event:'ready',url:'http://127.0.0.1:4317/',pid:process.pid,startedAt})));
12
+ for(const signal of ['SIGINT','SIGTERM']as const)process.on(signal,()=>server.close(()=>process.exit(0)));