@jmoyers/harness 0.1.8 → 0.1.10
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.md +33 -155
- package/package.json +5 -1
- package/packages/harness-ai/src/anthropic-client.ts +99 -0
- package/packages/harness-ai/src/anthropic-protocol.ts +581 -0
- package/packages/harness-ai/src/anthropic-provider.ts +82 -0
- package/packages/harness-ai/src/async-iterable-stream.ts +65 -0
- package/packages/harness-ai/src/index.ts +36 -0
- package/packages/harness-ai/src/json-parse.ts +66 -0
- package/packages/harness-ai/src/sse.ts +80 -0
- package/packages/harness-ai/src/stream-object.ts +96 -0
- package/packages/harness-ai/src/stream-text.ts +1340 -0
- package/packages/harness-ai/src/types.ts +330 -0
- package/packages/harness-ai/src/ui-stream.ts +217 -0
- package/scripts/codex-live-mux-runtime.ts +123 -7
- package/scripts/control-plane-daemon.ts +20 -3
- package/scripts/harness.ts +566 -133
- package/src/cli/gateway-record.ts +16 -1
- package/src/control-plane/agent-realtime-api.ts +4 -0
- package/src/control-plane/prompt/agent-prompt-extractor.ts +191 -0
- package/src/control-plane/prompt/extractors/claude-prompt-extractor.ts +53 -0
- package/src/control-plane/prompt/extractors/codex-prompt-extractor.ts +50 -0
- package/src/control-plane/prompt/extractors/cursor-prompt-extractor.ts +56 -0
- package/src/control-plane/prompt/session-prompt-engine.ts +69 -0
- package/src/control-plane/prompt/thread-title-namer.ts +290 -0
- package/src/control-plane/stream-command-parser.ts +12 -0
- package/src/control-plane/stream-protocol.ts +109 -0
- package/src/control-plane/stream-server-command.ts +14 -0
- package/src/control-plane/stream-server-session-runtime.ts +12 -0
- package/src/control-plane/stream-server.ts +485 -19
- package/src/mux/input-shortcuts.ts +9 -0
- package/src/mux/live-mux/critique-review.ts +5 -1
- package/src/mux/live-mux/git-parsing.ts +24 -0
- package/src/mux/live-mux/global-shortcut-handlers.ts +8 -0
- package/src/mux/render-frame.ts +1 -1
- package/src/pty/pty_host.ts +46 -1
- package/src/services/control-plane.ts +22 -0
- package/src/services/runtime-control-actions.ts +69 -0
- package/src/services/runtime-navigation-input.ts +4 -0
- package/src/services/runtime-rail-input.ts +4 -0
- package/src/services/runtime-workspace-actions.ts +5 -0
- package/src/ui/global-shortcut-input.ts +2 -0
|
@@ -93,6 +93,7 @@ import {
|
|
|
93
93
|
import {
|
|
94
94
|
normalizeGitHubRemoteUrl,
|
|
95
95
|
repositoryNameFromGitHubRemoteUrl,
|
|
96
|
+
resolveGitHubTrackedBranchForActions,
|
|
96
97
|
shouldShowGitHubPrActions,
|
|
97
98
|
} from '../src/mux/live-mux/git-parsing.ts';
|
|
98
99
|
import { readProcessUsageSample, runGitCommand } from '../src/mux/live-mux/git-snapshot.ts';
|
|
@@ -257,6 +258,13 @@ interface CommandMenuGitHubProjectPrState {
|
|
|
257
258
|
readonly loading: boolean;
|
|
258
259
|
}
|
|
259
260
|
|
|
261
|
+
interface GitHubDebugAuthState {
|
|
262
|
+
enabled: boolean;
|
|
263
|
+
token: 'env' | 'gh' | 'none';
|
|
264
|
+
auth: 'ok' | 'no' | 'er' | 'na' | 'uk';
|
|
265
|
+
projectPr: 'ok' | 'er' | 'na';
|
|
266
|
+
}
|
|
267
|
+
|
|
260
268
|
interface ThemePickerSessionState {
|
|
261
269
|
readonly initialThemeConfig: HarnessMuxThemeConfig | null;
|
|
262
270
|
committed: boolean;
|
|
@@ -404,6 +412,28 @@ function commandExistsOnPath(command: string): boolean {
|
|
|
404
412
|
}
|
|
405
413
|
}
|
|
406
414
|
|
|
415
|
+
function readGhAuthTokenForDebug(): string | null {
|
|
416
|
+
try {
|
|
417
|
+
const stdout = execFileSync('gh', ['auth', 'token'], {
|
|
418
|
+
encoding: 'utf8',
|
|
419
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
420
|
+
timeout: 2000,
|
|
421
|
+
windowsHide: true,
|
|
422
|
+
});
|
|
423
|
+
const token = stdout.trim();
|
|
424
|
+
return token.length > 0 ? token : null;
|
|
425
|
+
} catch {
|
|
426
|
+
return null;
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
function formatGitHubDebugTokens(state: GitHubDebugAuthState): string {
|
|
431
|
+
if (!state.enabled) {
|
|
432
|
+
return '[gh:off tk:na au:na pr:na]';
|
|
433
|
+
}
|
|
434
|
+
return `[gh:on tk:${state.token} au:${state.auth} pr:${state.projectPr}]`;
|
|
435
|
+
}
|
|
436
|
+
|
|
407
437
|
async function main(): Promise<number> {
|
|
408
438
|
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
409
439
|
process.stderr.write('codex:live:mux requires a TTY stdin/stdout\n');
|
|
@@ -502,6 +532,26 @@ async function main(): Promise<number> {
|
|
|
502
532
|
const resolvedMuxTheme = resolveAndApplyRuntimeTheme(runtimeThemeConfig, true);
|
|
503
533
|
let currentModalTheme = resolvedMuxTheme.theme.modalTheme;
|
|
504
534
|
const configuredMuxGit = loadedConfig.config.mux.git;
|
|
535
|
+
const githubTokenEnvVar = loadedConfig.config.github.tokenEnvVar;
|
|
536
|
+
const envGitHubTokenRaw = process.env[githubTokenEnvVar];
|
|
537
|
+
const hasEnvGitHubToken =
|
|
538
|
+
typeof envGitHubTokenRaw === 'string' && envGitHubTokenRaw.trim().length > 0;
|
|
539
|
+
const githubDebugAuthState: GitHubDebugAuthState = {
|
|
540
|
+
enabled: loadedConfig.config.github.enabled,
|
|
541
|
+
token: hasEnvGitHubToken ? 'env' : 'none',
|
|
542
|
+
auth: loadedConfig.config.github.enabled ? (hasEnvGitHubToken ? 'ok' : 'uk') : 'na',
|
|
543
|
+
projectPr: 'na',
|
|
544
|
+
};
|
|
545
|
+
if (githubDebugAuthState.enabled && !hasEnvGitHubToken) {
|
|
546
|
+
const ghToken = commandExistsOnPath('gh') ? readGhAuthTokenForDebug() : null;
|
|
547
|
+
if (ghToken !== null) {
|
|
548
|
+
githubDebugAuthState.token = 'gh';
|
|
549
|
+
githubDebugAuthState.auth = 'ok';
|
|
550
|
+
} else {
|
|
551
|
+
githubDebugAuthState.token = 'none';
|
|
552
|
+
githubDebugAuthState.auth = 'no';
|
|
553
|
+
}
|
|
554
|
+
}
|
|
505
555
|
const configuredCodexLaunch = loadedConfig.config.codex.launch;
|
|
506
556
|
const configuredCritique = loadedConfig.config.critique;
|
|
507
557
|
const codexLaunchModeByDirectoryPath: Record<string, 'yolo' | 'standard'> = {};
|
|
@@ -1414,6 +1464,14 @@ async function main(): Promise<number> {
|
|
|
1414
1464
|
commandMenuGitHubProjectPrState.directoryId === activeDirectoryId
|
|
1415
1465
|
? commandMenuGitHubProjectPrState
|
|
1416
1466
|
: null;
|
|
1467
|
+
const currentBranchForActions =
|
|
1468
|
+
activeDirectoryId === null
|
|
1469
|
+
? null
|
|
1470
|
+
: (gitSummaryByDirectoryId.get(activeDirectoryId)?.branch ?? null);
|
|
1471
|
+
const trackedBranchForActions = resolveGitHubTrackedBranchForActions({
|
|
1472
|
+
projectTrackedBranch: githubProjectPrState?.branchName ?? null,
|
|
1473
|
+
currentBranch: currentBranchForActions,
|
|
1474
|
+
});
|
|
1417
1475
|
const selectedText =
|
|
1418
1476
|
workspace.selection === null
|
|
1419
1477
|
? ''
|
|
@@ -1435,7 +1493,7 @@ async function main(): Promise<number> {
|
|
|
1435
1493
|
),
|
|
1436
1494
|
githubRepositoryUrl: activeDirectoryRepositorySnapshot?.normalizedRemoteUrl ?? null,
|
|
1437
1495
|
githubDefaultBranch: activeDirectoryRepositorySnapshot?.defaultBranch ?? null,
|
|
1438
|
-
githubTrackedBranch:
|
|
1496
|
+
githubTrackedBranch: trackedBranchForActions,
|
|
1439
1497
|
githubOpenPrUrl: githubProjectPrState?.openPrUrl ?? null,
|
|
1440
1498
|
githubProjectPrLoading: githubProjectPrState?.loading ?? false,
|
|
1441
1499
|
};
|
|
@@ -1707,6 +1765,13 @@ async function main(): Promise<number> {
|
|
|
1707
1765
|
};
|
|
1708
1766
|
const githubAuthHintNotice =
|
|
1709
1767
|
'GitHub PR actions become available after auth (`gh auth login` or `GITHUB_TOKEN`).';
|
|
1768
|
+
const setGitHubDebugAuthState = (
|
|
1769
|
+
update: Partial<Pick<GitHubDebugAuthState, 'token' | 'auth' | 'projectPr'>>,
|
|
1770
|
+
): void => {
|
|
1771
|
+
githubDebugAuthState.token = update.token ?? githubDebugAuthState.token;
|
|
1772
|
+
githubDebugAuthState.auth = update.auth ?? githubDebugAuthState.auth;
|
|
1773
|
+
githubDebugAuthState.projectPr = update.projectPr ?? githubDebugAuthState.projectPr;
|
|
1774
|
+
};
|
|
1710
1775
|
const isGitHubAuthUnavailableError = (error: unknown): boolean => {
|
|
1711
1776
|
const message = formatErrorMessage(error).toLowerCase();
|
|
1712
1777
|
return (
|
|
@@ -1729,7 +1794,14 @@ async function main(): Promise<number> {
|
|
|
1729
1794
|
directoryId,
|
|
1730
1795
|
});
|
|
1731
1796
|
commandMenuGitHubProjectPrState = parseGitHubProjectPrState(directoryId, result);
|
|
1732
|
-
|
|
1797
|
+
setGitHubDebugAuthState({
|
|
1798
|
+
projectPr: 'ok',
|
|
1799
|
+
});
|
|
1800
|
+
} catch (error: unknown) {
|
|
1801
|
+
setGitHubDebugAuthState({
|
|
1802
|
+
projectPr: 'er',
|
|
1803
|
+
auth: isGitHubAuthUnavailableError(error) ? 'no' : githubDebugAuthState.auth,
|
|
1804
|
+
});
|
|
1733
1805
|
commandMenuGitHubProjectPrState = {
|
|
1734
1806
|
directoryId,
|
|
1735
1807
|
branchName: null,
|
|
@@ -2517,6 +2589,12 @@ async function main(): Promise<number> {
|
|
|
2517
2589
|
},
|
|
2518
2590
|
});
|
|
2519
2591
|
},
|
|
2592
|
+
listConversationIdsForTitleRefresh: () => conversationManager.orderedIds(),
|
|
2593
|
+
conversationAgentTypeForTitleRefresh: (sessionId) =>
|
|
2594
|
+
conversationManager.get(sessionId)?.agentType ?? null,
|
|
2595
|
+
refreshConversationTitle: async (sessionId) => {
|
|
2596
|
+
return await controlPlaneService.refreshConversationTitle(sessionId);
|
|
2597
|
+
},
|
|
2520
2598
|
});
|
|
2521
2599
|
const runtimeWorkspaceActions = new RuntimeWorkspaceActions({
|
|
2522
2600
|
conversationActions: conversationLifecycle,
|
|
@@ -2619,17 +2697,23 @@ async function main(): Promise<number> {
|
|
|
2619
2697
|
|
|
2620
2698
|
const runCritiqueReviewFromCommandMenu = (
|
|
2621
2699
|
directoryId: string,
|
|
2622
|
-
mode: 'staged' | 'base-branch',
|
|
2700
|
+
mode: 'unstaged' | 'staged' | 'base-branch',
|
|
2623
2701
|
): void => {
|
|
2624
2702
|
queueControlPlaneOp(async () => {
|
|
2625
2703
|
const agent = resolveCritiqueReviewAgentFromEnvironment();
|
|
2626
|
-
if (mode
|
|
2704
|
+
if (mode !== 'base-branch') {
|
|
2627
2705
|
const commandText = buildCritiqueReviewCommand({
|
|
2628
|
-
mode
|
|
2706
|
+
mode,
|
|
2629
2707
|
agent,
|
|
2630
2708
|
});
|
|
2631
2709
|
await runCommandInNewTerminalThread(directoryId, commandText);
|
|
2632
|
-
|
|
2710
|
+
const reviewLabelByMode: Readonly<Record<'unstaged' | 'staged', string>> = {
|
|
2711
|
+
unstaged: 'unstaged',
|
|
2712
|
+
staged: 'staged',
|
|
2713
|
+
};
|
|
2714
|
+
setCommandNotice(
|
|
2715
|
+
`running critique ${reviewLabelByMode[mode]} review (${agent ?? 'default'})`,
|
|
2716
|
+
);
|
|
2633
2717
|
return;
|
|
2634
2718
|
}
|
|
2635
2719
|
const baseBranch = await resolveCritiqueReviewBaseBranchForDirectory(directoryId);
|
|
@@ -2653,6 +2737,16 @@ async function main(): Promise<number> {
|
|
|
2653
2737
|
return [];
|
|
2654
2738
|
}
|
|
2655
2739
|
return [
|
|
2740
|
+
{
|
|
2741
|
+
id: 'critique.review.unstaged',
|
|
2742
|
+
title: 'Critique AI Review: Unstaged Changes',
|
|
2743
|
+
aliases: ['critique unstaged review', 'review unstaged diff', 'ai review unstaged'],
|
|
2744
|
+
keywords: ['critique', 'review', 'unstaged', 'diff', 'ai'],
|
|
2745
|
+
detail: 'runs critique review',
|
|
2746
|
+
run: () => {
|
|
2747
|
+
runCritiqueReviewFromCommandMenu(directoryId, 'unstaged');
|
|
2748
|
+
},
|
|
2749
|
+
},
|
|
2656
2750
|
{
|
|
2657
2751
|
id: 'critique.review.staged',
|
|
2658
2752
|
title: 'Critique AI Review: Staged Changes',
|
|
@@ -2906,9 +3000,17 @@ async function main(): Promise<number> {
|
|
|
2906
3000
|
});
|
|
2907
3001
|
} catch (error: unknown) {
|
|
2908
3002
|
if (isGitHubAuthUnavailableError(error)) {
|
|
3003
|
+
setGitHubDebugAuthState({
|
|
3004
|
+
auth: 'no',
|
|
3005
|
+
projectPr: 'er',
|
|
3006
|
+
});
|
|
2909
3007
|
setCommandNotice(githubAuthHintNotice);
|
|
2910
3008
|
return;
|
|
2911
3009
|
}
|
|
3010
|
+
setGitHubDebugAuthState({
|
|
3011
|
+
auth: 'er',
|
|
3012
|
+
projectPr: 'er',
|
|
3013
|
+
});
|
|
2912
3014
|
throw error;
|
|
2913
3015
|
}
|
|
2914
3016
|
const parsedResult = asRecord(result);
|
|
@@ -2917,6 +3019,9 @@ async function main(): Promise<number> {
|
|
|
2917
3019
|
return;
|
|
2918
3020
|
}
|
|
2919
3021
|
const state = parseGitHubProjectPrState(directoryId, parsedResult);
|
|
3022
|
+
setGitHubDebugAuthState({
|
|
3023
|
+
projectPr: 'ok',
|
|
3024
|
+
});
|
|
2920
3025
|
commandMenuGitHubProjectPrState = state;
|
|
2921
3026
|
if (state.openPrUrl === null) {
|
|
2922
3027
|
setCommandNotice('no open pull request for tracked branch');
|
|
@@ -2949,9 +3054,15 @@ async function main(): Promise<number> {
|
|
|
2949
3054
|
});
|
|
2950
3055
|
} catch (error: unknown) {
|
|
2951
3056
|
if (isGitHubAuthUnavailableError(error)) {
|
|
3057
|
+
setGitHubDebugAuthState({
|
|
3058
|
+
auth: 'no',
|
|
3059
|
+
});
|
|
2952
3060
|
setCommandNotice(githubAuthHintNotice);
|
|
2953
3061
|
return;
|
|
2954
3062
|
}
|
|
3063
|
+
setGitHubDebugAuthState({
|
|
3064
|
+
auth: 'er',
|
|
3065
|
+
});
|
|
2955
3066
|
throw error;
|
|
2956
3067
|
}
|
|
2957
3068
|
const parsedResult = asRecord(result);
|
|
@@ -2963,6 +3074,10 @@ async function main(): Promise<number> {
|
|
|
2963
3074
|
if (prUrl === null) {
|
|
2964
3075
|
throw new Error('github.pr-create returned malformed pr url');
|
|
2965
3076
|
}
|
|
3077
|
+
setGitHubDebugAuthState({
|
|
3078
|
+
auth: 'ok',
|
|
3079
|
+
projectPr: 'ok',
|
|
3080
|
+
});
|
|
2966
3081
|
refreshCommandMenuGitHubProjectPrState(directoryId);
|
|
2967
3082
|
const opened = openUrlInBrowser(prUrl);
|
|
2968
3083
|
setCommandNotice(
|
|
@@ -3071,7 +3186,8 @@ async function main(): Promise<number> {
|
|
|
3071
3186
|
>({
|
|
3072
3187
|
renderFlush: {
|
|
3073
3188
|
perfNowNs,
|
|
3074
|
-
statusFooterForConversation: (conversation) =>
|
|
3189
|
+
statusFooterForConversation: (conversation) =>
|
|
3190
|
+
`${formatGitHubDebugTokens(githubDebugAuthState)} ${debugFooterForConversation(conversation)}`,
|
|
3075
3191
|
currentStatusNotice: () => debugFooterNotice.current(),
|
|
3076
3192
|
currentStatusRow: () => outputLoadSampler.currentStatusRow(),
|
|
3077
3193
|
onStatusLineComposed: (input) => {
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { resolve } from 'node:path';
|
|
2
2
|
import { startCodexLiveSession } from '../src/codex/live-session.ts';
|
|
3
3
|
import { startControlPlaneStreamServer } from '../src/control-plane/stream-server.ts';
|
|
4
|
-
import { loadHarnessConfig } from '../src/config/config-core.ts';
|
|
5
|
-
import {
|
|
4
|
+
import { loadHarnessConfig, resolveHarnessConfigPath } from '../src/config/config-core.ts';
|
|
5
|
+
import {
|
|
6
|
+
resolveHarnessRuntimePath,
|
|
7
|
+
resolveHarnessWorkspaceDirectory,
|
|
8
|
+
} from '../src/config/harness-paths.ts';
|
|
6
9
|
import { migrateLegacyHarnessLayout } from '../src/config/harness-runtime-migration.ts';
|
|
7
10
|
import { loadHarnessSecrets } from '../src/config/secrets-core.ts';
|
|
8
11
|
import {
|
|
@@ -140,6 +143,11 @@ function parseArgs(argv: string[], invocationDirectory: string): DaemonOptions {
|
|
|
140
143
|
|
|
141
144
|
async function main(): Promise<number> {
|
|
142
145
|
const invocationDirectory = resolveInvocationDirectory();
|
|
146
|
+
const gatewayRunIdRaw = process.env.HARNESS_GATEWAY_RUN_ID;
|
|
147
|
+
const gatewayRunId =
|
|
148
|
+
typeof gatewayRunIdRaw === 'string' && gatewayRunIdRaw.trim().length > 0
|
|
149
|
+
? gatewayRunIdRaw.trim()
|
|
150
|
+
: `gateway-run-${process.pid}`;
|
|
143
151
|
migrateLegacyHarnessLayout(invocationDirectory, process.env);
|
|
144
152
|
loadHarnessSecrets({ cwd: invocationDirectory });
|
|
145
153
|
configureProcessPerf(invocationDirectory);
|
|
@@ -159,14 +167,22 @@ async function main(): Promise<number> {
|
|
|
159
167
|
}
|
|
160
168
|
const startupSpan = startPerfSpan('daemon.startup.total', {
|
|
161
169
|
process: 'daemon',
|
|
170
|
+
gatewayRunId,
|
|
162
171
|
});
|
|
163
172
|
recordPerfEvent('daemon.startup.begin', {
|
|
164
173
|
process: 'daemon',
|
|
174
|
+
gatewayRunId,
|
|
165
175
|
});
|
|
166
176
|
const options = parseArgs(process.argv.slice(2), invocationDirectory);
|
|
177
|
+
const workspaceRuntimeRoot = resolveHarnessWorkspaceDirectory(invocationDirectory, process.env);
|
|
178
|
+
const configPath = resolveHarnessConfigPath(invocationDirectory, process.env);
|
|
179
|
+
process.stdout.write(
|
|
180
|
+
`[control-plane] boot runId=${gatewayRunId} pid=${String(process.pid)} ppid=${String(process.ppid)} workspaceRoot=${invocationDirectory} runtimeRoot=${workspaceRuntimeRoot} config=${configPath} db=${options.stateDbPath}\n`,
|
|
181
|
+
);
|
|
167
182
|
|
|
168
183
|
const listenSpan = startPerfSpan('daemon.startup.listen', {
|
|
169
184
|
process: 'daemon',
|
|
185
|
+
gatewayRunId,
|
|
170
186
|
});
|
|
171
187
|
const serverOptions: Parameters<typeof startControlPlaneStreamServer>[0] = {
|
|
172
188
|
host: options.host,
|
|
@@ -251,13 +267,14 @@ async function main(): Promise<number> {
|
|
|
251
267
|
const address = server.address();
|
|
252
268
|
recordPerfEvent('daemon.startup.listening', {
|
|
253
269
|
process: 'daemon',
|
|
270
|
+
gatewayRunId,
|
|
254
271
|
host: address.address,
|
|
255
272
|
port: address.port,
|
|
256
273
|
auth: options.authToken === null ? 'off' : 'on',
|
|
257
274
|
});
|
|
258
275
|
startupSpan.end({ listening: true });
|
|
259
276
|
process.stdout.write(
|
|
260
|
-
`[control-plane] listening host=${address.address} port=${String(address.port)} auth=${options.authToken === null ? 'off' : 'on'} db=${options.stateDbPath}\n`,
|
|
277
|
+
`[control-plane] listening host=${address.address} port=${String(address.port)} auth=${options.authToken === null ? 'off' : 'on'} db=${options.stateDbPath} runId=${gatewayRunId}\n`,
|
|
261
278
|
);
|
|
262
279
|
|
|
263
280
|
let stopRequested = false;
|