@samuelho-dev/coordinator 0.3.0-beta.2 → 0.3.0-beta.3

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.
@@ -1,6 +1,6 @@
1
1
  // src/plugin.ts
2
2
  import { NodeServices } from "@effect/platform-node";
3
- import { Cause as Cause3, Clock as Clock3, Config as Config3, Console as Console2, Effect as Effect6, Layer as Layer3, Logger, ManagedRuntime, Schema as Schema5 } from "effect";
3
+ import { Cause as Cause3, Clock as Clock3, Console as Console2, Effect as Effect7, Layer as Layer3, Logger, ManagedRuntime, Schema as Schema5 } from "effect";
4
4
 
5
5
  // src/inspection.ts
6
6
  import { Effect, Option, Path as PlatformPath, Schema, SchemaParser } from "effect";
@@ -40,6 +40,13 @@ var ProcessIdentity = Schema.Struct({
40
40
  ownership: Schema.Literals(["owned", "shared", "unknown"])
41
41
  });
42
42
  var NativeWorkState = Schema.Struct({ id: Id, state: Schema.Literals(["pending", "active", "completed", "cancelled", "unknown"]) });
43
+ var RexText = Schema.String.check(Schema.isMinLength(1), Schema.isMaxLength(4096), Schema.makeFilter(
44
+ (value) => /[\u0000-\u001f\u007f-\u009f]/u.test(value) ? "Control characters are forbidden" : void 0
45
+ ));
46
+ var ProgramStatus = Schema.Union([
47
+ Schema.Struct({ state: Schema.Literals(["idle", "working", "done", "error"]) }),
48
+ Schema.Struct({ state: Schema.Literal("blocked"), kind: Schema.Literals(["permission", "question", "auth"]) })
49
+ ]);
43
50
  var SessionPresence = Schema.Struct({
44
51
  ref: SessionRef,
45
52
  observedAt: Timestamp,
@@ -60,7 +67,8 @@ var SessionPresence = Schema.Struct({
60
67
  agent: Schema.optional(Id),
61
68
  cwd: Schema.optional(Path),
62
69
  repository: Schema.optional(Path),
63
- rex: Schema.optional(Schema.Struct({ serverId: Schema.optional(Id), blockId: Schema.optional(Id), sessionId: Schema.optional(Id), windowId: Schema.optional(Id), paneId: Schema.optional(Id), terminalId: Schema.optional(Id) })),
70
+ rex: Schema.optional(Schema.Struct({ serverAddress: Schema.optional(RexText), blockId: Schema.optional(RexText), sessionId: Schema.optional(RexText) })),
71
+ programStatus: Schema.optional(ProgramStatus),
64
72
  processes: Schema.optional(bounded(ProcessIdentity, 128)),
65
73
  activity: Schema.optional(Schema.Struct({
66
74
  state: Schema.Literals(["idle", "working", "waiting", "stopped", "unknown"]),
@@ -561,7 +569,7 @@ var Mcp = class extends Context.Service()("@samuelho-dev/coordinator/Mcp", {
561
569
  };
562
570
 
563
571
  // src/native.ts
564
- import { Cause as Cause2, Clock as Clock2, Config as Config2, Console, Crypto, Data as Data2, Duration, Effect as Effect5, FileSystem as FileSystem3, Option as Option4, Path as Path2, PlatformError as PlatformError2, Schedule, Schema as Schema4, Scope } from "effect";
572
+ import { Cause as Cause2, Clock as Clock2, Config as Config3, Console, Crypto, Data as Data2, Duration, Effect as Effect6, FileSystem as FileSystem3, Option as Option4, Path as Path2, PlatformError as PlatformError2, Schedule, Schema as Schema4, Scope } from "effect";
565
573
  import { Hex } from "effect/encoding";
566
574
 
567
575
  // src/platform.ts
@@ -879,16 +887,51 @@ var observeResources = (roots) => Effect4.gen(function* () {
879
887
  };
880
888
  });
881
889
 
890
+ // src/rex.ts
891
+ import { randomUUID } from "node:crypto";
892
+ import { Config as Config2, Effect as Effect5 } from "effect";
893
+ var readRexContext = Effect5.gen(function* () {
894
+ const serverAddress = yield* Config2.String("REX_SERVER").pipe(Config2.withDefault(""));
895
+ const sessionId = yield* Config2.String("REX_SESSION").pipe(Config2.withDefault(""));
896
+ const blockId = yield* Config2.String("REX_BLOCK").pipe(Config2.withDefault(""));
897
+ return serverAddress || sessionId || blockId ? {
898
+ ...serverAddress ? { serverAddress } : {},
899
+ ...sessionId ? { sessionId } : {},
900
+ ...blockId ? { blockId } : {}
901
+ } : void 0;
902
+ });
903
+ var rexSequence = (id, status) => {
904
+ if (id.length > 128 || id.split("/").length > 8 || !/^[A-Za-z0-9_.+-]{1,32}(?:\/[A-Za-z0-9_.+-]{1,32})*$/u.test(id)) return "";
905
+ return `\x1B]7501;id=${id}:state=${status?.state ?? "clear"}:app=coordinator:title=Q29vcmRpbmF0b3I=${status?.state === "blocked" ? `:kind=${status.kind}` : ""}\x1B\\`;
906
+ };
907
+ var createRexReporter = (write) => {
908
+ const id = `coordinator/${randomUUID().replaceAll("-", "")}`;
909
+ let previous;
910
+ return {
911
+ update: (status, terminal) => {
912
+ if (!terminal) return;
913
+ const sequence = rexSequence(id, status);
914
+ if (previous === sequence || previous === void 0 && status === void 0) return;
915
+ write(sequence);
916
+ previous = sequence;
917
+ },
918
+ close: (terminal) => {
919
+ if (terminal && previous !== void 0 && !/:state=(?:done|error):/u.test(previous)) write(rexSequence(id, void 0));
920
+ previous = void 0;
921
+ }
922
+ };
923
+ };
924
+
882
925
  // src/native.ts
883
926
  var NativeError = class extends Data2.TaggedError("NativeError") {
884
927
  };
885
- var json = (text) => Effect5.try({ try: () => JSON.parse(text), catch: () => new NativeError({ operation: "Invalid native JSON" }) });
928
+ var json = (text) => Effect6.try({ try: () => JSON.parse(text), catch: () => new NativeError({ operation: "Invalid native JSON" }) });
886
929
  var maximumBytes = 65536;
887
930
  var platformFailure = (operation) => () => new NativeError({ operation });
888
931
  var sanitizeFileFinalizers = (operation) => (effect) => effect.pipe(
889
- Effect5.catchCause((cause) => Effect5.failCause(Cause2.fromReasons(cause.reasons.map((reason) => reason._tag === "Die" && PlatformError2.isPlatformError(reason.defect) && reason.defect.reason.module === "FileSystem" ? Cause2.makeFailReason(new NativeError({ operation })) : reason))))
932
+ Effect6.catchCause((cause) => Effect6.failCause(Cause2.fromReasons(cause.reasons.map((reason) => reason._tag === "Die" && PlatformError2.isPlatformError(reason.defect) && reason.defect.reason.module === "FileSystem" ? Cause2.makeFailReason(new NativeError({ operation })) : reason))))
890
933
  );
891
- var validateDirectory = (directory, exact = false) => Effect5.gen(function* () {
934
+ var validateDirectory = (directory, exact = false) => Effect6.gen(function* () {
892
935
  const fs = yield* FileSystem3.FileSystem;
893
936
  const host = yield* Host;
894
937
  const info = yield* fs.stat(directory);
@@ -896,7 +939,7 @@ var validateDirectory = (directory, exact = false) => Effect5.gen(function* () {
896
939
  return yield* new NativeError({ operation: "Unsafe private directory" });
897
940
  }
898
941
  });
899
- var readPrivateText = (file, exact = false) => Effect5.scoped(Effect5.gen(function* () {
942
+ var readPrivateText = (file, exact = false) => Effect6.scoped(Effect6.gen(function* () {
900
943
  const fs = yield* FileSystem3.FileSystem;
901
944
  const path = yield* Path2.Path;
902
945
  const host = yield* Host;
@@ -915,73 +958,73 @@ var readPrivateText = (file, exact = false) => Effect5.scoped(Effect5.gen(functi
915
958
  if (length > maximumBytes) return yield* new NativeError({ operation: "Private file exceeds bound" });
916
959
  return new TextDecoder().decode(bytes.subarray(0, length));
917
960
  })).pipe(sanitizeFileFinalizers("Read private file"));
918
- var readPrivateFile = (file) => readPrivateText(file).pipe(Effect5.mapError(platformFailure("Read private file")));
919
- var loadConnectorConfig = (file) => Effect5.gen(function* () {
961
+ var readPrivateFile = (file) => readPrivateText(file).pipe(Effect6.mapError(platformFailure("Read private file")));
962
+ var loadConnectorConfig = (file) => Effect6.gen(function* () {
920
963
  const fs = yield* FileSystem3.FileSystem;
921
964
  const path = yield* Path2.Path;
922
965
  const host = yield* Host;
923
- const selected = yield* Config2.String("COORDINATOR_CONNECTOR_CONFIG").pipe(Config2.option);
924
- const xdg = yield* Config2.String("XDG_CONFIG_HOME").pipe(Config2.option);
966
+ const selected = yield* Config3.String("COORDINATOR_CONNECTOR_CONFIG").pipe(Config3.option);
967
+ const xdg = yield* Config3.String("XDG_CONFIG_HOME").pipe(Config3.option);
925
968
  const explicit = file ?? Option4.getOrUndefined(selected);
926
969
  const target = explicit ?? path.join(Option4.getOrUndefined(xdg) ?? path.join(host.homeDirectory, ".config"), "coordinator", "inspector.json");
927
970
  const exists = explicit !== void 0 || (yield* fs.readDirectory(path.dirname(target)).pipe(
928
- Effect5.map((entries) => entries.includes(path.basename(target))),
929
- Effect5.catch((error) => error.reason._tag === "NotFound" ? Effect5.succeed(false) : Effect5.fail(error)),
930
- Effect5.mapError(platformFailure("Locate connector configuration"))
971
+ Effect6.map((entries) => entries.includes(path.basename(target))),
972
+ Effect6.catch((error) => error.reason._tag === "NotFound" ? Effect6.succeed(false) : Effect6.fail(error)),
973
+ Effect6.mapError(platformFailure("Locate connector configuration"))
931
974
  ));
932
975
  const fields = yield* Schema4.decodeUnknownEffect(Schema4.Struct({
933
976
  inspectorUrl: Schema4.optional(Schema4.Unknown),
934
977
  apiToken: Schema4.optional(Schema4.Unknown),
935
978
  tokenFile: Schema4.optional(Schema4.Unknown)
936
979
  }), { onExcessProperty: "error" })(exists ? yield* json(yield* readPrivateFile(target)) : {}).pipe(
937
- Effect5.mapError(platformFailure("Invalid connector configuration"))
980
+ Effect6.mapError(platformFailure("Invalid connector configuration"))
938
981
  );
939
- const url = yield* Config2.String("COORDINATOR_API_URL").pipe(Config2.option);
940
- const token = yield* Config2.String("COORDINATOR_API_TOKEN").pipe(Config2.option);
982
+ const url = yield* Config3.String("COORDINATOR_API_URL").pipe(Config3.option);
983
+ const token = yield* Config3.String("COORDINATOR_API_TOKEN").pipe(Config3.option);
941
984
  const inspectorUrl = Option4.getOrUndefined(url) ?? fields.inspectorUrl;
942
985
  if (!exists && inspectorUrl === void 0) return void 0;
943
986
  return yield* Schema4.decodeUnknownEffect(ConnectorConfig, { onExcessProperty: "error" })({
944
987
  ...fields,
945
988
  ...inspectorUrl === void 0 ? {} : { inspectorUrl },
946
989
  ...Option4.isNone(token) ? {} : { apiToken: token.value }
947
- }).pipe(Effect5.mapError(platformFailure("Invalid connector configuration")));
990
+ }).pipe(Effect6.mapError(platformFailure("Invalid connector configuration")));
948
991
  });
949
- var readConnectorToken = (config) => Effect5.gen(function* () {
992
+ var readConnectorToken = (config) => Effect6.gen(function* () {
950
993
  if (config.apiToken !== void 0) return config.apiToken;
951
994
  if (config.tokenFile === void 0) return yield* new NativeError({ operation: "Connector credential required" });
952
995
  const token = (yield* readPrivateFile(config.tokenFile)).trim();
953
996
  if (token.length < 16 || token.length > 4096 || !/^[A-Za-z0-9._~+\/-]+=*$/.test(token)) return yield* new NativeError({ operation: "Invalid connector credential" });
954
997
  return token;
955
998
  });
956
- var resolveNativeConfig = (config) => Effect5.gen(function* () {
999
+ var resolveNativeConfig = (config) => Effect6.gen(function* () {
957
1000
  const fs = yield* FileSystem3.FileSystem;
958
1001
  const path = yield* Path2.Path;
959
1002
  const crypto = yield* Crypto.Crypto;
960
1003
  const host = yield* Host;
961
- const xdg = yield* Config2.String("XDG_STATE_HOME").pipe(Config2.option);
1004
+ const xdg = yield* Config3.String("XDG_STATE_HOME").pipe(Config3.option);
962
1005
  const base = Option4.getOrUndefined(xdg) ?? (host.platform === "darwin" ? path.join(host.homeDirectory, "Library", "Application Support") : path.join(host.homeDirectory, ".local", "state"));
963
1006
  if (!path.isAbsolute(base) || path.resolve(base) !== base || /[\0\r\n]/.test(base)) return yield* new NativeError({ operation: "Unsafe state path" });
964
1007
  const stateDirectory = path.join(base, "coordinator");
965
1008
  yield* fs.makeDirectory(stateDirectory, { recursive: true, mode: 448 });
966
1009
  yield* validateDirectory(stateDirectory, true);
967
1010
  const target = path.join(stateDirectory, "machine-id");
968
- const readIdentity = readPrivateText(target, true).pipe(Effect5.flatMap((text) => /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}\n$/.test(text) ? Effect5.succeed(text.slice(0, -1)) : Effect5.fail(new NativeError({ operation: "Invalid installation identity" }))));
969
- const existing = yield* readIdentity.pipe(Effect5.map(Option4.some), Effect5.catch((error) => error._tag === "PlatformError" && error.reason._tag === "NotFound" ? Effect5.succeed(Option4.none()) : Effect5.fail(error)));
1011
+ const readIdentity = readPrivateText(target, true).pipe(Effect6.flatMap((text) => /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}\n$/.test(text) ? Effect6.succeed(text.slice(0, -1)) : Effect6.fail(new NativeError({ operation: "Invalid installation identity" }))));
1012
+ const existing = yield* readIdentity.pipe(Effect6.map(Option4.some), Effect6.catch((error) => error._tag === "PlatformError" && error.reason._tag === "NotFound" ? Effect6.succeed(Option4.none()) : Effect6.fail(error)));
970
1013
  if (Option4.isSome(existing)) return { ...config, machineId: existing.value, stateDirectory };
971
1014
  const temporary = path.join(stateDirectory, `.machine-id-${yield* crypto.randomUUIDv4()}`);
972
1015
  const machineId = yield* crypto.randomUUIDv4();
973
- return yield* Effect5.scoped(Effect5.gen(function* () {
974
- const cleanupScope = yield* Effect5.scope;
975
- yield* Effect5.scoped(Effect5.uninterruptibleMask((restore) => Effect5.gen(function* () {
1016
+ return yield* Effect6.scoped(Effect6.gen(function* () {
1017
+ const cleanupScope = yield* Effect6.scope;
1018
+ yield* Effect6.scoped(Effect6.uninterruptibleMask((restore) => Effect6.gen(function* () {
976
1019
  const handle = yield* fs.open(temporary, { flag: "wx", mode: 384 });
977
- yield* Scope.addFinalizer(cleanupScope, fs.remove(temporary).pipe(Effect5.orDie));
1020
+ yield* Scope.addFinalizer(cleanupScope, fs.remove(temporary).pipe(Effect6.orDie));
978
1021
  yield* restore(handle.writeAll(new TextEncoder().encode(machineId + "\n")));
979
1022
  })));
980
- yield* fs.link(temporary, target).pipe(Effect5.catch((error) => error.reason._tag === "AlreadyExists" ? Effect5.void : Effect5.fail(error)));
1023
+ yield* fs.link(temporary, target).pipe(Effect6.catch((error) => error.reason._tag === "AlreadyExists" ? Effect6.void : Effect6.fail(error)));
981
1024
  return yield* readIdentity;
982
- })).pipe(Effect5.map((identity) => ({ ...config, machineId: identity, stateDirectory })));
983
- }).pipe(sanitizeFileFinalizers("Resolve private native installation identity"), Effect5.mapError(platformFailure("Resolve private native installation identity")));
984
- var withProcessResources = (inspection) => Effect5.gen(function* () {
1025
+ })).pipe(Effect6.map((identity) => ({ ...config, machineId: identity, stateDirectory })));
1026
+ }).pipe(sanitizeFileFinalizers("Resolve private native installation identity"), Effect6.mapError(platformFailure("Resolve private native installation identity")));
1027
+ var withProcessResources = (inspection) => Effect6.gen(function* () {
985
1028
  const roots = inspection.presence.processes ?? [];
986
1029
  if (roots.length === 0) {
987
1030
  if (new TextEncoder().encode(JSON.stringify(inspection)).length > INSPECTION_LIMIT - 2 * 1024) return yield* new NativeError({ operation: "Native inspection exceeds connector budget" });
@@ -1038,14 +1081,14 @@ var withProcessResources = (inspection) => Effect5.gen(function* () {
1038
1081
  if (new TextEncoder().encode(JSON.stringify(result)).length > INSPECTION_LIMIT - 2 * 1024) return yield* new NativeError({ operation: "Native inspection exceeds connector budget" });
1039
1082
  return result;
1040
1083
  });
1041
- var connectNative = (config, callbacks) => Effect5.gen(function* () {
1084
+ var connectNative = (config, callbacks) => Effect6.gen(function* () {
1042
1085
  const mcp = yield* Mcp;
1043
1086
  const clock = yield* Clock2.Clock;
1044
1087
  const now = () => Number(clock.monotonicTimeNanosUnsafe() / 1000000n);
1045
1088
  let token = yield* readConnectorToken(config);
1046
1089
  const crypto = yield* Crypto.Crypto;
1047
1090
  const identity = { connectorId: yield* crypto.randomUUIDv4(), incarnation: yield* crypto.randomUUIDv4() };
1048
- const report = callbacks.report ?? ((notice) => Effect5.logDebug({ message: "Native inspector", state: notice.state, operation: notice.operation, code: notice.code, status: notice.status }));
1091
+ const report = callbacks.report ?? ((notice) => Effect6.logDebug({ message: "Native inspector", state: notice.state, operation: notice.operation, code: notice.code, status: notice.status }));
1049
1092
  let isUnavailable = false;
1050
1093
  let hasReportedHealthy = false;
1051
1094
  let failedAt;
@@ -1056,14 +1099,14 @@ var connectNative = (config, callbacks) => Effect5.gen(function* () {
1056
1099
  code: error._tag,
1057
1100
  status: error.status
1058
1101
  });
1059
- const unavailable2 = (operation, error) => Effect5.gen(function* () {
1102
+ const unavailable2 = (operation, error) => Effect6.gen(function* () {
1060
1103
  healthyAt = void 0;
1061
1104
  failedAt ??= now();
1062
1105
  if (isUnavailable || now() - failedAt < 5e3) return;
1063
1106
  isUnavailable = true;
1064
1107
  yield* report(failure("unavailable", operation, error));
1065
1108
  });
1066
- const recovered = Effect5.gen(function* () {
1109
+ const recovered = Effect6.gen(function* () {
1067
1110
  healthyAt ??= now();
1068
1111
  if (now() - healthyAt < 1e4) return false;
1069
1112
  const resetBackoff = failedAt !== void 0;
@@ -1075,37 +1118,37 @@ var connectNative = (config, callbacks) => Effect5.gen(function* () {
1075
1118
  if (shouldReport) yield* report({ state: "recovered", operation: "connection" });
1076
1119
  return resetBackoff;
1077
1120
  });
1078
- const post = (route, input, output, value) => mcp.post(config.inspectorUrl, token, route, input, output, value).pipe(Effect5.tapError((error) => unavailable2(route, error)));
1079
- yield* Effect5.addFinalizer(() => mcp.post(config.inspectorUrl, token, "/connector/disconnect", ConnectorDisconnect, ConnectorAccepted, identity, 500).pipe(
1080
- Effect5.catch((error) => Effect5.logDebug({ operation: "/connector/disconnect", code: error._tag, status: error._tag === "HttpError" ? error.status : void 0 }))
1121
+ const post = (route, input, output, value) => mcp.post(config.inspectorUrl, token, route, input, output, value).pipe(Effect6.tapError((error) => unavailable2(route, error)));
1122
+ yield* Effect6.addFinalizer(() => mcp.post(config.inspectorUrl, token, "/connector/disconnect", ConnectorDisconnect, ConnectorAccepted, identity, 500).pipe(
1123
+ Effect6.catch((error) => Effect6.logDebug({ operation: "/connector/disconnect", code: error._tag, status: error._tag === "HttpError" ? error.status : void 0 }))
1081
1124
  ));
1082
- const announce = Effect5.gen(function* () {
1125
+ const announce = Effect6.gen(function* () {
1083
1126
  const sessions = yield* callbacks.presence();
1084
1127
  if (sessions.length > 256) return yield* new NativeError({ operation: "Native presence exceeds bound" });
1085
1128
  yield* post("/connector/announce", ConnectorAnnounce, ConnectorAccepted, { ...identity, machineId: config.machineId, sessions });
1086
- }).pipe(Effect5.tapError((error) => unavailable2("native presence", error)));
1087
- const observationFailure = (error) => Effect5.logDebug({ operation: "native detail", code: error._tag, status: error.status }).pipe(
1088
- Effect5.as({
1129
+ }).pipe(Effect6.tapError((error) => unavailable2("native presence", error)));
1130
+ const observationFailure = (error) => Effect6.logDebug({ operation: "native detail", code: error._tag, status: error.status }).pipe(
1131
+ Effect6.as({
1089
1132
  code: error._tag === "TimeoutError" ? "timeout" : error._tag === "HttpError" && error.status === 404 ? "not-found" : "unavailable",
1090
1133
  message: "Native session observation unavailable"
1091
1134
  })
1092
1135
  );
1093
- const cycle = Effect5.gen(function* () {
1136
+ const cycle = Effect6.gen(function* () {
1094
1137
  const pending = yield* post("/connector/poll", ConnectorPoll, ConnectorPollResult, identity);
1095
1138
  for (const request of pending.requests) {
1096
- const observation = yield* callbacks.inspect(request.ref).pipe(Effect5.timeout("8 seconds"), Effect5.catch(observationFailure));
1139
+ const observation = yield* callbacks.inspect(request.ref).pipe(Effect6.timeout("8 seconds"), Effect6.catch(observationFailure));
1097
1140
  yield* post("/connector/result", ConnectorResult, ConnectorAccepted, { ...identity, requestId: request.requestId, observation });
1098
1141
  }
1099
1142
  if (yield* recovered) return true;
1100
- yield* Effect5.sleep("1 second");
1143
+ yield* Effect6.sleep("1 second");
1101
1144
  return false;
1102
1145
  });
1103
- const retryable = (error) => Effect5.gen(function* () {
1146
+ const retryable = (error) => Effect6.gen(function* () {
1104
1147
  if (error._tag === "TimeoutError") return true;
1105
1148
  if (error._tag !== "HttpError") return false;
1106
1149
  if (error.status === 401 || error.status === 403) {
1107
1150
  const next = yield* readConnectorToken(config).pipe(
1108
- Effect5.catch((failure2) => unavailable2("credential reload", failure2).pipe(Effect5.as(void 0)))
1151
+ Effect6.catch((failure2) => unavailable2("credential reload", failure2).pipe(Effect6.as(void 0)))
1109
1152
  );
1110
1153
  if (next === void 0 || next === token) return false;
1111
1154
  token = next;
@@ -1116,15 +1159,15 @@ var connectNative = (config, callbacks) => Effect5.gen(function* () {
1116
1159
  const maximumDelay = Duration.seconds(30);
1117
1160
  const backoff = Schedule.min([Schedule.exponential("2 seconds"), Schedule.spaced(maximumDelay)]).pipe(
1118
1161
  Schedule.jittered,
1119
- Schedule.modifyDelay(({ duration }) => Effect5.succeed(Duration.min(duration, maximumDelay)))
1162
+ Schedule.modifyDelay(({ duration }) => Effect6.succeed(Duration.min(duration, maximumDelay)))
1120
1163
  );
1121
- return yield* Effect5.forever(Effect5.gen(function* () {
1164
+ return yield* Effect6.forever(Effect6.gen(function* () {
1122
1165
  yield* announce;
1123
- yield* Effect5.raceFirst(
1124
- Effect5.forever(Effect5.sleep("10 seconds").pipe(Effect5.andThen(announce))),
1125
- cycle.pipe(Effect5.repeat({ until: (resetBackoff) => resetBackoff }))
1166
+ yield* Effect6.raceFirst(
1167
+ Effect6.forever(Effect6.sleep("10 seconds").pipe(Effect6.andThen(announce))),
1168
+ cycle.pipe(Effect6.repeat({ until: (resetBackoff) => resetBackoff }))
1126
1169
  );
1127
- }).pipe(Effect5.retry({ while: retryable, schedule: backoff })));
1170
+ }).pipe(Effect6.retry({ while: retryable, schedule: backoff })));
1128
1171
  });
1129
1172
  var Id2 = Schema4.String.check(Schema4.isMinLength(1), Schema4.isMaxLength(256));
1130
1173
  var N = Schema4.Finite.check(Schema4.isGreaterThanOrEqualTo(0));
@@ -1160,11 +1203,13 @@ var provenance = (source, scope, scopeDescription, observedAt, nativeUpdatedAt =
1160
1203
  partial: false,
1161
1204
  stale: false
1162
1205
  });
1163
- var createNativeObserver = (harness, machineId, processes = []) => Effect6.gen(function* () {
1206
+ var createNativeObserver = (harness, machineId, processes = []) => Effect7.gen(function* () {
1164
1207
  const host = yield* Host;
1165
1208
  const clock = yield* Clock3.Clock;
1166
- const rexSession = yield* Config3.String("REX_SESSION").pipe(Config3.withDefault(""));
1167
- const rexBlock = yield* Config3.String("REX_BLOCK").pipe(Config3.withDefault(""));
1209
+ const rex = yield* readRexContext;
1210
+ let programStatus;
1211
+ let userPrompt;
1212
+ let stopReason;
1168
1213
  let current;
1169
1214
  let identity;
1170
1215
  let lastActivityAt;
@@ -1176,6 +1221,9 @@ var createNativeObserver = (harness, machineId, processes = []) => Effect6.gen(f
1176
1221
  const key = JSON.stringify([ctx.sessionManager.getSessionId(), harness === "omp" ? ctx.agent?.id : void 0]);
1177
1222
  if (identity !== key) {
1178
1223
  latest = void 0;
1224
+ programStatus = void 0;
1225
+ userPrompt = void 0;
1226
+ stopReason = void 0;
1179
1227
  activeTools.clear();
1180
1228
  identity = key;
1181
1229
  }
@@ -1184,6 +1232,24 @@ var createNativeObserver = (harness, machineId, processes = []) => Effect6.gen(f
1184
1232
  if (event?.type === "tool_execution_start" && event.toolCallId && event.toolName) activeTools.set(event.toolCallId, event.toolName);
1185
1233
  if (event?.type === "tool_execution_end" && event.toolCallId) activeTools.delete(event.toolCallId);
1186
1234
  if (event?.type === "agent_end") activeTools.clear();
1235
+ if (event?.type === "agent_start") {
1236
+ programStatus = { state: "working" };
1237
+ stopReason = void 0;
1238
+ }
1239
+ if (event?.type === "turn_start") {
1240
+ programStatus = { state: "working" };
1241
+ stopReason = void 0;
1242
+ }
1243
+ if (event?.type === "tool_execution_start") programStatus = { state: "working" };
1244
+ if (event?.message?.role === "assistant" && event.message.stopReason) stopReason = event.message.stopReason;
1245
+ if (event?.type === "agent_end") stopReason = event.messages?.findLast((message2) => message2.role === "assistant")?.stopReason ?? stopReason;
1246
+ if (harness === "pi" && event?.type === "agent_before_settle") stopReason = event.outcome === "completed" ? "stop" : event.outcome;
1247
+ if (event?.type === "agent_settled" || harness !== "pi" && event?.type === "agent_end") {
1248
+ programStatus = event.willContinue ? { state: "working" } : stopReason === "aborted" ? { state: "idle" } : stopReason === "error" ? { state: "error" } : stopReason === "stop" || stopReason === "length" ? { state: "done" } : void 0;
1249
+ } else if (event?.type === "agent_end") programStatus = { state: "working" };
1250
+ if (harness === "pi" && event?.type === "ui_prompt_start") userPrompt = { state: "blocked", kind: "question" };
1251
+ if (harness === "pi" && event?.type === "ui_prompt_end") userPrompt = void 0;
1252
+ if ((event === void 0 || event.type === "session_start" || event.type === "session_switch") && programStatus === void 0) programStatus = { state: ctx.isIdle() ? "idle" : "working" };
1187
1253
  const message = event?.message;
1188
1254
  if (message?.role === "assistant" && message.usage) {
1189
1255
  const u = message.usage;
@@ -1203,6 +1269,7 @@ var createNativeObserver = (harness, machineId, processes = []) => Effect6.gen(f
1203
1269
  const presence = (now = clock.currentTimeMillisUnsafe()) => {
1204
1270
  if (!current) return void 0;
1205
1271
  const currentToolName = activeTools.values().next().value;
1272
+ const status = userPrompt ?? programStatus;
1206
1273
  return {
1207
1274
  ref: { machineId, harness, nativeSessionId: current.sessionManager.getSessionId(), ...harness === "omp" && current.agent?.id ? { nativeAgentId: current.agent.id } : {} },
1208
1275
  observedAt: now,
@@ -1211,13 +1278,15 @@ var createNativeObserver = (harness, machineId, processes = []) => Effect6.gen(f
1211
1278
  processes,
1212
1279
  machineName,
1213
1280
  ...os ? { os } : {},
1214
- ...rexSession || rexBlock ? { rex: { ...rexSession ? { sessionId: rexSession } : {}, ...rexBlock ? { blockId: rexBlock } : {} } } : {},
1281
+ ...rex ? { rex } : {},
1282
+ ...status ? { programStatus: status } : {},
1215
1283
  ...current.model ? { provider: current.model.provider, model: current.model.id } : {},
1216
1284
  ...current.agent ? { kind: current.agent.kind, ...current.agent.name ? { agent: current.agent.name } : {} } : {},
1217
1285
  activity: {
1218
- state: current.isIdle() ? "idle" : "working",
1286
+ state: userPrompt ? "waiting" : current.isIdle() ? "idle" : "working",
1219
1287
  streaming: !current.isIdle(),
1220
- ...currentToolName === void 0 ? {} : { currentToolName, wait: "tool" }
1288
+ ...currentToolName === void 0 ? {} : { currentToolName },
1289
+ ...userPrompt ? { wait: "user" } : currentToolName === void 0 ? {} : { wait: "tool" }
1221
1290
  }
1222
1291
  };
1223
1292
  };
@@ -1321,6 +1390,9 @@ var createNativeObserver = (harness, machineId, processes = []) => Effect6.gen(f
1321
1390
  current = void 0;
1322
1391
  identity = void 0;
1323
1392
  latest = void 0;
1393
+ programStatus = void 0;
1394
+ userPrompt = void 0;
1395
+ stopReason = void 0;
1324
1396
  lastActivityAt = void 0;
1325
1397
  activeTools.clear();
1326
1398
  } };
@@ -1337,11 +1409,28 @@ Use the available session.inspect MCP tool for on-demand details relevant to thi
1337
1409
  </session-inspection>`;
1338
1410
  return harness === "pi" ? { action: "transform", text, ...input.images ? { images: input.images } : {} } : { text, ...input.images ? { images: input.images } : {} };
1339
1411
  };
1340
- var registerCoordinateKeyword = Effect6.gen(function* () {
1341
- const { MAGIC_KEYWORDS } = yield* Effect6.tryPromise({ try: () => import("@oh-my-pi/pi-coding-agent/modes/magic-keywords"), catch: () => new NativeError({ operation: "Load native keyword definitions" }) });
1342
- const { isMagicKeyword, setMagicKeywords } = yield* Effect6.tryPromise({ try: () => import("@oh-my-pi/pi-tui/prompt/magic-keywords"), catch: () => new NativeError({ operation: "Load native keyword highlighter" }) });
1412
+ var registerCoordinateKeyword = Effect7.gen(function* () {
1413
+ const { MAGIC_KEYWORDS } = yield* Effect7.tryPromise({ try: () => import("@oh-my-pi/pi-coding-agent/modes/magic-keywords"), catch: () => new NativeError({ operation: "Load native keyword definitions" }) });
1414
+ const { isMagicKeyword, setMagicKeywords } = yield* Effect7.tryPromise({ try: () => import("@oh-my-pi/pi-tui/prompt/magic-keywords"), catch: () => new NativeError({ operation: "Load native keyword highlighter" }) });
1343
1415
  setMagicKeywords([...MAGIC_KEYWORDS.filter(({ word }) => isMagicKeyword(word)), { word: "coordinate", hue: [190, 260] }]);
1344
1416
  });
1417
+ var formatSessionList = (sessions) => {
1418
+ if (sessions.length === 0) return "No sessions are currently visible to this API key.";
1419
+ const lines = [`Coordinator: ${sessions.length} sessions visible to this API key.`, "", "~~~text"];
1420
+ for (const session of sessions) {
1421
+ lines.push(
1422
+ `Machine: ${JSON.stringify(session.machineName ?? "unavailable")} (${JSON.stringify(session.ref.machineId)})`,
1423
+ `Session: ${session.ref.harness} ${JSON.stringify(session.ref.nativeSessionId)}`
1424
+ );
1425
+ if (session.ref.nativeAgentId !== void 0) lines.push(`Agent ID: ${JSON.stringify(session.ref.nativeAgentId)}`);
1426
+ for (const [field, value] of Object.entries(session)) {
1427
+ if (field !== "ref" && field !== "machineName" && value !== void 0) lines.push(`${field}: ${JSON.stringify(value)}`);
1428
+ }
1429
+ lines.push("");
1430
+ }
1431
+ lines.push("~~~");
1432
+ return lines.join("\n");
1433
+ };
1345
1434
  function plugin(host) {
1346
1435
  const harness = "pi";
1347
1436
  const runtime = ManagedRuntime.make(Layer3.mergeAll(NodeServices.layer, Host.layer, Mcp.layer, Layer3.succeed(Logger.LogToStderr, true)));
@@ -1349,6 +1438,48 @@ function plugin(host) {
1349
1438
  let observer;
1350
1439
  let starting;
1351
1440
  let stopped = false;
1441
+ const rexReporter = harness === "pi" ? createRexReporter((sequence) => process.stdout.write(sequence)) : void 0;
1442
+ let terminal = false;
1443
+ host.registerCommand("coordinator", {
1444
+ description: "List visible sessions, their machines, and native metadata",
1445
+ handler: async (args) => {
1446
+ if (args.trim() !== "list") {
1447
+ host.sendMessage({ customType: "coordinator-list", content: "Usage: /coordinator list", display: true }, { triggerTurn: false });
1448
+ return;
1449
+ }
1450
+ const exit = await runtime.runPromiseExit(Effect7.gen(function* () {
1451
+ const activeConfig = config ?? (yield* loadConnectorConfig());
1452
+ if (!activeConfig) return yield* new NativeError({ operation: "Connector configuration required" });
1453
+ const token = yield* readConnectorToken(activeConfig);
1454
+ const mcp = yield* Mcp;
1455
+ const sessions = [];
1456
+ let cursor;
1457
+ do {
1458
+ const page = yield* mcp.call(activeConfig.inspectorUrl, token, InspectionTools.list, { limit: 100, ...cursor === void 0 ? {} : { cursor } });
1459
+ for (const session of page.sessions) sessions.push(session);
1460
+ cursor = page.nextCursor ?? void 0;
1461
+ } while (cursor !== void 0);
1462
+ return sessions;
1463
+ }));
1464
+ if (stopped) return;
1465
+ if (exit._tag === "Failure") {
1466
+ const error = Cause3.findErrorOption(exit.cause);
1467
+ const reason = error._tag === "Some" ? error.value._tag === "HttpError" ? `HTTP ${error.value.status}` : error.value._tag : "unexpected failure";
1468
+ host.sendMessage({
1469
+ customType: "coordinator-list",
1470
+ content: `Coordinator list unavailable (${reason}). Check inspectorUrl, your access token, and API reachability.`,
1471
+ display: true
1472
+ }, { triggerTurn: false });
1473
+ return;
1474
+ }
1475
+ host.sendMessage({
1476
+ customType: "coordinator-list",
1477
+ content: formatSessionList(exit.value),
1478
+ display: true,
1479
+ details: { sessions: exit.value }
1480
+ }, { triggerTurn: false });
1481
+ }
1482
+ });
1352
1483
  if (harness === "pi") {
1353
1484
  const register = (descriptor) => {
1354
1485
  const document = Schema5.toJsonSchemaDocument(Schema5.toCodecJson(descriptor.input), { onExcessProperty: "error" });
@@ -1357,7 +1488,7 @@ function plugin(host) {
1357
1488
  label: `Coordinator ${descriptor.name}`,
1358
1489
  description: descriptor.description,
1359
1490
  parameters: { ...document.schema, type: "object", $defs: document.definitions },
1360
- execute: (_id, params, signal) => runtime.runPromise(Effect6.gen(function* () {
1491
+ execute: (_id, params, signal) => runtime.runPromise(Effect7.gen(function* () {
1361
1492
  const activeConfig = config ?? (yield* loadConnectorConfig());
1362
1493
  if (!activeConfig) return yield* new NativeError({ operation: "Connector configuration required" });
1363
1494
  const input = yield* Schema5.decodeUnknownEffect(Schema5.toCodecJson(descriptor.input), { onExcessProperty: "error" })(params);
@@ -1372,21 +1503,21 @@ function plugin(host) {
1372
1503
  register(InspectionTools.inspect);
1373
1504
  }
1374
1505
  let lastError;
1375
- const report = (ctx) => (notice) => Effect6.gen(function* () {
1506
+ const report = (ctx) => (notice) => Effect7.gen(function* () {
1376
1507
  if (stopped) return;
1377
1508
  if (notice.state !== "error") {
1378
1509
  if (notice.state === "recovered") lastError = void 0;
1379
- yield* Effect6.logDebug("Native inspector connection", { state: notice.state, operation: notice.operation, code: notice.code, status: notice.status });
1510
+ yield* Effect7.logDebug("Native inspector connection", { state: notice.state, operation: notice.operation, code: notice.code, status: notice.status });
1380
1511
  return;
1381
1512
  }
1382
1513
  if (lastError?.operation === notice.operation && lastError.code === notice.code && lastError.status === notice.status) return;
1383
1514
  lastError = notice;
1384
1515
  const message = `Native inspector unavailable (operation=${notice.operation}${notice.code === void 0 ? "" : `, code=${notice.code}`}${notice.status === void 0 ? "" : `, status=${notice.status}`})`;
1385
- if (ctx.hasUI) yield* Effect6.sync(() => ctx.ui.notify(message, "warning"));
1516
+ if (ctx.hasUI) yield* Effect7.sync(() => ctx.ui.notify(message, "warning"));
1386
1517
  else yield* Console2.error(message);
1387
1518
  });
1388
1519
  const reportCause = (ctx, operation) => (cause) => {
1389
- if (Cause3.hasInterruptsOnly(cause)) return Effect6.void;
1520
+ if (Cause3.hasInterruptsOnly(cause)) return Effect7.void;
1390
1521
  const error = Cause3.findErrorOption(cause);
1391
1522
  return report(ctx)({
1392
1523
  state: "error",
@@ -1395,7 +1526,7 @@ function plugin(host) {
1395
1526
  status: error._tag === "Some" ? error.value.status : void 0
1396
1527
  });
1397
1528
  };
1398
- const start = (ctx) => starting ??= runtime.runPromiseExit(Effect6.gen(function* () {
1529
+ const start = (ctx) => starting ??= runtime.runPromiseExit(Effect7.gen(function* () {
1399
1530
  config = yield* loadConnectorConfig();
1400
1531
  if (!config || stopped) return false;
1401
1532
  const nativeConfig = yield* resolveNativeConfig(config);
@@ -1403,13 +1534,15 @@ function plugin(host) {
1403
1534
  const processIdentity = yield* identifyProcess(host2.pid);
1404
1535
  observer = yield* createNativeObserver(harness, nativeConfig.machineId, processIdentity.status === "available" ? [{ pid: host2.pid, startIdentity: processIdentity.startTime, ownership: harness === "omp" ? "shared" : "owned" }] : []);
1405
1536
  observer.update(ctx);
1537
+ terminal = ctx.mode === "tui" && ctx.hasUI && process.stdout.isTTY === true;
1538
+ rexReporter?.update(observer.presence()?.programStatus, terminal);
1406
1539
  if (harness === "omp" && ctx.hasUI && ctx.mode === "tui") yield* registerCoordinateKeyword;
1407
1540
  runtime.runFork(connectNative(nativeConfig, {
1408
- presence: () => Effect6.sync(() => {
1541
+ presence: () => Effect7.sync(() => {
1409
1542
  const p = observer?.presence();
1410
1543
  return p ? [p] : [];
1411
1544
  }),
1412
- inspect: (ref) => Effect6.gen(function* () {
1545
+ inspect: (ref) => Effect7.gen(function* () {
1413
1546
  const observation = observer?.inspect();
1414
1547
  if (!observation || sessionRefKey(ref) !== sessionRefKey(observation.presence.ref)) {
1415
1548
  return yield* new HttpError({ status: 404, message: "Native session unavailable" });
@@ -1418,14 +1551,14 @@ function plugin(host) {
1418
1551
  }),
1419
1552
  report: report(ctx)
1420
1553
  }).pipe(
1421
- Effect6.scoped,
1422
- Effect6.tapCause(reportCause(ctx, "connector")),
1423
- Effect6.onExit(() => Effect6.sync(() => {
1554
+ Effect7.scoped,
1555
+ Effect7.tapCause(reportCause(ctx, "connector")),
1556
+ Effect7.onExit(() => Effect7.sync(() => {
1424
1557
  starting = void 0;
1425
1558
  }))
1426
1559
  ));
1427
1560
  return true;
1428
- }).pipe(Effect6.tapCause(reportCause(ctx, "startup")))).then((exit) => {
1561
+ }).pipe(Effect7.tapCause(reportCause(ctx, "startup")))).then((exit) => {
1429
1562
  if (exit._tag === "Failure" || !exit.value) {
1430
1563
  starting = void 0;
1431
1564
  observer?.clear();
@@ -1434,16 +1567,23 @@ function plugin(host) {
1434
1567
  }
1435
1568
  });
1436
1569
  const events = ["session_start", "session_switch", "agent_start", "agent_end", "turn_start", "turn_end", "message_end", "tool_execution_start", "tool_execution_end"];
1570
+ if (harness === "pi") events.push("agent_before_settle", "agent_settled", "ui_prompt_start", "ui_prompt_end");
1437
1571
  for (const name of events) {
1438
1572
  host.on(name, async (event, ctx) => {
1439
1573
  if (stopped) return;
1440
1574
  await start(ctx);
1441
1575
  if (stopped || !observer) return;
1442
- await runtime.runPromiseExit(Effect6.sync(() => observer?.update(ctx, event)).pipe(Effect6.tapCause(reportCause(ctx, "native update"))));
1576
+ await runtime.runPromiseExit(Effect7.sync(() => {
1577
+ if (name === "session_switch") rexReporter?.update(void 0, terminal);
1578
+ observer?.update(ctx, event);
1579
+ terminal = ctx.mode === "tui" && ctx.hasUI && process.stdout.isTTY === true;
1580
+ rexReporter?.update(observer?.presence()?.programStatus, terminal);
1581
+ }).pipe(Effect7.tapCause(reportCause(ctx, "native update"))));
1443
1582
  });
1444
1583
  }
1445
1584
  host.on("session_shutdown", async () => {
1446
1585
  stopped = true;
1586
+ rexReporter?.close(terminal);
1447
1587
  try {
1448
1588
  await runtime.dispose();
1449
1589
  } finally {
@@ -1454,14 +1594,14 @@ function plugin(host) {
1454
1594
  });
1455
1595
  host.on("input", async (event, ctx) => {
1456
1596
  if (harness !== "omp" || !isCoordinateInput(event.text) || stopped) return;
1457
- const exit = await runtime.runPromiseExit(Effect6.gen(function* () {
1597
+ const exit = await runtime.runPromiseExit(Effect7.gen(function* () {
1458
1598
  const activeConfig = config ?? (yield* loadConnectorConfig());
1459
1599
  if (!activeConfig) return;
1460
1600
  const token = yield* readConnectorToken(activeConfig);
1461
1601
  const mcp = yield* Mcp;
1462
1602
  const snapshot = yield* mcp.call(activeConfig.inspectorUrl, token, InspectionTools.list, { limit: 100 });
1463
1603
  return transformCoordinateInput(harness, event, snapshot);
1464
- }).pipe(Effect6.tapCause(reportCause(ctx, "coordinate"))));
1604
+ }).pipe(Effect7.tapCause(reportCause(ctx, "coordinate"))));
1465
1605
  return exit._tag === "Success" ? exit.value : void 0;
1466
1606
  });
1467
1607
  }