@steerable/agent-shell 0.6.55 → 0.6.57
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/cli-install.d.ts +27 -0
- package/dist/cli-install.js +105 -0
- package/dist/goal-store.js +16 -3
- package/dist/host/http-routes.d.ts +8 -0
- package/dist/host/http-routes.js +18 -0
- package/dist/host/pack-cli.d.ts +19 -0
- package/dist/host/pack-cli.js +56 -0
- package/dist/host/runtime.js +24 -4
- package/dist/host/visible-terminal-exec.js +1 -1
- package/dist/insights/flush.js +1 -1
- package/dist/json-store.js +17 -1
- package/dist/local-backend/router.d.ts +3 -0
- package/dist/local-backend/router.js +325 -249
- package/dist/local-backend/skill-install.js +11 -2
- package/dist/local-executor.js +1 -1
- package/dist/log.d.ts +18 -11
- package/dist/log.js +64 -8
- package/dist/scenario/pack.d.ts +1 -1
- package/dist/server/http-server.js +1 -1
- package/dist/server/start.js +1 -3
- package/dist/sidecar/boot.js +1 -1
- package/dist/sidecar/exec-sandbox.js +1 -1
- package/dist/sidecar/reverse-spawn.js +6 -2
- package/dist/storage/driver.d.ts +4 -0
- package/dist/storage/driver.js +4 -0
- package/dist/storage/index.d.ts +8 -3
- package/dist/storage/index.js +59 -20
- package/dist/storage/process-locks.d.ts +32 -0
- package/dist/storage/process-locks.js +99 -0
- package/dist/storage/scoped-store.d.ts +1 -0
- package/dist/storage/sqlite-driver.d.ts +3 -1
- package/dist/storage/sqlite-driver.js +89 -8
- package/dist/storage/write-lease.d.ts +7 -1
- package/dist/storage/write-lease.js +35 -4
- package/dist/terminal-manager.js +1 -1
- package/package.json +4 -5
|
@@ -1,11 +1,14 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
1
2
|
import path from 'node:path';
|
|
2
3
|
import Database from 'better-sqlite3';
|
|
3
4
|
import { getProductConfig } from '../product-config.js';
|
|
4
5
|
import { getUserDataDir } from '../runtime.js';
|
|
5
6
|
import { LOCAL_SCOPE } from './driver.js';
|
|
6
7
|
import { SqliteScopedStore } from './index.js';
|
|
7
|
-
import {
|
|
8
|
+
import { HOST_SCHEMA_VERSION, OPEN_LOCK_WAIT_MS, StorageUpgradeBlockedError, openLockPath, } from './process-locks.js';
|
|
9
|
+
import { acquireSharedLease, acquireWriteLease, lockPathForDb, StoreAlreadyOwnedError, } from './write-lease.js';
|
|
8
10
|
import { acquireWriteLeaseOrExit } from './write-lease-error.js';
|
|
11
|
+
const MAIN_DB_BUSY_TIMEOUT_MS = 5_000;
|
|
9
12
|
function invokeStatement(statement, operation, params) {
|
|
10
13
|
if (Array.isArray(params))
|
|
11
14
|
return statement[operation](...params);
|
|
@@ -71,32 +74,92 @@ class SqlitePackDbAccess {
|
|
|
71
74
|
}
|
|
72
75
|
}
|
|
73
76
|
}
|
|
77
|
+
function readUserVersion(dbPath) {
|
|
78
|
+
if (!existsSync(dbPath))
|
|
79
|
+
return 0;
|
|
80
|
+
const db = new Database(dbPath, { readonly: true, fileMustExist: true, timeout: 5_000 });
|
|
81
|
+
try {
|
|
82
|
+
return Number(db.pragma('user_version', { simple: true }));
|
|
83
|
+
}
|
|
84
|
+
finally {
|
|
85
|
+
db.close();
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
function blockUpgrade(message) {
|
|
89
|
+
console.error(`[bs] storage upgrade blocked: ${message}`);
|
|
90
|
+
queueMicrotask(() => process.exit(75));
|
|
91
|
+
throw new StorageUpgradeBlockedError(message);
|
|
92
|
+
}
|
|
74
93
|
/** Default local SQLite storage driver. */
|
|
75
94
|
export class SqliteStorageDriver {
|
|
76
95
|
db = null;
|
|
77
|
-
|
|
96
|
+
openLease = null;
|
|
97
|
+
changeTimer = null;
|
|
78
98
|
localSqliteStore = null;
|
|
79
99
|
stores = new Map();
|
|
80
100
|
async initialize() {
|
|
81
101
|
if (this.db)
|
|
82
102
|
return;
|
|
83
103
|
const dbPath = path.join(getUserDataDir(), getProductConfig().dbFileName ?? 'agent-shell.db');
|
|
84
|
-
this.
|
|
104
|
+
// A previous build held this lock for the whole process. If it is still
|
|
105
|
+
// held, that process cannot share the database, so refuse to migrate
|
|
106
|
+
// or to open alongside it.
|
|
107
|
+
acquireWriteLeaseOrExit(() => {
|
|
108
|
+
const legacy = acquireWriteLease(lockPathForDb(dbPath));
|
|
109
|
+
legacy.release();
|
|
110
|
+
});
|
|
111
|
+
const gate = openLockPath(dbPath);
|
|
112
|
+
let version = readUserVersion(dbPath);
|
|
113
|
+
if (version > HOST_SCHEMA_VERSION) {
|
|
114
|
+
blockUpgrade(`本地数据库版本 ${version} 高于本程序支持的版本 ${HOST_SCHEMA_VERSION}。请升级后再打开。`);
|
|
115
|
+
}
|
|
116
|
+
let exclusive = null;
|
|
117
|
+
if (version === HOST_SCHEMA_VERSION) {
|
|
118
|
+
// Make sure the open-lock table exists, then register as a shared holder.
|
|
119
|
+
// A live shared holder rejects this exclusive probe immediately.
|
|
120
|
+
try {
|
|
121
|
+
acquireWriteLease(gate).release();
|
|
122
|
+
}
|
|
123
|
+
catch (error) {
|
|
124
|
+
if (!(error instanceof StoreAlreadyOwnedError))
|
|
125
|
+
throw error;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
try {
|
|
130
|
+
exclusive = acquireWriteLease(gate, OPEN_LOCK_WAIT_MS);
|
|
131
|
+
}
|
|
132
|
+
catch (error) {
|
|
133
|
+
if (!(error instanceof StoreAlreadyOwnedError))
|
|
134
|
+
throw error;
|
|
135
|
+
version = readUserVersion(dbPath);
|
|
136
|
+
if (version !== HOST_SCHEMA_VERSION) {
|
|
137
|
+
blockUpgrade('本地数据库需要升级,但仍被另一个进程打开。请先关闭正在运行的应用及其命令行任务后再升级。');
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
85
141
|
let db = null;
|
|
86
142
|
try {
|
|
87
|
-
db = new Database(dbPath);
|
|
143
|
+
db = new Database(dbPath, { timeout: MAIN_DB_BUSY_TIMEOUT_MS });
|
|
144
|
+
db.pragma(`busy_timeout = ${MAIN_DB_BUSY_TIMEOUT_MS}`);
|
|
88
145
|
db.pragma('journal_mode = WAL');
|
|
89
146
|
db.pragma('foreign_keys = ON');
|
|
90
147
|
this.db = db;
|
|
91
148
|
const local = new SqliteScopedStore(db, LOCAL_SCOPE);
|
|
92
149
|
await local.initialize();
|
|
150
|
+
db.pragma(`user_version = ${HOST_SCHEMA_VERSION}`);
|
|
93
151
|
this.localSqliteStore = local;
|
|
94
152
|
this.stores.set(this.scopeKey(LOCAL_SCOPE), local);
|
|
153
|
+
exclusive?.release();
|
|
154
|
+
exclusive = null;
|
|
155
|
+
this.openLease = acquireSharedLease(gate);
|
|
95
156
|
}
|
|
96
157
|
catch (error) {
|
|
158
|
+
exclusive?.release();
|
|
97
159
|
db?.close();
|
|
98
|
-
this.
|
|
99
|
-
this.
|
|
160
|
+
this.db = null;
|
|
161
|
+
this.openLease?.release();
|
|
162
|
+
this.openLease = null;
|
|
100
163
|
throw error;
|
|
101
164
|
}
|
|
102
165
|
}
|
|
@@ -116,13 +179,31 @@ export class SqliteStorageDriver {
|
|
|
116
179
|
async applyPackMigrations() {
|
|
117
180
|
this.localSqliteStore?.applyPackMigrations();
|
|
118
181
|
}
|
|
182
|
+
watchChanges(onChange) {
|
|
183
|
+
const db = this.requireDb();
|
|
184
|
+
if (this.changeTimer)
|
|
185
|
+
return;
|
|
186
|
+
let version = Number(db.pragma('data_version', { simple: true }));
|
|
187
|
+
this.changeTimer = setInterval(() => {
|
|
188
|
+
const next = Number(db.pragma('data_version', { simple: true }));
|
|
189
|
+
if (next === version)
|
|
190
|
+
return;
|
|
191
|
+
version = next;
|
|
192
|
+
onChange();
|
|
193
|
+
}, 1_000);
|
|
194
|
+
this.changeTimer.unref?.();
|
|
195
|
+
}
|
|
119
196
|
async close() {
|
|
197
|
+
if (this.changeTimer) {
|
|
198
|
+
clearInterval(this.changeTimer);
|
|
199
|
+
this.changeTimer = null;
|
|
200
|
+
}
|
|
120
201
|
this.stores.clear();
|
|
121
202
|
this.db?.close();
|
|
122
203
|
this.db = null;
|
|
123
204
|
this.localSqliteStore = null;
|
|
124
|
-
this.
|
|
125
|
-
this.
|
|
205
|
+
this.openLease?.release();
|
|
206
|
+
this.openLease = null;
|
|
126
207
|
}
|
|
127
208
|
requireDb() {
|
|
128
209
|
if (!this.db)
|
|
@@ -23,4 +23,10 @@ export interface HeldWriteLease {
|
|
|
23
23
|
release(): void;
|
|
24
24
|
}
|
|
25
25
|
export declare function lockPathForDb(dbPath: string): string;
|
|
26
|
-
export declare function acquireWriteLease(lockPath: string): HeldWriteLease;
|
|
26
|
+
export declare function acquireWriteLease(lockPath: string, timeoutMs?: number): HeldWriteLease;
|
|
27
|
+
/**
|
|
28
|
+
* Shared open registration. Many processes may hold this at once.
|
|
29
|
+
* An exclusive lease on the same file waits until every shared holder closes.
|
|
30
|
+
* The lock file must already contain the lease table (the exclusive opener creates it).
|
|
31
|
+
*/
|
|
32
|
+
export declare function acquireSharedLease(lockPath: string): HeldWriteLease;
|
|
@@ -36,17 +36,18 @@ export function lockPathForDb(dbPath) {
|
|
|
36
36
|
const parsed = path.parse(path.resolve(dbPath));
|
|
37
37
|
return path.join(parsed.dir, `${parsed.name}.lock`);
|
|
38
38
|
}
|
|
39
|
-
export function acquireWriteLease(lockPath) {
|
|
39
|
+
export function acquireWriteLease(lockPath, timeoutMs = 0) {
|
|
40
|
+
const waitMs = Number.isFinite(timeoutMs) ? Math.max(0, Math.trunc(timeoutMs)) : 0;
|
|
40
41
|
fs.mkdirSync(path.dirname(lockPath), { recursive: true });
|
|
41
42
|
// Loaded lazily so tests that only exercise lockPathForDb do not need
|
|
42
43
|
// better-sqlite3's Electron ABI.
|
|
43
44
|
const Database = require('better-sqlite3');
|
|
44
|
-
const db = new Database(lockPath, { timeout:
|
|
45
|
+
const db = new Database(lockPath, { timeout: waitMs });
|
|
45
46
|
try {
|
|
46
47
|
// Every statement here can hit the incumbent's lock, including the
|
|
47
48
|
// journal_mode pragma — it rewrites the header and so needs the same
|
|
48
|
-
// write lock.
|
|
49
|
-
db.pragma(
|
|
49
|
+
// write lock. timeoutMs 0 makes that immediate instead of a stall.
|
|
50
|
+
db.pragma(`busy_timeout = ${waitMs}`);
|
|
50
51
|
db.pragma('journal_mode = DELETE');
|
|
51
52
|
db.exec('CREATE TABLE IF NOT EXISTS lease (id INTEGER PRIMARY KEY)');
|
|
52
53
|
db.exec('BEGIN EXCLUSIVE');
|
|
@@ -72,3 +73,33 @@ export function acquireWriteLease(lockPath) {
|
|
|
72
73
|
},
|
|
73
74
|
};
|
|
74
75
|
}
|
|
76
|
+
/**
|
|
77
|
+
* Shared open registration. Many processes may hold this at once.
|
|
78
|
+
* An exclusive lease on the same file waits until every shared holder closes.
|
|
79
|
+
* The lock file must already contain the lease table (the exclusive opener creates it).
|
|
80
|
+
*/
|
|
81
|
+
export function acquireSharedLease(lockPath) {
|
|
82
|
+
fs.mkdirSync(path.dirname(lockPath), { recursive: true });
|
|
83
|
+
const Database = require('better-sqlite3');
|
|
84
|
+
const db = new Database(lockPath, { timeout: 5_000 });
|
|
85
|
+
try {
|
|
86
|
+
db.pragma('busy_timeout = 5000');
|
|
87
|
+
db.exec('BEGIN');
|
|
88
|
+
db.prepare('SELECT id FROM lease LIMIT 1').get();
|
|
89
|
+
}
|
|
90
|
+
catch (err) {
|
|
91
|
+
db.close();
|
|
92
|
+
if (isSqliteBusy(err))
|
|
93
|
+
throw new StoreAlreadyOwnedError(lockPath);
|
|
94
|
+
throw err;
|
|
95
|
+
}
|
|
96
|
+
let released = false;
|
|
97
|
+
return {
|
|
98
|
+
release() {
|
|
99
|
+
if (released)
|
|
100
|
+
return;
|
|
101
|
+
released = true;
|
|
102
|
+
db.close();
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
}
|
package/dist/terminal-manager.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { EventEmitter } from 'events';
|
|
2
2
|
import os from 'os';
|
|
3
3
|
import { randomUUID } from 'crypto';
|
|
4
|
-
import log from '
|
|
4
|
+
import { log } from './log.js';
|
|
5
5
|
import * as pty from 'node-pty';
|
|
6
6
|
import { adaptCommandForPowerShell } from './shell-adapt.js';
|
|
7
7
|
const DEFAULT_COLS = 100;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@steerable/agent-shell",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.57",
|
|
4
4
|
"description": "Steerable framework — product-neutral Node HostRuntime and headless HTTP server shared by Tauri desktop and browser deployments; scenario packs extend it through @steerable/pack-sdk.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"homepage": "https://steerableframework.com/",
|
|
@@ -49,12 +49,11 @@
|
|
|
49
49
|
"better-sqlite3": "^11.9.1",
|
|
50
50
|
"conf": "10.2.0",
|
|
51
51
|
"dotenv": "^17.4.2",
|
|
52
|
-
"electron-log": "^5.4.3",
|
|
53
52
|
"he": "^1.2.0",
|
|
54
53
|
"node-pty": "^1.1.0",
|
|
55
|
-
"@steerable/agent-harness": "0.6.
|
|
56
|
-
"@steerable/pack-sdk": "0.6.
|
|
57
|
-
"@steerable/agent-protocol": "0.6.
|
|
54
|
+
"@steerable/agent-harness": "0.6.57",
|
|
55
|
+
"@steerable/pack-sdk": "0.6.57",
|
|
56
|
+
"@steerable/agent-protocol": "0.6.57"
|
|
58
57
|
},
|
|
59
58
|
"devDependencies": {
|
|
60
59
|
"@types/better-sqlite3": "^7.6.13",
|