@dungle-scrubs/tallow 0.8.25 → 0.8.26
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/dist/auth-hardening.d.ts +12 -0
- package/dist/auth-hardening.d.ts.map +1 -1
- package/dist/auth-hardening.js +30 -7
- package/dist/auth-hardening.js.map +1 -1
- package/dist/cli.js +5 -0
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +1 -1
- package/dist/config.js +1 -1
- package/dist/install.js +2 -2
- package/dist/install.js.map +1 -1
- package/dist/interactive-mode-patch.d.ts.map +1 -1
- package/dist/interactive-mode-patch.js +119 -7
- package/dist/interactive-mode-patch.js.map +1 -1
- package/dist/model-metadata-overrides.d.ts +19 -0
- package/dist/model-metadata-overrides.d.ts.map +1 -0
- package/dist/model-metadata-overrides.js +38 -0
- package/dist/model-metadata-overrides.js.map +1 -0
- package/dist/sdk.d.ts +2 -0
- package/dist/sdk.d.ts.map +1 -1
- package/dist/sdk.js +28 -1
- package/dist/sdk.js.map +1 -1
- package/extensions/__integration__/teams-runtime.test.ts +22 -1
- package/extensions/_shared/__tests__/shell-policy.test.ts +197 -0
- package/extensions/_shared/shell-policy.ts +27 -0
- package/extensions/background-task-tool/index.ts +2 -1
- package/extensions/bash-tool-enhanced/index.ts +2 -1
- package/extensions/custom-footer/__tests__/index.test.ts +29 -0
- package/extensions/custom-footer/context-display.ts +49 -0
- package/extensions/custom-footer/index.ts +10 -23
- package/extensions/permissions/index.ts +31 -10
- package/extensions/plan-mode-tool/__tests__/index.test.ts +32 -2
- package/extensions/plan-mode-tool/index.ts +6 -1
- package/extensions/slash-command-bridge/index.ts +30 -1
- package/extensions/subagent-tool/__tests__/process-liveness.test.ts +42 -3
- package/extensions/subagent-tool/process.ts +132 -21
- package/extensions/tasks/__tests__/store.test.ts +26 -2
- package/extensions/tasks/commands/register-tasks-extension.ts +2 -2
- package/extensions/tasks/index.ts +5 -5
- package/extensions/tasks/state/index.ts +90 -36
- package/extensions/teams-tool/__tests__/archive-store.test.ts +98 -0
- package/extensions/teams-tool/__tests__/peer-messaging.test.ts +26 -0
- package/extensions/teams-tool/archive-store.ts +200 -0
- package/extensions/teams-tool/sessions/spawn.ts +244 -71
- package/extensions/teams-tool/tools/register-extension.ts +146 -105
- package/extensions/teams-tool/tools/teammate-tools.ts +43 -1
- package/package.json +4 -4
- package/skills/tallow-expert/SKILL.md +1 -1
- package/templates/agents/architect.md +13 -5
- package/templates/agents/debug.md +3 -3
- package/templates/agents/explore.md +9 -2
- package/templates/agents/refactor.md +2 -2
- package/templates/agents/scout.md +3 -2
|
@@ -23,6 +23,11 @@ import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-age
|
|
|
23
23
|
import { Key, Loader, Text, type TUI } from "@mariozechner/pi-tui";
|
|
24
24
|
import { Type } from "@sinclair/typebox";
|
|
25
25
|
import { INTEROP_EVENT_NAMES, onInteropEvent } from "../../_shared/interop-events.js";
|
|
26
|
+
import {
|
|
27
|
+
deleteArchivedTeamFromDisk,
|
|
28
|
+
loadAllArchivedTeamsFromDisk,
|
|
29
|
+
writeArchivedTeamToDisk,
|
|
30
|
+
} from "../archive-store.js";
|
|
26
31
|
import {
|
|
27
32
|
appendDashboardFeedEvent,
|
|
28
33
|
bindDashboardSessionTracking,
|
|
@@ -66,7 +71,6 @@ import {
|
|
|
66
71
|
export function registerTeamsToolExtension(pi: ExtensionAPI): void {
|
|
67
72
|
setInteropEvents(pi.events);
|
|
68
73
|
let cwd = process.cwd();
|
|
69
|
-
let dashboardCancelInFlight = false;
|
|
70
74
|
let dashboardEnabled = false;
|
|
71
75
|
let dashboardTicker: ReturnType<typeof setInterval> | undefined;
|
|
72
76
|
let dashboardTui: TUI | undefined;
|
|
@@ -154,67 +158,82 @@ export function registerTeamsToolExtension(pi: ExtensionAPI): void {
|
|
|
154
158
|
}
|
|
155
159
|
|
|
156
160
|
/**
|
|
157
|
-
*
|
|
158
|
-
*
|
|
161
|
+
* Refresh the in-memory archived-team index from disk.
|
|
162
|
+
*
|
|
163
|
+
* Team archives must survive process restarts, so the on-disk view is the
|
|
164
|
+
* source of truth. This helper keeps the legacy in-memory map synchronized
|
|
165
|
+
* for the existing store/query helpers.
|
|
166
|
+
*
|
|
167
|
+
* @returns void
|
|
159
168
|
*/
|
|
160
|
-
|
|
161
|
-
const
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
running.push({ teammate, team });
|
|
166
|
-
}
|
|
169
|
+
function refreshArchivedTeamStore(): void {
|
|
170
|
+
const archives = getArchivedTeams();
|
|
171
|
+
archives.clear();
|
|
172
|
+
for (const archived of loadAllArchivedTeamsFromDisk()) {
|
|
173
|
+
archives.set(archived.name, archived);
|
|
167
174
|
}
|
|
168
|
-
|
|
175
|
+
}
|
|
169
176
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
);
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
dashboardActivity.touch(team.name, teammate.name);
|
|
185
|
-
appendDashboardFeedEvent(team.name, "orchestrator", teammate.name, "Cancelled run.");
|
|
186
|
-
touchedTeams.add(team);
|
|
177
|
+
/**
|
|
178
|
+
* Move an active team into the archive store and persist it to disk.
|
|
179
|
+
*
|
|
180
|
+
* @param teamName - Active team name to archive
|
|
181
|
+
* @returns void
|
|
182
|
+
*/
|
|
183
|
+
function archiveRuntimeTeam(teamName: string): void {
|
|
184
|
+
const archived = archiveTeam(teamName);
|
|
185
|
+
if (!archived) return;
|
|
186
|
+
try {
|
|
187
|
+
writeArchivedTeamToDisk(archived);
|
|
188
|
+
refreshArchivedTeamStore();
|
|
189
|
+
} catch (error) {
|
|
190
|
+
console.error(`Failed to persist archived team ${teamName}: ${error}`);
|
|
187
191
|
}
|
|
188
|
-
for (const team of touchedTeams) refreshTeamView(team);
|
|
189
|
-
notifyDashboardChanged();
|
|
190
|
-
return running.length;
|
|
191
192
|
}
|
|
192
193
|
|
|
193
194
|
/**
|
|
194
|
-
*
|
|
195
|
-
*
|
|
196
|
-
* @
|
|
195
|
+
* Dispose teammate sessions for one team.
|
|
196
|
+
*
|
|
197
|
+
* @param team - Team whose live teammate sessions should be cleaned up
|
|
198
|
+
* @param abortStreaming - Whether currently streaming teammates should be aborted first
|
|
199
|
+
* @returns Number of teammates that were processed
|
|
197
200
|
*/
|
|
198
|
-
function
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
201
|
+
async function disposeTeamSessions(
|
|
202
|
+
team: Team<Teammate>,
|
|
203
|
+
abortStreaming: boolean
|
|
204
|
+
): Promise<number> {
|
|
205
|
+
let count = 0;
|
|
206
|
+
for (const [, mate] of team.teammates) {
|
|
202
207
|
try {
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
ctx.ui.notify(
|
|
206
|
-
`Cancelled ${cancelled} running teammate${cancelled === 1 ? "" : "s"}. Press Esc again to close dashboard.`,
|
|
207
|
-
"warning"
|
|
208
|
-
);
|
|
209
|
-
return;
|
|
208
|
+
if (abortStreaming && mate.session.isStreaming) {
|
|
209
|
+
await mate.session.abort();
|
|
210
210
|
}
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
211
|
+
mate.unsubscribe?.();
|
|
212
|
+
mate.session.dispose();
|
|
213
|
+
} catch (error) {
|
|
214
|
+
console.error(`Failed to clean up teammate ${mate.name}: ${error}`);
|
|
214
215
|
} finally {
|
|
215
|
-
|
|
216
|
+
mate.status = "shutdown";
|
|
217
|
+
count++;
|
|
216
218
|
}
|
|
217
|
-
}
|
|
219
|
+
}
|
|
220
|
+
return count;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Handle Escape inside dashboard mode.
|
|
225
|
+
*
|
|
226
|
+
* The dashboard is a view, not an interrupt button. Escape should return to
|
|
227
|
+
* chat without killing teammates; explicit teardown goes through team_shutdown
|
|
228
|
+
* or normal session shutdown.
|
|
229
|
+
*
|
|
230
|
+
* @param ctx - Extension context
|
|
231
|
+
* @returns void
|
|
232
|
+
*/
|
|
233
|
+
function handleDashboardEscape(ctx: ExtensionContext): void {
|
|
234
|
+
if (!dashboardEnabled) return;
|
|
235
|
+
disableDashboard(ctx, false);
|
|
236
|
+
ctx.ui.notify("Team dashboard disabled. Teammates keep running.", "info");
|
|
218
237
|
}
|
|
219
238
|
|
|
220
239
|
/**
|
|
@@ -256,7 +275,6 @@ export function registerTeamsToolExtension(pi: ExtensionAPI): void {
|
|
|
256
275
|
* @returns void
|
|
257
276
|
*/
|
|
258
277
|
function disableDashboard(ctx: ExtensionContext, notify = true): void {
|
|
259
|
-
dashboardCancelInFlight = false;
|
|
260
278
|
dashboardEnabled = false;
|
|
261
279
|
stopDashboardTicker();
|
|
262
280
|
setDashboardRenderCallback(undefined);
|
|
@@ -289,6 +307,7 @@ export function registerTeamsToolExtension(pi: ExtensionAPI): void {
|
|
|
289
307
|
|
|
290
308
|
pi.on("session_start", async (_event, ctx) => {
|
|
291
309
|
cwd = ctx.cwd;
|
|
310
|
+
refreshArchivedTeamStore();
|
|
292
311
|
publishTeamSnapshots();
|
|
293
312
|
publishDashboardState();
|
|
294
313
|
});
|
|
@@ -301,20 +320,10 @@ export function registerTeamsToolExtension(pi: ExtensionAPI): void {
|
|
|
301
320
|
// Archive all teams on session shutdown (preserves tasks for future recovery)
|
|
302
321
|
pi.on("session_shutdown", async () => {
|
|
303
322
|
for (const [name, team] of getTeams() as Map<string, Team<Teammate>>) {
|
|
304
|
-
|
|
305
|
-
try {
|
|
306
|
-
if (mate.session.isStreaming) await mate.session.abort();
|
|
307
|
-
mate.unsubscribe?.();
|
|
308
|
-
mate.session.dispose();
|
|
309
|
-
} catch (err) {
|
|
310
|
-
console.error(`Failed to clean up teammate ${mate.name}: ${err}`);
|
|
311
|
-
}
|
|
312
|
-
mate.status = "shutdown";
|
|
313
|
-
}
|
|
323
|
+
await disposeTeamSessions(team, true);
|
|
314
324
|
removeTeamView(name);
|
|
315
|
-
|
|
325
|
+
archiveRuntimeTeam(name);
|
|
316
326
|
}
|
|
317
|
-
dashboardCancelInFlight = false;
|
|
318
327
|
dashboardEnabled = false;
|
|
319
328
|
stopDashboardTicker();
|
|
320
329
|
setDashboardRenderCallback(undefined);
|
|
@@ -334,21 +343,9 @@ export function registerTeamsToolExtension(pi: ExtensionAPI): void {
|
|
|
334
343
|
for (const [name, team] of getTeams() as Map<string, Team<Teammate>>) {
|
|
335
344
|
const hasActiveWork = [...team.teammates.values()].some((m) => m.status === "working");
|
|
336
345
|
if (hasActiveWork) continue;
|
|
337
|
-
|
|
338
|
-
// All teammates finished — clean up and archive
|
|
339
|
-
for (const [, mate] of team.teammates) {
|
|
340
|
-
if (mate.status === "idle") {
|
|
341
|
-
try {
|
|
342
|
-
mate.unsubscribe?.();
|
|
343
|
-
mate.session.dispose();
|
|
344
|
-
} catch {
|
|
345
|
-
// Best-effort cleanup
|
|
346
|
-
}
|
|
347
|
-
mate.status = "shutdown";
|
|
348
|
-
}
|
|
349
|
-
}
|
|
346
|
+
await disposeTeamSessions(team, false);
|
|
350
347
|
removeTeamView(name);
|
|
351
|
-
|
|
348
|
+
archiveRuntimeTeam(name);
|
|
352
349
|
}
|
|
353
350
|
});
|
|
354
351
|
|
|
@@ -388,6 +385,7 @@ export function registerTeamsToolExtension(pi: ExtensionAPI): void {
|
|
|
388
385
|
name: Type.String({ description: "Team name (unique)" }),
|
|
389
386
|
}),
|
|
390
387
|
async execute(_toolCallId, params) {
|
|
388
|
+
refreshArchivedTeamStore();
|
|
391
389
|
if (getTeams().has(params.name)) {
|
|
392
390
|
return {
|
|
393
391
|
content: [{ type: "text", text: `Team "${params.name}" already exists.` }],
|
|
@@ -395,6 +393,20 @@ export function registerTeamsToolExtension(pi: ExtensionAPI): void {
|
|
|
395
393
|
isError: true,
|
|
396
394
|
};
|
|
397
395
|
}
|
|
396
|
+
if (getArchivedTeams().has(params.name)) {
|
|
397
|
+
return {
|
|
398
|
+
content: [
|
|
399
|
+
{
|
|
400
|
+
type: "text",
|
|
401
|
+
text:
|
|
402
|
+
`Team "${params.name}" already exists as an archive. ` +
|
|
403
|
+
"Use team_resume to continue it or choose a new name.",
|
|
404
|
+
},
|
|
405
|
+
],
|
|
406
|
+
details: {},
|
|
407
|
+
isError: true,
|
|
408
|
+
};
|
|
409
|
+
}
|
|
398
410
|
createTeamStore(params.name);
|
|
399
411
|
appendDashboardFeedEvent(params.name, "orchestrator", "all", `Team "${params.name}" created`);
|
|
400
412
|
notifyDashboardChanged();
|
|
@@ -492,16 +504,27 @@ export function registerTeamsToolExtension(pi: ExtensionAPI): void {
|
|
|
492
504
|
description: [
|
|
493
505
|
"Spawn a teammate with their own agent session, shared task board access, and inter-agent messaging.",
|
|
494
506
|
"They get standard coding tools plus team coordination tools.",
|
|
507
|
+
"Optionally load a named agent template for prompt/frontmatter defaults.",
|
|
495
508
|
"After spawning, use team_send to give them initial instructions.",
|
|
496
509
|
].join(" "),
|
|
497
510
|
parameters: Type.Object({
|
|
511
|
+
agent: Type.Optional(
|
|
512
|
+
Type.String({
|
|
513
|
+
description: "Optional agent template name to load from user/project agent directories.",
|
|
514
|
+
})
|
|
515
|
+
),
|
|
498
516
|
team: Type.String({ description: "Team name" }),
|
|
499
517
|
name: Type.String({ description: "Teammate name (unique within team)" }),
|
|
500
|
-
role: Type.
|
|
518
|
+
role: Type.Optional(
|
|
519
|
+
Type.String({
|
|
520
|
+
description:
|
|
521
|
+
"Role/description (guides behavior). Optional when agent is provided; otherwise required.",
|
|
522
|
+
})
|
|
523
|
+
),
|
|
501
524
|
model: Type.Optional(
|
|
502
525
|
Type.String({
|
|
503
526
|
description:
|
|
504
|
-
"Explicit model ID (fuzzy matched). When omitted, auto-routes based on role complexity.",
|
|
527
|
+
"Explicit model ID (fuzzy matched). When omitted, auto-routes based on role complexity or agent template.",
|
|
505
528
|
})
|
|
506
529
|
),
|
|
507
530
|
modelScope: Type.Optional(
|
|
@@ -510,10 +533,16 @@ export function registerTeamsToolExtension(pi: ExtensionAPI): void {
|
|
|
510
533
|
'Constrain auto-routing to a model family (e.g. "codex", "gemini"). Ignored when explicit model is set.',
|
|
511
534
|
})
|
|
512
535
|
),
|
|
536
|
+
thinkingLevel: Type.Optional(
|
|
537
|
+
Type.String({
|
|
538
|
+
description:
|
|
539
|
+
"Optional teammate thinking level: off, low, medium, or high. Defaults to the parent session when available.",
|
|
540
|
+
})
|
|
541
|
+
),
|
|
513
542
|
tools: Type.Optional(
|
|
514
543
|
Type.Array(Type.String(), {
|
|
515
544
|
description:
|
|
516
|
-
"Standard tool names: read, bash, edit, write, grep, find, ls. Default: all coding tools.",
|
|
545
|
+
"Standard tool names: read, bash, edit, write, grep, find, ls. Default: all coding tools or the agent template allowlist.",
|
|
517
546
|
})
|
|
518
547
|
),
|
|
519
548
|
}),
|
|
@@ -538,19 +567,37 @@ export function registerTeamsToolExtension(pi: ExtensionAPI): void {
|
|
|
538
567
|
isError: true,
|
|
539
568
|
};
|
|
540
569
|
}
|
|
570
|
+
if (!params.role && !params.agent) {
|
|
571
|
+
return {
|
|
572
|
+
content: [
|
|
573
|
+
{
|
|
574
|
+
type: "text",
|
|
575
|
+
text: "team_spawn requires either a role or an agent template.",
|
|
576
|
+
},
|
|
577
|
+
],
|
|
578
|
+
details: {},
|
|
579
|
+
isError: true,
|
|
580
|
+
};
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
const inheritedThinkingLevel = (
|
|
584
|
+
ctx as { getThinkingLevel?: () => string | undefined } | undefined
|
|
585
|
+
)?.getThinkingLevel?.();
|
|
541
586
|
|
|
542
587
|
try {
|
|
543
|
-
const mate = await spawnTeammateSession(
|
|
588
|
+
const mate = await spawnTeammateSession({
|
|
589
|
+
agentName: params.agent,
|
|
544
590
|
cwd,
|
|
591
|
+
hints: params.modelScope ? { modelScope: params.modelScope } : undefined,
|
|
592
|
+
modelOverride: params.model,
|
|
593
|
+
name: params.name,
|
|
594
|
+
parentModelId: ctx?.model?.id,
|
|
595
|
+
piEvents: pi.events,
|
|
596
|
+
role: params.role,
|
|
545
597
|
team,
|
|
546
|
-
params.
|
|
547
|
-
params.
|
|
548
|
-
|
|
549
|
-
params.tools,
|
|
550
|
-
pi.events,
|
|
551
|
-
params.modelScope ? { modelScope: params.modelScope } : undefined,
|
|
552
|
-
ctx?.model?.id
|
|
553
|
-
);
|
|
598
|
+
thinkingLevel: params.thinkingLevel ?? inheritedThinkingLevel,
|
|
599
|
+
toolNames: params.tools,
|
|
600
|
+
});
|
|
554
601
|
mate.unsubscribe = bindDashboardSessionTracking(team.name, mate.name, mate.session);
|
|
555
602
|
const dashboardActivity = getDashboardActivity();
|
|
556
603
|
dashboardActivity.touch(team.name, mate.name);
|
|
@@ -801,23 +848,9 @@ export function registerTeamsToolExtension(pi: ExtensionAPI): void {
|
|
|
801
848
|
};
|
|
802
849
|
}
|
|
803
850
|
|
|
804
|
-
|
|
805
|
-
for (const [, mate] of team.teammates) {
|
|
806
|
-
try {
|
|
807
|
-
if (mate.session.isStreaming) await mate.session.abort();
|
|
808
|
-
mate.unsubscribe?.();
|
|
809
|
-
mate.session.dispose();
|
|
810
|
-
mate.status = "shutdown";
|
|
811
|
-
count++;
|
|
812
|
-
} catch (err) {
|
|
813
|
-
console.error(`Failed to clean up teammate ${mate.name}: ${err}`);
|
|
814
|
-
mate.status = "shutdown";
|
|
815
|
-
count++;
|
|
816
|
-
}
|
|
817
|
-
}
|
|
818
|
-
|
|
851
|
+
const count = await disposeTeamSessions(team, true);
|
|
819
852
|
removeTeamView(params.team);
|
|
820
|
-
|
|
853
|
+
archiveRuntimeTeam(params.team);
|
|
821
854
|
return {
|
|
822
855
|
content: [
|
|
823
856
|
{
|
|
@@ -857,6 +890,8 @@ export function registerTeamsToolExtension(pi: ExtensionAPI): void {
|
|
|
857
890
|
),
|
|
858
891
|
}),
|
|
859
892
|
async execute(_toolCallId, params) {
|
|
893
|
+
refreshArchivedTeamStore();
|
|
894
|
+
|
|
860
895
|
// List mode — show all archived teams
|
|
861
896
|
if (!params.team) {
|
|
862
897
|
const archives = getArchivedTeams();
|
|
@@ -917,6 +952,12 @@ export function registerTeamsToolExtension(pi: ExtensionAPI): void {
|
|
|
917
952
|
}
|
|
918
953
|
}
|
|
919
954
|
|
|
955
|
+
try {
|
|
956
|
+
deleteArchivedTeamFromDisk(params.team);
|
|
957
|
+
} catch (error) {
|
|
958
|
+
console.error(`Failed to delete archived team ${params.team}: ${error}`);
|
|
959
|
+
}
|
|
960
|
+
refreshArchivedTeamStore();
|
|
920
961
|
notifyDashboardChanged();
|
|
921
962
|
return {
|
|
922
963
|
content: [
|
|
@@ -9,7 +9,14 @@ import { getIcon } from "../../_icons/index.js";
|
|
|
9
9
|
import { appendDashboardFeedEvent, refreshTeamView } from "../dashboard/state.js";
|
|
10
10
|
import { autoDispatch, wakeTeammate } from "../dispatch/auto-dispatch.js";
|
|
11
11
|
import type { Teammate } from "../state/types.js";
|
|
12
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
addTeamMessage,
|
|
14
|
+
getUnread,
|
|
15
|
+
isTaskReady,
|
|
16
|
+
markRead,
|
|
17
|
+
type Team,
|
|
18
|
+
type TeamTask,
|
|
19
|
+
} from "../store.js";
|
|
13
20
|
|
|
14
21
|
/**
|
|
15
22
|
* Create the team coordination tools for a specific teammate.
|
|
@@ -24,6 +31,23 @@ export function createTeammateTools(
|
|
|
24
31
|
myName: string,
|
|
25
32
|
piEvents?: ExtensionAPI["events"]
|
|
26
33
|
): ToolDefinition[] {
|
|
34
|
+
/**
|
|
35
|
+
* Validate that the current teammate owns a claimed task before mutating it.
|
|
36
|
+
*
|
|
37
|
+
* @param action - Mutation being attempted (`complete` or `fail`)
|
|
38
|
+
* @param task - Task being mutated
|
|
39
|
+
* @returns Error text when the mutation should be rejected, otherwise null
|
|
40
|
+
*/
|
|
41
|
+
function getTaskOwnershipError(action: "complete" | "fail", task: TeamTask): string | null {
|
|
42
|
+
if (task.status !== "claimed") {
|
|
43
|
+
return `Task #${task.id} is ${task.status}. Claim it before trying to ${action} it.`;
|
|
44
|
+
}
|
|
45
|
+
if (task.assignee !== myName) {
|
|
46
|
+
return `Task #${task.id} is assigned to ${task.assignee ?? "(unassigned)"}. Only the assignee can ${action} it.`;
|
|
47
|
+
}
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
|
|
27
51
|
const tasksTool: ToolDefinition = {
|
|
28
52
|
name: "team_tasks",
|
|
29
53
|
label: "team_tasks",
|
|
@@ -108,6 +132,15 @@ export function createTeammateTools(
|
|
|
108
132
|
}
|
|
109
133
|
|
|
110
134
|
if (params.action === "complete") {
|
|
135
|
+
const ownershipError = getTaskOwnershipError("complete", task);
|
|
136
|
+
if (ownershipError) {
|
|
137
|
+
return {
|
|
138
|
+
content: [{ type: "text" as const, text: ownershipError }],
|
|
139
|
+
details: {},
|
|
140
|
+
isError: true,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
111
144
|
task.status = "completed";
|
|
112
145
|
task.result = params.result || "(completed)";
|
|
113
146
|
piEvents?.emit("task_completed", {
|
|
@@ -129,6 +162,15 @@ export function createTeammateTools(
|
|
|
129
162
|
}
|
|
130
163
|
|
|
131
164
|
if (params.action === "fail") {
|
|
165
|
+
const ownershipError = getTaskOwnershipError("fail", task);
|
|
166
|
+
if (ownershipError) {
|
|
167
|
+
return {
|
|
168
|
+
content: [{ type: "text" as const, text: ownershipError }],
|
|
169
|
+
details: {},
|
|
170
|
+
isError: true,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
132
174
|
task.status = "failed";
|
|
133
175
|
task.result = params.result || "(failed)";
|
|
134
176
|
refreshTeamView(team as Team<Teammate>);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dungle-scrubs/tallow",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.26",
|
|
4
4
|
"description": "An opinionated coding agent. Built on pi.",
|
|
5
5
|
"piConfig": {
|
|
6
6
|
"name": "tallow",
|
|
@@ -74,7 +74,7 @@
|
|
|
74
74
|
"dependencies": {
|
|
75
75
|
"@clack/prompts": "^1.1.0",
|
|
76
76
|
"@dungle-scrubs/synapse": "0.1.6",
|
|
77
|
-
"@mariozechner/pi-coding-agent": "^0.
|
|
77
|
+
"@mariozechner/pi-coding-agent": "^0.60.0",
|
|
78
78
|
"@mariozechner/pi-tui": "^0.60.0",
|
|
79
79
|
"@opentelemetry/api": "^1.9.0",
|
|
80
80
|
"@sinclair/typebox": "0.34.48",
|
|
@@ -85,8 +85,8 @@
|
|
|
85
85
|
},
|
|
86
86
|
"devDependencies": {
|
|
87
87
|
"@biomejs/biome": "2.4.2",
|
|
88
|
-
"@mariozechner/pi-agent-core": "^0.
|
|
89
|
-
"@mariozechner/pi-ai": "^0.
|
|
88
|
+
"@mariozechner/pi-agent-core": "^0.60.0",
|
|
89
|
+
"@mariozechner/pi-ai": "^0.60.0",
|
|
90
90
|
"@types/node": "25.2.3",
|
|
91
91
|
"husky": "^9.1.7",
|
|
92
92
|
"lint-staged": "^16.4.0",
|
|
@@ -33,7 +33,7 @@ Relay that answer to the user.
|
|
|
33
33
|
|
|
34
34
|
| Component | Location |
|
|
35
35
|
|-----------|----------|
|
|
36
|
-
| Core source | `src/` (agent-runner.ts, atomic-write.ts, auth-hardening.ts, cli-auto-rebuild.ts, cli.ts, compaction-cancel-patch.ts, config.ts, extensions-global.d.ts, fatal-errors.ts, index.ts, install.ts, interactive-mode-patch.ts, otel.ts, pid-manager.ts, plugins.ts, process-cleanup.ts, project-trust-banner.ts, project-trust-interop.ts, project-trust.ts, runtime-path-provider.ts, runtime-provenance.ts, sdk.ts, session-migration.ts, session-utils.ts, startup-profile.ts, startup-timing.ts, streaming-yield-patch.ts, workspace-transition-interactive.ts, workspace-transition-relay.ts, workspace-transition.ts, yield-to-io.ts) |
|
|
36
|
+
| Core source | `src/` (agent-runner.ts, atomic-write.ts, auth-hardening.ts, cli-auto-rebuild.ts, cli.ts, compaction-cancel-patch.ts, config.ts, extensions-global.d.ts, fatal-errors.ts, index.ts, install.ts, interactive-mode-patch.ts, model-metadata-overrides.ts, otel.ts, pid-manager.ts, plugins.ts, process-cleanup.ts, project-trust-banner.ts, project-trust-interop.ts, project-trust.ts, runtime-path-provider.ts, runtime-provenance.ts, sdk.ts, session-migration.ts, session-utils.ts, startup-profile.ts, startup-timing.ts, streaming-yield-patch.ts, workspace-transition-interactive.ts, workspace-transition-relay.ts, workspace-transition.ts, yield-to-io.ts) |
|
|
37
37
|
| Extensions | `extensions/` — extension.json + index.ts each (52 bundled) |
|
|
38
38
|
| Skills | `skills/` — subdirs with SKILL.md |
|
|
39
39
|
| Agents | `agents/` — markdown with YAML frontmatter |
|
|
@@ -7,17 +7,25 @@ description: High-level architecture agent — plans before coding, thinks in sy
|
|
|
7
7
|
# model: claude-sonnet-4-5
|
|
8
8
|
---
|
|
9
9
|
|
|
10
|
-
You are an architecture-focused agent.
|
|
10
|
+
You are an architecture-focused agent.
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
Default behavior: **design first, do not implement unless the delegated task explicitly asks for implementation.**
|
|
13
|
+
Subagents often run without interactive confirmation, so never block waiting for a reply.
|
|
14
|
+
If requirements are ambiguous, state the assumptions you made and continue with the best design you can justify.
|
|
15
|
+
|
|
16
|
+
When the task includes implementation:
|
|
17
|
+
1. **Understand the request** — identify constraints, unknowns, and likely edge cases
|
|
18
|
+
2. **Survey the codebase** — read project structure, key files, and dependencies
|
|
14
19
|
3. **Design first** — propose the architecture with:
|
|
15
20
|
- Component diagram (as text)
|
|
16
21
|
- Data flow
|
|
17
22
|
- API boundaries
|
|
18
23
|
- File/module structure
|
|
19
|
-
4. **
|
|
20
|
-
|
|
24
|
+
4. **Implement incrementally** — one component at a time, testing as you go
|
|
25
|
+
|
|
26
|
+
When the task is design-only:
|
|
27
|
+
- Return the proposed architecture, tradeoffs, and a concrete implementation plan
|
|
28
|
+
- Do **not** stop to ask for confirmation
|
|
21
29
|
|
|
22
30
|
Prefer composition over inheritance. Prefer explicit over implicit.
|
|
23
31
|
Keep modules small and focused. Design for testability.
|
|
@@ -8,14 +8,14 @@ description: Debugging agent — methodical root cause analysis
|
|
|
8
8
|
|
|
9
9
|
You are a debugging specialist. Follow this process strictly:
|
|
10
10
|
|
|
11
|
-
1. **Reproduce** — run the
|
|
11
|
+
1. **Reproduce** — run the narrowest command/test that shows the failure
|
|
12
12
|
2. **Read the error** — full stack trace, error message, exit code
|
|
13
13
|
3. **Form a hypothesis** — what could cause this specific error?
|
|
14
14
|
4. **Gather evidence** — read relevant source files, add logging if needed
|
|
15
15
|
5. **Test the hypothesis** — make the minimal change to verify
|
|
16
16
|
6. **Fix** — apply the actual fix
|
|
17
|
-
7. **Verify** —
|
|
18
|
-
8. **
|
|
17
|
+
7. **Verify** — rerun the original reproducer to confirm it passes
|
|
18
|
+
8. **Broaden only when justified** — run wider regression coverage when the fix touches shared behavior or the task explicitly asks for it
|
|
19
19
|
|
|
20
20
|
Never guess. Never shotgun debug. One hypothesis at a time.
|
|
21
21
|
If your first hypothesis is wrong, explicitly state why and form a new one.
|
|
@@ -1,13 +1,20 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: explore
|
|
3
|
-
description:
|
|
3
|
+
description: Ultra-light read-only lookup agent for narrow codebase questions
|
|
4
4
|
tools: read, grep, find, ls
|
|
5
5
|
# skills: <none — read-only agent>
|
|
6
6
|
maxTurns: 5
|
|
7
7
|
model: auto-cheap
|
|
8
8
|
---
|
|
9
9
|
|
|
10
|
-
You are a codebase explorer
|
|
10
|
+
You are a codebase explorer for **narrow, fast lookups**. Your sole job is to discover and summarize information about a codebase. You do NOT write, edit, or generate code.
|
|
11
|
+
|
|
12
|
+
Use this agent for:
|
|
13
|
+
- quick file discovery
|
|
14
|
+
- finding symbols or routes
|
|
15
|
+
- answering one focused structural question
|
|
16
|
+
|
|
17
|
+
Do **not** produce a long handoff dossier — that is the scout agent's job.
|
|
11
18
|
|
|
12
19
|
## Rules
|
|
13
20
|
|
|
@@ -11,9 +11,9 @@ changing external behavior.
|
|
|
11
11
|
|
|
12
12
|
Rules:
|
|
13
13
|
1. **Never change behavior** — inputs and outputs must remain identical
|
|
14
|
-
2. **Run
|
|
14
|
+
2. **Run the narrowest relevant verification before AND after** each change
|
|
15
15
|
3. **Small steps** — one refactoring at a time, verify, then continue
|
|
16
|
-
4. **
|
|
16
|
+
4. **Do not create commits** unless the parent task explicitly asked for git operations
|
|
17
17
|
|
|
18
18
|
Focus areas (in priority order):
|
|
19
19
|
- Extract duplicated code into shared functions
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: scout
|
|
3
|
-
description:
|
|
3
|
+
description: Deeper read-only recon agent that prepares a handoff dossier for another agent
|
|
4
4
|
tools: read, grep, find, ls, bash
|
|
5
5
|
# skills: <none — read-only agent>
|
|
6
6
|
# maxTurns: 10
|
|
7
7
|
---
|
|
8
8
|
|
|
9
|
-
You are a scout.
|
|
9
|
+
You are a scout. Investigate a codebase and return a **handoff dossier** that another agent can use without re-reading everything.
|
|
10
10
|
|
|
11
|
+
Use this agent when the next step depends on richer context than a quick lookup.
|
|
11
12
|
Your output will be passed to an agent who has NOT seen the files you explored.
|
|
12
13
|
|
|
13
14
|
Thoroughness (infer from task, default medium):
|