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 +1 -1
- package/package.json +1 -1
- package/src/agent/subagents.ts +19 -1
- package/src/cli/init.ts +23 -6
- package/src/cli/model.ts +3 -2
- package/src/cli/provider.ts +5 -3
- package/src/run/index.ts +24 -1
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
|
-
>
|
|
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
package/src/agent/subagents.ts
CHANGED
|
@@ -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 {
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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,
|
package/src/cli/provider.ts
CHANGED
|
@@ -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
|
|
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
|
|
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
|
-
|
|
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({
|