@htooayelwinict/appv23 2.3.0 → 2.3.2
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.
- package/README.md +9 -1
- package/agents/AGENTS.md +25 -0
- package/bin/appv23.js +87 -8
- package/package.json +3 -1
- package/skills/subagent-delegation/SKILL.md +64 -0
- package/skills/web-search/SKILL.md +51 -0
package/README.md
CHANGED
|
@@ -23,7 +23,15 @@ The launcher pulls and runs:
|
|
|
23
23
|
ghcr.io/htooayelwinict/appv23:production
|
|
24
24
|
```
|
|
25
25
|
|
|
26
|
-
It mounts only the selected `--cwd` as `/workspace`, stores sandbox state in `~/.appv23/sandbox-home`, and copies host `~/.agents/skills` into the sandbox.
|
|
26
|
+
It mounts only the selected `--cwd` as `/workspace`, stores sandbox state in `~/.appv23/sandbox-home`, copies host `~/.agents/AGENTS.md` into the sandbox agent context, and copies host `~/.agents/skills` into the sandbox.
|
|
27
|
+
|
|
28
|
+
On startup, the package restores compact default agent files only when they are missing:
|
|
29
|
+
|
|
30
|
+
- `~/.agents/AGENTS.md`
|
|
31
|
+
- `~/.agents/skills/web-search/SKILL.md`
|
|
32
|
+
- bundled package skills such as `subagent-delegation`
|
|
33
|
+
|
|
34
|
+
Existing user files are never overwritten.
|
|
27
35
|
|
|
28
36
|
## Options
|
|
29
37
|
|
package/agents/AGENTS.md
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# appv23 Agent Kernel
|
|
2
|
+
|
|
3
|
+
This is the default appv23 user-level agent prompt. It is installed only when
|
|
4
|
+
`~/.agents/AGENTS.md` is missing. Edit the host file to customize behavior.
|
|
5
|
+
|
|
6
|
+
## Core behavior
|
|
7
|
+
|
|
8
|
+
- Treat the selected `--cwd` as the normal workspace boundary.
|
|
9
|
+
- Do not read or write outside the workspace unless the user explicitly allows it.
|
|
10
|
+
- Keep the main agent direct and lightweight for ordinary requests.
|
|
11
|
+
- Use skills only when the user asks for that capability or the task clearly needs it.
|
|
12
|
+
- Prefer concise tool use and avoid repeated no-progress tool calls.
|
|
13
|
+
- Do not expose API keys, auth files, or other secrets.
|
|
14
|
+
|
|
15
|
+
## Skill routing
|
|
16
|
+
|
|
17
|
+
- Use `web-search` only for current public information, recent facts, news, sports/results, or explicit web-search requests.
|
|
18
|
+
- Use `subagent-delegation` only for explicit subagent requests, `/subagents` workflows, review/QA delegation, or large independent workstreams.
|
|
19
|
+
- For normal coding, act as the main agent without spawning subagents.
|
|
20
|
+
|
|
21
|
+
## Sandbox expectations
|
|
22
|
+
|
|
23
|
+
- The Docker sandbox mounts only the selected workspace and appv23 state.
|
|
24
|
+
- API keys configured through `/login` live in appv23 sandbox state, not in project files.
|
|
25
|
+
- If a path is blocked or outside scope, ask for explicit authorization instead of guessing.
|
package/bin/appv23.js
CHANGED
|
@@ -11,6 +11,7 @@ const DEFAULT_IMAGE =
|
|
|
11
11
|
const PUBLIC_APPV23_IMAGE_PREFIX = "ghcr.io/htooayelwinict/appv23:";
|
|
12
12
|
const CONTAINER_WORKSPACE = "/workspace";
|
|
13
13
|
const CONTAINER_AGENT_HOME = "/agent-home";
|
|
14
|
+
const IMPORTED_AGENTS_MARKER = "<!-- appv23-sandbox-imported-agents -->";
|
|
14
15
|
const SKIP_IMPORT_NAMES = new Set([
|
|
15
16
|
".DS_Store",
|
|
16
17
|
".git",
|
|
@@ -222,24 +223,68 @@ function buildPullEnv(config, dockerConfig, env = process.env) {
|
|
|
222
223
|
|
|
223
224
|
function prepareSandboxImports(config, runtime = {}) {
|
|
224
225
|
const homeDir = runtime.homeDir || os.homedir();
|
|
226
|
+
const packageRoot = runtime.packageRoot || path.resolve(__dirname, "..");
|
|
225
227
|
fs.mkdirSync(config.agentHome, { recursive: true, mode: 0o700 });
|
|
226
|
-
|
|
227
|
-
|
|
228
|
+
seedHostDefaults(homeDir, packageRoot);
|
|
229
|
+
prepareAgentsFiles(config, homeDir);
|
|
230
|
+
prepareSkills(config, homeDir, packageRoot);
|
|
228
231
|
}
|
|
229
232
|
|
|
230
|
-
function
|
|
231
|
-
|
|
233
|
+
function seedHostDefaults(homeDir, packageRoot) {
|
|
234
|
+
seedHostAgentsFile(homeDir, packageRoot);
|
|
235
|
+
seedHostSkills(homeDir, packageRoot);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function seedHostAgentsFile(homeDir, packageRoot) {
|
|
239
|
+
const source = path.join(packageRoot, "agents", "AGENTS.md");
|
|
240
|
+
const target = path.join(homeDir, ".agents", "AGENTS.md");
|
|
241
|
+
if (!fs.existsSync(source) || fs.existsSync(target)) {
|
|
232
242
|
return;
|
|
233
243
|
}
|
|
244
|
+
copyFileSafe(source, target);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function seedHostSkills(homeDir, packageRoot) {
|
|
248
|
+
const sourceRoot = path.join(packageRoot, "skills");
|
|
249
|
+
const targetRoot = path.join(homeDir, ".agents", "skills");
|
|
250
|
+
if (!fs.existsSync(sourceRoot)) {
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
for (const child of fs.readdirSync(sourceRoot).sort()) {
|
|
254
|
+
const source = path.join(sourceRoot, child);
|
|
255
|
+
if (shouldSkipImport(source)) {
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
258
|
+
const stat = fs.statSync(source);
|
|
259
|
+
if (stat.isDirectory()) {
|
|
260
|
+
const target = path.join(targetRoot, child);
|
|
261
|
+
if (!fs.existsSync(target)) {
|
|
262
|
+
copyTreeSafe(source, target);
|
|
263
|
+
}
|
|
264
|
+
} else if (stat.isFile() && path.extname(source) === ".md") {
|
|
265
|
+
const target = path.join(targetRoot, child);
|
|
266
|
+
if (!fs.existsSync(target)) {
|
|
267
|
+
copyFileSafe(source, target);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function prepareAgentsFiles(config, homeDir = os.homedir()) {
|
|
274
|
+
const sources = collectAgentsFiles(config, homeDir);
|
|
234
275
|
const target = path.join(config.agentHome, "agent", "AGENTS.md");
|
|
276
|
+
if (!sources.length) {
|
|
277
|
+
removeImportedAgentsFile(target);
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
235
280
|
const parts = [
|
|
236
|
-
|
|
281
|
+
IMPORTED_AGENTS_MARKER,
|
|
237
282
|
"# Imported appv23 sandbox instructions",
|
|
238
283
|
"",
|
|
239
|
-
"These instructions were copied into the sandbox from explicit --agents-file arguments.",
|
|
284
|
+
"These instructions were copied into the sandbox from host ~/.agents/AGENTS.md and explicit --agents-file arguments.",
|
|
240
285
|
"",
|
|
241
286
|
];
|
|
242
|
-
for (const source of
|
|
287
|
+
for (const source of sources) {
|
|
243
288
|
const stat = fs.statSync(source);
|
|
244
289
|
if (!stat.isFile()) {
|
|
245
290
|
throw new Error(`agents file is not a file: ${source}`);
|
|
@@ -250,8 +295,42 @@ function prepareAgentsFiles(config) {
|
|
|
250
295
|
fs.writeFileSync(target, parts.join("\n"), { mode: 0o600 });
|
|
251
296
|
}
|
|
252
297
|
|
|
253
|
-
function
|
|
298
|
+
function collectAgentsFiles(config, homeDir) {
|
|
299
|
+
const sources = [];
|
|
300
|
+
const userAgentsFile = path.join(homeDir, ".agents", "AGENTS.md");
|
|
301
|
+
if (fs.existsSync(userAgentsFile)) {
|
|
302
|
+
sources.push(userAgentsFile);
|
|
303
|
+
}
|
|
304
|
+
sources.push(...config.agentsFiles);
|
|
305
|
+
const deduped = [];
|
|
306
|
+
const seen = new Set();
|
|
307
|
+
for (const source of sources) {
|
|
308
|
+
const key = path.resolve(source);
|
|
309
|
+
if (seen.has(key)) {
|
|
310
|
+
continue;
|
|
311
|
+
}
|
|
312
|
+
deduped.push(key);
|
|
313
|
+
seen.add(key);
|
|
314
|
+
}
|
|
315
|
+
return deduped;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function removeImportedAgentsFile(target) {
|
|
319
|
+
if (!fs.existsSync(target)) {
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
const text = fs.readFileSync(target, "utf8");
|
|
323
|
+
if (text.startsWith(IMPORTED_AGENTS_MARKER)) {
|
|
324
|
+
fs.unlinkSync(target);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
function prepareSkills(config, homeDir, packageRoot) {
|
|
254
329
|
const sources = [];
|
|
330
|
+
const bundledSkills = path.join(packageRoot, "skills");
|
|
331
|
+
if (fs.existsSync(bundledSkills)) {
|
|
332
|
+
sources.push(bundledSkills);
|
|
333
|
+
}
|
|
255
334
|
const userSkills = path.join(homeDir, ".agents", "skills");
|
|
256
335
|
if (config.importUserSkills && fs.existsSync(userSkills)) {
|
|
257
336
|
sources.push(userSkills);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@htooayelwinict/appv23",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.2",
|
|
4
4
|
"description": "npx-friendly Docker launcher for the appv23 coding-agent sandbox.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"bin": {
|
|
@@ -8,7 +8,9 @@
|
|
|
8
8
|
"appv23-sandbox": "bin/appv23.js"
|
|
9
9
|
},
|
|
10
10
|
"files": [
|
|
11
|
+
"agents/**/*.md",
|
|
11
12
|
"bin/appv23.js",
|
|
13
|
+
"skills/**/*.md",
|
|
12
14
|
"README.md",
|
|
13
15
|
"package.json"
|
|
14
16
|
],
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: subagent-delegation
|
|
3
|
+
description: Use only when the user explicitly asks to spawn, delegate to, hand off to, or verify work through subagents, child agents, reviewer agents, explorer agents, research agents, web-search agents, or agent-to-agent workflows.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Subagent Delegation
|
|
7
|
+
|
|
8
|
+
Use this skill only when the user explicitly asks for subagents, delegation, handoff, reviewer/explorer/research child agents, `/delegate`, or `/subagents`.
|
|
9
|
+
|
|
10
|
+
## Default behavior
|
|
11
|
+
|
|
12
|
+
- Keep the parent agent normal unless this skill is explicitly triggered.
|
|
13
|
+
- This skill is active for the current user request only. After the parent reports child results, return to normal main-agent behavior.
|
|
14
|
+
- Do not use this skill on a later user request unless that later request explicitly asks for subagents, delegation, handoff, reviewer/explorer/research child agents, `/delegate`, or `/subagents`.
|
|
15
|
+
- Use one delegation wave by default, with at most 3 child agents.
|
|
16
|
+
- Do not launch a second wave unless the user explicitly asks for it after seeing the first child summaries.
|
|
17
|
+
- Do not describe or plan "Wave 2", "Wave 3", or future waves in the same answer.
|
|
18
|
+
- If the task is larger than 3 children can cover, process only the first bounded slice and ask the user whether to continue.
|
|
19
|
+
- Do not let children spawn more subagents.
|
|
20
|
+
- Do not write files unless the user explicitly asks for written artifacts.
|
|
21
|
+
|
|
22
|
+
## Scope control
|
|
23
|
+
|
|
24
|
+
- If the requested scope is vague, ask one concise clarification instead of broadening it.
|
|
25
|
+
- For phrases like "those files" or "those md files", use only exact files already named in the current conversation or visible parent output.
|
|
26
|
+
- If no exact file list is available, ask which files or directory to use.
|
|
27
|
+
- Never convert a vague request into a whole-repo or whole-workspace sweep.
|
|
28
|
+
- Never run whole-workspace file-count or whole-workspace discovery commands such as `find /workspace -type f`.
|
|
29
|
+
- Avoid unbounded discovery commands. Prefer a named directory and a capped listing, for example `find docs -maxdepth 1 -name '*.md' | head -20`.
|
|
30
|
+
- If there are more than 12 candidate files, ask the user to narrow scope before spawning children.
|
|
31
|
+
|
|
32
|
+
## Child task contract
|
|
33
|
+
|
|
34
|
+
Before spawning, the parent must give each child:
|
|
35
|
+
|
|
36
|
+
- A role.
|
|
37
|
+
- Exact paths or one narrow directory.
|
|
38
|
+
- A clear stop condition.
|
|
39
|
+
- A small output budget.
|
|
40
|
+
- A requirement to report status, blockers, and a concise summary.
|
|
41
|
+
|
|
42
|
+
Child instructions must say:
|
|
43
|
+
|
|
44
|
+
- Make one diagnostic attempt after a missing path or failed tool call, then stop and report the blocker.
|
|
45
|
+
- Do not retry the same tool call with the same arguments.
|
|
46
|
+
- Do not call a `glob` tool. This runtime does not provide one; use `ls`, `find`, `read`, or a bounded `bash` command when needed.
|
|
47
|
+
- Do not scan parent directories outside the assigned scope.
|
|
48
|
+
- Do not include full tool traces in the final answer.
|
|
49
|
+
|
|
50
|
+
## Parent reporting
|
|
51
|
+
|
|
52
|
+
After children finish, report only:
|
|
53
|
+
|
|
54
|
+
- Child task id.
|
|
55
|
+
- Child role.
|
|
56
|
+
- Child status.
|
|
57
|
+
- Concise child summary.
|
|
58
|
+
- Any blocker or guardrail status.
|
|
59
|
+
|
|
60
|
+
If a child hits a guardrail or cancellation, report it and stop. Do not retry automatically.
|
|
61
|
+
|
|
62
|
+
Do not compensate for child failure by directly scanning the remaining parent scope. Report the child status and ask the user for the next step.
|
|
63
|
+
|
|
64
|
+
Do not carry this workflow into the next user message. A completed child result is not permission to call more subagent tools later.
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: web-search
|
|
3
|
+
description: Use when the user explicitly asks for web search, current facts, latest news, recent public information, or sports/current-result lookup.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Web Search
|
|
7
|
+
|
|
8
|
+
Use this skill only for web/current-information tasks. Do not use it for local
|
|
9
|
+
repo inspection, code edits, or ordinary reasoning.
|
|
10
|
+
|
|
11
|
+
## Rules
|
|
12
|
+
|
|
13
|
+
- Use `curl` plus Python standard library parsing.
|
|
14
|
+
- Prefer Google News RSS for news/current-result lookup:
|
|
15
|
+
`https://news.google.com/rss/search?q=<encoded-query>&hl=en-US&gl=US&ceid=US:en`
|
|
16
|
+
- Keep output small: show at most 5 useful results.
|
|
17
|
+
- Do not print raw HTML/XML.
|
|
18
|
+
- Do not write files.
|
|
19
|
+
- If live search fails, say exactly what failed and suggest a direct source.
|
|
20
|
+
|
|
21
|
+
## Minimal command pattern
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
python3 - <<'PY'
|
|
25
|
+
import html
|
|
26
|
+
import sys
|
|
27
|
+
import urllib.parse
|
|
28
|
+
import urllib.request
|
|
29
|
+
import xml.etree.ElementTree as ET
|
|
30
|
+
|
|
31
|
+
query = " ".join(sys.argv[1:]).strip() or "latest news"
|
|
32
|
+
url = "https://news.google.com/rss/search?q={}&hl=en-US&gl=US&ceid=US:en".format(
|
|
33
|
+
urllib.parse.quote_plus(query)
|
|
34
|
+
)
|
|
35
|
+
req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0"})
|
|
36
|
+
with urllib.request.urlopen(req, timeout=15) as response:
|
|
37
|
+
data = response.read()
|
|
38
|
+
root = ET.fromstring(data)
|
|
39
|
+
for index, item in enumerate(root.findall("./channel/item")[:5], 1):
|
|
40
|
+
title = html.unescape(item.findtext("title") or "").strip()
|
|
41
|
+
link = html.unescape(item.findtext("link") or "").strip()
|
|
42
|
+
pub_date = html.unescape(item.findtext("pubDate") or "").strip()
|
|
43
|
+
print(f"{index}. {title}")
|
|
44
|
+
if pub_date:
|
|
45
|
+
print(f" date: {pub_date}")
|
|
46
|
+
if link:
|
|
47
|
+
print(f" source: {link}")
|
|
48
|
+
PY
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Replace the query in the command with the user's requested search.
|