@securecoms/agent-runtime 0.9.2 → 0.9.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/README.md +1 -1
- package/dist/bin.js +242 -47
- package/package.json +3 -2
- package/skills/sql_query/SKILL.md +7 -1
package/README.md
CHANGED
|
@@ -286,7 +286,7 @@ to a host the operator did not name.
|
|
|
286
286
|
| `fetch_url` | `url` | Fetches a page and returns extracted text (HTML is stripped to text). Up to 3 redirects, 10 s timeout, 2 MB response cap. |
|
|
287
287
|
| `web_search` | `query`, `count` (1–10, default 5) | `requires: [search_provider]` — needs a readable, non-empty `SECURECOMS_BRAVE_KEY_FILE` or `SECURECOMS_TAVILY_KEY_FILE`; Brave is used first when both are set. |
|
|
288
288
|
| `http_request` | `name` (a configured `endpoint:<name>`), `path` (a query string is kept, a `#fragment` is dropped), `method` (GET/POST/PUT/DELETE), `body` | `requires: [any_endpoint]`. The model names an endpoint and a path, never a host; a path that would leave the endpoint's `baseUrl` origin is refused. |
|
|
289
|
-
| `sql_query` | `name` (a configured `database:<name>`), `sql` (read-only `SELECT` only; a `;` or a write keyword anywhere in the text — inside a string literal or a comment included — is refused) | `requires: [any_database]`. SQLite or Postgres (`pg` required separately for Postgres). Results capped at 200 rows. |
|
|
289
|
+
| `sql_query` | `name` (a configured `database:<name>`), `sql` (read-only `SELECT` only; a `;` or a write keyword anywhere in the text — inside a string literal or a comment included — is refused) | `requires: [any_database]`. SQLite or Postgres (`pg` required separately for Postgres). Results capped at 200 rows. Postgres values come back as the server's text (json/jsonb as a JSON string, arrays like `{1,2}`, timestamps, int8 and numeric as strings); rows past a 1 MiB value budget are dropped, and `truncated_reason` (`row_limit` or `size_limit`) says why rows are missing. |
|
|
290
290
|
|
|
291
291
|
## skills.json
|
|
292
292
|
|
package/dist/bin.js
CHANGED
|
@@ -2449,6 +2449,7 @@ var httpRequest = async (args, ctx) => {
|
|
|
2449
2449
|
// src/builtins/sql-query.ts
|
|
2450
2450
|
import { readFileSync as readFileSync5 } from "node:fs";
|
|
2451
2451
|
import { spawn } from "node:child_process";
|
|
2452
|
+
import { createConnection } from "node:net";
|
|
2452
2453
|
|
|
2453
2454
|
// src/sql-guard.ts
|
|
2454
2455
|
var SqlNotReadOnlyError = class extends Error {
|
|
@@ -2641,15 +2642,181 @@ function executeSqliteQuery(path, sql, options = {}) {
|
|
|
2641
2642
|
}));
|
|
2642
2643
|
});
|
|
2643
2644
|
}
|
|
2644
|
-
|
|
2645
|
+
var PG_WIRE_BYTE_CAP = 4 * 1024 * 1024;
|
|
2646
|
+
var PG_ROW_BYTE_BUDGET = SQLITE_IPC_CAP_BYTES;
|
|
2647
|
+
var PG_IDLE_IN_TRANSACTION_MS = 15e3;
|
|
2648
|
+
var PG_CURSOR = "sc_q";
|
|
2649
|
+
var PG_SCALAR_PARSERS = {
|
|
2650
|
+
16: (t) => t === "t",
|
|
2651
|
+
// bool
|
|
2652
|
+
21: Number,
|
|
2653
|
+
// int2
|
|
2654
|
+
23: Number,
|
|
2655
|
+
// int4
|
|
2656
|
+
26: Number,
|
|
2657
|
+
// oid
|
|
2658
|
+
700: Number,
|
|
2659
|
+
// float4
|
|
2660
|
+
701: Number
|
|
2661
|
+
// float8
|
|
2662
|
+
};
|
|
2663
|
+
var PG_TEXT_TYPES = {
|
|
2664
|
+
getTypeParser(oid, format) {
|
|
2665
|
+
if (format === "binary") return (value) => value;
|
|
2666
|
+
return PG_SCALAR_PARSERS[oid] ?? ((value) => value);
|
|
2667
|
+
}
|
|
2668
|
+
};
|
|
2669
|
+
var PgWireByteCapError = class extends Error {
|
|
2670
|
+
constructor() {
|
|
2671
|
+
super("database result exceeded the size limit");
|
|
2672
|
+
this.name = "PgWireByteCapError";
|
|
2673
|
+
}
|
|
2674
|
+
};
|
|
2675
|
+
var PgWireBoundUnavailableError = class extends Error {
|
|
2676
|
+
constructor() {
|
|
2677
|
+
super(
|
|
2678
|
+
'the installed "pg" driver does not expose its socket, so the result size cannot be bounded; the query was not run'
|
|
2679
|
+
);
|
|
2680
|
+
this.name = "PgWireBoundUnavailableError";
|
|
2681
|
+
}
|
|
2682
|
+
};
|
|
2683
|
+
var PgDriverMissingError = class extends Error {
|
|
2684
|
+
};
|
|
2685
|
+
function isPgStream(value) {
|
|
2686
|
+
return typeof value === "object" && value !== null && typeof value.on === "function" && typeof value.destroy === "function";
|
|
2687
|
+
}
|
|
2688
|
+
async function loadInstalledPg() {
|
|
2689
|
+
return import("pg");
|
|
2690
|
+
}
|
|
2691
|
+
function rowValueBytes(row) {
|
|
2692
|
+
let n = 0;
|
|
2693
|
+
for (const v of row) n += typeof v === "string" ? Buffer.byteLength(v) : 8;
|
|
2694
|
+
return n;
|
|
2695
|
+
}
|
|
2696
|
+
function sendCancelRequest(client) {
|
|
2697
|
+
const { processID, secretKey, host, port } = client;
|
|
2698
|
+
if (typeof processID !== "number" || typeof secretKey !== "number") return;
|
|
2699
|
+
if (typeof host !== "string" || typeof port !== "number") return;
|
|
2700
|
+
const packet = Buffer.alloc(16);
|
|
2701
|
+
packet.writeInt32BE(16, 0);
|
|
2702
|
+
packet.writeInt32BE(80877102, 4);
|
|
2703
|
+
packet.writeInt32BE(processID, 8);
|
|
2704
|
+
packet.writeInt32BE(secretKey, 12);
|
|
2705
|
+
const socket = host.startsWith("/") ? createConnection(`${host}/.s.PGSQL.${port}`) : createConnection(port, host);
|
|
2706
|
+
socket.setTimeout(PG_CANCEL_TIMEOUT_MS, () => socket.destroy());
|
|
2707
|
+
socket.on("error", () => socket.destroy());
|
|
2708
|
+
socket.once("connect", () => socket.end(packet));
|
|
2709
|
+
socket.unref();
|
|
2710
|
+
}
|
|
2711
|
+
var PG_CANCEL_TIMEOUT_MS = 5e3;
|
|
2712
|
+
async function executePostgresQuery(connectionString, sql, options = {}) {
|
|
2713
|
+
const timeoutMs = Math.max(1, Math.floor(options.timeoutMs ?? SQL_TIMEOUT_MS));
|
|
2714
|
+
const wireByteCap = options.wireByteCap ?? PG_WIRE_BYTE_CAP;
|
|
2715
|
+
if (options.signal?.aborted) throw new Error("Postgres query cancelled");
|
|
2716
|
+
let pg;
|
|
2717
|
+
try {
|
|
2718
|
+
pg = await (options.loadDriver ?? loadInstalledPg)();
|
|
2719
|
+
} catch {
|
|
2720
|
+
throw new PgDriverMissingError('the optional "pg" module is not installed (npm i -g pg)');
|
|
2721
|
+
}
|
|
2722
|
+
const Client = pg.Client ?? pg.default?.Client;
|
|
2723
|
+
if (!Client) throw new Error(`the installed "pg" module does not export a Client`);
|
|
2724
|
+
const client = new Client({
|
|
2725
|
+
connectionString,
|
|
2726
|
+
// never logged (D4)
|
|
2727
|
+
statement_timeout: timeoutMs,
|
|
2728
|
+
connectionTimeoutMillis: timeoutMs,
|
|
2729
|
+
types: PG_TEXT_TYPES
|
|
2730
|
+
});
|
|
2731
|
+
client.on("error", () => {
|
|
2732
|
+
});
|
|
2733
|
+
let stopped;
|
|
2734
|
+
const stop = (reason) => {
|
|
2735
|
+
if (stopped) return;
|
|
2736
|
+
stopped = reason;
|
|
2737
|
+
sendCancelRequest(client);
|
|
2738
|
+
const stream = client.connection?.stream;
|
|
2739
|
+
if (isPgStream(stream)) stream.destroy();
|
|
2740
|
+
else void client.end().catch(() => {
|
|
2741
|
+
});
|
|
2742
|
+
};
|
|
2743
|
+
const onAbort = () => stop("cancelled");
|
|
2744
|
+
const deadline = setTimeout(() => stop("deadline"), timeoutMs);
|
|
2745
|
+
options.signal?.addEventListener("abort", onAbort, { once: true });
|
|
2746
|
+
const stoppedError = () => {
|
|
2747
|
+
if (stopped === "capped") return new PgWireByteCapError();
|
|
2748
|
+
if (stopped === "deadline") return new Error(`Postgres query timed out after ${timeoutMs}ms`);
|
|
2749
|
+
if (stopped === "cancelled") return new Error("Postgres query cancelled");
|
|
2750
|
+
return void 0;
|
|
2751
|
+
};
|
|
2752
|
+
let began = false;
|
|
2753
|
+
try {
|
|
2754
|
+
try {
|
|
2755
|
+
await client.connect();
|
|
2756
|
+
} catch (error) {
|
|
2757
|
+
throw stoppedError() ?? error;
|
|
2758
|
+
}
|
|
2759
|
+
const early = stoppedError();
|
|
2760
|
+
if (early) throw early;
|
|
2761
|
+
const stream = client.connection?.stream;
|
|
2762
|
+
if (!isPgStream(stream)) throw new PgWireBoundUnavailableError();
|
|
2763
|
+
let wireBytes = 0;
|
|
2764
|
+
stream.on("data", (chunk) => {
|
|
2765
|
+
if (stopped) return;
|
|
2766
|
+
wireBytes += typeof chunk === "string" ? Buffer.byteLength(chunk) : chunk.byteLength;
|
|
2767
|
+
if (wireBytes > wireByteCap) stop("capped");
|
|
2768
|
+
});
|
|
2769
|
+
const run = async (text, rowMode) => {
|
|
2770
|
+
try {
|
|
2771
|
+
const result = await client.query(rowMode ? { text, rowMode } : text);
|
|
2772
|
+
const late = stoppedError();
|
|
2773
|
+
if (late) throw late;
|
|
2774
|
+
return result;
|
|
2775
|
+
} catch (error) {
|
|
2776
|
+
throw stoppedError() ?? error;
|
|
2777
|
+
}
|
|
2778
|
+
};
|
|
2779
|
+
began = true;
|
|
2780
|
+
await run("BEGIN READ ONLY");
|
|
2781
|
+
await run(`SET LOCAL statement_timeout = ${timeoutMs}`);
|
|
2782
|
+
await run(`SET LOCAL idle_in_transaction_session_timeout = ${Math.max(timeoutMs, PG_IDLE_IN_TRANSACTION_MS)}`);
|
|
2783
|
+
await run(`DECLARE ${PG_CURSOR} NO SCROLL CURSOR FOR ${sql}`);
|
|
2784
|
+
const fetched = await run(`FETCH FORWARD ${SQL_ROW_CAP + 1} FROM ${PG_CURSOR}`, "array");
|
|
2785
|
+
const columns = fetched.fields.map((f) => f.name);
|
|
2786
|
+
const all = fetched.rows;
|
|
2787
|
+
let truncatedReason = all.length > SQL_ROW_CAP ? "row_limit" : void 0;
|
|
2788
|
+
const rows = [];
|
|
2789
|
+
let bytes = 0;
|
|
2790
|
+
for (const row of all.slice(0, SQL_ROW_CAP)) {
|
|
2791
|
+
bytes += rowValueBytes(row);
|
|
2792
|
+
if (bytes > PG_ROW_BYTE_BUDGET) {
|
|
2793
|
+
truncatedReason = "size_limit";
|
|
2794
|
+
break;
|
|
2795
|
+
}
|
|
2796
|
+
rows.push(row);
|
|
2797
|
+
}
|
|
2798
|
+
return { columns, rows, truncated: truncatedReason !== void 0, truncatedReason };
|
|
2799
|
+
} finally {
|
|
2800
|
+
options.signal?.removeEventListener("abort", onAbort);
|
|
2801
|
+
if (began && !stopped) await client.query("ROLLBACK").catch(() => {
|
|
2802
|
+
});
|
|
2803
|
+
if (stopped) void client.end().catch(() => {
|
|
2804
|
+
});
|
|
2805
|
+
else await client.end().catch(() => {
|
|
2806
|
+
});
|
|
2807
|
+
clearTimeout(deadline);
|
|
2808
|
+
}
|
|
2809
|
+
}
|
|
2810
|
+
function envelope(columns, rows, truncated, reason) {
|
|
2645
2811
|
let kept = rows;
|
|
2646
|
-
let
|
|
2812
|
+
let why = truncated ? reason ?? "row_limit" : void 0;
|
|
2813
|
+
const render = () => JSON.stringify(why ? { columns, rows: kept, truncated: true, truncated_reason: why } : { columns, rows: kept, truncated: false });
|
|
2647
2814
|
try {
|
|
2648
|
-
let out =
|
|
2815
|
+
let out = render();
|
|
2649
2816
|
while (resultBytes(out) > RESULT_CAP && kept.length > 0) {
|
|
2650
2817
|
kept = kept.slice(0, Math.floor(kept.length / 2));
|
|
2651
|
-
|
|
2652
|
-
out =
|
|
2818
|
+
why = "size_limit";
|
|
2819
|
+
out = render();
|
|
2653
2820
|
}
|
|
2654
2821
|
if (resultBytes(out) <= RESULT_CAP) return out;
|
|
2655
2822
|
} catch {
|
|
@@ -2674,46 +2841,21 @@ var sqlQuery = async (args, ctx) => {
|
|
|
2674
2841
|
try {
|
|
2675
2842
|
if ("sqlite" in db) {
|
|
2676
2843
|
const result = await executeSqliteQuery(db.sqlite, sql, { signal: ctx.signal });
|
|
2677
|
-
return envelope(result.columns, result.rows, result.truncated);
|
|
2844
|
+
return envelope(result.columns, result.rows, result.truncated, result.truncatedReason);
|
|
2678
2845
|
}
|
|
2679
|
-
let pg;
|
|
2680
|
-
try {
|
|
2681
|
-
pg = await import("pg");
|
|
2682
|
-
} catch {
|
|
2683
|
-
return toolError(`database "${name}" is Postgres, but the optional "pg" module is not installed (npm i -g pg)`);
|
|
2684
|
-
}
|
|
2685
|
-
const Client = pg.Client ?? pg.default?.Client;
|
|
2686
|
-
if (!Client) return toolError(`the installed "pg" module does not export a Client`);
|
|
2687
|
-
const client = new Client({
|
|
2688
|
-
connectionString: readFileSync5(db.postgresUrlFile, "utf8").trim(),
|
|
2689
|
-
// never logged (D4)
|
|
2690
|
-
statement_timeout: SQL_TIMEOUT_MS,
|
|
2691
|
-
connectionTimeoutMillis: SQL_TIMEOUT_MS,
|
|
2692
|
-
query_timeout: SQL_TIMEOUT_MS
|
|
2693
|
-
});
|
|
2694
|
-
let cancelled = ctx.signal?.aborted ?? false;
|
|
2695
|
-
const cancel = () => {
|
|
2696
|
-
cancelled = true;
|
|
2697
|
-
void client.end().catch(() => {
|
|
2698
|
-
});
|
|
2699
|
-
};
|
|
2700
|
-
if (cancelled) throw new Error("Postgres query cancelled");
|
|
2701
|
-
ctx.signal?.addEventListener("abort", cancel, { once: true });
|
|
2702
2846
|
try {
|
|
2703
|
-
await
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
await client.end().catch(() => {
|
|
2716
|
-
});
|
|
2847
|
+
const result = await executePostgresQuery(
|
|
2848
|
+
readFileSync5(db.postgresUrlFile, "utf8").trim(),
|
|
2849
|
+
// never logged (D4)
|
|
2850
|
+
sql,
|
|
2851
|
+
{ signal: ctx.signal }
|
|
2852
|
+
);
|
|
2853
|
+
return envelope(result.columns, result.rows, result.truncated, result.truncatedReason);
|
|
2854
|
+
} catch (e) {
|
|
2855
|
+
if (e instanceof PgDriverMissingError) {
|
|
2856
|
+
return toolError(`database "${name}" is Postgres, but ${e.message}`);
|
|
2857
|
+
}
|
|
2858
|
+
throw e;
|
|
2717
2859
|
}
|
|
2718
2860
|
} catch (e) {
|
|
2719
2861
|
return toolError(e instanceof Error ? e.message : String(e));
|
|
@@ -3340,6 +3482,49 @@ import { readFileSync as readFileSync7 } from "node:fs";
|
|
|
3340
3482
|
import { WebSocket } from "node:http";
|
|
3341
3483
|
import { homedir as homedir3 } from "node:os";
|
|
3342
3484
|
import { join as join5 } from "node:path";
|
|
3485
|
+
|
|
3486
|
+
// src/status-reporter.ts
|
|
3487
|
+
function createStatusReporter(log, fetchImpl = fetch) {
|
|
3488
|
+
const channels = /* @__PURE__ */ new Map();
|
|
3489
|
+
async function post(endpoint, channelId, state) {
|
|
3490
|
+
try {
|
|
3491
|
+
const res = await fetchImpl(`${endpoint.httpBase}/v1/status`, {
|
|
3492
|
+
method: "POST",
|
|
3493
|
+
headers: { "content-type": "application/json", authorization: `Bearer ${endpoint.token}` },
|
|
3494
|
+
body: JSON.stringify({ channel_id: channelId, state })
|
|
3495
|
+
});
|
|
3496
|
+
if (!res.ok) log("warn", "transport.status_failed", { state: logLabel(state), http_status: res.status });
|
|
3497
|
+
await res.body?.cancel();
|
|
3498
|
+
} catch (e) {
|
|
3499
|
+
log("warn", "transport.status_failed", { state: logLabel(state) }, e);
|
|
3500
|
+
}
|
|
3501
|
+
}
|
|
3502
|
+
async function drain(channelId, entry) {
|
|
3503
|
+
while (entry.desired !== entry.sent) {
|
|
3504
|
+
const state = entry.desired;
|
|
3505
|
+
entry.sent = state;
|
|
3506
|
+
await post(entry.endpoint, channelId, state);
|
|
3507
|
+
}
|
|
3508
|
+
entry.inFlight = false;
|
|
3509
|
+
if (channels.get(channelId) === entry) channels.delete(channelId);
|
|
3510
|
+
}
|
|
3511
|
+
return {
|
|
3512
|
+
set(endpoint, channelId, state) {
|
|
3513
|
+
let entry = channels.get(channelId);
|
|
3514
|
+
if (!entry) {
|
|
3515
|
+
entry = { desired: state, endpoint, sent: null, inFlight: false };
|
|
3516
|
+
channels.set(channelId, entry);
|
|
3517
|
+
}
|
|
3518
|
+
entry.desired = state;
|
|
3519
|
+
entry.endpoint = endpoint;
|
|
3520
|
+
if (entry.inFlight) return;
|
|
3521
|
+
entry.inFlight = true;
|
|
3522
|
+
void drain(channelId, entry);
|
|
3523
|
+
}
|
|
3524
|
+
};
|
|
3525
|
+
}
|
|
3526
|
+
|
|
3527
|
+
// src/data-plane.ts
|
|
3343
3528
|
var NO_TURN_MEMORY = Object.freeze({
|
|
3344
3529
|
recent: Object.freeze([]),
|
|
3345
3530
|
facts: Object.freeze([])
|
|
@@ -3473,6 +3658,8 @@ var SecureComsDataPlane = class {
|
|
|
3473
3658
|
seq = 0;
|
|
3474
3659
|
/** in_reply_to → timing of a reply handed to the daemon, until its `sent` frame. */
|
|
3475
3660
|
pendingReplies = /* @__PURE__ */ new Map();
|
|
3661
|
+
/** #910: working/idle for the web "thinking" strip; fire-and-forget, per-channel ordered. */
|
|
3662
|
+
status;
|
|
3476
3663
|
constructor(cfg, onMessage) {
|
|
3477
3664
|
this.identityId = cfg.identityId;
|
|
3478
3665
|
const dir = cfg.connectionsDir ?? join5(homedir3(), ".securecoms", "connections");
|
|
@@ -3492,6 +3679,7 @@ var SecureComsDataPlane = class {
|
|
|
3492
3679
|
this.onMessage = onMessage;
|
|
3493
3680
|
this.noticeReplies = cfg.noticeReplies ?? /* @__PURE__ */ new Set();
|
|
3494
3681
|
this.clock = cfg.clock ?? (() => performance.now());
|
|
3682
|
+
this.status = createStatusReporter(this.log);
|
|
3495
3683
|
}
|
|
3496
3684
|
elapsed(from, to = this.clock()) {
|
|
3497
3685
|
return Math.max(0, Math.round(to - from));
|
|
@@ -3759,6 +3947,7 @@ var SecureComsDataPlane = class {
|
|
|
3759
3947
|
let handlerMs = 0;
|
|
3760
3948
|
let sendMs = 0;
|
|
3761
3949
|
let outcome = "failed";
|
|
3950
|
+
let reportedWorking = false;
|
|
3762
3951
|
try {
|
|
3763
3952
|
if (!this.seen.has(messageId)) {
|
|
3764
3953
|
const receiptStart = this.clock();
|
|
@@ -3785,9 +3974,14 @@ var SecureComsDataPlane = class {
|
|
|
3785
3974
|
this.turns.set(ws, socketTurns);
|
|
3786
3975
|
}
|
|
3787
3976
|
socketTurns.add(turnController);
|
|
3977
|
+
const text = String(frame.text ?? "");
|
|
3978
|
+
if (text.trim()) {
|
|
3979
|
+
reportedWorking = true;
|
|
3980
|
+
this.status.set({ httpBase, token }, channelId, "working");
|
|
3981
|
+
}
|
|
3788
3982
|
const handlerStart = this.clock();
|
|
3789
3983
|
const reply = await this.onMessage({
|
|
3790
|
-
text
|
|
3984
|
+
text,
|
|
3791
3985
|
senderId: String(frame.sender_id ?? ""),
|
|
3792
3986
|
channelId,
|
|
3793
3987
|
messageId,
|
|
@@ -3797,14 +3991,14 @@ var SecureComsDataPlane = class {
|
|
|
3797
3991
|
}, turnController.signal);
|
|
3798
3992
|
handlerMs = this.elapsed(handlerStart);
|
|
3799
3993
|
if (reply != null) {
|
|
3800
|
-
const
|
|
3994
|
+
const text2 = String(reply);
|
|
3801
3995
|
const sendStart = this.clock();
|
|
3802
3996
|
ws.send(JSON.stringify({
|
|
3803
3997
|
type: "send",
|
|
3804
3998
|
channel_id: channelId,
|
|
3805
|
-
text,
|
|
3999
|
+
text: text2,
|
|
3806
4000
|
in_reply_to: messageId,
|
|
3807
|
-
...this.noticeReplies.has(
|
|
4001
|
+
...this.noticeReplies.has(text2) ? { notice: true } : {}
|
|
3808
4002
|
}));
|
|
3809
4003
|
const sentAt = this.clock();
|
|
3810
4004
|
sendMs = this.elapsed(sendStart, sentAt);
|
|
@@ -3823,6 +4017,7 @@ var SecureComsDataPlane = class {
|
|
|
3823
4017
|
this.log("error", "transport.handler_failed", {}, e);
|
|
3824
4018
|
this.failTurn(ws, messageId, "turn_failed");
|
|
3825
4019
|
} finally {
|
|
4020
|
+
if (reportedWorking) this.status.set({ httpBase, token }, channelId, "idle");
|
|
3826
4021
|
if (turnController) {
|
|
3827
4022
|
const socketTurns = this.turns.get(ws);
|
|
3828
4023
|
socketTurns?.delete(turnController);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@securecoms/agent-runtime",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"securecoms-agent": "dist/bin.js"
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {},
|
|
34
34
|
"peerDependencies": {
|
|
35
|
-
"pg": ">=8"
|
|
35
|
+
"pg": ">=8.11 <9"
|
|
36
36
|
},
|
|
37
37
|
"peerDependenciesMeta": {
|
|
38
38
|
"pg": {
|
|
@@ -42,6 +42,7 @@
|
|
|
42
42
|
"devDependencies": {
|
|
43
43
|
"@types/ws": "^8.5.12",
|
|
44
44
|
"esbuild": "0.24.0",
|
|
45
|
+
"pg": "~8.23.0",
|
|
45
46
|
"typescript": "^5.4.0",
|
|
46
47
|
"vitest": "^3.2.6",
|
|
47
48
|
"ws": "^8.21.0"
|
|
@@ -18,4 +18,10 @@ database the operator configured; never invent a connection string. A semicolon
|
|
|
18
18
|
or a write keyword inside a string literal or a comment is also refused, so
|
|
19
19
|
rewrite such a value out of the query (a bound-looking literal like '--' or
|
|
20
20
|
'DROP' will not pass). If a query is refused, rewrite it as a SELECT rather than
|
|
21
|
-
retrying the same statement.
|
|
21
|
+
retrying the same statement. Values come back as the database's text: a
|
|
22
|
+
Postgres json or jsonb value is a JSON string, arrays look like {1,2}, and
|
|
23
|
+
timestamps, int8 and numeric are strings. When `truncated` is true,
|
|
24
|
+
`truncated_reason` says why: `row_limit` means more rows exist than were
|
|
25
|
+
returned, and `size_limit` means rows were dropped because they were too large
|
|
26
|
+
(a single row over 1 MiB comes back as no rows with `size_limit`, not as an
|
|
27
|
+
empty result). Select fewer or narrower columns rather than retrying as is.
|