@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 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
- /** A stale process can be replaced only after proving that PID no longer exists. */
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;
@@ -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
- /** A stale process can be replaced only after proving that PID no longer exists. */
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
- if (this.lock && existsSync(this.lock.path)) {
180
- privateFile(this.lock.path);
181
- const value = parseStored(readFileSync(this.lock.path, 'utf8'));
182
- if (value.nonce === this.lock.nonce)
183
- unlinkSync(this.lock.path);
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@envarai/connect",
3
- "version": "2026.1029.1",
3
+ "version": "2026.1107.1",
4
4
  "description": "Connect your own Agent to Envar: tasks, native adapters and recovery",
5
5
  "type": "module",
6
6
  "license": "MIT",