pi-messenger 0.7.3
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/ARCHITECTURE.md +244 -0
- package/CHANGELOG.md +418 -0
- package/README.md +394 -0
- package/banner.png +0 -0
- package/config-overlay.ts +172 -0
- package/config.ts +178 -0
- package/crew/agents/crew-docs-scout.md +55 -0
- package/crew/agents/crew-gap-analyst.md +105 -0
- package/crew/agents/crew-github-scout.md +111 -0
- package/crew/agents/crew-interview-generator.md +79 -0
- package/crew/agents/crew-plan-sync.md +64 -0
- package/crew/agents/crew-practice-scout.md +62 -0
- package/crew/agents/crew-repo-scout.md +65 -0
- package/crew/agents/crew-reviewer.md +58 -0
- package/crew/agents/crew-web-scout.md +85 -0
- package/crew/agents/crew-worker.md +95 -0
- package/crew/agents.ts +200 -0
- package/crew/handlers/interview.ts +211 -0
- package/crew/handlers/plan.ts +358 -0
- package/crew/handlers/review.ts +341 -0
- package/crew/handlers/status.ts +257 -0
- package/crew/handlers/sync.ts +232 -0
- package/crew/handlers/task.ts +511 -0
- package/crew/handlers/work.ts +289 -0
- package/crew/id-allocator.ts +44 -0
- package/crew/index.ts +229 -0
- package/crew/state.ts +116 -0
- package/crew/store.ts +480 -0
- package/crew/types.ts +164 -0
- package/crew/utils/artifacts.ts +65 -0
- package/crew/utils/config.ts +104 -0
- package/crew/utils/discover.ts +170 -0
- package/crew/utils/install.ts +373 -0
- package/crew/utils/progress.ts +107 -0
- package/crew/utils/result.ts +16 -0
- package/crew/utils/truncate.ts +79 -0
- package/crew-overlay.ts +259 -0
- package/handlers.ts +799 -0
- package/index.ts +591 -0
- package/lib.ts +232 -0
- package/overlay.ts +687 -0
- package/package.json +20 -0
- package/skills/pi-messenger-crew/SKILL.md +140 -0
- package/store.ts +1068 -0
- package/tsconfig.json +19 -0
package/lib.ts
ADDED
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pi Messenger - Types and Pure Utilities
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type * as fs from "node:fs";
|
|
6
|
+
import { basename, isAbsolute, resolve, relative } from "node:path";
|
|
7
|
+
|
|
8
|
+
// =============================================================================
|
|
9
|
+
// Types
|
|
10
|
+
// =============================================================================
|
|
11
|
+
|
|
12
|
+
export interface FileReservation {
|
|
13
|
+
pattern: string;
|
|
14
|
+
reason?: string;
|
|
15
|
+
since: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface AgentRegistration {
|
|
19
|
+
name: string;
|
|
20
|
+
pid: number;
|
|
21
|
+
sessionId: string;
|
|
22
|
+
cwd: string;
|
|
23
|
+
model: string;
|
|
24
|
+
startedAt: string;
|
|
25
|
+
reservations?: FileReservation[];
|
|
26
|
+
gitBranch?: string;
|
|
27
|
+
spec?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface AgentMailMessage {
|
|
31
|
+
id: string;
|
|
32
|
+
from: string;
|
|
33
|
+
to: string;
|
|
34
|
+
text: string;
|
|
35
|
+
timestamp: string;
|
|
36
|
+
replyTo: string | null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface ReservationConflict {
|
|
40
|
+
path: string;
|
|
41
|
+
agent: string;
|
|
42
|
+
pattern: string;
|
|
43
|
+
reason?: string;
|
|
44
|
+
registration: AgentRegistration;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface MessengerState {
|
|
48
|
+
agentName: string;
|
|
49
|
+
registered: boolean;
|
|
50
|
+
watcher: fs.FSWatcher | null;
|
|
51
|
+
watcherRetries: number;
|
|
52
|
+
watcherRetryTimer: ReturnType<typeof setTimeout> | null;
|
|
53
|
+
watcherDebounceTimer: ReturnType<typeof setTimeout> | null;
|
|
54
|
+
reservations: FileReservation[];
|
|
55
|
+
chatHistory: Map<string, AgentMailMessage[]>;
|
|
56
|
+
unreadCounts: Map<string, number>;
|
|
57
|
+
broadcastHistory: AgentMailMessage[];
|
|
58
|
+
seenSenders: Map<string, string>; // name -> sessionId (detects agent restarts)
|
|
59
|
+
gitBranch?: string;
|
|
60
|
+
spec?: string;
|
|
61
|
+
scopeToFolder: boolean; // Only see agents in same cwd
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface Dirs {
|
|
65
|
+
base: string;
|
|
66
|
+
registry: string;
|
|
67
|
+
inbox: string;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface ClaimEntry {
|
|
71
|
+
agent: string;
|
|
72
|
+
sessionId: string;
|
|
73
|
+
pid: number;
|
|
74
|
+
claimedAt: string;
|
|
75
|
+
reason?: string;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export interface CompletionEntry {
|
|
79
|
+
completedBy: string;
|
|
80
|
+
completedAt: string;
|
|
81
|
+
notes?: string;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export type SpecClaims = Record<string, ClaimEntry>;
|
|
85
|
+
export type SpecCompletions = Record<string, CompletionEntry>;
|
|
86
|
+
export type AllClaims = Record<string, SpecClaims>;
|
|
87
|
+
export type AllCompletions = Record<string, SpecCompletions>;
|
|
88
|
+
|
|
89
|
+
// =============================================================================
|
|
90
|
+
// Constants
|
|
91
|
+
// =============================================================================
|
|
92
|
+
|
|
93
|
+
export const MAX_WATCHER_RETRIES = 5;
|
|
94
|
+
export const MAX_CHAT_HISTORY = 50;
|
|
95
|
+
|
|
96
|
+
const AGENT_COLORS = [
|
|
97
|
+
"38;2;178;129;214", // purple
|
|
98
|
+
"38;2;215;135;175", // pink
|
|
99
|
+
"38;2;254;188;56", // gold
|
|
100
|
+
"38;2;137;210;129", // green
|
|
101
|
+
"38;2;0;175;175", // cyan
|
|
102
|
+
"38;2;23;143;185", // blue
|
|
103
|
+
"38;2;228;192;15", // yellow
|
|
104
|
+
"38;2;255;135;135", // coral
|
|
105
|
+
];
|
|
106
|
+
|
|
107
|
+
const ADJECTIVES = [
|
|
108
|
+
"Swift", "Bright", "Calm", "Dark", "Epic", "Fast", "Gold", "Happy",
|
|
109
|
+
"Iron", "Jade", "Keen", "Loud", "Mint", "Nice", "Oak", "Pure",
|
|
110
|
+
"Quick", "Red", "Sage", "True", "Ultra", "Vivid", "Wild", "Young", "Zen"
|
|
111
|
+
];
|
|
112
|
+
|
|
113
|
+
const NOUNS = [
|
|
114
|
+
"Arrow", "Bear", "Castle", "Dragon", "Eagle", "Falcon", "Grove", "Hawk",
|
|
115
|
+
"Ice", "Jaguar", "Knight", "Lion", "Moon", "Nova", "Owl", "Phoenix",
|
|
116
|
+
"Quartz", "Raven", "Storm", "Tiger", "Union", "Viper", "Wolf", "Xenon", "Yak", "Zenith"
|
|
117
|
+
];
|
|
118
|
+
|
|
119
|
+
// =============================================================================
|
|
120
|
+
// Pure Utilities
|
|
121
|
+
// =============================================================================
|
|
122
|
+
|
|
123
|
+
export function generateMemorableName(): string {
|
|
124
|
+
const adj = ADJECTIVES[Math.floor(Math.random() * ADJECTIVES.length)];
|
|
125
|
+
const noun = NOUNS[Math.floor(Math.random() * NOUNS.length)];
|
|
126
|
+
return adj + noun;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function isProcessAlive(pid: number): boolean {
|
|
130
|
+
try {
|
|
131
|
+
process.kill(pid, 0);
|
|
132
|
+
return true;
|
|
133
|
+
} catch {
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export function isValidAgentName(name: string): boolean {
|
|
139
|
+
if (!name || name.length > 50) return false;
|
|
140
|
+
return /^[a-zA-Z0-9_][a-zA-Z0-9_-]*$/.test(name);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export function formatRelativeTime(timestamp: string): string {
|
|
144
|
+
const diff = Date.now() - new Date(timestamp).getTime();
|
|
145
|
+
const seconds = Math.floor(diff / 1000);
|
|
146
|
+
const minutes = Math.floor(seconds / 60);
|
|
147
|
+
const hours = Math.floor(minutes / 60);
|
|
148
|
+
|
|
149
|
+
if (hours > 0) return `${hours}h ago`;
|
|
150
|
+
if (minutes > 0) return `${minutes}m ago`;
|
|
151
|
+
return "just now";
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export function pathMatchesReservation(filePath: string, pattern: string): boolean {
|
|
155
|
+
if (pattern.endsWith("/")) {
|
|
156
|
+
return filePath.startsWith(pattern) || filePath + "/" === pattern;
|
|
157
|
+
}
|
|
158
|
+
return filePath === pattern;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export function stripAnsiCodes(text: string): string {
|
|
162
|
+
// eslint-disable-next-line no-control-regex
|
|
163
|
+
return text.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, "");
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const colorCache = new Map<string, string>();
|
|
167
|
+
|
|
168
|
+
export function agentColorCode(name: string): string {
|
|
169
|
+
const cached = colorCache.get(name);
|
|
170
|
+
if (cached) return cached;
|
|
171
|
+
|
|
172
|
+
let hash = 0;
|
|
173
|
+
for (const char of name) hash = ((hash << 5) - hash) + char.charCodeAt(0);
|
|
174
|
+
const color = AGENT_COLORS[Math.abs(hash) % AGENT_COLORS.length];
|
|
175
|
+
colorCache.set(name, color);
|
|
176
|
+
return color;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export function coloredAgentName(name: string): string {
|
|
180
|
+
return `\x1b[${agentColorCode(name)}m${name}\x1b[0m`;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export function extractFolder(cwd: string): string {
|
|
184
|
+
return basename(cwd) || cwd;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export function resolveSpecPath(specPath: string, cwd: string): string {
|
|
188
|
+
if (isAbsolute(specPath)) return specPath;
|
|
189
|
+
return resolve(cwd, specPath);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export function displaySpecPath(absPath: string, cwd: string): string {
|
|
193
|
+
try {
|
|
194
|
+
const rel = relative(cwd, absPath);
|
|
195
|
+
if (rel === "") return ".";
|
|
196
|
+
if (!rel.startsWith("..") && !isAbsolute(rel)) {
|
|
197
|
+
return "./" + rel;
|
|
198
|
+
}
|
|
199
|
+
} catch {
|
|
200
|
+
// Ignore and fall back to absolute
|
|
201
|
+
}
|
|
202
|
+
return absPath;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export function truncatePathLeft(filePath: string, maxLen: number): string {
|
|
206
|
+
if (filePath.length <= maxLen) return filePath;
|
|
207
|
+
if (maxLen <= 1) return '…';
|
|
208
|
+
const truncated = filePath.slice(-(maxLen - 1));
|
|
209
|
+
const slashIdx = truncated.indexOf('/');
|
|
210
|
+
if (slashIdx > 0) {
|
|
211
|
+
return '…' + truncated.slice(slashIdx);
|
|
212
|
+
}
|
|
213
|
+
return '…' + truncated;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export type DisplayMode = "same-folder-branch" | "same-folder" | "different";
|
|
217
|
+
|
|
218
|
+
export function getDisplayMode(agents: AgentRegistration[]): DisplayMode {
|
|
219
|
+
if (agents.length === 0) return "different";
|
|
220
|
+
|
|
221
|
+
const folders = agents.map(a => extractFolder(a.cwd));
|
|
222
|
+
const uniqueFolders = new Set(folders);
|
|
223
|
+
|
|
224
|
+
if (uniqueFolders.size > 1) return "different";
|
|
225
|
+
|
|
226
|
+
const branches = agents.map(a => a.gitBranch).filter(Boolean);
|
|
227
|
+
const uniqueBranches = new Set(branches);
|
|
228
|
+
|
|
229
|
+
if (uniqueBranches.size <= 1) return "same-folder-branch";
|
|
230
|
+
|
|
231
|
+
return "same-folder";
|
|
232
|
+
}
|