重开仓库内容:改推五个技能(browser-harness / humanizer / humanizer-zh / product-planning / session-mechanism)
按授权清空原有内容后重新提交(原 oil-ui-pro 一并移出,可从历史恢复)。 browser-harness 剔除 .venv 等运行环境;根 .gitignore 补记 .venv/ 与 node_modules/。
This commit is contained in:
1 parent
9a152a1952
commit
777f7fe5d0
240 files changed
+52518
-4409
No files matched your search
@@ -0,0 +1,68 @@
|
||||
# TaskSquad — Agents
|
||||
|
||||
Field-tested against tasksquad.ai on 2026-05-03 using a logged-in Chrome session.
|
||||
|
||||
## URL
|
||||
|
||||
```
|
||||
https://tasksquad.ai/dashboard/agents # Agents list for current project
|
||||
```
|
||||
|
||||
## Agent list
|
||||
|
||||
Agents are the daemon workers that execute tasks. Each agent card shows:
|
||||
- Name
|
||||
- Status badge (`active` / `inactive` / `running` / `waiting_input` / `paused`)
|
||||
- Role (optional label set by maintainer)
|
||||
- Last-seen timestamp
|
||||
|
||||
Navigate to agents via the sidebar link "Agents".
|
||||
|
||||
## Creating an agent
|
||||
|
||||
Maintainer/owner only. Click **"New agent"** (or similar CTA in the agents view).
|
||||
|
||||
Provide:
|
||||
- **Name** — display name for the agent
|
||||
- **Role** (optional) — a freeform string describing the agent's specialty
|
||||
|
||||
After creation, a **token** is generated. Copy it immediately — it is shown only once. The daemon uses this token to authenticate.
|
||||
|
||||
```
|
||||
tsq install --token <TOKEN>
|
||||
```
|
||||
|
||||
Tokens are created via `POST /teams/:teamId/tokens` with `{ label, agent_id }`.
|
||||
|
||||
## Agent statuses
|
||||
|
||||
| Status | Meaning |
|
||||
|-----------------|----------------------------------------------------|
|
||||
| `active` | Daemon connected, ready to accept tasks |
|
||||
| `inactive` | Daemon not connected (no recent ping) |
|
||||
| `running` | Currently executing a task |
|
||||
| `waiting_input` | Paused mid-task, waiting for user reply |
|
||||
| `paused` | Manually paused — will not pick up new tasks |
|
||||
|
||||
## Agent actions (maintainer / owner)
|
||||
|
||||
| Action | Trigger | Effect |
|
||||
|--------------|--------------------------|-----------------------------------------------------|
|
||||
| Pause/Resume | Toggle button on card | Sets `paused` flag — agent ignores new tasks |
|
||||
| Reset | "Reset" button | Clears the agent's running state (use if stuck) |
|
||||
| Delete | Delete button / menu | Permanently removes agent and its tokens |
|
||||
| Edit role | Role edit control | Updates the agent's role label |
|
||||
|
||||
## Token management
|
||||
|
||||
Tokens are per-agent credentials. Each token has a label. A maintainer can:
|
||||
- **Generate a new token** — use when setting up a new machine or rotating credentials
|
||||
- Tokens cannot be viewed after creation; only revoked implicitly by deleting the agent
|
||||
|
||||
## Gotchas
|
||||
|
||||
- **Status is driven by daemon pings**, not task state. An agent can be `active` while no task is running.
|
||||
- **`inactive` ≠ paused.** Inactive means the daemon process is offline. Paused means a maintainer explicitly suspended it via the UI.
|
||||
- **Only owners and maintainers can create agents or generate tokens.** Members see the list but cannot modify it.
|
||||
- **Token is shown once.** If you miss copying it, you must generate a new one — there is no "reveal token" button.
|
||||
- **Reset is for recovery only.** Use it when an agent is stuck in `running` after a daemon crash. Do not reset agents mid-task.
|
||||
@@ -0,0 +1,75 @@
|
||||
# TaskSquad — Authentication
|
||||
|
||||
Field-tested against tasksquad.ai on 2026-05-03 using a Chrome session.
|
||||
|
||||
## URLs
|
||||
|
||||
```
|
||||
https://tasksquad.ai/auth # login page (redirects to /dashboard if already signed in)
|
||||
https://tasksquad.ai/auth/cli # CLI token auth page
|
||||
```
|
||||
|
||||
## Login page
|
||||
|
||||
The login page is a centered card with two OAuth buttons. No email/password form exists — SSO only.
|
||||
|
||||
```python
|
||||
goto_url("https://tasksquad.ai/auth")
|
||||
wait_for_load()
|
||||
```
|
||||
|
||||
### Sign-in buttons
|
||||
|
||||
The card contains exactly two buttons:
|
||||
|
||||
| Visible label | Provider |
|
||||
|--------------------------|----------|
|
||||
| "Continue with Google" | Google |
|
||||
| "Continue with GitHub" | GitHub |
|
||||
|
||||
Both are `<button type="button">` inside a `<div class="px-6 pb-6 flex flex-col gap-3">`. Locate by label text and click coordinates returned from the rendered element:
|
||||
|
||||
```python
|
||||
import json
|
||||
|
||||
def click_button_by_text(label):
|
||||
"""Find a <button> by exact innerText and click its center."""
|
||||
sel = json.dumps(label)
|
||||
result = js(f"""
|
||||
var b = Array.from(document.querySelectorAll("button"))
|
||||
.find(e => (e.innerText || "").trim() === {sel});
|
||||
if (!b) return null;
|
||||
var r = b.getBoundingClientRect();
|
||||
return JSON.stringify({{x: Math.round(r.x + r.width/2), y: Math.round(r.y + r.height/2)}});
|
||||
""")
|
||||
if result is None:
|
||||
raise RuntimeError(f"button not found: {label!r} — has the React app finished hydrating?")
|
||||
pos = json.loads(result)
|
||||
click_at_xy(pos["x"], pos["y"])
|
||||
|
||||
# Google
|
||||
click_button_by_text("Continue with Google")
|
||||
|
||||
# GitHub
|
||||
click_button_by_text("Continue with GitHub")
|
||||
```
|
||||
|
||||
After clicking, a browser popup opens for the OAuth flow. **Stop and ask the user to complete sign-in** — do not attempt to interact with the OAuth popup window.
|
||||
|
||||
### Auth-wall pattern
|
||||
|
||||
Any route under `/dashboard/*` redirects to `/auth` when the user is not signed in. If you navigate to a dashboard URL and land on `/auth`, the session is unauthenticated — ask the user to sign in before proceeding.
|
||||
|
||||
### Redirect after sign-in
|
||||
|
||||
Firebase Auth resolves the session client-side. After the OAuth popup closes, React's `onAuthStateChanged` fires and automatically navigates the user to `/dashboard`. Do not click anything — just `wait_for_load()` and verify with `page_info()`.
|
||||
|
||||
## Sign-out
|
||||
|
||||
There is no dedicated sign-out page. The sign-out action is triggered from the sidebar in `/dashboard`. Look for a `LogOut` icon button in the bottom-left sidebar area.
|
||||
|
||||
## Gotchas
|
||||
|
||||
- **The page is a React SPA.** Navigating to `/auth` while the app is still hydrating returns the bare `<div id="root">` with no buttons yet. Always call `wait_for_load()` and verify the "Continue with Google" text is present before clicking.
|
||||
- **No error routes exist for failed OAuth.** On failure, the card renders an inline `<p class="text-sm text-red-500 text-center">` error message below the buttons.
|
||||
- **`authed === null` loading state.** While Firebase resolves the session on `/auth`, the `<Login>` component is not rendered (the route renders `null`). Wait for buttons to appear before clicking.
|
||||
@@ -0,0 +1,158 @@
|
||||
# TaskSquad — Task Inbox
|
||||
|
||||
Field-tested against tasksquad.ai on 2026-05-03 using a logged-in Chrome session.
|
||||
|
||||
## URL
|
||||
|
||||
```
|
||||
https://tasksquad.ai/dashboard # Inbox (default view)
|
||||
https://tasksquad.ai/dashboard/<taskId> # Task thread (individual task)
|
||||
```
|
||||
|
||||
## Inbox view
|
||||
|
||||
The inbox is the default view after sign-in. It shows a list of tasks assigned to agents in the currently selected project.
|
||||
|
||||
### Task list
|
||||
|
||||
Tasks are rendered as rows. Each row shows:
|
||||
- Subject line
|
||||
- Agent name
|
||||
- Status badge
|
||||
- Relative timestamp
|
||||
|
||||
### Status values
|
||||
|
||||
| Status | Meaning |
|
||||
|-----------------|------------------------------------------|
|
||||
| `pending` | Queued for the agent daemon |
|
||||
| `queued` | Agent is busy — task is waiting in line |
|
||||
| `running` | Agent is actively working |
|
||||
| `waiting_input` | Agent needs a reply from the user |
|
||||
| `done` | Completed successfully |
|
||||
| `failed` | Ended with an error |
|
||||
| `scheduled` | Will start at a future `scheduled_at` time |
|
||||
| `wrapping_up` | Running post-completion close steps |
|
||||
|
||||
### Filters
|
||||
|
||||
Two dropdowns appear above the task list:
|
||||
|
||||
**Status** (left): All / Pending / Queued / Running / Waiting / Done / Failed / Scheduled
|
||||
|
||||
**Origin** (right): All / System / Mine / From Note / Critique / Scheduled
|
||||
- "System" = tasks created by automated conveyors
|
||||
- "Mine" = tasks you composed yourself
|
||||
- "From Note" = tasks spawned from Notes
|
||||
- "Critique" = tasks that are note critiques
|
||||
|
||||
Both dropdowns use shadcn `<Select>`. Click the trigger to open, then click the item by text.
|
||||
|
||||
### Refresh
|
||||
|
||||
A `RefreshCw` icon button sits next to the "Inbox" heading. Click it to reload the task list without full navigation. Alternatively, `wait_for_load()` after any state change — the app auto-polls when active tasks exist (interval depends on plan; see Gotchas).
|
||||
|
||||
## Composing a new task
|
||||
|
||||
Click the **"New message"** button (top-right of Inbox). A dialog opens.
|
||||
|
||||
### Dialog fields
|
||||
|
||||
| Field | Type | Notes |
|
||||
|-------------|-------------|--------------------------------------------------------|
|
||||
| Agent | `<Select>` | Required. Dropdown of agents in the current project. |
|
||||
| Subject | `<input>` | Required. Short description of the task. |
|
||||
| Message | `<textarea>`| Optional. Task body / detailed instructions. |
|
||||
|
||||
Optional advanced toggles (revealed via UI controls):
|
||||
- **Schedule**: set a future delivery time
|
||||
- **Auto-close**: task closes automatically when the agent finishes
|
||||
- **Save tokens**: compress the task context (lite / full / ultra)
|
||||
- **Close steps**: newline-separated post-completion steps
|
||||
|
||||
Submit with the **"Send"** button inside the dialog.
|
||||
|
||||
```python
|
||||
import json
|
||||
|
||||
def _coords_by_text(label, tag="button"):
|
||||
"""Find an element by exact innerText and return click coords, or raise."""
|
||||
sel = json.dumps(tag)
|
||||
txt = json.dumps(label)
|
||||
result = js(f"""
|
||||
var el = Array.from(document.querySelectorAll({sel}))
|
||||
.find(e => (e.innerText || "").trim() === {txt});
|
||||
if (!el) return null;
|
||||
var r = el.getBoundingClientRect();
|
||||
return JSON.stringify({{x: Math.round(r.x + r.width/2), y: Math.round(r.y + r.height/2)}});
|
||||
""")
|
||||
if result is None:
|
||||
raise RuntimeError(f"{tag!r} with text {label!r} not found")
|
||||
p = json.loads(result)
|
||||
return p["x"], p["y"]
|
||||
|
||||
def _coords_by_selector(selector):
|
||||
"""Find an element by CSS selector and return click coords, or raise."""
|
||||
sel = json.dumps(selector)
|
||||
result = js(f"""
|
||||
var el = document.querySelector({sel});
|
||||
if (!el) return null;
|
||||
var r = el.getBoundingClientRect();
|
||||
return JSON.stringify({{x: Math.round(r.x + r.width/2), y: Math.round(r.y + r.height/2)}});
|
||||
""")
|
||||
if result is None:
|
||||
raise RuntimeError(f"selector not found: {selector!r}")
|
||||
p = json.loads(result)
|
||||
return p["x"], p["y"]
|
||||
|
||||
# Open the compose dialog
|
||||
click_at_xy(*_coords_by_text("New message"))
|
||||
wait(1.0) # let the dialog mount; agents may still be loading
|
||||
|
||||
# Open the agent <Select> trigger and pick an agent.
|
||||
# The trigger renders the placeholder "Select agent…" until an agent is chosen.
|
||||
click_at_xy(*_coords_by_text("Select agent…"))
|
||||
wait(0.5)
|
||||
# Then click the agent name from the opened dropdown:
|
||||
click_at_xy(*_coords_by_text("YOUR_AGENT_NAME"))
|
||||
|
||||
# Focus the subject input, then type
|
||||
click_at_xy(*_coords_by_selector('input[id="subject"]'))
|
||||
type_text("Check the build status")
|
||||
|
||||
# (Optional) focus the message textarea and type the body
|
||||
# click_at_xy(*_coords_by_selector('textarea'))
|
||||
# type_text("Detailed instructions go here.")
|
||||
|
||||
# Submit
|
||||
click_at_xy(*_coords_by_text("Send"))
|
||||
```
|
||||
|
||||
## Task thread
|
||||
|
||||
Clicking a task row opens the thread at `/dashboard/<taskId>`.
|
||||
|
||||
The thread is an email-style conversation. Messages alternate between:
|
||||
- **User** messages (right-aligned or labeled with sender name)
|
||||
- **Agent** messages (left-aligned)
|
||||
|
||||
### Replying
|
||||
|
||||
When a task is in `waiting_input` status, a reply box appears at the bottom. Type your reply and press Enter or click the send button.
|
||||
|
||||
A **scheduled reply** pending delivery blocks the reply box — a cancel option appears.
|
||||
|
||||
### Task actions
|
||||
|
||||
Actions available from the thread view (usually via icon buttons or a menu):
|
||||
- **Close** — mark the task done manually
|
||||
- **Delete** — permanently remove the task
|
||||
- **Forward** — reassign to a different agent (appears as "Forward to agent" with agent selector and optional instructions)
|
||||
|
||||
## Gotchas
|
||||
|
||||
- **Inbox auto-polls while active tasks exist** (`pending` / `running` / `waiting_input`). Free plan polls every 5 s, Pro every 2 s. For scraping, add a `wait_for_load()` between reads if statuses are changing.
|
||||
- **`queued` is a derived client-side status** — the server returns `pending`, but the UI shows `queued` when the target agent is itself `running` or `waiting_input`. Filter accordingly.
|
||||
- **Task list is per-project.** Switching projects (teams) reloads the list. The active project is stored in `localStorage` as `tsq_team_id`.
|
||||
- **Dialog does not open until agents are loaded.** If "Select agent…" placeholder is missing after clicking "New message", the agents list is still fetching — retry after a short wait.
|
||||
- **Free plan project limit**: free accounts have a project limit (currently 1).
|
||||
Reference in new issue
Block a user