shariq-pi-extensions 0.3.0 → 0.3.2
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 -4
- package/docs/ARCHITECTURE.md +2 -3
- package/docs/DEVELOPMENT.md +2 -2
- package/docs/EXTENSIONS.md +1 -15
- package/extensions/shared/README.md +1 -1
- package/extensions/shared/child-session.ts +0 -2
- package/extensions/shared/dashboard-state.ts +0 -44
- package/extensions/smart-compaction/engine.ts +83 -3
- package/extensions/subagents/src/backends/pi.ts +0 -2
- package/extensions/task-list/README.md +1 -1
- package/package.json +1 -5
- package/extensions/copy-all/README.md +0 -15
- package/extensions/copy-all/index.ts +0 -72
- package/extensions/git-info/README.md +0 -25
- package/extensions/git-info/index.ts +0 -160
- package/extensions/git-info/src/git.ts +0 -206
- package/extensions/git-info/src/ui.ts +0 -291
- package/extensions/orchestration/README.md +0 -58
- package/extensions/orchestration/dashboard.ts +0 -259
- package/extensions/orchestration/engine.ts +0 -706
- package/extensions/orchestration/index.ts +0 -279
- package/extensions/orchestration/prompts.ts +0 -124
- package/extensions/orchestration/protocol.ts +0 -116
- package/extensions/orchestration/settings-ui.ts +0 -213
- package/extensions/orchestration/settings.ts +0 -59
- package/extensions/orchestration/storage.ts +0 -49
- package/extensions/orchestration/types.ts +0 -105
- package/skills/orchestration/SKILL.md +0 -33
package/README.md
CHANGED
|
@@ -24,13 +24,10 @@ The package contains:
|
|
|
24
24
|
|
|
25
25
|
- structured user questions
|
|
26
26
|
- managed background terminals
|
|
27
|
-
- copy-all transcript support
|
|
28
27
|
- Firecrawl search and scraping
|
|
29
|
-
- Git status UI
|
|
30
28
|
- persistent task goals
|
|
31
29
|
- branch-safe model-maintained task lists with live progress UI and compaction continuity
|
|
32
30
|
- configurable steer, interrupt, or follow-up input behavior
|
|
33
|
-
- dedicated multi-agent orchestration
|
|
34
31
|
- Smart Compaction with high-fidelity checkpointing, delta-merging, and custom model routing
|
|
35
32
|
- per-response TPS, TTFT, elapsed-time, and output status
|
|
36
33
|
- Context Usage display
|
|
@@ -38,7 +35,7 @@ The package contains:
|
|
|
38
35
|
- Pi subagents
|
|
39
36
|
- lightweight URL fetching
|
|
40
37
|
|
|
41
|
-
The package also includes the `ember-warm-dark` TUI theme and declares the `background-terminals
|
|
38
|
+
The package also includes the `ember-warm-dark` TUI theme and declares the `background-terminals` and `subagents` skills so Pi loads their operating guidance automatically. Pi reads them from the installed package; duplicating files under `<agent-dir>/skills` is unnecessary and would leave stale copies after removal.
|
|
42
39
|
|
|
43
40
|
See [`docs/EXTENSIONS.md`](docs/EXTENSIONS.md) for commands, tools, configuration, and external dependencies.
|
|
44
41
|
|
package/docs/ARCHITECTURE.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Architecture
|
|
2
2
|
|
|
3
|
-
Last verified: 2026-
|
|
3
|
+
Last verified: 2026-09-04
|
|
4
4
|
|
|
5
5
|
## Package boundary
|
|
6
6
|
|
|
@@ -17,7 +17,7 @@ docs/ package-level maintenance documentation
|
|
|
17
17
|
scripts/ repository validation helpers
|
|
18
18
|
```
|
|
19
19
|
|
|
20
|
-
Extension entrypoints register tools, commands, providers, events, or UI. Shared code stays in `extensions/shared` only when more than one extension owns the behavior. The shared activity dock combines active Task List, terminal,
|
|
20
|
+
Extension entrypoints register tools, commands, providers, events, or UI. Shared code stays in `extensions/shared` only when more than one extension owns the behavior. The shared activity dock combines active Task List, terminal, and subagent rows under one bounded widget; shared tool cards keep lifecycle operations compact in the timeline while preserving expanded output. Provider-specific protocol and authentication code remains inside its provider directory. The background-terminal and subagent skills ship beside their extensions so a new machine receives the required lifecycle guidance with the tools.
|
|
21
21
|
|
|
22
22
|
## Dependency model
|
|
23
23
|
|
|
@@ -37,7 +37,6 @@ Installed package directories are treated as immutable. Extensions resolve writa
|
|
|
37
37
|
- Smart Compaction configuration and details ledgers: `<agent-dir>/smart-compaction.json`
|
|
38
38
|
- Input mode configuration: `<agent-dir>/input-mode.json`
|
|
39
39
|
- Subagent configuration and catalog: paths derived from `getAgentDir()`
|
|
40
|
-
- Orchestration settings and run ledgers: `<agent-dir>/orchestration/`
|
|
41
40
|
- Goal and Task List state: branch-local Pi session entries; neither writes a separate runtime-state file
|
|
42
41
|
- project configuration: paths derived from Pi's `CONFIG_DIR_NAME`
|
|
43
42
|
|
package/docs/DEVELOPMENT.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Development and cutover
|
|
2
2
|
|
|
3
|
-
Last verified: 2026-
|
|
3
|
+
Last verified: 2026-09-04
|
|
4
4
|
|
|
5
5
|
## Requirements
|
|
6
6
|
|
|
@@ -45,7 +45,7 @@ Keep secrets out of examples and fixtures. Tests should inject temporary roots,
|
|
|
45
45
|
|
|
46
46
|
## Skills paired with extensions
|
|
47
47
|
|
|
48
|
-
The root manifest declares `skills/background-terminals
|
|
48
|
+
The root manifest declares `skills/background-terminals` and `skills/subagents`. Pi loads them directly from the managed package. Do not copy them into the agent directory from install scripts; that would create duplicates and leave stale files after removal.
|
|
49
49
|
|
|
50
50
|
When any paired skill changes, validate its structure and keep its behavior aligned with the corresponding extension tools.
|
|
51
51
|
|
package/docs/EXTENSIONS.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Extension catalog
|
|
2
2
|
|
|
3
|
-
Last verified: 2026-09-
|
|
3
|
+
Last verified: 2026-09-04
|
|
4
4
|
|
|
5
5
|
## Agent workflow
|
|
6
6
|
|
|
@@ -24,12 +24,6 @@ The subagent extension runs flat Pi child agents with profiles, capability polic
|
|
|
24
24
|
|
|
25
25
|
The extension supplies tools including `spawn_agent`, `task`, `check_agent`, `list_agents`, `wait_agent`, `send_message`, `close_agent`, `reply_question`, and `apply_agent_changes`. These lifecycle operations render as compact expandable main-chat cards; only active manager entries contribute to the bounded Active work dock, while workflow progress is intentionally omitted from the footer so persisted history cannot inflate or duplicate live status. Cancellation and reload interruption use a distinct `cancelled` state rather than `error`. All completed and cancelled production subagent snapshots and transcripts persist across Pi restarts (`<agent-dir>/subagents/runs/`), allowing `resume_from` to resume completed workers at any point. Interruption immediately cascades across all child fibers in `<10ms`. Child settlement stays in a private extension queue while the parent is active, then starts one custom-result turn at Pi's safe idle edge with the summary guaranteed in model context and never rendered as user-authored or follow-up input; status tools are for explicit inspection, not waiting.
|
|
26
26
|
|
|
27
|
-
### [Orchestration](../extensions/orchestration/README.md)
|
|
28
|
-
|
|
29
|
-
The Orchestration extension coordinates explicitly requested large tasks through a dedicated orchestrator plus configurable explorer, frontend, backend, general-worker, and reviewer models. `/orchestration` opens the dashboard and `/orchestration settings` opens the interactive role-configuration panel with the Searchable Model Picker. It reuses the canonical Subagents runtime, maintains a 10-worker pool per project, isolates Git writers in task worktrees, resumes the original worker for substantial review fixes, and applies final reviewed changes without committing or pushing.
|
|
30
|
-
|
|
31
|
-
The model-facing `create_orchestration` tool starts planning only after an explicit orchestration request. `get_orchestration` reports status without advancing work. Both use compact expandable timeline cards, and active runs share the full-screen Active work dock with explicit plan-ready, blocked, running, and paused states instead of duplicating workflow progress in the footer. The full-height framed dashboard provides scrollable multi-task inspection and runs editors and confirmations outside its overlay to avoid nested-input stalls. Interrupted runs recover paused under `<agent-dir>/orchestration/`.
|
|
32
|
-
|
|
33
27
|
### [Smart Compaction](../extensions/smart-compaction/README.md)
|
|
34
28
|
|
|
35
29
|
Replaces standard context compaction with a defensive, high-fidelity continuity engine. It intercepts `session_before_compact` events and synthesizes multi-turn conversations into structured checkpoint summaries capturing primary goals and negative constraints, progress ledgers (`Done`/`In Progress`/`Blocked`), verbatim code snippets for active/uncommitted edits, exact error root causes, architectural decisions, resume anchors, and deterministic file/diff state.
|
|
@@ -73,14 +67,6 @@ Displays the estimated prompt and session contribution to the active context win
|
|
|
73
67
|
|
|
74
68
|
Adds a responsive footer-area status row while each assistant message is active and for an eight-second completion linger, then clears it to avoid permanent footer noise. Live TPS/output are marked as estimates; final TPS uses conventional decode throughput from first streamed token to message completion. TTFT and total elapsed time separately expose provider latency, prefill, and hidden reasoning; output tokens and active tools remain separate.
|
|
75
69
|
|
|
76
|
-
### [Git info](../extensions/git-info/README.md)
|
|
77
|
-
|
|
78
|
-
Adds repository state to Pi's interface. `/lg` opens the local Git view and `/pr` exposes pull-request context when available.
|
|
79
|
-
|
|
80
|
-
### [Copy all](../extensions/copy-all/README.md)
|
|
81
|
-
|
|
82
|
-
`/copy-all` copies the current conversation in a readable form while omitting tool protocol noise that does not belong in the transcript.
|
|
83
|
-
|
|
84
70
|
### [Input mode](../extensions/input-mode/README.md)
|
|
85
71
|
|
|
86
72
|
`/input-mode` chooses what ordinary Enter does while Pi is running: steer before the next model step, interrupt the active run, or wait as a follow-up. The global selection lives in `<agent-dir>/input-mode.json`; explicit Alt+Enter follow-ups and non-interactive inputs retain native behavior. Pi does not expose extension rows in core `/settings`, so this is a dedicated extension settings picker rather than a private TUI patch.
|
|
@@ -4,7 +4,7 @@ Internal runtime utilities used by more than one extension. This directory is no
|
|
|
4
4
|
|
|
5
5
|
## Modules
|
|
6
6
|
|
|
7
|
-
- `activity-dock.ts` aggregates bounded urgency-ordered live rows from Task List, Background Terminals,
|
|
7
|
+
- `activity-dock.ts` aggregates bounded urgency-ordered live rows from Task List, Background Terminals, and Subagents into one automatic widget.
|
|
8
8
|
- `activity-status.ts` formats compact running/completed/failed status counts.
|
|
9
9
|
- `child-session.ts` owns trust-aware child resources and bounded session shutdown.
|
|
10
10
|
- `context-utilization.ts` formats model-context usage and capacity.
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
export const MODEL_INFO_CHANNEL = "dashboard:model-info";
|
|
2
|
-
export const GIT_INFO_CHANNEL = "dashboard:git-info";
|
|
3
2
|
export const REFRESH_CHANNEL = "dashboard:refresh";
|
|
4
3
|
|
|
5
4
|
export interface ModelInfoState {
|
|
@@ -15,19 +14,6 @@ export interface ModelInfoState {
|
|
|
15
14
|
generating: boolean;
|
|
16
15
|
}
|
|
17
16
|
|
|
18
|
-
export interface PullRequestInfo {
|
|
19
|
-
number: number;
|
|
20
|
-
url: string;
|
|
21
|
-
isDraft: boolean;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export interface GitInfoState {
|
|
25
|
-
isRepository: boolean;
|
|
26
|
-
branch: string | null;
|
|
27
|
-
changedFiles: number;
|
|
28
|
-
pullRequest: PullRequestInfo | null;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
17
|
export function emptyModelInfoState(): ModelInfoState {
|
|
32
18
|
return {
|
|
33
19
|
provider: "",
|
|
@@ -43,15 +29,6 @@ export function emptyModelInfoState(): ModelInfoState {
|
|
|
43
29
|
};
|
|
44
30
|
}
|
|
45
31
|
|
|
46
|
-
export function emptyGitInfoState(): GitInfoState {
|
|
47
|
-
return {
|
|
48
|
-
isRepository: false,
|
|
49
|
-
branch: null,
|
|
50
|
-
changedFiles: 0,
|
|
51
|
-
pullRequest: null,
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
|
|
55
32
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
56
33
|
return typeof value === "object" && value !== null;
|
|
57
34
|
}
|
|
@@ -76,24 +53,3 @@ export function isModelInfoState(value: unknown): value is ModelInfoState {
|
|
|
76
53
|
typeof value.generating === "boolean"
|
|
77
54
|
);
|
|
78
55
|
}
|
|
79
|
-
|
|
80
|
-
function isPullRequestInfo(value: unknown): value is PullRequestInfo {
|
|
81
|
-
if (!isRecord(value)) return false;
|
|
82
|
-
|
|
83
|
-
return (
|
|
84
|
-
typeof value.number === "number" &&
|
|
85
|
-
typeof value.url === "string" &&
|
|
86
|
-
typeof value.isDraft === "boolean"
|
|
87
|
-
);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
export function isGitInfoState(value: unknown): value is GitInfoState {
|
|
91
|
-
if (!isRecord(value)) return false;
|
|
92
|
-
|
|
93
|
-
return (
|
|
94
|
-
typeof value.isRepository === "boolean" &&
|
|
95
|
-
(value.branch === null || typeof value.branch === "string") &&
|
|
96
|
-
typeof value.changedFiles === "number" &&
|
|
97
|
-
(value.pullRequest === null || isPullRequestInfo(value.pullRequest))
|
|
98
|
-
);
|
|
99
|
-
}
|
|
@@ -50,6 +50,7 @@ export interface SmartCompactionDetails {
|
|
|
50
50
|
serializedCharacters: number;
|
|
51
51
|
summaryCharacters: number;
|
|
52
52
|
attemptCount: number;
|
|
53
|
+
retainedIdentifiersAppended?: number;
|
|
53
54
|
durationMs: number;
|
|
54
55
|
timestamp: number;
|
|
55
56
|
}
|
|
@@ -328,7 +329,9 @@ const REQUIRED_SECTION_PATTERNS = [
|
|
|
328
329
|
/## 6\.\s+Resume Anchor/i,
|
|
329
330
|
];
|
|
330
331
|
|
|
331
|
-
export
|
|
332
|
+
export const RETAINED_IDENTIFIERS_HEADING = "### Retained Identifiers";
|
|
333
|
+
|
|
334
|
+
export function extractSummaryText(response: AssistantMessage): string {
|
|
332
335
|
if (response.stopReason !== "stop") {
|
|
333
336
|
const errorDetails = response.errorMessage ? `: ${response.errorMessage}` : "";
|
|
334
337
|
throw new Error(`Compaction model did not complete successfully (stopReason="${response.stopReason}"${errorDetails}).`);
|
|
@@ -350,13 +353,38 @@ export function validateSummaryOutput(response: AssistantMessage, protectedFacts
|
|
|
350
353
|
throw new Error("Compaction model returned an empty summary.");
|
|
351
354
|
}
|
|
352
355
|
|
|
356
|
+
return rawSummaryText;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
export function getDroppedProtectedFacts(summaryText: string, protectedFacts: readonly string[] = []): string[] {
|
|
360
|
+
return protectedFacts.filter((fact) => fact && !summaryText.includes(fact));
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
export function withRetainedIdentifiers(summaryText: string, droppedFacts: readonly string[]): string {
|
|
364
|
+
// Protected facts are single-line opaque identifiers (hashes, UUIDs, clean
|
|
365
|
+
// URLs, IPs), so reproducing them on their own bullet lines is verbatim-safe.
|
|
366
|
+
const lines = [summaryText.trimEnd(), "", RETAINED_IDENTIFIERS_HEADING];
|
|
367
|
+
for (const fact of droppedFacts) {
|
|
368
|
+
const clean = String(fact).trim();
|
|
369
|
+
if (clean) lines.push(`- ${clean}`);
|
|
370
|
+
}
|
|
371
|
+
return lines.join("\n") + "\n";
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
export function validateSummaryText(summaryText: string, protectedFacts: readonly string[] = []): string {
|
|
375
|
+
const rawSummaryText = summaryText.trim();
|
|
376
|
+
|
|
377
|
+
if (!rawSummaryText) {
|
|
378
|
+
throw new Error("Compaction model returned an empty summary.");
|
|
379
|
+
}
|
|
380
|
+
|
|
353
381
|
// Verify all 6 required sections exist
|
|
354
382
|
for (const pattern of REQUIRED_SECTION_PATTERNS) {
|
|
355
383
|
if (!pattern.test(rawSummaryText)) {
|
|
356
384
|
throw new Error(`Compaction summary is incomplete: missing required section matching ${pattern.source}`);
|
|
357
385
|
}
|
|
358
386
|
}
|
|
359
|
-
const missingFacts =
|
|
387
|
+
const missingFacts = getDroppedProtectedFacts(rawSummaryText, protectedFacts);
|
|
360
388
|
if (missingFacts.length > 0) {
|
|
361
389
|
throw new Error(`Compaction summary dropped protected facts: ${missingFacts.slice(0, 3).join(" | ")}`);
|
|
362
390
|
}
|
|
@@ -364,6 +392,29 @@ export function validateSummaryOutput(response: AssistantMessage, protectedFacts
|
|
|
364
392
|
return rawSummaryText;
|
|
365
393
|
}
|
|
366
394
|
|
|
395
|
+
export function validateSummaryOutput(response: AssistantMessage, protectedFacts: readonly string[] = []): string {
|
|
396
|
+
return validateSummaryText(extractSummaryText(response), protectedFacts);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* Return the summary text when it is structurally sound (extractable with all
|
|
401
|
+
* required sections) regardless of dropped protected facts. Identifier-only
|
|
402
|
+
* defects can be repaired deterministically with {@link withRetainedIdentifiers};
|
|
403
|
+
* structural defects cannot, so those return undefined.
|
|
404
|
+
*/
|
|
405
|
+
export function extractRepairableSummary(response: AssistantMessage): string | undefined {
|
|
406
|
+
let text: string;
|
|
407
|
+
try {
|
|
408
|
+
text = extractSummaryText(response);
|
|
409
|
+
} catch {
|
|
410
|
+
return undefined;
|
|
411
|
+
}
|
|
412
|
+
for (const pattern of REQUIRED_SECTION_PATTERNS) {
|
|
413
|
+
if (!pattern.test(text)) return undefined;
|
|
414
|
+
}
|
|
415
|
+
return text;
|
|
416
|
+
}
|
|
417
|
+
|
|
367
418
|
export function computeCompactionTokenCeiling(
|
|
368
419
|
model: Model<Api>,
|
|
369
420
|
config: SmartCompactionConfig,
|
|
@@ -529,6 +580,8 @@ export async function runSmartCompaction(
|
|
|
529
580
|
|
|
530
581
|
let lastError: Error | undefined;
|
|
531
582
|
let finalSummaryText = "";
|
|
583
|
+
let repairCandidate: string | undefined;
|
|
584
|
+
let retainedIdentifiersAppended = 0;
|
|
532
585
|
let accumulatedUsage: Usage | undefined;
|
|
533
586
|
let attemptCount = 0;
|
|
534
587
|
let activeModel = primaryModel;
|
|
@@ -572,7 +625,16 @@ export async function runSmartCompaction(
|
|
|
572
625
|
if (response.usage) {
|
|
573
626
|
accumulatedUsage = combineCompactionUsage(accumulatedUsage, response.usage);
|
|
574
627
|
}
|
|
575
|
-
|
|
628
|
+
try {
|
|
629
|
+
finalSummaryText = validateSummaryOutput(response, protectedFacts);
|
|
630
|
+
} catch (validationError) {
|
|
631
|
+
// Structurally sound summaries that only drop protected facts are
|
|
632
|
+
// kept as deterministic-repair candidates; structural defects cannot
|
|
633
|
+
// be repaired, so those leave no candidate behind.
|
|
634
|
+
const repairable = extractRepairableSummary(response);
|
|
635
|
+
if (repairable !== undefined) repairCandidate = repairable;
|
|
636
|
+
throw validationError;
|
|
637
|
+
}
|
|
576
638
|
lastError = undefined;
|
|
577
639
|
break; // Success!
|
|
578
640
|
} catch (err) {
|
|
@@ -586,6 +648,23 @@ export async function runSmartCompaction(
|
|
|
586
648
|
}
|
|
587
649
|
}
|
|
588
650
|
|
|
651
|
+
if ((lastError || !finalSummaryText) && repairCandidate !== undefined) {
|
|
652
|
+
// Every stage produced a structurally sound summary but the model
|
|
653
|
+
// deterministically refused to repeat low-signal identifiers. Restore
|
|
654
|
+
// them verbatim instead of failing the whole compaction.
|
|
655
|
+
const stillMissing = getDroppedProtectedFacts(repairCandidate, protectedFacts);
|
|
656
|
+
try {
|
|
657
|
+
finalSummaryText = validateSummaryText(
|
|
658
|
+
withRetainedIdentifiers(repairCandidate, stillMissing),
|
|
659
|
+
protectedFacts,
|
|
660
|
+
);
|
|
661
|
+
lastError = undefined;
|
|
662
|
+
retainedIdentifiersAppended = stillMissing.length;
|
|
663
|
+
} catch {
|
|
664
|
+
// Repair rejected; fall through to the original failure below.
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
|
|
589
668
|
if (lastError || !finalSummaryText) {
|
|
590
669
|
throw lastError ?? new Error("All smart compaction retry stages failed.");
|
|
591
670
|
}
|
|
@@ -643,6 +722,7 @@ export async function runSmartCompaction(
|
|
|
643
722
|
serializedCharacters: conversationText.length,
|
|
644
723
|
summaryCharacters: finalSummary.length,
|
|
645
724
|
attemptCount,
|
|
725
|
+
retainedIdentifiersAppended: retainedIdentifiersAppended > 0 ? retainedIdentifiersAppended : undefined,
|
|
646
726
|
durationMs: Date.now() - startedAt,
|
|
647
727
|
timestamp: Date.now(),
|
|
648
728
|
};
|
|
@@ -64,7 +64,7 @@ Run:
|
|
|
64
64
|
/tasks
|
|
65
65
|
```
|
|
66
66
|
|
|
67
|
-
While work is active, the shared bounded **Active work** dock above the editor shows progress and current items alongside any live terminals
|
|
67
|
+
While work is active, the shared bounded **Active work** dock above the editor shows progress and current items alongside any live terminals or subagents without stacking separate widgets. A finished list lingers for four seconds so the final checkmark is visible, then clears from the live chrome while remaining available in session history.
|
|
68
68
|
|
|
69
69
|
`/tasks` opens the full-width interactive dashboard:
|
|
70
70
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "shariq-pi-extensions",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"description": "Cross-platform extension suite for the Pi coding agent.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Shariq Riaz",
|
|
@@ -40,12 +40,9 @@
|
|
|
40
40
|
"./extensions/ask-user/index.ts",
|
|
41
41
|
"./extensions/background-terminals/index.ts",
|
|
42
42
|
"./extensions/context-usage/index.ts",
|
|
43
|
-
"./extensions/copy-all/index.ts",
|
|
44
43
|
"./extensions/firecrawl-web/index.ts",
|
|
45
|
-
"./extensions/git-info/index.ts",
|
|
46
44
|
"./extensions/goal/index.ts",
|
|
47
45
|
"./extensions/input-mode/index.ts",
|
|
48
|
-
"./extensions/orchestration/index.ts",
|
|
49
46
|
"./extensions/performance-status/index.ts",
|
|
50
47
|
"./extensions/shell-shortcuts/index.ts",
|
|
51
48
|
"./extensions/smart-compaction/index.ts",
|
|
@@ -55,7 +52,6 @@
|
|
|
55
52
|
],
|
|
56
53
|
"skills": [
|
|
57
54
|
"./skills/background-terminals",
|
|
58
|
-
"./skills/orchestration",
|
|
59
55
|
"./skills/subagents"
|
|
60
56
|
],
|
|
61
57
|
"themes": [
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
# Copy All
|
|
2
|
-
|
|
3
|
-
Copies a readable transcript of the active Pi conversation branch to the system clipboard.
|
|
4
|
-
|
|
5
|
-
## Command
|
|
6
|
-
|
|
7
|
-
`/copy-all` waits for the current agent run to settle, then copies visible user and assistant messages from the active branch. Messages are labeled `USER` and `ASSISTANT` and separated with a horizontal divider.
|
|
8
|
-
|
|
9
|
-
The exported transcript intentionally excludes system messages, tool calls, tool results, and hidden reasoning. Image blocks are represented as `[image]`. Empty branches produce an informational notification instead of changing the clipboard.
|
|
10
|
-
|
|
11
|
-
Clipboard access uses Pi's cross-platform clipboard helper. A clipboard failure is shown in the interface and does not alter the session.
|
|
12
|
-
|
|
13
|
-
## Validation
|
|
14
|
-
|
|
15
|
-
From the repository root, run `npm run validate`.
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
copyToClipboard,
|
|
3
|
-
type ExtensionAPI,
|
|
4
|
-
} from "@earendil-works/pi-coding-agent";
|
|
5
|
-
|
|
6
|
-
export function textFromMessageContent(content: unknown): string {
|
|
7
|
-
if (typeof content === "string") return content.trim();
|
|
8
|
-
if (!Array.isArray(content)) return "";
|
|
9
|
-
return content
|
|
10
|
-
.map((block) => {
|
|
11
|
-
if (!block || typeof block !== "object" || !("type" in block)) return "";
|
|
12
|
-
if (
|
|
13
|
-
block.type === "text" &&
|
|
14
|
-
"text" in block &&
|
|
15
|
-
typeof block.text === "string"
|
|
16
|
-
) {
|
|
17
|
-
return block.text;
|
|
18
|
-
}
|
|
19
|
-
if (block.type === "image") return "[image]";
|
|
20
|
-
return "";
|
|
21
|
-
})
|
|
22
|
-
.filter(Boolean)
|
|
23
|
-
.join("\n")
|
|
24
|
-
.trim();
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export function formatConversation(
|
|
28
|
-
messages: ReadonlyArray<{ role?: unknown; content?: unknown }>,
|
|
29
|
-
): string {
|
|
30
|
-
return messages
|
|
31
|
-
.filter(
|
|
32
|
-
(message) => message.role === "user" || message.role === "assistant",
|
|
33
|
-
)
|
|
34
|
-
.map((message) => ({
|
|
35
|
-
role: String(message.role).toUpperCase(),
|
|
36
|
-
text: textFromMessageContent(message.content),
|
|
37
|
-
}))
|
|
38
|
-
.filter((message) => message.text)
|
|
39
|
-
.map((message) => `${message.role}:\n${message.text}`)
|
|
40
|
-
.join("\n\n---\n\n");
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export default function copyAllExtension(pi: ExtensionAPI) {
|
|
44
|
-
pi.registerCommand("copy-all", {
|
|
45
|
-
description: "Copy visible user and assistant text from the current branch",
|
|
46
|
-
handler: async (_args, ctx) => {
|
|
47
|
-
await ctx.waitForIdle();
|
|
48
|
-
const messages = ctx.sessionManager
|
|
49
|
-
.getBranch()
|
|
50
|
-
.filter((entry) => entry.type === "message")
|
|
51
|
-
.map((entry) => entry.message);
|
|
52
|
-
const visibleMessages = messages.filter(
|
|
53
|
-
(message) => message.role === "user" || message.role === "assistant",
|
|
54
|
-
);
|
|
55
|
-
const transcript = formatConversation(visibleMessages);
|
|
56
|
-
if (!transcript) {
|
|
57
|
-
ctx.ui.notify("No user or assistant messages to copy", "info");
|
|
58
|
-
return;
|
|
59
|
-
}
|
|
60
|
-
try {
|
|
61
|
-
await copyToClipboard(transcript);
|
|
62
|
-
} catch (error) {
|
|
63
|
-
ctx.ui.notify(
|
|
64
|
-
`Could not copy the conversation: ${error instanceof Error ? error.message : String(error)}`,
|
|
65
|
-
"error",
|
|
66
|
-
);
|
|
67
|
-
return;
|
|
68
|
-
}
|
|
69
|
-
ctx.ui.notify(`Copied ${visibleMessages.length} branch messages`, "info");
|
|
70
|
-
},
|
|
71
|
-
});
|
|
72
|
-
}
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
# Git Info
|
|
2
|
-
|
|
3
|
-
Shows the current Git branch and working-tree state in Pi and provides an interactive changed-file viewer.
|
|
4
|
-
|
|
5
|
-
## Interface and commands
|
|
6
|
-
|
|
7
|
-
- The footer status displays the branch, changed-file count, and open pull-request number when known.
|
|
8
|
-
- `/lg` opens a TUI browser for changed and untracked files, line counts, and bounded textual diffs.
|
|
9
|
-
- `/pr` refreshes pull-request information for the current branch and reports its URL.
|
|
10
|
-
|
|
11
|
-
Status refreshes after user input and tool execution, with debounce and stale-result protection. Outside a Git repository, the footer is hidden and commands return a clear notice.
|
|
12
|
-
|
|
13
|
-
## Dependencies and limits
|
|
14
|
-
|
|
15
|
-
- Requires `git` for repository status and diffs.
|
|
16
|
-
- `/pr` additionally requires an authenticated GitHub CLI (`gh`); unavailable or non-open pull requests are treated as no matching open pull request.
|
|
17
|
-
- The changed-file viewer loads at most 250 files and bounds each textual diff to 20,000 lines.
|
|
18
|
-
- Binary file statistics may be unavailable and are displayed without fabricated counts.
|
|
19
|
-
- Repository paths and command output are sanitized before TUI rendering.
|
|
20
|
-
|
|
21
|
-
The extension reads repository state only. It does not stage, commit, push, or modify files.
|
|
22
|
-
|
|
23
|
-
## Validation
|
|
24
|
-
|
|
25
|
-
From the repository root, run `npm run validate`.
|
|
@@ -1,160 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
BorderedLoader,
|
|
3
|
-
type ExtensionAPI,
|
|
4
|
-
type ExtensionContext,
|
|
5
|
-
type ExtensionUIContext,
|
|
6
|
-
} from "@earendil-works/pi-coding-agent";
|
|
7
|
-
import {
|
|
8
|
-
loadChangedFiles,
|
|
9
|
-
loadGitSummary,
|
|
10
|
-
loadPullRequest,
|
|
11
|
-
type GitSummary,
|
|
12
|
-
} from "./src/git.ts";
|
|
13
|
-
import { openGitChanges } from "./src/ui.ts";
|
|
14
|
-
|
|
15
|
-
const STATUS_KEY = "git-info";
|
|
16
|
-
const REFRESH_DEBOUNCE_MS = 150;
|
|
17
|
-
|
|
18
|
-
function statusText(ui: ExtensionUIContext, summary: GitSummary): string | undefined {
|
|
19
|
-
if (!summary.isRepository || !summary.branch) return undefined;
|
|
20
|
-
const changed = summary.changedFiles === 0
|
|
21
|
-
? ui.theme.fg("muted", "✓")
|
|
22
|
-
: ui.theme.fg("warning", `+${summary.changedFiles}`);
|
|
23
|
-
const pr = summary.pullRequest
|
|
24
|
-
? ` · ${ui.theme.fg("accent", `PR #${summary.pullRequest.number}${summary.pullRequest.draft ? " draft" : ""}`)}`
|
|
25
|
-
: "";
|
|
26
|
-
const command = summary.changedFiles > 0 || summary.pullRequest ? ` · ${ui.theme.fg("dim", "/lg")}` : "";
|
|
27
|
-
return `${ui.theme.fg("muted", "git")} ${ui.theme.fg("accent", summary.branch)} ${changed}${pr}${command}`;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export default function gitInfoExtension(pi: ExtensionAPI) {
|
|
31
|
-
let ui: ExtensionUIContext | undefined;
|
|
32
|
-
let summary: GitSummary = { isRepository: false, changedFiles: 0 };
|
|
33
|
-
let generation = 0;
|
|
34
|
-
let refreshTimer: ReturnType<typeof setTimeout> | undefined;
|
|
35
|
-
let refreshSequence = 0;
|
|
36
|
-
|
|
37
|
-
const publish = () => ui?.setStatus(STATUS_KEY, statusText(ui, summary));
|
|
38
|
-
|
|
39
|
-
const refresh = async (ctx: ExtensionContext, expectedGeneration: number) => {
|
|
40
|
-
const sequence = ++refreshSequence;
|
|
41
|
-
try {
|
|
42
|
-
const next = await loadGitSummary(pi, ctx.cwd);
|
|
43
|
-
if (generation !== expectedGeneration || sequence !== refreshSequence) return;
|
|
44
|
-
const sameBranch = next.branch && next.branch === summary.branch;
|
|
45
|
-
summary = {
|
|
46
|
-
...next,
|
|
47
|
-
pullRequest: sameBranch ? summary.pullRequest : undefined,
|
|
48
|
-
};
|
|
49
|
-
publish();
|
|
50
|
-
} catch {
|
|
51
|
-
if (generation === expectedGeneration && sequence === refreshSequence) {
|
|
52
|
-
summary = { isRepository: false, changedFiles: 0 };
|
|
53
|
-
publish();
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
const scheduleRefresh = (ctx: ExtensionContext) => {
|
|
59
|
-
if (!ui) return;
|
|
60
|
-
const expectedGeneration = generation;
|
|
61
|
-
if (refreshTimer) clearTimeout(refreshTimer);
|
|
62
|
-
refreshTimer = setTimeout(() => {
|
|
63
|
-
refreshTimer = undefined;
|
|
64
|
-
void refresh(ctx, expectedGeneration);
|
|
65
|
-
}, REFRESH_DEBOUNCE_MS);
|
|
66
|
-
refreshTimer.unref?.();
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
pi.on("session_start", (_event, ctx) => {
|
|
70
|
-
generation++;
|
|
71
|
-
summary = { isRepository: false, changedFiles: 0 };
|
|
72
|
-
if (ctx.hasUI) ui = ctx.ui;
|
|
73
|
-
void refresh(ctx, generation);
|
|
74
|
-
});
|
|
75
|
-
pi.on("input", (_event, ctx) => {
|
|
76
|
-
scheduleRefresh(ctx);
|
|
77
|
-
return { action: "continue" };
|
|
78
|
-
});
|
|
79
|
-
pi.on("tool_execution_end", (_event, ctx) => scheduleRefresh(ctx));
|
|
80
|
-
pi.on("session_shutdown", () => {
|
|
81
|
-
generation++;
|
|
82
|
-
refreshSequence++;
|
|
83
|
-
if (refreshTimer) clearTimeout(refreshTimer);
|
|
84
|
-
refreshTimer = undefined;
|
|
85
|
-
ui?.setStatus(STATUS_KEY, undefined);
|
|
86
|
-
ui = undefined;
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
pi.registerCommand("lg", {
|
|
90
|
-
description: "Browse changed files and diffs in the current Git repository",
|
|
91
|
-
handler: async (_args, ctx) => {
|
|
92
|
-
if (ctx.mode !== "tui") {
|
|
93
|
-
if (ctx.hasUI) ctx.ui.notify("The Git changes viewer requires the TUI", "warning");
|
|
94
|
-
return;
|
|
95
|
-
}
|
|
96
|
-
type LoadOutcome =
|
|
97
|
-
| { loaded: Awaited<ReturnType<typeof loadChangedFiles>> }
|
|
98
|
-
| { error: string }
|
|
99
|
-
| null;
|
|
100
|
-
const outcome = await ctx.ui.custom<LoadOutcome>((tui, theme, _keybindings, done) => {
|
|
101
|
-
const loader = new BorderedLoader(tui, theme, "Loading Git changes…");
|
|
102
|
-
let settled = false;
|
|
103
|
-
const finish = (value: LoadOutcome) => {
|
|
104
|
-
if (settled) return;
|
|
105
|
-
settled = true;
|
|
106
|
-
done(value);
|
|
107
|
-
};
|
|
108
|
-
loader.onAbort = () => finish(null);
|
|
109
|
-
loadChangedFiles(pi, ctx.cwd, loader.signal).then(
|
|
110
|
-
(loaded) => finish({ loaded }),
|
|
111
|
-
(error: unknown) => {
|
|
112
|
-
if (loader.signal.aborted) finish(null);
|
|
113
|
-
else finish({ error: error instanceof Error ? error.message : String(error) });
|
|
114
|
-
},
|
|
115
|
-
);
|
|
116
|
-
return loader;
|
|
117
|
-
});
|
|
118
|
-
if (!outcome) {
|
|
119
|
-
ctx.ui.notify("Git changes load cancelled", "info");
|
|
120
|
-
return;
|
|
121
|
-
}
|
|
122
|
-
if ("error" in outcome) {
|
|
123
|
-
ctx.ui.notify(`Could not load Git changes: ${outcome.error}`, "error");
|
|
124
|
-
return;
|
|
125
|
-
}
|
|
126
|
-
const { loaded } = outcome;
|
|
127
|
-
if (!loaded) {
|
|
128
|
-
ctx.ui.notify("Not a Git repository", "warning");
|
|
129
|
-
return;
|
|
130
|
-
}
|
|
131
|
-
if (loaded.files.length === 0) {
|
|
132
|
-
ctx.ui.notify("Working tree is clean", "info");
|
|
133
|
-
return;
|
|
134
|
-
}
|
|
135
|
-
await openGitChanges(ctx, loaded.root, loaded.files, loaded.omitted);
|
|
136
|
-
},
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
pi.registerCommand("pr", {
|
|
140
|
-
description: "Refresh pull-request information for the current branch",
|
|
141
|
-
handler: async (_args, ctx) => {
|
|
142
|
-
const next = await loadGitSummary(pi, ctx.cwd);
|
|
143
|
-
if (!next.isRepository) {
|
|
144
|
-
ctx.ui.notify("Not a Git repository", "warning");
|
|
145
|
-
return;
|
|
146
|
-
}
|
|
147
|
-
const pullRequest = await loadPullRequest(pi, next.root ?? ctx.cwd);
|
|
148
|
-
summary = { ...next, pullRequest };
|
|
149
|
-
publish();
|
|
150
|
-
if (!pullRequest) {
|
|
151
|
-
ctx.ui.notify(`No open pull request found for ${next.branch ?? "this branch"}`, "info");
|
|
152
|
-
return;
|
|
153
|
-
}
|
|
154
|
-
ctx.ui.notify(
|
|
155
|
-
`PR #${pullRequest.number}${pullRequest.draft ? " (draft)" : ""}: ${pullRequest.url}`,
|
|
156
|
-
"info",
|
|
157
|
-
);
|
|
158
|
-
},
|
|
159
|
-
});
|
|
160
|
-
}
|