harness-mix 0.1.2 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -1
- package/README.md +60 -15
- package/docs/images/codex-desktop-home.png +0 -0
- package/docs/images/codex-desktop-session.png +0 -0
- package/docs/multi-agent-collaboration.md +68 -68
- package/output/native-build/renderer-extension.js +2225 -1761
- package/package.json +1 -2
- package/scripts/collaboration-recovery-test.cjs +22 -2
- package/scripts/collaboration-ui-smoke.cjs +10 -5
- package/scripts/native-protocol-test.cjs +20 -0
- package/scripts/workspace-test.cjs +2 -14
- package/src/main/adapters/antigravity.js +49 -18
- package/src/main/adapters/claude.js +3 -2
- package/src/main/adapters/codex.js +10 -8
- package/src/main/adapters/dsh.js +3 -2
- package/src/main/adapters/opencode.js +21 -13
- package/src/main/adapters/pi-family.js +3 -3
- package/src/main/host/collaboration-worktree.js +63 -4
- package/src/main/host/collaboration.js +28 -2
- package/src/main/host/runtime.js +22 -1
- package/src/main/native/protocol.js +250 -24
- package/src/native-ui/desktop-control/dist/tsconfig.tsbuildinfo +1 -1
- package/src/native-ui/renderer-extension/dist/types/harness-mix-settings.d.ts.map +1 -1
- package/src/native-ui/renderer-extension/dist/types/index.d.ts +2 -0
- package/src/native-ui/renderer-extension/dist/types/index.d.ts.map +1 -1
- package/src/native-ui/renderer-extension/dist/types/renderer-binding-probe.d.ts.map +1 -1
- package/src/native-ui/renderer-extension/dist/types/renderer-collab-cards.d.ts +40 -0
- package/src/native-ui/renderer-extension/dist/types/renderer-collab-cards.d.ts.map +1 -0
- package/src/native-ui/renderer-extension/dist/types/renderer-credits-control.d.ts.map +1 -1
- package/src/native-ui/renderer-extension/dist/types/renderer-harness-mentions.d.ts.map +1 -1
- package/src/native-ui/renderer-extension/dist/types/renderer-model-client.d.ts +8 -0
- package/src/native-ui/renderer-extension/dist/types/renderer-model-client.d.ts.map +1 -1
- package/src/native-ui/renderer-extension/dist/types/renderer-usage-control.d.ts.map +1 -1
- package/src/native-ui/renderer-extension/dist/types/settings/connections-page.d.ts +3 -1
- package/src/native-ui/renderer-extension/dist/types/settings/connections-page.d.ts.map +1 -1
- package/src/native-ui/renderer-extension/dist/types/settings/harness-accounts.d.ts +8 -1
- package/src/native-ui/renderer-extension/dist/types/settings/harness-accounts.d.ts.map +1 -1
- package/src/native-ui/renderer-extension/dist/types/settings/localization.d.ts +16 -0
- package/src/native-ui/renderer-extension/dist/types/settings/localization.d.ts.map +1 -1
- package/src/native-ui/renderer-extension/dist/types/settings/pages.d.ts +1 -0
- package/src/native-ui/renderer-extension/dist/types/settings/pages.d.ts.map +1 -1
- package/src/native-ui/renderer-extension/dist/types/tsconfig.tsbuildinfo +1 -1
- package/src/native-ui/renderer-extension/src/assets/README.md +8 -38
- package/src/native-ui/renderer-extension/src/harness-mix-settings.ts +9 -1
- package/src/native-ui/renderer-extension/src/index.ts +9 -0
- package/src/native-ui/renderer-extension/src/renderer-binding-probe.ts +67 -6
- package/src/native-ui/renderer-extension/src/renderer-collab-cards.ts +198 -0
- package/src/native-ui/renderer-extension/src/renderer-credits-control.ts +32 -12
- package/src/native-ui/renderer-extension/src/renderer-harness-mentions.ts +139 -46
- package/src/native-ui/renderer-extension/src/renderer-model-client.ts +7 -2
- package/src/native-ui/renderer-extension/src/renderer-usage-control.ts +34 -3
- package/src/native-ui/renderer-extension/src/settings/accounts.css +80 -4
- package/src/native-ui/renderer-extension/src/settings/connections-page.ts +68 -29
- package/src/native-ui/renderer-extension/src/settings/harness-accounts.ts +89 -28
- package/src/native-ui/renderer-extension/src/settings/localization.ts +48 -0
- package/src/native-ui/renderer-extension/src/settings/pages.ts +19 -2
- package/src/native-ui/renderer-extension/src/settings/shell.css +62 -0
- package/src/native-ui/renderer-extension/src/settings/shell.ts +1 -1
- package/src/native-ui/renderer-extension/test/renderer-collab-cards.test.ts +45 -0
- package/src/native-ui/renderer-extension/test/renderer-model-client.test.ts +1 -0
- package/src/native-ui/shared-contracts/dist/harness-accounts.d.ts +20 -6
- package/src/native-ui/shared-contracts/dist/harness-accounts.d.ts.map +1 -1
- package/src/native-ui/shared-contracts/dist/harness-accounts.js +4 -1
- package/src/native-ui/shared-contracts/dist/harness-accounts.js.map +1 -1
- package/src/native-ui/shared-contracts/dist/harness-models.d.ts +22 -0
- package/src/native-ui/shared-contracts/dist/harness-models.d.ts.map +1 -1
- package/src/native-ui/shared-contracts/dist/harness-models.js +2 -1
- package/src/native-ui/shared-contracts/dist/harness-models.js.map +1 -1
- package/src/native-ui/shared-contracts/dist/tsconfig.tsbuildinfo +1 -1
- package/src/native-ui/shared-contracts/src/harness-accounts.ts +4 -1
- package/src/native-ui/shared-contracts/src/harness-models.ts +6 -1
- package/docs/images/harness-mix-home.png +0 -0
- package/docs/images/harness-mix-session.png +0 -0
- package/scripts/git-test.cjs +0 -33
- package/src/assets/icons/activity-compact.svg +0 -1
- package/src/assets/icons/activity-edit.svg +0 -1
- package/src/assets/icons/activity-terminal.svg +0 -1
- package/src/assets/icons/commands.svg +0 -1
- package/src/assets/icons/home-chat.svg +0 -1
- package/src/assets/icons/home-folder.svg +0 -1
- package/src/assets/icons/thread-fork.svg +0 -1
- package/src/main/workspace/git.js +0 -73
- package/src/main/workspace/ipc.js +0 -50
- package/src/main/workspace/terminal.js +0 -34
- package/src/native-ui/renderer-extension/src/assets/claude-agent.svg +0 -3
- package/src/native-ui/renderer-extension/src/assets/codex-logo-bright.png +0 -0
- package/src/native-ui/renderer-extension/src/assets/codex-logo-transparent.png +0 -0
- package/src/native-ui/renderer-extension/src/assets/codex-logo.png +0 -0
- package/src/native-ui/renderer-extension/src/assets/codexhost-icon.png +0 -0
- package/src/native-ui/renderer-extension/src/assets/codexhost-logo-transparent.png +0 -0
- package/src/native-ui/renderer-extension/src/assets/codexhost-logo.png +0 -0
- package/src/native-ui/renderer-extension/src/assets/codexhost-readme.svg +0 -8
- package/src/native-ui/renderer-extension/src/assets/pi-agent.svg +0 -4
- package/src/native-ui/renderer-extension/src/settings/accounts-quota-interactive.prototype.html +0 -1418
- package/src/native-ui/renderer-extension/src/settings/update-notes.preview.html +0 -491
- package/src/native-ui/renderer-extension/src/settings/windows-update.preview.html +0 -2135
|
@@ -30,11 +30,87 @@
|
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
.settings-harness-account {
|
|
33
|
-
display:
|
|
34
|
-
|
|
33
|
+
display: flex;
|
|
34
|
+
align-items: center;
|
|
35
|
+
justify-content: space-between;
|
|
36
|
+
flex-wrap: wrap;
|
|
37
|
+
gap: 20px;
|
|
38
|
+
padding: 20px 24px;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.settings-harness-account > .settings-account-row__person {
|
|
42
|
+
flex: 1 1 280px;
|
|
43
|
+
min-width: 0;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.settings-harness-account > .settings-account-usage {
|
|
47
|
+
flex: 1 1 220px;
|
|
48
|
+
min-width: 0;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.settings-harness-account > .settings-harness-account__actions {
|
|
52
|
+
flex: 0 0 auto;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.settings-harness-account__badge {
|
|
56
|
+
display: inline-flex;
|
|
35
57
|
align-items: center;
|
|
36
|
-
|
|
37
|
-
|
|
58
|
+
padding: 1px 7px;
|
|
59
|
+
border-radius: 999px;
|
|
60
|
+
font-size: 11px;
|
|
61
|
+
font-weight: 500;
|
|
62
|
+
line-height: 16px;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.settings-harness-account__badge--ready {
|
|
66
|
+
background: rgba(16, 185, 129, 0.12);
|
|
67
|
+
color: #10b981;
|
|
68
|
+
border: 1px solid rgba(16, 185, 129, 0.28);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.settings-harness-account__badge--unconfigured {
|
|
72
|
+
background: rgba(245, 158, 11, 0.12);
|
|
73
|
+
color: #f59e0b;
|
|
74
|
+
border: 1px solid rgba(245, 158, 11, 0.28);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.settings-harness-account__badge--not_installed {
|
|
78
|
+
background: rgba(156, 163, 175, 0.12);
|
|
79
|
+
color: var(--settings-muted);
|
|
80
|
+
border: 1px solid rgba(156, 163, 175, 0.25);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.settings-harness-account__config-hint {
|
|
84
|
+
margin-top: 6px;
|
|
85
|
+
font-size: 11px;
|
|
86
|
+
color: var(--settings-muted);
|
|
87
|
+
display: flex;
|
|
88
|
+
align-items: center;
|
|
89
|
+
gap: 4px;
|
|
90
|
+
overflow: hidden;
|
|
91
|
+
text-overflow: ellipsis;
|
|
92
|
+
white-space: nowrap;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.settings-harness-account__config-hint code {
|
|
96
|
+
font-family: ui-monospace, Consolas, monospace;
|
|
97
|
+
font-size: 11px;
|
|
98
|
+
background: var(--settings-surface);
|
|
99
|
+
border: 1px solid var(--settings-border);
|
|
100
|
+
padding: 1px 5px;
|
|
101
|
+
border-radius: 4px;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.settings-harness-account__actions {
|
|
105
|
+
display: flex;
|
|
106
|
+
align-items: center;
|
|
107
|
+
gap: 8px;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.settings-harness-account__login-btn {
|
|
111
|
+
font-size: 12px;
|
|
112
|
+
padding: 6px 12px;
|
|
113
|
+
white-space: nowrap;
|
|
38
114
|
}
|
|
39
115
|
|
|
40
116
|
.settings-harness-account + .settings-harness-account {
|
|
@@ -27,7 +27,7 @@ export const HARNESS_INSTALL_COMMANDS: Readonly<Partial<Record<ExternalRendererA
|
|
|
27
27
|
"deepseek-harness": { command: "pip install deepseek-harness" },
|
|
28
28
|
opencode: { command: "npm install -g opencode-ai" },
|
|
29
29
|
grok: { command: "npm install -g @xai/grok-cli" },
|
|
30
|
-
omp: { command: "npm install -g @oh-my-
|
|
30
|
+
omp: { command: "npm install -g @oh-my-pi/pi-coding-agent" },
|
|
31
31
|
antigravity: { command: "npm install -g @google/antigravity-cli" },
|
|
32
32
|
openclaw: { command: "npm install -g openclaw" },
|
|
33
33
|
hermes: { command: "pip install hermes-agent" },
|
|
@@ -76,7 +76,7 @@ export interface RendererConnectionDiagnostics {
|
|
|
76
76
|
refresh(): Promise<void>;
|
|
77
77
|
openWebUi?(hostId: string, agent: ExternalRendererAgent): Promise<void>;
|
|
78
78
|
inspectHarness?(hostId: string, agent: ExternalRendererAgent): Promise<HarnessInspection>;
|
|
79
|
-
installHarness?(hostId: string, agent: ExternalRendererAgent): Promise<{ success: boolean; command?: string; stdout?: string; stderr?: string; error?: string }>;
|
|
79
|
+
installHarness?(hostId: string, agent: ExternalRendererAgent, options?: { terminal?: boolean }): Promise<{ success: boolean; command?: string; stdout?: string; stderr?: string; error?: string }>;
|
|
80
80
|
subscribe(listener: () => void): () => void;
|
|
81
81
|
}
|
|
82
82
|
|
|
@@ -446,52 +446,87 @@ function renderOneClickInstallSection(
|
|
|
446
446
|
head.append(icon, copy);
|
|
447
447
|
|
|
448
448
|
const commandInfo = HARNESS_INSTALL_COMMANDS[agent];
|
|
449
|
+
|
|
450
|
+
const actions = document.createElement("div");
|
|
451
|
+
actions.className = "settings-install-actions";
|
|
452
|
+
|
|
453
|
+
const feedback = document.createElement("div");
|
|
454
|
+
feedback.className = "settings-install-feedback";
|
|
455
|
+
|
|
456
|
+
const downloadLink = document.createElement("a");
|
|
457
|
+
downloadLink.href = HARNESS_INSTALL_URLS[agent];
|
|
458
|
+
downloadLink.target = "_blank";
|
|
459
|
+
downloadLink.rel = "noopener noreferrer";
|
|
460
|
+
downloadLink.append(createRendererSettingsIcon("external-link", 15), messages.officialDownload);
|
|
461
|
+
|
|
462
|
+
const refreshBtn = document.createElement("button");
|
|
463
|
+
refreshBtn.type = "button";
|
|
464
|
+
refreshBtn.className = "settings-command-button settings-command-button--secondary";
|
|
465
|
+
refreshBtn.append(createRendererSettingsIcon("diagnose", 15), messages.refreshDetection);
|
|
466
|
+
refreshBtn.addEventListener("click", () => {
|
|
467
|
+
void diagnostics?.refresh();
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
// If this harness does not have a CLI npm/pip install command (e.g. standalone IDEs/tools like Kiro, Cursor, CodeBuddy, Trae, ZCode)
|
|
471
|
+
if (!commandInfo) {
|
|
472
|
+
const manualCard = document.createElement("div");
|
|
473
|
+
manualCard.className = "settings-install-manual-card";
|
|
474
|
+
const manualBadge = document.createElement("div");
|
|
475
|
+
manualBadge.className = "settings-install-manual-badge";
|
|
476
|
+
manualBadge.textContent = messages.manualInstallTitle;
|
|
477
|
+
const manualNotice = document.createElement("p");
|
|
478
|
+
manualNotice.className = "settings-install-manual-notice";
|
|
479
|
+
manualNotice.textContent = messages.manualInstallNotice;
|
|
480
|
+
|
|
481
|
+
const stepsList = document.createElement("ol");
|
|
482
|
+
stepsList.className = "settings-install-steps";
|
|
483
|
+
for (const stepText of [messages.manualInstallStep1, messages.manualInstallStep2, messages.manualInstallStep3]) {
|
|
484
|
+
const stepItem = document.createElement("li");
|
|
485
|
+
stepItem.textContent = stepText;
|
|
486
|
+
stepsList.append(stepItem);
|
|
487
|
+
}
|
|
488
|
+
manualCard.append(manualBadge, manualNotice, stepsList);
|
|
489
|
+
|
|
490
|
+
downloadLink.className = "settings-command-button";
|
|
491
|
+
actions.append(downloadLink, refreshBtn);
|
|
492
|
+
section.append(head, manualCard, actions, feedback);
|
|
493
|
+
container.append(section);
|
|
494
|
+
return;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
// Harness has a CLI command:
|
|
449
498
|
const commandBox = document.createElement("div");
|
|
450
499
|
commandBox.className = "settings-install-command-box";
|
|
451
|
-
commandBox.hidden = !commandInfo;
|
|
452
500
|
const commandLabel = document.createElement("span");
|
|
453
501
|
commandLabel.className = "settings-install-command-label";
|
|
454
502
|
commandLabel.textContent = messages.installCommandLabel;
|
|
455
503
|
const commandRow = document.createElement("div");
|
|
456
504
|
commandRow.className = "settings-install-command-row";
|
|
457
505
|
const code = document.createElement("code");
|
|
458
|
-
code.textContent = commandInfo
|
|
506
|
+
code.textContent = commandInfo.command;
|
|
459
507
|
commandRow.append(code);
|
|
460
508
|
commandBox.append(commandLabel, commandRow);
|
|
461
509
|
|
|
462
|
-
|
|
463
|
-
actions.className = "settings-install-actions";
|
|
510
|
+
downloadLink.className = "settings-command-button settings-command-button--secondary";
|
|
464
511
|
|
|
465
512
|
const installBtn = document.createElement("button");
|
|
466
513
|
installBtn.type = "button";
|
|
467
|
-
installBtn.hidden = !commandInfo;
|
|
468
|
-
installBtn.disabled = !commandInfo;
|
|
469
514
|
installBtn.className = "settings-command-button";
|
|
470
515
|
installBtn.append(createRendererSettingsIcon("download", 15), messages.oneClickInstall);
|
|
471
516
|
|
|
517
|
+
const terminalBtn = document.createElement("button");
|
|
518
|
+
terminalBtn.type = "button";
|
|
519
|
+
terminalBtn.className = "settings-command-button settings-command-button--secondary";
|
|
520
|
+
terminalBtn.append(createRendererSettingsIcon("external-link", 15), messages.installInTerminal);
|
|
521
|
+
terminalBtn.title = messages.installInTerminal;
|
|
522
|
+
|
|
472
523
|
const copyBtn = document.createElement("button");
|
|
473
524
|
copyBtn.type = "button";
|
|
474
|
-
copyBtn.hidden = !commandInfo;
|
|
475
525
|
copyBtn.className = "settings-command-button settings-command-button--secondary";
|
|
476
526
|
copyBtn.append(createRendererSettingsIcon("copy", 15), messages.copyInstallCommand);
|
|
477
527
|
|
|
478
|
-
const downloadLink = document.createElement("a");
|
|
479
|
-
downloadLink.className = "settings-command-button settings-command-button--secondary";
|
|
480
|
-
downloadLink.href = HARNESS_INSTALL_URLS[agent];
|
|
481
|
-
downloadLink.target = "_blank";
|
|
482
|
-
downloadLink.rel = "noopener noreferrer";
|
|
483
|
-
downloadLink.append(createRendererSettingsIcon("external-link", 15), messages.officialDownload);
|
|
484
|
-
|
|
485
|
-
const refreshBtn = document.createElement("button");
|
|
486
|
-
refreshBtn.type = "button";
|
|
487
|
-
refreshBtn.className = "settings-command-button settings-command-button--secondary";
|
|
488
|
-
refreshBtn.append(createRendererSettingsIcon("diagnose", 15), messages.refreshDetection);
|
|
489
|
-
|
|
490
|
-
const feedback = document.createElement("div");
|
|
491
|
-
feedback.className = "settings-install-feedback";
|
|
492
|
-
|
|
493
528
|
copyBtn.addEventListener("click", () => {
|
|
494
|
-
const text = commandInfo
|
|
529
|
+
const text = commandInfo.command;
|
|
495
530
|
const clipboard = document.defaultView?.navigator?.clipboard;
|
|
496
531
|
if (clipboard) {
|
|
497
532
|
void clipboard.writeText(text).then(() => {
|
|
@@ -503,8 +538,12 @@ function renderOneClickInstallSection(
|
|
|
503
538
|
}
|
|
504
539
|
});
|
|
505
540
|
|
|
506
|
-
|
|
507
|
-
|
|
541
|
+
terminalBtn.addEventListener("click", () => {
|
|
542
|
+
feedback.className = "settings-install-feedback settings-feedback-success";
|
|
543
|
+
feedback.textContent = messages.terminalInstalling;
|
|
544
|
+
if (diagnostics?.installHarness) {
|
|
545
|
+
void diagnostics.installHarness(hostId, agent, { terminal: true });
|
|
546
|
+
}
|
|
508
547
|
});
|
|
509
548
|
|
|
510
549
|
installBtn.addEventListener("click", () => {
|
|
@@ -516,7 +555,7 @@ function renderOneClickInstallSection(
|
|
|
516
555
|
|
|
517
556
|
const run = async () => {
|
|
518
557
|
if (!diagnostics?.installHarness) {
|
|
519
|
-
const text = commandInfo
|
|
558
|
+
const text = commandInfo.command;
|
|
520
559
|
await document.defaultView?.navigator?.clipboard?.writeText(text);
|
|
521
560
|
feedback.className = "settings-install-feedback settings-feedback-warning";
|
|
522
561
|
feedback.textContent = messages.oneClickInstallFailed;
|
|
@@ -549,7 +588,7 @@ function renderOneClickInstallSection(
|
|
|
549
588
|
void run();
|
|
550
589
|
});
|
|
551
590
|
|
|
552
|
-
actions.append(installBtn, copyBtn, downloadLink, refreshBtn);
|
|
591
|
+
actions.append(installBtn, terminalBtn, copyBtn, downloadLink, refreshBtn);
|
|
553
592
|
section.append(head, commandBox, actions, feedback);
|
|
554
593
|
container.append(section);
|
|
555
594
|
}
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import type { HarnessAccountListResult } from "@codexhost/shared-contracts";
|
|
2
2
|
import { KNOWN_RENDERER_AGENTS } from "../agent-selection-state.js";
|
|
3
3
|
import { createRendererAgentIcon } from "../renderer-agent-icon.js";
|
|
4
|
+
import { createRendererSettingsIcon } from "./icons.js";
|
|
4
5
|
import { renderAccountUsage, type AccountUsageDisplay } from "./accounts-usage.js";
|
|
5
6
|
import type { RendererSettingsPageMountContext } from "./core.js";
|
|
6
7
|
import type { RendererSettingsMessages } from "./localization.js";
|
|
7
8
|
|
|
8
9
|
export interface RendererHarnessAccountClient {
|
|
9
10
|
listHarnessAccounts?(): Promise<HarnessAccountListResult>;
|
|
11
|
+
loginHarnessAccount?(input: { harnessId: string }): Promise<{ success: boolean; command?: string | undefined; error?: string | undefined }>;
|
|
10
12
|
}
|
|
11
13
|
|
|
12
14
|
/** Read-only telemetry, deliberately separate from Codex Account IDs and mutations. */
|
|
@@ -22,7 +24,7 @@ export function mountHarnessAccounts(
|
|
|
22
24
|
section.hidden = true;
|
|
23
25
|
const heading = document.createElement("h2");
|
|
24
26
|
heading.className = "settings-harness-accounts__title";
|
|
25
|
-
heading.textContent = messages.harnessAccountsTitle;
|
|
27
|
+
heading.textContent = messages.harnessAccountsPageTitle || messages.harnessAccountsTitle;
|
|
26
28
|
const list = document.createElement("div");
|
|
27
29
|
list.className = "settings-account-list";
|
|
28
30
|
section.append(heading, list);
|
|
@@ -31,6 +33,7 @@ export function mountHarnessAccounts(
|
|
|
31
33
|
let refreshing = false;
|
|
32
34
|
let query = "";
|
|
33
35
|
let display: AccountUsageDisplay = "remaining";
|
|
36
|
+
let refresh: () => Promise<void>;
|
|
34
37
|
|
|
35
38
|
const render = (): void => {
|
|
36
39
|
section.hidden = accounts.length === 0;
|
|
@@ -71,14 +74,44 @@ export function mountHarnessAccounts(
|
|
|
71
74
|
plan.textContent = account.plan;
|
|
72
75
|
metadata.append(plan);
|
|
73
76
|
}
|
|
77
|
+
if (account.status) {
|
|
78
|
+
if (accountName || account.plan) {
|
|
79
|
+
const separator = document.createElement("span");
|
|
80
|
+
separator.textContent = "·";
|
|
81
|
+
separator.setAttribute("aria-hidden", "true");
|
|
82
|
+
metadata.append(separator);
|
|
83
|
+
}
|
|
84
|
+
const statusBadge = document.createElement("span");
|
|
85
|
+
statusBadge.className = `settings-harness-account__badge settings-harness-account__badge--${account.status}`;
|
|
86
|
+
statusBadge.textContent =
|
|
87
|
+
account.status === "ready"
|
|
88
|
+
? messages.harnessAccountStatusReady
|
|
89
|
+
: account.status === "not_installed"
|
|
90
|
+
? messages.harnessAccountStatusNotInstalled
|
|
91
|
+
: messages.harnessAccountStatusUnconfigured;
|
|
92
|
+
metadata.append(statusBadge);
|
|
93
|
+
}
|
|
74
94
|
if (metadata.childElementCount) identity.append(metadata);
|
|
75
|
-
|
|
76
|
-
document
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
95
|
+
if (account.configHint) {
|
|
96
|
+
const configHint = document.createElement("div");
|
|
97
|
+
configHint.className = "settings-harness-account__config-hint";
|
|
98
|
+
configHint.title = account.configHint;
|
|
99
|
+
const label = document.createElement("span");
|
|
100
|
+
label.textContent = `${messages.harnessAccountConfigGuide}: `;
|
|
101
|
+
const code = document.createElement("code");
|
|
102
|
+
code.textContent = account.configHint;
|
|
103
|
+
configHint.append(label, code);
|
|
104
|
+
identity.append(configHint);
|
|
105
|
+
}
|
|
106
|
+
const usage = account.credits
|
|
107
|
+
? renderAccountUsage(
|
|
108
|
+
document,
|
|
109
|
+
{ status: "ready", credits: account.credits },
|
|
110
|
+
messages,
|
|
111
|
+
display,
|
|
112
|
+
() => undefined,
|
|
113
|
+
)
|
|
114
|
+
: null;
|
|
82
115
|
const person = document.createElement("div");
|
|
83
116
|
person.className = "settings-account-row__person";
|
|
84
117
|
const agent = KNOWN_RENDERER_AGENTS.find((agent) => agent === account.harnessId);
|
|
@@ -92,6 +125,32 @@ export function mountHarnessAccounts(
|
|
|
92
125
|
person.append(identity);
|
|
93
126
|
row.append(person);
|
|
94
127
|
if (usage) row.append(usage);
|
|
128
|
+
const actions = document.createElement("div");
|
|
129
|
+
actions.className = "settings-harness-account__actions";
|
|
130
|
+
if (account.loginCommand) {
|
|
131
|
+
const loginBtn = document.createElement("button");
|
|
132
|
+
loginBtn.type = "button";
|
|
133
|
+
loginBtn.className = "settings-command-button settings-command-button--secondary settings-harness-account__login-btn";
|
|
134
|
+
loginBtn.append(createRendererSettingsIcon("external-link", 14), messages.harnessAccountLogin);
|
|
135
|
+
loginBtn.title = account.loginCommand;
|
|
136
|
+
loginBtn.addEventListener("click", () => {
|
|
137
|
+
const client = getClient();
|
|
138
|
+
if (!client?.loginHarnessAccount) return;
|
|
139
|
+
loginBtn.disabled = true;
|
|
140
|
+
loginBtn.textContent = messages.harnessAccountLoginStarted;
|
|
141
|
+
void client.loginHarnessAccount({ harnessId: account.harnessId }).finally(() => {
|
|
142
|
+
document.defaultView?.setTimeout(() => {
|
|
143
|
+
loginBtn.disabled = false;
|
|
144
|
+
loginBtn.replaceChildren(createRendererSettingsIcon("external-link", 14), messages.harnessAccountLogin);
|
|
145
|
+
void refresh();
|
|
146
|
+
}, 3000);
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
actions.append(loginBtn);
|
|
150
|
+
}
|
|
151
|
+
if (actions.childElementCount > 0) {
|
|
152
|
+
row.append(actions);
|
|
153
|
+
}
|
|
95
154
|
list.append(row);
|
|
96
155
|
}
|
|
97
156
|
if (accounts.length && !visible.length) {
|
|
@@ -101,6 +160,27 @@ export function mountHarnessAccounts(
|
|
|
101
160
|
list.append(empty);
|
|
102
161
|
}
|
|
103
162
|
};
|
|
163
|
+
refresh = async (): Promise<void> => {
|
|
164
|
+
if (refreshing || context.signal.aborted) return;
|
|
165
|
+
const client = getClient();
|
|
166
|
+
if (!client?.listHarnessAccounts) return;
|
|
167
|
+
refreshing = true;
|
|
168
|
+
onChange();
|
|
169
|
+
try {
|
|
170
|
+
const result = await client.listHarnessAccounts();
|
|
171
|
+
if (!context.signal.aborted) accounts = result.accounts;
|
|
172
|
+
} catch {
|
|
173
|
+
// Older Hosts and unavailable authentication have no read-only rows.
|
|
174
|
+
if (!context.signal.aborted) accounts = [];
|
|
175
|
+
} finally {
|
|
176
|
+
refreshing = false;
|
|
177
|
+
if (!context.signal.aborted) {
|
|
178
|
+
render();
|
|
179
|
+
onChange();
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
|
|
104
184
|
return {
|
|
105
185
|
get refreshing() {
|
|
106
186
|
return refreshing;
|
|
@@ -110,25 +190,6 @@ export function mountHarnessAccounts(
|
|
|
110
190
|
display = nextDisplay;
|
|
111
191
|
render();
|
|
112
192
|
},
|
|
113
|
-
|
|
114
|
-
if (refreshing || context.signal.aborted) return;
|
|
115
|
-
const client = getClient();
|
|
116
|
-
if (!client?.listHarnessAccounts) return;
|
|
117
|
-
refreshing = true;
|
|
118
|
-
onChange();
|
|
119
|
-
try {
|
|
120
|
-
const result = await client.listHarnessAccounts();
|
|
121
|
-
if (!context.signal.aborted) accounts = result.accounts;
|
|
122
|
-
} catch {
|
|
123
|
-
// Older Hosts and unavailable authentication have no read-only rows.
|
|
124
|
-
if (!context.signal.aborted) accounts = [];
|
|
125
|
-
} finally {
|
|
126
|
-
refreshing = false;
|
|
127
|
-
if (!context.signal.aborted) {
|
|
128
|
-
render();
|
|
129
|
-
onChange();
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
},
|
|
193
|
+
refresh,
|
|
133
194
|
};
|
|
134
195
|
}
|
|
@@ -172,6 +172,22 @@ export interface RendererSettingsMessages {
|
|
|
172
172
|
readonly oneClickInstalling: string;
|
|
173
173
|
readonly oneClickInstallSuccess: string;
|
|
174
174
|
readonly oneClickInstallFailed: string;
|
|
175
|
+
readonly installInTerminal: string;
|
|
176
|
+
readonly terminalInstalling: string;
|
|
177
|
+
readonly manualInstallTitle: string;
|
|
178
|
+
readonly manualInstallNotice: string;
|
|
179
|
+
readonly manualInstallStep1: string;
|
|
180
|
+
readonly manualInstallStep2: string;
|
|
181
|
+
readonly manualInstallStep3: string;
|
|
182
|
+
readonly harnessAccountsPageTitle: string;
|
|
183
|
+
readonly harnessAccountsPageDescription: string;
|
|
184
|
+
readonly harnessAccountLogin: string;
|
|
185
|
+
readonly harnessAccountLoginStarted: string;
|
|
186
|
+
readonly harnessAccountConfigGuide: string;
|
|
187
|
+
readonly harnessAccountStatusReady: string;
|
|
188
|
+
readonly harnessAccountStatusUnconfigured: string;
|
|
189
|
+
readonly harnessAccountStatusNotInstalled: string;
|
|
190
|
+
readonly checkUpdateNow: string;
|
|
175
191
|
readonly installCommandLabel: string;
|
|
176
192
|
readonly copyInstallCommand: string;
|
|
177
193
|
readonly installCommandCopied: string;
|
|
@@ -401,6 +417,22 @@ const ENGLISH_MESSAGES: RendererSettingsMessages = Object.freeze({
|
|
|
401
417
|
oneClickInstalling: "Installing Harness in background...",
|
|
402
418
|
oneClickInstallSuccess: "Installed successfully! Status refreshed.",
|
|
403
419
|
oneClickInstallFailed: "Installation failed. Please try running the command manually.",
|
|
420
|
+
installInTerminal: "Install in Terminal",
|
|
421
|
+
terminalInstalling: "Installation launched in a visible terminal window. Click Refresh when done.",
|
|
422
|
+
manualInstallTitle: "Standalone Application / Manual Install",
|
|
423
|
+
manualInstallNotice: "This Harness is a standalone desktop application or binary and must be installed via its official installer.",
|
|
424
|
+
manualInstallStep1: "1. Click the button below to visit the official website and download the installer.",
|
|
425
|
+
manualInstallStep2: "2. Run the application and complete its authentication login.",
|
|
426
|
+
manualInstallStep3: "3. Ensure its CLI executable is in your PATH environment variable, then click Refresh.",
|
|
427
|
+
harnessAccountsPageTitle: "Harness Account Management",
|
|
428
|
+
harnessAccountsPageDescription: "View and manage login status, credentials, and usage quotas for each native harness. Credentials remain stored locally by each harness.",
|
|
429
|
+
harnessAccountLogin: "Log In / Authorize",
|
|
430
|
+
harnessAccountLoginStarted: "Login launched in terminal window. Click Refresh when done.",
|
|
431
|
+
harnessAccountConfigGuide: "Credential Guide",
|
|
432
|
+
harnessAccountStatusReady: "Ready",
|
|
433
|
+
harnessAccountStatusUnconfigured: "Needs Config / Not Logged In",
|
|
434
|
+
harnessAccountStatusNotInstalled: "Not Installed",
|
|
435
|
+
checkUpdateNow: "Check for Updates Now",
|
|
404
436
|
installCommandLabel: "Install Command",
|
|
405
437
|
copyInstallCommand: "Copy Command",
|
|
406
438
|
installCommandCopied: "Command Copied",
|
|
@@ -635,6 +667,22 @@ const CHINESE_MESSAGES: RendererSettingsMessages = Object.freeze({
|
|
|
635
667
|
oneClickInstalling: "正在后台安装 Harness……",
|
|
636
668
|
oneClickInstallSuccess: "安装成功!状态已自动刷新。",
|
|
637
669
|
oneClickInstallFailed: "安装失败,请尝试复制下方命令在终端手动运行。",
|
|
670
|
+
installInTerminal: "在终端中打开安装",
|
|
671
|
+
terminalInstalling: "已在独立终端窗口启动安装,请在终端完成后点击刷新检测。",
|
|
672
|
+
manualInstallTitle: "独立安装程序 / 手动安装指引",
|
|
673
|
+
manualInstallNotice: "此 Harness 为独立客户端程序或二进制工具,不支持通过 npm 一键安装。",
|
|
674
|
+
manualInstallStep1: "1. 点击下方按钮前往官网下载安装包并完成安装;",
|
|
675
|
+
manualInstallStep2: "2. 运行应用并在终端中完成该 Harness 的认证登录;",
|
|
676
|
+
manualInstallStep3: "3. 确保该 CLI 可执行文件已加入系统 PATH 环境变量,然后点击【刷新检测】。",
|
|
677
|
+
harnessAccountsPageTitle: "Harness 账号与授权管理",
|
|
678
|
+
harnessAccountsPageDescription: "查看与管理各原生 Harness 的登录状态、授权凭据与模型用量配额。所有认证均保存在本地原生 Harness 中,Harness Mix 绝不代存您的账号密码。",
|
|
679
|
+
harnessAccountLogin: "登录 / 授权",
|
|
680
|
+
harnessAccountLoginStarted: "已在终端窗口启动登录认证,请在终端完成后点击刷新状态。",
|
|
681
|
+
harnessAccountConfigGuide: "凭据配置指引",
|
|
682
|
+
harnessAccountStatusReady: "已就绪",
|
|
683
|
+
harnessAccountStatusUnconfigured: "待配置 / 未登录",
|
|
684
|
+
harnessAccountStatusNotInstalled: "未安装",
|
|
685
|
+
checkUpdateNow: "立即检查更新",
|
|
638
686
|
installCommandLabel: "推荐安装命令",
|
|
639
687
|
copyInstallCommand: "复制安装命令",
|
|
640
688
|
installCommandCopied: "已复制命令",
|
|
@@ -173,6 +173,23 @@ export function aboutPage(messages: RendererSettingsMessages): RendererSettingsP
|
|
|
173
173
|
const tagline = document.createElement("strong");
|
|
174
174
|
tagline.className = "settings-about-tagline";
|
|
175
175
|
tagline.textContent = messages.aboutTagline;
|
|
176
|
+
const versionRow = document.createElement("div");
|
|
177
|
+
versionRow.className = "settings-about-version-row";
|
|
178
|
+
const versionBadge = document.createElement("span");
|
|
179
|
+
versionBadge.className = "settings-about-version-badge";
|
|
180
|
+
versionBadge.textContent = "v0.1.2";
|
|
181
|
+
const checkUpdateBtn = document.createElement("button");
|
|
182
|
+
checkUpdateBtn.type = "button";
|
|
183
|
+
checkUpdateBtn.className = "settings-command-button settings-command-button--secondary";
|
|
184
|
+
checkUpdateBtn.append(createRendererSettingsIcon("updates", 14), messages.checkUpdateNow);
|
|
185
|
+
checkUpdateBtn.addEventListener("click", () => {
|
|
186
|
+
const shell = document.querySelector("[data-codexhost-settings-shell]");
|
|
187
|
+
const updatesNavBtn = (shell?.shadowRoot ?? document).querySelector<HTMLButtonElement>(
|
|
188
|
+
'button[data-page-id="updates"]',
|
|
189
|
+
);
|
|
190
|
+
updatesNavBtn?.click();
|
|
191
|
+
});
|
|
192
|
+
versionRow.append(versionBadge, checkUpdateBtn);
|
|
176
193
|
const introduction = document.createElement("div");
|
|
177
194
|
introduction.className = "settings-about-copy";
|
|
178
195
|
for (const paragraphText of messages.aboutParagraphs) {
|
|
@@ -200,14 +217,14 @@ export function aboutPage(messages: RendererSettingsMessages): RendererSettingsP
|
|
|
200
217
|
repositoryUrl,
|
|
201
218
|
);
|
|
202
219
|
repositorySection.append(openSource, repository);
|
|
203
|
-
panel.append(product, tagline, introduction, starCallout, repositorySection);
|
|
220
|
+
panel.append(product, tagline, versionRow, introduction, starCallout, repositorySection);
|
|
204
221
|
context.content.append(heading, panel);
|
|
205
222
|
return undefined;
|
|
206
223
|
},
|
|
207
224
|
});
|
|
208
225
|
}
|
|
209
226
|
|
|
210
|
-
function updatesPage(
|
|
227
|
+
export function updatesPage(
|
|
211
228
|
messages: RendererSettingsMessages,
|
|
212
229
|
getClient: () => RendererUpdateClient | null,
|
|
213
230
|
): RendererSettingsPageDefinition {
|
|
@@ -1390,6 +1390,25 @@ button {
|
|
|
1390
1390
|
line-height: 24px;
|
|
1391
1391
|
}
|
|
1392
1392
|
|
|
1393
|
+
.settings-about-version-row {
|
|
1394
|
+
display: flex;
|
|
1395
|
+
align-items: center;
|
|
1396
|
+
gap: 12px;
|
|
1397
|
+
margin: 4px 0 8px;
|
|
1398
|
+
}
|
|
1399
|
+
|
|
1400
|
+
.settings-about-version-badge {
|
|
1401
|
+
display: inline-flex;
|
|
1402
|
+
align-items: center;
|
|
1403
|
+
font-size: 13px;
|
|
1404
|
+
font-weight: 600;
|
|
1405
|
+
color: var(--settings-text);
|
|
1406
|
+
background: var(--settings-surface);
|
|
1407
|
+
border: 1px solid var(--settings-border);
|
|
1408
|
+
padding: 4px 10px;
|
|
1409
|
+
border-radius: 6px;
|
|
1410
|
+
}
|
|
1411
|
+
|
|
1393
1412
|
.settings-about-copy {
|
|
1394
1413
|
display: grid;
|
|
1395
1414
|
max-width: 660px;
|
|
@@ -2174,3 +2193,46 @@ button {
|
|
|
2174
2193
|
margin-top: 2px;
|
|
2175
2194
|
overflow-wrap: anywhere;
|
|
2176
2195
|
}
|
|
2196
|
+
|
|
2197
|
+
.settings-install-manual-card {
|
|
2198
|
+
display: flex;
|
|
2199
|
+
flex-direction: column;
|
|
2200
|
+
gap: 8px;
|
|
2201
|
+
background: var(--settings-inset);
|
|
2202
|
+
padding: 12px 14px;
|
|
2203
|
+
border-radius: 8px;
|
|
2204
|
+
border: 1px solid var(--settings-border);
|
|
2205
|
+
margin-top: 6px;
|
|
2206
|
+
margin-bottom: 4px;
|
|
2207
|
+
}
|
|
2208
|
+
|
|
2209
|
+
.settings-install-manual-badge {
|
|
2210
|
+
display: inline-flex;
|
|
2211
|
+
align-items: center;
|
|
2212
|
+
font-size: 11px;
|
|
2213
|
+
font-weight: 600;
|
|
2214
|
+
color: var(--settings-accent, #10a37f);
|
|
2215
|
+
background: var(--settings-accent-subtle, rgba(16, 163, 127, 0.1));
|
|
2216
|
+
padding: 2px 8px;
|
|
2217
|
+
border-radius: 4px;
|
|
2218
|
+
width: fit-content;
|
|
2219
|
+
}
|
|
2220
|
+
|
|
2221
|
+
.settings-install-manual-notice {
|
|
2222
|
+
font-size: 12px;
|
|
2223
|
+
line-height: 18px;
|
|
2224
|
+
color: var(--settings-text);
|
|
2225
|
+
margin: 0;
|
|
2226
|
+
}
|
|
2227
|
+
|
|
2228
|
+
.settings-install-steps {
|
|
2229
|
+
margin: 0;
|
|
2230
|
+
padding-left: 18px;
|
|
2231
|
+
font-size: 12px;
|
|
2232
|
+
line-height: 20px;
|
|
2233
|
+
color: var(--settings-muted);
|
|
2234
|
+
}
|
|
2235
|
+
|
|
2236
|
+
.settings-install-steps li {
|
|
2237
|
+
margin-bottom: 2px;
|
|
2238
|
+
}
|
|
@@ -186,7 +186,7 @@ export function mountRendererSettingsShell(
|
|
|
186
186
|
let otherSectionAdded = false;
|
|
187
187
|
appendNavigationSection(messages.generalSection);
|
|
188
188
|
for (const definition of resolvedRegistry.pages) {
|
|
189
|
-
if (definition.id === "about" && !otherSectionAdded) {
|
|
189
|
+
if ((definition.id === "updates" || definition.id === "about") && !otherSectionAdded) {
|
|
190
190
|
appendNavigationSection(messages.otherSection);
|
|
191
191
|
otherSectionAdded = true;
|
|
192
192
|
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
parseCollabPayload,
|
|
4
|
+
formatDiffHtml,
|
|
5
|
+
installCollabCards,
|
|
6
|
+
} from "../src/renderer-collab-cards.js";
|
|
7
|
+
|
|
8
|
+
describe("renderer-collab-cards", () => {
|
|
9
|
+
it("parses collaboration payloads correctly", () => {
|
|
10
|
+
expect(parseCollabPayload("")).toBeNull();
|
|
11
|
+
expect(parseCollabPayload("hello world")).toBeNull();
|
|
12
|
+
|
|
13
|
+
const payload = {
|
|
14
|
+
task_id: "task-123",
|
|
15
|
+
child_thread_id: "thread-child-456",
|
|
16
|
+
agent_type: "pi",
|
|
17
|
+
status: "completed",
|
|
18
|
+
};
|
|
19
|
+
const text = `Agent 协作 · Pi\n${JSON.stringify(payload)}`;
|
|
20
|
+
const parsed = parseCollabPayload(text);
|
|
21
|
+
expect(parsed).not.toBeNull();
|
|
22
|
+
expect(parsed?.task_id).toBe("task-123");
|
|
23
|
+
expect(parsed?.child_thread_id).toBe("thread-child-456");
|
|
24
|
+
expect(parsed?.agent_type).toBe("pi");
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("formats diff HTML with color coding", () => {
|
|
28
|
+
expect(formatDiffHtml("")).toContain("暂无文件改动");
|
|
29
|
+
|
|
30
|
+
const diff = `--- a/file.txt\n+++ b/file.txt\n@@ -1,3 +1,3 @@\n-old line\n+new line\n context`;
|
|
31
|
+
const html = formatDiffHtml(diff);
|
|
32
|
+
expect(html).toContain("+new line");
|
|
33
|
+
expect(html).toContain("-old line");
|
|
34
|
+
expect(html).toContain("#22c55e1a");
|
|
35
|
+
expect(html).toContain("#ef44441a");
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("handles environment safely when document is not in global scope", () => {
|
|
39
|
+
const control = installCollabCards();
|
|
40
|
+
expect(typeof control.scan).toBe("function");
|
|
41
|
+
expect(typeof control.dispose).toBe("function");
|
|
42
|
+
control.scan();
|
|
43
|
+
control.dispose();
|
|
44
|
+
});
|
|
45
|
+
});
|