@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.
Files changed (41) hide show
  1. package/README.md +33 -155
  2. package/package.json +5 -1
  3. package/packages/harness-ai/src/anthropic-client.ts +99 -0
  4. package/packages/harness-ai/src/anthropic-protocol.ts +581 -0
  5. package/packages/harness-ai/src/anthropic-provider.ts +82 -0
  6. package/packages/harness-ai/src/async-iterable-stream.ts +65 -0
  7. package/packages/harness-ai/src/index.ts +36 -0
  8. package/packages/harness-ai/src/json-parse.ts +66 -0
  9. package/packages/harness-ai/src/sse.ts +80 -0
  10. package/packages/harness-ai/src/stream-object.ts +96 -0
  11. package/packages/harness-ai/src/stream-text.ts +1340 -0
  12. package/packages/harness-ai/src/types.ts +330 -0
  13. package/packages/harness-ai/src/ui-stream.ts +217 -0
  14. package/scripts/codex-live-mux-runtime.ts +123 -7
  15. package/scripts/control-plane-daemon.ts +20 -3
  16. package/scripts/harness.ts +566 -133
  17. package/src/cli/gateway-record.ts +16 -1
  18. package/src/control-plane/agent-realtime-api.ts +4 -0
  19. package/src/control-plane/prompt/agent-prompt-extractor.ts +191 -0
  20. package/src/control-plane/prompt/extractors/claude-prompt-extractor.ts +53 -0
  21. package/src/control-plane/prompt/extractors/codex-prompt-extractor.ts +50 -0
  22. package/src/control-plane/prompt/extractors/cursor-prompt-extractor.ts +56 -0
  23. package/src/control-plane/prompt/session-prompt-engine.ts +69 -0
  24. package/src/control-plane/prompt/thread-title-namer.ts +290 -0
  25. package/src/control-plane/stream-command-parser.ts +12 -0
  26. package/src/control-plane/stream-protocol.ts +109 -0
  27. package/src/control-plane/stream-server-command.ts +14 -0
  28. package/src/control-plane/stream-server-session-runtime.ts +12 -0
  29. package/src/control-plane/stream-server.ts +485 -19
  30. package/src/mux/input-shortcuts.ts +9 -0
  31. package/src/mux/live-mux/critique-review.ts +5 -1
  32. package/src/mux/live-mux/git-parsing.ts +24 -0
  33. package/src/mux/live-mux/global-shortcut-handlers.ts +8 -0
  34. package/src/mux/render-frame.ts +1 -1
  35. package/src/pty/pty_host.ts +46 -1
  36. package/src/services/control-plane.ts +22 -0
  37. package/src/services/runtime-control-actions.ts +69 -0
  38. package/src/services/runtime-navigation-input.ts +4 -0
  39. package/src/services/runtime-rail-input.ts +4 -0
  40. package/src/services/runtime-workspace-actions.ts +5 -0
  41. 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: githubProjectPrState?.branchName ?? null,
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
- } catch {
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 === 'staged') {
2704
+ if (mode !== 'base-branch') {
2627
2705
  const commandText = buildCritiqueReviewCommand({
2628
- mode: 'staged',
2706
+ mode,
2629
2707
  agent,
2630
2708
  });
2631
2709
  await runCommandInNewTerminalThread(directoryId, commandText);
2632
- setCommandNotice(`running critique staged review (${agent ?? 'default'})`);
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) => debugFooterForConversation(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 { resolveHarnessRuntimePath } from '../src/config/harness-paths.ts';
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;