@samuelho-dev/coordinator 0.3.0-beta.1 → 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.
- package/README.md +51 -47
- package/THIRD_PARTY_NOTICES.md +4 -2
- package/dist/plugins/omp.js +146 -7
- package/dist/plugins/pi.js +217 -77
- package/dist/scripts/install.js +9 -5
- package/dist/src/inspection.js +7 -1
- package/dist/src/main.js +4 -1
- package/dist/src/native.js +10 -3
- package/dist/src/plugin.js +116 -8
- package/dist/src/rex.js +36 -0
- package/dist/src/setup.js +134 -0
- package/package.json +2 -1
package/dist/plugins/pi.js
CHANGED
|
@@ -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,
|
|
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({
|
|
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
|
|
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) =>
|
|
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
|
-
|
|
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) =>
|
|
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) =>
|
|
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(
|
|
919
|
-
var loadConnectorConfig = (file) =>
|
|
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*
|
|
924
|
-
const xdg = yield*
|
|
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
|
-
|
|
929
|
-
|
|
930
|
-
|
|
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
|
-
|
|
980
|
+
Effect6.mapError(platformFailure("Invalid connector configuration"))
|
|
938
981
|
);
|
|
939
|
-
const url = yield*
|
|
940
|
-
const token = yield*
|
|
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(
|
|
990
|
+
}).pipe(Effect6.mapError(platformFailure("Invalid connector configuration")));
|
|
948
991
|
});
|
|
949
|
-
var readConnectorToken = (config) =>
|
|
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) =>
|
|
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*
|
|
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(
|
|
969
|
-
const existing = yield* readIdentity.pipe(
|
|
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*
|
|
974
|
-
const cleanupScope = yield*
|
|
975
|
-
yield*
|
|
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(
|
|
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(
|
|
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(
|
|
983
|
-
}).pipe(sanitizeFileFinalizers("Resolve private native installation identity"),
|
|
984
|
-
var withProcessResources = (inspection) =>
|
|
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) =>
|
|
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) =>
|
|
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) =>
|
|
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 =
|
|
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(
|
|
1079
|
-
yield*
|
|
1080
|
-
|
|
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 =
|
|
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(
|
|
1087
|
-
const observationFailure = (error) =>
|
|
1088
|
-
|
|
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 =
|
|
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(
|
|
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*
|
|
1143
|
+
yield* Effect6.sleep("1 second");
|
|
1101
1144
|
return false;
|
|
1102
1145
|
});
|
|
1103
|
-
const retryable = (error) =>
|
|
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
|
-
|
|
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 }) =>
|
|
1162
|
+
Schedule.modifyDelay(({ duration }) => Effect6.succeed(Duration.min(duration, maximumDelay)))
|
|
1120
1163
|
);
|
|
1121
|
-
return yield*
|
|
1164
|
+
return yield* Effect6.forever(Effect6.gen(function* () {
|
|
1122
1165
|
yield* announce;
|
|
1123
|
-
yield*
|
|
1124
|
-
|
|
1125
|
-
cycle.pipe(
|
|
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(
|
|
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 = []) =>
|
|
1206
|
+
var createNativeObserver = (harness, machineId, processes = []) => Effect7.gen(function* () {
|
|
1164
1207
|
const host = yield* Host;
|
|
1165
1208
|
const clock = yield* Clock3.Clock;
|
|
1166
|
-
const
|
|
1167
|
-
|
|
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
|
-
...
|
|
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
|
|
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 =
|
|
1341
|
-
const { MAGIC_KEYWORDS } = yield*
|
|
1342
|
-
const { isMagicKeyword, setMagicKeywords } = yield*
|
|
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(
|
|
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) =>
|
|
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*
|
|
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*
|
|
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
|
|
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(
|
|
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: () =>
|
|
1541
|
+
presence: () => Effect7.sync(() => {
|
|
1409
1542
|
const p = observer?.presence();
|
|
1410
1543
|
return p ? [p] : [];
|
|
1411
1544
|
}),
|
|
1412
|
-
inspect: (ref) =>
|
|
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
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
1604
|
+
}).pipe(Effect7.tapCause(reportCause(ctx, "coordinate"))));
|
|
1465
1605
|
return exit._tag === "Success" ? exit.value : void 0;
|
|
1466
1606
|
});
|
|
1467
1607
|
}
|