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
|
@@ -0,0 +1,540 @@
|
|
|
1
|
+
"""Inject provider-native runtime meta docs (CLAUDE.md / AGENTS.md / GEMINI.md).
|
|
2
|
+
|
|
3
|
+
Port of server/internal/daemon/execenv/runtime_config.go + project_context.go
|
|
4
|
+
+ reply_instructions.go (trimmed but behaviour-aligned).
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
import platform
|
|
11
|
+
import sys
|
|
12
|
+
from dataclasses import dataclass, field
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import Any
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _is_windows() -> bool:
|
|
18
|
+
return sys.platform == "win32" or platform.system().lower() == "windows"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass
|
|
22
|
+
class TaskContextForEnv:
|
|
23
|
+
agent_id: str = ""
|
|
24
|
+
agent_name: str = ""
|
|
25
|
+
agent_instructions: str = ""
|
|
26
|
+
agent_skills: list[dict[str, Any]] = field(default_factory=list)
|
|
27
|
+
issue_id: str = ""
|
|
28
|
+
chat_session_id: str = ""
|
|
29
|
+
trigger_comment_id: str = ""
|
|
30
|
+
quick_create_prompt: str = ""
|
|
31
|
+
autopilot_run_id: str = ""
|
|
32
|
+
autopilot_id: str = ""
|
|
33
|
+
autopilot_title: str = ""
|
|
34
|
+
autopilot_source: str = ""
|
|
35
|
+
autopilot_description: str = ""
|
|
36
|
+
autopilot_trigger_payload: str = ""
|
|
37
|
+
design_mode: str = ""
|
|
38
|
+
design_system_name: str = ""
|
|
39
|
+
design_system_content: str = ""
|
|
40
|
+
design_example_hint: str = ""
|
|
41
|
+
artifact_entry: str = ""
|
|
42
|
+
project_id: str = ""
|
|
43
|
+
project_title: str = ""
|
|
44
|
+
project_prompt: str = ""
|
|
45
|
+
project_resources: list[dict[str, Any]] = field(default_factory=list)
|
|
46
|
+
repos: list[dict[str, Any]] = field(default_factory=list)
|
|
47
|
+
is_squad_leader: bool = False
|
|
48
|
+
memory_markdown: str = ""
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def task_context_from_task(task: Any) -> TaskContextForEnv:
|
|
52
|
+
agent = task.agent or {}
|
|
53
|
+
skills = agent.get("skills") if isinstance(agent.get("skills"), list) else []
|
|
54
|
+
return TaskContextForEnv(
|
|
55
|
+
agent_id=str(agent.get("id") or ""),
|
|
56
|
+
agent_name=str(agent.get("name") or ""),
|
|
57
|
+
agent_instructions=str(agent.get("instructions") or "").strip(),
|
|
58
|
+
agent_skills=[s for s in skills if isinstance(s, dict)],
|
|
59
|
+
issue_id=task.issue_id,
|
|
60
|
+
chat_session_id=task.chat_session_id,
|
|
61
|
+
trigger_comment_id=task.trigger_comment_id,
|
|
62
|
+
quick_create_prompt=task.quick_create_prompt,
|
|
63
|
+
autopilot_run_id=task.autopilot_run_id,
|
|
64
|
+
autopilot_id=task.autopilot_id,
|
|
65
|
+
autopilot_title=task.autopilot_title,
|
|
66
|
+
autopilot_source=task.autopilot_source,
|
|
67
|
+
autopilot_description=task.autopilot_description,
|
|
68
|
+
autopilot_trigger_payload=task.autopilot_trigger_payload,
|
|
69
|
+
design_mode=task.design_mode,
|
|
70
|
+
design_system_name=task.design_system_name,
|
|
71
|
+
design_system_content=task.design_system_content,
|
|
72
|
+
design_example_hint=task.design_example_hint,
|
|
73
|
+
artifact_entry=task.artifact_entry,
|
|
74
|
+
project_id=task.project_id,
|
|
75
|
+
project_title=task.project_title,
|
|
76
|
+
project_prompt=task.project_prompt,
|
|
77
|
+
project_resources=list(task.project_resources),
|
|
78
|
+
repos=list(task.repos),
|
|
79
|
+
is_squad_leader=bool(
|
|
80
|
+
getattr(task, "raw", {}).get("is_squad_leader")
|
|
81
|
+
or getattr(task, "raw", {}).get("isSquadLeader")
|
|
82
|
+
),
|
|
83
|
+
memory_markdown=str(
|
|
84
|
+
getattr(task, "raw", {}).get("memory_markdown")
|
|
85
|
+
or getattr(task, "raw", {}).get("memoryMarkdown")
|
|
86
|
+
or ""
|
|
87
|
+
),
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def inject_runtime_config(work_dir: Path, provider: str, ctx: TaskContextForEnv) -> str:
|
|
92
|
+
content = build_meta_skill_content(provider, ctx)
|
|
93
|
+
target: Path | None
|
|
94
|
+
if provider == "claude":
|
|
95
|
+
target = work_dir / "CLAUDE.md"
|
|
96
|
+
elif provider == "gemini":
|
|
97
|
+
target = work_dir / "GEMINI.md"
|
|
98
|
+
elif provider in (
|
|
99
|
+
"codex",
|
|
100
|
+
"copilot",
|
|
101
|
+
"opencode",
|
|
102
|
+
"openclaw",
|
|
103
|
+
"hermes",
|
|
104
|
+
"pi",
|
|
105
|
+
"cursor",
|
|
106
|
+
"kimi",
|
|
107
|
+
"kiro",
|
|
108
|
+
"dsh",
|
|
109
|
+
):
|
|
110
|
+
target = work_dir / "AGENTS.md"
|
|
111
|
+
else:
|
|
112
|
+
return content
|
|
113
|
+
target.write_text(content, encoding="utf-8")
|
|
114
|
+
return content
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def build_comment_reply_instructions(provider: str, issue_id: str, trigger_comment_id: str) -> str:
|
|
118
|
+
if not trigger_comment_id:
|
|
119
|
+
return ""
|
|
120
|
+
if _is_windows():
|
|
121
|
+
return (
|
|
122
|
+
"If you decide to reply, post it as a comment — always use the trigger comment ID below, "
|
|
123
|
+
"do NOT reuse --parent values from previous turns in this session.\n\n"
|
|
124
|
+
"On Windows, write the reply body to a UTF-8 file with your file-write tool, then post it with "
|
|
125
|
+
"`--content-file`. Do NOT pipe via `--content-stdin` — Windows PowerShell 5.1 may drop non-ASCII "
|
|
126
|
+
"as `?`. Do NOT use inline `--content` for agent-authored comments.\n\n"
|
|
127
|
+
"Use this form:\n\n"
|
|
128
|
+
f" aicortex issue comment add {issue_id} --parent {trigger_comment_id} "
|
|
129
|
+
"--content-file ./reply.md\n\n"
|
|
130
|
+
)
|
|
131
|
+
if provider == "codex":
|
|
132
|
+
return (
|
|
133
|
+
"If you decide to reply, post it as a comment — always use the trigger comment ID below, "
|
|
134
|
+
"do NOT reuse --parent values from previous turns in this session.\n\n"
|
|
135
|
+
"Always use `--content-stdin` with a HEREDOC for agent-authored issue comments.\n\n"
|
|
136
|
+
"Use this form:\n\n"
|
|
137
|
+
f" cat <<'COMMENT' | aicortex issue comment add {issue_id} "
|
|
138
|
+
f"--parent {trigger_comment_id} --content-stdin\n"
|
|
139
|
+
" First paragraph.\n\n"
|
|
140
|
+
" Second paragraph.\n"
|
|
141
|
+
" COMMENT\n\n"
|
|
142
|
+
)
|
|
143
|
+
return (
|
|
144
|
+
"If you decide to reply, post it as a comment — always use the trigger comment ID below, "
|
|
145
|
+
"do NOT reuse --parent values from previous turns in this session.\n\n"
|
|
146
|
+
f" aicortex issue comment add {issue_id} --parent {trigger_comment_id} --content \"...\"\n\n"
|
|
147
|
+
"For multi-line bodies prefer `--content-stdin` or `--content-file <path>`.\n"
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def build_meta_skill_content(provider: str, ctx: TaskContextForEnv) -> str:
|
|
152
|
+
parts: list[str] = [
|
|
153
|
+
"# AICortex Agent Runtime\n\n",
|
|
154
|
+
"You are a coding agent in the AICortex platform. "
|
|
155
|
+
"Use the `aicortex` CLI to interact with the platform.\n\n",
|
|
156
|
+
]
|
|
157
|
+
|
|
158
|
+
if ctx.agent_name or ctx.agent_id:
|
|
159
|
+
parts.append("## Agent Identity\n\n")
|
|
160
|
+
if ctx.agent_name:
|
|
161
|
+
line = f"**You are: {ctx.agent_name}**"
|
|
162
|
+
if ctx.agent_id:
|
|
163
|
+
line += f" (ID: `{ctx.agent_id}`)"
|
|
164
|
+
parts.append(line + "\n\n")
|
|
165
|
+
if ctx.agent_instructions:
|
|
166
|
+
parts.append(ctx.agent_instructions + "\n\n")
|
|
167
|
+
elif ctx.agent_instructions:
|
|
168
|
+
parts.append("## Agent Identity\n\n")
|
|
169
|
+
parts.append(ctx.agent_instructions + "\n\n")
|
|
170
|
+
|
|
171
|
+
if ctx.design_mode:
|
|
172
|
+
parts.append(_design_studio_runtime(provider, ctx))
|
|
173
|
+
return "".join(parts)
|
|
174
|
+
|
|
175
|
+
parts.append(_available_commands())
|
|
176
|
+
if provider == "codex":
|
|
177
|
+
parts.append(_codex_comment_section())
|
|
178
|
+
|
|
179
|
+
if ctx.repos:
|
|
180
|
+
parts.append(_repos_section(ctx))
|
|
181
|
+
|
|
182
|
+
parts.append(_project_context_section(ctx))
|
|
183
|
+
parts.append(_workflow_section(provider, ctx))
|
|
184
|
+
|
|
185
|
+
if ctx.agent_skills:
|
|
186
|
+
parts.append(_skills_section(provider, ctx.agent_skills))
|
|
187
|
+
|
|
188
|
+
if ctx.memory_markdown:
|
|
189
|
+
parts.append(
|
|
190
|
+
"## Agent Memory\n\n"
|
|
191
|
+
"Your working memory is in `.agent_context/memory.md`. "
|
|
192
|
+
"「记住」defaults to `aicortex memory append` / `write` — never `aicortex graph write`.\n\n"
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
parts.append(_mentions_section())
|
|
196
|
+
parts.append(_attachments_section())
|
|
197
|
+
parts.append(_cli_only_section())
|
|
198
|
+
parts.append(_output_section(ctx))
|
|
199
|
+
return "".join(parts)
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def _available_commands() -> str:
|
|
203
|
+
return (
|
|
204
|
+
"## Available Commands\n\n"
|
|
205
|
+
"**Use `--output json` for structured data.** "
|
|
206
|
+
"Prefer CLI flags over piping through `python`/`jq` (especially on Windows).\n\n"
|
|
207
|
+
"### Read\n"
|
|
208
|
+
"- `aicortex issue get <id> --output json`\n"
|
|
209
|
+
"- `aicortex issue list [--status X] [--assignee-id <uuid>] [--limit N] [--offset N] --output json`\n"
|
|
210
|
+
"- `aicortex issue comment list <issue-id> [--since <RFC3339>] --output json`\n"
|
|
211
|
+
"- `aicortex workspace get --output json` / `aicortex workspace members --output json`\n"
|
|
212
|
+
"- `aicortex agent list --output json` / `aicortex squad list --output json`\n"
|
|
213
|
+
"- `aicortex repo checkout <url> [--ref <branch-or-sha>]`\n"
|
|
214
|
+
"- `aicortex attachment download <id> [-o <dir>]`\n"
|
|
215
|
+
"- `aicortex project get <id> --output json` / `aicortex project resource list <id> --output json`\n\n"
|
|
216
|
+
"### Write\n"
|
|
217
|
+
"- `aicortex issue create|update|status|assign ...`\n"
|
|
218
|
+
"- `aicortex issue comment add <issue-id> [--content \"...\" | --content-stdin | --content-file <path>] "
|
|
219
|
+
"[--parent <comment-id>]`\n"
|
|
220
|
+
" - Windows: prefer `--content-file` for non-ASCII bodies\n"
|
|
221
|
+
" - Codex on Unix: prefer `--content-stdin` HEREDOC\n"
|
|
222
|
+
"- `aicortex issue label add/remove`, `subscriber add/remove`, `label create`\n"
|
|
223
|
+
"- `aicortex autopilot create|update|trigger|delete ...`\n"
|
|
224
|
+
"- `aicortex meeting create|advance|summary|close ...`\n\n"
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def _codex_comment_section() -> str:
|
|
229
|
+
if _is_windows():
|
|
230
|
+
return (
|
|
231
|
+
"## Codex-Specific Comment Formatting\n\n"
|
|
232
|
+
"On Windows, **always write the comment body to a UTF-8 file, then post with "
|
|
233
|
+
"`--content-file <path>`**. Do NOT use `--content-stdin` or inline `--content` "
|
|
234
|
+
"for agent-authored comments.\n\n"
|
|
235
|
+
)
|
|
236
|
+
return (
|
|
237
|
+
"## Codex-Specific Comment Formatting\n\n"
|
|
238
|
+
"Always use `--content-stdin` with a HEREDOC for issue comments. "
|
|
239
|
+
"Never use inline `--content` for agent-authored comments.\n\n"
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def _format_project_resource(r: dict[str, Any]) -> str:
|
|
244
|
+
rtype = str(r.get("resource_type") or r.get("type") or "resource")
|
|
245
|
+
label = str(r.get("label") or "")
|
|
246
|
+
ref = r.get("resource_ref") or r.get("ref") or {}
|
|
247
|
+
if isinstance(ref, str):
|
|
248
|
+
try:
|
|
249
|
+
ref = json.loads(ref)
|
|
250
|
+
except json.JSONDecodeError:
|
|
251
|
+
ref = {"raw": ref}
|
|
252
|
+
if not isinstance(ref, dict):
|
|
253
|
+
ref = {}
|
|
254
|
+
if rtype == "github_repo":
|
|
255
|
+
url = str(ref.get("url") or "")
|
|
256
|
+
out = f"**GitHub repo**: {url}"
|
|
257
|
+
hint = str(ref.get("default_branch_hint") or "")
|
|
258
|
+
if hint:
|
|
259
|
+
out += f" (default branch: `{hint}`)"
|
|
260
|
+
elif rtype == "local_path":
|
|
261
|
+
out = f"**Local path**: `{ref.get('path') or ''}`"
|
|
262
|
+
else:
|
|
263
|
+
out = f"**{rtype}**: `{json.dumps(ref, ensure_ascii=False)}`"
|
|
264
|
+
if label:
|
|
265
|
+
out += f" — {label}"
|
|
266
|
+
return out
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def _project_has_local_path(ctx: TaskContextForEnv) -> bool:
|
|
270
|
+
for r in ctx.project_resources:
|
|
271
|
+
if str(r.get("resource_type") or r.get("type") or "") == "local_path":
|
|
272
|
+
return True
|
|
273
|
+
return False
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def _has_project_context(ctx: TaskContextForEnv) -> bool:
|
|
277
|
+
return bool(ctx.project_id or ctx.project_resources or ctx.project_prompt.strip())
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def _repos_section(ctx: TaskContextForEnv) -> str:
|
|
281
|
+
parts = ["## Repositories\n\n"]
|
|
282
|
+
if _project_has_local_path(ctx):
|
|
283
|
+
parts.append(
|
|
284
|
+
"Repositories are attached to this project. Your working directory is already "
|
|
285
|
+
"the configured local path — do **not** run `aicortex repo checkout` for the same codebase.\n\n"
|
|
286
|
+
)
|
|
287
|
+
else:
|
|
288
|
+
parts.append(
|
|
289
|
+
"Use `aicortex repo checkout <url>` to check out a repository. "
|
|
290
|
+
"Add `--ref <branch-or-sha>` when needed.\n\n"
|
|
291
|
+
)
|
|
292
|
+
for repo in ctx.repos:
|
|
293
|
+
url = str(repo.get("url") or "")
|
|
294
|
+
if url:
|
|
295
|
+
parts.append(f"- {url}\n")
|
|
296
|
+
parts.append("\n")
|
|
297
|
+
return "".join(parts)
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def _project_context_section(ctx: TaskContextForEnv) -> str:
|
|
301
|
+
if not _has_project_context(ctx):
|
|
302
|
+
return ""
|
|
303
|
+
scope = "chat session" if ctx.chat_session_id else (
|
|
304
|
+
"design session" if ctx.design_mode else "issue"
|
|
305
|
+
)
|
|
306
|
+
parts = ["## Project Context\n\n"]
|
|
307
|
+
if ctx.project_title:
|
|
308
|
+
parts.append(f"This {scope} belongs to **{ctx.project_title}**.\n\n")
|
|
309
|
+
if ctx.project_id:
|
|
310
|
+
parts.append(f"Project working directory (logical): `/workspace/{ctx.project_id}/`\n\n")
|
|
311
|
+
parts.append(
|
|
312
|
+
"Use this shared layout:\n\n"
|
|
313
|
+
"- `docs/` — project materials\n"
|
|
314
|
+
"- `design/` — design HTML and static mockups\n"
|
|
315
|
+
"- `app/` — application / development code\n"
|
|
316
|
+
"- `test/` — tests, fixtures, and reports\n"
|
|
317
|
+
"- `release/` — build artifacts and release notes\n\n"
|
|
318
|
+
"Write only in the directory that matches the current task.\n\n"
|
|
319
|
+
)
|
|
320
|
+
if ctx.project_resources:
|
|
321
|
+
parts.append("Project resources (also at `.aicortex/project/resources.json`):\n\n")
|
|
322
|
+
for r in ctx.project_resources:
|
|
323
|
+
parts.append(f"- {_format_project_resource(r)}\n")
|
|
324
|
+
parts.append("\n")
|
|
325
|
+
if _project_has_local_path(ctx):
|
|
326
|
+
parts.append(
|
|
327
|
+
"A `local_path` is configured — do **not** run `aicortex repo checkout` "
|
|
328
|
+
"for the same codebase.\n\n"
|
|
329
|
+
)
|
|
330
|
+
if ctx.project_prompt.strip():
|
|
331
|
+
title = f" ({ctx.project_title})" if ctx.project_title else ""
|
|
332
|
+
parts.append(f"### Project Instructions{title}\n\n")
|
|
333
|
+
parts.append(ctx.project_prompt.strip() + "\n\n")
|
|
334
|
+
if ctx.project_id:
|
|
335
|
+
parts.append("Full text is also at `.aicortex/project/instructions.md`.\n\n")
|
|
336
|
+
return "".join(parts)
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
def _workflow_section(provider: str, ctx: TaskContextForEnv) -> str:
|
|
340
|
+
parts = ["### Workflow\n\n"]
|
|
341
|
+
if ctx.chat_session_id:
|
|
342
|
+
parts.append(
|
|
343
|
+
"**You are in chat mode.** Respond conversationally. Use the `aicortex` CLI for "
|
|
344
|
+
"lookups and actions. Keep responses concise.\n\n"
|
|
345
|
+
)
|
|
346
|
+
if _project_has_local_path(ctx):
|
|
347
|
+
parts.append(
|
|
348
|
+
"- Working directory is already the project's local path; do not checkout the same repo.\n\n"
|
|
349
|
+
)
|
|
350
|
+
elif ctx.quick_create_prompt:
|
|
351
|
+
parts.append(
|
|
352
|
+
"**This task was triggered by quick-create.** There is NO existing issue.\n\n"
|
|
353
|
+
"- Run exactly one `aicortex issue create`, then exit.\n"
|
|
354
|
+
"- Do NOT call `issue get` / `issue status` / `issue comment add` for this run.\n\n"
|
|
355
|
+
)
|
|
356
|
+
elif ctx.autopilot_run_id:
|
|
357
|
+
parts.append(
|
|
358
|
+
"**This task was triggered by an Autopilot in run-only mode.** "
|
|
359
|
+
"There is no assigned issue unless instructions say otherwise.\n\n"
|
|
360
|
+
)
|
|
361
|
+
parts.append(f"- Autopilot run ID: `{ctx.autopilot_run_id}`\n")
|
|
362
|
+
if ctx.autopilot_id:
|
|
363
|
+
parts.append(f"- Autopilot ID: `{ctx.autopilot_id}`\n")
|
|
364
|
+
if ctx.autopilot_title:
|
|
365
|
+
parts.append(f"- Autopilot title: {ctx.autopilot_title}\n")
|
|
366
|
+
if ctx.autopilot_description.strip():
|
|
367
|
+
parts.append("\nAutopilot instructions:\n\n" + ctx.autopilot_description.strip() + "\n\n")
|
|
368
|
+
parts.append(
|
|
369
|
+
"- Complete the autopilot instructions directly.\n"
|
|
370
|
+
"- Do not run issue get/comment/status unless instructions require it.\n\n"
|
|
371
|
+
)
|
|
372
|
+
elif ctx.trigger_comment_id:
|
|
373
|
+
parts.append(
|
|
374
|
+
"**This task was triggered by a NEW comment.** Focus on THIS comment.\n\n"
|
|
375
|
+
f"1. `aicortex issue get {ctx.issue_id} --output json`\n"
|
|
376
|
+
f"2. `aicortex issue comment list {ctx.issue_id} --output json`\n"
|
|
377
|
+
f"3. Find triggering comment `{ctx.trigger_comment_id}`\n"
|
|
378
|
+
"4. Decide whether a reply is warranted — silence is OK for pure acknowledgments.\n"
|
|
379
|
+
"5. If you reply, decide carefully about `@mention` (default: no mention).\n"
|
|
380
|
+
"6. "
|
|
381
|
+
)
|
|
382
|
+
parts.append(
|
|
383
|
+
build_comment_reply_instructions(provider, ctx.issue_id, ctx.trigger_comment_id)
|
|
384
|
+
)
|
|
385
|
+
parts.append("7. Do NOT change issue status unless the comment asks for it.\n\n")
|
|
386
|
+
else:
|
|
387
|
+
parts.append(
|
|
388
|
+
"You are responsible for managing the issue status throughout your work.\n\n"
|
|
389
|
+
f"1. `aicortex issue get {ctx.issue_id} --output json`\n"
|
|
390
|
+
f"2. `aicortex issue comment list {ctx.issue_id} --output json` (mandatory)\n"
|
|
391
|
+
f"3. `aicortex issue status {ctx.issue_id} in_progress`\n"
|
|
392
|
+
"4. Follow Skills and Agent Identity to complete the task\n"
|
|
393
|
+
f"5. Post results: `aicortex issue comment add {ctx.issue_id} ...`\n"
|
|
394
|
+
f"6. When done: `aicortex issue status {ctx.issue_id} in_review`\n"
|
|
395
|
+
f"7. If blocked: `aicortex issue status {ctx.issue_id} blocked` + comment\n\n"
|
|
396
|
+
)
|
|
397
|
+
return "".join(parts)
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
def _skills_section(provider: str, skills: list[dict[str, Any]]) -> str:
|
|
401
|
+
parts = ["## Skills\n\n"]
|
|
402
|
+
if provider in (
|
|
403
|
+
"claude",
|
|
404
|
+
"codex",
|
|
405
|
+
"copilot",
|
|
406
|
+
"opencode",
|
|
407
|
+
"openclaw",
|
|
408
|
+
"pi",
|
|
409
|
+
"cursor",
|
|
410
|
+
"kimi",
|
|
411
|
+
"kiro",
|
|
412
|
+
"dsh",
|
|
413
|
+
):
|
|
414
|
+
parts.append("You have the following skills installed (discovered automatically):\n\n")
|
|
415
|
+
else:
|
|
416
|
+
parts.append(
|
|
417
|
+
"Detailed skill instructions are in `.agent_context/skills/`. "
|
|
418
|
+
"Each subdirectory contains a `SKILL.md`.\n\n"
|
|
419
|
+
)
|
|
420
|
+
for sk in skills:
|
|
421
|
+
name = str(sk.get("name") or "").strip()
|
|
422
|
+
if name:
|
|
423
|
+
parts.append(f"- **{name}**\n")
|
|
424
|
+
parts.append("\n")
|
|
425
|
+
return "".join(parts)
|
|
426
|
+
|
|
427
|
+
|
|
428
|
+
def _mentions_section() -> str:
|
|
429
|
+
return (
|
|
430
|
+
"## Mentions\n\n"
|
|
431
|
+
"Mention links are **side-effecting actions**:\n\n"
|
|
432
|
+
"- `[KEY-123](mention://issue/<issue-id>)` — clickable issue link (safe)\n"
|
|
433
|
+
"- `[@Name](mention://member/<user-id>)` — notifies a human\n"
|
|
434
|
+
"- `[@Name](mention://agent/<agent-id>)` — enqueues another agent run\n\n"
|
|
435
|
+
"Do **not** @mention the agent you are replying to as thanks/sign-off "
|
|
436
|
+
"(that causes loops). Default to no mention unless escalating or delegating.\n\n"
|
|
437
|
+
)
|
|
438
|
+
|
|
439
|
+
|
|
440
|
+
def _attachments_section() -> str:
|
|
441
|
+
return (
|
|
442
|
+
"## Attachments\n\n"
|
|
443
|
+
"```\naicortex attachment download <attachment-id>\n```\n\n"
|
|
444
|
+
"Downloads to the current directory (use `-o <dir>` to choose a folder).\n\n"
|
|
445
|
+
)
|
|
446
|
+
|
|
447
|
+
|
|
448
|
+
def _cli_only_section() -> str:
|
|
449
|
+
return (
|
|
450
|
+
"## Important: Always Use the `aicortex` CLI\n\n"
|
|
451
|
+
"All AICortex platform resource access must go through the `aicortex` CLI. "
|
|
452
|
+
"Do NOT use curl/wget against AICortex URLs.\n\n"
|
|
453
|
+
)
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
def _output_section(ctx: TaskContextForEnv) -> str:
|
|
457
|
+
if ctx.autopilot_run_id:
|
|
458
|
+
return (
|
|
459
|
+
"## Output\n\n"
|
|
460
|
+
"Run-only autopilot: final assistant output is captured as the run result. "
|
|
461
|
+
"Keep it concise.\n"
|
|
462
|
+
)
|
|
463
|
+
if ctx.quick_create_prompt:
|
|
464
|
+
return (
|
|
465
|
+
"## Output\n\n"
|
|
466
|
+
"Quick-create: do NOT call `issue comment add`. "
|
|
467
|
+
"Print `Created <id>: <title>` after successful create.\n"
|
|
468
|
+
)
|
|
469
|
+
return (
|
|
470
|
+
"## Output\n\n"
|
|
471
|
+
"⚠️ **Final results MUST be delivered via `aicortex issue comment add`.** "
|
|
472
|
+
"The user does NOT see terminal output or run logs — only issue comments.\n"
|
|
473
|
+
)
|
|
474
|
+
|
|
475
|
+
|
|
476
|
+
def _design_studio_runtime(provider: str, ctx: TaskContextForEnv) -> str:
|
|
477
|
+
entry = (ctx.artifact_entry or "design/index.html").strip()
|
|
478
|
+
parts = [
|
|
479
|
+
"## Design Studio mode\n\n",
|
|
480
|
+
"You are in **Design Studio**. Deliver a static design artifact under `design/`. "
|
|
481
|
+
"The user previews files in an iframe — **not** live workspace data.\n\n",
|
|
482
|
+
f"- Design mode: `{ctx.design_mode}`\n",
|
|
483
|
+
f"- Primary preview entry: `{entry}`\n\n",
|
|
484
|
+
"### Hard rules — workspace mutation is forbidden\n\n",
|
|
485
|
+
"- Do **NOT** run issue/autopilot/meeting mutation commands.\n",
|
|
486
|
+
"- Use fictional sample data in HTML; do not create real issues.\n",
|
|
487
|
+
"- Do **NOT** call `aicortex issue comment add`.\n\n",
|
|
488
|
+
"### Workflow\n\n",
|
|
489
|
+
"1. Read the design brief / DESIGN.md.\n",
|
|
490
|
+
"2. Underspecified briefs → emit one `<question-form>` block.\n",
|
|
491
|
+
f"3. Write `{entry}` (and assets) under `design/`.\n",
|
|
492
|
+
"4. Tag interactive regions with `data-aicortex-id`.\n",
|
|
493
|
+
"5. Finish when the artifact renders; keep the summary short.\n\n",
|
|
494
|
+
"Reference files may already be in `_incoming/`.\n\n",
|
|
495
|
+
]
|
|
496
|
+
if ctx.design_system_content.strip():
|
|
497
|
+
name = ctx.design_system_name.strip() or "Design System"
|
|
498
|
+
parts.append(f"Active design system (**{name}**) is in the per-turn prompt.\n\n")
|
|
499
|
+
if _has_project_context(ctx):
|
|
500
|
+
parts.append("## Design project context\n\n")
|
|
501
|
+
if ctx.project_title:
|
|
502
|
+
parts.append(f"This design session belongs to **{ctx.project_title}**.\n\n")
|
|
503
|
+
if ctx.project_id:
|
|
504
|
+
parts.append(
|
|
505
|
+
f"Project working directory (logical): `/workspace/{ctx.project_id}/`\n"
|
|
506
|
+
"Write design HTML under `design/`.\n\n"
|
|
507
|
+
)
|
|
508
|
+
if ctx.project_resources:
|
|
509
|
+
parts.append("Project resources (tokens/branding only):\n\n")
|
|
510
|
+
for r in ctx.project_resources:
|
|
511
|
+
parts.append(f"- {_format_project_resource(r)}\n")
|
|
512
|
+
parts.append("\n")
|
|
513
|
+
if ctx.project_prompt.strip():
|
|
514
|
+
parts.append(ctx.project_prompt.strip() + "\n\n")
|
|
515
|
+
if ctx.design_example_hint.strip():
|
|
516
|
+
parts.append("### Bundled example templates\n\n")
|
|
517
|
+
parts.append(ctx.design_example_hint.strip() + "\n\n")
|
|
518
|
+
if ctx.agent_skills:
|
|
519
|
+
parts.append(_skills_section(provider, ctx.agent_skills))
|
|
520
|
+
parts.append(
|
|
521
|
+
"## Output\n\n"
|
|
522
|
+
"Design Studio: do not post issue comments. "
|
|
523
|
+
f"Deliverable is `{entry}` in the work directory.\n"
|
|
524
|
+
)
|
|
525
|
+
return "".join(parts)
|
|
526
|
+
|
|
527
|
+
|
|
528
|
+
def write_project_sidecar(work_dir: Path, ctx: TaskContextForEnv) -> None:
|
|
529
|
+
"""Write `.aicortex/project/` sidecars when project context exists."""
|
|
530
|
+
if not _has_project_context(ctx):
|
|
531
|
+
return
|
|
532
|
+
root = work_dir / ".aicortex" / "project"
|
|
533
|
+
root.mkdir(parents=True, exist_ok=True)
|
|
534
|
+
if ctx.project_prompt.strip():
|
|
535
|
+
(root / "instructions.md").write_text(ctx.project_prompt.strip() + "\n", encoding="utf-8")
|
|
536
|
+
if ctx.project_resources:
|
|
537
|
+
(root / "resources.json").write_text(
|
|
538
|
+
json.dumps(ctx.project_resources, ensure_ascii=False, indent=2) + "\n",
|
|
539
|
+
encoding="utf-8",
|
|
540
|
+
)
|