infinicode 2.8.15 → 2.8.16

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.
@@ -1,5 +1,5 @@
1
1
  export interface ServiceArgs {
2
- role: 'hub' | 'robot' | 'control' | 'hub-scheduler' | 'robot-preview-agent';
2
+ role: 'hub' | 'robot' | 'control' | 'hub-scheduler' | 'robot-preview-agent' | 'robot-vision-agent';
3
3
  name: string;
4
4
  command: string;
5
5
  args: string[];
@@ -1,9 +1,15 @@
1
+ /** Modules app_pi_clean.py imports — kept in sync with requirements_pi_unified.txt. */
2
+ export declare const VISION_REQUIRED_MODULES: string[];
1
3
  export declare function findSchedulerPath(): Promise<string | null>;
4
+ export declare function findVisionPath(): Promise<string | null>;
2
5
  export declare function findPython(): string;
3
6
  /**
4
- * Ensure preview_agent.py's Python deps are importable, installing them from
5
- * requirements-robot.txt (next to the script) if not. Returns false — with a
6
- * message already printed — if deps are still missing after the install
7
+ * Ensure a robot-side script's Python deps are importable, installing them
8
+ * from a requirements file (next to the script) if not. Returns false — with
9
+ * a message already printed — if deps are still missing after the install
7
10
  * attempt, so callers can bail out before hitting a raw traceback.
11
+ *
12
+ * Defaults match preview_agent.py; pass `modules`/`reqFile` for other scripts
13
+ * (e.g. app_pi_clean.py + requirements_pi_unified.txt).
8
14
  */
9
- export declare function ensurePythonDeps(python: string, scriptPath: string): boolean;
15
+ export declare function ensurePythonDeps(python: string, scriptPath: string, modules?: string[], reqFile?: string): boolean;
@@ -1,6 +1,6 @@
1
1
  /**
2
- * RoboPark — shared Python environment helpers for `enroll` and
3
- * `preview-agent`, which both spawn `preview_agent.py`.
2
+ * RoboPark — shared Python environment helpers for `enroll`, `preview-agent`,
3
+ * and `vision-agent`, which spawn preview_agent.py / app_pi_clean.py.
4
4
  */
5
5
  import { execSync, spawnSync } from 'node:child_process';
6
6
  import { existsSync } from 'node:fs';
@@ -8,10 +8,13 @@ import { join } from 'node:path';
8
8
  import chalk from 'chalk';
9
9
  /** Modules preview_agent.py imports — kept in sync with requirements-robot.txt. */
10
10
  const REQUIRED_MODULES = ['httpx', 'cv2', 'pyaudio', 'livekit'];
11
- export async function findSchedulerPath() {
11
+ /** Modules app_pi_clean.py imports — kept in sync with requirements_pi_unified.txt. */
12
+ export const VISION_REQUIRED_MODULES = ['flask', 'flask_cors', 'cv2', 'numpy', 'requests'];
13
+ async function findPackageScript(relativePath) {
14
+ const parts = relativePath.split('/');
12
15
  const candidates = [
13
- join(process.cwd(), 'packages', 'robopark', 'scheduler', 'preview_agent.py'),
14
- join(process.cwd(), 'scheduler', 'preview_agent.py'),
16
+ join(process.cwd(), 'packages', 'robopark', ...parts),
17
+ join(process.cwd(), ...parts),
15
18
  ];
16
19
  for (const p of candidates) {
17
20
  if (existsSync(p))
@@ -21,13 +24,19 @@ export async function findSchedulerPath() {
21
24
  const { createRequire } = await import('node:module');
22
25
  const require = createRequire(import.meta.url);
23
26
  const pkgMain = require.resolve('infinicode/package.json');
24
- const p = join(pkgMain, '..', 'packages', 'robopark', 'scheduler', 'preview_agent.py');
27
+ const p = join(pkgMain, '..', 'packages', 'robopark', ...relativePath.split('/'));
25
28
  if (existsSync(p))
26
29
  return p;
27
30
  }
28
31
  catch { /* ignore */ }
29
32
  return null;
30
33
  }
34
+ export async function findSchedulerPath() {
35
+ return findPackageScript('scheduler/preview_agent.py');
36
+ }
37
+ export async function findVisionPath() {
38
+ return findPackageScript('vision/app_pi_clean.py');
39
+ }
31
40
  export function findPython() {
32
41
  const names = process.platform === 'win32'
33
42
  ? ['python', 'python3', 'py']
@@ -43,25 +52,28 @@ export function findPython() {
43
52
  }
44
53
  return process.platform === 'win32' ? 'python' : 'python3';
45
54
  }
46
- function missingModules(python) {
47
- return REQUIRED_MODULES.filter(mod => {
55
+ function missingModules(python, modules) {
56
+ return modules.filter(mod => {
48
57
  const res = spawnSync(python, ['-c', `import ${mod}`], { stdio: 'ignore' });
49
58
  return res.status !== 0;
50
59
  });
51
60
  }
52
61
  /**
53
- * Ensure preview_agent.py's Python deps are importable, installing them from
54
- * requirements-robot.txt (next to the script) if not. Returns false — with a
55
- * message already printed — if deps are still missing after the install
62
+ * Ensure a robot-side script's Python deps are importable, installing them
63
+ * from a requirements file (next to the script) if not. Returns false — with
64
+ * a message already printed — if deps are still missing after the install
56
65
  * attempt, so callers can bail out before hitting a raw traceback.
66
+ *
67
+ * Defaults match preview_agent.py; pass `modules`/`reqFile` for other scripts
68
+ * (e.g. app_pi_clean.py + requirements_pi_unified.txt).
57
69
  */
58
- export function ensurePythonDeps(python, scriptPath) {
59
- const missing = missingModules(python);
70
+ export function ensurePythonDeps(python, scriptPath, modules = REQUIRED_MODULES, reqFile = 'requirements-robot.txt') {
71
+ const missing = missingModules(python, modules);
60
72
  if (missing.length === 0)
61
73
  return true;
62
- const reqPath = join(scriptPath, '..', 'requirements-robot.txt');
74
+ const reqPath = join(scriptPath, '..', reqFile);
63
75
  if (!existsSync(reqPath)) {
64
- console.log(chalk.red(` ✗ missing Python modules: ${missing.join(', ')} (and requirements-robot.txt not found next to ${scriptPath})`));
76
+ console.log(chalk.red(` ✗ missing Python modules: ${missing.join(', ')} (and ${reqFile} not found next to ${scriptPath})`));
65
77
  return false;
66
78
  }
67
79
  console.log(chalk.dim(` installing Python deps (${missing.join(', ')})…`));
@@ -70,7 +82,7 @@ export function ensurePythonDeps(python, scriptPath) {
70
82
  console.log(chalk.red(` ✗ pip install failed — install manually: ${python} -m pip install -r "${reqPath}"`));
71
83
  return false;
72
84
  }
73
- const stillMissing = missingModules(python);
85
+ const stillMissing = missingModules(python, modules);
74
86
  if (stillMissing.length > 0) {
75
87
  console.log(chalk.red(` ✗ still missing after install: ${stillMissing.join(', ')} — install manually: ${python} -m pip install -r "${reqPath}"`));
76
88
  return false;
@@ -16,6 +16,7 @@ export interface SetupOptions {
16
16
  enrollmentToken?: string;
17
17
  videoDevice?: string;
18
18
  audioDevice?: string;
19
+ vision?: boolean;
19
20
  start?: boolean;
20
21
  autoStart?: boolean;
21
22
  yes?: boolean;
@@ -171,9 +171,16 @@ async function setupRobot(config, opts, ctx, internal) {
171
171
  ];
172
172
  if (opts.enrollmentToken)
173
173
  previewAgentArgs.push('--enrollment-token', opts.enrollmentToken);
174
+ // Vision (camera/motion detection -> presence-triggered session) is the
175
+ // production trigger — on by default. --no-vision opts out (e.g. no camera
176
+ // on this box, or RoboVisionAI_PI isn't installed).
177
+ const visionEnabled = opts.vision !== false;
178
+ const visionAgentArgs = ['vision-agent'];
174
179
  console.log(chalk.dim(' robot command:'));
175
180
  console.log(' ' + chalk.cyan(`infinicode ${args.join(' ')}`));
176
181
  console.log(' ' + chalk.cyan(`robopark ${previewAgentArgs.join(' ')}`));
182
+ if (visionEnabled)
183
+ console.log(' ' + chalk.cyan(`robopark ${visionAgentArgs.join(' ')}`));
177
184
  console.log();
178
185
  if (opts.start) {
179
186
  const robotArgv = await infinicodeArgv(args);
@@ -191,6 +198,11 @@ async function setupRobot(config, opts, ctx, internal) {
191
198
  console.log(chalk.dim(' starting preview agent…'));
192
199
  const previewArgv = [process.execPath, process.argv[1], ...previewAgentArgs];
193
200
  runDetached(previewArgv[0], previewArgv.slice(1));
201
+ if (visionEnabled) {
202
+ console.log(chalk.dim(' starting vision agent…'));
203
+ const visionArgv = [process.execPath, process.argv[1], ...visionAgentArgs];
204
+ runDetached(visionArgv[0], visionArgv.slice(1));
205
+ }
194
206
  }
195
207
  if (opts.autoStart) {
196
208
  const robotArgv = await infinicodeArgv(args);
@@ -209,6 +221,16 @@ async function setupRobot(config, opts, ctx, internal) {
209
221
  args: previewArgv.slice(1),
210
222
  });
211
223
  console.log(reg2.ok ? chalk.green(` ✓ ${reg2.message}`) : chalk.yellow(` ⚠ ${reg2.message}`));
224
+ if (visionEnabled) {
225
+ const visionArgv = [process.execPath, process.argv[1], ...visionAgentArgs, '--foreground'];
226
+ const reg3 = await registerAutoStart({
227
+ role: 'robot-vision-agent',
228
+ name: internal.name,
229
+ command: visionArgv[0],
230
+ args: visionArgv.slice(1),
231
+ });
232
+ console.log(reg3.ok ? chalk.green(` ✓ ${reg3.message}`) : chalk.yellow(` ⚠ ${reg3.message}`));
233
+ }
212
234
  }
213
235
  }
214
236
  async function setupControl(config, opts, ctx, internal) {
@@ -0,0 +1,7 @@
1
+ export interface VisionAgentOptions {
2
+ port?: string;
3
+ motionWebhookUrl?: string;
4
+ motionActive?: boolean;
5
+ foreground?: boolean;
6
+ }
7
+ export declare function roboparkVisionAgent(opts: VisionAgentOptions): Promise<void>;
@@ -0,0 +1,55 @@
1
+ /**
2
+ * RoboPark — `robopark vision-agent` launcher.
3
+ *
4
+ * Starts app_pi_clean.py (RoboVisionAI_PI's camera/motion detector) with the
5
+ * right Python interpreter, self-armed and pointed at the local
6
+ * preview-agent's motion-webhook listener by default — no manual curl calls.
7
+ */
8
+ import { spawn } from 'node:child_process';
9
+ import chalk from 'chalk';
10
+ import { findPython, findVisionPath, ensurePythonDeps, VISION_REQUIRED_MODULES } from './python-env.js';
11
+ const DEFAULT_VISION_PORT = 5000;
12
+ const DEFAULT_MOTION_WEBHOOK_PORT = 5057; // must match preview-agent's --vision-webhook-port default
13
+ function isPiArm() {
14
+ return process.platform === 'linux' && (process.arch === 'arm' || process.arch === 'arm64');
15
+ }
16
+ export async function roboparkVisionAgent(opts) {
17
+ const script = await findVisionPath();
18
+ if (!script) {
19
+ console.log(chalk.red(' ✗ could not find app_pi_clean.py'));
20
+ process.exit(1);
21
+ }
22
+ const python = findPython();
23
+ // Real Pi: opencv comes from `apt install python3-opencv`, not pip (see
24
+ // requirements_pi_unified.txt) — pip-installing it here would fight the
25
+ // ARM-optimized system build. Everywhere else: plain pip install works.
26
+ const reqFile = isPiArm() ? 'requirements_pi_unified.txt' : 'requirements-demo.txt';
27
+ if (!ensurePythonDeps(python, script, VISION_REQUIRED_MODULES, reqFile))
28
+ process.exit(1);
29
+ const port = opts.port ?? String(DEFAULT_VISION_PORT);
30
+ const webhookUrl = opts.motionWebhookUrl ?? `http://127.0.0.1:${DEFAULT_MOTION_WEBHOOK_PORT}/`;
31
+ const motionActive = opts.motionActive ?? true; // armed by default — "vision trigger as production system"
32
+ const args = [script, '--port', port, '--motion-webhook-url', webhookUrl];
33
+ if (motionActive)
34
+ args.push('--motion-active');
35
+ console.log(chalk.bold('\n robopark vision-agent'));
36
+ console.log(chalk.dim(' ' + '─'.repeat(52)));
37
+ console.log(` port: ${chalk.cyan(port)}`);
38
+ console.log(` webhook: ${chalk.cyan(webhookUrl)}`);
39
+ console.log(` motion: ${motionActive ? chalk.green('armed') : chalk.dim('off')}`);
40
+ console.log();
41
+ if (opts.foreground) {
42
+ const proc = spawn(python, args, { stdio: 'inherit' });
43
+ await new Promise((resolve) => {
44
+ proc.on('close', () => resolve());
45
+ });
46
+ return;
47
+ }
48
+ const proc = spawn(python, args, {
49
+ stdio: 'ignore',
50
+ detached: true,
51
+ env: { ...process.env, ROBOPARK_VISION_AGENT: '1' },
52
+ });
53
+ proc.unref();
54
+ console.log(chalk.green(' ✓ vision agent started in background'));
55
+ }
@@ -108,6 +108,17 @@ program
108
108
  const { roboparkPreviewAgent } = await import('./robopark/preview-agent-launcher.js');
109
109
  await roboparkPreviewAgent(opts);
110
110
  });
111
+ program
112
+ .command('vision-agent')
113
+ .description('Start the robot-side camera/motion detector (RoboVisionAI_PI) — self-arms and wires to preview-agent\'s vision webhook')
114
+ .option('--port <port>', 'vision server port', '5000')
115
+ .option('--motion-webhook-url <url>', 'where to POST on motion (defaults to the local preview-agent\'s vision webhook, http://127.0.0.1:5057/)')
116
+ .option('--no-motion-active', 'do not arm motion detection on startup (armed by default)')
117
+ .option('--foreground', 'stay in foreground; do not detach')
118
+ .action(async (opts) => {
119
+ const { roboparkVisionAgent } = await import('./robopark/vision-agent-launcher.js');
120
+ await roboparkVisionAgent(opts);
121
+ });
111
122
  program
112
123
  .command('setup')
113
124
  .description('Set up this device in one pass')
@@ -128,6 +139,7 @@ program
128
139
  .option('--enrollment-token <token>', 'one-time scheduler enrollment token for robots')
129
140
  .option('--video-device <dev>', 'default camera for robot preview agent', 'auto')
130
141
  .option('--audio-device <dev>', 'default microphone for robot preview agent', 'default')
142
+ .option('--no-vision', 'do not start the vision (camera/motion trigger) agent for a robot — armed by default')
131
143
  .option('--start', 'launch the node now')
132
144
  .option('--auto-start', 'register to start on boot')
133
145
  .option('--yes', 'non-interactive mode')
@@ -152,6 +164,7 @@ program
152
164
  enrollmentToken: opts.enrollmentToken,
153
165
  videoDevice: opts.videoDevice,
154
166
  audioDevice: opts.audioDevice,
167
+ vision: opts.vision,
155
168
  start: opts.start,
156
169
  autoStart: opts.autoStart,
157
170
  yes: opts.yes,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "infinicode",
3
- "version": "2.8.15",
3
+ "version": "2.8.16",
4
4
  "description": "OpenKernel — provider-agnostic AI execution kernel. Native coding agent + mission-driven execution runtime.",
5
5
  "type": "module",
6
6
  "main": "./dist/kernel/index.js",
@@ -21,11 +21,14 @@ presence-triggered session (`POST /api/devices/{id}/request-session`),
21
21
  publishing camera + mic into the resulting LiveKit room. Point RoboVision at
22
22
  it:
23
23
 
24
+ Both of these calls go to RoboVision's own server (port 5000 by default —
25
+ NOT the scheduler on 8080):
26
+
24
27
  ```
25
- curl -X POST http://localhost:8080/api/motion/webhook \
28
+ curl -X POST http://localhost:5000/api/motion/webhook \
26
29
  -H "Content-Type: application/json" \
27
30
  -d '{"url": "http://localhost:5057/"}'
28
- curl -X POST http://localhost:8080/api/motion/toggle \
31
+ curl -X POST http://localhost:5000/api/motion/toggle \
29
32
  -H "Content-Type: application/json" -d '{"active": true}'
30
33
  ```
31
34
 
@@ -1,6 +1,8 @@
1
1
  from flask import Flask, Response, jsonify, request
2
2
  from flask_cors import CORS
3
+ import argparse
3
4
  import cv2
5
+ import os
4
6
  import time
5
7
  import numpy as np
6
8
  import threading
@@ -299,9 +301,26 @@ def motion_webhook():
299
301
  })
300
302
 
301
303
  if __name__ == '__main__':
304
+ parser = argparse.ArgumentParser(description="RoboVision — camera/motion detection server")
305
+ parser.add_argument("--port", type=int, default=int(os.getenv("VISION_PORT", "5000")))
306
+ parser.add_argument("--motion-webhook-url", default=os.getenv("MOTION_WEBHOOK_URL", ""),
307
+ help="where to POST a snapshot when motion is detected, e.g. http://localhost:5057/")
308
+ parser.add_argument("--motion-active", action="store_true",
309
+ default=os.getenv("MOTION_ACTIVE", "").lower() in ("1", "true", "yes"),
310
+ help="arm motion detection immediately on startup (no manual /api/motion/toggle call needed)")
311
+ args = parser.parse_args()
312
+
313
+ if args.motion_webhook_url:
314
+ webhook_url = args.motion_webhook_url
315
+ if args.motion_active:
316
+ motion_detection_active = True
317
+
302
318
  print("=" * 60)
303
319
  print("RoboVision - Raspberry Pi Vision Server (Minimal)")
304
320
  print("=" * 60)
305
- print("Starting Flask server on http://0.0.0.0:5000")
321
+ print(f"Starting Flask server on http://0.0.0.0:{args.port}")
322
+ if webhook_url:
323
+ print(f"Motion webhook: {webhook_url}")
324
+ print(f"Motion detection: {'ARMED' if motion_detection_active else 'off (POST /api/motion/toggle to arm)'}")
306
325
  print("=" * 60)
307
- app.run(host='0.0.0.0', port=5000, debug=False, threaded=True)
326
+ app.run(host='0.0.0.0', port=args.port, debug=False, threaded=True)
@@ -0,0 +1,12 @@
1
+ # Non-Pi (Windows/macOS/Linux dev laptop) requirements for app_pi_clean.py.
2
+ #
3
+ # requirements_pi_unified.txt deliberately excludes opencv-python (Pi installs
4
+ # it via `apt install python3-opencv` for the ARM-optimized system build) —
5
+ # that's wrong off a real Pi, where plain `pip install opencv-python` is the
6
+ # normal path. Use this file for a demo/dev machine; use
7
+ # requirements_pi_unified.txt (+ apt) on an actual Raspberry Pi.
8
+ flask==3.0.0
9
+ flask-cors==4.0.0
10
+ opencv-python>=4.8
11
+ numpy>=1.24,<2
12
+ requests>=2.31