@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,88 @@
|
|
|
1
|
+
import { refreshProcessUsageSnapshots } from '../mux/live-mux/process-usage.ts';
|
|
2
|
+
|
|
3
|
+
type PerfAttrs = Record<string, boolean | number | string>;
|
|
4
|
+
|
|
5
|
+
interface PerfSpanLike {
|
|
6
|
+
end(attrs: PerfAttrs): void;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
type StartPerfSpanLike = (name: string, attrs: PerfAttrs) => PerfSpanLike;
|
|
10
|
+
|
|
11
|
+
interface RefreshProcessUsageSnapshotsResult {
|
|
12
|
+
readonly samples: number;
|
|
13
|
+
readonly changed: boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface RefreshProcessUsageSnapshotsFn<TConversation, TSample> {
|
|
17
|
+
(options: {
|
|
18
|
+
conversations: ReadonlyMap<string, TConversation>;
|
|
19
|
+
processUsageBySessionId: Map<string, TSample>;
|
|
20
|
+
readProcessUsageSample: (processId: number | null) => Promise<TSample>;
|
|
21
|
+
processIdForConversation: (conversation: TConversation) => number | null;
|
|
22
|
+
processUsageEqual: (left: TSample, right: TSample) => boolean;
|
|
23
|
+
}): Promise<RefreshProcessUsageSnapshotsResult>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface ProcessUsageRefreshServiceOptions<TConversation, TSample> {
|
|
27
|
+
readonly readProcessUsageSample: (processId: number | null) => Promise<TSample>;
|
|
28
|
+
readonly processIdForConversation: (conversation: TConversation) => number | null;
|
|
29
|
+
readonly processUsageEqual: (left: TSample, right: TSample) => boolean;
|
|
30
|
+
readonly startPerfSpan: StartPerfSpanLike;
|
|
31
|
+
readonly onChanged: () => void;
|
|
32
|
+
readonly refreshSnapshots?: RefreshProcessUsageSnapshotsFn<TConversation, TSample>;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export class ProcessUsageRefreshService<TConversation, TSample> {
|
|
36
|
+
private readonly processUsageBySessionId = new Map<string, TSample>();
|
|
37
|
+
private refreshInFlight = false;
|
|
38
|
+
private readonly refreshSnapshots: RefreshProcessUsageSnapshotsFn<TConversation, TSample>;
|
|
39
|
+
|
|
40
|
+
constructor(private readonly options: ProcessUsageRefreshServiceOptions<TConversation, TSample>) {
|
|
41
|
+
this.refreshSnapshots = options.refreshSnapshots ?? refreshProcessUsageSnapshots;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
getSample(sessionId: string): TSample | undefined {
|
|
45
|
+
return this.processUsageBySessionId.get(sessionId);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
deleteSession(sessionId: string): void {
|
|
49
|
+
this.processUsageBySessionId.delete(sessionId);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
readonlyUsage(): ReadonlyMap<string, TSample> {
|
|
53
|
+
return this.processUsageBySessionId;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async refresh(
|
|
57
|
+
reason: 'startup' | 'interval',
|
|
58
|
+
conversations: ReadonlyMap<string, TConversation>,
|
|
59
|
+
): Promise<void> {
|
|
60
|
+
if (this.refreshInFlight) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
this.refreshInFlight = true;
|
|
64
|
+
const usageSpan = this.options.startPerfSpan('mux.background.process-usage', {
|
|
65
|
+
reason,
|
|
66
|
+
conversations: conversations.size,
|
|
67
|
+
});
|
|
68
|
+
try {
|
|
69
|
+
const refreshed = await this.refreshSnapshots({
|
|
70
|
+
conversations,
|
|
71
|
+
processUsageBySessionId: this.processUsageBySessionId,
|
|
72
|
+
readProcessUsageSample: this.options.readProcessUsageSample,
|
|
73
|
+
processIdForConversation: this.options.processIdForConversation,
|
|
74
|
+
processUsageEqual: this.options.processUsageEqual,
|
|
75
|
+
});
|
|
76
|
+
if (refreshed.changed) {
|
|
77
|
+
this.options.onChanged();
|
|
78
|
+
}
|
|
79
|
+
usageSpan.end({
|
|
80
|
+
reason,
|
|
81
|
+
samples: refreshed.samples,
|
|
82
|
+
changed: refreshed.changed,
|
|
83
|
+
});
|
|
84
|
+
} finally {
|
|
85
|
+
this.refreshInFlight = false;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
interface RecordingWriter {
|
|
2
|
+
close(): Promise<void>;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
interface RenderTerminalRecordingToGifInput {
|
|
6
|
+
readonly recordingPath: string;
|
|
7
|
+
readonly outputPath: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
interface RecordingServiceOptions {
|
|
11
|
+
readonly recordingWriter: RecordingWriter | null;
|
|
12
|
+
readonly recordingPath: string | null;
|
|
13
|
+
readonly recordingGifOutputPath: string | null;
|
|
14
|
+
readonly renderTerminalRecordingToGif: (
|
|
15
|
+
input: RenderTerminalRecordingToGifInput,
|
|
16
|
+
) => Promise<unknown>;
|
|
17
|
+
readonly writeStderr: (text: string) => void;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export class RecordingService {
|
|
21
|
+
constructor(private readonly options: RecordingServiceOptions) {}
|
|
22
|
+
|
|
23
|
+
async closeWriter(): Promise<unknown | null> {
|
|
24
|
+
if (this.options.recordingWriter === null) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
try {
|
|
28
|
+
await this.options.recordingWriter.close();
|
|
29
|
+
return null;
|
|
30
|
+
} catch (error: unknown) {
|
|
31
|
+
return error;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async finalizeAfterShutdown(recordingCloseError: unknown | null): Promise<void> {
|
|
36
|
+
if (
|
|
37
|
+
this.options.recordingGifOutputPath !== null &&
|
|
38
|
+
this.options.recordingPath !== null &&
|
|
39
|
+
recordingCloseError === null
|
|
40
|
+
) {
|
|
41
|
+
try {
|
|
42
|
+
await this.options.renderTerminalRecordingToGif({
|
|
43
|
+
recordingPath: this.options.recordingPath,
|
|
44
|
+
outputPath: this.options.recordingGifOutputPath,
|
|
45
|
+
});
|
|
46
|
+
this.options.writeStderr(
|
|
47
|
+
`[mux-recording] jsonl=${this.options.recordingPath} gif=${this.options.recordingGifOutputPath}\n`,
|
|
48
|
+
);
|
|
49
|
+
} catch (error: unknown) {
|
|
50
|
+
this.options.writeStderr(
|
|
51
|
+
`[mux-recording] gif-export-failed ${
|
|
52
|
+
error instanceof Error ? error.message : String(error)
|
|
53
|
+
}\n`,
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (recordingCloseError !== null) {
|
|
60
|
+
this.options.writeStderr(
|
|
61
|
+
`[mux-recording] close-failed ${this.formatCloseError(recordingCloseError)}\n`,
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
private formatCloseError(recordingCloseError: unknown): string {
|
|
67
|
+
if (recordingCloseError instanceof Error) {
|
|
68
|
+
return recordingCloseError.message;
|
|
69
|
+
}
|
|
70
|
+
if (typeof recordingCloseError === 'string') {
|
|
71
|
+
return recordingCloseError;
|
|
72
|
+
}
|
|
73
|
+
return 'unknown error';
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { closeSync, existsSync, mkdirSync, openSync, readFileSync, writeSync } from 'node:fs';
|
|
2
|
+
import { dirname, resolve } from 'node:path';
|
|
3
|
+
import {
|
|
4
|
+
parseActiveRenderTraceState,
|
|
5
|
+
type ActiveRenderTraceState,
|
|
6
|
+
} from '../mux/live-mux/render-trace-state.ts';
|
|
7
|
+
|
|
8
|
+
export interface RenderTraceLabels {
|
|
9
|
+
readonly repositoryId: string | null;
|
|
10
|
+
readonly repositoryName: string | null;
|
|
11
|
+
readonly projectId: string | null;
|
|
12
|
+
readonly projectPath: string | null;
|
|
13
|
+
readonly threadId: string | null;
|
|
14
|
+
readonly threadTitle: string | null;
|
|
15
|
+
readonly agentType: string | null;
|
|
16
|
+
readonly conversationId: string | null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface RenderTraceRecordInput {
|
|
20
|
+
readonly direction: 'incoming' | 'outgoing';
|
|
21
|
+
readonly source: string;
|
|
22
|
+
readonly eventType: string;
|
|
23
|
+
readonly labels: RenderTraceLabels;
|
|
24
|
+
readonly payload: unknown;
|
|
25
|
+
readonly dedupeKey?: string;
|
|
26
|
+
readonly dedupeValue?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
interface RenderTraceRecorderOptions {
|
|
30
|
+
readonly statePath: string;
|
|
31
|
+
readonly nowMs?: () => number;
|
|
32
|
+
readonly nowIso?: () => string;
|
|
33
|
+
readonly refreshIntervalMs?: number;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const DEFAULT_REFRESH_INTERVAL_MS = 250;
|
|
37
|
+
|
|
38
|
+
export class RenderTraceRecorder {
|
|
39
|
+
private readonly nowMs: () => number;
|
|
40
|
+
private readonly nowIso: () => string;
|
|
41
|
+
private readonly refreshIntervalMs: number;
|
|
42
|
+
private nextRefreshAtMs = 0;
|
|
43
|
+
private activeState: ActiveRenderTraceState | null = null;
|
|
44
|
+
private activeOutputPath: string | null = null;
|
|
45
|
+
private outputFd: number | null = null;
|
|
46
|
+
private readonly dedupeValueByKey = new Map<string, string>();
|
|
47
|
+
|
|
48
|
+
constructor(private readonly options: RenderTraceRecorderOptions) {
|
|
49
|
+
this.nowMs = options.nowMs ?? Date.now;
|
|
50
|
+
this.nowIso = options.nowIso ?? (() => new Date().toISOString());
|
|
51
|
+
this.refreshIntervalMs = options.refreshIntervalMs ?? DEFAULT_REFRESH_INTERVAL_MS;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
isActive(): boolean {
|
|
55
|
+
this.refreshIfDue();
|
|
56
|
+
return this.outputFd !== null && this.activeState !== null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
shouldCaptureConversation(conversationId: string | null): boolean {
|
|
60
|
+
this.refreshIfDue();
|
|
61
|
+
if (this.outputFd === null || this.activeState === null) {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
if (this.activeState.conversationId === null) {
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
return conversationId === this.activeState.conversationId;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
record(input: RenderTraceRecordInput): void {
|
|
71
|
+
this.refreshIfDue();
|
|
72
|
+
if (this.outputFd === null || this.activeState === null) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
if (!this.matchesConversationFilter(input.labels, this.activeState)) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
if (
|
|
79
|
+
input.dedupeKey !== undefined &&
|
|
80
|
+
input.dedupeValue !== undefined &&
|
|
81
|
+
this.dedupeValueByKey.get(input.dedupeKey) === input.dedupeValue
|
|
82
|
+
) {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
if (input.dedupeKey !== undefined && input.dedupeValue !== undefined) {
|
|
86
|
+
this.dedupeValueByKey.set(input.dedupeKey, input.dedupeValue);
|
|
87
|
+
}
|
|
88
|
+
const record = {
|
|
89
|
+
ts: this.nowIso(),
|
|
90
|
+
direction: input.direction,
|
|
91
|
+
source: input.source,
|
|
92
|
+
eventType: input.eventType,
|
|
93
|
+
labels: input.labels,
|
|
94
|
+
payload: input.payload,
|
|
95
|
+
filterConversationId: this.activeState.conversationId,
|
|
96
|
+
};
|
|
97
|
+
try {
|
|
98
|
+
writeSync(this.outputFd, `${JSON.stringify(record)}\n`);
|
|
99
|
+
} catch {
|
|
100
|
+
this.deactivate();
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
close(): void {
|
|
105
|
+
this.deactivate();
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
private matchesConversationFilter(
|
|
109
|
+
labels: RenderTraceLabels,
|
|
110
|
+
state: ActiveRenderTraceState,
|
|
111
|
+
): boolean {
|
|
112
|
+
if (state.conversationId === null) {
|
|
113
|
+
return true;
|
|
114
|
+
}
|
|
115
|
+
if (labels.conversationId === state.conversationId) {
|
|
116
|
+
return true;
|
|
117
|
+
}
|
|
118
|
+
return labels.threadId === state.conversationId;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
private refreshIfDue(): void {
|
|
122
|
+
const now = this.nowMs();
|
|
123
|
+
if (now < this.nextRefreshAtMs) {
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
this.nextRefreshAtMs = now + this.refreshIntervalMs;
|
|
127
|
+
const nextState = this.readState();
|
|
128
|
+
if (nextState === null) {
|
|
129
|
+
this.deactivate();
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
const nextOutputPath = resolve(nextState.outputPath);
|
|
133
|
+
if (this.activeOutputPath === nextOutputPath && this.outputFd !== null) {
|
|
134
|
+
this.activeState = nextState;
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
this.activate(nextState, nextOutputPath);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
private activate(state: ActiveRenderTraceState, outputPath: string): void {
|
|
141
|
+
this.deactivate();
|
|
142
|
+
try {
|
|
143
|
+
mkdirSync(dirname(outputPath), { recursive: true });
|
|
144
|
+
this.outputFd = openSync(outputPath, 'a');
|
|
145
|
+
this.activeOutputPath = outputPath;
|
|
146
|
+
this.activeState = state;
|
|
147
|
+
} catch {
|
|
148
|
+
this.deactivate();
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
private deactivate(): void {
|
|
153
|
+
if (this.outputFd !== null) {
|
|
154
|
+
try {
|
|
155
|
+
closeSync(this.outputFd);
|
|
156
|
+
} catch {
|
|
157
|
+
// Best-effort close only.
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
this.outputFd = null;
|
|
161
|
+
this.activeOutputPath = null;
|
|
162
|
+
this.activeState = null;
|
|
163
|
+
this.dedupeValueByKey.clear();
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
private readState(): ActiveRenderTraceState | null {
|
|
167
|
+
if (!existsSync(this.options.statePath)) {
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
try {
|
|
171
|
+
const raw = JSON.parse(readFileSync(this.options.statePath, 'utf8')) as unknown;
|
|
172
|
+
return parseActiveRenderTraceState(raw);
|
|
173
|
+
} catch {
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
interface RuntimeInterruptResult {
|
|
2
|
+
readonly interrupted: boolean;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
interface RuntimeConversationControlState {
|
|
6
|
+
live: boolean;
|
|
7
|
+
status: string;
|
|
8
|
+
attentionReason: string | null;
|
|
9
|
+
lastEventAt: string | null;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
interface RuntimeGatewayProfilerResult {
|
|
13
|
+
readonly message: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface RuntimeGatewayStatusTimelineResult {
|
|
17
|
+
readonly message: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface RuntimeGatewayRenderTraceResult {
|
|
21
|
+
readonly message: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
interface RuntimeControlActionsOptions<TConversation extends RuntimeConversationControlState> {
|
|
25
|
+
readonly conversationById: (sessionId: string) => TConversation | undefined;
|
|
26
|
+
readonly interruptSession: (sessionId: string) => Promise<RuntimeInterruptResult>;
|
|
27
|
+
readonly nowIso: () => string;
|
|
28
|
+
readonly markDirty: () => void;
|
|
29
|
+
readonly toggleGatewayProfiler: (input: {
|
|
30
|
+
invocationDirectory: string;
|
|
31
|
+
sessionName: string | null;
|
|
32
|
+
}) => Promise<RuntimeGatewayProfilerResult>;
|
|
33
|
+
readonly toggleGatewayStatusTimeline: (input: {
|
|
34
|
+
invocationDirectory: string;
|
|
35
|
+
sessionName: string | null;
|
|
36
|
+
}) => Promise<RuntimeGatewayStatusTimelineResult>;
|
|
37
|
+
readonly toggleGatewayRenderTrace: (input: {
|
|
38
|
+
invocationDirectory: string;
|
|
39
|
+
sessionName: string | null;
|
|
40
|
+
conversationId: string | null;
|
|
41
|
+
}) => Promise<RuntimeGatewayRenderTraceResult>;
|
|
42
|
+
readonly invocationDirectory: string;
|
|
43
|
+
readonly sessionName: string | null;
|
|
44
|
+
readonly setTaskPaneNotice: (message: string) => void;
|
|
45
|
+
readonly setDebugFooterNotice: (message: string) => void;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export class RuntimeControlActions<TConversation extends RuntimeConversationControlState> {
|
|
49
|
+
constructor(private readonly options: RuntimeControlActionsOptions<TConversation>) {}
|
|
50
|
+
|
|
51
|
+
async interruptConversation(sessionId: string): Promise<void> {
|
|
52
|
+
const conversation = this.options.conversationById(sessionId);
|
|
53
|
+
if (conversation === undefined || !conversation.live) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
const result = await this.options.interruptSession(sessionId);
|
|
57
|
+
if (!result.interrupted) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
conversation.status = 'completed';
|
|
61
|
+
conversation.attentionReason = null;
|
|
62
|
+
conversation.lastEventAt = this.options.nowIso();
|
|
63
|
+
this.options.markDirty();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async toggleGatewayProfiler(): Promise<void> {
|
|
67
|
+
try {
|
|
68
|
+
const result = await this.options.toggleGatewayProfiler({
|
|
69
|
+
invocationDirectory: this.options.invocationDirectory,
|
|
70
|
+
sessionName: this.options.sessionName,
|
|
71
|
+
});
|
|
72
|
+
this.setNotices(this.scopeMessage('profile', result.message));
|
|
73
|
+
} catch (error: unknown) {
|
|
74
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
75
|
+
this.setNotices(this.scopeMessage('profile', message));
|
|
76
|
+
} finally {
|
|
77
|
+
this.options.markDirty();
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async toggleGatewayStatusTimeline(): Promise<void> {
|
|
82
|
+
try {
|
|
83
|
+
const result = await this.options.toggleGatewayStatusTimeline({
|
|
84
|
+
invocationDirectory: this.options.invocationDirectory,
|
|
85
|
+
sessionName: this.options.sessionName,
|
|
86
|
+
});
|
|
87
|
+
this.setNotices(this.scopeMessage('status-trace', result.message));
|
|
88
|
+
} catch (error: unknown) {
|
|
89
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
90
|
+
this.setNotices(this.scopeMessage('status-trace', message));
|
|
91
|
+
} finally {
|
|
92
|
+
this.options.markDirty();
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async toggleGatewayRenderTrace(conversationId: string | null): Promise<void> {
|
|
97
|
+
try {
|
|
98
|
+
const result = await this.options.toggleGatewayRenderTrace({
|
|
99
|
+
invocationDirectory: this.options.invocationDirectory,
|
|
100
|
+
sessionName: this.options.sessionName,
|
|
101
|
+
conversationId,
|
|
102
|
+
});
|
|
103
|
+
this.setNotices(this.scopeMessage('render-trace', result.message));
|
|
104
|
+
} catch (error: unknown) {
|
|
105
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
106
|
+
this.setNotices(this.scopeMessage('render-trace', message));
|
|
107
|
+
} finally {
|
|
108
|
+
this.options.markDirty();
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
private scopeMessage(prefix: string, message: string): string {
|
|
113
|
+
if (this.options.sessionName === null) {
|
|
114
|
+
return `[${prefix}] ${message}`;
|
|
115
|
+
}
|
|
116
|
+
return `[${prefix}:${this.options.sessionName}] ${message}`;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
private setNotices(message: string): void {
|
|
120
|
+
this.options.setTaskPaneNotice(message);
|
|
121
|
+
this.options.setDebugFooterNotice(message);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { ControlPlaneOpQueue } from '../mux/control-plane-op-queue.ts';
|
|
2
|
+
|
|
3
|
+
type PerfAttrValue = boolean | number | string;
|
|
4
|
+
type PerfAttrs = Readonly<Record<string, PerfAttrValue>>;
|
|
5
|
+
|
|
6
|
+
interface RuntimeControlPlaneOpEvent {
|
|
7
|
+
readonly id: number;
|
|
8
|
+
readonly priority: 'interactive' | 'background';
|
|
9
|
+
readonly label: string;
|
|
10
|
+
readonly waitMs: number;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface RuntimeControlPlaneOpsOptions {
|
|
14
|
+
readonly onFatal: (error: unknown) => void;
|
|
15
|
+
readonly startPerfSpan: (
|
|
16
|
+
name: string,
|
|
17
|
+
attrs?: PerfAttrs,
|
|
18
|
+
parentSpanId?: string,
|
|
19
|
+
) => { end: (attrs?: PerfAttrs) => void };
|
|
20
|
+
readonly recordPerfEvent: (name: string, attrs?: PerfAttrs) => void;
|
|
21
|
+
readonly writeStderr: (text: string) => void;
|
|
22
|
+
readonly nowMs?: () => number;
|
|
23
|
+
readonly schedule?: (callback: () => void) => void;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export class RuntimeControlPlaneOps {
|
|
27
|
+
private readonly opSpans = new Map<
|
|
28
|
+
number,
|
|
29
|
+
{
|
|
30
|
+
end: (attrs?: PerfAttrs) => void;
|
|
31
|
+
}
|
|
32
|
+
>();
|
|
33
|
+
|
|
34
|
+
private readonly queue: ControlPlaneOpQueue;
|
|
35
|
+
|
|
36
|
+
constructor(private readonly options: RuntimeControlPlaneOpsOptions) {
|
|
37
|
+
this.queue = new ControlPlaneOpQueue({
|
|
38
|
+
...(options.nowMs === undefined
|
|
39
|
+
? {}
|
|
40
|
+
: {
|
|
41
|
+
nowMs: options.nowMs,
|
|
42
|
+
}),
|
|
43
|
+
...(options.schedule === undefined
|
|
44
|
+
? {}
|
|
45
|
+
: {
|
|
46
|
+
schedule: options.schedule,
|
|
47
|
+
}),
|
|
48
|
+
onFatal: (error: unknown) => {
|
|
49
|
+
this.options.onFatal(error);
|
|
50
|
+
},
|
|
51
|
+
onEnqueued: (event, metrics) => {
|
|
52
|
+
this.options.recordPerfEvent('mux.control-plane.op.enqueued', {
|
|
53
|
+
id: event.id,
|
|
54
|
+
label: event.label,
|
|
55
|
+
priority: event.priority,
|
|
56
|
+
interactiveQueued: metrics.interactiveQueued,
|
|
57
|
+
backgroundQueued: metrics.backgroundQueued,
|
|
58
|
+
});
|
|
59
|
+
},
|
|
60
|
+
onStart: (event, metrics) => {
|
|
61
|
+
const opSpan = this.options.startPerfSpan('mux.control-plane.op', {
|
|
62
|
+
id: event.id,
|
|
63
|
+
label: event.label,
|
|
64
|
+
priority: event.priority,
|
|
65
|
+
waitMs: event.waitMs,
|
|
66
|
+
});
|
|
67
|
+
this.opSpans.set(event.id, opSpan);
|
|
68
|
+
this.options.recordPerfEvent('mux.control-plane.op.start', {
|
|
69
|
+
id: event.id,
|
|
70
|
+
label: event.label,
|
|
71
|
+
priority: event.priority,
|
|
72
|
+
waitMs: event.waitMs,
|
|
73
|
+
interactiveQueued: metrics.interactiveQueued,
|
|
74
|
+
backgroundQueued: metrics.backgroundQueued,
|
|
75
|
+
});
|
|
76
|
+
},
|
|
77
|
+
onSuccess: (event) => {
|
|
78
|
+
this.endSpan(event, 'ok');
|
|
79
|
+
},
|
|
80
|
+
onError: (event, _metrics, error) => {
|
|
81
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
82
|
+
this.endSpan(event, 'error', message);
|
|
83
|
+
this.options.writeStderr(`[mux] control-plane error ${message}\n`);
|
|
84
|
+
},
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
enqueueInteractive(task: () => Promise<void>, label = 'interactive-op'): void {
|
|
89
|
+
this.queue.enqueueInteractive(task, label);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
enqueueBackground(task: () => Promise<void>, label = 'background-op'): void {
|
|
93
|
+
this.queue.enqueueBackground(task, label);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async waitForDrain(): Promise<void> {
|
|
97
|
+
await this.queue.waitForDrain();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
metrics(): {
|
|
101
|
+
readonly interactiveQueued: number;
|
|
102
|
+
readonly backgroundQueued: number;
|
|
103
|
+
readonly running: boolean;
|
|
104
|
+
} {
|
|
105
|
+
return this.queue.metrics();
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
private endSpan(
|
|
109
|
+
event: RuntimeControlPlaneOpEvent,
|
|
110
|
+
status: 'ok' | 'error',
|
|
111
|
+
message?: string,
|
|
112
|
+
): void {
|
|
113
|
+
const opSpan = this.opSpans.get(event.id);
|
|
114
|
+
if (opSpan === undefined) {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
opSpan.end({
|
|
118
|
+
id: event.id,
|
|
119
|
+
label: event.label,
|
|
120
|
+
priority: event.priority,
|
|
121
|
+
status,
|
|
122
|
+
waitMs: event.waitMs,
|
|
123
|
+
...(message === undefined
|
|
124
|
+
? {}
|
|
125
|
+
: {
|
|
126
|
+
message,
|
|
127
|
+
}),
|
|
128
|
+
});
|
|
129
|
+
this.opSpans.delete(event.id);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createAndActivateConversationInDirectory as createAndActivateConversationInDirectoryFn,
|
|
3
|
+
openOrCreateCritiqueConversationInDirectory as openOrCreateCritiqueConversationInDirectoryFn,
|
|
4
|
+
takeoverConversation as takeoverConversationFn,
|
|
5
|
+
} from '../mux/live-mux/actions-conversation.ts';
|
|
6
|
+
|
|
7
|
+
interface RuntimeConversationRecord {
|
|
8
|
+
readonly directoryId: string | null;
|
|
9
|
+
readonly agentType: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
interface RuntimeConversationActionService<TControllerRecord> {
|
|
13
|
+
createConversation(input: {
|
|
14
|
+
conversationId: string;
|
|
15
|
+
directoryId: string;
|
|
16
|
+
title: string;
|
|
17
|
+
agentType: string;
|
|
18
|
+
adapterState: Record<string, unknown>;
|
|
19
|
+
}): Promise<unknown>;
|
|
20
|
+
claimSession(input: {
|
|
21
|
+
sessionId: string;
|
|
22
|
+
controllerId: string;
|
|
23
|
+
controllerType: string;
|
|
24
|
+
controllerLabel: string;
|
|
25
|
+
reason: string;
|
|
26
|
+
takeover: boolean;
|
|
27
|
+
}): Promise<TControllerRecord | null>;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface RuntimeConversationActionsOptions<TControllerRecord> {
|
|
31
|
+
readonly controlPlaneService: RuntimeConversationActionService<TControllerRecord>;
|
|
32
|
+
readonly createConversationId: () => string;
|
|
33
|
+
readonly ensureConversation: (
|
|
34
|
+
sessionId: string,
|
|
35
|
+
seed: {
|
|
36
|
+
directoryId: string;
|
|
37
|
+
title: string;
|
|
38
|
+
agentType: string;
|
|
39
|
+
adapterState: Record<string, unknown>;
|
|
40
|
+
},
|
|
41
|
+
) => void;
|
|
42
|
+
readonly noteGitActivity: (directoryId: string) => void;
|
|
43
|
+
readonly startConversation: (sessionId: string) => Promise<unknown>;
|
|
44
|
+
readonly activateConversation: (sessionId: string) => Promise<unknown>;
|
|
45
|
+
readonly orderedConversationIds: () => readonly string[];
|
|
46
|
+
readonly conversationById: (sessionId: string) => RuntimeConversationRecord | null;
|
|
47
|
+
readonly conversationsHas: (sessionId: string) => boolean;
|
|
48
|
+
readonly applyController: (sessionId: string, controller: TControllerRecord) => void;
|
|
49
|
+
readonly setLastEventNow: (sessionId: string) => void;
|
|
50
|
+
readonly muxControllerId: string;
|
|
51
|
+
readonly muxControllerLabel: string;
|
|
52
|
+
readonly markDirty: () => void;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export class RuntimeConversationActions<TControllerRecord> {
|
|
56
|
+
constructor(private readonly options: RuntimeConversationActionsOptions<TControllerRecord>) {}
|
|
57
|
+
|
|
58
|
+
async createAndActivateConversationInDirectory(
|
|
59
|
+
directoryId: string,
|
|
60
|
+
agentType: string,
|
|
61
|
+
): Promise<void> {
|
|
62
|
+
await createAndActivateConversationInDirectoryFn({
|
|
63
|
+
directoryId,
|
|
64
|
+
agentType,
|
|
65
|
+
createConversationId: this.options.createConversationId,
|
|
66
|
+
createConversationRecord: async (sessionId, targetDirectoryId, targetAgentType) => {
|
|
67
|
+
await this.options.controlPlaneService.createConversation({
|
|
68
|
+
conversationId: sessionId,
|
|
69
|
+
directoryId: targetDirectoryId,
|
|
70
|
+
title: '',
|
|
71
|
+
agentType: String(targetAgentType),
|
|
72
|
+
adapterState: {},
|
|
73
|
+
});
|
|
74
|
+
},
|
|
75
|
+
ensureConversation: this.options.ensureConversation,
|
|
76
|
+
noteGitActivity: this.options.noteGitActivity,
|
|
77
|
+
startConversation: this.options.startConversation,
|
|
78
|
+
activateConversation: this.options.activateConversation,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async openOrCreateCritiqueConversationInDirectory(directoryId: string): Promise<void> {
|
|
83
|
+
await openOrCreateCritiqueConversationInDirectoryFn({
|
|
84
|
+
directoryId,
|
|
85
|
+
orderedConversationIds: this.options.orderedConversationIds,
|
|
86
|
+
conversationById: this.options.conversationById,
|
|
87
|
+
activateConversation: this.options.activateConversation,
|
|
88
|
+
createAndActivateCritiqueConversationInDirectory: async (targetDirectoryId) => {
|
|
89
|
+
await this.createAndActivateConversationInDirectory(targetDirectoryId, 'critique');
|
|
90
|
+
},
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async takeoverConversation(sessionId: string): Promise<void> {
|
|
95
|
+
await takeoverConversationFn({
|
|
96
|
+
sessionId,
|
|
97
|
+
conversationsHas: this.options.conversationsHas,
|
|
98
|
+
claimSession: async (targetSessionId) => {
|
|
99
|
+
return await this.options.controlPlaneService.claimSession({
|
|
100
|
+
sessionId: targetSessionId,
|
|
101
|
+
controllerId: this.options.muxControllerId,
|
|
102
|
+
controllerType: 'human',
|
|
103
|
+
controllerLabel: this.options.muxControllerLabel,
|
|
104
|
+
reason: 'human takeover',
|
|
105
|
+
takeover: true,
|
|
106
|
+
});
|
|
107
|
+
},
|
|
108
|
+
applyController: this.options.applyController,
|
|
109
|
+
setLastEventNow: this.options.setLastEventNow,
|
|
110
|
+
markDirty: this.options.markDirty,
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
}
|