ag-ai 0.3.8 → 0.4.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.
@@ -40,8 +40,6 @@ const runnerStack = (context) => {
40
40
  throw new Error('AG_AI_GENERATION_CIDR is required');
41
41
  const imageProvider = context.env.AG_AI_IMAGE_PROVIDER ?? 'openai';
42
42
  const opencodeProvider = context.env.AG_AI_OPENCODE_PROVIDER;
43
- // '1' makes the bridge claim cloud work when the queue pushes it (see runtime/push.mjs).
44
- const push = context.env.AG_AI_PUSH;
45
43
  const agAiPackage = context.env.AG_AI_PACKAGE;
46
44
  if (!agAiPackage)
47
45
  throw new Error('AG_AI_PACKAGE is required');
@@ -95,7 +93,6 @@ const runnerStack = (context) => {
95
93
  (0, common_1.env)('AG_AI_OPENCODE_URL', opencodeUrl),
96
94
  (0, common_1.env)('AG_AI_OPENCODE_PASSWORD_FILE', secrets.opencode.file),
97
95
  ...(opencodeProvider === undefined ? [] : [(0, common_1.env)('AG_AI_OPENCODE_PROVIDER', opencodeProvider)]),
98
- ...(push === undefined ? [] : [(0, common_1.env)('AG_AI_PUSH', push)]),
99
96
  (0, common_1.env)('AG_AI_IMAGE_PROVIDER', imageProvider),
100
97
  (0, common_1.env)('AG_AI_IMAGE_API_KEY_FILE', secrets.image.file),
101
98
  (0, common_1.env)('AG_AI_RUNNER_ID', common_1.RUNNER_APP),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ag-ai",
3
- "version": "0.3.8",
3
+ "version": "0.4.1",
4
4
  "description": "Provider-neutral AI client and cluster runtime for the rpi-mega cluster.",
5
5
  "license": "ISC",
6
6
  "author": "admin@gec.dev",
@@ -828,10 +828,6 @@ const readConfig = async (env = process.env) => {
828
828
  generationPort: env.AG_AI_GENERATION_PORT ?? DEFAULT_GENERATION_PORT,
829
829
  generationStore: path.resolve(env.AG_AI_GENERATION_STORE ?? DEFAULT_GENERATION_STORE),
830
830
  generationTokenFile: env.AG_AI_GENERATION_TOKEN_FILE ?? DEFAULT_GENERATION_TOKEN_FILE,
831
- // AG_AI_PUSH=1 claims cloud work when the queue pushes it instead of every few seconds.
832
- pushEnabled: env.AG_AI_PUSH === '1',
833
- pushTicketPath: env.AG_AI_PUSH_TICKET_PATH,
834
- pushHttpPath: env.AG_AI_PUSH_HTTP_PATH,
835
831
  analyticaToken,
836
832
  token,
837
833
  };
@@ -979,17 +975,14 @@ const main = async () => {
979
975
  process.once('SIGTERM', () => void stop('SIGTERM'));
980
976
  process.once('SIGINT', () => void stop('SIGINT'));
981
977
 
982
- const workSignal = createWorkSignal({ enabled: config.pushEnabled });
983
- const push = config.pushEnabled
984
- ? startPushChannel({
985
- baseUrl: new URL(config.queueUrl).origin,
986
- token: config.token,
987
- ticketPath: config.pushTicketPath,
988
- httpPath: config.pushHttpPath,
989
- onWork: () => workSignal.wake(),
990
- log: (message) => console.log(`[runner] ${redact(message, [config.token])}`),
991
- })
992
- : undefined;
978
+ // The queue pushes queued work over a WebSocket; cloud claims follow pushes.
979
+ const workSignal = createWorkSignal();
980
+ const push = startPushChannel({
981
+ baseUrl: new URL(config.queueUrl).origin,
982
+ token: config.token,
983
+ onWork: () => workSignal.wake(),
984
+ log: (message) => console.log(`[runner] ${redact(message, [config.token])}`),
985
+ });
993
986
  const heartbeatTimer = setInterval(() => void sendHeartbeat(), HEARTBEAT_INTERVAL_MS);
994
987
  const healthTimer = setInterval(() => void refreshHealth(), HEALTH_INTERVAL_MS);
995
988
  const claimLocal = async () => {
@@ -1076,7 +1069,7 @@ const main = async () => {
1076
1069
  } finally {
1077
1070
  clearInterval(heartbeatTimer);
1078
1071
  clearInterval(healthTimer);
1079
- push?.stop();
1072
+ push.stop();
1080
1073
  await stop('shutdown');
1081
1074
  }
1082
1075
  };
package/runtime/push.mjs CHANGED
@@ -1,32 +1,23 @@
1
1
  /* eslint-disable import/extensions -- Native Node ESM requires file extensions. */
2
2
  // Push-driven cloud claims. The queue pushes "work queued" over a reactive
3
- // WebSocket, so the runner claims only when there is work, plus a slow fallback
4
- // claim that covers missed pushes and queues without a push channel.
3
+ // WebSocket, so the runner claims only when there is work. Every (re)connect
4
+ // delivers a fresh snapshot of queued work, so work queued while the socket was
5
+ // down is claimed on reconnect.
5
6
 
6
7
  export const CLUSTER_WORK_PATH = 'cluster.work';
7
- export const DEFAULT_PUSH_FALLBACK_MS = 60_000;
8
- export const DEFAULT_PUSH_TICKET_PATH = '/api/cluster/token';
9
- export const DEFAULT_PUSH_HTTP_PATH = '/api/cluster/reactive';
8
+ const TICKET_PATH = '/api/cluster/token';
9
+ const HTTP_PATH = '/api/cluster/reactive';
10
10
 
11
- /**
12
- * Decides when a cloud claim is worth a request. Disabled, it always claims, which
13
- * keeps the original polling behaviour.
14
- */
15
- export const createWorkSignal = ({
16
- enabled,
17
- fallbackMs = DEFAULT_PUSH_FALLBACK_MS,
18
- now = Date.now,
19
- }) => {
11
+ /** Decides when a cloud claim is worth a request: on start, and after each push. */
12
+ export const createWorkSignal = () => {
20
13
  // Claim once at start: work may have been queued while the runner was down.
21
14
  let pending = true;
22
- let nextFallbackAt = 0;
23
15
  let wakeWaiter;
24
16
  return {
25
- shouldClaim: () => !enabled || pending || now() >= nextFallbackAt,
26
- /** A claim found no work: wait for a push or the fallback. */
17
+ shouldClaim: () => pending,
18
+ /** A claim found no work: wait for the next push. */
27
19
  drained: () => {
28
20
  pending = false;
29
- nextFallbackAt = now() + fallbackMs;
30
21
  },
31
22
  /** Work was pushed: claim on the next loop, waking an idle wait now. */
32
23
  wake: () => {
@@ -49,18 +40,10 @@ export const createWorkSignal = ({
49
40
 
50
41
  /**
51
42
  * Holds the reactive WebSocket and calls `onWork` whenever the pushed snapshot
52
- * lists queued work. Never throws: failures are logged and the fallback claim
53
- * keeps the runner working.
43
+ * lists queued work. Never throws: failures are logged and the connection keeps
44
+ * retrying, and the reconnect snapshot catches up on anything queued meanwhile.
54
45
  */
55
- export const startPushChannel = ({
56
- baseUrl,
57
- token,
58
- ticketPath = DEFAULT_PUSH_TICKET_PATH,
59
- httpPath = DEFAULT_PUSH_HTTP_PATH,
60
- onWork,
61
- log,
62
- connect,
63
- }) => {
46
+ export const startPushChannel = ({ baseUrl, token, onWork, log, connect }) => {
64
47
  const controller = new AbortController();
65
48
  let client;
66
49
  let subscription;
@@ -70,8 +53,8 @@ export const startPushChannel = ({
70
53
  client = await connectClusterWorker({
71
54
  baseUrl,
72
55
  runnerToken: token,
73
- ticketPath,
74
- httpPath,
56
+ ticketPath: TICKET_PATH,
57
+ httpPath: HTTP_PATH,
75
58
  signal: controller.signal,
76
59
  onRetry: (error) => log(`push connect failed; retrying: ${error.message}`),
77
60
  client: { onError: (error) => log(`push socket error: ${error.message}`) },