@tagma/sdk 0.7.3 → 0.7.4
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 +26 -5
- package/dist/adapters/stdin-approval.d.ts +1 -5
- package/dist/adapters/stdin-approval.d.ts.map +1 -1
- package/dist/adapters/stdin-approval.js +1 -89
- package/dist/adapters/stdin-approval.js.map +1 -1
- package/dist/adapters/websocket-approval.d.ts +1 -27
- package/dist/adapters/websocket-approval.d.ts.map +1 -1
- package/dist/adapters/websocket-approval.js +1 -146
- package/dist/adapters/websocket-approval.js.map +1 -1
- package/dist/approval.d.ts +2 -12
- package/dist/approval.d.ts.map +1 -1
- package/dist/approval.js +1 -90
- package/dist/approval.js.map +1 -1
- package/dist/bootstrap.d.ts +1 -1
- package/dist/bootstrap.d.ts.map +1 -1
- package/dist/core/task-executor.d.ts.map +1 -1
- package/dist/core/task-executor.js +13 -4
- package/dist/core/task-executor.js.map +1 -1
- package/dist/engine.d.ts +5 -56
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +7 -297
- package/dist/engine.js.map +1 -1
- package/dist/index.d.ts +4 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -4
- package/dist/index.js.map +1 -1
- package/dist/logger.d.ts +2 -60
- package/dist/logger.d.ts.map +1 -1
- package/dist/logger.js +1 -153
- package/dist/logger.js.map +1 -1
- package/dist/plugins.d.ts +2 -2
- package/dist/plugins.d.ts.map +1 -1
- package/dist/plugins.js +1 -1
- package/dist/plugins.js.map +1 -1
- package/dist/registry.d.ts +2 -66
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +1 -292
- package/dist/registry.js.map +1 -1
- package/dist/runner.d.ts +1 -35
- package/dist/runner.d.ts.map +1 -1
- package/dist/runner.js +1 -610
- package/dist/runner.js.map +1 -1
- package/dist/runtime/adapters/stdin-approval.d.ts +2 -0
- package/dist/runtime/adapters/stdin-approval.d.ts.map +1 -0
- package/dist/runtime/adapters/stdin-approval.js +2 -0
- package/dist/runtime/adapters/stdin-approval.js.map +1 -0
- package/dist/runtime/adapters/websocket-approval.d.ts +2 -0
- package/dist/runtime/adapters/websocket-approval.d.ts.map +1 -0
- package/dist/runtime/adapters/websocket-approval.js +2 -0
- package/dist/runtime/adapters/websocket-approval.js.map +1 -0
- package/dist/runtime/bun-process-runner.d.ts +2 -0
- package/dist/runtime/bun-process-runner.d.ts.map +1 -0
- package/dist/runtime/bun-process-runner.js +2 -0
- package/dist/runtime/bun-process-runner.js.map +1 -0
- package/dist/runtime.d.ts +2 -8
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +1 -7
- package/dist/runtime.js.map +1 -1
- package/dist/tagma.d.ts +3 -4
- package/dist/tagma.d.ts.map +1 -1
- package/dist/tagma.js +2 -3
- package/dist/tagma.js.map +1 -1
- package/dist/triggers/file.d.ts.map +1 -1
- package/dist/triggers/file.js +74 -107
- package/dist/triggers/file.js.map +1 -1
- package/package.json +15 -4
- package/src/adapters/stdin-approval.ts +1 -106
- package/src/adapters/websocket-approval.ts +1 -224
- package/src/approval.ts +5 -127
- package/src/bootstrap.ts +1 -1
- package/src/core/run-context.test.ts +35 -0
- package/src/core/task-executor.ts +13 -4
- package/src/engine-ports-mixed.test.ts +70 -44
- package/src/engine-ports.test.ts +77 -33
- package/src/engine.ts +18 -444
- package/src/index.ts +4 -6
- package/src/logger.ts +2 -182
- package/src/package-split.test.ts +15 -0
- package/src/pipeline-runner.test.ts +65 -12
- package/src/plugin-registry.test.ts +69 -3
- package/src/plugins.ts +2 -2
- package/src/registry.ts +7 -353
- package/src/runner.ts +1 -666
- package/src/runtime/adapters/stdin-approval.ts +1 -0
- package/src/runtime/adapters/websocket-approval.ts +1 -0
- package/src/runtime/bun-process-runner.ts +1 -0
- package/src/runtime-adapters.test.ts +10 -0
- package/src/runtime.ts +12 -20
- package/src/tagma.test.ts +162 -0
- package/src/tagma.ts +9 -4
- package/src/triggers/file.test.ts +79 -0
- package/src/triggers/file.ts +85 -118
package/README.md
CHANGED
|
@@ -64,6 +64,8 @@ console.log(result.success ? 'Done' : 'Failed');
|
|
|
64
64
|
|
|
65
65
|
The package root is intentionally small. Use explicit subpaths for YAML,
|
|
66
66
|
config editing, plugin registry helpers, and low-level dataflow utilities.
|
|
67
|
+
The SDK composes `@tagma/core` with `@tagma/runtime-bun`; import those packages
|
|
68
|
+
directly only when you need lower-level package boundaries.
|
|
67
69
|
|
|
68
70
|
## Features
|
|
69
71
|
|
|
@@ -408,6 +410,18 @@ YAML `ports` has been replaced by typed `inputs` / `outputs`. `validateRaw` repo
|
|
|
408
410
|
- placeholder substitution, binding resolution, output extraction, and internal prompt-contract inference
|
|
409
411
|
- the subpath name is historical; YAML `ports` is rejected by validation
|
|
410
412
|
|
|
413
|
+
### Runtime approval adapters
|
|
414
|
+
|
|
415
|
+
- `@tagma/sdk/runtime/adapters/stdin-approval`
|
|
416
|
+
- `@tagma/sdk/runtime/adapters/websocket-approval`
|
|
417
|
+
- the older `@tagma/sdk/adapters/*` subpaths remain thin compatibility re-exports
|
|
418
|
+
|
|
419
|
+
### Split packages
|
|
420
|
+
|
|
421
|
+
- `@tagma/core` -- runtime-independent orchestration, registry, approval, logging, event/result types, and the `TagmaRuntime` interface
|
|
422
|
+
- `@tagma/runtime-bun` -- Bun process execution, file watching, log storage, `bunRuntime()`, and runtime approval adapters
|
|
423
|
+
- `@tagma/sdk` -- convenience package that composes core + Bun runtime + built-in plugins
|
|
424
|
+
|
|
411
425
|
### `bootstrapBuiltins(registry)`
|
|
412
426
|
|
|
413
427
|
Registers all built-in plugins (opencode driver, file/manual triggers, completion checks, static-context middleware).
|
|
@@ -425,7 +439,7 @@ Options:
|
|
|
425
439
|
- `registry` -- use an existing `PluginRegistry` instance
|
|
426
440
|
- `builtins` -- register built-in plugins into the instance registry; defaults to `true`
|
|
427
441
|
- `plugins` -- register package-level `TagmaPlugin` capability objects into the instance registry
|
|
428
|
-
- `runtime` -- override
|
|
442
|
+
- `runtime` -- override process execution, file watching, file existence checks, log storage, time, and sleep; defaults to `bunRuntime()`
|
|
429
443
|
|
|
430
444
|
### `createTagma().run(config, options): Promise<EngineResult>`
|
|
431
445
|
|
|
@@ -446,7 +460,7 @@ Options:
|
|
|
446
460
|
- `maxLogRuns` -- number of per-run log directories to keep under `<workDir>/.tagma/logs/` (default: 20)
|
|
447
461
|
- `skipPluginLoading` -- skip the engine's built-in `loadPlugins(config.plugins)` call. Set this when the host has already pre-loaded plugins from a custom resolution path (e.g. the editor loading from the user's workspace `node_modules`) so the engine doesn't re-resolve them via Node's default cwd-based import.
|
|
448
462
|
|
|
449
|
-
> **stdout / stderr persistence.**
|
|
463
|
+
> **stdout / stderr persistence.** With the default Bun runtime, the engine streams every task's stdout and stderr to disk under `<workDir>/.tagma/logs/<runId>/<taskId>.stdout` and `.stderr`. Custom runtimes can relocate those artifacts by implementing `runtime.logStore.taskOutputPath()`. The `TaskResult.stdout` / `stderr` strings are bounded tails (8 MB / 4 MB by default) — long outputs are truncated from the head with a marker, and consumers that need the full bytes should read `TaskResult.stdoutPath` / `stderrPath`. Use `TaskResult.stdoutBytes` / `stderrBytes` to display "32 MB (truncated)" without re-stat'ing the file.
|
|
450
464
|
|
|
451
465
|
### `PipelineRunner`
|
|
452
466
|
|
|
@@ -534,6 +548,8 @@ export const HttpTrigger: TriggerPlugin = {
|
|
|
534
548
|
},
|
|
535
549
|
},
|
|
536
550
|
async watch(config, ctx) {
|
|
551
|
+
// Trigger plugins should use ctx.runtime for file IO, watching, and
|
|
552
|
+
// timing so they can run under non-Bun test/runtime implementations.
|
|
537
553
|
/* ... */
|
|
538
554
|
},
|
|
539
555
|
};
|
|
@@ -734,7 +750,9 @@ Use `buildDag` instead when you have a fully resolved `PipelineConfig` and need
|
|
|
734
750
|
Dual-channel logger — console + file. Creates a per-run log file at `<workDir>/.tagma/logs/<runId>/pipeline.log`.
|
|
735
751
|
|
|
736
752
|
```ts
|
|
737
|
-
|
|
753
|
+
import { bunRuntime } from '@tagma/sdk';
|
|
754
|
+
|
|
755
|
+
const logger = new Logger(workDir, runId, bunRuntime().logStore);
|
|
738
756
|
logger.info('[track]', 'message'); // console + file
|
|
739
757
|
logger.warn('[track]', 'message'); // console + file
|
|
740
758
|
logger.error('[track]', 'message'); // console + file
|
|
@@ -746,13 +764,14 @@ logger.dir; // run artifact directory
|
|
|
746
764
|
logger.close(); // close the persistent file handle (called automatically when Tagma.run completes)
|
|
747
765
|
```
|
|
748
766
|
|
|
749
|
-
Pass an optional
|
|
767
|
+
Pass an optional fourth argument to stream every appended line out as a
|
|
750
768
|
structured `LogRecord`; the engine uses this to emit `task_log` events:
|
|
751
769
|
|
|
752
770
|
```ts
|
|
771
|
+
import { bunRuntime } from '@tagma/sdk';
|
|
753
772
|
import { Logger, type LogRecord } from '@tagma/sdk/logger';
|
|
754
773
|
|
|
755
|
-
const logger = new Logger(workDir, runId, (record: LogRecord) => {
|
|
774
|
+
const logger = new Logger(workDir, runId, bunRuntime().logStore, (record: LogRecord) => {
|
|
756
775
|
// record = { level, taskId, timestamp, text }
|
|
757
776
|
// level = 'info' | 'warn' | 'error' | 'debug' | 'section' | 'quiet'
|
|
758
777
|
// taskId is extracted from a '[task:<id>]' prefix, or null for untagged lines
|
|
@@ -791,6 +810,8 @@ Truncates `text` to at most `maxBytes` UTF-8 bytes (default 16 KB), appending a
|
|
|
791
810
|
| Package | Description |
|
|
792
811
|
| ---------------------------------------------------------------------------------------- | --------------------------------------------- |
|
|
793
812
|
| [@tagma/types](https://www.npmjs.com/package/@tagma/types) | Shared TypeScript types |
|
|
813
|
+
| [@tagma/core](https://www.npmjs.com/package/@tagma/core) | Runtime-independent orchestration core |
|
|
814
|
+
| [@tagma/runtime-bun](https://www.npmjs.com/package/@tagma/runtime-bun) | Bun runtime implementation |
|
|
794
815
|
| [@tagma/driver-codex](https://www.npmjs.com/package/@tagma/driver-codex) | Codex CLI driver plugin |
|
|
795
816
|
| [@tagma/driver-claude-code](https://www.npmjs.com/package/@tagma/driver-claude-code) | Claude Code CLI driver plugin |
|
|
796
817
|
| [@tagma/middleware-lightrag](https://www.npmjs.com/package/@tagma/middleware-lightrag) | LightRAG knowledge-graph retrieval middleware |
|
|
@@ -1,6 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
export interface StdinApprovalAdapter {
|
|
3
|
-
readonly detach: () => void;
|
|
4
|
-
}
|
|
5
|
-
export declare function attachStdinApprovalAdapter(gateway: ApprovalGateway): StdinApprovalAdapter;
|
|
1
|
+
export * from '../runtime/adapters/stdin-approval';
|
|
6
2
|
//# sourceMappingURL=stdin-approval.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stdin-approval.d.ts","sourceRoot":"","sources":["../../src/adapters/stdin-approval.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"stdin-approval.d.ts","sourceRoot":"","sources":["../../src/adapters/stdin-approval.ts"],"names":[],"mappings":"AAAA,cAAc,oCAAoC,CAAC"}
|
|
@@ -1,90 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
export function attachStdinApprovalAdapter(gateway) {
|
|
3
|
-
const queue = [];
|
|
4
|
-
let processing = false;
|
|
5
|
-
let rl = null;
|
|
6
|
-
function ensureReadline() {
|
|
7
|
-
if (!rl) {
|
|
8
|
-
rl = readline.createInterface({ input: process.stdin, terminal: false });
|
|
9
|
-
}
|
|
10
|
-
return rl;
|
|
11
|
-
}
|
|
12
|
-
function readOneLine() {
|
|
13
|
-
return new Promise((resolvePromise) => {
|
|
14
|
-
const reader = ensureReadline();
|
|
15
|
-
const handler = (line) => {
|
|
16
|
-
reader.off('line', handler);
|
|
17
|
-
resolvePromise(line);
|
|
18
|
-
};
|
|
19
|
-
reader.on('line', handler);
|
|
20
|
-
});
|
|
21
|
-
}
|
|
22
|
-
async function processNext() {
|
|
23
|
-
if (processing)
|
|
24
|
-
return;
|
|
25
|
-
processing = true;
|
|
26
|
-
try {
|
|
27
|
-
while (queue.length > 0) {
|
|
28
|
-
const req = queue.shift();
|
|
29
|
-
// If the request was already resolved by another path while queued, skip it.
|
|
30
|
-
if (!gateway.pending().some((p) => p.id === req.id))
|
|
31
|
-
continue;
|
|
32
|
-
process.stdout.write(`\n[APPROVAL REQUIRED] ${req.message}\n` +
|
|
33
|
-
` id: ${req.id}\n` +
|
|
34
|
-
` task: ${req.taskId}${req.trackId ? ` (track: ${req.trackId})` : ''}\n` +
|
|
35
|
-
` approve / reject > `);
|
|
36
|
-
const input = (await readOneLine()).trim().toLowerCase();
|
|
37
|
-
const approveAliases = new Set(['approve', 'yes', 'y', 'ok', 'true', '1']);
|
|
38
|
-
const rejectAliases = new Set(['reject', 'no', 'n', 'deny', 'false', '0']);
|
|
39
|
-
if (approveAliases.has(input)) {
|
|
40
|
-
gateway.resolve(req.id, { outcome: 'approved', actor: 'cli' });
|
|
41
|
-
}
|
|
42
|
-
else if (rejectAliases.has(input)) {
|
|
43
|
-
gateway.resolve(req.id, {
|
|
44
|
-
outcome: 'rejected',
|
|
45
|
-
actor: 'cli',
|
|
46
|
-
reason: 'user rejected via CLI',
|
|
47
|
-
});
|
|
48
|
-
}
|
|
49
|
-
else {
|
|
50
|
-
process.stdout.write(` unrecognized input "${input}" — treating as rejection\n`);
|
|
51
|
-
gateway.resolve(req.id, {
|
|
52
|
-
outcome: 'rejected',
|
|
53
|
-
actor: 'cli',
|
|
54
|
-
reason: `unrecognized CLI input: ${input}`,
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
finally {
|
|
60
|
-
processing = false;
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
const unsubscribe = gateway.subscribe((event) => {
|
|
64
|
-
switch (event.type) {
|
|
65
|
-
case 'requested':
|
|
66
|
-
queue.push(event.request);
|
|
67
|
-
void processNext();
|
|
68
|
-
return;
|
|
69
|
-
case 'resolved':
|
|
70
|
-
case 'expired':
|
|
71
|
-
case 'aborted': {
|
|
72
|
-
// Drop from queue if it's still waiting its turn.
|
|
73
|
-
const idx = queue.findIndex((r) => r.id === event.request.id);
|
|
74
|
-
if (idx >= 0)
|
|
75
|
-
queue.splice(idx, 1);
|
|
76
|
-
return;
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
});
|
|
80
|
-
return {
|
|
81
|
-
detach: () => {
|
|
82
|
-
unsubscribe();
|
|
83
|
-
if (rl) {
|
|
84
|
-
rl.close();
|
|
85
|
-
rl = null;
|
|
86
|
-
}
|
|
87
|
-
},
|
|
88
|
-
};
|
|
89
|
-
}
|
|
1
|
+
export * from '../runtime/adapters/stdin-approval';
|
|
90
2
|
//# sourceMappingURL=stdin-approval.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stdin-approval.js","sourceRoot":"","sources":["../../src/adapters/stdin-approval.ts"],"names":[],"mappings":"AAAA,
|
|
1
|
+
{"version":3,"file":"stdin-approval.js","sourceRoot":"","sources":["../../src/adapters/stdin-approval.ts"],"names":[],"mappings":"AAAA,cAAc,oCAAoC,CAAC"}
|
|
@@ -1,28 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
export interface WebSocketApprovalAdapterOptions {
|
|
3
|
-
port?: number;
|
|
4
|
-
hostname?: string;
|
|
5
|
-
/**
|
|
6
|
-
* M11: shared secret required from the client during the WebSocket
|
|
7
|
-
* upgrade. The token can be supplied either as the `?token=` query
|
|
8
|
-
* parameter or in the `x-tagma-token` request header. When set, any
|
|
9
|
-
* upgrade request that fails the check is rejected with HTTP 401 and
|
|
10
|
-
* never reaches the WebSocket layer (so a misconfigured client cannot
|
|
11
|
-
* exhaust rate-limit slots either). Leave undefined for backward
|
|
12
|
-
* compatibility with localhost-only deployments.
|
|
13
|
-
*/
|
|
14
|
-
token?: string;
|
|
15
|
-
/**
|
|
16
|
-
* M11: opt-out of origin checking. Defaults to false, meaning Origin
|
|
17
|
-
* headers are restricted to loopback hosts (localhost / 127.0.0.1 / ::1).
|
|
18
|
-
* Requests without an Origin header are still allowed so non-browser local
|
|
19
|
-
* clients can connect. Set true only for trusted reverse-proxy setups.
|
|
20
|
-
*/
|
|
21
|
-
allowAnyOrigin?: boolean;
|
|
22
|
-
}
|
|
23
|
-
export interface WebSocketApprovalAdapter {
|
|
24
|
-
readonly port: number;
|
|
25
|
-
readonly detach: () => void;
|
|
26
|
-
}
|
|
27
|
-
export declare function attachWebSocketApprovalAdapter(gateway: ApprovalGateway, options?: WebSocketApprovalAdapterOptions): WebSocketApprovalAdapter;
|
|
1
|
+
export * from '../runtime/adapters/websocket-approval';
|
|
28
2
|
//# sourceMappingURL=websocket-approval.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"websocket-approval.d.ts","sourceRoot":"","sources":["../../src/adapters/websocket-approval.ts"],"names":[],"mappings":"AAAA,
|
|
1
|
+
{"version":3,"file":"websocket-approval.d.ts","sourceRoot":"","sources":["../../src/adapters/websocket-approval.ts"],"names":[],"mappings":"AAAA,cAAc,wCAAwC,CAAC"}
|
|
@@ -1,147 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
const MAX_PAYLOAD_BYTES = 4_096;
|
|
3
|
-
// Per-client rate limit: at most this many messages per window.
|
|
4
|
-
const RATE_LIMIT_MAX = 10;
|
|
5
|
-
const RATE_LIMIT_WINDOW_MS = 1_000;
|
|
6
|
-
export function attachWebSocketApprovalAdapter(gateway, options = {}) {
|
|
7
|
-
const port = options.port ?? 3000;
|
|
8
|
-
const hostname = options.hostname ?? 'localhost';
|
|
9
|
-
const requiredToken = options.token ?? null;
|
|
10
|
-
const enforceOriginCheck = options.allowAnyOrigin !== true;
|
|
11
|
-
function isLoopbackOrigin(origin) {
|
|
12
|
-
try {
|
|
13
|
-
const host = new URL(origin).hostname.toLowerCase();
|
|
14
|
-
return host === 'localhost' || host === '127.0.0.1' || host === '::1' || host === '[::1]';
|
|
15
|
-
}
|
|
16
|
-
catch {
|
|
17
|
-
return false;
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
const clients = new Set();
|
|
21
|
-
const clientRates = new Map();
|
|
22
|
-
function broadcast(msg) {
|
|
23
|
-
const text = JSON.stringify(msg);
|
|
24
|
-
for (const ws of clients) {
|
|
25
|
-
ws.send(text);
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
const unsubscribe = gateway.subscribe((event) => {
|
|
29
|
-
switch (event.type) {
|
|
30
|
-
case 'requested':
|
|
31
|
-
broadcast({ type: 'approval_requested', request: event.request });
|
|
32
|
-
break;
|
|
33
|
-
case 'resolved':
|
|
34
|
-
broadcast({ type: 'approval_resolved', request: event.request, decision: event.decision });
|
|
35
|
-
break;
|
|
36
|
-
case 'expired':
|
|
37
|
-
broadcast({ type: 'approval_expired', request: event.request });
|
|
38
|
-
break;
|
|
39
|
-
case 'aborted':
|
|
40
|
-
broadcast({ type: 'approval_aborted', request: event.request, reason: event.reason });
|
|
41
|
-
break;
|
|
42
|
-
}
|
|
43
|
-
});
|
|
44
|
-
const server = Bun.serve({
|
|
45
|
-
port,
|
|
46
|
-
hostname,
|
|
47
|
-
fetch(req, server) {
|
|
48
|
-
if (enforceOriginCheck) {
|
|
49
|
-
const origin = req.headers.get('origin');
|
|
50
|
-
if (origin && !isLoopbackOrigin(origin)) {
|
|
51
|
-
return new Response('forbidden origin', { status: 403 });
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
// M11: enforce token before any upgrade so an unauthenticated client
|
|
55
|
-
// can't even open a socket. Tokens may arrive via header or query.
|
|
56
|
-
if (requiredToken !== null) {
|
|
57
|
-
const headerToken = req.headers.get('x-tagma-token') ?? '';
|
|
58
|
-
let queryToken = '';
|
|
59
|
-
try {
|
|
60
|
-
queryToken = new URL(req.url).searchParams.get('token') ?? '';
|
|
61
|
-
}
|
|
62
|
-
catch {
|
|
63
|
-
/* malformed URL — leave queryToken empty */
|
|
64
|
-
}
|
|
65
|
-
const presented = headerToken || queryToken;
|
|
66
|
-
if (presented !== requiredToken) {
|
|
67
|
-
return new Response('unauthorized', { status: 401 });
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
if (server.upgrade(req))
|
|
71
|
-
return undefined;
|
|
72
|
-
return new Response('tagma-sdk WebSocket approval endpoint', { status: 426 });
|
|
73
|
-
},
|
|
74
|
-
websocket: {
|
|
75
|
-
open(ws) {
|
|
76
|
-
clients.add(ws);
|
|
77
|
-
// Sync current pending approvals to newly connected client.
|
|
78
|
-
ws.send(JSON.stringify({ type: 'pending', requests: gateway.pending() }));
|
|
79
|
-
},
|
|
80
|
-
message(ws, raw) {
|
|
81
|
-
const rawStr = typeof raw === 'string' ? raw : raw.toString();
|
|
82
|
-
// Payload size guard — reject oversized messages before parsing.
|
|
83
|
-
if (rawStr.length > MAX_PAYLOAD_BYTES) {
|
|
84
|
-
ws.send(JSON.stringify({ type: 'error', message: 'message too large' }));
|
|
85
|
-
return;
|
|
86
|
-
}
|
|
87
|
-
// Per-client rate limit.
|
|
88
|
-
const now = Date.now();
|
|
89
|
-
const rate = clientRates.get(ws) ?? { count: 0, resetAt: now + RATE_LIMIT_WINDOW_MS };
|
|
90
|
-
if (now >= rate.resetAt) {
|
|
91
|
-
rate.count = 0;
|
|
92
|
-
rate.resetAt = now + RATE_LIMIT_WINDOW_MS;
|
|
93
|
-
}
|
|
94
|
-
rate.count++;
|
|
95
|
-
clientRates.set(ws, rate);
|
|
96
|
-
if (rate.count > RATE_LIMIT_MAX) {
|
|
97
|
-
ws.send(JSON.stringify({ type: 'error', message: 'rate limit exceeded' }));
|
|
98
|
-
return;
|
|
99
|
-
}
|
|
100
|
-
let msg;
|
|
101
|
-
try {
|
|
102
|
-
msg = JSON.parse(rawStr);
|
|
103
|
-
}
|
|
104
|
-
catch {
|
|
105
|
-
ws.send(JSON.stringify({ type: 'error', message: 'invalid JSON' }));
|
|
106
|
-
return;
|
|
107
|
-
}
|
|
108
|
-
if (!isResolveMessage(msg)) {
|
|
109
|
-
ws.send(JSON.stringify({ type: 'error', message: 'unknown message type' }));
|
|
110
|
-
return;
|
|
111
|
-
}
|
|
112
|
-
const ok = gateway.resolve(msg.approvalId, {
|
|
113
|
-
outcome: msg.outcome,
|
|
114
|
-
actor: msg.actor ?? 'websocket',
|
|
115
|
-
reason: msg.reason,
|
|
116
|
-
});
|
|
117
|
-
if (!ok) {
|
|
118
|
-
ws.send(JSON.stringify({
|
|
119
|
-
type: 'error',
|
|
120
|
-
message: `approval ${msg.approvalId} not found or already resolved`,
|
|
121
|
-
}));
|
|
122
|
-
}
|
|
123
|
-
},
|
|
124
|
-
close(ws) {
|
|
125
|
-
clients.delete(ws);
|
|
126
|
-
clientRates.delete(ws);
|
|
127
|
-
},
|
|
128
|
-
},
|
|
129
|
-
});
|
|
130
|
-
return {
|
|
131
|
-
port: server.port,
|
|
132
|
-
detach() {
|
|
133
|
-
unsubscribe();
|
|
134
|
-
clients.clear();
|
|
135
|
-
server.stop(true);
|
|
136
|
-
},
|
|
137
|
-
};
|
|
138
|
-
}
|
|
139
|
-
function isResolveMessage(v) {
|
|
140
|
-
if (typeof v !== 'object' || v === null)
|
|
141
|
-
return false;
|
|
142
|
-
const m = v;
|
|
143
|
-
return (m['type'] === 'resolve' &&
|
|
144
|
-
typeof m['approvalId'] === 'string' &&
|
|
145
|
-
(m['outcome'] === 'approved' || m['outcome'] === 'rejected'));
|
|
146
|
-
}
|
|
1
|
+
export * from '../runtime/adapters/websocket-approval';
|
|
147
2
|
//# sourceMappingURL=websocket-approval.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"websocket-approval.js","sourceRoot":"","sources":["../../src/adapters/websocket-approval.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"websocket-approval.js","sourceRoot":"","sources":["../../src/adapters/websocket-approval.ts"],"names":[],"mappings":"AAAA,cAAc,wCAAwC,CAAC"}
|
package/dist/approval.d.ts
CHANGED
|
@@ -1,13 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
export type {
|
|
3
|
-
export declare class InMemoryApprovalGateway implements ApprovalGateway {
|
|
4
|
-
private readonly pendingMap;
|
|
5
|
-
private readonly listeners;
|
|
6
|
-
request(req: Omit<ApprovalRequest, 'id' | 'createdAt'>): Promise<ApprovalDecision>;
|
|
7
|
-
resolve(approvalId: string, decision: Omit<ApprovalDecision, 'approvalId' | 'decidedAt'>): boolean;
|
|
8
|
-
pending(): readonly ApprovalRequest[];
|
|
9
|
-
subscribe(listener: ApprovalListener): () => void;
|
|
10
|
-
abortAll(reason: string): void;
|
|
11
|
-
private emit;
|
|
12
|
-
}
|
|
1
|
+
export { InMemoryApprovalGateway } from '@tagma/core';
|
|
2
|
+
export type { ApprovalDecision, ApprovalEvent, ApprovalGateway, ApprovalListener, ApprovalOutcome, ApprovalRequest, } from '@tagma/core';
|
|
13
3
|
//# sourceMappingURL=approval.d.ts.map
|
package/dist/approval.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"approval.d.ts","sourceRoot":"","sources":["../src/approval.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"approval.d.ts","sourceRoot":"","sources":["../src/approval.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC;AACtD,YAAY,EACV,gBAAgB,EAChB,aAAa,EACb,eAAe,EACf,gBAAgB,EAChB,eAAe,EACf,eAAe,GAChB,MAAM,aAAa,CAAC"}
|
package/dist/approval.js
CHANGED
|
@@ -1,91 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
import { nowISO } from './utils';
|
|
3
|
-
export class InMemoryApprovalGateway {
|
|
4
|
-
pendingMap = new Map();
|
|
5
|
-
listeners = new Set();
|
|
6
|
-
request(req) {
|
|
7
|
-
const full = {
|
|
8
|
-
id: randomUUID(),
|
|
9
|
-
createdAt: nowISO(),
|
|
10
|
-
taskId: req.taskId,
|
|
11
|
-
trackId: req.trackId,
|
|
12
|
-
message: req.message,
|
|
13
|
-
timeoutMs: req.timeoutMs,
|
|
14
|
-
metadata: req.metadata,
|
|
15
|
-
};
|
|
16
|
-
return new Promise((resolvePromise) => {
|
|
17
|
-
let timer = null;
|
|
18
|
-
if (full.timeoutMs > 0) {
|
|
19
|
-
timer = setTimeout(() => {
|
|
20
|
-
const entry = this.pendingMap.get(full.id);
|
|
21
|
-
if (!entry)
|
|
22
|
-
return;
|
|
23
|
-
this.pendingMap.delete(full.id);
|
|
24
|
-
const decision = {
|
|
25
|
-
approvalId: full.id,
|
|
26
|
-
outcome: 'timeout',
|
|
27
|
-
reason: `Approval timed out after ${full.timeoutMs}ms`,
|
|
28
|
-
decidedAt: nowISO(),
|
|
29
|
-
};
|
|
30
|
-
this.emit({ type: 'expired', request: full });
|
|
31
|
-
resolvePromise(decision);
|
|
32
|
-
}, full.timeoutMs);
|
|
33
|
-
}
|
|
34
|
-
this.pendingMap.set(full.id, { request: full, settle: resolvePromise, timer });
|
|
35
|
-
this.emit({ type: 'requested', request: full });
|
|
36
|
-
});
|
|
37
|
-
}
|
|
38
|
-
resolve(approvalId, decision) {
|
|
39
|
-
const entry = this.pendingMap.get(approvalId);
|
|
40
|
-
if (!entry)
|
|
41
|
-
return false;
|
|
42
|
-
this.pendingMap.delete(approvalId);
|
|
43
|
-
if (entry.timer)
|
|
44
|
-
clearTimeout(entry.timer);
|
|
45
|
-
const full = {
|
|
46
|
-
approvalId,
|
|
47
|
-
outcome: decision.outcome,
|
|
48
|
-
actor: decision.actor,
|
|
49
|
-
reason: decision.reason,
|
|
50
|
-
decidedAt: nowISO(),
|
|
51
|
-
};
|
|
52
|
-
this.emit({ type: 'resolved', request: entry.request, decision: full });
|
|
53
|
-
entry.settle(full);
|
|
54
|
-
return true;
|
|
55
|
-
}
|
|
56
|
-
pending() {
|
|
57
|
-
return Array.from(this.pendingMap.values()).map((e) => e.request);
|
|
58
|
-
}
|
|
59
|
-
subscribe(listener) {
|
|
60
|
-
this.listeners.add(listener);
|
|
61
|
-
return () => {
|
|
62
|
-
this.listeners.delete(listener);
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
abortAll(reason) {
|
|
66
|
-
const entries = Array.from(this.pendingMap.entries());
|
|
67
|
-
this.pendingMap.clear();
|
|
68
|
-
for (const [id, entry] of entries) {
|
|
69
|
-
if (entry.timer)
|
|
70
|
-
clearTimeout(entry.timer);
|
|
71
|
-
this.emit({ type: 'aborted', request: entry.request, reason });
|
|
72
|
-
entry.settle({
|
|
73
|
-
approvalId: id,
|
|
74
|
-
outcome: 'aborted',
|
|
75
|
-
reason,
|
|
76
|
-
decidedAt: nowISO(),
|
|
77
|
-
});
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
emit(event) {
|
|
81
|
-
for (const listener of this.listeners) {
|
|
82
|
-
try {
|
|
83
|
-
listener(event);
|
|
84
|
-
}
|
|
85
|
-
catch (err) {
|
|
86
|
-
console.error('[approval gateway] listener error:', err);
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
}
|
|
1
|
+
export { InMemoryApprovalGateway } from '@tagma/core';
|
|
91
2
|
//# sourceMappingURL=approval.js.map
|
package/dist/approval.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"approval.js","sourceRoot":"","sources":["../src/approval.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"approval.js","sourceRoot":"","sources":["../src/approval.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC"}
|
package/dist/bootstrap.d.ts
CHANGED
package/dist/bootstrap.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bootstrap.d.ts","sourceRoot":"","sources":["../src/bootstrap.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"bootstrap.d.ts","sourceRoot":"","sources":["../src/bootstrap.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAsBlD,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;CAmBR,CAAC;AAExB;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,cAAc,GAAG,IAAI,CAE9D"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"task-executor.d.ts","sourceRoot":"","sources":["../../src/core/task-executor.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"task-executor.d.ts","sourceRoot":"","sources":["../../src/core/task-executor.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAWlD,OAAO,EAAmB,KAAK,MAAM,EAAE,MAAM,WAAW,CAAC;AACzD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAoBhD,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,GAAG,EAAE,UAAU,CAAC;IACzB,QAAQ,CAAC,QAAQ,EAAE,cAAc,CAAC;IAClC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,eAAe,EAAE,eAAe,CAAC;CAC3C;AAED,wBAAsB,WAAW,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CA0rB5E"}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { resolve } from 'path';
|
|
2
1
|
import { parseDuration, nowISO } from '../utils';
|
|
3
2
|
import { promptDocumentFromString, serializePromptDocument, prependContext, renderInputsBlock, renderOutputSchemaBlock, } from '../prompt-doc';
|
|
4
3
|
import { resolveTaskBindingInputs, resolveTaskInputs, substituteInputs } from '../ports';
|
|
@@ -88,6 +87,7 @@ export async function executeTask(options) {
|
|
|
88
87
|
workDir: task.cwd ?? workDir,
|
|
89
88
|
signal: ctx.abortController.signal,
|
|
90
89
|
approvalGateway,
|
|
90
|
+
runtime: ctx.runtime,
|
|
91
91
|
})
|
|
92
92
|
.then((v) => {
|
|
93
93
|
if (settled)
|
|
@@ -298,9 +298,18 @@ export async function executeTask(options) {
|
|
|
298
298
|
// and keep only a bounded tail in the returned TaskResult. Filenames
|
|
299
299
|
// mirror the existing `.stderr` naming — dots in task ids are replaced
|
|
300
300
|
// so hierarchical ids (e.g. `track1.task2`) map cleanly to a flat dir.
|
|
301
|
-
const
|
|
302
|
-
|
|
303
|
-
|
|
301
|
+
const stdoutPath = ctx.runtime.logStore.taskOutputPath({
|
|
302
|
+
workDir,
|
|
303
|
+
runId: ctx.runId,
|
|
304
|
+
taskId,
|
|
305
|
+
stream: 'stdout',
|
|
306
|
+
});
|
|
307
|
+
const stderrPath = ctx.runtime.logStore.taskOutputPath({
|
|
308
|
+
workDir,
|
|
309
|
+
runId: ctx.runId,
|
|
310
|
+
taskId,
|
|
311
|
+
stream: 'stderr',
|
|
312
|
+
});
|
|
304
313
|
const runOpts = {
|
|
305
314
|
timeoutMs,
|
|
306
315
|
signal: ctx.abortController.signal,
|