@claw-link/gateway-host 0.2.6 → 0.2.8
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/package.json +1 -1
- package/scripts/install.js +5 -3
- package/src/adapters/cli.js +40 -3
- package/src/adapters/codex.js +26 -15
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@claw-link/gateway-host",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.8",
|
|
4
4
|
"description": "ClawLink Host Gateway — a secure, outbound-only worker that bridges a local agent CLI (OpenClaw, Hermes, Claude, Codex, Cursor) to your ClawLink agents. No inbound ports; authenticated per-agent by a Host Token.",
|
|
5
5
|
"homepage": "https://claw-link.co",
|
|
6
6
|
"bin": {
|
package/scripts/install.js
CHANGED
|
@@ -171,11 +171,13 @@ function printAgents(cfg) {
|
|
|
171
171
|
if (!agents.length) { console.log(' No agents mapped. Run: clhost add-agent'); return; }
|
|
172
172
|
console.log(` ${agents.length} agent(s) mapped:`);
|
|
173
173
|
for (const a of agents) {
|
|
174
|
-
const id =
|
|
174
|
+
const id = a.agent_id || '(unresolved)';
|
|
175
175
|
const tok = a.host_token ? `…${String(a.host_token).slice(-4)}` : '—';
|
|
176
|
-
console.log(` • ${id}
|
|
176
|
+
console.log(` • ${id} runtime=${a.runtime || 'pull'} token=${tok}` + (a.work_dir ? ` work=${a.work_dir}` : ''));
|
|
177
177
|
}
|
|
178
|
-
|
|
178
|
+
// Show a concrete, copy-pasteable example (commands also accept any id prefix).
|
|
179
|
+
const example = agents[0].agent_id || '<id>';
|
|
180
|
+
console.log(`\n Re-enter a token after rotating (full id or any prefix): clhost retoken ${example}`);
|
|
179
181
|
}
|
|
180
182
|
|
|
181
183
|
// `agents` — list mapped agents.
|
package/src/adapters/cli.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
// The operator can override binary + args_template in config.
|
|
5
5
|
|
|
6
6
|
const fs = require('fs');
|
|
7
|
+
const path = require('path');
|
|
7
8
|
const { spawnStreaming, buildArgv } = require('./base');
|
|
8
9
|
const sessionStore = require('../session-store');
|
|
9
10
|
|
|
@@ -46,8 +47,42 @@ function makeStreamJsonParser(capture) {
|
|
|
46
47
|
};
|
|
47
48
|
}
|
|
48
49
|
|
|
50
|
+
// Codex `exec --json` JSONL events. The assistant's text is in
|
|
51
|
+
// item.completed → item.type 'agent_message' → item.text (there can be several:
|
|
52
|
+
// narration + final). thread.started.thread_id is the resume id. command/file/mcp
|
|
53
|
+
// items become tool/log lines for the activity log.
|
|
54
|
+
function makeCodexJsonParser(capture) {
|
|
55
|
+
return function parse(line) {
|
|
56
|
+
const s = line.trim();
|
|
57
|
+
if (!s || s[0] !== '{') return []; // skip non-JSON (e.g. "Reading additional input…")
|
|
58
|
+
let ev;
|
|
59
|
+
try { ev = JSON.parse(s); } catch { return []; }
|
|
60
|
+
const out = [];
|
|
61
|
+
if (ev.type === 'thread.started') {
|
|
62
|
+
if (ev.thread_id && capture) capture.sessionId = ev.thread_id;
|
|
63
|
+
} else if (ev.type === 'item.completed' && ev.item) {
|
|
64
|
+
const it = ev.item;
|
|
65
|
+
if (it.type === 'agent_message' && typeof it.text === 'string') {
|
|
66
|
+
out.push({ type: 'chunk', content: (capture && capture.sawText ? '\n\n' : '') + it.text });
|
|
67
|
+
if (capture) capture.sawText = true;
|
|
68
|
+
} else if (it.type === 'command_execution' && it.command) {
|
|
69
|
+
out.push({ type: 'tool', content: `run: ${String(it.command).replace(/^\/bin\/\w+ -lc /, '').slice(0, 80)}` });
|
|
70
|
+
} else if (it.type === 'file_change' && Array.isArray(it.changes)) {
|
|
71
|
+
out.push({ type: 'tool', content: it.changes.map((c) => `${c.kind || 'edit'} ${path.basename(c.path || '')}`).join(', ') || 'file_change' });
|
|
72
|
+
} else if (it.type === 'mcp_tool_call') {
|
|
73
|
+
out.push({ type: 'tool', content: it.tool || it.name || 'mcp' });
|
|
74
|
+
}
|
|
75
|
+
} else if (ev.type === 'turn.failed') {
|
|
76
|
+
const m = (ev.error && (ev.error.message || ev.error)) || 'turn failed';
|
|
77
|
+
out.push({ type: 'log', content: `error: ${String(m).slice(0, 200)}` });
|
|
78
|
+
}
|
|
79
|
+
return out;
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
49
83
|
/**
|
|
50
|
-
* @param cfg { name, binaries:[], defaultArgs(fullPrompt, resumeId):[],
|
|
84
|
+
* @param cfg { name, binaries:[], defaultArgs(fullPrompt, resumeId, promptViaStdin, permArgs):[],
|
|
85
|
+
* streamJson?:bool, makeParser?:(capture)=>parseFn, permissions?:(scope)=>[],
|
|
51
86
|
* promptViaStdin?:bool, timeoutMs?:number }
|
|
52
87
|
*/
|
|
53
88
|
function makeCliAdapter(cfg) {
|
|
@@ -90,7 +125,9 @@ function makeCliAdapter(cfg) {
|
|
|
90
125
|
let emittedAny = false;
|
|
91
126
|
const runOnce = async function* (rid) {
|
|
92
127
|
const capture = {};
|
|
93
|
-
const parseLine = cfg.
|
|
128
|
+
const parseLine = cfg.makeParser ? cfg.makeParser(capture)
|
|
129
|
+
: cfg.streamJson ? makeStreamJsonParser(capture)
|
|
130
|
+
: genericLineParser;
|
|
94
131
|
for await (const evt of spawnStreaming({
|
|
95
132
|
binary, argv: argvFor(rid), cwd: agent.work_dir, timeoutMs,
|
|
96
133
|
stdinInput: cfg.promptViaStdin ? fullPrompt : null, parseLine,
|
|
@@ -122,4 +159,4 @@ function makeCliAdapter(cfg) {
|
|
|
122
159
|
};
|
|
123
160
|
}
|
|
124
161
|
|
|
125
|
-
module.exports = { makeCliAdapter, genericLineParser, makeStreamJsonParser };
|
|
162
|
+
module.exports = { makeCliAdapter, genericLineParser, makeStreamJsonParser, makeCodexJsonParser };
|
package/src/adapters/codex.js
CHANGED
|
@@ -1,22 +1,33 @@
|
|
|
1
1
|
'use strict';
|
|
2
|
-
// OpenAI Codex CLI, non-interactive.
|
|
3
|
-
//
|
|
4
|
-
|
|
2
|
+
// OpenAI Codex CLI (`codex exec`), non-interactive. Verified against codex-cli
|
|
3
|
+
// 0.142.x. Notes that drove this implementation:
|
|
4
|
+
// - `exec` has NO `--ask-for-approval` flag (passing it = exit code 2). Approval is
|
|
5
|
+
// set via the `-c approval_policy=...` config override; in exec it never prompts.
|
|
6
|
+
// - Sandbox: `exec` accepts `-s/--sandbox`, but `exec resume` does NOT — so we set
|
|
7
|
+
// the sandbox via `-c sandbox_mode=...`, which BOTH accept (uniform args).
|
|
8
|
+
// - `--skip-git-repo-check` so a non-git workspace doesn't fail.
|
|
9
|
+
// - `--json` emits JSONL events; the answer is item.completed→agent_message→text.
|
|
10
|
+
// - Resume is a SUBCOMMAND: `codex exec resume [OPTIONS] <SESSION_ID> -- <prompt>`,
|
|
11
|
+
// and the session id is thread.started.thread_id from the previous run.
|
|
12
|
+
const { makeCliAdapter, makeCodexJsonParser } = require('./cli');
|
|
5
13
|
|
|
6
14
|
module.exports = makeCliAdapter({
|
|
7
15
|
name: 'codex',
|
|
8
16
|
binaries: ['codex'],
|
|
9
17
|
promptViaStdin: false,
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
18
|
+
makeParser: makeCodexJsonParser,
|
|
19
|
+
defaultArgs: (fullPrompt, resumeId, _stdin, perm = []) => {
|
|
20
|
+
const opts = ['--json', '--skip-git-repo-check', ...perm];
|
|
21
|
+
// `--` ends option parsing so a prompt starting with '-' is positional, never a flag.
|
|
22
|
+
return resumeId
|
|
23
|
+
? ['exec', 'resume', ...opts, resumeId, '--', fullPrompt]
|
|
24
|
+
: ['exec', ...opts, '--', fullPrompt];
|
|
25
|
+
},
|
|
26
|
+
// Admin "configure" → workspace-write (may write its work dir); customer/discovery
|
|
27
|
+
// → read-only. approval_policy=never so it never blocks on an approval prompt.
|
|
28
|
+
// Set via `-c` config overrides (the only form `exec resume` also accepts).
|
|
29
|
+
permissions: (scope) => {
|
|
30
|
+
const mode = (scope === 'configure' || scope === 'sub_configure') ? 'workspace-write' : 'read-only';
|
|
31
|
+
return ['-c', `sandbox_mode="${mode}"`, '-c', 'approval_policy="never"'];
|
|
32
|
+
},
|
|
22
33
|
});
|