@rzx/ohs 1.1.16 → 1.1.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. package/dist/frontend/index.js +1968 -1297
  2. package/dist/frontend/index.js.map +45 -19
  3. package/dist/index.js +40420 -33343
  4. package/dist/index.js.map +194 -132
  5. package/dist/migrations/0000_current_schema.sql +436 -0
  6. package/dist/migrations/meta/0000_snapshot.json +3442 -0
  7. package/dist/migrations/meta/_journal.json +3 -136
  8. package/package.json +12 -12
  9. package/dist/migrations/0000_session_runtime.sql +0 -68
  10. package/dist/migrations/0001_event_sequence.sql +0 -8
  11. package/dist/migrations/0002_cron.sql +0 -33
  12. package/dist/migrations/0003_projects.sql +0 -30
  13. package/dist/migrations/0004_project_default_shell.sql +0 -1
  14. package/dist/migrations/0005_scheduled_tasks.sql +0 -57
  15. package/dist/migrations/0006_event_schema_version.sql +0 -1
  16. package/dist/migrations/0007_projection_settlement.sql +0 -19
  17. package/dist/migrations/0008_run_attempt.sql +0 -23
  18. package/dist/migrations/0009_external_channels.sql +0 -45
  19. package/dist/migrations/0010_workflow_and_owner.sql +0 -68
  20. package/dist/migrations/0011_storage_format.sql +0 -6
  21. package/dist/migrations/0012_job_admission.sql +0 -6
  22. package/dist/migrations/0013_attachments.sql +0 -20
  23. package/dist/migrations/0014_session_input_attachments.sql +0 -45
  24. package/dist/migrations/0015_attachment_representations.sql +0 -22
  25. package/dist/migrations/0016_attachment_lifecycle.sql +0 -16
  26. package/dist/migrations/0017_structured_input_items.sql +0 -1
  27. package/dist/migrations/0018_session_goals.sql +0 -17
  28. package/dist/migrations/0019_session_goal_progress.sql +0 -3
@@ -128,91 +128,6 @@ var init_session = __esm(() => {
128
128
  };
129
129
  });
130
130
 
131
- // ../../packages/protocol/src/runtime-config.ts
132
- function isRecord(value) {
133
- return !!value && typeof value === "object" && !Array.isArray(value);
134
- }
135
- function stringValue(value) {
136
- return typeof value === "string" && value.trim() ? value : undefined;
137
- }
138
- function numberValue(value) {
139
- return typeof value === "number" && Number.isFinite(value) ? value : undefined;
140
- }
141
- function stringArrayValue(value) {
142
- if (!Array.isArray(value))
143
- return;
144
- const items = value.filter((item) => typeof item === "string" && item.trim().length > 0);
145
- return items.length > 0 ? items : undefined;
146
- }
147
- function permissionModeValue(value) {
148
- return value === "default" || value === "plan" || value === "full_auto" ? value : undefined;
149
- }
150
- function effortValue(value) {
151
- return value === "low" || value === "medium" || value === "high" ? value : undefined;
152
- }
153
- function sessionModeValue(value) {
154
- return value === "coordinator" ? "coordinator" : value === "direct" ? "direct" : undefined;
155
- }
156
- function apiFormatValue(value) {
157
- return value === "anthropic" || value === "openai" ? value : undefined;
158
- }
159
- function booleanValue(value) {
160
- return typeof value === "boolean" ? value : undefined;
161
- }
162
- function readRuntimeMetadata(metadata) {
163
- return isRecord(metadata?.runtime) ? metadata.runtime : {};
164
- }
165
- function readSessionRuntimeConfig(session, defaults) {
166
- const runtime = readRuntimeMetadata(session.metadata);
167
- const model = stringValue(runtime.model);
168
- if (!model) {
169
- throw new Error(`Session runtime config is missing metadata.runtime.model: ${session.id}`);
170
- }
171
- return {
172
- model,
173
- ...stringValue(runtime.provider) ?? defaults?.provider ? { provider: stringValue(runtime.provider) ?? defaults?.provider } : {},
174
- ...stringValue(runtime.baseUrl) ?? defaults?.baseUrl ? { baseUrl: stringValue(runtime.baseUrl) ?? defaults?.baseUrl } : {},
175
- ...apiFormatValue(runtime.apiFormat) ?? defaults?.apiFormat ? { apiFormat: apiFormatValue(runtime.apiFormat) ?? defaults?.apiFormat } : {},
176
- ...permissionModeValue(runtime.permissionMode) ?? defaults?.permissionMode ? { permissionMode: permissionModeValue(runtime.permissionMode) ?? defaults?.permissionMode } : {},
177
- ...numberValue(runtime.maxTurns) ?? defaults?.maxTurns ? { maxTurns: numberValue(runtime.maxTurns) ?? defaults?.maxTurns } : {},
178
- ...effortValue(runtime.effort) ?? defaults?.effort ? { effort: effortValue(runtime.effort) ?? defaults?.effort } : {},
179
- ...sessionModeValue(runtime.sessionMode) ?? defaults?.sessionMode ? { sessionMode: sessionModeValue(runtime.sessionMode) ?? defaults?.sessionMode } : {},
180
- ...stringValue(runtime.systemPrompt) ?? defaults?.systemPrompt ? { systemPrompt: stringValue(runtime.systemPrompt) ?? defaults?.systemPrompt } : {},
181
- ...stringArrayValue(runtime.allowedTools) ?? defaults?.allowedTools ? { allowedTools: stringArrayValue(runtime.allowedTools) ?? defaults?.allowedTools } : {},
182
- ...stringArrayValue(runtime.disallowedTools) ?? defaults?.disallowedTools ? { disallowedTools: stringArrayValue(runtime.disallowedTools) ?? defaults?.disallowedTools } : {},
183
- ...(booleanValue(runtime.pluginsEnabled) ?? defaults?.pluginsEnabled) !== undefined ? { pluginsEnabled: booleanValue(runtime.pluginsEnabled) ?? defaults?.pluginsEnabled } : {}
184
- };
185
- }
186
- function patchSessionRuntimeMetadata(metadata, patch) {
187
- const runtime = {
188
- ...readRuntimeMetadata(metadata),
189
- ...stripUndefined(patch)
190
- };
191
- return {
192
- ...metadata,
193
- runtime
194
- };
195
- }
196
- function stripUndefined(input) {
197
- return Object.fromEntries(Object.entries(input).filter(([, value]) => value !== undefined));
198
- }
199
-
200
- // ../../packages/protocol/src/session-input-items.ts
201
- var SESSION_INPUT_LIMITS;
202
- var init_session_input_items = __esm(() => {
203
- SESSION_INPUT_LIMITS = {
204
- maxItems: 256,
205
- maxSkills: 32,
206
- maxTextBytes: 1024 * 1024,
207
- maxNameChars: 128,
208
- maxDisplayNameChars: 256
209
- };
210
- });
211
-
212
- // ../../packages/protocol/src/requests.ts
213
- var init_requests = __esm(() => {
214
- init_session_input_items();
215
- });
216
131
  // ../../packages/protocol/src/serialization.ts
217
132
  function object(value, path) {
218
133
  if (!value || typeof value !== "object" || Array.isArray(value)) {
@@ -603,6 +518,138 @@ var init_serialization = __esm(() => {
603
518
  }
604
519
  };
605
520
  });
521
+
522
+ // ../../packages/protocol/src/runtime-config.ts
523
+ function isRecord(value) {
524
+ return !!value && typeof value === "object" && !Array.isArray(value);
525
+ }
526
+ function stringValue(value) {
527
+ return typeof value === "string" && value.trim() ? value : undefined;
528
+ }
529
+ function numberValue(value) {
530
+ return typeof value === "number" && Number.isFinite(value) ? value : undefined;
531
+ }
532
+ function stringArrayValue(value) {
533
+ if (!Array.isArray(value))
534
+ return;
535
+ const items = value.filter((item) => typeof item === "string" && item.trim().length > 0);
536
+ return items.length > 0 ? items : undefined;
537
+ }
538
+ function permissionModeValue(value) {
539
+ return value === "default" || value === "plan" || value === "full_auto" ? value : undefined;
540
+ }
541
+ function effortValue(value) {
542
+ return value === "low" || value === "medium" || value === "high" ? value : undefined;
543
+ }
544
+ function sessionModeValue(value) {
545
+ return value === "coordinator" ? "coordinator" : value === "direct" ? "direct" : undefined;
546
+ }
547
+ function apiFormatValue(value) {
548
+ return value === "anthropic" || value === "openai" ? value : undefined;
549
+ }
550
+ function booleanValue(value) {
551
+ return typeof value === "boolean" ? value : undefined;
552
+ }
553
+ function readRuntimeMetadata(metadata) {
554
+ const runtime = metadata?.runtime;
555
+ if (runtime === undefined)
556
+ return {};
557
+ if (!isRecord(runtime))
558
+ throw new ProtocolDataError("metadata.runtime must be an object", "metadata.runtime");
559
+ for (const [field, value] of Object.entries(runtime)) {
560
+ let valid = false;
561
+ switch (field) {
562
+ case "model":
563
+ case "provider":
564
+ case "baseUrl":
565
+ case "systemPrompt":
566
+ valid = typeof value === "string";
567
+ break;
568
+ case "apiFormat":
569
+ valid = apiFormatValue(value) !== undefined;
570
+ break;
571
+ case "permissionMode":
572
+ valid = permissionModeValue(value) !== undefined;
573
+ break;
574
+ case "effort":
575
+ valid = effortValue(value) !== undefined;
576
+ break;
577
+ case "sessionMode":
578
+ valid = sessionModeValue(value) !== undefined;
579
+ break;
580
+ case "maxTurns":
581
+ valid = numberValue(value) !== undefined;
582
+ break;
583
+ case "pluginsEnabled":
584
+ valid = booleanValue(value) !== undefined;
585
+ break;
586
+ case "allowedTools":
587
+ case "disallowedTools":
588
+ valid = Array.isArray(value) && value.every((item) => typeof item === "string" && item.trim().length > 0);
589
+ break;
590
+ }
591
+ if (!valid) {
592
+ throw new ProtocolDataError(`Invalid runtime config field metadata.runtime.${field}`, `metadata.runtime.${field}`);
593
+ }
594
+ }
595
+ return runtime;
596
+ }
597
+ function readSessionRuntimeConfig(session, defaults) {
598
+ const runtime = readRuntimeMetadata(session.metadata);
599
+ const model = stringValue(runtime.model);
600
+ if (!model) {
601
+ throw new Error(`Session runtime config is missing metadata.runtime.model: ${session.id}`);
602
+ }
603
+ return {
604
+ model,
605
+ ...stringValue(runtime.provider) ?? defaults?.provider ? { provider: stringValue(runtime.provider) ?? defaults?.provider } : {},
606
+ ...stringValue(runtime.baseUrl) ?? defaults?.baseUrl ? { baseUrl: stringValue(runtime.baseUrl) ?? defaults?.baseUrl } : {},
607
+ ...apiFormatValue(runtime.apiFormat) ?? defaults?.apiFormat ? { apiFormat: apiFormatValue(runtime.apiFormat) ?? defaults?.apiFormat } : {},
608
+ ...permissionModeValue(runtime.permissionMode) ?? defaults?.permissionMode ? { permissionMode: permissionModeValue(runtime.permissionMode) ?? defaults?.permissionMode } : {},
609
+ ...numberValue(runtime.maxTurns) ?? defaults?.maxTurns ? { maxTurns: numberValue(runtime.maxTurns) ?? defaults?.maxTurns } : {},
610
+ ...effortValue(runtime.effort) ?? defaults?.effort ? { effort: effortValue(runtime.effort) ?? defaults?.effort } : {},
611
+ ...sessionModeValue(runtime.sessionMode) ?? defaults?.sessionMode ? { sessionMode: sessionModeValue(runtime.sessionMode) ?? defaults?.sessionMode } : {},
612
+ ...stringValue(runtime.systemPrompt) ?? defaults?.systemPrompt ? { systemPrompt: stringValue(runtime.systemPrompt) ?? defaults?.systemPrompt } : {},
613
+ ...stringArrayValue(runtime.allowedTools) ?? defaults?.allowedTools ? { allowedTools: stringArrayValue(runtime.allowedTools) ?? defaults?.allowedTools } : {},
614
+ ...stringArrayValue(runtime.disallowedTools) ?? defaults?.disallowedTools ? { disallowedTools: stringArrayValue(runtime.disallowedTools) ?? defaults?.disallowedTools } : {},
615
+ ...(booleanValue(runtime.pluginsEnabled) ?? defaults?.pluginsEnabled) !== undefined ? { pluginsEnabled: booleanValue(runtime.pluginsEnabled) ?? defaults?.pluginsEnabled } : {}
616
+ };
617
+ }
618
+ function patchSessionRuntimeMetadata(metadata, patch) {
619
+ const runtime = {
620
+ ...readRuntimeMetadata(metadata),
621
+ ...stripUndefined(patch)
622
+ };
623
+ readRuntimeMetadata({ runtime });
624
+ return {
625
+ ...metadata,
626
+ runtime
627
+ };
628
+ }
629
+ function stripUndefined(input) {
630
+ return Object.fromEntries(Object.entries(input).filter(([, value]) => value !== undefined));
631
+ }
632
+ var init_runtime_config = __esm(() => {
633
+ init_serialization();
634
+ });
635
+
636
+ // ../../packages/protocol/src/session-input-items.ts
637
+ var SESSION_INPUT_LIMITS;
638
+ var init_session_input_items = __esm(() => {
639
+ SESSION_INPUT_LIMITS = {
640
+ maxItems: 256,
641
+ maxSkills: 32,
642
+ maxTextBytes: 1024 * 1024,
643
+ maxNameChars: 128,
644
+ maxDisplayNameChars: 256
645
+ };
646
+ });
647
+
648
+ // ../../packages/protocol/src/requests.ts
649
+ var init_requests = __esm(() => {
650
+ init_runtime_config();
651
+ init_session_input_items();
652
+ });
606
653
  // ../../packages/protocol/src/attachment.ts
607
654
  function parseAttachmentLimits(value) {
608
655
  const record = recordValue(value, "attachment limits");
@@ -794,6 +841,7 @@ function parseAttachmentTransferCapabilities(value) {
794
841
  uploadModes
795
842
  };
796
843
  }
844
+ var CURRENT_PROTOCOL_VERSION = 4, PROTOCOL_VERSION_HEADER = "x-openharness-protocol-version";
797
845
  var init_capabilities = __esm(() => {
798
846
  init_attachment();
799
847
  });
@@ -806,6 +854,7 @@ var init_session_goals = __esm(() => {
806
854
  // ../../packages/protocol/src/index.ts
807
855
  var init_src = __esm(() => {
808
856
  init_session();
857
+ init_runtime_config();
809
858
  init_requests();
810
859
  init_serialization();
811
860
  init_capabilities();
@@ -814,7 +863,45 @@ var init_src = __esm(() => {
814
863
  init_session_goals();
815
864
  });
816
865
 
817
- // ../../packages/client/src/transport/http-client.ts
866
+ // ../../packages/client/src/protocol/protocol-client.ts
867
+ class ProtocolClient {
868
+ transport;
869
+ constructor(transport) {
870
+ this.transport = transport;
871
+ }
872
+ async health(options = {}) {
873
+ return this.transport.request("/health", {
874
+ auth: false,
875
+ signal: options.signal
876
+ });
877
+ }
878
+ async capabilities(options = {}) {
879
+ const value = await this.transport.request("/capabilities", {
880
+ auth: false,
881
+ signal: options.signal
882
+ });
883
+ const capabilities = parseServerCapabilities(value);
884
+ const compatibility = checkProtocolCompatibility(capabilities, options.support ?? { version: CURRENT_PROTOCOL_VERSION });
885
+ if (!compatibility.compatible) {
886
+ throw new IncompatibleProtocolError(capabilities, compatibility.reason ?? "Client and server protocol versions are incompatible");
887
+ }
888
+ return capabilities;
889
+ }
890
+ }
891
+ var IncompatibleProtocolError;
892
+ var init_protocol_client = __esm(() => {
893
+ init_src();
894
+ IncompatibleProtocolError = class IncompatibleProtocolError extends Error {
895
+ capabilities;
896
+ constructor(capabilities, message) {
897
+ super(message);
898
+ this.capabilities = capabilities;
899
+ this.name = "IncompatibleProtocolError";
900
+ }
901
+ };
902
+ });
903
+
904
+ // ../../packages/client/src/transport/http-transport.ts
818
905
  function responseField(value, field) {
819
906
  if (!value || typeof value !== "object" || Array.isArray(value)) {
820
907
  throw new ProtocolDataError("Response body must be an object");
@@ -853,14 +940,56 @@ function normalizeDaemonBaseUrl(value) {
853
940
  const pathname = url.pathname.replace(/\/+$/, "");
854
941
  return `${url.origin}${pathname === "/" ? "" : pathname}`;
855
942
  }
856
- function createPromptRequestId() {
857
- if (typeof globalThis.crypto?.randomUUID === "function")
858
- return globalThis.crypto.randomUUID();
859
- promptRequestCounter += 1;
860
- return `prompt-${Date.now().toString(36)}-${promptRequestCounter.toString(36)}`;
943
+ function isReadableStream(value) {
944
+ return typeof ReadableStream !== "undefined" && value instanceof ReadableStream;
945
+ }
946
+ function attachmentRangeHeader(range) {
947
+ if (!range)
948
+ return;
949
+ const { start, end, suffixBytes } = range;
950
+ if (suffixBytes !== undefined) {
951
+ if (start !== undefined || end !== undefined) {
952
+ throw new Error("suffixBytes cannot be combined with start or end");
953
+ }
954
+ assertPositiveSafeInteger(suffixBytes, "suffixBytes");
955
+ return `bytes=-${suffixBytes}`;
956
+ }
957
+ if (start === undefined && end === undefined)
958
+ return;
959
+ if (start === undefined) {
960
+ throw new Error("range start is required when end is provided");
961
+ }
962
+ assertNonNegativeSafeInteger(start, "start");
963
+ if (end === undefined)
964
+ return `bytes=${start}-`;
965
+ assertNonNegativeSafeInteger(end, "end");
966
+ if (end < start)
967
+ throw new Error("range end must not be less than start");
968
+ return `bytes=${start}-${end}`;
969
+ }
970
+ function assertNonNegativeSafeInteger(value, field) {
971
+ if (!Number.isSafeInteger(value) || value < 0) {
972
+ throw new Error(`${field} must be a non-negative safe integer`);
973
+ }
974
+ }
975
+ function assertPositiveSafeInteger(value, field) {
976
+ if (!Number.isSafeInteger(value) || value < 1) {
977
+ throw new Error(`${field} must be a positive safe integer`);
978
+ }
979
+ }
980
+ async function throwResponseError(response) {
981
+ let body;
982
+ try {
983
+ body = await response.json();
984
+ } catch {
985
+ body = await response.text().catch(() => "");
986
+ }
987
+ const message = body && typeof body === "object" && "error" in body && typeof body.error === "string" ? body.error : body && typeof body === "object" && ("message" in body) && typeof body.message === "string" ? body.message : `OpenHarness API request failed with ${response.status}`;
988
+ throw new OpenHarnessApiError(message, response.status, body);
861
989
  }
862
990
 
863
- class OpenHarnessClient {
991
+ class HttpTransport {
992
+ protocolHandshake;
864
993
  baseUrl;
865
994
  token;
866
995
  fetchImpl;
@@ -869,162 +998,347 @@ class OpenHarnessClient {
869
998
  this.token = options.token;
870
999
  this.fetchImpl = options.fetch ?? fetch;
871
1000
  }
872
- async health(options = {}) {
873
- return this.request("/health", {
874
- auth: false,
875
- signal: options.signal
1001
+ isHandshakeExempt(pathname) {
1002
+ const path = pathname.split("?")[0];
1003
+ return path === "/health" || path === "/capabilities";
1004
+ }
1005
+ ensureProtocol(signal) {
1006
+ signal?.throwIfAborted();
1007
+ this.protocolHandshake ??= this.requestUnknown("/capabilities", { auth: false }).then((value) => {
1008
+ const capabilities = parseServerCapabilities(value);
1009
+ const result = checkProtocolCompatibility(capabilities, { version: CURRENT_PROTOCOL_VERSION });
1010
+ if (!result.compatible) {
1011
+ throw new IncompatibleProtocolError(capabilities, result.reason);
1012
+ }
1013
+ return capabilities;
1014
+ }).catch((error) => {
1015
+ this.protocolHandshake = undefined;
1016
+ throw error;
1017
+ });
1018
+ if (!signal)
1019
+ return this.protocolHandshake;
1020
+ return new Promise((resolve, reject) => {
1021
+ const onAbort = () => reject(signal.reason);
1022
+ signal.addEventListener("abort", onAbort, { once: true });
1023
+ this.protocolHandshake.then(resolve, reject).finally(() => {
1024
+ signal.removeEventListener("abort", onAbort);
1025
+ });
876
1026
  });
877
1027
  }
878
- async capabilities(options = {}) {
879
- const value = await this.request("/capabilities", {
880
- auth: false,
1028
+ headers(jsonOrOptions = false, auth = true) {
1029
+ let json = false;
1030
+ let authHeader = auth;
1031
+ let extra;
1032
+ if (typeof jsonOrOptions === "object" && jsonOrOptions !== null) {
1033
+ json = jsonOrOptions.json ?? false;
1034
+ authHeader = jsonOrOptions.auth ?? true;
1035
+ extra = jsonOrOptions.extra;
1036
+ } else {
1037
+ json = Boolean(jsonOrOptions);
1038
+ authHeader = auth;
1039
+ }
1040
+ return {
1041
+ ...authHeader && this.token ? { authorization: `Bearer ${this.token}` } : {},
1042
+ ...json ? { "content-type": "application/json" } : {},
1043
+ ...extra ?? {}
1044
+ };
1045
+ }
1046
+ path(pathname, query = {}) {
1047
+ const params = new URLSearchParams;
1048
+ for (const [key, value] of Object.entries(query)) {
1049
+ if (value === undefined || value === null || value === false)
1050
+ continue;
1051
+ params.set(key, String(value));
1052
+ }
1053
+ const qs = params.toString();
1054
+ return qs ? `${pathname}?${qs}` : pathname;
1055
+ }
1056
+ resolveUrl(pathname, query) {
1057
+ const resolvedPath = query ? this.path(pathname, query) : pathname;
1058
+ return `${this.baseUrl}${resolvedPath}`;
1059
+ }
1060
+ async request(path, options = {}) {
1061
+ const business = !this.isHandshakeExempt(path);
1062
+ if (business)
1063
+ await this.ensureProtocol(options.signal);
1064
+ options.signal?.throwIfAborted();
1065
+ const url = this.resolveUrl(path, options.query);
1066
+ const hasJsonBody = options.body !== undefined;
1067
+ const headers = mergeHeaders(this.headers(hasJsonBody, options.auth ?? true), options.headers);
1068
+ if (business)
1069
+ headers[PROTOCOL_VERSION_HEADER] = String(CURRENT_PROTOCOL_VERSION);
1070
+ const response = await this.fetchImpl(url, {
1071
+ method: options.method ?? "GET",
1072
+ headers,
1073
+ body: hasJsonBody ? JSON.stringify(options.body) : undefined,
881
1074
  signal: options.signal
882
1075
  });
883
- const capabilities = parseServerCapabilities(value);
884
- const compatibility = checkProtocolCompatibility(capabilities, options.support ?? { version: 2 });
885
- if (!compatibility.compatible) {
886
- throw new IncompatibleProtocolError(capabilities, compatibility.reason ?? "Client and server protocol versions are incompatible");
1076
+ if (!response.ok) {
1077
+ await throwResponseError(response);
887
1078
  }
888
- return capabilities;
1079
+ return await response.json();
889
1080
  }
890
- async uploadAttachment(input) {
891
- const headers = this.headers();
892
- headers["x-openharness-filename"] = encodeURIComponent(input.displayName);
893
- if (input.mediaType)
894
- headers["content-type"] = input.mediaType;
1081
+ async requestUnknown(path, options = {}) {
1082
+ return this.request(path, options);
1083
+ }
1084
+ async requestResponse(path, options = {}) {
1085
+ const business = !this.isHandshakeExempt(path);
1086
+ if (business)
1087
+ await this.ensureProtocol(options.signal ?? undefined);
1088
+ options.signal?.throwIfAborted();
1089
+ const url = this.resolveUrl(path, options.query);
1090
+ const auth = options.auth ?? true;
1091
+ const headers = mergeHeaders(this.headers(false, auth), options.headers);
1092
+ if (business)
1093
+ headers[PROTOCOL_VERSION_HEADER] = String(CURRENT_PROTOCOL_VERSION);
895
1094
  const init = {
896
- method: "POST",
897
- headers,
898
- body: input.body,
899
- signal: input.signal
1095
+ ...options,
1096
+ headers
900
1097
  };
901
- if (isReadableStream(input.body))
1098
+ if (isReadableStream(init.body) && !init.duplex) {
902
1099
  init.duplex = "half";
903
- const response = await this.fetchImpl(`${this.baseUrl}/attachments`, init);
904
- if (!response.ok)
905
- await this.throwResponseError(response);
906
- return parseAttachmentAssetRecord(await response.json());
907
- }
908
- async getAttachment(id, options = {}) {
909
- const value = await this.request(`/attachments/${encodeURIComponent(id)}`, { signal: options.signal });
910
- return parseAttachmentAssetRecord(value);
911
- }
912
- async downloadAttachment(id, options = {}) {
913
- const range = attachmentRangeHeader(options.range);
914
- const headers = this.headers();
915
- if (range)
916
- headers.range = range;
917
- const response = await this.fetchImpl(`${this.baseUrl}/attachments/${encodeURIComponent(id)}/content`, { method: "GET", headers, signal: options.signal });
918
- if (!response.ok)
919
- await this.throwResponseError(response);
1100
+ }
1101
+ const response = await this.fetchImpl(url, init);
1102
+ if (!response.ok) {
1103
+ await throwResponseError(response);
1104
+ }
920
1105
  return response;
921
1106
  }
922
- async deleteAttachment(id, options = {}) {
923
- const value = await this.request(`/attachments/${encodeURIComponent(id)}`, { method: "DELETE", signal: options.signal });
924
- return parseAttachmentAssetRecord(value);
1107
+ async throwResponseError(response) {
1108
+ return throwResponseError(response);
925
1109
  }
926
- async scanAttachmentStorage(options = {}) {
927
- return await this.request("/attachments/storage", {
928
- signal: options.signal
929
- });
1110
+ }
1111
+ function mergeHeaders(defaults, overrides) {
1112
+ const headers = new Headers(defaults);
1113
+ if (overrides) {
1114
+ new Headers(overrides).forEach((value, key) => headers.set(key, value));
930
1115
  }
931
- async repairAttachmentStorage(options = {}) {
932
- return await this.request("/attachments/storage/actions", {
933
- method: "POST",
934
- body: { action: "repair-safe" },
935
- signal: options.signal
936
- });
1116
+ return Object.fromEntries(headers.entries());
1117
+ }
1118
+ var OpenHarnessApiError;
1119
+ var init_http_transport = __esm(() => {
1120
+ init_src();
1121
+ init_protocol_client();
1122
+ OpenHarnessApiError = class OpenHarnessApiError extends Error {
1123
+ status;
1124
+ body;
1125
+ constructor(message, status, body) {
1126
+ super(message);
1127
+ this.status = status;
1128
+ this.body = body;
1129
+ this.name = "OpenHarnessApiError";
1130
+ }
1131
+ };
1132
+ });
1133
+
1134
+ // ../../packages/client/src/transport/sse-transport.ts
1135
+ function parseRawSseFrame(frame) {
1136
+ let id;
1137
+ let event;
1138
+ let retry;
1139
+ const dataLines = [];
1140
+ for (const line of frame.split(/\r?\n/)) {
1141
+ if (!line || line.startsWith(":"))
1142
+ continue;
1143
+ if (line.startsWith("data:")) {
1144
+ dataLines.push(line.startsWith("data: ") ? line.slice(6) : line.slice(5));
1145
+ } else if (line.startsWith("event:")) {
1146
+ event = (line.startsWith("event: ") ? line.slice(7) : line.slice(6)).trim();
1147
+ } else if (line.startsWith("id:")) {
1148
+ id = (line.startsWith("id: ") ? line.slice(4) : line.slice(3)).trim();
1149
+ } else if (line.startsWith("retry:")) {
1150
+ const value = (line.startsWith("retry: ") ? line.slice(7) : line.slice(6)).trim();
1151
+ const parsed = parseInt(value, 10);
1152
+ if (!Number.isNaN(parsed)) {
1153
+ retry = parsed;
1154
+ }
1155
+ }
937
1156
  }
938
- async gcAttachmentStorage(options = {}) {
939
- return await this.request("/attachments/storage/actions", {
940
- method: "POST",
941
- body: { action: "gc" },
942
- signal: options.signal
943
- });
1157
+ if (dataLines.length === 0 && !id && !event && retry === undefined) {
1158
+ return;
944
1159
  }
945
- async handleChannelMessage(input, options = {}) {
946
- return await this.request("/channels/messages", { method: "POST", body: input, signal: options.signal });
1160
+ return {
1161
+ id,
1162
+ event,
1163
+ retry,
1164
+ data: dataLines.length > 0 ? dataLines.join(`
1165
+ `) : undefined
1166
+ };
1167
+ }
1168
+ function parseSseFrame(frame) {
1169
+ let data = "";
1170
+ for (const line of frame.split(/\r?\n/)) {
1171
+ if (!line || line.startsWith(":"))
1172
+ continue;
1173
+ if (line.startsWith("data:")) {
1174
+ const slice = line.slice(5).trimStart();
1175
+ data = data ? `${data}
1176
+ ${slice}` : slice;
1177
+ }
947
1178
  }
948
- async recordChannelDelivery(deliveryId, input, options = {}) {
949
- const response = await this.request(`/channels/deliveries/${encodeURIComponent(deliveryId)}/result`, { method: "POST", body: input, signal: options.signal });
950
- return response.delivery;
1179
+ if (!data)
1180
+ return;
1181
+ return JSON.parse(data);
1182
+ }
1183
+ async function* streamServerSentEvents(open, decode = (value) => value) {
1184
+ const reader = (await open()).getReader();
1185
+ const decoder = new TextDecoder;
1186
+ let buffer = "";
1187
+ try {
1188
+ while (true) {
1189
+ const chunk = await reader.read();
1190
+ if (chunk.done)
1191
+ break;
1192
+ buffer += decoder.decode(chunk.value, { stream: true });
1193
+ const frames = buffer.split(/\r?\n\r?\n/);
1194
+ buffer = frames.pop() ?? "";
1195
+ for (const frame of frames) {
1196
+ const event = parseSseFrame(frame);
1197
+ if (event !== undefined)
1198
+ yield decode(event);
1199
+ }
1200
+ }
1201
+ buffer += decoder.decode();
1202
+ const event = parseSseFrame(buffer);
1203
+ if (event !== undefined)
1204
+ yield decode(event);
1205
+ } finally {
1206
+ reader.releaseLock();
951
1207
  }
952
- async getChannelStatus(options = {}) {
953
- const { signal, ...query } = options;
954
- return await this.request(this.path("/channels/status", query), { signal });
1208
+ }
1209
+
1210
+ class SseTransport {
1211
+ transport;
1212
+ constructor(transport) {
1213
+ this.transport = transport;
1214
+ }
1215
+ streamFromReader(open, decode = (val) => val) {
1216
+ return streamServerSentEvents(open, decode);
1217
+ }
1218
+ async* stream(url, options = {}) {
1219
+ let lastEventId = options.lastEventId;
1220
+ let reconnectDelayMs = options.reconnectDelayMs ?? 250;
1221
+ let connected = false;
1222
+ while (!options.signal?.aborted) {
1223
+ const headers = new Headers(options.headers);
1224
+ if (lastEventId)
1225
+ headers.set("Last-Event-ID", lastEventId);
1226
+ const requestUrl = connected && lastEventId ? withoutCursor(url) : url;
1227
+ const response = await this.transport.requestResponse(requestUrl.slice(this.transport.baseUrl.length), { headers, signal: options.signal });
1228
+ connected = true;
1229
+ if (!response.body) {
1230
+ throw new Error(options.noBodyMessage ?? "Event stream response has no body");
1231
+ }
1232
+ for await (const frame of readRawSseFrames(response.body)) {
1233
+ if (frame.id !== undefined)
1234
+ lastEventId = frame.id;
1235
+ if (frame.retry !== undefined)
1236
+ reconnectDelayMs = frame.retry;
1237
+ if (frame.data !== undefined) {
1238
+ yield (options.decode ?? ((value) => value))(JSON.parse(frame.data));
1239
+ }
1240
+ }
1241
+ if (!options.reconnect || options.signal?.aborted)
1242
+ return;
1243
+ await waitForReconnect(reconnectDelayMs, options.signal);
1244
+ }
955
1245
  }
956
- async listPendingChannelDeliveries(options = {}) {
957
- const { signal, ...query } = options;
958
- const response = await this.request(this.path("/channels/deliveries/pending", query), { signal });
959
- return response.deliveries;
1246
+ }
1247
+ async function* readRawSseFrames(stream) {
1248
+ const reader = stream.getReader();
1249
+ const decoder = new TextDecoder;
1250
+ let buffer = "";
1251
+ try {
1252
+ while (true) {
1253
+ const chunk = await reader.read();
1254
+ if (chunk.done)
1255
+ break;
1256
+ buffer += decoder.decode(chunk.value, { stream: true });
1257
+ const frames = buffer.split(/\r?\n\r?\n/);
1258
+ buffer = frames.pop() ?? "";
1259
+ for (const value of frames) {
1260
+ const frame = parseRawSseFrame(value);
1261
+ if (frame)
1262
+ yield frame;
1263
+ }
1264
+ }
1265
+ buffer += decoder.decode();
1266
+ const frame = parseRawSseFrame(buffer);
1267
+ if (frame)
1268
+ yield frame;
1269
+ } finally {
1270
+ reader.releaseLock();
1271
+ }
1272
+ }
1273
+ function waitForReconnect(ms, signal) {
1274
+ if (signal?.aborted)
1275
+ return Promise.reject(abortError(signal));
1276
+ return new Promise((resolve, reject) => {
1277
+ const timer = setTimeout(() => {
1278
+ signal?.removeEventListener("abort", onAbort);
1279
+ resolve();
1280
+ }, Math.max(0, ms));
1281
+ const onAbort = () => {
1282
+ clearTimeout(timer);
1283
+ reject(abortError(signal));
1284
+ };
1285
+ signal?.addEventListener("abort", onAbort, { once: true });
1286
+ });
1287
+ }
1288
+ function abortError(signal) {
1289
+ if (signal.reason instanceof Error)
1290
+ return signal.reason;
1291
+ return new DOMException("Aborted", "AbortError");
1292
+ }
1293
+ function withoutCursor(url) {
1294
+ const parsed = new URL(url);
1295
+ parsed.searchParams.delete("cursor");
1296
+ return parsed.toString();
1297
+ }
1298
+
1299
+ // ../../packages/client/src/protocol/index.ts
1300
+ var init_protocol = __esm(() => {
1301
+ init_protocol_client();
1302
+ });
1303
+
1304
+ // ../../packages/client/src/resources/system-resource.ts
1305
+ class SystemResource {
1306
+ transport;
1307
+ constructor(transport) {
1308
+ this.transport = transport;
960
1309
  }
961
1310
  async listCommands(options) {
962
1311
  const { signal, ...query } = options;
963
- const response = await this.request(this.path("/commands", query), { signal });
1312
+ const response = await this.transport.request(this.transport.path("/commands", query), { signal });
964
1313
  return response.commands;
965
1314
  }
966
1315
  async getSettings(options = {}) {
967
- const response = await this.request("/settings", { signal: options.signal });
1316
+ const response = await this.transport.request("/settings", { signal: options.signal });
968
1317
  return response.settings;
969
1318
  }
970
1319
  async patchSettings(patch, options = {}) {
971
- const response = await this.request("/settings", {
1320
+ const response = await this.transport.request("/settings", {
972
1321
  method: "PATCH",
973
1322
  body: patch,
974
1323
  signal: options.signal
975
1324
  });
976
1325
  return response.settings;
977
1326
  }
978
- async listProviders(options = {}) {
979
- const response = await this.request("/providers", { signal: options.signal });
980
- return response.providers;
981
- }
982
- async createCustomProvider(input, options = {}) {
983
- const response = await this.request("/providers/custom", {
984
- method: "POST",
985
- body: input,
986
- signal: options.signal
987
- });
988
- return response.provider;
989
- }
990
- async connectCatalogProvider(id, apiKey, options = {}) {
991
- const response = await this.request(`/providers/catalog/${encodeURIComponent(id)}/connect`, { method: "POST", body: { apiKey }, signal: options.signal });
992
- return response.provider;
993
- }
994
- async disconnectCatalogProvider(id, options = {}) {
995
- await this.request(`/providers/catalog/${encodeURIComponent(id)}/connect`, { method: "DELETE", signal: options.signal });
996
- }
997
- async updateCustomProvider(id, input, options = {}) {
998
- const response = await this.request(`/providers/custom/${encodeURIComponent(id)}`, { method: "PATCH", body: input, signal: options.signal });
999
- return response.provider;
1000
- }
1001
- async removeCustomProvider(id, options = {}) {
1002
- await this.request(`/providers/custom/${encodeURIComponent(id)}`, {
1003
- method: "DELETE",
1004
- signal: options.signal
1005
- });
1006
- }
1007
- async listModels(options = {}) {
1008
- const response = await this.request("/models", { signal: options.signal });
1009
- return response.providers;
1010
- }
1011
1327
  async getSessionMcp(sessionId, options = {}) {
1012
- const response = await this.request(`/sessions/${encodeURIComponent(sessionId)}/mcp`, { signal: options.signal });
1328
+ const response = await this.transport.request(`/sessions/${encodeURIComponent(sessionId)}/mcp`, { signal: options.signal });
1013
1329
  return response.servers;
1014
1330
  }
1015
1331
  async listMemory(options) {
1016
1332
  const { signal, ...query } = options;
1017
- return await this.request(this.path("/memory", query), {
1018
- signal
1019
- });
1333
+ return await this.transport.request(this.transport.path("/memory", query), { signal });
1020
1334
  }
1021
1335
  async getMemory(entryId, options) {
1022
1336
  const { signal, cwd } = options;
1023
- const response = await this.request(this.path(`/memory/${encodeURIComponent(entryId)}`, { cwd }), { signal });
1337
+ const response = await this.transport.request(this.transport.path(`/memory/${encodeURIComponent(entryId)}`, { cwd }), { signal });
1024
1338
  return response.entry;
1025
1339
  }
1026
1340
  async addMemory(input, options = {}) {
1027
- const response = await this.request("/memory", {
1341
+ const response = await this.transport.request("/memory", {
1028
1342
  method: "POST",
1029
1343
  body: input,
1030
1344
  signal: options.signal
@@ -1033,36 +1347,21 @@ class OpenHarnessClient {
1033
1347
  }
1034
1348
  async removeMemory(entryId, options) {
1035
1349
  const { signal, cwd } = options;
1036
- await this.request(this.path(`/memory/${encodeURIComponent(entryId)}`, { cwd }), { method: "DELETE", signal });
1037
- }
1038
- async getAuthStatus(options = {}) {
1039
- const response = await this.request("/auth", {
1040
- signal: options.signal
1041
- });
1042
- return response.auth;
1043
- }
1044
- async authLogin(input, options = {}) {
1045
- return await this.request("/auth/login", {
1046
- method: "POST",
1047
- body: input,
1048
- signal: options.signal
1049
- });
1350
+ await this.transport.request(this.transport.path(`/memory/${encodeURIComponent(entryId)}`, { cwd }), { method: "DELETE", signal });
1050
1351
  }
1051
- async authLogout(input, options = {}) {
1052
- return await this.request("/auth/logout", {
1053
- method: "POST",
1054
- body: input,
1055
- signal: options.signal
1056
- });
1352
+ async listContextPlugins(options) {
1353
+ const { signal, ...query } = options;
1354
+ const response = await this.transport.request(this.transport.path("/context/plugins", query), { signal });
1355
+ return response.plugins;
1057
1356
  }
1058
1357
  async getContextPreview(options) {
1059
1358
  const { signal, ...query } = options;
1060
- const response = await this.request(this.path("/context", query), { signal });
1359
+ const response = await this.transport.request(this.transport.path("/context", query), { signal });
1061
1360
  return response.report;
1062
1361
  }
1063
1362
  async getContextStatus(options) {
1064
1363
  const { signal, ...query } = options;
1065
- const response = await this.request(this.path("/context/status", query), { signal });
1364
+ const response = await this.transport.request(this.transport.path("/context/status", query), { signal });
1066
1365
  return response.report;
1067
1366
  }
1068
1367
  async getContextUsage(options) {
@@ -1072,357 +1371,559 @@ class OpenHarnessClient {
1072
1371
  ...refresh !== undefined ? { refresh: refresh ? "true" : "false" } : {},
1073
1372
  ...previousContextWindow !== undefined ? { previousContextWindow: String(previousContextWindow) } : {}
1074
1373
  };
1075
- return await this.request(this.path("/context/usage", query), { signal });
1076
- }
1077
- async compactSession(sessionId, options = {}) {
1078
- return await this.request(`/sessions/${encodeURIComponent(sessionId)}/compact`, { method: "POST", signal: options.signal });
1079
- }
1080
- async getSessionGoal(sessionId) {
1081
- const response = await this.request(`/sessions/${encodeURIComponent(sessionId)}/goal`);
1082
- return response.goal;
1083
- }
1084
- async createSessionGoal(sessionId, input) {
1085
- const response = await this.request(`/sessions/${encodeURIComponent(sessionId)}/goals`, { method: "POST", body: input });
1086
- return response.goal;
1087
- }
1088
- async updateSessionGoal(sessionId, goalId, input) {
1089
- const response = await this.request(`/sessions/${encodeURIComponent(sessionId)}/goals/${encodeURIComponent(goalId)}`, { method: "PATCH", body: input });
1090
- return response.goal;
1091
- }
1092
- async applySessionGoalAction(sessionId, goalId, input) {
1093
- const response = await this.request(`/sessions/${encodeURIComponent(sessionId)}/goals/${encodeURIComponent(goalId)}/actions`, { method: "POST", body: input });
1094
- return response.goal;
1095
- }
1096
- async rewindSession(sessionId, input = {}, options = {}) {
1097
- return await this.request(`/sessions/${encodeURIComponent(sessionId)}/rewind`, { method: "POST", body: input, signal: options.signal });
1098
- }
1099
- async rememberSession(sessionId, options = {}) {
1100
- return await this.request(`/sessions/${encodeURIComponent(sessionId)}/remember`, { method: "POST", signal: options.signal });
1374
+ return await this.transport.request(this.transport.path("/context/usage", query), { signal });
1101
1375
  }
1102
1376
  async startDream(input, options = {}) {
1103
- return await this.request("/dream", {
1377
+ return await this.transport.request("/dream", {
1104
1378
  method: "POST",
1105
1379
  body: input,
1106
1380
  signal: options.signal
1107
1381
  });
1108
1382
  }
1109
1383
  async getProfileStatus(options = {}) {
1110
- const response = await this.request("/profile", {
1384
+ const response = await this.transport.request("/profile", {
1111
1385
  signal: options.signal
1112
1386
  });
1113
1387
  return response.report;
1114
1388
  }
1115
1389
  async initProfile(options = {}) {
1116
- const response = await this.request("/profile/init", {
1390
+ const response = await this.transport.request("/profile/init", {
1117
1391
  method: "POST",
1118
1392
  signal: options.signal
1119
1393
  });
1120
1394
  return response.report;
1121
1395
  }
1122
1396
  async listOutputStyles(options = {}) {
1123
- const response = await this.request("/output-styles", {
1397
+ const response = await this.transport.request("/output-styles", {
1124
1398
  signal: options.signal
1125
1399
  });
1126
1400
  return response.styles;
1127
1401
  }
1128
- async initProject(input, options = {}) {
1129
- const response = await this.request("/project/init", {
1402
+ }
1403
+
1404
+ // ../../packages/client/src/resources/provider-resource.ts
1405
+ class ProviderResource {
1406
+ transport;
1407
+ constructor(transport) {
1408
+ this.transport = transport;
1409
+ }
1410
+ async listProviders(options = {}) {
1411
+ const response = await this.transport.request("/providers", { signal: options.signal });
1412
+ return response.providers;
1413
+ }
1414
+ async createCustomProvider(input, options = {}) {
1415
+ const response = await this.transport.request("/providers/custom", {
1416
+ method: "POST",
1417
+ body: input,
1418
+ signal: options.signal
1419
+ });
1420
+ return response.provider;
1421
+ }
1422
+ async connectCatalogProvider(id, apiKeyOrInput, options = {}) {
1423
+ const body = typeof apiKeyOrInput === "string" ? { apiKey: apiKeyOrInput } : apiKeyOrInput;
1424
+ const response = await this.transport.request(`/providers/catalog/${encodeURIComponent(id)}/connect`, { method: "POST", body, signal: options.signal });
1425
+ return response.provider;
1426
+ }
1427
+ async updateCatalogProviderHeaders(id, headers, options = {}) {
1428
+ const response = await this.transport.request(`/providers/catalog/${encodeURIComponent(id)}`, {
1429
+ method: "PATCH",
1430
+ body: { headers },
1431
+ signal: options.signal
1432
+ });
1433
+ return response.provider;
1434
+ }
1435
+ async disconnectCatalogProvider(id, options = {}) {
1436
+ await this.transport.request(`/providers/catalog/${encodeURIComponent(id)}/connect`, { method: "DELETE", signal: options.signal });
1437
+ }
1438
+ async updateCustomProvider(id, input, options = {}) {
1439
+ const response = await this.transport.request(`/providers/custom/${encodeURIComponent(id)}`, { method: "PATCH", body: input, signal: options.signal });
1440
+ return response.provider;
1441
+ }
1442
+ async removeCustomProvider(id, options = {}) {
1443
+ await this.transport.request(`/providers/custom/${encodeURIComponent(id)}`, {
1444
+ method: "DELETE",
1445
+ signal: options.signal
1446
+ });
1447
+ }
1448
+ async listModels(options = {}) {
1449
+ const response = await this.transport.request("/models", { signal: options.signal });
1450
+ return response.providers;
1451
+ }
1452
+ }
1453
+
1454
+ // ../../packages/client/src/resources/auth-resource.ts
1455
+ class AuthResource {
1456
+ transport;
1457
+ constructor(transport) {
1458
+ this.transport = transport;
1459
+ }
1460
+ async getStatus(options = {}) {
1461
+ const response = await this.transport.request("/auth", {
1462
+ signal: options.signal
1463
+ });
1464
+ return response.auth;
1465
+ }
1466
+ async login(input, options = {}) {
1467
+ return await this.transport.request("/auth/login", {
1468
+ method: "POST",
1469
+ body: input,
1470
+ signal: options.signal
1471
+ });
1472
+ }
1473
+ async logout(input, options = {}) {
1474
+ return await this.transport.request("/auth/logout", {
1475
+ method: "POST",
1476
+ body: input,
1477
+ signal: options.signal
1478
+ });
1479
+ }
1480
+ }
1481
+
1482
+ // ../../packages/client/src/resources/project-resource.ts
1483
+ class ProjectResource {
1484
+ transport;
1485
+ constructor(transport) {
1486
+ this.transport = transport;
1487
+ }
1488
+ async list(options = {}) {
1489
+ const { signal, ...query } = options;
1490
+ const response = await this.transport.request(this.transport.path("/projects", query), { signal });
1491
+ return response.projects;
1492
+ }
1493
+ async inspect(path) {
1494
+ return (await this.transport.request("/projects/inspect", {
1495
+ method: "POST",
1496
+ body: { path }
1497
+ })).project;
1498
+ }
1499
+ async rename(projectId, name) {
1500
+ return (await this.transport.request(`/projects/${encodeURIComponent(projectId)}`, { method: "PATCH", body: { name } })).project;
1501
+ }
1502
+ async setPinned(projectId, pinned) {
1503
+ return (await this.transport.request(`/projects/${encodeURIComponent(projectId)}`, { method: "PATCH", body: { pinned } })).project;
1504
+ }
1505
+ async setDefaultShell(projectId, defaultShell) {
1506
+ return (await this.transport.request(`/projects/${encodeURIComponent(projectId)}`, { method: "PATCH", body: { defaultShell } })).project;
1507
+ }
1508
+ async rebind(projectId, path) {
1509
+ return (await this.transport.request(`/projects/${encodeURIComponent(projectId)}/rebind`, { method: "POST", body: { path } })).project;
1510
+ }
1511
+ async archive(projectId) {
1512
+ return (await this.transport.request(`/projects/${encodeURIComponent(projectId)}`, { method: "DELETE" })).project;
1513
+ }
1514
+ async init(input, options = {}) {
1515
+ const response = await this.transport.request("/project/init", {
1130
1516
  method: "POST",
1131
1517
  body: input,
1132
1518
  signal: options.signal
1133
1519
  });
1134
1520
  return response.report;
1135
1521
  }
1136
- async listPlugins(options) {
1522
+ }
1523
+
1524
+ // ../../packages/client/src/resources/plugin-resource.ts
1525
+ class PluginResource {
1526
+ transport;
1527
+ constructor(transport) {
1528
+ this.transport = transport;
1529
+ }
1530
+ async list(options) {
1137
1531
  const { signal, ...query } = options;
1138
- return await this.request(this.path("/plugins", query), { signal });
1532
+ return await this.transport.request(this.transport.path("/plugins", query), { signal });
1533
+ }
1534
+ async enable(id, input, options = {}) {
1535
+ return await this.transport.request(`/plugins/${encodeURIComponent(id)}/enable`, { method: "POST", body: input, signal: options.signal });
1536
+ }
1537
+ async disable(id, input, options = {}) {
1538
+ return await this.transport.request(`/plugins/${encodeURIComponent(id)}/disable`, { method: "POST", body: input, signal: options.signal });
1139
1539
  }
1140
- async enablePlugin(id, input, options = {}) {
1141
- return await this.request(`/plugins/${encodeURIComponent(id)}/enable`, { method: "POST", body: input, signal: options.signal });
1540
+ async installLocal(input) {
1541
+ return await this.transport.request(input.link ? "/plugins/link-local" : "/plugins/install-local", { method: "POST", body: input });
1142
1542
  }
1143
- async disablePlugin(id, input, options = {}) {
1144
- return await this.request(`/plugins/${encodeURIComponent(id)}/disable`, { method: "POST", body: input, signal: options.signal });
1543
+ async previewArchive(input, options = {}) {
1544
+ return await this.transport.request("/plugins/archive/preview", {
1545
+ method: "POST",
1546
+ body: input,
1547
+ signal: options.signal
1548
+ });
1145
1549
  }
1146
- async installLocalPlugin(input) {
1147
- return await this.request(input.link ? "/plugins/link-local" : "/plugins/install-local", { method: "POST", body: input });
1550
+ async installArchive(input, options = {}) {
1551
+ return await this.transport.request("/plugins/archive/install", {
1552
+ method: "POST",
1553
+ body: input,
1554
+ signal: options.signal
1555
+ });
1148
1556
  }
1149
- async previewPluginArchive(input, options = {}) {
1150
- return await this.request("/plugins/archive/preview", {
1557
+ async previewGit(input, options = {}) {
1558
+ return await this.transport.request("/plugins/git/preview", {
1151
1559
  method: "POST",
1152
1560
  body: input,
1153
1561
  signal: options.signal
1154
1562
  });
1155
1563
  }
1156
- async installPluginArchive(input, options = {}) {
1157
- return await this.request("/plugins/archive/install", {
1564
+ async installGit(input, options = {}) {
1565
+ return await this.transport.request("/plugins/git/install", {
1158
1566
  method: "POST",
1159
1567
  body: input,
1160
1568
  signal: options.signal
1161
1569
  });
1162
1570
  }
1163
- async uninstallPlugin(id, input) {
1164
- return await this.request(`/plugins/${encodeURIComponent(id)}`, { method: "DELETE", body: input });
1571
+ async uninstall(id, input) {
1572
+ return await this.transport.request(`/plugins/${encodeURIComponent(id)}`, { method: "DELETE", body: input });
1165
1573
  }
1166
- async reloadPlugins(input, options = {}) {
1167
- return await this.request("/plugins/reload", {
1574
+ async reload(input, options = {}) {
1575
+ return await this.transport.request("/plugins/reload", {
1168
1576
  method: "POST",
1169
1577
  body: input,
1170
1578
  signal: options.signal
1171
1579
  });
1172
1580
  }
1581
+ }
1582
+
1583
+ // ../../packages/client/src/resources/development-resource.ts
1584
+ class DevelopmentResource {
1585
+ transport;
1586
+ constructor(transport) {
1587
+ this.transport = transport;
1588
+ }
1173
1589
  async listSkills(options = {}) {
1174
- return await this.request("/skills", {
1590
+ return await this.transport.request("/skills", {
1175
1591
  signal: options.signal
1176
1592
  });
1177
1593
  }
1178
1594
  async removeSkill(id, input, options = {}) {
1179
- return await this.request(`/skills/${encodeURIComponent(id)}`, {
1595
+ return await this.transport.request(`/skills/${encodeURIComponent(id)}`, {
1180
1596
  method: "DELETE",
1181
1597
  body: input,
1182
1598
  signal: options.signal
1183
1599
  });
1184
1600
  }
1185
1601
  async listAgentPersonas(options = {}) {
1186
- const response = await this.request("/agent-personas", {
1602
+ const response = await this.transport.request("/agent-personas", {
1187
1603
  signal: options.signal
1188
1604
  });
1189
1605
  return response.agents;
1190
1606
  }
1191
1607
  async listHooks(options) {
1192
1608
  const { signal, ...query } = options;
1193
- const response = await this.request(this.path("/hooks", query), { signal });
1609
+ const response = await this.transport.request(this.transport.path("/hooks", query), { signal });
1194
1610
  return response.hooks;
1195
1611
  }
1196
1612
  async getGitDiff(options) {
1197
1613
  const { signal, cwd, full } = options;
1198
- const response = await this.request(this.path("/git/diff", { cwd, ...full ? { full: "true" } : {} }), { signal });
1614
+ const response = await this.transport.request(this.transport.path("/git/diff", { cwd, ...full ? { full: "true" } : {} }), { signal });
1199
1615
  return response.output;
1200
1616
  }
1201
1617
  async getGitBranch(options) {
1202
1618
  const { signal, cwd, list } = options;
1203
- const response = await this.request(this.path("/git/branch", { cwd, ...list ? { list: "true" } : {} }), { signal });
1619
+ const response = await this.transport.request(this.transport.path("/git/branch", { cwd, ...list ? { list: "true" } : {} }), { signal });
1204
1620
  return response.output;
1205
1621
  }
1206
1622
  async getGitStatus(options) {
1207
1623
  const { signal, cwd } = options;
1208
- const response = await this.request(this.path("/git/status", { cwd }), { signal });
1624
+ const response = await this.transport.request(this.transport.path("/git/status", { cwd }), { signal });
1209
1625
  return response.output;
1210
1626
  }
1211
1627
  async gitCommit(input, options = {}) {
1212
- const response = await this.request("/git/commit", {
1628
+ const response = await this.transport.request("/git/commit", {
1213
1629
  method: "POST",
1214
1630
  body: input,
1215
1631
  signal: options.signal
1216
1632
  });
1217
1633
  return response.output;
1218
1634
  }
1219
- async getSessionUsage(sessionId, options = {}) {
1220
- return await this.request(`/sessions/${encodeURIComponent(sessionId)}/usage`, { signal: options.signal });
1635
+ }
1636
+
1637
+ // ../../packages/client/src/resources/session-resource.ts
1638
+ function createPromptRequestId() {
1639
+ if (typeof globalThis.crypto?.randomUUID === "function") {
1640
+ return globalThis.crypto.randomUUID();
1221
1641
  }
1222
- async exportSession(sessionId, input = {}, options = {}) {
1223
- return await this.request(`/sessions/${encodeURIComponent(sessionId)}/export`, { method: "POST", body: input, signal: options.signal });
1642
+ promptRequestCounter += 1;
1643
+ return `prompt-${Date.now().toString(36)}-${promptRequestCounter.toString(36)}`;
1644
+ }
1645
+
1646
+ class SessionResource {
1647
+ transport;
1648
+ constructor(transport) {
1649
+ this.transport = transport;
1224
1650
  }
1225
- async listSessions(options = {}) {
1651
+ async list(options = {}) {
1226
1652
  const { signal, ...query } = options;
1227
- const response = await this.request(this.path("/sessions", query), { signal });
1653
+ const response = await this.transport.request(this.transport.path("/sessions", query), { signal });
1228
1654
  return response.sessions;
1229
1655
  }
1230
- async listProjects(options = {}) {
1231
- const { signal, ...query } = options;
1232
- const response = await this.request(this.path("/projects", query), { signal });
1233
- return response.projects;
1234
- }
1235
- async inspectProject(path) {
1236
- return (await this.request("/projects/inspect", {
1237
- method: "POST",
1238
- body: { path }
1239
- })).project;
1240
- }
1241
- async renameProject(projectId, name) {
1242
- return (await this.request(`/projects/${encodeURIComponent(projectId)}`, { method: "PATCH", body: { name } })).project;
1243
- }
1244
- async setProjectPinned(projectId, pinned) {
1245
- return (await this.request(`/projects/${encodeURIComponent(projectId)}`, { method: "PATCH", body: { pinned } })).project;
1246
- }
1247
- async setProjectDefaultShell(projectId, defaultShell) {
1248
- return (await this.request(`/projects/${encodeURIComponent(projectId)}`, { method: "PATCH", body: { defaultShell } })).project;
1249
- }
1250
- async rebindProject(projectId, path) {
1251
- return (await this.request(`/projects/${encodeURIComponent(projectId)}/rebind`, { method: "POST", body: { path } })).project;
1252
- }
1253
- async archiveProject(projectId) {
1254
- return (await this.request(`/projects/${encodeURIComponent(projectId)}`, { method: "DELETE" })).project;
1656
+ async get(sessionId, options = {}) {
1657
+ const response = await this.transport.request(`/sessions/${encodeURIComponent(sessionId)}`, {
1658
+ signal: options.signal
1659
+ });
1660
+ return response.session;
1255
1661
  }
1256
- async createSession(input, options = {}) {
1257
- const response = await this.request("/sessions", {
1662
+ async create(input, options = {}) {
1663
+ const response = await this.transport.request("/sessions", {
1258
1664
  method: "POST",
1259
1665
  body: input,
1260
1666
  signal: options.signal
1261
1667
  });
1262
1668
  return response.session;
1263
1669
  }
1264
- async getSession(sessionId, options = {}) {
1265
- const response = await this.request(`/sessions/${encodeURIComponent(sessionId)}`, {
1670
+ async update(sessionId, input, options = {}) {
1671
+ const response = await this.transport.request(`/sessions/${encodeURIComponent(sessionId)}`, {
1672
+ method: "PATCH",
1673
+ body: input,
1266
1674
  signal: options.signal
1267
1675
  });
1268
1676
  return response.session;
1269
1677
  }
1270
- async forkSession(sessionId, input = {}, options = {}) {
1271
- const response = await this.request(`/sessions/${encodeURIComponent(sessionId)}/fork`, {
1678
+ async fork(sessionId, input = {}, options = {}) {
1679
+ const response = await this.transport.request(`/sessions/${encodeURIComponent(sessionId)}/fork`, {
1272
1680
  method: "POST",
1273
1681
  body: input,
1274
1682
  signal: options.signal
1275
1683
  });
1276
1684
  return response.session;
1277
1685
  }
1278
- async getSessionState(sessionId, options = {}) {
1279
- const response = await this.request(`/sessions/${encodeURIComponent(sessionId)}/state`, {
1280
- signal: options.signal
1281
- });
1282
- return decodeSessionStateSnapshot(response);
1283
- }
1284
- async archiveSession(sessionId, options = {}) {
1285
- const response = await this.request(`/sessions/${encodeURIComponent(sessionId)}`, {
1686
+ async archive(sessionId, options = {}) {
1687
+ const response = await this.transport.request(`/sessions/${encodeURIComponent(sessionId)}`, {
1286
1688
  method: "DELETE",
1287
1689
  signal: options.signal
1288
1690
  });
1289
1691
  return response.session;
1290
1692
  }
1291
- async deleteSession(sessionId, options = {}) {
1292
- const response = await this.request(`/sessions/${encodeURIComponent(sessionId)}/hard`, {
1693
+ async delete(sessionId, options = {}) {
1694
+ const response = await this.transport.request(`/sessions/${encodeURIComponent(sessionId)}/hard`, {
1293
1695
  method: "DELETE",
1294
1696
  signal: options.signal
1295
1697
  });
1296
1698
  return response.deletedSessionIds;
1297
1699
  }
1298
- async updateSession(sessionId, input, options = {}) {
1299
- const response = await this.request(`/sessions/${encodeURIComponent(sessionId)}`, {
1300
- method: "PATCH",
1301
- body: input,
1700
+ async getState(sessionId, options = {}) {
1701
+ const response = await this.transport.request(`/sessions/${encodeURIComponent(sessionId)}/state`, {
1302
1702
  signal: options.signal
1303
1703
  });
1304
- return response.session;
1704
+ return decodeSessionStateSnapshot(response);
1305
1705
  }
1306
1706
  async listMessages(sessionId, options = {}) {
1307
1707
  const { signal, ...query } = options;
1308
- const response = await this.request(this.path(`/sessions/${encodeURIComponent(sessionId)}/messages`, query), { signal });
1708
+ const response = await this.transport.request(this.transport.path(`/sessions/${encodeURIComponent(sessionId)}/messages`, query), { signal });
1309
1709
  return response.messages;
1310
1710
  }
1311
1711
  async listMessageParts(sessionId, options = {}) {
1312
1712
  const { signal, ...query } = options;
1313
- const response = await this.request(this.path(`/sessions/${encodeURIComponent(sessionId)}/parts`, query), { signal });
1713
+ const response = await this.transport.request(this.transport.path(`/sessions/${encodeURIComponent(sessionId)}/parts`, query), { signal });
1314
1714
  return response.parts;
1315
1715
  }
1316
1716
  async admitPrompt(sessionId, input, options = {}) {
1317
- return await this.request(`/sessions/${encodeURIComponent(sessionId)}/prompts`, {
1717
+ return await this.transport.request(`/sessions/${encodeURIComponent(sessionId)}/prompts`, {
1318
1718
  method: "POST",
1319
1719
  body: { ...input, id: input.id ?? createPromptRequestId() },
1320
1720
  signal: options.signal
1321
1721
  });
1322
1722
  }
1323
1723
  async editLatestPrompt(sessionId, input, options = {}) {
1324
- return await this.request(`/sessions/${encodeURIComponent(sessionId)}/prompts/latest/edit`, {
1724
+ return await this.transport.request(`/sessions/${encodeURIComponent(sessionId)}/prompts/latest/edit`, {
1325
1725
  method: "POST",
1326
1726
  body: input,
1327
1727
  signal: options.signal
1328
1728
  });
1329
1729
  }
1330
- async promoteQueuedPrompt(sessionId, inputId, input, options = {}) {
1331
- return await this.request(`/sessions/${encodeURIComponent(sessionId)}/prompts/${encodeURIComponent(inputId)}/promote`, { method: "POST", body: input, signal: options.signal });
1730
+ async promoteQueuedPrompt(sessionId, inputId, input, options = {}) {
1731
+ return await this.transport.request(`/sessions/${encodeURIComponent(sessionId)}/prompts/${encodeURIComponent(inputId)}/promote`, { method: "POST", body: input, signal: options.signal });
1732
+ }
1733
+ async cancelQueuedPrompt(sessionId, inputId, input, options = {}) {
1734
+ return await this.transport.request(`/sessions/${encodeURIComponent(sessionId)}/prompts/${encodeURIComponent(inputId)}/cancel`, { method: "POST", body: input, signal: options.signal });
1735
+ }
1736
+ async resumeInterruptedRun(sessionId, runId, input = {}, options = {}) {
1737
+ return await this.transport.request(`/sessions/${encodeURIComponent(sessionId)}/runs/${encodeURIComponent(runId)}/resume`, {
1738
+ method: "POST",
1739
+ body: { ...input, id: input.id ?? createPromptRequestId() },
1740
+ signal: options.signal
1741
+ });
1742
+ }
1743
+ async interrupt(sessionId, options = {}) {
1744
+ return await this.transport.request(`/sessions/${encodeURIComponent(sessionId)}/interrupt`, {
1745
+ method: "POST",
1746
+ body: options.expectedRunId ? { expectedRunId: options.expectedRunId } : undefined,
1747
+ signal: options.signal
1748
+ });
1749
+ }
1750
+ async compact(sessionId, options = {}) {
1751
+ return await this.transport.request(`/sessions/${encodeURIComponent(sessionId)}/compact`, { method: "POST", signal: options.signal });
1752
+ }
1753
+ async rewind(sessionId, input = {}, options = {}) {
1754
+ return await this.transport.request(`/sessions/${encodeURIComponent(sessionId)}/rewind`, { method: "POST", body: input, signal: options.signal });
1755
+ }
1756
+ async remember(sessionId, options = {}) {
1757
+ return await this.transport.request(`/sessions/${encodeURIComponent(sessionId)}/remember`, { method: "POST", signal: options.signal });
1758
+ }
1759
+ async export(sessionId, input = {}, options = {}) {
1760
+ return await this.transport.request(`/sessions/${encodeURIComponent(sessionId)}/export`, { method: "POST", body: input, signal: options.signal });
1761
+ }
1762
+ async getUsage(sessionId, options = {}) {
1763
+ return await this.transport.request(`/sessions/${encodeURIComponent(sessionId)}/usage`, { signal: options.signal });
1764
+ }
1765
+ async getGoal(sessionId) {
1766
+ const response = await this.transport.request(`/sessions/${encodeURIComponent(sessionId)}/goal`);
1767
+ return response.goal;
1768
+ }
1769
+ async createGoal(sessionId, input) {
1770
+ const response = await this.transport.request(`/sessions/${encodeURIComponent(sessionId)}/goals`, { method: "POST", body: input });
1771
+ return response.goal;
1772
+ }
1773
+ async updateGoal(sessionId, goalId, input) {
1774
+ const response = await this.transport.request(`/sessions/${encodeURIComponent(sessionId)}/goals/${encodeURIComponent(goalId)}`, { method: "PATCH", body: input });
1775
+ return response.goal;
1776
+ }
1777
+ async applyGoalAction(sessionId, goalId, input) {
1778
+ const response = await this.transport.request(`/sessions/${encodeURIComponent(sessionId)}/goals/${encodeURIComponent(goalId)}/actions`, { method: "POST", body: input });
1779
+ return response.goal;
1780
+ }
1781
+ }
1782
+ var promptRequestCounter = 0;
1783
+ var init_session_resource = __esm(() => {
1784
+ init_src();
1785
+ });
1786
+
1787
+ // ../../packages/client/src/resources/attachment-resource.ts
1788
+ class AttachmentResource {
1789
+ transport;
1790
+ constructor(transport) {
1791
+ this.transport = transport;
1792
+ }
1793
+ async upload(input) {
1794
+ const headers = {};
1795
+ headers["x-openharness-filename"] = encodeURIComponent(input.displayName);
1796
+ if (input.mediaType)
1797
+ headers["content-type"] = input.mediaType;
1798
+ const response = await this.transport.requestResponse("/attachments", {
1799
+ method: "POST",
1800
+ headers,
1801
+ body: input.body,
1802
+ signal: input.signal
1803
+ });
1804
+ return parseAttachmentAssetRecord(await response.json());
1805
+ }
1806
+ async get(id, options = {}) {
1807
+ const value = await this.transport.request(`/attachments/${encodeURIComponent(id)}`, { signal: options.signal });
1808
+ return parseAttachmentAssetRecord(value);
1809
+ }
1810
+ async download(id, options = {}) {
1811
+ const range = attachmentRangeHeader(options.range);
1812
+ const headers = {};
1813
+ if (range)
1814
+ headers.range = range;
1815
+ return await this.transport.requestResponse(`/attachments/${encodeURIComponent(id)}/content`, { method: "GET", headers, signal: options.signal });
1332
1816
  }
1333
- async cancelQueuedPrompt(sessionId, inputId, input, options = {}) {
1334
- return await this.request(`/sessions/${encodeURIComponent(sessionId)}/prompts/${encodeURIComponent(inputId)}/cancel`, { method: "POST", body: input, signal: options.signal });
1817
+ async delete(id, options = {}) {
1818
+ const value = await this.transport.request(`/attachments/${encodeURIComponent(id)}`, { method: "DELETE", signal: options.signal });
1819
+ return parseAttachmentAssetRecord(value);
1335
1820
  }
1336
- async resumeInterruptedRun(sessionId, runId, input = {}, options = {}) {
1337
- return await this.request(`/sessions/${encodeURIComponent(sessionId)}/runs/${encodeURIComponent(runId)}/resume`, {
1821
+ async scanStorage(options = {}) {
1822
+ return await this.transport.request("/attachments/storage", { signal: options.signal });
1823
+ }
1824
+ async repairStorage(options = {}) {
1825
+ return await this.transport.request("/attachments/storage/actions", {
1338
1826
  method: "POST",
1339
- body: { ...input, id: input.id ?? createPromptRequestId() },
1827
+ body: { action: "repair-safe" },
1340
1828
  signal: options.signal
1341
1829
  });
1342
1830
  }
1343
- async interruptSession(sessionId, options = {}) {
1344
- return await this.request(`/sessions/${encodeURIComponent(sessionId)}/interrupt`, {
1831
+ async gcStorage(options = {}) {
1832
+ return await this.transport.request("/attachments/storage/actions", {
1345
1833
  method: "POST",
1346
- body: options.expectedRunId ? { expectedRunId: options.expectedRunId } : undefined,
1834
+ body: { action: "gc" },
1347
1835
  signal: options.signal
1348
1836
  });
1349
1837
  }
1350
- async listEvents(options = {}) {
1351
- const { signal, ...query } = options;
1352
- const response = await this.request(this.path("/events", query), {
1353
- signal
1354
- });
1355
- return responseArray(response, "events", decodeSessionEventRecord);
1838
+ }
1839
+ var init_attachment_resource = __esm(() => {
1840
+ init_src();
1841
+ init_http_transport();
1842
+ });
1843
+
1844
+ // ../../packages/client/src/resources/permission-resource.ts
1845
+ class PermissionResource {
1846
+ transport;
1847
+ constructor(transport) {
1848
+ this.transport = transport;
1356
1849
  }
1357
- async listPermissions(options = {}) {
1850
+ async list(options = {}) {
1358
1851
  const { signal, ...query } = options;
1359
- const response = await this.request(this.path("/permissions", query), { signal });
1852
+ const response = await this.transport.request(this.transport.path("/permissions", query), { signal });
1360
1853
  return response.requests;
1361
1854
  }
1362
- async replyPermission(requestId, input, options = {}) {
1363
- const response = await this.request(`/permissions/${encodeURIComponent(requestId)}/reply`, {
1855
+ async reply(requestId, input, options = {}) {
1856
+ const response = await this.transport.request(`/permissions/${encodeURIComponent(requestId)}/reply`, {
1364
1857
  method: "POST",
1365
1858
  body: input,
1366
1859
  signal: options.signal
1367
1860
  });
1368
1861
  return response.request;
1369
1862
  }
1370
- async getScheduledTaskStatus(options = {}) {
1371
- return await this.request("/schedules/status", {
1863
+ }
1864
+
1865
+ // ../../packages/client/src/resources/schedule-resource.ts
1866
+ class ScheduleResource {
1867
+ transport;
1868
+ constructor(transport) {
1869
+ this.transport = transport;
1870
+ }
1871
+ async getStatus(options = {}) {
1872
+ return await this.transport.request("/schedules/status", {
1372
1873
  signal: options.signal
1373
1874
  });
1374
1875
  }
1375
- async listScheduledTasks(options = {}) {
1876
+ async listTasks(options = {}) {
1376
1877
  const { signal, ...query } = options;
1377
- const response = await this.request(this.path("/schedules/tasks", query), { signal });
1878
+ const response = await this.transport.request(this.transport.path("/schedules/tasks", query), { signal });
1378
1879
  return response.tasks;
1379
1880
  }
1380
- async getScheduledTask(id, options = {}) {
1381
- const response = await this.request(`/schedules/tasks/${encodeURIComponent(id)}`, { signal: options.signal });
1881
+ async getTask(id, options = {}) {
1882
+ const response = await this.transport.request(`/schedules/tasks/${encodeURIComponent(id)}`, { signal: options.signal });
1382
1883
  return response.task;
1383
1884
  }
1384
- async createScheduledTask(input, options = {}) {
1385
- const response = await this.request("/schedules/tasks", {
1885
+ async createTask(input, options = {}) {
1886
+ const response = await this.transport.request("/schedules/tasks", {
1386
1887
  method: "POST",
1387
1888
  body: input,
1388
1889
  signal: options.signal
1389
1890
  });
1390
1891
  return response.task;
1391
1892
  }
1392
- async updateScheduledTask(id, input, options = {}) {
1393
- const response = await this.request(`/schedules/tasks/${encodeURIComponent(id)}`, { method: "PATCH", body: input, signal: options.signal });
1893
+ async updateTask(id, input, options = {}) {
1894
+ const response = await this.transport.request(`/schedules/tasks/${encodeURIComponent(id)}`, { method: "PATCH", body: input, signal: options.signal });
1394
1895
  return response.task;
1395
1896
  }
1396
- async removeScheduledTask(id, options = {}) {
1397
- await this.request(`/schedules/tasks/${encodeURIComponent(id)}`, {
1897
+ async removeTask(id, options = {}) {
1898
+ await this.transport.request(`/schedules/tasks/${encodeURIComponent(id)}`, {
1398
1899
  method: "DELETE",
1399
1900
  signal: options.signal
1400
1901
  });
1401
1902
  }
1402
- async triggerScheduledTask(id, options = {}) {
1403
- const response = await this.request(`/schedules/tasks/${encodeURIComponent(id)}/run`, { method: "POST", signal: options.signal });
1903
+ async triggerTask(id, options = {}) {
1904
+ const response = await this.transport.request(`/schedules/tasks/${encodeURIComponent(id)}/run`, { method: "POST", signal: options.signal });
1404
1905
  return response.run;
1405
1906
  }
1406
- async listScheduledRuns(options = {}) {
1907
+ async listRuns(options = {}) {
1407
1908
  const { signal, ...query } = options;
1408
- const response = await this.request(this.path("/schedules/runs", query), { signal });
1909
+ const response = await this.transport.request(this.transport.path("/schedules/runs", query), { signal });
1409
1910
  return response.runs;
1410
1911
  }
1411
- async setScheduledRunUnread(id, unread, options = {}) {
1412
- const response = await this.request(`/schedules/runs/${encodeURIComponent(id)}/read`, { method: "PATCH", body: { unread }, signal: options.signal });
1912
+ async setRunUnread(id, unread, options = {}) {
1913
+ const response = await this.transport.request(`/schedules/runs/${encodeURIComponent(id)}/read`, { method: "PATCH", body: { unread }, signal: options.signal });
1413
1914
  return response.run;
1414
1915
  }
1415
- async createTerminal(input, options = {}) {
1416
- const response = await this.request("/terminals", {
1417
- method: "POST",
1418
- body: input,
1419
- signal: options.signal
1420
- });
1421
- return decodeTerminalSessionInfo(responseField(response, "terminal"));
1916
+ }
1917
+
1918
+ // ../../packages/client/src/resources/job-resource.ts
1919
+ class JobResource {
1920
+ transport;
1921
+ constructor(transport) {
1922
+ this.transport = transport;
1422
1923
  }
1423
- async listJobs(options) {
1924
+ async list(options) {
1424
1925
  const { signal, kinds, statuses, includeFinished, ...query } = options;
1425
- const response = await this.request(this.path("/jobs", {
1926
+ const response = await this.transport.request(this.transport.path("/jobs", {
1426
1927
  ...query,
1427
1928
  ...kinds ? { kinds: kinds.join(",") } : {},
1428
1929
  ...statuses ? { statuses: statuses.join(",") } : {},
@@ -1431,236 +1932,214 @@ class OpenHarnessClient {
1431
1932
  return responseArray(response, "jobs", decodeJobSnapshot);
1432
1933
  }
1433
1934
  async createBackgroundShell(input, options = {}) {
1434
- return await this.request("/background-shells", {
1935
+ return await this.transport.request("/background-shells", {
1435
1936
  method: "POST",
1436
1937
  body: input,
1437
1938
  signal: options.signal
1438
1939
  });
1439
1940
  }
1440
- async readJob(jobId, options) {
1941
+ async read(jobId, options) {
1441
1942
  const { signal, ...query } = options;
1442
- const response = await this.request(this.path(`/jobs/${encodeURIComponent(jobId)}`, query), { signal });
1943
+ const response = await this.transport.request(this.transport.path(`/jobs/${encodeURIComponent(jobId)}`, query), { signal });
1443
1944
  return decodeJobReadResult(response);
1444
1945
  }
1445
- async waitJob(jobId, input, options = {}) {
1446
- const response = await this.request(`/jobs/${encodeURIComponent(jobId)}/wait`, {
1946
+ async wait(jobId, input, options = {}) {
1947
+ const response = await this.transport.request(`/jobs/${encodeURIComponent(jobId)}/wait`, {
1447
1948
  method: "POST",
1448
1949
  body: input,
1449
1950
  signal: options.signal
1450
1951
  });
1451
1952
  return decodeJobWaitResult(response);
1452
1953
  }
1453
- async sendJob(jobId, input, options = {}) {
1454
- await this.request(`/jobs/${encodeURIComponent(jobId)}/input`, {
1954
+ async send(jobId, input, options = {}) {
1955
+ await this.transport.request(`/jobs/${encodeURIComponent(jobId)}/input`, {
1455
1956
  method: "POST",
1456
1957
  body: input,
1457
1958
  signal: options.signal
1458
1959
  });
1459
1960
  }
1460
- async cancelJob(jobId, input, options = {}) {
1461
- const response = await this.request(`/jobs/${encodeURIComponent(jobId)}/cancel`, { method: "POST", body: input, signal: options.signal });
1961
+ async cancel(jobId, input, options = {}) {
1962
+ const response = await this.transport.request(`/jobs/${encodeURIComponent(jobId)}/cancel`, { method: "POST", body: input, signal: options.signal });
1462
1963
  return decodeJobSnapshot(responseField(response, "snapshot"));
1463
1964
  }
1464
- async listTerminals(options = {}) {
1965
+ }
1966
+ var init_job_resource = __esm(() => {
1967
+ init_src();
1968
+ init_http_transport();
1969
+ });
1970
+
1971
+ // ../../packages/client/src/resources/terminal-resource.ts
1972
+ class TerminalResource {
1973
+ transport;
1974
+ sse;
1975
+ constructor(transport, sse) {
1976
+ this.transport = transport;
1977
+ this.sse = sse;
1978
+ }
1979
+ async create(input, options = {}) {
1980
+ const response = await this.transport.request("/terminals", {
1981
+ method: "POST",
1982
+ body: input,
1983
+ signal: options.signal
1984
+ });
1985
+ return decodeTerminalSessionInfo(responseField(response, "terminal"));
1986
+ }
1987
+ async list(options = {}) {
1465
1988
  const { signal, ...query } = options;
1466
- const response = await this.request(this.path("/terminals", query), { signal });
1989
+ const response = await this.transport.request(this.transport.path("/terminals", query), { signal });
1467
1990
  return responseArray(response, "terminals", decodeTerminalSessionInfo);
1468
1991
  }
1469
- async getTerminal(terminalId, options = {}) {
1470
- const response = await this.request(`/terminals/${encodeURIComponent(terminalId)}`, { signal: options.signal });
1992
+ async get(terminalId, options = {}) {
1993
+ const response = await this.transport.request(`/terminals/${encodeURIComponent(terminalId)}`, { signal: options.signal });
1471
1994
  return decodeTerminalSessionInfo(responseField(response, "terminal"));
1472
1995
  }
1473
- async readTerminal(terminalId, options = {}) {
1474
- const response = await this.request(`/terminals/${encodeURIComponent(terminalId)}/output`, { signal: options.signal });
1996
+ async read(terminalId, options = {}) {
1997
+ const response = await this.transport.request(`/terminals/${encodeURIComponent(terminalId)}/output`, { signal: options.signal });
1475
1998
  return decodeTerminalReadResult(responseField(response, "snapshot"));
1476
1999
  }
1477
- async writeTerminal(input, options = {}) {
1478
- await this.request(`/terminals/${encodeURIComponent(input.terminalId)}/input`, { method: "POST", body: { data: input.data }, signal: options.signal });
2000
+ async write(input, options = {}) {
2001
+ await this.transport.request(`/terminals/${encodeURIComponent(input.terminalId)}/input`, { method: "POST", body: { data: input.data }, signal: options.signal });
1479
2002
  }
1480
- async resizeTerminal(input, options = {}) {
1481
- await this.request(`/terminals/${encodeURIComponent(input.terminalId)}/resize`, {
2003
+ async resize(input, options = {}) {
2004
+ await this.transport.request(`/terminals/${encodeURIComponent(input.terminalId)}/resize`, {
1482
2005
  method: "POST",
1483
2006
  body: { cols: input.cols, rows: input.rows },
1484
2007
  signal: options.signal
1485
2008
  });
1486
2009
  }
1487
- async signalTerminal(terminalId, signal, options = {}) {
1488
- await this.request(`/terminals/${encodeURIComponent(terminalId)}/signal`, { method: "POST", body: { signal }, signal: options.signal });
2010
+ async signal(terminalId, signal, options = {}) {
2011
+ await this.transport.request(`/terminals/${encodeURIComponent(terminalId)}/signal`, { method: "POST", body: { signal }, signal: options.signal });
1489
2012
  }
1490
- async closeTerminal(terminalId, options = {}) {
1491
- await this.request(`/terminals/${encodeURIComponent(terminalId)}`, {
2013
+ async close(terminalId, options = {}) {
2014
+ await this.transport.request(`/terminals/${encodeURIComponent(terminalId)}`, {
1492
2015
  method: "DELETE",
1493
2016
  signal: options.signal
1494
2017
  });
1495
2018
  }
1496
- streamTerminalEvents(options = {}) {
1497
- return streamServerSentEvents(async () => {
1498
- const response = await this.fetchImpl(`${this.baseUrl}/terminals/stream`, {
1499
- headers: this.headers(),
1500
- signal: options.signal
1501
- });
1502
- if (!response.ok)
1503
- await this.throwResponseError(response);
1504
- if (!response.body)
1505
- throw new Error("Terminal event stream response has no body");
1506
- return response.body;
1507
- }, decodeTerminalEvent);
1508
- }
1509
2019
  streamEvents(options = {}) {
1510
- return streamServerSentEvents(async () => {
1511
- const query = {
1512
- cursor: options.cursor,
1513
- sessionId: options.sessionId
1514
- };
1515
- const response = await this.fetchImpl(`${this.baseUrl}${this.path("/events/stream", query)}`, {
1516
- headers: this.headers(),
1517
- signal: options.signal
1518
- });
1519
- if (!response.ok)
1520
- await this.throwResponseError(response);
1521
- if (!response.body)
1522
- throw new Error("Event stream response has no body");
1523
- return response.body;
1524
- }, decodeSessionEventRecord);
1525
- }
1526
- async request(path, options = {}) {
1527
- const response = await this.fetchImpl(`${this.baseUrl}${path}`, {
1528
- method: options.method ?? "GET",
1529
- headers: this.headers(options.body !== undefined, options.auth ?? true),
1530
- body: options.body === undefined ? undefined : JSON.stringify(options.body),
1531
- signal: options.signal
2020
+ return this.sse.stream(this.transport.resolveUrl("/terminals/stream"), {
2021
+ headers: this.transport.headers(),
2022
+ signal: options.signal,
2023
+ decode: decodeTerminalEvent,
2024
+ noBodyMessage: "Terminal event stream response has no body"
1532
2025
  });
1533
- if (!response.ok)
1534
- await this.throwResponseError(response);
1535
- return await response.json();
1536
- }
1537
- headers(json = false, auth = true) {
1538
- return {
1539
- ...auth && this.token ? { authorization: `Bearer ${this.token}` } : {},
1540
- ...json ? { "content-type": "application/json" } : {}
1541
- };
1542
2026
  }
1543
- path(pathname, query = {}) {
1544
- const params = new URLSearchParams;
1545
- for (const [key, value] of Object.entries(query)) {
1546
- if (value === undefined || value === null || value === false)
1547
- continue;
1548
- params.set(key, String(value));
1549
- }
1550
- const qs = params.toString();
1551
- return qs ? `${pathname}?${qs}` : pathname;
2027
+ }
2028
+ var init_terminal_resource = __esm(() => {
2029
+ init_src();
2030
+ init_http_transport();
2031
+ });
2032
+
2033
+ // ../../packages/client/src/resources/channel-resource.ts
2034
+ class ChannelResource {
2035
+ transport;
2036
+ constructor(transport) {
2037
+ this.transport = transport;
1552
2038
  }
1553
- async throwResponseError(response) {
1554
- let body;
1555
- try {
1556
- body = await response.json();
1557
- } catch {
1558
- body = await response.text().catch(() => "");
1559
- }
1560
- const message = body && typeof body === "object" && "error" in body && typeof body.error === "string" ? body.error : body && typeof body === "object" && ("message" in body) && typeof body.message === "string" ? body.message : `OpenHarness API request failed with ${response.status}`;
1561
- throw new OpenHarnessApiError(message, response.status, body);
2039
+ async handleMessage(input, options = {}) {
2040
+ return await this.transport.request("/channels/messages", { method: "POST", body: input, signal: options.signal });
1562
2041
  }
1563
- }
1564
- function isReadableStream(value) {
1565
- return typeof ReadableStream !== "undefined" && value instanceof ReadableStream;
1566
- }
1567
- function attachmentRangeHeader(range) {
1568
- if (!range)
1569
- return;
1570
- const { start, end, suffixBytes } = range;
1571
- if (suffixBytes !== undefined) {
1572
- if (start !== undefined || end !== undefined) {
1573
- throw new Error("suffixBytes cannot be combined with start or end");
1574
- }
1575
- assertPositiveSafeInteger(suffixBytes, "suffixBytes");
1576
- return `bytes=-${suffixBytes}`;
2042
+ async recordDelivery(deliveryId, input, options = {}) {
2043
+ const response = await this.transport.request(`/channels/deliveries/${encodeURIComponent(deliveryId)}/result`, { method: "POST", body: input, signal: options.signal });
2044
+ return response.delivery;
1577
2045
  }
1578
- if (start === undefined && end === undefined)
1579
- return;
1580
- if (start === undefined) {
1581
- throw new Error("range start is required when end is provided");
2046
+ async getStatus(options = {}) {
2047
+ const { signal, ...query } = options;
2048
+ return await this.transport.request(this.transport.path("/channels/status", query), { signal });
1582
2049
  }
1583
- assertNonNegativeSafeInteger(start, "start");
1584
- if (end === undefined)
1585
- return `bytes=${start}-`;
1586
- assertNonNegativeSafeInteger(end, "end");
1587
- if (end < start)
1588
- throw new Error("range end must not be less than start");
1589
- return `bytes=${start}-${end}`;
1590
- }
1591
- function assertNonNegativeSafeInteger(value, field) {
1592
- if (!Number.isSafeInteger(value) || value < 0) {
1593
- throw new Error(`${field} must be a non-negative safe integer`);
2050
+ async listPendingDeliveries(options = {}) {
2051
+ const { signal, ...query } = options;
2052
+ const response = await this.transport.request(this.transport.path("/channels/deliveries/pending", query), { signal });
2053
+ return response.deliveries;
1594
2054
  }
1595
2055
  }
1596
- function assertPositiveSafeInteger(value, field) {
1597
- if (!Number.isSafeInteger(value) || value < 1) {
1598
- throw new Error(`${field} must be a positive safe integer`);
2056
+
2057
+ // ../../packages/client/src/resources/event-resource.ts
2058
+ class EventResource {
2059
+ transport;
2060
+ sse;
2061
+ constructor(transport, sse) {
2062
+ this.transport = transport;
2063
+ this.sse = sse;
2064
+ }
2065
+ async list(options = {}) {
2066
+ const { signal, ...query } = options;
2067
+ const response = await this.transport.request(this.transport.path("/events", query), { signal });
2068
+ return responseArray(response, "events", decodeSessionEventRecord);
1599
2069
  }
1600
- }
1601
- async function* streamServerSentEvents(open, decode = (value) => value) {
1602
- const reader = (await open()).getReader();
1603
- const decoder = new TextDecoder;
1604
- let buffer = "";
1605
- try {
1606
- while (true) {
1607
- const chunk = await reader.read();
1608
- if (chunk.done)
1609
- break;
1610
- buffer += decoder.decode(chunk.value, { stream: true });
1611
- const frames = buffer.split(/\r?\n\r?\n/);
1612
- buffer = frames.pop() ?? "";
1613
- for (const frame of frames) {
1614
- const event = parseSseFrame(frame);
1615
- if (event !== undefined)
1616
- yield decode(event);
1617
- }
1618
- }
1619
- buffer += decoder.decode();
1620
- const event = parseSseFrame(buffer);
1621
- if (event !== undefined)
1622
- yield decode(event);
1623
- } finally {
1624
- reader.releaseLock();
2070
+ stream(options = {}) {
2071
+ const query = {
2072
+ cursor: options.cursor,
2073
+ sessionId: options.sessionId
2074
+ };
2075
+ return this.sse.stream(this.transport.resolveUrl("/events/stream", query), {
2076
+ headers: this.transport.headers(),
2077
+ signal: options.signal,
2078
+ decode: decodeSessionEventRecord,
2079
+ reconnect: options.transportReconnect ?? false
2080
+ });
1625
2081
  }
1626
2082
  }
1627
- function parseSseFrame(frame) {
1628
- let data = "";
1629
- for (const line of frame.split(/\r?\n/)) {
1630
- if (!line || line.startsWith(":"))
1631
- continue;
1632
- if (line.startsWith("data:"))
1633
- data += line.slice(5).trimStart();
2083
+ var init_event_resource = __esm(() => {
2084
+ init_src();
2085
+ init_http_transport();
2086
+ });
2087
+
2088
+ // ../../packages/client/src/resources/index.ts
2089
+ var init_resources = __esm(() => {
2090
+ init_session_resource();
2091
+ init_attachment_resource();
2092
+ init_job_resource();
2093
+ init_terminal_resource();
2094
+ init_event_resource();
2095
+ });
2096
+
2097
+ // ../../packages/client/src/transport/http-client.ts
2098
+ class OpenHarnessClient {
2099
+ protocol;
2100
+ system;
2101
+ providers;
2102
+ auth;
2103
+ projects;
2104
+ plugins;
2105
+ development;
2106
+ sessions;
2107
+ attachments;
2108
+ permissions;
2109
+ schedules;
2110
+ jobs;
2111
+ terminals;
2112
+ channels;
2113
+ events;
2114
+ constructor(options) {
2115
+ const transport = new HttpTransport(options);
2116
+ const sse = new SseTransport(transport);
2117
+ this.protocol = new ProtocolClient(transport);
2118
+ this.system = new SystemResource(transport);
2119
+ this.providers = new ProviderResource(transport);
2120
+ this.auth = new AuthResource(transport);
2121
+ this.projects = new ProjectResource(transport);
2122
+ this.plugins = new PluginResource(transport);
2123
+ this.development = new DevelopmentResource(transport);
2124
+ this.sessions = new SessionResource(transport);
2125
+ this.attachments = new AttachmentResource(transport);
2126
+ this.permissions = new PermissionResource(transport);
2127
+ this.schedules = new ScheduleResource(transport);
2128
+ this.jobs = new JobResource(transport);
2129
+ this.terminals = new TerminalResource(transport, sse);
2130
+ this.channels = new ChannelResource(transport);
2131
+ this.events = new EventResource(transport, sse);
1634
2132
  }
1635
- if (!data)
1636
- return;
1637
- return JSON.parse(data);
1638
2133
  }
1639
- var promptRequestCounter = 0, OpenHarnessApiError, IncompatibleProtocolError;
1640
2134
  var init_http_client = __esm(() => {
1641
- init_src();
1642
- OpenHarnessApiError = class OpenHarnessApiError extends Error {
1643
- status;
1644
- body;
1645
- constructor(message, status, body) {
1646
- super(message);
1647
- this.status = status;
1648
- this.body = body;
1649
- this.name = "OpenHarnessApiError";
1650
- }
1651
- };
1652
- IncompatibleProtocolError = class IncompatibleProtocolError extends Error {
1653
- capabilities;
1654
- constructor(capabilities, message) {
1655
- super(message);
1656
- this.capabilities = capabilities;
1657
- this.name = "IncompatibleProtocolError";
1658
- }
1659
- };
2135
+ init_http_transport();
2136
+ init_protocol();
2137
+ init_resources();
1660
2138
  });
1661
2139
 
1662
2140
  // ../../packages/client/src/transport/index.ts
1663
2141
  var init_transport = __esm(() => {
2142
+ init_http_transport();
1664
2143
  init_http_client();
1665
2144
  });
1666
2145
 
@@ -1677,6 +2156,9 @@ function createInitialClientState() {
1677
2156
  };
1678
2157
  }
1679
2158
  function applySessionSnapshot(state, snapshot) {
2159
+ const knownCursor = state.snapshotCursorBySession[snapshot.session.id] ?? 0;
2160
+ if (snapshot.cursor < knownCursor)
2161
+ return state;
1680
2162
  const partsByMessageId = {};
1681
2163
  for (const part of snapshot.parts) {
1682
2164
  const parts = partsByMessageId[part.messageId] ?? [];
@@ -2010,126 +2492,221 @@ function selectSessionMessagesWithParts(bucket) {
2010
2492
  parts: [...bucket.partsByMessageId[message.id] ?? []].sort((a, b) => a.seq - b.seq)
2011
2493
  }));
2012
2494
  }
2495
+ function selectFirstPendingPermission(state, sessionId) {
2496
+ if (!sessionId)
2497
+ return;
2498
+ const bucket = state.buckets[sessionId];
2499
+ if (!bucket)
2500
+ return;
2501
+ return Object.values(bucket.permissions).filter((request) => request.status === "pending").sort((a, b) => a.createdAt - b.createdAt).at(0);
2502
+ }
2013
2503
 
2014
2504
  // ../../packages/client/src/state/sync.ts
2015
- async function* syncEvents(client, options = {}) {
2016
- let state = createInitialClientState();
2017
- if (options.sessionId) {
2018
- const snapshot = await client.getSessionState(options.sessionId, { signal: options.signal });
2019
- state = applySessionSnapshot(state, snapshot);
2020
- yield { state, source: "snapshot" };
2021
- yield* liveWithReconnect(client, state, options, snapshot.cursor);
2022
- return;
2505
+ var init_sync = __esm(() => {
2506
+ init_reducer();
2507
+ });
2508
+
2509
+ // ../../packages/client/src/state/session-sync-controller.ts
2510
+ class SessionSyncController {
2511
+ client;
2512
+ sessionId;
2513
+ initialCursor;
2514
+ generation;
2515
+ externalSignal;
2516
+ reconnectDelayMs;
2517
+ onUpdate;
2518
+ onStatusChange;
2519
+ onError;
2520
+ abortController = new AbortController;
2521
+ state;
2522
+ status = "idle";
2523
+ running = false;
2524
+ constructor(options) {
2525
+ this.client = options.client;
2526
+ this.sessionId = options.sessionId;
2527
+ this.state = options.initialState ?? createInitialClientState();
2528
+ if ((options.cursor ?? 0) > 0 && !options.initialState) {
2529
+ throw new Error("A non-zero cursor requires initialState so durable state is not skipped");
2530
+ }
2531
+ this.initialCursor = Math.max(0, options.cursor ?? 0, this.state.lastSeq);
2532
+ this.generation = options.generation ?? 0;
2533
+ this.externalSignal = options.signal;
2534
+ this.reconnectDelayMs = options.reconnectDelayMs ?? DEFAULT_RECONNECT_DELAY_MS;
2535
+ this.onUpdate = options.onUpdate;
2536
+ this.onStatusChange = options.onStatusChange;
2537
+ this.onError = options.onError;
2538
+ if (this.externalSignal) {
2539
+ if (this.externalSignal.aborted) {
2540
+ this.abortController.abort(this.externalSignal.reason);
2541
+ } else {
2542
+ this.externalSignal.addEventListener("abort", () => this.abortController.abort(this.externalSignal?.reason), { once: true });
2543
+ }
2544
+ }
2023
2545
  }
2024
- const replay = await client.listEvents({
2025
- cursor: options.cursor,
2026
- sessionId: options.sessionId,
2027
- signal: options.signal
2028
- });
2029
- for (const event of replay) {
2030
- state = applyEvent(state, event);
2031
- yield { event, state, source: "replay" };
2032
- }
2033
- yield* liveWithReconnect(client, state, options, state.lastSeq);
2034
- }
2035
- async function* liveWithReconnect(client, initialState, options, initialCursor) {
2036
- let state = initialState;
2037
- let cursor = initialCursor;
2038
- let attempt = 0;
2039
- const delayMs = options.reconnectDelayMs ?? DEFAULT_RECONNECT_DELAY_MS;
2040
- while (!options.signal?.aborted) {
2546
+ get currentStatus() {
2547
+ return this.status;
2548
+ }
2549
+ get currentState() {
2550
+ return this.state;
2551
+ }
2552
+ get currentGeneration() {
2553
+ return this.generation;
2554
+ }
2555
+ get signal() {
2556
+ return this.abortController.signal;
2557
+ }
2558
+ abort(reason) {
2559
+ this.abortController.abort(reason);
2560
+ }
2561
+ setStatus(status) {
2562
+ if (this.status === status)
2563
+ return;
2564
+ this.status = status;
2565
+ this.onStatusChange?.(status, this.generation);
2566
+ }
2567
+ emitUpdate(update) {
2568
+ if (this.abortController.signal.aborted)
2569
+ return;
2570
+ this.state = update.state;
2571
+ this.onUpdate?.(update, this.generation);
2572
+ }
2573
+ async start() {
2574
+ if (this.running)
2575
+ return;
2576
+ this.running = true;
2577
+ const signal = this.abortController.signal;
2578
+ if (signal.aborted) {
2579
+ this.setStatus("idle");
2580
+ return;
2581
+ }
2582
+ let cursor = this.initialCursor;
2583
+ let attempt = 0;
2041
2584
  try {
2042
- for await (const event of client.streamEvents({
2043
- cursor,
2044
- sessionId: options.sessionId,
2045
- signal: options.signal
2046
- })) {
2047
- attempt = 0;
2048
- if (event.seq > state.lastSeq + 1 && !options.sessionId) {
2049
- const gap = await client.listEvents({
2050
- cursor: state.lastSeq,
2051
- signal: options.signal
2052
- });
2053
- for (const missed of gap) {
2054
- const beforeGap = state;
2055
- state = applyEvent(state, missed);
2056
- if (state !== beforeGap)
2057
- yield { event: missed, state, source: "replay" };
2585
+ this.setStatus("connecting");
2586
+ if (this.sessionId) {
2587
+ const snapshot = await this.client.sessions.getState(this.sessionId, { signal });
2588
+ if (signal.aborted)
2589
+ return;
2590
+ this.state = applySessionSnapshot(this.state, snapshot);
2591
+ cursor = Math.max(cursor, snapshot.cursor, this.state.lastSeq);
2592
+ this.setStatus("connected");
2593
+ this.emitUpdate({ state: this.state, source: "snapshot" });
2594
+ } else {
2595
+ const replay = await this.client.events.list({
2596
+ cursor,
2597
+ sessionId: this.sessionId,
2598
+ signal
2599
+ });
2600
+ if (signal.aborted)
2601
+ return;
2602
+ for (const event of replay) {
2603
+ this.state = applyEvent(this.state, event);
2604
+ this.emitUpdate({ event, state: this.state, source: "replay" });
2605
+ }
2606
+ cursor = Math.max(cursor, this.state.lastSeq);
2607
+ this.setStatus("connected");
2608
+ }
2609
+ while (!signal.aborted) {
2610
+ try {
2611
+ for await (const event of this.client.events.stream({
2612
+ cursor,
2613
+ sessionId: this.sessionId,
2614
+ signal,
2615
+ transportReconnect: false
2616
+ })) {
2617
+ if (signal.aborted)
2618
+ return;
2619
+ attempt = 0;
2620
+ this.setStatus("connected");
2621
+ if (event.seq > cursor + 1) {
2622
+ const gap = await this.client.events.list({
2623
+ cursor,
2624
+ sessionId: this.sessionId,
2625
+ signal
2626
+ });
2627
+ if (signal.aborted)
2628
+ return;
2629
+ for (const missed of gap) {
2630
+ const beforeGap = this.state;
2631
+ this.state = applyEvent(this.state, missed);
2632
+ if (this.state !== beforeGap) {
2633
+ this.emitUpdate({ event: missed, state: this.state, source: "replay" });
2634
+ }
2635
+ }
2636
+ cursor = Math.max(cursor, this.state.lastSeq);
2637
+ }
2638
+ const before = this.state;
2639
+ this.state = applyEvent(this.state, event);
2640
+ cursor = Math.max(cursor, this.state.lastSeq);
2641
+ if (this.state !== before) {
2642
+ this.emitUpdate({ event, state: this.state, source: "live" });
2643
+ }
2644
+ }
2645
+ if (signal.aborted)
2646
+ return;
2647
+ this.setStatus("reconnecting");
2648
+ this.emitUpdate({ state: this.state, source: "reconnecting" });
2649
+ const delay = this.reconnectDelayMs(attempt);
2650
+ attempt += 1;
2651
+ cursor = this.state.lastSeq;
2652
+ if (!await this.waitForDelay(delay, signal))
2653
+ return;
2654
+ } catch (error) {
2655
+ if (error instanceof UnsupportedSessionEventSchemaVersionError) {
2656
+ throw error;
2058
2657
  }
2059
- cursor = state.lastSeq;
2658
+ if (this.isAbortError(error) || signal.aborted)
2659
+ return;
2660
+ this.setStatus("reconnecting");
2661
+ this.emitUpdate({ state: this.state, source: "reconnecting" });
2662
+ const delay = this.reconnectDelayMs(attempt);
2663
+ attempt += 1;
2664
+ cursor = this.state.lastSeq;
2665
+ if (!await this.waitForDelay(delay, signal))
2666
+ return;
2060
2667
  }
2061
- const before = state;
2062
- state = applyEvent(state, event);
2063
- cursor = state.lastSeq;
2064
- if (state !== before)
2065
- yield { event, state, source: "live" };
2066
2668
  }
2067
- if (options.signal?.aborted)
2068
- return;
2069
- yield { state, source: "reconnecting" };
2070
- if (!await waitForReconnect(delayMs(attempt), options.signal))
2071
- return;
2072
- attempt += 1;
2073
- cursor = state.lastSeq;
2074
2669
  } catch (error) {
2075
- if (error instanceof UnsupportedSessionEventSchemaVersionError)
2076
- throw error;
2077
- if (isAbortError(error) || options.signal?.aborted)
2078
- return;
2079
- yield { state, source: "reconnecting" };
2080
- if (!await waitForReconnect(delayMs(attempt), options.signal))
2081
- return;
2082
- attempt += 1;
2083
- cursor = state.lastSeq;
2670
+ if (!this.isAbortError(error) && !signal.aborted) {
2671
+ this.setStatus("error");
2672
+ this.onError?.(error, this.generation);
2673
+ }
2674
+ } finally {
2675
+ this.running = false;
2676
+ if (signal.aborted && this.status !== "error") {
2677
+ this.setStatus("idle");
2678
+ }
2084
2679
  }
2085
2680
  }
2086
- }
2087
- async function waitForReconnect(ms, signal) {
2088
- try {
2089
- await sleep(ms, signal);
2090
- return !signal?.aborted;
2091
- } catch (error) {
2092
- if (isAbortError(error) || signal?.aborted)
2681
+ isAbortError(error) {
2682
+ if (!error || typeof error !== "object")
2093
2683
  return false;
2094
- throw error;
2095
- }
2096
- }
2097
- function isAbortError(error) {
2098
- if (!error || typeof error !== "object")
2684
+ if ("name" in error && error.name === "AbortError")
2685
+ return true;
2099
2686
  return false;
2100
- if ("name" in error && error.name === "AbortError")
2101
- return true;
2102
- return false;
2103
- }
2104
- function sleep(ms, signal) {
2105
- if (ms <= 0) {
2106
- if (signal?.aborted)
2107
- return Promise.reject(abortError(signal));
2108
- return Promise.resolve();
2109
2687
  }
2110
- return new Promise((resolve, reject) => {
2111
- if (signal?.aborted) {
2112
- reject(abortError(signal));
2113
- return;
2114
- }
2115
- const timer = setTimeout(() => {
2116
- signal?.removeEventListener("abort", onAbort);
2117
- resolve();
2118
- }, ms);
2119
- const onAbort = () => {
2120
- clearTimeout(timer);
2121
- reject(abortError(signal));
2122
- };
2123
- signal?.addEventListener("abort", onAbort, { once: true });
2124
- });
2125
- }
2126
- function abortError(signal) {
2127
- if (signal.reason instanceof Error)
2128
- return signal.reason;
2129
- return new DOMException("Aborted", "AbortError");
2688
+ waitForDelay(ms, signal) {
2689
+ if (ms <= 0)
2690
+ return Promise.resolve(!signal.aborted);
2691
+ return new Promise((resolve) => {
2692
+ if (signal.aborted) {
2693
+ resolve(false);
2694
+ return;
2695
+ }
2696
+ const timer = setTimeout(() => {
2697
+ signal.removeEventListener("abort", onAbort);
2698
+ resolve(!signal.aborted);
2699
+ }, ms);
2700
+ const onAbort = () => {
2701
+ clearTimeout(timer);
2702
+ resolve(false);
2703
+ };
2704
+ signal.addEventListener("abort", onAbort, { once: true });
2705
+ });
2706
+ }
2130
2707
  }
2131
2708
  var DEFAULT_RECONNECT_DELAY_MS = (attempt) => Math.min(30000, 250 * 2 ** Math.max(0, attempt));
2132
- var init_sync = __esm(() => {
2709
+ var init_session_sync_controller = __esm(() => {
2133
2710
  init_reducer();
2134
2711
  });
2135
2712
 
@@ -2137,6 +2714,7 @@ var init_sync = __esm(() => {
2137
2714
  var init_state = __esm(() => {
2138
2715
  init_reducer();
2139
2716
  init_sync();
2717
+ init_session_sync_controller();
2140
2718
  });
2141
2719
 
2142
2720
  // ../../packages/client/src/commands/plugin-presentation.ts
@@ -2330,7 +2908,7 @@ async function dispatchSessionCommand(slash, host) {
2330
2908
  }
2331
2909
  patchStatus({ permission_mode: next });
2332
2910
  if (sessionId) {
2333
- await client.updateSession(sessionId, {
2911
+ await client.sessions.update(sessionId, {
2334
2912
  metadata: patchSessionRuntimeMetadata({}, { permissionMode: next })
2335
2913
  });
2336
2914
  }
@@ -2365,7 +2943,7 @@ async function dispatchSessionCommand(slash, host) {
2365
2943
  }
2366
2944
  if (slash?.name === "/version") {
2367
2945
  await readPresentation("version", "Version", async () => {
2368
- const health = await client.health();
2946
+ const health = await client.protocol.health();
2369
2947
  return `OpenHarness${health.version ? ` v${health.version}` : ""}`;
2370
2948
  });
2371
2949
  return "handled";
@@ -2386,7 +2964,7 @@ async function dispatchSessionCommand(slash, host) {
2386
2964
  const args = slash.args.trim();
2387
2965
  if (!args || args === "show") {
2388
2966
  await readPresentation(`config:${cwd}`, "Config", async () => {
2389
- const settings = await client.getSettings();
2967
+ const settings = await client.system.getSettings();
2390
2968
  return JSON.stringify(settings, null, 2);
2391
2969
  });
2392
2970
  return "handled";
@@ -2396,14 +2974,14 @@ async function dispatchSessionCommand(slash, host) {
2396
2974
  emit("Usage: /config [show | set KEY VALUE]");
2397
2975
  return "handled";
2398
2976
  }
2399
- await client.patchSettings({ path: setMatch[1], value: setMatch[2].trim() });
2977
+ await client.system.patchSettings({ path: setMatch[1], value: setMatch[2].trim() });
2400
2978
  emit(`Set ${setMatch[1]} = ${setMatch[2].trim()}`);
2401
2979
  return "handled";
2402
2980
  }
2403
2981
  if (slash?.name === "/provider") {
2404
2982
  if (!slash.args) {
2405
2983
  await readPresentation("providers", "Provider", async () => {
2406
- const providers = await client.listProviders();
2984
+ const providers = await client.providers.listProviders();
2407
2985
  const lines = ["Available providers:", ""];
2408
2986
  for (const provider of providers) {
2409
2987
  const marker = provider.active ? " (active)" : "";
@@ -2415,7 +2993,7 @@ async function dispatchSessionCommand(slash, host) {
2415
2993
  });
2416
2994
  return "handled";
2417
2995
  }
2418
- const settings = await client.patchSettings(slash.args === "auto" ? { provider: "auto" } : { provider: slash.args });
2996
+ const settings = await client.system.patchSettings(slash.args === "auto" ? { provider: "auto" } : { provider: slash.args });
2419
2997
  if (typeof settings.model === "string") {
2420
2998
  patchStatus({ model: settings.model });
2421
2999
  }
@@ -2426,7 +3004,7 @@ async function dispatchSessionCommand(slash, host) {
2426
3004
  if (!sessionId)
2427
3005
  return "handled";
2428
3006
  await readPresentation(`mcp:${sessionId}`, "MCP", async () => {
2429
- const servers = await client.getSessionMcp(sessionId);
3007
+ const servers = await client.system.getSessionMcp(sessionId);
2430
3008
  if (servers.length === 0)
2431
3009
  return "No MCP servers connected.";
2432
3010
  return [
@@ -2451,7 +3029,7 @@ async function dispatchSessionCommand(slash, host) {
2451
3029
  const [sub, id, ...extra] = args.split(/\s+/).filter(Boolean);
2452
3030
  if ((!sub || sub === "list") && !id) {
2453
3031
  await readPresentation(`jobs:${sessionId}`, "Jobs", async () => {
2454
- const jobs = await client.listJobs({
3032
+ const jobs = await client.jobs.list({
2455
3033
  sessionId,
2456
3034
  includeFinished: true,
2457
3035
  limit: 100
@@ -2469,13 +3047,13 @@ async function dispatchSessionCommand(slash, host) {
2469
3047
  }
2470
3048
  if (sub === "show" && id && extra.length === 0) {
2471
3049
  await readPresentation(`job:${sessionId}:${id}`, "Jobs", async () => {
2472
- const result = await client.readJob(id, { sessionId });
3050
+ const result = await client.jobs.read(id, { sessionId });
2473
3051
  return formatJobReadResult(result);
2474
3052
  });
2475
3053
  return "handled";
2476
3054
  }
2477
3055
  if (sub === "cancel" && id && extra.length === 0) {
2478
- const snapshot = await client.cancelJob(id, {
3056
+ const snapshot = await client.jobs.cancel(id, {
2479
3057
  sessionId,
2480
3058
  reason: "Cancelled from slash command"
2481
3059
  });
@@ -2493,7 +3071,7 @@ async function dispatchSessionCommand(slash, host) {
2493
3071
  }
2494
3072
  if (!sessionId)
2495
3073
  return "handled";
2496
- const result = await client.createBackgroundShell({ sessionId, command });
3074
+ const result = await client.jobs.createBackgroundShell({ sessionId, command });
2497
3075
  emit(`Background shell started: ${result.jobId}. Use /jobs to inspect it.`);
2498
3076
  return "handled";
2499
3077
  }
@@ -2502,7 +3080,7 @@ async function dispatchSessionCommand(slash, host) {
2502
3080
  const [sub, ...rest] = args.split(/\s+/).filter(Boolean);
2503
3081
  if (!sub || sub === "list") {
2504
3082
  await readPresentation(`memory:${cwd}:list`, "Memory", async () => {
2505
- const listed = await client.listMemory({ cwd });
3083
+ const listed = await client.system.listMemory({ cwd });
2506
3084
  if (listed.entries.length === 0)
2507
3085
  return `Memory directory: ${listed.directory}
2508
3086
  No entries found.`;
@@ -2522,7 +3100,7 @@ No entries found.`;
2522
3100
  if (sub === "show" && rest[0]) {
2523
3101
  const memoryId = rest[0];
2524
3102
  await readPresentation(`memory:${cwd}:show:${memoryId}`, "Memory", async () => {
2525
- const entry = await client.getMemory(memoryId, { cwd });
3103
+ const entry = await client.system.getMemory(memoryId, { cwd });
2526
3104
  return [
2527
3105
  `ID: ${entry.id}`,
2528
3106
  `Created: ${new Date(entry.createdAt).toISOString()}`,
@@ -2541,12 +3119,12 @@ No entries found.`;
2541
3119
  emit("Usage: /memory add <content>");
2542
3120
  return "handled";
2543
3121
  }
2544
- const entry = await client.addMemory({ cwd, content });
3122
+ const entry = await client.system.addMemory({ cwd, content });
2545
3123
  emit(`Memory added: ${entry.id}`);
2546
3124
  return "handled";
2547
3125
  }
2548
3126
  if (sub === "remove" && rest[0]) {
2549
- await client.removeMemory(rest[0], { cwd });
3127
+ await client.system.removeMemory(rest[0], { cwd });
2550
3128
  emit(`Memory removed: ${rest[0]}`);
2551
3129
  return "handled";
2552
3130
  }
@@ -2558,7 +3136,7 @@ No entries found.`;
2558
3136
  const [sub, provider, apiKey] = args.split(/\s+/).filter(Boolean);
2559
3137
  if (!sub || sub === "status") {
2560
3138
  await readPresentation("auth:status", "Auth", async () => {
2561
- const auth = await client.getAuthStatus();
3139
+ const auth = await client.auth.getStatus();
2562
3140
  const lines = ["Credential status:", "", " Auth sources:"];
2563
3141
  lines.push(` codex_subscription: ${auth.codex.configured ? "ready" : auth.codex.state} (${auth.codex.source})`);
2564
3142
  if (auth.storedProviders.length > 0) {
@@ -2586,7 +3164,7 @@ No entries found.`;
2586
3164
  emit("Usage: /auth login <provider> <api-key> or /auth login codex");
2587
3165
  return "handled";
2588
3166
  }
2589
- const result = await client.authLogin({ provider, apiKey });
3167
+ const result = await client.auth.login({ provider, apiKey });
2590
3168
  emit(result.message);
2591
3169
  return "handled";
2592
3170
  }
@@ -2595,7 +3173,7 @@ No entries found.`;
2595
3173
  emit("Usage: /auth logout <provider>");
2596
3174
  return "handled";
2597
3175
  }
2598
- const result = await client.authLogout({ provider });
3176
+ const result = await client.auth.logout({ provider });
2599
3177
  emit(result.message);
2600
3178
  return "handled";
2601
3179
  }
@@ -2605,12 +3183,12 @@ No entries found.`;
2605
3183
  if (slash?.name === "/context") {
2606
3184
  const action = slash.args.trim().split(/\s+/).filter(Boolean)[0] ?? "preview";
2607
3185
  if (action === "status") {
2608
- await readPresentation(`context:${cwd}:status`, "Context", async () => await client.getContextStatus({ cwd }));
3186
+ await readPresentation(`context:${cwd}:status`, "Context", async () => await client.system.getContextStatus({ cwd }));
2609
3187
  return "handled";
2610
3188
  }
2611
3189
  if (action === "usage") {
2612
3190
  await readPresentation(`context:${cwd}:usage:${sessionId ?? "none"}`, "Context", async () => {
2613
- const result = await client.getContextUsage({
3191
+ const result = await client.system.getContextUsage({
2614
3192
  cwd,
2615
3193
  ...sessionId ? { sessionId } : {}
2616
3194
  });
@@ -2622,7 +3200,7 @@ No entries found.`;
2622
3200
  emit("Usage: /context [preview|status|usage]");
2623
3201
  return "handled";
2624
3202
  }
2625
- await readPresentation(`context:${cwd}`, "Context", async () => await client.getContextPreview({ cwd }));
3203
+ await readPresentation(`context:${cwd}`, "Context", async () => await client.system.getContextPreview({ cwd }));
2626
3204
  return "handled";
2627
3205
  }
2628
3206
  if (slash?.name === "/stats") {
@@ -2633,11 +3211,11 @@ No entries found.`;
2633
3211
  const text = (bucket?.messages ?? []).flatMap((message) => bucket?.partsByMessageId[message.id] ?? []).map((part) => part.text ?? "").join(" ");
2634
3212
  const estimatedTokens = Math.max(1, Math.ceil(text.length / 4));
2635
3213
  const [memory, jobsResult, settings] = await Promise.all([
2636
- client.listMemory({ cwd }).catch(() => ({ entries: [] })),
2637
- client.listJobs({ sessionId, includeFinished: true, limit: 100 }).then((jobs) => ({ jobs })).catch((error) => ({
3214
+ client.system.listMemory({ cwd }).catch(() => ({ entries: [] })),
3215
+ client.jobs.list({ sessionId, includeFinished: true, limit: 100 }).then((jobs) => ({ jobs })).catch((error) => ({
2638
3216
  error: error instanceof Error ? error.message : String(error)
2639
3217
  })),
2640
- client.getSettings().catch(() => ({}))
3218
+ client.system.getSettings().catch(() => ({}))
2641
3219
  ]);
2642
3220
  const jobsSummary = "jobs" in jobsResult ? String(jobsResult.jobs.length) : `unavailable (${jobsResult.error})`;
2643
3221
  emit([
@@ -2654,7 +3232,7 @@ No entries found.`;
2654
3232
  if (slash?.name === "/agents") {
2655
3233
  if (!sessionId)
2656
3234
  return "handled";
2657
- const agents = await client.listJobs({
3235
+ const agents = await client.jobs.list({
2658
3236
  sessionId,
2659
3237
  kinds: ["agent"],
2660
3238
  includeFinished: true,
@@ -2681,21 +3259,21 @@ No entries found.`;
2681
3259
  emit("Count must be a positive integer");
2682
3260
  return "handled";
2683
3261
  }
2684
- const result = await client.rewindSession(sessionId, { count });
3262
+ const result = await client.sessions.rewind(sessionId, { count });
2685
3263
  emit(`Rewound ${result.turns} turn(s), removed ${result.removed} message(s).`);
2686
3264
  return "handled";
2687
3265
  }
2688
3266
  if (slash?.name === "/compact") {
2689
3267
  if (!sessionId)
2690
3268
  return "handled";
2691
- const result = await client.compactSession(sessionId);
3269
+ const result = await client.sessions.compact(sessionId);
2692
3270
  emit(`Conversation compacted (${result.messageCount} messages retained).`);
2693
3271
  return "handled";
2694
3272
  }
2695
3273
  if (slash?.name === "/remember") {
2696
3274
  if (!sessionId)
2697
3275
  return "handled";
2698
- const result = await client.rememberSession(sessionId);
3276
+ const result = await client.sessions.remember(sessionId);
2699
3277
  if (result.skipped) {
2700
3278
  emit(`\u672A\u5199\u5165\u8BB0\u5FC6:${result.reason ?? "skipped"}`);
2701
3279
  return "handled";
@@ -2705,7 +3283,7 @@ No entries found.`;
2705
3283
  }
2706
3284
  if (slash?.name === "/dream") {
2707
3285
  const preview = slash.args.includes("--preview");
2708
- const result = await client.startDream({
3286
+ const result = await client.system.startDream({
2709
3287
  cwd,
2710
3288
  ...sessionId ? { sessionId } : {},
2711
3289
  preview
@@ -2716,11 +3294,11 @@ No entries found.`;
2716
3294
  if (slash?.name === "/profile") {
2717
3295
  const action = slash.args.trim().split(/\s+/).filter(Boolean)[0] ?? "status";
2718
3296
  if (action === "status" || action === "show") {
2719
- emit(await client.getProfileStatus());
3297
+ emit(await client.system.getProfileStatus());
2720
3298
  return "handled";
2721
3299
  }
2722
3300
  if (action === "init") {
2723
- emit(await client.initProfile());
3301
+ emit(await client.system.initProfile());
2724
3302
  return "handled";
2725
3303
  }
2726
3304
  emit("Usage: /profile [status|init]");
@@ -2728,11 +3306,11 @@ No entries found.`;
2728
3306
  }
2729
3307
  if (slash?.name === "/doctor") {
2730
3308
  const [settings, auth, memory, mcp, jobsResult] = await Promise.all([
2731
- client.getSettings().catch(() => ({})),
2732
- client.getAuthStatus().catch(() => null),
2733
- client.listMemory({ cwd }).catch(() => ({ directory: "(unavailable)", entries: [] })),
2734
- sessionId ? client.getSessionMcp(sessionId).catch(() => []) : Promise.resolve([]),
2735
- sessionId ? client.listJobs({ sessionId, includeFinished: true, limit: 100 }).then((jobs) => ({ jobs })).catch((error) => ({
3309
+ client.system.getSettings().catch(() => ({})),
3310
+ client.auth.getStatus().catch(() => null),
3311
+ client.system.listMemory({ cwd }).catch(() => ({ directory: "(unavailable)", entries: [] })),
3312
+ sessionId ? client.system.getSessionMcp(sessionId).catch(() => []) : Promise.resolve([]),
3313
+ sessionId ? client.jobs.list({ sessionId, includeFinished: true, limit: 100 }).then((jobs) => ({ jobs })).catch((error) => ({
2736
3314
  error: error instanceof Error ? error.message : String(error)
2737
3315
  })) : Promise.resolve({ jobs: [] })
2738
3316
  ]);
@@ -2782,7 +3360,7 @@ No entries found.`;
2782
3360
  if (slash?.name === "/effort") {
2783
3361
  const level = slash.args.trim().split(/\s+/).filter(Boolean)[0];
2784
3362
  if (!level) {
2785
- const settings = await client.getSettings();
3363
+ const settings = await client.system.getSettings();
2786
3364
  emit(`Current effort: ${String(settings.effort ?? "medium")}`);
2787
3365
  return "handled";
2788
3366
  }
@@ -2790,13 +3368,13 @@ No entries found.`;
2790
3368
  emit("Invalid effort. Use: low, medium, or high");
2791
3369
  return "handled";
2792
3370
  }
2793
- await client.patchSettings({ effort: level });
3371
+ await client.system.patchSettings({ effort: level });
2794
3372
  emit(`Effort set to: ${level}`);
2795
3373
  return "handled";
2796
3374
  }
2797
3375
  if (slash?.name === "/fast") {
2798
3376
  const arg = slash.args.trim().split(/\s+/).filter(Boolean)[0];
2799
- const settings = await client.getSettings();
3377
+ const settings = await client.system.getSettings();
2800
3378
  const current = settings.fastMode === true;
2801
3379
  let next;
2802
3380
  if (arg === "on")
@@ -2805,14 +3383,14 @@ No entries found.`;
2805
3383
  next = false;
2806
3384
  else
2807
3385
  next = !current;
2808
- await client.patchSettings({ fastMode: next });
3386
+ await client.system.patchSettings({ fastMode: next });
2809
3387
  emit(`Fast mode: ${next ? "ON" : "OFF"}`);
2810
3388
  return "handled";
2811
3389
  }
2812
3390
  if (slash?.name === "/turns") {
2813
3391
  const value = slash.args.trim().split(/\s+/).filter(Boolean)[0];
2814
3392
  if (!value) {
2815
- const settings = await client.getSettings();
3393
+ const settings = await client.system.getSettings();
2816
3394
  emit(`Current max turns: ${String(settings.maxTurns ?? "(default)")}`);
2817
3395
  return "handled";
2818
3396
  }
@@ -2821,7 +3399,7 @@ No entries found.`;
2821
3399
  emit("Value must be between 1 and 512");
2822
3400
  return "handled";
2823
3401
  }
2824
- await client.patchSettings({ maxTurns: n });
3402
+ await client.system.patchSettings({ maxTurns: n });
2825
3403
  emit(`Max turns set to: ${n}`);
2826
3404
  return "handled";
2827
3405
  }
@@ -2830,7 +3408,7 @@ No entries found.`;
2830
3408
  return "handled";
2831
3409
  if (slash.name === "/cost") {
2832
3410
  await readPresentation(`cost:${sessionId}`, "Cost", async () => {
2833
- const usage = await client.getSessionUsage(sessionId);
3411
+ const usage = await client.sessions.getUsage(sessionId);
2834
3412
  return [
2835
3413
  "Cost estimate:",
2836
3414
  ` Model: ${usage.model}`,
@@ -2845,7 +3423,7 @@ No entries found.`;
2845
3423
  return "handled";
2846
3424
  }
2847
3425
  await readPresentation(`usage:${sessionId}`, "Usage", async () => {
2848
- const usage = await client.getSessionUsage(sessionId);
3426
+ const usage = await client.sessions.getUsage(sessionId);
2849
3427
  return [
2850
3428
  "Token usage:",
2851
3429
  ` Input: ${usage.inputTokens.toLocaleString()}`,
@@ -2866,7 +3444,7 @@ No entries found.`;
2866
3444
  const args = slash.args.trim().split(/\s+/).filter(Boolean);
2867
3445
  const forceJson = args.includes("--json");
2868
3446
  const filename = args.find((arg) => !arg.startsWith("--"));
2869
- const result = await client.exportSession(sessionId, {
3447
+ const result = await client.sessions.export(sessionId, {
2870
3448
  ...filename ? { filename } : {},
2871
3449
  json: forceJson
2872
3450
  });
@@ -2875,8 +3453,8 @@ No entries found.`;
2875
3453
  }
2876
3454
  if (slash?.name === "/output-style") {
2877
3455
  const args = slash.args.trim();
2878
- const styles = await client.listOutputStyles();
2879
- const settings = await client.getSettings();
3456
+ const styles = await client.system.listOutputStyles();
3457
+ const settings = await client.system.getSettings();
2880
3458
  const current = typeof settings.outputStyle === "string" ? settings.outputStyle : "default";
2881
3459
  const firstSpace = args.search(/\s/);
2882
3460
  const first = firstSpace === -1 ? args : args.slice(0, firstSpace);
@@ -2899,12 +3477,12 @@ No entries found.`;
2899
3477
  emit(`Unknown output style: ${styleName}`);
2900
3478
  return "handled";
2901
3479
  }
2902
- await client.patchSettings({ outputStyle: styleName });
3480
+ await client.system.patchSettings({ outputStyle: styleName });
2903
3481
  emit(`Output style set to ${styleName}`);
2904
3482
  return "handled";
2905
3483
  }
2906
3484
  if (slash?.name === "/init") {
2907
- emit(await client.initProject({ cwd }));
3485
+ emit(await client.projects.init({ cwd }));
2908
3486
  return "handled";
2909
3487
  }
2910
3488
  if (slash?.name === "/plugin") {
@@ -2912,7 +3490,7 @@ No entries found.`;
2912
3490
  const sub = args[0];
2913
3491
  if (!sub || sub === "list") {
2914
3492
  await readPresentation(`plugins:${cwd}`, "Plugin", async () => {
2915
- const listed = await client.listPlugins({ cwd });
3493
+ const listed = await client.plugins.list({ cwd });
2916
3494
  if (listed.plugins.length === 0)
2917
3495
  return "No plugins discovered.";
2918
3496
  return [
@@ -2925,7 +3503,7 @@ No entries found.`;
2925
3503
  return "handled";
2926
3504
  }
2927
3505
  if ((sub === "enable" || sub === "disable") && args[1]) {
2928
- const result = sub === "enable" ? await client.enablePlugin(args[1], { cwd }) : await client.disablePlugin(args[1], { cwd });
3506
+ const result = sub === "enable" ? await client.plugins.enable(args[1], { cwd }) : await client.plugins.disable(args[1], { cwd });
2929
3507
  emit(result.message);
2930
3508
  return "handled";
2931
3509
  }
@@ -2933,13 +3511,13 @@ No entries found.`;
2933
3511
  return "handled";
2934
3512
  }
2935
3513
  if (slash?.name === "/reload-plugins") {
2936
- const result = await client.reloadPlugins({ cwd });
3514
+ const result = await client.plugins.reload({ cwd });
2937
3515
  emit(formatPluginReload(result));
2938
3516
  return "handled";
2939
3517
  }
2940
3518
  if (slash?.name === "/hooks") {
2941
3519
  await readPresentation(`hooks:${cwd}:${sessionId ?? "global"}`, "Hooks", async () => {
2942
- const hooks = await client.listHooks({
3520
+ const hooks = await client.development.listHooks({
2943
3521
  cwd,
2944
3522
  ...sessionId ? { sessionId } : {}
2945
3523
  });
@@ -2967,7 +3545,7 @@ No entries found.`;
2967
3545
  return "handled";
2968
3546
  }
2969
3547
  if (slash?.name === "/subagents") {
2970
- const agents = await client.listAgentPersonas();
3548
+ const agents = await client.development.listAgentPersonas();
2971
3549
  emit([
2972
3550
  `Available subagent personas (${agents.length}):`,
2973
3551
  "",
@@ -2984,21 +3562,21 @@ No entries found.`;
2984
3562
  }
2985
3563
  if (slash?.name === "/diff") {
2986
3564
  const full = slash.args.trim().split(/\s+/).includes("full");
2987
- await readPresentation(`git:diff:${cwd}:${full ? "full" : "summary"}`, "Diff", async () => await client.getGitDiff({ cwd, full }));
3565
+ await readPresentation(`git:diff:${cwd}:${full ? "full" : "summary"}`, "Diff", async () => await client.development.getGitDiff({ cwd, full }));
2988
3566
  return "handled";
2989
3567
  }
2990
3568
  if (slash?.name === "/branch") {
2991
3569
  const list = slash.args.trim().split(/\s+/).includes("list");
2992
- await readPresentation(`git:branch:${cwd}:${list ? "list" : "current"}`, "Branch", async () => await client.getGitBranch({ cwd, list }));
3570
+ await readPresentation(`git:branch:${cwd}:${list ? "list" : "current"}`, "Branch", async () => await client.development.getGitBranch({ cwd, list }));
2993
3571
  return "handled";
2994
3572
  }
2995
3573
  if (slash?.name === "/commit") {
2996
3574
  const message = slash.args.trim();
2997
3575
  if (!message) {
2998
- await readPresentation(`git:status:${cwd}`, "Commit", async () => await client.getGitStatus({ cwd }));
3576
+ await readPresentation(`git:status:${cwd}`, "Commit", async () => await client.development.getGitStatus({ cwd }));
2999
3577
  return "handled";
3000
3578
  }
3001
- emit(await client.gitCommit({ cwd, message }));
3579
+ emit(await client.development.gitCommit({ cwd, message }));
3002
3580
  return "handled";
3003
3581
  }
3004
3582
  if (slash && LOCAL_COMMAND_NAMES.has(slash.name)) {
@@ -3031,6 +3609,8 @@ var init_commands = __esm(() => {
3031
3609
  // ../../packages/client/src/index.ts
3032
3610
  var init_src2 = __esm(() => {
3033
3611
  init_transport();
3612
+ init_protocol();
3613
+ init_resources();
3034
3614
  init_src();
3035
3615
  init_src();
3036
3616
  init_state();
@@ -3301,128 +3881,588 @@ async function dispatchSessionSlashCommand(slash, ctx) {
3301
3881
  return "local_ui_ignored";
3302
3882
  return outcome;
3303
3883
  }
3304
- var init_sessionSlashCommands = __esm(() => {
3884
+ var init_sessionSlashCommands = __esm(() => {
3885
+ init_src2();
3886
+ });
3887
+
3888
+ // ../frontend/src/hooks/sync-submodules/connection.ts
3889
+ function stringSetting(value) {
3890
+ return typeof value === "string" && value.trim() ? value : undefined;
3891
+ }
3892
+ function normalizeSessionMode(value) {
3893
+ return value === "coordinator" ? "coordinator" : null;
3894
+ }
3895
+ function statusSessionMode(value) {
3896
+ return normalizeSessionMode(value) ?? "direct";
3897
+ }
3898
+ function shouldAutoActivateSession(session, defaultModel, pluginsEnabled) {
3899
+ const runtime = readSessionRuntimeConfig(session);
3900
+ return runtime.model === defaultModel && (pluginsEnabled == null || (runtime.pluginsEnabled ?? true) === pluginsEnabled);
3901
+ }
3902
+ function createDaemonClient(daemon) {
3903
+ if (!daemon?.url)
3904
+ return null;
3905
+ return new OpenHarnessClient({
3906
+ baseUrl: daemon.url,
3907
+ token: daemon.token ?? undefined
3908
+ });
3909
+ }
3910
+ var init_connection = __esm(() => {
3911
+ init_src2();
3912
+ });
3913
+
3914
+ // ../frontend/src/hooks/sync-submodules/permissions.ts
3915
+ function permissionToModal(request) {
3916
+ const input = request.payload.input && typeof request.payload.input === "object" ? request.payload.input : {};
3917
+ return {
3918
+ kind: "permission",
3919
+ request_id: request.id,
3920
+ tool_name: request.toolName,
3921
+ reason: typeof request.payload.reason === "string" ? request.payload.reason : null,
3922
+ input
3923
+ };
3924
+ }
3925
+ function firstPendingPermission(state, sessionId) {
3926
+ return selectFirstPendingPermission(state, sessionId);
3927
+ }
3928
+ var init_permissions = __esm(() => {
3929
+ init_src2();
3930
+ });
3931
+
3932
+ // ../frontend/src/hooks/sync-submodules/view-model.ts
3933
+ function isRecord5(value) {
3934
+ return !!value && typeof value === "object" && !Array.isArray(value);
3935
+ }
3936
+ function sessionRuntimeMetadata(input) {
3937
+ const runtime = {};
3938
+ if (typeof input.model === "string" && input.model) {
3939
+ runtime.model = input.model;
3940
+ }
3941
+ if (typeof input.provider === "string" && input.provider) {
3942
+ runtime.provider = input.provider;
3943
+ }
3944
+ if (typeof input.baseUrl === "string" && input.baseUrl) {
3945
+ runtime.baseUrl = input.baseUrl;
3946
+ }
3947
+ if (input.apiFormat === "anthropic" || input.apiFormat === "openai") {
3948
+ runtime.apiFormat = input.apiFormat;
3949
+ }
3950
+ if (input.permissionMode === "default" || input.permissionMode === "plan" || input.permissionMode === "full_auto") {
3951
+ runtime.permissionMode = input.permissionMode;
3952
+ }
3953
+ if (typeof input.maxTurns === "number" && Number.isFinite(input.maxTurns)) {
3954
+ runtime.maxTurns = input.maxTurns;
3955
+ }
3956
+ if (input.sessionMode === "coordinator" || input.sessionMode === "direct") {
3957
+ runtime.sessionMode = input.sessionMode;
3958
+ }
3959
+ if (typeof input.pluginsEnabled === "boolean") {
3960
+ runtime.pluginsEnabled = input.pluginsEnabled;
3961
+ }
3962
+ return patchSessionRuntimeMetadata({}, runtime);
3963
+ }
3964
+ function archiveClientSession(state, sessionId, archivedAt) {
3965
+ const current = state.sessions[sessionId];
3966
+ if (!current || current.status === "archived")
3967
+ return state;
3968
+ const archived = {
3969
+ ...current,
3970
+ status: "archived",
3971
+ archivedAt,
3972
+ updatedAt: Math.max(current.updatedAt, archivedAt)
3973
+ };
3974
+ const bucket = state.buckets[sessionId];
3975
+ return {
3976
+ ...state,
3977
+ sessions: { ...state.sessions, [sessionId]: archived },
3978
+ buckets: bucket ? { ...state.buckets, [sessionId]: { ...bucket, session: archived } } : state.buckets
3979
+ };
3980
+ }
3981
+ function listTopLevelSessions(sessions, activeSessionId) {
3982
+ return [...sessions].filter((session) => !session.parentId && session.status !== "archived").sort((a, b) => {
3983
+ if (a.id === activeSessionId)
3984
+ return -1;
3985
+ if (b.id === activeSessionId)
3986
+ return 1;
3987
+ return b.updatedAt - a.updatedAt;
3988
+ });
3989
+ }
3990
+ function sessionSelectOptions(sessions, activeSessionId) {
3991
+ return listTopLevelSessions(sessions, activeSessionId).map((session) => ({
3992
+ value: session.id,
3993
+ label: `${session.id === activeSessionId ? "* " : ""}${session.title || session.id}`,
3994
+ description: `${session.model} | ${session.status}`
3995
+ }));
3996
+ }
3997
+ function recoverableInterruptedRuns(bucket) {
3998
+ if (!bucket)
3999
+ return [];
4000
+ const recoveredSourceRunIds = new Set(bucket.inputs.flatMap((input) => isRecord5(input.metadata.recovery) && typeof input.metadata.recovery.sourceRunId === "string" ? [input.metadata.recovery.sourceRunId] : []));
4001
+ return Object.values(bucket.runs).filter((run) => run.status === "interrupted" && !!run.inputId && !recoveredSourceRunIds.has(run.id)).sort((a, b) => b.updatedAt - a.updatedAt).flatMap((run) => {
4002
+ const input = bucket.inputs.find((candidate) => candidate.id === run.inputId);
4003
+ return input ? [{ id: run.id, error: run.error, prompt: input.content }] : [];
4004
+ });
4005
+ }
4006
+ function shouldCoalesceClientState(update) {
4007
+ return update.source === "live" && update.event?.type === "session.message.part.delta";
4008
+ }
4009
+ var init_view_model = __esm(() => {
4010
+ init_src2();
4011
+ });
4012
+
4013
+ // ../frontend/src/hooks/sync-submodules/mcp.ts
4014
+ function mcpServerSnapshot(server) {
4015
+ return {
4016
+ name: server.name,
4017
+ state: server.status,
4018
+ ...server.error || server.command ? { detail: server.error ?? server.command } : {},
4019
+ tool_count: server.toolCount,
4020
+ resource_count: server.resourceCount
4021
+ };
4022
+ }
4023
+
4024
+ // ../frontend/src/hooks/sync-submodules/actions.ts
4025
+ async function executeTuiAction(action, ctx) {
4026
+ const client = ctx.clientRef.current;
4027
+ let sessionId = ctx.activeSessionIdRef.current;
4028
+ const runtimeAction = action;
4029
+ const runtimeType = typeof runtimeAction.type === "string" ? runtimeAction.type : "unknown";
4030
+ switch (action.type) {
4031
+ case "select_model": {
4032
+ if (!client)
4033
+ return;
4034
+ const model = action.model.trim();
4035
+ const provider = action.provider?.trim() ?? "";
4036
+ if (!model)
4037
+ return;
4038
+ if (sessionId) {
4039
+ const session = await client.sessions.update(sessionId, {
4040
+ metadata: patchSessionRuntimeMetadata({}, {
4041
+ model,
4042
+ ...provider ? { provider } : {}
4043
+ })
4044
+ });
4045
+ ctx.activateSession(session);
4046
+ } else {
4047
+ const settingsPatch = { model };
4048
+ if (provider)
4049
+ settingsPatch.provider = provider;
4050
+ await client.system.patchSettings(settingsPatch);
4051
+ ctx.defaultRuntimeRef.current = {
4052
+ ...ctx.defaultRuntimeRef.current,
4053
+ model,
4054
+ ...provider ? { provider } : {}
4055
+ };
4056
+ ctx.setStatus((current) => ({
4057
+ ...current,
4058
+ model,
4059
+ ...provider ? { provider } : {}
4060
+ }));
4061
+ }
4062
+ ctx.pushSystem(`Model selected: ${model}`);
4063
+ return;
4064
+ }
4065
+ case "submit_line": {
4066
+ if (!client)
4067
+ return;
4068
+ const line = action.line;
4069
+ const slash = parseSlashLine(line);
4070
+ const newSession = line.match(/^\/new(?:\s+(.+))?$/);
4071
+ if (newSession) {
4072
+ ctx.returnToHome(newSession[1]);
4073
+ return;
4074
+ }
4075
+ const switchSession = line.match(/^\/sessions\s+open\s+(.+)$/);
4076
+ if (switchSession?.[1]) {
4077
+ const target = switchSession[1].trim();
4078
+ ctx.setLocalBusy(false);
4079
+ ctx.setSubmittedRun(null);
4080
+ ctx.pendingNewSessionTitleRef.current = undefined;
4081
+ ctx.setSelectRequest(null);
4082
+ const knownSession = ctx.clientState.sessions[target] ?? ctx.listedSessionsRef.current[target];
4083
+ if (knownSession) {
4084
+ ctx.activateSession(knownSession);
4085
+ return;
4086
+ }
4087
+ const session = await client.sessions.get(target);
4088
+ ctx.activateSession(session);
4089
+ return;
4090
+ }
4091
+ if (slash?.name === "/resume") {
4092
+ if (!sessionId) {
4093
+ ctx.pushSystem("No active session to resume.");
4094
+ return;
4095
+ }
4096
+ const recoverable = recoverableInterruptedRuns(ctx.clientState.buckets[sessionId]);
4097
+ if (!slash.args) {
4098
+ if (recoverable.length === 0) {
4099
+ ctx.pushSystem("No interrupted prompt runs are available to resume.");
4100
+ return;
4101
+ }
4102
+ ctx.clearDisplayRequest();
4103
+ ctx.setSelectRequest({
4104
+ title: "Resume interrupted run",
4105
+ submitPrefix: "/resume ",
4106
+ options: recoverable.map((run) => ({
4107
+ value: run.id,
4108
+ label: run.prompt.length > 72 ? `${run.prompt.slice(0, 72)}...` : run.prompt,
4109
+ description: run.error ?? "Interrupted before completion"
4110
+ }))
4111
+ });
4112
+ return;
4113
+ }
4114
+ const runId = slash.args.trim();
4115
+ if (!recoverable.some((run) => run.id === runId)) {
4116
+ ctx.pushSystem(`Run is not available for recovery: ${runId}`);
4117
+ return;
4118
+ }
4119
+ ctx.setLocalBusy(true);
4120
+ const response = await client.sessions.resumeInterruptedRun(sessionId, runId, { id: createPromptRequestId() });
4121
+ ctx.setLocalBusy(false);
4122
+ ctx.setSubmittedRun(response.run ? { sessionId, runId: response.run.id } : null);
4123
+ ctx.setSelectRequest(null);
4124
+ return;
4125
+ }
4126
+ let slashResult;
4127
+ try {
4128
+ slashResult = await dispatchSessionSlashCommand(slash, {
4129
+ client,
4130
+ sessionId,
4131
+ pushSystem: ctx.pushSystem,
4132
+ presentSystem: (title, content) => ctx.showDisplayRequest({ title, content }),
4133
+ statusRef: ctx.statusRef,
4134
+ commandCatalogRef: ctx.commandCatalogRef,
4135
+ clientState: ctx.clientState,
4136
+ localBusy: ctx.localBusy,
4137
+ cacheFirstRead: ctx.cacheFirstRead,
4138
+ daemon: ctx.daemon,
4139
+ setStatus: ctx.setStatusAndDefault
4140
+ });
4141
+ } catch (error) {
4142
+ if (slash && JOBS_AUXILIARY_SLASH_COMMANDS.has(slash.name)) {
4143
+ ctx.reportAuxiliaryError("Jobs", error);
4144
+ return;
4145
+ }
4146
+ throw error;
4147
+ }
4148
+ if (slashResult === "handled") {
4149
+ if (slash?.name === "/background" && slash.args.trim() && sessionId) {
4150
+ await ctx.refreshJobs();
4151
+ }
4152
+ return;
4153
+ }
4154
+ if (slashResult === "local_ui_ignored")
4155
+ return;
4156
+ if (slash) {
4157
+ const catalogEntry = ctx.commandCatalogRef.current.find((entry) => entry.name === slash.name);
4158
+ if (catalogEntry?.kind === "template") {
4159
+ if (!sessionId)
4160
+ return;
4161
+ ctx.setLocalBusy(true);
4162
+ const response = await client.sessions.admitPrompt(sessionId, {
4163
+ id: createPromptRequestId(),
4164
+ items: [
4165
+ {
4166
+ type: "skill",
4167
+ name: catalogEntry.skillName,
4168
+ path: catalogEntry.path,
4169
+ ...catalogEntry.displayName ? { displayName: catalogEntry.displayName } : {},
4170
+ ...catalogEntry.source && catalogEntry.source !== "builtin" ? { source: catalogEntry.source } : {}
4171
+ },
4172
+ ...slash.args ? [{ type: "text", text: ` ${slash.args}` }] : []
4173
+ ]
4174
+ });
4175
+ ctx.setLocalBusy(false);
4176
+ ctx.setSubmittedRun(response.run ? { sessionId, runId: response.run.id } : null);
4177
+ return;
4178
+ }
4179
+ ctx.pushSystem(`Unknown command: ${slash.name}`);
4180
+ return;
4181
+ }
4182
+ if (!sessionId) {
4183
+ const session = await ctx.createAndSwitchSession(ctx.pendingNewSessionTitleRef.current);
4184
+ if (!session)
4185
+ return;
4186
+ sessionId = session.id;
4187
+ }
4188
+ ctx.setLocalBusy(true);
4189
+ const response = await client.sessions.admitPrompt(sessionId, {
4190
+ id: createPromptRequestId(),
4191
+ items: [{ type: "text", text: line }]
4192
+ });
4193
+ ctx.setLocalBusy(false);
4194
+ ctx.setSubmittedRun(response.run ? { sessionId, runId: response.run.id } : null);
4195
+ return;
4196
+ }
4197
+ case "delete_session": {
4198
+ if (!client)
4199
+ return;
4200
+ const target = action.session_id;
4201
+ if (!target)
4202
+ return;
4203
+ const archived = await client.sessions.archive(target);
4204
+ const archivedAt = archived.archivedAt ?? Date.now();
4205
+ delete ctx.listedSessionsRef.current[target];
4206
+ ctx.setClientState((current) => archiveClientSession(current, target, archivedAt));
4207
+ ctx.setLocalBusy(false);
4208
+ ctx.setSubmittedRun(null);
4209
+ ctx.setSelectRequest((current) => current ? {
4210
+ ...current,
4211
+ options: current.options.filter((option) => option.value !== target)
4212
+ } : current);
4213
+ if (target === ctx.activeSessionIdRef.current) {
4214
+ const remaining = listTopLevelSessions(Object.values({
4215
+ ...ctx.clientState.sessions,
4216
+ ...ctx.listedSessionsRef.current,
4217
+ [target]: { ...archived, status: "archived", archivedAt }
4218
+ }));
4219
+ if (remaining[0]) {
4220
+ ctx.activateSession(remaining[0]);
4221
+ } else {
4222
+ const sessions = await client.sessions.list({
4223
+ cwd: ctx.daemon?.cwd ?? undefined,
4224
+ includeArchived: false,
4225
+ limit: 20
4226
+ });
4227
+ ctx.listedSessionsRef.current = Object.fromEntries(sessions.map((session) => [session.id, session]));
4228
+ const remoteNext = sessions.find((session) => session.id !== target);
4229
+ if (remoteNext)
4230
+ ctx.activateSession(remoteNext);
4231
+ else
4232
+ ctx.returnToHome();
4233
+ }
4234
+ }
4235
+ return;
4236
+ }
4237
+ case "interrupt": {
4238
+ if (!client)
4239
+ return;
4240
+ if (sessionId)
4241
+ await client.sessions.interrupt(sessionId);
4242
+ ctx.setLocalBusy(false);
4243
+ ctx.setSubmittedRun(null);
4244
+ return;
4245
+ }
4246
+ case "permission_response": {
4247
+ if (!client)
4248
+ return;
4249
+ const requestId = action.request_id;
4250
+ const allowed = action.allowed;
4251
+ await client.permissions.reply(requestId, {
4252
+ status: allowed ? "approved" : "denied",
4253
+ decision: action.scope === "session" ? "session" : "once",
4254
+ clientId: "tui"
4255
+ });
4256
+ ctx.setModal(null);
4257
+ return;
4258
+ }
4259
+ case "list_sessions": {
4260
+ if (!client)
4261
+ return;
4262
+ const cachedSessions = {
4263
+ ...ctx.listedSessionsRef.current,
4264
+ ...ctx.clientState.sessions
4265
+ };
4266
+ const cachedOptions = sessionSelectOptions(Object.values(cachedSessions), ctx.activeSessionIdRef.current);
4267
+ ctx.clearDisplayRequest();
4268
+ ctx.setSelectRequest({
4269
+ title: "Sessions",
4270
+ submitPrefix: "/sessions open ",
4271
+ options: cachedOptions
4272
+ });
4273
+ const sessions = await client.sessions.list({
4274
+ cwd: ctx.daemon?.cwd ?? undefined,
4275
+ includeArchived: false,
4276
+ limit: 20
4277
+ });
4278
+ ctx.listedSessionsRef.current = Object.fromEntries(sessions.map((session) => [session.id, session]));
4279
+ ctx.setSelectRequest((current) => current?.submitPrefix === "/sessions open " ? {
4280
+ title: "Sessions",
4281
+ submitPrefix: "/sessions open ",
4282
+ options: sessionSelectOptions(sessions, ctx.activeSessionIdRef.current)
4283
+ } : current);
4284
+ return;
4285
+ }
4286
+ case "set_permission_mode": {
4287
+ if (!client)
4288
+ return;
4289
+ const mode = action.permission_mode;
4290
+ ctx.setStatus((current) => ({ ...current, permission_mode: mode }));
4291
+ if (sessionId) {
4292
+ await client.sessions.update(sessionId, {
4293
+ metadata: patchSessionRuntimeMetadata({}, { permissionMode: mode })
4294
+ });
4295
+ }
4296
+ return;
4297
+ }
4298
+ case "question_response": {
4299
+ if (!client)
4300
+ return;
4301
+ ctx.reportError("Interactive question responses are not available through the daemon client.");
4302
+ return;
4303
+ }
4304
+ case "set_session_mode": {
4305
+ if (!client)
4306
+ return;
4307
+ if (sessionId) {
4308
+ ctx.pushSystem("Coordinator mode can only be changed before starting a new session. Use /new first.");
4309
+ return;
4310
+ }
4311
+ const mode = normalizeSessionMode(action.session_mode);
4312
+ ctx.nextSessionModeRef.current = statusSessionMode(mode);
4313
+ ctx.setStatus((current) => ({
4314
+ ...current,
4315
+ session_mode: ctx.nextSessionModeRef.current
4316
+ }));
4317
+ return;
4318
+ }
4319
+ case "job_request": {
4320
+ if (!client) {
4321
+ ctx.reportAuxiliaryError("Jobs", "The daemon client is not connected.");
4322
+ return;
4323
+ }
4324
+ const currentSessionId = ctx.activeSessionIdRef.current;
4325
+ if (!currentSessionId)
4326
+ return;
4327
+ try {
4328
+ switch (action.job_action) {
4329
+ case "open":
4330
+ await ctx.refreshJobs();
4331
+ return;
4332
+ case "refresh": {
4333
+ ctx.jobControlGenerationRef.current += 1;
4334
+ ctx.jobControlAbortRef.current?.abort();
4335
+ ctx.jobControlAbortRef.current = null;
4336
+ const detail = ctx.jobDetailStateRef.current;
4337
+ const detailJobId = detail.status === "idle" ? undefined : detail.jobId;
4338
+ await Promise.all([
4339
+ ctx.refreshJobs(),
4340
+ detailJobId ? ctx.loadJobDetail(client, currentSessionId, detailJobId) : Promise.resolve()
4341
+ ]);
4342
+ return;
4343
+ }
4344
+ case "select":
4345
+ ctx.jobControlGenerationRef.current += 1;
4346
+ ctx.jobControlAbortRef.current?.abort();
4347
+ ctx.jobControlAbortRef.current = null;
4348
+ await ctx.loadJobDetail(client, currentSessionId, action.job_id);
4349
+ return;
4350
+ case "cancel": {
4351
+ const controlGeneration = ++ctx.jobControlGenerationRef.current;
4352
+ const detailGeneration = ctx.jobDetailGenerationRef.current;
4353
+ ctx.jobControlAbortRef.current?.abort();
4354
+ const controller = new AbortController;
4355
+ ctx.jobControlAbortRef.current = controller;
4356
+ let response;
4357
+ try {
4358
+ response = await client.jobs.cancel(action.job_id, {
4359
+ sessionId: currentSessionId,
4360
+ reason: action.reason
4361
+ }, { signal: controller.signal });
4362
+ } catch (error) {
4363
+ if (ctx.activeSessionIdRef.current !== currentSessionId)
4364
+ return;
4365
+ if (controller.signal.aborted || ctx.jobControlGenerationRef.current !== controlGeneration) {
4366
+ await ctx.refreshJobs();
4367
+ return;
4368
+ }
4369
+ throw error;
4370
+ } finally {
4371
+ if (ctx.jobControlAbortRef.current === controller)
4372
+ ctx.jobControlAbortRef.current = null;
4373
+ }
4374
+ if (ctx.activeSessionIdRef.current !== currentSessionId)
4375
+ return;
4376
+ const ownsDetail = !controller.signal.aborted && ctx.jobControlGenerationRef.current === controlGeneration && ctx.jobDetailGenerationRef.current === detailGeneration;
4377
+ const validated = validateJobSnapshot(response, currentSessionId, action.job_id);
4378
+ if (!validated.snapshot) {
4379
+ if (controller.signal.aborted || ctx.jobControlGenerationRef.current !== controlGeneration) {
4380
+ await ctx.refreshJobs();
4381
+ return;
4382
+ }
4383
+ throw new Error(validated.error ?? "Job snapshot has invalid fields.");
4384
+ }
4385
+ const snapshot = validated.snapshot;
4386
+ const next = mergeJobSnapshot(ctx.jobStateRef.current, snapshot, Date.now());
4387
+ ctx.jobStateRef.current = next;
4388
+ ctx.setJobState(next);
4389
+ if (ownsDetail) {
4390
+ await ctx.loadJobDetail(client, currentSessionId, action.job_id);
4391
+ }
4392
+ await ctx.refreshJobs();
4393
+ return;
4394
+ }
4395
+ case "send": {
4396
+ const controlGeneration = ++ctx.jobControlGenerationRef.current;
4397
+ const detailGeneration = ctx.jobDetailGenerationRef.current;
4398
+ ctx.jobControlAbortRef.current?.abort();
4399
+ const controller = new AbortController;
4400
+ ctx.jobControlAbortRef.current = controller;
4401
+ try {
4402
+ await client.jobs.send(action.job_id, {
4403
+ sessionId: currentSessionId,
4404
+ data: action.data
4405
+ }, { signal: controller.signal });
4406
+ } catch (error) {
4407
+ if (ctx.activeSessionIdRef.current !== currentSessionId)
4408
+ return;
4409
+ if (controller.signal.aborted || ctx.jobControlGenerationRef.current !== controlGeneration) {
4410
+ await ctx.refreshJobs();
4411
+ return;
4412
+ }
4413
+ throw error;
4414
+ } finally {
4415
+ if (ctx.jobControlAbortRef.current === controller)
4416
+ ctx.jobControlAbortRef.current = null;
4417
+ }
4418
+ if (ctx.activeSessionIdRef.current !== currentSessionId)
4419
+ return;
4420
+ const ownsDetail = !controller.signal.aborted && ctx.jobControlGenerationRef.current === controlGeneration && ctx.jobDetailGenerationRef.current === detailGeneration;
4421
+ if (ownsDetail) {
4422
+ await ctx.loadJobDetail(client, currentSessionId, action.job_id);
4423
+ }
4424
+ await ctx.refreshJobs();
4425
+ return;
4426
+ }
4427
+ }
4428
+ } catch (error) {
4429
+ ctx.reportAuxiliaryError("Jobs", error);
4430
+ return;
4431
+ }
4432
+ }
4433
+ default: {
4434
+ const exhaustive = action;
4435
+ ctx.reportError(`Unsupported TUI action: ${runtimeType}`);
4436
+ return;
4437
+ }
4438
+ }
4439
+ }
4440
+ var JOBS_AUXILIARY_SLASH_COMMANDS;
4441
+ var init_actions = __esm(() => {
3305
4442
  init_src2();
4443
+ init_job_remote_state();
4444
+ init_sessionSlashCommands();
4445
+ init_view_model();
4446
+ init_connection();
4447
+ JOBS_AUXILIARY_SLASH_COMMANDS = new Set([
4448
+ "/agents",
4449
+ "/background",
4450
+ "/doctor",
4451
+ "/jobs",
4452
+ "/stats"
4453
+ ]);
4454
+ });
4455
+
4456
+ // ../frontend/src/hooks/sync-submodules/index.ts
4457
+ var init_sync_submodules = __esm(() => {
4458
+ init_connection();
4459
+ init_permissions();
4460
+ init_view_model();
4461
+ init_actions();
3306
4462
  });
3307
4463
 
3308
4464
  // ../frontend/src/hooks/useServerSync.ts
3309
4465
  import { useCallback, useEffect, useMemo, useRef, useState } from "react";
3310
- function isRecord5(value) {
3311
- return !!value && typeof value === "object" && !Array.isArray(value);
3312
- }
3313
- function sessionRuntimeMetadata(input) {
3314
- const runtime = {};
3315
- if (typeof input.model === "string" && input.model) {
3316
- runtime.model = input.model;
3317
- }
3318
- if (typeof input.provider === "string" && input.provider) {
3319
- runtime.provider = input.provider;
3320
- }
3321
- if (typeof input.baseUrl === "string" && input.baseUrl) {
3322
- runtime.baseUrl = input.baseUrl;
3323
- }
3324
- if (input.apiFormat === "anthropic" || input.apiFormat === "openai") {
3325
- runtime.apiFormat = input.apiFormat;
3326
- }
3327
- if (input.permissionMode === "default" || input.permissionMode === "plan" || input.permissionMode === "full_auto") {
3328
- runtime.permissionMode = input.permissionMode;
3329
- }
3330
- if (typeof input.maxTurns === "number" && Number.isFinite(input.maxTurns)) {
3331
- runtime.maxTurns = input.maxTurns;
3332
- }
3333
- if (input.sessionMode === "coordinator" || input.sessionMode === "direct") {
3334
- runtime.sessionMode = input.sessionMode;
3335
- }
3336
- if (typeof input.pluginsEnabled === "boolean") {
3337
- runtime.pluginsEnabled = input.pluginsEnabled;
3338
- }
3339
- return patchSessionRuntimeMetadata({}, runtime);
3340
- }
3341
- function mcpServerSnapshot(server) {
3342
- return {
3343
- name: server.name,
3344
- state: server.status,
3345
- ...server.error || server.command ? { detail: server.error ?? server.command } : {},
3346
- tool_count: server.toolCount,
3347
- resource_count: server.resourceCount
3348
- };
3349
- }
3350
- function normalizeSessionMode(value) {
3351
- return value === "coordinator" ? "coordinator" : null;
3352
- }
3353
- function statusSessionMode(value) {
3354
- return normalizeSessionMode(value) ?? "direct";
3355
- }
3356
- function stringSetting(value) {
3357
- return typeof value === "string" && value.trim() ? value : undefined;
3358
- }
3359
- function archiveClientSession(state, sessionId, archivedAt) {
3360
- const current = state.sessions[sessionId];
3361
- if (!current || current.status === "archived")
3362
- return state;
3363
- const archived = {
3364
- ...current,
3365
- status: "archived",
3366
- archivedAt,
3367
- updatedAt: Math.max(current.updatedAt, archivedAt)
3368
- };
3369
- const bucket = state.buckets[sessionId];
3370
- return {
3371
- ...state,
3372
- sessions: { ...state.sessions, [sessionId]: archived },
3373
- buckets: bucket ? { ...state.buckets, [sessionId]: { ...bucket, session: archived } } : state.buckets
3374
- };
3375
- }
3376
- function listTopLevelSessions(sessions, activeSessionId) {
3377
- return [...sessions].filter((session) => !session.parentId && session.status !== "archived").sort((a, b) => {
3378
- if (a.id === activeSessionId)
3379
- return -1;
3380
- if (b.id === activeSessionId)
3381
- return 1;
3382
- return b.updatedAt - a.updatedAt;
3383
- });
3384
- }
3385
- function sessionSelectOptions(sessions, activeSessionId) {
3386
- return listTopLevelSessions(sessions, activeSessionId).map((session) => ({
3387
- value: session.id,
3388
- label: `${session.id === activeSessionId ? "* " : ""}${session.title || session.id}`,
3389
- description: `${session.model} | ${session.status}`
3390
- }));
3391
- }
3392
- function shouldAutoActivateSession(session, defaultModel, pluginsEnabled) {
3393
- const runtime = readSessionRuntimeConfig(session);
3394
- return runtime.model === defaultModel && (pluginsEnabled == null || (runtime.pluginsEnabled ?? true) === pluginsEnabled);
3395
- }
3396
- function recoverableInterruptedRuns(bucket) {
3397
- if (!bucket)
3398
- return [];
3399
- const recoveredSourceRunIds = new Set(bucket.inputs.flatMap((input) => isRecord5(input.metadata.recovery) && typeof input.metadata.recovery.sourceRunId === "string" ? [input.metadata.recovery.sourceRunId] : []));
3400
- return Object.values(bucket.runs).filter((run) => run.status === "interrupted" && !!run.inputId && !recoveredSourceRunIds.has(run.id)).sort((a, b) => b.updatedAt - a.updatedAt).flatMap((run) => {
3401
- const input = bucket.inputs.find((candidate) => candidate.id === run.inputId);
3402
- return input ? [{ id: run.id, error: run.error, prompt: input.content }] : [];
3403
- });
3404
- }
3405
- function shouldCoalesceClientState(update) {
3406
- return update.source === "live" && update.event?.type === "session.message.part.delta";
3407
- }
3408
- function permissionToModal(request) {
3409
- const input = request.payload.input && typeof request.payload.input === "object" ? request.payload.input : {};
3410
- return {
3411
- kind: "permission",
3412
- request_id: request.id,
3413
- tool_name: request.toolName,
3414
- reason: typeof request.payload.reason === "string" ? request.payload.reason : null,
3415
- input
3416
- };
3417
- }
3418
- function firstPendingPermission(state, sessionId) {
3419
- if (!sessionId)
3420
- return;
3421
- const bucket = state.buckets[sessionId];
3422
- if (!bucket)
3423
- return;
3424
- return Object.values(bucket.permissions).filter((request) => request.status === "pending").sort((a, b) => a.createdAt - b.createdAt).at(0);
3425
- }
3426
4466
  function useServerSync(config, onError) {
3427
4467
  const daemon = config.daemon;
3428
4468
  const [clientState, setClientState] = useState(() => createInitialClientState());
@@ -3462,6 +4502,7 @@ function useServerSync(config, onError) {
3462
4502
  const pendingClientStateTimerRef = useRef(null);
3463
4503
  const shownPermissionIdRef = useRef(undefined);
3464
4504
  const auxiliaryGenerationRef = useRef(0);
4505
+ const syncGenerationRef = useRef(0);
3465
4506
  const jobStateRef = useRef(jobState);
3466
4507
  const jobDetailStateRef = useRef(jobDetailState);
3467
4508
  const jobGenerationRef = useRef(0);
@@ -3576,7 +4617,7 @@ function useServerSync(config, onError) {
3576
4617
  return next;
3577
4618
  });
3578
4619
  try {
3579
- const response = await client.listJobs({
4620
+ const response = await client.jobs.list({
3580
4621
  sessionId,
3581
4622
  includeFinished: true,
3582
4623
  limit: 100,
@@ -3635,7 +4676,7 @@ function useServerSync(config, onError) {
3635
4676
  jobDetailStateRef.current = loading;
3636
4677
  setJobDetailState(loading);
3637
4678
  try {
3638
- const response = await client.readJob(jobId, {
4679
+ const response = await client.jobs.read(jobId, {
3639
4680
  sessionId,
3640
4681
  signal: controller.signal
3641
4682
  });
@@ -3678,7 +4719,7 @@ function useServerSync(config, onError) {
3678
4719
  const client = clientRef.current;
3679
4720
  if (!client)
3680
4721
  return [];
3681
- return await client.listModels();
4722
+ return await client.providers.listModels();
3682
4723
  }, []);
3683
4724
  const cacheFirstRead = useCallback((request) => {
3684
4725
  const cached = presentationCacheRef.current[request.key];
@@ -3787,16 +4828,19 @@ Refresh failed: ${message}` : `Failed to load ${request.title}: ${message}`;
3787
4828
  return;
3788
4829
  }
3789
4830
  let cancelled = false;
3790
- const client = new OpenHarnessClient({
3791
- baseUrl: daemon.url,
3792
- token: daemon.token ?? undefined
3793
- });
4831
+ const client = createDaemonClient(daemon);
4832
+ if (!client)
4833
+ return;
3794
4834
  clientRef.current = client;
3795
4835
  (async () => {
3796
4836
  try {
3797
- await client.health();
4837
+ await client.protocol.health();
3798
4838
  const cwd = daemon.cwd ?? process.cwd();
3799
- const [settings, sessions, commands] = await Promise.all([client.getSettings().catch(() => ({})), client.listSessions({ cwd, limit: 20 }), client.listCommands({ cwd }).catch(() => [])]);
4839
+ const [settings, sessions, commands] = await Promise.all([
4840
+ client.system.getSettings().catch(() => ({})),
4841
+ client.sessions.list({ cwd, limit: 20 }),
4842
+ client.system.listCommands({ cwd }).catch(() => [])
4843
+ ]);
3800
4844
  const model = daemon.model ?? stringSetting(settings.model) ?? "default";
3801
4845
  const provider = stringSetting(settings.provider);
3802
4846
  const baseUrl = stringSetting(settings.baseUrl);
@@ -3823,7 +4867,7 @@ Refresh failed: ${message}` : `Failed to load ${request.title}: ${message}`;
3823
4867
  const session = sessions[0];
3824
4868
  if (session && shouldAutoActivateSession(session, model, daemon?.pluginsEnabled)) {
3825
4869
  activateSession(session);
3826
- client.getSession(session.id).catch(() => {});
4870
+ client.sessions.get(session.id).catch(() => {});
3827
4871
  }
3828
4872
  setReady(true);
3829
4873
  } catch (error) {
@@ -3842,36 +4886,46 @@ Refresh failed: ${message}` : `Failed to load ${request.title}: ${message}`;
3842
4886
  const client = clientRef.current;
3843
4887
  if (!client || !activeSessionId)
3844
4888
  return;
3845
- const controller = new AbortController;
3846
- (async () => {
3847
- let reconnectNotice = false;
3848
- try {
3849
- for await (const update of syncEvents(client, {
3850
- sessionId: activeSessionId,
3851
- signal: controller.signal
3852
- })) {
3853
- if (update.source === "reconnecting") {
3854
- if (!reconnectNotice) {
3855
- reconnectNotice = true;
3856
- pushSystem("reconnecting...");
3857
- }
3858
- continue;
4889
+ const generation = ++syncGenerationRef.current;
4890
+ let reconnectNotice = false;
4891
+ const controller = new SessionSyncController({
4892
+ client,
4893
+ sessionId: activeSessionId,
4894
+ generation,
4895
+ onUpdate: (update, gen) => {
4896
+ if (gen !== syncGenerationRef.current)
4897
+ return;
4898
+ if (update.source === "reconnecting") {
4899
+ if (!reconnectNotice) {
4900
+ reconnectNotice = true;
4901
+ pushSystem("reconnecting...");
3859
4902
  }
3860
- if (reconnectNotice)
3861
- reconnectNotice = false;
3862
- commitClientState(update.state, shouldCoalesceClientState(update));
4903
+ return;
3863
4904
  }
3864
- } catch (error) {
3865
- if (controller.signal.aborted)
4905
+ if (reconnectNotice)
4906
+ reconnectNotice = false;
4907
+ commitClientState(update.state, shouldCoalesceClientState(update));
4908
+ },
4909
+ onError: (error, gen) => {
4910
+ if (gen !== syncGenerationRef.current)
3866
4911
  return;
3867
4912
  reportError(error instanceof Error ? error.message : String(error));
3868
4913
  }
3869
- })();
4914
+ });
4915
+ controller.start();
3870
4916
  return () => {
3871
4917
  clearPendingClientState();
3872
4918
  controller.abort();
3873
4919
  };
3874
- }, [activeSessionId, clearPendingClientState, commitClientState, pushSystem, reportError]);
4920
+ }, [
4921
+ activeSessionId,
4922
+ clearPendingClientState,
4923
+ commitClientState,
4924
+ daemon?.token,
4925
+ daemon?.url,
4926
+ pushSystem,
4927
+ reportError
4928
+ ]);
3875
4929
  useEffect(() => {
3876
4930
  const client = clientRef.current;
3877
4931
  const sessionId = activeSessionId;
@@ -3882,7 +4936,7 @@ Refresh failed: ${message}` : `Failed to load ${request.title}: ${message}`;
3882
4936
  const controller = new AbortController;
3883
4937
  mcpAbortRef.current = controller;
3884
4938
  const isCurrent = () => activeSessionIdRef.current === sessionId && auxiliaryGenerationRef.current === generation;
3885
- client.getSessionMcp(sessionId, { signal: controller.signal }).then((servers) => {
4939
+ client.system.getSessionMcp(sessionId, { signal: controller.signal }).then((servers) => {
3886
4940
  if (!isCurrent())
3887
4941
  return;
3888
4942
  setMcpServers(Array.isArray(servers) ? servers.map(mcpServerSnapshot) : []);
@@ -3957,7 +5011,7 @@ Refresh failed: ${message}` : `Failed to load ${request.title}: ${message}`;
3957
5011
  sessionMode: nextSessionModeRef.current,
3958
5012
  pluginsEnabled: daemon?.pluginsEnabled
3959
5013
  });
3960
- const session = await client.createSession({
5014
+ const session = await client.sessions.create({
3961
5015
  cwd,
3962
5016
  model,
3963
5017
  title: title?.trim() || "TUI",
@@ -3979,12 +5033,12 @@ Refresh failed: ${message}` : `Failed to load ${request.title}: ${message}`;
3979
5033
  return;
3980
5034
  setLocalBusy(true);
3981
5035
  (async () => {
3982
- const session = activeSessionId ? clientState.sessions[activeSessionId] ?? await client.getSession(activeSessionId) : await createAndSwitchSession();
5036
+ const session = activeSessionId ? clientState.sessions[activeSessionId] ?? await client.sessions.get(activeSessionId) : await createAndSwitchSession();
3983
5037
  if (!session) {
3984
5038
  setLocalBusy(false);
3985
5039
  return;
3986
5040
  }
3987
- const response = await client.admitPrompt(session.id, {
5041
+ const response = await client.sessions.admitPrompt(session.id, {
3988
5042
  id: createPromptRequestId(),
3989
5043
  items: [{ type: "text", text: config.initial_prompt }]
3990
5044
  });
@@ -3993,424 +5047,46 @@ Refresh failed: ${message}` : `Failed to load ${request.title}: ${message}`;
3993
5047
  })().catch((error) => reportError(error instanceof Error ? error.message : String(error)));
3994
5048
  }, [activeSessionId, clientState.sessions, config.initial_prompt, createAndSwitchSession, ready, reportError]);
3995
5049
  const sendRequest = useCallback((action) => {
3996
- const client = clientRef.current;
3997
- let sessionId = activeSessionIdRef.current;
3998
- (async () => {
3999
- const runtimeAction = action;
4000
- const runtimeType = typeof runtimeAction.type === "string" ? runtimeAction.type : "unknown";
4001
- switch (action.type) {
4002
- case "select_model": {
4003
- if (!client)
4004
- return;
4005
- const model = action.model.trim();
4006
- const provider = action.provider?.trim() ?? "";
4007
- if (!model)
4008
- return;
4009
- if (sessionId) {
4010
- const session = await client.updateSession(sessionId, {
4011
- metadata: patchSessionRuntimeMetadata({}, {
4012
- model,
4013
- ...provider ? { provider } : {}
4014
- })
4015
- });
4016
- activateSession(session);
4017
- } else {
4018
- const settingsPatch = { model };
4019
- if (provider)
4020
- settingsPatch.provider = provider;
4021
- await client.patchSettings(settingsPatch);
4022
- defaultRuntimeRef.current = {
4023
- ...defaultRuntimeRef.current,
4024
- model,
4025
- ...provider ? { provider } : {}
4026
- };
4027
- setStatus((current) => ({
4028
- ...current,
4029
- model,
4030
- ...provider ? { provider } : {}
4031
- }));
4032
- }
4033
- pushSystem(`Model selected: ${model}`);
4034
- return;
4035
- }
4036
- case "submit_line": {
4037
- if (!client)
4038
- return;
4039
- const line = action.line;
4040
- const slash = parseSlashLine(line);
4041
- const newSession = line.match(/^\/new(?:\s+(.+))?$/);
4042
- if (newSession) {
4043
- returnToHome(newSession[1]);
4044
- return;
4045
- }
4046
- const switchSession = line.match(/^\/sessions\s+open\s+(.+)$/);
4047
- if (switchSession?.[1]) {
4048
- const target = switchSession[1].trim();
4049
- setLocalBusy(false);
4050
- setSubmittedRun(null);
4051
- pendingNewSessionTitleRef.current = undefined;
4052
- setSelectRequest(null);
4053
- const knownSession = clientState.sessions[target] ?? listedSessionsRef.current[target];
4054
- if (knownSession) {
4055
- activateSession(knownSession);
4056
- return;
4057
- }
4058
- const session = await client.getSession(target);
4059
- activateSession(session);
4060
- return;
4061
- }
4062
- if (slash?.name === "/resume") {
4063
- if (!sessionId) {
4064
- pushSystem("No active session to resume.");
4065
- return;
4066
- }
4067
- const recoverable = recoverableInterruptedRuns(clientState.buckets[sessionId]);
4068
- if (!slash.args) {
4069
- if (recoverable.length === 0) {
4070
- pushSystem("No interrupted prompt runs are available to resume.");
4071
- return;
4072
- }
4073
- clearDisplayRequest();
4074
- setSelectRequest({
4075
- title: "Resume interrupted run",
4076
- submitPrefix: "/resume ",
4077
- options: recoverable.map((run) => ({
4078
- value: run.id,
4079
- label: run.prompt.length > 72 ? `${run.prompt.slice(0, 72)}...` : run.prompt,
4080
- description: run.error ?? "Interrupted before completion"
4081
- }))
4082
- });
4083
- return;
4084
- }
4085
- const runId = slash.args.trim();
4086
- if (!recoverable.some((run) => run.id === runId)) {
4087
- pushSystem(`Run is not available for recovery: ${runId}`);
4088
- return;
4089
- }
4090
- setLocalBusy(true);
4091
- const response = await client.resumeInterruptedRun(sessionId, runId, { id: createPromptRequestId() });
4092
- setLocalBusy(false);
4093
- setSubmittedRun(response.run ? { sessionId, runId: response.run.id } : null);
4094
- setSelectRequest(null);
4095
- return;
4096
- }
4097
- let slashResult;
4098
- try {
4099
- slashResult = await dispatchSessionSlashCommand(slash, {
4100
- client,
4101
- sessionId,
4102
- pushSystem,
4103
- presentSystem: (title, content) => showDisplayRequest({ title, content }),
4104
- statusRef,
4105
- commandCatalogRef,
4106
- clientState,
4107
- localBusy,
4108
- cacheFirstRead,
4109
- daemon,
4110
- setStatus: setStatusAndDefault
4111
- });
4112
- } catch (error) {
4113
- if (slash && JOBS_AUXILIARY_SLASH_COMMANDS.has(slash.name)) {
4114
- reportAuxiliaryError("Jobs", error);
4115
- return;
4116
- }
4117
- throw error;
4118
- }
4119
- if (slashResult === "handled") {
4120
- if (slash?.name === "/background" && slash.args.trim() && sessionId) {
4121
- await refreshJobs();
4122
- }
4123
- return;
4124
- }
4125
- if (slashResult === "local_ui_ignored")
4126
- return;
4127
- if (slash) {
4128
- const catalogEntry = commandCatalogRef.current.find((entry) => entry.name === slash.name);
4129
- if (catalogEntry?.kind === "template") {
4130
- if (!sessionId)
4131
- return;
4132
- setLocalBusy(true);
4133
- const response = await client.admitPrompt(sessionId, {
4134
- id: createPromptRequestId(),
4135
- items: [
4136
- {
4137
- type: "skill",
4138
- name: catalogEntry.skillName,
4139
- path: catalogEntry.path,
4140
- ...catalogEntry.displayName ? { displayName: catalogEntry.displayName } : {},
4141
- ...catalogEntry.source && catalogEntry.source !== "builtin" ? { source: catalogEntry.source } : {}
4142
- },
4143
- ...slash.args ? [{ type: "text", text: ` ${slash.args}` }] : []
4144
- ]
4145
- });
4146
- setLocalBusy(false);
4147
- setSubmittedRun(response.run ? { sessionId, runId: response.run.id } : null);
4148
- return;
4149
- }
4150
- pushSystem(`Unknown command: ${slash.name}`);
4151
- return;
4152
- }
4153
- if (!sessionId) {
4154
- const session = await createAndSwitchSession(pendingNewSessionTitleRef.current);
4155
- if (!session)
4156
- return;
4157
- sessionId = session.id;
4158
- }
4159
- setLocalBusy(true);
4160
- const response = await client.admitPrompt(sessionId, {
4161
- id: createPromptRequestId(),
4162
- items: [{ type: "text", text: line }]
4163
- });
4164
- setLocalBusy(false);
4165
- setSubmittedRun(response.run ? { sessionId, runId: response.run.id } : null);
4166
- return;
4167
- }
4168
- case "delete_session": {
4169
- if (!client)
4170
- return;
4171
- const target = action.session_id;
4172
- if (!target)
4173
- return;
4174
- const archived = await client.archiveSession(target);
4175
- const archivedAt = archived.archivedAt ?? Date.now();
4176
- delete listedSessionsRef.current[target];
4177
- setClientState((current) => archiveClientSession(current, target, archivedAt));
4178
- setLocalBusy(false);
4179
- setSubmittedRun(null);
4180
- setSelectRequest((current) => current ? {
4181
- ...current,
4182
- options: current.options.filter((option) => option.value !== target)
4183
- } : current);
4184
- if (target === activeSessionIdRef.current) {
4185
- const remaining = listTopLevelSessions(Object.values({
4186
- ...clientState.sessions,
4187
- ...listedSessionsRef.current,
4188
- [target]: { ...archived, status: "archived", archivedAt }
4189
- }));
4190
- if (remaining[0]) {
4191
- activateSession(remaining[0]);
4192
- } else {
4193
- const sessions = await client.listSessions({
4194
- cwd: daemon?.cwd ?? undefined,
4195
- includeArchived: false,
4196
- limit: 20
4197
- });
4198
- listedSessionsRef.current = Object.fromEntries(sessions.map((session) => [session.id, session]));
4199
- const remoteNext = sessions.find((session) => session.id !== target);
4200
- if (remoteNext)
4201
- activateSession(remoteNext);
4202
- else
4203
- returnToHome();
4204
- }
4205
- }
4206
- return;
4207
- }
4208
- case "interrupt": {
4209
- if (!client)
4210
- return;
4211
- if (sessionId)
4212
- await client.interruptSession(sessionId);
4213
- setLocalBusy(false);
4214
- setSubmittedRun(null);
4215
- return;
4216
- }
4217
- case "permission_response": {
4218
- if (!client)
4219
- return;
4220
- const requestId = action.request_id;
4221
- const allowed = action.allowed;
4222
- await client.replyPermission(requestId, {
4223
- status: allowed ? "approved" : "denied",
4224
- decision: action.scope === "session" ? "session" : "once",
4225
- clientId: "tui"
4226
- });
4227
- setModal(null);
4228
- return;
4229
- }
4230
- case "list_sessions": {
4231
- if (!client)
4232
- return;
4233
- const cachedSessions = {
4234
- ...listedSessionsRef.current,
4235
- ...clientState.sessions
4236
- };
4237
- const cachedOptions = sessionSelectOptions(Object.values(cachedSessions), activeSessionIdRef.current);
4238
- clearDisplayRequest();
4239
- setSelectRequest({
4240
- title: "Sessions",
4241
- submitPrefix: "/sessions open ",
4242
- options: cachedOptions
4243
- });
4244
- const sessions = await client.listSessions({
4245
- cwd: daemon?.cwd ?? undefined,
4246
- includeArchived: false,
4247
- limit: 20
4248
- });
4249
- listedSessionsRef.current = Object.fromEntries(sessions.map((session) => [session.id, session]));
4250
- setSelectRequest((current) => current?.submitPrefix === "/sessions open " ? {
4251
- title: "Sessions",
4252
- submitPrefix: "/sessions open ",
4253
- options: sessionSelectOptions(sessions, activeSessionIdRef.current)
4254
- } : current);
4255
- return;
4256
- }
4257
- case "set_permission_mode": {
4258
- if (!client)
4259
- return;
4260
- const mode = action.permission_mode;
4261
- setStatus((current) => ({ ...current, permission_mode: mode }));
4262
- if (sessionId) {
4263
- await client.updateSession(sessionId, {
4264
- metadata: patchSessionRuntimeMetadata({}, { permissionMode: mode })
4265
- });
4266
- }
4267
- return;
4268
- }
4269
- case "question_response": {
4270
- if (!client)
4271
- return;
4272
- reportError("Interactive question responses are not available through the daemon client.");
4273
- return;
4274
- }
4275
- case "set_session_mode": {
4276
- if (!client)
4277
- return;
4278
- if (sessionId) {
4279
- pushSystem("Coordinator mode can only be changed before starting a new session. Use /new first.");
4280
- return;
4281
- }
4282
- const mode = normalizeSessionMode(action.session_mode);
4283
- nextSessionModeRef.current = statusSessionMode(mode);
4284
- setStatus((current) => ({
4285
- ...current,
4286
- session_mode: nextSessionModeRef.current
4287
- }));
4288
- return;
4289
- }
4290
- case "job_request": {
4291
- if (!client) {
4292
- reportAuxiliaryError("Jobs", "The daemon client is not connected.");
4293
- return;
4294
- }
4295
- const currentSessionId = activeSessionIdRef.current;
4296
- if (!currentSessionId)
4297
- return;
4298
- try {
4299
- switch (action.job_action) {
4300
- case "open":
4301
- await refreshJobs();
4302
- return;
4303
- case "refresh": {
4304
- jobControlGenerationRef.current += 1;
4305
- jobControlAbortRef.current?.abort();
4306
- jobControlAbortRef.current = null;
4307
- const detail = jobDetailStateRef.current;
4308
- const detailJobId = detail.status === "idle" ? undefined : detail.jobId;
4309
- await Promise.all([
4310
- refreshJobs(),
4311
- detailJobId ? loadJobDetail(client, currentSessionId, detailJobId) : Promise.resolve()
4312
- ]);
4313
- return;
4314
- }
4315
- case "select":
4316
- jobControlGenerationRef.current += 1;
4317
- jobControlAbortRef.current?.abort();
4318
- jobControlAbortRef.current = null;
4319
- await loadJobDetail(client, currentSessionId, action.job_id);
4320
- return;
4321
- case "cancel": {
4322
- const controlGeneration = ++jobControlGenerationRef.current;
4323
- const detailGeneration = jobDetailGenerationRef.current;
4324
- jobControlAbortRef.current?.abort();
4325
- const controller = new AbortController;
4326
- jobControlAbortRef.current = controller;
4327
- let response;
4328
- try {
4329
- response = await client.cancelJob(action.job_id, {
4330
- sessionId: currentSessionId,
4331
- reason: action.reason
4332
- }, { signal: controller.signal });
4333
- } catch (error) {
4334
- if (activeSessionIdRef.current !== currentSessionId)
4335
- return;
4336
- if (controller.signal.aborted || jobControlGenerationRef.current !== controlGeneration) {
4337
- await refreshJobs();
4338
- return;
4339
- }
4340
- throw error;
4341
- } finally {
4342
- if (jobControlAbortRef.current === controller)
4343
- jobControlAbortRef.current = null;
4344
- }
4345
- if (activeSessionIdRef.current !== currentSessionId)
4346
- return;
4347
- const ownsDetail = !controller.signal.aborted && jobControlGenerationRef.current === controlGeneration && jobDetailGenerationRef.current === detailGeneration;
4348
- const validated = validateJobSnapshot(response, currentSessionId, action.job_id);
4349
- if (!validated.snapshot) {
4350
- if (controller.signal.aborted || jobControlGenerationRef.current !== controlGeneration) {
4351
- await refreshJobs();
4352
- return;
4353
- }
4354
- throw new Error(validated.error ?? "Job snapshot has invalid fields.");
4355
- }
4356
- const snapshot = validated.snapshot;
4357
- const next = mergeJobSnapshot(jobStateRef.current, snapshot, Date.now());
4358
- jobStateRef.current = next;
4359
- setJobState(next);
4360
- if (ownsDetail) {
4361
- await loadJobDetail(client, currentSessionId, action.job_id);
4362
- }
4363
- await refreshJobs();
4364
- return;
4365
- }
4366
- case "send": {
4367
- const controlGeneration = ++jobControlGenerationRef.current;
4368
- const detailGeneration = jobDetailGenerationRef.current;
4369
- jobControlAbortRef.current?.abort();
4370
- const controller = new AbortController;
4371
- jobControlAbortRef.current = controller;
4372
- try {
4373
- await client.sendJob(action.job_id, {
4374
- sessionId: currentSessionId,
4375
- data: action.data
4376
- }, { signal: controller.signal });
4377
- } catch (error) {
4378
- if (activeSessionIdRef.current !== currentSessionId)
4379
- return;
4380
- if (controller.signal.aborted || jobControlGenerationRef.current !== controlGeneration) {
4381
- await refreshJobs();
4382
- return;
4383
- }
4384
- throw error;
4385
- } finally {
4386
- if (jobControlAbortRef.current === controller)
4387
- jobControlAbortRef.current = null;
4388
- }
4389
- if (activeSessionIdRef.current !== currentSessionId)
4390
- return;
4391
- const ownsDetail = !controller.signal.aborted && jobControlGenerationRef.current === controlGeneration && jobDetailGenerationRef.current === detailGeneration;
4392
- if (ownsDetail) {
4393
- await loadJobDetail(client, currentSessionId, action.job_id);
4394
- }
4395
- await refreshJobs();
4396
- return;
4397
- }
4398
- }
4399
- } catch (error) {
4400
- reportAuxiliaryError("Jobs", error);
4401
- return;
4402
- }
4403
- }
4404
- default: {
4405
- const exhaustive = action;
4406
- reportError(`Unsupported TUI action: ${runtimeType}`);
4407
- return;
4408
- }
4409
- }
4410
- })().catch((error) => {
5050
+ executeTuiAction(action, {
5051
+ clientRef,
5052
+ activeSessionIdRef,
5053
+ clientState,
5054
+ setClientState,
5055
+ daemon,
5056
+ defaultRuntimeRef,
5057
+ nextSessionModeRef,
5058
+ statusRef,
5059
+ setStatus,
5060
+ setStatusAndDefault,
5061
+ listedSessionsRef,
5062
+ commandCatalogRef,
5063
+ pendingNewSessionTitleRef,
5064
+ localBusy,
5065
+ setLocalBusy,
5066
+ setSubmittedRun,
5067
+ setModal,
5068
+ setSelectRequest,
5069
+ clearDisplayRequest,
5070
+ showDisplayRequest,
5071
+ pushSystem,
5072
+ reportError,
5073
+ reportAuxiliaryError,
5074
+ activateSession,
5075
+ returnToHome,
5076
+ refreshJobs,
5077
+ loadJobDetail,
5078
+ createAndSwitchSession,
5079
+ cacheFirstRead,
5080
+ jobStateRef,
5081
+ setJobState,
5082
+ jobDetailStateRef,
5083
+ jobControlGenerationRef,
5084
+ jobDetailGenerationRef,
5085
+ jobControlAbortRef
5086
+ }).catch((error) => {
4411
5087
  reportError(error instanceof Error ? error.message : String(error));
4412
5088
  });
4413
- }, [activeSessionId, activateSession, cacheFirstRead, clearDisplayRequest, clientState, createAndSwitchSession, daemon, loadJobDetail, localBusy, pushSystem, refreshJobs, reportAuxiliaryError, reportError, returnToHome, showDisplayRequest]);
5089
+ }, [activateSession, cacheFirstRead, clearDisplayRequest, clientState, createAndSwitchSession, daemon, loadJobDetail, localBusy, pushSystem, refreshJobs, reportAuxiliaryError, reportError, returnToHome, setStatusAndDefault, showDisplayRequest]);
4414
5090
  const bucket = activeSessionId ? clientState.buckets[activeSessionId] : undefined;
4415
5091
  const recoveryItems = useMemo(() => recoverableInterruptedRuns(bucket).map((run) => ({
4416
5092
  id: `recovery:${run.id}`,
@@ -4452,19 +5128,14 @@ Use /resume ${run.id} to replay its original prompt.`
4452
5128
  sendRequest
4453
5129
  }), [commandDetails, commands, displayRequest, jobDetailState, jobState, loadModels, localBusy, mcpServers, modal, ready, running, selectRequest, sendRequest, status, transcriptView, waitingForSubmittedRun]);
4454
5130
  }
4455
- var JOBS_AUXILIARY_SLASH_COMMANDS, LIVE_TEXT_DELTA_FLUSH_MS = 33, PRESENTATION_LOADING_TEXT = "Loading...";
5131
+ var LIVE_TEXT_DELTA_FLUSH_MS = 33, PRESENTATION_LOADING_TEXT = "Loading...";
4456
5132
  var init_useServerSync = __esm(() => {
4457
5133
  init_src2();
4458
5134
  init_job_remote_state();
4459
5135
  init_transcript();
4460
5136
  init_sessionSlashCommands();
4461
- JOBS_AUXILIARY_SLASH_COMMANDS = new Set([
4462
- "/agents",
4463
- "/background",
4464
- "/doctor",
4465
- "/jobs",
4466
- "/stats"
4467
- ]);
5137
+ init_sync_submodules();
5138
+ init_sync_submodules();
4468
5139
  });
4469
5140
 
4470
5141
  // ../frontend/src/ui/constants.ts
@@ -8167,4 +8838,4 @@ try {
8167
8838
  process.exit(1);
8168
8839
  }
8169
8840
 
8170
- //# debugId=CC7EA554B2C5EAF964756E2164756E21
8841
+ //# debugId=9F3BE548A8F89F3864756E2164756E21