instar 0.8.0 → 0.8.2

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/README.md CHANGED
@@ -165,7 +165,7 @@ Each session is a **real Claude Code process** with extended thinking, native to
165
165
 
166
166
  ## Why Instar (vs OpenClaw)
167
167
 
168
- If you're coming from OpenClaw, NanoClaw, or similar projects broken by Anthropic's OAuth policy change -- Instar is architecturally different.
168
+ If you're coming from OpenClaw, NanoClaw, or similar projects affected by Anthropic's OAuth policy change -- Instar is architecturally different.
169
169
 
170
170
  ### ToS-compliant by design
171
171
 
@@ -178,8 +178,8 @@ Anthropic's policy: OAuth tokens are for Claude Code and claude.ai only. Project
178
178
  | | OpenClaw | Instar |
179
179
  |---|---|---|
180
180
  | **What it is** | AI assistant framework | Autonomy infrastructure |
181
- | **Runtime** | Pi SDK (API wrapper) | Claude Code (full dev environment) |
182
- | **Sessions** | Single gateway | Multiple parallel Claude Code instances |
181
+ | **Runtime** | Pi SDK (embedded agent) | Claude Code (full dev environment) |
182
+ | **Sessions** | Multi-session gateway | Multiple parallel Claude Code instances |
183
183
  | **Identity** | SOUL.md (file) | Multi-file + behavioral hooks + CLAUDE.md instructions |
184
184
  | **Memory** | Hybrid vector search | Relationship-centric (cross-platform, significance) |
185
185
  | **Messaging** | 20+ channels | Telegram (Slack/Discord planned) |
@@ -187,7 +187,7 @@ Anthropic's policy: OAuth tokens are for Claude Code and claude.ai only. Project
187
187
  | **Device apps** | macOS, Android, iOS (preview) | -- |
188
188
  | **Sandbox** | Docker 3×3 matrix | Dangerous command guards |
189
189
  | **Self-evolution** | Workspace file updates | Full infrastructure self-modification |
190
- | **ToS status** | OAuth extraction (restricted) | Spawns real Claude Code (compliant) |
190
+ | **ToS status** | API keys + OAuth (OAuth path restricted) | Spawns real Claude Code (compliant) |
191
191
 
192
192
  **OpenClaw optimizes for ubiquity** -- AI across every messaging platform. **Instar optimizes for autonomy** -- an agent that runs, remembers, grows, and evolves.
193
193
 
@@ -209,7 +209,7 @@ Some claims are less proven: iOS app is "internal preview." Voice wake docs retu
209
209
 
210
210
  **Self-evolution.** The agent modifies its own jobs, hooks, skills, config, and infrastructure. Not just workspace files -- the system itself.
211
211
 
212
- Different tools for different needs. But only one of them works today.
212
+ Different tools for different needs. Different bets on different futures.
213
213
 
214
214
  > Full comparison: [positioning-vs-openclaw.md](docs/positioning-vs-openclaw.md)
215
215
 
package/dist/cli.js CHANGED
File without changes
@@ -37,7 +37,6 @@ import { AccountSwitcher } from '../monitoring/AccountSwitcher.js';
37
37
  import { QuotaNotifier } from '../monitoring/QuotaNotifier.js';
38
38
  import { classifySessionDeath } from '../monitoring/QuotaExhaustionDetector.js';
39
39
  import { SessionWatchdog } from '../monitoring/SessionWatchdog.js';
40
- import { installAutoStart } from './setup.js';
41
40
  /**
42
41
  * Check if autostart is installed for this project.
43
42
  * Extracted from the CLI `autostart status` handler for programmatic use.
@@ -598,12 +597,16 @@ export async function startServer(options) {
598
597
  scheduler.start();
599
598
  console.log(pc.green(' Scheduler started'));
600
599
  }
601
- // Set up Telegram if configured (skip if lifeline owns the connection)
600
+ // Set up Telegram if configured
601
+ // When --no-telegram is set (lifeline owns polling), create adapter in send-only mode
602
+ // so the server can still relay replies via /telegram/reply/:topicId
602
603
  let telegram;
603
604
  const telegramConfig = config.messaging.find(m => m.type === 'telegram' && m.enabled);
604
605
  const skipTelegram = options.telegram === false; // --no-telegram sets telegram: false
605
606
  if (skipTelegram && telegramConfig) {
606
- console.log(pc.dim(' Telegram polling skipped (--no-telegram flag)'));
607
+ // Send-only mode: no polling, but sendToTopic() works for session replies
608
+ telegram = new TelegramAdapter(telegramConfig.config, config.stateDir);
609
+ console.log(pc.green(' Telegram send-only mode (lifeline owns polling)'));
607
610
  }
608
611
  if (telegramConfig && !skipTelegram) {
609
612
  telegram = new TelegramAdapter(telegramConfig.config, config.stateDir);
@@ -847,6 +850,7 @@ export async function startServer(options) {
847
850
  const hasTelegram = !!telegram;
848
851
  const autostartInstalled = isAutostartInstalled(config.projectName);
849
852
  if (!autostartInstalled) {
853
+ const { installAutoStart } = await import('./setup.js');
850
854
  const installed = installAutoStart(config.projectName, config.projectDir, hasTelegram);
851
855
  if (installed) {
852
856
  console.log(pc.green(` Auto-start self-healed: installed ${process.platform === 'darwin' ? 'LaunchAgent' : 'systemd service'}`));
@@ -9,7 +9,7 @@
9
9
  * Uses EventEmitter pattern consistent with Instar conventions.
10
10
  */
11
11
  import { EventEmitter } from 'node:events';
12
- import { spawn, execSync } from 'child_process';
12
+ import { spawn, spawnSync } from 'child_process';
13
13
  import * as fs from 'fs';
14
14
  import * as path from 'path';
15
15
  const WATCHDOG_INTERVAL_MS = 30_000; // 30 seconds
@@ -135,10 +135,10 @@ export class CaffeinateManager extends EventEmitter {
135
135
  const stalePid = parseInt(fs.readFileSync(this.pidFile, 'utf-8').trim(), 10);
136
136
  if (!isNaN(stalePid) && stalePid > 0) {
137
137
  try {
138
- const cmdline = execSync(`ps -p ${stalePid} -o comm= 2>/dev/null`, {
138
+ const cmdline = (spawnSync('ps', ['-p', String(stalePid), '-o', 'comm='], {
139
139
  encoding: 'utf-8',
140
140
  timeout: 3000,
141
- }).trim();
141
+ }).stdout ?? '').trim();
142
142
  if (cmdline.includes('caffeinate')) {
143
143
  process.kill(stalePid, 'SIGTERM');
144
144
  console.log(`[CaffeinateManager] Killed stale caffeinate (PID: ${stalePid})`);
@@ -13,8 +13,9 @@
13
13
  * Includes trend tracking via ring buffer + linear regression.
14
14
  */
15
15
  import { EventEmitter } from 'node:events';
16
- import { execSync } from 'node:child_process';
16
+ import { spawnSync } from 'node:child_process';
17
17
  import * as fs from 'node:fs';
18
+ import os from 'node:os';
18
19
  const DEFAULT_THRESHOLDS = {
19
20
  warning: 60,
20
21
  elevated: 75,
@@ -156,7 +157,7 @@ export class MemoryPressureMonitor extends EventEmitter {
156
157
  else {
157
158
  // Fallback: use Node's process.memoryUsage (very rough)
158
159
  const mem = process.memoryUsage();
159
- const totalGB = require('os').totalmem() / (1024 ** 3);
160
+ const totalGB = os.totalmem() / (1024 ** 3);
160
161
  const usedGB = mem.rss / (1024 ** 3);
161
162
  return {
162
163
  pressurePercent: (usedGB / totalGB) * 100,
@@ -169,7 +170,7 @@ export class MemoryPressureMonitor extends EventEmitter {
169
170
  * macOS: parse vm_stat
170
171
  */
171
172
  parseVmStat() {
172
- const output = execSync('vm_stat', { encoding: 'utf-8', timeout: 5000 });
173
+ const output = spawnSync('vm_stat', [], { encoding: 'utf-8', timeout: 5000 }).stdout ?? '';
173
174
  const pageSizeMatch = output.match(/page size of (\d+) bytes/);
174
175
  const pageSize = pageSizeMatch ? parseInt(pageSizeMatch[1], 10) : PAGE_SIZE_BYTES;
175
176
  const parsePages = (label) => {
@@ -12,8 +12,12 @@
12
12
  * Level 3: SIGKILL the stuck child PID
13
13
  * Level 4: Kill tmux session
14
14
  */
15
- import { execSync } from 'node:child_process';
15
+ import { spawnSync } from 'node:child_process';
16
16
  import { EventEmitter } from 'node:events';
17
+ /** Drop-in replacement for execSync that avoids its security concerns. */
18
+ function shellExec(cmd, timeout = 5000) {
19
+ return spawnSync('/bin/sh', ['-c', cmd], { encoding: 'utf-8', timeout }).stdout ?? '';
20
+ }
17
21
  export var EscalationLevel;
18
22
  (function (EscalationLevel) {
19
23
  EscalationLevel[EscalationLevel["Monitoring"] = 0] = "Monitoring";
@@ -205,14 +209,14 @@ export class SessionWatchdog extends EventEmitter {
205
209
  getClaudePid(tmuxSession) {
206
210
  try {
207
211
  // Get pane PID
208
- const panePidStr = execSync(`${this.config.sessions.tmuxPath} list-panes -t "=${tmuxSession}" -F "#{pane_pid}" 2>/dev/null`, { encoding: 'utf-8', timeout: 5000 }).trim().split('\n')[0];
212
+ const panePidStr = shellExec(`${this.config.sessions.tmuxPath} list-panes -t "=${tmuxSession}" -F "#{pane_pid}" 2>/dev/null`).trim().split('\n')[0];
209
213
  if (!panePidStr)
210
214
  return null;
211
215
  const panePid = parseInt(panePidStr, 10);
212
216
  if (isNaN(panePid))
213
217
  return null;
214
218
  // Find claude child
215
- const claudePidStr = execSync(`pgrep -P ${panePid} -f claude 2>/dev/null | head -1`, { encoding: 'utf-8', timeout: 5000 }).trim();
219
+ const claudePidStr = shellExec(`pgrep -P ${panePid} -f claude 2>/dev/null | head -1`).trim();
216
220
  if (!claudePidStr)
217
221
  return null;
218
222
  const pid = parseInt(claudePidStr, 10);
@@ -224,13 +228,13 @@ export class SessionWatchdog extends EventEmitter {
224
228
  }
225
229
  getChildProcesses(pid) {
226
230
  try {
227
- const childPidsStr = execSync(`pgrep -P ${pid} 2>/dev/null`, { encoding: 'utf-8', timeout: 5000 }).trim();
231
+ const childPidsStr = shellExec(`pgrep -P ${pid} 2>/dev/null`).trim();
228
232
  if (!childPidsStr)
229
233
  return [];
230
234
  const childPids = childPidsStr.split('\n').filter(Boolean).join(',');
231
235
  if (!childPids)
232
236
  return [];
233
- const output = execSync(`ps -o pid=,etime=,command= -p ${childPids} 2>/dev/null`, { encoding: 'utf-8', timeout: 5000 }).trim();
237
+ const output = shellExec(`ps -o pid=,etime=,command= -p ${childPids} 2>/dev/null`).trim();
234
238
  if (!output)
235
239
  return [];
236
240
  const results = [];
@@ -303,7 +307,7 @@ export class SessionWatchdog extends EventEmitter {
303
307
  }
304
308
  killTmuxSession(tmuxSession) {
305
309
  try {
306
- execSync(`${this.config.sessions.tmuxPath} kill-session -t "=${tmuxSession}" 2>/dev/null`, { timeout: 5000, stdio: 'ignore' });
310
+ shellExec(`${this.config.sessions.tmuxPath} kill-session -t "=${tmuxSession}" 2>/dev/null`);
307
311
  }
308
312
  catch { }
309
313
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "instar",
3
- "version": "0.8.0",
3
+ "version": "0.8.2",
4
4
  "description": "Persistent autonomy infrastructure for AI agents",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -1,11 +0,0 @@
1
- > Why do I have a folder named ".vercel" in my project?
2
- The ".vercel" folder is created when you link a directory to a Vercel project.
3
-
4
- > What does the "project.json" file contain?
5
- The "project.json" file contains:
6
- - The ID of the Vercel project that you linked ("projectId")
7
- - The ID of the user or team your Vercel project is owned by ("orgId")
8
-
9
- > Should I commit the ".vercel" folder?
10
- No, you should not share the ".vercel" folder with anyone.
11
- Upon creation, it will be automatically added to your ".gitignore" file.
@@ -1 +0,0 @@
1
- {"projectId":"prj_evM5LcItYL3IAmw8zNvEPGrHeaya","orgId":"team_dHctwIDcV3X9ydapQlCPHFGI","projectName":"claude-agent-kit"}