loadtoagent 1.3.10 → 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 +2 -1
- package/preload.js +3 -0
- package/renderer/app-agent-actions.js +83 -35
- package/renderer/app-drawer.js +107 -26
- package/renderer/app-events-dialogs.js +66 -8
- package/renderer/app-events-sessions.js +3 -23
- 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-terminal.css +6 -0
- package/renderer/terminal-events.js +30 -4
- package/renderer/terminal-workbench.js +15 -3
- package/renderer/terminal.js +2 -0
- 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
|
+
}
|
|
@@ -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; }
|
|
@@ -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
|
});
|
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 };
|
package/src/terminalHost.js
CHANGED
|
@@ -10,12 +10,15 @@ const { spawn } = require('child_process');
|
|
|
10
10
|
const { endpointFor, safeWriteJson } = require('./bridgeServer');
|
|
11
11
|
const { runBestEffort } = require('./diagnostics');
|
|
12
12
|
|
|
13
|
-
const TERMINAL_HOST_PROTOCOL =
|
|
13
|
+
const TERMINAL_HOST_PROTOCOL = 4;
|
|
14
14
|
const TERMINAL_HOST_RUNTIME = `node-pty-${require('node-pty/package.json').version}`;
|
|
15
15
|
const MAX_FRAME_CHARS = 4 * 1024 * 1024;
|
|
16
16
|
const AUTH_TIMEOUT_MS = 5_000;
|
|
17
17
|
const REQUEST_TIMEOUT_MS = 15_000;
|
|
18
|
-
const HOST_OPERATIONS = new Set([
|
|
18
|
+
const HOST_OPERATIONS = new Set([
|
|
19
|
+
'list', 'get', 'create', 'write', 'command', 'resize', 'signal',
|
|
20
|
+
'restart', 'reconnect', 'detach', 'stop', 'close',
|
|
21
|
+
]);
|
|
19
22
|
|
|
20
23
|
function sendFrame(socket, payload) {
|
|
21
24
|
if (!socket || socket.destroyed) return;
|
|
@@ -524,6 +527,9 @@ class TerminalHostClient extends EventEmitter {
|
|
|
524
527
|
resize(id, cols, rows) { return this.request('resize', id, cols, rows); }
|
|
525
528
|
signal(id, signal) { return this.request('signal', id, signal); }
|
|
526
529
|
restart(id) { return this.request('restart', id); }
|
|
530
|
+
reconnect(id) { return this.request('reconnect', id); }
|
|
531
|
+
detach(id) { return this.request('detach', id); }
|
|
532
|
+
stop(id) { return this.request('stop', id); }
|
|
527
533
|
close(id) { return this.request('close', id); }
|
|
528
534
|
|
|
529
535
|
dispose({ shutdownIfIdle = false } = {}) {
|