pi-ui-extend 0.1.36 → 0.1.38
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/app/app.d.ts +7 -0
- package/dist/app/app.js +40 -5
- package/dist/app/commands/command-controller.js +1 -0
- package/dist/app/commands/command-registry.d.ts +1 -0
- package/dist/app/commands/command-registry.js +8 -0
- package/dist/app/commands/command-session-actions.d.ts +2 -0
- package/dist/app/commands/command-session-actions.js +79 -1
- package/dist/app/extensions/extension-actions-controller.d.ts +4 -1
- package/dist/app/extensions/extension-actions-controller.js +31 -2
- package/dist/app/input/input-controller.d.ts +1 -0
- package/dist/app/input/input-controller.js +23 -2
- package/dist/app/input/terminal-edit-shortcuts.d.ts +1 -0
- package/dist/app/input/terminal-edit-shortcuts.js +7 -0
- package/dist/app/input/voice-controller.js +1 -1
- package/dist/app/popup/popup-action-controller.d.ts +1 -3
- package/dist/app/popup/popup-action-controller.js +1 -5
- package/dist/app/rendering/message-content.js +4 -3
- package/dist/app/rendering/render-controller.js +21 -38
- package/dist/app/rendering/status-line-renderer.d.ts +1 -0
- package/dist/app/rendering/status-line-renderer.js +14 -2
- package/dist/app/runtime.js +12 -2
- package/dist/app/screen/mouse-controller.js +2 -0
- package/dist/app/session/session-event-controller.d.ts +7 -0
- package/dist/app/session/session-event-controller.js +10 -13
- package/dist/app/terminal/terminal-controller.js +1 -0
- package/dist/app/terminal/terminal-output-buffer.d.ts +8 -6
- package/dist/app/terminal/terminal-output-buffer.js +24 -16
- package/dist/bundled-extensions/terminal-bell/index.js +118 -33
- package/dist/markdown-format.d.ts +1 -0
- package/dist/markdown-format.js +30 -16
- package/dist/schemas/pi-tools-suite-schema.d.ts +5 -0
- package/dist/schemas/pi-tools-suite-schema.js +5 -0
- package/dist/tool-renderers/apply-patch.js +6 -1
- package/dist/tool-renderers/patch-normalize.d.ts +24 -0
- package/dist/tool-renderers/patch-normalize.js +163 -0
- package/external/pi-tools-suite/README.md +3 -2
- package/external/pi-tools-suite/package.json +3 -3
- package/external/pi-tools-suite/src/antigravity-auth/index.ts +15 -2
- package/external/pi-tools-suite/src/antigravity-auth/status.ts +36 -19
- package/external/pi-tools-suite/src/async-subagents/async-subagents.sample.jsonc +5 -2
- package/external/pi-tools-suite/src/async-subagents/commands.ts +12 -2
- package/external/pi-tools-suite/src/async-subagents/core/config.ts +8 -3
- package/external/pi-tools-suite/src/async-subagents/core/routing.ts +63 -28
- package/external/pi-tools-suite/src/async-subagents/core/tool-guard.ts +9 -4
- package/external/pi-tools-suite/src/comment-checker/config.ts +98 -0
- package/external/pi-tools-suite/src/comment-checker/detect.ts +215 -0
- package/external/pi-tools-suite/src/comment-checker/index.ts +294 -0
- package/external/pi-tools-suite/src/dcp/commands.ts +29 -15
- package/external/pi-tools-suite/src/dcp/compress-tool.ts +111 -60
- package/external/pi-tools-suite/src/dcp/config.ts +10 -6
- package/external/pi-tools-suite/src/dcp/debug-log.ts +235 -0
- package/external/pi-tools-suite/src/dcp/index.ts +204 -27
- package/external/pi-tools-suite/src/dcp/prompts.ts +25 -28
- package/external/pi-tools-suite/src/dcp/pruner-candidates.ts +6 -10
- package/external/pi-tools-suite/src/dcp/pruner-compression-blocks.ts +19 -1
- package/external/pi-tools-suite/src/dcp/pruner-message-ids.ts +36 -58
- package/external/pi-tools-suite/src/dcp/pruner-metadata.ts +18 -0
- package/external/pi-tools-suite/src/dcp/pruner-nudge.ts +3 -3
- package/external/pi-tools-suite/src/dcp/pruner.ts +4 -2
- package/external/pi-tools-suite/src/dcp/state-persistence.ts +31 -2
- package/external/pi-tools-suite/src/dcp/state.ts +62 -4
- package/external/pi-tools-suite/src/default-pi-tools-suite-config.ts +18 -0
- package/external/pi-tools-suite/src/index.ts +1 -0
- package/external/pi-tools-suite/src/model-tools/index.ts +11 -3
- package/external/pi-tools-suite/src/telegram-mirror/index.ts +1 -1
- package/external/pi-tools-suite/src/todo/index.ts +24 -0
- package/external/pi-tools-suite/src/tool-descriptions.ts +3 -3
- package/external/pi-tools-suite/src/usage/index.ts +18 -4
- package/package.json +4 -4
- package/schemas/pi-tools-suite.json +24 -0
|
@@ -9,6 +9,12 @@ const IDLE_RETRY_DELAY_MS = 100;
|
|
|
9
9
|
const MAX_IDLE_RETRIES = 40;
|
|
10
10
|
const SUBAGENTS_LIVE_COUNT_EVENT = "pi-tools-suite:async-subagents:live-count";
|
|
11
11
|
const TERMINAL_BELL_ATTENTION_EVENT = "pix:terminal-bell:attention";
|
|
12
|
+
/**
|
|
13
|
+
* Renderer-relayed signal that the session is in an auto-retry cycle.
|
|
14
|
+
* Payload: `{ active: boolean }`. The SDK does not forward retry state to
|
|
15
|
+
* extensions, so the renderer emits this on the extension event bus.
|
|
16
|
+
*/
|
|
17
|
+
const RETRY_ACTIVE_EVENT = "pix:retry-active";
|
|
12
18
|
const DEFAULT_COMPLETION_NOTIFICATION_TITLE = "Pix - completion";
|
|
13
19
|
const DEFAULT_ERROR_NOTIFICATION_TITLE = "Pix - error";
|
|
14
20
|
const DEFAULT_QUESTION_NOTIFICATION_TITLE = "Pix - question";
|
|
@@ -154,9 +160,22 @@ function trimmed(value) {
|
|
|
154
160
|
const normalized = value?.trim();
|
|
155
161
|
return normalized ? normalized : undefined;
|
|
156
162
|
}
|
|
163
|
+
function isStaleExtensionContextError(error) {
|
|
164
|
+
return error instanceof Error && /ctx is stale|stale ctx|stale after session replacement|stale after.*reload/i.test(error.message);
|
|
165
|
+
}
|
|
166
|
+
function safeSessionName(ctx, pi) {
|
|
167
|
+
try {
|
|
168
|
+
return trimmed(pi?.getSessionName?.() ?? ctx.sessionManager.getSessionName?.());
|
|
169
|
+
}
|
|
170
|
+
catch (error) {
|
|
171
|
+
if (isStaleExtensionContextError(error))
|
|
172
|
+
return undefined;
|
|
173
|
+
throw error;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
157
176
|
function buildNotificationTemplateValues(ctx, pi) {
|
|
158
177
|
const sessionId = ctx.sessionManager.getSessionId();
|
|
159
|
-
const sessionName =
|
|
178
|
+
const sessionName = safeSessionName(ctx, pi);
|
|
160
179
|
const sessionFile = ctx.sessionManager.getSessionFile() ?? "";
|
|
161
180
|
return {
|
|
162
181
|
sessionId,
|
|
@@ -167,6 +186,19 @@ function buildNotificationTemplateValues(ctx, pi) {
|
|
|
167
186
|
cwd: ctx.cwd,
|
|
168
187
|
};
|
|
169
188
|
}
|
|
189
|
+
function buildNotificationContextSnapshot(ctx, pi) {
|
|
190
|
+
try {
|
|
191
|
+
return {
|
|
192
|
+
hasUI: ctx.hasUI === true,
|
|
193
|
+
templateValues: buildNotificationTemplateValues(ctx, pi),
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
catch (error) {
|
|
197
|
+
if (isStaleExtensionContextError(error))
|
|
198
|
+
return undefined;
|
|
199
|
+
throw error;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
170
202
|
function renderNotificationTemplate(template, values, appendReasonIfUnused = false) {
|
|
171
203
|
let usedReason = false;
|
|
172
204
|
const rendered = template.replace(/\{(sessionId|sessionName|sessionTitle|sessionFile|sessionFileBase|cwd|reason)\}/g, (_match, key) => {
|
|
@@ -242,8 +274,8 @@ function detectMacActivationBundleId() {
|
|
|
242
274
|
return undefined;
|
|
243
275
|
return TERM_PROGRAM_BUNDLE_IDS[termProgram];
|
|
244
276
|
}
|
|
245
|
-
function
|
|
246
|
-
if (!terminalBellSoundEnabled(
|
|
277
|
+
function playAttentionSoundFor(context) {
|
|
278
|
+
if (!terminalBellSoundEnabled(context))
|
|
247
279
|
return;
|
|
248
280
|
if (process.platform !== "darwin")
|
|
249
281
|
return;
|
|
@@ -286,13 +318,13 @@ function sendTelegramNotification(title, message) {
|
|
|
286
318
|
// fetch may be unavailable or throw synchronously; ignore.
|
|
287
319
|
}
|
|
288
320
|
}
|
|
289
|
-
function notifySessionStopped(
|
|
290
|
-
const templateValues =
|
|
321
|
+
function notifySessionStopped(context, macActivationBundleId, options) {
|
|
322
|
+
const templateValues = context.templateValues;
|
|
291
323
|
const title = renderNotificationTemplate(notificationTitleTemplate(options.title), templateValues);
|
|
292
324
|
const renderedMessage = renderNotificationTemplate(options.message ?? process.env.PI_TERMINAL_BELL_NOTIFY_MESSAGE ?? DEFAULT_NOTIFICATION_MESSAGE, templateValues);
|
|
293
325
|
// Telegram is independent of the bundled desktop sound/notification gate.
|
|
294
326
|
sendTelegramNotification(title, renderedMessage);
|
|
295
|
-
if (!terminalBellNotificationsEnabled(
|
|
327
|
+
if (!terminalBellNotificationsEnabled(context))
|
|
296
328
|
return;
|
|
297
329
|
if (process.platform === "darwin") {
|
|
298
330
|
const terminalNotifier = findExecutable(process.env.PI_TERMINAL_BELL_NOTIFIER ?? "terminal-notifier");
|
|
@@ -351,10 +383,15 @@ export default function terminalBell(pi) {
|
|
|
351
383
|
if (extensionDisabled())
|
|
352
384
|
return;
|
|
353
385
|
let timer;
|
|
354
|
-
let
|
|
386
|
+
let pendingBell;
|
|
355
387
|
let deferredUntilSubagentsFinish = false;
|
|
356
388
|
let liveSubagentCount = 0;
|
|
357
389
|
let lastFailureReason;
|
|
390
|
+
// True while the session is in an auto-retry cycle (relayed via the
|
|
391
|
+
// extension event bus). Suppresses the failure bell on intermediate retry
|
|
392
|
+
// attempts; the final exhausted failure still rings because no retry-start
|
|
393
|
+
// signal precedes it.
|
|
394
|
+
let retryActive = false;
|
|
358
395
|
const activeSubagentWaitToolCallIds = new Set();
|
|
359
396
|
const notifiedAskUserToolCallIds = new Set();
|
|
360
397
|
const idleDelayMs = parseDelayMs(process.env.PI_TERMINAL_BELL_DELAY_MS);
|
|
@@ -368,57 +405,91 @@ export default function terminalBell(pi) {
|
|
|
368
405
|
function hasSubagentWork() {
|
|
369
406
|
return liveSubagentCount > 0 || activeSubagentWaitToolCallIds.size > 0;
|
|
370
407
|
}
|
|
371
|
-
function notifyAttention(
|
|
372
|
-
if (canRingTerminal(
|
|
408
|
+
function notifyAttention(notification, message) {
|
|
409
|
+
if (canRingTerminal(notification))
|
|
373
410
|
writeBell();
|
|
374
|
-
|
|
375
|
-
notifySessionStopped(
|
|
411
|
+
playAttentionSoundFor(notification);
|
|
412
|
+
notifySessionStopped(notification, macActivationBundleId, {
|
|
376
413
|
title: message ? DEFAULT_ERROR_NOTIFICATION_TITLE : DEFAULT_COMPLETION_NOTIFICATION_TITLE,
|
|
377
414
|
...(message ? { message } : {}),
|
|
378
415
|
});
|
|
379
416
|
pi.events.emit(TERMINAL_BELL_ATTENTION_EVENT, {
|
|
380
|
-
cwd:
|
|
381
|
-
sessionFile:
|
|
382
|
-
sessionId:
|
|
417
|
+
cwd: notification.templateValues.cwd,
|
|
418
|
+
sessionFile: notification.templateValues.sessionFile,
|
|
419
|
+
sessionId: notification.templateValues.sessionId,
|
|
383
420
|
});
|
|
384
421
|
}
|
|
385
|
-
function attemptBell(
|
|
422
|
+
function attemptBell(pending, attempt) {
|
|
386
423
|
timer = undefined;
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
424
|
+
const { ctx, notification, message } = pending;
|
|
425
|
+
// Safety net: if a retry-start signal arrives between the agent_end that
|
|
426
|
+
// queued this bell and the timer firing, suppress the bell entirely.
|
|
427
|
+
if (retryActive)
|
|
390
428
|
return;
|
|
429
|
+
try {
|
|
430
|
+
if (!ctx.isIdle()) {
|
|
431
|
+
if (attempt < MAX_IDLE_RETRIES)
|
|
432
|
+
scheduleBell(ctx, IDLE_RETRY_DELAY_MS, attempt + 1, message, notification);
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
if (ctx.hasPendingMessages())
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
catch (error) {
|
|
439
|
+
if (isStaleExtensionContextError(error)) {
|
|
440
|
+
pendingBell = undefined;
|
|
441
|
+
deferredUntilSubagentsFinish = false;
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
throw error;
|
|
391
445
|
}
|
|
392
|
-
if (ctx.hasPendingMessages())
|
|
393
|
-
return;
|
|
394
446
|
if (hasSubagentWork()) {
|
|
395
447
|
deferredUntilSubagentsFinish = true;
|
|
396
448
|
return;
|
|
397
449
|
}
|
|
398
450
|
deferredUntilSubagentsFinish = false;
|
|
399
|
-
notifyAttention(
|
|
451
|
+
notifyAttention(notification, message);
|
|
400
452
|
}
|
|
401
|
-
function scheduleBell(ctx, delayMs = idleDelayMs, attempt = 0, message) {
|
|
402
|
-
|
|
453
|
+
function scheduleBell(ctx, delayMs = idleDelayMs, attempt = 0, message, notification = buildNotificationContextSnapshot(ctx, pi)) {
|
|
454
|
+
if (!notification)
|
|
455
|
+
return;
|
|
456
|
+
pendingBell = { ctx, notification, ...(message ? { message } : {}) };
|
|
403
457
|
clearTimer();
|
|
404
|
-
timer = setTimeout(() =>
|
|
458
|
+
timer = setTimeout(() => {
|
|
459
|
+
if (!pendingBell)
|
|
460
|
+
return;
|
|
461
|
+
try {
|
|
462
|
+
attemptBell(pendingBell, attempt);
|
|
463
|
+
}
|
|
464
|
+
catch (error) {
|
|
465
|
+
if (isStaleExtensionContextError(error)) {
|
|
466
|
+
pendingBell = undefined;
|
|
467
|
+
deferredUntilSubagentsFinish = false;
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
throw error;
|
|
471
|
+
}
|
|
472
|
+
}, delayMs);
|
|
405
473
|
timer.unref?.();
|
|
406
474
|
}
|
|
407
475
|
function notifyAskUserWaiting(toolCallId, ctx) {
|
|
408
476
|
if (notifiedAskUserToolCallIds.has(toolCallId))
|
|
409
477
|
return;
|
|
410
478
|
notifiedAskUserToolCallIds.add(toolCallId);
|
|
411
|
-
|
|
479
|
+
const notification = buildNotificationContextSnapshot(ctx, pi);
|
|
480
|
+
if (!notification)
|
|
481
|
+
return;
|
|
482
|
+
if (canRingTerminal(notification))
|
|
412
483
|
writeBell();
|
|
413
|
-
|
|
414
|
-
notifySessionStopped(
|
|
484
|
+
playAttentionSoundFor(notification);
|
|
485
|
+
notifySessionStopped(notification, macActivationBundleId, {
|
|
415
486
|
title: DEFAULT_QUESTION_NOTIFICATION_TITLE,
|
|
416
487
|
message: process.env.PI_TERMINAL_BELL_ASK_USER_NOTIFY_MESSAGE ?? DEFAULT_ASK_USER_NOTIFICATION_MESSAGE,
|
|
417
488
|
});
|
|
418
489
|
pi.events.emit(TERMINAL_BELL_ATTENTION_EVENT, {
|
|
419
|
-
cwd:
|
|
420
|
-
sessionFile:
|
|
421
|
-
sessionId:
|
|
490
|
+
cwd: notification.templateValues.cwd,
|
|
491
|
+
sessionFile: notification.templateValues.sessionFile,
|
|
492
|
+
sessionId: notification.templateValues.sessionId,
|
|
422
493
|
});
|
|
423
494
|
}
|
|
424
495
|
pi.events.on(SUBAGENTS_LIVE_COUNT_EVENT, (data) => {
|
|
@@ -427,14 +498,27 @@ export default function terminalBell(pi) {
|
|
|
427
498
|
if (count === undefined)
|
|
428
499
|
return;
|
|
429
500
|
liveSubagentCount = count;
|
|
430
|
-
if (count === 0 && deferredUntilSubagentsFinish &&
|
|
431
|
-
scheduleBell(
|
|
501
|
+
if (count === 0 && deferredUntilSubagentsFinish && pendingBell) {
|
|
502
|
+
scheduleBell(pendingBell.ctx, idleDelayMs, 0, pendingBell.message, pendingBell.notification);
|
|
503
|
+
}
|
|
504
|
+
});
|
|
505
|
+
pi.events.on(RETRY_ACTIVE_EVENT, (data) => {
|
|
506
|
+
retryActive = data != null && typeof data === "object" && data.active === true;
|
|
507
|
+
if (retryActive) {
|
|
508
|
+
// A retry is starting right after an intermediate agent_end: cancel
|
|
509
|
+
// any bell queued from that attempt so we don't chime on every
|
|
510
|
+
// failed retry attempt. The final exhausted failure rings normally
|
|
511
|
+
// because it is not followed by a retry-start signal.
|
|
512
|
+
clearTimer();
|
|
513
|
+
pendingBell = undefined;
|
|
514
|
+
deferredUntilSubagentsFinish = false;
|
|
432
515
|
}
|
|
433
516
|
});
|
|
434
517
|
pi.on("agent_start", async () => {
|
|
435
518
|
clearTimer();
|
|
436
519
|
deferredUntilSubagentsFinish = false;
|
|
437
520
|
lastFailureReason = undefined;
|
|
521
|
+
retryActive = false;
|
|
438
522
|
activeSubagentWaitToolCallIds.clear();
|
|
439
523
|
notifiedAskUserToolCallIds.clear();
|
|
440
524
|
});
|
|
@@ -481,10 +565,11 @@ export default function terminalBell(pi) {
|
|
|
481
565
|
});
|
|
482
566
|
pi.on("session_shutdown", async () => {
|
|
483
567
|
clearTimer();
|
|
484
|
-
|
|
568
|
+
pendingBell = undefined;
|
|
485
569
|
deferredUntilSubagentsFinish = false;
|
|
486
570
|
liveSubagentCount = 0;
|
|
487
571
|
lastFailureReason = undefined;
|
|
572
|
+
retryActive = false;
|
|
488
573
|
activeSubagentWaitToolCallIds.clear();
|
|
489
574
|
notifiedAskUserToolCallIds.clear();
|
|
490
575
|
});
|
|
@@ -29,4 +29,5 @@ export declare function formatMarkdownTables(text: string, maxWidth?: number): s
|
|
|
29
29
|
export declare function renderMarkdownLine(text: string, start?: number): RenderedMarkdownLine;
|
|
30
30
|
export declare function renderMarkdownTextLines(text: string, width: number, start?: number, options?: RenderMarkdownTextLinesOptions): RenderedMarkdownTextLine[];
|
|
31
31
|
export declare function markdownSyntaxHighlightsForText(text: string, startColumn?: number): ToolBodySyntaxHighlights;
|
|
32
|
+
export declare function stripDcpControlMetadata(text: string): string;
|
|
32
33
|
export declare function isOnlyHiddenMetadata(text: string): boolean;
|
package/dist/markdown-format.js
CHANGED
|
@@ -620,15 +620,40 @@ function markdownLineSyntaxHighlight(fence, fenceDelimiterLine, start) {
|
|
|
620
620
|
return { language: "markdown", start };
|
|
621
621
|
}
|
|
622
622
|
function sanitizeMarkdownText(text) {
|
|
623
|
-
return expandTabs(text.replace(/\x1b/g, "␛").replace(/\r/g, ""));
|
|
623
|
+
return expandTabs(stripDcpControlMetadata(text).replace(/\x1b/g, "␛").replace(/\r/g, ""));
|
|
624
|
+
}
|
|
625
|
+
export function stripDcpControlMetadata(text) {
|
|
626
|
+
if (!text.includes("<dcp-message-ids>"))
|
|
627
|
+
return text;
|
|
628
|
+
const lines = text.split("\n");
|
|
629
|
+
const kept = [];
|
|
630
|
+
let inMessageIds = false;
|
|
631
|
+
let touched = false;
|
|
632
|
+
for (const line of lines) {
|
|
633
|
+
if (inMessageIds) {
|
|
634
|
+
touched = true;
|
|
635
|
+
if (/<\/dcp-message-ids>\s*$/i.test(line))
|
|
636
|
+
inMessageIds = false;
|
|
637
|
+
continue;
|
|
638
|
+
}
|
|
639
|
+
if (/^\s*<dcp-message-ids>/i.test(line)) {
|
|
640
|
+
touched = true;
|
|
641
|
+
if (!/<\/dcp-message-ids>\s*$/i.test(line))
|
|
642
|
+
inMessageIds = true;
|
|
643
|
+
continue;
|
|
644
|
+
}
|
|
645
|
+
kept.push(line);
|
|
646
|
+
}
|
|
647
|
+
return touched ? kept.join("\n").trimEnd() : text;
|
|
624
648
|
}
|
|
625
649
|
function isHiddenMarkdownMetadataLine(line) {
|
|
626
|
-
return isMarkdownReferenceDefinition(line)
|
|
650
|
+
return isMarkdownReferenceDefinition(line);
|
|
627
651
|
}
|
|
628
652
|
export function isOnlyHiddenMetadata(text) {
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
653
|
+
const stripped = stripDcpControlMetadata(text);
|
|
654
|
+
if (!stripped)
|
|
655
|
+
return text.length > 0;
|
|
656
|
+
for (const line of stripped.split("\n")) {
|
|
632
657
|
if (line.length === 0)
|
|
633
658
|
continue;
|
|
634
659
|
if (!isHiddenMarkdownMetadataLine(line))
|
|
@@ -639,17 +664,6 @@ export function isOnlyHiddenMetadata(text) {
|
|
|
639
664
|
function isMarkdownReferenceDefinition(line) {
|
|
640
665
|
return /^ {0,3}\[[^\]\n]+\]:[ \t]*\S.*$/u.test(line);
|
|
641
666
|
}
|
|
642
|
-
function isStreamingDcpMetadataPrefix(line) {
|
|
643
|
-
const content = line.replace(/^ {0,3}/u, "");
|
|
644
|
-
if (content.length === 0)
|
|
645
|
-
return false;
|
|
646
|
-
return isDcpReferencePrefix(content, DCP_MESSAGE_REFERENCE_PREFIX) || isDcpReferencePrefix(content, DCP_BLOCK_REFERENCE_PREFIX);
|
|
647
|
-
}
|
|
648
|
-
function isDcpReferencePrefix(content, markerPrefix) {
|
|
649
|
-
return markerPrefix.startsWith(content) || (content.startsWith(markerPrefix) && /^\d*$/u.test(content.slice(markerPrefix.length)));
|
|
650
|
-
}
|
|
651
|
-
const DCP_MESSAGE_REFERENCE_PREFIX = "[dcp-id]: # (m";
|
|
652
|
-
const DCP_BLOCK_REFERENCE_PREFIX = "[dcp-block-id]: # (b";
|
|
653
667
|
function markdownFence(line) {
|
|
654
668
|
const match = /^\s{0,3}(`{3,}|~{3,})(.*)$/.exec(line);
|
|
655
669
|
const marker = match?.[1];
|
|
@@ -21,6 +21,10 @@ export declare const PiToolsSuiteConfigSchema: Type.TObject<{
|
|
|
21
21
|
dcp: Type.TOptional<Type.TObject<{
|
|
22
22
|
enabled: Type.TOptional<Type.TBoolean>;
|
|
23
23
|
debug: Type.TOptional<Type.TBoolean>;
|
|
24
|
+
debugLog: Type.TOptional<Type.TObject<{
|
|
25
|
+
maxBytes: Type.TOptional<Type.TNumber>;
|
|
26
|
+
maxBackups: Type.TOptional<Type.TNumber>;
|
|
27
|
+
}>>;
|
|
24
28
|
manualMode: Type.TOptional<Type.TObject<{
|
|
25
29
|
enabled: Type.TOptional<Type.TBoolean>;
|
|
26
30
|
automaticStrategies: Type.TOptional<Type.TBoolean>;
|
|
@@ -84,6 +88,7 @@ export declare const PiToolsSuiteConfigSchema: Type.TObject<{
|
|
|
84
88
|
routing: Type.TOptional<Type.TObject<{
|
|
85
89
|
enabled: Type.TOptional<Type.TBoolean>;
|
|
86
90
|
model: Type.TOptional<Type.TString>;
|
|
91
|
+
fallbackModels: Type.TOptional<Type.TArray<Type.TString>>;
|
|
87
92
|
maxTaskChars: Type.TOptional<Type.TNumber>;
|
|
88
93
|
maxTokens: Type.TOptional<Type.TNumber>;
|
|
89
94
|
maxRetries: Type.TOptional<Type.TNumber>;
|
|
@@ -100,6 +100,10 @@ const DcpStrategiesConfig = Type.Object({
|
|
|
100
100
|
const DcpConfig = Type.Object({
|
|
101
101
|
enabled: Type.Optional(Type.Boolean({ description: "Enable DCP (Dynamic Context Pruning)." })),
|
|
102
102
|
debug: Type.Optional(Type.Boolean({ description: "Enable DCP debug logging." })),
|
|
103
|
+
debugLog: Type.Optional(Type.Object({
|
|
104
|
+
maxBytes: Type.Optional(Type.Number({ description: "Maximum size in bytes of the active debug log before it is rotated. Default 5242880 (5 MB).", minimum: 1024 })),
|
|
105
|
+
maxBackups: Type.Optional(Type.Number({ description: "Number of rotated backups to keep (.1 .. .N). Default 3, minimum 1.", minimum: 1 })),
|
|
106
|
+
}, { description: "Debug log rotation. The JSONL log is written to ~/.pi/agent/dcp-debug.jsonl." })),
|
|
103
107
|
manualMode: Type.Optional(DcpManualModeConfig),
|
|
104
108
|
compress: Type.Optional(DcpCompressConfig),
|
|
105
109
|
strategies: Type.Optional(DcpStrategiesConfig),
|
|
@@ -117,6 +121,7 @@ const RetryConfig = Type.Object({
|
|
|
117
121
|
const SubagentRoutingConfig = Type.Object({
|
|
118
122
|
enabled: Type.Optional(Type.Boolean({ description: "Enable LLM-based automatic role routing." })),
|
|
119
123
|
model: Type.Optional(Type.String({ description: "Router model in provider/model form." })),
|
|
124
|
+
fallbackModels: Type.Optional(Type.Array(Type.String(), { uniqueItems: true, description: "Ordered router model fallbacks tried when the primary routing model is unavailable or fails. The current parent model is always tried last." })),
|
|
120
125
|
maxTaskChars: Type.Optional(Type.Number({ description: "Max task/scope characters sent to router.", minimum: 100 })),
|
|
121
126
|
maxTokens: Type.Optional(Type.Number({ description: "Max router response tokens.", minimum: 8 })),
|
|
122
127
|
maxRetries: Type.Optional(Type.Number({ description: "Router request retries.", minimum: 0 })),
|
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import { isAbsolute, relative, sep } from "node:path";
|
|
2
|
+
import { normalizeBeginPatchForDisplay } from "./patch-normalize.js";
|
|
2
3
|
import { expandedTextFromParts, resultText, stringArg, summarizePatch } from "./utils.js";
|
|
3
4
|
export const renderApplyPatchTool = (input) => {
|
|
4
5
|
const detailsDiff = diffFromDetails(input.details);
|
|
5
6
|
const argPatch = stringArg(input, ["input", "patch"]);
|
|
6
|
-
const
|
|
7
|
+
const rawPatch = argPatch ?? detailsDiff?.text;
|
|
8
|
+
// Re-minimize loose `*** Begin Patch` hunks so unchanged neighbor lines are
|
|
9
|
+
// rendered as context instead of spurious `-` deletions. Plain unified diffs
|
|
10
|
+
// and other formats pass through unchanged.
|
|
11
|
+
const patch = rawPatch ? normalizeBeginPatchForDisplay(rawPatch) : rawPatch;
|
|
7
12
|
const path = pathForDisplay(stringArg(input, ["path", "file_path", "filePath"]), input.cwd);
|
|
8
13
|
const summary = summarizePatch(patch) ?? "patch";
|
|
9
14
|
const expanded = expandedTextFromParts({ text: patch }, { text: resultText(input, { empty: !patch }) });
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Normalize OpenAI-style `*** Begin Patch` hunks for display.
|
|
3
|
+
*
|
|
4
|
+
* Problem: in the `Begin Patch` format the model often emits a hunk that
|
|
5
|
+
* reproduces a whole block of a file with the old version as `-` lines and the
|
|
6
|
+
* new version as `+` lines, even when most of those lines are identical. This is
|
|
7
|
+
* the "contextless / loose hunk matching" behavior. A patch that effectively
|
|
8
|
+
* only adds one line can therefore look like it deletes several existing rules.
|
|
9
|
+
*
|
|
10
|
+
* The naive per-line renderer (`diffLineStyle`) faithfully colors every `-` red
|
|
11
|
+
* and every `+` green, which misleads the user.
|
|
12
|
+
*
|
|
13
|
+
* Fix: for each `*** Update File:` hunk we reconstruct the old side
|
|
14
|
+
* (context + `-` lines) and the new side (context + `+` lines), compute a
|
|
15
|
+
* minimal LCS line diff between them, and re-emit the hunk so that:
|
|
16
|
+
* - lines present in both sides become plain context (no `-`),
|
|
17
|
+
* - truly removed lines stay `-`,
|
|
18
|
+
* - truly added lines stay `+`.
|
|
19
|
+
*
|
|
20
|
+
* This is a display-only transformation. Well-formed minimal hunks (and any
|
|
21
|
+
* non-`Begin Patch` unified diffs) are left untouched.
|
|
22
|
+
*/
|
|
23
|
+
/** Re-emit a `*** Begin Patch` string with loose hunks re-minimized. */
|
|
24
|
+
export declare function normalizeBeginPatchForDisplay(patch: string): string;
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Normalize OpenAI-style `*** Begin Patch` hunks for display.
|
|
3
|
+
*
|
|
4
|
+
* Problem: in the `Begin Patch` format the model often emits a hunk that
|
|
5
|
+
* reproduces a whole block of a file with the old version as `-` lines and the
|
|
6
|
+
* new version as `+` lines, even when most of those lines are identical. This is
|
|
7
|
+
* the "contextless / loose hunk matching" behavior. A patch that effectively
|
|
8
|
+
* only adds one line can therefore look like it deletes several existing rules.
|
|
9
|
+
*
|
|
10
|
+
* The naive per-line renderer (`diffLineStyle`) faithfully colors every `-` red
|
|
11
|
+
* and every `+` green, which misleads the user.
|
|
12
|
+
*
|
|
13
|
+
* Fix: for each `*** Update File:` hunk we reconstruct the old side
|
|
14
|
+
* (context + `-` lines) and the new side (context + `+` lines), compute a
|
|
15
|
+
* minimal LCS line diff between them, and re-emit the hunk so that:
|
|
16
|
+
* - lines present in both sides become plain context (no `-`),
|
|
17
|
+
* - truly removed lines stay `-`,
|
|
18
|
+
* - truly added lines stay `+`.
|
|
19
|
+
*
|
|
20
|
+
* This is a display-only transformation. Well-formed minimal hunks (and any
|
|
21
|
+
* non-`Begin Patch` unified diffs) are left untouched.
|
|
22
|
+
*/
|
|
23
|
+
const MAX_NORMALIZE_LINES = 4000;
|
|
24
|
+
/** Re-emit a `*** Begin Patch` string with loose hunks re-minimized. */
|
|
25
|
+
export function normalizeBeginPatchForDisplay(patch) {
|
|
26
|
+
if (!patch.includes("*** Begin Patch"))
|
|
27
|
+
return patch;
|
|
28
|
+
const lines = patch.split("\n");
|
|
29
|
+
if (lines.length > MAX_NORMALIZE_LINES)
|
|
30
|
+
return patch;
|
|
31
|
+
const sections = parseBeginPatchSections(lines);
|
|
32
|
+
return sections.map(renderSection).filter((line) => line !== null).join("\n");
|
|
33
|
+
}
|
|
34
|
+
function parseBeginPatchSections(lines) {
|
|
35
|
+
const sections = [];
|
|
36
|
+
let i = 0;
|
|
37
|
+
while (i < lines.length) {
|
|
38
|
+
const line = lines[i] ?? "";
|
|
39
|
+
if (line.startsWith("*** Update File:")) {
|
|
40
|
+
sections.push({ kind: "marker", line });
|
|
41
|
+
i += 1;
|
|
42
|
+
while (i < lines.length) {
|
|
43
|
+
const inner = lines[i] ?? "";
|
|
44
|
+
if (inner.startsWith("*** "))
|
|
45
|
+
break;
|
|
46
|
+
if (inner.startsWith("@@")) {
|
|
47
|
+
sections.push({ kind: "hunk-header", line: inner });
|
|
48
|
+
i += 1;
|
|
49
|
+
const body = [];
|
|
50
|
+
while (i < lines.length) {
|
|
51
|
+
const bodyLine = lines[i] ?? "";
|
|
52
|
+
if (bodyLine.startsWith("@@") || bodyLine.startsWith("*** "))
|
|
53
|
+
break;
|
|
54
|
+
body.push(parseHunkLine(bodyLine));
|
|
55
|
+
i += 1;
|
|
56
|
+
}
|
|
57
|
+
sections.push({ kind: "hunk-body", lines: body });
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
sections.push({ kind: "raw", line: inner });
|
|
61
|
+
i += 1;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
if (line.startsWith("*** Add File:") || line.startsWith("*** Delete File:") || line.startsWith("*** Begin Patch") || line.startsWith("*** End Patch")) {
|
|
67
|
+
sections.push({ kind: "marker", line });
|
|
68
|
+
i += 1;
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
// Stray line outside any file section (e.g. a loose @@ without an Update
|
|
72
|
+
// File header). Keep it verbatim to preserve structure.
|
|
73
|
+
sections.push({ kind: "raw", line });
|
|
74
|
+
i += 1;
|
|
75
|
+
}
|
|
76
|
+
return sections;
|
|
77
|
+
}
|
|
78
|
+
function parseHunkLine(line) {
|
|
79
|
+
if (line.startsWith("+"))
|
|
80
|
+
return { type: "add", text: line.slice(1) };
|
|
81
|
+
if (line.startsWith("-"))
|
|
82
|
+
return { type: "del", text: line.slice(1) };
|
|
83
|
+
if (line.startsWith(" "))
|
|
84
|
+
return { type: "context", text: line.slice(1) };
|
|
85
|
+
// Lines without a prefix inside a Begin Patch hunk body are treated as
|
|
86
|
+
// context (the format uses a leading space for context, but loose patches
|
|
87
|
+
// sometimes omit it).
|
|
88
|
+
return { type: "context", text: line };
|
|
89
|
+
}
|
|
90
|
+
function renderSection(section) {
|
|
91
|
+
switch (section.kind) {
|
|
92
|
+
case "marker":
|
|
93
|
+
case "hunk-header":
|
|
94
|
+
case "raw":
|
|
95
|
+
return section.line;
|
|
96
|
+
case "hunk-body": {
|
|
97
|
+
const oldLines = section.lines.filter((entry) => entry.type !== "add").map((entry) => entry.text);
|
|
98
|
+
const newLines = section.lines.filter((entry) => entry.type !== "del").map((entry) => entry.text);
|
|
99
|
+
const ops = diffLines(oldLines, newLines);
|
|
100
|
+
const rendered = [];
|
|
101
|
+
for (const op of ops) {
|
|
102
|
+
rendered.push(renderDiffOp(op));
|
|
103
|
+
}
|
|
104
|
+
return rendered.length > 0 ? rendered.join("\n") : null;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
function renderDiffOp(op) {
|
|
109
|
+
if (op.type === "delete")
|
|
110
|
+
return `-${op.text}`;
|
|
111
|
+
if (op.type === "insert")
|
|
112
|
+
return `+${op.text}`;
|
|
113
|
+
// Context marker is a leading space. Loose `-`/`+` blocks carry the space
|
|
114
|
+
// that separated the marker from the content (e.g. `- rule one`), so reuse
|
|
115
|
+
// that space instead of emitting a second one.
|
|
116
|
+
return op.text.startsWith(" ") ? op.text : ` ${op.text}`;
|
|
117
|
+
}
|
|
118
|
+
/** Minimal LCS-based line diff. */
|
|
119
|
+
function diffLines(oldLines, newLines) {
|
|
120
|
+
const m = oldLines.length;
|
|
121
|
+
const n = newLines.length;
|
|
122
|
+
if (m === 0 && n === 0)
|
|
123
|
+
return [];
|
|
124
|
+
const dp = Array.from({ length: m + 1 }, () => new Array(n + 1).fill(0));
|
|
125
|
+
for (let i = m - 1; i >= 0; i -= 1) {
|
|
126
|
+
for (let j = n - 1; j >= 0; j -= 1) {
|
|
127
|
+
const oldLine = oldLines[i] ?? "";
|
|
128
|
+
const newLine = newLines[j] ?? "";
|
|
129
|
+
dp[i][j] = oldLine === newLine
|
|
130
|
+
? (dp[i + 1]?.[j + 1] ?? 0) + 1
|
|
131
|
+
: Math.max(dp[i + 1]?.[j] ?? 0, dp[i]?.[j + 1] ?? 0);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
const result = [];
|
|
135
|
+
let i = 0;
|
|
136
|
+
let j = 0;
|
|
137
|
+
while (i < m && j < n) {
|
|
138
|
+
const oldLine = oldLines[i] ?? "";
|
|
139
|
+
const newLine = newLines[j] ?? "";
|
|
140
|
+
if (oldLine === newLine) {
|
|
141
|
+
result.push({ type: "equal", text: oldLine });
|
|
142
|
+
i += 1;
|
|
143
|
+
j += 1;
|
|
144
|
+
}
|
|
145
|
+
else if ((dp[i + 1]?.[j] ?? 0) >= (dp[i]?.[j + 1] ?? 0)) {
|
|
146
|
+
result.push({ type: "delete", text: oldLine });
|
|
147
|
+
i += 1;
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
result.push({ type: "insert", text: newLine });
|
|
151
|
+
j += 1;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
while (i < m) {
|
|
155
|
+
result.push({ type: "delete", text: oldLines[i] ?? "" });
|
|
156
|
+
i += 1;
|
|
157
|
+
}
|
|
158
|
+
while (j < n) {
|
|
159
|
+
result.push({ type: "insert", text: newLines[j] ?? "" });
|
|
160
|
+
j += 1;
|
|
161
|
+
}
|
|
162
|
+
return result;
|
|
163
|
+
}
|
|
@@ -8,6 +8,7 @@ This package keeps shared Pi tools as ordinary source folders under `src/` and r
|
|
|
8
8
|
- `src/ast-grep` — `ast_grep` / `ast_apply`
|
|
9
9
|
- `src/async-subagents` — `subagents` tool and sub-agent slash commands, including oh-my-openagent-style `/ultrawork` (`/ulw`) and `/hyperplan` orchestration prompts, plus config-defined sub-agent model/thinking/args presets selected via `/subagent-preset` from `asyncSubagents` in `~/.config/pi/pi-tools-suite.jsonc`; includes the `frontend` profile for Gemini-friendly UI/UX and visual frontend work plus the `vision` profile for screenshot/image description via `openai-codex/gpt-5.4-mini`; enforces a 30-minute per-agent execution timeout, project-wide `maxConcurrent` queueing, optional retry/backoff, and `result.json` structured metadata/chaining fields next to raw `result.md`; stores project-local run files and a registry under `.pi/subagents/` so result/status collection can recover after compaction or reload while the main session remains alive
|
|
10
10
|
- `src/lsp` — shared LSP diagnostics hook/library that enriches mutating tool results with diagnostics and shuts down language servers on session shutdown
|
|
11
|
+
- `src/comment-checker` — AI-slop comment guard that listens to the `tool_result` event for `write` / `edit` / `apply_patch` mutations, extracts net-new code comment lines, classifies them (filler phrasing, restating code, decorative separators, generic paraphrasing, or — under aggressive strictness — any non-valuable comment), and appends a short nudge to the tool result so the agent removes unnecessary comments on its next turn; TODO/FIXME, license headers, docstrings, pragmas, linter directives, shebangs, and decorators are never flagged; language-agnostic across `//` / `/* */` / `#` / `--` / `<!-- -->` / triple-quote comment styles; per-session deduplication (at most one nudge per 30 s) prevents fix/remark loops; configured via the `commentChecker` section (`enabled`, `strictness`: `conservative` | `balanced` | `aggressive`, default `balanced`) or `PI_COMMENT_CHECKER_ENABLED` / `PI_COMMENT_CHECKER_STRICTNESS`
|
|
11
12
|
- `src/repo-discovery` — `/idx-init`, `/idx-update`, and indexed-only `repo_architecture` / `repo_structure` / `repo_ast` / `repo_search` / `repo_explain` / `repo_deps`; tools register only when the launch project has `.indexer-cli`
|
|
12
13
|
- `src/antigravity-auth` — `antigravity` custom provider with Google Antigravity OAuth login, startup account list, auth.json-only runtime account loading, `/antigravity-add-account` OAuth append into rotation, `/antigravity-account` status display, account rotation/failover, Antigravity plus Gemini CLI model registration, and streaming through the Cloud Code Assist unified gateway
|
|
13
14
|
- `src/todo` — `todo` tool, `/todos`, `/todos-persist`, and `/todos-scope`; supports parent/subtask hierarchy, blockers, ready-task filtering, deferred out-of-scope items, batch operations, JSON/Markdown import/export, automatic clearing when all visible todos are completed, and optional project persistence via `/todos persist on` or `/todos-persist on`; localization/i18n has been removed
|
|
@@ -19,7 +20,7 @@ This package keeps shared Pi tools as ordinary source folders under `src/` and r
|
|
|
19
20
|
|
|
20
21
|
`index.ts` is intentionally only a thin auto-discovery shim that re-exports `src/index.ts`. There is no `pi.extensions` manifest here, so local Pi auto-discovery loads the suite once via `~/.pi/agent/extensions/pi-tools-suite/index.ts` and does not double-register tools.
|
|
21
22
|
|
|
22
|
-
Registration order is preserved in `src/index.ts`: glm-coding-discipline, ast-grep, async-subagents, lsp, repo-discovery command/tool gate, antigravity-auth provider, todo, model-tools, usage, web-search, dcp, then prompt-commands. Tool metadata and active model-specific tool sets have two modes: standard and repo-aware. When `.indexer-cli` enables `repo_*`, those tools stay active ahead of overlapping lower-level aliases so the indexed discovery surface has priority.
|
|
23
|
+
Registration order is preserved in `src/index.ts`: glm-coding-discipline, ast-grep, async-subagents, lsp, comment-checker, repo-discovery command/tool gate, antigravity-auth provider, todo, model-tools, usage, web-search, dcp, then prompt-commands. Tool metadata and active model-specific tool sets have two modes: standard and repo-aware. When `.indexer-cli` enables `repo_*`, those tools stay active ahead of overlapping lower-level aliases so the indexed discovery surface has priority.
|
|
23
24
|
|
|
24
25
|
## Disabling modules
|
|
25
26
|
|
|
@@ -123,7 +124,7 @@ DCP settings are stored only under `dcp` in the user shared config file `~/.conf
|
|
|
123
124
|
}
|
|
124
125
|
```
|
|
125
126
|
|
|
126
|
-
`minContextPercent` / `maxContextPercent` accept legacy fractions (`0.25`), percent strings (`"25%"`), or absolute token counts when Pi knows the current model context window. `minContextLimit` / `maxContextLimit` and `modelMinContextLimits` / `modelMaxContextLimits` are explicit absolute-or-percent aliases. `modelOverrides` and the `modelMin*` / `modelMax*` maps support exact model keys plus `*` / `?` wildcard patterns; matching is applied from generic to specific so exact bare-model matches override bare wildcards, and exact `provider/model` matches override provider wildcards. Array fields are union-merged, so model-specific `protectedTools` extend the defaults instead of replacing them. If `compress.protectUserMessages` is enabled, range compression appends selected user messages verbatim instead of rejecting the range; individual message compression still skips protected raw user messages. Protected tool outputs are copied into summaries for tools protected by name or `protectedFilePatterns`; protected `subagents` result reads also try to include the saved `result.md` artifact when available.
|
|
127
|
+
`minContextPercent` / `maxContextPercent` accept legacy fractions (`0.25`), percent strings (`"25%"`), or absolute token counts when Pi knows the current model context window. `minContextLimit` / `maxContextLimit` and `modelMinContextLimits` / `modelMaxContextLimits` are explicit absolute-or-percent aliases. `modelOverrides` and the `modelMin*` / `modelMax*` maps support exact model keys plus `*` / `?` wildcard patterns; matching is applied from generic to specific so exact bare-model matches override bare wildcards, and exact `provider/model` matches override provider wildcards. Array fields are union-merged, so model-specific `protectedTools` extend the defaults instead of replacing them. If `compress.protectUserMessages` is enabled, range compression appends selected user messages verbatim instead of rejecting the range; individual message compression still skips protected raw user messages. Protected tool outputs are copied into summaries for tools protected by name or `protectedFilePatterns`; protected `subagents` result reads also try to include the saved `result.md` artifact when available. Set `dcp.debug: true` to write a JSONL debug log of DCP context/prune/compress events to `~/.pi/agent/dcp-debug.jsonl` (override the path with `PI_DCP_DEBUG_LOG`, or enable without config via `PI_DCP_DEBUG=1`); off by default. The log is size-limited and rotated: once it reaches `dcp.debugLog.maxBytes` (default `5242880` = 5 MB) it is renamed to `.1`, older backups shift down (`.1`→`.2`, …) and the oldest beyond `dcp.debugLog.maxBackups` (default `3`, minimum `1`) is dropped; override either with `PI_DCP_DEBUG_MAX_BYTES` / `PI_DCP_DEBUG_MAX_BACKUPS`.
|
|
127
128
|
|
|
128
129
|
## LSP setup
|
|
129
130
|
|
|
@@ -38,9 +38,9 @@
|
|
|
38
38
|
"vscode-languageserver-protocol": "^3.17.5"
|
|
39
39
|
},
|
|
40
40
|
"peerDependencies": {
|
|
41
|
-
"@earendil-works/pi-ai": "0.79.
|
|
42
|
-
"@earendil-works/pi-coding-agent": "0.79.
|
|
43
|
-
"@earendil-works/pi-tui": "0.79.
|
|
41
|
+
"@earendil-works/pi-ai": "0.79.6",
|
|
42
|
+
"@earendil-works/pi-coding-agent": "0.79.6",
|
|
43
|
+
"@earendil-works/pi-tui": "0.79.6",
|
|
44
44
|
"typebox": "*"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|