@jmoyers/harness 0.1.0
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/LICENSE +21 -0
- package/README.md +145 -0
- package/native/ptyd/Cargo.lock +16 -0
- package/native/ptyd/Cargo.toml +7 -0
- package/native/ptyd/src/main.rs +257 -0
- package/package.json +90 -0
- package/scripts/build-ptyd.sh +73 -0
- package/scripts/control-plane-daemon.ts +277 -0
- package/scripts/cursor-hook-relay.ts +82 -0
- package/scripts/harness-animate.ts +469 -0
- package/scripts/harness-bin.js +77 -0
- package/scripts/harness-core.ts +1 -0
- package/scripts/harness-inspector.ts +439 -0
- package/scripts/harness.ts +2493 -0
- package/src/adapters/agent-session-state.ts +390 -0
- package/src/cli/gateway-record.ts +173 -0
- package/src/codex/live-session.ts +872 -0
- package/src/config/config-core.ts +1359 -0
- package/src/config/secrets-core.ts +170 -0
- package/src/control-plane/agent-realtime-api.ts +2441 -0
- package/src/control-plane/codex-session-stream.ts +392 -0
- package/src/control-plane/codex-telemetry.ts +1325 -0
- package/src/control-plane/lifecycle-hooks.ts +706 -0
- package/src/control-plane/session-summary.ts +380 -0
- package/src/control-plane/status/agent-status-reducer.ts +21 -0
- package/src/control-plane/status/reducer-base.ts +170 -0
- package/src/control-plane/status/reducers/claude-status-reducer.ts +37 -0
- package/src/control-plane/status/reducers/codex-status-reducer.ts +48 -0
- package/src/control-plane/status/reducers/critique-status-reducer.ts +15 -0
- package/src/control-plane/status/reducers/cursor-status-reducer.ts +37 -0
- package/src/control-plane/status/reducers/terminal-status-reducer.ts +15 -0
- package/src/control-plane/status/session-status-engine.ts +76 -0
- package/src/control-plane/stream-client.ts +396 -0
- package/src/control-plane/stream-command-parser.ts +1673 -0
- package/src/control-plane/stream-protocol.ts +1808 -0
- package/src/control-plane/stream-server-background.ts +486 -0
- package/src/control-plane/stream-server-command.ts +2557 -0
- package/src/control-plane/stream-server-connection.ts +234 -0
- package/src/control-plane/stream-server-observed-filter.ts +112 -0
- package/src/control-plane/stream-server-session-runtime.ts +566 -0
- package/src/control-plane/stream-server-state-store.ts +15 -0
- package/src/control-plane/stream-server.ts +3192 -0
- package/src/cursor/managed-hooks.ts +282 -0
- package/src/domain/conversations.ts +414 -0
- package/src/domain/directories.ts +78 -0
- package/src/domain/repositories.ts +123 -0
- package/src/domain/tasks.ts +148 -0
- package/src/domain/workspace.ts +156 -0
- package/src/events/normalized-events.ts +124 -0
- package/src/mux/ansi-integrity.ts +103 -0
- package/src/mux/control-plane-op-queue.ts +212 -0
- package/src/mux/conversation-rail.ts +339 -0
- package/src/mux/double-click.ts +78 -0
- package/src/mux/dual-pane-core.ts +435 -0
- package/src/mux/harness-core-ui.ts +817 -0
- package/src/mux/input-shortcuts.ts +667 -0
- package/src/mux/live-mux/actions-conversation.ts +344 -0
- package/src/mux/live-mux/actions-repository.ts +246 -0
- package/src/mux/live-mux/actions-task.ts +115 -0
- package/src/mux/live-mux/args.ts +142 -0
- package/src/mux/live-mux/command-menu.ts +298 -0
- package/src/mux/live-mux/control-plane-records.ts +546 -0
- package/src/mux/live-mux/conversation-state.ts +188 -0
- package/src/mux/live-mux/directory-resolution.ts +34 -0
- package/src/mux/live-mux/event-mapping.ts +96 -0
- package/src/mux/live-mux/gateway-profiler.ts +152 -0
- package/src/mux/live-mux/gateway-render-trace.ts +177 -0
- package/src/mux/live-mux/gateway-status-timeline.ts +166 -0
- package/src/mux/live-mux/git-parsing.ts +131 -0
- package/src/mux/live-mux/git-snapshot.ts +263 -0
- package/src/mux/live-mux/git-state.ts +136 -0
- package/src/mux/live-mux/global-shortcut-handlers.ts +143 -0
- package/src/mux/live-mux/home-pane-actions.ts +58 -0
- package/src/mux/live-mux/home-pane-drop.ts +44 -0
- package/src/mux/live-mux/home-pane-entity-click.ts +96 -0
- package/src/mux/live-mux/home-pane-pointer.ts +96 -0
- package/src/mux/live-mux/input-forwarding.ts +112 -0
- package/src/mux/live-mux/layout.ts +30 -0
- package/src/mux/live-mux/left-nav-activation.ts +103 -0
- package/src/mux/live-mux/left-nav.ts +85 -0
- package/src/mux/live-mux/left-rail-actions.ts +118 -0
- package/src/mux/live-mux/left-rail-conversation-click.ts +82 -0
- package/src/mux/live-mux/left-rail-pointer.ts +74 -0
- package/src/mux/live-mux/modal-command-menu-handler.ts +101 -0
- package/src/mux/live-mux/modal-conversation-handlers.ts +217 -0
- package/src/mux/live-mux/modal-input-reducers.ts +94 -0
- package/src/mux/live-mux/modal-overlays.ts +287 -0
- package/src/mux/live-mux/modal-pointer.ts +70 -0
- package/src/mux/live-mux/modal-prompt-handlers.ts +187 -0
- package/src/mux/live-mux/modal-task-editor-handler.ts +156 -0
- package/src/mux/live-mux/observed-stream.ts +87 -0
- package/src/mux/live-mux/palette-parsing.ts +128 -0
- package/src/mux/live-mux/pointer-routing.ts +108 -0
- package/src/mux/live-mux/process-usage.ts +53 -0
- package/src/mux/live-mux/project-pane-pointer.ts +44 -0
- package/src/mux/live-mux/rail-layout.ts +244 -0
- package/src/mux/live-mux/render-trace-analysis.ts +213 -0
- package/src/mux/live-mux/render-trace-state.ts +84 -0
- package/src/mux/live-mux/repository-folding.ts +207 -0
- package/src/mux/live-mux/runtime-shutdown.ts +51 -0
- package/src/mux/live-mux/selection.ts +411 -0
- package/src/mux/live-mux/startup-utils.ts +187 -0
- package/src/mux/live-mux/status-timeline-state.ts +82 -0
- package/src/mux/live-mux/task-pane-shortcuts.ts +206 -0
- package/src/mux/live-mux/terminal-palette.ts +79 -0
- package/src/mux/new-thread-prompt.ts +165 -0
- package/src/mux/project-tree.ts +295 -0
- package/src/mux/render-frame.ts +113 -0
- package/src/mux/runtime-wiring.ts +185 -0
- package/src/mux/selector-index.ts +160 -0
- package/src/mux/startup-sequencer.ts +238 -0
- package/src/mux/task-composer.ts +289 -0
- package/src/mux/task-focused-pane.ts +417 -0
- package/src/mux/task-screen-keybindings.ts +539 -0
- package/src/mux/terminal-input-modes.ts +35 -0
- package/src/mux/workspace-path.ts +55 -0
- package/src/mux/workspace-rail-model.ts +701 -0
- package/src/mux/workspace-rail.ts +247 -0
- package/src/perf/perf-core.ts +307 -0
- package/src/pty/pty_host.ts +217 -0
- package/src/pty/session-broker.ts +158 -0
- package/src/recording/terminal-recording.ts +383 -0
- package/src/services/control-plane.ts +567 -0
- package/src/services/conversation-lifecycle.ts +176 -0
- package/src/services/conversation-startup-hydration.ts +47 -0
- package/src/services/directory-hydration.ts +49 -0
- package/src/services/event-persistence.ts +104 -0
- package/src/services/mux-ui-state-persistence.ts +82 -0
- package/src/services/output-load-sampler.ts +231 -0
- package/src/services/process-usage-refresh.ts +88 -0
- package/src/services/recording.ts +75 -0
- package/src/services/render-trace-recorder.ts +177 -0
- package/src/services/runtime-control-actions.ts +123 -0
- package/src/services/runtime-control-plane-ops.ts +131 -0
- package/src/services/runtime-conversation-actions.ts +113 -0
- package/src/services/runtime-conversation-activation.ts +78 -0
- package/src/services/runtime-conversation-starter.ts +171 -0
- package/src/services/runtime-conversation-title-edit.ts +149 -0
- package/src/services/runtime-directory-actions.ts +164 -0
- package/src/services/runtime-envelope-handler.ts +198 -0
- package/src/services/runtime-git-state.ts +92 -0
- package/src/services/runtime-input-pipeline.ts +50 -0
- package/src/services/runtime-input-router.ts +202 -0
- package/src/services/runtime-layout-resize.ts +236 -0
- package/src/services/runtime-left-rail-render.ts +159 -0
- package/src/services/runtime-main-pane-input.ts +230 -0
- package/src/services/runtime-modal-input.ts +119 -0
- package/src/services/runtime-navigation-input.ts +207 -0
- package/src/services/runtime-process-wiring.ts +68 -0
- package/src/services/runtime-rail-input.ts +287 -0
- package/src/services/runtime-render-flush.ts +146 -0
- package/src/services/runtime-render-lifecycle.ts +104 -0
- package/src/services/runtime-render-orchestrator.ts +108 -0
- package/src/services/runtime-render-pipeline.ts +167 -0
- package/src/services/runtime-render-state.ts +72 -0
- package/src/services/runtime-repository-actions.ts +197 -0
- package/src/services/runtime-right-pane-render.ts +132 -0
- package/src/services/runtime-shutdown.ts +79 -0
- package/src/services/runtime-stream-subscriptions.ts +56 -0
- package/src/services/runtime-task-composer-persistence.ts +139 -0
- package/src/services/runtime-task-editor-actions.ts +83 -0
- package/src/services/runtime-task-pane-actions.ts +198 -0
- package/src/services/runtime-task-pane-shortcuts.ts +189 -0
- package/src/services/runtime-task-pane.ts +62 -0
- package/src/services/runtime-workspace-actions.ts +153 -0
- package/src/services/runtime-workspace-observed-events.ts +190 -0
- package/src/services/session-projection-instrumentation.ts +190 -0
- package/src/services/startup-background-probe.ts +91 -0
- package/src/services/startup-background-resume.ts +65 -0
- package/src/services/startup-orchestrator.ts +166 -0
- package/src/services/startup-output-tracker.ts +54 -0
- package/src/services/startup-paint-tracker.ts +115 -0
- package/src/services/startup-persisted-conversation-queue.ts +45 -0
- package/src/services/startup-settled-gate.ts +67 -0
- package/src/services/startup-shutdown.ts +53 -0
- package/src/services/startup-span-tracker.ts +77 -0
- package/src/services/startup-state-hydration.ts +94 -0
- package/src/services/startup-visibility.ts +35 -0
- package/src/services/status-timeline-recorder.ts +144 -0
- package/src/services/task-pane-selection-actions.ts +153 -0
- package/src/services/task-planning-hydration.ts +58 -0
- package/src/services/task-planning-observed-events.ts +89 -0
- package/src/services/workspace-observed-events.ts +113 -0
- package/src/store/control-plane-store-normalize.ts +760 -0
- package/src/store/control-plane-store-types.ts +224 -0
- package/src/store/control-plane-store.ts +2951 -0
- package/src/store/event-store.ts +253 -0
- package/src/store/sqlite.ts +81 -0
- package/src/terminal/compat-matrix.ts +345 -0
- package/src/terminal/differential-checkpoints.ts +132 -0
- package/src/terminal/parity-suite.ts +441 -0
- package/src/terminal/snapshot-oracle.ts +1840 -0
- package/src/ui/conversation-input-forwarder.ts +114 -0
- package/src/ui/conversation-selection-input.ts +103 -0
- package/src/ui/debug-footer-notice.ts +39 -0
- package/src/ui/global-shortcut-input.ts +126 -0
- package/src/ui/input-preflight.ts +68 -0
- package/src/ui/input-token-router.ts +312 -0
- package/src/ui/input.ts +238 -0
- package/src/ui/kit.ts +509 -0
- package/src/ui/left-nav-input.ts +80 -0
- package/src/ui/left-rail-pointer-input.ts +148 -0
- package/src/ui/main-pane-pointer-input.ts +150 -0
- package/src/ui/modals/manager.ts +192 -0
- package/src/ui/mux-theme.ts +529 -0
- package/src/ui/panes/conversation.ts +19 -0
- package/src/ui/panes/home-gridfire.ts +302 -0
- package/src/ui/panes/home.ts +109 -0
- package/src/ui/panes/left-rail.ts +12 -0
- package/src/ui/panes/project.ts +44 -0
- package/src/ui/pointer-routing-input.ts +158 -0
- package/src/ui/repository-fold-input.ts +91 -0
- package/src/ui/screen.ts +210 -0
- package/src/ui/surface.ts +224 -0
|
@@ -0,0 +1,2441 @@
|
|
|
1
|
+
import type { PtyExit } from '../pty/pty_host.ts';
|
|
2
|
+
import { connectControlPlaneStreamClient, type ControlPlaneStreamClient } from './stream-client.ts';
|
|
3
|
+
import { parseSessionSummaryList, parseSessionSummaryRecord } from './session-summary.ts';
|
|
4
|
+
import {
|
|
5
|
+
type StreamCommand,
|
|
6
|
+
type StreamObservedEvent,
|
|
7
|
+
type StreamSessionController,
|
|
8
|
+
type StreamSessionControllerType,
|
|
9
|
+
type StreamSessionRuntimeStatus,
|
|
10
|
+
type StreamSessionStatusModel,
|
|
11
|
+
type StreamSignal,
|
|
12
|
+
} from './stream-protocol.ts';
|
|
13
|
+
|
|
14
|
+
export interface AgentRealtimeSubscriptionFilter {
|
|
15
|
+
tenantId?: string;
|
|
16
|
+
userId?: string;
|
|
17
|
+
workspaceId?: string;
|
|
18
|
+
repositoryId?: string;
|
|
19
|
+
taskId?: string;
|
|
20
|
+
directoryId?: string;
|
|
21
|
+
conversationId?: string;
|
|
22
|
+
includeOutput?: boolean;
|
|
23
|
+
afterCursor?: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface AgentRealtimeConnectOptions {
|
|
27
|
+
host: string;
|
|
28
|
+
port: number;
|
|
29
|
+
authToken?: string;
|
|
30
|
+
connectRetryWindowMs?: number;
|
|
31
|
+
connectRetryDelayMs?: number;
|
|
32
|
+
subscription?: AgentRealtimeSubscriptionFilter;
|
|
33
|
+
onHandlerError?: (error: unknown, event: AgentRealtimeEventEnvelope) => void;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
interface AgentEventTypeMap {
|
|
37
|
+
'directory.upserted': Extract<StreamObservedEvent, { type: 'directory-upserted' }>;
|
|
38
|
+
'directory.archived': Extract<StreamObservedEvent, { type: 'directory-archived' }>;
|
|
39
|
+
'directory.git-updated': Extract<StreamObservedEvent, { type: 'directory-git-updated' }>;
|
|
40
|
+
'conversation.created': Extract<StreamObservedEvent, { type: 'conversation-created' }>;
|
|
41
|
+
'conversation.updated': Extract<StreamObservedEvent, { type: 'conversation-updated' }>;
|
|
42
|
+
'conversation.archived': Extract<StreamObservedEvent, { type: 'conversation-archived' }>;
|
|
43
|
+
'conversation.deleted': Extract<StreamObservedEvent, { type: 'conversation-deleted' }>;
|
|
44
|
+
'repository.upserted': Extract<StreamObservedEvent, { type: 'repository-upserted' }>;
|
|
45
|
+
'repository.updated': Extract<StreamObservedEvent, { type: 'repository-updated' }>;
|
|
46
|
+
'repository.archived': Extract<StreamObservedEvent, { type: 'repository-archived' }>;
|
|
47
|
+
'task.created': Extract<StreamObservedEvent, { type: 'task-created' }>;
|
|
48
|
+
'task.updated': Extract<StreamObservedEvent, { type: 'task-updated' }>;
|
|
49
|
+
'task.deleted': Extract<StreamObservedEvent, { type: 'task-deleted' }>;
|
|
50
|
+
'task.reordered': Extract<StreamObservedEvent, { type: 'task-reordered' }>;
|
|
51
|
+
'github.pr-upserted': Extract<StreamObservedEvent, { type: 'github-pr-upserted' }>;
|
|
52
|
+
'github.pr-closed': Extract<StreamObservedEvent, { type: 'github-pr-closed' }>;
|
|
53
|
+
'github.pr-jobs-updated': Extract<StreamObservedEvent, { type: 'github-pr-jobs-updated' }>;
|
|
54
|
+
'session.status': Extract<StreamObservedEvent, { type: 'session-status' }>;
|
|
55
|
+
'session.event': Extract<StreamObservedEvent, { type: 'session-event' }>;
|
|
56
|
+
'session.telemetry': Extract<StreamObservedEvent, { type: 'session-key-event' }>;
|
|
57
|
+
'session.control': Extract<StreamObservedEvent, { type: 'session-control' }>;
|
|
58
|
+
'session.output': Extract<StreamObservedEvent, { type: 'session-output' }>;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export type AgentRealtimeEventType = keyof AgentEventTypeMap;
|
|
62
|
+
|
|
63
|
+
export interface AgentRealtimeEventEnvelope<
|
|
64
|
+
TEventType extends AgentRealtimeEventType = AgentRealtimeEventType,
|
|
65
|
+
> {
|
|
66
|
+
readonly type: TEventType;
|
|
67
|
+
readonly subscriptionId?: string;
|
|
68
|
+
readonly cursor: number;
|
|
69
|
+
readonly observed: AgentEventTypeMap[TEventType];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
type AgentRealtimeListener<TEventType extends AgentRealtimeEventType> = (
|
|
73
|
+
event: AgentRealtimeEventEnvelope<TEventType>,
|
|
74
|
+
) => void | Promise<void>;
|
|
75
|
+
|
|
76
|
+
type AnyRealtimeListener = (event: AgentRealtimeEventEnvelope) => void | Promise<void>;
|
|
77
|
+
|
|
78
|
+
export interface AgentClaimSessionInput {
|
|
79
|
+
sessionId: string;
|
|
80
|
+
controllerId: string;
|
|
81
|
+
controllerType: StreamSessionControllerType;
|
|
82
|
+
controllerLabel?: string;
|
|
83
|
+
reason?: string;
|
|
84
|
+
takeover?: boolean;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export interface AgentReleaseSessionInput {
|
|
88
|
+
sessionId: string;
|
|
89
|
+
reason?: string;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export interface AgentSessionClaimResult {
|
|
93
|
+
sessionId: string;
|
|
94
|
+
action: 'claimed' | 'taken-over';
|
|
95
|
+
controller: StreamSessionController;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export interface AgentSessionReleaseResult {
|
|
99
|
+
sessionId: string;
|
|
100
|
+
released: boolean;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export type AgentSessionSummary = NonNullable<ReturnType<typeof parseSessionSummaryRecord>>;
|
|
104
|
+
|
|
105
|
+
export interface AgentScopeQuery {
|
|
106
|
+
tenantId?: string;
|
|
107
|
+
userId?: string;
|
|
108
|
+
workspaceId?: string;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export interface AgentProject {
|
|
112
|
+
projectId: string;
|
|
113
|
+
tenantId: string;
|
|
114
|
+
userId: string;
|
|
115
|
+
workspaceId: string;
|
|
116
|
+
path: string;
|
|
117
|
+
createdAt: string;
|
|
118
|
+
archivedAt: string | null;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export interface AgentProjectUpsertInput extends AgentScopeQuery {
|
|
122
|
+
projectId?: string;
|
|
123
|
+
path: string;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export interface AgentProjectListQuery extends AgentScopeQuery {
|
|
127
|
+
includeArchived?: boolean;
|
|
128
|
+
limit?: number;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
interface AgentProjectGitStatusListQuery extends AgentScopeQuery {
|
|
132
|
+
projectId?: string;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
interface AgentProjectGitStatus {
|
|
136
|
+
directoryId: string;
|
|
137
|
+
summary: {
|
|
138
|
+
branch: string;
|
|
139
|
+
changedFiles: number;
|
|
140
|
+
additions: number;
|
|
141
|
+
deletions: number;
|
|
142
|
+
};
|
|
143
|
+
repositorySnapshot: {
|
|
144
|
+
normalizedRemoteUrl: string | null;
|
|
145
|
+
commitCount: number | null;
|
|
146
|
+
lastCommitAt: string | null;
|
|
147
|
+
shortCommitHash: string | null;
|
|
148
|
+
inferredName: string | null;
|
|
149
|
+
defaultBranch: string | null;
|
|
150
|
+
};
|
|
151
|
+
repositoryId: string | null;
|
|
152
|
+
repository: AgentRepository | null;
|
|
153
|
+
observedAt: string;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export interface AgentThread {
|
|
157
|
+
threadId: string;
|
|
158
|
+
projectId: string;
|
|
159
|
+
tenantId: string;
|
|
160
|
+
userId: string;
|
|
161
|
+
workspaceId: string;
|
|
162
|
+
title: string;
|
|
163
|
+
agentType: string;
|
|
164
|
+
createdAt: string;
|
|
165
|
+
archivedAt: string | null;
|
|
166
|
+
runtimeStatus: StreamSessionRuntimeStatus;
|
|
167
|
+
runtimeStatusModel: StreamSessionStatusModel | null;
|
|
168
|
+
runtimeLive: boolean;
|
|
169
|
+
runtimeAttentionReason: string | null;
|
|
170
|
+
runtimeProcessId: number | null;
|
|
171
|
+
runtimeLastEventAt: string | null;
|
|
172
|
+
runtimeLastExit: PtyExit | null;
|
|
173
|
+
adapterState: Record<string, unknown>;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export interface AgentThreadCreateInput {
|
|
177
|
+
threadId?: string;
|
|
178
|
+
projectId: string;
|
|
179
|
+
title: string;
|
|
180
|
+
agentType: string;
|
|
181
|
+
adapterState?: Record<string, unknown>;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export interface AgentThreadListQuery extends AgentScopeQuery {
|
|
185
|
+
projectId?: string;
|
|
186
|
+
includeArchived?: boolean;
|
|
187
|
+
limit?: number;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export interface AgentThreadUpdateInput {
|
|
191
|
+
title: string;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export interface AgentRepository {
|
|
195
|
+
repositoryId: string;
|
|
196
|
+
tenantId: string;
|
|
197
|
+
userId: string;
|
|
198
|
+
workspaceId: string;
|
|
199
|
+
name: string;
|
|
200
|
+
remoteUrl: string;
|
|
201
|
+
defaultBranch: string;
|
|
202
|
+
metadata: Record<string, unknown>;
|
|
203
|
+
createdAt: string;
|
|
204
|
+
archivedAt: string | null;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
export interface AgentRepositoryUpsertInput extends AgentScopeQuery {
|
|
208
|
+
repositoryId?: string;
|
|
209
|
+
name: string;
|
|
210
|
+
remoteUrl: string;
|
|
211
|
+
defaultBranch?: string;
|
|
212
|
+
metadata?: Record<string, unknown>;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export interface AgentRepositoryListQuery extends AgentScopeQuery {
|
|
216
|
+
includeArchived?: boolean;
|
|
217
|
+
limit?: number;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export interface AgentRepositoryUpdateInput {
|
|
221
|
+
name?: string;
|
|
222
|
+
remoteUrl?: string;
|
|
223
|
+
defaultBranch?: string;
|
|
224
|
+
metadata?: Record<string, unknown>;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export type AgentTaskStatus = 'draft' | 'ready' | 'in-progress' | 'completed';
|
|
228
|
+
export type AgentTaskLinearPriority = 0 | 1 | 2 | 3 | 4;
|
|
229
|
+
|
|
230
|
+
export interface AgentTaskLinearRecord {
|
|
231
|
+
issueId: string | null;
|
|
232
|
+
identifier: string | null;
|
|
233
|
+
url: string | null;
|
|
234
|
+
teamId: string | null;
|
|
235
|
+
projectId: string | null;
|
|
236
|
+
projectMilestoneId: string | null;
|
|
237
|
+
cycleId: string | null;
|
|
238
|
+
stateId: string | null;
|
|
239
|
+
assigneeId: string | null;
|
|
240
|
+
priority: AgentTaskLinearPriority | null;
|
|
241
|
+
estimate: number | null;
|
|
242
|
+
dueDate: string | null;
|
|
243
|
+
labelIds: readonly string[];
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
export interface AgentTaskLinearInput {
|
|
247
|
+
issueId?: string | null;
|
|
248
|
+
identifier?: string | null;
|
|
249
|
+
url?: string | null;
|
|
250
|
+
teamId?: string | null;
|
|
251
|
+
projectId?: string | null;
|
|
252
|
+
projectMilestoneId?: string | null;
|
|
253
|
+
cycleId?: string | null;
|
|
254
|
+
stateId?: string | null;
|
|
255
|
+
assigneeId?: string | null;
|
|
256
|
+
priority?: AgentTaskLinearPriority | null;
|
|
257
|
+
estimate?: number | null;
|
|
258
|
+
dueDate?: string | null;
|
|
259
|
+
labelIds?: readonly string[] | null;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
export interface AgentTask {
|
|
263
|
+
taskId: string;
|
|
264
|
+
tenantId: string;
|
|
265
|
+
userId: string;
|
|
266
|
+
workspaceId: string;
|
|
267
|
+
repositoryId: string | null;
|
|
268
|
+
scopeKind: 'global' | 'repository' | 'project';
|
|
269
|
+
projectId: string | null;
|
|
270
|
+
title: string;
|
|
271
|
+
description: string;
|
|
272
|
+
status: AgentTaskStatus;
|
|
273
|
+
orderIndex: number;
|
|
274
|
+
claimedByControllerId: string | null;
|
|
275
|
+
claimedByProjectId: string | null;
|
|
276
|
+
branchName: string | null;
|
|
277
|
+
baseBranch: string | null;
|
|
278
|
+
claimedAt: string | null;
|
|
279
|
+
completedAt: string | null;
|
|
280
|
+
linear: AgentTaskLinearRecord;
|
|
281
|
+
createdAt: string;
|
|
282
|
+
updatedAt: string;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
export interface AgentTaskCreateInput extends AgentScopeQuery {
|
|
286
|
+
taskId?: string;
|
|
287
|
+
repositoryId?: string;
|
|
288
|
+
projectId?: string;
|
|
289
|
+
title: string;
|
|
290
|
+
description?: string;
|
|
291
|
+
linear?: AgentTaskLinearInput;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
export interface AgentTaskListQuery extends AgentScopeQuery {
|
|
295
|
+
repositoryId?: string;
|
|
296
|
+
projectId?: string;
|
|
297
|
+
scopeKind?: 'global' | 'repository' | 'project';
|
|
298
|
+
status?: AgentTaskStatus;
|
|
299
|
+
limit?: number;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
export interface AgentTaskUpdateInput {
|
|
303
|
+
title?: string;
|
|
304
|
+
description?: string;
|
|
305
|
+
repositoryId?: string | null;
|
|
306
|
+
projectId?: string | null;
|
|
307
|
+
linear?: AgentTaskLinearInput | null;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
export interface AgentTaskClaimInput {
|
|
311
|
+
taskId: string;
|
|
312
|
+
controllerId: string;
|
|
313
|
+
projectId?: string;
|
|
314
|
+
branchName?: string;
|
|
315
|
+
baseBranch?: string;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
export interface AgentTaskPullInput extends AgentScopeQuery {
|
|
319
|
+
controllerId: string;
|
|
320
|
+
projectId?: string;
|
|
321
|
+
repositoryId?: string;
|
|
322
|
+
branchName?: string;
|
|
323
|
+
baseBranch?: string;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
export interface AgentProjectSettings {
|
|
327
|
+
directoryId: string;
|
|
328
|
+
tenantId: string;
|
|
329
|
+
userId: string;
|
|
330
|
+
workspaceId: string;
|
|
331
|
+
pinnedBranch: string | null;
|
|
332
|
+
taskFocusMode: 'balanced' | 'own-only';
|
|
333
|
+
threadSpawnMode: 'new-thread' | 'reuse-thread';
|
|
334
|
+
createdAt: string;
|
|
335
|
+
updatedAt: string;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
export interface AgentProjectSettingsUpdateInput {
|
|
339
|
+
pinnedBranch?: string | null;
|
|
340
|
+
taskFocusMode?: 'balanced' | 'own-only';
|
|
341
|
+
threadSpawnMode?: 'new-thread' | 'reuse-thread';
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
export interface AgentAutomationPolicy {
|
|
345
|
+
policyId: string;
|
|
346
|
+
tenantId: string;
|
|
347
|
+
userId: string;
|
|
348
|
+
workspaceId: string;
|
|
349
|
+
scope: 'global' | 'repository' | 'project';
|
|
350
|
+
scopeId: string | null;
|
|
351
|
+
automationEnabled: boolean;
|
|
352
|
+
frozen: boolean;
|
|
353
|
+
createdAt: string;
|
|
354
|
+
updatedAt: string;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
export interface AgentTaskReorderInput {
|
|
358
|
+
tenantId: string;
|
|
359
|
+
userId: string;
|
|
360
|
+
workspaceId: string;
|
|
361
|
+
orderedTaskIds: readonly string[];
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
export interface AgentRealtimeSubscription {
|
|
365
|
+
readonly subscriptionId: string;
|
|
366
|
+
readonly cursor: number;
|
|
367
|
+
unsubscribe(): Promise<{ unsubscribed: boolean }>;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
function asRecord(value: unknown): Record<string, unknown> | null {
|
|
371
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
372
|
+
return null;
|
|
373
|
+
}
|
|
374
|
+
return value as Record<string, unknown>;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function readString(value: unknown): string | null {
|
|
378
|
+
return typeof value === 'string' ? value : null;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
function readNumber(value: unknown): number | null {
|
|
382
|
+
return typeof value === 'number' && Number.isFinite(value) ? value : null;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
function readBoolean(value: unknown): boolean | null {
|
|
386
|
+
return typeof value === 'boolean' ? value : null;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
function readNullableString(value: unknown): string | null | undefined {
|
|
390
|
+
if (value === undefined) {
|
|
391
|
+
return undefined;
|
|
392
|
+
}
|
|
393
|
+
if (value === null) {
|
|
394
|
+
return null;
|
|
395
|
+
}
|
|
396
|
+
if (typeof value === 'string') {
|
|
397
|
+
return value;
|
|
398
|
+
}
|
|
399
|
+
return undefined;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
function readNullableNumber(value: unknown): number | null | undefined {
|
|
403
|
+
if (value === undefined) {
|
|
404
|
+
return undefined;
|
|
405
|
+
}
|
|
406
|
+
if (value === null) {
|
|
407
|
+
return null;
|
|
408
|
+
}
|
|
409
|
+
if (typeof value === 'number' && Number.isFinite(value)) {
|
|
410
|
+
return value;
|
|
411
|
+
}
|
|
412
|
+
return undefined;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
function readStringArray(value: unknown): readonly string[] | undefined {
|
|
416
|
+
if (!Array.isArray(value)) {
|
|
417
|
+
return undefined;
|
|
418
|
+
}
|
|
419
|
+
if (!value.every((entry) => typeof entry === 'string')) {
|
|
420
|
+
return undefined;
|
|
421
|
+
}
|
|
422
|
+
return [...value];
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
function defaultTaskLinearRecord(): AgentTaskLinearRecord {
|
|
426
|
+
return {
|
|
427
|
+
issueId: null,
|
|
428
|
+
identifier: null,
|
|
429
|
+
url: null,
|
|
430
|
+
teamId: null,
|
|
431
|
+
projectId: null,
|
|
432
|
+
projectMilestoneId: null,
|
|
433
|
+
cycleId: null,
|
|
434
|
+
stateId: null,
|
|
435
|
+
assigneeId: null,
|
|
436
|
+
priority: null,
|
|
437
|
+
estimate: null,
|
|
438
|
+
dueDate: null,
|
|
439
|
+
labelIds: [],
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
function parseTaskLinearRecord(value: unknown): AgentTaskLinearRecord | null {
|
|
444
|
+
if (value === undefined) {
|
|
445
|
+
return defaultTaskLinearRecord();
|
|
446
|
+
}
|
|
447
|
+
const record = asRecord(value);
|
|
448
|
+
if (record === null) {
|
|
449
|
+
return null;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
const issueId = readNullableString(record['issueId']);
|
|
453
|
+
const identifier = readNullableString(record['identifier']);
|
|
454
|
+
const url = readNullableString(record['url']);
|
|
455
|
+
const teamId = readNullableString(record['teamId']);
|
|
456
|
+
const projectId = readNullableString(record['projectId']);
|
|
457
|
+
const projectMilestoneId = readNullableString(record['projectMilestoneId']);
|
|
458
|
+
const cycleId = readNullableString(record['cycleId']);
|
|
459
|
+
const stateId = readNullableString(record['stateId']);
|
|
460
|
+
const assigneeId = readNullableString(record['assigneeId']);
|
|
461
|
+
const priority = readNullableNumber(record['priority']);
|
|
462
|
+
const estimate = readNullableNumber(record['estimate']);
|
|
463
|
+
const dueDate = readNullableString(record['dueDate']);
|
|
464
|
+
const labelIdsRaw = record['labelIds'];
|
|
465
|
+
const labelIds = labelIdsRaw === undefined ? [] : readStringArray(labelIdsRaw);
|
|
466
|
+
if (
|
|
467
|
+
issueId === undefined ||
|
|
468
|
+
identifier === undefined ||
|
|
469
|
+
url === undefined ||
|
|
470
|
+
teamId === undefined ||
|
|
471
|
+
projectId === undefined ||
|
|
472
|
+
projectMilestoneId === undefined ||
|
|
473
|
+
cycleId === undefined ||
|
|
474
|
+
stateId === undefined ||
|
|
475
|
+
assigneeId === undefined ||
|
|
476
|
+
priority === undefined ||
|
|
477
|
+
estimate === undefined ||
|
|
478
|
+
dueDate === undefined ||
|
|
479
|
+
labelIds === undefined
|
|
480
|
+
) {
|
|
481
|
+
return null;
|
|
482
|
+
}
|
|
483
|
+
if (priority !== null && !Number.isInteger(priority)) {
|
|
484
|
+
return null;
|
|
485
|
+
}
|
|
486
|
+
if (priority !== null && (priority < 0 || priority > 4)) {
|
|
487
|
+
return null;
|
|
488
|
+
}
|
|
489
|
+
if (estimate !== null && (!Number.isInteger(estimate) || estimate < 0)) {
|
|
490
|
+
return null;
|
|
491
|
+
}
|
|
492
|
+
return {
|
|
493
|
+
issueId,
|
|
494
|
+
identifier,
|
|
495
|
+
url,
|
|
496
|
+
teamId,
|
|
497
|
+
projectId,
|
|
498
|
+
projectMilestoneId,
|
|
499
|
+
cycleId,
|
|
500
|
+
stateId,
|
|
501
|
+
assigneeId,
|
|
502
|
+
priority: priority as AgentTaskLinearPriority | null,
|
|
503
|
+
estimate,
|
|
504
|
+
dueDate,
|
|
505
|
+
labelIds,
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
function parseTaskStatus(value: unknown): AgentTaskStatus | null {
|
|
510
|
+
if (value === 'draft' || value === 'ready' || value === 'in-progress' || value === 'completed') {
|
|
511
|
+
return value;
|
|
512
|
+
}
|
|
513
|
+
return null;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
function parseTaskScopeKind(
|
|
517
|
+
value: unknown,
|
|
518
|
+
repositoryId: string | null | undefined,
|
|
519
|
+
projectId: string | null | undefined,
|
|
520
|
+
): 'global' | 'repository' | 'project' | null {
|
|
521
|
+
if (value === 'global' || value === 'repository' || value === 'project') {
|
|
522
|
+
return value;
|
|
523
|
+
}
|
|
524
|
+
if (projectId !== null) {
|
|
525
|
+
return 'project';
|
|
526
|
+
}
|
|
527
|
+
if (repositoryId !== null) {
|
|
528
|
+
return 'repository';
|
|
529
|
+
}
|
|
530
|
+
if (value === undefined || value === null) {
|
|
531
|
+
return 'global';
|
|
532
|
+
}
|
|
533
|
+
return null;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
function parseRuntimeStatus(value: unknown): StreamSessionRuntimeStatus | null {
|
|
537
|
+
if (
|
|
538
|
+
value === 'running' ||
|
|
539
|
+
value === 'needs-input' ||
|
|
540
|
+
value === 'completed' ||
|
|
541
|
+
value === 'exited'
|
|
542
|
+
) {
|
|
543
|
+
return value;
|
|
544
|
+
}
|
|
545
|
+
return null;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
function parseRuntimeStatusModel(value: unknown): StreamSessionStatusModel | null | undefined {
|
|
549
|
+
if (value === null) {
|
|
550
|
+
return null;
|
|
551
|
+
}
|
|
552
|
+
const record = asRecord(value);
|
|
553
|
+
if (record === null) {
|
|
554
|
+
return undefined;
|
|
555
|
+
}
|
|
556
|
+
const runtimeStatus = parseRuntimeStatus(record['runtimeStatus']);
|
|
557
|
+
const phase = readString(record['phase']);
|
|
558
|
+
const glyph = readString(record['glyph']);
|
|
559
|
+
const badge = readString(record['badge']);
|
|
560
|
+
const detailText = readString(record['detailText']);
|
|
561
|
+
const attentionReason = readNullableString(record['attentionReason']);
|
|
562
|
+
const lastKnownWork = readNullableString(record['lastKnownWork']);
|
|
563
|
+
const lastKnownWorkAt = readNullableString(record['lastKnownWorkAt']);
|
|
564
|
+
const phaseHint = readNullableString(record['phaseHint']);
|
|
565
|
+
const observedAt = readString(record['observedAt']);
|
|
566
|
+
if (
|
|
567
|
+
runtimeStatus === null ||
|
|
568
|
+
phase === null ||
|
|
569
|
+
(phase !== 'needs-action' &&
|
|
570
|
+
phase !== 'starting' &&
|
|
571
|
+
phase !== 'working' &&
|
|
572
|
+
phase !== 'idle' &&
|
|
573
|
+
phase !== 'exited') ||
|
|
574
|
+
glyph === null ||
|
|
575
|
+
(glyph !== '▲' && glyph !== '◔' && glyph !== '◆' && glyph !== '○' && glyph !== '■') ||
|
|
576
|
+
badge === null ||
|
|
577
|
+
(badge !== 'NEED' && badge !== 'RUN ' && badge !== 'DONE' && badge !== 'EXIT') ||
|
|
578
|
+
detailText === null ||
|
|
579
|
+
attentionReason === undefined ||
|
|
580
|
+
lastKnownWork === undefined ||
|
|
581
|
+
lastKnownWorkAt === undefined ||
|
|
582
|
+
phaseHint === undefined ||
|
|
583
|
+
(phaseHint !== null &&
|
|
584
|
+
phaseHint !== 'needs-action' &&
|
|
585
|
+
phaseHint !== 'working' &&
|
|
586
|
+
phaseHint !== 'idle') ||
|
|
587
|
+
observedAt === null
|
|
588
|
+
) {
|
|
589
|
+
return undefined;
|
|
590
|
+
}
|
|
591
|
+
return {
|
|
592
|
+
runtimeStatus,
|
|
593
|
+
phase,
|
|
594
|
+
glyph,
|
|
595
|
+
badge,
|
|
596
|
+
detailText,
|
|
597
|
+
attentionReason,
|
|
598
|
+
lastKnownWork,
|
|
599
|
+
lastKnownWorkAt,
|
|
600
|
+
phaseHint,
|
|
601
|
+
observedAt,
|
|
602
|
+
};
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
function parseSignal(value: unknown): NodeJS.Signals | null | undefined {
|
|
606
|
+
if (value === undefined) {
|
|
607
|
+
return undefined;
|
|
608
|
+
}
|
|
609
|
+
if (value === null) {
|
|
610
|
+
return null;
|
|
611
|
+
}
|
|
612
|
+
if (typeof value !== 'string') {
|
|
613
|
+
return undefined;
|
|
614
|
+
}
|
|
615
|
+
if (!/^SIG[A-Z0-9]+(?:_[A-Z0-9]+)*$/.test(value)) {
|
|
616
|
+
return undefined;
|
|
617
|
+
}
|
|
618
|
+
return value as NodeJS.Signals;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
function parseExit(value: unknown): PtyExit | null | undefined {
|
|
622
|
+
if (value === undefined) {
|
|
623
|
+
return undefined;
|
|
624
|
+
}
|
|
625
|
+
if (value === null) {
|
|
626
|
+
return null;
|
|
627
|
+
}
|
|
628
|
+
const record = asRecord(value);
|
|
629
|
+
if (record === null) {
|
|
630
|
+
return undefined;
|
|
631
|
+
}
|
|
632
|
+
const code = readNullableNumber(record['code']);
|
|
633
|
+
const signal = parseSignal(record['signal']);
|
|
634
|
+
if (code === undefined || signal === undefined) {
|
|
635
|
+
return undefined;
|
|
636
|
+
}
|
|
637
|
+
return {
|
|
638
|
+
code,
|
|
639
|
+
signal,
|
|
640
|
+
};
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
function parseProjectRecord(value: unknown): AgentProject | null {
|
|
644
|
+
const record = asRecord(value);
|
|
645
|
+
if (record === null) {
|
|
646
|
+
return null;
|
|
647
|
+
}
|
|
648
|
+
const projectId = readString(record['directoryId']);
|
|
649
|
+
const tenantId = readString(record['tenantId']);
|
|
650
|
+
const userId = readString(record['userId']);
|
|
651
|
+
const workspaceId = readString(record['workspaceId']);
|
|
652
|
+
const path = readString(record['path']);
|
|
653
|
+
const createdAt = readString(record['createdAt']);
|
|
654
|
+
const archivedAt = readNullableString(record['archivedAt']);
|
|
655
|
+
if (
|
|
656
|
+
projectId === null ||
|
|
657
|
+
tenantId === null ||
|
|
658
|
+
userId === null ||
|
|
659
|
+
workspaceId === null ||
|
|
660
|
+
path === null ||
|
|
661
|
+
createdAt === null ||
|
|
662
|
+
archivedAt === undefined
|
|
663
|
+
) {
|
|
664
|
+
return null;
|
|
665
|
+
}
|
|
666
|
+
return {
|
|
667
|
+
projectId,
|
|
668
|
+
tenantId,
|
|
669
|
+
userId,
|
|
670
|
+
workspaceId,
|
|
671
|
+
path,
|
|
672
|
+
createdAt,
|
|
673
|
+
archivedAt,
|
|
674
|
+
};
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
function parseProjectSettingsRecord(value: unknown): AgentProjectSettings | null {
|
|
678
|
+
const record = asRecord(value);
|
|
679
|
+
if (record === null) {
|
|
680
|
+
return null;
|
|
681
|
+
}
|
|
682
|
+
const directoryId = readString(record['directoryId']);
|
|
683
|
+
const tenantId = readString(record['tenantId']);
|
|
684
|
+
const userId = readString(record['userId']);
|
|
685
|
+
const workspaceId = readString(record['workspaceId']);
|
|
686
|
+
const pinnedBranch = readNullableString(record['pinnedBranch']);
|
|
687
|
+
const taskFocusMode = record['taskFocusMode'];
|
|
688
|
+
const threadSpawnMode = record['threadSpawnMode'];
|
|
689
|
+
const createdAt = readString(record['createdAt']);
|
|
690
|
+
const updatedAt = readString(record['updatedAt']);
|
|
691
|
+
if (
|
|
692
|
+
directoryId === null ||
|
|
693
|
+
tenantId === null ||
|
|
694
|
+
userId === null ||
|
|
695
|
+
workspaceId === null ||
|
|
696
|
+
pinnedBranch === undefined ||
|
|
697
|
+
(taskFocusMode !== 'balanced' && taskFocusMode !== 'own-only') ||
|
|
698
|
+
(threadSpawnMode !== 'new-thread' && threadSpawnMode !== 'reuse-thread') ||
|
|
699
|
+
createdAt === null ||
|
|
700
|
+
updatedAt === null
|
|
701
|
+
) {
|
|
702
|
+
return null;
|
|
703
|
+
}
|
|
704
|
+
return {
|
|
705
|
+
directoryId,
|
|
706
|
+
tenantId,
|
|
707
|
+
userId,
|
|
708
|
+
workspaceId,
|
|
709
|
+
pinnedBranch,
|
|
710
|
+
taskFocusMode,
|
|
711
|
+
threadSpawnMode,
|
|
712
|
+
createdAt,
|
|
713
|
+
updatedAt,
|
|
714
|
+
};
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
function parseAutomationPolicyRecord(value: unknown): AgentAutomationPolicy | null {
|
|
718
|
+
const record = asRecord(value);
|
|
719
|
+
if (record === null) {
|
|
720
|
+
return null;
|
|
721
|
+
}
|
|
722
|
+
const policyId = readString(record['policyId']);
|
|
723
|
+
const tenantId = readString(record['tenantId']);
|
|
724
|
+
const userId = readString(record['userId']);
|
|
725
|
+
const workspaceId = readString(record['workspaceId']);
|
|
726
|
+
const scope = record['scope'];
|
|
727
|
+
const scopeId = readNullableString(record['scopeId']);
|
|
728
|
+
const automationEnabled = readBoolean(record['automationEnabled']);
|
|
729
|
+
const frozen = readBoolean(record['frozen']);
|
|
730
|
+
const createdAt = readString(record['createdAt']);
|
|
731
|
+
const updatedAt = readString(record['updatedAt']);
|
|
732
|
+
if (
|
|
733
|
+
policyId === null ||
|
|
734
|
+
tenantId === null ||
|
|
735
|
+
userId === null ||
|
|
736
|
+
workspaceId === null ||
|
|
737
|
+
(scope !== 'global' && scope !== 'repository' && scope !== 'project') ||
|
|
738
|
+
scopeId === undefined ||
|
|
739
|
+
automationEnabled === null ||
|
|
740
|
+
frozen === null ||
|
|
741
|
+
createdAt === null ||
|
|
742
|
+
updatedAt === null
|
|
743
|
+
) {
|
|
744
|
+
return null;
|
|
745
|
+
}
|
|
746
|
+
return {
|
|
747
|
+
policyId,
|
|
748
|
+
tenantId,
|
|
749
|
+
userId,
|
|
750
|
+
workspaceId,
|
|
751
|
+
scope,
|
|
752
|
+
scopeId,
|
|
753
|
+
automationEnabled,
|
|
754
|
+
frozen,
|
|
755
|
+
createdAt,
|
|
756
|
+
updatedAt,
|
|
757
|
+
};
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
function parseThreadRecord(value: unknown): AgentThread | null {
|
|
761
|
+
const record = asRecord(value);
|
|
762
|
+
if (record === null) {
|
|
763
|
+
return null;
|
|
764
|
+
}
|
|
765
|
+
const threadId = readString(record['conversationId']);
|
|
766
|
+
const projectId = readString(record['directoryId']);
|
|
767
|
+
const tenantId = readString(record['tenantId']);
|
|
768
|
+
const userId = readString(record['userId']);
|
|
769
|
+
const workspaceId = readString(record['workspaceId']);
|
|
770
|
+
const title = readString(record['title']);
|
|
771
|
+
const agentType = readString(record['agentType']);
|
|
772
|
+
const createdAt = readString(record['createdAt']);
|
|
773
|
+
const archivedAt = readNullableString(record['archivedAt']);
|
|
774
|
+
const runtimeStatus = parseRuntimeStatus(record['runtimeStatus']);
|
|
775
|
+
const runtimeStatusModel = parseRuntimeStatusModel(record['runtimeStatusModel']);
|
|
776
|
+
const runtimeLive = readBoolean(record['runtimeLive']);
|
|
777
|
+
const runtimeAttentionReason = readNullableString(record['runtimeAttentionReason']);
|
|
778
|
+
const runtimeProcessId = readNullableNumber(record['runtimeProcessId']);
|
|
779
|
+
const runtimeLastEventAt = readNullableString(record['runtimeLastEventAt']);
|
|
780
|
+
const runtimeLastExit = parseExit(record['runtimeLastExit']);
|
|
781
|
+
const adapterState = asRecord(record['adapterState']);
|
|
782
|
+
if (
|
|
783
|
+
threadId === null ||
|
|
784
|
+
projectId === null ||
|
|
785
|
+
tenantId === null ||
|
|
786
|
+
userId === null ||
|
|
787
|
+
workspaceId === null ||
|
|
788
|
+
title === null ||
|
|
789
|
+
agentType === null ||
|
|
790
|
+
createdAt === null ||
|
|
791
|
+
archivedAt === undefined ||
|
|
792
|
+
runtimeStatus === null ||
|
|
793
|
+
runtimeStatusModel === undefined ||
|
|
794
|
+
runtimeLive === null ||
|
|
795
|
+
runtimeAttentionReason === undefined ||
|
|
796
|
+
runtimeProcessId === undefined ||
|
|
797
|
+
runtimeLastEventAt === undefined ||
|
|
798
|
+
runtimeLastExit === undefined ||
|
|
799
|
+
adapterState === null
|
|
800
|
+
) {
|
|
801
|
+
return null;
|
|
802
|
+
}
|
|
803
|
+
return {
|
|
804
|
+
threadId,
|
|
805
|
+
projectId,
|
|
806
|
+
tenantId,
|
|
807
|
+
userId,
|
|
808
|
+
workspaceId,
|
|
809
|
+
title,
|
|
810
|
+
agentType,
|
|
811
|
+
createdAt,
|
|
812
|
+
archivedAt,
|
|
813
|
+
runtimeStatus,
|
|
814
|
+
runtimeStatusModel,
|
|
815
|
+
runtimeLive,
|
|
816
|
+
runtimeAttentionReason,
|
|
817
|
+
runtimeProcessId,
|
|
818
|
+
runtimeLastEventAt,
|
|
819
|
+
runtimeLastExit,
|
|
820
|
+
adapterState,
|
|
821
|
+
};
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
function parseRepositoryRecord(value: unknown): AgentRepository | null {
|
|
825
|
+
const record = asRecord(value);
|
|
826
|
+
if (record === null) {
|
|
827
|
+
return null;
|
|
828
|
+
}
|
|
829
|
+
const repositoryId = readString(record['repositoryId']);
|
|
830
|
+
const tenantId = readString(record['tenantId']);
|
|
831
|
+
const userId = readString(record['userId']);
|
|
832
|
+
const workspaceId = readString(record['workspaceId']);
|
|
833
|
+
const name = readString(record['name']);
|
|
834
|
+
const remoteUrl = readString(record['remoteUrl']);
|
|
835
|
+
const defaultBranch = readString(record['defaultBranch']);
|
|
836
|
+
const metadata = asRecord(record['metadata']);
|
|
837
|
+
const createdAt = readString(record['createdAt']);
|
|
838
|
+
const archivedAt = readNullableString(record['archivedAt']);
|
|
839
|
+
if (
|
|
840
|
+
repositoryId === null ||
|
|
841
|
+
tenantId === null ||
|
|
842
|
+
userId === null ||
|
|
843
|
+
workspaceId === null ||
|
|
844
|
+
name === null ||
|
|
845
|
+
remoteUrl === null ||
|
|
846
|
+
defaultBranch === null ||
|
|
847
|
+
metadata === null ||
|
|
848
|
+
createdAt === null ||
|
|
849
|
+
archivedAt === undefined
|
|
850
|
+
) {
|
|
851
|
+
return null;
|
|
852
|
+
}
|
|
853
|
+
return {
|
|
854
|
+
repositoryId,
|
|
855
|
+
tenantId,
|
|
856
|
+
userId,
|
|
857
|
+
workspaceId,
|
|
858
|
+
name,
|
|
859
|
+
remoteUrl,
|
|
860
|
+
defaultBranch,
|
|
861
|
+
metadata,
|
|
862
|
+
createdAt,
|
|
863
|
+
archivedAt,
|
|
864
|
+
};
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
function parseProjectGitStatusRecord(value: unknown): AgentProjectGitStatus | null {
|
|
868
|
+
const record = asRecord(value);
|
|
869
|
+
if (record === null) {
|
|
870
|
+
return null;
|
|
871
|
+
}
|
|
872
|
+
const directoryId = readString(record['directoryId']);
|
|
873
|
+
const summaryRecord = asRecord(record['summary']);
|
|
874
|
+
const repositorySnapshotRecord = asRecord(record['repositorySnapshot']);
|
|
875
|
+
const repositoryId = readNullableString(record['repositoryId']);
|
|
876
|
+
const observedAt = readString(record['observedAt']);
|
|
877
|
+
const repositoryRaw = record['repository'];
|
|
878
|
+
const repository =
|
|
879
|
+
repositoryRaw === null || repositoryRaw === undefined
|
|
880
|
+
? null
|
|
881
|
+
: parseRepositoryRecord(repositoryRaw);
|
|
882
|
+
if (
|
|
883
|
+
directoryId === null ||
|
|
884
|
+
summaryRecord === null ||
|
|
885
|
+
repositorySnapshotRecord === null ||
|
|
886
|
+
repositoryId === undefined ||
|
|
887
|
+
observedAt === null ||
|
|
888
|
+
(repositoryRaw !== null && repositoryRaw !== undefined && repository === null)
|
|
889
|
+
) {
|
|
890
|
+
return null;
|
|
891
|
+
}
|
|
892
|
+
const branch = readString(summaryRecord['branch']);
|
|
893
|
+
const changedFiles = readNumber(summaryRecord['changedFiles']);
|
|
894
|
+
const additions = readNumber(summaryRecord['additions']);
|
|
895
|
+
const deletions = readNumber(summaryRecord['deletions']);
|
|
896
|
+
const normalizedRemoteUrl = readNullableString(repositorySnapshotRecord['normalizedRemoteUrl']);
|
|
897
|
+
const commitCount = readNullableNumber(repositorySnapshotRecord['commitCount']);
|
|
898
|
+
const lastCommitAt = readNullableString(repositorySnapshotRecord['lastCommitAt']);
|
|
899
|
+
const shortCommitHash = readNullableString(repositorySnapshotRecord['shortCommitHash']);
|
|
900
|
+
const inferredName = readNullableString(repositorySnapshotRecord['inferredName']);
|
|
901
|
+
const defaultBranch = readNullableString(repositorySnapshotRecord['defaultBranch']);
|
|
902
|
+
if (
|
|
903
|
+
branch === null ||
|
|
904
|
+
changedFiles === null ||
|
|
905
|
+
additions === null ||
|
|
906
|
+
deletions === null ||
|
|
907
|
+
normalizedRemoteUrl === undefined ||
|
|
908
|
+
commitCount === undefined ||
|
|
909
|
+
lastCommitAt === undefined ||
|
|
910
|
+
shortCommitHash === undefined ||
|
|
911
|
+
inferredName === undefined ||
|
|
912
|
+
defaultBranch === undefined
|
|
913
|
+
) {
|
|
914
|
+
return null;
|
|
915
|
+
}
|
|
916
|
+
return {
|
|
917
|
+
directoryId,
|
|
918
|
+
summary: {
|
|
919
|
+
branch,
|
|
920
|
+
changedFiles,
|
|
921
|
+
additions,
|
|
922
|
+
deletions,
|
|
923
|
+
},
|
|
924
|
+
repositorySnapshot: {
|
|
925
|
+
normalizedRemoteUrl,
|
|
926
|
+
commitCount,
|
|
927
|
+
lastCommitAt,
|
|
928
|
+
shortCommitHash,
|
|
929
|
+
inferredName,
|
|
930
|
+
defaultBranch,
|
|
931
|
+
},
|
|
932
|
+
repositoryId,
|
|
933
|
+
repository,
|
|
934
|
+
observedAt,
|
|
935
|
+
};
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
function parseTaskRecord(value: unknown): AgentTask | null {
|
|
939
|
+
const record = asRecord(value);
|
|
940
|
+
if (record === null) {
|
|
941
|
+
return null;
|
|
942
|
+
}
|
|
943
|
+
const taskId = readString(record['taskId']);
|
|
944
|
+
const tenantId = readString(record['tenantId']);
|
|
945
|
+
const userId = readString(record['userId']);
|
|
946
|
+
const workspaceId = readString(record['workspaceId']);
|
|
947
|
+
const repositoryId = readNullableString(record['repositoryId']);
|
|
948
|
+
const projectId = readNullableString(record['projectId']);
|
|
949
|
+
const title = readString(record['title']);
|
|
950
|
+
const description = readString(record['description']);
|
|
951
|
+
const status = parseTaskStatus(record['status']);
|
|
952
|
+
const scopeKind = parseTaskScopeKind(record['scopeKind'], repositoryId, projectId);
|
|
953
|
+
const orderIndex = readNumber(record['orderIndex']);
|
|
954
|
+
const claimedByControllerId = readNullableString(record['claimedByControllerId']);
|
|
955
|
+
const claimedByProjectId = readNullableString(record['claimedByDirectoryId']);
|
|
956
|
+
const branchName = readNullableString(record['branchName']);
|
|
957
|
+
const baseBranch = readNullableString(record['baseBranch']);
|
|
958
|
+
const claimedAt = readNullableString(record['claimedAt']);
|
|
959
|
+
const completedAt = readNullableString(record['completedAt']);
|
|
960
|
+
const linear = parseTaskLinearRecord(record['linear']);
|
|
961
|
+
const createdAt = readString(record['createdAt']);
|
|
962
|
+
const updatedAt = readString(record['updatedAt']);
|
|
963
|
+
if (
|
|
964
|
+
taskId === null ||
|
|
965
|
+
tenantId === null ||
|
|
966
|
+
userId === null ||
|
|
967
|
+
workspaceId === null ||
|
|
968
|
+
repositoryId === undefined ||
|
|
969
|
+
projectId === undefined ||
|
|
970
|
+
scopeKind === null ||
|
|
971
|
+
title === null ||
|
|
972
|
+
description === null ||
|
|
973
|
+
status === null ||
|
|
974
|
+
orderIndex === null ||
|
|
975
|
+
claimedByControllerId === undefined ||
|
|
976
|
+
claimedByProjectId === undefined ||
|
|
977
|
+
branchName === undefined ||
|
|
978
|
+
baseBranch === undefined ||
|
|
979
|
+
claimedAt === undefined ||
|
|
980
|
+
completedAt === undefined ||
|
|
981
|
+
linear === null ||
|
|
982
|
+
createdAt === null ||
|
|
983
|
+
updatedAt === null
|
|
984
|
+
) {
|
|
985
|
+
return null;
|
|
986
|
+
}
|
|
987
|
+
return {
|
|
988
|
+
taskId,
|
|
989
|
+
tenantId,
|
|
990
|
+
userId,
|
|
991
|
+
workspaceId,
|
|
992
|
+
repositoryId,
|
|
993
|
+
scopeKind,
|
|
994
|
+
projectId,
|
|
995
|
+
title,
|
|
996
|
+
description,
|
|
997
|
+
status,
|
|
998
|
+
orderIndex,
|
|
999
|
+
claimedByControllerId,
|
|
1000
|
+
claimedByProjectId,
|
|
1001
|
+
branchName,
|
|
1002
|
+
baseBranch,
|
|
1003
|
+
claimedAt,
|
|
1004
|
+
completedAt,
|
|
1005
|
+
linear,
|
|
1006
|
+
createdAt,
|
|
1007
|
+
updatedAt,
|
|
1008
|
+
};
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
function parseExactRecordArray<T>(
|
|
1012
|
+
value: unknown,
|
|
1013
|
+
parser: (entry: unknown) => T | null,
|
|
1014
|
+
): readonly T[] | null {
|
|
1015
|
+
if (!Array.isArray(value)) {
|
|
1016
|
+
return null;
|
|
1017
|
+
}
|
|
1018
|
+
const parsed: T[] = [];
|
|
1019
|
+
for (const entry of value) {
|
|
1020
|
+
const normalized = parser(entry);
|
|
1021
|
+
if (normalized === null) {
|
|
1022
|
+
return null;
|
|
1023
|
+
}
|
|
1024
|
+
parsed.push(normalized);
|
|
1025
|
+
}
|
|
1026
|
+
return parsed;
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
function requireParsed<T>(
|
|
1030
|
+
value: unknown,
|
|
1031
|
+
parser: (entry: unknown) => T | null,
|
|
1032
|
+
errorMessage: string,
|
|
1033
|
+
): T {
|
|
1034
|
+
const parsed = parser(value);
|
|
1035
|
+
if (parsed === null) {
|
|
1036
|
+
throw new Error(errorMessage);
|
|
1037
|
+
}
|
|
1038
|
+
return parsed;
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
function requireParsedArray<T>(
|
|
1042
|
+
value: unknown,
|
|
1043
|
+
parser: (entry: unknown) => T | null,
|
|
1044
|
+
errorMessage: string,
|
|
1045
|
+
): readonly T[] {
|
|
1046
|
+
const parsed = parseExactRecordArray(value, parser);
|
|
1047
|
+
if (parsed === null) {
|
|
1048
|
+
throw new Error(errorMessage);
|
|
1049
|
+
}
|
|
1050
|
+
return parsed;
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
function requireBoolean(value: unknown, errorMessage: string): boolean {
|
|
1054
|
+
if (typeof value !== 'boolean') {
|
|
1055
|
+
throw new Error(errorMessage);
|
|
1056
|
+
}
|
|
1057
|
+
return value;
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
function optionalField<TKey extends string, TValue>(
|
|
1061
|
+
key: TKey,
|
|
1062
|
+
value: TValue | undefined,
|
|
1063
|
+
): Partial<Record<TKey, TValue>> {
|
|
1064
|
+
if (value === undefined) {
|
|
1065
|
+
return {};
|
|
1066
|
+
}
|
|
1067
|
+
return {
|
|
1068
|
+
[key]: value,
|
|
1069
|
+
} as Record<TKey, TValue>;
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
function parseSessionController(value: unknown): StreamSessionController | null {
|
|
1073
|
+
const record = asRecord(value);
|
|
1074
|
+
if (record === null) {
|
|
1075
|
+
return null;
|
|
1076
|
+
}
|
|
1077
|
+
const controllerId = record['controllerId'];
|
|
1078
|
+
const controllerType = record['controllerType'];
|
|
1079
|
+
const controllerLabel = record['controllerLabel'];
|
|
1080
|
+
const claimedAt = record['claimedAt'];
|
|
1081
|
+
if (
|
|
1082
|
+
typeof controllerId !== 'string' ||
|
|
1083
|
+
(controllerType !== 'human' && controllerType !== 'agent' && controllerType !== 'automation') ||
|
|
1084
|
+
(controllerLabel !== null && typeof controllerLabel !== 'string') ||
|
|
1085
|
+
typeof claimedAt !== 'string'
|
|
1086
|
+
) {
|
|
1087
|
+
return null;
|
|
1088
|
+
}
|
|
1089
|
+
return {
|
|
1090
|
+
controllerId,
|
|
1091
|
+
controllerType,
|
|
1092
|
+
controllerLabel,
|
|
1093
|
+
claimedAt,
|
|
1094
|
+
};
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
function mapObservedEventType(observed: StreamObservedEvent): AgentRealtimeEventType {
|
|
1098
|
+
if (observed.type === 'directory-upserted') {
|
|
1099
|
+
return 'directory.upserted';
|
|
1100
|
+
}
|
|
1101
|
+
if (observed.type === 'directory-archived') {
|
|
1102
|
+
return 'directory.archived';
|
|
1103
|
+
}
|
|
1104
|
+
if (observed.type === 'directory-git-updated') {
|
|
1105
|
+
return 'directory.git-updated';
|
|
1106
|
+
}
|
|
1107
|
+
if (observed.type === 'conversation-created') {
|
|
1108
|
+
return 'conversation.created';
|
|
1109
|
+
}
|
|
1110
|
+
if (observed.type === 'conversation-updated') {
|
|
1111
|
+
return 'conversation.updated';
|
|
1112
|
+
}
|
|
1113
|
+
if (observed.type === 'conversation-archived') {
|
|
1114
|
+
return 'conversation.archived';
|
|
1115
|
+
}
|
|
1116
|
+
if (observed.type === 'conversation-deleted') {
|
|
1117
|
+
return 'conversation.deleted';
|
|
1118
|
+
}
|
|
1119
|
+
if (observed.type === 'repository-upserted') {
|
|
1120
|
+
return 'repository.upserted';
|
|
1121
|
+
}
|
|
1122
|
+
if (observed.type === 'repository-updated') {
|
|
1123
|
+
return 'repository.updated';
|
|
1124
|
+
}
|
|
1125
|
+
if (observed.type === 'repository-archived') {
|
|
1126
|
+
return 'repository.archived';
|
|
1127
|
+
}
|
|
1128
|
+
if (observed.type === 'task-created') {
|
|
1129
|
+
return 'task.created';
|
|
1130
|
+
}
|
|
1131
|
+
if (observed.type === 'task-updated') {
|
|
1132
|
+
return 'task.updated';
|
|
1133
|
+
}
|
|
1134
|
+
if (observed.type === 'task-deleted') {
|
|
1135
|
+
return 'task.deleted';
|
|
1136
|
+
}
|
|
1137
|
+
if (observed.type === 'task-reordered') {
|
|
1138
|
+
return 'task.reordered';
|
|
1139
|
+
}
|
|
1140
|
+
if (observed.type === 'github-pr-upserted') {
|
|
1141
|
+
return 'github.pr-upserted';
|
|
1142
|
+
}
|
|
1143
|
+
if (observed.type === 'github-pr-closed') {
|
|
1144
|
+
return 'github.pr-closed';
|
|
1145
|
+
}
|
|
1146
|
+
if (observed.type === 'github-pr-jobs-updated') {
|
|
1147
|
+
return 'github.pr-jobs-updated';
|
|
1148
|
+
}
|
|
1149
|
+
if (observed.type === 'session-status') {
|
|
1150
|
+
return 'session.status';
|
|
1151
|
+
}
|
|
1152
|
+
if (observed.type === 'session-event') {
|
|
1153
|
+
return 'session.event';
|
|
1154
|
+
}
|
|
1155
|
+
if (observed.type === 'session-key-event') {
|
|
1156
|
+
return 'session.telemetry';
|
|
1157
|
+
}
|
|
1158
|
+
if (observed.type === 'session-control') {
|
|
1159
|
+
return 'session.control';
|
|
1160
|
+
}
|
|
1161
|
+
return 'session.output';
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
function parseClaimResult(result: Record<string, unknown>): AgentSessionClaimResult {
|
|
1165
|
+
const sessionId = result['sessionId'];
|
|
1166
|
+
const action = result['action'];
|
|
1167
|
+
const controller = parseSessionController(result['controller']);
|
|
1168
|
+
if (
|
|
1169
|
+
typeof sessionId !== 'string' ||
|
|
1170
|
+
(action !== 'claimed' && action !== 'taken-over') ||
|
|
1171
|
+
controller === null
|
|
1172
|
+
) {
|
|
1173
|
+
throw new Error('control-plane session.claim returned malformed response');
|
|
1174
|
+
}
|
|
1175
|
+
return {
|
|
1176
|
+
sessionId,
|
|
1177
|
+
action,
|
|
1178
|
+
controller,
|
|
1179
|
+
};
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
function parseReleaseResult(result: Record<string, unknown>): AgentSessionReleaseResult {
|
|
1183
|
+
const sessionId = result['sessionId'];
|
|
1184
|
+
const released = result['released'];
|
|
1185
|
+
if (typeof sessionId !== 'string' || typeof released !== 'boolean') {
|
|
1186
|
+
throw new Error('control-plane session.release returned malformed response');
|
|
1187
|
+
}
|
|
1188
|
+
return {
|
|
1189
|
+
sessionId,
|
|
1190
|
+
released,
|
|
1191
|
+
};
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
function parseSubscriptionResult(result: Record<string, unknown>): {
|
|
1195
|
+
subscriptionId: string;
|
|
1196
|
+
cursor: number;
|
|
1197
|
+
} {
|
|
1198
|
+
const subscriptionId = result['subscriptionId'];
|
|
1199
|
+
const cursor = result['cursor'];
|
|
1200
|
+
if (
|
|
1201
|
+
typeof subscriptionId !== 'string' ||
|
|
1202
|
+
subscriptionId.length === 0 ||
|
|
1203
|
+
typeof cursor !== 'number' ||
|
|
1204
|
+
!Number.isFinite(cursor)
|
|
1205
|
+
) {
|
|
1206
|
+
throw new Error('control-plane stream.subscribe returned malformed subscription id');
|
|
1207
|
+
}
|
|
1208
|
+
return {
|
|
1209
|
+
subscriptionId,
|
|
1210
|
+
cursor,
|
|
1211
|
+
};
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
function parseUnsubscribeResult(result: Record<string, unknown>): { unsubscribed: boolean } {
|
|
1215
|
+
const unsubscribed = result['unsubscribed'];
|
|
1216
|
+
if (typeof unsubscribed !== 'boolean') {
|
|
1217
|
+
throw new Error('control-plane stream.unsubscribe returned malformed response');
|
|
1218
|
+
}
|
|
1219
|
+
return {
|
|
1220
|
+
unsubscribed,
|
|
1221
|
+
};
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
function buildSubscriptionCommand(filter?: AgentRealtimeSubscriptionFilter): {
|
|
1225
|
+
type: 'stream.subscribe';
|
|
1226
|
+
tenantId?: string;
|
|
1227
|
+
userId?: string;
|
|
1228
|
+
workspaceId?: string;
|
|
1229
|
+
repositoryId?: string;
|
|
1230
|
+
taskId?: string;
|
|
1231
|
+
directoryId?: string;
|
|
1232
|
+
conversationId?: string;
|
|
1233
|
+
includeOutput?: boolean;
|
|
1234
|
+
afterCursor?: number;
|
|
1235
|
+
} {
|
|
1236
|
+
const command: {
|
|
1237
|
+
type: 'stream.subscribe';
|
|
1238
|
+
tenantId?: string;
|
|
1239
|
+
userId?: string;
|
|
1240
|
+
workspaceId?: string;
|
|
1241
|
+
repositoryId?: string;
|
|
1242
|
+
taskId?: string;
|
|
1243
|
+
directoryId?: string;
|
|
1244
|
+
conversationId?: string;
|
|
1245
|
+
includeOutput?: boolean;
|
|
1246
|
+
afterCursor?: number;
|
|
1247
|
+
} = {
|
|
1248
|
+
type: 'stream.subscribe',
|
|
1249
|
+
includeOutput: filter?.includeOutput ?? false,
|
|
1250
|
+
};
|
|
1251
|
+
if (filter?.tenantId !== undefined) {
|
|
1252
|
+
command.tenantId = filter.tenantId;
|
|
1253
|
+
}
|
|
1254
|
+
if (filter?.userId !== undefined) {
|
|
1255
|
+
command.userId = filter.userId;
|
|
1256
|
+
}
|
|
1257
|
+
if (filter?.workspaceId !== undefined) {
|
|
1258
|
+
command.workspaceId = filter.workspaceId;
|
|
1259
|
+
}
|
|
1260
|
+
if (filter?.repositoryId !== undefined) {
|
|
1261
|
+
command.repositoryId = filter.repositoryId;
|
|
1262
|
+
}
|
|
1263
|
+
if (filter?.taskId !== undefined) {
|
|
1264
|
+
command.taskId = filter.taskId;
|
|
1265
|
+
}
|
|
1266
|
+
if (filter?.directoryId !== undefined) {
|
|
1267
|
+
command.directoryId = filter.directoryId;
|
|
1268
|
+
}
|
|
1269
|
+
if (filter?.conversationId !== undefined) {
|
|
1270
|
+
command.conversationId = filter.conversationId;
|
|
1271
|
+
}
|
|
1272
|
+
if (filter?.afterCursor !== undefined) {
|
|
1273
|
+
command.afterCursor = filter.afterCursor;
|
|
1274
|
+
}
|
|
1275
|
+
return command;
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
export class HarnessAgentRealtimeClient {
|
|
1279
|
+
readonly client: ControlPlaneStreamClient;
|
|
1280
|
+
readonly projects = {
|
|
1281
|
+
create: async (input: AgentProjectUpsertInput): Promise<AgentProject> =>
|
|
1282
|
+
await this.createProject(input),
|
|
1283
|
+
upsert: async (input: AgentProjectUpsertInput): Promise<AgentProject> =>
|
|
1284
|
+
await this.upsertProject(input),
|
|
1285
|
+
get: async (projectId: string, scope: AgentScopeQuery = {}): Promise<AgentProject> =>
|
|
1286
|
+
await this.getProject(projectId, scope),
|
|
1287
|
+
list: async (query: AgentProjectListQuery = {}): Promise<readonly AgentProject[]> =>
|
|
1288
|
+
await this.listProjects(query),
|
|
1289
|
+
listGitStatus: async (
|
|
1290
|
+
query: AgentProjectGitStatusListQuery = {},
|
|
1291
|
+
): Promise<readonly AgentProjectGitStatus[]> => await this.listProjectGitStatus(query),
|
|
1292
|
+
update: async (
|
|
1293
|
+
projectId: string,
|
|
1294
|
+
input: Omit<AgentProjectUpsertInput, 'projectId'>,
|
|
1295
|
+
): Promise<AgentProject> => await this.updateProject(projectId, input),
|
|
1296
|
+
archive: async (projectId: string): Promise<AgentProject> =>
|
|
1297
|
+
await this.archiveProject(projectId),
|
|
1298
|
+
status: async (projectId: string): Promise<Record<string, unknown>> =>
|
|
1299
|
+
await this.projectStatus(projectId),
|
|
1300
|
+
settings: {
|
|
1301
|
+
get: async (projectId: string): Promise<AgentProjectSettings> =>
|
|
1302
|
+
await this.getProjectSettings(projectId),
|
|
1303
|
+
update: async (
|
|
1304
|
+
projectId: string,
|
|
1305
|
+
update: AgentProjectSettingsUpdateInput,
|
|
1306
|
+
): Promise<AgentProjectSettings> => await this.updateProjectSettings(projectId, update),
|
|
1307
|
+
},
|
|
1308
|
+
};
|
|
1309
|
+
|
|
1310
|
+
readonly threads = {
|
|
1311
|
+
create: async (input: AgentThreadCreateInput): Promise<AgentThread> =>
|
|
1312
|
+
await this.createThread(input),
|
|
1313
|
+
get: async (threadId: string, query: AgentThreadListQuery = {}): Promise<AgentThread> =>
|
|
1314
|
+
await this.getThread(threadId, query),
|
|
1315
|
+
list: async (query: AgentThreadListQuery = {}): Promise<readonly AgentThread[]> =>
|
|
1316
|
+
await this.listThreads(query),
|
|
1317
|
+
update: async (threadId: string, input: AgentThreadUpdateInput): Promise<AgentThread> =>
|
|
1318
|
+
await this.updateThread(threadId, input),
|
|
1319
|
+
archive: async (threadId: string): Promise<AgentThread> => await this.archiveThread(threadId),
|
|
1320
|
+
delete: async (threadId: string): Promise<{ deleted: boolean }> =>
|
|
1321
|
+
await this.deleteThread(threadId),
|
|
1322
|
+
status: async (threadId: string): Promise<AgentSessionSummary> =>
|
|
1323
|
+
await this.threadStatus(threadId),
|
|
1324
|
+
};
|
|
1325
|
+
|
|
1326
|
+
readonly repositories = {
|
|
1327
|
+
create: async (input: AgentRepositoryUpsertInput): Promise<AgentRepository> =>
|
|
1328
|
+
await this.createRepository(input),
|
|
1329
|
+
upsert: async (input: AgentRepositoryUpsertInput): Promise<AgentRepository> =>
|
|
1330
|
+
await this.upsertRepository(input),
|
|
1331
|
+
get: async (repositoryId: string): Promise<AgentRepository> =>
|
|
1332
|
+
await this.getRepository(repositoryId),
|
|
1333
|
+
list: async (query: AgentRepositoryListQuery = {}): Promise<readonly AgentRepository[]> =>
|
|
1334
|
+
await this.listRepositories(query),
|
|
1335
|
+
update: async (
|
|
1336
|
+
repositoryId: string,
|
|
1337
|
+
update: AgentRepositoryUpdateInput,
|
|
1338
|
+
): Promise<AgentRepository> => await this.updateRepository(repositoryId, update),
|
|
1339
|
+
archive: async (repositoryId: string): Promise<AgentRepository> =>
|
|
1340
|
+
await this.archiveRepository(repositoryId),
|
|
1341
|
+
};
|
|
1342
|
+
|
|
1343
|
+
readonly tasks = {
|
|
1344
|
+
create: async (input: AgentTaskCreateInput): Promise<AgentTask> => await this.createTask(input),
|
|
1345
|
+
get: async (taskId: string): Promise<AgentTask> => await this.getTask(taskId),
|
|
1346
|
+
list: async (query: AgentTaskListQuery = {}): Promise<readonly AgentTask[]> =>
|
|
1347
|
+
await this.listTasks(query),
|
|
1348
|
+
update: async (taskId: string, update: AgentTaskUpdateInput): Promise<AgentTask> =>
|
|
1349
|
+
await this.updateTask(taskId, update),
|
|
1350
|
+
delete: async (taskId: string): Promise<{ deleted: boolean }> => await this.deleteTask(taskId),
|
|
1351
|
+
claim: async (input: AgentTaskClaimInput): Promise<AgentTask> => await this.claimTask(input),
|
|
1352
|
+
pull: async (
|
|
1353
|
+
input: AgentTaskPullInput,
|
|
1354
|
+
): Promise<{
|
|
1355
|
+
task: AgentTask | null;
|
|
1356
|
+
directoryId: string | null;
|
|
1357
|
+
availability: string;
|
|
1358
|
+
reason: string | null;
|
|
1359
|
+
settings: AgentProjectSettings | null;
|
|
1360
|
+
repositoryId: string | null;
|
|
1361
|
+
}> => await this.pullTask(input),
|
|
1362
|
+
complete: async (taskId: string): Promise<AgentTask> => await this.completeTask(taskId),
|
|
1363
|
+
ready: async (taskId: string): Promise<AgentTask> => await this.readyTask(taskId),
|
|
1364
|
+
draft: async (taskId: string): Promise<AgentTask> => await this.draftTask(taskId),
|
|
1365
|
+
queue: async (taskId: string): Promise<AgentTask> => await this.queueTask(taskId),
|
|
1366
|
+
reorder: async (input: AgentTaskReorderInput): Promise<readonly AgentTask[]> =>
|
|
1367
|
+
await this.reorderTasks(input),
|
|
1368
|
+
};
|
|
1369
|
+
|
|
1370
|
+
readonly automation = {
|
|
1371
|
+
getPolicy: async (input: {
|
|
1372
|
+
scope: 'global' | 'repository' | 'project';
|
|
1373
|
+
scopeId?: string;
|
|
1374
|
+
tenantId?: string;
|
|
1375
|
+
userId?: string;
|
|
1376
|
+
workspaceId?: string;
|
|
1377
|
+
}): Promise<AgentAutomationPolicy> => await this.getAutomationPolicy(input),
|
|
1378
|
+
setPolicy: async (input: {
|
|
1379
|
+
scope: 'global' | 'repository' | 'project';
|
|
1380
|
+
scopeId?: string;
|
|
1381
|
+
tenantId?: string;
|
|
1382
|
+
userId?: string;
|
|
1383
|
+
workspaceId?: string;
|
|
1384
|
+
automationEnabled?: boolean;
|
|
1385
|
+
frozen?: boolean;
|
|
1386
|
+
}): Promise<AgentAutomationPolicy> => await this.setAutomationPolicy(input),
|
|
1387
|
+
};
|
|
1388
|
+
|
|
1389
|
+
readonly sessions = {
|
|
1390
|
+
list: async (
|
|
1391
|
+
query: Parameters<HarnessAgentRealtimeClient['listSessions']>[0] = {},
|
|
1392
|
+
): Promise<readonly AgentSessionSummary[]> => await this.listSessions(query),
|
|
1393
|
+
status: async (sessionId: string): Promise<AgentSessionSummary> =>
|
|
1394
|
+
await this.sessionStatus(sessionId),
|
|
1395
|
+
claim: async (input: AgentClaimSessionInput): Promise<AgentSessionClaimResult> =>
|
|
1396
|
+
await this.claimSession(input),
|
|
1397
|
+
takeover: async (
|
|
1398
|
+
input: Omit<AgentClaimSessionInput, 'takeover'>,
|
|
1399
|
+
): Promise<AgentSessionClaimResult> => await this.takeoverSession(input),
|
|
1400
|
+
release: async (input: AgentReleaseSessionInput): Promise<AgentSessionReleaseResult> =>
|
|
1401
|
+
await this.releaseSession(input),
|
|
1402
|
+
respond: async (
|
|
1403
|
+
sessionId: string,
|
|
1404
|
+
text: string,
|
|
1405
|
+
): Promise<{ responded: boolean; sentBytes: number }> => await this.respond(sessionId, text),
|
|
1406
|
+
interrupt: async (sessionId: string): Promise<{ interrupted: boolean }> =>
|
|
1407
|
+
await this.interrupt(sessionId),
|
|
1408
|
+
remove: async (sessionId: string): Promise<{ removed: boolean }> =>
|
|
1409
|
+
await this.removeSession(sessionId),
|
|
1410
|
+
start: async (
|
|
1411
|
+
input: Parameters<HarnessAgentRealtimeClient['startSession']>[0],
|
|
1412
|
+
): Promise<{ sessionId: string }> => await this.startSession(input),
|
|
1413
|
+
attach: async (sessionId: string, sinceCursor = 0): Promise<{ latestCursor: number }> =>
|
|
1414
|
+
await this.attachSession(sessionId, sinceCursor),
|
|
1415
|
+
detach: async (sessionId: string): Promise<{ detached: boolean }> =>
|
|
1416
|
+
await this.detachSession(sessionId),
|
|
1417
|
+
close: async (sessionId: string): Promise<{ closed: boolean }> =>
|
|
1418
|
+
await this.closeSession(sessionId),
|
|
1419
|
+
subscribeEvents: async (sessionId: string): Promise<{ subscribed: boolean }> =>
|
|
1420
|
+
await this.subscribeSessionEvents(sessionId),
|
|
1421
|
+
unsubscribeEvents: async (sessionId: string): Promise<{ subscribed: boolean }> =>
|
|
1422
|
+
await this.unsubscribeSessionEvents(sessionId),
|
|
1423
|
+
};
|
|
1424
|
+
|
|
1425
|
+
readonly subscriptions = {
|
|
1426
|
+
create: async (filter?: AgentRealtimeSubscriptionFilter): Promise<AgentRealtimeSubscription> =>
|
|
1427
|
+
await this.subscribe(filter),
|
|
1428
|
+
remove: async (subscriptionId: string): Promise<{ unsubscribed: boolean }> =>
|
|
1429
|
+
await this.unsubscribe(subscriptionId),
|
|
1430
|
+
};
|
|
1431
|
+
|
|
1432
|
+
private readonly listenersByType = new Map<
|
|
1433
|
+
AgentRealtimeEventType | '*',
|
|
1434
|
+
Set<AnyRealtimeListener>
|
|
1435
|
+
>();
|
|
1436
|
+
private readonly subscriptionIds = new Set<string>();
|
|
1437
|
+
private readonly onHandlerError:
|
|
1438
|
+
| ((error: unknown, event: AgentRealtimeEventEnvelope) => void)
|
|
1439
|
+
| undefined;
|
|
1440
|
+
private readonly removeEnvelopeListener: () => void;
|
|
1441
|
+
private closed = false;
|
|
1442
|
+
|
|
1443
|
+
private constructor(
|
|
1444
|
+
client: ControlPlaneStreamClient,
|
|
1445
|
+
initialSubscriptionId: string,
|
|
1446
|
+
removeEnvelopeListener: () => void,
|
|
1447
|
+
onHandlerError: ((error: unknown, event: AgentRealtimeEventEnvelope) => void) | undefined,
|
|
1448
|
+
) {
|
|
1449
|
+
this.client = client;
|
|
1450
|
+
this.subscriptionIds.add(initialSubscriptionId);
|
|
1451
|
+
this.removeEnvelopeListener = removeEnvelopeListener;
|
|
1452
|
+
this.onHandlerError = onHandlerError;
|
|
1453
|
+
}
|
|
1454
|
+
|
|
1455
|
+
static async connect(options: AgentRealtimeConnectOptions): Promise<HarnessAgentRealtimeClient> {
|
|
1456
|
+
const connectOptions: {
|
|
1457
|
+
host: string;
|
|
1458
|
+
port: number;
|
|
1459
|
+
authToken?: string;
|
|
1460
|
+
connectRetryWindowMs?: number;
|
|
1461
|
+
connectRetryDelayMs?: number;
|
|
1462
|
+
} = {
|
|
1463
|
+
host: options.host,
|
|
1464
|
+
port: options.port,
|
|
1465
|
+
};
|
|
1466
|
+
if (options.authToken !== undefined) {
|
|
1467
|
+
connectOptions.authToken = options.authToken;
|
|
1468
|
+
}
|
|
1469
|
+
if (options.connectRetryWindowMs !== undefined) {
|
|
1470
|
+
connectOptions.connectRetryWindowMs = options.connectRetryWindowMs;
|
|
1471
|
+
}
|
|
1472
|
+
if (options.connectRetryDelayMs !== undefined) {
|
|
1473
|
+
connectOptions.connectRetryDelayMs = options.connectRetryDelayMs;
|
|
1474
|
+
}
|
|
1475
|
+
const client = await connectControlPlaneStreamClient(connectOptions);
|
|
1476
|
+
|
|
1477
|
+
const buffered: Array<{
|
|
1478
|
+
subscriptionId: string;
|
|
1479
|
+
cursor: number;
|
|
1480
|
+
observed: StreamObservedEvent;
|
|
1481
|
+
}> = [];
|
|
1482
|
+
let instance: HarnessAgentRealtimeClient | null = null;
|
|
1483
|
+
|
|
1484
|
+
const removeEnvelopeListener = client.onEnvelope((envelope) => {
|
|
1485
|
+
if (envelope.kind !== 'stream.event') {
|
|
1486
|
+
return;
|
|
1487
|
+
}
|
|
1488
|
+
const payload = {
|
|
1489
|
+
subscriptionId: envelope.subscriptionId,
|
|
1490
|
+
cursor: envelope.cursor,
|
|
1491
|
+
observed: envelope.event,
|
|
1492
|
+
};
|
|
1493
|
+
if (instance === null) {
|
|
1494
|
+
buffered.push(payload);
|
|
1495
|
+
return;
|
|
1496
|
+
}
|
|
1497
|
+
if (!instance.hasSubscription(payload.subscriptionId)) {
|
|
1498
|
+
return;
|
|
1499
|
+
}
|
|
1500
|
+
instance.dispatch(payload.subscriptionId, payload.cursor, payload.observed);
|
|
1501
|
+
});
|
|
1502
|
+
|
|
1503
|
+
try {
|
|
1504
|
+
const subscribed = parseSubscriptionResult(
|
|
1505
|
+
await client.sendCommand(buildSubscriptionCommand(options.subscription)),
|
|
1506
|
+
);
|
|
1507
|
+
instance = new HarnessAgentRealtimeClient(
|
|
1508
|
+
client,
|
|
1509
|
+
subscribed.subscriptionId,
|
|
1510
|
+
removeEnvelopeListener,
|
|
1511
|
+
options.onHandlerError,
|
|
1512
|
+
);
|
|
1513
|
+
for (const payload of buffered) {
|
|
1514
|
+
if (!instance.hasSubscription(payload.subscriptionId)) {
|
|
1515
|
+
continue;
|
|
1516
|
+
}
|
|
1517
|
+
instance.dispatch(payload.subscriptionId, payload.cursor, payload.observed);
|
|
1518
|
+
}
|
|
1519
|
+
buffered.length = 0;
|
|
1520
|
+
return instance;
|
|
1521
|
+
} catch (error: unknown) {
|
|
1522
|
+
removeEnvelopeListener();
|
|
1523
|
+
client.close();
|
|
1524
|
+
throw error;
|
|
1525
|
+
}
|
|
1526
|
+
}
|
|
1527
|
+
|
|
1528
|
+
on<TEventType extends AgentRealtimeEventType>(
|
|
1529
|
+
type: TEventType,
|
|
1530
|
+
listener: AgentRealtimeListener<TEventType>,
|
|
1531
|
+
): () => void;
|
|
1532
|
+
on(type: '*', listener: AnyRealtimeListener): () => void;
|
|
1533
|
+
on(type: AgentRealtimeEventType | '*', listener: AnyRealtimeListener): () => void {
|
|
1534
|
+
const existing = this.listenersByType.get(type);
|
|
1535
|
+
if (existing === undefined) {
|
|
1536
|
+
this.listenersByType.set(type, new Set([listener]));
|
|
1537
|
+
} else {
|
|
1538
|
+
existing.add(listener);
|
|
1539
|
+
}
|
|
1540
|
+
return () => {
|
|
1541
|
+
const current = this.listenersByType.get(type);
|
|
1542
|
+
current?.delete(listener);
|
|
1543
|
+
if (current !== undefined && current.size === 0) {
|
|
1544
|
+
this.listenersByType.delete(type);
|
|
1545
|
+
}
|
|
1546
|
+
};
|
|
1547
|
+
}
|
|
1548
|
+
|
|
1549
|
+
async sendCommand(command: StreamCommand): Promise<Record<string, unknown>> {
|
|
1550
|
+
return await this.client.sendCommand(command);
|
|
1551
|
+
}
|
|
1552
|
+
|
|
1553
|
+
async subscribe(filter?: AgentRealtimeSubscriptionFilter): Promise<AgentRealtimeSubscription> {
|
|
1554
|
+
const subscribed = parseSubscriptionResult(
|
|
1555
|
+
await this.client.sendCommand(buildSubscriptionCommand(filter)),
|
|
1556
|
+
);
|
|
1557
|
+
this.subscriptionIds.add(subscribed.subscriptionId);
|
|
1558
|
+
return {
|
|
1559
|
+
subscriptionId: subscribed.subscriptionId,
|
|
1560
|
+
cursor: subscribed.cursor,
|
|
1561
|
+
unsubscribe: async (): Promise<{ unsubscribed: boolean }> =>
|
|
1562
|
+
await this.unsubscribe(subscribed.subscriptionId),
|
|
1563
|
+
};
|
|
1564
|
+
}
|
|
1565
|
+
|
|
1566
|
+
async unsubscribe(subscriptionId: string): Promise<{ unsubscribed: boolean }> {
|
|
1567
|
+
if (!this.subscriptionIds.has(subscriptionId)) {
|
|
1568
|
+
return {
|
|
1569
|
+
unsubscribed: false,
|
|
1570
|
+
};
|
|
1571
|
+
}
|
|
1572
|
+
const result = parseUnsubscribeResult(
|
|
1573
|
+
await this.client.sendCommand({
|
|
1574
|
+
type: 'stream.unsubscribe',
|
|
1575
|
+
subscriptionId,
|
|
1576
|
+
}),
|
|
1577
|
+
);
|
|
1578
|
+
this.subscriptionIds.delete(subscriptionId);
|
|
1579
|
+
return result;
|
|
1580
|
+
}
|
|
1581
|
+
|
|
1582
|
+
async upsertProject(input: AgentProjectUpsertInput): Promise<AgentProject> {
|
|
1583
|
+
const result = await this.client.sendCommand({
|
|
1584
|
+
type: 'directory.upsert',
|
|
1585
|
+
...optionalField('directoryId', input.projectId),
|
|
1586
|
+
...optionalField('tenantId', input.tenantId),
|
|
1587
|
+
...optionalField('userId', input.userId),
|
|
1588
|
+
...optionalField('workspaceId', input.workspaceId),
|
|
1589
|
+
path: input.path,
|
|
1590
|
+
});
|
|
1591
|
+
return requireParsed(
|
|
1592
|
+
result['directory'],
|
|
1593
|
+
parseProjectRecord,
|
|
1594
|
+
'control-plane directory.upsert returned malformed project',
|
|
1595
|
+
);
|
|
1596
|
+
}
|
|
1597
|
+
|
|
1598
|
+
async createProject(input: AgentProjectUpsertInput): Promise<AgentProject> {
|
|
1599
|
+
return await this.upsertProject(input);
|
|
1600
|
+
}
|
|
1601
|
+
|
|
1602
|
+
async updateProject(
|
|
1603
|
+
projectId: string,
|
|
1604
|
+
input: Omit<AgentProjectUpsertInput, 'projectId'>,
|
|
1605
|
+
): Promise<AgentProject> {
|
|
1606
|
+
return await this.upsertProject({
|
|
1607
|
+
...input,
|
|
1608
|
+
projectId,
|
|
1609
|
+
});
|
|
1610
|
+
}
|
|
1611
|
+
|
|
1612
|
+
async listProjects(query: AgentProjectListQuery = {}): Promise<readonly AgentProject[]> {
|
|
1613
|
+
const result = await this.client.sendCommand({
|
|
1614
|
+
type: 'directory.list',
|
|
1615
|
+
...optionalField('tenantId', query.tenantId),
|
|
1616
|
+
...optionalField('userId', query.userId),
|
|
1617
|
+
...optionalField('workspaceId', query.workspaceId),
|
|
1618
|
+
...optionalField('includeArchived', query.includeArchived),
|
|
1619
|
+
...optionalField('limit', query.limit),
|
|
1620
|
+
});
|
|
1621
|
+
return requireParsedArray(
|
|
1622
|
+
result['directories'],
|
|
1623
|
+
parseProjectRecord,
|
|
1624
|
+
'control-plane directory.list returned malformed projects',
|
|
1625
|
+
);
|
|
1626
|
+
}
|
|
1627
|
+
|
|
1628
|
+
async listProjectGitStatus(
|
|
1629
|
+
query: AgentProjectGitStatusListQuery = {},
|
|
1630
|
+
): Promise<readonly AgentProjectGitStatus[]> {
|
|
1631
|
+
const result = await this.client.sendCommand({
|
|
1632
|
+
type: 'directory.git-status',
|
|
1633
|
+
...optionalField('tenantId', query.tenantId),
|
|
1634
|
+
...optionalField('userId', query.userId),
|
|
1635
|
+
...optionalField('workspaceId', query.workspaceId),
|
|
1636
|
+
...optionalField('directoryId', query.projectId),
|
|
1637
|
+
});
|
|
1638
|
+
return requireParsedArray(
|
|
1639
|
+
result['gitStatuses'],
|
|
1640
|
+
parseProjectGitStatusRecord,
|
|
1641
|
+
'control-plane directory.git-status returned malformed statuses',
|
|
1642
|
+
);
|
|
1643
|
+
}
|
|
1644
|
+
|
|
1645
|
+
async getProject(projectId: string, scope: AgentScopeQuery = {}): Promise<AgentProject> {
|
|
1646
|
+
const projects = await this.listProjects({
|
|
1647
|
+
...scope,
|
|
1648
|
+
includeArchived: true,
|
|
1649
|
+
});
|
|
1650
|
+
const project = projects.find((entry) => entry.projectId === projectId);
|
|
1651
|
+
if (project === undefined) {
|
|
1652
|
+
throw new Error(`project not found: ${projectId}`);
|
|
1653
|
+
}
|
|
1654
|
+
return project;
|
|
1655
|
+
}
|
|
1656
|
+
|
|
1657
|
+
async archiveProject(projectId: string): Promise<AgentProject> {
|
|
1658
|
+
const result = await this.client.sendCommand({
|
|
1659
|
+
type: 'directory.archive',
|
|
1660
|
+
directoryId: projectId,
|
|
1661
|
+
});
|
|
1662
|
+
return requireParsed(
|
|
1663
|
+
result['directory'],
|
|
1664
|
+
parseProjectRecord,
|
|
1665
|
+
'control-plane directory.archive returned malformed project',
|
|
1666
|
+
);
|
|
1667
|
+
}
|
|
1668
|
+
|
|
1669
|
+
async projectStatus(projectId: string): Promise<Record<string, unknown>> {
|
|
1670
|
+
return await this.client.sendCommand({
|
|
1671
|
+
type: 'project.status',
|
|
1672
|
+
directoryId: projectId,
|
|
1673
|
+
});
|
|
1674
|
+
}
|
|
1675
|
+
|
|
1676
|
+
async getProjectSettings(projectId: string): Promise<AgentProjectSettings> {
|
|
1677
|
+
const result = await this.client.sendCommand({
|
|
1678
|
+
type: 'project.settings-get',
|
|
1679
|
+
directoryId: projectId,
|
|
1680
|
+
});
|
|
1681
|
+
return requireParsed(
|
|
1682
|
+
result['settings'],
|
|
1683
|
+
parseProjectSettingsRecord,
|
|
1684
|
+
'control-plane project.settings-get returned malformed settings',
|
|
1685
|
+
);
|
|
1686
|
+
}
|
|
1687
|
+
|
|
1688
|
+
async updateProjectSettings(
|
|
1689
|
+
projectId: string,
|
|
1690
|
+
update: AgentProjectSettingsUpdateInput,
|
|
1691
|
+
): Promise<AgentProjectSettings> {
|
|
1692
|
+
const result = await this.client.sendCommand({
|
|
1693
|
+
type: 'project.settings-update',
|
|
1694
|
+
directoryId: projectId,
|
|
1695
|
+
...update,
|
|
1696
|
+
});
|
|
1697
|
+
return requireParsed(
|
|
1698
|
+
result['settings'],
|
|
1699
|
+
parseProjectSettingsRecord,
|
|
1700
|
+
'control-plane project.settings-update returned malformed settings',
|
|
1701
|
+
);
|
|
1702
|
+
}
|
|
1703
|
+
|
|
1704
|
+
async createThread(input: AgentThreadCreateInput): Promise<AgentThread> {
|
|
1705
|
+
const result = await this.client.sendCommand({
|
|
1706
|
+
type: 'conversation.create',
|
|
1707
|
+
...optionalField('conversationId', input.threadId),
|
|
1708
|
+
directoryId: input.projectId,
|
|
1709
|
+
title: input.title,
|
|
1710
|
+
agentType: input.agentType,
|
|
1711
|
+
...optionalField('adapterState', input.adapterState),
|
|
1712
|
+
});
|
|
1713
|
+
return requireParsed(
|
|
1714
|
+
result['conversation'],
|
|
1715
|
+
parseThreadRecord,
|
|
1716
|
+
'control-plane conversation.create returned malformed thread',
|
|
1717
|
+
);
|
|
1718
|
+
}
|
|
1719
|
+
|
|
1720
|
+
async listThreads(query: AgentThreadListQuery = {}): Promise<readonly AgentThread[]> {
|
|
1721
|
+
const result = await this.client.sendCommand({
|
|
1722
|
+
type: 'conversation.list',
|
|
1723
|
+
...optionalField('directoryId', query.projectId),
|
|
1724
|
+
...optionalField('tenantId', query.tenantId),
|
|
1725
|
+
...optionalField('userId', query.userId),
|
|
1726
|
+
...optionalField('workspaceId', query.workspaceId),
|
|
1727
|
+
...optionalField('includeArchived', query.includeArchived),
|
|
1728
|
+
...optionalField('limit', query.limit),
|
|
1729
|
+
});
|
|
1730
|
+
return requireParsedArray(
|
|
1731
|
+
result['conversations'],
|
|
1732
|
+
parseThreadRecord,
|
|
1733
|
+
'control-plane conversation.list returned malformed threads',
|
|
1734
|
+
);
|
|
1735
|
+
}
|
|
1736
|
+
|
|
1737
|
+
async getThread(threadId: string, query: AgentThreadListQuery = {}): Promise<AgentThread> {
|
|
1738
|
+
const threads = await this.listThreads({
|
|
1739
|
+
...query,
|
|
1740
|
+
includeArchived: true,
|
|
1741
|
+
});
|
|
1742
|
+
const thread = threads.find((entry) => entry.threadId === threadId);
|
|
1743
|
+
if (thread === undefined) {
|
|
1744
|
+
throw new Error(`thread not found: ${threadId}`);
|
|
1745
|
+
}
|
|
1746
|
+
return thread;
|
|
1747
|
+
}
|
|
1748
|
+
|
|
1749
|
+
async updateThread(threadId: string, input: AgentThreadUpdateInput): Promise<AgentThread> {
|
|
1750
|
+
const result = await this.client.sendCommand({
|
|
1751
|
+
type: 'conversation.update',
|
|
1752
|
+
conversationId: threadId,
|
|
1753
|
+
title: input.title,
|
|
1754
|
+
});
|
|
1755
|
+
return requireParsed(
|
|
1756
|
+
result['conversation'],
|
|
1757
|
+
parseThreadRecord,
|
|
1758
|
+
'control-plane conversation.update returned malformed thread',
|
|
1759
|
+
);
|
|
1760
|
+
}
|
|
1761
|
+
|
|
1762
|
+
async archiveThread(threadId: string): Promise<AgentThread> {
|
|
1763
|
+
const result = await this.client.sendCommand({
|
|
1764
|
+
type: 'conversation.archive',
|
|
1765
|
+
conversationId: threadId,
|
|
1766
|
+
});
|
|
1767
|
+
return requireParsed(
|
|
1768
|
+
result['conversation'],
|
|
1769
|
+
parseThreadRecord,
|
|
1770
|
+
'control-plane conversation.archive returned malformed thread',
|
|
1771
|
+
);
|
|
1772
|
+
}
|
|
1773
|
+
|
|
1774
|
+
async deleteThread(threadId: string): Promise<{ deleted: boolean }> {
|
|
1775
|
+
const result = await this.client.sendCommand({
|
|
1776
|
+
type: 'conversation.delete',
|
|
1777
|
+
conversationId: threadId,
|
|
1778
|
+
});
|
|
1779
|
+
const deleted = requireBoolean(
|
|
1780
|
+
result['deleted'],
|
|
1781
|
+
'control-plane conversation.delete returned malformed response',
|
|
1782
|
+
);
|
|
1783
|
+
return {
|
|
1784
|
+
deleted,
|
|
1785
|
+
};
|
|
1786
|
+
}
|
|
1787
|
+
|
|
1788
|
+
async threadStatus(threadId: string): Promise<AgentSessionSummary> {
|
|
1789
|
+
return await this.sessionStatus(threadId);
|
|
1790
|
+
}
|
|
1791
|
+
|
|
1792
|
+
async upsertRepository(input: AgentRepositoryUpsertInput): Promise<AgentRepository> {
|
|
1793
|
+
const result = await this.client.sendCommand({
|
|
1794
|
+
type: 'repository.upsert',
|
|
1795
|
+
...optionalField('repositoryId', input.repositoryId),
|
|
1796
|
+
...optionalField('tenantId', input.tenantId),
|
|
1797
|
+
...optionalField('userId', input.userId),
|
|
1798
|
+
...optionalField('workspaceId', input.workspaceId),
|
|
1799
|
+
name: input.name,
|
|
1800
|
+
remoteUrl: input.remoteUrl,
|
|
1801
|
+
...optionalField('defaultBranch', input.defaultBranch),
|
|
1802
|
+
...optionalField('metadata', input.metadata),
|
|
1803
|
+
});
|
|
1804
|
+
return requireParsed(
|
|
1805
|
+
result['repository'],
|
|
1806
|
+
parseRepositoryRecord,
|
|
1807
|
+
'control-plane repository.upsert returned malformed repository',
|
|
1808
|
+
);
|
|
1809
|
+
}
|
|
1810
|
+
|
|
1811
|
+
async createRepository(input: AgentRepositoryUpsertInput): Promise<AgentRepository> {
|
|
1812
|
+
return await this.upsertRepository(input);
|
|
1813
|
+
}
|
|
1814
|
+
|
|
1815
|
+
async getRepository(repositoryId: string): Promise<AgentRepository> {
|
|
1816
|
+
const result = await this.client.sendCommand({
|
|
1817
|
+
type: 'repository.get',
|
|
1818
|
+
repositoryId,
|
|
1819
|
+
});
|
|
1820
|
+
return requireParsed(
|
|
1821
|
+
result['repository'],
|
|
1822
|
+
parseRepositoryRecord,
|
|
1823
|
+
'control-plane repository.get returned malformed repository',
|
|
1824
|
+
);
|
|
1825
|
+
}
|
|
1826
|
+
|
|
1827
|
+
async listRepositories(
|
|
1828
|
+
query: AgentRepositoryListQuery = {},
|
|
1829
|
+
): Promise<readonly AgentRepository[]> {
|
|
1830
|
+
const result = await this.client.sendCommand({
|
|
1831
|
+
type: 'repository.list',
|
|
1832
|
+
...optionalField('tenantId', query.tenantId),
|
|
1833
|
+
...optionalField('userId', query.userId),
|
|
1834
|
+
...optionalField('workspaceId', query.workspaceId),
|
|
1835
|
+
...optionalField('includeArchived', query.includeArchived),
|
|
1836
|
+
...optionalField('limit', query.limit),
|
|
1837
|
+
});
|
|
1838
|
+
return requireParsedArray(
|
|
1839
|
+
result['repositories'],
|
|
1840
|
+
parseRepositoryRecord,
|
|
1841
|
+
'control-plane repository.list returned malformed repositories',
|
|
1842
|
+
);
|
|
1843
|
+
}
|
|
1844
|
+
|
|
1845
|
+
async updateRepository(
|
|
1846
|
+
repositoryId: string,
|
|
1847
|
+
update: AgentRepositoryUpdateInput,
|
|
1848
|
+
): Promise<AgentRepository> {
|
|
1849
|
+
const result = await this.client.sendCommand({
|
|
1850
|
+
type: 'repository.update',
|
|
1851
|
+
repositoryId,
|
|
1852
|
+
...update,
|
|
1853
|
+
});
|
|
1854
|
+
return requireParsed(
|
|
1855
|
+
result['repository'],
|
|
1856
|
+
parseRepositoryRecord,
|
|
1857
|
+
'control-plane repository.update returned malformed repository',
|
|
1858
|
+
);
|
|
1859
|
+
}
|
|
1860
|
+
|
|
1861
|
+
async archiveRepository(repositoryId: string): Promise<AgentRepository> {
|
|
1862
|
+
const result = await this.client.sendCommand({
|
|
1863
|
+
type: 'repository.archive',
|
|
1864
|
+
repositoryId,
|
|
1865
|
+
});
|
|
1866
|
+
return requireParsed(
|
|
1867
|
+
result['repository'],
|
|
1868
|
+
parseRepositoryRecord,
|
|
1869
|
+
'control-plane repository.archive returned malformed repository',
|
|
1870
|
+
);
|
|
1871
|
+
}
|
|
1872
|
+
|
|
1873
|
+
async createTask(input: AgentTaskCreateInput): Promise<AgentTask> {
|
|
1874
|
+
const result = await this.client.sendCommand({
|
|
1875
|
+
type: 'task.create',
|
|
1876
|
+
...optionalField('taskId', input.taskId),
|
|
1877
|
+
...optionalField('tenantId', input.tenantId),
|
|
1878
|
+
...optionalField('userId', input.userId),
|
|
1879
|
+
...optionalField('workspaceId', input.workspaceId),
|
|
1880
|
+
...optionalField('repositoryId', input.repositoryId),
|
|
1881
|
+
...optionalField('projectId', input.projectId),
|
|
1882
|
+
title: input.title,
|
|
1883
|
+
...optionalField('description', input.description),
|
|
1884
|
+
...optionalField('linear', input.linear),
|
|
1885
|
+
});
|
|
1886
|
+
return requireParsed(
|
|
1887
|
+
result['task'],
|
|
1888
|
+
parseTaskRecord,
|
|
1889
|
+
'control-plane task.create returned malformed task',
|
|
1890
|
+
);
|
|
1891
|
+
}
|
|
1892
|
+
|
|
1893
|
+
async getTask(taskId: string): Promise<AgentTask> {
|
|
1894
|
+
const result = await this.client.sendCommand({
|
|
1895
|
+
type: 'task.get',
|
|
1896
|
+
taskId,
|
|
1897
|
+
});
|
|
1898
|
+
return requireParsed(
|
|
1899
|
+
result['task'],
|
|
1900
|
+
parseTaskRecord,
|
|
1901
|
+
'control-plane task.get returned malformed task',
|
|
1902
|
+
);
|
|
1903
|
+
}
|
|
1904
|
+
|
|
1905
|
+
async listTasks(query: AgentTaskListQuery = {}): Promise<readonly AgentTask[]> {
|
|
1906
|
+
const result = await this.client.sendCommand({
|
|
1907
|
+
type: 'task.list',
|
|
1908
|
+
...optionalField('tenantId', query.tenantId),
|
|
1909
|
+
...optionalField('userId', query.userId),
|
|
1910
|
+
...optionalField('workspaceId', query.workspaceId),
|
|
1911
|
+
...optionalField('repositoryId', query.repositoryId),
|
|
1912
|
+
...optionalField('projectId', query.projectId),
|
|
1913
|
+
...optionalField('scopeKind', query.scopeKind),
|
|
1914
|
+
...optionalField('status', query.status),
|
|
1915
|
+
...optionalField('limit', query.limit),
|
|
1916
|
+
});
|
|
1917
|
+
return requireParsedArray(
|
|
1918
|
+
result['tasks'],
|
|
1919
|
+
parseTaskRecord,
|
|
1920
|
+
'control-plane task.list returned malformed tasks',
|
|
1921
|
+
);
|
|
1922
|
+
}
|
|
1923
|
+
|
|
1924
|
+
async updateTask(taskId: string, update: AgentTaskUpdateInput): Promise<AgentTask> {
|
|
1925
|
+
const result = await this.client.sendCommand({
|
|
1926
|
+
type: 'task.update',
|
|
1927
|
+
taskId,
|
|
1928
|
+
...update,
|
|
1929
|
+
});
|
|
1930
|
+
return requireParsed(
|
|
1931
|
+
result['task'],
|
|
1932
|
+
parseTaskRecord,
|
|
1933
|
+
'control-plane task.update returned malformed task',
|
|
1934
|
+
);
|
|
1935
|
+
}
|
|
1936
|
+
|
|
1937
|
+
async deleteTask(taskId: string): Promise<{ deleted: boolean }> {
|
|
1938
|
+
const result = await this.client.sendCommand({
|
|
1939
|
+
type: 'task.delete',
|
|
1940
|
+
taskId,
|
|
1941
|
+
});
|
|
1942
|
+
const deleted = requireBoolean(
|
|
1943
|
+
result['deleted'],
|
|
1944
|
+
'control-plane task.delete returned malformed response',
|
|
1945
|
+
);
|
|
1946
|
+
return {
|
|
1947
|
+
deleted,
|
|
1948
|
+
};
|
|
1949
|
+
}
|
|
1950
|
+
|
|
1951
|
+
async claimTask(input: AgentTaskClaimInput): Promise<AgentTask> {
|
|
1952
|
+
const result = await this.client.sendCommand({
|
|
1953
|
+
type: 'task.claim',
|
|
1954
|
+
taskId: input.taskId,
|
|
1955
|
+
controllerId: input.controllerId,
|
|
1956
|
+
...optionalField('directoryId', input.projectId),
|
|
1957
|
+
...optionalField('branchName', input.branchName),
|
|
1958
|
+
...optionalField('baseBranch', input.baseBranch),
|
|
1959
|
+
});
|
|
1960
|
+
return requireParsed(
|
|
1961
|
+
result['task'],
|
|
1962
|
+
parseTaskRecord,
|
|
1963
|
+
'control-plane task.claim returned malformed task',
|
|
1964
|
+
);
|
|
1965
|
+
}
|
|
1966
|
+
|
|
1967
|
+
async pullTask(input: AgentTaskPullInput): Promise<{
|
|
1968
|
+
task: AgentTask | null;
|
|
1969
|
+
directoryId: string | null;
|
|
1970
|
+
availability: string;
|
|
1971
|
+
reason: string | null;
|
|
1972
|
+
settings: AgentProjectSettings | null;
|
|
1973
|
+
repositoryId: string | null;
|
|
1974
|
+
}> {
|
|
1975
|
+
const result = await this.client.sendCommand({
|
|
1976
|
+
type: 'task.pull',
|
|
1977
|
+
...optionalField('tenantId', input.tenantId),
|
|
1978
|
+
...optionalField('userId', input.userId),
|
|
1979
|
+
...optionalField('workspaceId', input.workspaceId),
|
|
1980
|
+
controllerId: input.controllerId,
|
|
1981
|
+
...optionalField('directoryId', input.projectId),
|
|
1982
|
+
...optionalField('repositoryId', input.repositoryId),
|
|
1983
|
+
...optionalField('branchName', input.branchName),
|
|
1984
|
+
...optionalField('baseBranch', input.baseBranch),
|
|
1985
|
+
});
|
|
1986
|
+
const taskRaw = result['task'];
|
|
1987
|
+
const task =
|
|
1988
|
+
taskRaw === null || taskRaw === undefined
|
|
1989
|
+
? null
|
|
1990
|
+
: requireParsed(
|
|
1991
|
+
taskRaw,
|
|
1992
|
+
parseTaskRecord,
|
|
1993
|
+
'control-plane task.pull returned malformed task',
|
|
1994
|
+
);
|
|
1995
|
+
const directoryId = readNullableString(result['directoryId']);
|
|
1996
|
+
const availability = result['availability'];
|
|
1997
|
+
const reason = readNullableString(result['reason']);
|
|
1998
|
+
const settingsRaw = result['settings'];
|
|
1999
|
+
const settings =
|
|
2000
|
+
settingsRaw === null || settingsRaw === undefined
|
|
2001
|
+
? null
|
|
2002
|
+
: requireParsed(
|
|
2003
|
+
settingsRaw,
|
|
2004
|
+
parseProjectSettingsRecord,
|
|
2005
|
+
'control-plane task.pull returned malformed settings',
|
|
2006
|
+
);
|
|
2007
|
+
const repositoryId = readNullableString(result['repositoryId']);
|
|
2008
|
+
if (
|
|
2009
|
+
directoryId === undefined ||
|
|
2010
|
+
typeof availability !== 'string' ||
|
|
2011
|
+
reason === undefined ||
|
|
2012
|
+
repositoryId === undefined
|
|
2013
|
+
) {
|
|
2014
|
+
throw new Error('control-plane task.pull returned malformed response');
|
|
2015
|
+
}
|
|
2016
|
+
return {
|
|
2017
|
+
task,
|
|
2018
|
+
directoryId,
|
|
2019
|
+
availability,
|
|
2020
|
+
reason,
|
|
2021
|
+
settings,
|
|
2022
|
+
repositoryId,
|
|
2023
|
+
};
|
|
2024
|
+
}
|
|
2025
|
+
|
|
2026
|
+
async completeTask(taskId: string): Promise<AgentTask> {
|
|
2027
|
+
const result = await this.client.sendCommand({
|
|
2028
|
+
type: 'task.complete',
|
|
2029
|
+
taskId,
|
|
2030
|
+
});
|
|
2031
|
+
return requireParsed(
|
|
2032
|
+
result['task'],
|
|
2033
|
+
parseTaskRecord,
|
|
2034
|
+
'control-plane task.complete returned malformed task',
|
|
2035
|
+
);
|
|
2036
|
+
}
|
|
2037
|
+
|
|
2038
|
+
async readyTask(taskId: string): Promise<AgentTask> {
|
|
2039
|
+
const result = await this.client.sendCommand({
|
|
2040
|
+
type: 'task.ready',
|
|
2041
|
+
taskId,
|
|
2042
|
+
});
|
|
2043
|
+
return requireParsed(
|
|
2044
|
+
result['task'],
|
|
2045
|
+
parseTaskRecord,
|
|
2046
|
+
'control-plane task.ready returned malformed task',
|
|
2047
|
+
);
|
|
2048
|
+
}
|
|
2049
|
+
|
|
2050
|
+
async draftTask(taskId: string): Promise<AgentTask> {
|
|
2051
|
+
const result = await this.client.sendCommand({
|
|
2052
|
+
type: 'task.draft',
|
|
2053
|
+
taskId,
|
|
2054
|
+
});
|
|
2055
|
+
return requireParsed(
|
|
2056
|
+
result['task'],
|
|
2057
|
+
parseTaskRecord,
|
|
2058
|
+
'control-plane task.draft returned malformed task',
|
|
2059
|
+
);
|
|
2060
|
+
}
|
|
2061
|
+
|
|
2062
|
+
async queueTask(taskId: string): Promise<AgentTask> {
|
|
2063
|
+
const result = await this.client.sendCommand({
|
|
2064
|
+
type: 'task.queue',
|
|
2065
|
+
taskId,
|
|
2066
|
+
});
|
|
2067
|
+
return requireParsed(
|
|
2068
|
+
result['task'],
|
|
2069
|
+
parseTaskRecord,
|
|
2070
|
+
'control-plane task.queue returned malformed task',
|
|
2071
|
+
);
|
|
2072
|
+
}
|
|
2073
|
+
|
|
2074
|
+
async reorderTasks(input: AgentTaskReorderInput): Promise<readonly AgentTask[]> {
|
|
2075
|
+
const result = await this.client.sendCommand({
|
|
2076
|
+
type: 'task.reorder',
|
|
2077
|
+
tenantId: input.tenantId,
|
|
2078
|
+
userId: input.userId,
|
|
2079
|
+
workspaceId: input.workspaceId,
|
|
2080
|
+
orderedTaskIds: [...input.orderedTaskIds],
|
|
2081
|
+
});
|
|
2082
|
+
return requireParsedArray(
|
|
2083
|
+
result['tasks'],
|
|
2084
|
+
parseTaskRecord,
|
|
2085
|
+
'control-plane task.reorder returned malformed tasks',
|
|
2086
|
+
);
|
|
2087
|
+
}
|
|
2088
|
+
|
|
2089
|
+
async getAutomationPolicy(input: {
|
|
2090
|
+
scope: 'global' | 'repository' | 'project';
|
|
2091
|
+
scopeId?: string;
|
|
2092
|
+
tenantId?: string;
|
|
2093
|
+
userId?: string;
|
|
2094
|
+
workspaceId?: string;
|
|
2095
|
+
}): Promise<AgentAutomationPolicy> {
|
|
2096
|
+
const result = await this.client.sendCommand({
|
|
2097
|
+
type: 'automation.policy-get',
|
|
2098
|
+
scope: input.scope,
|
|
2099
|
+
...optionalField('scopeId', input.scopeId),
|
|
2100
|
+
...optionalField('tenantId', input.tenantId),
|
|
2101
|
+
...optionalField('userId', input.userId),
|
|
2102
|
+
...optionalField('workspaceId', input.workspaceId),
|
|
2103
|
+
});
|
|
2104
|
+
return requireParsed(
|
|
2105
|
+
result['policy'],
|
|
2106
|
+
parseAutomationPolicyRecord,
|
|
2107
|
+
'control-plane automation.policy-get returned malformed policy',
|
|
2108
|
+
);
|
|
2109
|
+
}
|
|
2110
|
+
|
|
2111
|
+
async setAutomationPolicy(input: {
|
|
2112
|
+
scope: 'global' | 'repository' | 'project';
|
|
2113
|
+
scopeId?: string;
|
|
2114
|
+
tenantId?: string;
|
|
2115
|
+
userId?: string;
|
|
2116
|
+
workspaceId?: string;
|
|
2117
|
+
automationEnabled?: boolean;
|
|
2118
|
+
frozen?: boolean;
|
|
2119
|
+
}): Promise<AgentAutomationPolicy> {
|
|
2120
|
+
const result = await this.client.sendCommand({
|
|
2121
|
+
type: 'automation.policy-set',
|
|
2122
|
+
scope: input.scope,
|
|
2123
|
+
...optionalField('scopeId', input.scopeId),
|
|
2124
|
+
...optionalField('tenantId', input.tenantId),
|
|
2125
|
+
...optionalField('userId', input.userId),
|
|
2126
|
+
...optionalField('workspaceId', input.workspaceId),
|
|
2127
|
+
...optionalField('automationEnabled', input.automationEnabled),
|
|
2128
|
+
...optionalField('frozen', input.frozen),
|
|
2129
|
+
});
|
|
2130
|
+
return requireParsed(
|
|
2131
|
+
result['policy'],
|
|
2132
|
+
parseAutomationPolicyRecord,
|
|
2133
|
+
'control-plane automation.policy-set returned malformed policy',
|
|
2134
|
+
);
|
|
2135
|
+
}
|
|
2136
|
+
|
|
2137
|
+
async listSessions(
|
|
2138
|
+
command: {
|
|
2139
|
+
tenantId?: string;
|
|
2140
|
+
userId?: string;
|
|
2141
|
+
workspaceId?: string;
|
|
2142
|
+
worktreeId?: string;
|
|
2143
|
+
status?: 'running' | 'needs-input' | 'completed' | 'exited';
|
|
2144
|
+
live?: boolean;
|
|
2145
|
+
sort?: 'attention-first' | 'started-desc' | 'started-asc';
|
|
2146
|
+
limit?: number;
|
|
2147
|
+
} = {},
|
|
2148
|
+
): Promise<readonly AgentSessionSummary[]> {
|
|
2149
|
+
const result = await this.client.sendCommand({
|
|
2150
|
+
type: 'session.list',
|
|
2151
|
+
...command,
|
|
2152
|
+
});
|
|
2153
|
+
const parsed = parseSessionSummaryList(result['sessions']);
|
|
2154
|
+
return parsed;
|
|
2155
|
+
}
|
|
2156
|
+
|
|
2157
|
+
async sessionStatus(sessionId: string): Promise<AgentSessionSummary> {
|
|
2158
|
+
const result = await this.client.sendCommand({
|
|
2159
|
+
type: 'session.status',
|
|
2160
|
+
sessionId,
|
|
2161
|
+
});
|
|
2162
|
+
const parsed = parseSessionSummaryRecord(result);
|
|
2163
|
+
if (parsed === null) {
|
|
2164
|
+
throw new Error('control-plane session.status returned malformed summary');
|
|
2165
|
+
}
|
|
2166
|
+
return parsed;
|
|
2167
|
+
}
|
|
2168
|
+
|
|
2169
|
+
async claimSession(input: AgentClaimSessionInput): Promise<AgentSessionClaimResult> {
|
|
2170
|
+
const command: {
|
|
2171
|
+
type: 'session.claim';
|
|
2172
|
+
sessionId: string;
|
|
2173
|
+
controllerId: string;
|
|
2174
|
+
controllerType: StreamSessionControllerType;
|
|
2175
|
+
controllerLabel?: string;
|
|
2176
|
+
reason?: string;
|
|
2177
|
+
takeover?: boolean;
|
|
2178
|
+
} = {
|
|
2179
|
+
type: 'session.claim',
|
|
2180
|
+
sessionId: input.sessionId,
|
|
2181
|
+
controllerId: input.controllerId,
|
|
2182
|
+
controllerType: input.controllerType,
|
|
2183
|
+
};
|
|
2184
|
+
if (input.controllerLabel !== undefined) {
|
|
2185
|
+
command.controllerLabel = input.controllerLabel;
|
|
2186
|
+
}
|
|
2187
|
+
if (input.reason !== undefined) {
|
|
2188
|
+
command.reason = input.reason;
|
|
2189
|
+
}
|
|
2190
|
+
if (input.takeover !== undefined) {
|
|
2191
|
+
command.takeover = input.takeover;
|
|
2192
|
+
}
|
|
2193
|
+
const result = await this.client.sendCommand(command);
|
|
2194
|
+
return parseClaimResult(result);
|
|
2195
|
+
}
|
|
2196
|
+
|
|
2197
|
+
async takeoverSession(
|
|
2198
|
+
input: Omit<AgentClaimSessionInput, 'takeover'>,
|
|
2199
|
+
): Promise<AgentSessionClaimResult> {
|
|
2200
|
+
return await this.claimSession({
|
|
2201
|
+
...input,
|
|
2202
|
+
takeover: true,
|
|
2203
|
+
});
|
|
2204
|
+
}
|
|
2205
|
+
|
|
2206
|
+
async releaseSession(input: AgentReleaseSessionInput): Promise<AgentSessionReleaseResult> {
|
|
2207
|
+
const command: {
|
|
2208
|
+
type: 'session.release';
|
|
2209
|
+
sessionId: string;
|
|
2210
|
+
reason?: string;
|
|
2211
|
+
} = {
|
|
2212
|
+
type: 'session.release',
|
|
2213
|
+
sessionId: input.sessionId,
|
|
2214
|
+
};
|
|
2215
|
+
if (input.reason !== undefined) {
|
|
2216
|
+
command.reason = input.reason;
|
|
2217
|
+
}
|
|
2218
|
+
const result = await this.client.sendCommand(command);
|
|
2219
|
+
return parseReleaseResult(result);
|
|
2220
|
+
}
|
|
2221
|
+
|
|
2222
|
+
async respond(
|
|
2223
|
+
sessionId: string,
|
|
2224
|
+
text: string,
|
|
2225
|
+
): Promise<{ responded: boolean; sentBytes: number }> {
|
|
2226
|
+
const result = await this.client.sendCommand({
|
|
2227
|
+
type: 'session.respond',
|
|
2228
|
+
sessionId,
|
|
2229
|
+
text,
|
|
2230
|
+
});
|
|
2231
|
+
const responded = result['responded'];
|
|
2232
|
+
const sentBytes = result['sentBytes'];
|
|
2233
|
+
if (typeof responded !== 'boolean' || typeof sentBytes !== 'number') {
|
|
2234
|
+
throw new Error('control-plane session.respond returned malformed response');
|
|
2235
|
+
}
|
|
2236
|
+
return {
|
|
2237
|
+
responded,
|
|
2238
|
+
sentBytes,
|
|
2239
|
+
};
|
|
2240
|
+
}
|
|
2241
|
+
|
|
2242
|
+
async interrupt(sessionId: string): Promise<{ interrupted: boolean }> {
|
|
2243
|
+
const result = await this.client.sendCommand({
|
|
2244
|
+
type: 'session.interrupt',
|
|
2245
|
+
sessionId,
|
|
2246
|
+
});
|
|
2247
|
+
const interrupted = result['interrupted'];
|
|
2248
|
+
if (typeof interrupted !== 'boolean') {
|
|
2249
|
+
throw new Error('control-plane session.interrupt returned malformed response');
|
|
2250
|
+
}
|
|
2251
|
+
return {
|
|
2252
|
+
interrupted,
|
|
2253
|
+
};
|
|
2254
|
+
}
|
|
2255
|
+
|
|
2256
|
+
async removeSession(sessionId: string): Promise<{ removed: boolean }> {
|
|
2257
|
+
const result = await this.client.sendCommand({
|
|
2258
|
+
type: 'session.remove',
|
|
2259
|
+
sessionId,
|
|
2260
|
+
});
|
|
2261
|
+
const removed = result['removed'];
|
|
2262
|
+
if (typeof removed !== 'boolean') {
|
|
2263
|
+
throw new Error('control-plane session.remove returned malformed response');
|
|
2264
|
+
}
|
|
2265
|
+
return {
|
|
2266
|
+
removed,
|
|
2267
|
+
};
|
|
2268
|
+
}
|
|
2269
|
+
|
|
2270
|
+
async startSession(input: {
|
|
2271
|
+
sessionId: string;
|
|
2272
|
+
args: string[];
|
|
2273
|
+
env?: Record<string, string>;
|
|
2274
|
+
cwd?: string;
|
|
2275
|
+
initialCols: number;
|
|
2276
|
+
initialRows: number;
|
|
2277
|
+
terminalForegroundHex?: string;
|
|
2278
|
+
terminalBackgroundHex?: string;
|
|
2279
|
+
tenantId?: string;
|
|
2280
|
+
userId?: string;
|
|
2281
|
+
workspaceId?: string;
|
|
2282
|
+
worktreeId?: string;
|
|
2283
|
+
}): Promise<{ sessionId: string }> {
|
|
2284
|
+
const result = await this.client.sendCommand({
|
|
2285
|
+
type: 'pty.start',
|
|
2286
|
+
...input,
|
|
2287
|
+
});
|
|
2288
|
+
const sessionId = result['sessionId'];
|
|
2289
|
+
if (typeof sessionId !== 'string') {
|
|
2290
|
+
throw new Error('control-plane pty.start returned malformed response');
|
|
2291
|
+
}
|
|
2292
|
+
return {
|
|
2293
|
+
sessionId,
|
|
2294
|
+
};
|
|
2295
|
+
}
|
|
2296
|
+
|
|
2297
|
+
async attachSession(sessionId: string, sinceCursor = 0): Promise<{ latestCursor: number }> {
|
|
2298
|
+
const result = await this.client.sendCommand({
|
|
2299
|
+
type: 'pty.attach',
|
|
2300
|
+
sessionId,
|
|
2301
|
+
sinceCursor,
|
|
2302
|
+
});
|
|
2303
|
+
const latestCursor = result['latestCursor'];
|
|
2304
|
+
if (typeof latestCursor !== 'number') {
|
|
2305
|
+
throw new Error('control-plane pty.attach returned malformed response');
|
|
2306
|
+
}
|
|
2307
|
+
return {
|
|
2308
|
+
latestCursor,
|
|
2309
|
+
};
|
|
2310
|
+
}
|
|
2311
|
+
|
|
2312
|
+
async detachSession(sessionId: string): Promise<{ detached: boolean }> {
|
|
2313
|
+
const result = await this.client.sendCommand({
|
|
2314
|
+
type: 'pty.detach',
|
|
2315
|
+
sessionId,
|
|
2316
|
+
});
|
|
2317
|
+
const detached = result['detached'];
|
|
2318
|
+
if (typeof detached !== 'boolean') {
|
|
2319
|
+
throw new Error('control-plane pty.detach returned malformed response');
|
|
2320
|
+
}
|
|
2321
|
+
return {
|
|
2322
|
+
detached,
|
|
2323
|
+
};
|
|
2324
|
+
}
|
|
2325
|
+
|
|
2326
|
+
async subscribeSessionEvents(sessionId: string): Promise<{ subscribed: boolean }> {
|
|
2327
|
+
const result = await this.client.sendCommand({
|
|
2328
|
+
type: 'pty.subscribe-events',
|
|
2329
|
+
sessionId,
|
|
2330
|
+
});
|
|
2331
|
+
const subscribed = requireBoolean(
|
|
2332
|
+
result['subscribed'],
|
|
2333
|
+
'control-plane pty.subscribe-events returned malformed response',
|
|
2334
|
+
);
|
|
2335
|
+
return {
|
|
2336
|
+
subscribed,
|
|
2337
|
+
};
|
|
2338
|
+
}
|
|
2339
|
+
|
|
2340
|
+
async unsubscribeSessionEvents(sessionId: string): Promise<{ subscribed: boolean }> {
|
|
2341
|
+
const result = await this.client.sendCommand({
|
|
2342
|
+
type: 'pty.unsubscribe-events',
|
|
2343
|
+
sessionId,
|
|
2344
|
+
});
|
|
2345
|
+
const subscribed = requireBoolean(
|
|
2346
|
+
result['subscribed'],
|
|
2347
|
+
'control-plane pty.unsubscribe-events returned malformed response',
|
|
2348
|
+
);
|
|
2349
|
+
return {
|
|
2350
|
+
subscribed,
|
|
2351
|
+
};
|
|
2352
|
+
}
|
|
2353
|
+
|
|
2354
|
+
async closeSession(sessionId: string): Promise<{ closed: boolean }> {
|
|
2355
|
+
const result = await this.client.sendCommand({
|
|
2356
|
+
type: 'pty.close',
|
|
2357
|
+
sessionId,
|
|
2358
|
+
});
|
|
2359
|
+
const closed = result['closed'];
|
|
2360
|
+
if (typeof closed !== 'boolean') {
|
|
2361
|
+
throw new Error('control-plane pty.close returned malformed response');
|
|
2362
|
+
}
|
|
2363
|
+
return {
|
|
2364
|
+
closed,
|
|
2365
|
+
};
|
|
2366
|
+
}
|
|
2367
|
+
|
|
2368
|
+
sendInput(sessionId: string, data: string | Buffer): void {
|
|
2369
|
+
const chunk = typeof data === 'string' ? Buffer.from(data, 'utf8') : data;
|
|
2370
|
+
this.client.sendInput(sessionId, chunk);
|
|
2371
|
+
}
|
|
2372
|
+
|
|
2373
|
+
sendResize(sessionId: string, cols: number, rows: number): void {
|
|
2374
|
+
this.client.sendResize(sessionId, cols, rows);
|
|
2375
|
+
}
|
|
2376
|
+
|
|
2377
|
+
sendSignal(sessionId: string, signal: StreamSignal): void {
|
|
2378
|
+
this.client.sendSignal(sessionId, signal);
|
|
2379
|
+
}
|
|
2380
|
+
|
|
2381
|
+
async close(): Promise<void> {
|
|
2382
|
+
if (this.closed) {
|
|
2383
|
+
return;
|
|
2384
|
+
}
|
|
2385
|
+
this.closed = true;
|
|
2386
|
+
this.removeEnvelopeListener();
|
|
2387
|
+
const subscriptions = [...this.subscriptionIds];
|
|
2388
|
+
for (const subscriptionId of subscriptions) {
|
|
2389
|
+
try {
|
|
2390
|
+
await this.client.sendCommand({
|
|
2391
|
+
type: 'stream.unsubscribe',
|
|
2392
|
+
subscriptionId,
|
|
2393
|
+
});
|
|
2394
|
+
} catch {
|
|
2395
|
+
// Best-effort unsubscribe only.
|
|
2396
|
+
}
|
|
2397
|
+
this.subscriptionIds.delete(subscriptionId);
|
|
2398
|
+
}
|
|
2399
|
+
this.client.close();
|
|
2400
|
+
}
|
|
2401
|
+
|
|
2402
|
+
private hasSubscription(subscriptionId: string): boolean {
|
|
2403
|
+
return this.subscriptionIds.has(subscriptionId);
|
|
2404
|
+
}
|
|
2405
|
+
|
|
2406
|
+
private dispatch(subscriptionId: string, cursor: number, observed: StreamObservedEvent): void {
|
|
2407
|
+
const type = mapObservedEventType(observed);
|
|
2408
|
+
const envelope = {
|
|
2409
|
+
type,
|
|
2410
|
+
subscriptionId,
|
|
2411
|
+
cursor,
|
|
2412
|
+
observed,
|
|
2413
|
+
} as AgentRealtimeEventEnvelope;
|
|
2414
|
+
const specific = this.listenersByType.get(type);
|
|
2415
|
+
if (specific !== undefined) {
|
|
2416
|
+
for (const handler of specific) {
|
|
2417
|
+
this.invokeHandler(handler, envelope);
|
|
2418
|
+
}
|
|
2419
|
+
}
|
|
2420
|
+
const wildcard = this.listenersByType.get('*');
|
|
2421
|
+
if (wildcard !== undefined) {
|
|
2422
|
+
for (const handler of wildcard) {
|
|
2423
|
+
this.invokeHandler(handler, envelope);
|
|
2424
|
+
}
|
|
2425
|
+
}
|
|
2426
|
+
}
|
|
2427
|
+
|
|
2428
|
+
private invokeHandler(handler: AnyRealtimeListener, event: AgentRealtimeEventEnvelope): void {
|
|
2429
|
+
void Promise.resolve(handler(event)).catch((error: unknown) => {
|
|
2430
|
+
if (this.onHandlerError !== undefined) {
|
|
2431
|
+
this.onHandlerError(error, event);
|
|
2432
|
+
}
|
|
2433
|
+
});
|
|
2434
|
+
}
|
|
2435
|
+
}
|
|
2436
|
+
|
|
2437
|
+
export async function connectHarnessAgentRealtimeClient(
|
|
2438
|
+
options: AgentRealtimeConnectOptions,
|
|
2439
|
+
): Promise<HarnessAgentRealtimeClient> {
|
|
2440
|
+
return await HarnessAgentRealtimeClient.connect(options);
|
|
2441
|
+
}
|