@knpkv/herdr-coordinator 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 knpkv
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,9 @@
1
+ # `@knpkv/herdr-coordinator`
2
+
3
+ Persistent chat contracts over the Herdr fleet job protocol.
4
+
5
+ Ask mode submits an `agent.delegate` consult job. Work mode submits a locally authorized work job and therefore follows the fleet approval policy. Chat turns persist beside their job IDs and history derives current state from the owning host's durable job record. The browser-facing history contains the newest 32 turns in chronological order; older turns remain durable and addressable by job ID.
6
+
7
+ Coordinator output is newline-delimited `herdr.coordinator.child.v1` lifecycle events. Both events carry the exact fleet job ID and request ID. A `started` event with the exact sanitized worker identity and reported relationship must arrive before the matching `completed` event. Missing, malformed, duplicate, reordered, job-mismatched, or request-mismatched events fail with named lifecycle errors. Terminal transcripts are never accepted as chat replies. Chat history exposes the same persisted worker and canonical Connect target after restart.
8
+
9
+ Browser-safe schemas are exported from `@knpkv/herdr-coordinator/model`.
package/dist/chat.d.ts ADDED
@@ -0,0 +1,137 @@
1
+ import type { FleetService, HostConfiguration } from "@knpkv/herdr-fleet";
2
+ import { Crypto, Effect } from "effect";
3
+ import { ChatHistoryError } from "./errors.js";
4
+ import { type ChatRequest } from "./model.js";
5
+ import type { ChatStoreService } from "./store.js";
6
+ export interface CoordinatorChatOptions {
7
+ readonly config: HostConfiguration;
8
+ readonly fleet: FleetService;
9
+ readonly now?: Effect.Effect<number>;
10
+ readonly nextId?: Effect.Effect<string>;
11
+ readonly store: ChatStoreService;
12
+ }
13
+ export declare const makeCoordinatorChat: (options: CoordinatorChatOptions) => Effect.Effect<{
14
+ history: () => Effect.Effect<{
15
+ entries: ({
16
+ createdAt: number;
17
+ id: string;
18
+ message: string;
19
+ mode: "ask" | "work";
20
+ reply: string | null;
21
+ state: "completed" | "failed" | "interrupted" | "pending" | "running";
22
+ updatedAt: number;
23
+ } | {
24
+ createdAt: number;
25
+ id: string;
26
+ message: string;
27
+ mode: "ask" | "work";
28
+ reply: string | null;
29
+ state: "completed" | "failed" | "interrupted" | "pending" | "running";
30
+ updatedAt: number;
31
+ worker: {
32
+ readonly host: string;
33
+ readonly agentId: string;
34
+ readonly name: string;
35
+ readonly paneId: string;
36
+ readonly relationship?: {
37
+ readonly parentAgentId: string;
38
+ readonly relation: "delegated" | "pair" | "review";
39
+ };
40
+ };
41
+ connectTarget: {
42
+ readonly host: string;
43
+ readonly agentId: string;
44
+ readonly url: string;
45
+ };
46
+ })[];
47
+ }, ChatHistoryError | import("@knpkv/herdr-fleet").FleetJobNotFoundError | import("@knpkv/herdr-fleet").FleetStoreError, never>;
48
+ run: (jobId: string) => Effect.Effect<{
49
+ readonly id: string;
50
+ readonly createdAt: number;
51
+ readonly updatedAt: number;
52
+ readonly actor: string;
53
+ readonly hash: string;
54
+ readonly approvalNonce: string | null;
55
+ readonly approvalExpiresAt?: number | null;
56
+ readonly approvedBy: string | null;
57
+ readonly approvedAt?: number | null;
58
+ readonly rejectedBy?: string | null;
59
+ readonly rejectedAt?: number | null;
60
+ readonly expiredAt?: number | null;
61
+ readonly status: "expired" | "failed" | "interrupted" | "pending_approval" | "queued" | "rejected" | "running" | "succeeded";
62
+ readonly payload: {
63
+ readonly kind: "nix.check";
64
+ } | {
65
+ readonly kind: "nix.apply";
66
+ readonly ref: string;
67
+ } | {
68
+ readonly kind: "agent.delegate";
69
+ readonly repository: string;
70
+ readonly prompt: string;
71
+ readonly mode: "consult" | "review" | "work";
72
+ readonly channel?: "coordinator_chat";
73
+ } | {
74
+ readonly kind: "agent.message";
75
+ readonly session: string;
76
+ readonly message: string;
77
+ } | {
78
+ readonly kind: "browser.mcp.recover";
79
+ };
80
+ readonly result: string | null;
81
+ readonly error: string | null;
82
+ readonly worker?: {
83
+ readonly host: string;
84
+ readonly agentId: string;
85
+ readonly name: string;
86
+ readonly paneId: string;
87
+ readonly relationship?: {
88
+ readonly parentAgentId: string;
89
+ readonly relation: "delegated" | "pair" | "review";
90
+ };
91
+ };
92
+ readonly connectTarget?: {
93
+ readonly host: string;
94
+ readonly agentId: string;
95
+ readonly url: string;
96
+ };
97
+ readonly workerTerminalObservedAt?: number | null;
98
+ }, ChatHistoryError | import("@knpkv/herdr-fleet").FleetApprovalError | import("@knpkv/herdr-fleet").FleetJobNotFoundError | import("@knpkv/herdr-fleet").FleetStoreError | import("@knpkv/herdr-fleet").FleetTransitionConflictError | import("@knpkv/herdr-fleet").FleetValidationError, never>;
99
+ submit: (request: ChatRequest, actor: string) => Effect.Effect<{
100
+ entry: {
101
+ createdAt: number;
102
+ id: string;
103
+ message: string;
104
+ mode: "ask" | "work";
105
+ reply: string | null;
106
+ state: "completed" | "failed" | "interrupted" | "pending" | "running";
107
+ updatedAt: number;
108
+ } | {
109
+ createdAt: number;
110
+ id: string;
111
+ message: string;
112
+ mode: "ask" | "work";
113
+ reply: string | null;
114
+ state: "completed" | "failed" | "interrupted" | "pending" | "running";
115
+ updatedAt: number;
116
+ worker: {
117
+ readonly host: string;
118
+ readonly agentId: string;
119
+ readonly name: string;
120
+ readonly paneId: string;
121
+ readonly relationship?: {
122
+ readonly parentAgentId: string;
123
+ readonly relation: "delegated" | "pair" | "review";
124
+ };
125
+ };
126
+ connectTarget: {
127
+ readonly host: string;
128
+ readonly agentId: string;
129
+ readonly url: string;
130
+ };
131
+ };
132
+ jobId: string;
133
+ queued: boolean;
134
+ }, ChatHistoryError | import("@knpkv/herdr-fleet").FleetJobConflictError | import("@knpkv/herdr-fleet").FleetOperationError | import("@knpkv/herdr-fleet").FleetStoreError | import("@knpkv/herdr-fleet").FleetValidationError, never>;
135
+ }, never, Crypto.Crypto>;
136
+ export type CoordinatorChat = Effect.Success<ReturnType<typeof makeCoordinatorChat>>;
137
+ //# sourceMappingURL=chat.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chat.d.ts","sourceRoot":"","sources":["../src/chat.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,iBAAiB,EAAa,MAAM,oBAAoB,CAAA;AACpF,OAAO,EAAS,MAAM,EAAE,MAAM,EAAkB,MAAM,QAAQ,CAAA;AAC9D,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA;AAC9C,OAAO,EAAoC,KAAK,WAAW,EAAyC,MAAM,YAAY,CAAA;AACtH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAElD,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,MAAM,EAAE,iBAAiB,CAAA;IAClC,QAAQ,CAAC,KAAK,EAAE,YAAY,CAAA;IAC5B,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;IACpC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;IACvC,QAAQ,CAAC,KAAK,EAAE,gBAAgB,CAAA;CACjC;AAyDD,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;wBA0F9B,CAAA;AAEF,MAAM,MAAM,eAAe,GAAG,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,OAAO,mBAAmB,CAAC,CAAC,CAAA"}
package/dist/chat.js ADDED
@@ -0,0 +1,117 @@
1
+ import { Clock, Crypto, Effect, Result, Schema } from "effect";
2
+ import { ChatHistoryError } from "./errors.js";
3
+ import { CoordinatorReply } from "./model.js";
4
+ const historyError = (operation) => (cause) => new ChatHistoryError({ cause, detail: String(cause), operation });
5
+ const stateOf = (record) => {
6
+ switch (record.status) {
7
+ case "pending_approval":
8
+ case "queued":
9
+ return "pending";
10
+ case "running":
11
+ return "running";
12
+ case "succeeded":
13
+ return "completed";
14
+ case "expired":
15
+ case "failed":
16
+ case "rejected":
17
+ return "failed";
18
+ case "interrupted":
19
+ return "interrupted";
20
+ }
21
+ };
22
+ const entryFor = Effect.fn("CoordinatorChat.entryFor")(function* (turn, record) {
23
+ const state = stateOf(record);
24
+ const reply = state === "completed"
25
+ ? yield* Schema.decodeUnknownEffect(CoordinatorReply)(record.result).pipe(Effect.mapError(historyError("chat.reply.decode")))
26
+ : null;
27
+ const entry = {
28
+ createdAt: turn.createdAt,
29
+ id: turn.id,
30
+ message: turn.message,
31
+ mode: turn.mode,
32
+ reply,
33
+ state,
34
+ updatedAt: record.updatedAt
35
+ };
36
+ if (record.worker === undefined)
37
+ return entry;
38
+ if (record.connectTarget === undefined) {
39
+ return yield* new ChatHistoryError({
40
+ cause: record,
41
+ detail: "started coordinator worker is missing its exact Connect target",
42
+ operation: "chat.worker_target"
43
+ });
44
+ }
45
+ return {
46
+ ...entry,
47
+ worker: record.worker,
48
+ connectTarget: record.connectTarget
49
+ };
50
+ });
51
+ export const makeCoordinatorChat = Effect.fn("CoordinatorChat.make")(function* (options) {
52
+ const cryptoService = yield* Crypto.Crypto;
53
+ const nextId = options.nextId ??
54
+ cryptoService.randomUUIDv4.pipe(Effect.mapError(historyError("chat.id")));
55
+ const now = options.now ?? Clock.currentTimeMillis;
56
+ const submit = Effect.fn("CoordinatorChat.submit")(function* (request, actor) {
57
+ const createdAt = yield* now;
58
+ const id = yield* nextId;
59
+ const record = yield* options.fleet.submit({
60
+ payload: {
61
+ kind: "agent.delegate",
62
+ channel: "coordinator_chat",
63
+ mode: request.mode === "ask" ? "consult" : "work",
64
+ prompt: request.message,
65
+ repository: options.config.repository
66
+ }
67
+ }, actor);
68
+ const persisted = yield* Effect.result(options.store.put({
69
+ createdAt,
70
+ id,
71
+ jobId: record.id,
72
+ message: request.message,
73
+ mode: request.mode
74
+ }));
75
+ if (Result.isFailure(persisted)) {
76
+ const compensated = yield* Effect.result(options.fleet.abortSubmission(record.id, "coordinator chat turn was not persisted"));
77
+ if (Result.isFailure(compensated)) {
78
+ return yield* new ChatHistoryError({
79
+ cause: {
80
+ compensation: compensated.failure,
81
+ persistence: persisted.failure
82
+ },
83
+ detail: "chat persistence failed and the fleet job could not be aborted",
84
+ operation: "chat.submit.compensate"
85
+ });
86
+ }
87
+ return yield* persisted.failure;
88
+ }
89
+ const turn = persisted.success;
90
+ return {
91
+ entry: yield* entryFor(turn, record),
92
+ jobId: record.id,
93
+ queued: record.status === "queued"
94
+ };
95
+ });
96
+ const history = Effect.fn("CoordinatorChat.history")(function* () {
97
+ const turns = yield* options.store.list();
98
+ const entries = yield* Effect.forEach(turns, (turn) => options.fleet.get(turn.jobId).pipe(Effect.flatMap((record) => entryFor(turn, record))));
99
+ return { entries };
100
+ });
101
+ const run = Effect.fn("CoordinatorChat.run")(function* (jobId) {
102
+ const turn = yield* options.store.getByJob(jobId);
103
+ if (turn === undefined) {
104
+ const aborted = yield* Effect.result(options.fleet.abortSubmission(jobId, "coordinator chat turn is missing"));
105
+ return yield* new ChatHistoryError({
106
+ cause: Result.isFailure(aborted) ? aborted.failure : jobId,
107
+ detail: Result.isFailure(aborted)
108
+ ? "coordinator chat turn is missing and its fleet job could not be aborted"
109
+ : "coordinator chat turn is missing; its fleet job was aborted",
110
+ operation: "chat.run.lookup"
111
+ });
112
+ }
113
+ return yield* options.fleet.runCoordinatorChat(jobId);
114
+ });
115
+ return { history, run, submit };
116
+ });
117
+ //# sourceMappingURL=chat.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chat.js","sourceRoot":"","sources":["../src/chat.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAC9D,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA;AAC9C,OAAO,EAAsD,gBAAgB,EAAuB,MAAM,YAAY,CAAA;AAWtH,MAAM,YAAY,GAAG,CAAC,SAAiB,EAAE,EAAE,CAAC,CAAC,KAAc,EAAE,EAAE,CAC7D,IAAI,gBAAgB,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,KAAK,CAAC,EAAE,SAAS,EAAE,CAAC,CAAA;AAEnE,MAAM,OAAO,GAAG,CAAC,MAAiB,EAAsB,EAAE;IACxD,QAAQ,MAAM,CAAC,MAAM,EAAE,CAAC;QACtB,KAAK,kBAAkB,CAAC;QACxB,KAAK,QAAQ;YACX,OAAO,SAAS,CAAA;QAClB,KAAK,SAAS;YACZ,OAAO,SAAS,CAAA;QAClB,KAAK,WAAW;YACd,OAAO,WAAW,CAAA;QACpB,KAAK,SAAS,CAAC;QACf,KAAK,QAAQ,CAAC;QACd,KAAK,UAAU;YACb,OAAO,QAAQ,CAAA;QACjB,KAAK,aAAa;YAChB,OAAO,aAAa,CAAA;IACxB,CAAC;AACH,CAAC,CAAA;AAED,MAAM,QAAQ,GAAG,MAAM,CAAC,EAAE,CAAC,0BAA0B,CAAC,CAAC,QAAQ,CAAC,EAC9D,IAAoB,EACpB,MAAiB;IAEjB,MAAM,KAAK,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;IAC7B,MAAM,KAAK,GAAG,KAAK,KAAK,WAAW;QACjC,CAAC,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,mBAAmB,CAAC,gBAAgB,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CACvE,MAAM,CAAC,QAAQ,CAAC,YAAY,CAAC,mBAAmB,CAAC,CAAC,CACnD;QACD,CAAC,CAAC,IAAI,CAAA;IACR,MAAM,KAAK,GAAG;QACZ,SAAS,EAAE,IAAI,CAAC,SAAS;QACzB,EAAE,EAAE,IAAI,CAAC,EAAE;QACX,OAAO,EAAE,IAAI,CAAC,OAAO;QACrB,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,KAAK;QACL,KAAK;QACL,SAAS,EAAE,MAAM,CAAC,SAAS;KAC5B,CAAA;IACD,IAAI,MAAM,CAAC,MAAM,KAAK,SAAS;QAAE,OAAO,KAAyB,CAAA;IACjE,IAAI,MAAM,CAAC,aAAa,KAAK,SAAS,EAAE,CAAC;QACvC,OAAO,KAAK,CAAC,CAAC,IAAI,gBAAgB,CAAC;YACjC,KAAK,EAAE,MAAM;YACb,MAAM,EAAE,gEAAgE;YACxE,SAAS,EAAE,oBAAoB;SAChC,CAAC,CAAA;IACJ,CAAC;IACD,OAAO;QACL,GAAG,KAAK;QACR,MAAM,EAAE,MAAM,CAAC,MAAM;QACrB,aAAa,EAAE,MAAM,CAAC,aAAa;KAChB,CAAA;AACvB,CAAC,CAAC,CAAA;AAEF,MAAM,CAAC,MAAM,mBAAmB,GAAG,MAAM,CAAC,EAAE,CAAC,sBAAsB,CAAC,CAAC,QAAQ,CAAC,EAC5E,OAA+B;IAE/B,MAAM,aAAa,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,MAAM,CAAA;IAC1C,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM;QAC3B,aAAa,CAAC,YAAY,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,YAAY,CAAC,SAAS,CAAC,CAAC,CAAC,CAAA;IAC3E,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,KAAK,CAAC,iBAAiB,CAAA;IAElD,MAAM,MAAM,GAAG,MAAM,CAAC,EAAE,CAAC,wBAAwB,CAAC,CAAC,QAAQ,CAAC,EAC1D,OAAoB,EACpB,KAAa;QAEb,MAAM,SAAS,GAAG,KAAK,CAAC,CAAC,GAAG,CAAA;QAC5B,MAAM,EAAE,GAAG,KAAK,CAAC,CAAC,MAAM,CAAA;QACxB,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,CACxC;YACE,OAAO,EAAE;gBACP,IAAI,EAAE,gBAAgB;gBACtB,OAAO,EAAE,kBAAkB;gBAC3B,IAAI,EAAE,OAAO,CAAC,IAAI,KAAK,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM;gBACjD,MAAM,EAAE,OAAO,CAAC,OAAO;gBACvB,UAAU,EAAE,OAAO,CAAC,MAAM,CAAC,UAAU;aACtC;SACF,EACD,KAAK,CACN,CAAA;QACD,MAAM,SAAS,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC;YACvD,SAAS;YACT,EAAE;YACF,KAAK,EAAE,MAAM,CAAC,EAAE;YAChB,OAAO,EAAE,OAAO,CAAC,OAAO;YACxB,IAAI,EAAE,OAAO,CAAC,IAAI;SACnB,CAAC,CAAC,CAAA;QACH,IAAI,MAAM,CAAC,SAAS,CAAC,SAAS,CAAC,EAAE,CAAC;YAChC,MAAM,WAAW,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,MAAM,CACtC,OAAO,CAAC,KAAK,CAAC,eAAe,CAC3B,MAAM,CAAC,EAAE,EACT,yCAAyC,CAC1C,CACF,CAAA;YACD,IAAI,MAAM,CAAC,SAAS,CAAC,WAAW,CAAC,EAAE,CAAC;gBAClC,OAAO,KAAK,CAAC,CAAC,IAAI,gBAAgB,CAAC;oBACjC,KAAK,EAAE;wBACL,YAAY,EAAE,WAAW,CAAC,OAAO;wBACjC,WAAW,EAAE,SAAS,CAAC,OAAO;qBAC/B;oBACD,MAAM,EAAE,gEAAgE;oBACxE,SAAS,EAAE,wBAAwB;iBACpC,CAAC,CAAA;YACJ,CAAC;YACD,OAAO,KAAK,CAAC,CAAC,SAAS,CAAC,OAAO,CAAA;QACjC,CAAC;QACD,MAAM,IAAI,GAAG,SAAS,CAAC,OAAO,CAAA;QAC9B,OAAO;YACL,KAAK,EAAE,KAAK,CAAC,CAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;YACpC,KAAK,EAAE,MAAM,CAAC,EAAE;YAChB,MAAM,EAAE,MAAM,CAAC,MAAM,KAAK,QAAQ;SACnC,CAAA;IACH,CAAC,CAAC,CAAA;IAEF,MAAM,OAAO,GAAG,MAAM,CAAC,EAAE,CAAC,yBAAyB,CAAC,CAAC,QAAQ,CAAC;QAC5D,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,EAAE,CAAA;QACzC,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,CACnC,KAAK,EACL,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,CACjG,CAAA;QACD,OAAO,EAAE,OAAO,EAAwB,CAAA;IAC1C,CAAC,CAAC,CAAA;IAEF,MAAM,GAAG,GAAG,MAAM,CAAC,EAAE,CAAC,qBAAqB,CAAC,CAAC,QAAQ,CAAC,EAAC,KAAa;QAClE,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAA;QACjD,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;YACvB,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,MAAM,CAClC,OAAO,CAAC,KAAK,CAAC,eAAe,CAC3B,KAAK,EACL,kCAAkC,CACnC,CACF,CAAA;YACD,OAAO,KAAK,CAAC,CAAC,IAAI,gBAAgB,CAAC;gBACjC,KAAK,EAAE,MAAM,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK;gBAC1D,MAAM,EAAE,MAAM,CAAC,SAAS,CAAC,OAAO,CAAC;oBAC/B,CAAC,CAAC,yEAAyE;oBAC3E,CAAC,CAAC,6DAA6D;gBACjE,SAAS,EAAE,iBAAiB;aAC7B,CAAC,CAAA;QACJ,CAAC;QACD,OAAO,KAAK,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,kBAAkB,CAAC,KAAK,CAAC,CAAA;IACvD,CAAC,CAAC,CAAA;IAEF,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,CAAA;AACjC,CAAC,CAAC,CAAA"}
@@ -0,0 +1,25 @@
1
+ import { Schema } from "effect";
2
+ declare const CoordinatorLifecycleMalformedError_base: Schema.Class<CoordinatorLifecycleMalformedError, Schema.TaggedStruct<"CoordinatorLifecycleMalformedError", {
3
+ readonly detail: Schema.String;
4
+ }>, import("effect/Cause").YieldableError>;
5
+ export declare class CoordinatorLifecycleMalformedError extends CoordinatorLifecycleMalformedError_base {
6
+ }
7
+ declare const CoordinatorLifecycleMissingEventError_base: Schema.Class<CoordinatorLifecycleMissingEventError, Schema.TaggedStruct<"CoordinatorLifecycleMissingEventError", {
8
+ readonly event: Schema.Literals<readonly ["started", "completed"]>;
9
+ }>, import("effect/Cause").YieldableError>;
10
+ export declare class CoordinatorLifecycleMissingEventError extends CoordinatorLifecycleMissingEventError_base {
11
+ }
12
+ declare const CoordinatorLifecycleConflictError_base: Schema.Class<CoordinatorLifecycleConflictError, Schema.TaggedStruct<"CoordinatorLifecycleConflictError", {
13
+ readonly reason: Schema.Literals<readonly ["duplicate_started", "duplicate_completed", "completed_before_started", "request_mismatch", "job_mismatch"]>;
14
+ }>, import("effect/Cause").YieldableError>;
15
+ export declare class CoordinatorLifecycleConflictError extends CoordinatorLifecycleConflictError_base {
16
+ }
17
+ declare const ChatHistoryError_base: Schema.Class<ChatHistoryError, Schema.TaggedStruct<"ChatHistoryError", {
18
+ readonly cause: Schema.Defect;
19
+ readonly detail: Schema.String;
20
+ readonly operation: Schema.String;
21
+ }>, import("effect/Cause").YieldableError>;
22
+ export declare class ChatHistoryError extends ChatHistoryError_base {
23
+ }
24
+ export {};
25
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;;;;AAE/B,qBAAa,kCAAmC,SAAQ,uCAGvD;CAAG;;;;AAEJ,qBAAa,qCAAsC,SAAQ,0CAG1D;CAAG;;;;AAEJ,qBAAa,iCAAkC,SAAQ,sCAWtD;CAAG;;;;;;AAEJ,qBAAa,gBAAiB,SAAQ,qBAGrC;CAAG"}
package/dist/errors.js ADDED
@@ -0,0 +1,18 @@
1
+ import { Schema } from "effect";
2
+ export class CoordinatorLifecycleMalformedError extends Schema.TaggedError()("CoordinatorLifecycleMalformedError", { detail: Schema.String }) {
3
+ }
4
+ export class CoordinatorLifecycleMissingEventError extends Schema.TaggedError()("CoordinatorLifecycleMissingEventError", { event: Schema.Literals(["started", "completed"]) }) {
5
+ }
6
+ export class CoordinatorLifecycleConflictError extends Schema.TaggedError()("CoordinatorLifecycleConflictError", {
7
+ reason: Schema.Literals([
8
+ "duplicate_started",
9
+ "duplicate_completed",
10
+ "completed_before_started",
11
+ "request_mismatch",
12
+ "job_mismatch"
13
+ ])
14
+ }) {
15
+ }
16
+ export class ChatHistoryError extends Schema.TaggedError()("ChatHistoryError", { cause: Schema.Defect(), detail: Schema.String, operation: Schema.String }) {
17
+ }
18
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAE/B,MAAM,OAAO,kCAAmC,SAAQ,MAAM,CAAC,WAAW,EAAsC,CAC9G,oCAAoC,EACpC,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAC1B;CAAG;AAEJ,MAAM,OAAO,qCAAsC,SAAQ,MAAM,CAAC,WAAW,EAAyC,CACpH,uCAAuC,EACvC,EAAE,KAAK,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC,SAAS,EAAE,WAAW,CAAC,CAAC,EAAE,CACrD;CAAG;AAEJ,MAAM,OAAO,iCAAkC,SAAQ,MAAM,CAAC,WAAW,EAAqC,CAC5G,mCAAmC,EACnC;IACE,MAAM,EAAE,MAAM,CAAC,QAAQ,CAAC;QACtB,mBAAmB;QACnB,qBAAqB;QACrB,0BAA0B;QAC1B,kBAAkB;QAClB,cAAc;KACf,CAAC;CACH,CACF;CAAG;AAEJ,MAAM,OAAO,gBAAiB,SAAQ,MAAM,CAAC,WAAW,EAAoB,CAC1E,kBAAkB,EAClB,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,CAC5E;CAAG"}
@@ -0,0 +1,6 @@
1
+ export * from "./chat.js";
2
+ export * from "./errors.js";
3
+ export * from "./lifecycle.js";
4
+ export * from "./model.js";
5
+ export * from "./store.js";
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAA;AACzB,cAAc,aAAa,CAAA;AAC3B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,YAAY,CAAA;AAC1B,cAAc,YAAY,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,6 @@
1
+ export * from "./chat.js";
2
+ export * from "./errors.js";
3
+ export * from "./lifecycle.js";
4
+ export * from "./model.js";
5
+ export * from "./store.js";
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAA;AACzB,cAAc,aAAa,CAAA;AAC3B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,YAAY,CAAA;AAC1B,cAAc,YAAY,CAAA"}
@@ -0,0 +1,20 @@
1
+ import type { AgentWorkerIdentity } from "@knpkv/herdr-fleet";
2
+ import { Effect } from "effect";
3
+ import { CoordinatorLifecycleConflictError, CoordinatorLifecycleMalformedError, CoordinatorLifecycleMissingEventError } from "./errors.js";
4
+ export declare const makeCoordinatorLifecycle: <E, R>(expectedJobId: string, workerStarted: (identity: AgentWorkerIdentity) => Effect.Effect<void, E, R>) => {
5
+ accept: (line: string) => Effect.Effect<undefined, E | CoordinatorLifecycleConflictError | CoordinatorLifecycleMalformedError, R>;
6
+ finish: () => Effect.Effect<{
7
+ reply: string;
8
+ worker: {
9
+ readonly host: string;
10
+ readonly agentId: string;
11
+ readonly name: string;
12
+ readonly paneId: string;
13
+ readonly relationship?: {
14
+ readonly parentAgentId: string;
15
+ readonly relation: "delegated" | "pair" | "review";
16
+ };
17
+ };
18
+ }, CoordinatorLifecycleMissingEventError, never>;
19
+ };
20
+ //# sourceMappingURL=lifecycle.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lifecycle.d.ts","sourceRoot":"","sources":["../src/lifecycle.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAA;AAC7D,OAAO,EAAE,MAAM,EAAU,MAAM,QAAQ,CAAA;AACvC,OAAO,EACL,iCAAiC,EACjC,kCAAkC,EAClC,qCAAqC,EACtC,MAAM,aAAa,CAAA;AAmEpB,eAAO,MAAM,wBAAwB,GAAI,CAAC,EAAE,CAAC,iBAC5B,MAAM,iBACN,CACb,QAAQ,EAAE,mBAAmB,KAC1B,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC;;;;;;;;;;;;;;;CA8D/B,CAAA"}
@@ -0,0 +1,102 @@
1
+ import { Effect, Schema } from "effect";
2
+ import { CoordinatorLifecycleConflictError, CoordinatorLifecycleMalformedError, CoordinatorLifecycleMissingEventError } from "./errors.js";
3
+ import { CoordinatorLifecycleEvent } from "./model.js";
4
+ const JsonObject = Schema.Record(Schema.String, Schema.Json);
5
+ const EventTag = Schema.Struct({ type: Schema.Literals(["started", "completed"]) });
6
+ const exactKeys = (value, expected) => {
7
+ const actual = Object.keys(value).sort();
8
+ const wanted = [...expected].sort();
9
+ return actual.length === wanted.length &&
10
+ actual.every((key, index) => key === wanted[index]);
11
+ };
12
+ const malformed = (detail) => new CoordinatorLifecycleMalformedError({ detail });
13
+ const decodeLine = Effect.fn("CoordinatorLifecycle.decodeLine")(function* (line) {
14
+ const json = yield* Effect.try({
15
+ try: () => JSON.parse(line),
16
+ catch: () => malformed("lifecycle event is not JSON")
17
+ });
18
+ const object = yield* Schema.decodeUnknownEffect(JsonObject)(json).pipe(Effect.mapError(() => malformed("lifecycle event is not an object")));
19
+ const tag = yield* Schema.decodeUnknownEffect(EventTag)(object).pipe(Effect.mapError(() => malformed("lifecycle event type is invalid")));
20
+ const expected = tag.type === "started"
21
+ ? ["jobId", "protocol", "requestId", "type", "worker"]
22
+ : ["jobId", "protocol", "reply", "requestId", "type"];
23
+ if (!exactKeys(object, expected)) {
24
+ return yield* malformed("lifecycle event contains unexpected fields");
25
+ }
26
+ if (tag.type === "started") {
27
+ const worker = yield* Schema.decodeUnknownEffect(JsonObject)(object.worker).pipe(Effect.mapError(() => malformed("started worker payload is not an object")));
28
+ const workerKeys = Object.keys(worker);
29
+ if (!exactKeys(worker, ["agentId", "host", "name", "paneId"]) &&
30
+ !exactKeys(worker, ["agentId", "host", "name", "paneId", "relationship"])) {
31
+ return yield* malformed("started worker payload contains unexpected fields");
32
+ }
33
+ if (!workerKeys.includes("relationship")) {
34
+ return yield* Schema.decodeUnknownEffect(CoordinatorLifecycleEvent)(object).pipe(Effect.mapError(() => malformed("lifecycle event fields are invalid")));
35
+ }
36
+ const relationship = yield* Schema.decodeUnknownEffect(JsonObject)(worker.relationship).pipe(Effect.mapError(() => malformed("started relationship payload is not an object")));
37
+ if (!exactKeys(relationship, ["parentAgentId", "relation"])) {
38
+ return yield* malformed("started relationship payload contains unexpected fields");
39
+ }
40
+ }
41
+ return yield* Schema.decodeUnknownEffect(CoordinatorLifecycleEvent)(object).pipe(Effect.mapError(() => malformed("lifecycle event fields are invalid")));
42
+ });
43
+ export const makeCoordinatorLifecycle = (expectedJobId, workerStarted) => {
44
+ let identity;
45
+ let reply;
46
+ let requestId;
47
+ const accept = Effect.fn("CoordinatorLifecycle.accept")(function* (line) {
48
+ const event = yield* decodeLine(line);
49
+ if (event.jobId !== expectedJobId) {
50
+ return yield* new CoordinatorLifecycleConflictError({
51
+ reason: "job_mismatch"
52
+ });
53
+ }
54
+ if (event.type === "started") {
55
+ if (identity !== undefined) {
56
+ return yield* new CoordinatorLifecycleConflictError({
57
+ reason: "duplicate_started"
58
+ });
59
+ }
60
+ if (reply !== undefined) {
61
+ return yield* new CoordinatorLifecycleConflictError({
62
+ reason: "completed_before_started"
63
+ });
64
+ }
65
+ identity = event.worker;
66
+ requestId = event.requestId;
67
+ yield* workerStarted(event.worker);
68
+ return;
69
+ }
70
+ if (identity === undefined) {
71
+ return yield* new CoordinatorLifecycleConflictError({
72
+ reason: "completed_before_started"
73
+ });
74
+ }
75
+ if (reply !== undefined) {
76
+ return yield* new CoordinatorLifecycleConflictError({
77
+ reason: "duplicate_completed"
78
+ });
79
+ }
80
+ if (requestId !== event.requestId) {
81
+ return yield* new CoordinatorLifecycleConflictError({
82
+ reason: "request_mismatch"
83
+ });
84
+ }
85
+ reply = event.reply;
86
+ });
87
+ const finish = Effect.fn("CoordinatorLifecycle.finish")(function* () {
88
+ if (identity === undefined) {
89
+ return yield* new CoordinatorLifecycleMissingEventError({
90
+ event: "started"
91
+ });
92
+ }
93
+ if (reply === undefined) {
94
+ return yield* new CoordinatorLifecycleMissingEventError({
95
+ event: "completed"
96
+ });
97
+ }
98
+ return { reply, worker: identity };
99
+ });
100
+ return { accept, finish };
101
+ };
102
+ //# sourceMappingURL=lifecycle.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lifecycle.js","sourceRoot":"","sources":["../src/lifecycle.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,126 @@
1
+ import { Schema } from "effect";
2
+ export declare const ChatMode: Schema.Literals<readonly ["ask", "work"]>;
3
+ export type ChatMode = typeof ChatMode.Type;
4
+ export declare const ChatMessage: Schema.Trimmed;
5
+ export declare const ChatRequest: Schema.Struct<{
6
+ readonly message: Schema.Trimmed;
7
+ readonly mode: Schema.Literals<readonly ["ask", "work"]>;
8
+ }>;
9
+ export interface ChatRequest extends Schema.Schema.Type<typeof ChatRequest> {
10
+ }
11
+ export declare const StoredChatTurn: Schema.Struct<{
12
+ readonly createdAt: Schema.Number;
13
+ readonly id: Schema.String;
14
+ readonly jobId: Schema.String;
15
+ readonly message: Schema.Trimmed;
16
+ readonly mode: Schema.Literals<readonly ["ask", "work"]>;
17
+ }>;
18
+ export interface StoredChatTurn extends Schema.Schema.Type<typeof StoredChatTurn> {
19
+ }
20
+ export declare const ChatState: Schema.Literals<readonly ["pending", "running", "failed", "interrupted", "completed"]>;
21
+ export type ChatState = typeof ChatState.Type;
22
+ export declare const CoordinatorReply: Schema.String;
23
+ export declare const chatHistoryMaxEntries = 32;
24
+ export declare const ChatEntry: Schema.Struct<{
25
+ readonly createdAt: Schema.Number;
26
+ readonly id: Schema.String;
27
+ readonly message: Schema.Trimmed;
28
+ readonly mode: Schema.Literals<readonly ["ask", "work"]>;
29
+ readonly reply: Schema.NullOr<Schema.String>;
30
+ readonly state: Schema.Literals<readonly ["pending", "running", "failed", "interrupted", "completed"]>;
31
+ readonly updatedAt: Schema.Number;
32
+ readonly worker: Schema.optionalKey<Schema.Struct<{
33
+ readonly host: Schema.String;
34
+ readonly agentId: Schema.String;
35
+ readonly name: Schema.String;
36
+ readonly paneId: Schema.String;
37
+ readonly relationship: Schema.optionalKey<Schema.Struct<{
38
+ readonly parentAgentId: Schema.String;
39
+ readonly relation: Schema.Literals<readonly ["delegated", "pair", "review"]>;
40
+ }>>;
41
+ }>>;
42
+ readonly connectTarget: Schema.optionalKey<Schema.Struct<{
43
+ readonly host: Schema.String;
44
+ readonly agentId: Schema.String;
45
+ readonly url: Schema.String;
46
+ }>>;
47
+ }>;
48
+ export interface ChatEntry extends Schema.Schema.Type<typeof ChatEntry> {
49
+ }
50
+ export declare const ChatHistory: Schema.Struct<{
51
+ readonly entries: Schema.$Array<Schema.Struct<{
52
+ readonly createdAt: Schema.Number;
53
+ readonly id: Schema.String;
54
+ readonly message: Schema.Trimmed;
55
+ readonly mode: Schema.Literals<readonly ["ask", "work"]>;
56
+ readonly reply: Schema.NullOr<Schema.String>;
57
+ readonly state: Schema.Literals<readonly ["pending", "running", "failed", "interrupted", "completed"]>;
58
+ readonly updatedAt: Schema.Number;
59
+ readonly worker: Schema.optionalKey<Schema.Struct<{
60
+ readonly host: Schema.String;
61
+ readonly agentId: Schema.String;
62
+ readonly name: Schema.String;
63
+ readonly paneId: Schema.String;
64
+ readonly relationship: Schema.optionalKey<Schema.Struct<{
65
+ readonly parentAgentId: Schema.String;
66
+ readonly relation: Schema.Literals<readonly ["delegated", "pair", "review"]>;
67
+ }>>;
68
+ }>>;
69
+ readonly connectTarget: Schema.optionalKey<Schema.Struct<{
70
+ readonly host: Schema.String;
71
+ readonly agentId: Schema.String;
72
+ readonly url: Schema.String;
73
+ }>>;
74
+ }>>;
75
+ }>;
76
+ export interface ChatHistory extends Schema.Schema.Type<typeof ChatHistory> {
77
+ }
78
+ export declare const CoordinatorLifecycleStarted: Schema.Struct<{
79
+ readonly protocol: Schema.Literal<"herdr.coordinator.child.v1">;
80
+ readonly jobId: Schema.String;
81
+ readonly requestId: Schema.String;
82
+ readonly type: Schema.Literal<"started">;
83
+ readonly worker: Schema.Struct<{
84
+ readonly host: Schema.String;
85
+ readonly agentId: Schema.String;
86
+ readonly name: Schema.String;
87
+ readonly paneId: Schema.String;
88
+ readonly relationship: Schema.optionalKey<Schema.Struct<{
89
+ readonly parentAgentId: Schema.String;
90
+ readonly relation: Schema.Literals<readonly ["delegated", "pair", "review"]>;
91
+ }>>;
92
+ }>;
93
+ }>;
94
+ export type CoordinatorLifecycleStarted = typeof CoordinatorLifecycleStarted.Type;
95
+ export declare const CoordinatorLifecycleCompleted: Schema.Struct<{
96
+ readonly protocol: Schema.Literal<"herdr.coordinator.child.v1">;
97
+ readonly jobId: Schema.String;
98
+ readonly reply: Schema.String;
99
+ readonly requestId: Schema.String;
100
+ readonly type: Schema.Literal<"completed">;
101
+ }>;
102
+ export type CoordinatorLifecycleCompleted = typeof CoordinatorLifecycleCompleted.Type;
103
+ export declare const CoordinatorLifecycleEvent: Schema.Union<readonly [Schema.Struct<{
104
+ readonly protocol: Schema.Literal<"herdr.coordinator.child.v1">;
105
+ readonly jobId: Schema.String;
106
+ readonly requestId: Schema.String;
107
+ readonly type: Schema.Literal<"started">;
108
+ readonly worker: Schema.Struct<{
109
+ readonly host: Schema.String;
110
+ readonly agentId: Schema.String;
111
+ readonly name: Schema.String;
112
+ readonly paneId: Schema.String;
113
+ readonly relationship: Schema.optionalKey<Schema.Struct<{
114
+ readonly parentAgentId: Schema.String;
115
+ readonly relation: Schema.Literals<readonly ["delegated", "pair", "review"]>;
116
+ }>>;
117
+ }>;
118
+ }>, Schema.Struct<{
119
+ readonly protocol: Schema.Literal<"herdr.coordinator.child.v1">;
120
+ readonly jobId: Schema.String;
121
+ readonly reply: Schema.String;
122
+ readonly requestId: Schema.String;
123
+ readonly type: Schema.Literal<"completed">;
124
+ }>]>;
125
+ export type CoordinatorLifecycleEvent = typeof CoordinatorLifecycleEvent.Type;
126
+ //# sourceMappingURL=model.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"model.d.ts","sourceRoot":"","sources":["../src/model.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAU/B,eAAO,MAAM,QAAQ,2CAAmC,CAAA;AACxD,MAAM,MAAM,QAAQ,GAAG,OAAO,QAAQ,CAAC,IAAI,CAAA;AAE3C,eAAO,MAAM,WAAW,gBAIvB,CAAA;AAED,eAAO,MAAM,WAAW;;;EAA0D,CAAA;AAClF,MAAM,WAAW,WAAY,SAAQ,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,WAAW,CAAC;CAAG;AAE9E,eAAO,MAAM,cAAc;;;;;;EAMzB,CAAA;AACF,MAAM,WAAW,cAAe,SAAQ,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,cAAc,CAAC;CAAG;AAEpF,eAAO,MAAM,SAAS,wFAAgF,CAAA;AACtG,MAAM,MAAM,SAAS,GAAG,OAAO,SAAS,CAAC,IAAI,CAAA;AAE7C,eAAO,MAAM,gBAAgB,eAI5B,CAAA;AAED,eAAO,MAAM,qBAAqB,KAAK,CAAA;AAEvC,eAAO,MAAM,SAAS;;;;;;;;;;;;;;;;;;;;;;;EAoBrB,CAAA;AACD,MAAM,WAAW,SAAU,SAAQ,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,SAAS,CAAC;CAAG;AAE1E,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;EAEtB,CAAA;AACF,MAAM,WAAW,WAAY,SAAQ,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,WAAW,CAAC;CAAG;AAE9E,eAAO,MAAM,2BAA2B;;;;;;;;;;;;;;;EAMtC,CAAA;AACF,MAAM,MAAM,2BAA2B,GAAG,OAAO,2BAA2B,CAAC,IAAI,CAAA;AAEjF,eAAO,MAAM,6BAA6B;;;;;;EAMxC,CAAA;AACF,MAAM,MAAM,6BAA6B,GAAG,OAAO,6BAA6B,CAAC,IAAI,CAAA;AAErF,eAAO,MAAM,yBAAyB;;;;;;;;;;;;;;;;;;;;;IAGpC,CAAA;AACF,MAAM,MAAM,yBAAyB,GAAG,OAAO,yBAAyB,CAAC,IAAI,CAAA"}
package/dist/model.js ADDED
@@ -0,0 +1,55 @@
1
+ import { AgentConnectTarget, AgentWorkerIdentity } from "@knpkv/herdr-fleet/model";
2
+ import { Schema } from "effect";
3
+ const Identifier = Schema.String.check(Schema.isNonEmpty(), Schema.isMaxLength(256));
4
+ const utf8 = new TextEncoder();
5
+ const utf8MaxBytes = (maximumBytes) => Schema.makeFilter((value) => utf8.encode(value).byteLength <= maximumBytes, { expected: `UTF-8 text no larger than ${maximumBytes} bytes` });
6
+ export const ChatMode = Schema.Literals(["ask", "work"]);
7
+ export const ChatMessage = Schema.Trimmed.check(Schema.isNonEmpty(), Schema.isMaxLength(2_000), utf8MaxBytes(2_000));
8
+ export const ChatRequest = Schema.Struct({ message: ChatMessage, mode: ChatMode });
9
+ export const StoredChatTurn = Schema.Struct({
10
+ createdAt: Schema.Number,
11
+ id: Identifier,
12
+ jobId: Identifier,
13
+ message: ChatMessage,
14
+ mode: ChatMode
15
+ });
16
+ export const ChatState = Schema.Literals(["pending", "running", "failed", "interrupted", "completed"]);
17
+ export const CoordinatorReply = Schema.String.check(Schema.isNonEmpty(), Schema.isMaxLength(20_000), utf8MaxBytes(20_000));
18
+ export const chatHistoryMaxEntries = 32;
19
+ export const ChatEntry = Schema.Struct({
20
+ createdAt: Schema.Number,
21
+ id: Identifier,
22
+ message: ChatMessage,
23
+ mode: ChatMode,
24
+ reply: Schema.NullOr(CoordinatorReply),
25
+ state: ChatState,
26
+ updatedAt: Schema.Number,
27
+ worker: Schema.optionalKey(AgentWorkerIdentity),
28
+ connectTarget: Schema.optionalKey(AgentConnectTarget)
29
+ }).check(Schema.makeFilter((entry) => (entry.worker === undefined && entry.connectTarget === undefined) ||
30
+ (entry.worker !== undefined &&
31
+ entry.connectTarget !== undefined &&
32
+ entry.worker.host === entry.connectTarget.host &&
33
+ entry.worker.agentId === entry.connectTarget.agentId), { expected: "exact Connect target for the matching coordinator worker" }));
34
+ export const ChatHistory = Schema.Struct({
35
+ entries: Schema.Array(ChatEntry).check(Schema.isMaxLength(chatHistoryMaxEntries))
36
+ });
37
+ export const CoordinatorLifecycleStarted = Schema.Struct({
38
+ protocol: Schema.Literal("herdr.coordinator.child.v1"),
39
+ jobId: Identifier,
40
+ requestId: Identifier,
41
+ type: Schema.Literal("started"),
42
+ worker: AgentWorkerIdentity
43
+ });
44
+ export const CoordinatorLifecycleCompleted = Schema.Struct({
45
+ protocol: Schema.Literal("herdr.coordinator.child.v1"),
46
+ jobId: Identifier,
47
+ reply: CoordinatorReply,
48
+ requestId: Identifier,
49
+ type: Schema.Literal("completed")
50
+ });
51
+ export const CoordinatorLifecycleEvent = Schema.Union([
52
+ CoordinatorLifecycleStarted,
53
+ CoordinatorLifecycleCompleted
54
+ ]);
55
+ //# sourceMappingURL=model.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"model.js","sourceRoot":"","sources":["../src/model.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,32 @@
1
+ import { Effect, FileSystem, Path } from "effect";
2
+ import { ChatHistoryError } from "./errors.js";
3
+ import { StoredChatTurn, type StoredChatTurn as StoredChatTurnType } from "./model.js";
4
+ export interface ChatStoreService {
5
+ readonly getByJob: (jobId: string) => Effect.Effect<StoredChatTurnType | undefined, ChatHistoryError>;
6
+ readonly list: () => Effect.Effect<ReadonlyArray<StoredChatTurnType>, ChatHistoryError>;
7
+ readonly put: (turn: StoredChatTurnType) => Effect.Effect<StoredChatTurnType, ChatHistoryError>;
8
+ }
9
+ export declare class ChatStore {
10
+ #private;
11
+ readonly path: string;
12
+ private constructor();
13
+ static readonly open: (path: string) => Effect.Effect<ChatStore, ChatHistoryError, FileSystem.FileSystem | Path.Path>;
14
+ readonly put: (this: ChatStore, turn: StoredChatTurn) => Effect.Effect<StoredChatTurn, ChatHistoryError, never>;
15
+ readonly getByJob: (this: ChatStore, jobId: string) => Effect.Effect<{
16
+ readonly createdAt: number;
17
+ readonly id: string;
18
+ readonly jobId: string;
19
+ readonly message: string;
20
+ readonly mode: "ask" | "work";
21
+ } | undefined, ChatHistoryError, never>;
22
+ readonly list: (this: ChatStore) => Effect.Effect<{
23
+ readonly createdAt: number;
24
+ readonly id: string;
25
+ readonly jobId: string;
26
+ readonly message: string;
27
+ readonly mode: "ask" | "work";
28
+ }[], ChatHistoryError, never>;
29
+ private secureFiles;
30
+ close(): void;
31
+ }
32
+ //# sourceMappingURL=store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,IAAI,EAAU,MAAM,QAAQ,CAAA;AAEzD,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA;AAC9C,OAAO,EAAyB,cAAc,EAAE,KAAK,cAAc,IAAI,kBAAkB,EAAE,MAAM,YAAY,CAAA;AAmB7G,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,QAAQ,EAAE,CACjB,KAAK,EAAE,MAAM,KACV,MAAM,CAAC,MAAM,CAAC,kBAAkB,GAAG,SAAS,EAAE,gBAAgB,CAAC,CAAA;IACpE,QAAQ,CAAC,IAAI,EAAE,MAAM,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,kBAAkB,CAAC,EAAE,gBAAgB,CAAC,CAAA;IACvF,QAAQ,CAAC,GAAG,EAAE,CACZ,IAAI,EAAE,kBAAkB,KACrB,MAAM,CAAC,MAAM,CAAC,kBAAkB,EAAE,gBAAgB,CAAC,CAAA;CACzD;AAED,qBAAa,SAAS;;IAGpB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IAErB,OAAO,eAkBN;IAED,MAAM,CAAC,QAAQ,CAAC,IAAI,kGAgBlB;IAEF,QAAQ,CAAC,GAAG,oGAeV;IAEF,QAAQ,CAAC,QAAQ;;;;;;4CASf;IAEF,QAAQ,CAAC,IAAI;;;;;;kCAeX;IAEF,OAAO,CAAC,WAAW;IAoBnB,KAAK,IAAI,IAAI,CAEZ;CACF"}
package/dist/store.js ADDED
@@ -0,0 +1,93 @@
1
+ import { Effect, FileSystem, Path, Schema } from "effect";
2
+ import { DatabaseSync } from "node:sqlite";
3
+ import { ChatHistoryError } from "./errors.js";
4
+ import { chatHistoryMaxEntries, StoredChatTurn } from "./model.js";
5
+ const storeError = (operation) => (cause) => new ChatHistoryError({ cause, detail: String(cause), operation });
6
+ const StoredTurnRow = Schema.Struct({ record: Schema.String });
7
+ const decodeTurn = (encoded) => Effect.try({
8
+ try: () => Schema.decodeUnknownSync(StoredChatTurn)(JSON.parse(encoded)),
9
+ catch: storeError("chat.parse")
10
+ });
11
+ const decodeRow = (operation, row) => Schema.decodeUnknownEffect(StoredTurnRow)(row).pipe(Effect.mapError(storeError(operation)), Effect.flatMap(({ record }) => decodeTurn(record)));
12
+ export class ChatStore {
13
+ #database;
14
+ #fileSystem;
15
+ path;
16
+ constructor(path, fileSystem) {
17
+ this.path = path;
18
+ this.#fileSystem = fileSystem;
19
+ this.#database = new DatabaseSync(path);
20
+ try {
21
+ this.#database.exec(`
22
+ PRAGMA journal_mode = WAL;
23
+ CREATE TABLE IF NOT EXISTS chat_turns (
24
+ id TEXT PRIMARY KEY,
25
+ job_id TEXT NOT NULL UNIQUE,
26
+ created_at INTEGER NOT NULL,
27
+ record TEXT NOT NULL
28
+ );
29
+ `);
30
+ }
31
+ catch (error) {
32
+ this.#database.close();
33
+ throw error;
34
+ }
35
+ }
36
+ static open = Effect.fn("ChatStore.open")(function* (path) {
37
+ const fileSystem = yield* FileSystem.FileSystem;
38
+ const paths = yield* Path.Path;
39
+ const directory = paths.dirname(path);
40
+ yield* fileSystem.makeDirectory(directory, { recursive: true, mode: 0o700 }).pipe(Effect.mapError(storeError("chat.openDirectory")));
41
+ yield* fileSystem.chmod(directory, 0o700).pipe(Effect.mapError(storeError("chat.secureDirectory")));
42
+ const store = yield* Effect.try({
43
+ try: () => new ChatStore(path, fileSystem),
44
+ catch: storeError("chat.openDatabase")
45
+ });
46
+ yield* store.secureFiles();
47
+ return store;
48
+ });
49
+ put = Effect.fn("ChatStore.put")(function* (turn) {
50
+ const stored = yield* Effect.try({
51
+ try: () => {
52
+ this.#database
53
+ .prepare("INSERT INTO chat_turns (id, job_id, created_at, record) VALUES (?, ?, ?, ?)")
54
+ .run(turn.id, turn.jobId, turn.createdAt, JSON.stringify(turn));
55
+ return turn;
56
+ },
57
+ catch: storeError("chat.put")
58
+ });
59
+ yield* this.secureFiles();
60
+ return stored;
61
+ });
62
+ getByJob = Effect.fn("ChatStore.getByJob")(function* (jobId) {
63
+ const row = yield* Effect.try({
64
+ try: () => this.#database.prepare("SELECT record FROM chat_turns WHERE job_id = ?").get(jobId),
65
+ catch: storeError("chat.getByJob")
66
+ });
67
+ return row === undefined ? undefined : yield* decodeRow("chat.getByJob", row);
68
+ });
69
+ list = Effect.fn("ChatStore.list")(function* () {
70
+ const rows = yield* Effect.try({
71
+ try: () => this.#database.prepare(`SELECT record FROM (
72
+ SELECT id, created_at, record
73
+ FROM chat_turns
74
+ ORDER BY created_at DESC, id DESC
75
+ LIMIT ?
76
+ )
77
+ ORDER BY created_at ASC, id ASC`).all(chatHistoryMaxEntries),
78
+ catch: storeError("chat.list")
79
+ });
80
+ return yield* Effect.forEach(rows, (row) => decodeRow("chat.list", row));
81
+ });
82
+ secureFiles() {
83
+ const fileSystem = this.#fileSystem;
84
+ const files = [this.path, `${this.path}-wal`, `${this.path}-shm`];
85
+ return Effect.forEach(files, (path) => fileSystem.exists(path).pipe(Effect.mapError(storeError("chat.secureExists")), Effect.flatMap((exists) => exists
86
+ ? fileSystem.chmod(path, 0o600).pipe(Effect.mapError(storeError("chat.secureChmod")))
87
+ : Effect.void)), { discard: true });
88
+ }
89
+ close() {
90
+ this.#database.close();
91
+ }
92
+ }
93
+ //# sourceMappingURL=store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.js","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AACzD,OAAO,EAAE,YAAY,EAAuB,MAAM,aAAa,CAAA;AAC/D,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA;AAC9C,OAAO,EAAE,qBAAqB,EAAE,cAAc,EAA6C,MAAM,YAAY,CAAA;AAE7G,MAAM,UAAU,GAAG,CAAC,SAAiB,EAAE,EAAE,CAAC,CAAC,KAAc,EAAE,EAAE,CAC3D,IAAI,gBAAgB,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,KAAK,CAAC,EAAE,SAAS,EAAE,CAAC,CAAA;AAEnE,MAAM,aAAa,GAAG,MAAM,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAA;AAE9D,MAAM,UAAU,GAAG,CAAC,OAAe,EAAE,EAAE,CACrC,MAAM,CAAC,GAAG,CAAC;IACT,GAAG,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,iBAAiB,CAAC,cAAc,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IACxE,KAAK,EAAE,UAAU,CAAC,YAAY,CAAC;CAChC,CAAC,CAAA;AAEJ,MAAM,SAAS,GAAG,CAAC,SAAiB,EAAE,GAAmC,EAAE,EAAE,CAC3E,MAAM,CAAC,mBAAmB,CAAC,aAAa,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,CACjD,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC,EACtC,MAAM,CAAC,OAAO,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CACnD,CAAA;AAYH,MAAM,OAAO,SAAS;IACX,SAAS,CAAc;IACvB,WAAW,CAAuB;IAClC,IAAI,CAAQ;IAErB,YAAoB,IAAY,EAAE,UAAiC;QACjE,IAAI,CAAC,IAAI,GAAG,IAAI,CAAA;QAChB,IAAI,CAAC,WAAW,GAAG,UAAU,CAAA;QAC7B,IAAI,CAAC,SAAS,GAAG,IAAI,YAAY,CAAC,IAAI,CAAC,CAAA;QACvC,IAAI,CAAC;YACH,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC;;;;;;;;OAQnB,CAAC,CAAA;QACJ,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,CAAA;YACtB,MAAM,KAAK,CAAA;QACb,CAAC;IACH,CAAC;IAED,MAAM,CAAU,IAAI,GAAG,MAAM,CAAC,EAAE,CAAC,gBAAgB,CAAC,CAAC,QAAQ,CAAC,EAAC,IAAY;QACvE,MAAM,UAAU,GAAG,KAAK,CAAC,CAAC,UAAU,CAAC,UAAU,CAAA;QAC/C,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,IAAI,CAAC,IAAI,CAAA;QAC9B,MAAM,SAAS,GAAG,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAA;QACrC,KAAK,CAAC,CAAC,UAAU,CAAC,aAAa,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,IAAI,CAC/E,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAC,oBAAoB,CAAC,CAAC,CAClD,CAAA;QACD,KAAK,CAAC,CAAC,UAAU,CAAC,KAAK,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC,IAAI,CAC5C,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAC,sBAAsB,CAAC,CAAC,CACpD,CAAA;QACD,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;YAC9B,GAAG,EAAE,GAAG,EAAE,CAAC,IAAI,SAAS,CAAC,IAAI,EAAE,UAAU,CAAC;YAC1C,KAAK,EAAE,UAAU,CAAC,mBAAmB,CAAC;SACvC,CAAC,CAAA;QACF,KAAK,CAAC,CAAC,KAAK,CAAC,WAAW,EAAE,CAAA;QAC1B,OAAO,KAAK,CAAA;IACd,CAAC,CAAC,CAAA;IAEO,GAAG,GAAG,MAAM,CAAC,EAAE,CAAC,eAAe,CAAC,CAAC,QAAQ,CAAC,EAEjD,IAAwB;QAExB,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;YAC/B,GAAG,EAAE,GAAG,EAAE;gBACR,IAAI,CAAC,SAAS;qBACX,OAAO,CAAC,6EAA6E,CAAC;qBACtF,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAA;gBACjE,OAAO,IAAI,CAAA;YACb,CAAC;YACD,KAAK,EAAE,UAAU,CAAC,UAAU,CAAC;SAC9B,CAAC,CAAA;QACF,KAAK,CAAC,CAAC,IAAI,CAAC,WAAW,EAAE,CAAA;QACzB,OAAO,MAAM,CAAA;IACf,CAAC,CAAC,CAAA;IAEO,QAAQ,GAAG,MAAM,CAAC,EAAE,CAAC,oBAAoB,CAAC,CAAC,QAAQ,CAAC,EAE3D,KAAa;QAEb,MAAM,GAAG,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;YAC5B,GAAG,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,gDAAgD,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC;YAC9F,KAAK,EAAE,UAAU,CAAC,eAAe,CAAC;SACnC,CAAC,CAAA;QACF,OAAO,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,SAAS,CAAC,eAAe,EAAE,GAAG,CAAC,CAAA;IAC/E,CAAC,CAAC,CAAA;IAEO,IAAI,GAAG,MAAM,CAAC,EAAE,CAAC,gBAAgB,CAAC,CAAC,QAAQ,CAAC;QACnD,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;YAC7B,GAAG,EAAE,GAAG,EAAE,CACR,IAAI,CAAC,SAAS,CAAC,OAAO,CACpB;;;;;;yCAM+B,CAChC,CAAC,GAAG,CAAC,qBAAqB,CAAC;YAC9B,KAAK,EAAE,UAAU,CAAC,WAAW,CAAC;SAC/B,CAAC,CAAA;QACF,OAAO,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,SAAS,CAAC,WAAW,EAAE,GAAG,CAAC,CAAC,CAAA;IAC1E,CAAC,CAAC,CAAA;IAEM,WAAW;QACjB,MAAM,UAAU,GAAG,IAAI,CAAC,WAAW,CAAA;QACnC,MAAM,KAAK,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,IAAI,CAAC,IAAI,MAAM,EAAE,GAAG,IAAI,CAAC,IAAI,MAAM,CAAC,CAAA;QACjE,OAAO,MAAM,CAAC,OAAO,CACnB,KAAK,EACL,CAAC,IAAI,EAAE,EAAE,CACP,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,CAC1B,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAC,mBAAmB,CAAC,CAAC,EAChD,MAAM,CAAC,OAAO,CAAC,CAAC,MAAM,EAAE,EAAE,CACxB,MAAM;YACJ,CAAC,CAAC,UAAU,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,IAAI,CAClC,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAC,kBAAkB,CAAC,CAAC,CAChD;YACD,CAAC,CAAC,MAAM,CAAC,IAAI,CAChB,CACF,EACH,EAAE,OAAO,EAAE,IAAI,EAAE,CAClB,CAAA;IACH,CAAC;IAED,KAAK;QACH,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,CAAA;IACxB,CAAC;CACF"}
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "@knpkv/herdr-coordinator",
3
+ "version": "0.2.0",
4
+ "description": "Persistent coordinator chat contracts for Herdr fleet jobs",
5
+ "license": "MIT",
6
+ "author": "knpkv",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/knpkv/npm.git",
10
+ "directory": "packages/herdr-coordinator"
11
+ },
12
+ "type": "module",
13
+ "sideEffects": false,
14
+ "main": "./dist/index.js",
15
+ "types": "./dist/index.d.ts",
16
+ "exports": {
17
+ ".": {
18
+ "types": "./dist/index.d.ts",
19
+ "import": "./dist/index.js"
20
+ },
21
+ "./model": {
22
+ "types": "./dist/model.d.ts",
23
+ "import": "./dist/model.js"
24
+ }
25
+ },
26
+ "files": [
27
+ "dist",
28
+ "README.md"
29
+ ],
30
+ "publishConfig": {
31
+ "access": "public"
32
+ },
33
+ "engines": {
34
+ "node": ">=26"
35
+ },
36
+ "dependencies": {
37
+ "effect": "4.0.0-rc.109",
38
+ "@knpkv/herdr-fleet": "^0.2.0"
39
+ },
40
+ "devDependencies": {
41
+ "@effect/platform-node": "4.0.0-rc.109",
42
+ "@effect/vitest": "4.0.0-rc.109",
43
+ "@types/node": "^26.2.0",
44
+ "rimraf": "^6.1.3",
45
+ "typescript": "npm:@typescript/typescript6@^6.0.2",
46
+ "vitest": "^4.1.10"
47
+ },
48
+ "scripts": {
49
+ "build": "rimraf dist && tsc -b --force",
50
+ "check": "tsc -b tsconfig.json",
51
+ "clean": "rimraf dist *.tsbuildinfo",
52
+ "lint": "eslint \"{src,test}/**/*.ts\"",
53
+ "pack:deterministic": "pnpm build && node ../../scripts/pack-herdr.mjs .",
54
+ "test": "vitest run"
55
+ }
56
+ }