@nocville/npc 0.4.1 → 0.5.0
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/cjs/core/base-npc.js +9 -0
- package/dist/cjs/core/pixel-art.js +4 -1
- package/dist/cjs/core/polling-npc.js +3 -2
- package/dist/cjs/npcs/agent-common/session.js +35 -9
- package/dist/cjs/npcs/agent-common/watchdog.js +4 -3
- package/dist/cjs/npcs/claude-code/defaults.js +2 -2
- package/dist/cjs/npcs/codex/defaults.js +2 -2
- package/dist/cjs/npcs/cursor/defaults.js +1 -1
- package/dist/cjs/npcs/cursor/hook-handler.js +1 -1
- package/dist/cjs/npcs/gemini/defaults.js +1 -1
- package/dist/cjs/npcs/web-page-watcher/pixelate.js +3 -2
- package/dist/core/base-npc.js +9 -0
- package/dist/core/pixel-art.js +4 -1
- package/dist/core/polling-npc.d.ts +3 -2
- package/dist/core/polling-npc.js +3 -2
- package/dist/npcs/agent-common/session.js +35 -9
- package/dist/npcs/agent-common/watchdog.d.ts +2 -1
- package/dist/npcs/agent-common/watchdog.js +4 -3
- package/dist/npcs/claude-code/defaults.d.ts +1 -1
- package/dist/npcs/claude-code/defaults.js +2 -2
- package/dist/npcs/codex/defaults.d.ts +1 -1
- package/dist/npcs/codex/defaults.js +2 -2
- package/dist/npcs/cursor/defaults.js +1 -1
- package/dist/npcs/cursor/hook-handler.js +1 -1
- package/dist/npcs/gemini/defaults.js +1 -1
- package/dist/npcs/web-page-watcher/pixelate.js +3 -2
- package/package.json +2 -2
|
@@ -139,6 +139,15 @@ class BaseNPC {
|
|
|
139
139
|
catch {
|
|
140
140
|
/* observer only */
|
|
141
141
|
}
|
|
142
|
+
if (!result.success && [401, 410].includes(result.statusCode ?? 0)) {
|
|
143
|
+
// A revoked key or an SDK version the server retired: retrying forever only
|
|
144
|
+
// costs requests. Stop; restarting after fixing it resumes monitoring.
|
|
145
|
+
this.logger.error(result.statusCode === 410
|
|
146
|
+
? 'This @nocville/npc version is no longer supported — upgrade it. Stopping.'
|
|
147
|
+
: 'API key rejected (HTTP 401) — check NOCVILLE_API_KEY / team settings. Stopping.');
|
|
148
|
+
void this.stop();
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
142
151
|
if (!result.success) {
|
|
143
152
|
this.reportPushFailure(result.error);
|
|
144
153
|
}
|
|
@@ -4,6 +4,7 @@ exports.encodePixelArt = encodePixelArt;
|
|
|
4
4
|
exports.pixelArtFromPng = pixelArtFromPng;
|
|
5
5
|
exports.pixelDevice = pixelDevice;
|
|
6
6
|
const node_fs_1 = require("node:fs");
|
|
7
|
+
const node_zlib_1 = require("node:zlib");
|
|
7
8
|
const pngjs_1 = require("pngjs");
|
|
8
9
|
/**
|
|
9
10
|
* Helpers for custom pixel-art companions (`screenType: 'pixel-device'`): your NPC's own
|
|
@@ -68,7 +69,9 @@ function encodePixelArt(art) {
|
|
|
68
69
|
const fps = Math.min(12, Math.max(1, Math.round(art.fps ?? 4)));
|
|
69
70
|
tokens.push(`f${frames.length}`, `fps${fps}`);
|
|
70
71
|
}
|
|
71
|
-
|
|
72
|
+
// Deflated indices (`z`) are typically 15–25× smaller for pixel art.
|
|
73
|
+
tokens.push('z');
|
|
74
|
+
const encoded = (0, node_zlib_1.deflateRawSync)(pixels, { level: 9 }).toString('base64');
|
|
72
75
|
if (encoded.length > MAX_VALUE)
|
|
73
76
|
throw new Error('Pixel art is too large to send; use fewer frames or a smaller size.');
|
|
74
77
|
return {
|
|
@@ -3,8 +3,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.PollingNPC = exports.MIN_POLL_INTERVAL_MS = void 0;
|
|
4
4
|
const base_npc_js_1 = require("./base-npc.js");
|
|
5
5
|
/**
|
|
6
|
-
* Lower bound for any poll interval, so a typo can't
|
|
7
|
-
*
|
|
6
|
+
* Lower bound for any poll interval, so a typo can't hammer the watched source.
|
|
7
|
+
* The SDK sends changed status at most every 10 s and heartbeats every 30 s;
|
|
8
|
+
* the server limits producers per key, team and NPC and answers 429.
|
|
8
9
|
*/
|
|
9
10
|
exports.MIN_POLL_INTERVAL_MS = 5000;
|
|
10
11
|
/** How long `stop()` waits for an in-flight collection before pushing offline anyway. */
|
|
@@ -261,17 +261,43 @@ function buildAgentPayload(adapter, state, config) {
|
|
|
261
261
|
map: config.map,
|
|
262
262
|
};
|
|
263
263
|
}
|
|
264
|
+
/**
|
|
265
|
+
* Clients reused within one process (a long-lived watchdog): a fresh client
|
|
266
|
+
* re-runs enrollment, manifest, version and session requests on every push.
|
|
267
|
+
* Any failure (e.g. a hook process took over the producer session) drops it.
|
|
268
|
+
*/
|
|
269
|
+
const clients = new Map();
|
|
264
270
|
async function defaultPush(config, serviceId, payload, timeoutMs) {
|
|
265
|
-
const
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
271
|
+
const key = JSON.stringify([
|
|
272
|
+
config.workspace,
|
|
273
|
+
config.apiUrl,
|
|
274
|
+
config.apiKey,
|
|
269
275
|
serviceId,
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
276
|
+
timeoutMs,
|
|
277
|
+
]);
|
|
278
|
+
let client = clients.get(key);
|
|
279
|
+
if (!client) {
|
|
280
|
+
client = new node_1.NocvilleClient({
|
|
281
|
+
workspace: config.workspace,
|
|
282
|
+
apiUrl: config.apiUrl,
|
|
283
|
+
apiKey: config.apiKey,
|
|
284
|
+
serviceId,
|
|
285
|
+
timeout: timeoutMs,
|
|
286
|
+
retries: 0,
|
|
287
|
+
debug: config.debug,
|
|
288
|
+
});
|
|
289
|
+
clients.set(key, client);
|
|
290
|
+
}
|
|
291
|
+
try {
|
|
292
|
+
const result = await client.pushStatus(payload);
|
|
293
|
+
if (!result.success)
|
|
294
|
+
clients.delete(key);
|
|
295
|
+
return result;
|
|
296
|
+
}
|
|
297
|
+
catch (error) {
|
|
298
|
+
clients.delete(key);
|
|
299
|
+
throw error;
|
|
300
|
+
}
|
|
275
301
|
}
|
|
276
302
|
/**
|
|
277
303
|
* Push the session's current state if it has unpushed changes (or is offline),
|
|
@@ -7,7 +7,8 @@ const liveness_js_1 = require("./liveness.js");
|
|
|
7
7
|
const lock_js_1 = require("./lock.js");
|
|
8
8
|
const session_js_1 = require("./session.js");
|
|
9
9
|
exports.WATCHDOG_TICK_MS = 15_000;
|
|
10
|
-
|
|
10
|
+
/** Liveness only (status changes push at once); each heartbeat is a billed request. */
|
|
11
|
+
exports.WATCHDOG_HEARTBEAT_MS = 300_000;
|
|
11
12
|
exports.WATCHDOG_OFFLINE_RETRY_MS = 15 * 60_000;
|
|
12
13
|
async function ensureWatchdog(adapter, config, sessionId, cliPath, lockWaitMs) {
|
|
13
14
|
if (adapter.processNames?.length === 0 ||
|
|
@@ -42,7 +43,7 @@ async function runWatchdog(adapter, config, sessionId, deps = {}) {
|
|
|
42
43
|
const delivery = {
|
|
43
44
|
...deps,
|
|
44
45
|
onResult: (result) => {
|
|
45
|
-
if (!result.success && [400, 401, 403, 404].includes(result.statusCode ?? 0))
|
|
46
|
+
if (!result.success && [400, 401, 403, 404, 410].includes(result.statusCode ?? 0))
|
|
46
47
|
rejected = true;
|
|
47
48
|
deps.onResult?.(result);
|
|
48
49
|
},
|
|
@@ -123,7 +124,7 @@ async function runWatchdog(adapter, config, sessionId, deps = {}) {
|
|
|
123
124
|
...deps,
|
|
124
125
|
force: true,
|
|
125
126
|
onResult: (result) => {
|
|
126
|
-
if (!result.success && [400, 401, 403, 404].includes(result.statusCode ?? 0))
|
|
127
|
+
if (!result.success && [400, 401, 403, 404, 410].includes(result.statusCode ?? 0))
|
|
127
128
|
heartbeatRejected = true;
|
|
128
129
|
deps.onResult?.(result);
|
|
129
130
|
},
|
|
@@ -8,8 +8,8 @@ exports.DEFAULTS = {
|
|
|
8
8
|
stateFilePrefix: 'session-',
|
|
9
9
|
stateFileMaxAge: 24 * 60 * 60 * 1000, // 24 hours
|
|
10
10
|
httpTimeout: 4000,
|
|
11
|
-
/** Watchdog-backed sessions become unknown after
|
|
12
|
-
heartbeatSeconds:
|
|
11
|
+
/** Watchdog-backed sessions become unknown after twelve minutes without a heartbeat. */
|
|
12
|
+
heartbeatSeconds: 360,
|
|
13
13
|
/** Number of recent activity lines kept in the desk-sign ticker. */
|
|
14
14
|
activityBufferSize: 8,
|
|
15
15
|
/** Coalesce same-phase activity pushes closer together than this. */
|
|
@@ -8,8 +8,8 @@ exports.CODEX_DEFAULTS = {
|
|
|
8
8
|
/** Hook-mode per-session state (shared with the watcher to avoid double pushes). */
|
|
9
9
|
stateDir: (0, node_path_1.join)((0, node_os_1.tmpdir)(), 'nocville-codex'),
|
|
10
10
|
stateFileMaxAge: 24 * 60 * 60 * 1000,
|
|
11
|
-
/** Watchdog-backed sessions become unknown after
|
|
12
|
-
heartbeatSeconds:
|
|
11
|
+
/** Watchdog-backed sessions become unknown after twelve minutes without a heartbeat. */
|
|
12
|
+
heartbeatSeconds: 360,
|
|
13
13
|
minPushGapMs: 1500,
|
|
14
14
|
hookTimeoutSeconds: 10,
|
|
15
15
|
/** Watcher: Codex session rollout logs. */
|
|
@@ -6,7 +6,7 @@ const node_path_1 = require("node:path");
|
|
|
6
6
|
exports.CURSOR_DEFAULTS = {
|
|
7
7
|
stateDir: (0, node_path_1.join)((0, node_os_1.tmpdir)(), 'nocville-cursor'),
|
|
8
8
|
stateFileMaxAge: 24 * 60 * 60 * 1000,
|
|
9
|
-
heartbeatSeconds:
|
|
9
|
+
heartbeatSeconds: 360,
|
|
10
10
|
minPushGapMs: 1500,
|
|
11
11
|
hookTimeoutSeconds: 10,
|
|
12
12
|
};
|
|
@@ -6,7 +6,7 @@ const node_path_1 = require("node:path");
|
|
|
6
6
|
exports.GEMINI_DEFAULTS = {
|
|
7
7
|
stateDir: (0, node_path_1.join)((0, node_os_1.tmpdir)(), 'nocville-gemini'),
|
|
8
8
|
stateFileMaxAge: 24 * 60 * 60 * 1000,
|
|
9
|
-
heartbeatSeconds:
|
|
9
|
+
heartbeatSeconds: 360,
|
|
10
10
|
minPushGapMs: 1500,
|
|
11
11
|
hookTimeoutMs: 5000,
|
|
12
12
|
};
|
|
@@ -7,6 +7,7 @@ exports.quantize = quantize;
|
|
|
7
7
|
exports.buildPixelImageScreen = buildPixelImageScreen;
|
|
8
8
|
exports.fnv1a = fnv1a;
|
|
9
9
|
exports.pngToPixelImage = pngToPixelImage;
|
|
10
|
+
const node_zlib_1 = require("node:zlib");
|
|
10
11
|
const pngjs_1 = require("pngjs");
|
|
11
12
|
/** Default target resolution for the web-view TV (4:3 to match a 4×3 companion). */
|
|
12
13
|
exports.TARGET_W = 160;
|
|
@@ -143,9 +144,9 @@ function buildPixelImageScreen(width, height, palette, indices) {
|
|
|
143
144
|
return {
|
|
144
145
|
layout: 'pixel-image',
|
|
145
146
|
values: [
|
|
146
|
-
{ label: 'fmt', value: `idx8;${width}x${height};p${paletteCount}` },
|
|
147
|
+
{ label: 'fmt', value: `idx8;${width}x${height};p${paletteCount};z` },
|
|
147
148
|
{ label: 'palette', value: Buffer.from(palette).toString('base64') },
|
|
148
|
-
{ label: 'pixels', value:
|
|
149
|
+
{ label: 'pixels', value: (0, node_zlib_1.deflateRawSync)(indices, { level: 9 }).toString('base64') },
|
|
149
150
|
],
|
|
150
151
|
};
|
|
151
152
|
}
|
package/dist/core/base-npc.js
CHANGED
|
@@ -136,6 +136,15 @@ export class BaseNPC {
|
|
|
136
136
|
catch {
|
|
137
137
|
/* observer only */
|
|
138
138
|
}
|
|
139
|
+
if (!result.success && [401, 410].includes(result.statusCode ?? 0)) {
|
|
140
|
+
// A revoked key or an SDK version the server retired: retrying forever only
|
|
141
|
+
// costs requests. Stop; restarting after fixing it resumes monitoring.
|
|
142
|
+
this.logger.error(result.statusCode === 410
|
|
143
|
+
? 'This @nocville/npc version is no longer supported — upgrade it. Stopping.'
|
|
144
|
+
: 'API key rejected (HTTP 401) — check NOCVILLE_API_KEY / team settings. Stopping.');
|
|
145
|
+
void this.stop();
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
139
148
|
if (!result.success) {
|
|
140
149
|
this.reportPushFailure(result.error);
|
|
141
150
|
}
|
package/dist/core/pixel-art.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { deflateRawSync } from 'node:zlib';
|
|
2
3
|
import { PNG } from 'pngjs';
|
|
3
4
|
/**
|
|
4
5
|
* Helpers for custom pixel-art companions (`screenType: 'pixel-device'`): your NPC's own
|
|
@@ -63,7 +64,9 @@ export function encodePixelArt(art) {
|
|
|
63
64
|
const fps = Math.min(12, Math.max(1, Math.round(art.fps ?? 4)));
|
|
64
65
|
tokens.push(`f${frames.length}`, `fps${fps}`);
|
|
65
66
|
}
|
|
66
|
-
|
|
67
|
+
// Deflated indices (`z`) are typically 15–25× smaller for pixel art.
|
|
68
|
+
tokens.push('z');
|
|
69
|
+
const encoded = deflateRawSync(pixels, { level: 9 }).toString('base64');
|
|
67
70
|
if (encoded.length > MAX_VALUE)
|
|
68
71
|
throw new Error('Pixel art is too large to send; use fewer frames or a smaller size.');
|
|
69
72
|
return {
|
|
@@ -6,8 +6,9 @@ export interface PollingNpcConfig extends NpcConfig {
|
|
|
6
6
|
onCollection?: (metrics: CollectedMetrics) => void;
|
|
7
7
|
}
|
|
8
8
|
/**
|
|
9
|
-
* Lower bound for any poll interval, so a typo can't
|
|
10
|
-
*
|
|
9
|
+
* Lower bound for any poll interval, so a typo can't hammer the watched source.
|
|
10
|
+
* The SDK sends changed status at most every 10 s and heartbeats every 30 s;
|
|
11
|
+
* the server limits producers per key, team and NPC and answers 429.
|
|
11
12
|
*/
|
|
12
13
|
export declare const MIN_POLL_INTERVAL_MS = 5000;
|
|
13
14
|
export declare abstract class PollingNPC extends BaseNPC {
|
package/dist/core/polling-npc.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { BaseNPC } from './base-npc.js';
|
|
2
2
|
/**
|
|
3
|
-
* Lower bound for any poll interval, so a typo can't
|
|
4
|
-
*
|
|
3
|
+
* Lower bound for any poll interval, so a typo can't hammer the watched source.
|
|
4
|
+
* The SDK sends changed status at most every 10 s and heartbeats every 30 s;
|
|
5
|
+
* the server limits producers per key, team and NPC and answers 429.
|
|
5
6
|
*/
|
|
6
7
|
export const MIN_POLL_INTERVAL_MS = 5000;
|
|
7
8
|
/** How long `stop()` waits for an in-flight collection before pushing offline anyway. */
|
|
@@ -239,17 +239,43 @@ export function buildAgentPayload(adapter, state, config) {
|
|
|
239
239
|
map: config.map,
|
|
240
240
|
};
|
|
241
241
|
}
|
|
242
|
+
/**
|
|
243
|
+
* Clients reused within one process (a long-lived watchdog): a fresh client
|
|
244
|
+
* re-runs enrollment, manifest, version and session requests on every push.
|
|
245
|
+
* Any failure (e.g. a hook process took over the producer session) drops it.
|
|
246
|
+
*/
|
|
247
|
+
const clients = new Map();
|
|
242
248
|
async function defaultPush(config, serviceId, payload, timeoutMs) {
|
|
243
|
-
const
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
249
|
+
const key = JSON.stringify([
|
|
250
|
+
config.workspace,
|
|
251
|
+
config.apiUrl,
|
|
252
|
+
config.apiKey,
|
|
247
253
|
serviceId,
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
254
|
+
timeoutMs,
|
|
255
|
+
]);
|
|
256
|
+
let client = clients.get(key);
|
|
257
|
+
if (!client) {
|
|
258
|
+
client = new NocvilleClient({
|
|
259
|
+
workspace: config.workspace,
|
|
260
|
+
apiUrl: config.apiUrl,
|
|
261
|
+
apiKey: config.apiKey,
|
|
262
|
+
serviceId,
|
|
263
|
+
timeout: timeoutMs,
|
|
264
|
+
retries: 0,
|
|
265
|
+
debug: config.debug,
|
|
266
|
+
});
|
|
267
|
+
clients.set(key, client);
|
|
268
|
+
}
|
|
269
|
+
try {
|
|
270
|
+
const result = await client.pushStatus(payload);
|
|
271
|
+
if (!result.success)
|
|
272
|
+
clients.delete(key);
|
|
273
|
+
return result;
|
|
274
|
+
}
|
|
275
|
+
catch (error) {
|
|
276
|
+
clients.delete(key);
|
|
277
|
+
throw error;
|
|
278
|
+
}
|
|
253
279
|
}
|
|
254
280
|
/**
|
|
255
281
|
* Push the session's current state if it has unpushed changes (or is offline),
|
|
@@ -2,7 +2,8 @@ import type { NpcConfig } from '../../core/types.js';
|
|
|
2
2
|
import { isAgentAlive } from './liveness.js';
|
|
3
3
|
import type { AgentAdapter, PushDeps } from './session.js';
|
|
4
4
|
export declare const WATCHDOG_TICK_MS = 15000;
|
|
5
|
-
|
|
5
|
+
/** Liveness only (status changes push at once); each heartbeat is a billed request. */
|
|
6
|
+
export declare const WATCHDOG_HEARTBEAT_MS = 300000;
|
|
6
7
|
export declare const WATCHDOG_OFFLINE_RETRY_MS: number;
|
|
7
8
|
export declare function ensureWatchdog(adapter: AgentAdapter, config: NpcConfig, sessionId: string, cliPath?: string, lockWaitMs?: number): Promise<void>;
|
|
8
9
|
export interface WatchdogDeps extends PushDeps {
|
|
@@ -2,7 +2,8 @@ import { isAgentAlive } from './liveness.js';
|
|
|
2
2
|
import { withStateLock } from './lock.js';
|
|
3
3
|
import { flushSession, readSessionState, sleep, spawnDetachedAgent, stateFilePath, writeSessionState, } from './session.js';
|
|
4
4
|
export const WATCHDOG_TICK_MS = 15_000;
|
|
5
|
-
|
|
5
|
+
/** Liveness only (status changes push at once); each heartbeat is a billed request. */
|
|
6
|
+
export const WATCHDOG_HEARTBEAT_MS = 300_000;
|
|
6
7
|
export const WATCHDOG_OFFLINE_RETRY_MS = 15 * 60_000;
|
|
7
8
|
export async function ensureWatchdog(adapter, config, sessionId, cliPath, lockWaitMs) {
|
|
8
9
|
if (adapter.processNames?.length === 0 ||
|
|
@@ -37,7 +38,7 @@ export async function runWatchdog(adapter, config, sessionId, deps = {}) {
|
|
|
37
38
|
const delivery = {
|
|
38
39
|
...deps,
|
|
39
40
|
onResult: (result) => {
|
|
40
|
-
if (!result.success && [400, 401, 403, 404].includes(result.statusCode ?? 0))
|
|
41
|
+
if (!result.success && [400, 401, 403, 404, 410].includes(result.statusCode ?? 0))
|
|
41
42
|
rejected = true;
|
|
42
43
|
deps.onResult?.(result);
|
|
43
44
|
},
|
|
@@ -118,7 +119,7 @@ export async function runWatchdog(adapter, config, sessionId, deps = {}) {
|
|
|
118
119
|
...deps,
|
|
119
120
|
force: true,
|
|
120
121
|
onResult: (result) => {
|
|
121
|
-
if (!result.success && [400, 401, 403, 404].includes(result.statusCode ?? 0))
|
|
122
|
+
if (!result.success && [400, 401, 403, 404, 410].includes(result.statusCode ?? 0))
|
|
122
123
|
heartbeatRejected = true;
|
|
123
124
|
deps.onResult?.(result);
|
|
124
125
|
},
|
|
@@ -3,7 +3,7 @@ export declare const DEFAULTS: {
|
|
|
3
3
|
stateFilePrefix: string;
|
|
4
4
|
stateFileMaxAge: number;
|
|
5
5
|
httpTimeout: number;
|
|
6
|
-
/** Watchdog-backed sessions become unknown after
|
|
6
|
+
/** Watchdog-backed sessions become unknown after twelve minutes without a heartbeat. */
|
|
7
7
|
heartbeatSeconds: number;
|
|
8
8
|
/** Number of recent activity lines kept in the desk-sign ticker. */
|
|
9
9
|
activityBufferSize: number;
|
|
@@ -5,8 +5,8 @@ export const DEFAULTS = {
|
|
|
5
5
|
stateFilePrefix: 'session-',
|
|
6
6
|
stateFileMaxAge: 24 * 60 * 60 * 1000, // 24 hours
|
|
7
7
|
httpTimeout: 4000,
|
|
8
|
-
/** Watchdog-backed sessions become unknown after
|
|
9
|
-
heartbeatSeconds:
|
|
8
|
+
/** Watchdog-backed sessions become unknown after twelve minutes without a heartbeat. */
|
|
9
|
+
heartbeatSeconds: 360,
|
|
10
10
|
/** Number of recent activity lines kept in the desk-sign ticker. */
|
|
11
11
|
activityBufferSize: 8,
|
|
12
12
|
/** Coalesce same-phase activity pushes closer together than this. */
|
|
@@ -2,7 +2,7 @@ export declare const CODEX_DEFAULTS: {
|
|
|
2
2
|
/** Hook-mode per-session state (shared with the watcher to avoid double pushes). */
|
|
3
3
|
stateDir: string;
|
|
4
4
|
stateFileMaxAge: number;
|
|
5
|
-
/** Watchdog-backed sessions become unknown after
|
|
5
|
+
/** Watchdog-backed sessions become unknown after twelve minutes without a heartbeat. */
|
|
6
6
|
heartbeatSeconds: number;
|
|
7
7
|
minPushGapMs: number;
|
|
8
8
|
hookTimeoutSeconds: number;
|
|
@@ -4,8 +4,8 @@ export const CODEX_DEFAULTS = {
|
|
|
4
4
|
/** Hook-mode per-session state (shared with the watcher to avoid double pushes). */
|
|
5
5
|
stateDir: join(tmpdir(), 'nocville-codex'),
|
|
6
6
|
stateFileMaxAge: 24 * 60 * 60 * 1000,
|
|
7
|
-
/** Watchdog-backed sessions become unknown after
|
|
8
|
-
heartbeatSeconds:
|
|
7
|
+
/** Watchdog-backed sessions become unknown after twelve minutes without a heartbeat. */
|
|
8
|
+
heartbeatSeconds: 360,
|
|
9
9
|
minPushGapMs: 1500,
|
|
10
10
|
hookTimeoutSeconds: 10,
|
|
11
11
|
/** Watcher: Codex session rollout logs. */
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { deflateRawSync } from 'node:zlib';
|
|
1
2
|
import { PNG } from 'pngjs';
|
|
2
3
|
/** Default target resolution for the web-view TV (4:3 to match a 4×3 companion). */
|
|
3
4
|
export const TARGET_W = 160;
|
|
@@ -134,9 +135,9 @@ export function buildPixelImageScreen(width, height, palette, indices) {
|
|
|
134
135
|
return {
|
|
135
136
|
layout: 'pixel-image',
|
|
136
137
|
values: [
|
|
137
|
-
{ label: 'fmt', value: `idx8;${width}x${height};p${paletteCount}` },
|
|
138
|
+
{ label: 'fmt', value: `idx8;${width}x${height};p${paletteCount};z` },
|
|
138
139
|
{ label: 'palette', value: Buffer.from(palette).toString('base64') },
|
|
139
|
-
{ label: 'pixels', value:
|
|
140
|
+
{ label: 'pixels', value: deflateRawSync(indices, { level: 9 }).toString('base64') },
|
|
140
141
|
],
|
|
141
142
|
};
|
|
142
143
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nocville/npc",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Put your Claude Code and Codex sessions (and your servers) in the Nocville office as live NPCs",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
],
|
|
25
25
|
"dependencies": {
|
|
26
26
|
"pngjs": "^7.0.0",
|
|
27
|
-
"@nocville/node": "^0.
|
|
27
|
+
"@nocville/node": "^0.5.0"
|
|
28
28
|
},
|
|
29
29
|
"peerDependencies": {
|
|
30
30
|
"puppeteer-core": "*"
|