@jmoyers/harness 0.1.9 → 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 (32) hide show
  1. package/README.md +33 -156
  2. package/package.json +3 -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 +103 -3
  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/prompt/thread-title-namer.ts +290 -0
  19. package/src/control-plane/stream-command-parser.ts +12 -0
  20. package/src/control-plane/stream-protocol.ts +6 -0
  21. package/src/control-plane/stream-server-command.ts +14 -0
  22. package/src/control-plane/stream-server.ts +382 -19
  23. package/src/mux/input-shortcuts.ts +9 -0
  24. package/src/mux/live-mux/git-parsing.ts +24 -0
  25. package/src/mux/live-mux/global-shortcut-handlers.ts +8 -0
  26. package/src/mux/render-frame.ts +1 -1
  27. package/src/services/control-plane.ts +22 -0
  28. package/src/services/runtime-control-actions.ts +69 -0
  29. package/src/services/runtime-navigation-input.ts +4 -0
  30. package/src/services/runtime-rail-input.ts +4 -0
  31. package/src/services/runtime-workspace-actions.ts +5 -0
  32. package/src/ui/global-shortcut-input.ts +2 -0
@@ -51,7 +51,7 @@ export function buildRenderRows(
51
51
  const statusText =
52
52
  statusRowDetailText === undefined || statusRowDetailText.length === 0
53
53
  ? defaultStatus
54
- : `${defaultStatus} ${statusRowDetailText}`;
54
+ : `${statusRowDetailText} ${defaultStatus}`;
55
55
  const status = padOrTrimDisplay(statusText, layout.cols);
56
56
  rows.push(status);
57
57
  return rows;
@@ -245,6 +245,28 @@ export class ControlPlaneService {
245
245
  return parseConversationRecord(result['conversation']);
246
246
  }
247
247
 
248
+ async refreshConversationTitle(conversationId: string): Promise<{
249
+ status: 'updated' | 'unchanged' | 'skipped';
250
+ reason: string | null;
251
+ }> {
252
+ const result = await this.client.sendCommand({
253
+ type: 'conversation.title.refresh',
254
+ conversationId,
255
+ });
256
+ const status = result['status'];
257
+ if (status !== 'updated' && status !== 'unchanged' && status !== 'skipped') {
258
+ throw new Error('control-plane conversation.title.refresh returned malformed status');
259
+ }
260
+ const reason = result['reason'];
261
+ if (reason !== null && reason !== undefined && typeof reason !== 'string') {
262
+ throw new Error('control-plane conversation.title.refresh returned malformed reason');
263
+ }
264
+ return {
265
+ status,
266
+ reason: reason ?? null,
267
+ };
268
+ }
269
+
248
270
  async archiveConversation(conversationId: string): Promise<void> {
249
271
  await this.client.sendCommand({
250
272
  type: 'conversation.archive',
@@ -21,6 +21,13 @@ interface RuntimeGatewayRenderTraceResult {
21
21
  readonly message: string;
22
22
  }
23
23
 
24
+ interface RuntimeConversationTitleRefreshResult {
25
+ readonly status: 'updated' | 'unchanged' | 'skipped';
26
+ readonly reason: string | null;
27
+ }
28
+
29
+ const THREAD_TITLE_AGENT_TYPES = new Set(['codex', 'claude', 'cursor']);
30
+
24
31
  interface RuntimeControlActionsOptions<TConversation extends RuntimeConversationControlState> {
25
32
  readonly conversationById: (sessionId: string) => TConversation | undefined;
26
33
  readonly interruptSession: (sessionId: string) => Promise<RuntimeInterruptResult>;
@@ -43,6 +50,11 @@ interface RuntimeControlActionsOptions<TConversation extends RuntimeConversation
43
50
  readonly sessionName: string | null;
44
51
  readonly setTaskPaneNotice: (message: string) => void;
45
52
  readonly setDebugFooterNotice: (message: string) => void;
53
+ readonly listConversationIdsForTitleRefresh?: () => readonly string[];
54
+ readonly conversationAgentTypeForTitleRefresh?: (sessionId: string) => string | null;
55
+ readonly refreshConversationTitle?: (
56
+ sessionId: string,
57
+ ) => Promise<RuntimeConversationTitleRefreshResult>;
46
58
  }
47
59
 
48
60
  export class RuntimeControlActions<TConversation extends RuntimeConversationControlState> {
@@ -109,6 +121,63 @@ export class RuntimeControlActions<TConversation extends RuntimeConversationCont
109
121
  }
110
122
  }
111
123
 
124
+ async refreshAllConversationTitles(): Promise<void> {
125
+ const listConversationIds = this.options.listConversationIdsForTitleRefresh;
126
+ const resolveAgentType = this.options.conversationAgentTypeForTitleRefresh;
127
+ const refreshConversationTitle = this.options.refreshConversationTitle;
128
+ if (
129
+ listConversationIds === undefined ||
130
+ resolveAgentType === undefined ||
131
+ refreshConversationTitle === undefined
132
+ ) {
133
+ this.setNotices(this.scopeMessage('thread-title', 'refresh unavailable'));
134
+ this.options.markDirty();
135
+ return;
136
+ }
137
+ const allConversationIds = listConversationIds();
138
+ const eligibleConversationIds = allConversationIds.filter((sessionId) => {
139
+ const agentType = resolveAgentType(sessionId)?.trim().toLowerCase();
140
+ return agentType !== undefined && THREAD_TITLE_AGENT_TYPES.has(agentType);
141
+ });
142
+ if (eligibleConversationIds.length === 0) {
143
+ this.setNotices(this.scopeMessage('thread-title', 'no agent threads to refresh'));
144
+ this.options.markDirty();
145
+ return;
146
+ }
147
+ const total = eligibleConversationIds.length;
148
+ let updated = 0;
149
+ let unchanged = 0;
150
+ let skipped = 0;
151
+ this.setNotices(this.scopeMessage('thread-title', `refreshing names 0/${String(total)}`));
152
+ this.options.markDirty();
153
+ for (let index = 0; index < eligibleConversationIds.length; index += 1) {
154
+ const sessionId = eligibleConversationIds[index]!;
155
+ try {
156
+ const result = await refreshConversationTitle(sessionId);
157
+ if (result.status === 'updated') {
158
+ updated += 1;
159
+ } else if (result.status === 'unchanged') {
160
+ unchanged += 1;
161
+ } else {
162
+ skipped += 1;
163
+ }
164
+ } catch {
165
+ skipped += 1;
166
+ }
167
+ this.setNotices(
168
+ this.scopeMessage('thread-title', `refreshing names ${String(index + 1)}/${String(total)}`),
169
+ );
170
+ this.options.markDirty();
171
+ }
172
+ this.setNotices(
173
+ this.scopeMessage(
174
+ 'thread-title',
175
+ `refreshed ${String(updated)} updated ${String(unchanged)} unchanged ${String(skipped)} skipped`,
176
+ ),
177
+ );
178
+ this.options.markDirty();
179
+ }
180
+
112
181
  private scopeMessage(prefix: string, message: string): string {
113
182
  if (this.options.sessionName === null) {
114
183
  return `[${prefix}] ${message}`;
@@ -17,6 +17,7 @@ interface RuntimeNavigationWorkspaceActions {
17
17
  toggleGatewayStatusTimeline(): Promise<void>;
18
18
  toggleGatewayRenderTrace(conversationId: string | null): Promise<void>;
19
19
  archiveConversation(sessionId: string): Promise<void>;
20
+ refreshAllConversationTitles(): Promise<void>;
20
21
  interruptConversation(sessionId: string): Promise<void>;
21
22
  takeoverConversation(sessionId: string): Promise<void>;
22
23
  closeDirectory(directoryId: string): Promise<void>;
@@ -157,6 +158,9 @@ export class RuntimeNavigationInput {
157
158
  archiveConversation: async (sessionId) => {
158
159
  await options.workspaceActions.archiveConversation(sessionId);
159
160
  },
161
+ refreshAllConversationTitles: async () => {
162
+ await options.workspaceActions.refreshAllConversationTitles();
163
+ },
160
164
  interruptConversation: async (sessionId) => {
161
165
  await options.workspaceActions.interruptConversation(sessionId);
162
166
  },
@@ -15,6 +15,7 @@ interface RuntimeRailWorkspaceActions {
15
15
  toggleGatewayStatusTimeline(): Promise<void>;
16
16
  toggleGatewayRenderTrace(conversationId: string | null): Promise<void>;
17
17
  archiveConversation(sessionId: string): Promise<void>;
18
+ refreshAllConversationTitles(): Promise<void>;
18
19
  interruptConversation(sessionId: string): Promise<void>;
19
20
  takeoverConversation(sessionId: string): Promise<void>;
20
21
  closeDirectory(directoryId: string): Promise<void>;
@@ -142,6 +143,9 @@ export class RuntimeRailInput {
142
143
  archiveConversation: async (sessionId) => {
143
144
  await options.runtimeWorkspaceActions.archiveConversation(sessionId);
144
145
  },
146
+ refreshAllConversationTitles: async () => {
147
+ await options.runtimeWorkspaceActions.refreshAllConversationTitles();
148
+ },
145
149
  interruptConversation: async (sessionId) => {
146
150
  await options.runtimeWorkspaceActions.interruptConversation(sessionId);
147
151
  },
@@ -30,6 +30,7 @@ interface RuntimeWorkspaceControlActions {
30
30
  toggleGatewayProfiler(): Promise<void>;
31
31
  toggleGatewayStatusTimeline(): Promise<void>;
32
32
  toggleGatewayRenderTrace(conversationId: string | null): Promise<void>;
33
+ refreshAllConversationTitles(): Promise<void>;
33
34
  }
34
35
 
35
36
  interface RuntimeWorkspaceTaskPaneActions {
@@ -135,6 +136,10 @@ export class RuntimeWorkspaceActions {
135
136
  await this.options.controlActions.toggleGatewayRenderTrace(conversationId);
136
137
  }
137
138
 
139
+ async refreshAllConversationTitles(): Promise<void> {
140
+ await this.options.controlActions.refreshAllConversationTitles();
141
+ }
142
+
138
143
  runTaskPaneAction(action: TaskPaneAction): void {
139
144
  this.options.taskPaneActions.runTaskPaneAction(action);
140
145
  }
@@ -23,6 +23,7 @@ interface GlobalShortcutInputOptions {
23
23
  readonly conversationsHas: (sessionId: string) => boolean;
24
24
  readonly queueControlPlaneOp: (task: () => Promise<void>, label: string) => void;
25
25
  readonly archiveConversation: (sessionId: string) => Promise<void>;
26
+ readonly refreshAllConversationTitles: () => Promise<void>;
26
27
  readonly interruptConversation: (sessionId: string) => Promise<void>;
27
28
  readonly takeoverConversation: (sessionId: string) => Promise<void>;
28
29
  readonly openAddDirectoryPrompt: () => void;
@@ -70,6 +71,7 @@ export class GlobalShortcutInput {
70
71
  conversationsHas: this.options.conversationsHas,
71
72
  queueControlPlaneOp: this.options.queueControlPlaneOp,
72
73
  archiveConversation: this.options.archiveConversation,
74
+ refreshAllConversationTitles: this.options.refreshAllConversationTitles,
73
75
  interruptConversation: this.options.interruptConversation,
74
76
  takeoverConversation: this.options.takeoverConversation,
75
77
  openAddDirectoryPrompt: this.options.openAddDirectoryPrompt,