@pleaseai/work 0.0.0 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +112 -0
- package/README.md +563 -0
- package/dist/index.js +240 -25
- package/package.json +12 -8
package/LICENSE
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
Functional Source License, Version 1.1, MIT Future License
|
|
2
|
+
|
|
3
|
+
Copyright 2026 Passion Factory, Inc
|
|
4
|
+
|
|
5
|
+
## Abbreviation
|
|
6
|
+
|
|
7
|
+
FSL-1.1-MIT
|
|
8
|
+
|
|
9
|
+
## Notice
|
|
10
|
+
|
|
11
|
+
Copyright 2026 Passion Factory, Inc
|
|
12
|
+
|
|
13
|
+
## Terms and Conditions
|
|
14
|
+
|
|
15
|
+
### Licensor ("We")
|
|
16
|
+
|
|
17
|
+
The party offering the Software under these Terms and Conditions.
|
|
18
|
+
|
|
19
|
+
### The Software
|
|
20
|
+
|
|
21
|
+
The "Software" is each version of the software that we make available under
|
|
22
|
+
these Terms and Conditions, as indicated by our inclusion of these Terms and
|
|
23
|
+
Conditions with the Software.
|
|
24
|
+
|
|
25
|
+
### License Grant
|
|
26
|
+
|
|
27
|
+
Subject to your compliance with this License Grant and the Patents,
|
|
28
|
+
Redistribution and Trademark clauses below, we hereby grant you the right to
|
|
29
|
+
use, copy, modify, create derivative works, publicly perform, publicly display
|
|
30
|
+
and redistribute the Software for any Permitted Purpose identified below.
|
|
31
|
+
|
|
32
|
+
### Permitted Purpose
|
|
33
|
+
|
|
34
|
+
A Permitted Purpose is any purpose other than a Competing Use. A Competing Use
|
|
35
|
+
means making the Software available to others in a commercial product or
|
|
36
|
+
service that:
|
|
37
|
+
|
|
38
|
+
1. substitutes for the Software;
|
|
39
|
+
|
|
40
|
+
2. substitutes for any other product or service we offer using the Software
|
|
41
|
+
that exists as of the date we make the Software available; or
|
|
42
|
+
|
|
43
|
+
3. offers the same or substantially similar functionality as the Software.
|
|
44
|
+
|
|
45
|
+
Permitted Purposes specifically include using the Software:
|
|
46
|
+
|
|
47
|
+
1. for your internal use and access;
|
|
48
|
+
|
|
49
|
+
2. for non-commercial education;
|
|
50
|
+
|
|
51
|
+
3. for non-commercial research; and
|
|
52
|
+
|
|
53
|
+
4. in connection with professional services that you provide to a licensee
|
|
54
|
+
using the Software in accordance with these Terms and Conditions.
|
|
55
|
+
|
|
56
|
+
### Patents
|
|
57
|
+
|
|
58
|
+
To the extent your use for a Permitted Purpose would necessarily infringe our
|
|
59
|
+
patents, the license grant above includes a license under our patents. If you
|
|
60
|
+
make a claim against any party that the Software infringes or contributes to
|
|
61
|
+
the infringement of any patent, then your patent license to the Software ends
|
|
62
|
+
immediately.
|
|
63
|
+
|
|
64
|
+
### Redistribution
|
|
65
|
+
|
|
66
|
+
The Terms and Conditions apply to all copies, modifications and derivatives of
|
|
67
|
+
the Software.
|
|
68
|
+
|
|
69
|
+
If you redistribute any copies, modifications or derivatives of the Software,
|
|
70
|
+
you must include a copy of or a link to these Terms and Conditions and not
|
|
71
|
+
remove any copyright notices provided in or with the Software.
|
|
72
|
+
|
|
73
|
+
### Disclaimer
|
|
74
|
+
|
|
75
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND WITHOUT WARRANTIES OF ANY KIND, EXPRESS OR
|
|
76
|
+
IMPLIED, INCLUDING WITHOUT LIMITATION WARRANTIES OF FITNESS FOR A PARTICULAR
|
|
77
|
+
PURPOSE, MERCHANTABILITY, TITLE OR NON-INFRINGEMENT.
|
|
78
|
+
|
|
79
|
+
IN NO EVENT WILL WE HAVE ANY LIABILITY TO YOU ARISING OUT OF OR RELATED TO THE
|
|
80
|
+
SOFTWARE, INCLUDING INDIRECT, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES,
|
|
81
|
+
EVEN IF WE HAVE BEEN INFORMED OF THEIR POSSIBILITY IN ADVANCE.
|
|
82
|
+
|
|
83
|
+
### Trademarks
|
|
84
|
+
|
|
85
|
+
Except for displaying the License Details and identifying us as the origin of
|
|
86
|
+
the Software, you have no right under these Terms and Conditions to use our
|
|
87
|
+
trademarks, trade names, service marks or product names.
|
|
88
|
+
|
|
89
|
+
## Grant of Future License
|
|
90
|
+
|
|
91
|
+
We hereby irrevocably grant you an additional license to use the Software under
|
|
92
|
+
the MIT License that is effective on 2028-03-13. On or after that date, you
|
|
93
|
+
may use the Software under the MIT License, in which case the following will
|
|
94
|
+
apply:
|
|
95
|
+
|
|
96
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
97
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
98
|
+
in the Software without restriction, including without limitation the rights
|
|
99
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
100
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
101
|
+
furnished to do so, subject to the following conditions:
|
|
102
|
+
|
|
103
|
+
The above copyright notice and this permission notice shall be included in all
|
|
104
|
+
copies or substantial portions of the Software.
|
|
105
|
+
|
|
106
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
107
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
108
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
109
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
110
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
111
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
112
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,563 @@
|
|
|
1
|
+
# Work Please
|
|
2
|
+
|
|
3
|
+
[](https://sonarcloud.io/summary/new_code?id=pleaseai_work-please) [](https://sonarcloud.io/summary/new_code?id=pleaseai_work-please) [](https://sonarcloud.io/summary/new_code?id=pleaseai_work-please) [](https://sonarcloud.io/summary/new_code?id=pleaseai_work-please)
|
|
4
|
+
[](https://codecov.io/gh/pleaseai/work-please)
|
|
5
|
+
|
|
6
|
+
Work Please turns issue tracker tasks into isolated, autonomous implementation runs — managing work
|
|
7
|
+
instead of supervising coding agents.
|
|
8
|
+
|
|
9
|
+
> **Warning**: Work Please is an engineering preview for use in trusted environments.
|
|
10
|
+
|
|
11
|
+
## Table of Contents
|
|
12
|
+
|
|
13
|
+
- [Overview](#overview)
|
|
14
|
+
- [Key Differences from Symphony](#key-differences-from-symphony)
|
|
15
|
+
- [Features](#features)
|
|
16
|
+
- [Architecture](#architecture)
|
|
17
|
+
- [Quick Start](#quick-start)
|
|
18
|
+
- [Prerequisites](#prerequisites)
|
|
19
|
+
- [Install](#install)
|
|
20
|
+
- [Configure](#configure)
|
|
21
|
+
- [Run](#run)
|
|
22
|
+
- [WORKFLOW.md Configuration](#workflowmd-configuration)
|
|
23
|
+
- [Full Front Matter Schema](#full-front-matter-schema)
|
|
24
|
+
- [Template Variables](#template-variables)
|
|
25
|
+
- [CLI Usage](#cli-usage)
|
|
26
|
+
- [GitHub App Authentication](#github-app-authentication)
|
|
27
|
+
- [Setting up GitHub App credentials](#setting-up-github-app-credentials)
|
|
28
|
+
- [Validation](#validation)
|
|
29
|
+
- [Trust and Safety](#trust-and-safety)
|
|
30
|
+
- [Permission Modes](#permission-modes)
|
|
31
|
+
- [Workspace Isolation](#workspace-isolation)
|
|
32
|
+
- [Recommendations](#recommendations)
|
|
33
|
+
- [License](#license)
|
|
34
|
+
|
|
35
|
+
## Overview
|
|
36
|
+
|
|
37
|
+
Work Please is a long-running TypeScript service that:
|
|
38
|
+
|
|
39
|
+
1. Polls an issue tracker (Asana or GitHub Projects v2) for tasks in configured active states.
|
|
40
|
+
2. Creates an isolated workspace directory for each eligible issue.
|
|
41
|
+
3. Launches a Claude Code agent session inside that workspace with a rendered prompt.
|
|
42
|
+
4. Monitors the session, handles retries, and reconciles issue state on each poll cycle.
|
|
43
|
+
|
|
44
|
+
It is a TypeScript implementation of the [Symphony specification](vendor/symphony/SPEC.md),
|
|
45
|
+
adapted for Asana / GitHub Projects v2 and Claude Code instead of Linear and Codex.
|
|
46
|
+
|
|
47
|
+
For full technical details, see [SPEC.md](SPEC.md).
|
|
48
|
+
|
|
49
|
+
## Key Differences from Symphony
|
|
50
|
+
|
|
51
|
+
| | Symphony (reference) | Work Please |
|
|
52
|
+
|---|---|---|
|
|
53
|
+
| Issue Tracker | Linear | Asana & GitHub Projects v2 |
|
|
54
|
+
| Coding Agent | Codex (app-server mode) | Claude Code CLI |
|
|
55
|
+
| Language | Elixir/OTP | TypeScript + Bun |
|
|
56
|
+
| Tracker Auth | `LINEAR_API_KEY` | `ASANA_ACCESS_TOKEN`, `GITHUB_TOKEN`, or GitHub App credentials |
|
|
57
|
+
| Project Config | `project_slug` | `project_gid` (Asana) or `owner` + `project_number` (GitHub Projects v2) |
|
|
58
|
+
| Issue States | Linear workflow states | Asana sections / GitHub Projects v2 Status field |
|
|
59
|
+
| Agent Protocol | JSON-RPC over stdio | `@anthropic-ai/claude-agent-sdk` |
|
|
60
|
+
| Permission Model | Codex approval/sandbox policies | Claude Code `--permission-mode` |
|
|
61
|
+
|
|
62
|
+
## Features
|
|
63
|
+
|
|
64
|
+
- **Multi-tracker support** — Dispatch work from Asana tasks or GitHub Projects v2 items on a
|
|
65
|
+
fixed cadence.
|
|
66
|
+
- **GitHub App authentication** — Authenticate the GitHub tracker with a GitHub App installation
|
|
67
|
+
token (`app_id` + `private_key` + `installation_id`) instead of a PAT, for fine-grained
|
|
68
|
+
permissions and higher API rate limits.
|
|
69
|
+
- **Assignee & label filters** — Filter eligible issues by assignee and/or label. Multiple values
|
|
70
|
+
within each filter use OR logic; assignee and label filters are ANDed when both are specified.
|
|
71
|
+
Applies at dispatch time only — already-running issues are unaffected. Configured per-tracker
|
|
72
|
+
in `WORKFLOW.md`.
|
|
73
|
+
- **Isolated workspaces** — Each issue gets a dedicated directory; workspaces persist across runs.
|
|
74
|
+
- **`WORKFLOW.md` config** — Version agent prompt and runtime settings alongside your code.
|
|
75
|
+
- **Bounded concurrency** — Global and per-state concurrent agent limits.
|
|
76
|
+
- **Retry with backoff** — Exponential backoff on failures; short continuation retry on clean exit.
|
|
77
|
+
- **Dynamic config reload** — Edit `WORKFLOW.md` and changes apply without restarting the service.
|
|
78
|
+
- **Workspace hooks** — Shell scripts run at `after_create`, `before_run`, `after_run`, and
|
|
79
|
+
`before_remove` lifecycle events.
|
|
80
|
+
- **Structured logging** — Operator-visible logs with stable `key=value` format.
|
|
81
|
+
- **Optional HTTP dashboard** — Enable with `--port` for runtime status and JSON API.
|
|
82
|
+
|
|
83
|
+
## Architecture
|
|
84
|
+
|
|
85
|
+
```
|
|
86
|
+
WORKFLOW.md
|
|
87
|
+
|
|
|
88
|
+
v
|
|
89
|
+
Config Layer ──> Orchestrator ──> Workspace Manager ──> Agent Runner (Claude Code)
|
|
90
|
+
| |
|
|
91
|
+
v v
|
|
92
|
+
Issue Tracker Client Isolated workspace/
|
|
93
|
+
(Asana REST API or per-issue directory
|
|
94
|
+
GitHub GraphQL API,
|
|
95
|
+
polling + reconciliation)
|
|
96
|
+
|
|
|
97
|
+
v
|
|
98
|
+
Status Surface (optional HTTP dashboard / structured logs)
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Components:
|
|
102
|
+
|
|
103
|
+
- **Workflow Loader** — Parses `WORKFLOW.md` YAML front matter and prompt template body.
|
|
104
|
+
- **Config Layer** — Typed getters with env-var indirection and built-in defaults.
|
|
105
|
+
- **Issue Tracker Client** — Fetches candidate issues, reconciles running-issue states. Supports
|
|
106
|
+
Asana (REST API) and GitHub Projects v2 (GraphQL API) adapters.
|
|
107
|
+
- **Orchestrator** — Owns in-memory state; drives the poll/dispatch/retry loop.
|
|
108
|
+
- **Workspace Manager** — Creates, reuses, and cleans per-issue workspaces; runs hooks.
|
|
109
|
+
- **Agent Runner** — Launches Claude Code, streams events back to the orchestrator.
|
|
110
|
+
- **Status Surface** — Optional terminal view and HTTP API for operator visibility.
|
|
111
|
+
|
|
112
|
+
See [SPEC.md](SPEC.md) for the full specification.
|
|
113
|
+
|
|
114
|
+
## Quick Start
|
|
115
|
+
|
|
116
|
+
### Prerequisites
|
|
117
|
+
|
|
118
|
+
- **Bun** (see [bun.sh](https://bun.sh) for installation)
|
|
119
|
+
- **Claude Code CLI** (see the [official installation guide](https://docs.anthropic.com/en/docs/claude-code))
|
|
120
|
+
- **Asana access token** (`ASANA_ACCESS_TOKEN`) **or** **GitHub token** (`GITHUB_TOKEN`) with
|
|
121
|
+
access to the target project, **or** **GitHub App credentials** (`GITHUB_APP_ID`,
|
|
122
|
+
`GITHUB_APP_PRIVATE_KEY`, `GITHUB_APP_INSTALLATION_ID`) — see [GitHub App Authentication](#github-app-authentication)
|
|
123
|
+
|
|
124
|
+
### Install
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
git clone https://github.com/chatbot-pf/work-please.git
|
|
128
|
+
cd work-please
|
|
129
|
+
bun install
|
|
130
|
+
bun run build
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Configure
|
|
134
|
+
|
|
135
|
+
Create a `WORKFLOW.md` in your target repository. Two examples are shown below.
|
|
136
|
+
|
|
137
|
+
#### Asana
|
|
138
|
+
|
|
139
|
+
```markdown
|
|
140
|
+
---
|
|
141
|
+
tracker:
|
|
142
|
+
kind: asana
|
|
143
|
+
api_key: $ASANA_ACCESS_TOKEN
|
|
144
|
+
project_gid: "1234567890123456"
|
|
145
|
+
active_sections:
|
|
146
|
+
- In Progress
|
|
147
|
+
terminal_sections:
|
|
148
|
+
- Done
|
|
149
|
+
- Cancelled
|
|
150
|
+
|
|
151
|
+
polling:
|
|
152
|
+
interval_ms: 30000
|
|
153
|
+
|
|
154
|
+
workspace:
|
|
155
|
+
root: ~/work-please_workspaces
|
|
156
|
+
|
|
157
|
+
hooks:
|
|
158
|
+
after_create: |
|
|
159
|
+
git clone https://github.com/your-org/your-repo.git .
|
|
160
|
+
bun install
|
|
161
|
+
|
|
162
|
+
agent:
|
|
163
|
+
max_concurrent_agents: 3
|
|
164
|
+
max_turns: 20
|
|
165
|
+
|
|
166
|
+
claude:
|
|
167
|
+
permission_mode: acceptEdits
|
|
168
|
+
turn_timeout_ms: 3600000
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
You are working on an Asana task for the project.
|
|
172
|
+
|
|
173
|
+
Task: {{ issue.title }}
|
|
174
|
+
|
|
175
|
+
{{ issue.description }}
|
|
176
|
+
|
|
177
|
+
{% if issue.blocked_by.size > 0 %}
|
|
178
|
+
Blocked by:
|
|
179
|
+
{% for blocker in issue.blocked_by %}
|
|
180
|
+
- {{ blocker.identifier }} ({{ blocker.state }})
|
|
181
|
+
{% endfor %}
|
|
182
|
+
{% endif %}
|
|
183
|
+
|
|
184
|
+
{% if attempt %}
|
|
185
|
+
This is attempt #{{ attempt }}. Review any prior work in the workspace before continuing.
|
|
186
|
+
{% endif %}
|
|
187
|
+
|
|
188
|
+
Your task:
|
|
189
|
+
1. Understand the task requirements.
|
|
190
|
+
2. Implement the requested changes.
|
|
191
|
+
3. Write or update tests as needed.
|
|
192
|
+
4. Open a pull request and move this task to the review section.
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
#### GitHub Projects v2 (PAT)
|
|
196
|
+
|
|
197
|
+
```markdown
|
|
198
|
+
---
|
|
199
|
+
tracker:
|
|
200
|
+
kind: github_projects
|
|
201
|
+
api_key: $GITHUB_TOKEN
|
|
202
|
+
owner: your-org
|
|
203
|
+
project_number: 42
|
|
204
|
+
active_statuses:
|
|
205
|
+
- In Progress
|
|
206
|
+
terminal_statuses:
|
|
207
|
+
- Done
|
|
208
|
+
- Cancelled
|
|
209
|
+
|
|
210
|
+
polling:
|
|
211
|
+
interval_ms: 30000
|
|
212
|
+
|
|
213
|
+
workspace:
|
|
214
|
+
root: ~/work-please_workspaces
|
|
215
|
+
|
|
216
|
+
hooks:
|
|
217
|
+
after_create: |
|
|
218
|
+
git clone https://github.com/your-org/your-repo.git .
|
|
219
|
+
bun install
|
|
220
|
+
|
|
221
|
+
agent:
|
|
222
|
+
max_concurrent_agents: 3
|
|
223
|
+
max_turns: 20
|
|
224
|
+
|
|
225
|
+
claude:
|
|
226
|
+
permission_mode: acceptEdits
|
|
227
|
+
turn_timeout_ms: 3600000
|
|
228
|
+
---
|
|
229
|
+
|
|
230
|
+
You are working on a GitHub issue for the repository `your-org/your-repo`.
|
|
231
|
+
|
|
232
|
+
Issue {{ issue.identifier }}: {{ issue.title }}
|
|
233
|
+
|
|
234
|
+
{{ issue.description }}
|
|
235
|
+
|
|
236
|
+
{% if issue.blocked_by.size > 0 %}
|
|
237
|
+
Blocked by:
|
|
238
|
+
{% for blocker in issue.blocked_by %}
|
|
239
|
+
- {{ blocker.identifier }} ({{ blocker.state }})
|
|
240
|
+
{% endfor %}
|
|
241
|
+
{% endif %}
|
|
242
|
+
|
|
243
|
+
{% if attempt %}
|
|
244
|
+
This is attempt #{{ attempt }}. Review any prior work in the workspace before continuing.
|
|
245
|
+
{% endif %}
|
|
246
|
+
|
|
247
|
+
Your task:
|
|
248
|
+
1. Understand the issue requirements.
|
|
249
|
+
2. Implement the requested changes.
|
|
250
|
+
3. Write or update tests as needed.
|
|
251
|
+
4. Open a pull request and move this issue to the review status.
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
#### GitHub Projects v2 (GitHub App)
|
|
255
|
+
|
|
256
|
+
Use GitHub App credentials instead of a PAT for fine-grained permissions and higher API rate limits:
|
|
257
|
+
|
|
258
|
+
```markdown
|
|
259
|
+
---
|
|
260
|
+
tracker:
|
|
261
|
+
kind: github_projects
|
|
262
|
+
app_id: $GITHUB_APP_ID
|
|
263
|
+
private_key: $GITHUB_APP_PRIVATE_KEY
|
|
264
|
+
installation_id: $GITHUB_APP_INSTALLATION_ID
|
|
265
|
+
owner: your-org
|
|
266
|
+
project_number: 42
|
|
267
|
+
active_statuses:
|
|
268
|
+
- In Progress
|
|
269
|
+
terminal_statuses:
|
|
270
|
+
- Done
|
|
271
|
+
- Cancelled
|
|
272
|
+
|
|
273
|
+
polling:
|
|
274
|
+
interval_ms: 30000
|
|
275
|
+
|
|
276
|
+
workspace:
|
|
277
|
+
root: ~/work-please_workspaces
|
|
278
|
+
|
|
279
|
+
hooks:
|
|
280
|
+
after_create: |
|
|
281
|
+
git clone https://github.com/your-org/your-repo.git .
|
|
282
|
+
bun install
|
|
283
|
+
|
|
284
|
+
agent:
|
|
285
|
+
max_concurrent_agents: 3
|
|
286
|
+
max_turns: 20
|
|
287
|
+
|
|
288
|
+
claude:
|
|
289
|
+
permission_mode: acceptEdits
|
|
290
|
+
turn_timeout_ms: 3600000
|
|
291
|
+
---
|
|
292
|
+
|
|
293
|
+
You are working on a GitHub issue for the repository `your-org/your-repo`.
|
|
294
|
+
|
|
295
|
+
Issue {{ issue.identifier }}: {{ issue.title }}
|
|
296
|
+
|
|
297
|
+
{{ issue.description }}
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
### Run
|
|
301
|
+
|
|
302
|
+
```bash
|
|
303
|
+
# Set your tracker token
|
|
304
|
+
export ASANA_ACCESS_TOKEN=your_token_here
|
|
305
|
+
# or (GitHub PAT)
|
|
306
|
+
export GITHUB_TOKEN=ghp_your_token_here
|
|
307
|
+
# or (GitHub App)
|
|
308
|
+
export GITHUB_APP_ID=12345
|
|
309
|
+
export GITHUB_APP_PRIVATE_KEY="$(cat path/to/private-key.pem)"
|
|
310
|
+
export GITHUB_APP_INSTALLATION_ID=67890
|
|
311
|
+
|
|
312
|
+
# Run Work Please against a WORKFLOW.md in the current directory
|
|
313
|
+
bunx work-please
|
|
314
|
+
|
|
315
|
+
# Or specify a WORKFLOW.md path
|
|
316
|
+
bunx work-please /path/to/WORKFLOW.md
|
|
317
|
+
|
|
318
|
+
# Enable the optional HTTP dashboard on port 3000
|
|
319
|
+
bunx work-please --port 3000
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
## WORKFLOW.md Configuration
|
|
323
|
+
|
|
324
|
+
`WORKFLOW.md` is the single source of truth for Work Please's runtime behavior. It combines a YAML
|
|
325
|
+
front matter configuration block with a Markdown prompt template body.
|
|
326
|
+
|
|
327
|
+
### Full Front Matter Schema
|
|
328
|
+
|
|
329
|
+
```yaml
|
|
330
|
+
---
|
|
331
|
+
tracker:
|
|
332
|
+
kind: asana # Required: "asana" or "github_projects"
|
|
333
|
+
|
|
334
|
+
# --- Asana fields (when kind == "asana") ---
|
|
335
|
+
api_key: $ASANA_ACCESS_TOKEN # Required: token or $ENV_VAR
|
|
336
|
+
endpoint: https://app.asana.com/api/1.0 # Optional: override Asana API base URL
|
|
337
|
+
project_gid: "1234567890123456" # Required: Asana project GID
|
|
338
|
+
active_sections: # Optional: default ["To Do", "In Progress"]
|
|
339
|
+
- In Progress
|
|
340
|
+
terminal_sections: # Optional: default ["Done", "Cancelled"]
|
|
341
|
+
- Done
|
|
342
|
+
- Cancelled
|
|
343
|
+
|
|
344
|
+
# --- GitHub Projects v2 fields (when kind == "github_projects") ---
|
|
345
|
+
# api_key: $GITHUB_TOKEN # Required: token or $ENV_VAR
|
|
346
|
+
# endpoint: https://api.github.com # Optional: override GitHub API base URL
|
|
347
|
+
# owner: your-org # Required: GitHub organization or user login
|
|
348
|
+
# project_number: 42 # Required: GitHub Projects v2 project number
|
|
349
|
+
# project_id: PVT_kwDOxxxxx # Optional: project node ID (bypasses owner+project_number lookup)
|
|
350
|
+
# active_statuses: # Optional: default ["Todo", "In Progress"]
|
|
351
|
+
# - In Progress
|
|
352
|
+
# terminal_statuses: # Optional: default ["Done", "Cancelled"]
|
|
353
|
+
# - Done
|
|
354
|
+
# - Cancelled
|
|
355
|
+
# GitHub App authentication (alternative to api_key — all three required together):
|
|
356
|
+
# app_id: $GITHUB_APP_ID # Optional: GitHub App ID (integer or $ENV_VAR)
|
|
357
|
+
# private_key: $GITHUB_APP_PRIVATE_KEY # Optional: GitHub App private key PEM or $ENV_VAR
|
|
358
|
+
# installation_id: $GITHUB_APP_INSTALLATION_ID # Optional: installation ID (integer or $ENV_VAR)
|
|
359
|
+
|
|
360
|
+
# --- Shared filter fields (both trackers) ---
|
|
361
|
+
# filter:
|
|
362
|
+
# assignee: user1, user2 # Optional: CSV or YAML array; case-insensitive OR match
|
|
363
|
+
# # (unassigned issues are excluded when this filter is set)
|
|
364
|
+
# label: bug, feature # Optional: CSV or YAML array; case-insensitive OR match
|
|
365
|
+
# Both filters AND together when both are specified. Applies at dispatch time only.
|
|
366
|
+
|
|
367
|
+
polling:
|
|
368
|
+
interval_ms: 30000 # Optional: poll cadence in ms, default 30000
|
|
369
|
+
|
|
370
|
+
workspace:
|
|
371
|
+
root: ~/work-please_workspaces # Optional: default <tmpdir>/work-please_workspaces
|
|
372
|
+
|
|
373
|
+
hooks:
|
|
374
|
+
after_create: | # Optional: run once when workspace is first created
|
|
375
|
+
git clone https://github.com/your-org/your-repo.git .
|
|
376
|
+
before_run: | # Optional: run before each agent attempt
|
|
377
|
+
git pull --rebase
|
|
378
|
+
after_run: | # Optional: run after each agent attempt
|
|
379
|
+
echo "Run completed"
|
|
380
|
+
before_remove: | # Optional: run before workspace deletion
|
|
381
|
+
echo "Cleaning up"
|
|
382
|
+
timeout_ms: 60000 # Optional: hook timeout in ms, default 60000
|
|
383
|
+
|
|
384
|
+
agent:
|
|
385
|
+
max_concurrent_agents: 10 # Optional: global concurrency limit, default 10
|
|
386
|
+
max_retry_backoff_ms: 300000 # Optional: max retry delay in ms, default 300000
|
|
387
|
+
max_concurrent_agents_by_state: # Optional: per-state concurrency limits
|
|
388
|
+
in progress: 5
|
|
389
|
+
|
|
390
|
+
claude:
|
|
391
|
+
command: claude # Optional: Claude Code CLI command, default "claude"
|
|
392
|
+
permission_mode: acceptEdits # Optional: one of 'default', 'acceptEdits', 'bypassPermissions'. Defaults to 'bypassPermissions'.
|
|
393
|
+
allowed_tools: # Optional: restrict available tools
|
|
394
|
+
- Read
|
|
395
|
+
- Write
|
|
396
|
+
- Bash
|
|
397
|
+
turn_timeout_ms: 3600000 # Optional: per-turn timeout in ms, default 3600000
|
|
398
|
+
read_timeout_ms: 5000 # Optional: initial subprocess read timeout in ms, default 5000
|
|
399
|
+
stall_timeout_ms: 300000 # Optional: stall detection timeout, default 300000
|
|
400
|
+
|
|
401
|
+
server:
|
|
402
|
+
port: 3000 # Optional: enable HTTP dashboard on this port
|
|
403
|
+
---
|
|
404
|
+
|
|
405
|
+
Your prompt template goes here. Available variables:
|
|
406
|
+
|
|
407
|
+
- {{ issue.id }} — Tracker-internal issue ID
|
|
408
|
+
- {{ issue.identifier }} — Human-readable identifier (e.g. "#42" or task GID)
|
|
409
|
+
- {{ issue.title }} — Issue title
|
|
410
|
+
- {{ issue.description }} — Issue body/description
|
|
411
|
+
- {{ issue.state }} — Current tracker state name
|
|
412
|
+
- {{ issue.url }} — Issue URL
|
|
413
|
+
- {{ issue.assignee }} — Primary assignee login (GitHub) or email (Asana), or null if unassigned
|
|
414
|
+
- {{ issue.labels }} — Array of label strings (normalized to lowercase)
|
|
415
|
+
- {{ issue.blocked_by }} — Array of blocker refs (each has id, identifier, state)
|
|
416
|
+
- {{ issue.priority }} — Numeric priority or null
|
|
417
|
+
- {{ issue.created_at }} — ISO-8601 creation timestamp
|
|
418
|
+
- {{ issue.updated_at }} — ISO-8601 last-updated timestamp
|
|
419
|
+
- {{ attempt }} — Retry attempt number (null on first run)
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
### Template Variables
|
|
423
|
+
|
|
424
|
+
The prompt template uses Liquid-compatible syntax. All `issue` fields are available:
|
|
425
|
+
|
|
426
|
+
```markdown
|
|
427
|
+
{{ issue.identifier }}: {{ issue.title }}
|
|
428
|
+
|
|
429
|
+
{{ issue.description }}
|
|
430
|
+
|
|
431
|
+
State: {{ issue.state }}
|
|
432
|
+
|
|
433
|
+
{% if issue.blocked_by.size > 0 %}
|
|
434
|
+
Blocked by:
|
|
435
|
+
{% for blocker in issue.blocked_by %}
|
|
436
|
+
- {{ blocker.identifier }} ({{ blocker.state }})
|
|
437
|
+
{% endfor %}
|
|
438
|
+
{% endif %}
|
|
439
|
+
|
|
440
|
+
{% if attempt %}
|
|
441
|
+
Retry attempt: {{ attempt }}
|
|
442
|
+
{% endif %}
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
## CLI Usage
|
|
446
|
+
|
|
447
|
+
```bash
|
|
448
|
+
# Basic usage (reads WORKFLOW.md from current directory)
|
|
449
|
+
work-please
|
|
450
|
+
|
|
451
|
+
# Specify WORKFLOW.md path (positional argument)
|
|
452
|
+
work-please ./WORKFLOW.md
|
|
453
|
+
|
|
454
|
+
# Enable HTTP dashboard
|
|
455
|
+
work-please --port 3000
|
|
456
|
+
|
|
457
|
+
# Initialize a new GitHub Projects v2 project and scaffold WORKFLOW.md
|
|
458
|
+
# (Requires GITHUB_TOKEN environment variable to be set)
|
|
459
|
+
work-please init --owner <org-or-user> --title "My Project"
|
|
460
|
+
|
|
461
|
+
# Alternatively, provide the token via a flag:
|
|
462
|
+
work-please init --owner <org-or-user> --title "My Project" --token <your-github-token>
|
|
463
|
+
|
|
464
|
+
# Show help
|
|
465
|
+
work-please --help
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
## GitHub App Authentication
|
|
469
|
+
|
|
470
|
+
The `github_projects` tracker supports two authentication methods:
|
|
471
|
+
|
|
472
|
+
| Method | Config fields | When to use |
|
|
473
|
+
|--------|--------------|-------------|
|
|
474
|
+
| **PAT** | `api_key` | Personal access tokens — quick setup |
|
|
475
|
+
| **GitHub App** | `app_id`, `private_key`, `installation_id` | Organizations — fine-grained permissions, higher rate limits |
|
|
476
|
+
|
|
477
|
+
When both are present, `api_key` (PAT) takes precedence.
|
|
478
|
+
|
|
479
|
+
### Setting up GitHub App credentials
|
|
480
|
+
|
|
481
|
+
1. Create a GitHub App with the following permissions:
|
|
482
|
+
- **Repository permissions**:
|
|
483
|
+
- `Contents`: Read-only
|
|
484
|
+
- `Issues`: Read & write
|
|
485
|
+
- `Pull requests`: Read & write
|
|
486
|
+
- **Organization permissions**:
|
|
487
|
+
- `Projects`: Read & write
|
|
488
|
+
2. Install the app on your organization and note the **installation ID** (visible in the app's
|
|
489
|
+
installation settings URL).
|
|
490
|
+
3. Generate a **private key** (`.pem` file) from the app's settings page.
|
|
491
|
+
4. Set the environment variables:
|
|
492
|
+
|
|
493
|
+
```bash
|
|
494
|
+
export GITHUB_APP_ID=12345
|
|
495
|
+
export GITHUB_APP_PRIVATE_KEY="$(cat /path/to/private-key.pem)"
|
|
496
|
+
export GITHUB_APP_INSTALLATION_ID=67890
|
|
497
|
+
```
|
|
498
|
+
|
|
499
|
+
5. Reference them in `WORKFLOW.md`:
|
|
500
|
+
|
|
501
|
+
```yaml
|
|
502
|
+
tracker:
|
|
503
|
+
kind: github_projects
|
|
504
|
+
app_id: $GITHUB_APP_ID
|
|
505
|
+
private_key: $GITHUB_APP_PRIVATE_KEY
|
|
506
|
+
installation_id: $GITHUB_APP_INSTALLATION_ID
|
|
507
|
+
owner: your-org
|
|
508
|
+
project_number: 42
|
|
509
|
+
```
|
|
510
|
+
|
|
511
|
+
The values can also be inlined directly (not recommended for secrets):
|
|
512
|
+
|
|
513
|
+
```yaml
|
|
514
|
+
app_id: 12345
|
|
515
|
+
private_key: "-----BEGIN RSA PRIVATE KEY-----\n..."
|
|
516
|
+
installation_id: 67890
|
|
517
|
+
```
|
|
518
|
+
|
|
519
|
+
### Validation
|
|
520
|
+
|
|
521
|
+
Work Please validates GitHub App config at startup:
|
|
522
|
+
|
|
523
|
+
| Scenario | Result |
|
|
524
|
+
|----------|--------|
|
|
525
|
+
| `api_key` set | PAT auth — app fields ignored |
|
|
526
|
+
| All three app fields set (`app_id`, `private_key`, `installation_id`) | App auth |
|
|
527
|
+
| Only some app fields set | `incomplete_github_app_config` error |
|
|
528
|
+
| No auth configured | `missing_tracker_api_key` error |
|
|
529
|
+
|
|
530
|
+
## Trust and Safety
|
|
531
|
+
|
|
532
|
+
Work Please runs Claude Code autonomously. Understand the trust implications before deploying.
|
|
533
|
+
|
|
534
|
+
### Permission Modes
|
|
535
|
+
|
|
536
|
+
| Mode | Behavior | Recommended For |
|
|
537
|
+
|---|---|---|
|
|
538
|
+
| `default` | Interactive approval for sensitive operations | Development, unknown repositories |
|
|
539
|
+
| `acceptEdits` | Auto-approve file edits; prompt for shell commands | Trusted codebases |
|
|
540
|
+
| `bypassPermissions` | Auto-approve all operations | Sandboxed CI environments |
|
|
541
|
+
|
|
542
|
+
Start with `default` or `acceptEdits` unless you are running in a fully isolated environment.
|
|
543
|
+
|
|
544
|
+
### Workspace Isolation
|
|
545
|
+
|
|
546
|
+
- Each issue runs in a dedicated directory under `workspace.root`.
|
|
547
|
+
- Claude Code's working directory is validated against the workspace path before launch.
|
|
548
|
+
- Workspace paths are sanitized to prevent path traversal attacks.
|
|
549
|
+
|
|
550
|
+
### Recommendations
|
|
551
|
+
|
|
552
|
+
- Use `acceptEdits` permission mode as a baseline for most deployments.
|
|
553
|
+
- Use `bypassPermissions` only in network-isolated CI runners or Docker containers.
|
|
554
|
+
- Set `agent.max_concurrent_agents` conservatively when first testing.
|
|
555
|
+
- Monitor agent runs via the HTTP dashboard (`--port`) or structured logs.
|
|
556
|
+
- Keep API tokens scoped to the minimum required permissions.
|
|
557
|
+
|
|
558
|
+
## License
|
|
559
|
+
|
|
560
|
+
Apache License 2.0. See [LICENSE](vendor/symphony/LICENSE) for details.
|
|
561
|
+
|
|
562
|
+
Work Please is a TypeScript implementation based on the
|
|
563
|
+
[Symphony specification](vendor/symphony/SPEC.md) by OpenAI (Apache 2.0).
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
1
2
|
// @bun
|
|
2
3
|
var __create = Object.create;
|
|
3
4
|
var __getProtoOf = Object.getPrototypeOf;
|
|
@@ -31840,6 +31841,8 @@ function buildConfig(workflow) {
|
|
|
31840
31841
|
};
|
|
31841
31842
|
}
|
|
31842
31843
|
function buildTrackerConfig(kind, tracker) {
|
|
31844
|
+
const label_prefix = stringValue(tracker.label_prefix) ?? null;
|
|
31845
|
+
const filter = buildFilterConfig(sectionMap(tracker, "filter"));
|
|
31843
31846
|
if (kind === "asana") {
|
|
31844
31847
|
return {
|
|
31845
31848
|
kind,
|
|
@@ -31847,7 +31850,9 @@ function buildTrackerConfig(kind, tracker) {
|
|
|
31847
31850
|
api_key: resolveEnvValue(stringValue(tracker.api_key), process4.env.ASANA_ACCESS_TOKEN),
|
|
31848
31851
|
project_gid: stringValue(tracker.project_gid) ?? null,
|
|
31849
31852
|
active_sections: csvValue(tracker.active_sections) ?? csvValue(tracker.active_states) ?? DEFAULTS2.ASANA_ACTIVE_SECTIONS,
|
|
31850
|
-
terminal_sections: csvValue(tracker.terminal_sections) ?? csvValue(tracker.terminal_states) ?? DEFAULTS2.ASANA_TERMINAL_SECTIONS
|
|
31853
|
+
terminal_sections: csvValue(tracker.terminal_sections) ?? csvValue(tracker.terminal_states) ?? DEFAULTS2.ASANA_TERMINAL_SECTIONS,
|
|
31854
|
+
label_prefix,
|
|
31855
|
+
filter
|
|
31851
31856
|
};
|
|
31852
31857
|
}
|
|
31853
31858
|
if (kind === "github_projects") {
|
|
@@ -31862,13 +31867,23 @@ function buildTrackerConfig(kind, tracker) {
|
|
|
31862
31867
|
terminal_statuses: csvValue(tracker.terminal_statuses) ?? csvValue(tracker.terminal_states) ?? DEFAULTS2.GITHUB_TERMINAL_STATUSES,
|
|
31863
31868
|
app_id: resolveEnvValue(stringValue(tracker.app_id), process4.env.GITHUB_APP_ID),
|
|
31864
31869
|
private_key: resolveEnvValue(stringValue(tracker.private_key), process4.env.GITHUB_APP_PRIVATE_KEY),
|
|
31865
|
-
installation_id: resolveInstallationId(tracker.installation_id)
|
|
31870
|
+
installation_id: resolveInstallationId(tracker.installation_id),
|
|
31871
|
+
label_prefix,
|
|
31872
|
+
filter
|
|
31866
31873
|
};
|
|
31867
31874
|
}
|
|
31868
31875
|
return {
|
|
31869
31876
|
kind,
|
|
31870
31877
|
endpoint: stringValue(tracker.endpoint) ?? "",
|
|
31871
|
-
api_key: resolveEnvValue(stringValue(tracker.api_key), undefined)
|
|
31878
|
+
api_key: resolveEnvValue(stringValue(tracker.api_key), undefined),
|
|
31879
|
+
label_prefix,
|
|
31880
|
+
filter
|
|
31881
|
+
};
|
|
31882
|
+
}
|
|
31883
|
+
function buildFilterConfig(filter) {
|
|
31884
|
+
return {
|
|
31885
|
+
assignee: csvValue(filter.assignee) ?? [],
|
|
31886
|
+
label: csvValue(filter.label) ?? []
|
|
31872
31887
|
};
|
|
31873
31888
|
}
|
|
31874
31889
|
function validateConfig(config2) {
|
|
@@ -32062,6 +32077,120 @@ function normalizeTrackerKind(kind) {
|
|
|
32062
32077
|
return normalized || null;
|
|
32063
32078
|
}
|
|
32064
32079
|
|
|
32080
|
+
// src/label.ts
|
|
32081
|
+
var LABEL_COLORS = {
|
|
32082
|
+
dispatched: "1d76db",
|
|
32083
|
+
done: "0e8a16",
|
|
32084
|
+
failed: "d93f0b"
|
|
32085
|
+
};
|
|
32086
|
+
var LABEL_TIMEOUT_MS = 1e4;
|
|
32087
|
+
var GITHUB_ISSUE_URL_RE = /https?:\/\/[^/]+\/([^/]+)\/([^/]+)\/(?:issues|pull)\/(\d+)/;
|
|
32088
|
+
function parseGitHubIssueUrl(url2) {
|
|
32089
|
+
const match = url2.match(GITHUB_ISSUE_URL_RE);
|
|
32090
|
+
if (!match)
|
|
32091
|
+
return null;
|
|
32092
|
+
const number4 = Number.parseInt(match[3], 10);
|
|
32093
|
+
if (Number.isNaN(number4))
|
|
32094
|
+
return null;
|
|
32095
|
+
return { owner: match[1], repo: match[2], number: number4 };
|
|
32096
|
+
}
|
|
32097
|
+
function formatLabelName(prefix, state) {
|
|
32098
|
+
return `${prefix}: ${state}`;
|
|
32099
|
+
}
|
|
32100
|
+
function createLabelService(config2) {
|
|
32101
|
+
const { kind, label_prefix } = config2.tracker;
|
|
32102
|
+
if (!label_prefix)
|
|
32103
|
+
return null;
|
|
32104
|
+
if (kind !== "github_projects")
|
|
32105
|
+
return null;
|
|
32106
|
+
const apiKey = config2.tracker.api_key;
|
|
32107
|
+
const endpoint2 = config2.tracker.endpoint;
|
|
32108
|
+
const prefix = label_prefix;
|
|
32109
|
+
const headers = {
|
|
32110
|
+
Authorization: `bearer ${apiKey ?? ""}`,
|
|
32111
|
+
"Content-Type": "application/json",
|
|
32112
|
+
Accept: "application/vnd.github+json"
|
|
32113
|
+
};
|
|
32114
|
+
return {
|
|
32115
|
+
async setLabel(issue2, state) {
|
|
32116
|
+
if (!issue2.url)
|
|
32117
|
+
return;
|
|
32118
|
+
const parsed = parseGitHubIssueUrl(issue2.url);
|
|
32119
|
+
if (!parsed)
|
|
32120
|
+
return;
|
|
32121
|
+
const ctx = { endpoint: endpoint2, owner: parsed.owner, repo: parsed.repo, headers };
|
|
32122
|
+
try {
|
|
32123
|
+
const labelName = formatLabelName(prefix, state);
|
|
32124
|
+
await ensureLabelExists(ctx, labelName, state);
|
|
32125
|
+
await removeExistingPrefixLabels(ctx, parsed.number, prefix);
|
|
32126
|
+
await addLabel(ctx, parsed.number, labelName);
|
|
32127
|
+
} catch (err) {
|
|
32128
|
+
console.warn(`[label] error setting label issue_url=${issue2.url}: ${err}`);
|
|
32129
|
+
}
|
|
32130
|
+
}
|
|
32131
|
+
};
|
|
32132
|
+
}
|
|
32133
|
+
async function fetchWithTimeout(url2, options) {
|
|
32134
|
+
const controller = new AbortController;
|
|
32135
|
+
const timer = setTimeout(() => controller.abort(), LABEL_TIMEOUT_MS);
|
|
32136
|
+
try {
|
|
32137
|
+
return await fetch(url2, { ...options, signal: controller.signal });
|
|
32138
|
+
} finally {
|
|
32139
|
+
clearTimeout(timer);
|
|
32140
|
+
}
|
|
32141
|
+
}
|
|
32142
|
+
async function ensureLabelExists(ctx, name, state) {
|
|
32143
|
+
const { endpoint: endpoint2, owner, repo, headers } = ctx;
|
|
32144
|
+
const url2 = `${endpoint2}/repos/${owner}/${repo}/labels`;
|
|
32145
|
+
const response = await fetchWithTimeout(url2, {
|
|
32146
|
+
method: "POST",
|
|
32147
|
+
headers,
|
|
32148
|
+
body: JSON.stringify({ name, color: LABEL_COLORS[state] })
|
|
32149
|
+
});
|
|
32150
|
+
if (!response.ok && response.status !== 422) {
|
|
32151
|
+
console.warn(`[label] failed to ensure label exists label_name=${name} owner=${owner} repo=${repo}: HTTP ${response.status}`);
|
|
32152
|
+
}
|
|
32153
|
+
}
|
|
32154
|
+
async function removeExistingPrefixLabels(ctx, number4, prefix) {
|
|
32155
|
+
const { endpoint: endpoint2, owner, repo, headers } = ctx;
|
|
32156
|
+
const url2 = `${endpoint2}/repos/${owner}/${repo}/issues/${number4}/labels`;
|
|
32157
|
+
const response = await fetchWithTimeout(url2, { method: "GET", headers });
|
|
32158
|
+
if (!response.ok) {
|
|
32159
|
+
console.warn(`[label] failed to fetch existing labels owner=${owner} repo=${repo} issue_number=${number4}: HTTP ${response.status}`);
|
|
32160
|
+
return;
|
|
32161
|
+
}
|
|
32162
|
+
let labels;
|
|
32163
|
+
try {
|
|
32164
|
+
labels = await response.json();
|
|
32165
|
+
} catch {
|
|
32166
|
+
console.warn(`[label] failed to parse label list response for issue_number=${number4}`);
|
|
32167
|
+
return;
|
|
32168
|
+
}
|
|
32169
|
+
const toRemove = labels.filter((l) => l.name.startsWith(`${prefix}: `));
|
|
32170
|
+
for (const label of toRemove) {
|
|
32171
|
+
const deleteUrl = `${url2}/${encodeURIComponent(label.name)}`;
|
|
32172
|
+
const deleteResponse = await fetchWithTimeout(deleteUrl, { method: "DELETE", headers }).catch((err) => {
|
|
32173
|
+
console.warn(`[label] failed to remove label "${label.name}" issue_number=${number4}: ${err}`);
|
|
32174
|
+
return null;
|
|
32175
|
+
});
|
|
32176
|
+
if (deleteResponse && !deleteResponse.ok) {
|
|
32177
|
+
console.warn(`[label] failed to remove label "${label.name}" owner=${owner} repo=${repo} issue_number=${number4}: HTTP ${deleteResponse.status}`);
|
|
32178
|
+
}
|
|
32179
|
+
}
|
|
32180
|
+
}
|
|
32181
|
+
async function addLabel(ctx, number4, name) {
|
|
32182
|
+
const { endpoint: endpoint2, owner, repo, headers } = ctx;
|
|
32183
|
+
const url2 = `${endpoint2}/repos/${owner}/${repo}/issues/${number4}/labels`;
|
|
32184
|
+
const response = await fetchWithTimeout(url2, {
|
|
32185
|
+
method: "POST",
|
|
32186
|
+
headers,
|
|
32187
|
+
body: JSON.stringify({ labels: [name] })
|
|
32188
|
+
});
|
|
32189
|
+
if (!response.ok) {
|
|
32190
|
+
console.warn(`[label] failed to add label label_name=${name} owner=${owner} repo=${repo} issue_number=${number4}: HTTP ${response.status}`);
|
|
32191
|
+
}
|
|
32192
|
+
}
|
|
32193
|
+
|
|
32065
32194
|
// ../../node_modules/.bun/liquidjs@10.25.0/node_modules/liquidjs/dist/liquid.node.mjs
|
|
32066
32195
|
import { PassThrough } from "stream";
|
|
32067
32196
|
import { sep as sep3, extname, resolve as resolve$1, dirname as dirname$1 } from "path";
|
|
@@ -36756,6 +36885,53 @@ function issueToTemplateVars(issue2) {
|
|
|
36756
36885
|
};
|
|
36757
36886
|
}
|
|
36758
36887
|
|
|
36888
|
+
// src/filter.ts
|
|
36889
|
+
function matchesFilter(issue2, filter2) {
|
|
36890
|
+
if (filter2.assignee.length > 0) {
|
|
36891
|
+
const filterAssignees = new Set(filter2.assignee.map((a2) => a2.toLowerCase()));
|
|
36892
|
+
if (!issue2.assignees.some((a2) => filterAssignees.has(a2.toLowerCase()))) {
|
|
36893
|
+
return false;
|
|
36894
|
+
}
|
|
36895
|
+
}
|
|
36896
|
+
if (filter2.label.length > 0) {
|
|
36897
|
+
const filterLabels = new Set(filter2.label.map((l) => l.toLowerCase()));
|
|
36898
|
+
if (!issue2.labels.some((l) => filterLabels.has(l.toLowerCase()))) {
|
|
36899
|
+
return false;
|
|
36900
|
+
}
|
|
36901
|
+
}
|
|
36902
|
+
return true;
|
|
36903
|
+
}
|
|
36904
|
+
|
|
36905
|
+
// src/tracker/types.ts
|
|
36906
|
+
function isTrackerError(val) {
|
|
36907
|
+
return typeof val === "object" && val !== null && "code" in val;
|
|
36908
|
+
}
|
|
36909
|
+
function serializeCause(cause) {
|
|
36910
|
+
if (cause instanceof Error)
|
|
36911
|
+
return cause.message;
|
|
36912
|
+
if (typeof cause === "string")
|
|
36913
|
+
return cause;
|
|
36914
|
+
try {
|
|
36915
|
+
return JSON.stringify(cause) ?? String(cause);
|
|
36916
|
+
} catch {
|
|
36917
|
+
return String(cause);
|
|
36918
|
+
}
|
|
36919
|
+
}
|
|
36920
|
+
function formatTrackerError(err) {
|
|
36921
|
+
switch (err.code) {
|
|
36922
|
+
case "github_projects_api_status":
|
|
36923
|
+
case "asana_api_status":
|
|
36924
|
+
return `${err.code} (HTTP ${err.status})`;
|
|
36925
|
+
case "github_projects_graphql_errors":
|
|
36926
|
+
return `${err.code}: ${JSON.stringify(err.errors)}`;
|
|
36927
|
+
case "github_projects_api_request":
|
|
36928
|
+
case "asana_api_request":
|
|
36929
|
+
return `${err.code}: ${serializeCause(err.cause)}`;
|
|
36930
|
+
default:
|
|
36931
|
+
return err.code;
|
|
36932
|
+
}
|
|
36933
|
+
}
|
|
36934
|
+
|
|
36759
36935
|
// src/tracker/asana.ts
|
|
36760
36936
|
var PAGE_SIZE = 50;
|
|
36761
36937
|
var NETWORK_TIMEOUT_MS3 = 30000;
|
|
@@ -36764,6 +36940,7 @@ function createAsanaAdapter(config2) {
|
|
|
36764
36940
|
const apiKey = config2.tracker.api_key;
|
|
36765
36941
|
const projectGid = config2.tracker.project_gid ?? "";
|
|
36766
36942
|
const activeSections = config2.tracker.active_sections ?? ["To Do", "In Progress"];
|
|
36943
|
+
const filter2 = config2.tracker.filter;
|
|
36767
36944
|
function headers() {
|
|
36768
36945
|
return {
|
|
36769
36946
|
Authorization: `Bearer ${apiKey}`,
|
|
@@ -36809,7 +36986,7 @@ function createAsanaAdapter(config2) {
|
|
|
36809
36986
|
async function fetchTasksInSection(sectionGid, sectionName) {
|
|
36810
36987
|
const issues = [];
|
|
36811
36988
|
let offset2 = null;
|
|
36812
|
-
const fields = "gid,name,notes,dependencies,tags,created_at,modified_at,custom_fields,memberships.section.name";
|
|
36989
|
+
const fields = "gid,name,notes,dependencies,tags,created_at,modified_at,custom_fields,memberships.section.name,assignee,assignee.email";
|
|
36813
36990
|
do {
|
|
36814
36991
|
const url2 = offset2 ? `${endpoint2}/sections/${sectionGid}/tasks?opt_fields=${fields}&limit=${PAGE_SIZE}&offset=${encodeURIComponent(offset2)}` : `${endpoint2}/sections/${sectionGid}/tasks?opt_fields=${fields}&limit=${PAGE_SIZE}`;
|
|
36815
36992
|
const result = await fetchJson(url2);
|
|
@@ -36828,7 +37005,10 @@ function createAsanaAdapter(config2) {
|
|
|
36828
37005
|
}
|
|
36829
37006
|
return {
|
|
36830
37007
|
async fetchCandidateIssues() {
|
|
36831
|
-
|
|
37008
|
+
const issues = await fetchTasks(activeSections);
|
|
37009
|
+
if (isTrackerError(issues))
|
|
37010
|
+
return issues;
|
|
37011
|
+
return issues.filter((issue2) => matchesFilter(issue2, filter2));
|
|
36832
37012
|
},
|
|
36833
37013
|
async fetchIssuesByStates(states) {
|
|
36834
37014
|
if (states.length === 0)
|
|
@@ -36859,6 +37039,7 @@ function createAsanaAdapter(config2) {
|
|
|
36859
37039
|
state: state ?? "",
|
|
36860
37040
|
branch_name: null,
|
|
36861
37041
|
url: null,
|
|
37042
|
+
assignees: [],
|
|
36862
37043
|
labels: [],
|
|
36863
37044
|
blocked_by: [],
|
|
36864
37045
|
created_at: null,
|
|
@@ -36877,6 +37058,8 @@ function normalizeAsanaTask(task, sectionName) {
|
|
|
36877
37058
|
identifier: String(dep.gid ?? ""),
|
|
36878
37059
|
state: null
|
|
36879
37060
|
})) : [];
|
|
37061
|
+
const assigneeObj = task.assignee;
|
|
37062
|
+
const assignees = assigneeObj?.email ? [assigneeObj.email] : [];
|
|
36880
37063
|
return {
|
|
36881
37064
|
id: gid,
|
|
36882
37065
|
identifier: gid,
|
|
@@ -36886,6 +37069,7 @@ function normalizeAsanaTask(task, sectionName) {
|
|
|
36886
37069
|
state: sectionName,
|
|
36887
37070
|
branch_name: null,
|
|
36888
37071
|
url: null,
|
|
37072
|
+
assignees,
|
|
36889
37073
|
labels,
|
|
36890
37074
|
blocked_by: blockedBy,
|
|
36891
37075
|
created_at: task.created_at ? new Date(String(task.created_at)) : null,
|
|
@@ -36911,6 +37095,7 @@ function createGitHubAdapter(config2) {
|
|
|
36911
37095
|
const projectNumber = config2.tracker.project_number ?? 0;
|
|
36912
37096
|
const projectId = config2.tracker.project_id ?? null;
|
|
36913
37097
|
const activeStatuses = config2.tracker.active_statuses ?? ["Todo", "In Progress"];
|
|
37098
|
+
const filter2 = config2.tracker.filter;
|
|
36914
37099
|
const octokit = createAuthenticatedGraphql(config2);
|
|
36915
37100
|
async function runGraphql2(query, variables = {}) {
|
|
36916
37101
|
try {
|
|
@@ -36928,11 +37113,11 @@ function createGitHubAdapter(config2) {
|
|
|
36928
37113
|
}
|
|
36929
37114
|
}
|
|
36930
37115
|
const PROJECT_ITEMS_QUERY = `
|
|
36931
|
-
query($owner: String!, $number: Int!, $cursor: String) {
|
|
37116
|
+
query($owner: String!, $number: Int!, $cursor: String, $search: String) {
|
|
36932
37117
|
repositoryOwner(login: $owner) {
|
|
36933
37118
|
... on Organization {
|
|
36934
37119
|
projectV2(number: $number) {
|
|
36935
|
-
items(first: ${PAGE_SIZE2}, after: $cursor) {
|
|
37120
|
+
items(first: ${PAGE_SIZE2}, after: $cursor, query: $search) {
|
|
36936
37121
|
pageInfo { hasNextPage endCursor }
|
|
36937
37122
|
nodes {
|
|
36938
37123
|
id
|
|
@@ -36948,11 +37133,13 @@ function createGitHubAdapter(config2) {
|
|
|
36948
37133
|
... on Issue {
|
|
36949
37134
|
number title body url
|
|
36950
37135
|
labels(first: 20) { nodes { name } }
|
|
37136
|
+
assignees(first: 10) { nodes { login } }
|
|
36951
37137
|
createdAt updatedAt
|
|
36952
37138
|
}
|
|
36953
37139
|
... on PullRequest {
|
|
36954
37140
|
number title body url
|
|
36955
37141
|
labels(first: 20) { nodes { name } }
|
|
37142
|
+
assignees(first: 10) { nodes { login } }
|
|
36956
37143
|
createdAt updatedAt
|
|
36957
37144
|
}
|
|
36958
37145
|
}
|
|
@@ -36962,7 +37149,7 @@ function createGitHubAdapter(config2) {
|
|
|
36962
37149
|
}
|
|
36963
37150
|
... on User {
|
|
36964
37151
|
projectV2(number: $number) {
|
|
36965
|
-
items(first: ${PAGE_SIZE2}, after: $cursor) {
|
|
37152
|
+
items(first: ${PAGE_SIZE2}, after: $cursor, query: $search) {
|
|
36966
37153
|
pageInfo { hasNextPage endCursor }
|
|
36967
37154
|
nodes {
|
|
36968
37155
|
id
|
|
@@ -36978,11 +37165,13 @@ function createGitHubAdapter(config2) {
|
|
|
36978
37165
|
... on Issue {
|
|
36979
37166
|
number title body url
|
|
36980
37167
|
labels(first: 20) { nodes { name } }
|
|
37168
|
+
assignees(first: 10) { nodes { login } }
|
|
36981
37169
|
createdAt updatedAt
|
|
36982
37170
|
}
|
|
36983
37171
|
... on PullRequest {
|
|
36984
37172
|
number title body url
|
|
36985
37173
|
labels(first: 20) { nodes { name } }
|
|
37174
|
+
assignees(first: 10) { nodes { login } }
|
|
36986
37175
|
createdAt updatedAt
|
|
36987
37176
|
}
|
|
36988
37177
|
}
|
|
@@ -36994,10 +37183,10 @@ function createGitHubAdapter(config2) {
|
|
|
36994
37183
|
}
|
|
36995
37184
|
`;
|
|
36996
37185
|
const PROJECT_BY_ID_QUERY = `
|
|
36997
|
-
query($projectId: ID!, $cursor: String) {
|
|
37186
|
+
query($projectId: ID!, $cursor: String, $search: String) {
|
|
36998
37187
|
node(id: $projectId) {
|
|
36999
37188
|
... on ProjectV2 {
|
|
37000
|
-
items(first: ${PAGE_SIZE2}, after: $cursor) {
|
|
37189
|
+
items(first: ${PAGE_SIZE2}, after: $cursor, query: $search) {
|
|
37001
37190
|
pageInfo { hasNextPage endCursor }
|
|
37002
37191
|
nodes {
|
|
37003
37192
|
id
|
|
@@ -37013,11 +37202,13 @@ function createGitHubAdapter(config2) {
|
|
|
37013
37202
|
... on Issue {
|
|
37014
37203
|
number title body url
|
|
37015
37204
|
labels(first: 20) { nodes { name } }
|
|
37205
|
+
assignees(first: 10) { nodes { login } }
|
|
37016
37206
|
createdAt updatedAt
|
|
37017
37207
|
}
|
|
37018
37208
|
... on PullRequest {
|
|
37019
37209
|
number title body url
|
|
37020
37210
|
labels(first: 20) { nodes { name } }
|
|
37211
|
+
assignees(first: 10) { nodes { login } }
|
|
37021
37212
|
createdAt updatedAt
|
|
37022
37213
|
}
|
|
37023
37214
|
}
|
|
@@ -37048,11 +37239,11 @@ function createGitHubAdapter(config2) {
|
|
|
37048
37239
|
}
|
|
37049
37240
|
}
|
|
37050
37241
|
`;
|
|
37051
|
-
async function fetchAllItems(statusFilter) {
|
|
37242
|
+
async function fetchAllItems(statusFilter, search2 = "") {
|
|
37052
37243
|
const issues = [];
|
|
37053
37244
|
let cursor = null;
|
|
37054
37245
|
do {
|
|
37055
|
-
const result = projectId ? await runGraphql2(PROJECT_BY_ID_QUERY, { projectId, cursor }) : await runGraphql2(PROJECT_ITEMS_QUERY, { owner, number: projectNumber, cursor });
|
|
37246
|
+
const result = projectId ? await runGraphql2(PROJECT_BY_ID_QUERY, { projectId, cursor, search: search2 }) : await runGraphql2(PROJECT_ITEMS_QUERY, { owner, number: projectNumber, cursor, search: search2 });
|
|
37056
37247
|
if ("code" in result)
|
|
37057
37248
|
return result;
|
|
37058
37249
|
const payload = result.data;
|
|
@@ -37072,8 +37263,8 @@ function createGitHubAdapter(config2) {
|
|
|
37072
37263
|
const status = extractStatus(node);
|
|
37073
37264
|
if (!status)
|
|
37074
37265
|
continue;
|
|
37075
|
-
const
|
|
37076
|
-
if (
|
|
37266
|
+
const statusMatches = statusFilter.length === 0 || statusFilter.some((s2) => normalizeState(s2) === normalizeState(status));
|
|
37267
|
+
if (statusMatches) {
|
|
37077
37268
|
issues.push(normalizeProjectItem(node, status));
|
|
37078
37269
|
}
|
|
37079
37270
|
}
|
|
@@ -37091,7 +37282,7 @@ function createGitHubAdapter(config2) {
|
|
|
37091
37282
|
}
|
|
37092
37283
|
return {
|
|
37093
37284
|
async fetchCandidateIssues() {
|
|
37094
|
-
return fetchAllItems(activeStatuses);
|
|
37285
|
+
return fetchAllItems(activeStatuses, buildQueryString(filter2));
|
|
37095
37286
|
},
|
|
37096
37287
|
async fetchIssuesByStates(states) {
|
|
37097
37288
|
if (states.length === 0)
|
|
@@ -37138,11 +37329,21 @@ function extractStatus(node) {
|
|
|
37138
37329
|
}
|
|
37139
37330
|
return null;
|
|
37140
37331
|
}
|
|
37332
|
+
function buildQueryString(filter2) {
|
|
37333
|
+
const parts = [];
|
|
37334
|
+
if (filter2.assignee.length > 0)
|
|
37335
|
+
parts.push(`assignee:${filter2.assignee.join(",")}`);
|
|
37336
|
+
if (filter2.label.length > 0)
|
|
37337
|
+
parts.push(`label:${filter2.label.join(",")}`);
|
|
37338
|
+
return parts.join(" ");
|
|
37339
|
+
}
|
|
37141
37340
|
function normalizeProjectItem(node, status) {
|
|
37142
37341
|
const content = node.content;
|
|
37143
37342
|
const number4 = content?.number;
|
|
37144
37343
|
const identifier = number4 ? `#${number4}` : String(node.id ?? "");
|
|
37145
37344
|
const labels = Array.isArray(content?.labels?.nodes) ? content.labels.nodes.map((l) => (l.name ?? "").toLowerCase()).filter(Boolean) : [];
|
|
37345
|
+
const assigneeNodes = content?.assignees?.nodes;
|
|
37346
|
+
const assignees = Array.isArray(assigneeNodes) ? assigneeNodes.map((n2) => n2.login ?? "").filter(Boolean) : [];
|
|
37146
37347
|
return {
|
|
37147
37348
|
id: String(node.id ?? ""),
|
|
37148
37349
|
identifier,
|
|
@@ -37152,6 +37353,7 @@ function normalizeProjectItem(node, status) {
|
|
|
37152
37353
|
state: status,
|
|
37153
37354
|
branch_name: null,
|
|
37154
37355
|
url: content?.url ? String(content.url) : null,
|
|
37356
|
+
assignees,
|
|
37155
37357
|
labels,
|
|
37156
37358
|
blocked_by: [],
|
|
37157
37359
|
created_at: content?.createdAt ? new Date(String(content.createdAt)) : null,
|
|
@@ -37159,11 +37361,6 @@ function normalizeProjectItem(node, status) {
|
|
|
37159
37361
|
};
|
|
37160
37362
|
}
|
|
37161
37363
|
|
|
37162
|
-
// src/tracker/types.ts
|
|
37163
|
-
function isTrackerError(val) {
|
|
37164
|
-
return typeof val === "object" && val !== null && "code" in val;
|
|
37165
|
-
}
|
|
37166
|
-
|
|
37167
37364
|
// src/tracker/index.ts
|
|
37168
37365
|
function createTrackerAdapter(config2) {
|
|
37169
37366
|
const { kind } = config2.tracker;
|
|
@@ -40171,6 +40368,7 @@ class Orchestrator {
|
|
|
40171
40368
|
workflowPath;
|
|
40172
40369
|
pollTimer = null;
|
|
40173
40370
|
fileWatcher = null;
|
|
40371
|
+
labelService = null;
|
|
40174
40372
|
constructor(workflowPath) {
|
|
40175
40373
|
this.workflowPath = workflowPath;
|
|
40176
40374
|
const wf = loadWorkflow(workflowPath);
|
|
@@ -40179,6 +40377,7 @@ class Orchestrator {
|
|
|
40179
40377
|
}
|
|
40180
40378
|
this.workflow = wf;
|
|
40181
40379
|
this.config = buildConfig(wf);
|
|
40380
|
+
this.labelService = createLabelService(this.config);
|
|
40182
40381
|
this.state = {
|
|
40183
40382
|
poll_interval_ms: this.config.polling.interval_ms,
|
|
40184
40383
|
max_concurrent_agents: this.config.agent.max_concurrent_agents,
|
|
@@ -40241,13 +40440,13 @@ class Orchestrator {
|
|
|
40241
40440
|
}
|
|
40242
40441
|
const adapter = createTrackerAdapter(this.config);
|
|
40243
40442
|
if (isTrackerError(adapter)) {
|
|
40244
|
-
console.error(`[orchestrator] tracker adapter error: ${adapter
|
|
40443
|
+
console.error(`[orchestrator] tracker adapter error: ${formatTrackerError(adapter)}`);
|
|
40245
40444
|
this.scheduleTick(this.state.poll_interval_ms);
|
|
40246
40445
|
return;
|
|
40247
40446
|
}
|
|
40248
40447
|
const candidatesResult = await adapter.fetchCandidateIssues();
|
|
40249
40448
|
if (isTrackerError(candidatesResult)) {
|
|
40250
|
-
console.error(`[orchestrator] tracker fetch failed: ${candidatesResult
|
|
40449
|
+
console.error(`[orchestrator] tracker fetch failed: ${formatTrackerError(candidatesResult)}`);
|
|
40251
40450
|
this.scheduleTick(this.state.poll_interval_ms);
|
|
40252
40451
|
return;
|
|
40253
40452
|
}
|
|
@@ -40309,6 +40508,9 @@ class Orchestrator {
|
|
|
40309
40508
|
};
|
|
40310
40509
|
this.state.running.set(issue2.id, entry);
|
|
40311
40510
|
console.warn(`[orchestrator] dispatching issue_id=${issue2.id} issue_identifier=${issue2.identifier} attempt=${attempt ?? "first"}`);
|
|
40511
|
+
this.labelService?.setLabel(issue2, "dispatched").catch((err) => {
|
|
40512
|
+
console.warn(`[orchestrator] label service error issue_id=${issue2.id}: ${err}`);
|
|
40513
|
+
});
|
|
40312
40514
|
this.runWorker(issue2, attempt).catch((err) => {
|
|
40313
40515
|
console.error(`[orchestrator] worker uncaught error issue_id=${issue2.id}: ${err}`);
|
|
40314
40516
|
});
|
|
@@ -40428,9 +40630,15 @@ class Orchestrator {
|
|
|
40428
40630
|
this.state.agent_totals.output_tokens += running.agent_output_tokens;
|
|
40429
40631
|
this.state.agent_totals.total_tokens += running.agent_total_tokens;
|
|
40430
40632
|
if (reason === "normal") {
|
|
40633
|
+
this.labelService?.setLabel(running.issue, "done").catch((err) => {
|
|
40634
|
+
console.warn(`[orchestrator] label service error issue_id=${issueId}: ${err}`);
|
|
40635
|
+
});
|
|
40431
40636
|
this.state.completed.add(issueId);
|
|
40432
40637
|
this.scheduleRetry(issueId, running.identifier, 1, null, "continuation");
|
|
40433
40638
|
} else {
|
|
40639
|
+
this.labelService?.setLabel(running.issue, "failed").catch((err) => {
|
|
40640
|
+
console.warn(`[orchestrator] label service error issue_id=${issueId}: ${err}`);
|
|
40641
|
+
});
|
|
40434
40642
|
const nextAttempt = nextAttemptFrom(running.retry_attempt);
|
|
40435
40643
|
this.scheduleRetry(issueId, running.identifier, nextAttempt, error48, "failure");
|
|
40436
40644
|
}
|
|
@@ -40509,7 +40717,7 @@ class Orchestrator {
|
|
|
40509
40717
|
return;
|
|
40510
40718
|
const refreshed = await adapter.fetchIssueStatesByIds(runningIds);
|
|
40511
40719
|
if (isTrackerError(refreshed)) {
|
|
40512
|
-
console.warn(`[orchestrator] state refresh failed: ${refreshed
|
|
40720
|
+
console.warn(`[orchestrator] state refresh failed: ${formatTrackerError(refreshed)} \u2014 keeping workers running`);
|
|
40513
40721
|
return;
|
|
40514
40722
|
}
|
|
40515
40723
|
const activeStates = getActiveStates(this.config);
|
|
@@ -40520,6 +40728,12 @@ class Orchestrator {
|
|
|
40520
40728
|
const isActive = activeStates.some((s2) => normalizeState(s2) === normalizedState);
|
|
40521
40729
|
if (isTerminal) {
|
|
40522
40730
|
console.warn(`[orchestrator] issue terminal, stopping worker issue_id=${issue2.id} state=${issue2.state}`);
|
|
40731
|
+
const runningEntry = this.state.running.get(issue2.id);
|
|
40732
|
+
if (runningEntry) {
|
|
40733
|
+
this.labelService?.setLabel(runningEntry.issue, "done").catch((err) => {
|
|
40734
|
+
console.warn(`[orchestrator] label service error issue_id=${issue2.id}: ${err}`);
|
|
40735
|
+
});
|
|
40736
|
+
}
|
|
40523
40737
|
this.terminateRunningIssue(issue2.id, true);
|
|
40524
40738
|
} else if (isActive) {
|
|
40525
40739
|
const entry = this.state.running.get(issue2.id);
|
|
@@ -40547,12 +40761,12 @@ class Orchestrator {
|
|
|
40547
40761
|
const terminalStates = getTerminalStates(this.config);
|
|
40548
40762
|
const adapter = createTrackerAdapter(this.config);
|
|
40549
40763
|
if (isTrackerError(adapter)) {
|
|
40550
|
-
console.warn(`[orchestrator] startup cleanup: adapter error ${adapter
|
|
40764
|
+
console.warn(`[orchestrator] startup cleanup: adapter error ${formatTrackerError(adapter)}`);
|
|
40551
40765
|
return;
|
|
40552
40766
|
}
|
|
40553
40767
|
const result = await adapter.fetchIssuesByStates(terminalStates);
|
|
40554
40768
|
if (isTrackerError(result)) {
|
|
40555
|
-
console.warn(`[orchestrator] startup terminal cleanup failed: ${result
|
|
40769
|
+
console.warn(`[orchestrator] startup terminal cleanup failed: ${formatTrackerError(result)}`);
|
|
40556
40770
|
return;
|
|
40557
40771
|
}
|
|
40558
40772
|
for (const issue2 of result) {
|
|
@@ -40585,6 +40799,7 @@ class Orchestrator {
|
|
|
40585
40799
|
}
|
|
40586
40800
|
this.workflow = wf;
|
|
40587
40801
|
this.config = newConfig;
|
|
40802
|
+
this.labelService = createLabelService(newConfig);
|
|
40588
40803
|
this.state.poll_interval_ms = newConfig.polling.interval_ms;
|
|
40589
40804
|
this.state.max_concurrent_agents = newConfig.agent.max_concurrent_agents;
|
|
40590
40805
|
console.warn("[orchestrator] workflow reloaded successfully");
|
package/package.json
CHANGED
|
@@ -1,28 +1,32 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pleaseai/work",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.1.3",
|
|
5
5
|
"description": "Symphony-spec orchestrator for Claude Code + Asana/GitHub Projects v2",
|
|
6
6
|
"license": "FSL-1.1-MIT",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
9
|
-
"url": "https://github.com/
|
|
9
|
+
"url": "https://github.com/pleaseai/work-please.git",
|
|
10
10
|
"directory": "apps/work-please"
|
|
11
11
|
},
|
|
12
|
-
"files": [
|
|
13
|
-
"dist",
|
|
14
|
-
"LICENSE"
|
|
15
|
-
],
|
|
16
12
|
"bin": {
|
|
17
13
|
"work-please": "./dist/index.js"
|
|
18
14
|
},
|
|
15
|
+
"files": [
|
|
16
|
+
"LICENSE",
|
|
17
|
+
"README.md",
|
|
18
|
+
"dist"
|
|
19
|
+
],
|
|
19
20
|
"scripts": {
|
|
20
|
-
"
|
|
21
|
+
"prepublishOnly": "cp ../../LICENSE ../../README.md .",
|
|
22
|
+
"postpublish": "rm -f LICENSE README.md",
|
|
23
|
+
"build": "bun build ./src/index.ts --outdir ./dist --target bun && bun run scripts/add-shebang.ts",
|
|
21
24
|
"dev": "bun run --watch src/index.ts",
|
|
22
25
|
"lint": "eslint .",
|
|
23
26
|
"lint:fix": "eslint . --fix",
|
|
24
27
|
"check": "tsc --noEmit",
|
|
25
|
-
"test": "bun test"
|
|
28
|
+
"test": "bun test",
|
|
29
|
+
"test:coverage": "bun test --coverage --coverage-reporter=lcov"
|
|
26
30
|
},
|
|
27
31
|
"dependencies": {
|
|
28
32
|
"@anthropic-ai/claude-agent-sdk": "^0.2.72",
|