@poncho-ai/harness 0.46.0 → 0.47.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/.turbo/turbo-build.log +5 -5
- package/CHANGELOG.md +58 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +56 -8
- package/package.json +1 -1
- package/src/harness.ts +7 -6
- package/src/storage/postgres-engine.ts +55 -3
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
> @poncho-ai/harness@0.
|
|
2
|
+
> @poncho-ai/harness@0.47.1 build /home/runner/work/poncho-ai/poncho-ai/packages/harness
|
|
3
3
|
> node scripts/embed-docs.js && tsup src/index.ts --format esm --dts
|
|
4
4
|
|
|
5
5
|
[embed-docs] Generated poncho-docs.ts with 4 topics
|
|
@@ -8,9 +8,9 @@
|
|
|
8
8
|
[34mCLI[39m tsup v8.5.1
|
|
9
9
|
[34mCLI[39m Target: es2022
|
|
10
10
|
[34mESM[39m Build start
|
|
11
|
-
[32mESM[39m [1mdist/index.js [22m[
|
|
11
|
+
[32mESM[39m [1mdist/index.js [22m[32m527.60 KB[39m
|
|
12
12
|
[32mESM[39m [1mdist/isolate-VY35DGLM.js [22m[32m49.43 KB[39m
|
|
13
|
-
[32mESM[39m ⚡️ Build success in
|
|
13
|
+
[32mESM[39m ⚡️ Build success in 275ms
|
|
14
14
|
[34mDTS[39m Build start
|
|
15
|
-
[32mDTS[39m ⚡️ Build success in
|
|
16
|
-
[32mDTS[39m [1mdist/index.d.ts [22m[
|
|
15
|
+
[32mDTS[39m ⚡️ Build success in 7513ms
|
|
16
|
+
[32mDTS[39m [1mdist/index.d.ts [22m[32m86.25 KB[39m
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,63 @@
|
|
|
1
1
|
# @poncho-ai/harness
|
|
2
2
|
|
|
3
|
+
## 0.47.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#122](https://github.com/cesr/poncho-ai/pull/122) [`661536b`](https://github.com/cesr/poncho-ai/commit/661536b8d24691d91dc01e345b828ef6c9884beb) Thanks [@cesr](https://github.com/cesr)! - harness: postgres connection-pool resilience for managed-postgres hosts
|
|
8
|
+
|
|
9
|
+
Managed Postgres providers (Railway, Neon, Heroku, etc.) drop idle
|
|
10
|
+
TCP connections server-side after a few minutes. The previous
|
|
11
|
+
postgres-engine config left `idle_timeout` at the porsager/postgres
|
|
12
|
+
default (0 = never close client-side), so the pool accumulated stale
|
|
13
|
+
sockets; the first query on one rejected with `write CONNECTION_ENDED
|
|
14
|
+
<host>:5432` at `durMs=0` and bubbled up as a hard failure to the
|
|
15
|
+
caller — including user-facing chat turns and the orchestrator's
|
|
16
|
+
subagent callback rerun.
|
|
17
|
+
|
|
18
|
+
Two complementary settings, plus one belt-and-suspenders retry:
|
|
19
|
+
- `idle_timeout: 20` — close idle client-side connections before
|
|
20
|
+
any reasonable provider-side timer fires. Fresh connection on
|
|
21
|
+
next checkout, no stale-socket race.
|
|
22
|
+
- `max_lifetime: 60 * 10` (10 min) — recycle long-lived
|
|
23
|
+
connections defensively, sidestepping provider-side
|
|
24
|
+
"max connection age" limits.
|
|
25
|
+
- `private query()` now retries once on `CONNECTION_ENDED` /
|
|
26
|
+
`CONNECTION_CLOSED` / `CONNECTION_DESTROYED`. Covers the
|
|
27
|
+
narrow race where a query lands on a connection at the exact
|
|
28
|
+
instant the provider drops it.
|
|
29
|
+
|
|
30
|
+
Defaults unchanged: `max: 10`, `connect_timeout: 30`. Migration DDL
|
|
31
|
+
(`sql.unsafe(sql)` inside `executeRaw`) and transactions
|
|
32
|
+
(`sql.begin(...)`) deliberately don't go through the retry — DDL
|
|
33
|
+
is `IF NOT EXISTS` idempotent and transactions need atomic scoping.
|
|
34
|
+
|
|
35
|
+
Observed in production: the PonchOS api running on Railway hit this
|
|
36
|
+
during a subagent test, the orchestrator's auto-callback rerun
|
|
37
|
+
threw the connection-ended error, a concurrent unhandled async
|
|
38
|
+
rejection killed the node process, and Railway restarted the
|
|
39
|
+
replica (~50s). User-facing chat turns started seeing the same
|
|
40
|
+
error after that. Patch eliminates the source.
|
|
41
|
+
|
|
42
|
+
## 0.47.0
|
|
43
|
+
|
|
44
|
+
### Minor Changes
|
|
45
|
+
|
|
46
|
+
- [#120](https://github.com/cesr/poncho-ai/pull/120) [`6cda4ab`](https://github.com/cesr/poncho-ai/commit/6cda4ab39865d89590f42927e281c5fb58cc99f4) Thanks [@cesr](https://github.com/cesr)! - harness: always inject the current hour into the system prompt
|
|
47
|
+
|
|
48
|
+
The dynamic system-prompt builder now emits
|
|
49
|
+
`Current UTC time (hour precision): Mon 2026-05-20T09Z` on every run,
|
|
50
|
+
not just when a `reminderStore` is configured. Knowing "what day is it"
|
|
51
|
+
is universally useful — drafting messages, computing relative dates,
|
|
52
|
+
deciding whether a stale memory still applies — and isn't specific to
|
|
53
|
+
reminder-firing logic.
|
|
54
|
+
|
|
55
|
+
Format also drops the zeroed-out minutes/seconds tail (`T09:00:00.000Z`
|
|
56
|
+
→ `T09Z`) so the hour quantization is visible to the model rather than
|
|
57
|
+
hidden behind noise. The prompt-cache properties are unchanged: the
|
|
58
|
+
string is still hour-stable and lives in the dynamic prompt section, so
|
|
59
|
+
hourly rollovers don't bust the static cache breakpoint.
|
|
60
|
+
|
|
3
61
|
## 0.46.0
|
|
4
62
|
|
|
5
63
|
### Minor Changes
|
package/dist/index.d.ts
CHANGED
|
@@ -1714,6 +1714,25 @@ declare class PostgresEngine extends SqlStorageEngine {
|
|
|
1714
1714
|
refreshPathCache(tenantId: string): Promise<void>;
|
|
1715
1715
|
private patchVfs;
|
|
1716
1716
|
private query;
|
|
1717
|
+
/**
|
|
1718
|
+
* Single retry on a transient connection-layer failure. The
|
|
1719
|
+
* `idle_timeout` / `max_lifetime` config above prevents *most*
|
|
1720
|
+
* stale-connection cases, but a query can still race a
|
|
1721
|
+
* provider-initiated drop in flight — the postgres.js client
|
|
1722
|
+
* rejects with `code: "CONNECTION_ENDED"` and the next attempt
|
|
1723
|
+
* checks out a fresh connection from the pool. One retry is
|
|
1724
|
+
* enough; if it fails again the host-side network is genuinely
|
|
1725
|
+
* broken and the caller should see the error.
|
|
1726
|
+
*
|
|
1727
|
+
* Only retries reads + the standard exec/run paths in `query`;
|
|
1728
|
+
* `sql.unsafe(sql)` calls in `executeRaw` (migration DDL) and
|
|
1729
|
+
* `sql.begin(...)` transactions are unwrapped — those are
|
|
1730
|
+
* idempotent-by-construction (DDL is `IF NOT EXISTS`) or
|
|
1731
|
+
* atomically scoped (transactions roll back cleanly), and adding
|
|
1732
|
+
* a retry around them would complicate the transaction
|
|
1733
|
+
* semantics.
|
|
1734
|
+
*/
|
|
1735
|
+
private runWithRetry;
|
|
1717
1736
|
private addToPathCache;
|
|
1718
1737
|
private removeFromPathCache;
|
|
1719
1738
|
}
|
package/dist/index.js
CHANGED
|
@@ -4433,7 +4433,28 @@ var PostgresEngine = class extends SqlStorageEngine {
|
|
|
4433
4433
|
this.sql = postgres(url, {
|
|
4434
4434
|
onnotice: () => {
|
|
4435
4435
|
},
|
|
4436
|
-
prepare: false
|
|
4436
|
+
prepare: false,
|
|
4437
|
+
// Connection-pool resilience. Managed Postgres providers
|
|
4438
|
+
// (Railway, Neon, Heroku, etc.) routinely drop idle TCP
|
|
4439
|
+
// connections server-side after a few minutes. Without these
|
|
4440
|
+
// knobs, porsager/postgres keeps stale sockets in the pool;
|
|
4441
|
+
// the next query on one rejects with
|
|
4442
|
+
// `write CONNECTION_ENDED <host>:5432` at `durMs=0`, surfacing
|
|
4443
|
+
// as a hard failure to the caller. Two complementary settings:
|
|
4444
|
+
//
|
|
4445
|
+
// - `idle_timeout: 20` closes idle connections client-side
|
|
4446
|
+
// after 20s, before any reasonable provider-side timer
|
|
4447
|
+
// fires. Fresh connection on next checkout = no stale
|
|
4448
|
+
// socket race.
|
|
4449
|
+
// - `max_lifetime: 600` (10 min) recycles long-lived
|
|
4450
|
+
// connections defensively even if they've stayed busy,
|
|
4451
|
+
// which sidesteps a separate class of provider-side
|
|
4452
|
+
// "max connection age" limits.
|
|
4453
|
+
//
|
|
4454
|
+
// Defaults remain `max: 10`, `connect_timeout: 30` — leaving
|
|
4455
|
+
// pool size + initial connect behavior unchanged.
|
|
4456
|
+
idle_timeout: 20,
|
|
4457
|
+
max_lifetime: 60 * 10
|
|
4437
4458
|
});
|
|
4438
4459
|
}
|
|
4439
4460
|
async initialize() {
|
|
@@ -4477,10 +4498,38 @@ var PostgresEngine = class extends SqlStorageEngine {
|
|
|
4477
4498
|
};
|
|
4478
4499
|
}
|
|
4479
4500
|
async query(sql, params) {
|
|
4480
|
-
|
|
4481
|
-
|
|
4501
|
+
return this.runWithRetry(
|
|
4502
|
+
() => !params || params.length === 0 ? this.sql.unsafe(sql) : this.sql.unsafe(sql, params)
|
|
4503
|
+
);
|
|
4504
|
+
}
|
|
4505
|
+
/**
|
|
4506
|
+
* Single retry on a transient connection-layer failure. The
|
|
4507
|
+
* `idle_timeout` / `max_lifetime` config above prevents *most*
|
|
4508
|
+
* stale-connection cases, but a query can still race a
|
|
4509
|
+
* provider-initiated drop in flight — the postgres.js client
|
|
4510
|
+
* rejects with `code: "CONNECTION_ENDED"` and the next attempt
|
|
4511
|
+
* checks out a fresh connection from the pool. One retry is
|
|
4512
|
+
* enough; if it fails again the host-side network is genuinely
|
|
4513
|
+
* broken and the caller should see the error.
|
|
4514
|
+
*
|
|
4515
|
+
* Only retries reads + the standard exec/run paths in `query`;
|
|
4516
|
+
* `sql.unsafe(sql)` calls in `executeRaw` (migration DDL) and
|
|
4517
|
+
* `sql.begin(...)` transactions are unwrapped — those are
|
|
4518
|
+
* idempotent-by-construction (DDL is `IF NOT EXISTS`) or
|
|
4519
|
+
* atomically scoped (transactions roll back cleanly), and adding
|
|
4520
|
+
* a retry around them would complicate the transaction
|
|
4521
|
+
* semantics.
|
|
4522
|
+
*/
|
|
4523
|
+
async runWithRetry(fn) {
|
|
4524
|
+
try {
|
|
4525
|
+
return await fn();
|
|
4526
|
+
} catch (err) {
|
|
4527
|
+
const code = err?.code;
|
|
4528
|
+
if (code === "CONNECTION_ENDED" || code === "CONNECTION_CLOSED" || code === "CONNECTION_DESTROYED") {
|
|
4529
|
+
return await fn();
|
|
4530
|
+
}
|
|
4531
|
+
throw err;
|
|
4482
4532
|
}
|
|
4483
|
-
return this.sql.unsafe(sql, params);
|
|
4484
4533
|
}
|
|
4485
4534
|
addToPathCache(tenantId, path) {
|
|
4486
4535
|
const paths = this.pathCache.get(tenantId);
|
|
@@ -10233,13 +10282,12 @@ Code is wrapped in an async IIFE \u2014 use \`return\` to return a value to the
|
|
|
10233
10282
|
${skillContextWindow}${browserContext}${fsContext}${isolateContext}` : `${agentPrompt}${developmentContext}${browserContext}${fsContext}${isolateContext}`;
|
|
10234
10283
|
const hourlyTime = (() => {
|
|
10235
10284
|
const d = /* @__PURE__ */ new Date();
|
|
10236
|
-
d.setUTCMinutes(0, 0, 0);
|
|
10237
10285
|
const weekday = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"][d.getUTCDay()];
|
|
10238
|
-
return `${weekday} ${d.toISOString()}`;
|
|
10286
|
+
return `${weekday} ${d.toISOString().slice(0, 13)}Z`;
|
|
10239
10287
|
})();
|
|
10240
|
-
const timeContext =
|
|
10288
|
+
const timeContext = `
|
|
10241
10289
|
|
|
10242
|
-
Current UTC time (hour precision): ${hourlyTime}
|
|
10290
|
+
Current UTC time (hour precision): ${hourlyTime}`;
|
|
10243
10291
|
const dynamicPart = `${memoryContext}${todoContext}${timeContext}`;
|
|
10244
10292
|
return { staticPart, dynamicPart };
|
|
10245
10293
|
};
|
package/package.json
CHANGED
package/src/harness.ts
CHANGED
|
@@ -2233,16 +2233,17 @@ Code is wrapped in an async IIFE — use \`return\` to return a value to the too
|
|
|
2233
2233
|
// Quantize to the hour so the system prompt is stable across runs
|
|
2234
2234
|
// within the same hour. Including a per-millisecond timestamp would
|
|
2235
2235
|
// invalidate the prompt cache on every run, since the system prompt
|
|
2236
|
-
// is the first block the cache tries to match.
|
|
2236
|
+
// is the first block the cache tries to match. Format is
|
|
2237
|
+
// `Weekday YYYY-MM-DDTHHZ` — minutes/seconds dropped to make the
|
|
2238
|
+
// hour-quantization visible to the model rather than hidden behind
|
|
2239
|
+
// a zeroed-out tail. Always emitted: every agent needs to know
|
|
2240
|
+
// "what day is it" even without reminders configured.
|
|
2237
2241
|
const hourlyTime = (() => {
|
|
2238
2242
|
const d = new Date();
|
|
2239
|
-
d.setUTCMinutes(0, 0, 0);
|
|
2240
2243
|
const weekday = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"][d.getUTCDay()];
|
|
2241
|
-
return `${weekday} ${d.toISOString()}`;
|
|
2244
|
+
return `${weekday} ${d.toISOString().slice(0, 13)}Z`;
|
|
2242
2245
|
})();
|
|
2243
|
-
const timeContext =
|
|
2244
|
-
? `\n\nCurrent UTC time (hour precision): ${hourlyTime}`
|
|
2245
|
-
: "";
|
|
2246
|
+
const timeContext = `\n\nCurrent UTC time (hour precision): ${hourlyTime}`;
|
|
2246
2247
|
const dynamicPart = `${memoryContext}${todoContext}${timeContext}`;
|
|
2247
2248
|
return { staticPart, dynamicPart };
|
|
2248
2249
|
};
|
|
@@ -57,6 +57,27 @@ export class PostgresEngine extends SqlStorageEngine {
|
|
|
57
57
|
this.sql = postgres(url, {
|
|
58
58
|
onnotice: () => {},
|
|
59
59
|
prepare: false,
|
|
60
|
+
// Connection-pool resilience. Managed Postgres providers
|
|
61
|
+
// (Railway, Neon, Heroku, etc.) routinely drop idle TCP
|
|
62
|
+
// connections server-side after a few minutes. Without these
|
|
63
|
+
// knobs, porsager/postgres keeps stale sockets in the pool;
|
|
64
|
+
// the next query on one rejects with
|
|
65
|
+
// `write CONNECTION_ENDED <host>:5432` at `durMs=0`, surfacing
|
|
66
|
+
// as a hard failure to the caller. Two complementary settings:
|
|
67
|
+
//
|
|
68
|
+
// - `idle_timeout: 20` closes idle connections client-side
|
|
69
|
+
// after 20s, before any reasonable provider-side timer
|
|
70
|
+
// fires. Fresh connection on next checkout = no stale
|
|
71
|
+
// socket race.
|
|
72
|
+
// - `max_lifetime: 600` (10 min) recycles long-lived
|
|
73
|
+
// connections defensively even if they've stayed busy,
|
|
74
|
+
// which sidesteps a separate class of provider-side
|
|
75
|
+
// "max connection age" limits.
|
|
76
|
+
//
|
|
77
|
+
// Defaults remain `max: 10`, `connect_timeout: 30` — leaving
|
|
78
|
+
// pool size + initial connect behavior unchanged.
|
|
79
|
+
idle_timeout: 20,
|
|
80
|
+
max_lifetime: 60 * 10,
|
|
60
81
|
});
|
|
61
82
|
}
|
|
62
83
|
|
|
@@ -118,10 +139,41 @@ export class PostgresEngine extends SqlStorageEngine {
|
|
|
118
139
|
}
|
|
119
140
|
|
|
120
141
|
private async query(sql: string, params?: unknown[]): Promise<any[]> {
|
|
121
|
-
|
|
122
|
-
|
|
142
|
+
return this.runWithRetry(() =>
|
|
143
|
+
!params || params.length === 0
|
|
144
|
+
? this.sql.unsafe(sql)
|
|
145
|
+
: this.sql.unsafe(sql, params),
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Single retry on a transient connection-layer failure. The
|
|
151
|
+
* `idle_timeout` / `max_lifetime` config above prevents *most*
|
|
152
|
+
* stale-connection cases, but a query can still race a
|
|
153
|
+
* provider-initiated drop in flight — the postgres.js client
|
|
154
|
+
* rejects with `code: "CONNECTION_ENDED"` and the next attempt
|
|
155
|
+
* checks out a fresh connection from the pool. One retry is
|
|
156
|
+
* enough; if it fails again the host-side network is genuinely
|
|
157
|
+
* broken and the caller should see the error.
|
|
158
|
+
*
|
|
159
|
+
* Only retries reads + the standard exec/run paths in `query`;
|
|
160
|
+
* `sql.unsafe(sql)` calls in `executeRaw` (migration DDL) and
|
|
161
|
+
* `sql.begin(...)` transactions are unwrapped — those are
|
|
162
|
+
* idempotent-by-construction (DDL is `IF NOT EXISTS`) or
|
|
163
|
+
* atomically scoped (transactions roll back cleanly), and adding
|
|
164
|
+
* a retry around them would complicate the transaction
|
|
165
|
+
* semantics.
|
|
166
|
+
*/
|
|
167
|
+
private async runWithRetry<T>(fn: () => Promise<T>): Promise<T> {
|
|
168
|
+
try {
|
|
169
|
+
return await fn();
|
|
170
|
+
} catch (err) {
|
|
171
|
+
const code = (err as { code?: string } | null | undefined)?.code;
|
|
172
|
+
if (code === "CONNECTION_ENDED" || code === "CONNECTION_CLOSED" || code === "CONNECTION_DESTROYED") {
|
|
173
|
+
return await fn();
|
|
174
|
+
}
|
|
175
|
+
throw err;
|
|
123
176
|
}
|
|
124
|
-
return this.sql.unsafe(sql, params);
|
|
125
177
|
}
|
|
126
178
|
|
|
127
179
|
private addToPathCache(tenantId: string, path: string): void {
|