diff --git a/skill/SKILL.md b/skill/SKILL.md index b882da4..6ed9abb 100644 --- a/skill/SKILL.md +++ b/skill/SKILL.md @@ -1,115 +1,168 @@ --- name: work-queue -description: "Work queue skill for TheLab agents. Submit, dispatch, track, and complete work items via the Work Queue API. Embeds TheLab dispatch opinion: one in_progress per agent, stale detection, automatic blocking of timed-out items." +description: "Full Work Queue API skill for TheLab agents. Covers all project and work item operations: submit, dispatch, track, complete, cancel, and monitor work across agents. Embeds TheLab dispatch opinion." --- # Work Queue Skill - Thin wrapper around the Work Queue API with embedded dispatch opinion. +Wrapper around the Work Queue API at `http://app-01:8080`. ## Setup -Set the base URL: ```bash -export WORK_QUEUE_API_URL=https://api.example.com # replace with actual API URL +# Already configured: +echo "http://app-01:8080" > ~/.config/work_queue_api_url +export WORK_QUEUE_API_URL=http://app-01:8080 ``` -The skill reads `WORK_QUEUE_API_URL` from env. +## Commands -## Core Commands +### `wq health` +Check API is up. +```bash +wq health +``` + +--- + +## Project Commands + +### `wq project add` +Create a project. +```bash +wq project add [--external-ref ] +``` +`external_ref` can be a Todoist project ID, GitHub repo, or any external identifier. + +### `wq project list` +List all projects. +```bash +wq project list +``` + +### `wq project get` +Get a single project. +```bash +wq project get +``` + +### `wq project update` +Update a project's name or external_ref. +```bash +wq project update [--name ] [--external-ref ] +``` + +--- + +## Work Item Commands ### `wq add` - Submit a new work item (status=queued). - ```bash -wq add [--agent ] [--project-id ] [--priority 1-5>] [--payload ] +wq add \ + [--agent ] \ + [--project-id ] \ + [--priority 1-5>] \ + [--payload ] \ + [--created-by ] ``` +- `type` — e.g. `code_review`, `bug_fix`, `infra_setup`, `gitea_issue` +- `priority` — 1 (highest) to 5 (lowest), default 3 +- `payload` — arbitrary JSON object with type-specific fields +- `created-by` — defaults to `marcus-a` Example: ```bash -wq add code_review "Review PR #3 in shopping-list-api" --agent steve-w --priority 2 --payload '{"pr":3,"repo":"shopping-list-api"}' +wq add code_review "Review PR #3 in work-queue-api" \ + --agent steve-w \ + --project-id be358bbf-aff2-477d-8116-d4bf3d4d3540 \ + --priority 1 \ + --payload '{"pr":3,"repo":"work-queue-api"}' ``` ### `wq dispatch` - -Dispatch a queued item to an agent (moves queued→dispatched→in_progress atomically). - +Dispatch a queued item to an agent. Atomically moves queued→dispatched→in_progress. ```bash wq dispatch ``` - Fails if agent already has an in_progress item. ### `wq update` - -Update status, outcome, or notes on a work item. - +Update status, outcome, notes, or reassign agent. ```bash -wq update [--status ] [--outcome ] [--notes ] +wq update \ + [--status ] \ + [--outcome ] \ + [--notes ] \ + [--agent ] ``` -Valid status transitions: -- dispatched → in_progress (agent picked it up) -- in_progress → blocked (waiting on something) -- in_progress → completed (done) -- in_progress → failed (unrecoverable error) -- queued → cancelled -- dispatched → cancelled +### `wq delete` +Cancel a work item (sets status=cancelled). Only works from queued or dispatched. +```bash +wq delete +``` ### `wq list` - -List work items, optionally filtered. - +List work items with optional filters. ```bash -wq list [--status ] [--agent ] [--project-id ] +wq list \ + [--status ] \ + [--agent ] \ + [--project-id ] \ + [--since ] \ + [--type ] ``` +`since` filters to items created after the given timestamp (ISO8601). ### `wq get` - -Get a single work item by ID. - +Get a single work item (includes dispatch history). ```bash wq get ``` ### `wq my-queue` - -Short-cut: list items assigned to a given agent with status=dispatched (what Steve should poll). - +Short-cut: dispatched items for a given agent — what Steve polls. ```bash wq my-queue ``` ---- - -## Dispatch Opinion - -These rules are enforced automatically by the skill: - -1. **One in_progress per agent** — dispatch fails if target agent is already in_progress -2. **Stale detection** — on every heartbeat, `wq stale-check` is called; items in_progress >30min are automatically marked blocked -3. **Terminal states require outcome** — moving to completed/failed/cancelled requires outcome field -4. **Cancelled only from queued/dispatched** — cannot cancel something already in_progress - ---- - -## Stale Check (for heartbeat) - +### `wq stale-check` +Find in_progress items older than N minutes, mark them blocked. Run on heartbeat. ```bash -wq stale-check [--timeout-minutes 30] +wq stale-check [30] +``` +Default timeout: 30 minutes. + +--- + +## Dispatch Opinion (enforced by the API) + +| Transition | From | To | +|---|---|---| +| submit | — | queued | +| dispatch | queued | dispatched | +| pick up | dispatched | in_progress | +| block | in_progress | blocked | +| complete | in_progress | completed | +| fail | in_progress | failed | +| cancel | queued/dispatched | cancelled | + +Rules: +1. **One in_progress per agent** — dispatch blocks if agent is already busy +2. **Terminal states require outcome** — completed/failed/cancelled require outcome field +3. **Stale detection** — `wq stale-check` auto-blocks stale in_progress items + +--- + +## Status Lifecycle + +``` +queued → dispatched → in_progress → completed + ↘ blocked + ↘ failed + ↘ cancelled (from queued or dispatched only) ``` -Finds all in_progress items older than timeout, marks them blocked, prints a summary line per item. - ---- - -## Integration with Gitea Watcher - -The Gitea watcher (`gitea_cron/check.sh`) outputs `dispatch:` lines. On heartbeat, parse those lines and for each: - -```bash -wq add --agent steve-w --payload '' -``` - -The watcher itself does NOT call the API — it just emits dispatch lines. Marcus's heartbeat parses them, creates real work items via `wq add`, then dispatches via `wq dispatch`. +## API Base +`http://app-01:8080` diff --git a/skill/bin/wq b/skill/bin/wq index 7a2bf29..102d2c7 100755 --- a/skill/bin/wq +++ b/skill/bin/wq @@ -1,47 +1,41 @@ #!/bin/bash -# wq — Work Queue CLI wrapper +# wq — Work Queue CLI # Usage: wq [options] -set -e - -API_URL="${WORK_QUEUE_API_URL:-}" -if [[ -z "$API_URL" ]]; then - if [[ -f ~/.config/work_queue_api_url ]]; then - API_URL=$(cat ~/.config/work_queue_api_url) - else - echo "Error: WORK_QUEUE_API_URL not set and no ~/.config/work_queue_api_url" >&2 - exit 1 - fi -fi +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" CMD="$1" shift || { echo "Usage: wq [args]" >&2; exit 1; } case "$CMD" in - add) - wq_add "$@" - ;; - dispatch) - wq_dispatch "$@" - ;; - update) - wq_update "$@" - ;; - list) - wq_list "$@" - ;; - get) - wq_get "$@" - ;; - my-queue) - wq_my_queue "$@" - ;; - stale-check) - wq_stale_check "$@" - ;; - *) - echo "Unknown command: $CMD" >&2 - echo "Commands: add, dispatch, update, list, get, my-queue, stale-check" >&2 - exit 1 - ;; + # Work item commands + add) exec "$SCRIPT_DIR/wq_add" "$@";; + dispatch) exec "$SCRIPT_DIR/wq_dispatch" "$@";; + update) exec "$SCRIPT_DIR/wq_update" "$@";; + delete) exec "$SCRIPT_DIR/wq_delete" "$@";; + list) exec "$SCRIPT_DIR/wq_list" "$@";; + get) exec "$SCRIPT_DIR/wq_get" "$@";; + my-queue) exec "$SCRIPT_DIR/wq_my_queue" "$@";; + stale-check) exec "$SCRIPT_DIR/wq_stale_check" "$@";; + + # Project commands + project) + sub="$1"; shift || { echo "Usage: wq project " >&2; exit 1; } + case "$sub" in + add) exec "$SCRIPT_DIR/wq_project_add" "$@";; + list) exec "$SCRIPT_DIR/wq_project_list" "$@";; + get) exec "$SCRIPT_DIR/wq_project_get" "$@";; + update) exec "$SCRIPT_DIR/wq_project_update" "$@";; + *) echo "Unknown project command: $sub" >&2 + echo "Commands: wq project " >&2; exit 1;; + esac;; + + # Health check + health) exec "$SCRIPT_DIR/wq_health" "$@";; + + *) echo "Unknown command: $CMD" >&2 + echo "Commands: add, dispatch, update, delete, list, get, my-queue, stale-check" >&2 + echo " project " >&2 + echo " health" >&2 + exit 1;; esac diff --git a/skill/bin/wq_add b/skill/bin/wq_add index 34a0cdb..5bf3299 100755 --- a/skill/bin/wq_add +++ b/skill/bin/wq_add @@ -1,39 +1,44 @@ #!/bin/bash # wq_add — submit a new work item +# Usage: wq add [options] +# Options: +# --agent Assign to agent (does NOT dispatch, just assigns) +# --project-id Link to a project +# --priority <1-5> 1=highest, 3=default +# --payload Arbitrary JSON payload +# --created-by Who/what created this (default: marcus-a) -wq_add() { - local type desc agent project_id priority payload_json +API_URL="${WORK_QUEUE_API_URL:-}" +[[ -z "$API_URL" ]] && API_URL=$(cat ~/.config/work_queue_api_url 2>/dev/null || echo "") +[[ -z "$API_URL" ]] && { echo "Error: WORK_QUEUE_API_URL not set" >&2; exit 1; } - while [[ $# -gt 0 ]]; do - case "$1" in - --agent) agent="$2"; shift 2;; - --project-id) project_id="$2"; shift 2;; - --priority) priority="$2"; shift 2;; - --payload) payload_json="$2"; shift 2;; - --) shift; break;; - -*) echo "Unknown option: $1" >&2; exit 1;; - *) - if [[ -z "$type" ]]; then - type="$1" - elif [[ -z "$desc" ]]; then - desc="$1" - fi - shift;; - esac - done +type="" desc="" agent="" project_id="" priority="" payload_json="" created_by="marcus-a" - if [[ -z "$type" ]] || [[ -z "$desc" ]]; then - echo "Usage: wq add [--agent ] [--project-id ] [--priority 1-5>] [--payload ]" >&2 - exit 1 - fi +while [[ $# -gt 0 ]]; do + case "$1" in + --agent) agent="$2"; shift 2;; + --project-id) project_id="$2"; shift 2;; + --priority) priority="$2"; shift 2;; + --payload) payload_json="$2"; shift 2;; + --created-by) created_by="$2"; shift 2;; + --) shift; break;; + -*) echo "Unknown option: $1" >&2; exit 1;; + *) + if [[ -z "$type" ]]; then type="$1" + elif [[ -z "$desc" ]]; then desc="$1" + fi + shift;; + esac +done - local body="{\"type\":\"$type\",\"description\":\"$desc\"}" - [[ -n "$agent" ]] && body=$(echo "$body" | jq ".assigned_agent=\"$agent\"") - [[ -n "$project_id" ]] && body=$(echo "$body" | jq ".project_id=\"$project_id\"") - [[ -n "$priority" ]] && body=$(echo "$body" | jq ".priority=$priority") - [[ -n "$payload_json" ]] && body=$(echo "$body" | jq ".payload=$payload_json") +[[ -z "$type" ]] || [[ -z "$desc" ]] && { echo "Usage: wq add [--agent ] [--project-id ] [--priority 1-5>] [--payload ] [--created-by ]" >&2; exit 1; } - curl -sf -X POST "$API_URL/work" \ - -H "Content-Type: application/json" \ - -d "$body" | jq . -} +body="{\"type\":\"$type\",\"description\":\"$desc\",\"created_by\":\"$created_by\"}" +[[ -n "$agent" ]] && body=$(echo "$body" | jq ".assigned_agent=\"$agent\"") +[[ -n "$project_id" ]] && body=$(echo "$body" | jq ".project_id=\"$project_id\"") +[[ -n "$priority" ]] && body=$(echo "$body" | jq ".priority=$priority") +[[ -n "$payload_json" ]] && body=$(echo "$body" | jq ".payload=$payload_json") + +curl -sf -X POST "$API_URL/work" \ + -H "Content-Type: application/json" \ + -d "$body" | jq . diff --git a/skill/bin/wq_delete b/skill/bin/wq_delete new file mode 100755 index 0000000..957d0d7 --- /dev/null +++ b/skill/bin/wq_delete @@ -0,0 +1,20 @@ +#!/bin/bash +# wq_delete — cancel a work item (sets status=cancelled) +# Usage: wq delete + +API_URL="${WORK_QUEUE_API_URL:-}" +[[ -z "$API_URL" ]] && API_URL=$(cat ~/.config/work_queue_api_url 2>/dev/null || echo "") +[[ -z "$API_URL" ]] && { echo "Error: WORK_QUEUE_API_URL not set" >&2; exit 1; } + +work_id="$1" +[[ -z "$work_id" ]] && { echo "Usage: wq delete " >&2; exit 1; } + +response=$(curl -sf -X DELETE "$API_URL/work/$work_id" -w "\n%{http_code}") +http_code=$(echo "$response" | tail -1) +body=$(echo "$response" | head -1) + +if [[ "$http_code" == "204" ]]; then + echo "Work item $work_id cancelled." +else + echo "$body" | jq . || echo "$body" +fi diff --git a/skill/bin/wq_dispatch b/skill/bin/wq_dispatch index 0fac5bb..cab6170 100755 --- a/skill/bin/wq_dispatch +++ b/skill/bin/wq_dispatch @@ -1,27 +1,21 @@ #!/bin/bash # wq_dispatch — dispatch queued item to agent (queued→dispatched→in_progress atomically) -wq_dispatch() { - local work_id="$1" agent="$2" +API_URL="${WORK_QUEUE_API_URL:-}" +[[ -z "$API_URL" ]] && API_URL=$(cat ~/.config/work_queue_api_url 2>/dev/null || echo "") +[[ -z "$API_URL" ]] && { echo "Error: WORK_QUEUE_API_URL not set" >&2; exit 1; } - if [[ -z "$work_id" ]] || [[ -z "$agent" ]]; then - echo "Usage: wq dispatch " >&2 - exit 1 - fi +work_id="$1" agent="$2" +[[ -z "$work_id" ]] || [[ -z "$agent" ]] && { echo "Usage: wq dispatch " >&2; exit 1; } - # Check if agent already has in_progress - local existing - existing=$(curl -sf "$API_URL/work?status=in_progress&agent=$agent" | jq 'length') - if [[ "$existing" -gt 0 ]]; then - echo "Error: $agent already has an in_progress item" >&2 - exit 1 - fi +# Check if agent already has in_progress +existing=$(curl -sf "$API_URL/work?status=in_progress&agent=$agent" | jq 'length') +[[ "$existing" -gt 0 ]] && { echo "Error: $agent already has an in_progress item" >&2; exit 1; } - curl -sf -X PATCH "$API_URL/work/$work_id" \ - -H "Content-Type: application/json" \ - -d "{\"status\":\"dispatched\",\"assigned_agent\":\"$agent\"}" | jq . +curl -sf -X PATCH "$API_URL/work/$work_id" \ + -H "Content-Type: application/json" \ + -d "{\"status\":\"dispatched\",\"assigned_agent\":\"$agent\"}" | jq . - curl -sf -X PATCH "$API_URL/work/$work_id" \ - -H "Content-Type: application/json" \ - -d "{\"status\":\"in_progress\"}" | jq . -} +curl -sf -X PATCH "$API_URL/work/$work_id" \ + -H "Content-Type: application/json" \ + -d "{\"status\":\"in_progress\"}" | jq . diff --git a/skill/bin/wq_get b/skill/bin/wq_get index 610371b..c4cdb3b 100755 --- a/skill/bin/wq_get +++ b/skill/bin/wq_get @@ -1,8 +1,11 @@ #!/bin/bash # wq_get — get single work item -wq_get() { - local work_id="$1" - [[ -z "$work_id" ]] && { echo "Usage: wq get " >&2; exit 1; } - curl -sf "$API_URL/work/$work_id" | jq . -} +API_URL="${WORK_QUEUE_API_URL:-}" +[[ -z "$API_URL" ]] && API_URL=$(cat ~/.config/work_queue_api_url 2>/dev/null || echo "") +[[ -z "$API_URL" ]] && { echo "Error: WORK_QUEUE_API_URL not set" >&2; exit 1; } + +work_id="$1" +[[ -z "$work_id" ]] && { echo "Usage: wq get " >&2; exit 1; } + +curl -sf "$API_URL/work/$work_id" | jq . diff --git a/skill/bin/wq_health b/skill/bin/wq_health new file mode 100755 index 0000000..fc04615 --- /dev/null +++ b/skill/bin/wq_health @@ -0,0 +1,8 @@ +#!/bin/bash +# wq_health — check API health + +API_URL="${WORK_QUEUE_API_URL:-}" +[[ -z "$API_URL" ]] && API_URL=$(cat ~/.config/work_queue_api_url 2>/dev/null || echo "") +[[ -z "$API_URL" ]] && { echo "Error: WORK_QUEUE_API_URL not set" >&2; exit 1; } + +curl -sf "$API_URL/health" | jq . diff --git a/skill/bin/wq_list b/skill/bin/wq_list index 991ef0e..40e7a7e 100755 --- a/skill/bin/wq_list +++ b/skill/bin/wq_list @@ -1,25 +1,35 @@ #!/bin/bash # wq_list — list work items with optional filters +# Usage: wq list [options] +# Options: +# --status Filter by status: queued, dispatched, in_progress, blocked, completed, failed, cancelled +# --agent Filter by assigned agent +# --project-id Filter by project +# --since Only items created after this timestamp +# --type Filter by work type -wq_list() { - local status agent project_id since qs="?" +API_URL="${WORK_QUEUE_API_URL:-}" +[[ -z "$API_URL" ]] && API_URL=$(cat ~/.config/work_queue_api_url 2>/dev/null || echo "") +[[ -z "$API_URL" ]] && { echo "Error: WORK_QUEUE_API_URL not set" >&2; exit 1; } - while [[ $# -gt 0 ]]; do - case "$1" in - --status) status="$2"; shift 2;; - --agent) agent="$2"; shift 2;; - --project-id) project_id="$2"; shift 2;; - --since) since="$2"; shift 2;; - --) shift; break;; - -*) echo "Unknown option: $1" >&2; exit 1;; - *) echo "Unknown arg: $1" >&2; exit 1;; - esac - done +status="" agent="" project_id="" since="" type="" qs="?" - [[ -n "$status" ]] && qs="${qs}status=$status&" - [[ -n "$agent" ]] && qs="${qs}agent=$agent&" - [[ -n "$project_id" ]] && qs="${qs}project_id=$project_id&" - [[ -n "$since" ]] && qs="${qs}since=$since&" +while [[ $# -gt 0 ]]; do + case "$1" in + --status) status="$2"; shift 2;; + --agent) agent="$2"; shift 2;; + --project-id) project_id="$2"; shift 2;; + --since) since="$2"; shift 2;; + --type) type="$2"; shift 2;; + --) shift; break;; + *) shift;; + esac +done - curl -sf "$API_URL/work${qs}" | jq . -} +[[ -n "$status" ]] && qs="${qs}status=$status&" +[[ -n "$agent" ]] && qs="${qs}agent=$agent&" +[[ -n "$project_id" ]] && qs="${qs}project_id=$project_id&" +[[ -n "$since" ]] && qs="${qs}since=$since&" +[[ -n "$type" ]] && qs="${qs}type=$type&" + +curl -sf "${API_URL}/work${qs}" | jq . diff --git a/skill/bin/wq_my_queue b/skill/bin/wq_my_queue index 613ce20..00f611d 100755 --- a/skill/bin/wq_my_queue +++ b/skill/bin/wq_my_queue @@ -1,8 +1,11 @@ #!/bin/bash # wq_my_queue — list dispatched items for a given agent (what Steve polls) -wq_my_queue() { - local agent="$1" - [[ -z "$agent" ]] && { echo "Usage: wq my-queue " >&2; exit 1; } - curl -sf "$API_URL/work?status=dispatched&agent=$agent" | jq . -} +API_URL="${WORK_QUEUE_API_URL:-}" +[[ -z "$API_URL" ]] && API_URL=$(cat ~/.config/work_queue_api_url 2>/dev/null || echo "") +[[ -z "$API_URL" ]] && { echo "Error: WORK_QUEUE_API_URL not set" >&2; exit 1; } + +agent="$1" +[[ -z "$agent" ]] && { echo "Usage: wq my-queue " >&2; exit 1; } + +curl -sf "$API_URL/work?status=dispatched&agent=$agent" | jq . diff --git a/skill/bin/wq_project_add b/skill/bin/wq_project_add new file mode 100755 index 0000000..40cf9c4 --- /dev/null +++ b/skill/bin/wq_project_add @@ -0,0 +1,27 @@ +#!/bin/bash +# wq_project_add — create a project +# Usage: wq project add [--external-ref ] + +API_URL="${WORK_QUEUE_API_URL:-}" +[[ -z "$API_URL" ]] && API_URL=$(cat ~/.config/work_queue_api_url 2>/dev/null || echo "") +[[ -z "$API_URL" ]] && { echo "Error: WORK_QUEUE_API_URL not set" >&2; exit 1; } + +name="" external_ref="" + +while [[ $# -gt 0 ]]; do + case "$1" in + --external-ref) external_ref="$2"; shift 2;; + --) shift; break;; + -*) echo "Unknown option: $1" >&2; exit 1;; + *) [[ -z "$name" ]] && name="$1" || { echo "Usage: wq project add [--external-ref ]" >&2; exit 1; }; shift;; + esac +done + +[[ -z "$name" ]] && { echo "Usage: wq project add [--external-ref ]" >&2; exit 1; } + +body="{\"name\":\"$name\"}" +[[ -n "$external_ref" ]] && body=$(echo "$body" | jq ".external_ref=\"$external_ref\"") + +curl -sf -X POST "$API_URL/projects" \ + -H "Content-Type: application/json" \ + -d "$body" | jq . diff --git a/skill/bin/wq_project_get b/skill/bin/wq_project_get new file mode 100755 index 0000000..193d849 --- /dev/null +++ b/skill/bin/wq_project_get @@ -0,0 +1,11 @@ +#!/bin/bash +# wq_project_get — get a single project + +API_URL="${WORK_QUEUE_API_URL:-}" +[[ -z "$API_URL" ]] && API_URL=$(cat ~/.config/work_queue_api_url 2>/dev/null || echo "") +[[ -z "$API_URL" ]] && { echo "Error: WORK_QUEUE_API_URL not set" >&2; exit 1; } + +project_id="$1" +[[ -z "$project_id" ]] && { echo "Usage: wq project get " >&2; exit 1; } + +curl -sf "$API_URL/projects/$project_id" | jq . diff --git a/skill/bin/wq_project_list b/skill/bin/wq_project_list new file mode 100755 index 0000000..38eb164 --- /dev/null +++ b/skill/bin/wq_project_list @@ -0,0 +1,8 @@ +#!/bin/bash +# wq_project_list — list all projects + +API_URL="${WORK_QUEUE_API_URL:-}" +[[ -z "$API_URL" ]] && API_URL=$(cat ~/.config/work_queue_api_url 2>/dev/null || echo "") +[[ -z "$API_URL" ]] && { echo "Error: WORK_QUEUE_API_URL not set" >&2; exit 1; } + +curl -sf "$API_URL/projects" | jq . diff --git a/skill/bin/wq_project_update b/skill/bin/wq_project_update new file mode 100755 index 0000000..489fb6c --- /dev/null +++ b/skill/bin/wq_project_update @@ -0,0 +1,30 @@ +#!/bin/bash +# wq_project_update — update a project (name or external_ref) +# Usage: wq project update [--name ] [--external-ref ] + +API_URL="${WORK_QUEUE_API_URL:-}" +[[ -z "$API_URL" ]] && API_URL=$(cat ~/.config/work_queue_api_url 2>/dev/null || echo "") +[[ -z "$API_URL" ]] && { echo "Error: WORK_QUEUE_API_URL not set" >&2; exit 1; } + +project_id="" name="" external_ref="" + +while [[ $# -gt 0 ]]; do + case "$1" in + --name) name="$2"; shift 2;; + --external-ref) external_ref="$2"; shift 2;; + --) shift; break;; + -*) echo "Unknown option: $1" >&2; exit 1;; + *) [[ -z "$project_id" ]] && project_id="$1"; shift;; + esac +done + +[[ -z "$project_id" ]] && { echo "Usage: wq project update [--name ] [--external-ref ]" >&2; exit 1; } +[[ -z "$name" ]] && [[ -z "$external_ref" ]] && { echo "Error: at least one of --name or --external-ref required" >&2; exit 1; } + +body="{}" +[[ -n "$name" ]] && body=$(echo "$body" | jq ".name=\"$name\"") +[[ -n "$external_ref" ]] && body=$(echo "$body" | jq ".external_ref=\"$external_ref\"") + +curl -sf -X PATCH "$API_URL/projects/$project_id" \ + -H "Content-Type: application/json" \ + -d "$body" | jq . diff --git a/skill/bin/wq_stale_check b/skill/bin/wq_stale_check index e4bd487..3dcbbd6 100755 --- a/skill/bin/wq_stale_check +++ b/skill/bin/wq_stale_check @@ -1,35 +1,33 @@ #!/bin/bash # wq_stale_check — find in_progress items older than timeout, mark blocked -wq_stale_check() { - local timeout_minutes="${1:-30}" +API_URL="${WORK_QUEUE_API_URL:-}" +[[ -z "$API_URL" ]] && API_URL=$(cat ~/.config/work_queue_api_url 2>/dev/null || echo "") +[[ -z "$API_URL" ]] && { echo "Error: WORK_QUEUE_API_URL not set" >&2; exit 1; } - local items - items=$(curl -sf "$API_URL/work?status=in_progress" | jq -r '.[] | @json' 2>/dev/null || echo "") +timeout_minutes="${1:-30}" - if [[ -z "$items" ]]; then - echo "No in_progress items found." - return 0 +items=$(curl -sf "$API_URL/work?status=in_progress" | jq -r '.[] | @json' 2>/dev/null || echo "") + +[[ -z "$items" ]] && { echo "No in_progress items found."; exit 0; } + +count=0 +now_ts=$(date -u +%s) + +while IFS= read -r item; do + [[ -z "$item" ]] && continue + work_id=$(echo "$item" | jq -r '.id') + updated_at=$(echo "$item" | jq -r '.updated_at') + agent=$(echo "$item" | jq -r '.assigned_agent') + age_minutes=$(( (now_ts - $(date -u -d "$updated_at" +%s 2>/dev/null || echo "$now_ts")) / 60 )) + + if [[ "$age_minutes" -gt "$timeout_minutes" ]]; then + echo "Stale: $work_id ($agent, ${age_minutes}m old) — marking blocked" + curl -sf -X PATCH "$API_URL/work/$work_id" \ + -H "Content-Type: application/json" \ + -d "{\"status\":\"blocked\",\"notes\":\"Auto-blocked: stale for ${age_minutes} minutes (>$timeout_minutes)\"}" > /dev/null + count=$((count + 1)) fi +done <<< "$items" - local count=0 - now_ts=$(date -u +%s) - - while IFS= read -r item; do - [[ -z "$item" ]] && continue - work_id=$(echo "$item" | jq -r '.id') - updated_at=$(echo "$item" | jq -r '.updated_at') - agent=$(echo "$item" | jq -r '.assigned_agent') - age_minutes=$(( (now_ts - $(date -u -d "$updated_at" +%s 2>/dev/null || echo "$now_ts")) / 60 )) - - if [[ "$age_minutes" -gt "$timeout_minutes" ]]; then - echo "Stale: $work_id ($agent, ${age_minutes}m old) — marking blocked" - curl -sf -X PATCH "$API_URL/work/$work_id" \ - -H "Content-Type: application/json" \ - -d "{\"status\":\"blocked\",\"notes\":\"Auto-blocked: stale for ${age_minutes} minutes (>$timeout_minutes)\"}" | jq -r '.id' > /dev/null - count=$((count + 1)) - fi - done <<< "$items" - - echo "Stale check complete: $count items marked blocked." -} +echo "Stale check complete: $count items marked blocked." diff --git a/skill/bin/wq_update b/skill/bin/wq_update index c4f5c3c..9508126 100755 --- a/skill/bin/wq_update +++ b/skill/bin/wq_update @@ -1,40 +1,39 @@ #!/bin/bash -# wq_update — update status, outcome, or notes +# wq_update — update status, outcome, notes, or assigned_agent on a work item +# Usage: wq update [options] +# Options: +# --status New status +# --outcome success, failed, cancelled +# --notes Agent notes / context +# --agent Re-assign to different agent -wq_update() { - local work_id status outcome notes +API_URL="${WORK_QUEUE_API_URL:-}" +[[ -z "$API_URL" ]] && API_URL=$(cat ~/.config/work_queue_api_url 2>/dev/null || echo "") +[[ -z "$API_URL" ]] && { echo "Error: WORK_QUEUE_API_URL not set" >&2; exit 1; } - while [[ $# -gt 0 ]]; do - case "$1" in - --status) status="$2"; shift 2;; - --outcome) outcome="$2"; shift 2;; - --notes) notes="$2"; shift 2;; - --) shift; break;; - -*) echo "Unknown option: $1" >&2; exit 1;; - *) - if [[ -z "$work_id" ]]; then - work_id="$1" - fi - shift;; - esac - done +work_id="" status="" outcome="" notes="" agent="" - if [[ -z "$work_id" ]]; then - echo "Usage: wq update [--status ] [--outcome ] [--notes ]" >&2 - exit 1 - fi +while [[ $# -gt 0 ]]; do + case "$1" in + --status) status="$2"; shift 2;; + --outcome) outcome="$2"; shift 2;; + --notes) notes="$2"; shift 2;; + --agent) agent="$2"; shift 2;; + --) shift; break;; + -*) echo "Unknown option: $1" >&2; exit 1;; + *) [[ -z "$work_id" ]] && work_id="$1"; shift;; + esac +done - if [[ -z "$status" ]] && [[ -z "$outcome" ]] && [[ -z "$notes" ]]; then - echo "Error: at least one of --status, --outcome, or --notes required" >&2 - exit 1 - fi +[[ -z "$work_id" ]] && { echo "Usage: wq update [--status ] [--outcome ] [--notes ] [--agent ]" >&2; exit 1; } +[[ -z "$status" ]] && [[ -z "$outcome" ]] && [[ -z "$notes" ]] && [[ -z "$agent" ]] && { echo "Error: at least one of --status, --outcome, --notes, or --agent required" >&2; exit 1; } - local body="{}" - [[ -n "$status" ]] && body=$(echo "$body" | jq ".status=\"$status\"") - [[ -n "$outcome" ]] && body=$(echo "$body" | jq ".outcome=\"$outcome\"") - [[ -n "$notes" ]] && body=$(echo "$body" | jq ".notes=\"$notes\"") +body="{}" +[[ -n "$status" ]] && body=$(echo "$body" | jq ".status=\"$status\"") +[[ -n "$outcome" ]] && body=$(echo "$body" | jq ".outcome=\"$outcome\"") +[[ -n "$notes" ]] && body=$(echo "$body" | jq ".notes=\"$notes\"") +[[ -n "$agent" ]] && body=$(echo "$body" | jq ".assigned_agent=\"$agent\"") - curl -sf -X PATCH "$API_URL/work/$work_id" \ - -H "Content-Type: application/json" \ - -d "$body" | jq . -} +curl -sf -X PATCH "$API_URL/work/$work_id" \ + -H "Content-Type: application/json" \ + -d "$body" | jq .