instar 0.7.21 → 0.7.22

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.
@@ -250,6 +250,31 @@ This project uses instar for persistent agent capabilities.
250
250
  - **Research before escalating** — Check tools first. Build solutions. "Needs human" is last resort.
251
251
  ```
252
252
 
253
+ **If Telegram was configured**, also add a Telegram Relay section to CLAUDE.md:
254
+
255
+ ```markdown
256
+ ## Telegram Relay
257
+
258
+ When user input starts with `[telegram:N]` (e.g., `[telegram:26] hello`), the message came via Telegram topic N.
259
+
260
+ **IMMEDIATE ACKNOWLEDGMENT (MANDATORY):** When you receive a Telegram message, your FIRST action must be sending a brief acknowledgment back. This confirms the message was received. Examples: "Got it, looking into this now." / "On it." Then do the work, then send the full response.
261
+
262
+ **Response relay:** After completing your work, relay your response back:
263
+
264
+ \`\`\`bash
265
+ cat <<'EOF' | .claude/scripts/telegram-reply.sh N
266
+ Your response text here
267
+ EOF
268
+ \`\`\`
269
+
270
+ Or for short messages:
271
+ \`\`\`bash
272
+ .claude/scripts/telegram-reply.sh N "Your response text here"
273
+ \`\`\`
274
+
275
+ Strip the `[telegram:N]` prefix before interpreting the message. Respond naturally, then relay. Only relay your conversational text — not tool output or internal reasoning.
276
+ ```
277
+
253
278
  ## Phase 3: Telegram Setup — The Destination
254
279
 
255
280
  **Telegram comes BEFORE technical configuration.** It's the whole point — everything else supports getting the user onto Telegram.
@@ -557,7 +582,7 @@ Create the directory structure and write config files:
557
582
  mkdir -p .instar/state/sessions .instar/state/jobs .instar/logs
558
583
  ```
559
584
 
560
- **`.instar/config.json`**:
585
+ **`.instar/config.json`** (messaging section shown with Telegram — use `"messaging": []` if Telegram was not configured):
561
586
  ```json
562
587
  {
563
588
  "projectName": "my-project",
@@ -581,7 +606,19 @@ mkdir -p .instar/state/sessions .instar/state/jobs .instar/logs
581
606
  "quotaThresholds": { "normal": 50, "elevated": 70, "critical": 85, "shutdown": 95 }
582
607
  },
583
608
  "users": [],
584
- "messaging": [],
609
+ "messaging": [
610
+ {
611
+ "type": "telegram",
612
+ "enabled": true,
613
+ "config": {
614
+ "token": "<BOT_TOKEN from BotFather>",
615
+ "chatId": "<CHAT_ID from Step 3e>",
616
+ "lifelineTopicId": "<LIFELINE_THREAD_ID from Step 3e>",
617
+ "pollIntervalMs": 2000,
618
+ "stallTimeoutMinutes": 5
619
+ }
620
+ }
621
+ ],
585
622
  "monitoring": {
586
623
  "quotaTracking": false,
587
624
  "memoryMonitoring": true,
@@ -603,6 +640,63 @@ Append if not present:
603
640
  .instar/logs/
604
641
  ```
605
642
 
643
+ ### 4f. Install Telegram Relay Script (if Telegram configured)
644
+
645
+ If Telegram was set up in Phase 3, install the relay script that lets Claude sessions send messages back to Telegram:
646
+
647
+ ```bash
648
+ mkdir -p .claude/scripts
649
+ ```
650
+
651
+ Write `.claude/scripts/telegram-reply.sh`:
652
+
653
+ ```bash
654
+ #!/bin/bash
655
+ # telegram-reply.sh — Send a message back to a Telegram topic via instar server.
656
+ #
657
+ # Usage:
658
+ # .claude/scripts/telegram-reply.sh TOPIC_ID "message text"
659
+ # echo "message text" | .claude/scripts/telegram-reply.sh TOPIC_ID
660
+ # cat <<'EOF' | .claude/scripts/telegram-reply.sh TOPIC_ID
661
+ # Multi-line message here
662
+ # EOF
663
+
664
+ TOPIC_ID=$1
665
+ shift
666
+
667
+ if [ -z "$TOPIC_ID" ]; then
668
+ echo "Usage: telegram-reply.sh TOPIC_ID [message]" >&2
669
+ exit 1
670
+ fi
671
+
672
+ PORT=<PORT>
673
+
674
+ # Get message from args or stdin
675
+ if [ $# -gt 0 ]; then
676
+ MESSAGE="$*"
677
+ else
678
+ MESSAGE=$(cat)
679
+ fi
680
+
681
+ if [ -z "$MESSAGE" ]; then
682
+ echo "No message provided" >&2
683
+ exit 1
684
+ fi
685
+
686
+ # Send via instar server API
687
+ curl -s -X POST "http://localhost:${PORT}/telegram/topic/${TOPIC_ID}/send" \
688
+ -H 'Content-Type: application/json' \
689
+ -d "$(jq -n --arg text "$MESSAGE" '{text: $text}')" > /dev/null 2>&1
690
+
691
+ echo "Sent $(echo "$MESSAGE" | wc -c | tr -d ' ') chars to topic $TOPIC_ID"
692
+ ```
693
+
694
+ Replace `<PORT>` with the actual server port. Then make it executable:
695
+
696
+ ```bash
697
+ chmod +x .claude/scripts/telegram-reply.sh
698
+ ```
699
+
606
700
  ## Phase 5: Launch & Handoff
607
701
 
608
702
  **Do NOT ask "want me to start the server?" — just start it.** There is no reason not to. The whole point of setup is to get the agent running.
@@ -127,20 +127,24 @@ export class SessionManager extends EventEmitter {
127
127
  if (this.tmuxSessionExists(tmuxSession)) {
128
128
  throw new Error(`tmux session "${tmuxSession}" already exists`);
129
129
  }
130
- // Build Claude CLI arguments no shell intermediary.
131
- // tmux new-session executes the command directly (no bash -c needed)
132
- // when given as separate arguments after the session options.
130
+ // Build Claude CLI arguments via bash wrapper.
131
+ // Must unset CLAUDECODE to prevent "cannot be launched inside another Claude Code session"
132
+ // error when instar itself runs inside Claude Code.
133
133
  const claudeArgs = ['--dangerously-skip-permissions'];
134
134
  if (options.model) {
135
135
  claudeArgs.push('--model', options.model);
136
136
  }
137
137
  claudeArgs.push('-p', options.prompt);
138
+ const claudeCmd = [this.config.claudePath, ...claudeArgs]
139
+ .map(a => a.replace(/'/g, "'\\''"))
140
+ .map(a => `'${a}'`)
141
+ .join(' ');
138
142
  try {
139
143
  execFileSync(this.config.tmuxPath, [
140
144
  'new-session', '-d',
141
145
  '-s', tmuxSession,
142
146
  '-c', this.config.projectDir,
143
- this.config.claudePath, ...claudeArgs,
147
+ 'bash', '-c', `unset CLAUDECODE; exec ${claudeCmd}`,
144
148
  ], { encoding: 'utf-8' });
145
149
  }
146
150
  catch (err) {
@@ -371,11 +375,14 @@ export class SessionManager extends EventEmitter {
371
375
  ];
372
376
  if (options?.telegramTopicId) {
373
377
  // Wrap in bash shell to export env var before Claude starts
378
+ // Also unset CLAUDECODE to prevent nested Claude Code errors
374
379
  const claudeCmd = `${this.config.claudePath} --dangerously-skip-permissions`;
375
- tmuxArgs.push('bash', '-c', `export INSTAR_TELEGRAM_TOPIC=${options.telegramTopicId} && exec ${claudeCmd}`);
380
+ tmuxArgs.push('bash', '-c', `unset CLAUDECODE; export INSTAR_TELEGRAM_TOPIC=${options.telegramTopicId} && exec ${claudeCmd}`);
376
381
  }
377
382
  else {
378
- tmuxArgs.push(this.config.claudePath, '--dangerously-skip-permissions');
383
+ // Unset CLAUDECODE to prevent nested Claude Code errors
384
+ const claudeCmd = `${this.config.claudePath} --dangerously-skip-permissions`;
385
+ tmuxArgs.push('bash', '-c', `unset CLAUDECODE; exec ${claudeCmd}`);
379
386
  }
380
387
  execFileSync(this.config.tmuxPath, tmuxArgs, { encoding: 'utf-8' });
381
388
  }
@@ -406,6 +406,12 @@ export class JobScheduler {
406
406
  else {
407
407
  summary += '\n_No output captured (session already closed)_';
408
408
  }
409
+ // Skip Telegram notification for successful jobs with no meaningful output
410
+ // Prevents empty notification spam (e.g., dispatch-check when dispatch is unconfigured)
411
+ if (!failed && (!output || !output.trim())) {
412
+ console.log(`[scheduler] Skipping notification for ${job.slug} — no meaningful output`);
413
+ return;
414
+ }
409
415
  // Send to the job's dedicated topic if available, otherwise fall back to generic messenger
410
416
  if (this.telegram && job.topicId) {
411
417
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "instar",
3
- "version": "0.7.21",
3
+ "version": "0.7.22",
4
4
  "description": "Persistent autonomy infrastructure for AI agents",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",