typeclaw 0.37.3 → 0.37.5
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 +69 -46
- package/package.json +1 -1
- package/src/agent/compaction.ts +24 -15
- package/src/agent/doctor.ts +6 -1
- package/src/agent/session-origin.ts +101 -173
- package/src/agent/subagents.ts +146 -14
- package/src/agent/system-prompt.ts +46 -48
- package/src/agent/todo/scope.ts +4 -2
- package/src/agent/tools/channel-reply.ts +7 -9
- package/src/bundled-plugins/memory/index.ts +33 -33
- package/src/bundled-plugins/memory/load-memory.ts +92 -35
- package/src/bundled-plugins/memory/slug.ts +19 -0
- package/src/bundled-plugins/memory/turn-dedup.ts +32 -29
- package/src/bundled-plugins/security/policies/private-surface-read.ts +4 -1
- package/src/bundled-plugins/tool-result-cap/README.md +7 -7
- package/src/bundled-plugins/tool-result-cap/index.ts +1 -1
- package/src/channels/adapters/discord-bot.ts +11 -4
- package/src/channels/adapters/github/inbound.ts +68 -43
- package/src/channels/adapters/github/index.ts +57 -9
- package/src/channels/adapters/github/recover-failed-deliveries.ts +270 -0
- package/src/channels/adapters/kakaotalk.ts +5 -1
- package/src/channels/adapters/mention-hints.ts +75 -0
- package/src/channels/adapters/slack-bot.ts +8 -2
- package/src/channels/continuation-willingness.ts +216 -68
- package/src/channels/router.ts +149 -15
- package/src/cli/dreams.ts +2 -2
- package/src/cli/init.ts +41 -7
- package/src/cli/inspect.ts +2 -2
- package/src/cli/logs.ts +2 -2
- package/src/cli/qr.ts +4 -3
- package/src/cli/require-agent-dir.ts +31 -0
- package/src/cli/shell.ts +2 -2
- package/src/cli/stop.ts +2 -2
- package/src/cli/tui.ts +20 -6
- package/src/cli/ui.ts +8 -4
- package/src/container/shared.ts +18 -0
- package/src/container/start.ts +1 -1
- package/src/doctor/checks.ts +145 -2
- package/src/hostd/client.ts +48 -52
- package/src/hostd/daemon.ts +82 -39
- package/src/hostd/paths.ts +22 -2
- package/src/hostd/spawn.ts +7 -0
- package/src/hostd/tailscale.ts +12 -1
- package/src/init/index.ts +35 -8
- package/src/init/kakaotalk-auth.ts +2 -2
- package/src/init/packagejson.ts +2 -2
- package/src/init/run-bun-install.ts +71 -37
- package/src/inspect/transcript-view.ts +15 -2
- package/src/plugin/loader.ts +7 -4
- package/src/portbroker/hostd-client.ts +32 -6
- package/src/sandbox/session-tmp.ts +6 -1
- package/src/secrets/export-claude-credentials-file.ts +2 -2
- package/src/shared/index.ts +4 -0
- package/src/shared/platform.ts +11 -0
- package/src/shared/wsl.ts +139 -0
- package/src/tui/index.ts +26 -8
- package/src/tui/terminal-guard.ts +139 -0
- package/typeclaw.schema.json +2 -2
package/src/agent/subagents.ts
CHANGED
|
@@ -241,6 +241,11 @@ export type InvokeSubagentOptions = {
|
|
|
241
241
|
sessionId: string | undefined
|
|
242
242
|
abort: () => Promise<void>
|
|
243
243
|
}) => void
|
|
244
|
+
// Sink for the subagent's captured final message (a reviewer `<review>` block,
|
|
245
|
+
// a researcher `<report>` block, or the last free-form assistant text).
|
|
246
|
+
// `runSession` owns the capture so the required-block guard can re-prompt
|
|
247
|
+
// before the result settles; `startSubagent` passes this to receive the output.
|
|
248
|
+
onFinalMessageCaptured?: (msg: string) => void
|
|
244
249
|
}
|
|
245
250
|
|
|
246
251
|
export async function invokeSubagent(name: string, options: InvokeSubagentOptions): Promise<void> {
|
|
@@ -261,6 +266,8 @@ export async function invokeSubagent(name: string, options: InvokeSubagentOption
|
|
|
261
266
|
normalizeSubagentSession(await createSessionForSubagent(subagent, sessionOptions))
|
|
262
267
|
let aborted = false
|
|
263
268
|
let drainWatch: SubagentDrainWatch | undefined
|
|
269
|
+
const requiredBlockTag = REQUIRED_FINAL_BLOCK[name]
|
|
270
|
+
const capture = attachFinalMessageCapture(session, requiredBlockTag, options.onFinalMessageCaptured ?? (() => {}))
|
|
264
271
|
if (options.onSessionCreated !== undefined) {
|
|
265
272
|
options.onSessionCreated({
|
|
266
273
|
session,
|
|
@@ -312,6 +319,28 @@ export async function invokeSubagent(name: string, options: InvokeSubagentOption
|
|
|
312
319
|
cancelled: () => aborted,
|
|
313
320
|
})
|
|
314
321
|
}
|
|
322
|
+
// Required-block guard (mirrors the channel empty-response guard): a subagent
|
|
323
|
+
// that owes a result block but ended without one gets a bounded re-prompt to
|
|
324
|
+
// emit it as text, then an honest fallback — never a silent stale-preamble
|
|
325
|
+
// result or a loud failure. Runs strictly after the drain settles so it is a
|
|
326
|
+
// final contract-repair pass, not another research phase; it deliberately
|
|
327
|
+
// does NOT re-run the drain.
|
|
328
|
+
if (requiredBlockTag !== undefined) {
|
|
329
|
+
for (
|
|
330
|
+
let attempt = 1;
|
|
331
|
+
!aborted && !capture.hasRequiredBlock() && attempt <= MAX_REQUIRED_BLOCK_RETRIES;
|
|
332
|
+
attempt++
|
|
333
|
+
) {
|
|
334
|
+
console.warn(
|
|
335
|
+
`[subagent] ${name} required_block_retry attempt=${attempt}/${MAX_REQUIRED_BLOCK_RETRIES} tag=${requiredBlockTag}`,
|
|
336
|
+
)
|
|
337
|
+
await session.prompt(`${renderTurnTimeAnchor()}\n\n${renderRequiredBlockRetryNudge(requiredBlockTag)}`)
|
|
338
|
+
}
|
|
339
|
+
if (!aborted && !capture.hasRequiredBlock()) {
|
|
340
|
+
console.warn(`[subagent] ${name} required_block_fallback tag=${requiredBlockTag}`)
|
|
341
|
+
capture.setSyntheticFinalMessage(renderMissingRequiredBlockFallback(name, requiredBlockTag))
|
|
342
|
+
}
|
|
343
|
+
}
|
|
315
344
|
if (hooks && sessionId !== undefined) {
|
|
316
345
|
await hooks.runSessionIdle({
|
|
317
346
|
sessionId,
|
|
@@ -432,6 +461,9 @@ export function startSubagent(name: string, options: StartSubagentOptions): Star
|
|
|
432
461
|
|
|
433
462
|
const work = invokeSubagent(name, {
|
|
434
463
|
...options,
|
|
464
|
+
onFinalMessageCaptured: (msg) => {
|
|
465
|
+
finalMessage = msg
|
|
466
|
+
},
|
|
435
467
|
onSessionCreated: (event) => {
|
|
436
468
|
handleSettled = true
|
|
437
469
|
abortSession = event.abort
|
|
@@ -439,9 +471,6 @@ export function startSubagent(name: string, options: StartSubagentOptions): Star
|
|
|
439
471
|
if (options.onSession !== undefined) {
|
|
440
472
|
options.onSession(event)
|
|
441
473
|
}
|
|
442
|
-
attachFinalMessageCapture(event.session, (msg) => {
|
|
443
|
-
finalMessage = msg
|
|
444
|
-
})
|
|
445
474
|
},
|
|
446
475
|
})
|
|
447
476
|
.then(() => ({ ok: true as const, ...(finalMessage !== undefined ? { finalMessage } : {}) }))
|
|
@@ -497,22 +526,102 @@ function raceSubagentCompletion(
|
|
|
497
526
|
})
|
|
498
527
|
}
|
|
499
528
|
|
|
500
|
-
//
|
|
501
|
-
//
|
|
502
|
-
//
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
//
|
|
506
|
-
|
|
529
|
+
// The tags a subagent can use to wrap its structured result: the reviewer's
|
|
530
|
+
// `<review>`, the researcher's `<report>`. Fixed literals — never user input —
|
|
531
|
+
// so the per-tag patterns below are injection-safe.
|
|
532
|
+
type FinalBlockTag = 'review' | 'report'
|
|
533
|
+
|
|
534
|
+
// A complete <TAG>...</TAG> block. The block IS the result: same-message
|
|
535
|
+
// preamble/trailing chatter or a later summary turn must not become the captured
|
|
536
|
+
// final message. `[\s\S]` spans newlines (the block is multi-line); non-greedy
|
|
537
|
+
// stops at the first close so an incidental `<TAG>` literal in the wrapped text
|
|
538
|
+
// cannot swallow real content. Global so a message with several blocks yields the
|
|
539
|
+
// last (the revision).
|
|
540
|
+
const FINAL_BLOCK_RE: Readonly<Record<FinalBlockTag, RegExp>> = {
|
|
541
|
+
review: /<review>[\s\S]*?<\/review>/g,
|
|
542
|
+
report: /<report>[\s\S]*?<\/report>/g,
|
|
543
|
+
}
|
|
507
544
|
|
|
508
|
-
function
|
|
509
|
-
const matches = text.match(
|
|
545
|
+
function lastTaggedBlock(text: string, tag: FinalBlockTag): string | null {
|
|
546
|
+
const matches = text.match(FINAL_BLOCK_RE[tag])
|
|
510
547
|
return matches === null ? null : (matches[matches.length - 1] ?? null)
|
|
511
548
|
}
|
|
512
549
|
|
|
513
|
-
|
|
550
|
+
// Subagents whose result IS a REQUIRED tagged block — the parent must receive
|
|
551
|
+
// that block or a loud failure, never a stale earlier turn. The researcher's
|
|
552
|
+
// contract (src/bundled-plugins/researcher/researcher.ts) mandates a closing
|
|
553
|
+
// `<report>` block; when an upstream provider retry loop ends the run on
|
|
554
|
+
// unexecuted `write_report` tool calls, the researcher never emits it, and
|
|
555
|
+
// without this gate the capture would silently return its earlier `<analysis>`
|
|
556
|
+
// preamble as a "successful" result — the production regression this guards.
|
|
557
|
+
// Keyed by the stable bundled-subagent registry name. This is STRICTER than the
|
|
558
|
+
// reviewer's `<review>` (preferred, but falls back to free-form text): only the
|
|
559
|
+
// subagents listed here fail loud when their block is absent.
|
|
560
|
+
const REQUIRED_FINAL_BLOCK: Readonly<Record<string, FinalBlockTag>> = { researcher: 'report' }
|
|
561
|
+
|
|
562
|
+
// Bounded re-prompt budget for the required-block guard, mirroring the channel
|
|
563
|
+
// empty-response guard's MAX_EMPTY_TURN_RETRIES. A subagent that owes a result
|
|
564
|
+
// block but ended without one is nudged at most this many times before the
|
|
565
|
+
// honest fallback is installed.
|
|
566
|
+
const MAX_REQUIRED_BLOCK_RETRIES = 2
|
|
567
|
+
|
|
568
|
+
// The recovery nudge. It MUST forbid tools: the known failure mode is a provider
|
|
569
|
+
// retry loop on the report-writing tool call, so re-driving the tool path would
|
|
570
|
+
// just re-trigger the loop. Asking for the block as plain text is the repair.
|
|
571
|
+
function renderRequiredBlockRetryNudge(tag: FinalBlockTag): string {
|
|
572
|
+
return `---
|
|
573
|
+
**[SYSTEM MESSAGE — not from a human]**
|
|
574
|
+
|
|
575
|
+
Your previous turn ended without the required <${tag}> block. This is an automated runtime recovery signal, not a human message.
|
|
576
|
+
|
|
577
|
+
Emit the final <${tag}>...</${tag}> block NOW as plain assistant text. Do NOT call any tools — in particular do NOT call write_report. Do NOT continue researching or spawn more subagents.
|
|
578
|
+
|
|
579
|
+
If the report file was not successfully written, still emit the block: set <report_file>none</report_file>, <confidence>low</confidence> (explain the report artifact was not completed), and note in <open_questions> that the run should be retried.
|
|
580
|
+
|
|
581
|
+
Output exactly one <${tag}> block and nothing else.`
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
// The terminal graceful fallback when the nudges are exhausted. It fabricates NO
|
|
585
|
+
// findings — only a structured, low-confidence "could not complete" notice — so
|
|
586
|
+
// the parent gets a usable result instead of stale `<analysis>` or a hard error.
|
|
587
|
+
function renderMissingRequiredBlockFallback(name: string, tag: FinalBlockTag): string {
|
|
588
|
+
if (tag === 'report') {
|
|
589
|
+
return `<report>
|
|
590
|
+
<summary>
|
|
591
|
+
The ${name} subagent could not complete a research report in this run: it ended without emitting the required <report> block (a known cause is the report tool not completing). Do not treat any earlier analysis text as findings — rerun the researcher or gather the sources directly if the answer is still needed.
|
|
592
|
+
</summary>
|
|
593
|
+
<report_file>
|
|
594
|
+
none
|
|
595
|
+
</report_file>
|
|
596
|
+
<confidence>
|
|
597
|
+
low — no complete research report artifact was produced.
|
|
598
|
+
</confidence>
|
|
599
|
+
<open_questions>
|
|
600
|
+
The original research request remains unresolved; rerun the ${name} subagent or gather the sources directly.
|
|
601
|
+
</open_questions>
|
|
602
|
+
</report>`
|
|
603
|
+
}
|
|
604
|
+
return `<${tag}>\nThe ${name} subagent ended without emitting the required <${tag}> block and could not recover; rerun it or inspect the transcript.\n</${tag}>`
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
type SubagentCapture = {
|
|
608
|
+
// True once a required-block subagent (researcher) has emitted its `<report>`
|
|
609
|
+
// block; always false for subagents without a required block.
|
|
610
|
+
hasRequiredBlock: () => boolean
|
|
611
|
+
// Install a captured final message directly — used by the required-block guard
|
|
612
|
+
// to set an honest fallback when the block was never emitted, so the parent
|
|
613
|
+
// gets a structured result rather than stale preamble.
|
|
614
|
+
setSyntheticFinalMessage: (msg: string) => void
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
function attachFinalMessageCapture(
|
|
618
|
+
session: AgentSession,
|
|
619
|
+
requiredBlockTag: FinalBlockTag | undefined,
|
|
620
|
+
onFinalMessage: (msg: string) => void,
|
|
621
|
+
): SubagentCapture {
|
|
514
622
|
let lastAssistant: string | null = null
|
|
515
623
|
let lastReview: string | null = null
|
|
624
|
+
let lastRequired: string | null = null
|
|
516
625
|
try {
|
|
517
626
|
session.subscribe((event: unknown) => {
|
|
518
627
|
const ev = event as { type?: string; message?: { role?: string; content?: unknown } }
|
|
@@ -524,7 +633,23 @@ function attachFinalMessageCapture(session: AgentSession, onFinalMessage: (msg:
|
|
|
524
633
|
const text = extractFinalMessageText(ev.message?.content)
|
|
525
634
|
if (text === null) return
|
|
526
635
|
lastAssistant = text
|
|
527
|
-
|
|
636
|
+
|
|
637
|
+
// Required-block contract (researcher): the result IS the block. A turn
|
|
638
|
+
// with text but no block — the `<analysis>` preamble, a process narrative —
|
|
639
|
+
// must NOT become the captured result, so `hasRequiredBlock` stays false and
|
|
640
|
+
// the guard in runSession re-prompts rather than returning stale preamble.
|
|
641
|
+
if (requiredBlockTag !== undefined) {
|
|
642
|
+
const block = lastTaggedBlock(text, requiredBlockTag)
|
|
643
|
+
if (block !== null) {
|
|
644
|
+
lastRequired = block
|
|
645
|
+
onFinalMessage(lastRequired)
|
|
646
|
+
}
|
|
647
|
+
return
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
// Preferred-block contract (reviewer) / free-form: a `<review>` block wins
|
|
651
|
+
// when present; otherwise the last free-form assistant text is the result.
|
|
652
|
+
const review = lastTaggedBlock(text, 'review')
|
|
528
653
|
if (review !== null) lastReview = review
|
|
529
654
|
onFinalMessage(lastReview ?? lastAssistant)
|
|
530
655
|
})
|
|
@@ -532,6 +657,13 @@ function attachFinalMessageCapture(session: AgentSession, onFinalMessage: (msg:
|
|
|
532
657
|
// session.subscribe is a stable upstream API; defensive try is for test
|
|
533
658
|
// doubles that don't implement it.
|
|
534
659
|
}
|
|
660
|
+
return {
|
|
661
|
+
hasRequiredBlock: () => lastRequired !== null,
|
|
662
|
+
setSyntheticFinalMessage: (msg) => {
|
|
663
|
+
lastRequired = msg
|
|
664
|
+
onFinalMessage(msg)
|
|
665
|
+
},
|
|
666
|
+
}
|
|
535
667
|
}
|
|
536
668
|
|
|
537
669
|
function extractFinalMessageText(content: unknown): string | null {
|
|
@@ -14,104 +14,102 @@ const PACKAGE_JSON_INSTALL_RULE =
|
|
|
14
14
|
export function buildDefaultSystemPrompt(subagentRoster: string): string {
|
|
15
15
|
return `You are a general-purpose AI agent running inside TypeClaw.
|
|
16
16
|
|
|
17
|
-
TypeClaw is domain-agnostic
|
|
17
|
+
TypeClaw is domain-agnostic: \`IDENTITY.md\` defines your role, \`SOUL.md\` your voice, and \`AGENTS.md\` your operating manual. This prompt describes only the runtime.
|
|
18
18
|
|
|
19
19
|
## Your agent folder
|
|
20
20
|
|
|
21
|
-
- **IDENTITY.md** *(
|
|
22
|
-
- **SOUL.md** *(
|
|
23
|
-
- **USER.md** *(read on demand)* —
|
|
24
|
-
- **AGENTS.md** *(read on demand)* —
|
|
25
|
-
- **\`memory/topics/\`** *(
|
|
21
|
+
- **IDENTITY.md** *(injected)* — role/scope; edit when responsibilities change.
|
|
22
|
+
- **SOUL.md** *(injected)* — tone/persona; edit rarely.
|
|
23
|
+
- **USER.md** *(read on demand)* — durable facts/preferences about the user.
|
|
24
|
+
- **AGENTS.md** *(read on demand)* — operating manual; read before non-trivial work and re-read whenever process is unclear.
|
|
25
|
+
- **\`memory/topics/\`** *(injected, READ-ONLY)* — long-term memory shards owned by dreaming; never edit memory shards directly. Surface memorable facts in your reply or let memory-logger write streams.
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
For durable updates, route them here — never to memory shards:
|
|
28
28
|
|
|
29
|
-
-
|
|
30
|
-
-
|
|
31
|
-
-
|
|
32
|
-
-
|
|
33
|
-
-
|
|
29
|
+
- role, function, scope of work → IDENTITY.md
|
|
30
|
+
- voice, tone, register, language preferences, persona → SOUL.md
|
|
31
|
+
- facts about the user and durable preferences → USER.md
|
|
32
|
+
- working conventions, repeatable procedures, "always do X" rules, future-you guidance → AGENTS.md
|
|
33
|
+
- one-off conversation context → no file; \`memory/streams/\` captures it automatically
|
|
34
34
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
**Edit discipline.** Prefer rewriting in place to growing files. SOUL.md should stay short — a paragraph or two; if it's drifting past a screen, you're using it as a scratchpad and the model that reads it will start ignoring the back half. IDENTITY.md is similar — a few lines of who you are, not a résumé. AGENTS.md is the one allowed to grow. Don't rewrite SOUL.md on the first piece of tone feedback in a session — wait until the user repeats a preference or asks you directly to update it; a single off-day request isn't a durable change.
|
|
35
|
+
If it describes how you sound, use SOUL.md; how you work, AGENTS.md. **Edit discipline.** Prefer rewriting in place. SOUL.md should stay short, as should IDENTITY.md; AGENTS.md may grow. Do not treat one-off tone feedback as durable; a single off-day request isn't a durable change unless repeated or explicitly requested.
|
|
38
36
|
|
|
39
37
|
## Your workspace
|
|
40
38
|
|
|
41
|
-
- **\`workspace/\`** —
|
|
42
|
-
- **\`public/\`** —
|
|
43
|
-
- **\`sessions/\`** —
|
|
44
|
-
- **\`memory/streams/\`** *(not injected
|
|
45
|
-
- **\`memory/skills/\`** —
|
|
39
|
+
- **\`workspace/\`** — free-write drafts/artifacts. Do not write agent-folder root unless asked.
|
|
40
|
+
- **\`public/\`** — guest-visible sharing area. If the role is untrusted or \`workspace/\` writes are denied, use \`public/\`.
|
|
41
|
+
- **\`sessions/\`** — runtime-managed transcripts; don't write.
|
|
42
|
+
- **\`memory/streams/\`** *(not injected; use \`memory_search\`)* — runtime-owned dated observations.
|
|
43
|
+
- **\`memory/skills/\`** — auto-loaded dreaming skills; don't write directly.
|
|
46
44
|
- **\`.agents/skills/\`** — user-installed skills.
|
|
47
45
|
|
|
48
46
|
## Configuration
|
|
49
47
|
|
|
50
48
|
- **\`typeclaw.json\`** — runtime config. Read when needed.
|
|
51
|
-
- **\`secrets.json\`** — canonical
|
|
49
|
+
- **\`secrets.json\`** — canonical gitignored secrets store. \`.env\` is legacy/env override. Never echo, log, or commit either file's values; hand-edit only when explicitly rotating credentials.
|
|
52
50
|
|
|
53
51
|
## Execution bias
|
|
54
52
|
|
|
55
|
-
|
|
53
|
+
Start work in the same turn when the next action is clear; do not answer with only a plan. For multi-step work, give one short progress update, not narration.
|
|
56
54
|
|
|
57
55
|
## Tracking your work
|
|
58
56
|
|
|
59
|
-
For
|
|
57
|
+
For multi-step or long-running tasks, use \`todo_write\` when you start and mark items complete as you finish; incomplete items let the runtime resume after interruptions. Use \`todo_clear\` only to abandon remaining work. Single-step requests need no todo list.
|
|
60
58
|
|
|
61
59
|
## Tool-call style
|
|
62
60
|
|
|
63
|
-
Do not narrate routine
|
|
61
|
+
Do not narrate routine low-risk tools. Narrate only for multi-step context, risky/irreversible actions, external sends, or when asked.
|
|
64
62
|
|
|
65
63
|
## Delivering reports and documents
|
|
66
64
|
|
|
67
|
-
When the user asks for a *report*, *document*, *brief*, *PDF*, or asks you to *send/show/attach/export* a generated result — anything
|
|
65
|
+
When the user asks for a *report*, *document*, *brief*, *PDF*, or asks you to *send/show/attach/export* a generated result — anything a human would download, print, or forward — produce a polished file, not a chat wall or substance-dropping summary. A summary is a pointer to the deliverable, never the deliverable itself; when the user asked for the report, ship the report.
|
|
68
66
|
|
|
69
|
-
|
|
67
|
+
For Markdown-to-PDF, use the bundled \`typeclaw-render-pdf\` skill; it is the supported path and renders headings, lists, and tables. Never hand-roll PDFs with jsPDF, pdfkit, canvas text dumps, raw headless-browser prints, or ReportLab: they often emit raw markup and mojibake for non-Latin text. For Korean/Japanese/Chinese, follow the skill's CJK font guidance and do not ship tofu boxes. Short answers/snippets/explanations can stay inline.
|
|
70
68
|
|
|
71
69
|
## Long-running and interactive shell work
|
|
72
70
|
|
|
73
|
-
Foreground \`bash\` blocks
|
|
71
|
+
Foreground \`bash\` blocks until exit. Run minutes-long or input-waiting programs (dev servers, REPLs, watchers, \`docker compose up\`, installers) detached in \`tmux\`:
|
|
74
72
|
|
|
75
73
|
- Start: \`tmux new-session -d -s <name> "<cmd>"\`
|
|
76
|
-
- Observe: \`tmux capture-pane -t <name> -p\`
|
|
77
|
-
- Drive: \`tmux send-keys -t <name> "<input>" Enter\`
|
|
74
|
+
- Observe: \`tmux capture-pane -t <name> -p\`
|
|
75
|
+
- Drive: \`tmux send-keys -t <name> "<input>" Enter\`
|
|
78
76
|
- Stop: \`tmux kill-session -t <name>\`
|
|
79
77
|
|
|
80
|
-
Use
|
|
78
|
+
Use tmux only for work that belongs in your session. Delegate self-contained long work (builds, tests, installs, batches) to \`operator\`.
|
|
81
79
|
|
|
82
80
|
## Version control
|
|
83
81
|
|
|
84
|
-
Your agent folder is a git repository, but **it is your own private backup repo — not a software project you develop.**
|
|
82
|
+
Your agent folder is a git repository, but **it is your own private backup repo — not a software project you develop.** TypeClaw snapshots identity files, \`sessions/\`, and \`memory/\` there over time. It normally has no remote, nothing is pushed, and it is **not a checkout of any project**. Commits here save your state, not a codebase contribution.
|
|
85
83
|
|
|
86
|
-
|
|
84
|
+
For project work (bug, feature, PR), clone the project repo into \`/tmp/<repo>\`, work there, and open the PR from that clone with \`gh\`. Never \`git init\`, add a remote, or push your agent folder as the project. If there is no remote or you cannot find the repo, ask the user where it lives. Your agent folder is where you live; the clone is where you work.
|
|
87
85
|
|
|
88
86
|
Commits to your agent folder (your own state):
|
|
89
87
|
|
|
90
|
-
- Commit
|
|
91
|
-
- Use \`git add <paths
|
|
92
|
-
- Never commit \`secrets.json\`, \`.env\`, or
|
|
88
|
+
- Commit files you created/edited/deleted before declaring done. One logical change = one commit.
|
|
89
|
+
- Use \`git add <paths>\`, not \`git add -A\`. Use imperative commit messages; explain why if non-obvious.
|
|
90
|
+
- Never commit \`secrets.json\`, \`.env\`, or \`workspace/\`. Do not manually add runtime-managed \`sessions/\` or \`memory/\`.
|
|
93
91
|
- ${PACKAGE_JSON_INSTALL_RULE}
|
|
94
|
-
- Never \`git push\`, \`git reset --hard\`, \`git rebase\`, or rewrite remote history in this folder unless
|
|
92
|
+
- Never \`git push\`, \`git reset --hard\`, \`git rebase\`, or rewrite remote history in this folder unless explicitly asked. Pushing a separate project clone for a requested PR is fine.
|
|
95
93
|
|
|
96
94
|
## How to behave
|
|
97
95
|
|
|
98
|
-
- Match the user's register. If SOUL.md specifies a voice, use it
|
|
99
|
-
-
|
|
100
|
-
- Answer questions
|
|
101
|
-
-
|
|
96
|
+
- Match the user's register. If SOUL.md specifies a voice, use it; otherwise be concise and direct.
|
|
97
|
+
- Read files/memory before guessing. Follow AGENTS.md under your IDENTITY.md role; suggest AGENTS.md additions for repeatable gaps.
|
|
98
|
+
- Answer questions, do work, and avoid over-explaining unless asked.
|
|
99
|
+
- Ask one clarifying question only when ambiguity would materially change the work; otherwise choose a reasonable default.
|
|
102
100
|
- Never suppress errors to make things "work", and never fabricate results. Report failures clearly.
|
|
103
101
|
|
|
104
102
|
## Subagent orchestration
|
|
105
103
|
|
|
106
|
-
Delegate focused work
|
|
104
|
+
Delegate focused work with \`spawn_subagent\`, \`subagent_output\`, and \`subagent_cancel\`. Each subagent has its own context/tools; re-read the tool description before delegating. Briefly: ${subagentRoster}.
|
|
107
105
|
|
|
108
|
-
|
|
106
|
+
Pick one of three modes:
|
|
109
107
|
|
|
110
|
-
**Mode A — Research fan-out.**
|
|
108
|
+
**Mode A — Research fan-out.** Broad search: spawn 2-5 \`explorer\`/\`scout\` workers in parallel with \`run_in_background: true\`, end your response, then collect each completion once via \`subagent_output\`. Use \`scout\` for narrow lookups; \`researcher\` for decomposed, multi-source, cross-validated synthesis. When the user *explicitly* says "research"/"investigate" (or equivalent), you MUST spawn \`researcher\` — answering from training memory or a single inline \`web_search\` does not satisfy the request, even if you think you know the answer. (Fanning out \`scout\`/\`explorer\` underneath is fine, but it does not replace \`researcher\`.)
|
|
111
109
|
|
|
112
|
-
**Mode B — Delegate-and-converse.**
|
|
110
|
+
**Mode B — Delegate-and-converse.** For >~30s side-effectful/noisy work (installs, builds, \`docker\`, scrapes, long tests, multi-host loops, fetch-and-synthesize chains), spawn one background subagent and stay responsive: \`operator\` for side effects, \`scout\` quick lookup, \`researcher\` deep investigation, \`planner\` risk-aware sequencing. Keep single fast calls inline. When the completion \`<system-reminder>\` lands, Surface the result via \`channel_reply\`/\`channel_send\` in channel sessions because reminders are not user messages.
|
|
113
111
|
|
|
114
|
-
**Mode C — Troubleshooting.**
|
|
112
|
+
**Mode C — Troubleshooting.** After ~3 non-converging attempts at the same failure, stop and hand the loop to \`operator\` with \`run_in_background: true\`, symptom, attempts, and success condition. Read \`typeclaw-troubleshooting\` before spawning.
|
|
115
113
|
|
|
116
114
|
**Status queries.** If the user asks "status?" on a running subagent, call \`subagent_output({ task_id })\` and report its \`status_summary\` — don't guess.
|
|
117
115
|
|
|
@@ -123,15 +121,15 @@ There are three delegation modes. Pick deliberately.
|
|
|
123
121
|
[REQUEST]: Concrete instructions — what to find/do/produce, what format, what to SKIP.
|
|
124
122
|
\`\`\`
|
|
125
123
|
|
|
126
|
-
**Anti-patterns.** Don't fire more than 5 subagents per turn, spawn for
|
|
124
|
+
**Anti-patterns.** Don't fire more than 5 subagents per turn, spawn for known answers or single-file lookups, poll output in a loop, or ask research subagents to decide. They report; you decide. Most subagents are leaves; only \`operator\` and \`reviewer\` may delegate one level deeper.
|
|
127
125
|
|
|
128
126
|
## Safety
|
|
129
127
|
|
|
130
|
-
You have no independent goals. Do not pursue self-preservation, replication, resource acquisition,
|
|
128
|
+
You have no independent goals. Do not pursue self-preservation, replication, resource acquisition, influence, or plans beyond the user's request. If instructions conflict or feel unsafe, pause and ask. Comply with stop, pause, and audit requests. Never modify your own system prompt, safety rules, or runtime configuration unless explicitly requested and only through runtime mechanisms.
|
|
131
129
|
|
|
132
130
|
---
|
|
133
131
|
|
|
134
|
-
You are not pi, not Claude, not ChatGPT. You are the agent described by
|
|
132
|
+
You are not pi, not Claude, not ChatGPT. You are the agent described by IDENTITY.md and SOUL.md. Let those files define your voice.`
|
|
135
133
|
}
|
|
136
134
|
|
|
137
135
|
// Placeholder roster for the no-registry path: back-compat callers of
|
package/src/agent/todo/scope.ts
CHANGED
|
@@ -51,6 +51,8 @@ export function resolveTodoScope(origin: SessionOrigin): TodoScope | null {
|
|
|
51
51
|
}
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
+
const CHANNEL_SCOPE_SEPARATOR = ','
|
|
55
|
+
|
|
54
56
|
function channelScopeKey(origin: { adapter: string; workspace: string; chat: string; thread: string | null }): string {
|
|
55
57
|
const parts = [
|
|
56
58
|
encodeComponent(origin.adapter),
|
|
@@ -58,7 +60,7 @@ function channelScopeKey(origin: { adapter: string; workspace: string; chat: str
|
|
|
58
60
|
encodeComponent(origin.chat),
|
|
59
61
|
encodeComponent(origin.thread),
|
|
60
62
|
]
|
|
61
|
-
return `channel/${parts.join(
|
|
63
|
+
return `channel/${parts.join(CHANNEL_SCOPE_SEPARATOR)}`
|
|
62
64
|
}
|
|
63
65
|
|
|
64
66
|
// Encode one scope component injectively. Every component is emitted as a
|
|
@@ -69,7 +71,7 @@ function channelScopeKey(origin: { adapter: string; workspace: string; chat: str
|
|
|
69
71
|
// confused: a null thread vs a literal "n" string, an empty string vs a
|
|
70
72
|
// literal "_empty" string, and any value vs another whose unsafe chars happen
|
|
71
73
|
// to map together. `encodeURIComponent` is itself injective and never emits
|
|
72
|
-
// `/` or
|
|
74
|
+
// `/` or `,`, so the joined key is both a single filesystem-safe path segment
|
|
73
75
|
// and a collision-free identity for the conversation whose todo file it names.
|
|
74
76
|
function encodeComponent(value: string | null): string {
|
|
75
77
|
if (value === null) return 'n'
|
|
@@ -78,15 +78,13 @@ export function createChannelReplyTool({
|
|
|
78
78
|
},
|
|
79
79
|
),
|
|
80
80
|
),
|
|
81
|
-
continue: Type.
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
}),
|
|
89
|
-
),
|
|
81
|
+
continue: Type.Boolean({
|
|
82
|
+
description:
|
|
83
|
+
'REQUIRED on every channel_reply — you must explicitly choose, there is no default. Set `true` when this reply is a mid-turn status update (e.g. "working on it…") and you still have work to do THIS turn — fetching data, running a tool, spawning a subagent, then replying again; `true` keeps the turn alive so that follow-up actually runs. ' +
|
|
84
|
+
'Set `false` when this reply is your final message for the turn (the common case). ' +
|
|
85
|
+
'This choice is mandatory precisely because a missing value used to default to ending the turn silently: a successful reply ends the turn unless `continue` is `true`, so a `false` on an ack you meant to keep working from drops the work you promised. ' +
|
|
86
|
+
'Do not set `true` just to seem responsive; only when genuine multi-step work follows in the same turn.',
|
|
87
|
+
}),
|
|
90
88
|
resolve_review_thread: Type.Optional(
|
|
91
89
|
Type.Boolean({
|
|
92
90
|
description:
|