instar 0.7.22 → 0.7.23
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/dist/commands/init.js +7 -7
- package/dist/commands/server.js +11 -3
- package/dist/commands/setup.js +2 -2
- package/dist/core/AutoUpdater.d.ts +114 -0
- package/dist/core/AutoUpdater.js +280 -0
- package/dist/scaffold/templates.js +3 -3
- package/dist/server/AgentServer.d.ts +2 -0
- package/dist/server/AgentServer.js +1 -0
- package/dist/server/routes.d.ts +2 -0
- package/dist/server/routes.js +36 -0
- package/package.json +1 -1
package/dist/commands/init.js
CHANGED
|
@@ -594,9 +594,9 @@ Instar has a built-in feedback loop. When something isn't working, your user can
|
|
|
594
594
|
- User complains to you naturally about an issue or missing capability
|
|
595
595
|
- You route the feedback to the Instar maintainers with context
|
|
596
596
|
- A fix or improvement is published as a new npm version
|
|
597
|
-
- The
|
|
597
|
+
- The built-in auto-updater detects the new version, applies it, notifies your user, and restarts the server
|
|
598
598
|
|
|
599
|
-
**A rising tide lifts all ships** — every user's feedback improves the platform for everyone.
|
|
599
|
+
**A rising tide lifts all ships** — every user's feedback improves the platform for everyone. Updates are applied automatically.
|
|
600
600
|
|
|
601
601
|
- Report: \`curl -X POST http://localhost:${port}/feedback -d '{"issue":"description","context":"relevant logs"}'\`
|
|
602
602
|
- Check updates: \`npm outdated -g instar\`
|
|
@@ -930,7 +930,7 @@ curl -s -X POST http://localhost:${port}/feedback/retry
|
|
|
930
930
|
|
|
931
931
|
## How It Works
|
|
932
932
|
|
|
933
|
-
Your feedback is stored locally AND forwarded to the instar maintainers. When they fix the issue and publish an update,
|
|
933
|
+
Your feedback is stored locally AND forwarded to the instar maintainers. When they fix the issue and publish an update, the built-in auto-updater detects it, applies it, and restarts the server — no manual intervention needed. One agent's bug report lifts all ships.
|
|
934
934
|
|
|
935
935
|
**User feedback matters too.** When your user says "this isn't working" or "I wish I could..." — capture it with their original words. User language carries context that technical rephrasing loses.
|
|
936
936
|
`,
|
|
@@ -995,16 +995,16 @@ function getDefaultJobs(port) {
|
|
|
995
995
|
{
|
|
996
996
|
slug: 'update-check',
|
|
997
997
|
name: 'Update Check',
|
|
998
|
-
description: '
|
|
998
|
+
description: 'Legacy update check job — disabled because the built-in AutoUpdater handles updates automatically (check, apply, notify, restart). See GET /updates/auto for status.',
|
|
999
999
|
schedule: '*/30 * * * *',
|
|
1000
1000
|
priority: 'medium',
|
|
1001
1001
|
expectedDurationMinutes: 2,
|
|
1002
1002
|
model: 'haiku',
|
|
1003
|
-
enabled:
|
|
1003
|
+
enabled: false,
|
|
1004
1004
|
gate: `curl -sf http://localhost:${port}/updates 2>/dev/null | python3 -c "import sys,json; d=json.load(sys.stdin); exit(0 if d.get('updateAvailable') else 1)"`,
|
|
1005
1005
|
execute: {
|
|
1006
|
-
type: '
|
|
1007
|
-
value: `
|
|
1006
|
+
type: 'script',
|
|
1007
|
+
value: `curl -s http://localhost:${port}/updates/auto`,
|
|
1008
1008
|
},
|
|
1009
1009
|
tags: ['coherence', 'default'],
|
|
1010
1010
|
},
|
package/dist/commands/server.js
CHANGED
|
@@ -23,6 +23,7 @@ import { AnthropicIntelligenceProvider } from '../core/AnthropicIntelligenceProv
|
|
|
23
23
|
import { FeedbackManager } from '../core/FeedbackManager.js';
|
|
24
24
|
import { DispatchManager } from '../core/DispatchManager.js';
|
|
25
25
|
import { UpdateChecker } from '../core/UpdateChecker.js';
|
|
26
|
+
import { AutoUpdater } from '../core/AutoUpdater.js';
|
|
26
27
|
import { registerPort, unregisterPort, startHeartbeat } from '../core/PortRegistry.js';
|
|
27
28
|
import { TelegraphService } from '../publishing/TelegraphService.js';
|
|
28
29
|
import { PrivateViewer } from '../publishing/PrivateViewer.js';
|
|
@@ -459,16 +460,22 @@ export async function startServer(options) {
|
|
|
459
460
|
hasTelegram: config.messaging.some(m => m.type === 'telegram' && m.enabled),
|
|
460
461
|
projectName: config.projectName,
|
|
461
462
|
});
|
|
462
|
-
// Check for updates on startup
|
|
463
|
+
// Check for updates on startup (non-blocking)
|
|
463
464
|
updateChecker.check().then(info => {
|
|
464
465
|
if (info.updateAvailable) {
|
|
465
466
|
console.log(pc.yellow(` Update available: ${info.currentVersion} → ${info.latestVersion}`));
|
|
466
|
-
console.log(pc.yellow(` Run: npm update -g instar`));
|
|
467
467
|
}
|
|
468
468
|
else {
|
|
469
469
|
console.log(pc.green(` Instar ${info.currentVersion} is up to date`));
|
|
470
470
|
}
|
|
471
471
|
}).catch(() => { });
|
|
472
|
+
// Start auto-updater — periodic check + auto-apply + notify + restart
|
|
473
|
+
const autoUpdater = new AutoUpdater(updateChecker, state, config.stateDir, {
|
|
474
|
+
checkIntervalMinutes: 30,
|
|
475
|
+
autoApply: config.updates?.autoApply ?? true,
|
|
476
|
+
autoRestart: true,
|
|
477
|
+
}, telegram);
|
|
478
|
+
autoUpdater.start();
|
|
472
479
|
// Set up Telegraph publishing (auto-enabled when config exists or Telegram is configured)
|
|
473
480
|
let publisher;
|
|
474
481
|
const pubConfig = config.publishing;
|
|
@@ -503,7 +510,7 @@ export async function startServer(options) {
|
|
|
503
510
|
...(config.evolution || {}),
|
|
504
511
|
});
|
|
505
512
|
console.log(pc.green(' Evolution system enabled'));
|
|
506
|
-
const server = new AgentServer({ config, sessionManager, state, scheduler, telegram, relationships, feedback, dispatches, updateChecker, quotaTracker, publisher, viewer, tunnel, evolution });
|
|
513
|
+
const server = new AgentServer({ config, sessionManager, state, scheduler, telegram, relationships, feedback, dispatches, updateChecker, autoUpdater, quotaTracker, publisher, viewer, tunnel, evolution });
|
|
507
514
|
await server.start();
|
|
508
515
|
// Start tunnel AFTER server is listening
|
|
509
516
|
if (tunnel) {
|
|
@@ -519,6 +526,7 @@ export async function startServer(options) {
|
|
|
519
526
|
// Graceful shutdown
|
|
520
527
|
const shutdown = async () => {
|
|
521
528
|
console.log('\nShutting down...');
|
|
529
|
+
autoUpdater.stop();
|
|
522
530
|
if (tunnel)
|
|
523
531
|
await tunnel.stop();
|
|
524
532
|
stopHeartbeat();
|
package/dist/commands/setup.js
CHANGED
|
@@ -1300,9 +1300,9 @@ Instar has a built-in feedback loop — a rising tide that lifts all ships. When
|
|
|
1300
1300
|
- User complains naturally about an issue or missing capability
|
|
1301
1301
|
- Agent packages the issue with context and routes it upstream
|
|
1302
1302
|
- A fix is published as a new npm version
|
|
1303
|
-
- The
|
|
1303
|
+
- The built-in auto-updater detects the new version, applies it, notifies the user, and restarts the server
|
|
1304
1304
|
|
|
1305
|
-
Every user's feedback makes the platform better for everyone. Report issues when you encounter them.
|
|
1305
|
+
Every user's feedback makes the platform better for everyone. Report issues when you encounter them. Updates are applied automatically — check status with \`curl http://localhost:PORT/updates/auto\`.
|
|
1306
1306
|
|
|
1307
1307
|
### Self-Evolution
|
|
1308
1308
|
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auto Updater — built-in periodic update mechanism.
|
|
3
|
+
*
|
|
4
|
+
* Runs inside the server process (no Claude session needed).
|
|
5
|
+
* Periodically checks for updates, auto-applies when available,
|
|
6
|
+
* notifies via Telegram, and handles server restart.
|
|
7
|
+
*
|
|
8
|
+
* This replaces the heavyweight prompt-based update-check job.
|
|
9
|
+
* Updates should never depend on the job scheduler — they're
|
|
10
|
+
* core infrastructure that must run independently.
|
|
11
|
+
*
|
|
12
|
+
* Flow:
|
|
13
|
+
* check → apply → migrate → notify → restart
|
|
14
|
+
*
|
|
15
|
+
* Restart strategy:
|
|
16
|
+
* After npm update replaces the CLI on disk, spawn a replacement
|
|
17
|
+
* server process and exit. The new process binds to the port after
|
|
18
|
+
* the old one releases it during shutdown.
|
|
19
|
+
*/
|
|
20
|
+
import type { UpdateChecker } from './UpdateChecker.js';
|
|
21
|
+
import type { TelegramAdapter } from '../messaging/TelegramAdapter.js';
|
|
22
|
+
import type { StateManager } from './StateManager.js';
|
|
23
|
+
export interface AutoUpdaterConfig {
|
|
24
|
+
/** How often to check for updates, in minutes. Default: 30 */
|
|
25
|
+
checkIntervalMinutes?: number;
|
|
26
|
+
/** Whether to auto-apply updates. Default: true */
|
|
27
|
+
autoApply?: boolean;
|
|
28
|
+
/** Telegram topic ID for update notifications (uses Agent Attention if not set) */
|
|
29
|
+
notificationTopicId?: number;
|
|
30
|
+
/** Whether to auto-restart after applying an update. Default: true */
|
|
31
|
+
autoRestart?: boolean;
|
|
32
|
+
}
|
|
33
|
+
export interface AutoUpdaterStatus {
|
|
34
|
+
/** Whether the auto-updater is running */
|
|
35
|
+
running: boolean;
|
|
36
|
+
/** Last time we checked for updates */
|
|
37
|
+
lastCheck: string | null;
|
|
38
|
+
/** Last time we applied an update */
|
|
39
|
+
lastApply: string | null;
|
|
40
|
+
/** Current configuration */
|
|
41
|
+
config: Required<AutoUpdaterConfig>;
|
|
42
|
+
/** Any pending update that hasn't been applied yet */
|
|
43
|
+
pendingUpdate: string | null;
|
|
44
|
+
/** Last error if any */
|
|
45
|
+
lastError: string | null;
|
|
46
|
+
}
|
|
47
|
+
export declare class AutoUpdater {
|
|
48
|
+
private updateChecker;
|
|
49
|
+
private telegram;
|
|
50
|
+
private state;
|
|
51
|
+
private config;
|
|
52
|
+
private interval;
|
|
53
|
+
private lastCheck;
|
|
54
|
+
private lastApply;
|
|
55
|
+
private lastError;
|
|
56
|
+
private pendingUpdate;
|
|
57
|
+
private isApplying;
|
|
58
|
+
private stateFile;
|
|
59
|
+
constructor(updateChecker: UpdateChecker, state: StateManager, stateDir: string, config?: AutoUpdaterConfig, telegram?: TelegramAdapter | null);
|
|
60
|
+
/**
|
|
61
|
+
* Start the periodic update checker.
|
|
62
|
+
* Idempotent — calling start() when already running is a no-op.
|
|
63
|
+
*/
|
|
64
|
+
start(): void;
|
|
65
|
+
/**
|
|
66
|
+
* Stop the periodic checker.
|
|
67
|
+
*/
|
|
68
|
+
stop(): void;
|
|
69
|
+
/**
|
|
70
|
+
* Get current auto-updater status.
|
|
71
|
+
*/
|
|
72
|
+
getStatus(): AutoUpdaterStatus;
|
|
73
|
+
/**
|
|
74
|
+
* Set the Telegram adapter (may be wired after construction).
|
|
75
|
+
*/
|
|
76
|
+
setTelegram(telegram: TelegramAdapter): void;
|
|
77
|
+
/**
|
|
78
|
+
* One tick of the update loop.
|
|
79
|
+
* Check → optionally apply → notify → optionally restart.
|
|
80
|
+
*/
|
|
81
|
+
private tick;
|
|
82
|
+
/**
|
|
83
|
+
* Self-restart the server after an update.
|
|
84
|
+
*
|
|
85
|
+
* Strategy:
|
|
86
|
+
* 1. Spawn a shell that waits 2 seconds (for port release), then
|
|
87
|
+
* starts the new server version using the same CLI arguments.
|
|
88
|
+
* 2. Send SIGTERM to ourselves to trigger graceful shutdown.
|
|
89
|
+
*
|
|
90
|
+
* The 2-second delay ensures the old process has time to release
|
|
91
|
+
* the port before the new one tries to bind.
|
|
92
|
+
*
|
|
93
|
+
* If running in tmux, the replacement process inherits the PTY.
|
|
94
|
+
* If running under a process manager (launchd, systemd), the
|
|
95
|
+
* manager handles restart automatically after we exit.
|
|
96
|
+
*/
|
|
97
|
+
private selfRestart;
|
|
98
|
+
/**
|
|
99
|
+
* Send a notification via Telegram (if configured).
|
|
100
|
+
* Falls back to console logging if Telegram is not available.
|
|
101
|
+
*/
|
|
102
|
+
private notify;
|
|
103
|
+
/**
|
|
104
|
+
* Get the Agent Attention topic ID from state (where infrastructure notifications go).
|
|
105
|
+
*/
|
|
106
|
+
private getAttentionTopicId;
|
|
107
|
+
/**
|
|
108
|
+
* Get the server port from the update checker config (for notification messages).
|
|
109
|
+
*/
|
|
110
|
+
private getPort;
|
|
111
|
+
private loadState;
|
|
112
|
+
private saveState;
|
|
113
|
+
}
|
|
114
|
+
//# sourceMappingURL=AutoUpdater.d.ts.map
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auto Updater — built-in periodic update mechanism.
|
|
3
|
+
*
|
|
4
|
+
* Runs inside the server process (no Claude session needed).
|
|
5
|
+
* Periodically checks for updates, auto-applies when available,
|
|
6
|
+
* notifies via Telegram, and handles server restart.
|
|
7
|
+
*
|
|
8
|
+
* This replaces the heavyweight prompt-based update-check job.
|
|
9
|
+
* Updates should never depend on the job scheduler — they're
|
|
10
|
+
* core infrastructure that must run independently.
|
|
11
|
+
*
|
|
12
|
+
* Flow:
|
|
13
|
+
* check → apply → migrate → notify → restart
|
|
14
|
+
*
|
|
15
|
+
* Restart strategy:
|
|
16
|
+
* After npm update replaces the CLI on disk, spawn a replacement
|
|
17
|
+
* server process and exit. The new process binds to the port after
|
|
18
|
+
* the old one releases it during shutdown.
|
|
19
|
+
*/
|
|
20
|
+
import { spawn } from 'node:child_process';
|
|
21
|
+
import fs from 'node:fs';
|
|
22
|
+
import path from 'node:path';
|
|
23
|
+
export class AutoUpdater {
|
|
24
|
+
updateChecker;
|
|
25
|
+
telegram;
|
|
26
|
+
state;
|
|
27
|
+
config;
|
|
28
|
+
interval = null;
|
|
29
|
+
lastCheck = null;
|
|
30
|
+
lastApply = null;
|
|
31
|
+
lastError = null;
|
|
32
|
+
pendingUpdate = null;
|
|
33
|
+
isApplying = false;
|
|
34
|
+
stateFile;
|
|
35
|
+
constructor(updateChecker, state, stateDir, config, telegram) {
|
|
36
|
+
this.updateChecker = updateChecker;
|
|
37
|
+
this.state = state;
|
|
38
|
+
this.telegram = telegram ?? null;
|
|
39
|
+
this.stateFile = path.join(stateDir, 'state', 'auto-updater.json');
|
|
40
|
+
this.config = {
|
|
41
|
+
checkIntervalMinutes: config?.checkIntervalMinutes ?? 30,
|
|
42
|
+
autoApply: config?.autoApply ?? true,
|
|
43
|
+
autoRestart: config?.autoRestart ?? true,
|
|
44
|
+
notificationTopicId: config?.notificationTopicId ?? 0,
|
|
45
|
+
};
|
|
46
|
+
// Load persisted state (survives restarts)
|
|
47
|
+
this.loadState();
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Start the periodic update checker.
|
|
51
|
+
* Idempotent — calling start() when already running is a no-op.
|
|
52
|
+
*/
|
|
53
|
+
start() {
|
|
54
|
+
if (this.interval)
|
|
55
|
+
return;
|
|
56
|
+
const intervalMs = this.config.checkIntervalMinutes * 60 * 1000;
|
|
57
|
+
console.log(`[AutoUpdater] Started (every ${this.config.checkIntervalMinutes}m, ` +
|
|
58
|
+
`autoApply: ${this.config.autoApply}, autoRestart: ${this.config.autoRestart})`);
|
|
59
|
+
// Run first check after a short delay (don't block startup)
|
|
60
|
+
setTimeout(() => this.tick(), 10_000);
|
|
61
|
+
// Then run periodically
|
|
62
|
+
this.interval = setInterval(() => this.tick(), intervalMs);
|
|
63
|
+
this.interval.unref(); // Don't prevent process exit
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Stop the periodic checker.
|
|
67
|
+
*/
|
|
68
|
+
stop() {
|
|
69
|
+
if (this.interval) {
|
|
70
|
+
clearInterval(this.interval);
|
|
71
|
+
this.interval = null;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Get current auto-updater status.
|
|
76
|
+
*/
|
|
77
|
+
getStatus() {
|
|
78
|
+
return {
|
|
79
|
+
running: this.interval !== null,
|
|
80
|
+
lastCheck: this.lastCheck,
|
|
81
|
+
lastApply: this.lastApply,
|
|
82
|
+
config: { ...this.config },
|
|
83
|
+
pendingUpdate: this.pendingUpdate,
|
|
84
|
+
lastError: this.lastError,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Set the Telegram adapter (may be wired after construction).
|
|
89
|
+
*/
|
|
90
|
+
setTelegram(telegram) {
|
|
91
|
+
this.telegram = telegram;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* One tick of the update loop.
|
|
95
|
+
* Check → optionally apply → notify → optionally restart.
|
|
96
|
+
*/
|
|
97
|
+
async tick() {
|
|
98
|
+
if (this.isApplying) {
|
|
99
|
+
console.log('[AutoUpdater] Skipping tick — update already in progress');
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
try {
|
|
103
|
+
// Step 1: Check for updates
|
|
104
|
+
const info = await this.updateChecker.check();
|
|
105
|
+
this.lastCheck = new Date().toISOString();
|
|
106
|
+
this.lastError = null;
|
|
107
|
+
if (!info.updateAvailable) {
|
|
108
|
+
this.pendingUpdate = null;
|
|
109
|
+
this.saveState();
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
console.log(`[AutoUpdater] Update available: ${info.currentVersion} → ${info.latestVersion}`);
|
|
113
|
+
this.pendingUpdate = info.latestVersion;
|
|
114
|
+
this.saveState();
|
|
115
|
+
// Step 2: Auto-apply if configured
|
|
116
|
+
if (!this.config.autoApply) {
|
|
117
|
+
// Just notify — don't apply
|
|
118
|
+
await this.notify(`Update available: v${info.currentVersion} → v${info.latestVersion}\n\n` +
|
|
119
|
+
(info.changeSummary ? `Changes: ${info.changeSummary}\n\n` : '') +
|
|
120
|
+
`Auto-apply is disabled. Apply manually:\n` +
|
|
121
|
+
`curl -X POST http://localhost:${this.getPort()}/updates/apply`);
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
// Step 3: Apply the update
|
|
125
|
+
this.isApplying = true;
|
|
126
|
+
console.log(`[AutoUpdater] Applying update to v${info.latestVersion}...`);
|
|
127
|
+
const result = await this.updateChecker.applyUpdate();
|
|
128
|
+
this.isApplying = false;
|
|
129
|
+
if (!result.success) {
|
|
130
|
+
this.lastError = result.message;
|
|
131
|
+
this.saveState();
|
|
132
|
+
console.error(`[AutoUpdater] Update failed: ${result.message}`);
|
|
133
|
+
await this.notify(`Update to v${info.latestVersion} failed: ${result.message}\n\n` +
|
|
134
|
+
`The current version (v${result.previousVersion}) is still running.`);
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
// Step 4: Update succeeded
|
|
138
|
+
this.lastApply = new Date().toISOString();
|
|
139
|
+
this.pendingUpdate = null;
|
|
140
|
+
this.saveState();
|
|
141
|
+
console.log(`[AutoUpdater] Updated: v${result.previousVersion} → v${result.newVersion}`);
|
|
142
|
+
// Step 5: Notify via Telegram
|
|
143
|
+
const restartNote = result.restartNeeded && this.config.autoRestart
|
|
144
|
+
? 'Server is restarting now...'
|
|
145
|
+
: result.restartNeeded
|
|
146
|
+
? 'A server restart is needed to use the new version.'
|
|
147
|
+
: '';
|
|
148
|
+
await this.notify(`Updated: v${result.previousVersion} → v${result.newVersion}\n\n` +
|
|
149
|
+
(info.changeSummary ? `What changed:\n${info.changeSummary}\n\n` : '') +
|
|
150
|
+
restartNote);
|
|
151
|
+
// Step 6: Self-restart if needed and configured
|
|
152
|
+
if (result.restartNeeded && this.config.autoRestart) {
|
|
153
|
+
// Brief delay to let the Telegram notification send
|
|
154
|
+
await new Promise(r => setTimeout(r, 2000));
|
|
155
|
+
this.selfRestart();
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
catch (err) {
|
|
159
|
+
this.isApplying = false;
|
|
160
|
+
this.lastError = err instanceof Error ? err.message : String(err);
|
|
161
|
+
this.saveState();
|
|
162
|
+
console.error(`[AutoUpdater] Tick error: ${this.lastError}`);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Self-restart the server after an update.
|
|
167
|
+
*
|
|
168
|
+
* Strategy:
|
|
169
|
+
* 1. Spawn a shell that waits 2 seconds (for port release), then
|
|
170
|
+
* starts the new server version using the same CLI arguments.
|
|
171
|
+
* 2. Send SIGTERM to ourselves to trigger graceful shutdown.
|
|
172
|
+
*
|
|
173
|
+
* The 2-second delay ensures the old process has time to release
|
|
174
|
+
* the port before the new one tries to bind.
|
|
175
|
+
*
|
|
176
|
+
* If running in tmux, the replacement process inherits the PTY.
|
|
177
|
+
* If running under a process manager (launchd, systemd), the
|
|
178
|
+
* manager handles restart automatically after we exit.
|
|
179
|
+
*/
|
|
180
|
+
selfRestart() {
|
|
181
|
+
console.log('[AutoUpdater] Initiating self-restart...');
|
|
182
|
+
// Build the command to restart
|
|
183
|
+
// process.argv[0] = node binary
|
|
184
|
+
// process.argv[1..] = CLI args (e.g., /path/to/cli.js server start --foreground)
|
|
185
|
+
const args = process.argv.slice(1)
|
|
186
|
+
.map(a => `'${a.replace(/'/g, "'\\''")}'`)
|
|
187
|
+
.join(' ');
|
|
188
|
+
const cmd = `sleep 2 && exec ${process.execPath} ${args}`;
|
|
189
|
+
try {
|
|
190
|
+
const child = spawn('sh', ['-c', cmd], {
|
|
191
|
+
detached: true,
|
|
192
|
+
stdio: 'inherit',
|
|
193
|
+
cwd: process.cwd(),
|
|
194
|
+
env: process.env,
|
|
195
|
+
});
|
|
196
|
+
child.unref();
|
|
197
|
+
console.log('[AutoUpdater] Replacement process spawned. Shutting down...');
|
|
198
|
+
// Trigger graceful shutdown (the SIGTERM handler in server.ts will clean up)
|
|
199
|
+
process.kill(process.pid, 'SIGTERM');
|
|
200
|
+
}
|
|
201
|
+
catch (err) {
|
|
202
|
+
console.error(`[AutoUpdater] Self-restart failed: ${err}`);
|
|
203
|
+
console.error('[AutoUpdater] Update was applied but manual restart is needed.');
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Send a notification via Telegram (if configured).
|
|
208
|
+
* Falls back to console logging if Telegram is not available.
|
|
209
|
+
*/
|
|
210
|
+
async notify(message) {
|
|
211
|
+
const formatted = `🔄 *Auto-Update*\n\n${message}`;
|
|
212
|
+
if (this.telegram) {
|
|
213
|
+
try {
|
|
214
|
+
const topicId = this.config.notificationTopicId || this.getAttentionTopicId();
|
|
215
|
+
if (topicId) {
|
|
216
|
+
await this.telegram.sendToTopic(topicId, formatted);
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
catch (err) {
|
|
221
|
+
console.error(`[AutoUpdater] Telegram notification failed: ${err}`);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
// Fallback: just log
|
|
225
|
+
console.log(`[AutoUpdater] Notification: ${message}`);
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Get the Agent Attention topic ID from state (where infrastructure notifications go).
|
|
229
|
+
*/
|
|
230
|
+
getAttentionTopicId() {
|
|
231
|
+
return this.state.get('agent-attention-topic') ?? 0;
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Get the server port from the update checker config (for notification messages).
|
|
235
|
+
*/
|
|
236
|
+
getPort() {
|
|
237
|
+
// The port is available on the UpdateChecker config but not exposed.
|
|
238
|
+
// Use a reasonable default — agents can find their port from config.
|
|
239
|
+
return 4040;
|
|
240
|
+
}
|
|
241
|
+
// ── State persistence ──────────────────────────────────────────────
|
|
242
|
+
loadState() {
|
|
243
|
+
try {
|
|
244
|
+
if (fs.existsSync(this.stateFile)) {
|
|
245
|
+
const data = JSON.parse(fs.readFileSync(this.stateFile, 'utf-8'));
|
|
246
|
+
this.lastCheck = data.lastCheck ?? null;
|
|
247
|
+
this.lastApply = data.lastApply ?? null;
|
|
248
|
+
this.lastError = data.lastError ?? null;
|
|
249
|
+
this.pendingUpdate = data.pendingUpdate ?? null;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
catch {
|
|
253
|
+
// Start fresh if state is corrupted
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
saveState() {
|
|
257
|
+
const dir = path.dirname(this.stateFile);
|
|
258
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
259
|
+
const data = {
|
|
260
|
+
lastCheck: this.lastCheck,
|
|
261
|
+
lastApply: this.lastApply,
|
|
262
|
+
lastError: this.lastError,
|
|
263
|
+
pendingUpdate: this.pendingUpdate,
|
|
264
|
+
savedAt: new Date().toISOString(),
|
|
265
|
+
};
|
|
266
|
+
// Atomic write
|
|
267
|
+
const tmpPath = this.stateFile + `.${process.pid}.tmp`;
|
|
268
|
+
try {
|
|
269
|
+
fs.writeFileSync(tmpPath, JSON.stringify(data, null, 2));
|
|
270
|
+
fs.renameSync(tmpPath, this.stateFile);
|
|
271
|
+
}
|
|
272
|
+
catch {
|
|
273
|
+
try {
|
|
274
|
+
fs.unlinkSync(tmpPath);
|
|
275
|
+
}
|
|
276
|
+
catch { /* ignore */ }
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
//# sourceMappingURL=AutoUpdater.js.map
|
|
@@ -385,14 +385,14 @@ Types: \`bug\`, \`feature\`, \`improvement\`, \`question\`
|
|
|
385
385
|
|
|
386
386
|
### The Feedback Loop — Rising Tide
|
|
387
387
|
|
|
388
|
-
Instar has a built-in feedback loop that connects all agents to the development team (Dawn). When you submit feedback, it gets forwarded upstream. When a fix ships, the
|
|
388
|
+
Instar has a built-in feedback loop that connects all agents to the development team (Dawn). When you submit feedback, it gets forwarded upstream. When a fix ships, the built-in auto-updater detects it, applies it, and restarts the server. Every agent's bug report lifts all ships.
|
|
389
389
|
|
|
390
390
|
**The cycle:**
|
|
391
391
|
1. You encounter an issue → submit feedback
|
|
392
392
|
2. Feedback forwards upstream to Dawn
|
|
393
393
|
3. Dawn fixes and publishes a new version
|
|
394
|
-
4.
|
|
395
|
-
5. Update auto-applies, hooks refresh, you get the fix
|
|
394
|
+
4. The auto-updater detects it within 30 minutes
|
|
395
|
+
5. Update auto-applies, hooks refresh, server restarts, you get the fix
|
|
396
396
|
|
|
397
397
|
**User feedback matters too.** When your user says "this isn't working" or "I wish I could..." — that's feedback. Categorize it and submit it the same way.
|
|
398
398
|
|
|
@@ -14,6 +14,7 @@ import type { RelationshipManager } from '../core/RelationshipManager.js';
|
|
|
14
14
|
import type { FeedbackManager } from '../core/FeedbackManager.js';
|
|
15
15
|
import type { DispatchManager } from '../core/DispatchManager.js';
|
|
16
16
|
import type { UpdateChecker } from '../core/UpdateChecker.js';
|
|
17
|
+
import type { AutoUpdater } from '../core/AutoUpdater.js';
|
|
17
18
|
import type { QuotaTracker } from '../monitoring/QuotaTracker.js';
|
|
18
19
|
import type { TelegraphService } from '../publishing/TelegraphService.js';
|
|
19
20
|
import type { PrivateViewer } from '../publishing/PrivateViewer.js';
|
|
@@ -34,6 +35,7 @@ export declare class AgentServer {
|
|
|
34
35
|
feedback?: FeedbackManager;
|
|
35
36
|
dispatches?: DispatchManager;
|
|
36
37
|
updateChecker?: UpdateChecker;
|
|
38
|
+
autoUpdater?: AutoUpdater;
|
|
37
39
|
quotaTracker?: QuotaTracker;
|
|
38
40
|
publisher?: TelegraphService;
|
|
39
41
|
viewer?: PrivateViewer;
|
|
@@ -32,6 +32,7 @@ export class AgentServer {
|
|
|
32
32
|
feedback: options.feedback ?? null,
|
|
33
33
|
dispatches: options.dispatches ?? null,
|
|
34
34
|
updateChecker: options.updateChecker ?? null,
|
|
35
|
+
autoUpdater: options.autoUpdater ?? null,
|
|
35
36
|
quotaTracker: options.quotaTracker ?? null,
|
|
36
37
|
publisher: options.publisher ?? null,
|
|
37
38
|
viewer: options.viewer ?? null,
|
package/dist/server/routes.d.ts
CHANGED
|
@@ -14,6 +14,7 @@ import type { RelationshipManager } from '../core/RelationshipManager.js';
|
|
|
14
14
|
import type { FeedbackManager } from '../core/FeedbackManager.js';
|
|
15
15
|
import type { DispatchManager } from '../core/DispatchManager.js';
|
|
16
16
|
import type { UpdateChecker } from '../core/UpdateChecker.js';
|
|
17
|
+
import type { AutoUpdater } from '../core/AutoUpdater.js';
|
|
17
18
|
import type { QuotaTracker } from '../monitoring/QuotaTracker.js';
|
|
18
19
|
import type { TelegraphService } from '../publishing/TelegraphService.js';
|
|
19
20
|
import type { PrivateViewer } from '../publishing/PrivateViewer.js';
|
|
@@ -29,6 +30,7 @@ export interface RouteContext {
|
|
|
29
30
|
feedback: FeedbackManager | null;
|
|
30
31
|
dispatches: DispatchManager | null;
|
|
31
32
|
updateChecker: UpdateChecker | null;
|
|
33
|
+
autoUpdater: AutoUpdater | null;
|
|
32
34
|
quotaTracker: QuotaTracker | null;
|
|
33
35
|
publisher: TelegraphService | null;
|
|
34
36
|
viewer: PrivateViewer | null;
|
package/dist/server/routes.js
CHANGED
|
@@ -476,6 +476,34 @@ export function createRoutes(ctx) {
|
|
|
476
476
|
}
|
|
477
477
|
res.json({ topics: ctx.telegram.getAllTopicMappings() });
|
|
478
478
|
});
|
|
479
|
+
router.post('/telegram/topics', async (req, res) => {
|
|
480
|
+
if (!ctx.telegram) {
|
|
481
|
+
res.status(503).json({ error: 'Telegram not configured' });
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
const { name, color } = req.body;
|
|
485
|
+
if (!name || typeof name !== 'string' || name.trim().length < 1) {
|
|
486
|
+
res.status(400).json({ error: '"name" is required (non-empty string)' });
|
|
487
|
+
return;
|
|
488
|
+
}
|
|
489
|
+
if (name.length > 128) {
|
|
490
|
+
res.status(400).json({ error: '"name" must be 128 characters or fewer' });
|
|
491
|
+
return;
|
|
492
|
+
}
|
|
493
|
+
// Color is optional — defaults to green (9367192)
|
|
494
|
+
const iconColor = typeof color === 'number' ? color : 9367192;
|
|
495
|
+
try {
|
|
496
|
+
const topic = await ctx.telegram.createForumTopic(name.trim(), iconColor);
|
|
497
|
+
res.status(201).json({
|
|
498
|
+
topicId: topic.topicId,
|
|
499
|
+
name: name.trim(),
|
|
500
|
+
created: true,
|
|
501
|
+
});
|
|
502
|
+
}
|
|
503
|
+
catch (err) {
|
|
504
|
+
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
|
|
505
|
+
}
|
|
506
|
+
});
|
|
479
507
|
router.post('/telegram/reply/:topicId', async (req, res) => {
|
|
480
508
|
if (!ctx.telegram) {
|
|
481
509
|
res.status(503).json({ error: 'Telegram not configured' });
|
|
@@ -870,6 +898,14 @@ export function createRoutes(ctx) {
|
|
|
870
898
|
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
|
|
871
899
|
}
|
|
872
900
|
});
|
|
901
|
+
// ── Auto-Updater ────────────────────────────────────────────────
|
|
902
|
+
router.get('/updates/auto', (_req, res) => {
|
|
903
|
+
if (!ctx.autoUpdater) {
|
|
904
|
+
res.status(503).json({ error: 'Auto-updater not configured' });
|
|
905
|
+
return;
|
|
906
|
+
}
|
|
907
|
+
res.json(ctx.autoUpdater.getStatus());
|
|
908
|
+
});
|
|
873
909
|
// ── Dispatches ───────────────────────────────────────────────────
|
|
874
910
|
router.get('/dispatches', async (_req, res) => {
|
|
875
911
|
if (!ctx.dispatches) {
|