crewly 1.20.159 → 1.20.161
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/config/constants.ts +13 -0
- package/config/roles/orchestrator/prompt.md +1 -0
- package/config/roles/team-leader/prompt.md +3 -0
- package/config/roles/team-leader/tl-addon.md +3 -0
- package/config/skills/agent/core/project-tickets/SKILL.md +17 -1
- package/config/skills/agent/core/project-tickets/execute.sh +16 -3
- package/config/skills/agent/core/project-tickets/execute.test.sh +11 -0
- package/config/skills/orchestrator/delegate-task/SKILL.md +18 -0
- package/config/skills/orchestrator/delegate-task/execute.sh +23 -5
- package/config/skills/orchestrator/delegate-task/execute.test.sh +44 -1
- package/config/skills/team-leader/delegate-task/SKILL.md +18 -0
- package/config/skills/team-leader/delegate-task/execute.sh +31 -4
- package/config/skills/team-leader/delegate-task/execute.test.sh +59 -0
- package/dist/backend/backend/src/constants.d.ts +46 -0
- package/dist/backend/backend/src/constants.d.ts.map +1 -1
- package/dist/backend/backend/src/constants.js +41 -0
- package/dist/backend/backend/src/constants.js.map +1 -1
- package/dist/backend/backend/src/controllers/browser/browser.controller.d.ts +27 -0
- package/dist/backend/backend/src/controllers/browser/browser.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/browser/browser.controller.js +203 -1
- package/dist/backend/backend/src/controllers/browser/browser.controller.js.map +1 -1
- package/dist/backend/backend/src/controllers/browser/browser.routes.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/browser/browser.routes.js +4 -1
- package/dist/backend/backend/src/controllers/browser/browser.routes.js.map +1 -1
- package/dist/backend/backend/src/controllers/project-tickets/project-tickets.controller.d.ts +8 -0
- package/dist/backend/backend/src/controllers/project-tickets/project-tickets.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/project-tickets/project-tickets.controller.js +13 -0
- package/dist/backend/backend/src/controllers/project-tickets/project-tickets.controller.js.map +1 -1
- package/dist/backend/backend/src/controllers/project-tickets/project-tickets.routes.d.ts +1 -0
- package/dist/backend/backend/src/controllers/project-tickets/project-tickets.routes.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/project-tickets/project-tickets.routes.js +3 -1
- package/dist/backend/backend/src/controllers/project-tickets/project-tickets.routes.js.map +1 -1
- package/dist/backend/backend/src/controllers/task-pool/task-pool.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/task-pool/task-pool.controller.js +73 -3
- package/dist/backend/backend/src/controllers/task-pool/task-pool.controller.js.map +1 -1
- package/dist/backend/backend/src/services/agent/agent-registration.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/agent/agent-registration.service.js +9 -1
- package/dist/backend/backend/src/services/agent/agent-registration.service.js.map +1 -1
- package/dist/backend/backend/src/services/browser/browser-session.service.d.ts +15 -0
- package/dist/backend/backend/src/services/browser/browser-session.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/browser/browser-session.service.js +25 -0
- package/dist/backend/backend/src/services/browser/browser-session.service.js.map +1 -1
- package/dist/backend/backend/src/services/browser/owner-browser-input.d.ts +211 -0
- package/dist/backend/backend/src/services/browser/owner-browser-input.d.ts.map +1 -0
- package/dist/backend/backend/src/services/browser/owner-browser-input.js +373 -0
- package/dist/backend/backend/src/services/browser/owner-browser-input.js.map +1 -0
- package/dist/backend/backend/src/services/cloud/mobile-api-relay.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/cloud/mobile-api-relay.service.js +5 -3
- package/dist/backend/backend/src/services/cloud/mobile-api-relay.service.js.map +1 -1
- package/dist/backend/backend/src/services/project-tickets/delegation-ticket-route.d.ts +63 -0
- package/dist/backend/backend/src/services/project-tickets/delegation-ticket-route.d.ts.map +1 -0
- package/dist/backend/backend/src/services/project-tickets/delegation-ticket-route.js +80 -0
- package/dist/backend/backend/src/services/project-tickets/delegation-ticket-route.js.map +1 -0
- package/dist/backend/backend/src/services/project-tickets/project-ticket-workflow.service.d.ts +92 -1
- package/dist/backend/backend/src/services/project-tickets/project-ticket-workflow.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/project-tickets/project-ticket-workflow.service.js +255 -4
- package/dist/backend/backend/src/services/project-tickets/project-ticket-workflow.service.js.map +1 -1
- package/dist/backend/build-info.json +2 -2
- package/dist/backend/config/constants.d.ts +13 -0
- package/dist/backend/config/constants.d.ts.map +1 -1
- package/dist/backend/config/constants.js +13 -0
- package/dist/backend/config/constants.js.map +1 -1
- package/dist/cli/backend/src/constants.d.ts +46 -0
- package/dist/cli/backend/src/constants.d.ts.map +1 -1
- package/dist/cli/backend/src/constants.js +41 -0
- package/dist/cli/backend/src/constants.js.map +1 -1
- package/dist/cli/backend/src/services/agent/agent-registration.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/agent/agent-registration.service.js +9 -1
- package/dist/cli/backend/src/services/agent/agent-registration.service.js.map +1 -1
- package/dist/cli/config/constants.d.ts +13 -0
- package/dist/cli/config/constants.d.ts.map +1 -1
- package/dist/cli/config/constants.js +13 -0
- package/dist/cli/config/constants.js.map +1 -1
- package/frontend/dist/assets/index-0707e2ac.css +42 -0
- package/frontend/dist/assets/{index-f79676aa.js → index-8f30aa74.js} +265 -265
- package/frontend/dist/index.html +2 -2
- package/package.json +1 -1
- package/frontend/dist/assets/index-edd6f262.css +0 -42
package/config/constants.ts
CHANGED
|
@@ -1483,6 +1483,19 @@ export const PROJECT_TICKET_CONSTANTS = {
|
|
|
1483
1483
|
SYNC_SWEEP_INTERVAL_MS: 60 * 1000,
|
|
1484
1484
|
/** Successor hops followed from a ticket's WorkItem before giving up */
|
|
1485
1485
|
MAX_SUCCESSOR_HOPS: 8,
|
|
1486
|
+
/**
|
|
1487
|
+
* Delegation through tickets (spec §11): the task-pool body field naming the
|
|
1488
|
+
* project ticket a delegated WorkItem works (`delegate-task --ticket`).
|
|
1489
|
+
*/
|
|
1490
|
+
DELEGATION_TICKET_BODY_KEY: 'projectTicketId',
|
|
1491
|
+
/** WorkItem metadata key a delegating skill uses to name the delegator when no X-Agent-Session header is sent */
|
|
1492
|
+
DELEGATION_CALLER_METADATA_KEY: 'delegatedBy',
|
|
1493
|
+
/** WorkItem types that are delegations of work to someone (the only ones routed through tickets) */
|
|
1494
|
+
DELEGATION_WORK_ITEM_TYPES: ['delegate'] as const,
|
|
1495
|
+
/** Max characters of a ticket title derived from a delegation's title */
|
|
1496
|
+
DELEGATION_TITLE_MAX_CHARS: 120,
|
|
1497
|
+
/** Error `code` of a task-pool add refused by the ticket rules (skills stop instead of delivering) */
|
|
1498
|
+
DELEGATION_REFUSED_CODE: 'project_ticket_refused',
|
|
1486
1499
|
} as const;
|
|
1487
1500
|
|
|
1488
1501
|
/**
|
|
@@ -878,6 +878,7 @@ Every project keeps its own backlog in `<project>/.crewly/tickets/` — one mark
|
|
|
878
878
|
- **Owner says "put this in the backlog" / "add a ticket for …"** → `project-tickets create --project <path> --title "…" --acceptance "…" --source request:<TKT id>` and tell the owner the new ticket id. It starts in `backlog`; do not start the work unless the owner also asked for that.
|
|
879
879
|
- **Owner says a ticket should be done now** → `project-tickets update --project <path> --id <ID> --status ready`. Idle members of the project's teams pick up `ready` tickets themselves (highest priority first).
|
|
880
880
|
- **A specific person must do it** → `project-tickets assign --project <path> --id <ID> --to <session>`.
|
|
881
|
+
- **Delegating project work** (`delegate-task` to an agent whose team works on a project) always runs through a ticket: pass `--ticket <ID>` when one exists, otherwise one is created and returned as `projectTicket`. Work already running without a ticket → `project-tickets link --project <path> --id <ID> --work-item <WorkItem id>`.
|
|
881
882
|
- A ticket moves to `done` by itself when its WorkItem is verified (or to `review` when the owner wants to check it personally — then the owner closes it).
|
|
882
883
|
|
|
883
884
|
### Task Routing
|
|
@@ -52,6 +52,7 @@ Use when: Orchestrator sends a new objective. Creates one WorkItem per sub-task
|
|
|
52
52
|
bash {{TL_SKILLS_PATH}}/delegate-task/execute.sh '{"to":"worker-session","task":"implement feature","priority":"high","teamId":"{{TEAM_ID}}","tlMemberId":"{{MEMBER_ID}}","projectPath":"{{PROJECT_PATH}}"}'
|
|
53
53
|
```
|
|
54
54
|
Use when: After decompose-goal, or when handle-failure says reassign. Validates hierarchy before delegation.
|
|
55
|
+
Project work for a worker always runs through a project ticket: add `"ticket":"APP-12"` (or `--ticket APP-12`) when a ticket exists; otherwise one is created for you and printed. Refused (nothing delivered) when that ticket is already being worked or closed.
|
|
55
56
|
|
|
56
57
|
### 3. verify-output — Check completed work quality
|
|
57
58
|
```bash
|
|
@@ -103,6 +104,8 @@ Use when: A specific worker should take a specific ticket of the project backlog
|
|
|
103
104
|
|
|
104
105
|
Each project keeps its own backlog in `<project>/.crewly/tickets/` (one markdown file per ticket, tracked in git). Use the `project-tickets` skill (`{{AGENT_SKILLS_PATH}}/core/project-tickets/execute.sh`) to list, read, create and update tickets. Workers pick up `ready` tickets by themselves when idle; you decide what is `ready` (`update --status ready`), you may assign (`assign-ticket`), and you review the work through the normal verification item. Worker-created tickets land in `backlog` until you or the owner make them ready.
|
|
105
106
|
|
|
107
|
+
**Every piece of project work for a worker has a ticket.** Delegate with `--ticket <ID>` when the ticket exists (or `assign-ticket`); without it `delegate-task` creates one for you. Work already running without a ticket: `project-tickets link --project {{PROJECT_PATH}} --id <ID> --work-item <WorkItem id>`. Backlog tickets that should be picked up next: `update --status ready`. Reminders you set for yourself get no ticket.
|
|
108
|
+
|
|
106
109
|
---
|
|
107
110
|
|
|
108
111
|
## Brief Reception Protocol
|
|
@@ -33,6 +33,7 @@ Use when: Orchestrator sends a new objective. Creates one WorkItem per sub-task
|
|
|
33
33
|
bash {{TL_SKILLS_PATH}}/delegate-task/execute.sh '{"to":"worker-session","task":"implement feature","priority":"high","teamId":"{{TEAM_ID}}","tlMemberId":"{{MEMBER_ID}}","projectPath":"{{PROJECT_PATH}}"}'
|
|
34
34
|
```
|
|
35
35
|
Use when: After decompose-goal, or when handle-failure says reassign. Validates hierarchy before delegation.
|
|
36
|
+
Project work for a worker always runs through a project ticket: add `"ticket":"APP-12"` (or `--ticket APP-12`) when a ticket exists; otherwise one is created for you and printed. Refused (nothing delivered) when that ticket is already being worked or closed.
|
|
36
37
|
|
|
37
38
|
#### 3. verify-output — Check completed work quality
|
|
38
39
|
```bash
|
|
@@ -89,6 +90,8 @@ Use when: A specific worker should take a specific ticket of the project backlog
|
|
|
89
90
|
#### Project backlog (project tickets)
|
|
90
91
|
Each project keeps its own backlog in `<project>/.crewly/tickets/` (one markdown file per ticket, tracked in git). Use the `project-tickets` skill to list, read, create and update tickets. Workers pick up `ready` tickets by themselves when idle; you decide what is `ready`, you may assign (`assign-ticket`), and you review the work through the normal verification item. Worker-created tickets land in `backlog` until you or the owner make them ready.
|
|
91
92
|
|
|
93
|
+
**Every piece of project work for a worker has a ticket.** Delegate with `--ticket <ID>` when the ticket exists (or `assign-ticket`); without it `delegate-task` creates one for you. Work already running without a ticket: `project-tickets link --project {{PROJECT_PATH}} --id <ID> --work-item <WorkItem id>`. Backlog tickets that should be picked up next: `update --status ready`. Reminders you set for yourself get no ticket.
|
|
94
|
+
|
|
92
95
|
---
|
|
93
96
|
|
|
94
97
|
### Standard Operating Procedure (5-Step SOP)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: Project Tickets
|
|
3
|
-
description: A project's own backlog — list, read, create, update, claim, release and log project tickets (markdown files in <project>/.crewly/tickets/, tracked in git).
|
|
3
|
+
description: A project's own backlog — list, read, create, update, claim, release, assign, link and log project tickets (markdown files in <project>/.crewly/tickets/, tracked in git).
|
|
4
4
|
version: 1.0.0
|
|
5
5
|
category: task-management
|
|
6
6
|
skillType: claude-skill
|
|
@@ -125,3 +125,19 @@ bash execute.sh assign --project P --id APP-12 --to worker-session --no-start
|
|
|
125
125
|
Only the owner, the orchestrator or a lead of a team on the project may
|
|
126
126
|
assign, and only to members of those teams (a person's name is recorded
|
|
127
127
|
without a WorkItem). Team leads also have the `assign-ticket` skill.
|
|
128
|
+
|
|
129
|
+
## Link work already in flight (orchestrator / team lead)
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
bash execute.sh link --project P --id APP-12 --work-item <WorkItem id>
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
For a WorkItem that is already queued or running without a ticket (e.g. it was
|
|
136
|
+
delegated before tickets were used). The ticket becomes `in_progress` (a ticket
|
|
137
|
+
in `review` stays there), its assignee is the WorkItem's target, and it closes
|
|
138
|
+
by itself when that WorkItem is verified. Refused when the ticket is done or
|
|
139
|
+
cancelled, the WorkItem is finished or already works another ticket, or the
|
|
140
|
+
ticket already has another live WorkItem.
|
|
141
|
+
|
|
142
|
+
New delegations need no `link`: `delegate-task` puts work for a teammate on a
|
|
143
|
+
project through a ticket by itself (pass `--ticket APP-12` to use an existing one).
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
# bash execute.sh release --project P --id APP-12 [--note "why"]
|
|
17
17
|
# bash execute.sh assign --project P --id APP-12 --to <session> [--no-start] (owner / orc / lead)
|
|
18
18
|
# bash execute.sh log --project P --id APP-12 --note "progress"
|
|
19
|
+
# bash execute.sh link --project P --id APP-12 --work-item <WorkItem id> (owner / orc / lead)
|
|
19
20
|
# bash execute.sh '{"action":"create","project":"P","title":"…"}'
|
|
20
21
|
#
|
|
21
22
|
# P = project id, name, or absolute path.
|
|
@@ -39,6 +40,9 @@ Usage:
|
|
|
39
40
|
bash execute.sh assign --project P --id APP-12 --to <session> [--no-start]
|
|
40
41
|
Orchestrator / team lead: put someone on it
|
|
41
42
|
bash execute.sh log --project P --id APP-12 --note "…" Add a progress note to the ticket's Log
|
|
43
|
+
bash execute.sh link --project P --id APP-12 --work-item <id>
|
|
44
|
+
Orchestrator / team lead: tie work already in
|
|
45
|
+
flight (a live WorkItem) to this ticket
|
|
42
46
|
|
|
43
47
|
P = project id, name or absolute path. Workers' new tickets start in backlog;
|
|
44
48
|
the owner, the orchestrator or a team lead makes them ready.
|
|
@@ -46,7 +50,7 @@ EOF_USAGE
|
|
|
46
50
|
}
|
|
47
51
|
|
|
48
52
|
ACTION=""; PROJECT=""; ID=""; TITLE=""; DESCRIPTION=""; PRIORITY=""; LABELS=""; TEAM=""
|
|
49
|
-
STATUS=""; SOURCE=""; REQUEST_ID=""; NOTE=""; OWNER_REVIEW=""; ASSIGNEE=""; START="true"
|
|
53
|
+
STATUS=""; SOURCE=""; REQUEST_ID=""; NOTE=""; OWNER_REVIEW=""; ASSIGNEE=""; START="true"; WORK_ITEM=""
|
|
50
54
|
ACCEPTANCE_JSON="null"
|
|
51
55
|
HAS_DESCRIPTION=0
|
|
52
56
|
|
|
@@ -70,6 +74,7 @@ if [[ $# -gt 0 && ${1:0:1} == '{' ]]; then
|
|
|
70
74
|
ACCEPTANCE_JSON=$(printf '%s' "$J" | jq -c 'if (.acceptance|type) == "array" then .acceptance else null end')
|
|
71
75
|
ASSIGNEE=$(printf '%s' "$J" | jq -r '.to // .assignee // empty')
|
|
72
76
|
START=$(printf '%s' "$J" | jq -r 'if .start == false then "false" else "true" end')
|
|
77
|
+
WORK_ITEM=$(printf '%s' "$J" | jq -r '.workItemId // .workItem // empty')
|
|
73
78
|
fi
|
|
74
79
|
if [[ -z "$ACTION" && $# -gt 0 && ${1:0:1} != '-' ]]; then ACTION="$1"; shift; fi
|
|
75
80
|
|
|
@@ -91,13 +96,15 @@ while [[ $# -gt 0 ]]; do
|
|
|
91
96
|
--owner-review) OWNER_REVIEW="true"; shift ;;
|
|
92
97
|
--to|--assignee) [ $# -ge 2 ] || error_exit "--to requires a value"; ASSIGNEE="$2"; shift 2 ;;
|
|
93
98
|
--no-start) START="false"; shift ;;
|
|
99
|
+
--work-item|--work-item-id|--workItemId)
|
|
100
|
+
[ $# -ge 2 ] || error_exit "--work-item requires a value"; WORK_ITEM="$2"; shift 2 ;;
|
|
94
101
|
--full) shift ;;
|
|
95
102
|
--help|-h) print_usage; exit 0 ;;
|
|
96
103
|
*) error_exit "Unknown option: $1" ;;
|
|
97
104
|
esac
|
|
98
105
|
done
|
|
99
106
|
|
|
100
|
-
[ -n "$ACTION" ] || { print_usage >&2; error_exit "Missing action: list | show | create | update | claim | release | assign | log"; }
|
|
107
|
+
[ -n "$ACTION" ] || { print_usage >&2; error_exit "Missing action: list | show | create | update | claim | release | assign | log | link"; }
|
|
101
108
|
|
|
102
109
|
# URL-encode a path segment (project paths contain slashes).
|
|
103
110
|
enc() { jq -rn --arg v "$1" '$v|@uri'; }
|
|
@@ -176,7 +183,13 @@ case "$ACTION" in
|
|
|
176
183
|
BODY=$(jq -n --arg note "$NOTE" '{note: $note}')
|
|
177
184
|
api_call POST "/project-tickets/$(enc "$PROJECT")/$(enc "$ID")/log" "$BODY" | jq '{success, lastLog: (.data.log | last)}'
|
|
178
185
|
;;
|
|
186
|
+
link)
|
|
187
|
+
require_param "project" "$PROJECT"; require_param "id" "$ID"; require_param "work-item" "$WORK_ITEM"
|
|
188
|
+
BODY=$(jq -n --arg wi "$WORK_ITEM" '{workItemId: $wi}')
|
|
189
|
+
api_call POST "/project-tickets/$(enc "$PROJECT")/$(enc "$ID")/link" "$BODY" \
|
|
190
|
+
| jq "{success, workItemId: .data.workItem.id, ticket: (.data.ticket | ${TICKET_ROW})}"
|
|
191
|
+
;;
|
|
179
192
|
*)
|
|
180
|
-
error_exit "Unknown action: $ACTION (use list | show | create | update | claim | release | assign | log)"
|
|
193
|
+
error_exit "Unknown action: $ACTION (use list | show | create | update | claim | release | assign | log | link)"
|
|
181
194
|
;;
|
|
182
195
|
esac
|
|
@@ -37,6 +37,8 @@ class H(BaseHTTPRequestHandler):
|
|
|
37
37
|
def do_POST(self):
|
|
38
38
|
n = int(self.headers.get('content-length', '0')); body = json.loads(self.rfile.read(n).decode() or '{}')
|
|
39
39
|
self.record(body)
|
|
40
|
+
if self.path.endswith('/link'):
|
|
41
|
+
return self.reply(200, {'success': True, 'data': {'workItem': {'id': body.get('workItemId')}, 'ticket': dict(T, status='in_progress', assignee='dev-bo', workItemId=body.get('workItemId'))}})
|
|
40
42
|
if self.path.endswith('/claim'):
|
|
41
43
|
return self.reply(200, {'success': True, 'data': {'claimed': True, 'workItem': {'id': 'wi-9'}, 'ticket': dict(T, status='in_progress', assignee='dev-ann', workItemId='wi-9')}})
|
|
42
44
|
if 'forbidden' in self.path:
|
|
@@ -106,6 +108,15 @@ OUT=$(run log --project p1 --id APP-1 --note "halfway")
|
|
|
106
108
|
check "log: body" "$(last '.body | tostring')" '{"note":"halfway"}'
|
|
107
109
|
check "log: last line" "$(printf '%s' "$OUT" | jq -r .lastLog)" "b · dev · note"
|
|
108
110
|
|
|
111
|
+
# --- link (orchestrator / lead): tie a live WorkItem to a ticket ---
|
|
112
|
+
OUT=$(run link --project p1 --id APP-1 --work-item wi-42)
|
|
113
|
+
check "link: path" "$(last .path)" "/api/project-tickets/p1/APP-1/link"
|
|
114
|
+
check "link: body" "$(last '.body | tostring')" '{"workItemId":"wi-42"}'
|
|
115
|
+
check "link: output" "$(printf '%s' "$OUT" | jq -c '[.workItemId, .ticket.status, .ticket.workItemId]')" '["wi-42","in_progress","wi-42"]'
|
|
116
|
+
run '{"action":"link","project":"p1","id":"APP-1","workItemId":"wi-43"}' >/dev/null
|
|
117
|
+
check "link json" "$(last '.body | tostring')" '{"workItemId":"wi-43"}'
|
|
118
|
+
check "link: missing work item" "$(run_err link --project p1 --id APP-1 | grep -c 'work-item')" "1"
|
|
119
|
+
|
|
109
120
|
# --- errors ---
|
|
110
121
|
check "missing action" "$(run_err | grep -c 'Missing action')" "1"
|
|
111
122
|
check "unknown action" "$(run_err explode | grep -c 'Unknown action')" "1"
|
|
@@ -47,8 +47,26 @@ The script auto-resolves `config/skills/...` references to absolute paths so del
|
|
|
47
47
|
| `--task-type` | `taskType` | No | Task type: `general`, `technical` (default: `general`) |
|
|
48
48
|
| `--force-cross-team` | `forceCrossTeam` | No | Allow cross-team delegation |
|
|
49
49
|
| `--request-id` / `-R` | `requestId` | No | Ticket (Request) this work is for — the id or `TKT-123` from the `[TICKET:TKT-123 <id>]` line of the message you are acting on. Links the WorkItem into `Request.workItemIds[]`. Omit it and the WorkItem is still linked when your current turn has exactly one ticket |
|
|
50
|
+
| `--ticket` | `ticket` | No | Project ticket this work is for (e.g. `APP-12`). Without it a ticket is created for you when the target works on a project — see Project tickets |
|
|
50
51
|
| `--fallback-minutes` | — | No | Minutes until the ONE §3.0 fallback check fires for this delegation (default `120` ≈ 2× a TL milestone ETA). `0` disables it. Not cancelled on completion — keep it at 2× ETA, never a poll interval |
|
|
51
52
|
|
|
53
|
+
## Project tickets
|
|
54
|
+
|
|
55
|
+
Work for a teammate on a project always runs through a project ticket
|
|
56
|
+
(`<project>/.crewly/tickets/`, spec `2026-09-28-project-tickets.md` §11):
|
|
57
|
+
|
|
58
|
+
- `--ticket APP-12` — that ticket (must be `backlog` or `ready`, with no other
|
|
59
|
+
live WorkItem) is assigned to `--to` and this WorkItem becomes its work. A
|
|
60
|
+
ticket that is already being worked, done, cancelled or unknown is refused:
|
|
61
|
+
nothing is delivered and the reason is printed.
|
|
62
|
+
- no `--ticket` — a ticket is created from this delegation (title, brief,
|
|
63
|
+
the target's team, priority) and set `in_progress` for the target. The
|
|
64
|
+
output names it (`projectTicket`).
|
|
65
|
+
|
|
66
|
+
No ticket is made for work you target at yourself, review/verify items,
|
|
67
|
+
system or scheduled items, or targets whose team has no project. Work already
|
|
68
|
+
in flight without a ticket: `project-tickets link --project P --id APP-12 --work-item <id>`.
|
|
69
|
+
|
|
52
70
|
## Usage — CLI Flags (preferred)
|
|
53
71
|
|
|
54
72
|
```bash
|
|
@@ -17,6 +17,7 @@ TASK_TYPE="general"
|
|
|
17
17
|
TEAM_ID=""
|
|
18
18
|
FORCE_CROSS_TEAM="false"
|
|
19
19
|
REQUEST_ID=""
|
|
20
|
+
PROJECT_TICKET=""
|
|
20
21
|
# Self-heal fix #3 (2026-05-20): default fallback timer. Per the
|
|
21
22
|
# orchestrator prompt §3.0 the orc is supposed to set 2× ETA manually
|
|
22
23
|
# for every dispatch, but the convention was unenforced — observable in
|
|
@@ -54,11 +55,13 @@ while [[ $# -gt 0 ]]; do
|
|
|
54
55
|
--task-type) TASK_TYPE="$2"; shift 2 ;;
|
|
55
56
|
--team|-g) TEAM_ID="$2"; shift 2 ;;
|
|
56
57
|
--request-id|-R) REQUEST_ID="$2"; shift 2 ;;
|
|
58
|
+
--ticket) PROJECT_TICKET="$2"; shift 2 ;;
|
|
57
59
|
--force-cross-team) FORCE_CROSS_TEAM="true"; shift ;;
|
|
58
60
|
--fallback-minutes) FALLBACK_MINUTES="$2"; shift 2 ;;
|
|
59
61
|
--json|-j) INPUT_JSON="$2"; shift 2 ;;
|
|
60
62
|
--help|-h)
|
|
61
|
-
echo "Usage: execute.sh --to agent-session --task 'implement feature' --priority high --project /path [--team teamId] [--context 'extra info']"
|
|
63
|
+
echo "Usage: execute.sh --to agent-session --task 'implement feature' --priority high --project /path [--team teamId] [--context 'extra info'] [--ticket <project ticket id, e.g. APP-12>]"
|
|
64
|
+
echo "Work for an agent on a project always runs through a project ticket: --ticket uses that backlog/ready ticket, otherwise one is created for you."
|
|
62
65
|
exit 0
|
|
63
66
|
;;
|
|
64
67
|
--) shift; break ;;
|
|
@@ -94,6 +97,7 @@ if [ -n "$INPUT_JSON" ]; then
|
|
|
94
97
|
[ -z "$TEAM_ID" ] && TEAM_ID=$(printf '%s' "$INPUT" | jq -r '.teamId // empty')
|
|
95
98
|
[ "$FORCE_CROSS_TEAM" = "false" ] && FORCE_CROSS_TEAM=$(printf '%s' "$INPUT" | jq -r '.forceCrossTeam // "false"')
|
|
96
99
|
[ -z "$REQUEST_ID" ] && REQUEST_ID=$(printf '%s' "$INPUT" | jq -r '.requestId // empty')
|
|
100
|
+
[ -z "$PROJECT_TICKET" ] && PROJECT_TICKET=$(printf '%s' "$INPUT" | jq -r '.ticket // .projectTicketId // empty')
|
|
97
101
|
fi
|
|
98
102
|
|
|
99
103
|
require_param "to (--to)" "$TO"
|
|
@@ -292,17 +296,29 @@ POOL_BODY=$(jq -n \
|
|
|
292
296
|
--arg priority "$WI_PRIORITY" \
|
|
293
297
|
--arg projectPath "${PROJECT_PATH:-}" \
|
|
294
298
|
--arg requestId "${REQUEST_ID:-}" \
|
|
295
|
-
|
|
299
|
+
--arg projectTicketId "${PROJECT_TICKET:-}" \
|
|
300
|
+
--arg delegatedBy "${CREWLY_SESSION_NAME:-}" \
|
|
301
|
+
'{type: $type, owner: $owner, target: $target, title: $title, description: $description, briefMarkdown: $briefMarkdown, metadata: ({priority: $priority} + (if $projectPath != "" then {projectPath: $projectPath} else {} end) + (if $delegatedBy != "" then {delegatedBy: $delegatedBy} else {} end))} + (if $requestId != "" then {requestId: $requestId} else {} end) + (if $projectTicketId != "" then {projectTicketId: $projectTicketId} else {} end)')
|
|
302
|
+
# projectTicketId / metadata.delegatedBy: work for an agent on a project runs
|
|
303
|
+
# through a project ticket (specs/2026-09-28-project-tickets.md §11). The
|
|
304
|
+
# backend uses --ticket, or creates a ticket and links this WorkItem to it.
|
|
296
305
|
|
|
297
306
|
# Pipeline-#4 fix (spec 2026-05-05-request-decompose-pipeline-gap.md, Patch B):
|
|
298
307
|
# Route is /api/task-pool/add (not /api/pool/add — that endpoint does not exist
|
|
299
308
|
# and previously returned 404, silently swallowed by the || fallback below).
|
|
300
|
-
|
|
309
|
+
POOL_ERR_FILE="$(mktemp)"
|
|
310
|
+
POOL_RESULT=$(api_call POST "/task-pool/add" "$POOL_BODY" 2>"$POOL_ERR_FILE" || echo '{"success":false}')
|
|
311
|
+
POOL_ERR="$(cat "$POOL_ERR_FILE" 2>/dev/null || true)"
|
|
312
|
+
rm -f "$POOL_ERR_FILE"
|
|
301
313
|
POOL_OK=$(echo "$POOL_RESULT" | jq -r '.success // "false"' 2>/dev/null)
|
|
302
314
|
WI_ID=$(echo "$POOL_RESULT" | jq -r '.data.id // .workItemId // empty' 2>/dev/null || true)
|
|
315
|
+
PROJECT_TICKET_INFO=$(echo "$POOL_RESULT" | jq -c '.data.projectTicket // null' 2>/dev/null || echo 'null')
|
|
316
|
+
[ -n "$PROJECT_TICKET_INFO" ] || PROJECT_TICKET_INFO='null'
|
|
303
317
|
|
|
304
318
|
if [ "$POOL_OK" != "true" ]; then
|
|
305
|
-
|
|
319
|
+
# Surface the backend's reason (e.g. a --ticket refused by the project-ticket rules).
|
|
320
|
+
ERR_DETAILS=$(printf '%s' "$POOL_ERR" | grep '"error":true' | tail -1 | jq -c '.details // .' 2>/dev/null || true)
|
|
321
|
+
echo "{\"error\":\"Failed to create WorkItem in TaskPool\",\"details\":${ERR_DETAILS:-$(echo "$POOL_RESULT" | jq -c . 2>/dev/null || echo '{}')}}"
|
|
306
322
|
exit 1
|
|
307
323
|
fi
|
|
308
324
|
|
|
@@ -400,5 +416,7 @@ jq -n \
|
|
|
400
416
|
--arg message "$RESULT_MESSAGE" \
|
|
401
417
|
--arg fallbackTriggerId "${FALLBACK_TRIGGER_ID:-}" \
|
|
402
418
|
--arg fallbackMinutes "${FALLBACK_MINUTES:-0}" \
|
|
419
|
+
--argjson projectTicket "$PROJECT_TICKET_INFO" \
|
|
403
420
|
'{success: true, workItemId: $workItemId, target: $target, priority: $priority, title: $title, state: $state, message: $message}
|
|
404
|
-
+ (if $fallbackTriggerId != "" then {fallbackTriggerId: $fallbackTriggerId, fallbackMinutes: ($fallbackMinutes|tonumber)} else {} end)
|
|
421
|
+
+ (if $fallbackTriggerId != "" then {fallbackTriggerId: $fallbackTriggerId, fallbackMinutes: ($fallbackMinutes|tonumber)} else {} end)
|
|
422
|
+
+ (if $projectTicket != null then {projectTicket: $projectTicket} else {} end)'
|
|
@@ -47,7 +47,15 @@ api_call() {
|
|
|
47
47
|
echo "\${method} \${path} \${body}" >> "${CALL_LOG}"
|
|
48
48
|
case "\${path}" in
|
|
49
49
|
/task-pool/add)
|
|
50
|
-
|
|
50
|
+
if [ "\${TEST_ADD_REFUSE:-0}" = "1" ]; then
|
|
51
|
+
# Mirrors the real api_call on a 4xx: error envelope on stderr, exit 1.
|
|
52
|
+
echo '{"error":true,"status":409,"details":{"success":false,"error":"APP-3 is already being worked in WorkItem wi-9","code":"project_ticket_refused"}}' >&2
|
|
53
|
+
return 1
|
|
54
|
+
elif [ "\${TEST_ADD_TICKET:-0}" = "1" ]; then
|
|
55
|
+
echo '{"success":true,"data":{"id":"wi-stub-id","projectTicket":{"id":"APP-4","status":"in_progress","projectPath":"/p","project":"App","created":true}}}'
|
|
56
|
+
else
|
|
57
|
+
echo '{"success":true,"data":{"id":"wi-stub-id"}}'
|
|
58
|
+
fi
|
|
51
59
|
;;
|
|
52
60
|
/task-pool/claim)
|
|
53
61
|
if [ "\${TEST_CLAIM_FAIL:-0}" = "1" ]; then
|
|
@@ -386,6 +394,41 @@ else
|
|
|
386
394
|
fi
|
|
387
395
|
assert_log_contains "add still fires without a quoted Request" "POST /task-pool/add"
|
|
388
396
|
|
|
397
|
+
# ---------------------------------------------------------------------------
|
|
398
|
+
# Test 9: delegation through project tickets (spec 2026-09-28 §11)
|
|
399
|
+
# ---------------------------------------------------------------------------
|
|
400
|
+
echo ""
|
|
401
|
+
echo "=== ORC delegate-task — project tickets ==="
|
|
402
|
+
> "$CALL_LOG"
|
|
403
|
+
OUT=$(CALL_LOG="$CALL_LOG" CREWLY_ROOT=/tmp/crewly-test CREWLY_SESSION_NAME=crewly-orc \
|
|
404
|
+
bash "${SKILL_PARENT}/delegate-task/execute.sh" --to crewly-test-bob --task "Ship it" --ticket APP-3 --fallback-minutes 0 2>&1 || true)
|
|
405
|
+
assert_log_contains "--ticket travels as projectTicketId" '"projectTicketId": "APP-3"'
|
|
406
|
+
assert_log_contains "the delegator is stamped as metadata.delegatedBy" '"delegatedBy": "crewly-orc"'
|
|
407
|
+
|
|
408
|
+
> "$CALL_LOG"
|
|
409
|
+
OUT=$(CALL_LOG="$CALL_LOG" CREWLY_ROOT=/tmp/crewly-test TEST_ADD_TICKET=1 \
|
|
410
|
+
bash "${SKILL_PARENT}/delegate-task/execute.sh" --to crewly-test-bob --task "Ship it" --fallback-minutes 0 2>&1 || true)
|
|
411
|
+
assert_output_contains "result names the ticket the work is tracked under" '"id": "APP-4"' "$OUT"
|
|
412
|
+
if grep -q 'projectTicketId' "$CALL_LOG"; then
|
|
413
|
+
echo " FAIL: projectTicketId sent without --ticket"; FAIL=$((FAIL + 1))
|
|
414
|
+
else
|
|
415
|
+
echo " PASS: no projectTicketId without --ticket"; PASS=$((PASS + 1))
|
|
416
|
+
fi
|
|
417
|
+
|
|
418
|
+
> "$CALL_LOG"
|
|
419
|
+
set +e
|
|
420
|
+
OUT=$(CALL_LOG="$CALL_LOG" CREWLY_ROOT=/tmp/crewly-test TEST_ADD_REFUSE=1 \
|
|
421
|
+
bash "${SKILL_PARENT}/delegate-task/execute.sh" --to crewly-test-bob --task "Ship it" --ticket APP-3 --fallback-minutes 0 2>/dev/null)
|
|
422
|
+
RC=$?
|
|
423
|
+
set -e
|
|
424
|
+
if [ "$RC" -ne 0 ]; then echo " PASS: a refused --ticket fails the skill"; PASS=$((PASS + 1)); else echo " FAIL: refused --ticket exited 0"; FAIL=$((FAIL + 1)); fi
|
|
425
|
+
assert_output_contains "the refusal reason is shown" 'already being worked' "$OUT"
|
|
426
|
+
if grep -q "POST /task-pool/claim" "$CALL_LOG"; then
|
|
427
|
+
echo " FAIL: claim attempted after a refusal"; FAIL=$((FAIL + 1))
|
|
428
|
+
else
|
|
429
|
+
echo " PASS: nothing claimed after a refusal"; PASS=$((PASS + 1))
|
|
430
|
+
fi
|
|
431
|
+
|
|
389
432
|
# --- Summary ---
|
|
390
433
|
echo ""
|
|
391
434
|
echo "=== Results: $PASS passed, $FAIL failed ==="
|
|
@@ -48,6 +48,24 @@ Assigns a task to a worker within the Team Leader's subordinate scope. Validates
|
|
|
48
48
|
| `--tl-member` | `tlMemberId` | No | TL's member ID for hierarchy validation |
|
|
49
49
|
| `--from` | `fromSession` | No | Delegating TL's session name (for monitoring) |
|
|
50
50
|
| `--request-id` / `-R` | `requestId` | No | Ticket this work is for: the id (or `TKT-123`) from the `[TICKET:TKT-123 <id>]` line of the message you are acting on. Omit it and the task is still linked when your current turn has exactly one ticket |
|
|
51
|
+
| `--ticket` | `ticket` | No | Project ticket this work is for (e.g. `APP-12`). Without it a ticket is created for you when the target works on a project — see Project tickets |
|
|
52
|
+
|
|
53
|
+
## Project tickets
|
|
54
|
+
|
|
55
|
+
Work for a teammate on a project always runs through a project ticket
|
|
56
|
+
(`<project>/.crewly/tickets/`, spec `2026-09-28-project-tickets.md` §11):
|
|
57
|
+
|
|
58
|
+
- `--ticket APP-12` — that ticket (must be `backlog` or `ready`, with no other
|
|
59
|
+
live WorkItem) is assigned to `--to` and this WorkItem becomes its work. A
|
|
60
|
+
ticket that is already being worked, done, cancelled or unknown is refused:
|
|
61
|
+
nothing is delivered and the reason is printed.
|
|
62
|
+
- no `--ticket` — a ticket is created from this delegation (title, brief,
|
|
63
|
+
the target's team, priority) and set `in_progress` for the target. The
|
|
64
|
+
output names it (`projectTicket`).
|
|
65
|
+
|
|
66
|
+
No ticket is made for work you target at yourself, review/verify items,
|
|
67
|
+
system or scheduled items, or targets whose team has no project. Work already
|
|
68
|
+
in flight without a ticket: `project-tickets link --project P --id APP-12 --work-item <id>`.
|
|
51
69
|
|
|
52
70
|
## Request Contract check
|
|
53
71
|
|
|
@@ -17,6 +17,7 @@ TEAM_ID=""
|
|
|
17
17
|
TL_MEMBER_ID=""
|
|
18
18
|
FROM_SESSION=""
|
|
19
19
|
REQUEST_ID=""
|
|
20
|
+
PROJECT_TICKET=""
|
|
20
21
|
|
|
21
22
|
# Detect legacy JSON argument
|
|
22
23
|
if [[ $# -gt 0 && ${1:0:1} == '{' ]]; then
|
|
@@ -36,9 +37,11 @@ while [[ $# -gt 0 ]]; do
|
|
|
36
37
|
--tl-member) TL_MEMBER_ID="$2"; shift 2 ;;
|
|
37
38
|
--from) FROM_SESSION="$2"; shift 2 ;;
|
|
38
39
|
--request-id|-R) REQUEST_ID="$2"; shift 2 ;;
|
|
40
|
+
--ticket) PROJECT_TICKET="$2"; shift 2 ;;
|
|
39
41
|
--json|-j) INPUT_JSON="$2"; shift 2 ;;
|
|
40
42
|
--help|-h)
|
|
41
|
-
echo "Usage: execute.sh --to worker-session --task 'implement feature' --priority high --project /path [--team teamId] [--tl-member memberId] [--request-id <ticket id from the [TICKET:TKT-123 <id>] line>]"
|
|
43
|
+
echo "Usage: execute.sh --to worker-session --task 'implement feature' --priority high --project /path [--team teamId] [--tl-member memberId] [--request-id <ticket id from the [TICKET:TKT-123 <id>] line>] [--ticket <project ticket id, e.g. APP-12>]"
|
|
44
|
+
echo "Work for a teammate on a project always runs through a project ticket: --ticket uses that backlog/ready ticket, otherwise one is created for you."
|
|
42
45
|
exit 0
|
|
43
46
|
;;
|
|
44
47
|
--) shift; break ;;
|
|
@@ -75,6 +78,7 @@ if [ -n "$INPUT_JSON" ]; then
|
|
|
75
78
|
[ -z "$TL_MEMBER_ID" ] && TL_MEMBER_ID=$(printf '%s' "$INPUT" | jq -r '.tlMemberId // empty')
|
|
76
79
|
[ -z "$FROM_SESSION" ] && FROM_SESSION=$(printf '%s' "$INPUT" | jq -r '.fromSession // empty')
|
|
77
80
|
[ -z "$REQUEST_ID" ] && REQUEST_ID=$(printf '%s' "$INPUT" | jq -r '.requestId // empty')
|
|
81
|
+
[ -z "$PROJECT_TICKET" ] && PROJECT_TICKET=$(printf '%s' "$INPUT" | jq -r '.ticket // .projectTicketId // empty')
|
|
78
82
|
fi
|
|
79
83
|
|
|
80
84
|
require_param "to (--to)" "$TO"
|
|
@@ -212,15 +216,38 @@ POOL_BODY=$(jq -n \
|
|
|
212
216
|
--arg priority "$WI_PRIORITY" \
|
|
213
217
|
--arg projectPath "${PROJECT_PATH:-}" \
|
|
214
218
|
--arg requestId "${REQUEST_ID:-}" \
|
|
215
|
-
|
|
219
|
+
--arg projectTicketId "${PROJECT_TICKET:-}" \
|
|
220
|
+
--arg delegatedBy "${CREWLY_SESSION_NAME:-${FROM_SESSION:-}}" \
|
|
221
|
+
'{type: $type, owner: $owner, target: $target, title: $title, description: $description, briefMarkdown: $briefMarkdown, metadata: ({priority: $priority, directDelivery: true} + (if $projectPath != "" then {projectPath: $projectPath} else {} end) + (if $delegatedBy != "" then {delegatedBy: $delegatedBy} else {} end))} + (if $requestId != "" then {requestId: $requestId} else {} end) + (if $projectTicketId != "" then {projectTicketId: $projectTicketId} else {} end)')
|
|
216
222
|
# metadata.directDelivery: this script delivers the brief itself (below), so
|
|
217
223
|
# the backend's workitem:queued push holds off and only fires if that
|
|
218
224
|
# delivery never lands — the task reaches the worker once.
|
|
219
|
-
|
|
220
|
-
|
|
225
|
+
# projectTicketId / metadata.delegatedBy: work for a teammate on a project runs
|
|
226
|
+
# through a project ticket (specs/2026-09-28-project-tickets.md §11). The
|
|
227
|
+
# backend uses --ticket, or creates a ticket and links this WorkItem to it.
|
|
228
|
+
|
|
229
|
+
POOL_ERR_FILE="$(mktemp)"
|
|
230
|
+
POOL_RESULT=$(api_call POST "/task-pool/add" "$POOL_BODY" 2>"$POOL_ERR_FILE" || echo '{"success":false}')
|
|
231
|
+
POOL_ERR="$(cat "$POOL_ERR_FILE" 2>/dev/null || true)"
|
|
232
|
+
rm -f "$POOL_ERR_FILE"
|
|
221
233
|
POOL_OK=$(echo "$POOL_RESULT" | jq -r '.success // "false"' 2>/dev/null)
|
|
222
234
|
TASK_ID=$(echo "$POOL_RESULT" | jq -r '.data.id // .workItemId // empty' 2>/dev/null || true)
|
|
223
235
|
|
|
236
|
+
# A refusal by the project-ticket rules (e.g. --ticket already being worked,
|
|
237
|
+
# done, or unknown) means the delegation must NOT go out: stop here instead
|
|
238
|
+
# of the deliver-anyway path below.
|
|
239
|
+
if [ "$POOL_OK" != "true" ] && printf '%s' "$POOL_ERR" | grep -q 'project_ticket_refused'; then
|
|
240
|
+
REFUSAL=$(printf '%s' "$POOL_ERR" | grep 'project_ticket_refused' | tail -1 | jq -r '.details.error // empty' 2>/dev/null || true)
|
|
241
|
+
jq -n --arg error "${REFUSAL:-Delegation refused by the project-ticket rules}" --arg to "$TO" --arg ticket "${PROJECT_TICKET:-}" \
|
|
242
|
+
'{success: false, error: $error, to: $to, hint: "Nothing was delivered. Fix the ticket (project-tickets show / update --status ready) or delegate without --ticket."} + (if $ticket != "" then {ticket: $ticket} else {} end)'
|
|
243
|
+
exit 1
|
|
244
|
+
fi
|
|
245
|
+
|
|
246
|
+
TICKET_INFO=$(echo "$POOL_RESULT" | jq -c '.data.projectTicket // empty' 2>/dev/null || true)
|
|
247
|
+
if [ -n "$TICKET_INFO" ]; then
|
|
248
|
+
echo "$TICKET_INFO" | jq -c '{projectTicket: ., info: ("Tracked as project ticket " + .id + " (" + .status + (if .created then ", created for this delegation" else "" end) + ")")}'
|
|
249
|
+
fi
|
|
250
|
+
|
|
224
251
|
# The delivered text names the WorkItem id: the worker needs it for
|
|
225
252
|
# report-status, and after a fresh-conversation clear the backend finds the
|
|
226
253
|
# new conversation by it. The deliver body carries it too (workItemId), so
|
|
@@ -662,6 +662,65 @@ else
|
|
|
662
662
|
PASS=$((PASS + 1))
|
|
663
663
|
fi
|
|
664
664
|
|
|
665
|
+
# -----------------------------------------------------------------------
|
|
666
|
+
# Scenario 24: delegation through project tickets (spec 2026-09-28 §11).
|
|
667
|
+
#
|
|
668
|
+
# Runs a COPY of the real execute.sh next to a lib.sh whose api_call is
|
|
669
|
+
# stubbed (the same layout trick as the ORC delegate-task test), so the
|
|
670
|
+
# production --ticket handling is exercised verbatim.
|
|
671
|
+
# -----------------------------------------------------------------------
|
|
672
|
+
echo "Scenario 24: --ticket, the ticket report, and a refused ticket"
|
|
673
|
+
TK_PARENT=$(mktemp -d)
|
|
674
|
+
TK_LOG=$(mktemp)
|
|
675
|
+
mkdir -p "$TK_PARENT/_common" "$TK_PARENT/delegate-task"
|
|
676
|
+
cat > "$TK_PARENT/_common/lib.sh" <<LIBEOF
|
|
677
|
+
source "$(cd "$SCRIPT_DIR/../.." && pwd)/_common/lib.sh"
|
|
678
|
+
api_call() {
|
|
679
|
+
local method="\$1" path="\$2" body="\${3:-}"
|
|
680
|
+
printf '%s %s %s\n' "\$method" "\$path" "\$(printf '%s' "\$body" | tr -d '\n')" >> "$TK_LOG"
|
|
681
|
+
case "\$path" in
|
|
682
|
+
/task-pool/add)
|
|
683
|
+
if [ "\${TEST_ADD_REFUSE:-0}" = "1" ]; then
|
|
684
|
+
echo '{"error":true,"status":409,"details":{"success":false,"error":"CE-2 is already being worked in WorkItem wi-9","code":"project_ticket_refused"}}' >&2
|
|
685
|
+
return 1
|
|
686
|
+
fi
|
|
687
|
+
echo '{"success":true,"data":{"id":"wi-tl","projectTicket":{"id":"CE-14","status":"in_progress","projectPath":"/p","project":"ce-core","created":true}}}'
|
|
688
|
+
;;
|
|
689
|
+
*) echo '{"success":true}' ;;
|
|
690
|
+
esac
|
|
691
|
+
}
|
|
692
|
+
LIBEOF
|
|
693
|
+
cp "$SKILL_DIR/execute.sh" "$TK_PARENT/delegate-task/execute.sh"
|
|
694
|
+
|
|
695
|
+
OUT=$(CREWLY_SESSION_NAME=ce-owen bash "$TK_PARENT/delegate-task/execute.sh" --to ce-vera --task "Goal: a. Expected Outcome: b. Eval Criteria: c." --ticket CE-9 2>/dev/null || true)
|
|
696
|
+
if grep -q '"projectTicketId": *"CE-9"' "$TK_LOG" && grep -q '"delegatedBy": *"ce-owen"' "$TK_LOG"; then
|
|
697
|
+
echo " PASS: --ticket and the delegator travel to /task-pool/add"; PASS=$((PASS + 1))
|
|
698
|
+
else
|
|
699
|
+
echo " FAIL: projectTicketId/delegatedBy missing: $(cat "$TK_LOG")"; FAIL=$((FAIL + 1))
|
|
700
|
+
fi
|
|
701
|
+
if printf '%s' "$OUT" | grep -q 'Tracked as project ticket CE-14'; then
|
|
702
|
+
echo " PASS: output names the project ticket"; PASS=$((PASS + 1))
|
|
703
|
+
else
|
|
704
|
+
echo " FAIL: no ticket line in output: $OUT"; FAIL=$((FAIL + 1))
|
|
705
|
+
fi
|
|
706
|
+
|
|
707
|
+
: > "$TK_LOG"
|
|
708
|
+
set +e
|
|
709
|
+
OUT=$(TEST_ADD_REFUSE=1 CREWLY_SESSION_NAME=ce-owen bash "$TK_PARENT/delegate-task/execute.sh" --to ce-vera --task "Goal: a. Expected Outcome: b. Eval Criteria: c." --ticket CE-2 2>/dev/null)
|
|
710
|
+
RC=$?
|
|
711
|
+
set -e
|
|
712
|
+
if [ "$RC" -ne 0 ] && printf '%s' "$OUT" | grep -q 'already being worked'; then
|
|
713
|
+
echo " PASS: a refused --ticket fails with the reason"; PASS=$((PASS + 1))
|
|
714
|
+
else
|
|
715
|
+
echo " FAIL: refused --ticket: rc=$RC out=$OUT"; FAIL=$((FAIL + 1))
|
|
716
|
+
fi
|
|
717
|
+
if grep -q '/deliver' "$TK_LOG"; then
|
|
718
|
+
echo " FAIL: the brief was delivered despite the refusal"; FAIL=$((FAIL + 1))
|
|
719
|
+
else
|
|
720
|
+
echo " PASS: nothing delivered after a refusal"; PASS=$((PASS + 1))
|
|
721
|
+
fi
|
|
722
|
+
rm -rf "$TK_PARENT" "$TK_LOG"
|
|
723
|
+
|
|
665
724
|
# Cleanup
|
|
666
725
|
rm -f "$REAL_FN_FILE"
|
|
667
726
|
rm -rf "$MOCK_DIR"
|
|
@@ -36,6 +36,11 @@ export declare const PROJECT_TICKET_CONSTANTS: {
|
|
|
36
36
|
readonly WORK_ITEM_METADATA_KEY: "projectTicket";
|
|
37
37
|
readonly SYNC_SWEEP_INTERVAL_MS: number;
|
|
38
38
|
readonly MAX_SUCCESSOR_HOPS: 8;
|
|
39
|
+
readonly DELEGATION_TICKET_BODY_KEY: "projectTicketId";
|
|
40
|
+
readonly DELEGATION_CALLER_METADATA_KEY: "delegatedBy";
|
|
41
|
+
readonly DELEGATION_WORK_ITEM_TYPES: readonly ["delegate"];
|
|
42
|
+
readonly DELEGATION_TITLE_MAX_CHARS: 120;
|
|
43
|
+
readonly DELEGATION_REFUSED_CODE: "project_ticket_refused";
|
|
39
44
|
};
|
|
40
45
|
/**
|
|
41
46
|
* Safe restart: drain in-flight agent turns before a shutdown kills the PTYs,
|
|
@@ -2245,6 +2250,47 @@ export declare const BROWSER_SESSION_CONSTANTS: {
|
|
|
2245
2250
|
/** How long a finished session stays listed before being pruned (ms) */
|
|
2246
2251
|
readonly RETAIN_FINISHED_MS: number;
|
|
2247
2252
|
};
|
|
2253
|
+
/**
|
|
2254
|
+
* Owner input into a browser session the owner has taken over
|
|
2255
|
+
* (`POST /api/browser/sessions/:id/input`). The owner is usually on a phone,
|
|
2256
|
+
* tapping a picture of the page, so every limit here is about keeping one tap
|
|
2257
|
+
* or one "Send" a single bounded action.
|
|
2258
|
+
*/
|
|
2259
|
+
export declare const BROWSER_OWNER_INPUT_CONSTANTS: {
|
|
2260
|
+
/** Longest text one `type` may carry (characters) */
|
|
2261
|
+
readonly MAX_TEXT_LENGTH: 2000;
|
|
2262
|
+
/** Longest URL one `navigate` may carry (characters) */
|
|
2263
|
+
readonly MAX_URL_LENGTH: 4096;
|
|
2264
|
+
/** Largest single scroll step either way (CSS px) */
|
|
2265
|
+
readonly MAX_SCROLL_PX: 5000;
|
|
2266
|
+
/**
|
|
2267
|
+
* How long a measured viewport is trusted before a tap measures again
|
|
2268
|
+
* (ms). Each measurement is a round trip to the extension, which over the
|
|
2269
|
+
* relay is the slowest part of a tap.
|
|
2270
|
+
*/
|
|
2271
|
+
readonly VIEWPORT_CACHE_MS: 15000;
|
|
2272
|
+
/** Keys the owner can press from the control bar */
|
|
2273
|
+
readonly KEYS: readonly ["Enter", "Tab", "Backspace", "Escape", "ArrowUp", "ArrowDown"];
|
|
2274
|
+
/** How far an arrow key scrolls when focus is not in a text field (CSS px) */
|
|
2275
|
+
readonly ARROW_SCROLL_PX: 40;
|
|
2276
|
+
/** Command timeout for one owner action (ms) */
|
|
2277
|
+
readonly COMMAND_TIMEOUT_MS: 15000;
|
|
2278
|
+
/**
|
|
2279
|
+
* Pause after a tap, key or Back before the fresh frame is captured (ms),
|
|
2280
|
+
* so the picture sent back shows what the action did rather than the
|
|
2281
|
+
* instant before it. Navigation needs none: it waits for the page load.
|
|
2282
|
+
*/
|
|
2283
|
+
readonly SETTLE_BEFORE_FRAME_MS: 350;
|
|
2284
|
+
/**
|
|
2285
|
+
* Page-quiet wait before a tap is clicked (ms). The extension's click
|
|
2286
|
+
* waits for the page to go idle (default up to 2 s) because an agent
|
|
2287
|
+
* clicks blind; the owner is looking at the page, so a tap should land
|
|
2288
|
+
* when they tapped.
|
|
2289
|
+
*/
|
|
2290
|
+
readonly TAP_IDLE_QUIET_MS: 50;
|
|
2291
|
+
/** Longest the extension may wait for that quiet before clicking (ms) */
|
|
2292
|
+
readonly TAP_IDLE_MAX_WAIT_MS: 300;
|
|
2293
|
+
};
|
|
2248
2294
|
export declare const CLAUDE_TRANSCRIPT_SYNC_CONSTANTS: {
|
|
2249
2295
|
/** How often to read the unread tail of each agent's transcript (ms) */
|
|
2250
2296
|
readonly SYNC_INTERVAL_MS: 60000;
|