@provos/ironcurtain 0.5.1 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +85 -66
- package/dist/cli-help.d.ts +63 -0
- package/dist/cli-help.js +102 -0
- package/dist/cli-help.js.map +1 -0
- package/dist/cli.js +78 -52
- package/dist/cli.js.map +1 -1
- package/dist/config/config-command.js +136 -2
- package/dist/config/config-command.js.map +1 -1
- package/dist/config/generated/tool-annotations.json +487 -338
- package/dist/config/index.d.ts +21 -1
- package/dist/config/index.js +12 -4
- package/dist/config/index.js.map +1 -1
- package/dist/config/mcp-servers.json +2 -8
- package/dist/config/paths.d.ts +30 -0
- package/dist/config/paths.js +58 -0
- package/dist/config/paths.js.map +1 -1
- package/dist/config/tool-description-hints.json +15 -0
- package/dist/config/types.d.ts +13 -0
- package/dist/config/user-config.d.ts +21 -0
- package/dist/config/user-config.js +8 -0
- package/dist/config/user-config.js.map +1 -1
- package/dist/cron/compile-task-policy.d.ts +17 -0
- package/dist/cron/compile-task-policy.js +39 -0
- package/dist/cron/compile-task-policy.js.map +1 -0
- package/dist/cron/cron-scheduler.d.ts +65 -0
- package/dist/cron/cron-scheduler.js +184 -0
- package/dist/cron/cron-scheduler.js.map +1 -0
- package/dist/cron/file-lock.d.ts +41 -0
- package/dist/cron/file-lock.js +156 -0
- package/dist/cron/file-lock.js.map +1 -0
- package/dist/cron/format-utils.d.ts +18 -0
- package/dist/cron/format-utils.js +145 -0
- package/dist/cron/format-utils.js.map +1 -0
- package/dist/cron/git-sync.d.ts +22 -0
- package/dist/cron/git-sync.js +121 -0
- package/dist/cron/git-sync.js.map +1 -0
- package/dist/cron/headless-transport.d.ts +27 -0
- package/dist/cron/headless-transport.js +43 -0
- package/dist/cron/headless-transport.js.map +1 -0
- package/dist/cron/job-commands.d.ts +39 -0
- package/dist/cron/job-commands.js +522 -0
- package/dist/cron/job-commands.js.map +1 -0
- package/dist/cron/job-store.d.ts +35 -0
- package/dist/cron/job-store.js +111 -0
- package/dist/cron/job-store.js.map +1 -0
- package/dist/cron/types.d.ts +127 -0
- package/dist/cron/types.js +25 -0
- package/dist/cron/types.js.map +1 -0
- package/dist/daemon/control-socket.d.ts +127 -0
- package/dist/daemon/control-socket.js +261 -0
- package/dist/daemon/control-socket.js.map +1 -0
- package/dist/daemon/daemon-command.d.ts +13 -0
- package/dist/daemon/daemon-command.js +230 -0
- package/dist/daemon/daemon-command.js.map +1 -0
- package/dist/daemon/ironcurtain-daemon.d.ts +90 -0
- package/dist/daemon/ironcurtain-daemon.js +548 -0
- package/dist/daemon/ironcurtain-daemon.js.map +1 -0
- package/dist/docker/adapters/claude-code.js +8 -48
- package/dist/docker/adapters/claude-code.js.map +1 -1
- package/dist/docker/adapters/goose.d.ts +58 -0
- package/dist/docker/adapters/goose.js +250 -0
- package/dist/docker/adapters/goose.js.map +1 -0
- package/dist/docker/adapters/shared-scripts.d.ts +29 -0
- package/dist/docker/adapters/shared-scripts.js +80 -0
- package/dist/docker/adapters/shared-scripts.js.map +1 -0
- package/dist/docker/agent-adapter.d.ts +12 -0
- package/dist/docker/agent-adapter.js.map +1 -1
- package/dist/docker/agent-registry.d.ts +6 -1
- package/dist/docker/agent-registry.js +10 -1
- package/dist/docker/agent-registry.js.map +1 -1
- package/dist/docker/docker-agent-session.d.ts +2 -25
- package/dist/docker/docker-agent-session.js +7 -106
- package/dist/docker/docker-agent-session.js.map +1 -1
- package/dist/docker/docker-infrastructure.d.ts +8 -0
- package/dist/docker/docker-infrastructure.js +22 -14
- package/dist/docker/docker-infrastructure.js.map +1 -1
- package/dist/docker/mitm-proxy.d.ts +5 -2
- package/dist/docker/mitm-proxy.js +169 -46
- package/dist/docker/mitm-proxy.js.map +1 -1
- package/dist/docker/oauth-credentials.d.ts +15 -2
- package/dist/docker/oauth-credentials.js +92 -8
- package/dist/docker/oauth-credentials.js.map +1 -1
- package/dist/docker/oauth-token-manager.d.ts +80 -0
- package/dist/docker/oauth-token-manager.js +133 -0
- package/dist/docker/oauth-token-manager.js.map +1 -0
- package/dist/docker/pty-session.d.ts +2 -0
- package/dist/docker/pty-session.js +17 -8
- package/dist/docker/pty-session.js.map +1 -1
- package/dist/escalation/escalation-watcher.d.ts +2 -0
- package/dist/escalation/escalation-watcher.js +5 -0
- package/dist/escalation/escalation-watcher.js.map +1 -1
- package/dist/escalation/listener-command.js +9 -58
- package/dist/escalation/listener-command.js.map +1 -1
- package/dist/escalation/listener-lock.d.ts +20 -0
- package/dist/escalation/listener-lock.js +69 -0
- package/dist/escalation/listener-lock.js.map +1 -0
- package/dist/escalation/session-registry.js +1 -14
- package/dist/escalation/session-registry.js.map +1 -1
- package/dist/index.js +51 -2
- package/dist/index.js.map +1 -1
- package/dist/mux/mux-app.d.ts +24 -0
- package/dist/mux/mux-app.js +450 -0
- package/dist/mux/mux-app.js.map +1 -0
- package/dist/mux/mux-command.d.ts +7 -0
- package/dist/mux/mux-command.js +104 -0
- package/dist/mux/mux-command.js.map +1 -0
- package/dist/mux/mux-escalation-manager.d.ts +48 -0
- package/dist/mux/mux-escalation-manager.js +162 -0
- package/dist/mux/mux-escalation-manager.js.map +1 -0
- package/dist/mux/mux-input-handler.d.ts +54 -0
- package/dist/mux/mux-input-handler.js +512 -0
- package/dist/mux/mux-input-handler.js.map +1 -0
- package/dist/mux/mux-renderer.d.ts +106 -0
- package/dist/mux/mux-renderer.js +850 -0
- package/dist/mux/mux-renderer.js.map +1 -0
- package/dist/mux/mux-splash.d.ts +17 -0
- package/dist/mux/mux-splash.js +323 -0
- package/dist/mux/mux-splash.js.map +1 -0
- package/dist/mux/paste-interceptor.d.ts +22 -0
- package/dist/mux/paste-interceptor.js +143 -0
- package/dist/mux/paste-interceptor.js.map +1 -0
- package/dist/mux/pty-bridge.d.ts +79 -0
- package/dist/mux/pty-bridge.js +151 -0
- package/dist/mux/pty-bridge.js.map +1 -0
- package/dist/mux/trusted-input.d.ts +27 -0
- package/dist/mux/trusted-input.js +27 -0
- package/dist/mux/trusted-input.js.map +1 -0
- package/dist/mux/types.d.ts +99 -0
- package/dist/mux/types.js +57 -0
- package/dist/mux/types.js.map +1 -0
- package/dist/pipeline/annotate.js +2 -1
- package/dist/pipeline/annotate.js.map +1 -1
- package/dist/pipeline/compile.d.ts +4 -34
- package/dist/pipeline/compile.js +29 -568
- package/dist/pipeline/compile.js.map +1 -1
- package/dist/pipeline/constitution-compiler.js +1 -1
- package/dist/pipeline/mcp-connections.d.ts +16 -0
- package/dist/pipeline/mcp-connections.js +61 -0
- package/dist/pipeline/mcp-connections.js.map +1 -0
- package/dist/pipeline/pipeline-runner.d.ts +86 -0
- package/dist/pipeline/pipeline-runner.js +554 -0
- package/dist/pipeline/pipeline-runner.js.map +1 -0
- package/dist/pipeline/pipeline-shared.d.ts +29 -2
- package/dist/pipeline/pipeline-shared.js +68 -12
- package/dist/pipeline/pipeline-shared.js.map +1 -1
- package/dist/pipeline/refresh-lists.js +38 -12
- package/dist/pipeline/refresh-lists.js.map +1 -1
- package/dist/pipeline/scenario-generator.js +2 -1
- package/dist/pipeline/scenario-generator.js.map +1 -1
- package/dist/pipeline/tool-annotator.d.ts +3 -3
- package/dist/pipeline/tool-annotator.js +96 -4
- package/dist/pipeline/tool-annotator.js.map +1 -1
- package/dist/pipeline/types.d.ts +52 -0
- package/dist/sandbox/index.js +40 -1
- package/dist/sandbox/index.js.map +1 -1
- package/dist/session/agent-session.d.ts +3 -0
- package/dist/session/agent-session.js +13 -1
- package/dist/session/agent-session.js.map +1 -1
- package/dist/session/index.js +65 -9
- package/dist/session/index.js.map +1 -1
- package/dist/session/preflight.js +26 -6
- package/dist/session/preflight.js.map +1 -1
- package/dist/session/prompts.d.ts +14 -0
- package/dist/session/prompts.js +39 -2
- package/dist/session/prompts.js.map +1 -1
- package/dist/session/session-manager.d.ts +105 -0
- package/dist/session/session-manager.js +134 -0
- package/dist/session/session-manager.js.map +1 -0
- package/dist/session/types.d.ts +33 -0
- package/dist/session/workspace-validation.d.ts +19 -0
- package/dist/session/workspace-validation.js +66 -0
- package/dist/session/workspace-validation.js.map +1 -0
- package/dist/signal/bot-command.d.ts +2 -0
- package/dist/signal/bot-command.js +8 -0
- package/dist/signal/bot-command.js.map +1 -1
- package/dist/signal/format.d.ts +27 -0
- package/dist/signal/format.js +59 -0
- package/dist/signal/format.js.map +1 -1
- package/dist/signal/signal-bot-daemon.d.ts +27 -25
- package/dist/signal/signal-bot-daemon.js +145 -130
- package/dist/signal/signal-bot-daemon.js.map +1 -1
- package/dist/signal/signal-container.js +63 -19
- package/dist/signal/signal-container.js.map +1 -1
- package/dist/signal/signal-transport.js +3 -8
- package/dist/signal/signal-transport.js.map +1 -1
- package/dist/trusted-process/auto-approver.d.ts +13 -4
- package/dist/trusted-process/auto-approver.js +12 -5
- package/dist/trusted-process/auto-approver.js.map +1 -1
- package/dist/trusted-process/domain-utils.d.ts +38 -4
- package/dist/trusted-process/domain-utils.js +118 -11
- package/dist/trusted-process/domain-utils.js.map +1 -1
- package/dist/trusted-process/escalation.js +5 -0
- package/dist/trusted-process/escalation.js.map +1 -1
- package/dist/trusted-process/index.js +6 -2
- package/dist/trusted-process/index.js.map +1 -1
- package/dist/trusted-process/mcp-client-manager.js +11 -5
- package/dist/trusted-process/mcp-client-manager.js.map +1 -1
- package/dist/trusted-process/mcp-proxy-server.d.ts +2 -0
- package/dist/trusted-process/mcp-proxy-server.js +98 -32
- package/dist/trusted-process/mcp-proxy-server.js.map +1 -1
- package/dist/trusted-process/permissive-output-validator.d.ts +23 -0
- package/dist/trusted-process/permissive-output-validator.js +10 -0
- package/dist/trusted-process/permissive-output-validator.js.map +1 -0
- package/dist/trusted-process/policy-engine.d.ts +27 -4
- package/dist/trusted-process/policy-engine.js +152 -57
- package/dist/trusted-process/policy-engine.js.map +1 -1
- package/dist/trusted-process/tool-description-hints.d.ts +19 -0
- package/dist/trusted-process/tool-description-hints.js +53 -0
- package/dist/trusted-process/tool-description-hints.js.map +1 -0
- package/dist/types/argument-roles.d.ts +33 -1
- package/dist/types/argument-roles.js +129 -1
- package/dist/types/argument-roles.js.map +1 -1
- package/docker/Dockerfile.goose +34 -0
- package/docker/entrypoint-goose.sh +30 -0
- package/package.json +6 -1
- package/src/config/generated/tool-annotations.json +487 -338
- package/src/config/mcp-servers.json +2 -8
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Filesystem-backed store for job definitions and run records.
|
|
3
|
+
*
|
|
4
|
+
* Directory layout:
|
|
5
|
+
* ~/.ironcurtain/jobs/{jobId}/
|
|
6
|
+
* job.json -- JobDefinition
|
|
7
|
+
* generated/ -- per-job compiled policy artifacts
|
|
8
|
+
* compiled-policy.json
|
|
9
|
+
* dynamic-lists.json -- (optional)
|
|
10
|
+
* workspace/ -- persistent agent workspace
|
|
11
|
+
* runs/ -- run history
|
|
12
|
+
* 2026-03-04T09:00:00.000Z.json
|
|
13
|
+
*
|
|
14
|
+
* Tool annotations (tool-annotations.json) are always loaded from
|
|
15
|
+
* the global location, never stored per-job.
|
|
16
|
+
*/
|
|
17
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from 'node:fs';
|
|
18
|
+
import { resolve } from 'node:path';
|
|
19
|
+
import { getJobsDir, getJobDir, getJobRunsDir } from '../config/paths.js';
|
|
20
|
+
import { createJobId } from './types.js';
|
|
21
|
+
import { withFileLock, getJobLockDir } from './file-lock.js';
|
|
22
|
+
/** Loads a job definition from disk. Returns undefined if not found. */
|
|
23
|
+
export function loadJob(jobId) {
|
|
24
|
+
const jobFile = resolve(getJobDir(jobId), 'job.json');
|
|
25
|
+
if (!existsSync(jobFile))
|
|
26
|
+
return undefined;
|
|
27
|
+
try {
|
|
28
|
+
return JSON.parse(readFileSync(jobFile, 'utf-8'));
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/** Loads all job definitions from disk. */
|
|
35
|
+
export function loadAllJobs() {
|
|
36
|
+
const jobsDir = getJobsDir();
|
|
37
|
+
if (!existsSync(jobsDir))
|
|
38
|
+
return [];
|
|
39
|
+
const jobs = [];
|
|
40
|
+
let entries;
|
|
41
|
+
try {
|
|
42
|
+
entries = readdirSync(jobsDir);
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return [];
|
|
46
|
+
}
|
|
47
|
+
for (const entry of entries) {
|
|
48
|
+
try {
|
|
49
|
+
const jobId = createJobId(entry);
|
|
50
|
+
const job = loadJob(jobId);
|
|
51
|
+
if (job)
|
|
52
|
+
jobs.push(job);
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
// Skip invalid directory names or malformed job files
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return jobs;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Saves a job definition to disk. Creates directories as needed.
|
|
62
|
+
* Acquires an advisory file lock to prevent concurrent write corruption.
|
|
63
|
+
*/
|
|
64
|
+
export async function saveJob(job) {
|
|
65
|
+
const jobDir = getJobDir(job.id);
|
|
66
|
+
mkdirSync(jobDir, { recursive: true });
|
|
67
|
+
await withFileLock(getJobLockDir(jobDir), () => {
|
|
68
|
+
writeFileSync(resolve(jobDir, 'job.json'), JSON.stringify(job, null, 2) + '\n');
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
/** Deletes a job and all its artifacts (generated, workspace, runs). */
|
|
72
|
+
export function deleteJob(jobId) {
|
|
73
|
+
rmSync(getJobDir(jobId), { recursive: true, force: true });
|
|
74
|
+
}
|
|
75
|
+
/** Records a completed run. Filenames are unique (timestamp-based), so no lock needed. */
|
|
76
|
+
export function saveRunRecord(jobId, record) {
|
|
77
|
+
const runsDir = getJobRunsDir(jobId);
|
|
78
|
+
mkdirSync(runsDir, { recursive: true });
|
|
79
|
+
const filename = record.startedAt.replace(/:/g, '-') + '.json';
|
|
80
|
+
writeFileSync(resolve(runsDir, filename), JSON.stringify(record, null, 2) + '\n');
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Loads recent run records for a job, most recent first.
|
|
84
|
+
* @param limit Maximum number of records to return (default 10).
|
|
85
|
+
*/
|
|
86
|
+
export function loadRecentRuns(jobId, limit = 10) {
|
|
87
|
+
const runsDir = getJobRunsDir(jobId);
|
|
88
|
+
if (!existsSync(runsDir))
|
|
89
|
+
return [];
|
|
90
|
+
let files;
|
|
91
|
+
try {
|
|
92
|
+
files = readdirSync(runsDir).filter((f) => f.endsWith('.json'));
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
return [];
|
|
96
|
+
}
|
|
97
|
+
// Sort by filename (ISO timestamp-based) descending
|
|
98
|
+
files.sort((a, b) => b.localeCompare(a));
|
|
99
|
+
const records = [];
|
|
100
|
+
for (const file of files.slice(0, limit)) {
|
|
101
|
+
try {
|
|
102
|
+
const record = JSON.parse(readFileSync(resolve(runsDir, file), 'utf-8'));
|
|
103
|
+
records.push(record);
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
// Skip malformed run records
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return records;
|
|
110
|
+
}
|
|
111
|
+
//# sourceMappingURL=job-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"job-store.js","sourceRoot":"","sources":["../../src/cron/job-store.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cron Mode types -- job definitions, run records, and budget overrides.
|
|
3
|
+
*
|
|
4
|
+
* These types are used by the job store, cron scheduler, daemon,
|
|
5
|
+
* and CLI commands.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Stable, user-chosen identifier for a cron job.
|
|
9
|
+
* Must be a valid slug: lowercase alphanumeric, hyphens, underscores.
|
|
10
|
+
* Branded to prevent mixing with other string identifiers.
|
|
11
|
+
*/
|
|
12
|
+
export type JobId = string & {
|
|
13
|
+
readonly __brand: 'JobId';
|
|
14
|
+
};
|
|
15
|
+
/** Regex for valid job IDs: 1-63 chars, lowercase alphanumeric, hyphens, or underscores. */
|
|
16
|
+
export declare const JOB_ID_PATTERN: RegExp;
|
|
17
|
+
/** Validates and creates a JobId from a user-provided string. */
|
|
18
|
+
export declare function createJobId(raw: string): JobId;
|
|
19
|
+
/**
|
|
20
|
+
* Persisted job definition. Stored as JSON at
|
|
21
|
+
* ~/.ironcurtain/jobs/{jobId}/job.json.
|
|
22
|
+
*
|
|
23
|
+
* Invariant: the `task` field is the "constitution" input for
|
|
24
|
+
* per-job policy compilation. Changing it invalidates the
|
|
25
|
+
* compiled policy (detected via content hash comparison).
|
|
26
|
+
*/
|
|
27
|
+
export interface JobDefinition {
|
|
28
|
+
/** Stable user-chosen slug. Matches the directory name. */
|
|
29
|
+
readonly id: JobId;
|
|
30
|
+
/** Human-friendly display name. */
|
|
31
|
+
readonly name: string;
|
|
32
|
+
/** Standard cron expression (5 fields: min hour dom mon dow). */
|
|
33
|
+
readonly schedule: string;
|
|
34
|
+
/**
|
|
35
|
+
* English task description sent to the agent as its work prompt.
|
|
36
|
+
*/
|
|
37
|
+
readonly taskDescription: string;
|
|
38
|
+
/**
|
|
39
|
+
* English constitution text used as the policy compilation input.
|
|
40
|
+
* Describes what the agent is and is not permitted to do.
|
|
41
|
+
*/
|
|
42
|
+
readonly taskConstitution: string;
|
|
43
|
+
/**
|
|
44
|
+
* Working directory for this job. Persists across runs.
|
|
45
|
+
* Default: ~/.ironcurtain/jobs/{jobId}/workspace/
|
|
46
|
+
*/
|
|
47
|
+
readonly workspace?: string;
|
|
48
|
+
/**
|
|
49
|
+
* Optional git repository URI to clone into the workspace.
|
|
50
|
+
* On first run, the repo is cloned. On subsequent runs,
|
|
51
|
+
* tracked files are reset to remote HEAD (untracked files preserved).
|
|
52
|
+
*/
|
|
53
|
+
readonly gitRepo?: string;
|
|
54
|
+
/**
|
|
55
|
+
* When true and Signal is configured, the daemon sends a Signal
|
|
56
|
+
* message on escalation and waits for approval. When false or
|
|
57
|
+
* Signal is not configured, escalations are auto-denied.
|
|
58
|
+
*/
|
|
59
|
+
readonly notifyOnEscalation: boolean;
|
|
60
|
+
/**
|
|
61
|
+
* When true and Signal is configured, the daemon sends a Signal
|
|
62
|
+
* message with the run summary on completion.
|
|
63
|
+
*/
|
|
64
|
+
readonly notifyOnCompletion: boolean;
|
|
65
|
+
/**
|
|
66
|
+
* Per-job resource budget overrides. Null fields inherit from
|
|
67
|
+
* the cron-specific defaults.
|
|
68
|
+
*/
|
|
69
|
+
readonly budgetOverrides?: Partial<JobBudgetOverrides>;
|
|
70
|
+
/** Whether this job is active. Disabled jobs are not scheduled. */
|
|
71
|
+
readonly enabled: boolean;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Budget fields that can be overridden per-job.
|
|
75
|
+
* All fields are nullable: null means "disable this limit".
|
|
76
|
+
*/
|
|
77
|
+
export interface JobBudgetOverrides {
|
|
78
|
+
readonly maxTotalTokens: number | null;
|
|
79
|
+
readonly maxSteps: number | null;
|
|
80
|
+
readonly maxSessionSeconds: number | null;
|
|
81
|
+
readonly maxEstimatedCostUsd: number | null;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Outcome status for a completed run.
|
|
85
|
+
*/
|
|
86
|
+
export type RunOutcome = {
|
|
87
|
+
readonly kind: 'success';
|
|
88
|
+
} | {
|
|
89
|
+
readonly kind: 'budget_exhausted';
|
|
90
|
+
readonly dimension: string;
|
|
91
|
+
} | {
|
|
92
|
+
readonly kind: 'error';
|
|
93
|
+
readonly message: string;
|
|
94
|
+
};
|
|
95
|
+
/**
|
|
96
|
+
* Persisted run record. Written to
|
|
97
|
+
* ~/.ironcurtain/jobs/{jobId}/runs/{timestamp}.json
|
|
98
|
+
* after each run completes.
|
|
99
|
+
*/
|
|
100
|
+
export interface RunRecord {
|
|
101
|
+
/** ISO 8601 start time. Also used as the filename. */
|
|
102
|
+
readonly startedAt: string;
|
|
103
|
+
/** ISO 8601 end time. */
|
|
104
|
+
readonly completedAt: string;
|
|
105
|
+
/** How the run ended. */
|
|
106
|
+
readonly outcome: RunOutcome;
|
|
107
|
+
/** Resource consumption snapshot. */
|
|
108
|
+
readonly budget: {
|
|
109
|
+
readonly totalTokens: number;
|
|
110
|
+
readonly stepCount: number;
|
|
111
|
+
readonly elapsedSeconds: number;
|
|
112
|
+
readonly estimatedCostUsd: number;
|
|
113
|
+
};
|
|
114
|
+
/**
|
|
115
|
+
* Brief summary extracted from workspace/last-run.md, if it exists.
|
|
116
|
+
* Truncated to 2000 chars. Null if the file was not written.
|
|
117
|
+
*/
|
|
118
|
+
readonly summary: string | null;
|
|
119
|
+
/** Whether any escalations were surfaced during this run. */
|
|
120
|
+
readonly escalationsEncountered: number;
|
|
121
|
+
/** Whether any escalations were approved (vs. denied or timed out). */
|
|
122
|
+
readonly escalationsApproved: number;
|
|
123
|
+
/** git diff --stat of tracked-file changes discarded during pre-run sync. */
|
|
124
|
+
readonly discardedChanges: string | null;
|
|
125
|
+
}
|
|
126
|
+
/** Cron-specific budget defaults (differ from interactive defaults). */
|
|
127
|
+
export declare const CRON_BUDGET_DEFAULTS: Required<JobBudgetOverrides>;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cron Mode types -- job definitions, run records, and budget overrides.
|
|
3
|
+
*
|
|
4
|
+
* These types are used by the job store, cron scheduler, daemon,
|
|
5
|
+
* and CLI commands.
|
|
6
|
+
*/
|
|
7
|
+
/** Regex for valid job IDs: 1-63 chars, lowercase alphanumeric, hyphens, or underscores. */
|
|
8
|
+
export const JOB_ID_PATTERN = /^[a-z0-9][a-z0-9_-]{0,62}$/;
|
|
9
|
+
/** Validates and creates a JobId from a user-provided string. */
|
|
10
|
+
export function createJobId(raw) {
|
|
11
|
+
if (!JOB_ID_PATTERN.test(raw)) {
|
|
12
|
+
throw new Error(`Invalid job ID "${raw}": must be 1-63 chars, ` +
|
|
13
|
+
`lowercase alphanumeric, hyphens, or underscores, ` +
|
|
14
|
+
`starting with a letter or digit`);
|
|
15
|
+
}
|
|
16
|
+
return raw;
|
|
17
|
+
}
|
|
18
|
+
/** Cron-specific budget defaults (differ from interactive defaults). */
|
|
19
|
+
export const CRON_BUDGET_DEFAULTS = {
|
|
20
|
+
maxTotalTokens: 500_000,
|
|
21
|
+
maxSteps: 100,
|
|
22
|
+
maxSessionSeconds: 3_600,
|
|
23
|
+
maxEstimatedCostUsd: 2.0,
|
|
24
|
+
};
|
|
25
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/cron/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AASH,4FAA4F;AAC5F,MAAM,CAAC,MAAM,cAAc,GAAG,4BAA4B,CAAC;AAE3D,iEAAiE;AACjE,MAAM,UAAU,WAAW,CAAC,GAAW;IACrC,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;QAC9B,MAAM,IAAI,KAAK,CACb,mBAAmB,GAAG,yBAAyB;YAC7C,mDAAmD;YACnD,iCAAiC,CACpC,CAAC;IACJ,CAAC;IACD,OAAO,GAAY,CAAC;AACtB,CAAC;AA6HD,wEAAwE;AACxE,MAAM,CAAC,MAAM,oBAAoB,GAAiC;IAChE,cAAc,EAAE,OAAO;IACvB,QAAQ,EAAE,GAAG;IACb,iBAAiB,EAAE,KAAK;IACxB,mBAAmB,EAAE,GAAG;CACzB,CAAC"}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Control socket for live daemon communication.
|
|
3
|
+
*
|
|
4
|
+
* The daemon listens on a Unix domain socket at ~/.ironcurtain/daemon.sock.
|
|
5
|
+
* CLI commands detect whether the daemon is running (socket exists and
|
|
6
|
+
* responds) and forward commands to it. If no daemon is running, CLI
|
|
7
|
+
* commands fall back to direct filesystem operations.
|
|
8
|
+
*
|
|
9
|
+
* Protocol: newline-delimited JSON. Each request is a single JSON line;
|
|
10
|
+
* the daemon writes a single JSON response line and closes the connection.
|
|
11
|
+
*/
|
|
12
|
+
import type { JobDefinition, RunRecord } from '../cron/types.js';
|
|
13
|
+
/** Commands the CLI can send to the daemon. */
|
|
14
|
+
export type ControlRequest = {
|
|
15
|
+
readonly command: 'ping';
|
|
16
|
+
} | {
|
|
17
|
+
readonly command: 'status';
|
|
18
|
+
} | {
|
|
19
|
+
readonly command: 'add-job';
|
|
20
|
+
readonly job: JobDefinition;
|
|
21
|
+
} | {
|
|
22
|
+
readonly command: 'remove-job';
|
|
23
|
+
readonly jobId: string;
|
|
24
|
+
} | {
|
|
25
|
+
readonly command: 'enable-job';
|
|
26
|
+
readonly jobId: string;
|
|
27
|
+
} | {
|
|
28
|
+
readonly command: 'disable-job';
|
|
29
|
+
readonly jobId: string;
|
|
30
|
+
} | {
|
|
31
|
+
readonly command: 'recompile-job';
|
|
32
|
+
readonly jobId: string;
|
|
33
|
+
} | {
|
|
34
|
+
readonly command: 'reload-job';
|
|
35
|
+
readonly jobId: string;
|
|
36
|
+
} | {
|
|
37
|
+
readonly command: 'run-job';
|
|
38
|
+
readonly jobId: string;
|
|
39
|
+
} | {
|
|
40
|
+
readonly command: 'list-jobs';
|
|
41
|
+
};
|
|
42
|
+
/** Successful response from the daemon. */
|
|
43
|
+
export interface ControlResponseOk {
|
|
44
|
+
readonly ok: true;
|
|
45
|
+
readonly data?: unknown;
|
|
46
|
+
}
|
|
47
|
+
/** Error response from the daemon. */
|
|
48
|
+
export interface ControlResponseError {
|
|
49
|
+
readonly ok: false;
|
|
50
|
+
readonly error: string;
|
|
51
|
+
}
|
|
52
|
+
export type ControlResponse = ControlResponseOk | ControlResponseError;
|
|
53
|
+
/** Status snapshot returned by the daemon. */
|
|
54
|
+
export interface DaemonStatus {
|
|
55
|
+
readonly uptimeSeconds: number;
|
|
56
|
+
readonly jobs: {
|
|
57
|
+
total: number;
|
|
58
|
+
enabled: number;
|
|
59
|
+
running: number;
|
|
60
|
+
};
|
|
61
|
+
readonly signalConnected: boolean;
|
|
62
|
+
readonly nextFireTime: Date | null;
|
|
63
|
+
}
|
|
64
|
+
/** JSON-serialized form of DaemonStatus (Date → ISO string). */
|
|
65
|
+
export interface DaemonStatusDto {
|
|
66
|
+
readonly uptimeSeconds: number;
|
|
67
|
+
readonly jobs: {
|
|
68
|
+
total: number;
|
|
69
|
+
enabled: number;
|
|
70
|
+
running: number;
|
|
71
|
+
};
|
|
72
|
+
readonly signalConnected: boolean;
|
|
73
|
+
readonly nextFireTime: string | null;
|
|
74
|
+
}
|
|
75
|
+
export interface ControlRequestHandler {
|
|
76
|
+
getStatus(): DaemonStatus;
|
|
77
|
+
addJob(job: JobDefinition): Promise<void>;
|
|
78
|
+
removeJob(jobId: string): Promise<void>;
|
|
79
|
+
enableJob(jobId: string): Promise<void>;
|
|
80
|
+
disableJob(jobId: string): Promise<void>;
|
|
81
|
+
recompileJob(jobId: string): Promise<void>;
|
|
82
|
+
reloadJob(jobId: string): Promise<void>;
|
|
83
|
+
runJobNow(jobId: string): Promise<RunRecord>;
|
|
84
|
+
listJobs(): Array<{
|
|
85
|
+
job: JobDefinition;
|
|
86
|
+
nextRun: Date | undefined;
|
|
87
|
+
lastRun: RunRecord | undefined;
|
|
88
|
+
isRunning: boolean;
|
|
89
|
+
}>;
|
|
90
|
+
}
|
|
91
|
+
export declare class ControlSocketServer {
|
|
92
|
+
private server;
|
|
93
|
+
private readonly socketPath;
|
|
94
|
+
private readonly handler;
|
|
95
|
+
constructor(handler: ControlRequestHandler, socketPath?: string);
|
|
96
|
+
/** Starts listening on the Unix domain socket. */
|
|
97
|
+
start(): Promise<void>;
|
|
98
|
+
/** Stops the server and removes the socket file. */
|
|
99
|
+
stop(): Promise<void>;
|
|
100
|
+
/**
|
|
101
|
+
* Removes a stale socket file left behind by a crashed daemon.
|
|
102
|
+
* Attempts to connect first — only unlinks when the socket is
|
|
103
|
+
* confirmed stale (ECONNREFUSED) or absent (ENOENT). If the
|
|
104
|
+
* socket is live, throws so the caller knows another daemon is running.
|
|
105
|
+
*/
|
|
106
|
+
private removeStaleSocket;
|
|
107
|
+
/**
|
|
108
|
+
* Probes whether the socket is actively accepting connections.
|
|
109
|
+
* Returns true if a daemon is listening, false if stale/absent.
|
|
110
|
+
*/
|
|
111
|
+
private isSocketAlive;
|
|
112
|
+
private cleanupSocketFile;
|
|
113
|
+
private onConnection;
|
|
114
|
+
private handleRequest;
|
|
115
|
+
private dispatch;
|
|
116
|
+
private sendResponse;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Sends a request to the running daemon via the control socket.
|
|
120
|
+
* Returns the parsed response, or null if the daemon is not reachable.
|
|
121
|
+
*/
|
|
122
|
+
export declare function sendControlRequest(request: ControlRequest, socketPath?: string): Promise<ControlResponse | null>;
|
|
123
|
+
/**
|
|
124
|
+
* Checks if a daemon is currently running and responsive.
|
|
125
|
+
* Returns true if the daemon responds to a ping.
|
|
126
|
+
*/
|
|
127
|
+
export declare function isDaemonRunning(socketPath?: string): Promise<boolean>;
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Control socket for live daemon communication.
|
|
3
|
+
*
|
|
4
|
+
* The daemon listens on a Unix domain socket at ~/.ironcurtain/daemon.sock.
|
|
5
|
+
* CLI commands detect whether the daemon is running (socket exists and
|
|
6
|
+
* responds) and forward commands to it. If no daemon is running, CLI
|
|
7
|
+
* commands fall back to direct filesystem operations.
|
|
8
|
+
*
|
|
9
|
+
* Protocol: newline-delimited JSON. Each request is a single JSON line;
|
|
10
|
+
* the daemon writes a single JSON response line and closes the connection.
|
|
11
|
+
*/
|
|
12
|
+
import { createServer, createConnection } from 'node:net';
|
|
13
|
+
import { unlinkSync } from 'node:fs';
|
|
14
|
+
import { getDaemonSocketPath } from '../config/paths.js';
|
|
15
|
+
import * as logger from '../logger.js';
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
// Server (daemon-side)
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
export class ControlSocketServer {
|
|
20
|
+
server = null;
|
|
21
|
+
socketPath;
|
|
22
|
+
handler;
|
|
23
|
+
constructor(handler, socketPath) {
|
|
24
|
+
this.handler = handler;
|
|
25
|
+
this.socketPath = socketPath ?? getDaemonSocketPath();
|
|
26
|
+
}
|
|
27
|
+
/** Starts listening on the Unix domain socket. */
|
|
28
|
+
async start() {
|
|
29
|
+
await this.removeStaleSocket();
|
|
30
|
+
return new Promise((resolve, reject) => {
|
|
31
|
+
const server = createServer((socket) => this.onConnection(socket));
|
|
32
|
+
this.server = server;
|
|
33
|
+
server.on('error', (err) => {
|
|
34
|
+
logger.error(`[ControlSocket] Server error: ${err.message}`);
|
|
35
|
+
reject(err);
|
|
36
|
+
});
|
|
37
|
+
server.listen(this.socketPath, () => {
|
|
38
|
+
logger.info(`[ControlSocket] Listening on ${this.socketPath}`);
|
|
39
|
+
resolve();
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
/** Stops the server and removes the socket file. */
|
|
44
|
+
async stop() {
|
|
45
|
+
if (!this.server)
|
|
46
|
+
return;
|
|
47
|
+
const server = this.server;
|
|
48
|
+
return new Promise((resolve) => {
|
|
49
|
+
server.close(() => {
|
|
50
|
+
this.cleanupSocketFile();
|
|
51
|
+
this.server = null;
|
|
52
|
+
resolve();
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Removes a stale socket file left behind by a crashed daemon.
|
|
58
|
+
* Attempts to connect first — only unlinks when the socket is
|
|
59
|
+
* confirmed stale (ECONNREFUSED) or absent (ENOENT). If the
|
|
60
|
+
* socket is live, throws so the caller knows another daemon is running.
|
|
61
|
+
*/
|
|
62
|
+
async removeStaleSocket() {
|
|
63
|
+
const alive = await this.isSocketAlive();
|
|
64
|
+
if (alive) {
|
|
65
|
+
throw new Error(`Another daemon is already listening on ${this.socketPath}. ` + `Stop it first or remove the socket manually.`);
|
|
66
|
+
}
|
|
67
|
+
// Socket is stale or absent — safe to unlink
|
|
68
|
+
try {
|
|
69
|
+
unlinkSync(this.socketPath);
|
|
70
|
+
logger.info(`[ControlSocket] Removed stale socket file`);
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
// File doesn't exist — fine
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Probes whether the socket is actively accepting connections.
|
|
78
|
+
* Returns true if a daemon is listening, false if stale/absent.
|
|
79
|
+
*/
|
|
80
|
+
isSocketAlive() {
|
|
81
|
+
return new Promise((resolve) => {
|
|
82
|
+
const conn = createConnection(this.socketPath);
|
|
83
|
+
conn.once('connect', () => {
|
|
84
|
+
conn.destroy();
|
|
85
|
+
resolve(true);
|
|
86
|
+
});
|
|
87
|
+
conn.once('error', () => {
|
|
88
|
+
resolve(false);
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
cleanupSocketFile() {
|
|
93
|
+
try {
|
|
94
|
+
unlinkSync(this.socketPath);
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
// Best-effort cleanup
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
onConnection(socket) {
|
|
101
|
+
let data = '';
|
|
102
|
+
let dispatched = false;
|
|
103
|
+
socket.on('data', (chunk) => {
|
|
104
|
+
if (dispatched)
|
|
105
|
+
return;
|
|
106
|
+
data += chunk.toString();
|
|
107
|
+
const newlineIndex = data.indexOf('\n');
|
|
108
|
+
if (newlineIndex === -1)
|
|
109
|
+
return;
|
|
110
|
+
dispatched = true;
|
|
111
|
+
const line = data.slice(0, newlineIndex);
|
|
112
|
+
void this.handleRequest(line, socket);
|
|
113
|
+
});
|
|
114
|
+
socket.on('error', (err) => {
|
|
115
|
+
logger.warn(`[ControlSocket] Connection error: ${err.message}`);
|
|
116
|
+
});
|
|
117
|
+
// Timeout idle connections after 30s
|
|
118
|
+
socket.setTimeout(30_000, () => {
|
|
119
|
+
socket.destroy();
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
async handleRequest(line, socket) {
|
|
123
|
+
let request;
|
|
124
|
+
try {
|
|
125
|
+
request = JSON.parse(line);
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
this.sendResponse(socket, { ok: false, error: 'Invalid JSON' });
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
try {
|
|
132
|
+
const response = await this.dispatch(request);
|
|
133
|
+
this.sendResponse(socket, response);
|
|
134
|
+
}
|
|
135
|
+
catch (err) {
|
|
136
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
137
|
+
this.sendResponse(socket, { ok: false, error: message });
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
async dispatch(request) {
|
|
141
|
+
switch (request.command) {
|
|
142
|
+
case 'ping':
|
|
143
|
+
return { ok: true, data: { status: 'running' } };
|
|
144
|
+
case 'status': {
|
|
145
|
+
const status = this.handler.getStatus();
|
|
146
|
+
return {
|
|
147
|
+
ok: true,
|
|
148
|
+
data: {
|
|
149
|
+
...status,
|
|
150
|
+
nextFireTime: status.nextFireTime?.toISOString() ?? null,
|
|
151
|
+
},
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
case 'add-job':
|
|
155
|
+
await this.handler.addJob(request.job);
|
|
156
|
+
return { ok: true };
|
|
157
|
+
case 'remove-job':
|
|
158
|
+
await this.handler.removeJob(request.jobId);
|
|
159
|
+
return { ok: true };
|
|
160
|
+
case 'enable-job':
|
|
161
|
+
await this.handler.enableJob(request.jobId);
|
|
162
|
+
return { ok: true };
|
|
163
|
+
case 'disable-job':
|
|
164
|
+
await this.handler.disableJob(request.jobId);
|
|
165
|
+
return { ok: true };
|
|
166
|
+
case 'recompile-job':
|
|
167
|
+
await this.handler.recompileJob(request.jobId);
|
|
168
|
+
return { ok: true };
|
|
169
|
+
case 'reload-job':
|
|
170
|
+
await this.handler.reloadJob(request.jobId);
|
|
171
|
+
return { ok: true };
|
|
172
|
+
case 'run-job': {
|
|
173
|
+
const record = await this.handler.runJobNow(request.jobId);
|
|
174
|
+
return { ok: true, data: record };
|
|
175
|
+
}
|
|
176
|
+
case 'list-jobs': {
|
|
177
|
+
const jobs = this.handler.listJobs();
|
|
178
|
+
// Serialize dates for JSON transport
|
|
179
|
+
const serialized = jobs.map((j) => ({
|
|
180
|
+
...j,
|
|
181
|
+
nextRun: j.nextRun?.toISOString() ?? null,
|
|
182
|
+
}));
|
|
183
|
+
return { ok: true, data: serialized };
|
|
184
|
+
}
|
|
185
|
+
default:
|
|
186
|
+
return { ok: false, error: `Unknown command: ${request.command}` };
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
sendResponse(socket, response) {
|
|
190
|
+
try {
|
|
191
|
+
socket.end(JSON.stringify(response) + '\n');
|
|
192
|
+
}
|
|
193
|
+
catch {
|
|
194
|
+
// Connection may have been closed
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
// ---------------------------------------------------------------------------
|
|
199
|
+
// Client (CLI-side)
|
|
200
|
+
// ---------------------------------------------------------------------------
|
|
201
|
+
/** Timeout for connecting to the daemon socket (ms). */
|
|
202
|
+
const CONNECT_TIMEOUT = 2_000;
|
|
203
|
+
/** Timeout for receiving a response from the daemon (ms). */
|
|
204
|
+
const RESPONSE_TIMEOUT = 300_000; // 5 minutes -- run-job can take a while
|
|
205
|
+
/**
|
|
206
|
+
* Sends a request to the running daemon via the control socket.
|
|
207
|
+
* Returns the parsed response, or null if the daemon is not reachable.
|
|
208
|
+
*/
|
|
209
|
+
export async function sendControlRequest(request, socketPath) {
|
|
210
|
+
const path = socketPath ?? getDaemonSocketPath();
|
|
211
|
+
return new Promise((resolve) => {
|
|
212
|
+
const socket = createConnection({ path });
|
|
213
|
+
let data = '';
|
|
214
|
+
let settled = false;
|
|
215
|
+
let connectTimer = null;
|
|
216
|
+
const finish = (result) => {
|
|
217
|
+
if (settled)
|
|
218
|
+
return;
|
|
219
|
+
settled = true;
|
|
220
|
+
if (connectTimer)
|
|
221
|
+
clearTimeout(connectTimer);
|
|
222
|
+
socket.destroy();
|
|
223
|
+
resolve(result);
|
|
224
|
+
};
|
|
225
|
+
socket.setTimeout(RESPONSE_TIMEOUT, () => {
|
|
226
|
+
finish(null);
|
|
227
|
+
});
|
|
228
|
+
socket.on('connect', () => {
|
|
229
|
+
socket.write(JSON.stringify(request) + '\n');
|
|
230
|
+
});
|
|
231
|
+
socket.on('data', (chunk) => {
|
|
232
|
+
data += chunk.toString();
|
|
233
|
+
const newlineIndex = data.indexOf('\n');
|
|
234
|
+
if (newlineIndex === -1)
|
|
235
|
+
return;
|
|
236
|
+
try {
|
|
237
|
+
const response = JSON.parse(data.slice(0, newlineIndex));
|
|
238
|
+
finish(response);
|
|
239
|
+
}
|
|
240
|
+
catch {
|
|
241
|
+
finish(null);
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
socket.on('error', () => {
|
|
245
|
+
finish(null);
|
|
246
|
+
});
|
|
247
|
+
// Connect timeout -- daemon not responding
|
|
248
|
+
connectTimer = setTimeout(() => {
|
|
249
|
+
finish(null);
|
|
250
|
+
}, CONNECT_TIMEOUT);
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Checks if a daemon is currently running and responsive.
|
|
255
|
+
* Returns true if the daemon responds to a ping.
|
|
256
|
+
*/
|
|
257
|
+
export async function isDaemonRunning(socketPath) {
|
|
258
|
+
const response = await sendControlRequest({ command: 'ping' }, socketPath);
|
|
259
|
+
return response !== null && response.ok;
|
|
260
|
+
}
|
|
261
|
+
//# sourceMappingURL=control-socket.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"control-socket.js","sourceRoot":"","sources":["../../src/daemon/control-socket.ts"],"names":[],"mappings":"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"}
|