@shivaedev/work-fleet 0.3.0 → 0.5.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.
Files changed (78) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/README.md +3 -3
  3. package/dist/engine/readRetry.d.ts +1 -0
  4. package/dist/engine/readRetry.d.ts.map +1 -1
  5. package/dist/engine/readRetry.js +2 -1
  6. package/dist/engine/readRetry.js.map +1 -1
  7. package/dist/host/cliOptions.d.ts +1 -1
  8. package/dist/host/cliOptions.d.ts.map +1 -1
  9. package/dist/host/cliOptions.js +1 -1
  10. package/dist/host/cliOptions.js.map +1 -1
  11. package/dist/host/commands.d.ts +0 -1089
  12. package/dist/host/commands.d.ts.map +1 -1
  13. package/dist/host/commands.js +1 -13
  14. package/dist/host/commands.js.map +1 -1
  15. package/dist/host/config.d.ts +2 -63
  16. package/dist/host/config.d.ts.map +1 -1
  17. package/dist/host/config.js +4 -34
  18. package/dist/host/config.js.map +1 -1
  19. package/dist/host/configFile.d.ts +66 -0
  20. package/dist/host/configFile.d.ts.map +1 -0
  21. package/dist/host/configFile.js +37 -0
  22. package/dist/host/configFile.js.map +1 -0
  23. package/dist/host/configSettings.d.ts +3 -1
  24. package/dist/host/configSettings.d.ts.map +1 -1
  25. package/dist/host/configSettings.js +2 -0
  26. package/dist/host/configSettings.js.map +1 -1
  27. package/dist/host/daemon.d.ts +1 -0
  28. package/dist/host/daemon.d.ts.map +1 -1
  29. package/dist/host/daemonClient.d.ts +1058 -1057
  30. package/dist/host/daemonClient.d.ts.map +1 -1
  31. package/dist/host/daemonContract.d.ts +3 -0
  32. package/dist/host/daemonContract.d.ts.map +1 -1
  33. package/dist/host/daemonHandlers.d.ts.map +1 -1
  34. package/dist/host/daemonHandlers.js +1 -1
  35. package/dist/host/daemonHandlers.js.map +1 -1
  36. package/dist/host/{status.d.ts → fleetStatus.d.ts} +1 -3
  37. package/dist/host/fleetStatus.d.ts.map +1 -0
  38. package/dist/host/{status.js → fleetStatus.js} +1 -5
  39. package/dist/host/fleetStatus.js.map +1 -0
  40. package/dist/host/liveConfig.d.ts.map +1 -1
  41. package/dist/host/liveConfig.js +2 -1
  42. package/dist/host/liveConfig.js.map +1 -1
  43. package/dist/host/releaseWork.d.ts.map +1 -1
  44. package/dist/host/releaseWork.js +2 -2
  45. package/dist/host/releaseWork.js.map +1 -1
  46. package/dist/host/runtime.d.ts.map +1 -1
  47. package/dist/host/runtime.js +1 -0
  48. package/dist/host/runtime.js.map +1 -1
  49. package/dist/packageInfo.d.ts +1 -1
  50. package/dist/packageInfo.js +1 -1
  51. package/dist/policy.d.ts +1 -0
  52. package/dist/policy.d.ts.map +1 -1
  53. package/dist/policy.js.map +1 -1
  54. package/dist/scheduling/areas.d.ts.map +1 -1
  55. package/dist/scheduling/areas.js +1 -0
  56. package/dist/scheduling/areas.js.map +1 -1
  57. package/package.json +5 -5
  58. package/src/engine/readRetry.ts +3 -1
  59. package/src/host/cliOptions.ts +1 -1
  60. package/src/host/commands.ts +1 -16
  61. package/src/host/config.ts +5 -38
  62. package/src/host/configFile.ts +40 -0
  63. package/src/host/configSettings.ts +2 -0
  64. package/src/host/daemonHandlers.ts +1 -1
  65. package/src/host/{status.ts → fleetStatus.ts} +0 -5
  66. package/src/host/liveConfig.ts +2 -1
  67. package/src/host/releaseWork.ts +5 -2
  68. package/src/host/runtime.ts +1 -0
  69. package/src/packageInfo.ts +1 -1
  70. package/src/policy.ts +1 -0
  71. package/src/scheduling/areas.ts +1 -0
  72. package/dist/host/reassignWork.d.ts +0 -549
  73. package/dist/host/reassignWork.d.ts.map +0 -1
  74. package/dist/host/reassignWork.js +0 -9
  75. package/dist/host/reassignWork.js.map +0 -1
  76. package/dist/host/status.d.ts.map +0 -1
  77. package/dist/host/status.js.map +0 -1
  78. package/src/host/reassignWork.ts +0 -17
@@ -0,0 +1,40 @@
1
+ import * as Effect from "effect/Effect";
2
+ import * as FileSystem from "effect/FileSystem";
3
+ import * as Path from "effect/Path";
4
+ import * as Schema from "effect/Schema";
5
+ import * as SchemaIssue from "effect/SchemaIssue";
6
+ import { HostConfig, HostConfigRejected, HostFailure, resolveHost } from "#host/config.ts";
7
+ import { CONFIG_FILE } from "#host/defaults.ts";
8
+
9
+ export const readHostConfig = Effect.fn("FleetHost.readHostConfig")(function* (file: string) {
10
+ const fs = yield* FileSystem.FileSystem;
11
+ const text = yield* fs
12
+ .readFileString(file)
13
+ .pipe(Effect.mapError(() => new HostConfigRejected({ file, message: `Fleet host configuration is missing or unreadable: ${file}` })));
14
+ const config = yield* Schema.decodeUnknownEffect(Schema.fromJsonString(HostConfig))(text).pipe(
15
+ Effect.mapError((error) => {
16
+ const field = SchemaIssue.makeFormatterStandardSchemaV1()(error.issue).issues[0]?.path?.map(String).join(".");
17
+ return new HostConfigRejected({
18
+ ...(field === undefined || field === "" ? {} : { field }),
19
+ file,
20
+ message: `Fleet host configuration ${file} is invalid: ${error.message}`,
21
+ });
22
+ }),
23
+ );
24
+ if (config.provider === "cloud" && (config.cloud === undefined || config.worker === undefined)) {
25
+ return yield* new HostConfigRejected({
26
+ field: config.cloud === undefined ? "cloud" : "worker",
27
+ file,
28
+ message: `Fleet host configuration ${file} selects provider "cloud" without cloud and worker settings`,
29
+ });
30
+ }
31
+ return config;
32
+ });
33
+
34
+ export const loadHost = Effect.fn("FleetHost.loadHost")(function* (board: string, configPath?: string) {
35
+ const path = yield* Path.Path;
36
+ const boardDir = path.resolve(board);
37
+ const file = configPath === undefined ? path.join(boardDir, CONFIG_FILE) : path.resolve(configPath);
38
+ const config = yield* readHostConfig(file).pipe(Effect.mapError((rejected) => new HostFailure({ message: rejected.message })));
39
+ return resolveHost(boardDir, config, file);
40
+ });
@@ -12,6 +12,7 @@ export const RELOADABLE_SETTINGS = [
12
12
  "launch",
13
13
  "openPullRequests",
14
14
  "quota",
15
+ "readRetryMs",
15
16
  "stallMinutes",
16
17
  "worker.reviewerGuidance",
17
18
  "worker.workerGuidance",
@@ -50,6 +51,7 @@ function appliedSettings(host: FleetHost): HostConfig {
50
51
  ...(host.openPullRequests === undefined ? {} : { openPullRequests: host.openPullRequests }),
51
52
  provider: host.provider,
52
53
  quota: host.quota,
54
+ readRetryMs: host.readRetryMs,
53
55
  repository: host.repository,
54
56
  ...(host.result === undefined ? {} : { result: host.result }),
55
57
  stallMinutes: host.stallMinutes,
@@ -7,10 +7,10 @@ import { ClientContract } from "#host/daemonContract.ts";
7
7
  import { daemonDiagnostics } from "#host/daemonDiagnostics.ts";
8
8
  import { daemonSnapshots } from "#host/daemonSnapshots.ts";
9
9
  import { dispatchRequests } from "#host/dispatchRequests.ts";
10
+ import { fleetStatus } from "#host/fleetStatus.ts";
10
11
  import { LiveConfig } from "#host/liveConfig.ts";
11
12
  import { nameStoredThreads } from "#host/nameThreads.ts";
12
13
  import { decisionResponses, releaseItems } from "#host/releaseWork.ts";
13
- import { fleetStatus } from "#host/status.ts";
14
14
 
15
15
  export const daemonHandlers = Effect.fn("FleetDaemon.daemonHandlers")(function* (host: FleetHost) {
16
16
  const scope = yield* Effect.scope;
@@ -2,7 +2,6 @@ import { Effect, Schema } from "effect";
2
2
  import { makeRepository } from "@shivaedev/effect-sql/repository.ts";
3
3
  import { views } from "#engine/views.ts";
4
4
  import type { FleetHost } from "#host/config.ts";
5
- import { renderStatus as render } from "#host/renderStatus.ts";
6
5
  import { FleetRecord } from "#model.ts";
7
6
  import { dispatchStore } from "#storage/dispatchStore.ts";
8
7
  import { recordModel } from "#storage/model.ts";
@@ -10,10 +9,6 @@ import { readRecords } from "#storage/readRecords.ts";
10
9
  import { recordShape } from "#storage/recordShape.ts";
11
10
  import { recordValues } from "#storage/recordValue.ts";
12
11
 
13
- export function renderStatus(current: Parameters<typeof render>[0]) {
14
- return render(current);
15
- }
16
-
17
12
  export const fleetStatus = Effect.fn("FleetHost.fleetStatus")(function* (host: FleetHost, workId?: string) {
18
13
  const repository = yield* makeRepository(recordModel(), {
19
14
  idColumn: "work_id",
@@ -15,7 +15,8 @@ import {
15
15
  Stream,
16
16
  SubscriptionRef,
17
17
  } from "effect";
18
- import { type FleetHost, HostConfig, HostConfigRejected, type HostFailure, readHostConfig, resolveHost } from "#host/config.ts";
18
+ import { type FleetHost, HostConfig, HostConfigRejected, type HostFailure, resolveHost } from "#host/config.ts";
19
+ import { readHostConfig } from "#host/configFile.ts";
19
20
  import { changedSettings, withReloadable } from "#host/configSettings.ts";
20
21
  import { hostGuidance } from "#host/guidance.ts";
21
22
 
@@ -10,7 +10,7 @@ import { recordDecisionResponse } from "#host/recordDecisionResponse.ts";
10
10
  import { executing } from "#model.ts";
11
11
 
12
12
  const { searchSnapshot, watchChanges } = board;
13
- const { ResponseConfig, Responses } = responses;
13
+ const { Responses, Workspace, WorkspaceConfig } = responses;
14
14
 
15
15
  export function releaseWork(host: FleetHost, workIds: readonly string[], parts: HostParts = productionParts(host)) {
16
16
  return withDecisionResponses(host, releaseItems(host, workIds)).pipe(Effect.provide(hostLayer(host, parts)));
@@ -27,7 +27,10 @@ export function decisionResponses(host: FleetHost) {
27
27
  const changes = yield* watchChanges(host.boardDir);
28
28
  const index = (yield* searchSnapshot(host.boardDir, undefined, changes)).pipe(Effect.provide(NodeServices.layer));
29
29
  yield* index.pipe(Effect.ignore, Effect.forkScoped);
30
- return Responses.layer.pipe(Layer.provide(Layer.succeed(ResponseConfig, { changes, enabled: true, index, root: host.boardDir })));
30
+ return Responses.layer.pipe(
31
+ Layer.provide(Workspace.layer),
32
+ Layer.provide(Layer.succeed(WorkspaceConfig, { changes, index, root: host.boardDir, writable: true })),
33
+ );
31
34
  }),
32
35
  );
33
36
  }
@@ -51,6 +51,7 @@ export function hostPolicy(host: FleetHost) {
51
51
  operationId: () => crypto.randomUUID(),
52
52
  quota: { expiresAt: 0, observedAt: 0 },
53
53
  readGuidance: (role) => guidance[role].pipe(Effect.mapError((error) => new ServiceUnavailable({ message: error.message }))),
54
+ readRetryMs: applied.readRetryMs,
54
55
  refreshQuota: refreshQuota(applied),
55
56
  stallMinutes: applied.stallMinutes,
56
57
  ...(applied.worker === undefined ? {} : { sharedEnvironment: applied.worker.sharedEnvironment }),
@@ -1,3 +1,3 @@
1
1
  export const NAME = "work-fleet";
2
2
  export const TITLE = "Work Fleet";
3
- export const VERSION = "0.3.0";
3
+ export const VERSION = "0.5.0";
package/src/policy.ts CHANGED
@@ -120,6 +120,7 @@ export interface FleetConfiguration {
120
120
  readonly operationId: () => string;
121
121
  readonly quota: { readonly available?: number; readonly observedAt: number; readonly expiresAt: number };
122
122
  readonly readGuidance?: (role: "worker" | "reviewer") => Effect.Effect<string | undefined, FleetError>;
123
+ readonly readRetryMs?: number;
123
124
  readonly refreshQuota?: () => Effect.Effect<FleetConfiguration["quota"], FleetError>;
124
125
  readonly reviewerGuidance?: string;
125
126
  readonly sharedEnvironment?: string;
@@ -27,6 +27,7 @@ function holdsAreas(record: FleetRecord, now: number, mergeWaitMs: number): bool
27
27
  return (
28
28
  !["completed", "released"].includes(record.stage)
29
29
  && record.attempts.length > 0
30
+ && (record.stage !== "prepared" || executing(record))
30
31
  && record.mergeAreasReleased !== true
31
32
  && !mergeWaitExpired(record, now, mergeWaitMs)
32
33
  );