@gobing-ai/ts-runtime 0.4.8 → 0.4.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +62 -14
- package/dist/context.js +2 -2
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/process-executor.d.ts +58 -7
- package/dist/process-executor.d.ts.map +1 -1
- package/dist/process-executor.js +74 -14
- package/dist/process-registry.d.ts +143 -0
- package/dist/process-registry.d.ts.map +1 -0
- package/dist/process-registry.js +146 -0
- package/dist/runtime-node-bun.js +2 -2
- package/package.json +4 -4
- package/src/context.ts +2 -2
- package/src/index.ts +14 -2
- package/src/process-executor.ts +135 -12
- package/src/process-registry.ts +263 -0
- package/src/runtime-node-bun.ts +2 -2
package/README.md
CHANGED
|
@@ -8,15 +8,14 @@ and Cloudflare Workers through a factory pattern that auto-detects the runtime.
|
|
|
8
8
|
|
|
9
9
|
## Overview
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
11
|
**Key abstractions:**
|
|
14
12
|
|
|
15
13
|
| Concept | Interface | Bun/Node impl | Cloudflare impl |
|
|
16
|
-
|
|
14
|
+
| --------- | ----------- | --------------- | ----------------- |
|
|
17
15
|
| Runtime factory | `RuntimeFactory` → `loadRuntimeFactory()` | `nodeBunFactory` | `cloudflareWorkersFactory` |
|
|
18
16
|
| File system | `FileSystem` | `createNodeFileSystem()` (sync `node:fs`) | `createCfFileSystem()` (stub) |
|
|
19
|
-
| Process execution | `ProcessExecutor` (
|
|
17
|
+
| Process execution | `ProcessExecutor` (interface) | `NodeProcessExecutor` — `run()` via execa, `runStreaming()` via `Bun.spawn` | throws |
|
|
18
|
+
| Process registry | `ProcessRegistry` (optional) | `InMemoryProcessRegistry` / `createInMemoryProcessRegistry()` — list + subscribe all spawns | n/a (no process exec) |
|
|
20
19
|
| SQL database | `createDbAdapter(config)` → `DbAdapter` | Bun SQLite via `@gobing-ai/ts-db` (optional peer) | throws `D1NotConfiguredError` (D1 round pending) |
|
|
21
20
|
| Configuration | `Config` (Zod schema) | YAML + env vars | CONFIG_YAML blob + env vars |
|
|
22
21
|
| Context | `RuntimeContext` | service locator | service locator |
|
|
@@ -88,10 +87,33 @@ classDiagram
|
|
|
88
87
|
}
|
|
89
88
|
|
|
90
89
|
class ProcessExecutor {
|
|
90
|
+
<<interface>>
|
|
91
91
|
+run(options) Promise~ProcessResult~
|
|
92
92
|
+runStreaming(options) PipeProcess
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
+
class NodeProcessExecutor {
|
|
96
|
+
+run(options) Promise~ProcessResult~
|
|
97
|
+
+runStreaming(options) PipeProcess
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
class ProcessRegistry {
|
|
101
|
+
<<interface>>
|
|
102
|
+
+listExecutions(filter?) ProcessExecution[]
|
|
103
|
+
+getExecution(id) ProcessExecution?
|
|
104
|
+
+subscribe(listener) unsub
|
|
105
|
+
+begin(input) string
|
|
106
|
+
+update(id, patch) void
|
|
107
|
+
+complete(id, update?) void
|
|
108
|
+
+clear() void
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
class InMemoryProcessRegistry {
|
|
112
|
+
+listExecutions(filter?) ProcessExecution[]
|
|
113
|
+
+begin(input) string
|
|
114
|
+
+complete(id, update?) void
|
|
115
|
+
}
|
|
116
|
+
|
|
95
117
|
class PipeProcess {
|
|
96
118
|
<<interface>>
|
|
97
119
|
+pid number?
|
|
@@ -133,8 +155,11 @@ classDiagram
|
|
|
133
155
|
FileSystem <|.. createNodeFileSystem : implements
|
|
134
156
|
FileSystem <|.. createCfFileSystem : implements
|
|
135
157
|
ProcessExecutor --> PipeProcess : creates
|
|
158
|
+
ProcessExecutor <|.. NodeProcessExecutor : implements
|
|
159
|
+
ProcessRegistry <|.. InMemoryProcessRegistry : implements
|
|
160
|
+
NodeProcessExecutor ..> ProcessRegistry : optional registry
|
|
136
161
|
nodeBunFactory --> createNodeFileSystem : creates
|
|
137
|
-
nodeBunFactory -->
|
|
162
|
+
nodeBunFactory --> NodeProcessExecutor : creates
|
|
138
163
|
cloudflareWorkersFactory --> createCfFileSystem : creates
|
|
139
164
|
RuntimeContext --> FileSystem : "fileSystem"
|
|
140
165
|
RuntimeContext --> Config : "config"
|
|
@@ -232,7 +257,7 @@ There are three ways a config file can name its schema. **Prefer the bundled pac
|
|
|
232
257
|
the most secure and performant default:
|
|
233
258
|
|
|
234
259
|
| Style | Example | Resolution | Notes |
|
|
235
|
-
|
|
260
|
+
| ------- | --------- | ------------ | ------- |
|
|
236
261
|
| **Package specifier** (recommended) | `$schema: "@gobing-ai/ts-rule-engine/schemas/rule-file.schema.json"` | Resolved through `node_modules` via the module resolver, then read from disk | No network, no path guessing; survives hoisting/pnpm/monorepo layouts. Schemas ship in each package's `schemas/` (declared in `files`). **Quote the value** — YAML treats a leading `@` as reserved. |
|
|
237
262
|
| Relative path | `$schema: ./schemas/rule-file.schema.json` | Resolved against the config file's directory | Fine for repo-local schemas; brittle if the config moves. |
|
|
238
263
|
| Remote URL | `$schema: https://json-schema.org/.../rule-file.schema.json` | Fetched over HTTP(S) — **off by default** | SSRF/DoS surface for third-party configs. Opt in with `{ allowRemote: true }` (5s timeout) or supply your own `fetch`. |
|
|
@@ -251,7 +276,6 @@ await loadStructuredConfig('rules.yaml', { fetch: myFetch }); // or in
|
|
|
251
276
|
> `node_modules` keeps validation entirely local — no outbound request, no dependency on a schema host's
|
|
252
277
|
> availability, and no chance for a malicious config to point validation at an internal URL.
|
|
253
278
|
|
|
254
|
-
|
|
255
279
|
### 5. Path utilities
|
|
256
280
|
|
|
257
281
|
Runtime-portable path math that avoids `node:path` so the same logic works on Cloudflare Workers
|
|
@@ -284,6 +308,7 @@ The `./extension` subpath exposes a generic, domain-agnostic extension/capabilit
|
|
|
284
308
|
with origin metadata, a trust-gated extension loader, and a path guard — without knowing anything
|
|
285
309
|
about evaluators, resolvers, actions, or guards. Each engine owns its domain-specific kinds,
|
|
286
310
|
schemas, error types, and override semantics.
|
|
311
|
+
|
|
287
312
|
#### Capability registry
|
|
288
313
|
|
|
289
314
|
```ts
|
|
@@ -462,6 +487,7 @@ process.on('SIGTERM', async () => {
|
|
|
462
487
|
await ctx.dispose();
|
|
463
488
|
process.exit(0);
|
|
464
489
|
});
|
|
490
|
+
|
|
465
491
|
```
|
|
466
492
|
|
|
467
493
|
## Usage
|
|
@@ -513,6 +539,7 @@ const config = buildConfigFromObject({
|
|
|
513
539
|
database: { url: ':memory:' },
|
|
514
540
|
});
|
|
515
541
|
```
|
|
542
|
+
|
|
516
543
|
// From YAML file
|
|
517
544
|
const config = buildConfigFromYaml(yamlText);
|
|
518
545
|
|
|
@@ -521,16 +548,19 @@ const config = buildConfigFromObject({
|
|
|
521
548
|
app: { name: 'api', env: 'production', port: 3000 },
|
|
522
549
|
database: { url: ':memory:' },
|
|
523
550
|
});
|
|
551
|
+
|
|
524
552
|
```
|
|
525
553
|
|
|
526
554
|
### Process execution
|
|
527
555
|
|
|
528
|
-
`ProcessExecutor` is
|
|
556
|
+
`ProcessExecutor` is the canonical interface for process execution. `NodeProcessExecutor` is the concrete implementation wrapping `execa` (buffered) and `Bun.spawn` (streaming):
|
|
529
557
|
|
|
530
558
|
```ts
|
|
531
|
-
import {
|
|
559
|
+
import { NodeProcessExecutor, createInMemoryProcessRegistry } from '@gobing-ai/ts-runtime';
|
|
532
560
|
|
|
533
|
-
|
|
561
|
+
// Optional: shared registry so every run/runStreaming is listable (spur#0264 / M1).
|
|
562
|
+
const registry = createInMemoryProcessRegistry();
|
|
563
|
+
const exec = new NodeProcessExecutor({ defaultTimeout: 30_000, registry });
|
|
534
564
|
|
|
535
565
|
// Buffered — captures stdout/stderr, no throw on non-zero
|
|
536
566
|
const result = await exec.run({
|
|
@@ -538,27 +568,45 @@ const result = await exec.run({
|
|
|
538
568
|
args: ['status', '--short'],
|
|
539
569
|
cwd: '/path/to/repo',
|
|
540
570
|
rejectOnError: true,
|
|
571
|
+
// Optional registry metadata (defaults: run → source 'one-shot')
|
|
572
|
+
label: 'git.status',
|
|
541
573
|
});
|
|
542
574
|
|
|
543
575
|
console.log(result.stdout); // 'M src/index.ts\n'
|
|
544
576
|
console.log(`Duration: ${result.durationMs}ms`);
|
|
545
577
|
|
|
546
578
|
// Streaming — interactive subprocess with stdin control
|
|
547
|
-
const proc = exec.runStreaming({
|
|
579
|
+
const proc = exec.runStreaming({
|
|
580
|
+
command: 'cat',
|
|
581
|
+
source: 'supervisor', // tag supervised agent loops
|
|
582
|
+
agentId: 'alpha-claude',
|
|
583
|
+
});
|
|
548
584
|
proc.writeStdin('hello\n');
|
|
549
585
|
proc.endStdin();
|
|
550
586
|
const exitCode = await proc.exited; // 0
|
|
587
|
+
|
|
588
|
+
// Watch list snapshot + live subscription
|
|
589
|
+
console.log(registry.listExecutions());
|
|
590
|
+
const unsub = registry.subscribe((event) => {
|
|
591
|
+
if (event.type === 'started' || event.type === 'exited') {
|
|
592
|
+
console.log(event.type, event.execution.id, event.execution.command);
|
|
593
|
+
}
|
|
594
|
+
});
|
|
595
|
+
unsub();
|
|
551
596
|
```
|
|
552
597
|
|
|
553
598
|
`rejectOnError: true` throws on non-zero exits. `OutputPolicy` controls
|
|
554
599
|
buffered vs streamed output. `ProcessOptions` supports timeout, env, cwd,
|
|
555
|
-
maxOutput, and
|
|
600
|
+
maxOutput, forceBuffered, and optional registry fields (`source`, `teamId`,
|
|
601
|
+
`agentId`). Inject the same `ProcessRegistry` into every executor that should
|
|
602
|
+
appear in one watch list; without a registry, behavior is unchanged.
|
|
556
603
|
|
|
557
604
|
Cloudflare Workers do not expose process execution; check
|
|
558
605
|
`factory.capabilities.hasProcessExecution` first.
|
|
559
606
|
|
|
560
|
-
|
|
561
|
-
are kept as deprecated backward-compatible wrappers.
|
|
607
|
+
The `ProcessExecutor` const (value alias for `NodeProcessExecutor`), `BunSyncProcessExecutor`,
|
|
608
|
+
and `BunPipeProcessSpawner` are kept as deprecated backward-compatible wrappers. Prefer
|
|
609
|
+
`NodeProcessExecutor` or `nodeBunFactory.createProcessExecutor()` in new code.
|
|
562
610
|
|
|
563
611
|
### SpanContext (for telemetry)
|
|
564
612
|
|
package/dist/context.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { buildConfigFromObject } from './config.js';
|
|
2
2
|
import { createNodeFileSystem } from './file-system-node.js';
|
|
3
3
|
import { loadRuntimeFactory } from './platform.js';
|
|
4
|
-
import {
|
|
4
|
+
import { nodeBunFactory } from './runtime-node-bun.js';
|
|
5
5
|
/** Injectable service container scoped to a runtime environment (process, request, event, or test). */
|
|
6
6
|
export class RuntimeContext {
|
|
7
7
|
scope;
|
|
@@ -22,7 +22,7 @@ export class RuntimeContext {
|
|
|
22
22
|
this.register('config', (options.services?.config ?? buildConfigFromObject({})));
|
|
23
23
|
this.register('fileSystem', (options.services?.fileSystem ?? createNodeFileSystem()));
|
|
24
24
|
if (this.capabilities.hasProcessExecution && options.services?.processExecutor === undefined) {
|
|
25
|
-
this.register('processExecutor',
|
|
25
|
+
this.register('processExecutor', nodeBunFactory.createProcessExecutor());
|
|
26
26
|
}
|
|
27
27
|
for (const [key, value] of Object.entries(options.services ?? {})) {
|
|
28
28
|
if (value !== undefined) {
|
package/dist/index.d.ts
CHANGED
|
@@ -9,13 +9,15 @@ export { atomicWriteFile, atomicWriteJson, createLogStream, ensureDirForFile, re
|
|
|
9
9
|
export * from './path';
|
|
10
10
|
export { _resetRuntimeFactory, isCloudflareWorkerRuntime, loadRuntimeFactory } from './platform';
|
|
11
11
|
export type { OutputPolicy, PipeProcess, PipeProcessOptions, ProcessEventDetail, ProcessEventSink, ProcessEvents, ProcessExecutorConfig, ProcessExitReason, ProcessOptions, ProcessResult, ProcessSignal, TracerPort, } from './process-executor';
|
|
12
|
-
export { ProcessExecutor } from './process-executor';
|
|
12
|
+
export { NodeProcessExecutor, ProcessExecutor } from './process-executor';
|
|
13
|
+
export type { InMemoryProcessRegistryOptions, ProcessExecution, ProcessExecutionBegin, ProcessExecutionComplete, ProcessExecutionFilter, ProcessExecutionSource, ProcessExecutionStatus, ProcessRegistry, ProcessRegistryEvent, } from './process-registry';
|
|
14
|
+
export { createInMemoryProcessRegistry, InMemoryProcessRegistry } from './process-registry';
|
|
13
15
|
export { cloudflareWorkersFactory } from './runtime-cf';
|
|
14
16
|
export type { RuntimeFactory } from './runtime-factory';
|
|
15
17
|
export { _resetNodeFileSystem, nodeBunFactory } from './runtime-node-bun';
|
|
16
18
|
export * from './schema-validation';
|
|
17
19
|
export * from './types';
|
|
18
|
-
export { BunPipeProcessSpawner, BunSyncProcessExecutor
|
|
20
|
+
export { BunPipeProcessSpawner, BunSyncProcessExecutor } from './process-executor';
|
|
19
21
|
/**
|
|
20
22
|
* @deprecated Use {@link ProcessExecutor} directly for async execution.
|
|
21
23
|
* Use `Bun.spawnSync` or `child_process.spawnSync` for sync.
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAC;AACzB,cAAc,WAAW,CAAC;AAC1B,OAAO,EAAE,+BAA+B,EAAE,MAAM,WAAW,CAAC;AAC5D,OAAO,EAAE,oBAAoB,EAAE,yBAAyB,EAAE,MAAM,aAAa,CAAC;AAC9E,YAAY,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAC1D,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAE,oBAAoB,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAC3E,OAAO,EACH,eAAe,EACf,eAAe,EACf,eAAe,EACf,gBAAgB,EAChB,YAAY,EACZ,OAAO,EACP,aAAa,GAChB,MAAM,MAAM,CAAC;AACd,cAAc,QAAQ,CAAC;AACvB,OAAO,EAAE,oBAAoB,EAAE,yBAAyB,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AACjG,YAAY,EACR,YAAY,EACZ,WAAW,EACX,kBAAkB,EAClB,kBAAkB,EAClB,gBAAgB,EAChB,aAAa,EACb,qBAAqB,EACrB,iBAAiB,EACjB,cAAc,EACd,aAAa,EACb,aAAa,EACb,UAAU,GACb,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAC;AACzB,cAAc,WAAW,CAAC;AAC1B,OAAO,EAAE,+BAA+B,EAAE,MAAM,WAAW,CAAC;AAC5D,OAAO,EAAE,oBAAoB,EAAE,yBAAyB,EAAE,MAAM,aAAa,CAAC;AAC9E,YAAY,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAC1D,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAE,oBAAoB,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAC3E,OAAO,EACH,eAAe,EACf,eAAe,EACf,eAAe,EACf,gBAAgB,EAChB,YAAY,EACZ,OAAO,EACP,aAAa,GAChB,MAAM,MAAM,CAAC;AACd,cAAc,QAAQ,CAAC;AACvB,OAAO,EAAE,oBAAoB,EAAE,yBAAyB,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AACjG,YAAY,EACR,YAAY,EACZ,WAAW,EACX,kBAAkB,EAClB,kBAAkB,EAClB,gBAAgB,EAChB,aAAa,EACb,qBAAqB,EACrB,iBAAiB,EACjB,cAAc,EACd,aAAa,EACb,aAAa,EACb,UAAU,GACb,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,mBAAmB,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAC1E,YAAY,EACR,8BAA8B,EAC9B,gBAAgB,EAChB,qBAAqB,EACrB,wBAAwB,EACxB,sBAAsB,EACtB,sBAAsB,EACtB,sBAAsB,EACtB,eAAe,EACf,oBAAoB,GACvB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,6BAA6B,EAAE,uBAAuB,EAAE,MAAM,oBAAoB,CAAC;AAC5F,OAAO,EAAE,wBAAwB,EAAE,MAAM,cAAc,CAAC;AACxD,YAAY,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,oBAAoB,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAC1E,cAAc,qBAAqB,CAAC;AACpC,cAAc,SAAS,CAAC;AAIxB,OAAO,EAAE,qBAAqB,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAC;AAEnF;;;GAGG;AACH,MAAM,MAAM,mBAAmB,GAAG,YAAY,CAAC,cAAc,oBAAoB,EAAE,sBAAsB,CAAC,CAAC;AAE3G;;GAEG;AACH,MAAM,MAAM,kBAAkB,GAAG,YAAY,CAAC,cAAc,oBAAoB,EAAE,qBAAqB,CAAC,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -7,10 +7,11 @@ export { createNodeFileSystem, findProjectRoot } from './file-system-node.js';
|
|
|
7
7
|
export { atomicWriteFile, atomicWriteJson, createLogStream, ensureDirForFile, readJsonFile, walkDir, writeJsonFile, } from './fs.js';
|
|
8
8
|
export * from './path.js';
|
|
9
9
|
export { _resetRuntimeFactory, isCloudflareWorkerRuntime, loadRuntimeFactory } from './platform.js';
|
|
10
|
-
export { ProcessExecutor } from './process-executor.js';
|
|
10
|
+
export { NodeProcessExecutor, ProcessExecutor } from './process-executor.js';
|
|
11
|
+
export { createInMemoryProcessRegistry, InMemoryProcessRegistry } from './process-registry.js';
|
|
11
12
|
export { cloudflareWorkersFactory } from './runtime-cf.js';
|
|
12
13
|
export { _resetNodeFileSystem, nodeBunFactory } from './runtime-node-bun.js';
|
|
13
14
|
export * from './schema-validation.js';
|
|
14
15
|
export * from './types.js';
|
|
15
16
|
// ── Deprecated re-exports (backward compatibility) ──────────────────────
|
|
16
|
-
export { BunPipeProcessSpawner, BunSyncProcessExecutor
|
|
17
|
+
export { BunPipeProcessSpawner, BunSyncProcessExecutor } from './process-executor.js';
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ProcessExecutionSource, ProcessRegistry } from './process-registry';
|
|
1
2
|
/** Controls how stdout/stderr is captured: buffered in memory or streamed to the caller's terminal. */
|
|
2
3
|
export type OutputPolicy = {
|
|
3
4
|
mode: 'buffered';
|
|
@@ -12,6 +13,12 @@ export interface ProcessExecutorConfig {
|
|
|
12
13
|
output?: OutputPolicy;
|
|
13
14
|
events?: ProcessEventSink;
|
|
14
15
|
tracer?: TracerPort;
|
|
16
|
+
/**
|
|
17
|
+
* Optional process registry (spur#0264). When set, every `run` / `runStreaming`
|
|
18
|
+
* invocation is recorded for list/subscribe consumers (e.g. Spur Processes tab).
|
|
19
|
+
* Share one registry across all executors that should appear in the same watch list.
|
|
20
|
+
*/
|
|
21
|
+
registry?: ProcessRegistry;
|
|
15
22
|
}
|
|
16
23
|
/** Options for spawning a child process. */
|
|
17
24
|
export interface ProcessOptions {
|
|
@@ -26,6 +33,14 @@ export interface ProcessOptions {
|
|
|
26
33
|
forceBuffered?: boolean;
|
|
27
34
|
/** AbortSignal forwarded to execa as `cancelSignal` — aborts the child process when fired. */
|
|
28
35
|
signal?: AbortSignal;
|
|
36
|
+
/**
|
|
37
|
+
* Registry metadata (spur#0264). Defaults: source `'one-shot'` for `run`.
|
|
38
|
+
* Pass `source: 'supervisor'` (and optional teamId/agentId) when the spawn is
|
|
39
|
+
* a supervised team agent loop.
|
|
40
|
+
*/
|
|
41
|
+
source?: ProcessExecutionSource;
|
|
42
|
+
teamId?: string;
|
|
43
|
+
agentId?: string;
|
|
29
44
|
}
|
|
30
45
|
/** Result of a completed child process, including exit code, captured output, and duration. */
|
|
31
46
|
export interface ProcessResult {
|
|
@@ -71,6 +86,13 @@ export interface PipeProcessOptions {
|
|
|
71
86
|
cwd?: string;
|
|
72
87
|
env?: Record<string, string>;
|
|
73
88
|
label?: string;
|
|
89
|
+
/**
|
|
90
|
+
* Registry metadata (spur#0264). Defaults: source `'other'` for streaming.
|
|
91
|
+
* Supervised agent loops should pass `source: 'supervisor'` + agentId.
|
|
92
|
+
*/
|
|
93
|
+
source?: ProcessExecutionSource;
|
|
94
|
+
teamId?: string;
|
|
95
|
+
agentId?: string;
|
|
74
96
|
}
|
|
75
97
|
/** Signal values accepted by subprocess kill. */
|
|
76
98
|
type BunSubprocess = ReturnType<typeof Bun.spawn>;
|
|
@@ -87,12 +109,37 @@ export interface PipeProcess {
|
|
|
87
109
|
kill(signal?: ProcessSignal): void;
|
|
88
110
|
}
|
|
89
111
|
/**
|
|
90
|
-
* Runtime-agnostic process executor
|
|
112
|
+
* Runtime-agnostic process executor contract.
|
|
91
113
|
*
|
|
92
114
|
* Every invocation supports timeout enforcement, output capture, and
|
|
93
|
-
* configurable output policy (buffered vs streamed).
|
|
115
|
+
* configurable output policy (buffered vs streamed). Concrete implementations
|
|
116
|
+
* are obtained through `RuntimeFactory.createProcessExecutor`; the Node/Bun
|
|
117
|
+
* implementation is {@link NodeProcessExecutor}. Test doubles implement this
|
|
118
|
+
* interface structurally — no concrete subclassing required.
|
|
94
119
|
*/
|
|
95
|
-
export
|
|
120
|
+
export interface ProcessExecutor {
|
|
121
|
+
/**
|
|
122
|
+
* Run a command, buffered by default. Returns a structured {@link ProcessResult}.
|
|
123
|
+
* Does NOT throw on non-zero exit codes unless `rejectOnError` is set.
|
|
124
|
+
*/
|
|
125
|
+
run(options: ProcessOptions): Promise<ProcessResult>;
|
|
126
|
+
/**
|
|
127
|
+
* Spawn a long-running interactive process with streaming I/O.
|
|
128
|
+
*
|
|
129
|
+
* Returns a {@link PipeProcess} handle with streaming stdout/stderr and
|
|
130
|
+
* stdin write support.
|
|
131
|
+
*/
|
|
132
|
+
runStreaming(options: PipeProcessOptions): PipeProcess;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Concrete Node/Bun implementation of {@link ProcessExecutor}, wrapping `execa`
|
|
136
|
+
* for buffered execution and `Bun.spawn` for streaming pipe execution.
|
|
137
|
+
*
|
|
138
|
+
* Obtain a default instance through `RuntimeFactory.createProcessExecutor`
|
|
139
|
+
* (e.g. `nodeBunFactory.createProcessExecutor()`); construct directly only in
|
|
140
|
+
* runtime-factory wiring or concrete implementation tests.
|
|
141
|
+
*/
|
|
142
|
+
export declare class NodeProcessExecutor implements ProcessExecutor {
|
|
96
143
|
private readonly config;
|
|
97
144
|
constructor(config?: ProcessExecutorConfig);
|
|
98
145
|
/**
|
|
@@ -109,15 +156,19 @@ export declare class ProcessExecutor {
|
|
|
109
156
|
*/
|
|
110
157
|
runStreaming(options: PipeProcessOptions): PipeProcess;
|
|
111
158
|
private trace;
|
|
159
|
+
private beginRegistry;
|
|
160
|
+
private completeRegistry;
|
|
112
161
|
private emitExitedFromResult;
|
|
113
162
|
private emitProcessEvent;
|
|
114
163
|
}
|
|
115
164
|
/**
|
|
116
|
-
* @deprecated
|
|
117
|
-
*
|
|
165
|
+
* @deprecated Construct {@link NodeProcessExecutor} directly or obtain a default
|
|
166
|
+
* through `RuntimeFactory.createProcessExecutor` (e.g. `nodeBunFactory.createProcessExecutor()`).
|
|
167
|
+
* This value alias preserves source compatibility for `new ProcessExecutor(...)` callers
|
|
168
|
+
* during the interface extraction release; it will be removed in a future release.
|
|
169
|
+
* `import type { ProcessExecutor }` resolves to the canonical interface, not this alias.
|
|
118
170
|
*/
|
|
119
|
-
export declare
|
|
120
|
-
}
|
|
171
|
+
export declare const ProcessExecutor: typeof NodeProcessExecutor;
|
|
121
172
|
/**
|
|
122
173
|
* @deprecated Use `Bun.spawnSync` or `child_process.spawnSync` directly.
|
|
123
174
|
* Synchronous process execution is no longer recommended from ts-runtime.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"process-executor.d.ts","sourceRoot":"","sources":["../src/process-executor.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"process-executor.d.ts","sourceRoot":"","sources":["../src/process-executor.ts"],"names":[],"mappings":"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"}
|
package/dist/process-executor.js
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import { isatty } from 'node:tty';
|
|
2
2
|
import { execa } from 'execa';
|
|
3
|
-
// ──
|
|
3
|
+
// ── NodeProcessExecutor (concrete Node/Bun implementation) ───────────────
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
5
|
+
* Concrete Node/Bun implementation of {@link ProcessExecutor}, wrapping `execa`
|
|
6
|
+
* for buffered execution and `Bun.spawn` for streaming pipe execution.
|
|
6
7
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
8
|
+
* Obtain a default instance through `RuntimeFactory.createProcessExecutor`
|
|
9
|
+
* (e.g. `nodeBunFactory.createProcessExecutor()`); construct directly only in
|
|
10
|
+
* runtime-factory wiring or concrete implementation tests.
|
|
9
11
|
*/
|
|
10
|
-
export class
|
|
12
|
+
export class NodeProcessExecutor {
|
|
11
13
|
config;
|
|
12
14
|
constructor(config = {}) {
|
|
13
15
|
this.config = config;
|
|
@@ -32,17 +34,26 @@ export class ProcessExecutor {
|
|
|
32
34
|
signal: options.signal,
|
|
33
35
|
});
|
|
34
36
|
const startedAt = Date.now();
|
|
37
|
+
const startedIso = new Date(startedAt).toISOString();
|
|
38
|
+
const registryId = this.beginRegistry(options.command, args, {
|
|
39
|
+
label: options.label,
|
|
40
|
+
source: options.source ?? 'one-shot',
|
|
41
|
+
teamId: options.teamId,
|
|
42
|
+
agentId: options.agentId,
|
|
43
|
+
startedAt: startedIso,
|
|
44
|
+
});
|
|
35
45
|
this.emitProcessEvent('process.started', {
|
|
36
46
|
command: options.command,
|
|
37
47
|
args,
|
|
38
48
|
exitCode: null,
|
|
39
49
|
durationMs: 0,
|
|
40
50
|
reason: 'exit',
|
|
41
|
-
timestamp:
|
|
51
|
+
timestamp: startedIso,
|
|
42
52
|
...(options.label !== undefined ? { label: options.label } : {}),
|
|
43
53
|
});
|
|
44
54
|
try {
|
|
45
55
|
const result = await execa(options.command, args, execaOptions);
|
|
56
|
+
// execa@9 Result does not expose pid — buffered runs leave pid unset.
|
|
46
57
|
const processResult = {
|
|
47
58
|
command: options.command,
|
|
48
59
|
args,
|
|
@@ -52,6 +63,7 @@ export class ProcessExecutor {
|
|
|
52
63
|
...(result.signalDescription !== undefined ? { signal: result.signalDescription } : {}),
|
|
53
64
|
durationMs: result.durationMs,
|
|
54
65
|
};
|
|
66
|
+
this.completeRegistry(registryId, processResult.exitCode);
|
|
55
67
|
this.emitExitedFromResult(options, processResult, result);
|
|
56
68
|
return processResult;
|
|
57
69
|
}
|
|
@@ -70,6 +82,7 @@ export class ProcessExecutor {
|
|
|
70
82
|
: {}),
|
|
71
83
|
durationMs: failed.durationMs ?? Date.now() - startedAt,
|
|
72
84
|
};
|
|
85
|
+
this.completeRegistry(registryId, processResult.exitCode);
|
|
73
86
|
this.emitExitedFromResult(options, processResult, error, error);
|
|
74
87
|
if (options.rejectOnError)
|
|
75
88
|
throw error;
|
|
@@ -85,15 +98,24 @@ export class ProcessExecutor {
|
|
|
85
98
|
runStreaming(options) {
|
|
86
99
|
const args = options.args ?? [];
|
|
87
100
|
void this.config.tracer?.traceAsync('process.runStreaming', async () => undefined).catch(() => undefined);
|
|
101
|
+
const startedAt = Date.now();
|
|
102
|
+
const startedIso = new Date(startedAt).toISOString();
|
|
103
|
+
// Begin registry before spawn so failed spawns still appear (then complete as error).
|
|
104
|
+
const registryId = this.beginRegistry(options.command, args, {
|
|
105
|
+
label: options.label,
|
|
106
|
+
source: options.source ?? 'other',
|
|
107
|
+
teamId: options.teamId,
|
|
108
|
+
agentId: options.agentId,
|
|
109
|
+
startedAt: startedIso,
|
|
110
|
+
});
|
|
88
111
|
try {
|
|
89
|
-
const startedAt = Date.now();
|
|
90
112
|
this.emitProcessEvent('process.started', {
|
|
91
113
|
command: options.command,
|
|
92
114
|
args,
|
|
93
115
|
exitCode: null,
|
|
94
116
|
durationMs: 0,
|
|
95
117
|
reason: 'exit',
|
|
96
|
-
timestamp:
|
|
118
|
+
timestamp: startedIso,
|
|
97
119
|
...(options.label !== undefined ? { label: options.label } : {}),
|
|
98
120
|
});
|
|
99
121
|
const subprocess = Bun.spawn({
|
|
@@ -104,14 +126,21 @@ export class ProcessExecutor {
|
|
|
104
126
|
...(options.cwd !== undefined ? { cwd: options.cwd } : {}),
|
|
105
127
|
...(options.env !== undefined ? { env: options.env } : {}),
|
|
106
128
|
});
|
|
107
|
-
|
|
129
|
+
const pipe = new BunPipeProcess(subprocess);
|
|
130
|
+
if (pipe.pid !== null) {
|
|
131
|
+
this.config.registry?.update(registryId, { pid: pipe.pid });
|
|
132
|
+
}
|
|
133
|
+
return new ObservedPipeProcess(pipe, this.config.events, {
|
|
108
134
|
command: options.command,
|
|
109
135
|
args,
|
|
110
136
|
startedAt,
|
|
137
|
+
registry: this.config.registry,
|
|
138
|
+
registryId,
|
|
111
139
|
...(options.label !== undefined ? { label: options.label } : {}),
|
|
112
140
|
});
|
|
113
141
|
}
|
|
114
142
|
catch (error) {
|
|
143
|
+
this.completeRegistry(registryId, null);
|
|
115
144
|
this.emitProcessEvent('process.exited', {
|
|
116
145
|
command: options.command,
|
|
117
146
|
args,
|
|
@@ -130,6 +159,28 @@ export class ProcessExecutor {
|
|
|
130
159
|
return await fn();
|
|
131
160
|
return await this.config.tracer.traceAsync(name, async () => await fn());
|
|
132
161
|
}
|
|
162
|
+
beginRegistry(command, args, meta) {
|
|
163
|
+
const registry = this.config.registry;
|
|
164
|
+
if (!registry)
|
|
165
|
+
return '';
|
|
166
|
+
return registry.begin({
|
|
167
|
+
command,
|
|
168
|
+
args,
|
|
169
|
+
source: meta.source,
|
|
170
|
+
startedAt: meta.startedAt,
|
|
171
|
+
...(meta.label !== undefined ? { label: meta.label } : {}),
|
|
172
|
+
...(meta.teamId !== undefined ? { teamId: meta.teamId } : {}),
|
|
173
|
+
...(meta.agentId !== undefined ? { agentId: meta.agentId } : {}),
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
completeRegistry(id, exitCode, pid) {
|
|
177
|
+
if (!id || !this.config.registry)
|
|
178
|
+
return;
|
|
179
|
+
this.config.registry.complete(id, {
|
|
180
|
+
exitCode,
|
|
181
|
+
...(pid !== undefined ? { pid } : {}),
|
|
182
|
+
});
|
|
183
|
+
}
|
|
133
184
|
emitExitedFromResult(options, result, completion, error) {
|
|
134
185
|
const reason = isTimedOut(completion)
|
|
135
186
|
? 'timeout'
|
|
@@ -161,6 +212,12 @@ class ObservedPipeProcess {
|
|
|
161
212
|
constructor(inner, events, context) {
|
|
162
213
|
this.inner = inner;
|
|
163
214
|
this.exited = inner.exited.then((exitCode) => {
|
|
215
|
+
if (context.registry && context.registryId) {
|
|
216
|
+
context.registry.complete(context.registryId, {
|
|
217
|
+
exitCode,
|
|
218
|
+
...(inner.pid !== null ? { pid: inner.pid } : {}),
|
|
219
|
+
});
|
|
220
|
+
}
|
|
164
221
|
events?.emit('process.exited', {
|
|
165
222
|
command: context.command,
|
|
166
223
|
args: context.args,
|
|
@@ -224,13 +281,16 @@ class BunPipeProcess {
|
|
|
224
281
|
this.subprocess.kill(signal);
|
|
225
282
|
}
|
|
226
283
|
}
|
|
227
|
-
// ── Deprecated
|
|
284
|
+
// ── Deprecated constructible ProcessExecutor value alias ──────────────────
|
|
228
285
|
/**
|
|
229
|
-
* @deprecated
|
|
230
|
-
*
|
|
286
|
+
* @deprecated Construct {@link NodeProcessExecutor} directly or obtain a default
|
|
287
|
+
* through `RuntimeFactory.createProcessExecutor` (e.g. `nodeBunFactory.createProcessExecutor()`).
|
|
288
|
+
* This value alias preserves source compatibility for `new ProcessExecutor(...)` callers
|
|
289
|
+
* during the interface extraction release; it will be removed in a future release.
|
|
290
|
+
* `import type { ProcessExecutor }` resolves to the canonical interface, not this alias.
|
|
231
291
|
*/
|
|
232
|
-
export
|
|
233
|
-
|
|
292
|
+
export const ProcessExecutor = NodeProcessExecutor;
|
|
293
|
+
// ── Deprecated backward-compatible helpers ────────────────────────────────
|
|
234
294
|
/**
|
|
235
295
|
* @deprecated Use `Bun.spawnSync` or `child_process.spawnSync` directly.
|
|
236
296
|
* Synchronous process execution is no longer recommended from ts-runtime.
|