loadtoagent 1.3.9 → 1.3.11
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/README.ko.md +6 -3
- package/README.md +6 -3
- package/README.zh-CN.md +6 -3
- package/bin/loadtoagent.js +3 -1
- package/docs/ARCHITECTURE.md +7 -0
- package/docs/MANAGED-TERMINAL-SESSIONS.md +64 -0
- package/main.js +5 -1
- package/package.json +10 -3
- package/preload.js +3 -0
- package/renderer/app-agent-actions.js +84 -35
- package/renderer/app-dashboard.js +3 -1
- package/renderer/app-drawer.js +126 -27
- package/renderer/app-events-dialogs.js +66 -8
- package/renderer/app-events-sessions.js +3 -23
- package/renderer/app-graph-orchestration.js +5 -3
- package/renderer/app-management.js +4 -1
- package/renderer/app-runtime-overview.js +7 -0
- package/renderer/app.js +11 -1
- package/renderer/i18n-messages.js +33 -0
- package/renderer/index.html +2 -0
- package/renderer/styles-control-room.css +399 -0
- package/renderer/styles-overlays.css +2 -1
- package/renderer/styles-terminal.css +6 -0
- package/renderer/terminal-agent.js +5 -2
- package/renderer/terminal-events.js +30 -4
- package/renderer/terminal-workbench.js +15 -3
- package/renderer/terminal.js +18 -5
- package/src/bridgeServer.js +1 -1
- package/src/ipc/registerTerminalIpc.js +1 -1
- package/src/managedTmuxRuntime.js +53 -0
- package/src/terminalHost.js +8 -2
- package/src/terminalManager.js +173 -33
|
@@ -2258,3 +2258,402 @@ body details.control-room-project-group > summary.control-project-header {
|
|
|
2258
2258
|
animation: none!important;
|
|
2259
2259
|
}
|
|
2260
2260
|
}
|
|
2261
|
+
|
|
2262
|
+
/*
|
|
2263
|
+
* Conversation workspace
|
|
2264
|
+
* ----------------------
|
|
2265
|
+
* Live-flow entries open beside the graph and keep terminal controls secondary
|
|
2266
|
+
* until the user explicitly asks for direct terminal input.
|
|
2267
|
+
*/
|
|
2268
|
+
|
|
2269
|
+
:root {
|
|
2270
|
+
--conversation-panel-width: clamp(600px,40vw,680px);
|
|
2271
|
+
}
|
|
2272
|
+
|
|
2273
|
+
.drawer-flow-button {
|
|
2274
|
+
min-height: 36px;
|
|
2275
|
+
display: inline-flex;
|
|
2276
|
+
align-items: center;
|
|
2277
|
+
justify-content: center;
|
|
2278
|
+
gap: 7px;
|
|
2279
|
+
padding: 0 10px;
|
|
2280
|
+
border: 1px solid #304050;
|
|
2281
|
+
border-radius: 9px;
|
|
2282
|
+
background: rgba(17,25,36,.92);
|
|
2283
|
+
color: #b9c7d2;
|
|
2284
|
+
font: inherit;
|
|
2285
|
+
font-size: 12px;
|
|
2286
|
+
white-space: nowrap;
|
|
2287
|
+
flex: 0 0 auto;
|
|
2288
|
+
cursor: pointer;
|
|
2289
|
+
transition: border-color var(--motion-fast) ease,background-color var(--motion-fast) ease,color var(--motion-fast) ease,transform var(--motion-fast) ease;
|
|
2290
|
+
}
|
|
2291
|
+
|
|
2292
|
+
.drawer-flow-button:hover {
|
|
2293
|
+
border-color: color-mix(in srgb,var(--drawer-provider,#79d9f8) 50%,#304050);
|
|
2294
|
+
background: color-mix(in srgb,var(--drawer-provider,#79d9f8) 8%,#111924);
|
|
2295
|
+
color: #f3f7fa;
|
|
2296
|
+
}
|
|
2297
|
+
|
|
2298
|
+
.drawer-flow-button:active {
|
|
2299
|
+
transform: translateY(1px);
|
|
2300
|
+
}
|
|
2301
|
+
|
|
2302
|
+
.drawer-flow-button > span {
|
|
2303
|
+
color: var(--drawer-provider,#79d9f8);
|
|
2304
|
+
font-style: normal;
|
|
2305
|
+
}
|
|
2306
|
+
|
|
2307
|
+
.drawer-flow-button b {
|
|
2308
|
+
font-weight: 720;
|
|
2309
|
+
}
|
|
2310
|
+
|
|
2311
|
+
.drawer-resize-handle {
|
|
2312
|
+
position: absolute;
|
|
2313
|
+
z-index: 4;
|
|
2314
|
+
inset: 0 auto 0 -7px;
|
|
2315
|
+
width: 14px;
|
|
2316
|
+
cursor: col-resize;
|
|
2317
|
+
touch-action: none;
|
|
2318
|
+
}
|
|
2319
|
+
|
|
2320
|
+
.drawer-resize-handle::before {
|
|
2321
|
+
position: absolute;
|
|
2322
|
+
top: 50%;
|
|
2323
|
+
left: 5px;
|
|
2324
|
+
width: 3px;
|
|
2325
|
+
height: 58px;
|
|
2326
|
+
border-radius: 99px;
|
|
2327
|
+
background: #31485b;
|
|
2328
|
+
box-shadow: 0 0 0 1px rgba(8,14,21,.8);
|
|
2329
|
+
content: "";
|
|
2330
|
+
opacity: .62;
|
|
2331
|
+
transform: translateY(-50%);
|
|
2332
|
+
transition: height var(--motion-fast) ease,background-color var(--motion-fast) ease,opacity var(--motion-fast) ease;
|
|
2333
|
+
}
|
|
2334
|
+
|
|
2335
|
+
.drawer-resize-handle:hover::before,
|
|
2336
|
+
.drawer-resize-handle:focus-visible::before,
|
|
2337
|
+
body.conversation-panel-resizing .drawer-resize-handle::before {
|
|
2338
|
+
height: 88px;
|
|
2339
|
+
background: var(--drawer-provider,#79d9f8);
|
|
2340
|
+
opacity: 1;
|
|
2341
|
+
}
|
|
2342
|
+
|
|
2343
|
+
body.conversation-panel-resizing {
|
|
2344
|
+
cursor: col-resize;
|
|
2345
|
+
user-select: none;
|
|
2346
|
+
}
|
|
2347
|
+
|
|
2348
|
+
.conversation-composer-shell {
|
|
2349
|
+
display: grid;
|
|
2350
|
+
gap: 8px;
|
|
2351
|
+
}
|
|
2352
|
+
|
|
2353
|
+
.agent-command-actions .conversation-terminal-toggle {
|
|
2354
|
+
min-width: 0;
|
|
2355
|
+
border-color: #2c3c49;
|
|
2356
|
+
background: transparent;
|
|
2357
|
+
color: #82939f;
|
|
2358
|
+
}
|
|
2359
|
+
|
|
2360
|
+
.agent-command-actions .conversation-terminal-toggle > span {
|
|
2361
|
+
margin-right: 6px;
|
|
2362
|
+
color: #65b9cc;
|
|
2363
|
+
font-family: "Cascadia Code",Consolas,monospace;
|
|
2364
|
+
font-size: 11px;
|
|
2365
|
+
font-weight: 800;
|
|
2366
|
+
}
|
|
2367
|
+
|
|
2368
|
+
.conversation-terminal-expanded > span {
|
|
2369
|
+
min-width: 0;
|
|
2370
|
+
}
|
|
2371
|
+
|
|
2372
|
+
.conversation-terminal-expanded > span b,
|
|
2373
|
+
.conversation-terminal-expanded > span small {
|
|
2374
|
+
display: block;
|
|
2375
|
+
overflow: hidden;
|
|
2376
|
+
text-overflow: ellipsis;
|
|
2377
|
+
white-space: nowrap;
|
|
2378
|
+
}
|
|
2379
|
+
|
|
2380
|
+
.conversation-terminal-expanded > span b {
|
|
2381
|
+
color: #c8d2db;
|
|
2382
|
+
font-size: 12px;
|
|
2383
|
+
}
|
|
2384
|
+
|
|
2385
|
+
.conversation-terminal-expanded > span small {
|
|
2386
|
+
margin-top: 2px;
|
|
2387
|
+
color: #7f8f9d!important;
|
|
2388
|
+
}
|
|
2389
|
+
|
|
2390
|
+
.conversation-terminal-expanded > button,
|
|
2391
|
+
.conversation-terminal-expanded > [data-agent-terminal-open],
|
|
2392
|
+
.conversation-terminal-expanded > [data-resume-agent] {
|
|
2393
|
+
min-height: 32px;
|
|
2394
|
+
padding: 0 10px;
|
|
2395
|
+
border: 1px solid #34495a;
|
|
2396
|
+
border-radius: 8px;
|
|
2397
|
+
background: #121d28;
|
|
2398
|
+
color: #adbecb;
|
|
2399
|
+
font: inherit;
|
|
2400
|
+
font-size: 12px;
|
|
2401
|
+
font-weight: 680;
|
|
2402
|
+
cursor: pointer;
|
|
2403
|
+
}
|
|
2404
|
+
|
|
2405
|
+
.conversation-terminal-expanded > button:hover:not(:disabled) {
|
|
2406
|
+
border-color: #4d7188;
|
|
2407
|
+
color: #eef6fa;
|
|
2408
|
+
}
|
|
2409
|
+
|
|
2410
|
+
.conversation-terminal-expanded button:disabled {
|
|
2411
|
+
opacity: .4;
|
|
2412
|
+
cursor: not-allowed;
|
|
2413
|
+
}
|
|
2414
|
+
|
|
2415
|
+
.conversation-terminal-expanded {
|
|
2416
|
+
min-width: 0;
|
|
2417
|
+
display: grid;
|
|
2418
|
+
grid-template-columns: auto minmax(0,1fr) auto;
|
|
2419
|
+
align-items: center;
|
|
2420
|
+
gap: 10px;
|
|
2421
|
+
padding: 6px;
|
|
2422
|
+
border: 1px solid rgba(91,169,255,.22);
|
|
2423
|
+
border-radius: 10px;
|
|
2424
|
+
background: rgba(91,169,255,.045);
|
|
2425
|
+
}
|
|
2426
|
+
|
|
2427
|
+
.conversation-terminal-expanded > button:first-child {
|
|
2428
|
+
border-color: transparent;
|
|
2429
|
+
background: transparent;
|
|
2430
|
+
}
|
|
2431
|
+
|
|
2432
|
+
.conversation-terminal-expanded > button:first-child > span {
|
|
2433
|
+
margin-right: 6px;
|
|
2434
|
+
color: #79d9f8;
|
|
2435
|
+
}
|
|
2436
|
+
|
|
2437
|
+
@media (min-width:1280px) {
|
|
2438
|
+
body.conversation-context-open #appShell {
|
|
2439
|
+
width: calc(100vw - var(--conversation-panel-width));
|
|
2440
|
+
max-width: calc(100vw - var(--conversation-panel-width));
|
|
2441
|
+
transition: width var(--motion-medium) var(--motion-ease),max-width var(--motion-medium) var(--motion-ease);
|
|
2442
|
+
}
|
|
2443
|
+
|
|
2444
|
+
body.conversation-context-open {
|
|
2445
|
+
--sidebar: clamp(220px,16vw,272px);
|
|
2446
|
+
}
|
|
2447
|
+
|
|
2448
|
+
body.conversation-context-open .main-stage {
|
|
2449
|
+
padding-inline: clamp(18px,1.6vw,28px);
|
|
2450
|
+
}
|
|
2451
|
+
|
|
2452
|
+
body.conversation-context-open[data-current-view="all"] .topbar {
|
|
2453
|
+
display: block;
|
|
2454
|
+
}
|
|
2455
|
+
|
|
2456
|
+
body.conversation-context-open[data-current-view="all"] .topbar .eyebrow,
|
|
2457
|
+
body.conversation-context-open[data-current-view="all"] .topbar .subtitle {
|
|
2458
|
+
display: none;
|
|
2459
|
+
}
|
|
2460
|
+
|
|
2461
|
+
body.conversation-context-open[data-current-view="all"] .topbar h1 {
|
|
2462
|
+
max-width: 640px;
|
|
2463
|
+
font-size: clamp(25px,2.2vw,32px);
|
|
2464
|
+
line-height: 1.18;
|
|
2465
|
+
}
|
|
2466
|
+
|
|
2467
|
+
body.conversation-context-open[data-current-view="all"] .top-actions {
|
|
2468
|
+
width: 100%;
|
|
2469
|
+
flex-wrap: nowrap;
|
|
2470
|
+
justify-content: flex-start;
|
|
2471
|
+
gap: 8px;
|
|
2472
|
+
margin-top: 14px;
|
|
2473
|
+
padding-top: 0;
|
|
2474
|
+
}
|
|
2475
|
+
|
|
2476
|
+
body.conversation-context-open[data-current-view="all"] #guideBtn {
|
|
2477
|
+
width: 44px;
|
|
2478
|
+
min-width: 44px;
|
|
2479
|
+
padding: 0;
|
|
2480
|
+
}
|
|
2481
|
+
|
|
2482
|
+
body.conversation-context-open[data-current-view="all"] #guideBtn > span:nth-child(2),
|
|
2483
|
+
body.conversation-context-open[data-current-view="all"] .new-run-cta-copy {
|
|
2484
|
+
display: none;
|
|
2485
|
+
}
|
|
2486
|
+
|
|
2487
|
+
body.conversation-context-open[data-current-view="all"] .sync-state {
|
|
2488
|
+
max-width: 168px;
|
|
2489
|
+
}
|
|
2490
|
+
|
|
2491
|
+
body.conversation-context-open[data-current-view="all"] .new-run-cta {
|
|
2492
|
+
width: 48px;
|
|
2493
|
+
min-width: 48px;
|
|
2494
|
+
padding-inline: 0;
|
|
2495
|
+
}
|
|
2496
|
+
|
|
2497
|
+
body.conversation-context-open .detail-drawer[data-presentation="context"] {
|
|
2498
|
+
width: var(--conversation-panel-width);
|
|
2499
|
+
border-left-color: #304253;
|
|
2500
|
+
box-shadow: -12px 0 38px rgba(0,0,0,.28);
|
|
2501
|
+
}
|
|
2502
|
+
|
|
2503
|
+
body.conversation-context-open .detail-drawer .drawer-head {
|
|
2504
|
+
min-height: 58px;
|
|
2505
|
+
display: grid;
|
|
2506
|
+
grid-template-columns: auto minmax(0,1fr);
|
|
2507
|
+
padding: 11px 18px 8px;
|
|
2508
|
+
gap: 12px;
|
|
2509
|
+
}
|
|
2510
|
+
|
|
2511
|
+
body.conversation-context-open .detail-drawer .provider-mark,
|
|
2512
|
+
body.conversation-context-open .detail-drawer .close-button {
|
|
2513
|
+
display: none;
|
|
2514
|
+
}
|
|
2515
|
+
|
|
2516
|
+
body.conversation-context-open .detail-drawer .drawer-title h2 {
|
|
2517
|
+
margin-top: 0;
|
|
2518
|
+
font-size: 16px;
|
|
2519
|
+
}
|
|
2520
|
+
|
|
2521
|
+
body.conversation-context-open .detail-drawer .drawer-title > span {
|
|
2522
|
+
display: none;
|
|
2523
|
+
}
|
|
2524
|
+
|
|
2525
|
+
body.conversation-context-open .detail-drawer .drawer-meta {
|
|
2526
|
+
padding: 0 18px 10px;
|
|
2527
|
+
}
|
|
2528
|
+
|
|
2529
|
+
body.conversation-context-open .detail-drawer .drawer-tabs {
|
|
2530
|
+
padding-inline: 18px;
|
|
2531
|
+
}
|
|
2532
|
+
|
|
2533
|
+
body.conversation-context-open .detail-drawer[data-mode="subagent"] .drawer-tabs,
|
|
2534
|
+
body.conversation-context-open .detail-drawer[data-mode="subagent"] .subagent-work-source {
|
|
2535
|
+
display: none;
|
|
2536
|
+
}
|
|
2537
|
+
|
|
2538
|
+
body.conversation-context-open .detail-drawer .drawer-content {
|
|
2539
|
+
padding: 18px 20px 24px;
|
|
2540
|
+
background: #0d141d;
|
|
2541
|
+
}
|
|
2542
|
+
|
|
2543
|
+
body.conversation-context-open .detail-drawer[data-mode="subagent"] .drawer-content {
|
|
2544
|
+
padding-top: 14px;
|
|
2545
|
+
}
|
|
2546
|
+
|
|
2547
|
+
body.conversation-context-open .detail-drawer[data-mode="subagent"] .subagent-assignment-card {
|
|
2548
|
+
grid-template-columns: minmax(0,1fr);
|
|
2549
|
+
gap: 0;
|
|
2550
|
+
margin-bottom: 14px;
|
|
2551
|
+
padding: 0 0 14px;
|
|
2552
|
+
border: 0;
|
|
2553
|
+
border-bottom: 1px solid #22313e;
|
|
2554
|
+
border-radius: 0;
|
|
2555
|
+
background: transparent;
|
|
2556
|
+
}
|
|
2557
|
+
|
|
2558
|
+
body.conversation-context-open .detail-drawer[data-mode="subagent"] .subagent-assignment-card > span,
|
|
2559
|
+
body.conversation-context-open .detail-drawer[data-mode="subagent"] .subagent-assignment-card small {
|
|
2560
|
+
display: none;
|
|
2561
|
+
}
|
|
2562
|
+
|
|
2563
|
+
body.conversation-context-open .detail-drawer[data-mode="subagent"] .subagent-assignment-card b {
|
|
2564
|
+
color: #77c8d6;
|
|
2565
|
+
font-size: 11px;
|
|
2566
|
+
}
|
|
2567
|
+
|
|
2568
|
+
body.conversation-context-open .detail-drawer[data-mode="subagent"] .subagent-assignment-card p {
|
|
2569
|
+
margin-top: 5px;
|
|
2570
|
+
color: #b8c6d1;
|
|
2571
|
+
font-size: 12px;
|
|
2572
|
+
line-height: 1.5;
|
|
2573
|
+
}
|
|
2574
|
+
|
|
2575
|
+
body.conversation-context-open .detail-drawer[data-mode="subagent"] .subagent-work-source + .chat-history-head {
|
|
2576
|
+
top: -14px;
|
|
2577
|
+
margin: -14px 0 14px;
|
|
2578
|
+
padding-top: 10px;
|
|
2579
|
+
}
|
|
2580
|
+
|
|
2581
|
+
body.conversation-context-open .detail-drawer .chat-list {
|
|
2582
|
+
gap: 22px;
|
|
2583
|
+
}
|
|
2584
|
+
|
|
2585
|
+
body.conversation-context-open .detail-drawer .chat-turn {
|
|
2586
|
+
gap: 16px;
|
|
2587
|
+
padding-bottom: 22px;
|
|
2588
|
+
}
|
|
2589
|
+
|
|
2590
|
+
body.conversation-context-open .detail-drawer .chat-content.markdown {
|
|
2591
|
+
padding: 2px 0;
|
|
2592
|
+
border: 0;
|
|
2593
|
+
border-radius: 0;
|
|
2594
|
+
background: transparent;
|
|
2595
|
+
color: #d1dae2;
|
|
2596
|
+
font-size: 14px;
|
|
2597
|
+
line-height: 1.72;
|
|
2598
|
+
}
|
|
2599
|
+
|
|
2600
|
+
body.conversation-context-open .detail-drawer .chat-row.user .chat-content.markdown {
|
|
2601
|
+
padding: 10px 12px;
|
|
2602
|
+
border-left: 2px solid color-mix(in srgb,var(--drawer-provider,#79d9f8) 65%,#526271);
|
|
2603
|
+
border-radius: 0 8px 8px 0;
|
|
2604
|
+
background: rgba(255,255,255,.025);
|
|
2605
|
+
}
|
|
2606
|
+
|
|
2607
|
+
body.conversation-context-open .detail-drawer .chat-row {
|
|
2608
|
+
grid-template-columns: 31px minmax(0,1fr);
|
|
2609
|
+
gap: 12px;
|
|
2610
|
+
}
|
|
2611
|
+
|
|
2612
|
+
body.conversation-context-open .detail-drawer .chat-avatar {
|
|
2613
|
+
width: 29px;
|
|
2614
|
+
height: 29px;
|
|
2615
|
+
}
|
|
2616
|
+
|
|
2617
|
+
body.conversation-context-open .detail-drawer .chat-history-head {
|
|
2618
|
+
top: -18px;
|
|
2619
|
+
background: linear-gradient(180deg,#0d141d 78%,rgba(13,20,29,.94));
|
|
2620
|
+
}
|
|
2621
|
+
|
|
2622
|
+
body.conversation-context-open .drawer-composer {
|
|
2623
|
+
padding: 8px 14px 12px;
|
|
2624
|
+
border-top-color: #2b3a48;
|
|
2625
|
+
background: #0a1119;
|
|
2626
|
+
box-shadow: 0 -10px 28px rgba(0,0,0,.2);
|
|
2627
|
+
}
|
|
2628
|
+
|
|
2629
|
+
body.conversation-context-open .conversation-composer-shell {
|
|
2630
|
+
gap: 6px;
|
|
2631
|
+
}
|
|
2632
|
+
|
|
2633
|
+
body.conversation-context-open .drawer-composer .agent-command-panel {
|
|
2634
|
+
gap: 7px;
|
|
2635
|
+
padding: 0;
|
|
2636
|
+
border: 0;
|
|
2637
|
+
border-radius: 0;
|
|
2638
|
+
background: transparent;
|
|
2639
|
+
box-shadow: none;
|
|
2640
|
+
}
|
|
2641
|
+
|
|
2642
|
+
body.conversation-context-open .drawer-composer .conversation-composer > header {
|
|
2643
|
+
display: none;
|
|
2644
|
+
}
|
|
2645
|
+
|
|
2646
|
+
body.conversation-context-open .drawer-composer textarea {
|
|
2647
|
+
min-height: 52px;
|
|
2648
|
+
font-size: 13px;
|
|
2649
|
+
}
|
|
2650
|
+
|
|
2651
|
+
body.conversation-context-open .conversation-composer-shell.mode-terminal .agent-command-panel {
|
|
2652
|
+
padding-top: 2px;
|
|
2653
|
+
}
|
|
2654
|
+
|
|
2655
|
+
body.conversation-panel-resizing #appShell,
|
|
2656
|
+
body.conversation-panel-resizing .detail-drawer {
|
|
2657
|
+
transition: none!important;
|
|
2658
|
+
}
|
|
2659
|
+
}
|
|
@@ -151,7 +151,8 @@
|
|
|
151
151
|
animation: motion-backdrop-in var(--motion-medium) ease both;
|
|
152
152
|
}
|
|
153
153
|
|
|
154
|
-
.drawer-backdrop.closing
|
|
154
|
+
.drawer-backdrop.closing,
|
|
155
|
+
.drawer-backdrop.hidden {
|
|
155
156
|
animation: motion-backdrop-out 260ms ease both;
|
|
156
157
|
pointer-events: none;
|
|
157
158
|
}
|
|
@@ -1472,6 +1472,8 @@ body[data-current-view="terminal"] .topbar h1 {
|
|
|
1472
1472
|
}
|
|
1473
1473
|
|
|
1474
1474
|
.terminal-session-item[data-status="running"] { border-left-color: #51dbae; }
|
|
1475
|
+
.terminal-session-item[data-status="detached"] { border-left-color: #7ba9d8; }
|
|
1476
|
+
.terminal-session-item[data-status="stopped"] { border-left-color: #667783; opacity: .88; }
|
|
1475
1477
|
.terminal-session-item[data-status="attention"] { border-color: #60482f; border-left-color: #f0ad62; background: linear-gradient(100deg,rgba(133,83,31,.18),#0d171e 58%); }
|
|
1476
1478
|
.terminal-session-item[data-status="failed"] { border-color: #58323c; border-left-color: #ef7d8e; }
|
|
1477
1479
|
.terminal-session-item[data-status="completed"] { border-left-color: #6cbdd1; }
|
|
@@ -1500,6 +1502,10 @@ body[data-current-view="terminal"] .topbar h1 {
|
|
|
1500
1502
|
.terminal-session-status i { width: 6px; height: 6px; border-radius: 50%; background: #657581; }
|
|
1501
1503
|
.terminal-session-status[data-status="running"] { border-color: #2e6958; color: #86e8c5; background: #102921; }
|
|
1502
1504
|
.terminal-session-status[data-status="running"] i { background: #55e2ad; box-shadow: 0 0 8px rgba(85,226,173,.65); }
|
|
1505
|
+
.terminal-session-status[data-status="detached"] { border-color: #365875; color: #a8d1f2; background: #112333; }
|
|
1506
|
+
.terminal-session-status[data-status="detached"] i { background: #78afe0; }
|
|
1507
|
+
.terminal-session-status[data-status="stopped"] { border-color: #3b4a54; color: #a8b4bc; background: #141d23; }
|
|
1508
|
+
.terminal-session-status[data-status="stopped"] i { background: #71818c; }
|
|
1503
1509
|
.terminal-session-status[data-status="attention"] { border-color: #725432; color: #f3bb77; background: #2a2014; }
|
|
1504
1510
|
.terminal-session-status[data-status="attention"] i { background: #ffbd66; box-shadow: 0 0 8px rgba(255,189,102,.5); }
|
|
1505
1511
|
.terminal-session-status[data-status="failed"] { border-color: #6f3b47; color: #f3a0ad; background: #29161d; }
|
|
@@ -153,12 +153,15 @@ window.LoadToAgentTerminalAgentActions = function createModule(context) {
|
|
|
153
153
|
const created = await window.loadtoagent.terminalCreate({
|
|
154
154
|
type: 'agent',
|
|
155
155
|
provider: support.provider,
|
|
156
|
-
args: resumeLaunchArgs(support, sendDraft ? prompt : ''
|
|
156
|
+
args: resumeLaunchArgs(support, sendDraft ? prompt : ''),
|
|
157
157
|
cwd,
|
|
158
158
|
distro,
|
|
159
159
|
bridgeId: agentSession.id,
|
|
160
160
|
title,
|
|
161
|
-
|
|
161
|
+
// Conversation sends must keep the resumed PTY alive. A transient
|
|
162
|
+
// one-shot process can exit after spawn (for example when the session is
|
|
163
|
+
// busy) while the composer incorrectly reports that the prompt was sent.
|
|
164
|
+
transient: false,
|
|
162
165
|
cols: 120,
|
|
163
166
|
rows: 32,
|
|
164
167
|
});
|
|
@@ -262,7 +262,14 @@ window.LoadToAgentTerminalEvents = function bindTerminalEvents(context) {
|
|
|
262
262
|
const session = currentSession();
|
|
263
263
|
if (!session) return;
|
|
264
264
|
await runBusy(event.currentTarget, async () => {
|
|
265
|
-
const
|
|
265
|
+
const managedSession = session.backend === 'managed-tmux';
|
|
266
|
+
const restarted = await guarded(
|
|
267
|
+
() => managedSession
|
|
268
|
+
? window.loadtoagent.terminalReconnect(session.id)
|
|
269
|
+
: window.loadtoagent.terminalRestart(session.id),
|
|
270
|
+
managedSession ? t('terminal.session.reconnected') : t('terminal.session.restarted'),
|
|
271
|
+
`${managedSession ? 'terminal-reconnect' : 'terminal-restart'}:${session.id}`,
|
|
272
|
+
);
|
|
266
273
|
if (restarted) {
|
|
267
274
|
const entry = state.terminals.get(session.id);
|
|
268
275
|
if (entry) entry.terminal.reset();
|
|
@@ -273,11 +280,21 @@ window.LoadToAgentTerminalEvents = function bindTerminalEvents(context) {
|
|
|
273
280
|
});
|
|
274
281
|
const endTerminalSession = async (button, session) => {
|
|
275
282
|
if (!session) return;
|
|
283
|
+
const managedSession = session.backend === 'managed-tmux';
|
|
284
|
+
const stopManagedSession = managedSession && session.status !== 'stopped';
|
|
276
285
|
const confirmation = isAiTerminalSession(session) ? 'terminal.session.confirm_end_ai' : 'terminal.session.confirm_end';
|
|
277
|
-
if (session.type !== 'tmux' && session.status
|
|
286
|
+
if (session.type !== 'tmux' && ['running', 'detached'].includes(session.status) && !window.confirm(t(confirmation, { title: session.title }))) return;
|
|
278
287
|
await runBusy(button, async () => {
|
|
279
|
-
const message =
|
|
280
|
-
|
|
288
|
+
const message = stopManagedSession
|
|
289
|
+
? t('terminal.session.stopped')
|
|
290
|
+
: session.type === 'tmux' ? t('terminal.tmux.detached_input') : t('terminal.session.ended');
|
|
291
|
+
const closed = await guarded(
|
|
292
|
+
() => stopManagedSession
|
|
293
|
+
? window.loadtoagent.terminalStop(session.id)
|
|
294
|
+
: window.loadtoagent.terminalClose(session.id),
|
|
295
|
+
message,
|
|
296
|
+
`${stopManagedSession ? 'terminal-stop' : 'terminal-close'}:${session.id}`,
|
|
297
|
+
);
|
|
281
298
|
if (!closed) return;
|
|
282
299
|
const entry = state.terminals.get(session.id);
|
|
283
300
|
if (entry) {
|
|
@@ -304,6 +321,15 @@ window.LoadToAgentTerminalEvents = function bindTerminalEvents(context) {
|
|
|
304
321
|
return;
|
|
305
322
|
}
|
|
306
323
|
if (isAiTerminalSession(session)) {
|
|
324
|
+
if (session.backend === 'managed-tmux' && session.status === 'running') {
|
|
325
|
+
const detached = await runBusy(event.currentTarget, () => guarded(
|
|
326
|
+
() => window.loadtoagent.terminalDetach(session.id),
|
|
327
|
+
t('terminal.tmux.detached_input'),
|
|
328
|
+
`terminal-detach:${session.id}`,
|
|
329
|
+
));
|
|
330
|
+
if (!detached) return;
|
|
331
|
+
await refreshSessions();
|
|
332
|
+
}
|
|
307
333
|
state.captureGeneration += 1;
|
|
308
334
|
state.selectedId = null;
|
|
309
335
|
state.boundAgent = null;
|
|
@@ -212,6 +212,8 @@ window.LoadToAgentTerminalWorkbench = function createModule(context) {
|
|
|
212
212
|
const remote = currentTmux();
|
|
213
213
|
const bound = visibleBoundAgent();
|
|
214
214
|
const aiTerminal = isAiTerminalSession(session);
|
|
215
|
+
const managedSession = Boolean(session && session.backend === 'managed-tmux');
|
|
216
|
+
const reconnectable = managedSession && session.status === 'detached';
|
|
215
217
|
const hasTarget = Boolean(session || remote);
|
|
216
218
|
const canInput = Boolean((remote && !remote.pane.dead) || (session && session.status === 'running'));
|
|
217
219
|
const closeButton = $('#terminalCloseBtn');
|
|
@@ -225,8 +227,14 @@ window.LoadToAgentTerminalWorkbench = function createModule(context) {
|
|
|
225
227
|
const endSessionButton = $('#terminalEndSessionBtn');
|
|
226
228
|
endSessionButton.classList.toggle('hidden', !aiTerminal);
|
|
227
229
|
endSessionButton.disabled = !aiTerminal;
|
|
228
|
-
|
|
229
|
-
|
|
230
|
+
endSessionButton.textContent = managedSession && session.status === 'stopped'
|
|
231
|
+
? t('terminal.remove_session_record')
|
|
232
|
+
: t('terminal.end_ai_session');
|
|
233
|
+
const restartButton = $('#terminalRestartBtn');
|
|
234
|
+
const showRestart = Boolean(session && (reconnectable || (session.type !== 'agent' && session.status !== 'running')));
|
|
235
|
+
restartButton.classList.toggle('hidden', !showRestart);
|
|
236
|
+
restartButton.disabled = !showRestart;
|
|
237
|
+
restartButton.textContent = reconnectable ? t('terminal.reconnect') : t('ui.restart');
|
|
230
238
|
$('#terminalCommandInput').disabled = !canInput;
|
|
231
239
|
const commandForm = $('#terminalCommandForm');
|
|
232
240
|
const commandButton = commandForm.querySelector('button[type="submit"]');
|
|
@@ -244,7 +252,11 @@ window.LoadToAgentTerminalWorkbench = function createModule(context) {
|
|
|
244
252
|
$('#terminalTargetIcon').textContent = terminalTypeMark(session);
|
|
245
253
|
$('#terminalTargetMeta').innerHTML = `<b>${esc(session.title)}</b><span>${bound ? `● ${t('terminal.bound_ai_session')} · ` : ''}${session.recoveredAfterHostRestart ? `${t('terminal.recovered_after_host_restart')} · ` : ''}${esc(session.type.toUpperCase())} · PID ${session.pid || '--'} · ${esc(session.cwd || session.distro || '')}</span>`;
|
|
246
254
|
$('#terminalConsoleCaption').textContent = `${terminalTypeLabel(session)} · PID ${session.pid || '--'}`;
|
|
247
|
-
$('#terminalConsoleState').textContent =
|
|
255
|
+
$('#terminalConsoleState').textContent = session.status === 'detached'
|
|
256
|
+
? t('terminal.detached_work_continues')
|
|
257
|
+
: session.status === 'stopped'
|
|
258
|
+
? t('terminal.stopped_record_kept')
|
|
259
|
+
: presentation.tone === 'attention' || presentation.tone === 'completed'
|
|
248
260
|
? presentation.label
|
|
249
261
|
: canInput ? window.LoadToAgentI18n.t("ui.direct_input_available") : window.LoadToAgentI18n.t("ui.ended_session");
|
|
250
262
|
$('#terminalConsoleState').dataset.status = presentation.tone;
|
package/renderer/terminal.js
CHANGED
|
@@ -83,6 +83,8 @@
|
|
|
83
83
|
Object.assign(STATUS_LABELS, {
|
|
84
84
|
starting: t('ui.preparing'),
|
|
85
85
|
running: t('terminal.status.running'),
|
|
86
|
+
detached: t('terminal.status.detached'),
|
|
87
|
+
stopped: t('terminal.status.stopped'),
|
|
86
88
|
exited: t('terminal.status.exited'),
|
|
87
89
|
failed: t('terminal.status.failed'),
|
|
88
90
|
});
|
|
@@ -179,11 +181,9 @@
|
|
|
179
181
|
return { supported: true, provider, sessionId, args };
|
|
180
182
|
}
|
|
181
183
|
|
|
182
|
-
function resumeLaunchArgs(support, prompt = ''
|
|
184
|
+
function resumeLaunchArgs(support, prompt = '') {
|
|
183
185
|
const args = [...support.args];
|
|
184
186
|
const text = String(prompt || '').trim();
|
|
185
|
-
if (options.background && text && support.provider === 'codex') return ['exec', 'resume', support.sessionId, text];
|
|
186
|
-
if (options.background && text && support.provider === 'claude') return ['--resume', support.sessionId, '--print', text];
|
|
187
187
|
if (text) args.push(text);
|
|
188
188
|
return args;
|
|
189
189
|
}
|
|
@@ -698,13 +698,26 @@
|
|
|
698
698
|
state.active = false;
|
|
699
699
|
return;
|
|
700
700
|
}
|
|
701
|
+
// Snapshot renders call activate repeatedly while this view is already
|
|
702
|
+
// open. Refreshing and re-showing the same xterm screen would temporarily
|
|
703
|
+
// hide its helper textarea and steal direct keyboard focus.
|
|
704
|
+
if (!enteringMode) return;
|
|
701
705
|
await refreshSessions();
|
|
702
706
|
if (!state.active || state.mode !== nextMode) return;
|
|
707
|
+
let selectionChanged = false;
|
|
703
708
|
if (enteringMode && !state.selectedId && !state.selectedTmux) {
|
|
704
709
|
const visible = modeSessions();
|
|
705
|
-
if (visible.length)
|
|
706
|
-
|
|
710
|
+
if (visible.length) {
|
|
711
|
+
state.selectedId = visible[0].id;
|
|
712
|
+
selectionChanged = true;
|
|
713
|
+
} else if (state.mode === 'tmux') {
|
|
714
|
+
state.selectedTmux = tmuxRows()[0] || null;
|
|
715
|
+
selectionChanged = Boolean(state.selectedTmux);
|
|
716
|
+
}
|
|
707
717
|
}
|
|
718
|
+
// refreshSessions already rendered and showed the existing selection. Avoid
|
|
719
|
+
// hiding the same xterm host a second time after users can start typing.
|
|
720
|
+
if (!selectionChanged) return;
|
|
708
721
|
renderAll();
|
|
709
722
|
await showSelection();
|
|
710
723
|
}
|
package/src/bridgeServer.js
CHANGED
|
@@ -209,7 +209,7 @@ class BridgeServer {
|
|
|
209
209
|
for (const client of this.clients.values()) {
|
|
210
210
|
if (client.terminalId !== session.id) continue;
|
|
211
211
|
sendFrame(client.socket, { type: 'state', status: session.status, exitCode: session.exitCode, signal: session.signal });
|
|
212
|
-
if (
|
|
212
|
+
if (['detached', 'stopped', 'exited', 'failed'].includes(session.status)) client.socket.end();
|
|
213
213
|
}
|
|
214
214
|
}
|
|
215
215
|
|
|
@@ -31,7 +31,7 @@ function registerTerminalIpc({ ipcMain, requireTrustedSender, trustedSender, man
|
|
|
31
31
|
requireTrustedSender(event);
|
|
32
32
|
return requireManager(manager).resize(id, cols, rows);
|
|
33
33
|
});
|
|
34
|
-
for (const operation of ['signal', 'restart', 'close']) {
|
|
34
|
+
for (const operation of ['signal', 'restart', 'reconnect', 'detach', 'stop', 'close']) {
|
|
35
35
|
ipcMain.handle(`terminals:${operation}`, (event, ...args) => {
|
|
36
36
|
requireTrustedSender(event);
|
|
37
37
|
return requireManager(manager)[operation](...args);
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { execFileSync } = require('child_process');
|
|
4
|
+
|
|
5
|
+
class ManagedTmuxRuntime {
|
|
6
|
+
constructor(options = {}) {
|
|
7
|
+
this.platform = options.platform || process.platform;
|
|
8
|
+
this.execFileSync = options.execFileSync || execFileSync;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
command(options, args) {
|
|
12
|
+
if (this.platform === 'win32') {
|
|
13
|
+
return {
|
|
14
|
+
file: 'wsl.exe',
|
|
15
|
+
args: ['-d', options.distro, '--', 'tmux', '-L', options.tmuxSocket, ...args],
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
return {
|
|
19
|
+
file: 'tmux',
|
|
20
|
+
args: ['-L', options.tmuxSocket, ...args],
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
execute(options, args) {
|
|
25
|
+
const command = this.command(options, args);
|
|
26
|
+
return this.execFileSync(command.file, command.args, {
|
|
27
|
+
encoding: 'utf8',
|
|
28
|
+
timeout: this.platform === 'win32' ? 15_000 : 5_000,
|
|
29
|
+
windowsHide: true,
|
|
30
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
exists(options) {
|
|
35
|
+
try {
|
|
36
|
+
this.execute(options, ['has-session', '-t', `=${options.managedTmuxSession}`]);
|
|
37
|
+
return true;
|
|
38
|
+
} catch (_missingSession) {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
stop(options) {
|
|
44
|
+
try {
|
|
45
|
+
this.execute(options, ['kill-session', '-t', `=${options.managedTmuxSession}`]);
|
|
46
|
+
} catch (error) {
|
|
47
|
+
if (error?.status !== 1) throw error;
|
|
48
|
+
}
|
|
49
|
+
return { ok: true };
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
module.exports = { ManagedTmuxRuntime };
|