@usine/config 0.2.1 → 0.3.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 (73) hide show
  1. package/README.md +30 -18
  2. package/dist/agent.d.ts +2 -3
  3. package/dist/agent.d.ts.map +1 -1
  4. package/dist/agent.js +2 -3
  5. package/dist/agent.js.map +1 -1
  6. package/dist/board.d.ts +8 -6
  7. package/dist/board.d.ts.map +1 -1
  8. package/dist/board.js +13 -6
  9. package/dist/board.js.map +1 -1
  10. package/dist/checks.d.ts +3 -0
  11. package/dist/checks.d.ts.map +1 -1
  12. package/dist/checks.js +3 -0
  13. package/dist/checks.js.map +1 -1
  14. package/dist/factory.d.ts +75 -22
  15. package/dist/factory.d.ts.map +1 -1
  16. package/dist/factory.js +84 -29
  17. package/dist/factory.js.map +1 -1
  18. package/dist/file.d.ts +3 -3
  19. package/dist/file.d.ts.map +1 -1
  20. package/dist/file.js +3 -3
  21. package/dist/file.js.map +1 -1
  22. package/dist/frontmatter.d.ts +1 -1
  23. package/dist/frontmatter.d.ts.map +1 -1
  24. package/dist/frontmatter.js +1 -1
  25. package/dist/frontmatter.js.map +1 -1
  26. package/dist/instance/diagnostics.d.ts +1 -1
  27. package/dist/instance/diagnostics.d.ts.map +1 -1
  28. package/dist/instance/diagnostics.js +1 -1
  29. package/dist/instance/diagnostics.js.map +1 -1
  30. package/dist/instance/instance.d.ts +1 -1
  31. package/dist/instance/instance.d.ts.map +1 -1
  32. package/dist/instance/instance.js +2 -2
  33. package/dist/instance/instance.js.map +1 -1
  34. package/dist/instance/references.d.ts +5 -6
  35. package/dist/instance/references.d.ts.map +1 -1
  36. package/dist/instance/references.js +9 -9
  37. package/dist/instance/references.js.map +1 -1
  38. package/dist/instance-folder.d.ts +1 -1
  39. package/dist/instance-folder.d.ts.map +1 -1
  40. package/dist/instance-folder.js +13 -1
  41. package/dist/instance-folder.js.map +1 -1
  42. package/dist/label.d.ts +5 -5
  43. package/dist/label.d.ts.map +1 -1
  44. package/dist/label.js +5 -5
  45. package/dist/label.js.map +1 -1
  46. package/dist/loop.d.ts +4 -3
  47. package/dist/loop.d.ts.map +1 -1
  48. package/dist/loop.js +6 -5
  49. package/dist/loop.js.map +1 -1
  50. package/dist/parser.d.ts +6 -6
  51. package/dist/parser.d.ts.map +1 -1
  52. package/dist/parser.js +27 -10
  53. package/dist/parser.js.map +1 -1
  54. package/dist/payload-slices.d.ts +4 -7
  55. package/dist/payload-slices.d.ts.map +1 -1
  56. package/dist/payload-slices.js +30 -25
  57. package/dist/payload-slices.js.map +1 -1
  58. package/dist/selector.d.ts +6 -3
  59. package/dist/selector.d.ts.map +1 -1
  60. package/dist/selector.js +10 -6
  61. package/dist/selector.js.map +1 -1
  62. package/dist/trigger.d.ts +4 -7
  63. package/dist/trigger.d.ts.map +1 -1
  64. package/dist/trigger.js +6 -9
  65. package/dist/trigger.js.map +1 -1
  66. package/dist/yaml/document.d.ts +2 -2
  67. package/dist/yaml/document.js +3 -3
  68. package/dist/yaml/document.js.map +1 -1
  69. package/dist/yaml/writer.d.ts +4 -4
  70. package/dist/yaml/writer.d.ts.map +1 -1
  71. package/dist/yaml/writer.js +5 -6
  72. package/dist/yaml/writer.js.map +1 -1
  73. package/package.json +6 -5
package/README.md CHANGED
@@ -1,15 +1,16 @@
1
1
  # @usine/config
2
2
 
3
- Reads and edits the authored files in a [usine](https://github.com/basaltbytes/usine) instance folder:
3
+ Reads and edits the files of a [usine](https://github.com/basaltbytes/usine) instance folder. The [`usine.yaml`](https://docs.usine.dev/reference/configuration/usine-yaml/), [loop file](https://docs.usine.dev/reference/configuration/loop-files/), and [agent file](https://docs.usine.dev/reference/configuration/agent-files/) references describe every key.
4
4
 
5
- - `usine.yaml` defines the schema version, factory-wide GitHub labels, managed-repo registry, settings, workers, webhook mode, and operator login.
6
- - `loops/*.md` combines strict YAML frontmatter with a Markdown prompt. The parser type-checks the trigger and every `{{ CEL expression }}` against that trigger's payload.
7
- - `agents/*.md` records a named agent's harness, model, effort, optional parent model, optional parent effort, and persona.
8
- - `updateYaml` and `updateFrontmatter` change one path through the YAML document tree while retaining comments, key order, and the Markdown body.
5
+ - `usine.yaml`: schema version, factory-wide GitHub labels, the managed-repo registry, settings, workers, webhook mode, and operator login.
6
+ - `loops/*.md`: strict YAML frontmatter and a Markdown prompt. The parser type-checks the trigger and every `{{ CEL expression }}` against the trigger's payload.
7
+ - `agents/*.md`: harness, model, effort, optional parent model and parent effort, and persona.
9
8
 
10
- Authored keys are snake_case (`poll_interval`, `max_concurrent_attempts`); the decoded domain types expose camelCase fields (`pollInterval`, `maxConcurrentAttempts`). The rename is part of the schema, so edits encode back to the authored casing.
9
+ `updateYaml` and `updateFrontmatter` change one path in the YAML tree and keep comments, key order, and the Markdown body. Files use snake_case keys (`poll_interval`, `max_concurrent_attempts`); decoded types use camelCase (`pollInterval`, `maxConcurrentAttempts`). The schema does the rename, so edits keep snake_case.
11
10
 
12
- Declare the GitHub labels used by Loops and Board lanes once. Usine provisions missing declarations in every managed repository; existing definitions are never edited or deleted. `color` is an optional six-digit hexadecimal value without `#`:
11
+ ## Labels and lanes
12
+
13
+ Declare the labels that loops and lanes use once. usine creates missing labels in every managed repo and never edits or deletes labels:
13
14
 
14
15
  ```yaml
15
16
  labels:
@@ -19,11 +20,9 @@ labels:
19
20
  - name: ready-for-review
20
21
  ```
21
22
 
22
- Label names follow GitHub's contract: at most 50 characters, no surrounding whitespace, and unique without regard to case; descriptions are at most 100 characters. A lane source or trigger-expression string literal that refers to an undeclared label becomes a non-blocking entry in `LoadedInstance.warnings`; prose in prompt bodies is not inspected.
23
-
24
- Set `settings.max_children` to the number of child sessions one Attempt may spawn. The default is 4, and the count includes sequential and concurrent child sessions.
23
+ An undeclared label in a lane source or a trigger expression string literal adds a non-blocking entry to `LoadedInstance.warnings`. Prompt prose isn't checked.
25
24
 
26
- Each managed repository can define an exclusive Board lane map. A lane accepts one or more GitHub label or Item-state sources:
25
+ Each managed repo can define an exclusive board lane map of label and item state sources:
27
26
 
28
27
  ```yaml
29
28
  repos:
@@ -38,16 +37,29 @@ repos:
38
37
  - state: pr-open
39
38
  ```
40
39
 
41
- Valid state sources are `issue-open`, `issue-closed`, `pr-open`, `pr-closed`, and `pr-merged`. Lane names and sources must be unique within the repository. Repositories without an authored map receive proposed lanes from the labels observed during GitHub polling.
40
+ `isSettledItemState` reports the settled states, `issue-closed`, `pr-closed`, and `pr-merged`; the board places such an item by state alone, whatever its labels. A repo without a lane map gets proposed lanes from the labels that polling finds.
41
+
42
+ ## Settings and workers
43
+
44
+ `settings.max_children` (default 4) limits the child sessions, sequential and concurrent, that one attempt can start. Without a `workers` key, the parser adds a local worker named `local` that runs one attempt at a time. A worker's `kind` (`local`, `exe.dev`, or `ssh`) selects its executor.
45
+
46
+ Issue and pull request loops accept `max_cycles`, a positive integer (default 3): the number of runs each loop can open on an item between operator resets. The first admission past it blocks runs from every loop on the item. Schedule loops are exempt. See [Write review and fix loops](https://docs.usine.dev/guides/review-fix-loops/).
47
+
48
+ ## Load an instance
49
+
50
+ `InstanceFolder.load` takes an `InstanceDirectory` (`@usine/core/instance`) and returns one atomic `LoadedInstance` snapshot:
42
51
 
43
- `workers` must contain at least one uniquely named worker. If the key is absent, the parser supplies one local worker named `local` with a concurrency limit of one. Set `kind` to `local` or `exe.dev` to select its executor, and set `max_concurrent_attempts` to control how many Attempts it can run at once.
52
+ - An invalid `usine.yaml` fails the load with `FactoryConfigError`.
53
+ - An invalid loop goes to `quarantinedLoops` with a `ConfigFileError` naming the file and stage. Valid loops still load.
54
+ - Invalid agents go to `invalidAgents`, and loops that use them are quarantined.
55
+ - Non-blocking reference diagnostics go to `warnings`.
44
56
 
45
- `InstanceFolder.load` takes an `InstanceDirectory` (from `@usine/core/instance`) and returns one atomic `LoadedInstance` snapshot. An invalid `usine.yaml` fails the load with a `FactoryConfigError`; an invalid loop enters `quarantinedLoops` with a `ConfigFileError` naming the file and stage, without blocking valid siblings. Invalid agents appear in `invalidAgents`, and loops that name them are quarantined. A `LoadedInstance` always satisfies its own reference rule: every loaded loop names a rostered agent and registered selector entries, enforced at construction. Non-blocking authored-reference diagnostics appear in `warnings`. The `ConfigError` union covers both failure types.
57
+ `ConfigError` is the union of both errors. The `LoadedInstance` constructor enforces that every loop references a rostered agent and registered selector entries.
46
58
 
47
- Import factory settings and registry types from `@usine/config/factory`, Board lanes from `@usine/config/board`, and label declarations from `@usine/config/label`. Authored loop, trigger, and agent schemas are available at `/loop`, `/trigger`, and `/agent`; `/selector` provides both the selector schema and matching operation. `/instance` provides `LoadedInstance` and its warnings, while `/file` provides file paths and typed loading failures. Shared identities and agent personas come from their owning `@usine/core` modules.
59
+ ## Imports
48
60
 
49
- The package exports Effect services and layers. Import `InstanceFolder` from `@usine/config/instance-folder` and `ConfigParser` from `@usine/config/parser`. Supply `FileSystem.FileSystem` and `Path.Path` implementations when composing `InstanceFolder.layer`; `ConfigParser.layer` requires `ExpressionEngine` from `@usine/expression/expression-engine`.
61
+ Subpaths: `/factory` (settings and registry), `/board` (lanes), `/label`, `/loop`, `/trigger`, `/agent`, `/selector` (schema and matching), `/instance` (`LoadedInstance` and warnings), and `/file` (file paths and loading failures). Shared identities and agent personas come from `@usine/core`.
50
62
 
51
- All APIs remain 0.x and can change between minor releases.
63
+ `InstanceFolder` (`/instance-folder`) and `ConfigParser` (`/parser`) are Effect services. `InstanceFolder.layer` needs `FileSystem.FileSystem` and `Path.Path`; `ConfigParser.layer` needs `ExpressionEngine` from `@usine/expression/expression-engine`.
52
64
 
53
- Issue and pull-request Loops accept `max_cycles`, a positive integer defaulting to three. Each Loop may open that many Runs on an Item between operator resets. The first excess admission blocks further Runs from every Loop on the Item. Schedule Loops are exempt. See the [review/fix guide](../../docs/guides/review-fix-loops.md) for authored examples.
65
+ The 0.x APIs can change between minor releases.
package/dist/agent.d.ts CHANGED
@@ -10,16 +10,15 @@ declare const AgentFile_base: Schema.Class<AgentFile, Schema.Struct<{
10
10
  readonly parentEffort: Schema.Option<Schema.Literals<readonly ["off", "minimal", "low", "medium", "high", "xhigh", "max"]>>;
11
11
  readonly persona: Schema.brand<Schema.String, "AgentPersona">;
12
12
  }>, {}>;
13
- /** An Agent's authored brain, parent overrides, and standing instructions, named by its Markdown filename. */
13
+ /** A parsed Agent Markdown file; `name` comes from the filename. */
14
14
  export declare class AgentFile extends AgentFile_base {
15
- /** The roster member represented by this file, including persona and parent overrides. */
16
15
  get agent(): Agent;
17
16
  }
18
17
  declare const InvalidAgent_base: Schema.Class<InvalidAgent, Schema.Struct<{
19
18
  readonly file: Schema.brand<Schema.NonEmptyString, "InstancePath">;
20
19
  readonly error: typeof ConfigFileError;
21
20
  }>, {}>;
22
- /** An Agent file excluded from the roster because it could not be loaded. */
21
+ /** An Agent file that failed to load and is left out of the roster. */
23
22
  export declare class InvalidAgent extends InvalidAgent_base {
24
23
  }
25
24
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../src/agent.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAA2B,MAAM,mBAAmB,CAAC;AAEnE,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,EAAgB,eAAe,EAAE,MAAM,WAAW,CAAC;;;;;;;;;;AAE1D,8GAA8G;AAC9G,qBAAa,SAAU,SAAQ,cAQ7B;IACA,0FAA0F;IAC1F,IAAI,KAAK,IAAI,KAAK,CAQjB;CACF;;;;;AAED,6EAA6E;AAC7E,qBAAa,YAAa,SAAQ,iBAGhC;CAAG"}
1
+ {"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../src/agent.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAA2B,MAAM,mBAAmB,CAAC;AAEnE,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,EAAgB,eAAe,EAAE,MAAM,WAAW,CAAC;;;;;;;;;;AAE1D,oEAAoE;AACpE,qBAAa,SAAU,SAAQ,cAQ7B;IACA,IAAI,KAAK,IAAI,KAAK,CAQjB;CACF;;;;;AAED,uEAAuE;AACvE,qBAAa,YAAa,SAAQ,iBAGhC;CAAG"}
package/dist/agent.js CHANGED
@@ -2,7 +2,7 @@ import { Agent, AgentPersona, AgentName } from "@usine/core/agent";
2
2
  import { BrainSpec, Effort, Harness, ModelId } from "@usine/core/agent/brain";
3
3
  import { Schema } from "effect";
4
4
  import { InstancePath, ConfigFileError } from "./file.js";
5
- /** An Agent's authored brain, parent overrides, and standing instructions, named by its Markdown filename. */
5
+ /** A parsed Agent Markdown file; `name` comes from the filename. */
6
6
  export class AgentFile extends Schema.Class("@usine/config/AgentFile")({
7
7
  name: AgentName,
8
8
  harness: Harness,
@@ -12,7 +12,6 @@ export class AgentFile extends Schema.Class("@usine/config/AgentFile")({
12
12
  parentEffort: Schema.Option(Effort),
13
13
  persona: AgentPersona,
14
14
  }) {
15
- /** The roster member represented by this file, including persona and parent overrides. */
16
15
  get agent() {
17
16
  return new Agent({
18
17
  name: this.name,
@@ -23,7 +22,7 @@ export class AgentFile extends Schema.Class("@usine/config/AgentFile")({
23
22
  });
24
23
  }
25
24
  }
26
- /** An Agent file excluded from the roster because it could not be loaded. */
25
+ /** An Agent file that failed to load and is left out of the roster. */
27
26
  export class InvalidAgent extends Schema.Class("@usine/config/InvalidAgent")({
28
27
  file: InstancePath,
29
28
  error: ConfigFileError,
package/dist/agent.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"agent.js","sourceRoot":"","sources":["../src/agent.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AACnE,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAC;AAC9E,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAE1D,8GAA8G;AAC9G,MAAM,OAAO,SAAU,SAAQ,MAAM,CAAC,KAAK,CAAY,yBAAyB,CAAC,CAAC;IAChF,IAAI,EAAE,SAAS;IACf,OAAO,EAAE,OAAO;IAChB,KAAK,EAAE,OAAO;IACd,MAAM,EAAE,MAAM;IACd,WAAW,EAAE,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC;IACnC,YAAY,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC;IACnC,OAAO,EAAE,YAAY;CACtB,CAAC;IACA,0FAA0F;IAC1F,IAAI,KAAK;QACP,OAAO,IAAI,KAAK,CAAC;YACf,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,OAAO,EAAE,IAAI,CAAC,OAAO;YACrB,KAAK,EAAE,IAAI,SAAS,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC;YACvF,WAAW,EAAE,IAAI,CAAC,WAAW;YAC7B,YAAY,EAAE,IAAI,CAAC,YAAY;SAChC,CAAC,CAAC;IACL,CAAC;CACF;AAED,6EAA6E;AAC7E,MAAM,OAAO,YAAa,SAAQ,MAAM,CAAC,KAAK,CAAe,4BAA4B,CAAC,CAAC;IACzF,IAAI,EAAE,YAAY;IAClB,KAAK,EAAE,eAAe;CACvB,CAAC;CAAG"}
1
+ {"version":3,"file":"agent.js","sourceRoot":"","sources":["../src/agent.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AACnE,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAC;AAC9E,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAE1D,oEAAoE;AACpE,MAAM,OAAO,SAAU,SAAQ,MAAM,CAAC,KAAK,CAAY,yBAAyB,CAAC,CAAC;IAChF,IAAI,EAAE,SAAS;IACf,OAAO,EAAE,OAAO;IAChB,KAAK,EAAE,OAAO;IACd,MAAM,EAAE,MAAM;IACd,WAAW,EAAE,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC;IACnC,YAAY,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC;IACnC,OAAO,EAAE,YAAY;CACtB,CAAC;IACA,IAAI,KAAK;QACP,OAAO,IAAI,KAAK,CAAC;YACf,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,OAAO,EAAE,IAAI,CAAC,OAAO;YACrB,KAAK,EAAE,IAAI,SAAS,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC;YACvF,WAAW,EAAE,IAAI,CAAC,WAAW;YAC7B,YAAY,EAAE,IAAI,CAAC,YAAY;SAChC,CAAC,CAAC;IACL,CAAC;CACF;AAED,uEAAuE;AACvE,MAAM,OAAO,YAAa,SAAQ,MAAM,CAAC,KAAK,CAAe,4BAA4B,CAAC,CAAC;IACzF,IAAI,EAAE,YAAY;IAClB,KAAK,EAAE,eAAe;CACvB,CAAC;CAAG"}
package/dist/board.d.ts CHANGED
@@ -1,30 +1,32 @@
1
1
  import { Schema } from "effect";
2
- /** The operator-visible name of one lane on a Project's Board. */
3
2
  export declare const LaneName: Schema.brand<Schema.String, "LaneName">;
4
3
  export type LaneName = typeof LaneName.Type;
5
- /** An issue or pull-request state that can place an Item in a Board lane. */
6
4
  export declare const LaneItemState: Schema.Literals<readonly ["issue-open", "issue-closed", "pr-open", "pr-closed", "pr-merged"]>;
7
5
  export type LaneItemState = typeof LaneItemState.Type;
6
+ /**
7
+ * Returns whether `state` is a closed issue or a closed or merged pull request, which the Board
8
+ * places by state regardless of labels.
9
+ */
10
+ export declare const isSettledItemState: (state: LaneItemState) => boolean;
8
11
  declare const LabelLaneSource_base: Schema.Class<LabelLaneSource, Schema.Struct<{
9
12
  readonly label: Schema.brand<Schema.String, "GitHubLabelName">;
10
13
  }>, {}>;
11
- /** Places Items with this GitHub label in the lane; Board moves can write the label. */
14
+ /** A lane source that matches Items with this GitHub label, which Board moves can set. */
12
15
  export declare class LabelLaneSource extends LabelLaneSource_base {
13
16
  }
14
17
  declare const StateLaneSource_base: Schema.Class<StateLaneSource, Schema.Struct<{
15
18
  readonly state: Schema.Literals<readonly ["issue-open", "issue-closed", "pr-open", "pr-closed", "pr-merged"]>;
16
19
  }>, {}>;
17
- /** Places Items with this GitHub state in the lane; Board moves cannot change the state. */
20
+ /** A lane source that matches Items in this state, which Board moves can't change. */
18
21
  export declare class StateLaneSource extends StateLaneSource_base {
19
22
  }
20
- /** A GitHub label or Item state that routes matching Items to a Board lane. */
21
23
  export declare const LaneSource: Schema.Union<readonly [typeof LabelLaneSource, typeof StateLaneSource]>;
22
24
  export type LaneSource = typeof LaneSource.Type;
23
25
  declare const Lane_base: Schema.Class<Lane, Schema.Struct<{
24
26
  readonly name: Schema.brand<Schema.String, "LaneName">;
25
27
  readonly sources: Schema.$Array<Schema.Union<readonly [typeof LabelLaneSource, typeof StateLaneSource]>>;
26
28
  }>, {}>;
27
- /** A named Board column containing Items that match any of its configured sources. */
29
+ /** A Board column that contains the Items matching any of `sources`. */
28
30
  export declare class Lane extends Lane_base {
29
31
  }
30
32
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"board.d.ts","sourceRoot":"","sources":["../src/board.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAIhC,kEAAkE;AAClE,eAAO,MAAM,QAAQ,yCAEpB,CAAC;AAEF,MAAM,MAAM,QAAQ,GAAG,OAAO,QAAQ,CAAC,IAAI,CAAC;AAE5C,6EAA6E;AAC7E,eAAO,MAAM,aAAa,+FAMxB,CAAC;AAEH,MAAM,MAAM,aAAa,GAAG,OAAO,aAAa,CAAC,IAAI,CAAC;;;;AAEtD,wFAAwF;AACxF,qBAAa,eAAgB,SAAQ,oBAIpC;CAAG;;;;AAEJ,4FAA4F;AAC5F,qBAAa,eAAgB,SAAQ,oBAIpC;CAAG;AAEJ,+EAA+E;AAC/E,eAAO,MAAM,UAAU,yEAAmD,CAAC;AAE3E,MAAM,MAAM,UAAU,GAAG,OAAO,UAAU,CAAC,IAAI,CAAC;;;;;AAEhD,sFAAsF;AACtF,qBAAa,IAAK,SAAQ,SAGxB;CAAG"}
1
+ {"version":3,"file":"board.d.ts","sourceRoot":"","sources":["../src/board.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAIhC,eAAO,MAAM,QAAQ,yCAEpB,CAAC;AAEF,MAAM,MAAM,QAAQ,GAAG,OAAO,QAAQ,CAAC,IAAI,CAAC;AAE5C,eAAO,MAAM,aAAa,+FAMxB,CAAC;AAEH,MAAM,MAAM,aAAa,GAAG,OAAO,aAAa,CAAC,IAAI,CAAC;AAQtD;;;GAGG;AACH,eAAO,MAAM,kBAAkB,UAAW,aAAa,KAAG,OAAyC,CAAC;;;;AAEpG,0FAA0F;AAC1F,qBAAa,eAAgB,SAAQ,oBAIpC;CAAG;;;;AAEJ,sFAAsF;AACtF,qBAAa,eAAgB,SAAQ,oBAIpC;CAAG;AAEJ,eAAO,MAAM,UAAU,yEAAmD,CAAC;AAE3E,MAAM,MAAM,UAAU,GAAG,OAAO,UAAU,CAAC,IAAI,CAAC;;;;;AAEhD,wEAAwE;AACxE,qBAAa,IAAK,SAAQ,SAGxB;CAAG"}
package/dist/board.js CHANGED
@@ -1,9 +1,7 @@
1
1
  import { Schema } from "effect";
2
2
  import { nonBlankString } from "./checks.js";
3
3
  import { GitHubLabelName } from "./label.js";
4
- /** The operator-visible name of one lane on a Project's Board. */
5
4
  export const LaneName = nonBlankString("Expected a non-blank lane name").pipe(Schema.brand("LaneName"));
6
- /** An issue or pull-request state that can place an Item in a Board lane. */
7
5
  export const LaneItemState = Schema.Literals([
8
6
  "issue-open",
9
7
  "issue-closed",
@@ -11,19 +9,28 @@ export const LaneItemState = Schema.Literals([
11
9
  "pr-closed",
12
10
  "pr-merged",
13
11
  ]);
14
- /** Places Items with this GitHub label in the lane; Board moves can write the label. */
12
+ const SETTLED_ITEM_STATES = new Set([
13
+ "issue-closed",
14
+ "pr-closed",
15
+ "pr-merged",
16
+ ]);
17
+ /**
18
+ * Returns whether `state` is a closed issue or a closed or merged pull request, which the Board
19
+ * places by state regardless of labels.
20
+ */
21
+ export const isSettledItemState = (state) => SETTLED_ITEM_STATES.has(state);
22
+ /** A lane source that matches Items with this GitHub label, which Board moves can set. */
15
23
  export class LabelLaneSource extends Schema.Class("@usine/config/LabelLaneSource")({
16
24
  label: GitHubLabelName,
17
25
  }) {
18
26
  }
19
- /** Places Items with this GitHub state in the lane; Board moves cannot change the state. */
27
+ /** A lane source that matches Items in this state, which Board moves can't change. */
20
28
  export class StateLaneSource extends Schema.Class("@usine/config/StateLaneSource")({
21
29
  state: LaneItemState,
22
30
  }) {
23
31
  }
24
- /** A GitHub label or Item state that routes matching Items to a Board lane. */
25
32
  export const LaneSource = Schema.Union([LabelLaneSource, StateLaneSource]);
26
- /** A named Board column containing Items that match any of its configured sources. */
33
+ /** A Board column that contains the Items matching any of `sources`. */
27
34
  export class Lane extends Schema.Class("@usine/config/Lane")({
28
35
  name: LaneName,
29
36
  sources: Schema.Array(LaneSource).check(Schema.isNonEmpty()),
package/dist/board.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"board.js","sourceRoot":"","sources":["../src/board.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAE7C,kEAAkE;AAClE,MAAM,CAAC,MAAM,QAAQ,GAAG,cAAc,CAAC,gCAAgC,CAAC,CAAC,IAAI,CAC3E,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,CACzB,CAAC;AAIF,6EAA6E;AAC7E,MAAM,CAAC,MAAM,aAAa,GAAG,MAAM,CAAC,QAAQ,CAAC;IAC3C,YAAY;IACZ,cAAc;IACd,SAAS;IACT,WAAW;IACX,WAAW;CACZ,CAAC,CAAC;AAIH,wFAAwF;AACxF,MAAM,OAAO,eAAgB,SAAQ,MAAM,CAAC,KAAK,CAAkB,+BAA+B,CAAC,CACjG;IACE,KAAK,EAAE,eAAe;CACvB,CACF;CAAG;AAEJ,4FAA4F;AAC5F,MAAM,OAAO,eAAgB,SAAQ,MAAM,CAAC,KAAK,CAAkB,+BAA+B,CAAC,CACjG;IACE,KAAK,EAAE,aAAa;CACrB,CACF;CAAG;AAEJ,+EAA+E;AAC/E,MAAM,CAAC,MAAM,UAAU,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,eAAe,EAAE,eAAe,CAAC,CAAC,CAAC;AAI3E,sFAAsF;AACtF,MAAM,OAAO,IAAK,SAAQ,MAAM,CAAC,KAAK,CAAO,oBAAoB,CAAC,CAAC;IACjE,IAAI,EAAE,QAAQ;IACd,OAAO,EAAE,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,UAAU,EAAE,CAAC;CAC7D,CAAC;CAAG"}
1
+ {"version":3,"file":"board.js","sourceRoot":"","sources":["../src/board.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAE7C,MAAM,CAAC,MAAM,QAAQ,GAAG,cAAc,CAAC,gCAAgC,CAAC,CAAC,IAAI,CAC3E,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,CACzB,CAAC;AAIF,MAAM,CAAC,MAAM,aAAa,GAAG,MAAM,CAAC,QAAQ,CAAC;IAC3C,YAAY;IACZ,cAAc;IACd,SAAS;IACT,WAAW;IACX,WAAW;CACZ,CAAC,CAAC;AAIH,MAAM,mBAAmB,GAA+B,IAAI,GAAG,CAAC;IAC9D,cAAc;IACd,WAAW;IACX,WAAW;CACZ,CAAC,CAAC;AAEH;;;GAGG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,KAAoB,EAAW,EAAE,CAAC,mBAAmB,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;AAEpG,0FAA0F;AAC1F,MAAM,OAAO,eAAgB,SAAQ,MAAM,CAAC,KAAK,CAAkB,+BAA+B,CAAC,CACjG;IACE,KAAK,EAAE,eAAe;CACvB,CACF;CAAG;AAEJ,sFAAsF;AACtF,MAAM,OAAO,eAAgB,SAAQ,MAAM,CAAC,KAAK,CAAkB,+BAA+B,CAAC,CACjG;IACE,KAAK,EAAE,aAAa;CACrB,CACF;CAAG;AAEJ,MAAM,CAAC,MAAM,UAAU,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,eAAe,EAAE,eAAe,CAAC,CAAC,CAAC;AAI3E,wEAAwE;AACxE,MAAM,OAAO,IAAK,SAAQ,MAAM,CAAC,KAAK,CAAO,oBAAoB,CAAC,CAAC;IACjE,IAAI,EAAE,QAAQ;IACd,OAAO,EAAE,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,UAAU,EAAE,CAAC;CAC7D,CAAC;CAAG"}
package/dist/checks.d.ts CHANGED
@@ -1,3 +1,6 @@
1
+ import { type RepositoryName } from "@usine/core/repository";
1
2
  import { Schema } from "effect";
2
3
  export declare const nonBlankString: (message: string) => Schema.String;
4
+ /** Returns a filter that rejects two names GitHub treats as the same repository. */
5
+ export declare const uniqueRepositoryNames: <A>(nameOf: (value: A) => RepositoryName) => import("effect/SchemaAST").Filter<readonly A[]>;
3
6
  //# sourceMappingURL=checks.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"checks.d.ts","sourceRoot":"","sources":["../src/checks.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEhC,eAAO,MAAM,cAAc,YAAa,MAAM,kBACa,CAAC"}
1
+ {"version":3,"file":"checks.d.ts","sourceRoot":"","sources":["../src/checks.ts"],"names":[],"mappings":"AAAA,OAAO,EAA2B,KAAK,cAAc,EAAE,MAAM,wBAAwB,CAAC;AACtF,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEhC,eAAO,MAAM,cAAc,YAAa,MAAM,kBACa,CAAC;AAE5D,oFAAoF;AACpF,eAAO,MAAM,qBAAqB,GAAI,CAAC,UAAU,CAAC,KAAK,EAAE,CAAC,KAAK,cAAc,oDAK1E,CAAC"}
package/dist/checks.js CHANGED
@@ -1,3 +1,6 @@
1
+ import { canonicalRepositoryName } from "@usine/core/repository";
1
2
  import { Schema } from "effect";
2
3
  export const nonBlankString = (message) => Schema.String.check(Schema.isPattern(/\S/u, { message }));
4
+ /** Returns a filter that rejects two names GitHub treats as the same repository. */
5
+ export const uniqueRepositoryNames = (nameOf) => Schema.makeFilter((values) => new Set(values.map((value) => canonicalRepositoryName(nameOf(value)))).size === values.length, { message: "Expected unique repository names, ignoring case" });
3
6
  //# sourceMappingURL=checks.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"checks.js","sourceRoot":"","sources":["../src/checks.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEhC,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,OAAe,EAAE,EAAE,CAChD,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC"}
1
+ {"version":3,"file":"checks.js","sourceRoot":"","sources":["../src/checks.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAuB,MAAM,wBAAwB,CAAC;AACtF,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEhC,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,OAAe,EAAE,EAAE,CAChD,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC;AAE5D,oFAAoF;AACpF,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAI,MAAoC,EAAE,EAAE,CAC/E,MAAM,CAAC,UAAU,CACf,CAAC,MAAM,EAAE,EAAE,CACT,IAAI,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,uBAAuB,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC,MAAM,EAC/F,EAAE,OAAO,EAAE,iDAAiD,EAAE,CAC/D,CAAC"}
package/dist/factory.d.ts CHANGED
@@ -1,46 +1,94 @@
1
1
  import { Schema } from "effect";
2
2
  import { Lane } from "./board.ts";
3
3
  import { DeclaredLabel } from "./label.ts";
4
- /** A factory-defined repository grouping that Loops can select by tag. */
4
+ /** A repository tag that Loop selectors match. */
5
5
  export declare const RegistryTag: Schema.brand<Schema.String, "RegistryTag">;
6
6
  export type RegistryTag = typeof RegistryTag.Type;
7
- /** GitHub login identifying the human operator for this factory. */
8
- export declare const OperatorLogin: Schema.brand<Schema.String, "OperatorLogin">;
9
- export type OperatorLogin = typeof OperatorLogin.Type;
10
7
  declare const ManagedRepo_base: Schema.Class<ManagedRepo, Schema.Struct<{
11
8
  readonly name: Schema.brand<Schema.String, "RepositoryName">;
12
9
  readonly tags: Schema.withDecodingDefaultKey<Schema.$Array<Schema.brand<Schema.String, "RegistryTag">>, never>;
13
10
  readonly lanes: Schema.withDecodingDefaultKey<Schema.$Array<typeof Lane>, never>;
14
11
  }>, {}>;
15
- /** A repository the factory manages, with its selector tags and configured Board lanes. */
16
12
  export declare class ManagedRepo extends ManagedRepo_base {
17
13
  }
18
- declare const Worker_base: Schema.Class<Worker, Schema.Struct<{
14
+ declare const LocalWorker_base: Schema.Class<LocalWorker, Schema.Struct<{
19
15
  readonly name: Schema.brand<Schema.String, "WorkerName">;
20
- readonly kind: Schema.Literals<readonly ["local", "exe.dev"]>;
16
+ readonly kind: Schema.Literal<"local">;
21
17
  readonly maxConcurrentAttempts: Schema.withDecodingDefaultKey<Schema.Int, never>;
22
18
  }>, {}>;
23
- /** A named place to execute Attempts, with the number it may run at the same time. */
24
- export declare class Worker extends Worker_base {
19
+ /** A Worker that runs Attempts on the orchestrator's machine. */
20
+ export declare class LocalWorker extends LocalWorker_base {
25
21
  }
22
+ declare const ExeDevWorker_base: Schema.Class<ExeDevWorker, Schema.Struct<{
23
+ readonly name: Schema.brand<Schema.String, "WorkerName">;
24
+ readonly kind: Schema.Literal<"exe.dev">;
25
+ readonly maxConcurrentAttempts: Schema.withDecodingDefaultKey<Schema.Int, never>;
26
+ }>, {}>;
27
+ /** A Worker on the exe.dev VM with the same name. */
28
+ export declare class ExeDevWorker extends ExeDevWorker_base {
29
+ }
30
+ declare const WorkerGateway_base: Schema.Class<WorkerGateway, Schema.Struct<{
31
+ readonly domain: Schema.brand<Schema.String, "GatewayDomain">;
32
+ readonly destination: Schema.brand<Schema.String, "SshDestination">;
33
+ readonly adminPort: Schema.withDecodingDefaultKey<Schema.brand<Schema.Int, "GatewayAdminPort">, never>;
34
+ readonly adminTokenEnv: Schema.brand<Schema.String, "EnvironmentVariableName">;
35
+ readonly llmProviders: Schema.withDecodingDefaultKey<Schema.$Array<Schema.Literals<readonly ["anthropic"]>>, never>;
36
+ }>, {}>;
37
+ /**
38
+ * The Gateway in front of an SSH Worker's machine on a private network (ADR 0027).
39
+ *
40
+ * - `domain`: the Gateway's private DNS domain.
41
+ * - `destination`: the Gateway's `ssh` destination, usually the Host alias in the Worker's
42
+ * `ProxyJump`. The executor forwards through it to the admin API at `adminPort` on the
43
+ * Gateway's loopback interface.
44
+ * - `adminTokenEnv`: the orchestrator environment variable with the admin token, read when
45
+ * `usine up` starts or a reload adds the Worker.
46
+ * - `llmProviders`: the providers whose credential the Gateway injects. Harnesses for other
47
+ * providers, and Codex, use the operator's login on the Worker through the forward proxy.
48
+ */
49
+ export declare class WorkerGateway extends WorkerGateway_base {
50
+ }
51
+ declare const SshWorker_base: Schema.Class<SshWorker, Schema.Struct<{
52
+ readonly name: Schema.brand<Schema.String, "WorkerName">;
53
+ readonly kind: Schema.Literal<"ssh">;
54
+ readonly maxConcurrentAttempts: Schema.withDecodingDefaultKey<Schema.Int, never>;
55
+ readonly destination: Schema.brand<Schema.String, "SshDestination">;
56
+ readonly identityFile: Schema.OptionFromOptionalKey<Schema.brand<Schema.String, "SshIdentityFile">>;
57
+ readonly port: Schema.OptionFromOptionalKey<Schema.brand<Schema.Int, "SshPort">>;
58
+ readonly root: Schema.brand<Schema.String, "WorkerRoot">;
59
+ readonly gateway: Schema.OptionFromOptionalKey<Schema.encodeKeys<typeof WorkerGateway, {
60
+ readonly adminPort: "admin_port";
61
+ readonly adminTokenEnv: "admin_token_env";
62
+ readonly llmProviders: "llm_providers";
63
+ }>>;
64
+ }>, {}>;
65
+ /**
66
+ * A Linux machine reached with `ssh`, with Worker state under `<root>/workers/<name>`. If
67
+ * `gateway` is set, the machine stores no GitHub token; the executor sends each Attempt's token
68
+ * to the Gateway.
69
+ */
70
+ export declare class SshWorker extends SshWorker_base {
71
+ }
72
+ /** A configured Worker; `kind` selects the adapter. */
73
+ export declare const Worker: Schema.Union<readonly [typeof LocalWorker, typeof ExeDevWorker, typeof SshWorker]>;
74
+ export type Worker = typeof Worker.Type;
26
75
  declare const Settings_base: Schema.Class<Settings, Schema.Struct<{
27
- /** Seconds between routine GitHub polling cycles; defaults to 60. */
76
+ /** The interval between GitHub polling cycles, in seconds. */
28
77
  readonly pollInterval: Schema.withDecodingDefaultKey<Schema.Int, never>;
29
- /** Total child sessions allowed over one Attempt's lifetime, including sequential spawns; defaults to four. */
78
+ /** The maximum number of child sessions per Attempt, counting sequential spawns. */
30
79
  readonly maxChildren: Schema.withDecodingDefaultKey<Schema.brand<Schema.Int, "ChildSessionBudget">, never>;
31
- /** Configured Attempt duration ceiling in minutes; defaults to 60. */
32
- readonly maxAttemptMinutes: Schema.withDecodingDefaultKey<Schema.Int, never>;
33
- /** Configured total-token ceiling per Attempt; defaults to 10,000,000. */
34
- readonly maxAttemptTokens: Schema.withDecodingDefaultKey<Schema.Int, never>;
35
- /** Parent model used when the Agent has no override; an absent default leaves selection to Pi. */
80
+ /** The wall-clock limit per Attempt, in minutes, or `None` for no limit. */
81
+ readonly maxAttemptMinutes: Schema.OptionFromOptionalKey<Schema.Int>;
82
+ /** The token limit per Attempt, counting the parent and every child, or `None` for no limit. */
83
+ readonly maxAttemptTokens: Schema.OptionFromOptionalKey<Schema.Int>;
84
+ /** The parent model for Agents that set none, or `None` to let Pi select it. */
36
85
  readonly parentModel: Schema.OptionFromOptionalKey<Schema.brand<Schema.String, "ModelId">>;
37
- /** Default parent reasoning effort; if neither Agent nor factory supplies one, the child's effort applies. */
86
+ /** The parent reasoning effort for Agents that set none, or `None` to use the child's effort. */
38
87
  readonly parentEffort: Schema.OptionFromOptionalKey<Schema.Literals<readonly ["off", "minimal", "low", "medium", "high", "xhigh", "max"]>>;
39
88
  }>, {}>;
40
- /** Factory-wide polling, Attempt limits, and defaults for the coordinating parent. */
41
89
  export declare class Settings extends Settings_base {
42
90
  }
43
- /** How GitHub hints reach the factory to request earlier polling; routine polling remains the backstop. */
91
+ /** The source of GitHub webhook events that trigger an early poll; polling continues in every mode. */
44
92
  export declare const Webhooks: Schema.Union<readonly [Schema.Struct<{
45
93
  readonly mode: Schema.Literal<"off">;
46
94
  }>, Schema.Struct<{
@@ -64,9 +112,14 @@ declare const UsineConfig_base: Schema.Class<UsineConfig, Schema.Struct<{
64
112
  readonly parentModel: "parent_model";
65
113
  readonly parentEffort: "parent_effort";
66
114
  }>, never>;
67
- readonly workers: Schema.withDecodingDefaultKey<Schema.$Array<Schema.encodeKeys<typeof Worker, {
115
+ readonly workers: Schema.withDecodingDefaultKey<Schema.$Array<Schema.Union<readonly [Schema.encodeKeys<typeof LocalWorker, {
116
+ readonly maxConcurrentAttempts: "max_concurrent_attempts";
117
+ }>, Schema.encodeKeys<typeof ExeDevWorker, {
118
+ readonly maxConcurrentAttempts: "max_concurrent_attempts";
119
+ }>, Schema.encodeKeys<typeof SshWorker, {
68
120
  readonly maxConcurrentAttempts: "max_concurrent_attempts";
69
- }>>, never>;
121
+ readonly identityFile: "identity_file";
122
+ }>]>>, never>;
70
123
  readonly webhooks: Schema.withDecodingDefaultKey<Schema.Union<readonly [Schema.Struct<{
71
124
  readonly mode: Schema.Literal<"off">;
72
125
  }>, Schema.Struct<{
@@ -79,7 +132,7 @@ declare const UsineConfig_base: Schema.Class<UsineConfig, Schema.Struct<{
79
132
  }>]>, never>;
80
133
  readonly operator: Schema.OptionFromOptionalKey<Schema.brand<Schema.String, "OperatorLogin">>;
81
134
  }>, {}>;
82
- /** The factory's repository registry, Worker capacity, labels, and operating defaults from `usine.yaml`. */
135
+ /** The decoded `usine.yaml`. */
83
136
  export declare class UsineConfig extends UsineConfig_base {
84
137
  }
85
138
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../src/factory.ts"],"names":[],"mappings":"AAIA,OAAO,EAAU,MAAM,EAAE,MAAM,QAAQ,CAAC;AAExC,OAAO,EAAc,IAAI,EAAE,MAAM,YAAY,CAAC;AAC9C,OAAO,EAAsB,aAAa,EAAE,MAAM,YAAY,CAAC;AAE/D,0EAA0E;AAC1E,eAAO,MAAM,WAAW,4CAEvB,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG,OAAO,WAAW,CAAC,IAAI,CAAC;AAElD,oEAAoE;AACpE,eAAO,MAAM,aAAa,8CAIW,CAAC;AAEtC,MAAM,MAAM,aAAa,GAAG,OAAO,aAAa,CAAC,IAAI,CAAC;;;;;;AA6BtD,2FAA2F;AAC3F,qBAAa,WAAY,SAAQ,gBAM/B;CAAG;;;;;;AAEL,sFAAsF;AACtF,qBAAa,MAAO,SAAQ,WAI1B;CAAG;;IAIH,qEAAqE;;IAErE,+GAA+G;;IAE/G,sEAAsE;;IAEtE,0EAA0E;;IAE1E,kGAAkG;;IAElG,8GAA8G;;;AAZhH,sFAAsF;AACtF,qBAAa,QAAS,SAAQ,aAa5B;CAAG;AAEL,2GAA2G;AAC3G,eAAO,MAAM,QAAQ;;;;;;;;;IAMnB,CAAC;AAEH,MAAM,MAAM,QAAQ,GAAG,OAAO,QAAQ,CAAC,IAAI,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAuB5C,4GAA4G;AAC5G,qBAAa,WAAY,SAAQ,gBAU/B;CAAG"}
1
+ {"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../src/factory.ts"],"names":[],"mappings":"AAcA,OAAO,EAAU,MAAM,EAAE,MAAM,QAAQ,CAAC;AAExC,OAAO,EAAc,IAAI,EAAE,MAAM,YAAY,CAAC;AAC9C,OAAO,EAAsB,aAAa,EAAE,MAAM,YAAY,CAAC;AAE/D,kDAAkD;AAClD,eAAO,MAAM,WAAW,4CAEvB,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG,OAAO,WAAW,CAAC,IAAI,CAAC;;;;;;AA6BlD,qBAAa,WAAY,SAAQ,gBAM/B;CAAG;;;;;;AAIL,iEAAiE;AACjE,qBAAa,WAAY,SAAQ,gBAI/B;CAAG;;;;;;AAEL,qDAAqD;AACrD,qBAAa,YAAa,SAAQ,iBAIhC;CAAG;;;;;;;;AAQL;;;;;;;;;;;GAWG;AACH,qBAAa,aAAc,SAAQ,kBAQjC;CAAG;;;;;;;;;;;;;;;AAEL;;;;GAIG;AACH,qBAAa,SAAU,SAAQ,cAiB7B;CAAG;AAEL,uDAAuD;AACvD,eAAO,MAAM,MAAM,oFAAuD,CAAC;AAC3E,MAAM,MAAM,MAAM,GAAG,OAAO,MAAM,CAAC,IAAI,CAAC;;IAGtC,8DAA8D;;IAE9D,oFAAoF;;IAEpF,4EAA4E;;IAE5E,gGAAgG;;IAEhG,gFAAgF;;IAEhF,iGAAiG;;;AAXnG,qBAAa,QAAS,SAAQ,aAa5B;CAAG;AAEL,uGAAuG;AACvG,eAAO,MAAM,QAAQ;;;;;;;;;IAMnB,CAAC;AAEH,MAAM,MAAM,QAAQ,GAAG,OAAO,QAAQ,CAAC,IAAI,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8B5C,gCAAgC;AAChC,qBAAa,WAAY,SAAQ,gBAU/B;CAAG"}
package/dist/factory.js CHANGED
@@ -1,28 +1,24 @@
1
1
  import { Effort, ModelId } from "@usine/core/agent/brain";
2
2
  import { ChildSessionBudget } from "@usine/core/agent/session";
3
+ import { OperatorLogin } from "@usine/core/operator";
3
4
  import { RepositoryName } from "@usine/core/repository";
4
- import { WorkerName, WorkerKind } from "@usine/core/worker";
5
+ import { GatewayAdminPort, GatewayDomain, GatewayLlmProvider, SshDestination, SshIdentityFile, SshPort, WorkerName, WorkerRoot, } from "@usine/core/worker";
5
6
  import { Effect, Schema } from "effect";
6
- import { nonBlankString } from "./checks.js";
7
+ import { nonBlankString, uniqueRepositoryNames } from "./checks.js";
7
8
  import { LaneSource, Lane } from "./board.js";
8
9
  import { canonicalLabelName, DeclaredLabel } from "./label.js";
9
- /** A factory-defined repository grouping that Loops can select by tag. */
10
+ /** A repository tag that Loop selectors match. */
10
11
  export const RegistryTag = nonBlankString("Expected a non-blank registry tag").pipe(Schema.brand("RegistryTag"));
11
- /** GitHub login identifying the human operator for this factory. */
12
- export const OperatorLogin = Schema.String.check(Schema.isPattern(/^[A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?$/u, {
13
- message: "Expected a GitHub login",
14
- })).pipe(Schema.brand("OperatorLogin"));
15
12
  const PositiveInt = Schema.Int.check(Schema.isGreaterThanOrEqualTo(1));
16
13
  const uniqueNames = Schema.makeFilter((values) => new Set(values.map(({ name }) => name)).size === values.length, { message: "Expected unique names" });
17
- const laneSourceKey = (source) => "label" in source ? `label:${source.label}` : `state:${source.state}`;
14
+ const laneSourceKey = (source) => "label" in source ? `label:${canonicalLabelName(source.label)}` : `state:${source.state}`;
18
15
  const uniqueLaneSources = Schema.makeFilter((lanes) => {
19
16
  const keys = lanes.flatMap(({ sources }) => sources.map(laneSourceKey));
20
17
  return new Set(keys).size === keys.length;
21
- }, { message: "Expected every lane source to be mapped once" });
18
+ }, { message: "Expected each lane source in only one lane" });
22
19
  const Lanes = Schema.Array(Lane).check(uniqueNames, uniqueLaneSources);
23
20
  const uniqueLabelNames = Schema.makeFilter((values) => new Set(values.map(({ name }) => canonicalLabelName(name))).size === values.length, { message: "Expected unique GitHub label names" });
24
21
  const DeclaredLabels = Schema.Array(DeclaredLabel).check(uniqueLabelNames);
25
- /** A repository the factory manages, with its selector tags and configured Board lanes. */
26
22
  export class ManagedRepo extends Schema.Class("@usine/config/ManagedRepo")({
27
23
  name: RepositoryName,
28
24
  tags: Schema.Array(RegistryTag)
@@ -31,37 +27,89 @@ export class ManagedRepo extends Schema.Class("@usine/config/ManagedRepo")({
31
27
  lanes: Lanes.pipe(Schema.withDecodingDefaultKey(Effect.succeed([]))),
32
28
  }) {
33
29
  }
34
- /** A named place to execute Attempts, with the number it may run at the same time. */
35
- export class Worker extends Schema.Class("@usine/config/Worker")({
30
+ const MaxConcurrentAttempts = PositiveInt.pipe(Schema.withDecodingDefaultKey(Effect.succeed(1)));
31
+ /** A Worker that runs Attempts on the orchestrator's machine. */
32
+ export class LocalWorker extends Schema.Class("@usine/config/LocalWorker")({
36
33
  name: WorkerName,
37
- kind: WorkerKind,
38
- maxConcurrentAttempts: PositiveInt.pipe(Schema.withDecodingDefaultKey(Effect.succeed(1))),
34
+ kind: Schema.Literal("local"),
35
+ maxConcurrentAttempts: MaxConcurrentAttempts,
39
36
  }) {
40
37
  }
41
- /** Factory-wide polling, Attempt limits, and defaults for the coordinating parent. */
38
+ /** A Worker on the exe.dev VM with the same name. */
39
+ export class ExeDevWorker extends Schema.Class("@usine/config/ExeDevWorker")({
40
+ name: WorkerName,
41
+ kind: Schema.Literal("exe.dev"),
42
+ maxConcurrentAttempts: MaxConcurrentAttempts,
43
+ }) {
44
+ }
45
+ const EnvironmentVariableName = Schema.String.check(Schema.isPattern(/^[A-Za-z_][A-Za-z0-9_]*$/u, {
46
+ message: "Expected an environment variable name",
47
+ })).pipe(Schema.brand("EnvironmentVariableName"));
48
+ /**
49
+ * The Gateway in front of an SSH Worker's machine on a private network (ADR 0027).
50
+ *
51
+ * - `domain`: the Gateway's private DNS domain.
52
+ * - `destination`: the Gateway's `ssh` destination, usually the Host alias in the Worker's
53
+ * `ProxyJump`. The executor forwards through it to the admin API at `adminPort` on the
54
+ * Gateway's loopback interface.
55
+ * - `adminTokenEnv`: the orchestrator environment variable with the admin token, read when
56
+ * `usine up` starts or a reload adds the Worker.
57
+ * - `llmProviders`: the providers whose credential the Gateway injects. Harnesses for other
58
+ * providers, and Codex, use the operator's login on the Worker through the forward proxy.
59
+ */
60
+ export class WorkerGateway extends Schema.Class("@usine/config/WorkerGateway")({
61
+ domain: GatewayDomain,
62
+ destination: SshDestination,
63
+ adminPort: GatewayAdminPort.pipe(Schema.withDecodingDefaultKey(Effect.succeed(7070))),
64
+ adminTokenEnv: EnvironmentVariableName,
65
+ llmProviders: Schema.Array(GatewayLlmProvider)
66
+ .check(Schema.isUnique())
67
+ .pipe(Schema.withDecodingDefaultKey(Effect.succeed([]))),
68
+ }) {
69
+ }
70
+ /**
71
+ * A Linux machine reached with `ssh`, with Worker state under `<root>/workers/<name>`. If
72
+ * `gateway` is set, the machine stores no GitHub token; the executor sends each Attempt's token
73
+ * to the Gateway.
74
+ */
75
+ export class SshWorker extends Schema.Class("@usine/config/SshWorker")({
76
+ name: WorkerName,
77
+ kind: Schema.Literal("ssh"),
78
+ maxConcurrentAttempts: MaxConcurrentAttempts,
79
+ destination: SshDestination,
80
+ identityFile: Schema.OptionFromOptionalKey(SshIdentityFile),
81
+ port: Schema.OptionFromOptionalKey(SshPort),
82
+ root: WorkerRoot,
83
+ gateway: Schema.OptionFromOptionalKey(WorkerGateway.pipe(Schema.encodeKeys({
84
+ adminPort: "admin_port",
85
+ adminTokenEnv: "admin_token_env",
86
+ llmProviders: "llm_providers",
87
+ }))),
88
+ }) {
89
+ }
90
+ /** A configured Worker; `kind` selects the adapter. */
91
+ export const Worker = Schema.Union([LocalWorker, ExeDevWorker, SshWorker]);
42
92
  export class Settings extends Schema.Class("@usine/config/Settings")({
43
- /** Seconds between routine GitHub polling cycles; defaults to 60. */
93
+ /** The interval between GitHub polling cycles, in seconds. */
44
94
  pollInterval: PositiveInt.pipe(Schema.withDecodingDefaultKey(Effect.succeed(60))),
45
- /** Total child sessions allowed over one Attempt's lifetime, including sequential spawns; defaults to four. */
95
+ /** The maximum number of child sessions per Attempt, counting sequential spawns. */
46
96
  maxChildren: ChildSessionBudget.pipe(Schema.withDecodingDefaultKey(Effect.succeed(4))),
47
- /** Configured Attempt duration ceiling in minutes; defaults to 60. */
48
- maxAttemptMinutes: PositiveInt.pipe(Schema.withDecodingDefaultKey(Effect.succeed(60))),
49
- /** Configured total-token ceiling per Attempt; defaults to 10,000,000. */
50
- maxAttemptTokens: PositiveInt.pipe(Schema.withDecodingDefaultKey(Effect.succeed(10_000_000))),
51
- /** Parent model used when the Agent has no override; an absent default leaves selection to Pi. */
97
+ /** The wall-clock limit per Attempt, in minutes, or `None` for no limit. */
98
+ maxAttemptMinutes: Schema.OptionFromOptionalKey(PositiveInt),
99
+ /** The token limit per Attempt, counting the parent and every child, or `None` for no limit. */
100
+ maxAttemptTokens: Schema.OptionFromOptionalKey(PositiveInt),
101
+ /** The parent model for Agents that set none, or `None` to let Pi select it. */
52
102
  parentModel: Schema.OptionFromOptionalKey(ModelId),
53
- /** Default parent reasoning effort; if neither Agent nor factory supplies one, the child's effort applies. */
103
+ /** The parent reasoning effort for Agents that set none, or `None` to use the child's effort. */
54
104
  parentEffort: Schema.OptionFromOptionalKey(Effort),
55
105
  }) {
56
106
  }
57
- /** How GitHub hints reach the factory to request earlier polling; routine polling remains the backstop. */
107
+ /** The source of GitHub webhook events that trigger an early poll; polling continues in every mode. */
58
108
  export const Webhooks = Schema.Union([
59
109
  Schema.Struct({ mode: Schema.Literal("off") }),
60
110
  Schema.Struct({ mode: Schema.Literal("deliveries") }),
61
111
  Schema.Struct({ mode: Schema.Literal("endpoint"), publicUrl: Schema.URLFromString }).pipe(Schema.encodeKeys({ publicUrl: "public_url" })),
62
112
  ]);
63
- // Authored YAML keys are snake_case and domain fields camelCase. The rename
64
- // happens once here and encodes back for edits.
65
113
  const SettingsFromYaml = Settings.pipe(Schema.encodeKeys({
66
114
  pollInterval: "poll_interval",
67
115
  maxChildren: "max_children",
@@ -70,10 +118,17 @@ const SettingsFromYaml = Settings.pipe(Schema.encodeKeys({
70
118
  parentModel: "parent_model",
71
119
  parentEffort: "parent_effort",
72
120
  }));
73
- const WorkerFromYaml = Worker.pipe(Schema.encodeKeys({ maxConcurrentAttempts: "max_concurrent_attempts" }));
74
- const ManagedRepos = Schema.Array(ManagedRepo).check(uniqueNames);
121
+ const WorkerFromYaml = Schema.Union([
122
+ LocalWorker.pipe(Schema.encodeKeys({ maxConcurrentAttempts: "max_concurrent_attempts" })),
123
+ ExeDevWorker.pipe(Schema.encodeKeys({ maxConcurrentAttempts: "max_concurrent_attempts" })),
124
+ SshWorker.pipe(Schema.encodeKeys({
125
+ maxConcurrentAttempts: "max_concurrent_attempts",
126
+ identityFile: "identity_file",
127
+ })),
128
+ ]);
129
+ const ManagedRepos = Schema.Array(ManagedRepo).check(uniqueRepositoryNames((repo) => repo.name));
75
130
  const Workers = Schema.Array(WorkerFromYaml).check(Schema.isNonEmpty(), uniqueNames);
76
- /** The factory's repository registry, Worker capacity, labels, and operating defaults from `usine.yaml`. */
131
+ /** The decoded `usine.yaml`. */
77
132
  export class UsineConfig extends Schema.Class("@usine/config/UsineConfig")({
78
133
  version: Schema.Literal(1),
79
134
  labels: DeclaredLabels.pipe(Schema.withDecodingDefaultKey(Effect.succeed([]))),