opensyn 0.1.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.
@@ -0,0 +1,173 @@
1
+ {
2
+ "id": "opensyn",
3
+ "name": "OpenSyn",
4
+ "description": "Expose local OpenSyn context to OpenClaw through native plugin tools.",
5
+ "version": "0.1.0",
6
+ "configSchema": {
7
+ "type": "object",
8
+ "additionalProperties": false,
9
+ "properties": {
10
+ "runtimeRoot": {
11
+ "type": "string",
12
+ "description": "Optional OpenSyn home directory. Defaults to ~/.opensyn and stores the database plus managed runtime assets."
13
+ },
14
+ "dbPath": {
15
+ "type": "string",
16
+ "description": "Optional absolute path to the OpenSyn SQLite database. Defaults to <runtimeRoot>/opensyn.db."
17
+ },
18
+ "cliBin": {
19
+ "type": "string",
20
+ "description": "Optional absolute path to the synapse-cli binary or OpenSyn CLI wrapper."
21
+ },
22
+ "daemonBin": {
23
+ "type": "string",
24
+ "description": "Optional absolute path to the synapse-daemon binary or OpenSyn daemon wrapper."
25
+ },
26
+ "helperPath": {
27
+ "type": "string",
28
+ "description": "Optional absolute path to the shell-hook ingest helper."
29
+ },
30
+ "runnerPath": {
31
+ "type": "string",
32
+ "description": "Optional absolute path to the command wrapper used by the shell hook."
33
+ },
34
+ "defaultShell": {
35
+ "type": "string",
36
+ "description": "Optional shell name used when plugin-managed collection repairs the local shell hook."
37
+ },
38
+ "defaultProjectRoot": {
39
+ "type": "string",
40
+ "description": "Optional default project root when the caller does not pass one."
41
+ },
42
+ "defaultWindowSecs": {
43
+ "type": "integer",
44
+ "minimum": 1,
45
+ "default": 300
46
+ },
47
+ "defaultSearchLimit": {
48
+ "type": "integer",
49
+ "minimum": 1,
50
+ "default": 10
51
+ },
52
+ "daemonTimeoutMs": {
53
+ "type": "integer",
54
+ "minimum": 1000,
55
+ "default": 10000,
56
+ "description": "Maximum time to wait for synapse-daemon before the tool call fails."
57
+ },
58
+ "projectionBundleRoot": {
59
+ "type": "string",
60
+ "description": "Optional projection root. For OpenClaw host mode this should usually be the OpenClaw workspace root, for example ~/.openclaw/workspace."
61
+ },
62
+ "projectionHost": {
63
+ "type": "string",
64
+ "description": "Optional projection host flavor: openclaw, claudecode, or generic."
65
+ },
66
+ "projectionAutoApplyApproved": {
67
+ "type": "boolean",
68
+ "description": "Optional default for whether approved assets should be projected automatically."
69
+ },
70
+ "watchIdleWindowSecs": {
71
+ "type": "integer",
72
+ "minimum": 1,
73
+ "description": "Optional idle window for the plugin-managed background watch-linux collector. Defaults to 10 seconds."
74
+ },
75
+ "watchPollIntervalSecs": {
76
+ "type": "integer",
77
+ "minimum": 1,
78
+ "description": "Optional poll interval for the plugin-managed background watch-linux collector. Defaults to 2 seconds."
79
+ },
80
+ "openclawBin": {
81
+ "type": "string",
82
+ "description": "Optional path to the OpenClaw CLI used by the plugin-managed host-model distillation worker. Defaults to 'openclaw'."
83
+ },
84
+ "distillationWorkerEnabled": {
85
+ "type": "boolean",
86
+ "description": "Whether the plugin-managed host-model distillation worker should run automatically. Defaults to true."
87
+ },
88
+ "distillationWorkerIntervalSecs": {
89
+ "type": "integer",
90
+ "minimum": 5,
91
+ "description": "Optional interval for the plugin-managed host-model distillation worker. Defaults to 60 seconds."
92
+ },
93
+ "distillationWorkerAgentId": {
94
+ "type": "string",
95
+ "description": "Optional OpenClaw agent id used by the plugin-managed host-model distillation worker. Defaults to 'main'."
96
+ }
97
+ }
98
+ },
99
+ "uiHints": {
100
+ "dbPath": {
101
+ "label": "OpenSyn DB Path",
102
+ "placeholder": "/home/user/.opensyn/opensyn.db"
103
+ },
104
+ "runtimeRoot": {
105
+ "label": "OpenSyn Home",
106
+ "placeholder": "/home/user/.opensyn"
107
+ },
108
+ "cliBin": {
109
+ "label": "OpenSyn CLI Path",
110
+ "placeholder": "/path/to/opensyn-cli"
111
+ },
112
+ "daemonBin": {
113
+ "label": "OpenSyn Daemon Path",
114
+ "placeholder": "/path/to/opensyn-daemon"
115
+ },
116
+ "helperPath": {
117
+ "label": "Helper Path",
118
+ "placeholder": "/path/to/synapse-ingest-command"
119
+ },
120
+ "runnerPath": {
121
+ "label": "Runner Path",
122
+ "placeholder": "/path/to/synapse-run"
123
+ },
124
+ "defaultShell": {
125
+ "label": "Default Shell",
126
+ "placeholder": "bash"
127
+ },
128
+ "defaultProjectRoot": {
129
+ "label": "Default Project Root",
130
+ "placeholder": "/path/to/project"
131
+ },
132
+ "daemonTimeoutMs": {
133
+ "label": "Daemon Timeout (ms)",
134
+ "placeholder": "10000"
135
+ },
136
+ "projectionBundleRoot": {
137
+ "label": "Projection Root",
138
+ "placeholder": "/home/user/.openclaw/workspace"
139
+ },
140
+ "projectionHost": {
141
+ "label": "Projection Host",
142
+ "placeholder": "openclaw"
143
+ },
144
+ "projectionAutoApplyApproved": {
145
+ "label": "Auto Apply Approved Assets",
146
+ "placeholder": "false"
147
+ },
148
+ "watchIdleWindowSecs": {
149
+ "label": "Background Idle Window (s)",
150
+ "placeholder": "10"
151
+ },
152
+ "watchPollIntervalSecs": {
153
+ "label": "Background Poll Interval (s)",
154
+ "placeholder": "2"
155
+ },
156
+ "openclawBin": {
157
+ "label": "OpenClaw CLI Path",
158
+ "placeholder": "openclaw"
159
+ },
160
+ "distillationWorkerEnabled": {
161
+ "label": "Host Distillation Worker",
162
+ "placeholder": "true"
163
+ },
164
+ "distillationWorkerIntervalSecs": {
165
+ "label": "Worker Interval (s)",
166
+ "placeholder": "60"
167
+ },
168
+ "distillationWorkerAgentId": {
169
+ "label": "Worker Agent Id",
170
+ "placeholder": "main"
171
+ }
172
+ }
173
+ }
package/package.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "opensyn",
3
+ "version": "0.1.0",
4
+ "description": "OpenClaw plugin for OpenSyn autonomous local collection, distillation, and host overlay projection.",
5
+ "type": "module",
6
+ "private": false,
7
+ "publishConfig": {
8
+ "access": "public"
9
+ },
10
+ "files": [
11
+ "index.ts",
12
+ "openclaw.plugin.json",
13
+ "README.md",
14
+ "README.zh-CN.md",
15
+ "runtime/**/*"
16
+ ],
17
+ "scripts": {
18
+ "postinstall": "node ./runtime/postinstall.mjs",
19
+ "prepack": "cargo build --manifest-path ../../Cargo.toml -p synapse-daemon -p synapse-cli --release && bash ../../scripts/stage-opensyn-plugin-runtime --profile release --plugin-dir \"$PWD\"",
20
+ "release:check": "npm pack --dry-run",
21
+ "release:publish": "npm publish --access public",
22
+ "stage:runtime:debug": "bash ../../scripts/stage-opensyn-plugin-runtime --profile debug --plugin-dir \"$PWD\"",
23
+ "stage:runtime:release": "bash ../../scripts/stage-opensyn-plugin-runtime --profile release --plugin-dir \"$PWD\""
24
+ },
25
+ "dependencies": {
26
+ "@sinclair/typebox": "^0.34.41"
27
+ },
28
+ "openclaw": {
29
+ "extensions": [
30
+ "./index.ts"
31
+ ]
32
+ }
33
+ }
@@ -0,0 +1,43 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
5
+
6
+ platform="$(uname -s | tr '[:upper:]' '[:lower:]')"
7
+ arch_raw="$(uname -m)"
8
+ case "$arch_raw" in
9
+ x86_64|amd64)
10
+ arch="x64"
11
+ ;;
12
+ aarch64|arm64)
13
+ arch="arm64"
14
+ ;;
15
+ *)
16
+ arch="$arch_raw"
17
+ ;;
18
+ esac
19
+
20
+ candidates=()
21
+ if [[ -n "${OPENSYN_CLI_BIN:-}" ]]; then
22
+ candidates+=("${OPENSYN_CLI_BIN}")
23
+ fi
24
+
25
+ candidates+=(
26
+ "$script_dir/bin/${platform}-${arch}/synapse-cli"
27
+ "$script_dir/bin/synapse-cli"
28
+ "$script_dir/../../../target/debug/synapse-cli"
29
+ "$script_dir/../../../target/release/synapse-cli"
30
+ )
31
+
32
+ for candidate in "${candidates[@]}"; do
33
+ if [[ -n "$candidate" && -x "$candidate" ]]; then
34
+ exec "$candidate" "$@"
35
+ fi
36
+ done
37
+
38
+ echo "OpenSyn CLI runtime not found. Expected one of:" >&2
39
+ for candidate in "${candidates[@]}"; do
40
+ echo " - $candidate" >&2
41
+ done
42
+ echo "Stage package-local binaries with scripts/stage-opensyn-plugin-runtime, or set OPENSYN_CLI_BIN." >&2
43
+ exit 1
@@ -0,0 +1,43 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
5
+
6
+ platform="$(uname -s | tr '[:upper:]' '[:lower:]')"
7
+ arch_raw="$(uname -m)"
8
+ case "$arch_raw" in
9
+ x86_64|amd64)
10
+ arch="x64"
11
+ ;;
12
+ aarch64|arm64)
13
+ arch="arm64"
14
+ ;;
15
+ *)
16
+ arch="$arch_raw"
17
+ ;;
18
+ esac
19
+
20
+ candidates=()
21
+ if [[ -n "${OPENSYN_DAEMON_BIN:-}" ]]; then
22
+ candidates+=("${OPENSYN_DAEMON_BIN}")
23
+ fi
24
+
25
+ candidates+=(
26
+ "$script_dir/bin/${platform}-${arch}/synapse-daemon"
27
+ "$script_dir/bin/synapse-daemon"
28
+ "$script_dir/../../../target/debug/synapse-daemon"
29
+ "$script_dir/../../../target/release/synapse-daemon"
30
+ )
31
+
32
+ for candidate in "${candidates[@]}"; do
33
+ if [[ -n "$candidate" && -x "$candidate" ]]; then
34
+ exec "$candidate" "$@"
35
+ fi
36
+ done
37
+
38
+ echo "OpenSyn daemon runtime not found. Expected one of:" >&2
39
+ for candidate in "${candidates[@]}"; do
40
+ echo " - $candidate" >&2
41
+ done
42
+ echo "Stage package-local binaries with scripts/stage-opensyn-plugin-runtime, or set OPENSYN_DAEMON_BIN." >&2
43
+ exit 1
@@ -0,0 +1,173 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { spawnSync } from "node:child_process";
4
+ import fs from "node:fs";
5
+ import path from "node:path";
6
+
7
+ function parseArgs(argv) {
8
+ const options = {
9
+ openclawBin: "openclaw",
10
+ agentId: "main",
11
+ intervalSecs: 60,
12
+ timeoutSecs: 120,
13
+ projectRoot: process.cwd(),
14
+ statePath: "",
15
+ };
16
+
17
+ for (let i = 0; i < argv.length; i += 1) {
18
+ const arg = argv[i];
19
+ const next = argv[i + 1];
20
+ if (arg === "--openclaw-bin" && next) {
21
+ options.openclawBin = next;
22
+ i += 1;
23
+ } else if (arg === "--agent-id" && next) {
24
+ options.agentId = next;
25
+ i += 1;
26
+ } else if (arg === "--interval-secs" && next) {
27
+ options.intervalSecs = Number.parseInt(next, 10) || options.intervalSecs;
28
+ i += 1;
29
+ } else if (arg === "--timeout-secs" && next) {
30
+ options.timeoutSecs = Number.parseInt(next, 10) || options.timeoutSecs;
31
+ i += 1;
32
+ } else if (arg === "--project-root" && next) {
33
+ options.projectRoot = next;
34
+ i += 1;
35
+ } else if (arg === "--state-path" && next) {
36
+ options.statePath = next;
37
+ i += 1;
38
+ }
39
+ }
40
+
41
+ return options;
42
+ }
43
+
44
+ function sleep(ms) {
45
+ return new Promise((resolve) => setTimeout(resolve, ms));
46
+ }
47
+
48
+ function updateState(statePath, payload) {
49
+ if (!statePath) {
50
+ return;
51
+ }
52
+ let previous = {};
53
+ if (fs.existsSync(statePath)) {
54
+ try {
55
+ previous = JSON.parse(fs.readFileSync(statePath, "utf8"));
56
+ } catch {
57
+ previous = {};
58
+ }
59
+ }
60
+ fs.mkdirSync(path.dirname(statePath), { recursive: true });
61
+ fs.writeFileSync(
62
+ statePath,
63
+ `${JSON.stringify({ ...previous, ...payload }, null, 2)}\n`,
64
+ "utf8",
65
+ );
66
+ }
67
+
68
+ function buildPrompt(projectRoot) {
69
+ return [
70
+ `Run exactly one OpenSyn autonomous distillation cycle for project ${projectRoot}.`,
71
+ "Rules:",
72
+ "- Use only the OpenClaw-configured host model already available to this local runtime.",
73
+ "- Do not browse, do not call external network tools, and do not use any model outside OpenClaw.",
74
+ "- First call opensyn_autonomous_tick with no arguments.",
75
+ "- If automation_state is idle_waiting_for_activity, stop immediately.",
76
+ "- If automation_state is distillation_work_ready, follow cycle_plan.primary_steps in order.",
77
+ "- If the primary path cannot complete safely, follow cycle_plan.fallback_steps.",
78
+ "- Always execute cycle_plan.resume_steps before ending when they are available.",
79
+ "- Complete at most one queued work packet in this run.",
80
+ "- End with one short sentence describing the outcome.",
81
+ ].join("\n");
82
+ }
83
+
84
+ async function main() {
85
+ const options = parseArgs(process.argv.slice(2));
86
+ const prompt = buildPrompt(options.projectRoot);
87
+ const usesNodeShim =
88
+ options.openclawBin.endsWith(".mjs") || options.openclawBin.endsWith(".js");
89
+ let stopped = false;
90
+
91
+ process.on("SIGTERM", () => {
92
+ stopped = true;
93
+ });
94
+ process.on("SIGINT", () => {
95
+ stopped = true;
96
+ });
97
+
98
+ while (!stopped) {
99
+ const startedAt = new Date().toISOString();
100
+ const command = usesNodeShim ? process.execPath : options.openclawBin;
101
+ const commandArgs = usesNodeShim
102
+ ? [
103
+ options.openclawBin,
104
+ "agent",
105
+ "--local",
106
+ "--agent",
107
+ options.agentId,
108
+ "--thinking",
109
+ "minimal",
110
+ "--timeout",
111
+ String(options.timeoutSecs),
112
+ "--message",
113
+ prompt,
114
+ "--json",
115
+ ]
116
+ : [
117
+ "agent",
118
+ "--local",
119
+ "--agent",
120
+ options.agentId,
121
+ "--thinking",
122
+ "minimal",
123
+ "--timeout",
124
+ String(options.timeoutSecs),
125
+ "--message",
126
+ prompt,
127
+ "--json",
128
+ ];
129
+ const result = spawnSync(
130
+ command,
131
+ commandArgs,
132
+ {
133
+ cwd: options.projectRoot,
134
+ env: process.env,
135
+ encoding: "utf8",
136
+ },
137
+ );
138
+
139
+ const finishedAt = new Date().toISOString();
140
+ const payload = {
141
+ started_at: startedAt,
142
+ finished_at: finishedAt,
143
+ command,
144
+ command_args: commandArgs,
145
+ exit_code: result.status,
146
+ signal: result.signal,
147
+ error: result.error ? String(result.error.message || result.error) : "",
148
+ stdout: result.stdout?.trim() || "",
149
+ stderr: result.stderr?.trim() || "",
150
+ };
151
+ updateState(options.statePath, {
152
+ last_started_at: startedAt,
153
+ last_finished_at: finishedAt,
154
+ last_exit_code: result.status,
155
+ last_signal: result.signal,
156
+ last_error: payload.error || payload.stderr || "",
157
+ last_stdout: payload.stdout,
158
+ last_command: payload.command,
159
+ last_command_args: payload.command_args,
160
+ });
161
+ process.stdout.write(`${JSON.stringify(payload)}\n`);
162
+
163
+ if (stopped) {
164
+ break;
165
+ }
166
+ await sleep(Math.max(5, options.intervalSecs) * 1000);
167
+ }
168
+ }
169
+
170
+ main().catch((error) => {
171
+ process.stderr.write(`${error instanceof Error ? error.stack || error.message : String(error)}\n`);
172
+ process.exitCode = 1;
173
+ });
@@ -0,0 +1,124 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ db=""
5
+ daemon_bin=""
6
+ cwd=""
7
+ command_line=""
8
+ exit_code=""
9
+ project_root=""
10
+ source_kind="${OPENSYN_SOURCE_KIND:-host}"
11
+ device_id="${OPENSYN_DEVICE_ID:-}"
12
+ stdout_tail="${OPENSYN_STDOUT_TAIL:-}"
13
+ stderr_tail="${OPENSYN_STDERR_TAIL:-}"
14
+ stdout_file=""
15
+ stderr_file=""
16
+ max_bytes="${OPENSYN_MAX_TAIL_BYTES:-4000}"
17
+
18
+ while [[ $# -gt 0 ]]; do
19
+ case "$1" in
20
+ --db)
21
+ db="$2"
22
+ shift 2
23
+ ;;
24
+ --daemon-bin)
25
+ daemon_bin="$2"
26
+ shift 2
27
+ ;;
28
+ --cwd)
29
+ cwd="$2"
30
+ shift 2
31
+ ;;
32
+ --command-line)
33
+ command_line="$2"
34
+ shift 2
35
+ ;;
36
+ --exit-code)
37
+ exit_code="$2"
38
+ shift 2
39
+ ;;
40
+ --project-root)
41
+ project_root="$2"
42
+ shift 2
43
+ ;;
44
+ --source-kind)
45
+ source_kind="$2"
46
+ shift 2
47
+ ;;
48
+ --device-id)
49
+ device_id="$2"
50
+ shift 2
51
+ ;;
52
+ --stdout-tail)
53
+ stdout_tail="$2"
54
+ shift 2
55
+ ;;
56
+ --stderr-tail)
57
+ stderr_tail="$2"
58
+ shift 2
59
+ ;;
60
+ --stdout-file)
61
+ stdout_file="$2"
62
+ shift 2
63
+ ;;
64
+ --stderr-file)
65
+ stderr_file="$2"
66
+ shift 2
67
+ ;;
68
+ --max-bytes)
69
+ max_bytes="$2"
70
+ shift 2
71
+ ;;
72
+ *)
73
+ echo "unknown argument: $1" >&2
74
+ exit 2
75
+ ;;
76
+ esac
77
+ done
78
+
79
+ if [[ -z "$db" || -z "$daemon_bin" || -z "$cwd" || -z "$command_line" || -z "$exit_code" ]]; then
80
+ echo "missing required arguments" >&2
81
+ exit 2
82
+ fi
83
+
84
+ if [[ -z "$project_root" ]]; then
85
+ project_root="$cwd"
86
+ fi
87
+
88
+ if [[ -z "$stdout_tail" && -n "$stdout_file" && -f "$stdout_file" ]]; then
89
+ stdout_tail="$(tail -c "$max_bytes" "$stdout_file" 2>/dev/null || true)"
90
+ fi
91
+
92
+ if [[ -z "$stderr_tail" && -n "$stderr_file" && -f "$stderr_file" ]]; then
93
+ stderr_tail="$(tail -c "$max_bytes" "$stderr_file" 2>/dev/null || true)"
94
+ fi
95
+
96
+ payload="$(
97
+ python3 - "$project_root" "$cwd" "$command_line" "$source_kind" "$exit_code" "$device_id" "$stdout_tail" "$stderr_tail" <<'PY'
98
+ import json
99
+ import shlex
100
+ import sys
101
+
102
+ project_root, cwd, command_line, source_kind, exit_code, device_id, stdout_tail, stderr_tail = sys.argv[1:]
103
+ parts = shlex.split(command_line)
104
+ command = parts[0] if parts else command_line
105
+ argv = parts[1:] if parts else []
106
+ payload = {
107
+ "project_root": project_root,
108
+ "cwd": cwd,
109
+ "command": command,
110
+ "argv": argv,
111
+ "source_kind": source_kind,
112
+ "exit_code": int(exit_code),
113
+ }
114
+ if stdout_tail:
115
+ payload["stdout_tail"] = stdout_tail
116
+ if stderr_tail:
117
+ payload["stderr_tail"] = stderr_tail
118
+ if device_id:
119
+ payload["device_id"] = device_id
120
+ print(json.dumps(payload, ensure_ascii=True))
121
+ PY
122
+ )"
123
+
124
+ "$daemon_bin" ingest-command-json --db "$db" --payload "$payload"
@@ -0,0 +1,99 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ db=""
5
+ daemon_bin=""
6
+ cwd=""
7
+ project_root=""
8
+ source_kind="${OPENSYN_SOURCE_KIND:-host}"
9
+ device_id="${OPENSYN_DEVICE_ID:-}"
10
+ max_bytes="${OPENSYN_MAX_TAIL_BYTES:-4000}"
11
+
12
+ while [[ $# -gt 0 ]]; do
13
+ case "$1" in
14
+ --db)
15
+ db="$2"
16
+ shift 2
17
+ ;;
18
+ --daemon-bin)
19
+ daemon_bin="$2"
20
+ shift 2
21
+ ;;
22
+ --cwd)
23
+ cwd="$2"
24
+ shift 2
25
+ ;;
26
+ --project-root)
27
+ project_root="$2"
28
+ shift 2
29
+ ;;
30
+ --source-kind)
31
+ source_kind="$2"
32
+ shift 2
33
+ ;;
34
+ --device-id)
35
+ device_id="$2"
36
+ shift 2
37
+ ;;
38
+ --)
39
+ shift
40
+ break
41
+ ;;
42
+ *)
43
+ echo "unknown argument: $1" >&2
44
+ exit 2
45
+ ;;
46
+ esac
47
+ done
48
+
49
+ if [[ -z "$db" || -z "$daemon_bin" || -z "$cwd" || $# -eq 0 ]]; then
50
+ echo "missing required arguments" >&2
51
+ exit 2
52
+ fi
53
+
54
+ if [[ -z "$project_root" ]]; then
55
+ project_root="$cwd"
56
+ fi
57
+
58
+ tmpdir="$(mktemp -d)"
59
+ stdout_file="$tmpdir/stdout.log"
60
+ stderr_file="$tmpdir/stderr.log"
61
+ trap 'rm -rf "$tmpdir"' EXIT
62
+
63
+ command_line=""
64
+ for arg in "$@"; do
65
+ printf -v quoted '%q' "$arg"
66
+ command_line+="${command_line:+ }$quoted"
67
+ done
68
+
69
+ set +e
70
+ (
71
+ cd "$cwd"
72
+ "$@"
73
+ ) \
74
+ > >(tee "$stdout_file") \
75
+ 2> >(tee "$stderr_file" >&2)
76
+ exit_code=$?
77
+ set -e
78
+
79
+ helper_args=(
80
+ --db "$db"
81
+ --daemon-bin "$daemon_bin"
82
+ --project-root "$project_root"
83
+ --cwd "$cwd"
84
+ --source-kind "$source_kind"
85
+ --command-line "$command_line"
86
+ --exit-code "$exit_code"
87
+ --stdout-file "$stdout_file"
88
+ --stderr-file "$stderr_file"
89
+ --max-bytes "$max_bytes"
90
+ )
91
+
92
+ if [[ -n "$device_id" ]]; then
93
+ helper_args+=(--device-id "$device_id")
94
+ fi
95
+
96
+ "$(dirname "$0")/opensyn-ingest-command" \
97
+ "${helper_args[@]}"
98
+
99
+ exit "$exit_code"