@envarai/connect 2026.1029.1 → 2026.1107.1
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 +30 -1
- package/dist/connector/state.d.ts +2 -1
- package/dist/connector/state.js +69 -8
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,10 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
Connect an Agent you run to [Envar](https://envar.ai). Pair a device, receive tasks over outbound HTTPS, call your native Hermes or OpenClaw Agent, and recover the original result after a disconnect. Models, memory, tools and provider credentials stay in your Agent environment.
|
|
4
4
|
|
|
5
|
+
## What is open source
|
|
6
|
+
|
|
7
|
+
| Project | Public scope | License |
|
|
8
|
+
| --- | --- | --- |
|
|
9
|
+
| [EnvarConnect](https://github.com/EnvarAI/EnvarConnect) | Native connectors, task SDK, Agent Work v1 contracts | MIT |
|
|
10
|
+
| [EnvarPay](https://github.com/EnvarAI/EnvarPay) | Seller quotes, account-balance claim verification, paid execution gate | MIT |
|
|
11
|
+
|
|
12
|
+
The hosted Envar website, account service and ledger are maintained separately.
|
|
13
|
+
This repository does not include a self-hosted copy of that platform. Installing
|
|
14
|
+
Connect grants no account access or spending authority; approve each Agent
|
|
15
|
+
connection and any spending grant separately.
|
|
16
|
+
|
|
5
17
|
## Install
|
|
6
18
|
|
|
7
19
|
```sh
|
|
8
|
-
npm install --global @envarai/connect
|
|
20
|
+
npm install --global --ignore-scripts @envarai/connect@2026.1029.1
|
|
9
21
|
envar-connect --version
|
|
10
22
|
```
|
|
11
23
|
|
|
@@ -22,6 +34,13 @@ Use `--framework openclaw` for OpenClaw. Restart the existing native gateway whe
|
|
|
22
34
|
|
|
23
35
|
The default state remains `~/.envarpay/connectors/AGENT_UUID` for compatibility. Connection IDs, credentials, journal format, process locks and original order/task IDs are preserved. Run only one receiver for a journal. Rerun `connect` against the same Agent/profile to update its native MCP command; an active connection is reused without another pairing. Keep the original directory when reinstalling or rolling back.
|
|
24
36
|
|
|
37
|
+
Linux receivers require `flock` from util-linux and a filesystem that supports
|
|
38
|
+
file locks. The kernel releases the receiver lock when its process exits, so
|
|
39
|
+
container restarts and reused PIDs cannot strand the connection. When upgrading
|
|
40
|
+
an orphaned legacy PID lock, first stop the old receiver and verify no other
|
|
41
|
+
receiver uses that journal, then move only `receiver.lock` aside once. Preserve
|
|
42
|
+
the SQLite journal, credentials and native binding. Never remove an active lock.
|
|
43
|
+
|
|
25
44
|
EnvarPay retains seller charging and exposes the old `envarpay connector ...` command as a compatibility entry. EnvarConnect does not depend on EnvarPay. Free tasks need no payment setup. Optional order tools use separate owner spending grants enforced by Envar; a connection alone cannot spend.
|
|
26
45
|
|
|
27
46
|
## API and development
|
|
@@ -34,4 +53,14 @@ npm test
|
|
|
34
53
|
npm run version:date
|
|
35
54
|
```
|
|
36
55
|
|
|
56
|
+
If your npm registry is a corporate mirror, check `npm config get registry`.
|
|
57
|
+
The connection command on your Envar Agent page also offers a fixed official
|
|
58
|
+
bundled package with integrity verification; it installs without fetching npm
|
|
59
|
+
dependencies. A mirror returning 404 does not establish that the public package
|
|
60
|
+
is missing.
|
|
61
|
+
|
|
37
62
|
See [native setup](docs/native-connector.md), [migration](docs/migration.md), [release automation](docs/releasing.md), and [contributing](CONTRIBUTING.md). The non-root container is `ghcr.io/envarai/connect`; it contains the Connector, not a native Agent runtime.
|
|
63
|
+
GHCR package access is separate from repository visibility. Anonymous image pulls
|
|
64
|
+
are not currently part of the verified public installation path. Use the npm
|
|
65
|
+
package, the official bundled download, or build the checked-out `Dockerfile`
|
|
66
|
+
locally; a managed image may require an authorized GHCR login.
|
|
@@ -35,8 +35,9 @@ export declare class ConnectorJournal {
|
|
|
35
35
|
private readonly db;
|
|
36
36
|
private lock?;
|
|
37
37
|
constructor(path: string, binding: ConnectorBinding);
|
|
38
|
-
/**
|
|
38
|
+
/** One receiver per journal; Linux kernel locks survive PID reuse safely. */
|
|
39
39
|
acquireProcessLock(path: string): void;
|
|
40
|
+
private acquireKernelLock;
|
|
40
41
|
ingest(delivery: Delivery): void;
|
|
41
42
|
save(record: LocalWork): void;
|
|
42
43
|
get(id: string): LocalWork | undefined;
|
package/dist/connector/state.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/** Owner-only durable journal. Transport receipts never imply a second execution. */
|
|
2
|
-
import { chmodSync, closeSync, existsSync, lstatSync, mkdirSync, openSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { chmodSync, closeSync, constants, existsSync, lstatSync, mkdirSync, openSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { spawnSync } from 'node:child_process';
|
|
3
4
|
import { dirname } from 'node:path';
|
|
4
5
|
import { randomUUID, createHash } from 'node:crypto';
|
|
5
6
|
import { DatabaseSync } from 'node:sqlite';
|
|
@@ -49,10 +50,14 @@ export class ConnectorJournal {
|
|
|
49
50
|
}
|
|
50
51
|
this.db.prepare("INSERT OR IGNORE INTO connector_meta VALUES ('binding',?)").run(JSON.stringify(binding));
|
|
51
52
|
}
|
|
52
|
-
/**
|
|
53
|
+
/** One receiver per journal; Linux kernel locks survive PID reuse safely. */
|
|
53
54
|
acquireProcessLock(path) {
|
|
54
55
|
if (this.lock)
|
|
55
56
|
throw new Error('Connector already owns a process lock');
|
|
57
|
+
if (process.platform === 'linux') {
|
|
58
|
+
this.acquireKernelLock(path);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
56
61
|
if (existsSync(path)) {
|
|
57
62
|
privateFile(path);
|
|
58
63
|
const value = parseStored(readFileSync(path, 'utf8'));
|
|
@@ -77,6 +82,55 @@ export class ConnectorJournal {
|
|
|
77
82
|
}
|
|
78
83
|
this.lock = { path, nonce };
|
|
79
84
|
}
|
|
85
|
+
acquireKernelLock(path) {
|
|
86
|
+
const guard = `${path}.flock`;
|
|
87
|
+
if (existsSync(guard))
|
|
88
|
+
privateFile(guard);
|
|
89
|
+
const fd = openSync(guard, constants.O_CREAT | constants.O_RDWR | constants.O_NOFOLLOW, 0o600);
|
|
90
|
+
try {
|
|
91
|
+
// The child inherits this open file description. Its exit keeps the lock
|
|
92
|
+
// held by our descriptor; process death releases it even after SIGKILL.
|
|
93
|
+
const result = spawnSync('flock', ['--nonblock', '3'], { stdio: ['ignore', 'ignore', 'ignore', fd] });
|
|
94
|
+
if (result.status === 1)
|
|
95
|
+
throw new Error('Another Connector process is using this journal');
|
|
96
|
+
if (result.error || result.status !== 0)
|
|
97
|
+
throw new Error('Linux Connector locking requires util-linux flock and filesystem lock support');
|
|
98
|
+
if (existsSync(path)) {
|
|
99
|
+
privateFile(path);
|
|
100
|
+
const stored = readFileSync(path, 'utf8');
|
|
101
|
+
const value = parseStored(stored);
|
|
102
|
+
if (value.locking !== 'flock') {
|
|
103
|
+
// Do not steal a legacy PID lock during an upgrade. An operator must
|
|
104
|
+
// confirm an orphaned container lock before moving it aside once.
|
|
105
|
+
if (!Number.isSafeInteger(value.pid) || value.pid < 1)
|
|
106
|
+
throw new Error('Inspect the original Connector lock');
|
|
107
|
+
try {
|
|
108
|
+
process.kill(value.pid, 0);
|
|
109
|
+
throw new Error('Another Connector process is using this journal (legacy PID lock)');
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
if (error.code !== 'ESRCH')
|
|
113
|
+
throw error;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
unlinkSync(path);
|
|
117
|
+
}
|
|
118
|
+
const nonce = randomUUID();
|
|
119
|
+
// Exclusive creation also excludes a legacy receiver racing this upgrade.
|
|
120
|
+
const metadata = openSync(path, 'wx', 0o600);
|
|
121
|
+
try {
|
|
122
|
+
writeFileSync(metadata, JSON.stringify({ pid: process.pid, nonce, locking: 'flock' }), { flush: true });
|
|
123
|
+
}
|
|
124
|
+
finally {
|
|
125
|
+
closeSync(metadata);
|
|
126
|
+
}
|
|
127
|
+
this.lock = { path, nonce, fd };
|
|
128
|
+
}
|
|
129
|
+
catch (error) {
|
|
130
|
+
closeSync(fd);
|
|
131
|
+
throw error;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
80
134
|
ingest(delivery) {
|
|
81
135
|
const work = delivery.work;
|
|
82
136
|
const incoming = ['work', 'input', 'cancel'].includes(delivery.kind);
|
|
@@ -176,12 +230,19 @@ export class ConnectorJournal {
|
|
|
176
230
|
sameObservation(record, observation) { return record.lastObservation === digest(observation); }
|
|
177
231
|
close() {
|
|
178
232
|
this.db.close();
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
233
|
+
try {
|
|
234
|
+
if (this.lock && existsSync(this.lock.path)) {
|
|
235
|
+
privateFile(this.lock.path);
|
|
236
|
+
const value = parseStored(readFileSync(this.lock.path, 'utf8'));
|
|
237
|
+
if (value.nonce === this.lock.nonce)
|
|
238
|
+
unlinkSync(this.lock.path);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
finally {
|
|
242
|
+
// The guard inode stays in place while the kernel releases its lock.
|
|
243
|
+
if (this.lock?.fd !== undefined)
|
|
244
|
+
closeSync(this.lock.fd);
|
|
245
|
+
this.lock = undefined;
|
|
184
246
|
}
|
|
185
|
-
this.lock = undefined;
|
|
186
247
|
}
|
|
187
248
|
}
|