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.
Files changed (72) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/dist/assets/{BossLogsModal-DZWUfQXs.js → BossLogsModal-Bo0pUCWU.js} +1 -1
  3. package/dist/assets/{BossSpawnModal-7IwzcqE7.js → BossSpawnModal-BOOHaqaD.js} +1 -1
  4. package/dist/assets/{ControlsModal-D8L1xD-H.js → ControlsModal-DItGV-CW.js} +1 -1
  5. package/dist/assets/{DockerLogsModal-_zLe5UFl.js → DockerLogsModal-DjAZ3GL7.js} +1 -1
  6. package/dist/assets/{EmbeddedEditor-gbiFXvt_.js → EmbeddedEditor-Cihkr8Xb.js} +1 -1
  7. package/dist/assets/{GmailOAuthSetup-CcXTV3NP.js → GmailOAuthSetup-G1XqiM44.js} +1 -1
  8. package/dist/assets/{GoogleOAuthSetup-wbETidgv.js → GoogleOAuthSetup-Ce6gojdm.js} +1 -1
  9. package/dist/assets/{IframeModal-a9E5UDBk.js → IframeModal-CSSZJzPv.js} +1 -1
  10. package/dist/assets/{IntegrationsPanel-BbGyluL0.js → IntegrationsPanel-EspPH09n.js} +2 -2
  11. package/dist/assets/LogViewerModal-Bn-HDysM.js +1 -0
  12. package/dist/assets/{MonitoringModal-DymqjenY.js → MonitoringModal-DuSLTKVR.js} +1 -1
  13. package/dist/assets/{PM2LogsModal-CEaCk-dt.js → PM2LogsModal-Bo2OtaQd.js} +1 -1
  14. package/dist/assets/{RestoreArchivedAreaModal-q_FFv7L6.js → RestoreArchivedAreaModal-BOx-V6tl.js} +1 -1
  15. package/dist/assets/{Scene2DCanvas-DOvVtiYO.js → Scene2DCanvas-BIUbi14G.js} +1 -1
  16. package/dist/assets/{SceneManager-D0NdrG8K.js → SceneManager-CIWrGc-K.js} +4 -4
  17. package/dist/assets/{SkillsPanel-DpqD60Hy.js → SkillsPanel-_KHtTDhC.js} +1 -1
  18. package/dist/assets/{SlackMultiInstanceSetup-BdoALd_g.js → SlackMultiInstanceSetup-DbQ3rUtd.js} +1 -1
  19. package/dist/assets/{SpawnModal-DFFFH2kc.js → SpawnModal-CYEoOMPW.js} +1 -1
  20. package/dist/assets/{StatisticsModal-Dv_rYZDt.js → StatisticsModal-BJ-coeis.js} +1 -1
  21. package/dist/assets/{SubordinateAssignmentModal-CRCCI686.js → SubordinateAssignmentModal-5Z3jvbM9.js} +1 -1
  22. package/dist/assets/SymbolsNerdFontMono-Regular-BM2yZ5nr.woff2 +0 -0
  23. package/dist/assets/{TriggerManagerPanel-DBN8IRa-.js → TriggerManagerPanel-DvC0xP9r.js} +1 -1
  24. package/dist/assets/{WorkflowEditorPanel-uQEvNTs2.js → WorkflowEditorPanel-6ym1_csr.js} +1 -1
  25. package/dist/assets/{area-logos-CuVxToGM.js → area-logos-mWPCtCnf.js} +1 -1
  26. package/dist/assets/{index-Dwll5Eyj.js → index-8VVbTmtb.js} +1 -1
  27. package/dist/assets/{index-BNzYPyBm.css → index-BPjQZacc.css} +1 -1
  28. package/dist/assets/index-C34l72MW.js +1 -0
  29. package/dist/assets/{index-DKnfLe7S.js → index-CS8OutmQ.js} +1 -1
  30. package/dist/assets/{index-ipuWrg1S.js → index-CT2vtbed.js} +3 -3
  31. package/dist/assets/{index-Blc4MwKs.js → index-CY_NzVPm.js} +1 -1
  32. package/dist/assets/index-CiKbhMfN.js +1 -0
  33. package/dist/assets/{index-HvyFhszc.js → index-CnESn83U.js} +2 -2
  34. package/dist/assets/{index-8Ghh3AQ-.js → index-DZiNmiyC.js} +1 -1
  35. package/dist/assets/{index-D4iie3Vd.js → index-DxyZ-rYT.js} +1 -1
  36. package/dist/assets/main-BHBs2E1L.css +1 -0
  37. package/dist/assets/main-D5j8Ul0m.js +289 -0
  38. package/dist/assets/{web-D0JSbYeo.js → web-BvAQVbIF.js} +1 -1
  39. package/dist/assets/{web-1pfPwxBS.js → web-Cd0-XOkz.js} +1 -1
  40. package/dist/assets/{web-DjgiLxvi.js → web-DMxl9AVF.js} +1 -1
  41. package/dist/build-info.json +1 -0
  42. package/dist/index.html +2 -2
  43. package/dist/locales/de/config.json +1 -0
  44. package/dist/locales/en/config.json +6 -1
  45. package/dist/locales/en/terminal.json +36 -0
  46. package/dist/locales/es/config.json +4 -1
  47. package/dist/locales/es/terminal.json +8 -0
  48. package/dist/locales/fr/config.json +1 -0
  49. package/dist/locales/hi/config.json +1 -0
  50. package/dist/locales/it/config.json +1 -0
  51. package/dist/locales/ja/config.json +1 -0
  52. package/dist/locales/pt/config.json +1 -0
  53. package/dist/locales/ru/config.json +1 -0
  54. package/dist/locales/zh-CN/config.json +1 -0
  55. package/dist/src/packages/server/codex/app-server/app-server-event-adapter.js +222 -0
  56. package/dist/src/packages/server/codex/app-server/app-server-process.js +304 -0
  57. package/dist/src/packages/server/codex/app-server/app-server-recovery-store.js +76 -0
  58. package/dist/src/packages/server/codex/app-server/codex-app-server-runner.js +453 -0
  59. package/dist/src/packages/server/routes/agents.js +29 -1
  60. package/dist/src/packages/server/routes/system.js +216 -1
  61. package/dist/src/packages/server/runtime/codex-runner-router.js +99 -0
  62. package/dist/src/packages/server/runtime/codex-runtime-provider.js +5 -3
  63. package/dist/src/packages/server/services/claude-credentials-service.js +536 -0
  64. package/dist/src/packages/server/services/claude-usage-service.js +35 -28
  65. package/dist/src/packages/server/services/provider-credentials-service.js +338 -0
  66. package/dist/src/packages/server/services/system-prompt-service.js +46 -0
  67. package/package.json +1 -1
  68. package/dist/assets/LogViewerModal-cnMAbNvY.js +0 -1
  69. package/dist/assets/index-BHM8V4i-.js +0 -1
  70. package/dist/assets/index-OkenGYC6.js +0 -1
  71. package/dist/assets/main-DpNJ8pqp.css +0 -1
  72. 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: getInstallInfo().currentVersion,
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 { ClaudeRunner } from '../claude/runner.js';
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
- return new ClaudeRunner(callbacks, new CodexBackend());
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() {