tide-commander 1.152.0 → 1.154.0
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/CHANGELOG.md +20 -0
- package/dist/assets/{BossLogsModal-DZWUfQXs.js → BossLogsModal-Bo0pUCWU.js} +1 -1
- package/dist/assets/{BossSpawnModal-7IwzcqE7.js → BossSpawnModal-BOOHaqaD.js} +1 -1
- package/dist/assets/{ControlsModal-D8L1xD-H.js → ControlsModal-DItGV-CW.js} +1 -1
- package/dist/assets/{DockerLogsModal-_zLe5UFl.js → DockerLogsModal-DjAZ3GL7.js} +1 -1
- package/dist/assets/{EmbeddedEditor-gbiFXvt_.js → EmbeddedEditor-Cihkr8Xb.js} +1 -1
- package/dist/assets/{GmailOAuthSetup-CcXTV3NP.js → GmailOAuthSetup-G1XqiM44.js} +1 -1
- package/dist/assets/{GoogleOAuthSetup-wbETidgv.js → GoogleOAuthSetup-Ce6gojdm.js} +1 -1
- package/dist/assets/{IframeModal-a9E5UDBk.js → IframeModal-CSSZJzPv.js} +1 -1
- package/dist/assets/{IntegrationsPanel-BbGyluL0.js → IntegrationsPanel-EspPH09n.js} +2 -2
- package/dist/assets/LogViewerModal-Bn-HDysM.js +1 -0
- package/dist/assets/{MonitoringModal-DymqjenY.js → MonitoringModal-DuSLTKVR.js} +1 -1
- package/dist/assets/{PM2LogsModal-CEaCk-dt.js → PM2LogsModal-Bo2OtaQd.js} +1 -1
- package/dist/assets/{RestoreArchivedAreaModal-q_FFv7L6.js → RestoreArchivedAreaModal-BOx-V6tl.js} +1 -1
- package/dist/assets/{Scene2DCanvas-DOvVtiYO.js → Scene2DCanvas-BIUbi14G.js} +1 -1
- package/dist/assets/{SceneManager-D0NdrG8K.js → SceneManager-CIWrGc-K.js} +4 -4
- package/dist/assets/{SkillsPanel-DpqD60Hy.js → SkillsPanel-_KHtTDhC.js} +1 -1
- package/dist/assets/{SlackMultiInstanceSetup-BdoALd_g.js → SlackMultiInstanceSetup-DbQ3rUtd.js} +1 -1
- package/dist/assets/{SpawnModal-DFFFH2kc.js → SpawnModal-CYEoOMPW.js} +1 -1
- package/dist/assets/{StatisticsModal-Dv_rYZDt.js → StatisticsModal-BJ-coeis.js} +1 -1
- package/dist/assets/{SubordinateAssignmentModal-CRCCI686.js → SubordinateAssignmentModal-5Z3jvbM9.js} +1 -1
- package/dist/assets/SymbolsNerdFontMono-Regular-BM2yZ5nr.woff2 +0 -0
- package/dist/assets/{TriggerManagerPanel-DBN8IRa-.js → TriggerManagerPanel-DvC0xP9r.js} +1 -1
- package/dist/assets/{WorkflowEditorPanel-uQEvNTs2.js → WorkflowEditorPanel-6ym1_csr.js} +1 -1
- package/dist/assets/{area-logos-CuVxToGM.js → area-logos-mWPCtCnf.js} +1 -1
- package/dist/assets/{index-Dwll5Eyj.js → index-8VVbTmtb.js} +1 -1
- package/dist/assets/{index-BNzYPyBm.css → index-BPjQZacc.css} +1 -1
- package/dist/assets/index-C34l72MW.js +1 -0
- package/dist/assets/{index-DKnfLe7S.js → index-CS8OutmQ.js} +1 -1
- package/dist/assets/{index-ipuWrg1S.js → index-CT2vtbed.js} +3 -3
- package/dist/assets/{index-Blc4MwKs.js → index-CY_NzVPm.js} +1 -1
- package/dist/assets/index-CiKbhMfN.js +1 -0
- package/dist/assets/{index-HvyFhszc.js → index-CnESn83U.js} +2 -2
- package/dist/assets/{index-8Ghh3AQ-.js → index-DZiNmiyC.js} +1 -1
- package/dist/assets/{index-D4iie3Vd.js → index-DxyZ-rYT.js} +1 -1
- package/dist/assets/main-BHBs2E1L.css +1 -0
- package/dist/assets/main-D5j8Ul0m.js +289 -0
- package/dist/assets/{web-D0JSbYeo.js → web-BvAQVbIF.js} +1 -1
- package/dist/assets/{web-1pfPwxBS.js → web-Cd0-XOkz.js} +1 -1
- package/dist/assets/{web-DjgiLxvi.js → web-DMxl9AVF.js} +1 -1
- package/dist/build-info.json +1 -0
- package/dist/index.html +2 -2
- package/dist/locales/de/config.json +1 -0
- package/dist/locales/en/config.json +6 -1
- package/dist/locales/en/terminal.json +36 -0
- package/dist/locales/es/config.json +4 -1
- package/dist/locales/es/terminal.json +8 -0
- package/dist/locales/fr/config.json +1 -0
- package/dist/locales/hi/config.json +1 -0
- package/dist/locales/it/config.json +1 -0
- package/dist/locales/ja/config.json +1 -0
- package/dist/locales/pt/config.json +1 -0
- package/dist/locales/ru/config.json +1 -0
- package/dist/locales/zh-CN/config.json +1 -0
- package/dist/src/packages/server/codex/app-server/app-server-event-adapter.js +222 -0
- package/dist/src/packages/server/codex/app-server/app-server-process.js +304 -0
- package/dist/src/packages/server/codex/app-server/app-server-recovery-store.js +76 -0
- package/dist/src/packages/server/codex/app-server/codex-app-server-runner.js +453 -0
- package/dist/src/packages/server/routes/agents.js +29 -1
- package/dist/src/packages/server/routes/system.js +216 -1
- package/dist/src/packages/server/runtime/codex-runner-router.js +99 -0
- package/dist/src/packages/server/runtime/codex-runtime-provider.js +5 -3
- package/dist/src/packages/server/services/claude-credentials-service.js +536 -0
- package/dist/src/packages/server/services/claude-usage-service.js +35 -28
- package/dist/src/packages/server/services/provider-credentials-service.js +338 -0
- package/dist/src/packages/server/services/system-prompt-service.js +46 -0
- package/package.json +1 -1
- package/dist/assets/LogViewerModal-cnMAbNvY.js +0 -1
- package/dist/assets/index-BHM8V4i-.js +0 -1
- package/dist/assets/index-OkenGYC6.js +0 -1
- package/dist/assets/main-DpNJ8pqp.css +0 -1
- package/dist/assets/main-aIpEYDDR.js +0 -289
|
@@ -11,6 +11,8 @@ import { createLogger } from '../utils/logger.js';
|
|
|
11
11
|
import { fetchLatestNpmVersion, getVersionRelation } from '../../shared/version.js';
|
|
12
12
|
import { endUpdate, getInstallInfo, isAutoUpdateSupported, isUpdateInProgress, runNpmGlobalUpdate, schedulePostUpdateRestart, tryBeginUpdate, } from '../services/self-update-service.js';
|
|
13
13
|
import { getAutoUpdateStatus, setAutoUpdateEnabled } from '../services/auto-update-service.js';
|
|
14
|
+
import { deleteClaudeCredentialProfile, getClaudeCredentialProfilesUsage, listClaudeCredentialProfiles, renameClaudeCredentialProfile, saveActiveClaudeCredentialProfile, switchClaudeCredentialProfile, } from '../services/claude-credentials-service.js';
|
|
15
|
+
import { deleteProviderCredentialProfile, listProviderCredentialProfiles, renameProviderCredentialProfile, saveActiveProviderCredentialProfile, switchProviderCredentialProfile, } from '../services/provider-credentials-service.js';
|
|
14
16
|
const log = createLogger('SystemRoutes');
|
|
15
17
|
const router = Router();
|
|
16
18
|
const PACKAGE_NAME = 'tide-commander';
|
|
@@ -130,8 +132,22 @@ function computeWebBundleInfo() {
|
|
|
130
132
|
.update(indexContent)
|
|
131
133
|
.digest('hex')
|
|
132
134
|
.slice(0, 16);
|
|
135
|
+
// The version must describe the dist we SERVE, not the live package.json —
|
|
136
|
+
// package.json moves ahead of the last build (version bumps, dev work), and
|
|
137
|
+
// advertising a version the bundle doesn't contain sends phones into an
|
|
138
|
+
// eternal re-sync loop (pull → still "behind" → pull …). build-info.json is
|
|
139
|
+
// emitted by the vite build; fall back to package.json for older dists.
|
|
140
|
+
let version = getInstallInfo().currentVersion;
|
|
141
|
+
try {
|
|
142
|
+
const buildInfo = JSON.parse(fs.readFileSync(path.join(dir, 'build-info.json'), 'utf8'));
|
|
143
|
+
if (buildInfo.version)
|
|
144
|
+
version = buildInfo.version;
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
/* pre-build-info dist — package.json is the best guess */
|
|
148
|
+
}
|
|
133
149
|
return {
|
|
134
|
-
version
|
|
150
|
+
version,
|
|
135
151
|
hash,
|
|
136
152
|
fileCount: files.length,
|
|
137
153
|
totalBytes: files.reduce((sum, f) => sum + f.size, 0),
|
|
@@ -397,4 +413,203 @@ router.post('/auto-update', (req, res) => {
|
|
|
397
413
|
log.log(`Auto-update ${enabled ? 'enabled' : 'disabled'} via API`);
|
|
398
414
|
res.json(setAutoUpdateEnabled(enabled));
|
|
399
415
|
});
|
|
416
|
+
/**
|
|
417
|
+
* Claude CLI OAuth credential profiles (~/.claude/.credentials.json and
|
|
418
|
+
* ~/.claude/.credentials.<name>.json). Used to switch the active Claude
|
|
419
|
+
* account when rate limits are hit without manually renaming files.
|
|
420
|
+
*
|
|
421
|
+
* Tokens are never returned — only non-secret metadata + fingerprints.
|
|
422
|
+
*/
|
|
423
|
+
router.get('/claude-credentials', (_req, res) => {
|
|
424
|
+
try {
|
|
425
|
+
res.json(listClaudeCredentialProfiles());
|
|
426
|
+
}
|
|
427
|
+
catch (err) {
|
|
428
|
+
const message = err.message;
|
|
429
|
+
log.error(`Failed to list Claude credentials: ${message}`);
|
|
430
|
+
res.status(500).json({ error: message });
|
|
431
|
+
}
|
|
432
|
+
});
|
|
433
|
+
/**
|
|
434
|
+
* Session (5h) + weekly rate-limit gauges per stored credential profile,
|
|
435
|
+
* keyed by token fingerprint. Cached server-side; expired dormant tokens are
|
|
436
|
+
* reported (with last-known gauges when available) instead of fetched.
|
|
437
|
+
*/
|
|
438
|
+
router.get('/claude-credentials/usage', async (_req, res) => {
|
|
439
|
+
try {
|
|
440
|
+
res.json(await getClaudeCredentialProfilesUsage());
|
|
441
|
+
}
|
|
442
|
+
catch (err) {
|
|
443
|
+
const message = err.message;
|
|
444
|
+
log.error(`Failed to fetch Claude credentials usage: ${message}`);
|
|
445
|
+
res.status(500).json({ error: message });
|
|
446
|
+
}
|
|
447
|
+
});
|
|
448
|
+
router.post('/claude-credentials/switch', (req, res) => {
|
|
449
|
+
try {
|
|
450
|
+
const name = req.body?.name;
|
|
451
|
+
const stashActiveAs = req.body?.stashActiveAs;
|
|
452
|
+
if (typeof name !== 'string' || !name.trim()) {
|
|
453
|
+
res.status(400).json({ error: 'Body must include { name: string }' });
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
if (stashActiveAs !== undefined && typeof stashActiveAs !== 'string') {
|
|
457
|
+
res.status(400).json({ error: 'stashActiveAs must be a string when provided' });
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
const result = switchClaudeCredentialProfile(name.trim(), {
|
|
461
|
+
stashActiveAs: typeof stashActiveAs === 'string' ? stashActiveAs.trim() : undefined,
|
|
462
|
+
});
|
|
463
|
+
res.json(result);
|
|
464
|
+
}
|
|
465
|
+
catch (err) {
|
|
466
|
+
const message = err.message;
|
|
467
|
+
log.error(`Failed to switch Claude credentials: ${message}`);
|
|
468
|
+
const status = /not found|Invalid|required|already exists|cannot be/i.test(message) ? 400 : 500;
|
|
469
|
+
res.status(status).json({ error: message });
|
|
470
|
+
}
|
|
471
|
+
});
|
|
472
|
+
router.post('/claude-credentials/save', (req, res) => {
|
|
473
|
+
try {
|
|
474
|
+
const name = req.body?.name;
|
|
475
|
+
const force = Boolean(req.body?.force);
|
|
476
|
+
if (typeof name !== 'string' || !name.trim()) {
|
|
477
|
+
res.status(400).json({ error: 'Body must include { name: string }' });
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
res.json(saveActiveClaudeCredentialProfile(name.trim(), { force }));
|
|
481
|
+
}
|
|
482
|
+
catch (err) {
|
|
483
|
+
const message = err.message;
|
|
484
|
+
log.error(`Failed to save Claude credentials profile: ${message}`);
|
|
485
|
+
const status = /not found|Invalid|already exists|No valid/i.test(message) ? 400 : 500;
|
|
486
|
+
res.status(status).json({ error: message });
|
|
487
|
+
}
|
|
488
|
+
});
|
|
489
|
+
router.post('/claude-credentials/rename', (req, res) => {
|
|
490
|
+
try {
|
|
491
|
+
const from = req.body?.from;
|
|
492
|
+
const to = req.body?.to;
|
|
493
|
+
if (typeof from !== 'string' || !from.trim() || typeof to !== 'string' || !to.trim()) {
|
|
494
|
+
res.status(400).json({ error: 'Body must include { from: string, to: string }' });
|
|
495
|
+
return;
|
|
496
|
+
}
|
|
497
|
+
res.json(renameClaudeCredentialProfile(from.trim(), to.trim()));
|
|
498
|
+
}
|
|
499
|
+
catch (err) {
|
|
500
|
+
const message = err.message;
|
|
501
|
+
log.error(`Failed to rename Claude credentials profile: ${message}`);
|
|
502
|
+
const status = /not found|Invalid|already exists/i.test(message) ? 400 : 500;
|
|
503
|
+
res.status(status).json({ error: message });
|
|
504
|
+
}
|
|
505
|
+
});
|
|
506
|
+
router.delete('/claude-credentials/:name', (req, res) => {
|
|
507
|
+
try {
|
|
508
|
+
const name = typeof req.params.name === 'string' ? req.params.name : '';
|
|
509
|
+
if (!name) {
|
|
510
|
+
res.status(400).json({ error: 'Profile name required' });
|
|
511
|
+
return;
|
|
512
|
+
}
|
|
513
|
+
res.json(deleteClaudeCredentialProfile(name));
|
|
514
|
+
}
|
|
515
|
+
catch (err) {
|
|
516
|
+
const message = err.message;
|
|
517
|
+
log.error(`Failed to delete Claude credentials profile: ${message}`);
|
|
518
|
+
const status = /not found|Invalid/i.test(message) ? 400 : 500;
|
|
519
|
+
res.status(status).json({ error: message });
|
|
520
|
+
}
|
|
521
|
+
});
|
|
522
|
+
/**
|
|
523
|
+
* Grok + Codex OAuth credential profiles — same account-switcher model as
|
|
524
|
+
* Claude, generalized (~/.grok/auth.json, ~/.codex/auth.json + named copies).
|
|
525
|
+
* Mounted per provider at /grok-credentials/* and /codex-credentials/*.
|
|
526
|
+
* Tokens are never returned — only non-secret metadata + fingerprints.
|
|
527
|
+
*/
|
|
528
|
+
function mountProviderCredentialRoutes(provider, basePath) {
|
|
529
|
+
router.get(basePath, (_req, res) => {
|
|
530
|
+
try {
|
|
531
|
+
res.json(listProviderCredentialProfiles(provider));
|
|
532
|
+
}
|
|
533
|
+
catch (err) {
|
|
534
|
+
const message = err.message;
|
|
535
|
+
log.error(`Failed to list ${provider} credentials: ${message}`);
|
|
536
|
+
res.status(500).json({ error: message });
|
|
537
|
+
}
|
|
538
|
+
});
|
|
539
|
+
router.post(`${basePath}/switch`, (req, res) => {
|
|
540
|
+
try {
|
|
541
|
+
const name = req.body?.name;
|
|
542
|
+
const stashActiveAs = req.body?.stashActiveAs;
|
|
543
|
+
if (typeof name !== 'string' || !name.trim()) {
|
|
544
|
+
res.status(400).json({ error: 'Body must include { name: string }' });
|
|
545
|
+
return;
|
|
546
|
+
}
|
|
547
|
+
if (stashActiveAs !== undefined && typeof stashActiveAs !== 'string') {
|
|
548
|
+
res.status(400).json({ error: 'stashActiveAs must be a string when provided' });
|
|
549
|
+
return;
|
|
550
|
+
}
|
|
551
|
+
res.json(switchProviderCredentialProfile(provider, name.trim(), {
|
|
552
|
+
stashActiveAs: typeof stashActiveAs === 'string' ? stashActiveAs.trim() : undefined,
|
|
553
|
+
}));
|
|
554
|
+
}
|
|
555
|
+
catch (err) {
|
|
556
|
+
const message = err.message;
|
|
557
|
+
log.error(`Failed to switch ${provider} credentials: ${message}`);
|
|
558
|
+
const status = /not found|Invalid|required|already exists|cannot be|no valid/i.test(message) ? 400 : 500;
|
|
559
|
+
res.status(status).json({ error: message });
|
|
560
|
+
}
|
|
561
|
+
});
|
|
562
|
+
router.post(`${basePath}/save`, (req, res) => {
|
|
563
|
+
try {
|
|
564
|
+
const name = req.body?.name;
|
|
565
|
+
const force = Boolean(req.body?.force);
|
|
566
|
+
if (typeof name !== 'string' || !name.trim()) {
|
|
567
|
+
res.status(400).json({ error: 'Body must include { name: string }' });
|
|
568
|
+
return;
|
|
569
|
+
}
|
|
570
|
+
res.json(saveActiveProviderCredentialProfile(provider, name.trim(), { force }));
|
|
571
|
+
}
|
|
572
|
+
catch (err) {
|
|
573
|
+
const message = err.message;
|
|
574
|
+
log.error(`Failed to save ${provider} credentials profile: ${message}`);
|
|
575
|
+
const status = /not found|Invalid|already exists|No valid/i.test(message) ? 400 : 500;
|
|
576
|
+
res.status(status).json({ error: message });
|
|
577
|
+
}
|
|
578
|
+
});
|
|
579
|
+
router.post(`${basePath}/rename`, (req, res) => {
|
|
580
|
+
try {
|
|
581
|
+
const from = req.body?.from;
|
|
582
|
+
const to = req.body?.to;
|
|
583
|
+
if (typeof from !== 'string' || !from.trim() || typeof to !== 'string' || !to.trim()) {
|
|
584
|
+
res.status(400).json({ error: 'Body must include { from: string, to: string }' });
|
|
585
|
+
return;
|
|
586
|
+
}
|
|
587
|
+
res.json(renameProviderCredentialProfile(provider, from.trim(), to.trim()));
|
|
588
|
+
}
|
|
589
|
+
catch (err) {
|
|
590
|
+
const message = err.message;
|
|
591
|
+
log.error(`Failed to rename ${provider} credentials profile: ${message}`);
|
|
592
|
+
const status = /not found|Invalid|already exists/i.test(message) ? 400 : 500;
|
|
593
|
+
res.status(status).json({ error: message });
|
|
594
|
+
}
|
|
595
|
+
});
|
|
596
|
+
router.delete(`${basePath}/:name`, (req, res) => {
|
|
597
|
+
try {
|
|
598
|
+
const name = typeof req.params.name === 'string' ? req.params.name : '';
|
|
599
|
+
if (!name) {
|
|
600
|
+
res.status(400).json({ error: 'Profile name required' });
|
|
601
|
+
return;
|
|
602
|
+
}
|
|
603
|
+
res.json(deleteProviderCredentialProfile(provider, name));
|
|
604
|
+
}
|
|
605
|
+
catch (err) {
|
|
606
|
+
const message = err.message;
|
|
607
|
+
log.error(`Failed to delete ${provider} credentials profile: ${message}`);
|
|
608
|
+
const status = /not found|Invalid/i.test(message) ? 400 : 500;
|
|
609
|
+
res.status(status).json({ error: message });
|
|
610
|
+
}
|
|
611
|
+
});
|
|
612
|
+
}
|
|
613
|
+
mountProviderCredentialRoutes('grok', '/grok-credentials');
|
|
614
|
+
mountProviderCredentialRoutes('codex', '/codex-credentials');
|
|
400
615
|
export default router;
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CodexRunnerRouter
|
|
3
|
+
*
|
|
4
|
+
* Codex agents can run two ways: the default per-turn `codex exec` runner (the
|
|
5
|
+
* ClaudeRunner shell driving a CodexBackend), or the experimental persistent
|
|
6
|
+
* `codex app-server` runner that streams word-by-word agent-message deltas.
|
|
7
|
+
* Which one a NEW launch uses is decided per-run from the LIVE setting
|
|
8
|
+
* (isCodexAppServerModeEnabled) — no server restart needed to switch, mirroring
|
|
9
|
+
* ClaudeRunnerRouter's headless/interactive split.
|
|
10
|
+
*
|
|
11
|
+
* The router owns one instance of each runner and delegates the RuntimeRunner
|
|
12
|
+
* surface to whichever currently owns the agent (live thread/process wins over
|
|
13
|
+
* the setting), tearing the other down on a mode switch so one agent never has
|
|
14
|
+
* two live backends.
|
|
15
|
+
*/
|
|
16
|
+
import { ClaudeRunner } from '../claude/runner.js';
|
|
17
|
+
import { CodexBackend } from '../codex/backend.js';
|
|
18
|
+
import { CodexAppServerRunner } from '../codex/app-server/codex-app-server-runner.js';
|
|
19
|
+
import { isCodexAppServerModeEnabled } from '../services/system-prompt-service.js';
|
|
20
|
+
import { createLogger } from '../utils/logger.js';
|
|
21
|
+
const log = createLogger('CodexRouter');
|
|
22
|
+
export class CodexRunnerRouter {
|
|
23
|
+
exec;
|
|
24
|
+
appServer;
|
|
25
|
+
owner = new Map();
|
|
26
|
+
constructor(callbacks) {
|
|
27
|
+
this.exec = new ClaudeRunner(callbacks, new CodexBackend());
|
|
28
|
+
this.appServer = new CodexAppServerRunner(callbacks);
|
|
29
|
+
}
|
|
30
|
+
currentMode() {
|
|
31
|
+
return isCodexAppServerModeEnabled() ? 'app-server' : 'exec';
|
|
32
|
+
}
|
|
33
|
+
runnerFor(mode) {
|
|
34
|
+
return mode === 'app-server' ? this.appServer : this.exec;
|
|
35
|
+
}
|
|
36
|
+
ownerOf(agentId) {
|
|
37
|
+
if (this.appServer.isRunning(agentId))
|
|
38
|
+
return 'app-server';
|
|
39
|
+
if (this.exec.isRunning(agentId))
|
|
40
|
+
return 'exec';
|
|
41
|
+
return this.owner.get(agentId);
|
|
42
|
+
}
|
|
43
|
+
ownerRunner(agentId) {
|
|
44
|
+
return this.runnerFor(this.ownerOf(agentId) ?? this.currentMode());
|
|
45
|
+
}
|
|
46
|
+
start() {
|
|
47
|
+
this.exec.start?.();
|
|
48
|
+
this.appServer.start?.();
|
|
49
|
+
}
|
|
50
|
+
async run(request) {
|
|
51
|
+
const mode = this.currentMode();
|
|
52
|
+
const prev = this.ownerOf(request.agentId);
|
|
53
|
+
if (prev && prev !== mode) {
|
|
54
|
+
log.log(`Agent ${request.agentId.slice(0, 8)} switching ${prev} → ${mode}; stopping old runner first`);
|
|
55
|
+
await this.runnerFor(prev).stop(request.agentId, false);
|
|
56
|
+
}
|
|
57
|
+
this.owner.set(request.agentId, mode);
|
|
58
|
+
return this.runnerFor(mode).run(request);
|
|
59
|
+
}
|
|
60
|
+
sendMessage(agentId, message) {
|
|
61
|
+
return this.ownerRunner(agentId).sendMessage(agentId, message);
|
|
62
|
+
}
|
|
63
|
+
async stop(agentId, clearQueue) {
|
|
64
|
+
await this.exec.stop(agentId, clearQueue);
|
|
65
|
+
await this.appServer.stop(agentId, clearQueue);
|
|
66
|
+
this.owner.delete(agentId);
|
|
67
|
+
}
|
|
68
|
+
async stopAll(killProcesses, clearQueue) {
|
|
69
|
+
await this.exec.stopAll(killProcesses, clearQueue);
|
|
70
|
+
await this.appServer.stopAll(killProcesses, clearQueue);
|
|
71
|
+
this.owner.clear();
|
|
72
|
+
}
|
|
73
|
+
isRunning(agentId) {
|
|
74
|
+
return this.exec.isRunning(agentId) || this.appServer.isRunning(agentId);
|
|
75
|
+
}
|
|
76
|
+
hasRecentActivity(agentId, withinMs) {
|
|
77
|
+
return this.ownerRunner(agentId).hasRecentActivity(agentId, withinMs);
|
|
78
|
+
}
|
|
79
|
+
onNextActivity(agentId, callback) {
|
|
80
|
+
this.ownerRunner(agentId).onNextActivity(agentId, callback);
|
|
81
|
+
}
|
|
82
|
+
supportsStdin() {
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
closesStdinAfterPrompt() {
|
|
86
|
+
// Mode-dependent: exec closes stdin per turn, app-server keeps it open. The
|
|
87
|
+
// caller uses this to decide queue-vs-write; report per current owner so the
|
|
88
|
+
// right delivery path is chosen for the agent that's actually running.
|
|
89
|
+
return this.currentMode() === 'exec';
|
|
90
|
+
}
|
|
91
|
+
getTurnState(agentId) {
|
|
92
|
+
return this.ownerRunner(agentId).getTurnState?.(agentId);
|
|
93
|
+
}
|
|
94
|
+
getActiveProcessesState() {
|
|
95
|
+
const execState = this.exec.getActiveProcessesState().map((p) => ({ agentId: p.agentId, pid: p.pid }));
|
|
96
|
+
const appState = this.appServer.getActiveProcessesState();
|
|
97
|
+
return [...execState, ...appState];
|
|
98
|
+
}
|
|
99
|
+
}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { CodexBackend } from '../codex/backend.js';
|
|
1
|
+
import { CodexRunnerRouter } from './codex-runner-router.js';
|
|
3
2
|
class CodexRuntimeProvider {
|
|
4
3
|
name = 'codex';
|
|
5
4
|
createRunner(callbacks) {
|
|
6
|
-
|
|
5
|
+
// The router picks per-launch between the default `codex exec` runner and
|
|
6
|
+
// the experimental persistent `codex app-server` runner (streaming), driven
|
|
7
|
+
// by the live isCodexAppServerModeEnabled() setting.
|
|
8
|
+
return new CodexRunnerRouter(callbacks);
|
|
7
9
|
}
|
|
8
10
|
}
|
|
9
11
|
export function createCodexRuntimeProvider() {
|