@securecoms/agent-bridge 0.6.0 → 0.8.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 +2 -0
- package/connectors/hermes/__init__.py +60 -0
- package/connectors/hermes/__pycache__/__init__.cpython-311.pyc +0 -0
- package/connectors/hermes/__pycache__/adapter.cpython-311.pyc +0 -0
- package/connectors/hermes/__pycache__/gate.cpython-311.pyc +0 -0
- package/connectors/hermes/__pycache__/securecoms_gate.cpython-311.pyc +0 -0
- package/connectors/hermes/__pycache__/securecoms_transport.cpython-311.pyc +0 -0
- package/connectors/hermes/__pycache__/transport.cpython-311.pyc +0 -0
- package/connectors/hermes/adapter.py +470 -0
- package/connectors/hermes/plugin.yaml +25 -0
- package/connectors/hermes/pytest.ini +3 -0
- package/connectors/hermes/securecoms_gate.py +70 -0
- package/connectors/hermes/securecoms_transport.py +257 -0
- package/connectors/hermes/tests/__pycache__/conftest.cpython-311-pytest-8.4.0.pyc +0 -0
- package/connectors/hermes/tests/__pycache__/test_adapter_wiring.cpython-311-pytest-8.4.0.pyc +0 -0
- package/connectors/hermes/tests/__pycache__/test_gate.cpython-311-pytest-8.4.0.pyc +0 -0
- package/connectors/hermes/tests/__pycache__/test_securecoms_gate.cpython-311-pytest-8.4.0.pyc +0 -0
- package/connectors/hermes/tests/__pycache__/test_securecoms_transport.cpython-311-pytest-8.4.0.pyc +0 -0
- package/connectors/hermes/tests/__pycache__/test_transport.cpython-311-pytest-8.4.0.pyc +0 -0
- package/connectors/hermes/tests/conftest.py +23 -0
- package/connectors/hermes/tests/test_adapter_wiring.py +541 -0
- package/connectors/hermes/tests/test_securecoms_gate.py +113 -0
- package/connectors/hermes/tests/test_securecoms_transport.py +608 -0
- package/dist/bin.d.ts +2 -0
- package/dist/bin.js +436 -108
- package/dist/service/harness-wire.d.ts +22 -0
- package/dist/service/hermes-config.d.ts +51 -0
- package/dist/service/install-prep.d.ts +5 -8
- package/dist/service/wire.d.ts +97 -40
- package/package.json +1 -1
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Strict, line-based editor for `~/.hermes/config.yaml`'s `plugins.enabled`
|
|
3
|
+
* block sequence. Hermes only loads user plugins listed there (plugins.py's
|
|
4
|
+
* loader — see spec §4), so `wire --harness hermes` must add our plugin's
|
|
5
|
+
* entry or the copied connector never runs.
|
|
6
|
+
*
|
|
7
|
+
* This module is PURE: it takes the file's text and returns either a noop
|
|
8
|
+
* (already correct / refused) or the edited text. File I/O and the
|
|
9
|
+
* `config.yaml.bak-wire-<ts>-<pid>` backup live with the caller (wire.ts /
|
|
10
|
+
* harness-wire.ts, Task 6).
|
|
11
|
+
*
|
|
12
|
+
* Hardened rules (spec §4, audited against the real production file at
|
|
13
|
+
* `config.yaml:569-573`):
|
|
14
|
+
* 1. The inserted item copies the EXISTING first item's exact column —
|
|
15
|
+
* never `key-indent + 2`. Production shape has items at the SAME
|
|
16
|
+
* indent as the `enabled:` key itself (a legal, if unusual, YAML block
|
|
17
|
+
* sequence style); some configs indent items by +2. Both must work,
|
|
18
|
+
* which is why the column is READ from the first item, never computed.
|
|
19
|
+
* 2. Insertion lands after the LAST consecutive same-column `- ` item —
|
|
20
|
+
* stop scanning at the first line that isn't one (the boundary before
|
|
21
|
+
* a sibling key such as `disabled: []`).
|
|
22
|
+
* 2b. Stopping the scan is not enough on its own: the line the scan
|
|
23
|
+
* stopped at must be a LEGITIMATE list terminator before the insertion
|
|
24
|
+
* is allowed to happen there. Legitimate means one of: (a) a sibling
|
|
25
|
+
* key at the same column as `enabled:` itself (e.g. `disabled:`), (b)
|
|
26
|
+
* end-of-block via blank-line(s) then a genuine dedent below the
|
|
27
|
+
* items' column, or (c) end of file. Anything else that stops the
|
|
28
|
+
* scan — a bare `-`, a mapping-style item (`- ` followed by an
|
|
29
|
+
* indented `key: value` on the next line), a blank line followed by
|
|
30
|
+
* MORE same-column items (ambiguous: did the writer mean to end the
|
|
31
|
+
* list or just add a spacer?), or any other unrecognized content —
|
|
32
|
+
* refuses rather than insert at a spot that was never verified to be
|
|
33
|
+
* the list's actual end.
|
|
34
|
+
* 3. `plugins.disabled` is checked too: an entry listed there wins over
|
|
35
|
+
* `plugins.enabled` in the loader, so refuse rather than add a second,
|
|
36
|
+
* shadowed entry.
|
|
37
|
+
* 4. Any structural surprise — no `plugins:` block, an inline
|
|
38
|
+
* `enabled: [...]` form, an empty enabled list, mixed-indent items, a
|
|
39
|
+
* comment line interleaved with items (ambiguous: it could be a
|
|
40
|
+
* trailing comment on the item above or a leading comment on the item
|
|
41
|
+
* below), or a non-boundary list terminator (see 2b) — refuses with no
|
|
42
|
+
* write and an actionable message. Never reformats, never touches any
|
|
43
|
+
* other key.
|
|
44
|
+
*/
|
|
45
|
+
export declare const HERMES_PLUGIN_NAME = "securecoms-platform";
|
|
46
|
+
export interface EnsureHermesPluginEnabledResult {
|
|
47
|
+
action: "noop" | "edited";
|
|
48
|
+
text?: string;
|
|
49
|
+
reason?: string;
|
|
50
|
+
}
|
|
51
|
+
export declare function ensureHermesPluginEnabled(configText: string): EnsureHermesPluginEnabledResult;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type ServiceInput } from "./config.js";
|
|
2
|
+
import { type AutoWireDeps } from "./harness-wire.js";
|
|
2
3
|
export interface PrepDeps {
|
|
3
4
|
readFile: (p: string) => string;
|
|
4
5
|
writeFileMode: (p: string, data: string, mode: number) => void;
|
|
@@ -37,13 +38,6 @@ export declare function verifyDaemonUp(args: {
|
|
|
37
38
|
attempts?: number;
|
|
38
39
|
delayMs?: number;
|
|
39
40
|
}, deps: PrepDeps): Promise<boolean>;
|
|
40
|
-
export declare function harnessSnippet(args: {
|
|
41
|
-
slug: string;
|
|
42
|
-
port: number;
|
|
43
|
-
tokenFilePath: string;
|
|
44
|
-
harness: string;
|
|
45
|
-
identityId: string;
|
|
46
|
-
}): string;
|
|
47
41
|
export interface OrchestrateInstallCtx {
|
|
48
42
|
credentialAbsPath: string;
|
|
49
43
|
label?: string;
|
|
@@ -52,7 +46,9 @@ export interface OrchestrateInstallCtx {
|
|
|
52
46
|
home: string;
|
|
53
47
|
launchAgentsDir?: string;
|
|
54
48
|
systemdDir?: string;
|
|
49
|
+
connectorsSrcDir: string;
|
|
55
50
|
prepDeps: PrepDeps;
|
|
51
|
+
autoWireDeps: AutoWireDeps;
|
|
56
52
|
installService: (input: ServiceInput) => {
|
|
57
53
|
label: string;
|
|
58
54
|
filePath: string;
|
|
@@ -65,5 +61,6 @@ export interface OrchestrateInstallCtx {
|
|
|
65
61
|
}
|
|
66
62
|
export declare function orchestrateInstall(ctx: OrchestrateInstallCtx): Promise<{
|
|
67
63
|
echo: string;
|
|
68
|
-
|
|
64
|
+
harnessWired: boolean;
|
|
65
|
+
harnessError: boolean;
|
|
69
66
|
}>;
|
package/dist/service/wire.d.ts
CHANGED
|
@@ -1,10 +1,43 @@
|
|
|
1
1
|
import type { AgentFleetEntry } from "./fleet.js";
|
|
2
2
|
export type Harness = "openclaw" | "hermes";
|
|
3
|
-
|
|
3
|
+
/**
|
|
4
|
+
* Per-harness allow-list of file extensions the copier includes when it
|
|
5
|
+
* installs a connector — openclaw ships a JS runtime, hermes ships Python
|
|
6
|
+
* (F8: a single `.js|.json` filter silently produced an EMPTY hermes install,
|
|
7
|
+
* since none of adapter.py/securecoms_transport.py/securecoms_gate.py/
|
|
8
|
+
* plugin.yaml/__init__.py matched it).
|
|
9
|
+
*/
|
|
10
|
+
export declare const CONNECTOR_FILE_EXTENSIONS: Record<Harness, readonly string[]>;
|
|
11
|
+
/**
|
|
12
|
+
* Directory names the copier never descends into, checked AT THE DIRECTORY
|
|
13
|
+
* BRANCH before recursion (F8) — a filename-only extension filter still
|
|
14
|
+
* walks into `tests/` and would copy every `*.py` it finds there, since `.py`
|
|
15
|
+
* is itself an allow-listed hermes extension. `__pycache__` is compiled
|
|
16
|
+
* bytecode, never source, for either harness.
|
|
17
|
+
*/
|
|
18
|
+
export declare const CONNECTOR_SKIP_DIRS: ReadonlySet<string>;
|
|
19
|
+
/** The config read/backup/write seams the Hermes `plugins.enabled` auto-edit
|
|
20
|
+
* needs (spec §4, F4). Shared between `WireDeps` (the `wire` verb) and
|
|
21
|
+
* `AutoWireDeps` (the `install` auto-wire path) so `enableHermesConfig`
|
|
22
|
+
* below is the ONE implementation both paths call — never duplicated,
|
|
23
|
+
* never skippable on just one of the two. Unused for the openclaw harness,
|
|
24
|
+
* which has no config file to edit. */
|
|
25
|
+
export interface HermesConfigIoDeps {
|
|
26
|
+
/** Returns null when the file (or its parent ~/.hermes) doesn't exist or
|
|
27
|
+
* isn't readable — treated as a config-edit refusal, same posture as any
|
|
28
|
+
* other structural surprise `ensureHermesPluginEnabled` reports. */
|
|
29
|
+
readHermesConfig: (path: string) => string | null;
|
|
30
|
+
/** Persists the ORIGINAL text under a `hermesConfigBackupPath` name,
|
|
31
|
+
* called BEFORE `writeHermesConfig` — only invoked when an edit is about
|
|
32
|
+
* to happen, never on a noop/refusal. */
|
|
33
|
+
backupHermesConfig: (path: string, originalText: string) => void;
|
|
34
|
+
writeHermesConfig: (path: string, newText: string) => void;
|
|
35
|
+
}
|
|
36
|
+
export interface WireDeps extends HermesConfigIoDeps {
|
|
4
37
|
home: string;
|
|
5
38
|
argv: string[];
|
|
6
39
|
readFleet: () => AgentFleetEntry[];
|
|
7
|
-
copyConnector: (srcDir: string, dstDir: string) => void;
|
|
40
|
+
copyConnector: (srcDir: string, dstDir: string, harness: Harness) => void;
|
|
8
41
|
writeBinding: (dstDir: string, identityId: string) => void;
|
|
9
42
|
removeBinding: (dstDir: string) => void;
|
|
10
43
|
getHealth: (port: number) => {
|
|
@@ -12,20 +45,17 @@ export interface WireDeps {
|
|
|
12
45
|
} | null;
|
|
13
46
|
log: (s: string) => void;
|
|
14
47
|
/**
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
* (
|
|
23
|
-
*
|
|
24
|
-
* already supplies this field. Unit tests must pass a value too (any
|
|
25
|
-
* string is fine — `copyConnector` is mocked in tests and never inspects
|
|
26
|
-
* it for the non-hermes paths that reach it).
|
|
48
|
+
* Resolves a harness's connector SOURCE dir on demand, keyed by harness —
|
|
49
|
+
* Task 6 makes BOTH openclaw and hermes real installs, so a single
|
|
50
|
+
* WireDeps instance must be able to wire either one. Real callers
|
|
51
|
+
* (bin.ts's realWireDeps) supply `(h) => connectorsSrcDir(import.meta.url,
|
|
52
|
+
* h)` using bin.ts's OWN import.meta.url — see the standalone
|
|
53
|
+
* `connectorsSrcDir` function's doc comment below for why it has to be the
|
|
54
|
+
* entry point's URL, not wire.ts's own. Unit tests must pass a function
|
|
55
|
+
* too (any string-returning stub is fine — `copyConnector` is mocked in
|
|
56
|
+
* tests and never inspects the path for its own sake).
|
|
27
57
|
*/
|
|
28
|
-
connectorsSrcDir: string;
|
|
58
|
+
connectorsSrcDir: (harness: Harness) => string;
|
|
29
59
|
}
|
|
30
60
|
/**
|
|
31
61
|
* Detects an installed harness by probing well-known home-relative dirs.
|
|
@@ -35,42 +65,69 @@ export interface WireDeps {
|
|
|
35
65
|
*/
|
|
36
66
|
export declare function detectHarness(home: string, statDir: (p: string) => boolean): Harness | null;
|
|
37
67
|
/**
|
|
38
|
-
* Where the connector gets installed for a given harness.
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
* Hermes connector exists.
|
|
68
|
+
* Where the connector gets installed for a given harness. Both harnesses are
|
|
69
|
+
* real installs as of Task 6: openclaw at its `local-plugins` dir; hermes at
|
|
70
|
+
* `~/.hermes/plugins/securecoms` — the loader's actual user-plugins dir
|
|
71
|
+
* (verified against the production config; `local-plugins` was a Task 4/5
|
|
72
|
+
* placeholder value, never the real one).
|
|
44
73
|
*/
|
|
45
74
|
export declare function connectorTargetDir(home: string, harness: Harness): string;
|
|
46
75
|
/**
|
|
47
|
-
* Resolves
|
|
48
|
-
* shipped SOURCE (not compiled — H2), so this
|
|
49
|
-
* file's location back to the package root,
|
|
50
|
-
* their OWN import.meta.url (bin.ts's), not
|
|
51
|
-
* wire.ts's code into dist/bin.js, so only the
|
|
52
|
-
* is guaranteed to sit exactly one directory
|
|
53
|
-
* dev (src/bin.ts) and the built artifact
|
|
54
|
-
* resolve-bin.ts's loadPackageMeta (`dist/bin.js ->
|
|
76
|
+
* Resolves a connector's SOURCE dir at runtime, keyed by harness (`connectors/
|
|
77
|
+
* <harness>`) — `connectors/` is shipped SOURCE (not compiled — H2), so this
|
|
78
|
+
* must resolve from the running file's location back to the package root,
|
|
79
|
+
* never into dist/. Callers pass their OWN import.meta.url (bin.ts's), not
|
|
80
|
+
* wire.ts's: esbuild bundles wire.ts's code into dist/bin.js, so only the
|
|
81
|
+
* entry point's import.meta.url is guaranteed to sit exactly one directory
|
|
82
|
+
* below the package root in BOTH dev (src/bin.ts) and the built artifact
|
|
83
|
+
* (dist/bin.js) — mirrors resolve-bin.ts's loadPackageMeta (`dist/bin.js ->
|
|
84
|
+
* ../package.json`).
|
|
85
|
+
*/
|
|
86
|
+
export declare function connectorsSrcDir(importMetaUrl: string, harness: Harness): string;
|
|
87
|
+
/** Path to the Hermes gateway's `config.yaml`, whose `plugins.enabled` block
|
|
88
|
+
* the auto-edit targets (spec §4). v1 targets the default `~/.hermes`
|
|
89
|
+
* profile only — `HERMES_HOME`/secondary profiles are documented as
|
|
90
|
+
* unsupported by `wire` in this round (spec §4 scope limit). */
|
|
91
|
+
export declare function hermesConfigPath(home: string): string;
|
|
92
|
+
/** Backup filename for a config.yaml auto-edit: `<path>.bak-wire-<ts>-<pid>`
|
|
93
|
+
* (spec §4 rule 4) — unique enough per host that two `wire` runs essentially
|
|
94
|
+
* never collide (different timestamp, or different pid at the same ms). */
|
|
95
|
+
export declare function hermesConfigBackupPath(configPath: string, ts: number, pid: number): string;
|
|
96
|
+
/**
|
|
97
|
+
* F4 (load-bearing): the ONE enable helper invoked from BOTH `runWire` (the
|
|
98
|
+
* `wire` verb, below) and `autoWireHarness` (the `install` auto-wire path,
|
|
99
|
+
* harness-wire.ts) — a hermes wire that copies the connector and writes the
|
|
100
|
+
* binding but skips this step is the silent half-wire the spec forbids.
|
|
101
|
+
* Reads config.yaml, runs Task 5's pure `ensureHermesPluginEnabled`, and on
|
|
102
|
+
* an actual edit backs up the ORIGINAL text before writing the new text.
|
|
103
|
+
* Never writes anything on a noop or a refusal.
|
|
55
104
|
*/
|
|
56
|
-
export declare function
|
|
105
|
+
export declare function enableHermesConfig(home: string, deps: HermesConfigIoDeps): {
|
|
106
|
+
ok: boolean;
|
|
107
|
+
message: string;
|
|
108
|
+
};
|
|
57
109
|
/**
|
|
58
110
|
* Installs the reference connector into the harness's plugin dir and drops
|
|
59
111
|
* an explicit securecoms.binding.json naming the resolved agent's identity.
|
|
60
112
|
* There is NO auto-discovery (audit C1): the binding is written for EVERY
|
|
61
113
|
* agent, singleton included, every time `wire` runs.
|
|
62
114
|
*
|
|
63
|
-
* Hermes
|
|
64
|
-
* `
|
|
65
|
-
*
|
|
66
|
-
*
|
|
115
|
+
* Hermes (Task 6) additionally needs its `~/.hermes/config.yaml`
|
|
116
|
+
* `plugins.enabled` entry (F4's `enableHermesConfig`, shared with
|
|
117
|
+
* autoWireHarness) — Hermes only loads plugins listed there, so the copy +
|
|
118
|
+
* binding alone would leave the connector inert. If that edit refuses (no
|
|
119
|
+
* `plugins:` block, an already-disabled entry, an ambiguous list shape, or a
|
|
120
|
+
* missing config.yaml entirely), the copy and binding REMAIN on disk — they
|
|
121
|
+
* are harmless without the enable — but `wire` exits non-zero with the
|
|
122
|
+
* manual-edit instructions, so callers see harness-NOT-wired rather than a
|
|
123
|
+
* silent half-wire.
|
|
67
124
|
*/
|
|
68
125
|
export declare function runWire(deps: WireDeps): number;
|
|
69
126
|
/** Removes the explicit binding for the resolved agent. Leaves the copied
|
|
70
|
-
* connector files
|
|
71
|
-
* the plugin (that stays
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
127
|
+
* connector files (and, for hermes, the config.yaml plugin entry) in place
|
|
128
|
+
* — unwire un-names the agent, it doesn't uninstall the plugin (that stays
|
|
129
|
+
* a harness-native operation). For hermes specifically: removing the
|
|
130
|
+
* installed files while config.yaml still lists the plugin would make
|
|
131
|
+
* Hermes log load errors at boot, so the copy AND the config entry both
|
|
132
|
+
* deliberately remain — printed as explicit manual-cleanup instructions. */
|
|
76
133
|
export declare function runUnwire(deps: WireDeps): number;
|