@jmoyers/harness 0.1.10 → 0.1.20
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 +31 -35
- package/package.json +31 -11
- package/packages/harness-ai/src/anthropic-protocol.ts +68 -68
- package/packages/harness-ai/src/stream-text.ts +13 -91
- package/packages/harness-ui/src/frame-primitives.ts +158 -0
- package/packages/harness-ui/src/index.ts +18 -0
- package/packages/harness-ui/src/interaction/conversation-input-forwarder.ts +221 -0
- package/packages/harness-ui/src/interaction/conversation-selection-input.ts +213 -0
- package/packages/harness-ui/src/interaction/global-shortcut-input.ts +172 -0
- package/{src/ui → packages/harness-ui/src/interaction}/input-preflight.ts +10 -12
- package/{src/ui → packages/harness-ui/src/interaction}/input-token-router.ts +120 -69
- package/packages/harness-ui/src/interaction/input.ts +420 -0
- package/packages/harness-ui/src/interaction/left-nav-input.ts +166 -0
- package/{src/ui → packages/harness-ui/src/interaction}/main-pane-pointer-input.ts +91 -23
- package/{src/ui → packages/harness-ui/src/interaction}/pointer-routing-input.ts +112 -48
- package/packages/harness-ui/src/interaction/rail-pointer-input.ts +62 -0
- package/packages/harness-ui/src/interaction/repository-fold-input.ts +118 -0
- package/packages/harness-ui/src/kit.ts +476 -0
- package/packages/harness-ui/src/layout.ts +238 -0
- package/{src/ui/modals/manager.ts → packages/harness-ui/src/modal-manager.ts} +94 -64
- package/{src/ui → packages/harness-ui/src}/screen.ts +53 -26
- package/packages/harness-ui/src/surface.ts +252 -0
- package/packages/harness-ui/src/text-layout.ts +210 -0
- package/packages/nim-core/src/contracts.ts +239 -0
- package/packages/nim-core/src/event-store.ts +299 -0
- package/packages/nim-core/src/events.ts +53 -0
- package/packages/nim-core/src/index.ts +9 -0
- package/packages/nim-core/src/provider-router.ts +129 -0
- package/packages/nim-core/src/providers/anthropic-driver.ts +291 -0
- package/packages/nim-core/src/runtime-factory.ts +49 -0
- package/packages/nim-core/src/runtime.ts +1797 -0
- package/packages/nim-core/src/session-store.ts +516 -0
- package/packages/nim-core/src/telemetry.ts +48 -0
- package/packages/nim-test-tui/src/index.ts +150 -0
- package/packages/nim-ui-core/src/index.ts +1 -0
- package/packages/nim-ui-core/src/projection.ts +87 -0
- package/scripts/codex-live-mux-runtime.ts +2 -3721
- package/scripts/control-plane-daemon.ts +24 -2
- package/scripts/harness-bin.js +5 -0
- package/scripts/harness-commands.ts +300 -0
- package/scripts/harness-runtime.ts +82 -0
- package/scripts/harness.ts +33 -3007
- package/scripts/nim-tui-smoke.ts +748 -0
- package/src/cli/auth/runtime.ts +948 -0
- package/src/cli/default-gateway-pointer.ts +193 -0
- package/src/cli/gateway/runtime.ts +1872 -0
- package/src/cli/parsing/flags.ts +23 -0
- package/src/cli/parsing/session.ts +42 -0
- package/src/cli/runtime/context.ts +193 -0
- package/src/cli/runtime-app/application.ts +392 -0
- package/src/cli/runtime-infra/gateway-control.ts +729 -0
- package/{scripts/harness-inspector.ts → src/cli/workflows/inspector.ts} +14 -11
- package/src/cli/workflows/runtime.ts +965 -0
- package/src/clients/tui/left-rail-interactions.ts +519 -0
- package/src/clients/tui/main-pane-interactions.ts +509 -0
- package/src/clients/tui/modal-input-routing.ts +71 -0
- package/src/clients/tui/render-snapshot-adapter.ts +88 -0
- package/src/clients/web/synced-selectors.ts +132 -0
- package/src/codex/live-session.ts +82 -29
- package/src/config/config-core.ts +361 -10
- package/src/config/harness-paths.ts +4 -7
- package/src/config/harness-runtime-migration.ts +142 -19
- package/src/config/harness.config.template.jsonc +33 -0
- package/src/config/secrets-core.ts +92 -4
- package/src/control-plane/agent-realtime-api.ts +82 -427
- package/src/control-plane/prompt/thread-title-namer.ts +49 -23
- package/src/control-plane/session-summary.ts +10 -81
- package/src/control-plane/status/reducer-base.ts +12 -12
- package/src/control-plane/status/reducers/claude-status-reducer.ts +3 -3
- package/src/control-plane/status/reducers/codex-status-reducer.ts +4 -4
- package/src/control-plane/status/reducers/cursor-status-reducer.ts +3 -3
- package/src/control-plane/stream-client.ts +12 -2
- package/src/control-plane/stream-command-parser.ts +83 -143
- package/src/control-plane/stream-protocol.ts +53 -37
- package/src/control-plane/stream-server-background.ts +18 -2
- package/src/control-plane/stream-server-command.ts +376 -69
- package/src/control-plane/stream-server-session-runtime.ts +3 -2
- package/src/control-plane/stream-server.ts +943 -80
- package/src/control-plane/stream-session-runtime-types.ts +41 -0
- package/src/{mux/live-mux/control-plane-records.ts → core/contracts/records.ts} +24 -97
- package/src/core/state/observed-stream-cursor.ts +43 -0
- package/src/core/state/synced-observed-state.ts +273 -0
- package/src/core/store/harness-synced-store.ts +81 -0
- package/src/diff/budget.ts +136 -0
- package/src/diff/build.ts +289 -0
- package/src/diff/chunker.ts +146 -0
- package/src/diff/git-invoke.ts +315 -0
- package/src/diff/git-parse.ts +472 -0
- package/src/diff/hash.ts +70 -0
- package/src/diff/index.ts +24 -0
- package/src/diff/normalize.ts +134 -0
- package/src/diff/types.ts +178 -0
- package/src/diff-ui/args.ts +346 -0
- package/src/diff-ui/commands.ts +123 -0
- package/src/diff-ui/finder.ts +94 -0
- package/src/diff-ui/highlight.ts +127 -0
- package/src/diff-ui/index.ts +2 -0
- package/src/diff-ui/model.ts +141 -0
- package/src/diff-ui/pager.ts +412 -0
- package/src/diff-ui/render.ts +337 -0
- package/src/diff-ui/runtime.ts +379 -0
- package/src/diff-ui/state.ts +224 -0
- package/src/diff-ui/types.ts +236 -0
- package/src/domain/conversations.ts +11 -7
- package/src/domain/workspace.ts +76 -4
- package/src/mux/control-plane-op-queue.ts +93 -7
- package/src/mux/conversation-rail.ts +28 -71
- package/src/mux/dual-pane-core.ts +13 -13
- package/src/mux/harness-core-ui.ts +313 -42
- package/src/mux/input-shortcuts.ts +22 -112
- package/src/mux/keybinding-catalog.ts +340 -0
- package/src/mux/keybinding-registry.ts +103 -0
- package/src/mux/live-mux/command-menu-open-in.ts +280 -0
- package/src/mux/live-mux/command-menu.ts +167 -4
- package/src/mux/live-mux/conversation-state.ts +13 -0
- package/src/mux/live-mux/directory-resolution.ts +1 -1
- package/src/mux/live-mux/git-parsing.ts +16 -0
- package/src/mux/live-mux/git-snapshot.ts +33 -2
- package/src/mux/live-mux/global-shortcut-handlers.ts +6 -0
- package/src/mux/live-mux/home-pane-drop.ts +1 -1
- package/src/mux/live-mux/home-pane-pointer.ts +10 -0
- package/src/mux/live-mux/input-forwarding.ts +59 -2
- package/src/mux/live-mux/left-nav-activation.ts +124 -7
- package/src/mux/live-mux/left-nav.ts +35 -0
- package/src/mux/live-mux/link-click.ts +292 -0
- package/src/mux/live-mux/modal-command-menu-handler.ts +46 -9
- package/src/mux/live-mux/modal-conversation-handlers.ts +5 -1
- package/src/mux/live-mux/modal-input-reducers.ts +106 -8
- package/src/mux/live-mux/modal-overlays.ts +210 -31
- package/src/mux/live-mux/modal-pointer.ts +3 -7
- package/src/mux/live-mux/modal-prompt-handlers.ts +107 -1
- package/src/mux/live-mux/modal-release-notes-handler.ts +111 -0
- package/src/mux/live-mux/modal-task-editor-handler.ts +16 -11
- package/src/mux/live-mux/pointer-routing.ts +5 -2
- package/src/mux/live-mux/project-pane-pointer.ts +8 -0
- package/src/mux/live-mux/rail-layout.ts +33 -30
- package/src/mux/live-mux/release-notes.ts +383 -0
- package/src/mux/live-mux/render-trace-analysis.ts +52 -7
- package/src/mux/live-mux/repository-folding.ts +3 -0
- package/src/mux/live-mux/selection.ts +0 -4
- package/src/mux/live-mux/session-diagnostics-paths.ts +21 -0
- package/src/mux/project-pane-github-review.ts +271 -0
- package/src/mux/render-frame.ts +4 -0
- package/src/mux/runtime-app/codex-live-mux-runtime.ts +5191 -0
- package/src/mux/task-composer.ts +21 -14
- package/src/mux/task-focused-pane.ts +118 -117
- package/src/mux/task-screen-keybindings.ts +19 -82
- package/src/mux/workspace-rail-model.ts +270 -104
- package/src/mux/workspace-rail.ts +45 -22
- package/src/pty/session-broker.ts +1 -1
- package/{scripts → src/recording}/terminal-recording-gif-lib.ts +2 -2
- package/src/services/control-plane.ts +50 -32
- package/src/services/conversation-lifecycle.ts +118 -87
- package/src/services/conversation-startup-hydration.ts +20 -12
- package/src/services/directory-hydration.ts +21 -16
- package/src/services/event-persistence.ts +7 -0
- package/src/services/left-rail-pointer-handler.ts +329 -0
- package/src/services/mux-ui-state-persistence.ts +5 -1
- package/src/services/recording.ts +34 -26
- package/src/services/runtime-command-menu-agent-tools.ts +1 -1
- package/src/services/runtime-control-actions.ts +79 -61
- package/src/services/runtime-control-plane-ops.ts +122 -83
- package/src/services/runtime-conversation-actions.ts +40 -26
- package/src/services/runtime-conversation-activation.ts +82 -30
- package/src/services/runtime-conversation-starter.ts +80 -48
- package/src/services/runtime-conversation-title-edit.ts +91 -80
- package/src/services/runtime-envelope-handler.ts +107 -105
- package/src/services/runtime-git-state.ts +42 -29
- package/src/services/runtime-layout-resize.ts +3 -1
- package/src/services/runtime-left-rail-render.ts +99 -63
- package/src/services/runtime-nim-cli-session.ts +438 -0
- package/src/services/runtime-nim-session.ts +705 -0
- package/src/services/runtime-nim-tool-bridge.ts +141 -0
- package/src/services/runtime-observed-event-projection-pipeline.ts +45 -0
- package/src/services/runtime-process-wiring.ts +29 -36
- package/src/services/runtime-project-pane-github-review-cache.ts +164 -0
- package/src/services/runtime-render-flush.ts +63 -70
- package/src/services/runtime-render-lifecycle.ts +65 -64
- package/src/services/runtime-render-orchestrator.ts +55 -45
- package/src/services/runtime-render-pipeline.ts +106 -103
- package/src/services/runtime-render-state.ts +62 -49
- package/src/services/runtime-repository-actions.ts +97 -70
- package/src/services/runtime-right-pane-render.ts +80 -53
- package/src/services/runtime-shutdown.ts +38 -35
- package/src/services/runtime-stream-subscriptions.ts +35 -27
- package/src/services/runtime-task-composer-persistence.ts +71 -59
- package/src/services/runtime-task-composer-snapshot.ts +14 -0
- package/src/services/runtime-task-editor-actions.ts +46 -29
- package/src/services/runtime-task-pane-actions.ts +220 -134
- package/src/services/runtime-task-pane-shortcuts.ts +323 -123
- package/src/services/runtime-workspace-observed-effect-queue.ts +25 -0
- package/src/services/runtime-workspace-observed-events.ts +33 -184
- package/src/services/runtime-workspace-observed-transition-policy.ts +228 -0
- package/src/services/session-diagnostics-store.ts +217 -0
- package/src/services/startup-background-resume.ts +26 -21
- package/src/services/startup-orchestrator.ts +16 -13
- package/src/services/startup-paint-tracker.ts +29 -21
- package/src/services/startup-persisted-conversation-queue.ts +19 -13
- package/src/services/startup-settled-gate.ts +25 -15
- package/src/services/startup-shutdown.ts +18 -22
- package/src/services/startup-state-hydration.ts +44 -34
- package/src/services/startup-visibility.ts +12 -4
- package/src/services/task-pane-selection-actions.ts +89 -72
- package/src/services/task-planning-hydration.ts +24 -18
- package/src/services/task-planning-observed-events.ts +50 -52
- package/src/services/workspace-observed-events.ts +66 -63
- package/src/storage/storage-lifecycle-core.ts +438 -0
- package/src/store/control-plane-store-normalize.ts +33 -242
- package/src/store/control-plane-store-types.ts +1 -35
- package/src/store/control-plane-store.ts +396 -56
- package/src/store/event-store.ts +397 -3
- package/src/terminal/snapshot-oracle.ts +207 -94
- package/src/ui/mux-theme.ts +112 -8
- package/src/ui/panes/home-gridfire.ts +40 -31
- package/src/ui/panes/home.ts +10 -2
- package/src/ui/panes/nim.ts +315 -0
- package/src/mux/live-mux/actions-task.ts +0 -115
- package/src/mux/live-mux/left-rail-actions.ts +0 -118
- package/src/mux/live-mux/left-rail-conversation-click.ts +0 -82
- package/src/mux/live-mux/left-rail-pointer.ts +0 -74
- package/src/mux/live-mux/task-pane-shortcuts.ts +0 -206
- package/src/services/runtime-directory-actions.ts +0 -164
- package/src/services/runtime-input-pipeline.ts +0 -50
- package/src/services/runtime-input-router.ts +0 -189
- package/src/services/runtime-main-pane-input.ts +0 -230
- package/src/services/runtime-modal-input.ts +0 -119
- package/src/services/runtime-navigation-input.ts +0 -197
- package/src/services/runtime-rail-input.ts +0 -278
- package/src/services/runtime-task-pane.ts +0 -62
- package/src/services/runtime-workspace-actions.ts +0 -158
- package/src/ui/conversation-input-forwarder.ts +0 -114
- package/src/ui/conversation-selection-input.ts +0 -103
- package/src/ui/global-shortcut-input.ts +0 -89
- package/src/ui/input.ts +0 -238
- package/src/ui/kit.ts +0 -509
- package/src/ui/left-nav-input.ts +0 -80
- package/src/ui/left-rail-pointer-input.ts +0 -148
- package/src/ui/repository-fold-input.ts +0 -91
- package/src/ui/surface.ts +0 -224
|
@@ -35,7 +35,8 @@ const FALLBACK_STOP_WORDS = new Set([
|
|
|
35
35
|
'up',
|
|
36
36
|
'with',
|
|
37
37
|
]);
|
|
38
|
-
const DEFAULT_HAIKU_MODEL_ID = 'claude-
|
|
38
|
+
const DEFAULT_HAIKU_MODEL_ID = 'claude-haiku-4-5-20251001';
|
|
39
|
+
const FALLBACK_HAIKU_MODEL_IDS = ['claude-3-haiku-20240307'] as const;
|
|
39
40
|
|
|
40
41
|
interface ThreadTitlePromptHistoryEntry {
|
|
41
42
|
readonly text: string;
|
|
@@ -61,6 +62,22 @@ interface AnthropicThreadTitleNamerOptions {
|
|
|
61
62
|
readonly fetch?: typeof fetch;
|
|
62
63
|
}
|
|
63
64
|
|
|
65
|
+
function resolveModelCandidateIds(modelId: string | undefined): readonly string[] {
|
|
66
|
+
const ordered = [modelId, DEFAULT_HAIKU_MODEL_ID, ...FALLBACK_HAIKU_MODEL_IDS];
|
|
67
|
+
const deduped: string[] = [];
|
|
68
|
+
for (const candidate of ordered) {
|
|
69
|
+
if (typeof candidate !== 'string') {
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
const trimmed = candidate.trim();
|
|
73
|
+
if (trimmed.length === 0 || deduped.includes(trimmed)) {
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
deduped.push(trimmed);
|
|
77
|
+
}
|
|
78
|
+
return deduped;
|
|
79
|
+
}
|
|
80
|
+
|
|
64
81
|
function asRecord(value: unknown): Record<string, unknown> | null {
|
|
65
82
|
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
66
83
|
return null;
|
|
@@ -255,7 +272,7 @@ export function createAnthropicThreadTitleNamer(
|
|
|
255
272
|
...(options.baseUrl === undefined ? {} : { baseUrl: options.baseUrl }),
|
|
256
273
|
...(options.fetch === undefined ? {} : { fetch: options.fetch }),
|
|
257
274
|
});
|
|
258
|
-
const
|
|
275
|
+
const modelCandidateIds = resolveModelCandidateIds(options.modelId);
|
|
259
276
|
return {
|
|
260
277
|
async suggest(input: ThreadTitleNamerInput): Promise<string | null> {
|
|
261
278
|
if (input.promptHistory.length === 0) {
|
|
@@ -264,27 +281,36 @@ export function createAnthropicThreadTitleNamer(
|
|
|
264
281
|
const promptLines = input.promptHistory.map(
|
|
265
282
|
(entry, index) => `${String(index + 1)}. ${entry.text}`,
|
|
266
283
|
);
|
|
267
|
-
const
|
|
268
|
-
model
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
284
|
+
for (const modelId of modelCandidateIds) {
|
|
285
|
+
const model = anthropic(modelId);
|
|
286
|
+
const response = await generateText({
|
|
287
|
+
model,
|
|
288
|
+
system: [
|
|
289
|
+
'You name active coding-agent threads.',
|
|
290
|
+
'Use the full user prompt history to keep titles relevant and fresh.',
|
|
291
|
+
'Stay high-level and avoid low-level implementation details.',
|
|
292
|
+
'Return exactly 2 words in lowercase with no punctuation and no extra text.',
|
|
293
|
+
].join(' '),
|
|
294
|
+
prompt: [
|
|
295
|
+
`Agent: ${input.agentType}`,
|
|
296
|
+
`Current title: ${input.currentTitle}`,
|
|
297
|
+
`Conversation id: ${input.conversationId}`,
|
|
298
|
+
'Prompt history (oldest to newest):',
|
|
299
|
+
...promptLines,
|
|
300
|
+
'Return a new title now.',
|
|
301
|
+
].join('\n'),
|
|
302
|
+
maxOutputTokens: 16,
|
|
303
|
+
temperature: 0,
|
|
304
|
+
});
|
|
305
|
+
const normalized = normalizeThreadTitleCandidate(response.text);
|
|
306
|
+
if (normalized !== null) {
|
|
307
|
+
return normalized;
|
|
308
|
+
}
|
|
309
|
+
if (response.finishReason !== 'error') {
|
|
310
|
+
break;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
return fallbackThreadTitleFromPromptHistory(input.promptHistory);
|
|
288
314
|
},
|
|
289
315
|
};
|
|
290
316
|
}
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import type { PtyExit } from '../pty/pty_host.ts';
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
2
|
+
import {
|
|
3
|
+
isStreamSessionRuntimeStatus,
|
|
4
|
+
parseStreamSessionStatusModel,
|
|
5
|
+
type StreamSessionController,
|
|
6
|
+
type StreamSessionControllerType,
|
|
7
|
+
type StreamSessionRuntimeStatus,
|
|
8
|
+
type StreamSessionStatusModel,
|
|
9
|
+
type StreamTelemetrySummary,
|
|
8
10
|
} from './stream-protocol.ts';
|
|
9
11
|
|
|
10
12
|
interface StreamSessionSummary {
|
|
@@ -187,79 +189,6 @@ function readSessionController(value: unknown): StreamSessionController | null |
|
|
|
187
189
|
};
|
|
188
190
|
}
|
|
189
191
|
|
|
190
|
-
function readSessionStatusModel(value: unknown): StreamSessionStatusModel | null | undefined {
|
|
191
|
-
if (value === undefined) {
|
|
192
|
-
return undefined;
|
|
193
|
-
}
|
|
194
|
-
if (value === null) {
|
|
195
|
-
return null;
|
|
196
|
-
}
|
|
197
|
-
const record = asRecord(value);
|
|
198
|
-
if (record === null) {
|
|
199
|
-
return undefined;
|
|
200
|
-
}
|
|
201
|
-
const runtimeStatus = readString(record['runtimeStatus']);
|
|
202
|
-
const phase = readString(record['phase']);
|
|
203
|
-
const glyph = readString(record['glyph']);
|
|
204
|
-
const badge = readString(record['badge']);
|
|
205
|
-
const detailText = readString(record['detailText']);
|
|
206
|
-
const attentionReason = readNullableString(record['attentionReason']);
|
|
207
|
-
const lastKnownWork = readNullableString(record['lastKnownWork']);
|
|
208
|
-
const lastKnownWorkAt = readNullableString(record['lastKnownWorkAt']);
|
|
209
|
-
const phaseHintRaw = readNullableString(record['phaseHint']);
|
|
210
|
-
const observedAt = readString(record['observedAt']);
|
|
211
|
-
if (
|
|
212
|
-
runtimeStatus === null ||
|
|
213
|
-
!isRuntimeStatus(runtimeStatus) ||
|
|
214
|
-
phase === null ||
|
|
215
|
-
(phase !== 'needs-action' &&
|
|
216
|
-
phase !== 'starting' &&
|
|
217
|
-
phase !== 'working' &&
|
|
218
|
-
phase !== 'idle' &&
|
|
219
|
-
phase !== 'exited') ||
|
|
220
|
-
glyph === null ||
|
|
221
|
-
(glyph !== '▲' && glyph !== '◔' && glyph !== '◆' && glyph !== '○' && glyph !== '■') ||
|
|
222
|
-
badge === null ||
|
|
223
|
-
(badge !== 'NEED' && badge !== 'RUN ' && badge !== 'DONE' && badge !== 'EXIT') ||
|
|
224
|
-
detailText === null ||
|
|
225
|
-
attentionReason === undefined ||
|
|
226
|
-
lastKnownWork === undefined ||
|
|
227
|
-
lastKnownWorkAt === undefined ||
|
|
228
|
-
phaseHintRaw === undefined ||
|
|
229
|
-
observedAt === null
|
|
230
|
-
) {
|
|
231
|
-
return undefined;
|
|
232
|
-
}
|
|
233
|
-
const phaseHint =
|
|
234
|
-
phaseHintRaw === null ||
|
|
235
|
-
phaseHintRaw === 'needs-action' ||
|
|
236
|
-
phaseHintRaw === 'working' ||
|
|
237
|
-
phaseHintRaw === 'idle'
|
|
238
|
-
? phaseHintRaw
|
|
239
|
-
: undefined;
|
|
240
|
-
if (phaseHint === undefined) {
|
|
241
|
-
return undefined;
|
|
242
|
-
}
|
|
243
|
-
return {
|
|
244
|
-
runtimeStatus,
|
|
245
|
-
phase,
|
|
246
|
-
glyph,
|
|
247
|
-
badge,
|
|
248
|
-
detailText,
|
|
249
|
-
attentionReason,
|
|
250
|
-
lastKnownWork,
|
|
251
|
-
lastKnownWorkAt,
|
|
252
|
-
phaseHint,
|
|
253
|
-
observedAt,
|
|
254
|
-
};
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
function isRuntimeStatus(value: string): value is StreamSessionRuntimeStatus {
|
|
258
|
-
return (
|
|
259
|
-
value === 'running' || value === 'needs-input' || value === 'completed' || value === 'exited'
|
|
260
|
-
);
|
|
261
|
-
}
|
|
262
|
-
|
|
263
192
|
export function parseSessionSummaryRecord(value: unknown): StreamSessionSummary | null {
|
|
264
193
|
const record = asRecord(value);
|
|
265
194
|
if (record === null) {
|
|
@@ -280,12 +209,12 @@ export function parseSessionSummaryRecord(value: unknown): StreamSessionSummary
|
|
|
280
209
|
workspaceId === null ||
|
|
281
210
|
worktreeId === null ||
|
|
282
211
|
status === null ||
|
|
283
|
-
!
|
|
212
|
+
!isStreamSessionRuntimeStatus(status)
|
|
284
213
|
) {
|
|
285
214
|
return null;
|
|
286
215
|
}
|
|
287
216
|
const attentionReason = readNullableString(record['attentionReason']);
|
|
288
|
-
const statusModel =
|
|
217
|
+
const statusModel = parseStreamSessionStatusModel(record['statusModel']);
|
|
289
218
|
const latestCursor = readNullableNumber(record['latestCursor']);
|
|
290
219
|
const processId = readNullableNumber(record['processId']);
|
|
291
220
|
const attachedClients = readNumber(record['attachedClients']);
|
|
@@ -8,7 +8,7 @@ import type { AgentStatusProjectionInput, AgentStatusReducer } from './agent-sta
|
|
|
8
8
|
|
|
9
9
|
interface WorkProjection {
|
|
10
10
|
readonly text: string | null;
|
|
11
|
-
readonly
|
|
11
|
+
readonly activityHint: 'needs-action' | 'working' | 'idle' | null;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
function normalizeText(value: string | null): string | null {
|
|
@@ -40,7 +40,7 @@ function eventIsNewer(observedAt: string, previousObservedAt: string | null): bo
|
|
|
40
40
|
|
|
41
41
|
function phaseFromRuntimeStatus(
|
|
42
42
|
runtimeStatus: StreamSessionRuntimeStatus,
|
|
43
|
-
|
|
43
|
+
activityHint: WorkProjection['activityHint'],
|
|
44
44
|
): StreamSessionDisplayPhase {
|
|
45
45
|
if (runtimeStatus === 'needs-input') {
|
|
46
46
|
return 'needs-action';
|
|
@@ -48,13 +48,13 @@ function phaseFromRuntimeStatus(
|
|
|
48
48
|
if (runtimeStatus === 'exited') {
|
|
49
49
|
return 'exited';
|
|
50
50
|
}
|
|
51
|
-
if (
|
|
51
|
+
if (activityHint === 'working') {
|
|
52
52
|
return 'working';
|
|
53
53
|
}
|
|
54
|
-
if (
|
|
54
|
+
if (activityHint === 'needs-action') {
|
|
55
55
|
return 'needs-action';
|
|
56
56
|
}
|
|
57
|
-
if (
|
|
57
|
+
if (activityHint === 'idle') {
|
|
58
58
|
return 'idle';
|
|
59
59
|
}
|
|
60
60
|
if (runtimeStatus === 'running') {
|
|
@@ -118,14 +118,14 @@ export abstract class BaseAgentStatusReducer implements AgentStatusReducer {
|
|
|
118
118
|
project(input: AgentStatusProjectionInput): StreamSessionStatusModel | null {
|
|
119
119
|
const previous = input.previous;
|
|
120
120
|
let workText = previous?.lastKnownWork ?? null;
|
|
121
|
-
let
|
|
121
|
+
let workActivityHint = previous?.activityHint ?? null;
|
|
122
122
|
let workObservedAt = previous?.lastKnownWorkAt ?? null;
|
|
123
123
|
|
|
124
124
|
if (input.telemetry !== null && eventIsNewer(input.telemetry.observedAt, workObservedAt)) {
|
|
125
125
|
const projected = this.projectFromTelemetry(input.telemetry);
|
|
126
126
|
if (projected !== null) {
|
|
127
127
|
workText = projected.text;
|
|
128
|
-
|
|
128
|
+
workActivityHint = projected.activityHint;
|
|
129
129
|
workObservedAt = input.telemetry.observedAt;
|
|
130
130
|
}
|
|
131
131
|
}
|
|
@@ -133,19 +133,19 @@ export abstract class BaseAgentStatusReducer implements AgentStatusReducer {
|
|
|
133
133
|
if (
|
|
134
134
|
input.runtimeStatus === 'completed' &&
|
|
135
135
|
eventIsNewer(input.observedAt, workObservedAt) &&
|
|
136
|
-
|
|
136
|
+
workActivityHint !== 'needs-action'
|
|
137
137
|
) {
|
|
138
138
|
workText = 'inactive';
|
|
139
|
-
|
|
139
|
+
workActivityHint = 'idle';
|
|
140
140
|
workObservedAt = input.observedAt;
|
|
141
141
|
}
|
|
142
142
|
if (input.runtimeStatus === 'exited' && eventIsNewer(input.observedAt, workObservedAt)) {
|
|
143
143
|
workText = 'exited';
|
|
144
|
-
|
|
144
|
+
workActivityHint = 'idle';
|
|
145
145
|
workObservedAt = input.observedAt;
|
|
146
146
|
}
|
|
147
147
|
|
|
148
|
-
const phase = phaseFromRuntimeStatus(input.runtimeStatus,
|
|
148
|
+
const phase = phaseFromRuntimeStatus(input.runtimeStatus, workActivityHint);
|
|
149
149
|
const normalizedAttentionReason = normalizeText(input.attentionReason);
|
|
150
150
|
const detailText =
|
|
151
151
|
(input.runtimeStatus === 'needs-input' ? normalizedAttentionReason : null) ??
|
|
@@ -162,7 +162,7 @@ export abstract class BaseAgentStatusReducer implements AgentStatusReducer {
|
|
|
162
162
|
attentionReason: normalizedAttentionReason,
|
|
163
163
|
lastKnownWork: workText,
|
|
164
164
|
lastKnownWorkAt: workObservedAt,
|
|
165
|
-
|
|
165
|
+
activityHint: workActivityHint,
|
|
166
166
|
observedAt: input.observedAt,
|
|
167
167
|
};
|
|
168
168
|
}
|
|
@@ -14,12 +14,12 @@ export class ClaudeStatusReducer extends BaseAgentStatusReducer {
|
|
|
14
14
|
|
|
15
15
|
protected override projectFromTelemetry(
|
|
16
16
|
telemetry: StreamTelemetrySummary,
|
|
17
|
-
): { text: string | null;
|
|
17
|
+
): { text: string | null; activityHint: 'needs-action' | 'working' | 'idle' | null } | null {
|
|
18
18
|
const eventName = normalize(telemetry.eventName);
|
|
19
19
|
if (eventName === 'claude.userpromptsubmit' || eventName === 'claude.pretooluse') {
|
|
20
20
|
return {
|
|
21
21
|
text: 'active',
|
|
22
|
-
|
|
22
|
+
activityHint: 'working',
|
|
23
23
|
};
|
|
24
24
|
}
|
|
25
25
|
if (
|
|
@@ -29,7 +29,7 @@ export class ClaudeStatusReducer extends BaseAgentStatusReducer {
|
|
|
29
29
|
) {
|
|
30
30
|
return {
|
|
31
31
|
text: 'inactive',
|
|
32
|
-
|
|
32
|
+
activityHint: 'idle',
|
|
33
33
|
};
|
|
34
34
|
}
|
|
35
35
|
return null;
|
|
@@ -14,19 +14,19 @@ export class CodexStatusReducer extends BaseAgentStatusReducer {
|
|
|
14
14
|
|
|
15
15
|
protected override projectFromTelemetry(
|
|
16
16
|
telemetry: StreamTelemetrySummary,
|
|
17
|
-
): { text: string | null;
|
|
17
|
+
): { text: string | null; activityHint: 'needs-action' | 'working' | 'idle' | null } | null {
|
|
18
18
|
const eventName = normalize(telemetry.eventName);
|
|
19
19
|
const summary = normalize(telemetry.summary);
|
|
20
20
|
if (eventName === 'codex.user_prompt') {
|
|
21
21
|
return {
|
|
22
22
|
text: 'active',
|
|
23
|
-
|
|
23
|
+
activityHint: 'working',
|
|
24
24
|
};
|
|
25
25
|
}
|
|
26
26
|
if (eventName === 'codex.turn.e2e_duration_ms') {
|
|
27
27
|
return {
|
|
28
28
|
text: 'inactive',
|
|
29
|
-
|
|
29
|
+
activityHint: 'idle',
|
|
30
30
|
};
|
|
31
31
|
}
|
|
32
32
|
if (eventName === 'codex.sse_event') {
|
|
@@ -39,7 +39,7 @@ export class CodexStatusReducer extends BaseAgentStatusReducer {
|
|
|
39
39
|
) {
|
|
40
40
|
return {
|
|
41
41
|
text: 'active',
|
|
42
|
-
|
|
42
|
+
activityHint: 'working',
|
|
43
43
|
};
|
|
44
44
|
}
|
|
45
45
|
}
|
|
@@ -14,7 +14,7 @@ export class CursorStatusReducer extends BaseAgentStatusReducer {
|
|
|
14
14
|
|
|
15
15
|
protected override projectFromTelemetry(
|
|
16
16
|
telemetry: StreamTelemetrySummary,
|
|
17
|
-
): { text: string | null;
|
|
17
|
+
): { text: string | null; activityHint: 'needs-action' | 'working' | 'idle' | null } | null {
|
|
18
18
|
const eventName = normalize(telemetry.eventName);
|
|
19
19
|
if (
|
|
20
20
|
eventName === 'cursor.beforesubmitprompt' ||
|
|
@@ -23,13 +23,13 @@ export class CursorStatusReducer extends BaseAgentStatusReducer {
|
|
|
23
23
|
) {
|
|
24
24
|
return {
|
|
25
25
|
text: 'active',
|
|
26
|
-
|
|
26
|
+
activityHint: 'working',
|
|
27
27
|
};
|
|
28
28
|
}
|
|
29
29
|
if (eventName === 'cursor.stop' || eventName === 'cursor.sessionend') {
|
|
30
30
|
return {
|
|
31
31
|
text: 'inactive',
|
|
32
|
-
|
|
32
|
+
activityHint: 'idle',
|
|
33
33
|
};
|
|
34
34
|
}
|
|
35
35
|
return null;
|
|
@@ -26,7 +26,17 @@ interface PendingCommand {
|
|
|
26
26
|
reject: (error: Error) => void;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
export
|
|
29
|
+
export interface ControlPlaneStreamClient {
|
|
30
|
+
onEnvelope(listener: (envelope: StreamServerEnvelope) => void): () => void;
|
|
31
|
+
sendCommand(command: StreamCommand): Promise<Record<string, unknown>>;
|
|
32
|
+
authenticate(token: string): Promise<void>;
|
|
33
|
+
sendInput(sessionId: string, data: Buffer): void;
|
|
34
|
+
sendResize(sessionId: string, cols: number, rows: number): void;
|
|
35
|
+
sendSignal(sessionId: string, signal: StreamSignal): void;
|
|
36
|
+
close(): void;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
class ControlPlaneStreamClientImpl implements ControlPlaneStreamClient {
|
|
30
40
|
private readonly socket: Socket;
|
|
31
41
|
private readonly listeners = new Set<(envelope: StreamServerEnvelope) => void>();
|
|
32
42
|
private readonly pending = new Map<string, PendingCommand>();
|
|
@@ -380,7 +390,7 @@ export async function connectControlPlaneStreamClient(
|
|
|
380
390
|
}
|
|
381
391
|
}
|
|
382
392
|
|
|
383
|
-
const client = new
|
|
393
|
+
const client = new ControlPlaneStreamClientImpl(socket);
|
|
384
394
|
if (typeof options.authToken === 'string') {
|
|
385
395
|
await client.authenticate(options.authToken);
|
|
386
396
|
}
|