@xcanwin/manyoyo 5.2.8 → 5.2.15

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.
@@ -1339,7 +1339,7 @@ class PlaywrightPlugin {
1339
1339
  const scenes = this.resolveTargets('all');
1340
1340
  for (const sceneName of scenes) {
1341
1341
  const url = `http://${host}:${this.scenePort(sceneName)}/mcp`;
1342
- this.writeStdout(`claude mcp add --transport http playwright-${sceneName} ${url}`);
1342
+ this.writeStdout(`claude mcp add --transport http -s user playwright-${sceneName} ${url}`);
1343
1343
  }
1344
1344
  this.writeStdout('');
1345
1345
  for (const sceneName of scenes) {
@@ -104,17 +104,16 @@ body::after {
104
104
  z-index: 1;
105
105
  height: 100vh;
106
106
  height: 100dvh;
107
- padding: 18px;
107
+ padding: 0;
108
108
  display: grid;
109
109
  grid-template-columns: minmax(282px, 334px) minmax(0, 1fr);
110
- gap: 16px;
110
+ gap: 0;
111
111
  }
112
112
 
113
113
  .sidebar,
114
114
  .main {
115
- border-radius: 18px;
116
- border: 1px solid var(--line);
117
- box-shadow: var(--shadow-soft);
115
+ border-radius: 0;
116
+ border: none;
118
117
  }
119
118
 
120
119
  .sidebar {
@@ -125,6 +124,7 @@ body::after {
125
124
  gap: 12px;
126
125
  background:
127
126
  linear-gradient(165deg, #fffefd 0%, #f8f1e7 100%);
127
+ border-right: 1px solid var(--line);
128
128
  animation: panelIn 320ms ease both;
129
129
  }
130
130
 
@@ -246,12 +246,17 @@ textarea:focus-visible {
246
246
  }
247
247
 
248
248
  .mobile-session-toggle,
249
- .mobile-actions-toggle,
250
249
  .mobile-sidebar-close,
251
250
  .sidebar-backdrop {
252
251
  display: none;
253
252
  }
254
253
 
254
+ .mobile-actions-toggle {
255
+ display: inline-flex;
256
+ padding: 8px 12px;
257
+ font-size: 13px;
258
+ }
259
+
255
260
  .sidebar-backdrop[hidden] {
256
261
  display: none !important;
257
262
  }
@@ -561,6 +566,7 @@ textarea:focus-visible {
561
566
  }
562
567
 
563
568
  .header {
569
+ position: relative;
564
570
  display: flex;
565
571
  justify-content: space-between;
566
572
  align-items: flex-start;
@@ -570,6 +576,7 @@ textarea:focus-visible {
570
576
  }
571
577
 
572
578
  .header-main {
579
+ flex: 1;
573
580
  min-width: 0;
574
581
  display: flex;
575
582
  flex-direction: column;
@@ -605,10 +612,23 @@ textarea:focus-visible {
605
612
  }
606
613
 
607
614
  .header-actions {
608
- display: flex;
609
- align-items: center;
615
+ display: none;
616
+ position: absolute;
617
+ top: calc(100% + 6px);
618
+ right: 8px;
619
+ z-index: 8;
620
+ width: min(240px, calc(100vw - 36px));
621
+ padding: 8px;
622
+ border: 1px solid var(--line);
623
+ border-radius: 10px;
624
+ background: #fffaf2;
625
+ box-shadow: var(--shadow-strong);
626
+ grid-template-columns: 1fr;
610
627
  gap: 8px;
611
- flex-wrap: wrap;
628
+ }
629
+
630
+ body.mobile-actions-open .header-actions {
631
+ display: grid;
612
632
  }
613
633
 
614
634
  .mode-switch {
@@ -652,17 +672,6 @@ body.terminal-mode #modeTerminalBtn {
652
672
  border-color: var(--accent-strong);
653
673
  }
654
674
 
655
- .mode-terminal-controls {
656
- display: none;
657
- align-items: center;
658
- gap: 8px;
659
- min-width: 0;
660
- }
661
-
662
- body.terminal-mode .mode-terminal-controls {
663
- display: inline-flex;
664
- }
665
-
666
675
  #messages {
667
676
  min-height: 0;
668
677
  overflow-y: auto;
@@ -682,28 +691,13 @@ body.terminal-mode .mode-terminal-controls {
682
691
  min-height: 0;
683
692
  display: none;
684
693
  flex-direction: column;
685
- gap: 8px;
686
- margin: 10px 8px 0;
687
- padding: 12px;
688
- border: 1px solid var(--line);
689
- border-radius: 14px;
690
- background: rgba(255, 251, 245, 0.9);
691
- }
692
-
693
- .terminal-status {
694
- display: inline-block;
695
- color: var(--muted);
696
- font-size: 12px;
697
- white-space: nowrap;
694
+ margin: 4px 0 0;
698
695
  }
699
696
 
700
697
  #terminalScreen {
701
698
  flex: 1;
702
699
  min-height: 0;
703
700
  height: 100%;
704
- border-radius: 12px;
705
- border: 1px solid #2a3544;
706
- box-shadow: inset 0 0 0 1px rgba(255, 209, 102, 0.14);
707
701
  overflow: hidden;
708
702
  background: var(--terminal-bg);
709
703
  }
@@ -711,18 +705,13 @@ body.terminal-mode .mode-terminal-controls {
711
705
  #terminalScreen .xterm {
712
706
  width: 100%;
713
707
  height: 100%;
714
- padding: 8px 6px;
708
+ padding: 4px 6px;
715
709
  }
716
710
 
717
711
  #terminalScreen .xterm-screen {
718
712
  width: 100%;
719
713
  }
720
714
 
721
- .terminal-foot {
722
- color: var(--muted);
723
- font-size: 12px;
724
- }
725
-
726
715
  body.command-mode #messages {
727
716
  display: flex;
728
717
  }
@@ -989,13 +978,14 @@ body.command-mode .msg.origin-agent .bubble {
989
978
 
990
979
  .sidebar {
991
980
  position: fixed;
992
- left: max(10px, env(safe-area-inset-left));
993
- top: max(10px, env(safe-area-inset-top));
994
- bottom: max(10px, env(safe-area-inset-bottom));
981
+ left: env(safe-area-inset-left, 0px);
982
+ top: env(safe-area-inset-top, 0px);
983
+ bottom: env(safe-area-inset-bottom, 0px);
995
984
  width: min(86vw, 346px);
996
985
  max-width: 346px;
997
986
  max-height: none;
998
987
  background: rgba(255, 252, 246, 0.98);
988
+ border-radius: 0;
999
989
  z-index: 40;
1000
990
  transform: translateX(calc(-100% - 16px));
1001
991
  opacity: 0;
@@ -1012,14 +1002,15 @@ body.command-mode .msg.origin-agent .bubble {
1012
1002
 
1013
1003
  .main {
1014
1004
  position: fixed;
1015
- left: max(10px, env(safe-area-inset-left));
1016
- right: max(10px, env(safe-area-inset-right));
1017
- top: max(10px, env(safe-area-inset-top));
1018
- bottom: max(10px, env(safe-area-inset-bottom));
1005
+ left: env(safe-area-inset-left, 0px);
1006
+ right: env(safe-area-inset-right, 0px);
1007
+ top: env(safe-area-inset-top, 0px);
1008
+ bottom: env(safe-area-inset-bottom, 0px);
1019
1009
  height: auto;
1020
1010
  min-height: auto;
1021
1011
  overflow: hidden;
1022
1012
  z-index: 1;
1013
+ grid-template-columns: minmax(0, 1fr);
1023
1014
  }
1024
1015
 
1025
1016
  #messages,
@@ -1040,10 +1031,6 @@ body.command-mode .msg.origin-agent .bubble {
1040
1031
  font-size: 13px;
1041
1032
  }
1042
1033
 
1043
- .mobile-actions-toggle {
1044
- display: none;
1045
- }
1046
-
1047
1034
  .sidebar-backdrop {
1048
1035
  display: block;
1049
1036
  position: fixed;
@@ -1068,7 +1055,6 @@ body.command-mode .msg.origin-agent .bubble {
1068
1055
  }
1069
1056
 
1070
1057
  .header {
1071
- position: relative;
1072
1058
  padding: 10px 12px 16px;
1073
1059
  }
1074
1060
 
@@ -1080,57 +1066,21 @@ body.command-mode .msg.origin-agent .bubble {
1080
1066
  display: none;
1081
1067
  }
1082
1068
 
1083
- .header-actions {
1084
- display: none;
1085
- position: absolute;
1086
- top: calc(100% + 6px);
1087
- right: 8px;
1088
- z-index: 8;
1089
- width: min(240px, calc(100vw - 36px));
1090
- padding: 8px;
1091
- border: 1px solid var(--line);
1092
- border-radius: 10px;
1093
- background: #fffaf2;
1094
- box-shadow: var(--shadow-strong);
1095
- grid-template-columns: 1fr;
1096
- gap: 8px;
1097
- }
1098
-
1099
- body.mobile-actions-open .header-actions {
1100
- display: grid;
1101
- }
1102
-
1103
- .mobile-actions-toggle {
1104
- display: inline-flex;
1105
- padding: 8px 12px;
1106
- font-size: 13px;
1107
- }
1108
-
1109
1069
  .mode-switch {
1110
1070
  margin: 8px 8px 2px;
1111
- overflow-x: auto;
1071
+ flex-wrap: wrap;
1112
1072
  padding: 8px;
1113
1073
  gap: 8px;
1114
1074
  }
1115
1075
 
1116
- .mode-switch-left,
1117
- .mode-terminal-controls,
1118
- .mode-switch button {
1119
- flex: 0 0 auto;
1076
+ .mode-switch-left {
1077
+ flex: 1 1 auto;
1078
+ min-width: 0;
1120
1079
  }
1121
1080
 
1122
1081
  .mode-switch button {
1123
- min-width: 90px;
1124
- }
1125
-
1126
- .mode-terminal-controls {
1127
- gap: 6px;
1128
- }
1129
-
1130
- .terminal-status {
1131
- max-width: 5.2em;
1132
- overflow: hidden;
1133
- text-overflow: ellipsis;
1082
+ flex: 0 0 auto;
1083
+ min-width: 84px;
1134
1084
  }
1135
1085
 
1136
1086
  #commandInput {
@@ -70,16 +70,12 @@
70
70
  <button type="button" id="modeCommandBtn" class="secondary">命令模式</button>
71
71
  <button type="button" id="modeTerminalBtn" class="secondary">交互终端</button>
72
72
  </div>
73
- <div class="mode-terminal-controls">
74
- <button type="button" id="terminalConnectBtn">连接终端</button>
75
- <button type="button" id="terminalDisconnectBtn" class="secondary">断开终端</button>
76
- <span id="terminalStatus" class="terminal-status">未连接</span>
77
- </div>
73
+
78
74
  </section>
79
75
  <section id="messages"></section>
80
76
  <section id="terminalPanel" hidden>
81
77
  <div id="terminalScreen" aria-label="终端输出区域"></div>
82
- <div class="terminal-foot">点击终端后可直接输入;适用于 codex / claude 等交互式 agent。</div>
78
+
83
79
  </section>
84
80
  <form class="composer" id="composer">
85
81
  <div class="composer-inner">
@@ -118,9 +118,6 @@
118
118
  const modeTerminalBtn = document.getElementById('modeTerminalBtn');
119
119
  const messagesNode = document.getElementById('messages');
120
120
  const terminalPanel = document.getElementById('terminalPanel');
121
- const terminalConnectBtn = document.getElementById('terminalConnectBtn');
122
- const terminalDisconnectBtn = document.getElementById('terminalDisconnectBtn');
123
- const terminalStatus = document.getElementById('terminalStatus');
124
121
  const terminalScreen = document.getElementById('terminalScreen');
125
122
  const composer = document.getElementById('composer');
126
123
  const commandInput = document.getElementById('commandInput');
@@ -565,15 +562,6 @@
565
562
  state.terminal.term.writeln(text);
566
563
  }
567
564
 
568
- function renderTerminalIntro() {
569
- if (!state.terminal.term) return;
570
- state.terminal.term.reset();
571
- writeTerminalLine('MANYOYO Interactive Terminal');
572
- writeTerminalLine(state.active ? ('当前会话: ' + state.active) : '当前会话: 未选择');
573
- writeTerminalLine('点击“连接终端”后可运行 codex / claude 等交互式 agent。');
574
- writeTerminalLine('');
575
- }
576
-
577
565
  function resolveFitAddonCtor() {
578
566
  if (window.FitAddon && typeof window.FitAddon.FitAddon === 'function') {
579
567
  return window.FitAddon.FitAddon;
@@ -710,7 +698,6 @@
710
698
  notifyTerminalResize(false);
711
699
  });
712
700
  state.terminal.terminalReady = true;
713
- renderTerminalIntro();
714
701
  return true;
715
702
  }
716
703
 
@@ -856,10 +843,6 @@
856
843
  return MOBILE_LAYOUT_MEDIA.matches;
857
844
  }
858
845
 
859
- function isMobileCompactLayout() {
860
- return MOBILE_COMPACT_MEDIA.matches;
861
- }
862
-
863
846
  function setMobileSessionPanel(open) {
864
847
  state.mobileSidebarOpen = Boolean(open);
865
848
  const mobileLayout = isMobileLayout();
@@ -885,14 +868,9 @@
885
868
 
886
869
  function setMobileActionsMenu(open) {
887
870
  state.mobileActionsOpen = Boolean(open);
888
- const compactLayout = isMobileCompactLayout();
889
- if (!compactLayout) {
890
- state.mobileActionsOpen = false;
891
- }
892
- const shouldOpen = state.mobileActionsOpen && compactLayout;
893
- document.body.classList.toggle('mobile-actions-open', shouldOpen);
871
+ document.body.classList.toggle('mobile-actions-open', state.mobileActionsOpen);
894
872
  if (mobileActionsToggle) {
895
- mobileActionsToggle.setAttribute('aria-expanded', shouldOpen ? 'true' : 'false');
873
+ mobileActionsToggle.setAttribute('aria-expanded', state.mobileActionsOpen ? 'true' : 'false');
896
874
  }
897
875
  }
898
876
 
@@ -986,16 +964,6 @@
986
964
  if (createModal) {
987
965
  createModal.hidden = !state.createModalOpen;
988
966
  }
989
- if (terminalConnectBtn) {
990
- terminalConnectBtn.disabled = !state.active || busy || state.terminal.connecting || state.terminal.connected;
991
- }
992
- if (terminalDisconnectBtn) {
993
- terminalDisconnectBtn.disabled = !(state.terminal.connecting || state.terminal.connected);
994
- }
995
- if (terminalStatus) {
996
- terminalStatus.textContent = state.terminal.status;
997
- }
998
-
999
967
  if (!state.active) {
1000
968
  sendState.textContent = '未选择会话';
1001
969
  } else if (agentMode && !agentEnabled) {
@@ -1194,8 +1162,10 @@
1194
1162
  closeMobileSessionPanel();
1195
1163
  }
1196
1164
  if (state.mode === 'terminal' && ensureTerminalReady()) {
1197
- renderTerminalIntro();
1198
1165
  scheduleTerminalFit(false);
1166
+ if (!state.terminal.connected && !state.terminal.connecting) {
1167
+ connectTerminal();
1168
+ }
1199
1169
  }
1200
1170
  renderSessionActiveState();
1201
1171
  syncUi();
@@ -1528,8 +1498,8 @@
1528
1498
  }
1529
1499
 
1530
1500
  if (state.mode === 'terminal' && ensureTerminalReady() && !state.terminal.connected && !state.terminal.connecting) {
1531
- renderTerminalIntro();
1532
1501
  scheduleTerminalFit(false);
1502
+ connectTerminal();
1533
1503
  }
1534
1504
 
1535
1505
  if (opts.reloadMessages) {
@@ -1879,28 +1849,15 @@
1879
1849
  renderMessages(state.messages, { forceFullRender: true });
1880
1850
  syncUi();
1881
1851
  if (ensureTerminalReady()) {
1882
- if (!state.terminal.connected && !state.terminal.connecting) {
1883
- renderTerminalIntro();
1884
- }
1885
1852
  scheduleTerminalFit(false);
1886
1853
  state.terminal.term.focus();
1854
+ if (!state.terminal.connected && !state.terminal.connecting) {
1855
+ connectTerminal();
1856
+ }
1887
1857
  }
1888
1858
  });
1889
1859
  }
1890
1860
 
1891
- if (terminalConnectBtn) {
1892
- terminalConnectBtn.addEventListener('click', function () {
1893
- connectTerminal();
1894
- });
1895
- }
1896
-
1897
- if (terminalDisconnectBtn) {
1898
- terminalDisconnectBtn.addEventListener('click', function () {
1899
- disconnectTerminal('终端已手动断开');
1900
- syncUi();
1901
- });
1902
- }
1903
-
1904
1861
  refreshBtn.addEventListener('click', function () {
1905
1862
  closeMobileActionsMenu();
1906
1863
  loadSessions(state.active).catch(function (e) { alert(e.message); });
@@ -1993,7 +1950,6 @@
1993
1950
 
1994
1951
  document.addEventListener('click', function (event) {
1995
1952
  if (!state.mobileActionsOpen) return;
1996
- if (!isMobileCompactLayout()) return;
1997
1953
  const target = event.target;
1998
1954
  if (mobileActionsToggle && mobileActionsToggle.contains(target)) return;
1999
1955
  if (headerActions && headerActions.contains(target)) return;
package/lib/web/server.js CHANGED
@@ -940,9 +940,36 @@ function buildCreateRuntime(ctx, state, payload) {
940
940
  };
941
941
  }
942
942
 
943
+ // Estimate container start time from "Up X hours/minutes/seconds" status string.
944
+ // Uses relative time to avoid Podman Machine VM clock drift issues.
945
+ function estimateStartTimeFromStatus(status) {
946
+ if (!status) return null;
947
+ const s = status.trim().toLowerCase();
948
+ if (!s.startsWith('up ')) return null;
949
+ const rest = s.slice(3).trim();
950
+ const now = Date.now();
951
+
952
+ const units = [
953
+ { re: /^(\d+)\s+week/, ms: 7 * 24 * 3600 * 1000 },
954
+ { re: /^(\d+)\s+day/, ms: 24 * 3600 * 1000 },
955
+ { re: /^(\d+)\s+hour/, ms: 3600 * 1000 },
956
+ { re: /^(\d+)\s+min/, ms: 60 * 1000 },
957
+ { re: /^(\d+)\s+second/, ms: 1000 },
958
+ ];
959
+ for (const { re, ms } of units) {
960
+ const m = rest.match(re);
961
+ if (m) return new Date(now - parseInt(m[1]) * ms).toISOString();
962
+ }
963
+ // "about a minute", "a minute", "about an hour", "an hour", "less than a second"
964
+ if (/\bminute\b/.test(rest)) return new Date(now - 60 * 1000).toISOString();
965
+ if (/\bhour\b/.test(rest)) return new Date(now - 3600 * 1000).toISOString();
966
+ if (/\bsecond\b/.test(rest)) return new Date(now).toISOString();
967
+ return null;
968
+ }
969
+
943
970
  function listWebManyoyoContainers(ctx) {
944
971
  const output = ctx.dockerExecArgs(
945
- ['ps', '-a', '--filter', 'label=manyoyo.default_cmd', '--format', '{{.Names}}\t{{.Status}}\t{{.Image}}'],
972
+ ['ps', '-a', '--format', '{{.Names}}\t{{.Status}}\t{{.Image}}'],
946
973
  { ignoreError: true }
947
974
  );
948
975
 
@@ -956,10 +983,15 @@ function listWebManyoyoContainers(ctx) {
956
983
  if (!ctx.isValidContainerName(name)) {
957
984
  return;
958
985
  }
986
+ const imageName = image || '';
987
+ if (!imageName.includes('manyoyo') && !name.startsWith('manyoyo-') && !name.startsWith('my-')) {
988
+ return;
989
+ }
959
990
  map[name] = {
960
991
  name,
961
992
  status: status || 'unknown',
962
- image: image || ''
993
+ image: imageName,
994
+ createdAt: estimateStartTimeFromStatus(status)
963
995
  };
964
996
  });
965
997
 
@@ -1117,7 +1149,7 @@ function buildSessionSummary(ctx, state, containerMap, name) {
1117
1149
  const agentMeta = getAgentRuntimeMeta(history);
1118
1150
  const latestMessage = history.messages.length ? history.messages[history.messages.length - 1] : null;
1119
1151
  const containerInfo = containerMap[name] || {};
1120
- const updatedAt = history.updatedAt || (latestMessage && latestMessage.timestamp) || null;
1152
+ const updatedAt = history.updatedAt || (latestMessage && latestMessage.timestamp) || containerInfo.createdAt || null;
1121
1153
  return {
1122
1154
  name,
1123
1155
  status: containerInfo.status || 'history',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xcanwin/manyoyo",
3
- "version": "5.2.8",
3
+ "version": "5.2.15",
4
4
  "imageVersion": "1.8.1-common",
5
5
  "description": "AI Agent CLI Security Sandbox for Docker and Podman",
6
6
  "keywords": [