aicortexpy 0.1.0__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.
- aicortexpy/__init__.py +3 -0
- aicortexpy/__main__.py +5 -0
- aicortexpy/acp_client.py +559 -0
- aicortexpy/acp_conn.py +853 -0
- aicortexpy/acp_errors.py +107 -0
- aicortexpy/agent_env.py +88 -0
- aicortexpy/artifact_ws.py +233 -0
- aicortexpy/artifacts.py +67 -0
- aicortexpy/attachments.py +85 -0
- aicortexpy/auto_commit.py +106 -0
- aicortexpy/capabilities.py +92 -0
- aicortexpy/chat_acp_pool.py +321 -0
- aicortexpy/cli.py +133 -0
- aicortexpy/client.py +438 -0
- aicortexpy/codex_config.py +98 -0
- aicortexpy/config.py +298 -0
- aicortexpy/daemon.py +677 -0
- aicortexpy/data/default_skills/agent-memory/SKILL.md +67 -0
- aicortexpy/data/default_skills/aicorkit-atlassian/SKILL.md +144 -0
- aicortexpy/data/default_skills/aicorkit-graph/SKILL.md +145 -0
- aicortexpy/data/default_skills/aicorkit-outlook/SKILL.md +87 -0
- aicortexpy/data/default_skills/aicorkit-wxmsg/SKILL.md +96 -0
- aicortexpy/data/default_skills/aicortex-basics/SKILL.md +10 -0
- aicortexpy/data/default_skills/aicortex-bridge-cdp/SKILL.md +118 -0
- aicortexpy/data/default_skills/aicortex-cli-ops/SKILL.md +100 -0
- aicortexpy/data/default_skills/aicortex-graph/SKILL.md +30 -0
- aicortexpy/data/default_skills/graphify/SKILL.md +97 -0
- aicortexpy/data/default_skills/question-form/SKILL.md +162 -0
- aicortexpy/data/default_skills/test-report-generator/SKILL.md +179 -0
- aicortexpy/data/default_skills/test-report-generator/scripts/generate_cases.py +146 -0
- aicortexpy/data/default_skills/test-report-generator/scripts/generate_report.py +275 -0
- aicortexpy/design_seed.py +59 -0
- aicortexpy/diskusage.py +209 -0
- aicortexpy/execenv.py +264 -0
- aicortexpy/gc.py +286 -0
- aicortexpy/handoff_bundle.py +65 -0
- aicortexpy/health.py +178 -0
- aicortexpy/local_skills.py +157 -0
- aicortexpy/models.py +385 -0
- aicortexpy/openclaw_config.py +153 -0
- aicortexpy/pinned_workdir.py +60 -0
- aicortexpy/platform_info.py +61 -0
- aicortexpy/poisoned.py +44 -0
- aicortexpy/process.py +168 -0
- aicortexpy/procutil.py +61 -0
- aicortexpy/prompt.py +235 -0
- aicortexpy/repo.py +462 -0
- aicortexpy/runner.py +969 -0
- aicortexpy/runtime_config.py +540 -0
- aicortexpy/sandbox.py +264 -0
- aicortexpy/stream.py +422 -0
- aicortexpy/terminal.py +147 -0
- aicortexpy/tunnel.py +82 -0
- aicortexpy/wakeup.py +222 -0
- aicortexpy-0.1.0.dist-info/METADATA +106 -0
- aicortexpy-0.1.0.dist-info/RECORD +58 -0
- aicortexpy-0.1.0.dist-info/WHEEL +5 -0
- aicortexpy-0.1.0.dist-info/top_level.txt +1 -0
aicortexpy/process.py
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
"""Background process helpers for Windows / POSIX."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import subprocess
|
|
7
|
+
import sys
|
|
8
|
+
import time
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
from .config import Config
|
|
12
|
+
from .health import fetch_health, request_shutdown
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def pid_path(cfg: Config) -> Path:
|
|
16
|
+
return cfg.state_dir / "python-daemon.pid"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def log_path(cfg: Config) -> Path:
|
|
20
|
+
return cfg.state_dir / "python-daemon.log"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def write_pid(cfg: Config, pid: int) -> None:
|
|
24
|
+
cfg.state_dir.mkdir(parents=True, exist_ok=True)
|
|
25
|
+
pid_path(cfg).write_text(str(pid), encoding="utf-8")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def read_pid(cfg: Config) -> int | None:
|
|
29
|
+
p = pid_path(cfg)
|
|
30
|
+
if not p.is_file():
|
|
31
|
+
return None
|
|
32
|
+
try:
|
|
33
|
+
return int(p.read_text(encoding="utf-8").strip())
|
|
34
|
+
except ValueError:
|
|
35
|
+
return None
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def clear_pid(cfg: Config) -> None:
|
|
39
|
+
try:
|
|
40
|
+
pid_path(cfg).unlink(missing_ok=True)
|
|
41
|
+
except OSError:
|
|
42
|
+
pass
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def pid_alive(pid: int) -> bool:
|
|
46
|
+
if pid <= 0:
|
|
47
|
+
return False
|
|
48
|
+
if sys.platform == "win32":
|
|
49
|
+
import ctypes
|
|
50
|
+
|
|
51
|
+
PROCESS_QUERY_LIMITED_INFORMATION = 0x1000
|
|
52
|
+
handle = ctypes.windll.kernel32.OpenProcess(
|
|
53
|
+
PROCESS_QUERY_LIMITED_INFORMATION, False, pid
|
|
54
|
+
)
|
|
55
|
+
if not handle:
|
|
56
|
+
return False
|
|
57
|
+
ctypes.windll.kernel32.CloseHandle(handle)
|
|
58
|
+
return True
|
|
59
|
+
try:
|
|
60
|
+
os.kill(pid, 0)
|
|
61
|
+
return True
|
|
62
|
+
except OSError:
|
|
63
|
+
return False
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def spawn_background(cfg: Config, extra_args: list[str] | None = None) -> int:
|
|
67
|
+
"""Start `python -m aicortexpy run` detached; return child PID."""
|
|
68
|
+
cfg.state_dir.mkdir(parents=True, exist_ok=True)
|
|
69
|
+
log_file = log_path(cfg)
|
|
70
|
+
args = [
|
|
71
|
+
sys.executable,
|
|
72
|
+
"-m",
|
|
73
|
+
"aicortexpy",
|
|
74
|
+
"run",
|
|
75
|
+
]
|
|
76
|
+
if cfg.profile:
|
|
77
|
+
args.extend(["--profile", cfg.profile])
|
|
78
|
+
if extra_args:
|
|
79
|
+
args.extend(extra_args)
|
|
80
|
+
|
|
81
|
+
log_fh = open(log_file, "a", encoding="utf-8")
|
|
82
|
+
popen_kwargs: dict = {
|
|
83
|
+
"stdin": subprocess.DEVNULL,
|
|
84
|
+
"stdout": log_fh,
|
|
85
|
+
"stderr": subprocess.STDOUT,
|
|
86
|
+
"cwd": str(Path.cwd()),
|
|
87
|
+
"close_fds": True,
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if sys.platform == "win32":
|
|
91
|
+
# DETACHED_PROCESS | CREATE_NEW_PROCESS_GROUP | CREATE_BREAKAWAY_FROM_JOB
|
|
92
|
+
CREATE_BREAKAWAY_FROM_JOB = 0x01000000
|
|
93
|
+
flags = (
|
|
94
|
+
subprocess.DETACHED_PROCESS
|
|
95
|
+
| subprocess.CREATE_NEW_PROCESS_GROUP
|
|
96
|
+
| CREATE_BREAKAWAY_FROM_JOB
|
|
97
|
+
)
|
|
98
|
+
try:
|
|
99
|
+
proc = subprocess.Popen(args, creationflags=flags, **popen_kwargs)
|
|
100
|
+
except OSError:
|
|
101
|
+
# Parent job may disallow breakaway — fall back.
|
|
102
|
+
flags = subprocess.DETACHED_PROCESS | subprocess.CREATE_NEW_PROCESS_GROUP
|
|
103
|
+
proc = subprocess.Popen(args, creationflags=flags, **popen_kwargs)
|
|
104
|
+
else:
|
|
105
|
+
popen_kwargs["start_new_session"] = True
|
|
106
|
+
proc = subprocess.Popen(args, **popen_kwargs)
|
|
107
|
+
|
|
108
|
+
write_pid(cfg, proc.pid)
|
|
109
|
+
# Give health server a moment to bind
|
|
110
|
+
for _ in range(20):
|
|
111
|
+
time.sleep(0.25)
|
|
112
|
+
if fetch_health(cfg.health_port):
|
|
113
|
+
break
|
|
114
|
+
return proc.pid
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def stop_daemon(cfg: Config) -> str:
|
|
118
|
+
health = fetch_health(cfg.health_port)
|
|
119
|
+
if health:
|
|
120
|
+
if request_shutdown(cfg.health_port):
|
|
121
|
+
for _ in range(40):
|
|
122
|
+
time.sleep(0.25)
|
|
123
|
+
if not fetch_health(cfg.health_port):
|
|
124
|
+
clear_pid(cfg)
|
|
125
|
+
return "stopped via /shutdown"
|
|
126
|
+
return "shutdown requested but process still responding"
|
|
127
|
+
|
|
128
|
+
pid = read_pid(cfg)
|
|
129
|
+
if pid and pid_alive(pid):
|
|
130
|
+
if sys.platform == "win32":
|
|
131
|
+
subprocess.run(
|
|
132
|
+
["taskkill", "/PID", str(pid), "/T", "/F"],
|
|
133
|
+
capture_output=True,
|
|
134
|
+
check=False,
|
|
135
|
+
)
|
|
136
|
+
else:
|
|
137
|
+
os.kill(pid, 15)
|
|
138
|
+
time.sleep(1)
|
|
139
|
+
if pid_alive(pid):
|
|
140
|
+
os.kill(pid, 9)
|
|
141
|
+
clear_pid(cfg)
|
|
142
|
+
return f"killed pid {pid}"
|
|
143
|
+
|
|
144
|
+
clear_pid(cfg)
|
|
145
|
+
return "daemon not running"
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def status_text(cfg: Config) -> str:
|
|
149
|
+
health = fetch_health(cfg.health_port)
|
|
150
|
+
if health:
|
|
151
|
+
lines = [
|
|
152
|
+
"status: running",
|
|
153
|
+
f"pid: {health.get('pid')}",
|
|
154
|
+
f"uptime: {health.get('uptime')}",
|
|
155
|
+
f"daemon_id: {health.get('daemon_id')}",
|
|
156
|
+
f"server: {health.get('server_url')}",
|
|
157
|
+
f"version: {health.get('cli_version')}",
|
|
158
|
+
f"active_tasks: {health.get('active_task_count')}",
|
|
159
|
+
f"agents: {', '.join(health.get('agents') or [])}",
|
|
160
|
+
f"workspaces: {len(health.get('workspaces') or [])}",
|
|
161
|
+
f"health: http://127.0.0.1:{cfg.health_port}/health",
|
|
162
|
+
]
|
|
163
|
+
return "\n".join(lines)
|
|
164
|
+
|
|
165
|
+
pid = read_pid(cfg)
|
|
166
|
+
if pid and pid_alive(pid):
|
|
167
|
+
return f"status: pid {pid} alive but health port {cfg.health_port} not responding"
|
|
168
|
+
return "status: not running"
|
aicortexpy/procutil.py
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""Cross-platform process-tree helpers."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
import os
|
|
7
|
+
import signal
|
|
8
|
+
import subprocess
|
|
9
|
+
import sys
|
|
10
|
+
import time
|
|
11
|
+
|
|
12
|
+
log = logging.getLogger("aicortexpy.procutil")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def popen_kwargs_for_killable_group() -> dict:
|
|
16
|
+
"""Flags so the child (and its tree) can be killed as a unit."""
|
|
17
|
+
if sys.platform == "win32":
|
|
18
|
+
return {
|
|
19
|
+
"creationflags": subprocess.CREATE_NEW_PROCESS_GROUP
|
|
20
|
+
| getattr(subprocess, "CREATE_NO_WINDOW", 0),
|
|
21
|
+
}
|
|
22
|
+
return {"start_new_session": True}
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def kill_process_tree(pid: int, *, graceful_seconds: float = 2.0) -> None:
|
|
26
|
+
if pid <= 0:
|
|
27
|
+
return
|
|
28
|
+
if sys.platform == "win32":
|
|
29
|
+
subprocess.run(
|
|
30
|
+
["taskkill", "/PID", str(pid), "/T", "/F"],
|
|
31
|
+
capture_output=True,
|
|
32
|
+
check=False,
|
|
33
|
+
)
|
|
34
|
+
return
|
|
35
|
+
|
|
36
|
+
try:
|
|
37
|
+
os.killpg(pid, signal.SIGTERM)
|
|
38
|
+
except ProcessLookupError:
|
|
39
|
+
return
|
|
40
|
+
except PermissionError:
|
|
41
|
+
try:
|
|
42
|
+
os.kill(pid, signal.SIGTERM)
|
|
43
|
+
except ProcessLookupError:
|
|
44
|
+
return
|
|
45
|
+
|
|
46
|
+
deadline = time.time() + graceful_seconds
|
|
47
|
+
while time.time() < deadline:
|
|
48
|
+
try:
|
|
49
|
+
os.kill(pid, 0)
|
|
50
|
+
except ProcessLookupError:
|
|
51
|
+
return
|
|
52
|
+
time.sleep(0.1)
|
|
53
|
+
|
|
54
|
+
try:
|
|
55
|
+
os.killpg(pid, signal.SIGKILL)
|
|
56
|
+
except (ProcessLookupError, PermissionError):
|
|
57
|
+
try:
|
|
58
|
+
os.kill(pid, signal.SIGKILL)
|
|
59
|
+
except ProcessLookupError:
|
|
60
|
+
pass
|
|
61
|
+
log.info("killed process tree pid=%s", pid)
|
aicortexpy/prompt.py
ADDED
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
"""Task prompt builders (ported from Go daemon prompt.go)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from .client import Task
|
|
6
|
+
from .runtime_config import build_comment_reply_instructions
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def build_prompt(task: Task, provider: str = "") -> str:
|
|
10
|
+
if task.design_mode:
|
|
11
|
+
return build_design_prompt(task)
|
|
12
|
+
if task.chat_session_id:
|
|
13
|
+
return build_chat_prompt(task)
|
|
14
|
+
if task.trigger_comment_id or task.trigger_comment_content:
|
|
15
|
+
return build_comment_prompt(task, provider)
|
|
16
|
+
if task.autopilot_run_id:
|
|
17
|
+
return build_autopilot_prompt(task)
|
|
18
|
+
if task.quick_create_prompt:
|
|
19
|
+
return build_quick_create_prompt(task)
|
|
20
|
+
return build_issue_prompt(task)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def build_issue_prompt(task: Task) -> str:
|
|
24
|
+
parts = [
|
|
25
|
+
"You are running as a local coding agent for a AICortex workspace.\n\n",
|
|
26
|
+
f"Your assigned issue ID is: {task.issue_id}\n\n",
|
|
27
|
+
]
|
|
28
|
+
if task.project_title or task.project_prompt:
|
|
29
|
+
if task.project_title:
|
|
30
|
+
parts.append(f"Project: {task.project_title}\n")
|
|
31
|
+
if task.project_prompt:
|
|
32
|
+
parts.append(f"Project instructions:\n{task.project_prompt}\n\n")
|
|
33
|
+
parts.append(
|
|
34
|
+
f"Start by running `aicortex issue get {task.issue_id} --output json` "
|
|
35
|
+
"to understand your task, then complete it.\n"
|
|
36
|
+
f"If you need comment history, `aicortex issue comment list {task.issue_id} "
|
|
37
|
+
"--output json` returns all comments for the issue (server caps at 2000). "
|
|
38
|
+
"Pass `--since <RFC3339>` to fetch only comments newer than a known cursor.\n"
|
|
39
|
+
)
|
|
40
|
+
return "".join(parts)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def build_comment_prompt(task: Task, provider: str) -> str:
|
|
44
|
+
parts = [
|
|
45
|
+
"You are running as a local coding agent for a AICortex workspace.\n\n",
|
|
46
|
+
f"Your assigned issue ID is: {task.issue_id}\n\n",
|
|
47
|
+
]
|
|
48
|
+
if task.trigger_comment_content:
|
|
49
|
+
author = "A user"
|
|
50
|
+
if task.trigger_author_type == "agent":
|
|
51
|
+
name = task.trigger_author_name or "another agent"
|
|
52
|
+
author = f"Another agent ({name})"
|
|
53
|
+
parts.append(
|
|
54
|
+
f"[NEW COMMENT] {author} just left a new comment. "
|
|
55
|
+
"Focus on THIS comment — do not confuse it with previous ones:\n\n"
|
|
56
|
+
f"> {task.trigger_comment_content}\n\n"
|
|
57
|
+
)
|
|
58
|
+
if task.trigger_author_type == "agent":
|
|
59
|
+
parts.append(
|
|
60
|
+
"The triggering comment was posted by another agent. "
|
|
61
|
+
"If you produced real work this turn, post the result as a normal reply. "
|
|
62
|
+
"If it was only acknowledgment/thanks and you produced no work, do NOT reply "
|
|
63
|
+
"and do NOT @mention the other agent (that re-triggers a loop).\n\n"
|
|
64
|
+
)
|
|
65
|
+
parts.append(
|
|
66
|
+
f"Start by running `aicortex issue get {task.issue_id} --output json` "
|
|
67
|
+
"to understand your task, then decide how to proceed.\n\n"
|
|
68
|
+
f"If you need comment history, `aicortex issue comment list {task.issue_id} "
|
|
69
|
+
"--output json`.\n\n"
|
|
70
|
+
)
|
|
71
|
+
parent = task.trigger_comment_id
|
|
72
|
+
if parent:
|
|
73
|
+
parts.append(build_comment_reply_instructions(provider, task.issue_id, parent))
|
|
74
|
+
else:
|
|
75
|
+
parts.append(
|
|
76
|
+
f"Final results MUST be delivered via `aicortex issue comment add {task.issue_id}`.\n"
|
|
77
|
+
)
|
|
78
|
+
return "".join(parts)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def build_chat_prompt(task: Task) -> str:
|
|
82
|
+
parts = [
|
|
83
|
+
"You are running as a chat assistant for a AICortex workspace.\n",
|
|
84
|
+
"A user is chatting with you directly. Respond to their message.\n\n",
|
|
85
|
+
]
|
|
86
|
+
if task.chat_history:
|
|
87
|
+
parts.append("Recent conversation (context only; answer the latest user message below):\n")
|
|
88
|
+
for m in task.chat_history:
|
|
89
|
+
role = str(m.get("role") or "unknown").strip() or "unknown"
|
|
90
|
+
content = str(m.get("content") or "")
|
|
91
|
+
parts.append(f"{role}:\n{content}\n\n")
|
|
92
|
+
parts.append(f"User message:\n{task.chat_message}\n")
|
|
93
|
+
atts = task.chat_message_attachments
|
|
94
|
+
if atts:
|
|
95
|
+
parts.append("\nAttachments on this message:\n")
|
|
96
|
+
for a in atts:
|
|
97
|
+
aid = a.get("id")
|
|
98
|
+
fn = a.get("filename")
|
|
99
|
+
ct = a.get("content_type") or ""
|
|
100
|
+
if ct:
|
|
101
|
+
parts.append(f'- id={aid} filename="{fn}" content_type={ct}\n')
|
|
102
|
+
else:
|
|
103
|
+
parts.append(f'- id={aid} filename="{fn}"\n')
|
|
104
|
+
incoming = task.design_incoming_rel_paths or []
|
|
105
|
+
if incoming:
|
|
106
|
+
parts.append("\nThese files are already copied into the work directory:\n")
|
|
107
|
+
for rel in incoming:
|
|
108
|
+
parts.append(f"- `{rel}`\n")
|
|
109
|
+
parts.append(
|
|
110
|
+
"Read them directly from the work directory. "
|
|
111
|
+
"Use `aicortex attachment download <id>` only if you need another copy.\n"
|
|
112
|
+
)
|
|
113
|
+
else:
|
|
114
|
+
parts.append(
|
|
115
|
+
"Use `aicortex attachment download <id>` to fetch each file locally "
|
|
116
|
+
"before referring to it.\n"
|
|
117
|
+
)
|
|
118
|
+
return "".join(parts)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def build_autopilot_prompt(task: Task) -> str:
|
|
122
|
+
parts = [
|
|
123
|
+
"You are running as a local coding agent for a AICortex workspace.\n\n",
|
|
124
|
+
"This task was triggered by an Autopilot in run-only mode. "
|
|
125
|
+
"There is no assigned AICortex issue for this run.\n\n",
|
|
126
|
+
f"Autopilot run ID: {task.autopilot_run_id}\n",
|
|
127
|
+
]
|
|
128
|
+
if task.autopilot_id:
|
|
129
|
+
parts.append(f"Autopilot ID: {task.autopilot_id}\n")
|
|
130
|
+
if task.autopilot_title:
|
|
131
|
+
parts.append(f"Autopilot title: {task.autopilot_title}\n")
|
|
132
|
+
if task.autopilot_source:
|
|
133
|
+
parts.append(f"Trigger source: {task.autopilot_source}\n")
|
|
134
|
+
if task.autopilot_trigger_payload.strip():
|
|
135
|
+
parts.append(f"Trigger payload:\n{task.autopilot_trigger_payload.strip()}\n")
|
|
136
|
+
parts.append("\nAutopilot instructions:\n")
|
|
137
|
+
if task.autopilot_description.strip():
|
|
138
|
+
parts.append(task.autopilot_description + "\n\n")
|
|
139
|
+
elif task.autopilot_title:
|
|
140
|
+
parts.append(task.autopilot_title + "\n\n")
|
|
141
|
+
else:
|
|
142
|
+
parts.append(
|
|
143
|
+
"No additional autopilot instructions were provided. "
|
|
144
|
+
"Inspect the autopilot configuration before proceeding.\n\n"
|
|
145
|
+
)
|
|
146
|
+
if task.autopilot_id:
|
|
147
|
+
parts.append(
|
|
148
|
+
f"Start by running `aicortex autopilot get {task.autopilot_id} --output json` "
|
|
149
|
+
"if you need the full autopilot configuration, then complete the instructions above.\n"
|
|
150
|
+
)
|
|
151
|
+
else:
|
|
152
|
+
parts.append("Complete the instructions above.\n")
|
|
153
|
+
parts.append("Do not run `aicortex issue get`; this run does not have an issue ID.\n")
|
|
154
|
+
return "".join(parts)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def build_quick_create_prompt(task: Task) -> str:
|
|
158
|
+
parts = [
|
|
159
|
+
"You are running as a quick-create assistant for a AICortex workspace.\n\n",
|
|
160
|
+
"A user captured the following input via the quick-create modal. "
|
|
161
|
+
"There is NO existing issue. Your job is to create a well-formed issue "
|
|
162
|
+
"from this input with a single `aicortex issue create` command.\n\n",
|
|
163
|
+
f"User input:\n> {task.quick_create_prompt}\n\n",
|
|
164
|
+
"Field rules:\n\n",
|
|
165
|
+
"- **title**: required. Concise but semantically rich summary.\n\n",
|
|
166
|
+
"- **description**: High fidelity restatement of user intent "
|
|
167
|
+
"(User request + optional Context sections).\n\n",
|
|
168
|
+
"- **priority**: urgent|high|medium|low or omit.\n\n",
|
|
169
|
+
"- **assignee**: resolve names via workspace members/agents/squads JSON; "
|
|
170
|
+
"prefer `--assignee-id <uuid>`.\n",
|
|
171
|
+
]
|
|
172
|
+
agent = task.agent or {}
|
|
173
|
+
agent_id = str(agent.get("id") or "")
|
|
174
|
+
agent_name = str(agent.get("name") or "")
|
|
175
|
+
if task.squad_id:
|
|
176
|
+
label = task.squad_name or task.squad_id
|
|
177
|
+
parts.append(
|
|
178
|
+
f' - When the user did NOT name an assignee, default to the picker SQUAD "{label}": '
|
|
179
|
+
f'pass `--assignee-id "{task.squad_id}"`.\n\n'
|
|
180
|
+
)
|
|
181
|
+
elif agent_id:
|
|
182
|
+
parts.append(
|
|
183
|
+
f' - When the user did NOT name an assignee, default to YOURSELF: '
|
|
184
|
+
f'pass `--assignee-id "{agent_id}"`.\n\n'
|
|
185
|
+
)
|
|
186
|
+
elif agent_name:
|
|
187
|
+
parts.append(
|
|
188
|
+
f' - When the user did NOT name an assignee, default to YOURSELF: '
|
|
189
|
+
f'pass `--assignee "{agent_name}"`.\n\n'
|
|
190
|
+
)
|
|
191
|
+
if task.project_id:
|
|
192
|
+
title = task.project_title or task.project_id
|
|
193
|
+
parts.append(
|
|
194
|
+
f'- **project**: required. Pass `--project "{task.project_id}"` '
|
|
195
|
+
f'(project "{title}").\n'
|
|
196
|
+
)
|
|
197
|
+
else:
|
|
198
|
+
parts.append("- **project**: omit.\n")
|
|
199
|
+
parts.append(
|
|
200
|
+
"- **status**: omit (defaults to todo).\n"
|
|
201
|
+
"- **attachments**: do NOT pass `--attachment` for remote URLs.\n\n"
|
|
202
|
+
"Output format:\n"
|
|
203
|
+
"- Run exactly one `aicortex issue create --output json`.\n"
|
|
204
|
+
"- Print exactly one line: `Created <identifier-or-id>: <title>` and exit.\n"
|
|
205
|
+
"- Do NOT call `aicortex issue get` or comment add.\n"
|
|
206
|
+
)
|
|
207
|
+
return "".join(parts)
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def build_design_prompt(task: Task) -> str:
|
|
211
|
+
parts = [
|
|
212
|
+
"You are running in AICortex Design Studio mode.\n\n",
|
|
213
|
+
f"Design mode: {task.design_mode}\n",
|
|
214
|
+
]
|
|
215
|
+
if task.design_system_name:
|
|
216
|
+
parts.append(f"Design system: {task.design_system_name}\n")
|
|
217
|
+
if task.design_system_content:
|
|
218
|
+
parts.append(f"\nDesign system content:\n{task.design_system_content}\n")
|
|
219
|
+
if task.design_example_hint:
|
|
220
|
+
parts.append(f"\nExample hint: {task.design_example_hint}\n")
|
|
221
|
+
if task.artifact_entry:
|
|
222
|
+
parts.append(f"\nPrimary artifact entry: {task.artifact_entry}\n")
|
|
223
|
+
if task.project_prompt:
|
|
224
|
+
parts.append(f"\nProject instructions:\n{task.project_prompt}\n")
|
|
225
|
+
parts.append(f"\nUser message:\n{task.chat_message}\n")
|
|
226
|
+
incoming = task.design_incoming_rel_paths or []
|
|
227
|
+
if incoming:
|
|
228
|
+
parts.append("\nIncoming files already in the work directory:\n")
|
|
229
|
+
for rel in incoming:
|
|
230
|
+
parts.append(f"- `{rel}`\n")
|
|
231
|
+
parts.append(
|
|
232
|
+
"\nProduce the design artifacts in the work directory. "
|
|
233
|
+
"Prefer HTML under design/ when applicable.\n"
|
|
234
|
+
)
|
|
235
|
+
return "".join(parts)
|