@jmoyers/harness 0.1.11 → 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 -39
- 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/packages/harness-ui/src/modal-manager.ts +222 -0
- 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 -3872
- package/scripts/control-plane-daemon.ts +11 -0
- 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 -3019
- package/scripts/nim-tui-smoke.ts +748 -0
- package/src/cli/auth/runtime.ts +948 -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 +348 -8
- package/src/config/harness.config.template.jsonc +33 -0
- package/src/control-plane/agent-realtime-api.ts +82 -427
- 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-command.ts +376 -69
- package/src/control-plane/stream-server-session-runtime.ts +3 -2
- package/src/control-plane/stream-server.ts +864 -70
- 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/workspace.ts +68 -5
- 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 +13 -131
- 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-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 +77 -12
- package/src/mux/live-mux/modal-overlays.ts +168 -34
- package/src/mux/live-mux/modal-pointer.ts +3 -7
- package/src/mux/live-mux/modal-prompt-handlers.ts +23 -2
- 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 +10 -101
- 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 +73 -46
- package/src/services/runtime-conversation-starter.ts +53 -45
- 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 -72
- 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 +360 -56
- package/src/store/event-store.ts +366 -8
- 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 -85
- 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 -195
- package/src/services/runtime-main-pane-input.ts +0 -230
- package/src/services/runtime-modal-input.ts +0 -137
- package/src/services/runtime-navigation-input.ts +0 -197
- package/src/services/runtime-rail-input.ts +0 -279
- 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 -269
- 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/modals/manager.ts +0 -218
- package/src/ui/repository-fold-input.ts +0 -91
- package/src/ui/surface.ts +0 -224
|
@@ -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
|
}
|
|
@@ -3,7 +3,6 @@ import type { StreamCommand } from './stream-protocol.ts';
|
|
|
3
3
|
type StreamCommandType = StreamCommand['type'];
|
|
4
4
|
type CommandRecord = Record<string, unknown>;
|
|
5
5
|
type CommandParser = (record: CommandRecord) => StreamCommand | null;
|
|
6
|
-
type ParsedTaskLinearInput = NonNullable<Extract<StreamCommand, { type: 'task.create' }>['linear']>;
|
|
7
6
|
const INVALID_OPTIONAL = Symbol('invalid-optional');
|
|
8
7
|
|
|
9
8
|
function asRecord(value: unknown): CommandRecord | null {
|
|
@@ -95,115 +94,6 @@ function readOptionalNullableString(
|
|
|
95
94
|
return value;
|
|
96
95
|
}
|
|
97
96
|
|
|
98
|
-
function readOptionalNullableNonNegativeInteger(
|
|
99
|
-
record: CommandRecord,
|
|
100
|
-
field: string,
|
|
101
|
-
): number | null | undefined | typeof INVALID_OPTIONAL {
|
|
102
|
-
const value = record[field];
|
|
103
|
-
if (value === undefined) {
|
|
104
|
-
return undefined;
|
|
105
|
-
}
|
|
106
|
-
if (value === null) {
|
|
107
|
-
return null;
|
|
108
|
-
}
|
|
109
|
-
if (typeof value !== 'number' || !Number.isInteger(value) || value < 0) {
|
|
110
|
-
return INVALID_OPTIONAL;
|
|
111
|
-
}
|
|
112
|
-
return value;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
function parseTaskLinearInput(value: unknown): ParsedTaskLinearInput | null {
|
|
116
|
-
const record = asRecord(value);
|
|
117
|
-
if (record === null) {
|
|
118
|
-
return null;
|
|
119
|
-
}
|
|
120
|
-
const issueId = readOptionalNullableString(record, 'issueId');
|
|
121
|
-
const identifier = readOptionalNullableString(record, 'identifier');
|
|
122
|
-
const url = readOptionalNullableString(record, 'url');
|
|
123
|
-
const teamId = readOptionalNullableString(record, 'teamId');
|
|
124
|
-
const projectId = readOptionalNullableString(record, 'projectId');
|
|
125
|
-
const projectMilestoneId = readOptionalNullableString(record, 'projectMilestoneId');
|
|
126
|
-
const cycleId = readOptionalNullableString(record, 'cycleId');
|
|
127
|
-
const stateId = readOptionalNullableString(record, 'stateId');
|
|
128
|
-
const assigneeId = readOptionalNullableString(record, 'assigneeId');
|
|
129
|
-
const dueDate = readOptionalNullableString(record, 'dueDate');
|
|
130
|
-
const priority = readOptionalNullableNonNegativeInteger(record, 'priority');
|
|
131
|
-
const estimate = readOptionalNullableNonNegativeInteger(record, 'estimate');
|
|
132
|
-
const labelsRaw = record['labelIds'];
|
|
133
|
-
let labelIds: string[] | null | undefined;
|
|
134
|
-
if (labelsRaw !== undefined) {
|
|
135
|
-
if (labelsRaw === null) {
|
|
136
|
-
labelIds = null;
|
|
137
|
-
} else if (Array.isArray(labelsRaw) && labelsRaw.every((entry) => typeof entry === 'string')) {
|
|
138
|
-
labelIds = [...labelsRaw];
|
|
139
|
-
} else {
|
|
140
|
-
return null;
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
if (
|
|
145
|
-
issueId === INVALID_OPTIONAL ||
|
|
146
|
-
identifier === INVALID_OPTIONAL ||
|
|
147
|
-
url === INVALID_OPTIONAL ||
|
|
148
|
-
teamId === INVALID_OPTIONAL ||
|
|
149
|
-
projectId === INVALID_OPTIONAL ||
|
|
150
|
-
projectMilestoneId === INVALID_OPTIONAL ||
|
|
151
|
-
cycleId === INVALID_OPTIONAL ||
|
|
152
|
-
stateId === INVALID_OPTIONAL ||
|
|
153
|
-
assigneeId === INVALID_OPTIONAL ||
|
|
154
|
-
dueDate === INVALID_OPTIONAL ||
|
|
155
|
-
priority === INVALID_OPTIONAL ||
|
|
156
|
-
estimate === INVALID_OPTIONAL
|
|
157
|
-
) {
|
|
158
|
-
return null;
|
|
159
|
-
}
|
|
160
|
-
if (priority !== undefined && priority !== null && priority > 4) {
|
|
161
|
-
return null;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
const out: ParsedTaskLinearInput = {};
|
|
165
|
-
if (issueId !== undefined) {
|
|
166
|
-
out.issueId = issueId;
|
|
167
|
-
}
|
|
168
|
-
if (identifier !== undefined) {
|
|
169
|
-
out.identifier = identifier;
|
|
170
|
-
}
|
|
171
|
-
if (url !== undefined) {
|
|
172
|
-
out.url = url;
|
|
173
|
-
}
|
|
174
|
-
if (teamId !== undefined) {
|
|
175
|
-
out.teamId = teamId;
|
|
176
|
-
}
|
|
177
|
-
if (projectId !== undefined) {
|
|
178
|
-
out.projectId = projectId;
|
|
179
|
-
}
|
|
180
|
-
if (projectMilestoneId !== undefined) {
|
|
181
|
-
out.projectMilestoneId = projectMilestoneId;
|
|
182
|
-
}
|
|
183
|
-
if (cycleId !== undefined) {
|
|
184
|
-
out.cycleId = cycleId;
|
|
185
|
-
}
|
|
186
|
-
if (stateId !== undefined) {
|
|
187
|
-
out.stateId = stateId;
|
|
188
|
-
}
|
|
189
|
-
if (assigneeId !== undefined) {
|
|
190
|
-
out.assigneeId = assigneeId;
|
|
191
|
-
}
|
|
192
|
-
if (priority !== undefined) {
|
|
193
|
-
out.priority = priority as 0 | 1 | 2 | 3 | 4 | null;
|
|
194
|
-
}
|
|
195
|
-
if (estimate !== undefined) {
|
|
196
|
-
out.estimate = estimate;
|
|
197
|
-
}
|
|
198
|
-
if (dueDate !== undefined) {
|
|
199
|
-
out.dueDate = dueDate;
|
|
200
|
-
}
|
|
201
|
-
if (labelIds !== undefined) {
|
|
202
|
-
out.labelIds = labelIds;
|
|
203
|
-
}
|
|
204
|
-
return out;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
97
|
function parseSessionControllerType(value: unknown): 'human' | 'agent' | 'automation' | null {
|
|
208
98
|
if (value === 'human' || value === 'agent' || value === 'automation') {
|
|
209
99
|
return value;
|
|
@@ -586,32 +476,38 @@ function parseRepositoryArchive(record: CommandRecord): StreamCommand | null {
|
|
|
586
476
|
}
|
|
587
477
|
|
|
588
478
|
function parseTaskCreate(record: CommandRecord): StreamCommand | null {
|
|
589
|
-
const
|
|
590
|
-
if (
|
|
479
|
+
const body = readString(record['body']);
|
|
480
|
+
if (body === null) {
|
|
591
481
|
return null;
|
|
592
482
|
}
|
|
483
|
+
const title = readOptionalNullableString(record, 'title');
|
|
593
484
|
const taskId = readOptionalString(record, 'taskId');
|
|
594
485
|
const tenantId = readOptionalString(record, 'tenantId');
|
|
595
486
|
const userId = readOptionalString(record, 'userId');
|
|
596
487
|
const workspaceId = readOptionalString(record, 'workspaceId');
|
|
597
488
|
const repositoryId = readOptionalString(record, 'repositoryId');
|
|
598
489
|
const projectId = readOptionalString(record, 'projectId');
|
|
599
|
-
const description = readOptionalString(record, 'description');
|
|
600
490
|
if (
|
|
491
|
+
title === INVALID_OPTIONAL ||
|
|
601
492
|
taskId === undefined ||
|
|
602
493
|
tenantId === undefined ||
|
|
603
494
|
userId === undefined ||
|
|
604
495
|
workspaceId === undefined ||
|
|
605
496
|
repositoryId === undefined ||
|
|
606
|
-
projectId === undefined
|
|
607
|
-
description === undefined
|
|
497
|
+
projectId === undefined
|
|
608
498
|
) {
|
|
609
499
|
return null;
|
|
610
500
|
}
|
|
501
|
+
if (repositoryId === null && projectId === null) {
|
|
502
|
+
return null;
|
|
503
|
+
}
|
|
611
504
|
const command: StreamCommand = {
|
|
612
505
|
type: 'task.create',
|
|
613
|
-
|
|
506
|
+
body,
|
|
614
507
|
};
|
|
508
|
+
if (title !== undefined) {
|
|
509
|
+
command.title = title;
|
|
510
|
+
}
|
|
615
511
|
if (taskId !== null) {
|
|
616
512
|
command.taskId = taskId;
|
|
617
513
|
}
|
|
@@ -630,16 +526,6 @@ function parseTaskCreate(record: CommandRecord): StreamCommand | null {
|
|
|
630
526
|
if (projectId !== null) {
|
|
631
527
|
command.projectId = projectId;
|
|
632
528
|
}
|
|
633
|
-
if (description !== null) {
|
|
634
|
-
command.description = description;
|
|
635
|
-
}
|
|
636
|
-
if (record['linear'] !== undefined) {
|
|
637
|
-
const linear = parseTaskLinearInput(record['linear']);
|
|
638
|
-
if (linear === null) {
|
|
639
|
-
return null;
|
|
640
|
-
}
|
|
641
|
-
command.linear = linear;
|
|
642
|
-
}
|
|
643
529
|
return command;
|
|
644
530
|
}
|
|
645
531
|
|
|
@@ -728,9 +614,9 @@ function parseTaskUpdate(record: CommandRecord): StreamCommand | null {
|
|
|
728
614
|
if (taskId === null) {
|
|
729
615
|
return null;
|
|
730
616
|
}
|
|
731
|
-
const title =
|
|
732
|
-
const
|
|
733
|
-
if (title ===
|
|
617
|
+
const title = readOptionalNullableString(record, 'title');
|
|
618
|
+
const body = readOptionalString(record, 'body');
|
|
619
|
+
if (title === INVALID_OPTIONAL || body === undefined) {
|
|
734
620
|
return null;
|
|
735
621
|
}
|
|
736
622
|
let repositoryId: string | null | undefined;
|
|
@@ -753,16 +639,19 @@ function parseTaskUpdate(record: CommandRecord): StreamCommand | null {
|
|
|
753
639
|
} else {
|
|
754
640
|
return null;
|
|
755
641
|
}
|
|
642
|
+
if (repositoryId === null && projectId === null) {
|
|
643
|
+
return null;
|
|
644
|
+
}
|
|
756
645
|
|
|
757
646
|
const command: StreamCommand = {
|
|
758
647
|
type: 'task.update',
|
|
759
648
|
taskId,
|
|
760
649
|
};
|
|
761
|
-
if (title !==
|
|
650
|
+
if (title !== undefined) {
|
|
762
651
|
command.title = title;
|
|
763
652
|
}
|
|
764
|
-
if (
|
|
765
|
-
command.
|
|
653
|
+
if (body !== null) {
|
|
654
|
+
command.body = body;
|
|
766
655
|
}
|
|
767
656
|
if (repositoryId !== undefined) {
|
|
768
657
|
command.repositoryId = repositoryId;
|
|
@@ -770,17 +659,6 @@ function parseTaskUpdate(record: CommandRecord): StreamCommand | null {
|
|
|
770
659
|
if (projectId !== undefined) {
|
|
771
660
|
command.projectId = projectId;
|
|
772
661
|
}
|
|
773
|
-
if (record['linear'] !== undefined) {
|
|
774
|
-
if (record['linear'] === null) {
|
|
775
|
-
command.linear = null;
|
|
776
|
-
} else {
|
|
777
|
-
const linear = parseTaskLinearInput(record['linear']);
|
|
778
|
-
if (linear === null) {
|
|
779
|
-
return null;
|
|
780
|
-
}
|
|
781
|
-
command.linear = linear;
|
|
782
|
-
}
|
|
783
|
-
}
|
|
784
662
|
return command;
|
|
785
663
|
}
|
|
786
664
|
|
|
@@ -1109,6 +987,25 @@ function parseGitHubProjectPr(record: CommandRecord): StreamCommand | null {
|
|
|
1109
987
|
};
|
|
1110
988
|
}
|
|
1111
989
|
|
|
990
|
+
function parseGitHubProjectReview(record: CommandRecord): StreamCommand | null {
|
|
991
|
+
const directoryId = readString(record['directoryId']);
|
|
992
|
+
const forceRefresh = readOptionalBoolean(record, 'forceRefresh');
|
|
993
|
+
if (directoryId === null) {
|
|
994
|
+
return null;
|
|
995
|
+
}
|
|
996
|
+
if (forceRefresh === undefined) {
|
|
997
|
+
return null;
|
|
998
|
+
}
|
|
999
|
+
const command: StreamCommand = {
|
|
1000
|
+
type: 'github.project-review',
|
|
1001
|
+
directoryId,
|
|
1002
|
+
};
|
|
1003
|
+
if (forceRefresh !== null) {
|
|
1004
|
+
command.forceRefresh = forceRefresh;
|
|
1005
|
+
}
|
|
1006
|
+
return command;
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1112
1009
|
function parseGitHubPrList(record: CommandRecord): StreamCommand | null {
|
|
1113
1010
|
const tenantId = readOptionalString(record, 'tenantId');
|
|
1114
1011
|
const userId = readOptionalString(record, 'userId');
|
|
@@ -1256,6 +1153,47 @@ function parseGitHubRepoMyPrsUrl(record: CommandRecord): StreamCommand | null {
|
|
|
1256
1153
|
};
|
|
1257
1154
|
}
|
|
1258
1155
|
|
|
1156
|
+
function parseLinearIssueImport(record: CommandRecord): StreamCommand | null {
|
|
1157
|
+
const url = readString(record['url']);
|
|
1158
|
+
if (url === null) {
|
|
1159
|
+
return null;
|
|
1160
|
+
}
|
|
1161
|
+
const tenantId = readOptionalString(record, 'tenantId');
|
|
1162
|
+
const userId = readOptionalString(record, 'userId');
|
|
1163
|
+
const workspaceId = readOptionalString(record, 'workspaceId');
|
|
1164
|
+
const repositoryId = readOptionalString(record, 'repositoryId');
|
|
1165
|
+
const projectId = readOptionalString(record, 'projectId');
|
|
1166
|
+
if (
|
|
1167
|
+
tenantId === undefined ||
|
|
1168
|
+
userId === undefined ||
|
|
1169
|
+
workspaceId === undefined ||
|
|
1170
|
+
repositoryId === undefined ||
|
|
1171
|
+
projectId === undefined
|
|
1172
|
+
) {
|
|
1173
|
+
return null;
|
|
1174
|
+
}
|
|
1175
|
+
const command: StreamCommand = {
|
|
1176
|
+
type: 'linear.issue.import',
|
|
1177
|
+
url,
|
|
1178
|
+
};
|
|
1179
|
+
if (tenantId !== null) {
|
|
1180
|
+
command.tenantId = tenantId;
|
|
1181
|
+
}
|
|
1182
|
+
if (userId !== null) {
|
|
1183
|
+
command.userId = userId;
|
|
1184
|
+
}
|
|
1185
|
+
if (workspaceId !== null) {
|
|
1186
|
+
command.workspaceId = workspaceId;
|
|
1187
|
+
}
|
|
1188
|
+
if (repositoryId !== null) {
|
|
1189
|
+
command.repositoryId = repositoryId;
|
|
1190
|
+
}
|
|
1191
|
+
if (projectId !== null) {
|
|
1192
|
+
command.projectId = projectId;
|
|
1193
|
+
}
|
|
1194
|
+
return command;
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1259
1197
|
function parseStreamSubscribe(record: CommandRecord): StreamCommand | null {
|
|
1260
1198
|
const tenantId = readOptionalString(record, 'tenantId');
|
|
1261
1199
|
const userId = readOptionalString(record, 'userId');
|
|
@@ -1664,10 +1602,12 @@ export const DEFAULT_STREAM_COMMAND_PARSERS: StreamCommandParserRegistry = {
|
|
|
1664
1602
|
'automation.policy-get': parseAutomationPolicyGet,
|
|
1665
1603
|
'automation.policy-set': parseAutomationPolicySet,
|
|
1666
1604
|
'github.project-pr': parseGitHubProjectPr,
|
|
1605
|
+
'github.project-review': parseGitHubProjectReview,
|
|
1667
1606
|
'github.pr-list': parseGitHubPrList,
|
|
1668
1607
|
'github.pr-create': parseGitHubPrCreate,
|
|
1669
1608
|
'github.pr-jobs-list': parseGitHubPrJobsList,
|
|
1670
1609
|
'github.repo-my-prs-url': parseGitHubRepoMyPrsUrl,
|
|
1610
|
+
'linear.issue.import': parseLinearIssueImport,
|
|
1671
1611
|
'stream.subscribe': parseStreamSubscribe,
|
|
1672
1612
|
'stream.unsubscribe': parseStreamUnsubscribe,
|
|
1673
1613
|
'session.list': parseSessionList,
|