typeclaw 0.37.0 → 0.37.1

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 CHANGED
@@ -4,7 +4,7 @@
4
4
  <img src="./docs/public/typeclaw.png" alt="TypeClaw logo" width="240" />
5
5
  </p>
6
6
 
7
- > A TypeScript-native, Bun-powered, Docker-friendly general-purpose agent runtime.
7
+ > The agent for perfectionists crafted in every detail. It behaves in your team's chat and gets sharper the longer it runs. Sandboxed and self-managing.
8
8
 
9
9
  ## Why?
10
10
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "typeclaw",
3
- "version": "0.37.0",
3
+ "version": "0.37.1",
4
4
  "homepage": "https://github.com/typeclaw/typeclaw#readme",
5
5
  "bugs": {
6
6
  "url": "https://github.com/typeclaw/typeclaw/issues"
@@ -1,10 +1,11 @@
1
1
  import type { ToolDefinition } from '@mariozechner/pi-coding-agent'
2
2
  import type { z } from 'zod'
3
3
 
4
+ import type { PermissionService } from '@/permissions'
4
5
  import type { HookBus } from '@/plugin'
5
6
  import type { Stream, Unsubscribe } from '@/stream'
6
7
 
7
- import { type AgentSession, createSession } from './index'
8
+ import { type AgentSession, createSession, type PluginSessionWiring } from './index'
8
9
  import { subscribeProviderErrors } from './provider-error'
9
10
  import type { SubagentBashPolicy } from './reviewer-bash-policy'
10
11
  import type { SessionOrigin } from './session-origin'
@@ -143,6 +144,21 @@ export type CreateSessionForSubagentOptions = {
143
144
  parentSessionId?: string
144
145
  spawnedByRole?: string
145
146
  spawnedByOrigin?: SessionOrigin
147
+ // Plugin hook wiring for the subagent's tools. When present, the subagent's
148
+ // builtin bash/read/edit/write run through the plugin `tool.before`/`tool.after`
149
+ // hooks (security guards AND github-cli-auth GitHub-token injection) exactly
150
+ // like the main and plugin-subagent sessions. Without it, the builtin tools run
151
+ // raw (the prior behavior) — so standalone/test callers stay unaffected. The
152
+ // production runtime always supplies it (src/run/index.ts) so a generic
153
+ // task-spawned subagent's `git push`/`gh` gets a minted token instead of
154
+ // failing with "could not read Username".
155
+ plugins?: PluginSessionWiring
156
+ // The role/permission service that drives builtin-bash sandboxing. It MUST be
157
+ // forwarded alongside `plugins`: buildBuiltinPiToolOverrides only applies
158
+ // applyBashSandbox / applyTmpPathRedirect when `permissions` is present, so
159
+ // wiring hooks without permissions would inject the GitHub token yet leave the
160
+ // sandbox OFF — strictly weaker than the plugin-subagent branch this matches.
161
+ permissions?: PermissionService
146
162
  }
147
163
  export type CreateSessionForSubagent = (
148
164
  subagent: Subagent<any>,
@@ -161,6 +177,8 @@ export const defaultCreateSessionForSubagent: CreateSessionForSubagent = (subage
161
177
  },
162
178
  ...(subagent.tools ? { tools: subagent.tools } : {}),
163
179
  customTools: subagent.customTools ?? [],
180
+ ...(options?.plugins !== undefined ? { plugins: options.plugins } : {}),
181
+ ...(options?.permissions !== undefined ? { permissions: options.permissions } : {}),
164
182
  ...(subagent.profile !== undefined ? { profile: subagent.profile } : {}),
165
183
  ...(subagent.toolResultBudget !== undefined ? { toolResultBudget: subagent.toolResultBudget } : {}),
166
184
  ...(subagent.bashPolicy !== undefined ? { bashPolicy: subagent.bashPolicy } : {}),
package/src/cli/init.ts CHANGED
@@ -1,6 +1,18 @@
1
1
  import { randomBytes } from 'node:crypto'
2
2
 
3
- import { cancel, confirm, intro, isCancel, log, note, password, select, spinner, text } from '@clack/prompts'
3
+ import {
4
+ autocomplete,
5
+ cancel,
6
+ confirm,
7
+ intro,
8
+ isCancel,
9
+ log,
10
+ note,
11
+ password,
12
+ select,
13
+ spinner,
14
+ text,
15
+ } from '@clack/prompts'
4
16
  import { defineCommand } from 'citty'
5
17
 
6
18
  import {
@@ -1083,8 +1095,9 @@ async function pickVendor(
1083
1095
  initial: KnownProviderVendorId | undefined,
1084
1096
  ): Promise<StepResult<KnownProviderVendorId>> {
1085
1097
  const vendors = uniqueVendors(options)
1086
- const choice = await select({
1098
+ const choice = await autocomplete({
1087
1099
  message: 'Pick an LLM provider',
1100
+ placeholder: 'Type to search…',
1088
1101
  options: vendors.map((id) => ({
1089
1102
  value: id,
1090
1103
  label: KNOWN_PROVIDER_VENDORS[id].name,
@@ -1104,8 +1117,9 @@ async function pickProviderVariant(
1104
1117
  const variants = providersForVendorInCatalog(vendorId, options)
1105
1118
  if (variants.length === 0) throw new Error(`Internal error: vendor ${vendorId} has no providers in the catalog`)
1106
1119
  if (variants.length === 1) return autoValue(variants[0]!)
1107
- const choice = await select<KnownProviderId>({
1120
+ const choice = await autocomplete<KnownProviderId>({
1108
1121
  message: `Pick a ${KNOWN_PROVIDER_VENDORS[vendorId].name} option`,
1122
+ placeholder: 'Type to search…',
1109
1123
  options: variants.map((id) => {
1110
1124
  const hint = variantHint(vendorId, id)
1111
1125
  return hint !== undefined
@@ -1128,8 +1142,9 @@ async function pickModelForProvider(
1128
1142
  // distributive conditional type, so a large KnownModelRef union explodes into
1129
1143
  // a per-literal option union that no longer accepts `value: ref`. The runtime
1130
1144
  // value is the ref string and is re-narrowed via `candidates.find` below.
1131
- const choice = await select<string>({
1145
+ const choice = await autocomplete<string>({
1132
1146
  message: `Pick a ${KNOWN_PROVIDERS[providerId].name} model`,
1147
+ placeholder: 'Type to search…',
1133
1148
  options: candidates.map((o) => ({
1134
1149
  value: o.ref,
1135
1150
  label: formatModelLabel(o),
@@ -1173,8 +1188,9 @@ async function pickVisionVendor(
1173
1188
  log.warn('No vision-capable models available; skipping vision profile.')
1174
1189
  return autoValue('skip')
1175
1190
  }
1176
- const choice = await select<KnownProviderVendorId | 'skip'>({
1191
+ const choice = await autocomplete<KnownProviderVendorId | 'skip'>({
1177
1192
  message: 'Your model is text-only. Pick a provider for the `vision` profile (used for image input)',
1193
+ placeholder: 'Type to search…',
1178
1194
  options: [
1179
1195
  ...vendors.map((id) => ({
1180
1196
  value: id as KnownProviderVendorId | 'skip',
@@ -1204,8 +1220,9 @@ async function pickVisionModel(
1204
1220
  ): Promise<StepResult<ModelOption>> {
1205
1221
  const candidates = sortRecommendedFirst(options.filter((o) => o.providerId === providerId))
1206
1222
  // select<string> for the same distributive-Option reason as pickModelForProvider.
1207
- const choice = await select<string>({
1223
+ const choice = await autocomplete<string>({
1208
1224
  message: `Pick a vision-capable ${KNOWN_PROVIDERS[providerId].name} model`,
1225
+ placeholder: 'Type to search…',
1209
1226
  options: candidates.map((o) => ({
1210
1227
  value: o.ref,
1211
1228
  label: formatModelLabel(o),
package/src/cli/model.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { cancel, intro, isCancel, log, select } from '@clack/prompts'
1
+ import { autocomplete, cancel, intro, isCancel, log, select } from '@clack/prompts'
2
2
  import { defineCommand } from 'citty'
3
3
 
4
4
  import type { CustomModelMeta } from '@/config'
@@ -248,8 +248,9 @@ async function pickModelRef(cwd: string): Promise<PickedModelRef> {
248
248
  // assignability. Values are ref strings (+ the sentinel) and stay correct
249
249
  // at runtime — the sentinel check and `return choice` below are unaffected.
250
250
  const modelOptions = await listCredentialedModelOptions(refs)
251
- const choice = await select<string>({
251
+ const choice = await autocomplete<string>({
252
252
  message: 'Pick a model',
253
+ placeholder: 'Type to search…',
253
254
  options: [
254
255
  ...modelOptions.map((option) => ({
255
256
  value: option.ref,
@@ -1,4 +1,4 @@
1
- import { cancel, intro, isCancel, log, password, select } from '@clack/prompts'
1
+ import { autocomplete, cancel, intro, isCancel, log, password, select } from '@clack/prompts'
2
2
  import { defineCommand } from 'citty'
3
3
 
4
4
  import {
@@ -282,8 +282,9 @@ async function resolveProviderForAdd(input: string | undefined): Promise<KnownPr
282
282
 
283
283
  async function pickVendorToAdd(): Promise<KnownProviderVendorId> {
284
284
  const vendorIds = listKnownProviderVendorIds()
285
- const choice = await select<KnownProviderVendorId>({
285
+ const choice = await autocomplete<KnownProviderVendorId>({
286
286
  message: 'Pick a provider to add',
287
+ placeholder: 'Type to search…',
287
288
  options: vendorIds.map((id) => ({
288
289
  value: id,
289
290
  label: KNOWN_PROVIDER_VENDORS[id].name,
@@ -301,8 +302,9 @@ async function pickVendorToAdd(): Promise<KnownProviderVendorId> {
301
302
  async function pickVariantToAdd(vendorId: KnownProviderVendorId): Promise<KnownProviderId> {
302
303
  const variants = providerIdsForVendor(vendorId)
303
304
  if (variants.length === 1) return variants[0]!
304
- const choice = await select<KnownProviderId>({
305
+ const choice = await autocomplete<KnownProviderId>({
305
306
  message: `Pick a ${KNOWN_PROVIDER_VENDORS[vendorId].name} option`,
307
+ placeholder: 'Type to search…',
306
308
  options: variants.map((id) => {
307
309
  const hint = variantHint(vendorId, id)
308
310
  return hint !== undefined
package/src/run/index.ts CHANGED
@@ -439,7 +439,30 @@ export async function startAgent({
439
439
  : {}),
440
440
  }
441
441
  }
442
- return defaultCreateSessionForSubagent(subagent, subagentOptions)
442
+ // Non-plugin (built-in) subagents — general/explore/scout/memory-logger/
443
+ // dreaming and anything spawned through the generic task path. They used to
444
+ // run with NO plugin tool.before/tool.after coverage, so their bash skipped
445
+ // the security guards AND the github-cli-auth GitHub-token injection — a
446
+ // generic subagent's `git push` got no minted token and died with "could
447
+ // not read Username" even when a GitHub App was configured. Thread the same
448
+ // hook bus the plugin-subagent branch uses, against a freshly allocated
449
+ // subagent session id (never the parent's, so hooks/audit/permission
450
+ // attribution stay per-session).
451
+ const sessionManager = SessionManager.create(cwd, sessionFactory.sessionDir())
452
+ return defaultCreateSessionForSubagent(subagent, {
453
+ ...subagentOptions,
454
+ plugins: {
455
+ registry: snap.registry,
456
+ hooks: snap.hooks,
457
+ sessionId: sessionManager.getSessionId(),
458
+ agentDir: cwd,
459
+ },
460
+ // Pass permissions alongside plugins (same as the plugin-subagent branch
461
+ // at line 384): without it the builtin-bash sandbox (applyBashSandbox /
462
+ // applyTmpPathRedirect) stays off and the subagent would get the injected
463
+ // token but no role-derived sandboxing.
464
+ permissions: pluginsLoaded.permissions,
465
+ })
443
466
  }
444
467
 
445
468
  const subagentConsumer = createSubagentConsumer({