@excitedjs/dreamux-utils 0.1.1-alpha.g0ddd418597ca

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 excitedjs
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,29 @@
1
+ # @excitedjs/dreamux-utils
2
+
3
+ Pure shared utilities for the Dreamux host and its provider packages
4
+ (issue [#209](https://github.com/excitedjs/dreamux/issues/209)). These are the
5
+ byte-identical helpers that were previously vendored separately into the codex,
6
+ claude-code, and feishu-channel packages and the host core; consolidating them
7
+ removes the duplication while keeping the provider packages free of any
8
+ dependency on `@excitedjs/dreamux` core.
9
+
10
+ This package depends on `@excitedjs/dreamux-types` only — never on
11
+ `@excitedjs/dreamux`.
12
+
13
+ ## Exports
14
+
15
+ - **config-validate** — neutral JSON-shape validation primitives that produce
16
+ `dreamux config error in <file>: ...` messages (`isPlainObject`,
17
+ `rejectUnknownKeys`, `requireNonEmptyString`, `requireStringArray`, …).
18
+ - **os** — platform/filesystem primitives (`isProcessAlive`, `killProcessGroup`,
19
+ `ensureOwnerOnlyDir`, `removeEmptyLogFile`, `pathExists`). These are generic OS
20
+ helpers, not Dreamux layout/path contracts.
21
+ - **completion-body** — bounded teammate-completion resolution: inline a short
22
+ result, spill an over-budget result to an owner-only file under a
23
+ host-supplied spill directory (`resolveCompletionBody`,
24
+ `completionInlineBudget`, `teamMateCompletionOutputPath`,
25
+ `COMPLETION_INLINE_BUDGET_DEFAULT`, `COMPLETION_INLINE_BUDGET_MAX`).
26
+ - **turn-render** — inbound-turn render helpers that wrap a neutral
27
+ `InboundTurnInput` into the native `<channel source="…" …>` envelope
28
+ (`renderChannelInput`, `renderChannelBlock`,
29
+ `DEFAULT_MESSAGE_ID_DEDUPE_WINDOW`).
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Neutral completion-body resolution shared by every runtime and the host.
3
+ *
4
+ * A teammate completion is delivered back to the dispatcher as turn text. When
5
+ * the result is short it is inlined verbatim; when it overflows the inline
6
+ * budget the full result is spilled to an owner-only 0600 file under the
7
+ * host-supplied spill directory and only the path is inlined, so a large result
8
+ * never floods the dispatcher's context window.
9
+ *
10
+ * The spill directory is supplied by the caller (the host through its path
11
+ * context, or a runtime through the create context), so this module owns no
12
+ * Dreamux layout contract — only the safe filename shape inside that directory.
13
+ */
14
+ import type { CompletionEnvelope } from '@excitedjs/dreamux-types';
15
+ /**
16
+ * Inline character budget, mirroring claude-code's native task-output budget
17
+ * (`getMaxTaskOutputLength`): default 32000 chars, `TASK_MAX_OUTPUT_LENGTH`
18
+ * override, clamped to 160000. Counted in characters, not bytes.
19
+ */
20
+ export declare const COMPLETION_INLINE_BUDGET_DEFAULT = 32000;
21
+ export declare const COMPLETION_INLINE_BUDGET_MAX = 160000;
22
+ export type ResolvedCompletionBody = {
23
+ kind: 'inline';
24
+ text: string;
25
+ } | {
26
+ kind: 'spilled';
27
+ path: string;
28
+ };
29
+ /**
30
+ * Spill file for a teammate completion result that overflows the inline budget.
31
+ * Both runtimes write the full result here and inline only this path into the
32
+ * dispatcher turn, so a large result never floods the dispatcher's context.
33
+ * `spillDir` is the owning dispatcher's completion spill dir, supplied by the
34
+ * caller so a teammate runtime spills under its operator dispatcher, not its
35
+ * composite runtime id. `source` and `id` are sanitized for filename safety; the
36
+ * id is unique per completion (teammate name + turn id).
37
+ */
38
+ export declare function teamMateCompletionOutputPath(spillDir: string, source: string, id: string): string;
39
+ /**
40
+ * Resolve the effective inline budget from the environment, in the spirit of
41
+ * native `validateBoundedIntEnvVar`: unset/blank or non-positive falls back to
42
+ * the default; values above the upper bound are clamped (not rejected). Stricter
43
+ * than native's lenient `parseInt`, a value that is not a plain decimal integer
44
+ * (e.g. `32k` or `123abc`) falls back to the default rather than being partially
45
+ * parsed.
46
+ */
47
+ export declare function completionInlineBudget(env?: NodeJS.ProcessEnv): number;
48
+ /**
49
+ * Decide whether a completion result is inlined or spilled. Spilling writes the
50
+ * FULL result to a 0600 file (async fs only — no sync IO, repo rule #85) under
51
+ * the caller-supplied `spillDir` and returns the path; the caller inlines only
52
+ * that path. `spillDir` is supplied by the caller, so this module stays
53
+ * runtime-neutral and never names a dispatcher id.
54
+ */
55
+ export declare function resolveCompletionBody(completion: CompletionEnvelope, spillDir: string): Promise<ResolvedCompletionBody>;
56
+ //# sourceMappingURL=completion-body.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"completion-body.d.ts","sourceRoot":"","sources":["../src/completion-body.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAKH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAInE;;;;GAIG;AACH,eAAO,MAAM,gCAAgC,QAAS,CAAC;AACvD,eAAO,MAAM,4BAA4B,SAAU,CAAC;AAGpD,MAAM,MAAM,sBAAsB,GAC9B;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAChC;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAOtC;;;;;;;;GAQG;AACH,wBAAgB,4BAA4B,CAC1C,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EACd,EAAE,EAAE,MAAM,GACT,MAAM,CAER;AAED;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CACpC,GAAG,GAAE,MAAM,CAAC,UAAmC,GAC9C,MAAM,CAUR;AAED;;;;;;GAMG;AACH,wBAAsB,qBAAqB,CACzC,UAAU,EAAE,kBAAkB,EAC9B,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,sBAAsB,CAAC,CAkBjC"}
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Neutral completion-body resolution shared by every runtime and the host.
3
+ *
4
+ * A teammate completion is delivered back to the dispatcher as turn text. When
5
+ * the result is short it is inlined verbatim; when it overflows the inline
6
+ * budget the full result is spilled to an owner-only 0600 file under the
7
+ * host-supplied spill directory and only the path is inlined, so a large result
8
+ * never floods the dispatcher's context window.
9
+ *
10
+ * The spill directory is supplied by the caller (the host through its path
11
+ * context, or a runtime through the create context), so this module owns no
12
+ * Dreamux layout contract — only the safe filename shape inside that directory.
13
+ */
14
+ import { chmod, writeFile } from 'node:fs/promises';
15
+ import { join } from 'node:path';
16
+ import { ensureOwnerOnlyDir } from './os.js';
17
+ /**
18
+ * Inline character budget, mirroring claude-code's native task-output budget
19
+ * (`getMaxTaskOutputLength`): default 32000 chars, `TASK_MAX_OUTPUT_LENGTH`
20
+ * override, clamped to 160000. Counted in characters, not bytes.
21
+ */
22
+ export const COMPLETION_INLINE_BUDGET_DEFAULT = 32_000;
23
+ export const COMPLETION_INLINE_BUDGET_MAX = 160_000;
24
+ const COMPLETION_INLINE_BUDGET_ENV = 'TASK_MAX_OUTPUT_LENGTH';
25
+ /** Sanitize a name into a safe single path segment. */
26
+ function safeSegment(name) {
27
+ return name.replace(/[^A-Za-z0-9._-]/g, '_');
28
+ }
29
+ /**
30
+ * Spill file for a teammate completion result that overflows the inline budget.
31
+ * Both runtimes write the full result here and inline only this path into the
32
+ * dispatcher turn, so a large result never floods the dispatcher's context.
33
+ * `spillDir` is the owning dispatcher's completion spill dir, supplied by the
34
+ * caller so a teammate runtime spills under its operator dispatcher, not its
35
+ * composite runtime id. `source` and `id` are sanitized for filename safety; the
36
+ * id is unique per completion (teammate name + turn id).
37
+ */
38
+ export function teamMateCompletionOutputPath(spillDir, source, id) {
39
+ return join(spillDir, `teammate-${safeSegment(source)}-${safeSegment(id)}.output`);
40
+ }
41
+ /**
42
+ * Resolve the effective inline budget from the environment, in the spirit of
43
+ * native `validateBoundedIntEnvVar`: unset/blank or non-positive falls back to
44
+ * the default; values above the upper bound are clamped (not rejected). Stricter
45
+ * than native's lenient `parseInt`, a value that is not a plain decimal integer
46
+ * (e.g. `32k` or `123abc`) falls back to the default rather than being partially
47
+ * parsed.
48
+ */
49
+ export function completionInlineBudget(env = globalThis.process.env) {
50
+ const raw = env[COMPLETION_INLINE_BUDGET_ENV]?.trim();
51
+ if (raw === undefined || raw === '' || !/^\d+$/.test(raw)) {
52
+ return COMPLETION_INLINE_BUDGET_DEFAULT;
53
+ }
54
+ const parsed = Number(raw);
55
+ if (parsed <= 0) {
56
+ return COMPLETION_INLINE_BUDGET_DEFAULT;
57
+ }
58
+ return Math.min(parsed, COMPLETION_INLINE_BUDGET_MAX);
59
+ }
60
+ /**
61
+ * Decide whether a completion result is inlined or spilled. Spilling writes the
62
+ * FULL result to a 0600 file (async fs only — no sync IO, repo rule #85) under
63
+ * the caller-supplied `spillDir` and returns the path; the caller inlines only
64
+ * that path. `spillDir` is supplied by the caller, so this module stays
65
+ * runtime-neutral and never names a dispatcher id.
66
+ */
67
+ export async function resolveCompletionBody(completion, spillDir) {
68
+ const budget = completionInlineBudget();
69
+ if (completion.result.length <= budget) {
70
+ return { kind: 'inline', text: completion.result };
71
+ }
72
+ const path = teamMateCompletionOutputPath(spillDir, completion.source, completion.id);
73
+ // Owner-only spill dir: the cache may hold full teammate output. Use the
74
+ // shared helper so a pre-existing permissive dir is tightened and a symlink /
75
+ // foreign-uid dir is rejected (issue #182 — same invariant as the run tree),
76
+ // then a 0600 file + explicit chmod (writeFile's `mode` honors the umask).
77
+ await ensureOwnerOnlyDir(spillDir);
78
+ await writeFile(path, completion.result, { mode: 0o600 });
79
+ await chmod(path, 0o600);
80
+ return { kind: 'spilled', path };
81
+ }
82
+ //# sourceMappingURL=completion-body.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"completion-body.js","sourceRoot":"","sources":["../src/completion-body.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAIjC,OAAO,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAE7C;;;;GAIG;AACH,MAAM,CAAC,MAAM,gCAAgC,GAAG,MAAM,CAAC;AACvD,MAAM,CAAC,MAAM,4BAA4B,GAAG,OAAO,CAAC;AACpD,MAAM,4BAA4B,GAAG,wBAAwB,CAAC;AAM9D,uDAAuD;AACvD,SAAS,WAAW,CAAC,IAAY;IAC/B,OAAO,IAAI,CAAC,OAAO,CAAC,kBAAkB,EAAE,GAAG,CAAC,CAAC;AAC/C,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,4BAA4B,CAC1C,QAAgB,EAChB,MAAc,EACd,EAAU;IAEV,OAAO,IAAI,CAAC,QAAQ,EAAE,YAAY,WAAW,CAAC,MAAM,CAAC,IAAI,WAAW,CAAC,EAAE,CAAC,SAAS,CAAC,CAAC;AACrF,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,sBAAsB,CACpC,MAAyB,UAAU,CAAC,OAAO,CAAC,GAAG;IAE/C,MAAM,GAAG,GAAG,GAAG,CAAC,4BAA4B,CAAC,EAAE,IAAI,EAAE,CAAC;IACtD,IAAI,GAAG,KAAK,SAAS,IAAI,GAAG,KAAK,EAAE,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;QAC1D,OAAO,gCAAgC,CAAC;IAC1C,CAAC;IACD,MAAM,MAAM,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;IAC3B,IAAI,MAAM,IAAI,CAAC,EAAE,CAAC;QAChB,OAAO,gCAAgC,CAAC;IAC1C,CAAC;IACD,OAAO,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,4BAA4B,CAAC,CAAC;AACxD,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,qBAAqB,CACzC,UAA8B,EAC9B,QAAgB;IAEhB,MAAM,MAAM,GAAG,sBAAsB,EAAE,CAAC;IACxC,IAAI,UAAU,CAAC,MAAM,CAAC,MAAM,IAAI,MAAM,EAAE,CAAC;QACvC,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,UAAU,CAAC,MAAM,EAAE,CAAC;IACrD,CAAC;IACD,MAAM,IAAI,GAAG,4BAA4B,CACvC,QAAQ,EACR,UAAU,CAAC,MAAM,EACjB,UAAU,CAAC,EAAE,CACd,CAAC;IACF,yEAAyE;IACzE,8EAA8E;IAC9E,6EAA6E;IAC7E,2EAA2E;IAC3E,MAAM,kBAAkB,CAAC,QAAQ,CAAC,CAAC;IACnC,MAAM,SAAS,CAAC,IAAI,EAAE,UAAU,CAAC,MAAM,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IAC1D,MAAM,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IACzB,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;AACnC,CAAC"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Neutral config validation primitives.
3
+ *
4
+ * Extracted from `config/config.ts` so that per-runtime config readers (each
5
+ * builtin's `agent-runtime/builtin/<name>/config.ts`) can validate their own
6
+ * config blocks without importing `config/config.ts` — importing the host
7
+ * config module from a builtin would re-form the builtin -> config import
8
+ * cycle. These helpers are runtime-agnostic: they only know about JSON shapes
9
+ * and produce `dreamux config error in <file>: ...` messages.
10
+ */
11
+ export declare function isPlainObject(v: unknown): v is Record<string, unknown>;
12
+ export declare function describeType(v: unknown): string;
13
+ export declare function rejectUnknownKeys(obj: Record<string, unknown>, allowed: Set<string>, file: string, prefix: string): void;
14
+ export declare function requireNonEmptyString(obj: Record<string, unknown>, key: string, file: string, prefix?: string): string;
15
+ export declare function readOptionalString(obj: Record<string, unknown>, key: string, file: string, prefix?: string): string | null;
16
+ export declare function readOptionalBoolean(obj: Record<string, unknown>, key: string, fallback: boolean, file: string, prefix?: string): boolean;
17
+ export declare function requireStringArray(obj: Record<string, unknown>, key: string, fallback: string[], file: string, prefix?: string): string[];
18
+ export declare function requireStringRecord(obj: Record<string, unknown>, key: string, fallback: Record<string, string>, file: string, prefix?: string): Record<string, string>;
19
+ export declare function requirePositiveInt(obj: Record<string, unknown>, key: string, fallback: number, file: string, prefix?: string): number;
20
+ export declare function readProviderConfigObject(rawConfig: unknown, file: string, name: string, options?: {
21
+ allowMissing?: boolean;
22
+ }): Record<string, unknown>;
23
+ //# sourceMappingURL=config-validate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config-validate.d.ts","sourceRoot":"","sources":["../src/config-validate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,wBAAgB,aAAa,CAAC,CAAC,EAAE,OAAO,GAAG,CAAC,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAEtE;AAED,wBAAgB,YAAY,CAAC,CAAC,EAAE,OAAO,GAAG,MAAM,CAI/C;AAED,wBAAgB,iBAAiB,CAC/B,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC5B,OAAO,EAAE,GAAG,CAAC,MAAM,CAAC,EACpB,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,GACb,IAAI,CAgBN;AAuBD,wBAAgB,qBAAqB,CACnC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC5B,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,MAAM,EACZ,MAAM,SAAK,GACV,MAAM,CAMR;AAED,wBAAgB,kBAAkB,CAChC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC5B,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,MAAM,EACZ,MAAM,SAAK,GACV,MAAM,GAAG,IAAI,CAIf;AAED,wBAAgB,mBAAmB,CACjC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC5B,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,OAAO,EACjB,IAAI,EAAE,MAAM,EACZ,MAAM,SAAK,GACV,OAAO,CAOT;AAED,wBAAgB,kBAAkB,CAChC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC5B,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,MAAM,EAAE,EAClB,IAAI,EAAE,MAAM,EACZ,MAAM,SAAK,GACV,MAAM,EAAE,CAgBV;AAED,wBAAgB,mBAAmB,CACjC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC5B,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAChC,IAAI,EAAE,MAAM,EACZ,MAAM,SAAK,GACV,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAkBxB;AAgBD,wBAAgB,kBAAkB,CAChC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC5B,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,EACZ,MAAM,SAAK,GACV,MAAM,CASR;AAED,wBAAgB,wBAAwB,CACtC,SAAS,EAAE,OAAO,EAClB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE;IAAE,YAAY,CAAC,EAAE,OAAO,CAAA;CAAO,GACvC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAQzB"}
@@ -0,0 +1,122 @@
1
+ /**
2
+ * Neutral config validation primitives.
3
+ *
4
+ * Extracted from `config/config.ts` so that per-runtime config readers (each
5
+ * builtin's `agent-runtime/builtin/<name>/config.ts`) can validate their own
6
+ * config blocks without importing `config/config.ts` — importing the host
7
+ * config module from a builtin would re-form the builtin -> config import
8
+ * cycle. These helpers are runtime-agnostic: they only know about JSON shapes
9
+ * and produce `dreamux config error in <file>: ...` messages.
10
+ */
11
+ export function isPlainObject(v) {
12
+ return v !== null && typeof v === 'object' && !Array.isArray(v);
13
+ }
14
+ export function describeType(v) {
15
+ if (v === null)
16
+ return 'null';
17
+ if (Array.isArray(v))
18
+ return 'array';
19
+ return typeof v;
20
+ }
21
+ export function rejectUnknownKeys(obj, allowed, file, prefix) {
22
+ for (const key of Object.keys(obj)) {
23
+ if (allowed.has(key))
24
+ continue;
25
+ const name = `${prefix}${key}`;
26
+ if (/^dispatchers\[\d+\]\.$/.test(prefix) && (key === 'feishu' || key === 'codex')) {
27
+ throw new Error(`dreamux config error in ${file}: ${name} is not supported by the providerized config v2 schema.\n` +
28
+ 'Dreamux 0.x does not silently migrate operator-owned config. Rebuild this dispatcher with ' +
29
+ 'dispatchers[].channels[] for the channel and a named agents[] entry referenced via ' +
30
+ 'dispatchers[].agentRuntime for the runtime, then restart.');
31
+ }
32
+ throw new Error(`dreamux config error in ${file}: ${name} is not supported by the providerized config v2 schema`);
33
+ }
34
+ }
35
+ function ensureString(v, key, file) {
36
+ if (typeof v !== 'string') {
37
+ throw new Error(`dreamux config error in ${file}: ${key} must be a string (got ${describeType(v)})`);
38
+ }
39
+ return v;
40
+ }
41
+ function requireString(obj, key, fallback, file, prefix = '') {
42
+ const v = obj[key];
43
+ if (v === undefined)
44
+ return fallback;
45
+ return ensureString(v, `${prefix}${key}`, file);
46
+ }
47
+ export function requireNonEmptyString(obj, key, file, prefix = '') {
48
+ const value = requireString(obj, key, '', file, prefix);
49
+ if (value.trim() !== '')
50
+ return value;
51
+ throw new Error(`dreamux config error in ${file}: ${prefix}${key} must be a non-empty string`);
52
+ }
53
+ export function readOptionalString(obj, key, file, prefix = '') {
54
+ const v = obj[key];
55
+ if (v === undefined || v === null)
56
+ return null;
57
+ return ensureString(v, `${prefix}${key}`, file);
58
+ }
59
+ export function readOptionalBoolean(obj, key, fallback, file, prefix = '') {
60
+ const v = obj[key];
61
+ if (v === undefined)
62
+ return fallback;
63
+ if (typeof v === 'boolean')
64
+ return v;
65
+ throw new Error(`dreamux config error in ${file}: ${prefix}${key} must be a boolean (got ${describeType(v)})`);
66
+ }
67
+ export function requireStringArray(obj, key, fallback, file, prefix = '') {
68
+ const v = obj[key];
69
+ if (v === undefined)
70
+ return fallback;
71
+ if (!Array.isArray(v)) {
72
+ throw new Error(`dreamux config error in ${file}: ${prefix}${key} must be an array of strings (got ${describeType(v)})`);
73
+ }
74
+ return v.map((item, i) => {
75
+ if (typeof item !== 'string') {
76
+ throw new Error(`dreamux config error in ${file}: ${prefix}${key}[${i}] must be a string (got ${describeType(item)})`);
77
+ }
78
+ return item;
79
+ });
80
+ }
81
+ export function requireStringRecord(obj, key, fallback, file, prefix = '') {
82
+ const v = obj[key];
83
+ if (v === undefined)
84
+ return { ...fallback };
85
+ if (!isPlainObject(v)) {
86
+ throw new Error(`dreamux config error in ${file}: ${prefix}${key} must be an object of strings (got ${describeType(v)})`);
87
+ }
88
+ const out = {};
89
+ for (const [entryKey, entryValue] of Object.entries(v)) {
90
+ if (typeof entryValue !== 'string') {
91
+ throw new Error(`dreamux config error in ${file}: ${prefix}${key}.${entryKey} must be a string (got ${describeType(entryValue)})`);
92
+ }
93
+ out[entryKey] = entryValue;
94
+ }
95
+ return out;
96
+ }
97
+ function readInt(obj, key, file, prefix) {
98
+ const v = obj[key];
99
+ if (v === undefined)
100
+ return null;
101
+ if (typeof v === 'number' && Number.isInteger(v))
102
+ return v;
103
+ throw new Error(`dreamux config error in ${file}: ${prefix}${key} must be an integer (got ${describeType(v)})`);
104
+ }
105
+ export function requirePositiveInt(obj, key, fallback, file, prefix = '') {
106
+ const n = readInt(obj, key, file, prefix);
107
+ if (n === null)
108
+ return fallback;
109
+ if (n <= 0) {
110
+ throw new Error(`dreamux config error in ${file}: ${prefix}${key} must be > 0 (got ${n})`);
111
+ }
112
+ return n;
113
+ }
114
+ export function readProviderConfigObject(rawConfig, file, name, options = {}) {
115
+ if (rawConfig === undefined && options.allowMissing === true)
116
+ return {};
117
+ if (!isPlainObject(rawConfig)) {
118
+ throw new Error(`dreamux config error in ${file}: ${name} must be an object (got ${describeType(rawConfig)})`);
119
+ }
120
+ return rawConfig;
121
+ }
122
+ //# sourceMappingURL=config-validate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config-validate.js","sourceRoot":"","sources":["../src/config-validate.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ export * from './config-validate.js';
2
+ export * from './os.js';
3
+ export * from './completion-body.js';
4
+ export * from './socket-budget.js';
5
+ export * from './turn-render.js';
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,sBAAsB,CAAC;AACrC,cAAc,SAAS,CAAC;AACxB,cAAc,sBAAsB,CAAC;AACrC,cAAc,oBAAoB,CAAC;AACnC,cAAc,kBAAkB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,6 @@
1
+ export * from './config-validate.js';
2
+ export * from './os.js';
3
+ export * from './completion-body.js';
4
+ export * from './socket-budget.js';
5
+ export * from './turn-render.js';
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,sBAAsB,CAAC;AACrC,cAAc,SAAS,CAAC;AACxB,cAAc,sBAAsB,CAAC;AACrC,cAAc,oBAAoB,CAAC;AACnC,cAAc,kBAAkB,CAAC"}
package/dist/os.d.ts ADDED
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Generic OS/filesystem utilities shared across Dreamux providers and host,
3
+ * vendored into this leaf package so the provider packages depend on
4
+ * `@excitedjs/dreamux-types` (+ this package) only and never import
5
+ * `@excitedjs/dreamux` core. These are platform primitives (process-group
6
+ * signalling, owner-only dir enforcement, empty-log cleanup, existence probe),
7
+ * NOT Dreamux host layout/path/socket/log contracts — those are supplied by the
8
+ * host through the create context and provider options.
9
+ */
10
+ /** True when `pid` names a live process (EPERM counts as alive). */
11
+ export declare function isProcessAlive(pid: number): boolean;
12
+ /** Signal an entire process group; ESRCH/EPERM are swallowed. */
13
+ export declare function killProcessGroup(pgid: number, signal: NodeJS.Signals | number): void;
14
+ export interface EnsureOwnerOnlyDirOptions {
15
+ /** Current-user uid probe override (tests). */
16
+ getuid?: () => number;
17
+ }
18
+ /**
19
+ * Create/adopt a directory and guarantee the owner-only (0700) privacy
20
+ * invariant regardless of who created it: reject a symlink leaf, fail loud on a
21
+ * foreign-uid dir, and tighten any group/other permission bits.
22
+ */
23
+ export declare function ensureOwnerOnlyDir(path: string, options?: EnsureOwnerOnlyDirOptions): Promise<void>;
24
+ /**
25
+ * Remove `path` only if it exists and is zero bytes. Best-effort: a missing,
26
+ * non-empty, or busy file is left untouched and never throws.
27
+ */
28
+ export declare function removeEmptyLogFile(path: string): Promise<void>;
29
+ /** Best-effort existence probe — the async replacement for `existsSync`. */
30
+ export declare function pathExists(path: string): Promise<boolean>;
31
+ //# sourceMappingURL=os.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"os.d.ts","sourceRoot":"","sources":["../src/os.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,oEAAoE;AACpE,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CASnD;AAED,iEAAiE;AACjE,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,CAAC,OAAO,GAAG,MAAM,GAC9B,IAAI,CASN;AAED,MAAM,WAAW,yBAAyB;IACxC,+CAA+C;IAC/C,MAAM,CAAC,EAAE,MAAM,MAAM,CAAC;CACvB;AAED;;;;GAIG;AACH,wBAAsB,kBAAkB,CACtC,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE,yBAA8B,GACtC,OAAO,CAAC,IAAI,CAAC,CAkBf;AAED;;;GAGG;AACH,wBAAsB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAOpE;AAED,4EAA4E;AAC5E,wBAAsB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAO/D"}
package/dist/os.js ADDED
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Generic OS/filesystem utilities shared across Dreamux providers and host,
3
+ * vendored into this leaf package so the provider packages depend on
4
+ * `@excitedjs/dreamux-types` (+ this package) only and never import
5
+ * `@excitedjs/dreamux` core. These are platform primitives (process-group
6
+ * signalling, owner-only dir enforcement, empty-log cleanup, existence probe),
7
+ * NOT Dreamux host layout/path/socket/log contracts — those are supplied by the
8
+ * host through the create context and provider options.
9
+ */
10
+ import { access, chmod, lstat, mkdir, stat, unlink } from 'node:fs/promises';
11
+ /** True when `pid` names a live process (EPERM counts as alive). */
12
+ export function isProcessAlive(pid) {
13
+ if (!Number.isFinite(pid) || pid <= 0)
14
+ return false;
15
+ try {
16
+ process.kill(pid, 0);
17
+ return true;
18
+ }
19
+ catch (e) {
20
+ const errno = e.code;
21
+ return errno === 'EPERM';
22
+ }
23
+ }
24
+ /** Signal an entire process group; ESRCH/EPERM are swallowed. */
25
+ export function killProcessGroup(pgid, signal) {
26
+ if (!Number.isFinite(pgid) || pgid <= 0)
27
+ return;
28
+ try {
29
+ process.kill(-pgid, signal);
30
+ }
31
+ catch (e) {
32
+ const errno = e.code;
33
+ if (errno === 'ESRCH' || errno === 'EPERM')
34
+ return;
35
+ throw e;
36
+ }
37
+ }
38
+ /**
39
+ * Create/adopt a directory and guarantee the owner-only (0700) privacy
40
+ * invariant regardless of who created it: reject a symlink leaf, fail loud on a
41
+ * foreign-uid dir, and tighten any group/other permission bits.
42
+ */
43
+ export async function ensureOwnerOnlyDir(path, options = {}) {
44
+ await mkdir(path, { recursive: true, mode: 0o700 });
45
+ const info = await lstat(path);
46
+ if (info.isSymbolicLink()) {
47
+ throw new Error(`refusing to use Dreamux runtime directory ${path}: it is a symlink, not a real directory`);
48
+ }
49
+ const getuid = options.getuid ?? process.getuid?.bind(process);
50
+ if (getuid !== undefined && info.uid !== getuid()) {
51
+ throw new Error(`refusing to use Dreamux runtime directory ${path}: it is owned by uid ${info.uid}, ` +
52
+ `not the current user (uid ${getuid()})`);
53
+ }
54
+ if ((info.mode & 0o077) !== 0) {
55
+ await chmod(path, 0o700);
56
+ }
57
+ }
58
+ /**
59
+ * Remove `path` only if it exists and is zero bytes. Best-effort: a missing,
60
+ * non-empty, or busy file is left untouched and never throws.
61
+ */
62
+ export async function removeEmptyLogFile(path) {
63
+ try {
64
+ const info = await stat(path);
65
+ if (info.size === 0)
66
+ await unlink(path);
67
+ }
68
+ catch {
69
+ /* best effort — missing/busy/non-empty files are left as-is */
70
+ }
71
+ }
72
+ /** Best-effort existence probe — the async replacement for `existsSync`. */
73
+ export async function pathExists(path) {
74
+ try {
75
+ await access(path);
76
+ return true;
77
+ }
78
+ catch {
79
+ return false;
80
+ }
81
+ }
82
+ //# sourceMappingURL=os.js.map
package/dist/os.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"os.js","sourceRoot":"","sources":["../src/os.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAE7E,oEAAoE;AACpE,MAAM,UAAU,cAAc,CAAC,GAAW;IACxC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC;QAAE,OAAO,KAAK,CAAC;IACpD,IAAI,CAAC;QACH,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;QACrB,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,MAAM,KAAK,GAAI,CAA2B,CAAC,IAAI,CAAC;QAChD,OAAO,KAAK,KAAK,OAAO,CAAC;IAC3B,CAAC;AACH,CAAC;AAED,iEAAiE;AACjE,MAAM,UAAU,gBAAgB,CAC9B,IAAY,EACZ,MAA+B;IAE/B,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC;QAAE,OAAO;IAChD,IAAI,CAAC;QACH,OAAO,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IAC9B,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,MAAM,KAAK,GAAI,CAA2B,CAAC,IAAI,CAAC;QAChD,IAAI,KAAK,KAAK,OAAO,IAAI,KAAK,KAAK,OAAO;YAAE,OAAO;QACnD,MAAM,CAAC,CAAC;IACV,CAAC;AACH,CAAC;AAOD;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,IAAY,EACZ,UAAqC,EAAE;IAEvC,MAAM,KAAK,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IACpD,MAAM,IAAI,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC,CAAC;IAC/B,IAAI,IAAI,CAAC,cAAc,EAAE,EAAE,CAAC;QAC1B,MAAM,IAAI,KAAK,CACb,6CAA6C,IAAI,yCAAyC,CAC3F,CAAC;IACJ,CAAC;IACD,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,OAAO,CAAC,MAAM,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;IAC/D,IAAI,MAAM,KAAK,SAAS,IAAI,IAAI,CAAC,GAAG,KAAK,MAAM,EAAE,EAAE,CAAC;QAClD,MAAM,IAAI,KAAK,CACb,6CAA6C,IAAI,wBAAwB,IAAI,CAAC,GAAG,IAAI;YACnF,6BAA6B,MAAM,EAAE,GAAG,CAC3C,CAAC;IACJ,CAAC;IACD,IAAI,CAAC,IAAI,CAAC,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC;QAC9B,MAAM,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IAC3B,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,IAAY;IACnD,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,CAAC;QAC9B,IAAI,IAAI,CAAC,IAAI,KAAK,CAAC;YAAE,MAAM,MAAM,CAAC,IAAI,CAAC,CAAC;IAC1C,CAAC;IAAC,MAAM,CAAC;QACP,+DAA+D;IACjE,CAAC;AACH,CAAC;AAED,4EAA4E;AAC5E,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,IAAY;IAC3C,IAAI,CAAC;QACH,MAAM,MAAM,CAAC,IAAI,CAAC,CAAC;QACnB,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Unix-domain socket path-budget primitives (issue #209 cleanup).
3
+ *
4
+ * The kernel caps a Unix socket path at `sun_path` bytes (108 on Linux including
5
+ * the NUL terminator, 104 on macOS). 103 usable bytes is the safe cross-platform
6
+ * budget Dreamux enforces. These are pure functions with no IO and no host
7
+ * path-layout knowledge: a provider package that allocates its own rendezvous
8
+ * socket (e.g. the Codex app-server socket) uses them to pick a candidate
9
+ * directory whose full path fits, while Dreamux core uses the same primitives so
10
+ * the budget never drifts between the two.
11
+ */
12
+ /** Safe cross-platform `sun_path` byte budget for a Unix-domain socket path. */
13
+ export declare const DREAMUX_UNIX_SOCKET_PATH_MAX_BYTES = 103;
14
+ /** Whether `path` fits the Unix-domain socket `sun_path` budget. */
15
+ export declare function unixSocketPathFitsBudget(path: string): boolean;
16
+ /**
17
+ * Return `path` if it fits the socket budget, else throw a fail-loud error
18
+ * naming `label`, the byte count, and the offending path.
19
+ */
20
+ export declare function assertUnixSocketPathBudget(path: string, label: string): string;
21
+ //# sourceMappingURL=socket-budget.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"socket-budget.d.ts","sourceRoot":"","sources":["../src/socket-budget.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,gFAAgF;AAChF,eAAO,MAAM,kCAAkC,MAAM,CAAC;AAEtD,oEAAoE;AACpE,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAE9D;AAED;;;GAGG;AACH,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAM9E"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Unix-domain socket path-budget primitives (issue #209 cleanup).
3
+ *
4
+ * The kernel caps a Unix socket path at `sun_path` bytes (108 on Linux including
5
+ * the NUL terminator, 104 on macOS). 103 usable bytes is the safe cross-platform
6
+ * budget Dreamux enforces. These are pure functions with no IO and no host
7
+ * path-layout knowledge: a provider package that allocates its own rendezvous
8
+ * socket (e.g. the Codex app-server socket) uses them to pick a candidate
9
+ * directory whose full path fits, while Dreamux core uses the same primitives so
10
+ * the budget never drifts between the two.
11
+ */
12
+ /** Safe cross-platform `sun_path` byte budget for a Unix-domain socket path. */
13
+ export const DREAMUX_UNIX_SOCKET_PATH_MAX_BYTES = 103;
14
+ /** Whether `path` fits the Unix-domain socket `sun_path` budget. */
15
+ export function unixSocketPathFitsBudget(path) {
16
+ return Buffer.byteLength(path, 'utf8') <= DREAMUX_UNIX_SOCKET_PATH_MAX_BYTES;
17
+ }
18
+ /**
19
+ * Return `path` if it fits the socket budget, else throw a fail-loud error
20
+ * naming `label`, the byte count, and the offending path.
21
+ */
22
+ export function assertUnixSocketPathBudget(path, label) {
23
+ if (unixSocketPathFitsBudget(path))
24
+ return path;
25
+ const bytes = Buffer.byteLength(path, 'utf8');
26
+ throw new Error(`${label} is too long for Unix sockets (${bytes} bytes > ${DREAMUX_UNIX_SOCKET_PATH_MAX_BYTES} safe bytes): ${path}`);
27
+ }
28
+ //# sourceMappingURL=socket-budget.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"socket-budget.js","sourceRoot":"","sources":["../src/socket-budget.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,gFAAgF;AAChF,MAAM,CAAC,MAAM,kCAAkC,GAAG,GAAG,CAAC;AAEtD,oEAAoE;AACpE,MAAM,UAAU,wBAAwB,CAAC,IAAY;IACnD,OAAO,MAAM,CAAC,UAAU,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,kCAAkC,CAAC;AAC/E,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,0BAA0B,CAAC,IAAY,EAAE,KAAa;IACpE,IAAI,wBAAwB,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IAChD,MAAM,KAAK,GAAG,MAAM,CAAC,UAAU,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IAC9C,MAAM,IAAI,KAAK,CACb,GAAG,KAAK,kCAAkC,KAAK,YAAY,kCAAkC,iBAAiB,IAAI,EAAE,CACrH,CAAC;AACJ,CAAC"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Inbound-turn render helpers. The data shapes are published by
3
+ * `@excitedjs/dreamux-types`; these executable helpers render a neutral
4
+ * `InboundTurnInput` into runtime delivery text. Shared by every runtime and
5
+ * the host, vendored into this leaf package so the provider packages never
6
+ * import `@excitedjs/dreamux` core.
7
+ */
8
+ import type { InboundTurnInput } from '@excitedjs/dreamux-types';
9
+ export declare const DEFAULT_MESSAGE_ID_DEDUPE_WINDOW = 1024;
10
+ /**
11
+ * Wrap a channel turn body in the native `<channel source="…" …>` envelope.
12
+ * Only safe attribute keys (`^[a-zA-Z_][a-zA-Z0-9_]*$`) are rendered and every
13
+ * value is XML-escaped.
14
+ */
15
+ export declare function renderChannelBlock(source: string, attrs: ReadonlyArray<readonly [string, string]>, body: string): string;
16
+ /**
17
+ * Render an inbound turn to delivery text. A channel-structured input (both
18
+ * `attrs` and `body` present) is wrapped into the `<channel>` block; a plain
19
+ * input (e.g. a system trigger turn) passes its `text` through unchanged.
20
+ */
21
+ export declare function renderChannelInput(input: InboundTurnInput): string;
22
+ //# sourceMappingURL=turn-render.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"turn-render.d.ts","sourceRoot":"","sources":["../src/turn-render.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAEjE,eAAO,MAAM,gCAAgC,OAAO,CAAC;AAYrD;;;;GAIG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,aAAa,CAAC,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,EAC/C,IAAI,EAAE,MAAM,GACX,MAAM,CAMR;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,gBAAgB,GAAG,MAAM,CAKlE"}
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Inbound-turn render helpers. The data shapes are published by
3
+ * `@excitedjs/dreamux-types`; these executable helpers render a neutral
4
+ * `InboundTurnInput` into runtime delivery text. Shared by every runtime and
5
+ * the host, vendored into this leaf package so the provider packages never
6
+ * import `@excitedjs/dreamux` core.
7
+ */
8
+ export const DEFAULT_MESSAGE_ID_DEDUPE_WINDOW = 1024;
9
+ const SAFE_CHANNEL_ATTR_KEY = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
10
+ function escapeChannelAttr(value) {
11
+ return value
12
+ .replaceAll('&', '&amp;')
13
+ .replaceAll('<', '&lt;')
14
+ .replaceAll('>', '&gt;')
15
+ .replaceAll('"', '&quot;');
16
+ }
17
+ /**
18
+ * Wrap a channel turn body in the native `<channel source="…" …>` envelope.
19
+ * Only safe attribute keys (`^[a-zA-Z_][a-zA-Z0-9_]*$`) are rendered and every
20
+ * value is XML-escaped.
21
+ */
22
+ export function renderChannelBlock(source, attrs, body) {
23
+ const rendered = attrs
24
+ .filter(([key]) => SAFE_CHANNEL_ATTR_KEY.test(key))
25
+ .map(([key, value]) => ` ${key}="${escapeChannelAttr(value)}"`)
26
+ .join('');
27
+ return `<channel source="${escapeChannelAttr(source)}"${rendered}>\n${body}\n</channel>`;
28
+ }
29
+ /**
30
+ * Render an inbound turn to delivery text. A channel-structured input (both
31
+ * `attrs` and `body` present) is wrapped into the `<channel>` block; a plain
32
+ * input (e.g. a system trigger turn) passes its `text` through unchanged.
33
+ */
34
+ export function renderChannelInput(input) {
35
+ if (input.attrs === undefined || input.body === undefined) {
36
+ return input.text;
37
+ }
38
+ return renderChannelBlock(input.source ?? 'channel', input.attrs, input.body);
39
+ }
40
+ //# sourceMappingURL=turn-render.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"turn-render.js","sourceRoot":"","sources":["../src/turn-render.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,MAAM,CAAC,MAAM,gCAAgC,GAAG,IAAI,CAAC;AAErD,MAAM,qBAAqB,GAAG,0BAA0B,CAAC;AAEzD,SAAS,iBAAiB,CAAC,KAAa;IACtC,OAAO,KAAK;SACT,UAAU,CAAC,GAAG,EAAE,OAAO,CAAC;SACxB,UAAU,CAAC,GAAG,EAAE,MAAM,CAAC;SACvB,UAAU,CAAC,GAAG,EAAE,MAAM,CAAC;SACvB,UAAU,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;AAC/B,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,kBAAkB,CAChC,MAAc,EACd,KAA+C,EAC/C,IAAY;IAEZ,MAAM,QAAQ,GAAG,KAAK;SACnB,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,qBAAqB,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;SAClD,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,IAAI,GAAG,KAAK,iBAAiB,CAAC,KAAK,CAAC,GAAG,CAAC;SAC9D,IAAI,CAAC,EAAE,CAAC,CAAC;IACZ,OAAO,oBAAoB,iBAAiB,CAAC,MAAM,CAAC,IAAI,QAAQ,MAAM,IAAI,cAAc,CAAC;AAC3F,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,kBAAkB,CAAC,KAAuB;IACxD,IAAI,KAAK,CAAC,KAAK,KAAK,SAAS,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;QAC1D,OAAO,KAAK,CAAC,IAAI,CAAC;IACpB,CAAC;IACD,OAAO,kBAAkB,CAAC,KAAK,CAAC,MAAM,IAAI,SAAS,EAAE,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;AAChF,CAAC"}
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "@excitedjs/dreamux-utils",
3
+ "version": "0.1.1-alpha.g0ddd418597ca",
4
+ "description": "Pure shared utilities for Dreamux providers and host: neutral config-validation primitives, OS/filesystem helpers, completion-body resolution, and inbound-turn render helpers. Depends on @excitedjs/dreamux-types only, never on @excitedjs/dreamux core (issue excitedjs/dreamux#209).",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/excitedjs/dreamux.git",
9
+ "directory": "packages/dreamux-utils"
10
+ },
11
+ "type": "module",
12
+ "main": "./dist/index.js",
13
+ "types": "./dist/index.d.ts",
14
+ "exports": {
15
+ ".": {
16
+ "types": "./dist/index.d.ts",
17
+ "import": "./dist/index.js",
18
+ "default": "./dist/index.js"
19
+ }
20
+ },
21
+ "engines": {
22
+ "node": ">=22.7"
23
+ },
24
+ "files": [
25
+ "dist",
26
+ "README.md",
27
+ "LICENSE"
28
+ ],
29
+ "dependencies": {
30
+ "@excitedjs/dreamux-types": "0.2.0-alpha.g0ddd418597ca"
31
+ },
32
+ "devDependencies": {
33
+ "@types/node": "^22.10.0",
34
+ "eslint": "^9.39.0",
35
+ "typescript": "^5.7.0",
36
+ "vitest": "^2.1.0",
37
+ "@excitedjs/eslint-config": "0.0.0"
38
+ },
39
+ "scripts": {
40
+ "build": "tsc -p tsconfig.json",
41
+ "typecheck": "tsc -p tsconfig.json --noEmit",
42
+ "typecheck:tests": "tsc -p tsconfig.tests.json",
43
+ "lint": "eslint .",
44
+ "test": "vitest run",
45
+ "test:watch": "vitest",
46
+ "clean": "rm -rf dist"
47
+ }
48
+ }