@sentropic/remote-cli 0.0.3
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/dist/attach-YI2AMS3B.js +22 -0
- package/dist/chunk-3RG5ZIWI.js +10 -0
- package/dist/chunk-E3GXAKJ3.js +258 -0
- package/dist/chunk-PG4CWJNC.js +197 -0
- package/dist/chunk-RLFQALHC.js +45 -0
- package/dist/chunk-VRTZ23J3.js +541 -0
- package/dist/chunk-Z277FK2S.js +294 -0
- package/dist/h2a-bridge-FPB5D3TB.js +20 -0
- package/dist/index.d.ts +730 -0
- package/dist/index.js +13966 -0
- package/dist/llm-gateway-runtime/index.d.ts +2 -0
- package/dist/llm-gateway-runtime/index.js +1295 -0
- package/dist/sync-status-WRQK4YRK.js +15 -0
- package/dist/workspace-O32VX2JG.js +38 -0
- package/dist/workspace-sync-incremental-W3SHAYHS.js +159 -0
- package/package.json +46 -0
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getTunnel
|
|
3
|
+
} from "./chunk-E3GXAKJ3.js";
|
|
4
|
+
|
|
5
|
+
// src/h2a-bridge.ts
|
|
6
|
+
import { spawnSync } from "child_process";
|
|
7
|
+
import {
|
|
8
|
+
existsSync,
|
|
9
|
+
mkdirSync,
|
|
10
|
+
readdirSync,
|
|
11
|
+
readFileSync,
|
|
12
|
+
writeFileSync
|
|
13
|
+
} from "fs";
|
|
14
|
+
import { homedir } from "os";
|
|
15
|
+
import { join } from "path";
|
|
16
|
+
var H2A_POD_ROOT = "h2a-workspace/.h2a";
|
|
17
|
+
function defaultLocalH2aRoot() {
|
|
18
|
+
return join(homedir(), "h2a-workspace", ".h2a");
|
|
19
|
+
}
|
|
20
|
+
function instanceInboxDir(instance) {
|
|
21
|
+
return instance.replace(/:/g, "__");
|
|
22
|
+
}
|
|
23
|
+
function profileTool(profile) {
|
|
24
|
+
switch (profile) {
|
|
25
|
+
case "claude":
|
|
26
|
+
case "claude-code":
|
|
27
|
+
return "claude";
|
|
28
|
+
case "codex":
|
|
29
|
+
return "codex";
|
|
30
|
+
case "agy":
|
|
31
|
+
case "antigravity":
|
|
32
|
+
return "agy";
|
|
33
|
+
case "gemini":
|
|
34
|
+
case "mistral":
|
|
35
|
+
return profile;
|
|
36
|
+
default:
|
|
37
|
+
return profile && profile.length > 0 ? profile : "claude";
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function defaultPodInstance(sessionId, profile) {
|
|
41
|
+
return `${profileTool(profile)}:remote:${sessionId}`;
|
|
42
|
+
}
|
|
43
|
+
var SAFE_ENTRY = /^[A-Za-z0-9][A-Za-z0-9._-]*\/[A-Za-z0-9][A-Za-z0-9._-]*\.json$/;
|
|
44
|
+
function planBridge(args) {
|
|
45
|
+
const { podInstanceDirs } = args;
|
|
46
|
+
const split = (entry) => {
|
|
47
|
+
const slash = entry.indexOf("/");
|
|
48
|
+
return { dir: entry.slice(0, slash), file: entry.slice(slash + 1) };
|
|
49
|
+
};
|
|
50
|
+
let skipped = 0;
|
|
51
|
+
let ignored = 0;
|
|
52
|
+
const podSet = /* @__PURE__ */ new Set();
|
|
53
|
+
const pull = [];
|
|
54
|
+
const push = [];
|
|
55
|
+
for (const entry of args.podFiles) {
|
|
56
|
+
if (!SAFE_ENTRY.test(entry)) {
|
|
57
|
+
ignored += 1;
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
podSet.add(entry);
|
|
61
|
+
}
|
|
62
|
+
const localSet = /* @__PURE__ */ new Set();
|
|
63
|
+
for (const entry of args.localFiles) {
|
|
64
|
+
if (!SAFE_ENTRY.test(entry)) {
|
|
65
|
+
ignored += 1;
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
localSet.add(entry);
|
|
69
|
+
}
|
|
70
|
+
for (const entry of podSet) {
|
|
71
|
+
const f = split(entry);
|
|
72
|
+
if (podInstanceDirs.has(f.dir)) continue;
|
|
73
|
+
if (localSet.has(entry)) skipped += 1;
|
|
74
|
+
else pull.push(f);
|
|
75
|
+
}
|
|
76
|
+
for (const entry of localSet) {
|
|
77
|
+
const f = split(entry);
|
|
78
|
+
if (!podInstanceDirs.has(f.dir)) continue;
|
|
79
|
+
if (podSet.has(entry)) skipped += 1;
|
|
80
|
+
else push.push(f);
|
|
81
|
+
}
|
|
82
|
+
return { pull, push, skipped, ignored };
|
|
83
|
+
}
|
|
84
|
+
function parsePodListing(out) {
|
|
85
|
+
const instances = [];
|
|
86
|
+
const files = [];
|
|
87
|
+
let section = "none";
|
|
88
|
+
for (const raw of out.split("\n")) {
|
|
89
|
+
const line = raw.trim();
|
|
90
|
+
if (line === "==INSTANCES==") {
|
|
91
|
+
section = "instances";
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
if (line === "==FILES==") {
|
|
95
|
+
section = "files";
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
if (line.length === 0) continue;
|
|
99
|
+
if (section === "instances") instances.push(line);
|
|
100
|
+
else if (section === "files") files.push(line);
|
|
101
|
+
}
|
|
102
|
+
return { instances, files };
|
|
103
|
+
}
|
|
104
|
+
function expandHome(p) {
|
|
105
|
+
return p.startsWith("~") ? join(homedir(), p.slice(1)) : p;
|
|
106
|
+
}
|
|
107
|
+
function execPod(tunnel, pod, script, input) {
|
|
108
|
+
const args = ["-n", tunnel.namespace, "exec"];
|
|
109
|
+
if (input !== void 0) args.push("-i");
|
|
110
|
+
args.push(pod, "-c", "session-agent", "--", "bash", "-lc", script);
|
|
111
|
+
const env = { ...process.env };
|
|
112
|
+
if (tunnel.kubeconfig) env.KUBECONFIG = expandHome(tunnel.kubeconfig);
|
|
113
|
+
const r = spawnSync("kubectl", args, {
|
|
114
|
+
encoding: "utf8",
|
|
115
|
+
env,
|
|
116
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
117
|
+
...input !== void 0 ? { input } : {}
|
|
118
|
+
});
|
|
119
|
+
if (r.status !== 0) {
|
|
120
|
+
throw new Error(
|
|
121
|
+
`kubectl exec failed: ${(r.stderr || "").trim().slice(0, 200)}`
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
return r.stdout;
|
|
125
|
+
}
|
|
126
|
+
function podReadme(podInboxDir) {
|
|
127
|
+
return [
|
|
128
|
+
"# h2a store (bridged)",
|
|
129
|
+
"",
|
|
130
|
+
"This directory is an h2a file store kept in sync with the operator's",
|
|
131
|
+
"local ~/h2a-workspace/.h2a by `remote h2a bridge` (no h2a binary needed",
|
|
132
|
+
"in this Pod).",
|
|
133
|
+
"",
|
|
134
|
+
"To MESSAGE another agent, write a JSON envelope",
|
|
135
|
+
' { "protocol": "sentropic.h2a", "version": "0.1", "id": "env:<ts>:<slug>",',
|
|
136
|
+
' "type": "event", "actor": { "instance", "role", "scope" }, "body", "createdAt" }',
|
|
137
|
+
"to inbox/<instance-dir>/env__<ts>__<slug>.json, where <instance-dir> is",
|
|
138
|
+
'the target instance id with ":" replaced by "__"',
|
|
139
|
+
"(claude:track:abc -> claude__track__abc).",
|
|
140
|
+
"",
|
|
141
|
+
`Your own inbox is inbox/${podInboxDir}/ \u2014 check it for new envelopes.`,
|
|
142
|
+
"The bridge never deletes files; acks/cleanup belong to h2a on the local",
|
|
143
|
+
"side. Existing file names are never overwritten.",
|
|
144
|
+
""
|
|
145
|
+
].join("\n");
|
|
146
|
+
}
|
|
147
|
+
function listLocalInbox(localRoot) {
|
|
148
|
+
const inbox = join(localRoot, "inbox");
|
|
149
|
+
if (!existsSync(inbox)) return [];
|
|
150
|
+
const entries = [];
|
|
151
|
+
for (const dir of readdirSync(inbox, { withFileTypes: true })) {
|
|
152
|
+
if (!dir.isDirectory()) continue;
|
|
153
|
+
for (const f of readdirSync(join(inbox, dir.name), {
|
|
154
|
+
withFileTypes: true
|
|
155
|
+
})) {
|
|
156
|
+
if (f.isFile() && f.name.endsWith(".json"))
|
|
157
|
+
entries.push(`${dir.name}/${f.name}`);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return entries;
|
|
161
|
+
}
|
|
162
|
+
async function bridgeSession(sessionId, options = {}) {
|
|
163
|
+
const stderr = options.stderr ?? process.stderr;
|
|
164
|
+
const tunnel = getTunnel();
|
|
165
|
+
if (!tunnel) {
|
|
166
|
+
throw new Error(
|
|
167
|
+
"h2a bridge needs a tunnel configured (remote config tunnel \u2026)"
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
const pod = `session-${sessionId}`;
|
|
171
|
+
const localRoot = options.localRoot ?? defaultLocalH2aRoot();
|
|
172
|
+
const podInbox = `$HOME/${H2A_POD_ROOT}/inbox`;
|
|
173
|
+
const defaultDir = instanceInboxDir(
|
|
174
|
+
defaultPodInstance(sessionId, options.profile)
|
|
175
|
+
);
|
|
176
|
+
const scaffold = execPod(
|
|
177
|
+
tunnel,
|
|
178
|
+
pod,
|
|
179
|
+
[
|
|
180
|
+
`root="$HOME/${H2A_POD_ROOT}"`,
|
|
181
|
+
`if [ -d "$root/inbox" ]; then echo h2a-store-exists; else`,
|
|
182
|
+
`mkdir -p "$root/inbox/${defaultDir}"`,
|
|
183
|
+
`cat > "$root/README.md" <<'H2ADOC'`,
|
|
184
|
+
podReadme(defaultDir),
|
|
185
|
+
`H2ADOC`,
|
|
186
|
+
`echo h2a-store-created`,
|
|
187
|
+
`fi`
|
|
188
|
+
].join("\n")
|
|
189
|
+
);
|
|
190
|
+
const scaffolded = scaffold.includes("h2a-store-created");
|
|
191
|
+
const listing = parsePodListing(
|
|
192
|
+
execPod(
|
|
193
|
+
tunnel,
|
|
194
|
+
pod,
|
|
195
|
+
[
|
|
196
|
+
`cd "$HOME/${H2A_POD_ROOT}" 2>/dev/null || exit 0`,
|
|
197
|
+
`echo ==INSTANCES==`,
|
|
198
|
+
`sed -n 's/.*"instance"[[:space:]]*:[[:space:]]*"\\([^"]*\\)".*/\\1/p' registry/instances.jsonl 2>/dev/null`,
|
|
199
|
+
`echo ==FILES==`,
|
|
200
|
+
`shopt -s nullglob`,
|
|
201
|
+
`for f in inbox/*/*.json; do printf '%s\\n' "\${f#inbox/}"; done`
|
|
202
|
+
].join("\n")
|
|
203
|
+
)
|
|
204
|
+
);
|
|
205
|
+
const podInstanceDirs = /* @__PURE__ */ new Set([defaultDir]);
|
|
206
|
+
for (const instance of listing.instances) {
|
|
207
|
+
podInstanceDirs.add(instanceInboxDir(instance));
|
|
208
|
+
}
|
|
209
|
+
const plan = planBridge({
|
|
210
|
+
podFiles: listing.files,
|
|
211
|
+
localFiles: listLocalInbox(localRoot),
|
|
212
|
+
podInstanceDirs
|
|
213
|
+
});
|
|
214
|
+
if (plan.ignored > 0) {
|
|
215
|
+
stderr.write(
|
|
216
|
+
`[remote] h2a bridge ${sessionId}: ignored ${plan.ignored} unsafe inbox entry name(s)
|
|
217
|
+
`
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
let pulled = 0;
|
|
221
|
+
let pushed = 0;
|
|
222
|
+
let skipped = plan.skipped;
|
|
223
|
+
let failed = 0;
|
|
224
|
+
for (const f of plan.pull) {
|
|
225
|
+
const dst = join(localRoot, "inbox", f.dir, f.file);
|
|
226
|
+
if (existsSync(dst)) {
|
|
227
|
+
skipped += 1;
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
try {
|
|
231
|
+
const b64 = execPod(
|
|
232
|
+
tunnel,
|
|
233
|
+
pod,
|
|
234
|
+
`base64 < "${podInbox}/${f.dir}/${f.file}" | tr -d '\\n'`
|
|
235
|
+
);
|
|
236
|
+
mkdirSync(join(localRoot, "inbox", f.dir), { recursive: true });
|
|
237
|
+
writeFileSync(dst, Buffer.from(b64.trim(), "base64"));
|
|
238
|
+
pulled += 1;
|
|
239
|
+
} catch (error) {
|
|
240
|
+
failed += 1;
|
|
241
|
+
stderr.write(
|
|
242
|
+
`[remote] h2a bridge ${sessionId}: pull ${f.dir}/${f.file} failed: ${String(
|
|
243
|
+
error instanceof Error ? error.message : error
|
|
244
|
+
).slice(0, 160)}
|
|
245
|
+
`
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
for (const f of plan.push) {
|
|
250
|
+
const src = join(localRoot, "inbox", f.dir, f.file);
|
|
251
|
+
try {
|
|
252
|
+
const payload = readFileSync(src).toString("base64");
|
|
253
|
+
const out = execPod(
|
|
254
|
+
tunnel,
|
|
255
|
+
pod,
|
|
256
|
+
[
|
|
257
|
+
`d="${podInbox}/${f.dir}"; f="$d/${f.file}"`,
|
|
258
|
+
`mkdir -p "$d"`,
|
|
259
|
+
`if [ -e "$f" ]; then echo h2a-exists; else base64 -d > "$f" && echo h2a-written; fi`
|
|
260
|
+
].join("\n"),
|
|
261
|
+
payload
|
|
262
|
+
);
|
|
263
|
+
if (out.includes("h2a-written")) pushed += 1;
|
|
264
|
+
else skipped += 1;
|
|
265
|
+
} catch (error) {
|
|
266
|
+
failed += 1;
|
|
267
|
+
stderr.write(
|
|
268
|
+
`[remote] h2a bridge ${sessionId}: push ${f.dir}/${f.file} failed: ${String(
|
|
269
|
+
error instanceof Error ? error.message : error
|
|
270
|
+
).slice(0, 160)}
|
|
271
|
+
`
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
return {
|
|
276
|
+
sessionId,
|
|
277
|
+
pulled,
|
|
278
|
+
pushed,
|
|
279
|
+
skipped,
|
|
280
|
+
failed,
|
|
281
|
+
scaffolded,
|
|
282
|
+
podInstanceDirs: [...podInstanceDirs].sort()
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
export {
|
|
287
|
+
H2A_POD_ROOT,
|
|
288
|
+
defaultLocalH2aRoot,
|
|
289
|
+
instanceInboxDir,
|
|
290
|
+
defaultPodInstance,
|
|
291
|
+
planBridge,
|
|
292
|
+
parsePodListing,
|
|
293
|
+
bridgeSession
|
|
294
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import {
|
|
2
|
+
H2A_POD_ROOT,
|
|
3
|
+
bridgeSession,
|
|
4
|
+
defaultLocalH2aRoot,
|
|
5
|
+
defaultPodInstance,
|
|
6
|
+
instanceInboxDir,
|
|
7
|
+
parsePodListing,
|
|
8
|
+
planBridge
|
|
9
|
+
} from "./chunk-Z277FK2S.js";
|
|
10
|
+
import "./chunk-E3GXAKJ3.js";
|
|
11
|
+
import "./chunk-3RG5ZIWI.js";
|
|
12
|
+
export {
|
|
13
|
+
H2A_POD_ROOT,
|
|
14
|
+
bridgeSession,
|
|
15
|
+
defaultLocalH2aRoot,
|
|
16
|
+
defaultPodInstance,
|
|
17
|
+
instanceInboxDir,
|
|
18
|
+
parsePodListing,
|
|
19
|
+
planBridge
|
|
20
|
+
};
|