typeclaw 0.2.0 → 0.3.0

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.
@@ -48,37 +48,51 @@ const addSub = defineCommand({
48
48
  },
49
49
  async run({ args }) {
50
50
  const cwd = ensureAgentDir()
51
- const providerId = await resolveProviderForAdd(args.provider)
52
- const provider = KNOWN_PROVIDERS[providerId]
53
-
54
- intro(`Adding provider: ${provider.name}`)
55
-
56
- const method = await resolveAuthMethod(provider, args)
57
- if (method === 'oauth') {
58
- const result = await runOAuthLogin(cwd, providerId)
59
- if (!result.ok) {
60
- console.error(errorLine(`OAuth login failed: ${result.reason}`))
61
- process.exit(1)
62
- }
63
- done({
64
- title: c.green(`Logged in to ${provider.name}.`),
65
- hints: nextStepHints({ credentialChanged: true }),
66
- })
67
- return
68
- }
69
-
70
- const credential = await resolveApiKeyInputs(provider, args)
71
- const result = addProvider(cwd, providerId, credential)
51
+ const result = await runProviderAddFlow(cwd, args)
72
52
  if (!result.ok) {
73
53
  console.error(errorLine(result.reason))
74
54
  process.exit(1)
75
55
  }
56
+ },
57
+ })
58
+
59
+ export type ProviderAddFlowArgs = {
60
+ provider?: string | undefined
61
+ key?: string | undefined
62
+ env?: string | undefined
63
+ oauth?: boolean | undefined
64
+ }
65
+
66
+ export type ProviderAddFlowResult =
67
+ | { ok: true; providerId: KnownProviderId; method: 'api-key' | 'oauth' }
68
+ | { ok: false; reason: string }
69
+
70
+ export async function runProviderAddFlow(cwd: string, args: ProviderAddFlowArgs): Promise<ProviderAddFlowResult> {
71
+ const providerId = await resolveProviderForAdd(args.provider)
72
+ const provider = KNOWN_PROVIDERS[providerId]
73
+
74
+ intro(`Adding provider: ${provider.name}`)
75
+
76
+ const method = await resolveAuthMethod(provider, args)
77
+ if (method === 'oauth') {
78
+ const result = await runOAuthLogin(cwd, providerId)
79
+ if (!result.ok) return { ok: false, reason: `OAuth login failed: ${result.reason}` }
76
80
  done({
77
- title: c.green(`Added ${provider.name} credentials to secrets.json.`),
81
+ title: c.green(`Logged in to ${provider.name}.`),
78
82
  hints: nextStepHints({ credentialChanged: true }),
79
83
  })
80
- },
81
- })
84
+ return { ok: true, providerId, method: 'oauth' }
85
+ }
86
+
87
+ const credential = await resolveApiKeyInputs(provider, args)
88
+ const result = addProvider(cwd, providerId, credential)
89
+ if (!result.ok) return { ok: false, reason: result.reason }
90
+ done({
91
+ title: c.green(`Added ${provider.name} credentials to secrets.json.`),
92
+ hints: nextStepHints({ credentialChanged: true }),
93
+ })
94
+ return { ok: true, providerId, method: 'api-key' }
95
+ }
82
96
 
83
97
  const setSub = defineCommand({
84
98
  meta: {
@@ -11,7 +11,7 @@ import {
11
11
  type KnownModelRef,
12
12
  type KnownProviderId,
13
13
  } from './providers'
14
- import { isProviderConfigured } from './providers-mutation'
14
+ import { isProviderConfigured, listConfiguredProviders } from './providers-mutation'
15
15
 
16
16
  const CONFIG_FILE = 'typeclaw.json'
17
17
 
@@ -51,6 +51,25 @@ export function listAvailableModelRefs(): KnownModelRef[] {
51
51
  return listKnownModelRefs()
52
52
  }
53
53
 
54
+ // Subset of `listAvailableModelRefs()` filtered to providers with a usable
55
+ // credential in this agent folder — either a `secrets.json#providers.<id>`
56
+ // entry (api-key OR oauth) or a credential resolvable from the process env
57
+ // via the provider's canonical env-var name. Used by `typeclaw model set`'s
58
+ // interactive picker so users only see models they can actually run; the
59
+ // CLI surfaces an explicit "add provider" sentinel when the result is empty
60
+ // or when the user wants to wire a new one.
61
+ //
62
+ // Ordering preserves `listKnownModelRefs()` (provider-table declaration
63
+ // order, then per-provider model order) so the picker reads stably across
64
+ // invocations.
65
+ export function listRegisteredModelRefs(cwd: string, env: NodeJS.ProcessEnv = process.env): KnownModelRef[] {
66
+ const registered = new Set<KnownProviderId>()
67
+ for (const entry of listConfiguredProviders(cwd, env)) {
68
+ if (entry.known) registered.add(entry.id as KnownProviderId)
69
+ }
70
+ return listKnownModelRefs().filter((ref) => registered.has(providerForModelRef(ref)))
71
+ }
72
+
54
73
  export function isKnownModelRef(value: string): value is KnownModelRef {
55
74
  return (listKnownModelRefs() as ReadonlyArray<string>).includes(value)
56
75
  }
@@ -33,6 +33,7 @@ export type BuildChannelSessionFactoryDeps = {
33
33
  // their inbound messages came from.
34
34
  getChannelRouter: () => ChannelRouter
35
35
  containerName?: string
36
+ runtimeVersion?: string
36
37
  // When set, rehydrating a session JSONL caps oversized tool results in the
37
38
  // file before pi-coding-agent reads it. `null` disables the load-time pass
38
39
  // (tool-result-cap.enabled=false in config, or no plugin block at all).
@@ -105,6 +106,7 @@ export function buildChannelSessionFactory(deps: BuildChannelSessionFactoryDeps)
105
106
  }
106
107
  : {}),
107
108
  ...(deps.containerName !== undefined ? { containerName: deps.containerName } : {}),
109
+ ...(deps.runtimeVersion !== undefined ? { runtimeVersion: deps.runtimeVersion } : {}),
108
110
  ...(deps.permissions !== undefined ? { permissions: deps.permissions } : {}),
109
111
  })
110
112
 
package/src/run/index.ts CHANGED
@@ -24,6 +24,7 @@ import {
24
24
  loadCron as loadCronDefault,
25
25
  type Scheduler,
26
26
  } from '@/cron'
27
+ import { CLI_VERSION } from '@/init/cli-version'
27
28
  import { loadPlugins, type LoadPluginsResult, pluginCronJobs, type PluginRegistry, summarizeLoaded } from '@/plugin'
28
29
  import { createContainerBroker, publishForwardResult } from '@/portbroker'
29
30
  import { ReloadRegistry } from '@/reload'
@@ -90,6 +91,7 @@ export async function startAgent({
90
91
  // which is what we want, since there is no host daemon to honor it anyway.
91
92
  const containerName = process.env.TYPECLAW_CONTAINER_NAME
92
93
  const containerNameOpt = containerName !== undefined ? { containerName } : {}
94
+ const runtimeVersionOpt = { runtimeVersion: CLI_VERSION }
93
95
  const tuiToken = process.env.TYPECLAW_TUI_TOKEN
94
96
  const tuiTokenOpt = tuiToken !== undefined && tuiToken !== '' ? { tuiToken } : {}
95
97
 
@@ -155,6 +157,7 @@ export async function startAgent({
155
157
  rehydrateCapOptions: resolveCapOptionsFromConfig(pluginConfigsByName['tool-result-cap']),
156
158
  permissions: pluginsLoaded.permissions,
157
159
  ...containerNameOpt,
160
+ ...runtimeVersionOpt,
158
161
  }),
159
162
  permissions: pluginsLoaded.permissions,
160
163
  claimHandler: claimController.claimHandler,
@@ -198,6 +201,7 @@ export async function startAgent({
198
201
  ...(entry.pluginSubagent.toolResultBudget !== undefined
199
202
  ? { toolResultBudget: entry.pluginSubagent.toolResultBudget }
200
203
  : {}),
204
+ ...runtimeVersionOpt,
201
205
  })
202
206
  return {
203
207
  ...created,
@@ -267,6 +271,7 @@ export async function startAgent({
267
271
  }
268
272
  : {}),
269
273
  ...containerNameOpt,
274
+ ...runtimeVersionOpt,
270
275
  })
271
276
  return {
272
277
  prompt: (text) => session.prompt(text),
@@ -363,6 +368,7 @@ export async function startAgent({
363
368
  pluginRuntime,
364
369
  claimController,
365
370
  ...containerNameOpt,
371
+ ...runtimeVersionOpt,
366
372
  ...tuiTokenOpt,
367
373
  ...containerBrokerOpt,
368
374
  }).start()
@@ -38,6 +38,7 @@ export type ServerOptions = {
38
38
  agentDir?: string
39
39
  pluginRuntime?: PluginRuntime
40
40
  containerName?: string
41
+ runtimeVersion?: string
41
42
  tuiToken?: string
42
43
  // Optional in-process portbroker handler. When provided, requests to the
43
44
  // /portbroker WS path are routed to it instead of being treated as TUI
@@ -108,6 +109,7 @@ export function createServer({
108
109
  agentDir,
109
110
  pluginRuntime,
110
111
  containerName,
112
+ runtimeVersion,
111
113
  tuiToken,
112
114
  containerBroker,
113
115
  logger = consoleLogger,
@@ -167,6 +169,7 @@ export function createServer({
167
169
  ...(channelRouter ? { channelRouter } : {}),
168
170
  ...(pluginsWiring ? { plugins: pluginsWiring } : {}),
169
171
  ...(containerName !== undefined ? { containerName } : {}),
172
+ ...(runtimeVersion !== undefined ? { runtimeVersion } : {}),
170
173
  })
171
174
  const session = 'session' in result ? result.session : result
172
175
  const dispose = 'session' in result && result.dispose ? result.dispose : async () => {}
@@ -1,11 +1,11 @@
1
1
  ---
2
2
  name: typeclaw-memory
3
- description: Use this skill whenever the user asks what you remember, what you forgot, what you dreamed, why a fact is or isn't in your memory, when memory consolidation happens, or whenever you are about to read or write `MEMORY.md`, anything under `memory/`, or `memory/skills/`. Triggers include "what do you remember", "do you remember X", "forget that", "what did you dream", "when do you dream next", "why did you forget X", "edit MEMORY.md", "add to memory", "your daily streams", "memory-logger", "dreaming", "muscle memory", or any mention of `memory.idleMs` / `memory.dreaming.schedule` in `typeclaw.json`. Read it before you touch any memory file — `MEMORY.md` and `memory/yyyy-MM-dd.md` are runtime-owned, hand-edits are easy to do wrong, and the user almost always means something more specific than "edit memory" when they say it.
3
+ description: Use this skill whenever the user asks what you remember, what you forgot, what you dreamed, why a fact is or isn't in your memory, when memory consolidation happens, or whenever you are about to read or write `MEMORY.md`, anything under `memory/`, or `memory/skills/`. Triggers include "what do you remember", "do you remember X", "forget that", "what did you dream", "when do you dream next", "why did you forget X", "edit MEMORY.md", "add to memory", "your daily streams", "memory-logger", "dreaming", "muscle memory", or any mention of `memory.idleMs` / `memory.dreaming.schedule` in `typeclaw.json`. Read it before you touch any memory file — `MEMORY.md` and `memory/yyyy-MM-dd.jsonl` are runtime-owned, hand-edits are easy to do wrong, and the user almost always means something more specific than "edit memory" when they say it.
4
4
  ---
5
5
 
6
6
  # typeclaw-memory
7
7
 
8
- You have a two-stage memory system, owned by the bundled `memory` plugin (auto-loaded on every TypeClaw agent — there is no `plugins[]` entry to add and no opt-out). Daily observations flow into `memory/yyyy-MM-dd.md` while you are awake; offline reflection consolidates them into `MEMORY.md` and may distill repeated procedures into muscle-memory skills under `memory/skills/`. Both stages are run by subagents the runtime spawns on its own — not tools you call directly.
8
+ You have a two-stage memory system, owned by the bundled `memory` plugin (auto-loaded on every TypeClaw agent — there is no `plugins[]` entry to add and no opt-out). Daily observations flow into `memory/yyyy-MM-dd.jsonl` while you are awake; offline reflection consolidates them into `MEMORY.md` and may distill repeated procedures into muscle-memory skills under `memory/skills/`. Both stages are run by subagents the runtime spawns on its own — not tools you call directly.
9
9
 
10
10
  This skill exists so you can answer the user's questions about your own memory honestly and so you do not corrupt it by hand-editing.
11
11
 
@@ -18,7 +18,7 @@ After every prompt completes, the runtime fires the `session.idle` hook. The mem
18
18
  The memory-logger reads:
19
19
 
20
20
  1. `MEMORY.md` (long-term memory)
21
- 2. The current `memory/yyyy-MM-dd.md` daily stream
21
+ 2. The current `memory/yyyy-MM-dd.jsonl` daily stream
22
22
  3. The transcript of the parent session past a watermark (the `entry=` value of the last fragment or watermark marker for that session)
23
23
 
24
24
  It writes zero or more **fragments** to today's stream, plus a watermark marker so the next run knows where to resume. It writes nothing else, and it cannot run shell commands or edit existing content (its only tools are `read` and a custom `append`-only file tool — append never truncates, and a leading `\n` is auto-inserted if the existing file did not end in one).
@@ -43,9 +43,9 @@ The dreaming subagent runs on cron, configured under `memory.dreaming.schedule`
43
43
  When dreaming fires, it reads:
44
44
 
45
45
  1. `MEMORY.md`
46
- 2. The **undreamed tail** of every `memory/yyyy-MM-dd.md` (the runtime tells it the exact line rangeearlier lines are already consolidated into `MEMORY.md` and must NOT be re-read)
46
+ 2. The **undreamed fragments** of every `memory/yyyy-MM-dd.jsonl` (the runtime tells it which fragment ids are new fragments whose ids are already in `memory/.dreaming-state.json#dreamedThrough[date].dreamedIds` have been consolidated and must NOT be re-cited)
47
47
 
48
- It rewrites `MEMORY.md` with the merged result, advances the per-day watermark in `memory/.dreaming-state.json`, optionally writes muscle-memory skills under `memory/skills/<name>/SKILL.md`, then commits the snapshot with a message shaped like `dream: <summary> <emoji>` — e.g. `dream: 3 fragments + new skill 'pr-review' 🔮`. The summary is derived from the staged diff (line additions in daily streams, newly-added skills, etc.), and the emoji is a random pick from a small thematic pool. After the commit, the runtime sets the `skip-worktree` index flag on the tracked memory artifacts so the user's `git status` and `git diff` stay clean. The flag is cleared and re-applied around every commit.
48
+ It rewrites `MEMORY.md` with the merged result, advances the per-day dreamed-id set in `memory/.dreaming-state.json`, optionally writes muscle-memory skills under `memory/skills/<name>/SKILL.md`, **compacts the touched daily streams** (drops superseded watermarks per source and fragments that are in `dreamedIds` but not cited from `MEMORY.md`), then commits the snapshot with a message shaped like `dream: <summary> <emoji>` — e.g. `dream: 3 fragments + new skill 'pr-review' 🔮`. The summary is derived from the staged diff (line additions in daily streams, newly-added skills, etc.), and the emoji is a random pick from a small thematic pool. After the commit, the runtime sets the `skip-worktree` index flag on the tracked memory artifacts so the user's `git status` and `git diff` stay clean. The flag is cleared and re-applied around every commit.
49
49
 
50
50
  The dreaming subagent has only three tools: `read`, `write`, `ls`. No `bash`. No `edit`. It cannot run shell commands.
51
51
 
@@ -58,17 +58,17 @@ The dreaming subagent has only three tools: `read`, `write`, `ls`. No `bash`. No
58
58
  <conclusion paragraph in dreaming's own words>
59
59
 
60
60
  fragments:
61
- - memory/yyyy-MM-dd:<line>-<line>
62
- - memory/yyyy-MM-dd:<line>-<line>
61
+ - memory/yyyy-MM-dd#<fragment-id>
62
+ - memory/yyyy-MM-dd#<fragment-id>
63
63
 
64
64
  ## <topic>
65
65
  <conclusion paragraph>
66
66
 
67
67
  fragments:
68
- - memory/yyyy-MM-dd:<line>-<line>
68
+ - memory/yyyy-MM-dd#<fragment-id>
69
69
  ```
70
70
 
71
- The first line is always `# Memory`. Topics are level-2 headings. Every topic cites the source fragments by `memory/yyyy-MM-dd:<line>-<line>` so any claim is traceable back to the daily stream entry that justified it.
71
+ The first line is always `# Memory`. Topics are level-2 headings. Every topic cites the source fragments by `memory/yyyy-MM-dd#<uuidv7>` (the full id from the fragment event's `id` field) so any claim is traceable back to the daily stream entry that justified it. Citations are id-based, not line-based, so daily streams can be compacted between dreaming runs without invalidating prior references.
72
72
 
73
73
  If the undreamed tails contain only watermarks, or every new fragment is already represented in `MEMORY.md`, dreaming **does nothing** and exits without writing. The watermark advances either way. "No-op dreaming" is a normal outcome, not a failure.
74
74
 
@@ -77,7 +77,7 @@ If the undreamed tails contain only watermarks, or every new fragment is already
77
77
  Core's `createResourceLoader` appends a `# Memory` section as the LAST block of your system prompt (after `gitNudge`) by calling `loadMemory`. It is pinned to the cache-suffix end so growth in the daily stream invalidates only the memory section itself, not the skills/tools/history above. The section contains:
78
78
 
79
79
  - `MEMORY.md` (truncated to 12 KB; if larger, the rest is dropped with a `[truncated]` marker)
80
- - The **undreamed tails** of each `memory/yyyy-MM-dd.md`, with bare watermark lines stripped (they are bookkeeping for the memory-logger, no signal for you)
80
+ - The **undreamed tails** of each `memory/yyyy-MM-dd.jsonl`, with bare watermark lines stripped (they are bookkeeping for the memory-logger, no signal for you)
81
81
 
82
82
  Already-consolidated content is not injected twice — once a day's stream is fully dreamed, the loader drops it from the prompt entirely.
83
83
 
@@ -86,9 +86,9 @@ If `MEMORY.md` is missing, the section shows `[MISSING] Expected at: <path>`. If
86
86
  ## What you must not do
87
87
 
88
88
  - **Do not edit `MEMORY.md` directly.** It is dreaming-owned. The default system prompt says this verbatim. If you write to `MEMORY.md` from a normal session, your edit will survive only until the next dreaming run, which rewrites the file from scratch using the consolidation logic above. The user's intent is almost never "diff-edit `MEMORY.md`" — see "When the user asks ..." below for the right routings.
89
- - **Do not write to `memory/yyyy-MM-dd.md`.** Daily streams are memory-logger's territory. The runtime reads watermarks out of these files; a hand-edit in the wrong place silently corrupts the cursor. (`memory/` is gitignored at the agent level but force-committed by the dreaming snapshot — your hand-edit there will not look untracked, but it will still be a bug.)
89
+ - **Do not write to `memory/yyyy-MM-dd.jsonl`.** Daily streams are memory-logger's territory. The runtime reads watermarks out of these files; a hand-edit in the wrong place silently corrupts the cursor. (`memory/` is gitignored at the agent level but force-committed by the dreaming snapshot — your hand-edit there will not look untracked, but it will still be a bug.)
90
90
  - **Do not write to `memory/skills/<name>/SKILL.md`.** That is the _muscle memory_ layer, owned exclusively by the dreaming subagent. The `typeclaw-skills` skill says the same thing from the skills-system angle; this skill says it from the memory angle. If you want a hand-authored skill, put it in `.agents/skills/` instead.
91
- - **Do not write to `memory/.dreaming-state.json`.** It is internal bookkeeping (per-day line counts already consolidated). On malformed input the plugin fails open with empty state, so a wrong edit causes one redundant re-consolidation, but it is still a sign you misunderstood the contract.
91
+ - **Do not write to `memory/.dreaming-state.json`.** It is internal bookkeeping (per-day dreamed-id sets). On malformed input the plugin fails open with empty state, so a wrong edit causes one redundant re-consolidation, but it is still a sign you misunderstood the contract.
92
92
  - **Do not promise the user that an `idleMs` or `dreaming.schedule` change took effect just because you edited `typeclaw.json`.** Both fields are **restart-required** — the plugin reads them once at boot, and `reload` does not re-run plugin factories. Tell the user to run `typeclaw restart` (host stage).
93
93
  - **Do not invent fragments.** If you find yourself wanting to "seed" a memory by hand, that is a symptom of the previous rules — surface the fact in your reply (so the memory-logger captures it) instead of writing to memory yourself.
94
94
  - **Do not echo `[truncated]` or `[MISSING]` markers back at the user as if they were part of remembered content.** They are runtime annotations.
@@ -96,13 +96,13 @@ If `MEMORY.md` is missing, the section shows `[MISSING] Expected at: <path>`. If
96
96
  ## When the user asks "what do you remember?"
97
97
 
98
98
  1. Read `MEMORY.md`. Summarize at the topic level — do not dump the whole file unless asked. Cite specific topics by their level-2 headings.
99
- 2. If relevant to the current task, also read the undreamed-tail of recent `memory/yyyy-MM-dd.md` files for fresh observations not yet consolidated. (Note: these are already in your prompt under `# Memory`, so usually you can just refer to them rather than re-reading.)
99
+ 2. If relevant to the current task, also read the undreamed-tail of recent `memory/yyyy-MM-dd.jsonl` files for fresh observations not yet consolidated. (Note: these are already in your prompt under `# Memory`, so usually you can just refer to them rather than re-reading.)
100
100
  3. If `MEMORY.md` is `[MISSING]` or `[EMPTY]`, say so plainly. The first dreaming run creates the file; if dreaming has never fired (e.g. no `memory.dreaming.schedule` configured, or fewer than ~24 hours since hatching), there is genuinely nothing yet.
101
101
 
102
102
  ## When the user asks "do you remember X?"
103
103
 
104
104
  1. Search `MEMORY.md` and recent daily streams for a fragment matching X.
105
- 2. If you find one: say what you found and cite the source (the topic heading from `MEMORY.md`, or the fragment line range from the daily stream).
105
+ 2. If you find one: say what you found and cite the source (the topic heading from `MEMORY.md`, or the `memory/yyyy-MM-dd#<id>` citation from the daily stream).
106
106
  3. If you do not find one: say so plainly. **Do not invent a memory** to be helpful. The honest answer is "no, that is not in my memory" — the user can then decide whether to repeat the context now (which the memory-logger will pick up) or skip it.
107
107
 
108
108
  ## When the user asks "forget X" / "remove X from your memory"
@@ -125,7 +125,7 @@ Stay concrete. Use this map:
125
125
  | File / dir | What it is | Who writes it | Tracked in git |
126
126
  | ------------------------------- | ----------------------------------------------------------------------------- | -------------------------------------------------------------- | ------------------------------------------------------------ |
127
127
  | `MEMORY.md` | Long-term memory, consolidated topics with fragment citations. | Dreaming subagent (rewrites in full on each run). | Yes (force-committed under `dream:` commits, skip-worktree). |
128
- | `memory/yyyy-MM-dd.md` | Daily fragment streams. Append-only during the day. | Memory-logger subagent (one fragment ≈ one prompt completion). | Gitignored, but force-committed in the dreaming snapshot. |
128
+ | `memory/yyyy-MM-dd.jsonl` | Daily fragment streams. Append-only during the day. | Memory-logger subagent (one fragment ≈ one prompt completion). | Gitignored, but force-committed in the dreaming snapshot. |
129
129
  | `memory/skills/<name>/SKILL.md` | Muscle-memory skills distilled from recurring procedures. | Dreaming subagent only. | Gitignored, force-committed in the dreaming snapshot. |
130
130
  | `memory/.dreaming-state.json` | Per-day watermarks (line counts already consolidated). Plain JSON, fail-open. | Dreaming subagent. | Gitignored, force-committed in the dreaming snapshot. |
131
131