@telora/daemon 0.17.53 → 0.17.56
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/build-info.json +2 -2
- package/dist/auth-liveness.d.ts +41 -0
- package/dist/auth-liveness.d.ts.map +1 -0
- package/dist/auth-liveness.js +68 -0
- package/dist/auth-liveness.js.map +1 -0
- package/dist/backends/agent-backend.d.ts +9 -0
- package/dist/backends/agent-backend.d.ts.map +1 -1
- package/dist/backends/claude/claude-backend.d.ts.map +1 -1
- package/dist/backends/claude/claude-backend.js +8 -0
- package/dist/backends/claude/claude-backend.js.map +1 -1
- package/dist/backends/codex/codex-backend.d.ts +9 -4
- package/dist/backends/codex/codex-backend.d.ts.map +1 -1
- package/dist/backends/codex/codex-backend.js +13 -4
- package/dist/backends/codex/codex-backend.js.map +1 -1
- package/dist/cli/connect.d.ts.map +1 -1
- package/dist/cli/connect.js +10 -0
- package/dist/cli/connect.js.map +1 -1
- package/dist/focus-engine.d.ts.map +1 -1
- package/dist/focus-engine.js +12 -0
- package/dist/focus-engine.js.map +1 -1
- package/dist/focus-executor.d.ts +1 -1
- package/dist/focus-executor.d.ts.map +1 -1
- package/dist/focus-executor.js +41 -4
- package/dist/focus-executor.js.map +1 -1
- package/dist/spawn-environment.d.ts +38 -1
- package/dist/spawn-environment.d.ts.map +1 -1
- package/dist/spawn-environment.js +73 -4
- package/dist/spawn-environment.js.map +1 -1
- package/dist/spawn-sandbox.d.ts +117 -0
- package/dist/spawn-sandbox.d.ts.map +1 -0
- package/dist/spawn-sandbox.js +210 -0
- package/dist/spawn-sandbox.js.map +1 -0
- package/dist/types/config.d.ts +40 -0
- package/dist/types/config.d.ts.map +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OS sandbox for spawned agents -- "make the worktree boundary true" (D3).
|
|
3
|
+
*
|
|
4
|
+
* The daemon spawns agents with --dangerously-skip-permissions /
|
|
5
|
+
* --dangerously-bypass-approvals-and-sandbox. The ONLY isolation around such a
|
|
6
|
+
* spawn used to be a git worktree, which bounds the git branch but NOT the OS:
|
|
7
|
+
* the agent could read sibling repos, the operator's home, SSH/cloud creds, and
|
|
8
|
+
* reach the network freely. This module wraps the spawn command in an OS
|
|
9
|
+
* sandbox (bubblewrap on Linux) so the agent's filesystem is confined to its
|
|
10
|
+
* worktree plus an explicit read-only allowlist (toolchain + model-auth config),
|
|
11
|
+
* with privileged capabilities and namespaces dropped.
|
|
12
|
+
*
|
|
13
|
+
* Posture (see docs/security-posture-daemon-execution.md):
|
|
14
|
+
* - FILESYSTEM confinement + capability/namespace isolation are ENFORCED by bwrap.
|
|
15
|
+
* - OUTBOUND EGRESS allowlisting is NOT enforced here (bubblewrap has no L3/L7
|
|
16
|
+
* egress filter); it is a documented residual gap. Network is shared so the
|
|
17
|
+
* agent can still reach the Telora API / git / registries / model API.
|
|
18
|
+
*
|
|
19
|
+
* FAIL-CLOSED: an EXPLICIT 'bwrap' mode whose sandbox cannot initialize REFUSES
|
|
20
|
+
* the spawn (throws SandboxUnavailableError) rather than running unconfined.
|
|
21
|
+
*
|
|
22
|
+
* DEFAULT is 'off' (see resolveSandboxMode): auto-enabling a sandbox whose
|
|
23
|
+
* per-host read-bind set has not been validated could break real spawns, so
|
|
24
|
+
* operators opt in to 'bwrap' after the per-host validation in
|
|
25
|
+
* docs/runbook-daemon-service-user.md. On Linux with bwrap present we log a
|
|
26
|
+
* recommendation to enable it.
|
|
27
|
+
*
|
|
28
|
+
* Pure command construction (buildSandboxCommand) takes injected deps so it is
|
|
29
|
+
* unit-testable without a real bubblewrap.
|
|
30
|
+
*/
|
|
31
|
+
import { existsSync } from 'node:fs';
|
|
32
|
+
import { join, isAbsolute } from 'node:path';
|
|
33
|
+
import { healthEvents } from '@telora/daemon-core';
|
|
34
|
+
/** Thrown when an explicitly-required sandbox cannot initialize (fail-closed). */
|
|
35
|
+
export class SandboxUnavailableError extends Error {
|
|
36
|
+
constructor(detail) {
|
|
37
|
+
super(`Sandbox required but unavailable -- refusing spawn (fail-closed). ${detail}`);
|
|
38
|
+
this.name = 'SandboxUnavailableError';
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/** Default bubblewrap executable name (overridable via deps for tests). */
|
|
42
|
+
export const BWRAP_COMMAND = 'bwrap';
|
|
43
|
+
/** Return true if an executable name/path exists on the host PATH. */
|
|
44
|
+
export function commandOnPath(command, env = process.env) {
|
|
45
|
+
if (isAbsolute(command) || command.includes('/'))
|
|
46
|
+
return existsSync(command);
|
|
47
|
+
const pathEnv = env.PATH ?? '';
|
|
48
|
+
return pathEnv.split(':').filter(Boolean).some((dir) => existsSync(join(dir, command)));
|
|
49
|
+
}
|
|
50
|
+
/** Is bubblewrap available on this host? */
|
|
51
|
+
export function isBwrapAvailable(deps = {}) {
|
|
52
|
+
const cmd = deps.bwrapCommand ?? BWRAP_COMMAND;
|
|
53
|
+
const exists = deps.commandExists ?? ((c) => commandOnPath(c));
|
|
54
|
+
return exists(cmd);
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Resolve the effective sandbox mode.
|
|
58
|
+
*
|
|
59
|
+
* - explicit config.sandbox.mode wins (and is honored even if unavailable --
|
|
60
|
+
* buildSandboxCommand then fail-closes).
|
|
61
|
+
* - unset + darwin => 'off' (dev; bwrap is Linux-only).
|
|
62
|
+
* - unset + linux => 'off' (safe default), with a warning health event emitted
|
|
63
|
+
* when bwrap is available. Operators opt in explicitly after per-host validation
|
|
64
|
+
* -- see docs/runbook-daemon-service-user.md.
|
|
65
|
+
*/
|
|
66
|
+
export function resolveSandboxMode(sandbox, deps = {}) {
|
|
67
|
+
if (sandbox?.mode)
|
|
68
|
+
return sandbox.mode;
|
|
69
|
+
const platform = deps.platform ?? process.platform;
|
|
70
|
+
if (platform !== 'linux')
|
|
71
|
+
return 'off';
|
|
72
|
+
const available = deps.bwrapAvailable ?? isBwrapAvailable();
|
|
73
|
+
if (available) {
|
|
74
|
+
const message = 'bubblewrap is available but the spawn sandbox is OFF (default). ' +
|
|
75
|
+
'Enable filesystem confinement by setting sandbox.mode="bwrap" in daemon.json ' +
|
|
76
|
+
'after validating per-host read-binds -- see docs/runbook-daemon-service-user.md.';
|
|
77
|
+
if (deps.logRecommendation) {
|
|
78
|
+
deps.logRecommendation(message);
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
healthEvents.emit({
|
|
82
|
+
severity: 'warn',
|
|
83
|
+
source: 'spawn-sandbox',
|
|
84
|
+
code: 'sandbox.off_bwrap_available',
|
|
85
|
+
message,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return 'off';
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Build the (possibly sandbox-wrapped) command + args for a spawn.
|
|
93
|
+
*
|
|
94
|
+
* mode 'off' -> returns the inner command unchanged and emits a loud
|
|
95
|
+
* "running unconfined" signal (the spawn has the daemon user's
|
|
96
|
+
* full authority).
|
|
97
|
+
* mode 'bwrap' -> if bwrap is unavailable, THROWS SandboxUnavailableError
|
|
98
|
+
* (fail-closed); otherwise returns a bwrap invocation that binds
|
|
99
|
+
* the worktree read-write, the readOnlyPaths read-only, system
|
|
100
|
+
* dirs read-only, and drops privileged namespaces.
|
|
101
|
+
*/
|
|
102
|
+
export function buildSandboxCommand(opts) {
|
|
103
|
+
const { command, args, policy, mode, bwrapAvailable } = opts;
|
|
104
|
+
const bwrap = opts.bwrapCommand ?? BWRAP_COMMAND;
|
|
105
|
+
if (mode === 'off') {
|
|
106
|
+
opts.onUnconfined?.('spawn sandbox is OFF -- the agent runs with the daemon user\'s full filesystem authority');
|
|
107
|
+
return { command, args: [...args] };
|
|
108
|
+
}
|
|
109
|
+
// mode === 'bwrap'
|
|
110
|
+
if (!bwrapAvailable) {
|
|
111
|
+
throw new SandboxUnavailableError(`sandbox.mode is "bwrap" but the "${bwrap}" executable was not found on the daemon host. ` +
|
|
112
|
+
'Install bubblewrap or set sandbox.mode="off" (unconfined) deliberately.');
|
|
113
|
+
}
|
|
114
|
+
const bwrapArgs = [
|
|
115
|
+
// Lifecycle / isolation: drop privileged namespaces, keep NET (the agent
|
|
116
|
+
// needs Telora/git/registry/model access). Die with the daemon; new session.
|
|
117
|
+
'--die-with-parent',
|
|
118
|
+
'--new-session',
|
|
119
|
+
'--unshare-user',
|
|
120
|
+
'--unshare-ipc',
|
|
121
|
+
'--unshare-pid',
|
|
122
|
+
'--unshare-uts',
|
|
123
|
+
'--unshare-cgroup',
|
|
124
|
+
// Minimal virtual filesystems.
|
|
125
|
+
'--proc', '/proc',
|
|
126
|
+
'--dev', '/dev',
|
|
127
|
+
'--tmpfs', '/tmp',
|
|
128
|
+
// System runtime + config, read-only. -try so a missing dir is skipped, not fatal.
|
|
129
|
+
'--ro-bind', '/usr', '/usr',
|
|
130
|
+
'--ro-bind-try', '/bin', '/bin',
|
|
131
|
+
'--ro-bind-try', '/sbin', '/sbin',
|
|
132
|
+
'--ro-bind-try', '/lib', '/lib',
|
|
133
|
+
'--ro-bind-try', '/lib64', '/lib64',
|
|
134
|
+
// /etc read-only: CA certs, resolv.conf, passwd. Does NOT expose any home dir.
|
|
135
|
+
'--ro-bind-try', '/etc', '/etc',
|
|
136
|
+
];
|
|
137
|
+
// The worktree is the ONLY writable host tree.
|
|
138
|
+
bwrapArgs.push('--bind', policy.worktreePath, policy.worktreePath);
|
|
139
|
+
// Explicit read-only allowlist (toolchain + model-auth config). Anything not
|
|
140
|
+
// listed -- sibling repos, ~/.ssh, ~/.aws, other homes -- is simply not bound,
|
|
141
|
+
// so it is invisible inside the sandbox.
|
|
142
|
+
for (const p of policy.readOnlyPaths ?? []) {
|
|
143
|
+
if (p && p.trim())
|
|
144
|
+
bwrapArgs.push('--ro-bind-try', p, p);
|
|
145
|
+
}
|
|
146
|
+
bwrapArgs.push('--chdir', policy.worktreePath);
|
|
147
|
+
// Terminator, then the real command.
|
|
148
|
+
bwrapArgs.push('--', command, ...args);
|
|
149
|
+
return { command: bwrap, args: bwrapArgs };
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* High-level helper used by the spawn path: resolve mode from config, derive the
|
|
153
|
+
* read-only allowlist (toolchain + model-auth config), and build the wrapped
|
|
154
|
+
* command. Emits a health event on unconfined runs and refuses (throws) when an
|
|
155
|
+
* explicitly-required sandbox is unavailable.
|
|
156
|
+
*/
|
|
157
|
+
export function wrapSpawnCommand(config, spawn) {
|
|
158
|
+
const mode = resolveSandboxMode(config.sandbox);
|
|
159
|
+
const bwrapAvailable = isBwrapAvailable();
|
|
160
|
+
const readOnlyPaths = deriveReadOnlyPaths(config, spawn.command);
|
|
161
|
+
return buildSandboxCommand({
|
|
162
|
+
command: spawn.command,
|
|
163
|
+
args: spawn.args,
|
|
164
|
+
policy: { worktreePath: spawn.worktreePath, readOnlyPaths },
|
|
165
|
+
mode,
|
|
166
|
+
bwrapAvailable,
|
|
167
|
+
onUnconfined: (reason) => {
|
|
168
|
+
healthEvents.emit({
|
|
169
|
+
severity: 'warn',
|
|
170
|
+
source: 'spawn-sandbox',
|
|
171
|
+
code: 'sandbox.unconfined',
|
|
172
|
+
message: reason,
|
|
173
|
+
});
|
|
174
|
+
},
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Derive the read-only host paths a spawn legitimately needs: the toolchain
|
|
179
|
+
* (the backend command's dir + the daemon's own node), the agent's model-auth
|
|
180
|
+
* config under HOME (~/.claude.json, ~/.claude, ~/.codex), and any extra paths
|
|
181
|
+
* configured. Kept narrow -- this is the allowlist that decides what the agent
|
|
182
|
+
* can read OUTSIDE its worktree. Exported for tests.
|
|
183
|
+
*/
|
|
184
|
+
export function deriveReadOnlyPaths(config, backendCommand) {
|
|
185
|
+
const paths = new Set();
|
|
186
|
+
const home = process.env.HOME;
|
|
187
|
+
// Model-auth config the agent needs (NOT ~/.ssh, NOT ~/.aws).
|
|
188
|
+
// ~/.gitconfig: read-only, safe to expose -- agents commit work under the
|
|
189
|
+
// daemon user's identity; without this git refuses to commit inside the sandbox.
|
|
190
|
+
if (home) {
|
|
191
|
+
paths.add(join(home, '.claude.json'));
|
|
192
|
+
paths.add(join(home, '.claude'));
|
|
193
|
+
paths.add(join(home, '.codex'));
|
|
194
|
+
paths.add(join(home, '.gitconfig'));
|
|
195
|
+
}
|
|
196
|
+
// Per-product isolated CODEX_HOME, when set.
|
|
197
|
+
if (config.codexHome)
|
|
198
|
+
paths.add(config.codexHome);
|
|
199
|
+
// The daemon's node executable (so the agent's tooling can re-exec node).
|
|
200
|
+
if (process.execPath)
|
|
201
|
+
paths.add(process.execPath);
|
|
202
|
+
// The backend command itself, when it is an absolute path outside /usr.
|
|
203
|
+
if (isAbsolute(backendCommand))
|
|
204
|
+
paths.add(backendCommand);
|
|
205
|
+
// Operator-configured extras.
|
|
206
|
+
for (const p of config.sandbox?.readOnlyPaths ?? [])
|
|
207
|
+
paths.add(p);
|
|
208
|
+
return [...paths];
|
|
209
|
+
}
|
|
210
|
+
//# sourceMappingURL=spawn-sandbox.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spawn-sandbox.js","sourceRoot":"","sources":["../src/spawn-sandbox.ts"],"names":[],"mappings":"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"}
|
package/dist/types/config.d.ts
CHANGED
|
@@ -21,12 +21,52 @@ export interface TelemetryConfig {
|
|
|
21
21
|
/** Number of days to retain telemetry events. Default: 30. */
|
|
22
22
|
retentionDays: number;
|
|
23
23
|
}
|
|
24
|
+
/**
|
|
25
|
+
* OS sandbox mode for spawned agents.
|
|
26
|
+
* - 'bwrap': confine each spawn with bubblewrap (Linux). If bwrap cannot
|
|
27
|
+
* initialize, the spawn is REFUSED (fail-closed) -- never run unconfined.
|
|
28
|
+
* - 'off': no OS sandbox; the spawn runs with the daemon user's authority
|
|
29
|
+
* (the worktree bounds only the git branch, not the filesystem).
|
|
30
|
+
*/
|
|
31
|
+
export type SandboxMode = 'bwrap' | 'off';
|
|
32
|
+
/**
|
|
33
|
+
* Configuration for the spawn OS sandbox (D3 -- "make the worktree boundary true").
|
|
34
|
+
*
|
|
35
|
+
* Default resolution when `mode` is unset: macOS (dev) => 'off'; Linux => 'off'
|
|
36
|
+
* by default too, but the daemon logs a recommendation to enable 'bwrap' when
|
|
37
|
+
* bubblewrap is available. Defaulting to 'off' is deliberate -- auto-enabling a
|
|
38
|
+
* sandbox whose per-host read-bind set has not been validated could break real
|
|
39
|
+
* spawns; operators opt in to 'bwrap' after the per-host validation in
|
|
40
|
+
* docs/runbook-daemon-service-user.md. An EXPLICIT 'bwrap' is fail-closed: if
|
|
41
|
+
* bubblewrap is unavailable the spawn is refused, never silently unconfined.
|
|
42
|
+
*/
|
|
43
|
+
export interface SandboxConfig {
|
|
44
|
+
/** Sandbox mode. Unset => platform default (see above). */
|
|
45
|
+
mode?: SandboxMode;
|
|
46
|
+
/**
|
|
47
|
+
* Extra host paths to expose read-only inside the sandbox -- the toolchain
|
|
48
|
+
* (node/claude/codex bin dirs) and the agent's model-auth config the spawn
|
|
49
|
+
* legitimately needs (e.g. ~/.claude.json, ~/.codex). The worktree itself is
|
|
50
|
+
* always bound read-write; everything not listed is invisible.
|
|
51
|
+
*/
|
|
52
|
+
readOnlyPaths?: string[];
|
|
53
|
+
/**
|
|
54
|
+
* Outbound egress allowlist (hostnames). NOTE: bubblewrap does NOT enforce
|
|
55
|
+
* L3/L7 egress, so this is currently advisory and documented as a residual
|
|
56
|
+
* gap in docs/security-posture-daemon-execution.md (candidate enforcement:
|
|
57
|
+
* per-uid nftables rules at the service-user layer). Filesystem confinement
|
|
58
|
+
* + capability/namespace isolation are the enforced boundary today.
|
|
59
|
+
*/
|
|
60
|
+
egressAllowlist?: string[];
|
|
61
|
+
}
|
|
24
62
|
/**
|
|
25
63
|
* Configuration for the daemon.
|
|
26
64
|
*
|
|
27
65
|
* Extends BaseConfig from @telora/daemon-core with daemon-specific fields.
|
|
28
66
|
*/
|
|
29
67
|
export interface DaemonConfig extends BaseConfig {
|
|
68
|
+
/** OS sandbox for spawned agents (D3). Undefined => platform default. */
|
|
69
|
+
sandbox?: SandboxConfig;
|
|
30
70
|
/** Path/command for the Codex CLI executable. Default: 'codex' (on PATH). */
|
|
31
71
|
codexPath?: string;
|
|
32
72
|
worktreeDir: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/types/config.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAEtD;;;;GAIG;AACH,MAAM,MAAM,iBAAiB,GAAG,WAAW,GAAG,aAAa,CAAC;AAE5D;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,6DAA6D;IAC7D,OAAO,EAAE,OAAO,CAAC;IACjB,sDAAsD;IACtD,IAAI,EAAE,MAAM,CAAC;IACb,uEAAuE;IACvE,eAAe,EAAE,MAAM,CAAC;IACxB,8DAA8D;IAC9D,aAAa,EAAE,MAAM,CAAC;CACvB;AAED;;;;GAIG;AACH,MAAM,WAAW,YAAa,SAAQ,UAAU;IAC9C,6EAA6E;IAC7E,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,gBAAgB,EAAE,MAAM,CAAC;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,2EAA2E;IAC3E,kBAAkB,EAAE,MAAM,CAAC;IAC3B,4FAA4F;IAC5F,4BAA4B,EAAE,MAAM,CAAC;IACrC,gFAAgF;IAChF,iBAAiB,EAAE,iBAAiB,CAAC;IACrC,iDAAiD;IACjD,aAAa,EAAE,MAAM,CAAC;IACtB,qEAAqE;IACrE,gBAAgB,EAAE,MAAM,CAAC;IACzB,6CAA6C;IAC7C,WAAW,EAAE,MAAM,CAAC;IACpB,6CAA6C;IAC7C,SAAS,EAAE,eAAe,CAAC;IAC3B;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;OAGG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB"}
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/types/config.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAEtD;;;;GAIG;AACH,MAAM,MAAM,iBAAiB,GAAG,WAAW,GAAG,aAAa,CAAC;AAE5D;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,6DAA6D;IAC7D,OAAO,EAAE,OAAO,CAAC;IACjB,sDAAsD;IACtD,IAAI,EAAE,MAAM,CAAC;IACb,uEAAuE;IACvE,eAAe,EAAE,MAAM,CAAC;IACxB,8DAA8D;IAC9D,aAAa,EAAE,MAAM,CAAC;CACvB;AAED;;;;;;GAMG;AACH,MAAM,MAAM,WAAW,GAAG,OAAO,GAAG,KAAK,CAAC;AAE1C;;;;;;;;;;GAUG;AACH,MAAM,WAAW,aAAa;IAC5B,2DAA2D;IAC3D,IAAI,CAAC,EAAE,WAAW,CAAC;IACnB;;;;;OAKG;IACH,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB;;;;;;OAMG;IACH,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;CAC5B;AAED;;;;GAIG;AACH,MAAM,WAAW,YAAa,SAAQ,UAAU;IAC9C,yEAAyE;IACzE,OAAO,CAAC,EAAE,aAAa,CAAC;IACxB,6EAA6E;IAC7E,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,gBAAgB,EAAE,MAAM,CAAC;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,2EAA2E;IAC3E,kBAAkB,EAAE,MAAM,CAAC;IAC3B,4FAA4F;IAC5F,4BAA4B,EAAE,MAAM,CAAC;IACrC,gFAAgF;IAChF,iBAAiB,EAAE,iBAAiB,CAAC;IACrC,iDAAiD;IACjD,aAAa,EAAE,MAAM,CAAC;IACtB,qEAAqE;IACrE,gBAAgB,EAAE,MAAM,CAAC;IACzB,6CAA6C;IAC7C,WAAW,EAAE,MAAM,CAAC;IACpB,6CAA6C;IAC7C,SAAS,EAAE,eAAe,CAAC;IAC3B;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;OAGG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@telora/daemon",
|
|
3
|
-
"version": "0.17.
|
|
3
|
+
"version": "0.17.56",
|
|
4
4
|
"description": "Agent orchestration daemon for Telora - spawns and manages Claude Code instances",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
},
|
|
37
37
|
"//testRunner": "Uses Node.js built-in test runner (node:test) via tsx for TypeScript support. The root package uses Vitest; this is a deliberate choice for the daemon package to avoid framework dependencies. See src/__tests__/ for test files.",
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@telora/daemon-core": "^0.2.
|
|
39
|
+
"@telora/daemon-core": "^0.2.38",
|
|
40
40
|
"@telora/mcp-products": "^0.22.57",
|
|
41
41
|
"commander": "^14.0.3",
|
|
42
42
|
"yaml": "^2.4.0",
|