@webappwiz/arbor 0.0.25 → 0.0.27

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/merge.d.ts CHANGED
@@ -2,7 +2,6 @@ import { type Logger } from "webappwiz/log";
2
2
  import type { Fs, Lock } from "webappwiz/system";
3
3
  import type { Config } from "./config.js";
4
4
  import type { Git } from "./git.js";
5
- import type { Replies } from "./replies.js";
6
5
  import type { Shell } from "./shell.js";
7
6
  import { type Todos } from "./todo.js";
8
7
  import type { WorktreeService } from "./worktree-service.js";
@@ -13,7 +12,7 @@ import type { WorktreeService } from "./worktree-service.js";
13
12
  * `git merge --ff-only`, in whichever worktree has the base checked out.
14
13
  * History stays linear.
15
14
  */
16
- export declare function merge({ service, git, lock, shell, config, log, todos, replies, fs, }: {
15
+ export declare function merge({ service, git, lock, shell, config, log, todos, fs, }: {
17
16
  service: WorktreeService;
18
17
  git: Git;
19
18
  lock: Lock;
@@ -21,6 +20,5 @@ export declare function merge({ service, git, lock, shell, config, log, todos, r
21
20
  config: Config;
22
21
  log: Logger;
23
22
  todos: Todos;
24
- replies: Replies;
25
23
  fs: Fs;
26
24
  }, cwd: string): Promise<void>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webappwiz/arbor",
3
- "version": "0.0.25",
3
+ "version": "0.0.27",
4
4
  "description": "Runs several AI coding agents on one repository at once, each in its own git worktree",
5
5
  "license": "MIT",
6
6
  "author": "Jared Johnson",
@@ -20,7 +20,7 @@
20
20
  "access": "public"
21
21
  },
22
22
  "dependencies": {
23
- "webappwiz": "^0.0.25",
23
+ "webappwiz": "^0.0.27",
24
24
  "zod": "^4.4.3"
25
25
  },
26
26
  "main": "./index.js",
package/plan.d.ts CHANGED
@@ -7,12 +7,12 @@ export declare const PLAN_FILE = "ARBOR.md";
7
7
  */
8
8
  export declare function plannedFiles(text: string): string[];
9
9
  /**
10
- * One `- [ ] Q9.` item under `## Blocked`: something only a person can do.
10
+ * One `- [ ] 9.` item under `## Blocked`: something only a person can do.
11
11
  * The item's line is its subject; lines indented under it are its body, and
12
12
  * `- (a) ...` or `- [a] ...` lines among them are the answers it offers.
13
13
  */
14
14
  export interface Question {
15
- /** As the plan numbers it, `Q9`: the name a reply goes by. */
15
+ /** As the plan numbers it, `9`: the name a reply goes by. */
16
16
  number: string;
17
17
  /** Checked off, which the agent does once it has acted on the reply. */
18
18
  done: boolean;
@@ -41,10 +41,6 @@ export interface Question {
41
41
  * `- [a]` choices, where it picks all that apply. Null without choices.
42
42
  */
43
43
  pick: "one" | "any" | null;
44
- /** The keys of the choices the reply picked, in the order offered. */
45
- chosen: string[];
46
- /** The absolute paths of the images its text and body show. */
47
- images: string[];
48
44
  }
49
45
  /** One answer a question offers: `- (b) Migrate on next login`. */
50
46
  export interface Choice {
@@ -58,31 +54,6 @@ export interface Choice {
58
54
  * answer is one line, written by `arbor reply` or by hand after the arrow.
59
55
  */
60
56
  export declare function questions(text: string): Question[];
61
- /**
62
- * How a reply reads in the plan: each pick spelled out, so the agent needs
63
- * nothing but the line, then any words after it. `a (Email), c (Push): and
64
- * log it`.
65
- */
66
- export declare function replyLine(asked: Question, { choices, text }: {
67
- choices?: string[];
68
- text: string;
69
- }): string;
70
- /**
71
- * The plan with `reply` written onto that question's line, in place of any
72
- * earlier one, or with no reply there at all when it is null. The checkbox is left alone: checking it off is the agent's
73
- * word that it has acted on the answer. Null when the plan has no such
74
- * question.
75
- */
76
- export declare function withReply(text: string, number: string,
77
- /** Null takes the reply off, leaving the question as it was asked. */
78
- reply: string | null): string | null;
79
- /**
80
- * The plan with `reply` added under a question already answered, as an
81
- * indented `→ ` line after everything else under it, and the question
82
- * unchecked: whatever the agent did about the answer, it has more to read.
83
- * Null when the plan has no such question.
84
- */
85
- export declare function withFollowUp(text: string, number: string, reply: string): string | null;
86
57
  /**
87
58
  * The plan with a new open question at the end of `## Blocked`, the section
88
59
  * made if missing, numbered after the highest there. Returns the plan and
@@ -93,8 +64,8 @@ export declare function withQuestion(text: string, subject: string, body?: strin
93
64
  number: string;
94
65
  };
95
66
  /**
96
- * A question's number however a person typed it: `Q9`, `q9`, `9` and `Q9.`
97
- * all mean `Q9`. Null for anything that is not a number at all.
67
+ * A question's number however a person typed it: `9`, `9.`, `Q9` and `q9:`
68
+ * all mean `9`. Null for anything that is not a number at all.
98
69
  */
99
70
  export declare function questionNumber(raw: string): string | null;
100
71
  export interface PlanOptions {
package/repository.d.ts CHANGED
@@ -3,7 +3,6 @@ import { type Fs, type Lock } from "webappwiz/system";
3
3
  import type { Config } from "./config.js";
4
4
  import { Git } from "./git.js";
5
5
  import { Journal } from "./journal.js";
6
- import { Replies } from "./replies.js";
7
6
  import { Shell } from "./shell.js";
8
7
  import { Todos } from "./todo.js";
9
8
  import { WorktreeService } from "./worktree-service.js";
@@ -16,7 +15,6 @@ export interface Repository {
16
15
  shell: Shell;
17
16
  journal: Journal;
18
17
  todos: Todos;
19
- replies: Replies;
20
18
  }
21
19
  /**
22
20
  * Runs the action against the repository `cwd` sits in, and refuses to run it
package/show.d.ts CHANGED
@@ -15,7 +15,7 @@ export interface Details {
15
15
  age: string | null;
16
16
  escalation: string | null;
17
17
  /**
18
- * While escalated to be approved, the question that asks for it, `Q4`;
18
+ * While escalated to be approved, the question that asks for it, `4`;
19
19
  * null otherwise.
20
20
  */
21
21
  review: string | null;
package/snapshot.d.ts CHANGED
@@ -1,19 +1,16 @@
1
1
  import type { Fs } from "webappwiz/system";
2
- import { type Inbox } from "./inbox.js";
3
- import type { Replies } from "./replies.js";
4
2
  import { type Details } from "./show.js";
5
3
  import type { TodoState, Todos } from "./todo.js";
6
4
  import type { WorktreeService } from "./worktree-service.js";
7
5
  /**
8
- * Everything one page shows: `list` and `show` for each task, every question
9
- * each one asked however it stands, and `todo list`.
6
+ * Everything one page shows: `todo list`, and `list` and `show` for each
7
+ * task.
10
8
  */
11
9
  export interface Snapshot {
12
10
  /** The repository's directory name, so a page among many says whose it is. */
13
11
  repo: string;
14
12
  /** Past this age a todo is offered for removal rather than recommended. */
15
13
  todoStalenessMs: number;
16
- inbox: Inbox;
17
14
  todos: TodoState[];
18
15
  tasks: Details[];
19
16
  }
@@ -21,10 +18,9 @@ export interface Snapshot {
21
18
  * Reads the whole repo the way the CLI's read commands do. Takes no lease, so
22
19
  * the page can sit open on a live tree without disturbing the agent in it.
23
20
  */
24
- export declare function snapshot({ service, todos, replies, fs, }: {
21
+ export declare function snapshot({ service, todos, fs, }: {
25
22
  service: WorktreeService;
26
23
  todos: Todos;
27
- replies: Replies;
28
24
  fs: Fs;
29
25
  }): Promise<Snapshot>;
30
26
  /**
@@ -32,4 +28,4 @@ export declare function snapshot({ service, todos, replies, fs, }: {
32
28
  * `age` ticks every minute, and hashing it would push to every open page for
33
29
  * nothing.
34
30
  */
35
- export declare function fingerprint({ inbox, todos, tasks }: Snapshot): string;
31
+ export declare function fingerprint({ todos, tasks }: Snapshot): string;
package/todo.d.ts CHANGED
@@ -5,7 +5,12 @@ import { type Attachment, Attachments } from "./attachments.js";
5
5
  /** What a todo is on disk: one file apiece, so two agents never rewrite one. */
6
6
  export interface TodoState {
7
7
  id: number;
8
+ /** What is left to do, in a line. */
9
+ subject: string;
10
+ /** Whatever more there is to say about it, or empty when the line is enough. */
8
11
  text: string;
12
+ /** Where it stands in the list, 1 at the top: the one to pick up first. */
13
+ position: number;
9
14
  /** The task it came up in, or null when a person added it from the main tree. */
10
15
  from: string | null;
11
16
  createdAt: string;
@@ -14,15 +19,27 @@ export interface TodoState {
14
19
  /** Absolute paths of the files attached, in `todos/<id>/`. */
15
20
  files: string[];
16
21
  }
17
- /** A todo's words and files changed: new ones added, some of the old kept. */
22
+ /** A todo's words, place or files changed: new ones added, some of the old kept. */
18
23
  export interface TodoChange {
19
- /** New words; the old ones stay when this is absent. */
24
+ /** A new line; the old one stays when this is absent. */
25
+ subject?: string;
26
+ /** New detail, empty for none; the old stays when this is absent. */
20
27
  text?: string;
28
+ /** Where to move it in the list; it stays put when this is absent. */
29
+ position?: number;
21
30
  /** Files to add. */
22
31
  files?: Attachment[];
23
32
  /** The paths of the files it has now to keep; absent keeps them all. */
24
33
  keep?: string[];
25
34
  }
35
+ /** What a new todo has besides its subject, all of it optional. */
36
+ export interface TodoNew {
37
+ /** Whatever more there is to say about it. */
38
+ text?: string;
39
+ files?: Attachment[];
40
+ /** Where it goes in the list, pushing those from there down; the bottom by default. */
41
+ position?: number;
42
+ }
26
43
  /**
27
44
  * Work deferred for later: what an agent notes when something outside its
28
45
  * task comes up, so it can move on instead of growing the task. Kept under the
@@ -34,7 +51,9 @@ export declare class Todo {
34
51
  readonly state: TodoState;
35
52
  constructor(todos: Todos, state: TodoState);
36
53
  get id(): number;
54
+ get subject(): string;
37
55
  get text(): string;
56
+ get position(): number;
38
57
  get from(): string | null;
39
58
  get takenBy(): string | null;
40
59
  get createdAt(): Date;
@@ -47,10 +66,10 @@ export declare class Todo {
47
66
  get files(): string[];
48
67
  get attachments(): Attachments;
49
68
  /**
50
- * Says what is left to do in other words, or with other files, keeping its
51
- * id and history.
69
+ * Says what is left to do in other words, with other files, or higher or
70
+ * lower in the list, keeping its id and history.
52
71
  */
53
- update({ text, files, keep }: TodoChange): Promise<Todo>;
72
+ update({ subject, text, position, files, keep, }: TodoChange): Promise<Todo>;
54
73
  remove(): Promise<void>;
55
74
  }
56
75
  /** What `Todos` is stored through; the real filesystem by default. */
@@ -62,27 +81,62 @@ export interface TodosOptions {
62
81
  /** Every todo in the repo, one JSON file each under `.git/arbor/todos`. */
63
82
  export declare class Todos {
64
83
  readonly dir: string;
65
- /** Held while numbering a new todo, so two agents never share an id. */
84
+ /**
85
+ * Held while numbering a new todo, so two agents never share an id, and
86
+ * while writing one, so a move renumbering the rest never loses to a
87
+ * write of a position it just changed.
88
+ */
66
89
  private readonly lock;
67
90
  private readonly fs;
68
91
  private readonly ids;
69
92
  constructor(dir: string,
70
- /** Held while numbering a new todo, so two agents never share an id. */
93
+ /**
94
+ * Held while numbering a new todo, so two agents never share an id, and
95
+ * while writing one, so a move renumbering the rest never loses to a
96
+ * write of a position it just changed.
97
+ */
71
98
  lock: Lock, opts?: TodosOptions);
72
99
  /** Where a todo's files live, beside its record. */
73
100
  attachments(id: number): Attachments;
74
101
  /** Whether `path` is a file some todo holds, and not a way out of here. */
75
102
  owns(path: string): boolean;
76
- add(text: string, from: string | null, files?: Attachment[]): Promise<Todo>;
77
- /** Oldest first. A file that will not parse is skipped rather than fatal. */
103
+ add(subject: string, from: string | null, { text, files, position }?: TodoNew): Promise<Todo>;
104
+ /**
105
+ * Top of the list first. A file that will not parse is skipped rather than
106
+ * fatal, and positions are counted again from 1 as they are read, so a
107
+ * gap or a tie left by a crash, or a todo saved before they had any (it
108
+ * goes below those that do, oldest first), never shows.
109
+ */
78
110
  all(): Promise<Todo[]>;
79
111
  find(id: number): Promise<Todo>;
80
112
  /** The todos `task` has taken; what a merge or remove settles. */
81
113
  takenBy(task: string): Promise<Todo[]>;
114
+ /**
115
+ * Puts a todo at `position`, 1 at the top, past the bottom meaning the
116
+ * bottom, and numbers the rest around it.
117
+ */
118
+ move(id: number, position: number): Promise<Todo>;
119
+ /**
120
+ * @internal Rewrites one todo as it stands on disk now, under the lock, so
121
+ * a change to its words or who has it never undoes a move made meanwhile.
122
+ */
123
+ revise(id: number, change: (state: TodoState) => TodoState): Promise<Todo>;
82
124
  /** @internal Writes one todo. Rename makes the swap atomic for readers. */
83
125
  save(state: TodoState): Promise<Todo>;
84
- /** @internal */
126
+ /** @internal Removes one, moving those below it up to close the gap. */
85
127
  delete(id: number): Promise<void>;
128
+ private locked;
129
+ /** One todo as it stands on disk; call it under the lock. */
130
+ private state;
131
+ /**
132
+ * Slots `state` in at `position` among the rest and saves whichever moved.
133
+ * Call it under the lock.
134
+ */
135
+ private place;
136
+ /** Every todo in list order, with the positions it has on disk. */
137
+ private stored;
138
+ /** Saves each todo in `order` whose position on disk is not its place there. */
139
+ private renumber;
86
140
  private path;
87
141
  private lastId;
88
142
  private read;
@@ -95,9 +149,10 @@ export interface Recommendation {
95
149
  stale: Todo[];
96
150
  }
97
151
  /**
98
- * Which todo to take up after `task`: its own first, since whoever just
99
- * finished it knows that context best, then the longest waiting. Todos past
100
- * `staleness` are never recommended, only offered for removal.
152
+ * Which todo to take up after `task`: one that came up in it first, since
153
+ * whoever just finished it knows that context best, then the open one highest
154
+ * on the list, which is how whoever keeps the list says what matters most.
155
+ * Todos past `staleness` are never recommended, only offered for removal.
101
156
  */
102
157
  export declare function recommend(todos: Todos, task: string | null, staleness: number): Promise<Recommendation>;
103
158
  /** The lines `merge` ends with, or none when there is nothing to say. */
@@ -110,19 +165,38 @@ export interface TodoFileOptions {
110
165
  /** Files to attach, relative to the current directory or absolute. */
111
166
  files?: string[];
112
167
  }
168
+ export interface TodoAddOptions extends TodoFileOptions {
169
+ /** Whatever more there is to say than the subject. */
170
+ text?: string;
171
+ /** Where it goes in the list, 1 at the top; the bottom by default. */
172
+ position?: number;
173
+ }
113
174
  export declare function todoAdd(deps: {
114
175
  todos: Todos;
115
176
  log: Logger;
116
177
  fs: Fs;
117
178
  ps: Ps;
118
- }, text: string, from: string | null, { files }?: TodoFileOptions): Promise<Todo>;
179
+ }, subject: string, from: string | null, { text, files, position }?: TodoAddOptions): Promise<Todo>;
119
180
  export declare function todoList({ todos, log }: {
120
181
  todos: Todos;
121
182
  log: Logger;
122
183
  }, { json }?: TodoListOptions): Promise<void>;
184
+ export interface TodoShowOptions {
185
+ /** Print the todo as JSON instead of prose. */
186
+ json?: boolean;
187
+ }
188
+ /** One todo in full: what `todo list` shows of it, its files, and its detail. */
189
+ export declare function todoShow({ todos, log }: {
190
+ todos: Todos;
191
+ log: Logger;
192
+ }, id: number, { json }?: TodoShowOptions): Promise<void>;
123
193
  export interface TodoUpdateOptions extends TodoFileOptions {
124
- /** New words; the old ones stay when this is absent or empty. */
194
+ /** A new line; the old one stays when this is absent or empty. */
195
+ subject?: string;
196
+ /** New detail; the old stays when this is absent or empty. */
125
197
  text?: string;
198
+ /** Where to move it in the list, 1 at the top; it stays put when absent. */
199
+ position?: number;
126
200
  /** Attached files to drop, by path or by the name they were stored under. */
127
201
  removeFiles?: string[];
128
202
  }
@@ -131,7 +205,7 @@ export declare function todoUpdate(deps: {
131
205
  log: Logger;
132
206
  fs: Fs;
133
207
  ps: Ps;
134
- }, id: number, { text, files, removeFiles }?: TodoUpdateOptions): Promise<Todo>;
208
+ }, id: number, { subject, text, position, files, removeFiles, }?: TodoUpdateOptions): Promise<Todo>;
135
209
  /**
136
210
  * Takes up todos for `task` after it started, as when one turns out to be
137
211
  * part of the work. Every id is checked before any is taken, so one that is
package/wait.d.ts CHANGED
@@ -1,7 +1,5 @@
1
1
  import { type Logger } from "webappwiz/log";
2
- import type { Fs } from "webappwiz/system";
3
2
  import { Duration } from "webappwiz/time";
4
- import type { Replies } from "./replies.js";
5
3
  import type { WorktreeService } from "./worktree-service.js";
6
4
  /** How long `wait` gives a task before handing the wait back to its caller. */
7
5
  export declare const DEFAULT_TIMEOUT: Duration;
@@ -10,11 +8,6 @@ export interface WaitOptions {
10
8
  timeout?: Duration;
11
9
  /** How long between reads of the record; the default suits a human's patience. */
12
10
  poll?: Duration;
13
- /**
14
- * Wait for the task's open questions to be answered instead of for the task
15
- * to stop moving: what an agent that escalated waits on.
16
- */
17
- answered?: boolean;
18
11
  }
19
12
  /**
20
13
  * Blocks until a task stops moving: merged or removed (both leave the name
@@ -22,15 +15,8 @@ export interface WaitOptions {
22
15
  * own work overlaps this one's and would rather rebase onto the result than
23
16
  * against it, which is why the timeout is short enough to come back and think
24
17
  * again rather than block a session for an afternoon.
25
- *
26
- * With `answered` it blocks instead until every open question under the
27
- * task's `## Blocked` has a reply nobody is still editing, then claims them,
28
- * which writes them into the plan, and prints them: the agent that escalated
29
- * waits for its human this way, rather than polling its own plan.
30
18
  */
31
- export declare function wait({ service, log, fs, replies, }: {
19
+ export declare function wait({ service, log }: {
32
20
  service: WorktreeService;
33
21
  log: Logger;
34
- fs: Fs;
35
- replies: Replies;
36
- }, task: string, { timeout, poll, answered, }?: WaitOptions): Promise<void>;
22
+ }, task: string, { timeout, poll }?: WaitOptions): Promise<void>;
@@ -21,7 +21,6 @@ export declare class WorktreeService {
21
21
  readonly config: Config;
22
22
  private readonly tasksDir;
23
23
  private readonly removedDir;
24
- private readonly repliesDir;
25
24
  private readonly fs;
26
25
  readonly ps: Ps;
27
26
  constructor(git: Git, config: Config, arborDir: string, opts?: WorktreeServiceOptions);
@@ -32,12 +31,6 @@ export declare class WorktreeService {
32
31
  branchFor(task: string): string;
33
32
  taskFor(branch: string): string | null;
34
33
  recordPath(task: string): string;
35
- /**
36
- * Where a task's replies wait to be read, and the files attached to them.
37
- * Under `.git/arbor` rather than in the worktree, so they are never
38
- * committed and every tree can open them, and gone with the task.
39
- */
40
- repliesPath(task: string): string;
41
34
  /** Always answers; the returned worktree's status says what was found. */
42
35
  find(task: string): Promise<Worktree>;
43
36
  list(): Promise<Worktree[]>;
package/worktree.d.ts CHANGED
@@ -13,7 +13,7 @@ export interface Escalation {
13
13
  at: string;
14
14
  /**
15
15
  * For a task handed over to be approved rather than to answer questions,
16
- * the question in its plan that asks for the approval, `Q4`.
16
+ * the question in its plan that asks for the approval, `4`.
17
17
  */
18
18
  review?: string;
19
19
  }
package/inbox.d.ts DELETED
@@ -1,80 +0,0 @@
1
- import { type Logger } from "webappwiz/log";
2
- import type { Fs } from "webappwiz/system";
3
- import { type Question } from "./plan.js";
4
- import { type Replies, type ReplyState } from "./replies.js";
5
- import type { WorktreeStatus } from "./worktree.js";
6
- import type { WorktreeService } from "./worktree-service.js";
7
- /** A question, with the task that asked it and where it stands. */
8
- export interface OpenQuestion extends Question {
9
- task: string;
10
- status: WorktreeStatus;
11
- /**
12
- * `held` means the asking agent is in a live session: answer it there,
13
- * since `arbor reply` refuses a tree someone is driving.
14
- */
15
- lease: "held" | "stale" | "none";
16
- /**
17
- * A reply or follow-up given and waiting for the agent to claim it, or
18
- * null.
19
- */
20
- pending: ReplyState | null;
21
- /**
22
- * Where it stands. `open` waits on a person. `waiting` has a reply its
23
- * agent has yet to read, which can still be edited. `editing` is held by
24
- * someone changing it. `read` has been claimed by its agent, which has yet
25
- * to check it off. `done` is checked off. A follow-up takes a `read` or
26
- * `done` question back to `waiting`.
27
- */
28
- state: QuestionState;
29
- }
30
- export type QuestionState = "open" | "waiting" | "editing" | "read" | "done";
31
- export interface Inbox {
32
- /**
33
- * Questions, by task name and then in the order each plan lists them.
34
- * Only the unanswered ones unless the others were asked for too.
35
- */
36
- questions: OpenQuestion[];
37
- /** How many unchecked questions have a reply their agent has yet to act on. */
38
- replied: number;
39
- }
40
- export interface InboxOptions {
41
- /**
42
- * Keep the questions replied to but not yet checked off, to change or add
43
- * to an answer. Without it, a question leaves the inbox once it is answered.
44
- */
45
- replied?: boolean;
46
- /** Keep the questions checked off too, to follow one up. */
47
- done?: boolean;
48
- }
49
- /**
50
- * Every question still waiting on a person, across all tasks: the unchecked
51
- * items under each worktree's `## Blocked` with no reply yet, and with
52
- * `replied` also those answered but not yet checked off, whether their agent
53
- * has read the reply or not.
54
- *
55
- * Returns data rather than printing it, so the CLI and the dev server show the
56
- * same inbox.
57
- */
58
- export declare function openQuestions({ service, fs, replies, }: {
59
- service: WorktreeService;
60
- fs: Fs;
61
- replies: Replies;
62
- }, { replied, done }?: InboxOptions): Promise<Inbox>;
63
- export interface InboxPrintOptions extends InboxOptions {
64
- /** Print the inbox as JSON instead of a listing. */
65
- json?: boolean;
66
- }
67
- /** `arbor inbox`: the open questions, grouped by task. */
68
- export declare function inbox(deps: {
69
- service: WorktreeService;
70
- fs: Fs;
71
- replies: Replies;
72
- log: Logger;
73
- }, { json, replied }?: InboxPrintOptions): Promise<void>;
74
- /**
75
- * One question as the inbox prints it: `Q9 subject`, its body and choices
76
- * under it, then the reply the agent has yet to act on, if there is one.
77
- */
78
- export declare function formatQuestion(question: Question & {
79
- pending?: ReplyState | null;
80
- }): string[];
package/replies.d.ts DELETED
@@ -1,81 +0,0 @@
1
- import { type IdProvider } from "webappwiz/id";
2
- import type { Fs, Lock } from "webappwiz/system";
3
- import { Attachments } from "./attachments.js";
4
- import { type Question } from "./plan.js";
5
- /** How long a person opening a reply holds it before it lapses unsaved. */
6
- export declare const EDIT_MS: number;
7
- /**
8
- * A reply a person gave that its agent has not read yet. It waits here, out
9
- * of the plan, so the agent sees nothing half-written: the agent claims it to
10
- * read it, which writes it into the plan and ends any chance to edit it.
11
- */
12
- export interface ReplyState {
13
- task: string;
14
- /** `Q2`. */
15
- question: string;
16
- /** The keys of the choices picked. */
17
- choices: string[];
18
- /** Words, alongside the picks or instead of them. */
19
- text: string;
20
- /** Absolute paths of the files attached, in `replies/<task>/Q2/`. */
21
- files: string[];
22
- repliedAt: string;
23
- /**
24
- * While a person has it open to edit, when that hold lapses; null when
25
- * nobody does. An agent reads nothing held, so it never sees a reply
26
- * mid-change.
27
- */
28
- editingUntil: string | null;
29
- }
30
- /** One reply waiting to be read, with what can be done to it. */
31
- export declare class PendingReply {
32
- private readonly replies;
33
- readonly state: ReplyState;
34
- constructor(replies: Replies, state: ReplyState);
35
- /** Held by a person right now. */
36
- get editing(): boolean;
37
- get attachments(): Attachments;
38
- /** Holds it for a person to edit, for `EDIT_MS` from now. */
39
- hold(): Promise<PendingReply>;
40
- release(): Promise<PendingReply>;
41
- /** Drops the record. Its files go too unless `keepFiles`, as a claim wants. */
42
- remove({ keepFiles }?: {
43
- keepFiles?: boolean | undefined;
44
- }): Promise<void>;
45
- }
46
- /**
47
- * Every reply waiting to be read, one JSON file each at
48
- * `.git/arbor/replies/<task>/<question>.json`, its files beside it. Removing a
49
- * task takes its folder with it.
50
- */
51
- export declare class Replies {
52
- readonly dir: string;
53
- /** Held across a read and the write it decides, so a claim and an edit never cross. */
54
- private readonly lock;
55
- private readonly fs;
56
- private readonly ids;
57
- constructor(dir: string,
58
- /** Held across a read and the write it decides, so a claim and an edit never cross. */
59
- lock: Lock, fs: Fs, { ids }?: {
60
- ids?: IdProvider;
61
- });
62
- /** Runs `work` holding the lock every write to a reply goes through. */
63
- locked<T>(work: () => Promise<T>): Promise<T>;
64
- find(task: string, question: string): Promise<PendingReply | null>;
65
- /** A task's waiting replies, by question number. */
66
- forTask(task: string): Promise<PendingReply[]>;
67
- attachments(task: string, question: string): Attachments;
68
- /** Whether `path` is a file some reply holds, and not a way out of here. */
69
- owns(path: string): boolean;
70
- /** @internal Writes one reply. Rename makes the swap atomic for readers. */
71
- save(state: ReplyState): Promise<PendingReply>;
72
- /** @internal */
73
- delete(task: string, question: string): Promise<void>;
74
- private path;
75
- private read;
76
- }
77
- /**
78
- * A reply as its line in the plan reads once claimed: the picks spelled out,
79
- * the words, then each file's path, so the line alone is the whole answer.
80
- */
81
- export declare function replyText(asked: Question, reply: ReplyState): string;