taskboy 0.1.1__py3-none-any.whl
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.
- taskboy/__init__.py +0 -0
- taskboy/adapters/__init__.py +0 -0
- taskboy/adapters/_util.py +30 -0
- taskboy/adapters/aws_read.py +107 -0
- taskboy/adapters/confluence.py +88 -0
- taskboy/adapters/github_api.py +564 -0
- taskboy/adapters/issues.py +435 -0
- taskboy/adapters/jira.py +263 -0
- taskboy/adapters/sentry.py +82 -0
- taskboy/adapters/slack_history.py +204 -0
- taskboy/assets.py +27 -0
- taskboy/audit.py +78 -0
- taskboy/broker.py +266 -0
- taskboy/classifier.py +219 -0
- taskboy/cli.py +161 -0
- taskboy/config.py +539 -0
- taskboy/dashboard/__init__.py +5 -0
- taskboy/dashboard/api.py +963 -0
- taskboy/dashboard/app.py +78 -0
- taskboy/dashboard/auth.py +117 -0
- taskboy/dashboard/editors.py +140 -0
- taskboy/dashboard/gitops.py +60 -0
- taskboy/dashboard/render.py +48 -0
- taskboy/debug_feed.py +170 -0
- taskboy/deploy/env.example +17 -0
- taskboy/deploy/git-cred-helper.py +36 -0
- taskboy/deploy/install.sh +101 -0
- taskboy/deploy/remote-update.sh +52 -0
- taskboy/deploy/taskboy-restart.path +10 -0
- taskboy/deploy/taskboy-restart.service +8 -0
- taskboy/deploy/taskboy.service +22 -0
- taskboy/hooks.py +208 -0
- taskboy/issue_runs.py +203 -0
- taskboy/llm.py +125 -0
- taskboy/main.py +269 -0
- taskboy/memory.py +59 -0
- taskboy/models.py +103 -0
- taskboy/mrkdwn.py +19 -0
- taskboy/notify.py +41 -0
- taskboy/orchestrator.py +401 -0
- taskboy/personality.py +17 -0
- taskboy/prompts.py +288 -0
- taskboy/quick.py +234 -0
- taskboy/redact.py +49 -0
- taskboy/repocache.py +117 -0
- taskboy/review_requests.py +388 -0
- taskboy/router.py +146 -0
- taskboy/runner.py +581 -0
- taskboy/scheduler.py +301 -0
- taskboy/secrets.py +83 -0
- taskboy/settings.py +19 -0
- taskboy/setup_checks.py +184 -0
- taskboy/setup_wizard.py +699 -0
- taskboy/skills.py +95 -0
- taskboy/slack.py +666 -0
- taskboy/slack_users.py +32 -0
- taskboy/started_messages.py +33 -0
- taskboy/store.py +1393 -0
- taskboy/task_actions.py +86 -0
- taskboy/templates/config.example.yaml +149 -0
- taskboy/templates/conventions.md +60 -0
- taskboy/templates/help.example.md +29 -0
- taskboy/templates/personality_agent.example.md +5 -0
- taskboy/templates/personality_reviewer.example.md +5 -0
- taskboy/templates/services/aws.yaml +7 -0
- taskboy/templates/services/confluence.yaml +4 -0
- taskboy/templates/services/github.yaml +14 -0
- taskboy/templates/services/jira.yaml +6 -0
- taskboy/templates/services/sentry.yaml +4 -0
- taskboy/templates/services/slack.yaml +9 -0
- taskboy/templates/skills/README.md +33 -0
- taskboy/templates/skills/discoverissues/SKILL.md +46 -0
- taskboy/templates/skills/implementapprovedissues/SKILL.md +41 -0
- taskboy/templates/skills/jira2pr/SKILL.md +66 -0
- taskboy/templates/skills/monitor/SKILL.md +32 -0
- taskboy/templates/skills/monitornew/SKILL.md +27 -0
- taskboy/templates/skills/refineissue/SKILL.md +35 -0
- taskboy/templates/skills/release/SKILL.md +36 -0
- taskboy/templates/skills/review/SKILL.md +89 -0
- taskboy/templates/skills/reviewandmonitor/SKILL.md +22 -0
- taskboy/templates/skills/reviews/SKILL.md +26 -0
- taskboy/templates/skills/slack2jira/SKILL.md +47 -0
- taskboy/templates/skills/slack2pr/SKILL.md +64 -0
- taskboy/templates/skills/spec2pr/SKILL.md +35 -0
- taskboy/templates/slack_app_manifest.yaml +48 -0
- taskboy/templates/task_started_messages.yaml +14 -0
- taskboy/ui_dist/assets/index-DmYxR9Qy.css +1 -0
- taskboy/ui_dist/assets/index-LDDO4iT2.js +15 -0
- taskboy/ui_dist/index.html +15 -0
- taskboy/workspace.py +86 -0
- taskboy-0.1.1.dist-info/METADATA +120 -0
- taskboy-0.1.1.dist-info/RECORD +95 -0
- taskboy-0.1.1.dist-info/WHEEL +5 -0
- taskboy-0.1.1.dist-info/entry_points.txt +2 -0
- taskboy-0.1.1.dist-info/top_level.txt +1 -0
taskboy/cli.py
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
"""operator cli: inject tasks without slack (testing), inspect, cancel, pause intake."""
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import asyncio
|
|
5
|
+
import time
|
|
6
|
+
|
|
7
|
+
from taskboy import settings
|
|
8
|
+
from taskboy.config import load_config
|
|
9
|
+
from taskboy.models import CANCELLED, TERMINAL_STATES, utcnow
|
|
10
|
+
from taskboy.notify import StdoutNotifier
|
|
11
|
+
from taskboy.orchestrator import accept_task
|
|
12
|
+
from taskboy.store import Store, TransitionRaced
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def main() -> None:
|
|
16
|
+
parser = argparse.ArgumentParser(prog="taskboy")
|
|
17
|
+
sub = parser.add_subparsers(dest="command", required=True)
|
|
18
|
+
|
|
19
|
+
inject = sub.add_parser("inject", help="create a task without slack, through the identical intake path")
|
|
20
|
+
inject.add_argument("text")
|
|
21
|
+
inject.add_argument("--user", default="cli")
|
|
22
|
+
inject.add_argument("--channel", default="cli")
|
|
23
|
+
inject.add_argument("--model", default=None, help="model alias override (recorded and audited, MOD-008)")
|
|
24
|
+
inject.add_argument("--watch", action="store_true", help="poll until the task reaches a terminal state")
|
|
25
|
+
|
|
26
|
+
sub.add_parser("list", help="show recent tasks")
|
|
27
|
+
|
|
28
|
+
cancel = sub.add_parser("cancel", help="cancel a task")
|
|
29
|
+
cancel.add_argument("task_id")
|
|
30
|
+
|
|
31
|
+
permissions = sub.add_parser("permissions", help="list a task's permission requests")
|
|
32
|
+
permissions.add_argument("task_id")
|
|
33
|
+
|
|
34
|
+
for verb in ("grant", "deny"):
|
|
35
|
+
decide = sub.add_parser(verb, help=f"{verb} a sub-agent's permission request (granting a blocked task resumes it)")
|
|
36
|
+
decide.add_argument("task_id")
|
|
37
|
+
decide.add_argument("kind", choices=["tool", "repo"])
|
|
38
|
+
decide.add_argument("target", help="the tool name or owner/name repository")
|
|
39
|
+
|
|
40
|
+
sub.add_parser("pause-intake", help="refuse new tasks; running tasks continue (REL-009)")
|
|
41
|
+
sub.add_parser("resume-intake", help="accept new tasks again")
|
|
42
|
+
sub.add_parser("run", help="run the service in the foreground")
|
|
43
|
+
|
|
44
|
+
extract = sub.add_parser("assets", help="copy a packaged asset tree out of the installed package")
|
|
45
|
+
extract.add_argument("name", choices=["templates", "deploy"], help="templates = config example, per-service configs, personalities, skill templates, slack manifest; deploy = systemd units, env example, git credential helper")
|
|
46
|
+
extract.add_argument("dest", nargs="?", default=".", help="directory to copy into (default: current directory)")
|
|
47
|
+
|
|
48
|
+
from taskboy import setup_wizard
|
|
49
|
+
|
|
50
|
+
setup_wizard.add_parser(sub)
|
|
51
|
+
|
|
52
|
+
args = parser.parse_args()
|
|
53
|
+
if args.command == "run":
|
|
54
|
+
from taskboy.main import main as run_main
|
|
55
|
+
|
|
56
|
+
run_main()
|
|
57
|
+
return
|
|
58
|
+
if args.command == "setup":
|
|
59
|
+
raise SystemExit(setup_wizard.run(args))
|
|
60
|
+
if args.command == "assets":
|
|
61
|
+
from taskboy import assets
|
|
62
|
+
|
|
63
|
+
print(f"copied packaged {args.name} to {assets.extract(args.name, args.dest)}")
|
|
64
|
+
return
|
|
65
|
+
|
|
66
|
+
store = Store(settings.DB_PATH)
|
|
67
|
+
try:
|
|
68
|
+
if args.command == "inject":
|
|
69
|
+
asyncio.run(_inject(store, args))
|
|
70
|
+
elif args.command == "list":
|
|
71
|
+
_list(store)
|
|
72
|
+
elif args.command == "cancel":
|
|
73
|
+
_cancel(store, args.task_id)
|
|
74
|
+
elif args.command == "permissions":
|
|
75
|
+
_permissions(store, args.task_id)
|
|
76
|
+
elif args.command in ("grant", "deny"):
|
|
77
|
+
asyncio.run(_decide_permission(store, args.command, args.task_id, args.kind, args.target))
|
|
78
|
+
elif args.command == "pause-intake":
|
|
79
|
+
store.meta_set("intake_paused", "1")
|
|
80
|
+
print("intake paused")
|
|
81
|
+
elif args.command == "resume-intake":
|
|
82
|
+
store.meta_set("intake_paused", "0")
|
|
83
|
+
print("intake resumed")
|
|
84
|
+
finally:
|
|
85
|
+
store.close()
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
async def _inject(store: Store, args) -> None:
|
|
89
|
+
config = load_config(settings.CONFIG_PATH)
|
|
90
|
+
message_ts = str(time.time_ns()) # unique per injection, so every inject is a new task
|
|
91
|
+
task, status = await accept_task(
|
|
92
|
+
store,
|
|
93
|
+
config,
|
|
94
|
+
StdoutNotifier(),
|
|
95
|
+
team_id="cli",
|
|
96
|
+
channel_id=args.channel,
|
|
97
|
+
thread_ts=message_ts,
|
|
98
|
+
message_ts=message_ts,
|
|
99
|
+
user_id=args.user,
|
|
100
|
+
text=args.text,
|
|
101
|
+
model_override=args.model,
|
|
102
|
+
)
|
|
103
|
+
print(f"status: {status}" + (f", task: {task.task_id}" if task else ""))
|
|
104
|
+
if not args.watch or task is None:
|
|
105
|
+
return
|
|
106
|
+
last_state = task.state
|
|
107
|
+
while last_state not in TERMINAL_STATES:
|
|
108
|
+
await asyncio.sleep(0.5)
|
|
109
|
+
current = store.get_task(task.task_id)
|
|
110
|
+
assert current is not None
|
|
111
|
+
if current.state != last_state:
|
|
112
|
+
last_state = current.state
|
|
113
|
+
print(f"[{task.task_id}] -> {last_state}")
|
|
114
|
+
final = store.get_task(task.task_id)
|
|
115
|
+
assert final is not None
|
|
116
|
+
print(final.result_summary or final.error or final.blocked_reason or "")
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _list(store: Store) -> None:
|
|
120
|
+
for task in store.recent_tasks(limit=20):
|
|
121
|
+
print(f"{task.task_id} {task.state:9} {task.created_at} {task.request_text[:70]}")
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def _permissions(store: Store, task_id: str) -> None:
|
|
125
|
+
if store.get_task(task_id) is None:
|
|
126
|
+
print(f"no such task: {task_id}")
|
|
127
|
+
return
|
|
128
|
+
rows = store.permission_requests_for(task_id)
|
|
129
|
+
if not rows:
|
|
130
|
+
print("no permission requests")
|
|
131
|
+
return
|
|
132
|
+
for row in rows:
|
|
133
|
+
decided = f" by {row['decided_by']}" if row["decided_by"] else ""
|
|
134
|
+
print(f"{row['status']:8} {row['kind']:4} {row['target']}{decided} ({row['reason'][:80]})")
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
async def _decide_permission(store: Store, decision_verb: str, task_id: str, kind: str, target: str) -> None:
|
|
138
|
+
from taskboy.task_actions import decide_permission
|
|
139
|
+
|
|
140
|
+
decision = "granted" if decision_verb == "grant" else "denied"
|
|
141
|
+
task, status = await decide_permission(store, StdoutNotifier(), task_id, kind, target, decision, "cli")
|
|
142
|
+
print(f"{task_id}: {status}" + (f" (now {task.state})" if task else ""))
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def _cancel(store: Store, task_id: str) -> None:
|
|
146
|
+
task = store.get_task(task_id)
|
|
147
|
+
if task is None:
|
|
148
|
+
print(f"no such task: {task_id}")
|
|
149
|
+
return
|
|
150
|
+
if task.state in TERMINAL_STATES:
|
|
151
|
+
print(f"{task_id} is already {task.state}")
|
|
152
|
+
return
|
|
153
|
+
try:
|
|
154
|
+
store.transition(task_id, task.state, CANCELLED, "cancelled via cli", finished_at=utcnow())
|
|
155
|
+
print(f"{task_id} cancelled" + (" (the running session will stop shortly)" if task.state == "running" else ""))
|
|
156
|
+
except TransitionRaced:
|
|
157
|
+
print(f"{task_id} changed state while cancelling — check `taskboy list` and retry")
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
if __name__ == "__main__":
|
|
161
|
+
main()
|