infinicode 2.8.10 → 2.8.11

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.
@@ -9,6 +9,7 @@ import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
9
9
  import { homedir, hostname } from 'node:os';
10
10
  import { join } from 'node:path';
11
11
  import chalk from 'chalk';
12
+ import { findPython, findSchedulerPath, ensurePythonDeps } from './python-env.js';
12
13
  const ROBOPARK_DIR = join(homedir(), '.robopark');
13
14
  const ENROLLMENT_FILE = join(ROBOPARK_DIR, 'enrollment_token');
14
15
  function ensureDir() {
@@ -24,42 +25,6 @@ function saveEnrollmentToken(token) {
24
25
  ensureDir();
25
26
  writeFileSync(ENROLLMENT_FILE, token, { mode: 0o600 });
26
27
  }
27
- async function findSchedulerPath() {
28
- const candidates = [
29
- join(process.cwd(), 'packages', 'robopark', 'scheduler', 'preview_agent.py'),
30
- join(process.cwd(), 'scheduler', 'preview_agent.py'),
31
- ];
32
- for (const p of candidates) {
33
- if (existsSync(p))
34
- return p;
35
- }
36
- try {
37
- const { createRequire } = await import('node:module');
38
- const require = createRequire(import.meta.url);
39
- const pkgMain = require.resolve('infinicode/package.json');
40
- const p = join(pkgMain, '..', 'packages', 'robopark', 'scheduler', 'preview_agent.py');
41
- if (existsSync(p))
42
- return p;
43
- }
44
- catch { /* ignore */ }
45
- return null;
46
- }
47
- function findPython() {
48
- const names = process.platform === 'win32'
49
- ? ['python', 'python3', 'py']
50
- : ['python3', 'python'];
51
- for (const name of names) {
52
- try {
53
- const { execSync } = require('node:child_process');
54
- execSync(`${name} --version`, { stdio: 'ignore' });
55
- return name;
56
- }
57
- catch {
58
- continue;
59
- }
60
- }
61
- return process.platform === 'win32' ? 'python' : 'python3';
62
- }
63
28
  export async function roboparkEnroll(opts) {
64
29
  const token = opts.enrollmentToken ?? readEnrollmentToken();
65
30
  if (!token) {
@@ -75,6 +40,8 @@ export async function roboparkEnroll(opts) {
75
40
  process.exit(1);
76
41
  }
77
42
  const python = findPython();
43
+ if (!ensurePythonDeps(python, script))
44
+ process.exit(1);
78
45
  const args = [
79
46
  script,
80
47
  '--scheduler-url', schedulerUrl,
@@ -5,48 +5,11 @@
5
5
  * the scheduler URL from mesh/hub, and detaches unless --foreground.
6
6
  */
7
7
  import { spawn } from 'node:child_process';
8
- import { existsSync } from 'node:fs';
9
8
  import { hostname } from 'node:os';
10
- import { join } from 'node:path';
11
9
  import chalk from 'chalk';
12
10
  import { discoverContext } from './discovery.js';
11
+ import { findPython, findSchedulerPath, ensurePythonDeps } from './python-env.js';
13
12
  const DEFAULT_SCHEDULER_PORT = 8080;
14
- async function findSchedulerPath() {
15
- const candidates = [
16
- join(process.cwd(), 'packages', 'robopark', 'scheduler', 'preview_agent.py'),
17
- join(process.cwd(), 'scheduler', 'preview_agent.py'),
18
- ];
19
- for (const p of candidates) {
20
- if (existsSync(p))
21
- return p;
22
- }
23
- try {
24
- const { createRequire } = await import('node:module');
25
- const require = createRequire(import.meta.url);
26
- const pkgMain = require.resolve('infinicode/package.json');
27
- const p = join(pkgMain, '..', 'packages', 'robopark', 'scheduler', 'preview_agent.py');
28
- if (existsSync(p))
29
- return p;
30
- }
31
- catch { /* ignore */ }
32
- return null;
33
- }
34
- function findPython() {
35
- const names = process.platform === 'win32'
36
- ? ['python', 'python3', 'py']
37
- : ['python3', 'python'];
38
- for (const name of names) {
39
- try {
40
- const { execSync } = require('node:child_process');
41
- execSync(`${name} --version`, { stdio: 'ignore' });
42
- return name;
43
- }
44
- catch {
45
- continue;
46
- }
47
- }
48
- return process.platform === 'win32' ? 'python' : 'python3';
49
- }
50
13
  export async function roboparkPreviewAgent(opts) {
51
14
  const ctx = await discoverContext();
52
15
  const port = DEFAULT_SCHEDULER_PORT;
@@ -62,6 +25,8 @@ export async function roboparkPreviewAgent(opts) {
62
25
  process.exit(1);
63
26
  }
64
27
  const python = findPython();
28
+ if (!ensurePythonDeps(python, script))
29
+ process.exit(1);
65
30
  const args = [
66
31
  script,
67
32
  '--scheduler-url', schedulerUrl,
@@ -0,0 +1,9 @@
1
+ export declare function findSchedulerPath(): Promise<string | null>;
2
+ export declare function findPython(): string;
3
+ /**
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
+ * attempt, so callers can bail out before hitting a raw traceback.
8
+ */
9
+ export declare function ensurePythonDeps(python: string, scriptPath: string): boolean;
@@ -0,0 +1,79 @@
1
+ /**
2
+ * RoboPark — shared Python environment helpers for `enroll` and
3
+ * `preview-agent`, which both spawn `preview_agent.py`.
4
+ */
5
+ import { execSync, spawnSync } from 'node:child_process';
6
+ import { existsSync } from 'node:fs';
7
+ import { join } from 'node:path';
8
+ import chalk from 'chalk';
9
+ /** Modules preview_agent.py imports — kept in sync with requirements-robot.txt. */
10
+ const REQUIRED_MODULES = ['httpx', 'cv2', 'pyaudio', 'livekit'];
11
+ export async function findSchedulerPath() {
12
+ const candidates = [
13
+ join(process.cwd(), 'packages', 'robopark', 'scheduler', 'preview_agent.py'),
14
+ join(process.cwd(), 'scheduler', 'preview_agent.py'),
15
+ ];
16
+ for (const p of candidates) {
17
+ if (existsSync(p))
18
+ return p;
19
+ }
20
+ try {
21
+ const { createRequire } = await import('node:module');
22
+ const require = createRequire(import.meta.url);
23
+ const pkgMain = require.resolve('infinicode/package.json');
24
+ const p = join(pkgMain, '..', 'packages', 'robopark', 'scheduler', 'preview_agent.py');
25
+ if (existsSync(p))
26
+ return p;
27
+ }
28
+ catch { /* ignore */ }
29
+ return null;
30
+ }
31
+ export function findPython() {
32
+ const names = process.platform === 'win32'
33
+ ? ['python', 'python3', 'py']
34
+ : ['python3', 'python'];
35
+ for (const name of names) {
36
+ try {
37
+ execSync(`${name} --version`, { stdio: 'ignore' });
38
+ return name;
39
+ }
40
+ catch {
41
+ continue;
42
+ }
43
+ }
44
+ return process.platform === 'win32' ? 'python' : 'python3';
45
+ }
46
+ function missingModules(python) {
47
+ return REQUIRED_MODULES.filter(mod => {
48
+ const res = spawnSync(python, ['-c', `import ${mod}`], { stdio: 'ignore' });
49
+ return res.status !== 0;
50
+ });
51
+ }
52
+ /**
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
56
+ * attempt, so callers can bail out before hitting a raw traceback.
57
+ */
58
+ export function ensurePythonDeps(python, scriptPath) {
59
+ const missing = missingModules(python);
60
+ if (missing.length === 0)
61
+ return true;
62
+ const reqPath = join(scriptPath, '..', 'requirements-robot.txt');
63
+ if (!existsSync(reqPath)) {
64
+ console.log(chalk.red(` ✗ missing Python modules: ${missing.join(', ')} (and requirements-robot.txt not found next to ${scriptPath})`));
65
+ return false;
66
+ }
67
+ console.log(chalk.dim(` installing Python deps (${missing.join(', ')})…`));
68
+ const install = spawnSync(python, ['-m', 'pip', 'install', '-r', reqPath], { stdio: 'inherit' });
69
+ if (install.status !== 0) {
70
+ console.log(chalk.red(` ✗ pip install failed — install manually: ${python} -m pip install -r "${reqPath}"`));
71
+ return false;
72
+ }
73
+ const stillMissing = missingModules(python);
74
+ if (stillMissing.length > 0) {
75
+ console.log(chalk.red(` ✗ still missing after install: ${stillMissing.join(', ')} — install manually: ${python} -m pip install -r "${reqPath}"`));
76
+ return false;
77
+ }
78
+ return true;
79
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "infinicode",
3
- "version": "2.8.10",
3
+ "version": "2.8.11",
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",
@@ -142,6 +142,7 @@ class PreviewAgent:
142
142
  self.robot_id = cfg.get("robot_id", os.getenv("ROBOT_ID", _hostname()))
143
143
  self.device_token: Optional[str] = cfg.get("device_token") or os.getenv("DEVICE_TOKEN") or _load_token()
144
144
  self.device_id: Optional[str] = cfg.get("device_id")
145
+ self.enrollment_token: Optional[str] = cfg.get("enrollment_token")
145
146
  self.video_device = cfg.get("video_device", os.getenv("VIDEO_DEVICE", "auto"))
146
147
  self.audio_device = cfg.get("audio_device", os.getenv("AUDIO_DEVICE", "default"))
147
148
  self.width = int(cfg.get("video_width", os.getenv("VIDEO_WIDTH", DEFAULT_VIDEO_WIDTH)))
@@ -157,7 +158,7 @@ class PreviewAgent:
157
158
  self._publisher: Optional["LiveKitPublisher"] = None
158
159
 
159
160
  async def run(self) -> None:
160
- enrollment_token = os.getenv("ENROLLMENT_TOKEN") or cfg.get("enrollment_token")
161
+ enrollment_token = os.getenv("ENROLLMENT_TOKEN") or self.enrollment_token
161
162
  if not self.device_token and enrollment_token:
162
163
  self.device_token = await _enroll(self.scheduler_url, enrollment_token, self.robot_id)
163
164
 
@@ -0,0 +1,9 @@
1
+ # Python deps for the ROBOT/PI side only (preview_agent.py) — a subset of
2
+ # requirements.txt, which also covers the scheduler server (fastapi/uvicorn/
3
+ # aiosqlite/etc). Keeping this split means a Pi/robot install doesn't pull in
4
+ # server-only packages it will never use.
5
+ httpx==0.27.2
6
+ livekit>=0.18
7
+ opencv-python>=4.8
8
+ pyaudio>=0.2
9
+ picamera2>=0.3; platform_machine == 'aarch64' or platform_machine == 'arm64'