@webappwiz/arbor 0.0.26 → 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/README.md +77 -166
- package/add.d.ts +1 -1
- package/attachments.d.ts +4 -4
- package/dev.d.ts +5 -10
- package/exit.d.ts +0 -1
- package/index.js +439 -1254
- package/merge.d.ts +1 -3
- package/package.json +2 -2
- package/plan.d.ts +4 -33
- package/repository.d.ts +0 -2
- package/show.d.ts +1 -1
- package/snapshot.d.ts +4 -8
- package/todo.d.ts +90 -16
- package/wait.d.ts +2 -16
- package/worktree-service.d.ts +0 -7
- package/worktree.d.ts +1 -1
- package/inbox.d.ts +0 -80
- package/replies.d.ts +0 -81
- package/reply.d.ts +0 -102
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,
|
|
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.
|
|
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.
|
|
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 `- [ ]
|
|
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, `
|
|
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: `
|
|
97
|
-
* all mean `
|
|
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, `
|
|
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
|
|
9
|
-
*
|
|
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,
|
|
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({
|
|
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
|
|
22
|
+
/** A todo's words, place or files changed: new ones added, some of the old kept. */
|
|
18
23
|
export interface TodoChange {
|
|
19
|
-
/**
|
|
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,
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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(
|
|
77
|
-
/**
|
|
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`:
|
|
99
|
-
* finished it knows that context best, then the
|
|
100
|
-
*
|
|
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
|
-
},
|
|
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
|
-
/**
|
|
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
|
|
19
|
+
export declare function wait({ service, log }: {
|
|
32
20
|
service: WorktreeService;
|
|
33
21
|
log: Logger;
|
|
34
|
-
|
|
35
|
-
replies: Replies;
|
|
36
|
-
}, task: string, { timeout, poll, answered, }?: WaitOptions): Promise<void>;
|
|
22
|
+
}, task: string, { timeout, poll }?: WaitOptions): Promise<void>;
|
package/worktree-service.d.ts
CHANGED
|
@@ -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, `
|
|
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;
|