@bahulam/code 0.1.13 → 0.1.15
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/package.json +1 -1
- package/src/commands/install.mjs +213 -4
- package/src/commands/plugin-manage.mjs +215 -29
- package/src/config/model-catalog-default.json +0 -4
- package/src/core/resume-mode.mjs +0 -1
- package/src/core/stream-client.mjs +18 -0
- package/src/daemon/session-core.mjs +3 -0
- package/src/local-service/agent-relay.mjs +6 -1
- package/src/local-service/file-access.mjs +116 -2
- package/src/local-service/server.mjs +209 -20
- package/src/plugins/npm-install.mjs +138 -0
- package/src/plugins/pi-compat/requirements.mjs +465 -0
- package/src/plugins/pi-compat/scaffold.mjs +86 -10
- package/src/plugins/pi-compat/shim.mjs +29 -1
- package/src/plugins/preflight.mjs +2 -0
- package/src/terminal/main.mjs +5 -2
- package/src/terminal/repl.mjs +52 -23
- package/src/ui/input-dock.mjs +48 -10
- package/src/ui/text-layout.mjs +4 -3
package/src/terminal/repl.mjs
CHANGED
|
@@ -184,6 +184,7 @@ import {
|
|
|
184
184
|
moveToContent,
|
|
185
185
|
prepareInputPrompt,
|
|
186
186
|
redrawDockFrame,
|
|
187
|
+
redrawDockInput,
|
|
187
188
|
renderDockInput,
|
|
188
189
|
unmountInputDock,
|
|
189
190
|
} from '../ui/input-dock.mjs';
|
|
@@ -1559,11 +1560,35 @@ function subAgentRunId(data = {}) {
|
|
|
1559
1560
|
return data?.run_id || data?.sub_agent_run_id || null;
|
|
1560
1561
|
}
|
|
1561
1562
|
|
|
1563
|
+
function activeSubAgentRunsMap() {
|
|
1564
|
+
const runs = session.activeSubAgentRuns;
|
|
1565
|
+
if (runs instanceof Map) return runs;
|
|
1566
|
+
|
|
1567
|
+
const restored = new Map();
|
|
1568
|
+
if (Array.isArray(runs)) {
|
|
1569
|
+
for (const item of runs) {
|
|
1570
|
+
if (Array.isArray(item) && item.length >= 2) {
|
|
1571
|
+
restored.set(item[0], item[1]);
|
|
1572
|
+
} else if (item && typeof item === 'object') {
|
|
1573
|
+
const runId = item.runId || item.run_id || item.id;
|
|
1574
|
+
if (runId) restored.set(runId, item);
|
|
1575
|
+
}
|
|
1576
|
+
}
|
|
1577
|
+
} else if (runs && typeof runs === 'object') {
|
|
1578
|
+
for (const [key, value] of Object.entries(runs)) {
|
|
1579
|
+
if (value && typeof value === 'object') restored.set(key, value);
|
|
1580
|
+
}
|
|
1581
|
+
}
|
|
1582
|
+
|
|
1583
|
+
session.activeSubAgentRuns = restored;
|
|
1584
|
+
return restored;
|
|
1585
|
+
}
|
|
1586
|
+
|
|
1562
1587
|
function normalizeSubAgentRunData(data = {}) {
|
|
1563
1588
|
if (!data || typeof data !== 'object') return data;
|
|
1564
1589
|
const runId = subAgentRunId(data);
|
|
1565
1590
|
if (!runId) return data;
|
|
1566
|
-
const laneRun =
|
|
1591
|
+
const laneRun = activeSubAgentRunsMap().get(runId);
|
|
1567
1592
|
const patch = {};
|
|
1568
1593
|
if (!data.run_id) patch.run_id = runId;
|
|
1569
1594
|
if (laneRun?.type && !data.sub_agent) patch.sub_agent = laneRun.type;
|
|
@@ -1591,7 +1616,7 @@ function ensureFoldedSubAgentTools(agentType, key = agentType, data = {}) {
|
|
|
1591
1616
|
let fold = runtime.foldedSubAgentToolMap.get(key);
|
|
1592
1617
|
if (!fold) {
|
|
1593
1618
|
const runId = subAgentRunId(data);
|
|
1594
|
-
const laneRun = runId ?
|
|
1619
|
+
const laneRun = runId ? activeSubAgentRunsMap().get(runId) : null;
|
|
1595
1620
|
fold = {
|
|
1596
1621
|
key,
|
|
1597
1622
|
runId,
|
|
@@ -1604,7 +1629,7 @@ function ensureFoldedSubAgentTools(agentType, key = agentType, data = {}) {
|
|
|
1604
1629
|
runtime.foldedSubAgentToolMap.set(key, fold);
|
|
1605
1630
|
} else {
|
|
1606
1631
|
const runId = subAgentRunId(data);
|
|
1607
|
-
const laneRun = runId ?
|
|
1632
|
+
const laneRun = runId ? activeSubAgentRunsMap().get(runId) : null;
|
|
1608
1633
|
if (runId && !fold.runId) fold.runId = runId;
|
|
1609
1634
|
if (laneRun) fold.label = subAgentLaneLabel(laneRun);
|
|
1610
1635
|
if (laneRun?.query && !fold.query) fold.query = laneRun.query;
|
|
@@ -1614,8 +1639,8 @@ function ensureFoldedSubAgentTools(agentType, key = agentType, data = {}) {
|
|
|
1614
1639
|
}
|
|
1615
1640
|
|
|
1616
1641
|
function createSubAgentLane(agentType, query, runId, data = {}) {
|
|
1617
|
-
|
|
1618
|
-
const sameTypeOrdinals = [...
|
|
1642
|
+
const activeRuns = activeSubAgentRunsMap();
|
|
1643
|
+
const sameTypeOrdinals = [...activeRuns.values()]
|
|
1619
1644
|
.filter(run => run.type === agentType)
|
|
1620
1645
|
.map(run => Number(run.ordinal || 1));
|
|
1621
1646
|
const ordinal = sameTypeOrdinals.length ? Math.max(...sameTypeOrdinals) + 1 : 1;
|
|
@@ -1631,16 +1656,14 @@ function createSubAgentLane(agentType, query, runId, data = {}) {
|
|
|
1631
1656
|
// the close line agree even for the first run of a batch.
|
|
1632
1657
|
forceOrdinal: Number(data?.parallel_batch) > 1,
|
|
1633
1658
|
};
|
|
1634
|
-
|
|
1659
|
+
activeRuns.set(runId, lane);
|
|
1635
1660
|
ensureFoldedSubAgentTools(agentType, runId, { type: agentType, query, run_id: runId });
|
|
1636
1661
|
_syncSubAgentWindow();
|
|
1637
1662
|
return lane;
|
|
1638
1663
|
}
|
|
1639
1664
|
|
|
1640
1665
|
function activeSubAgentLanes() {
|
|
1641
|
-
return
|
|
1642
|
-
? [...session.activeSubAgentRuns.values()]
|
|
1643
|
-
: [];
|
|
1666
|
+
return [...activeSubAgentRunsMap().values()];
|
|
1644
1667
|
}
|
|
1645
1668
|
|
|
1646
1669
|
function subAgentLaneLabel(lane, lanes = activeSubAgentLanes()) {
|
|
@@ -2322,8 +2345,8 @@ function renderEvent(event) {
|
|
|
2322
2345
|
// concurrent runs of the same type are only distinguishable by the
|
|
2323
2346
|
// backend-issued run_id; label lanes explore#1 / explore#2 when
|
|
2324
2347
|
// more than one run is active. Solo runs render exactly as before.
|
|
2325
|
-
|
|
2326
|
-
const hadActiveRuns =
|
|
2348
|
+
const activeRuns = activeSubAgentRunsMap();
|
|
2349
|
+
const hadActiveRuns = activeRuns.size > 0;
|
|
2327
2350
|
if (!hadActiveRuns) {
|
|
2328
2351
|
stopSpinner();
|
|
2329
2352
|
clearPendingHead();
|
|
@@ -2331,7 +2354,8 @@ function renderEvent(event) {
|
|
|
2331
2354
|
}
|
|
2332
2355
|
const runId = data?.run_id || `${agentType}:${Date.now().toString(36)}`;
|
|
2333
2356
|
const lane = createSubAgentLane(agentType, query, runId, data);
|
|
2334
|
-
const
|
|
2357
|
+
const activeRunsAfterStart = activeSubAgentRunsMap();
|
|
2358
|
+
const parallel = activeRunsAfterStart.size > 1 || lane.forceOrdinal;
|
|
2335
2359
|
const label = subAgentLaneLabel(lane);
|
|
2336
2360
|
renderBlockBoundary('subagent');
|
|
2337
2361
|
process.stderr.write(renderSubAgentOpen({ id: runId, type: label, query, parentDepth: parallel ? 0 : undefined }).replace(/^\n/, '') + '\n');
|
|
@@ -2342,7 +2366,7 @@ function renderEvent(event) {
|
|
|
2342
2366
|
// inner tool calls stream here instead of flooding the transcript.
|
|
2343
2367
|
setSubAgentWindowActive(true);
|
|
2344
2368
|
if (hadActiveRuns) {
|
|
2345
|
-
updateSpinner(`${
|
|
2369
|
+
updateSpinner(`${activeRunsAfterStart.size} agents running`);
|
|
2346
2370
|
} else {
|
|
2347
2371
|
// Phase per sub-agent run: the status line counts elapsed time and
|
|
2348
2372
|
// tool calls live ("plan agent · 4 calls · 32s") for the whole run.
|
|
@@ -2367,16 +2391,17 @@ function renderEvent(event) {
|
|
|
2367
2391
|
if (!eventRunId && hasParallelSubAgentRuns()) {
|
|
2368
2392
|
break;
|
|
2369
2393
|
}
|
|
2370
|
-
const laneRun =
|
|
2394
|
+
const laneRun = activeSubAgentRunsMap().get(eventRunId);
|
|
2371
2395
|
if (laneRun) laneRun.tools++;
|
|
2372
2396
|
foldSubAgentToolProgress(eventData);
|
|
2373
|
-
const
|
|
2397
|
+
const activeRuns = activeSubAgentRunsMap();
|
|
2398
|
+
const laneParallel = activeRuns.size > 1;
|
|
2374
2399
|
if (laneParallel) {
|
|
2375
2400
|
bumpSpinnerProgress();
|
|
2376
2401
|
const activeLanes = activeSubAgentLanes();
|
|
2377
2402
|
const lanes = activeLanes
|
|
2378
2403
|
.map(r => `${subAgentLaneLabel(r, activeLanes)} ${r.tools}`).join(' · ');
|
|
2379
|
-
updateSpinner(`${
|
|
2404
|
+
updateSpinner(`${activeRuns.size} agents · ${lanes}`);
|
|
2380
2405
|
break;
|
|
2381
2406
|
}
|
|
2382
2407
|
// Feed the live window from THIS event — it always fires (55/55 in
|
|
@@ -2433,16 +2458,17 @@ function renderEvent(event) {
|
|
|
2433
2458
|
// Retire this run's display lane; while sibling runs are still
|
|
2434
2459
|
// active keep the shared window/spinner alive for them.
|
|
2435
2460
|
const eventRunId = subAgentRunId(eventData);
|
|
2436
|
-
const
|
|
2461
|
+
const activeRuns = activeSubAgentRunsMap();
|
|
2462
|
+
const doneRun = activeRuns.get(eventRunId);
|
|
2437
2463
|
const doneLabel = doneRun ? subAgentLaneLabel(doneRun) : agentType;
|
|
2438
2464
|
const doneFold = eventRunId ? removeFoldedSubAgentTools(eventRunId) : null;
|
|
2439
2465
|
if (doneFold) {
|
|
2440
2466
|
doneFold.agentType = doneLabel;
|
|
2441
2467
|
doneFold.label = doneLabel;
|
|
2442
2468
|
}
|
|
2443
|
-
if (eventRunId)
|
|
2444
|
-
else
|
|
2445
|
-
const siblingsActive =
|
|
2469
|
+
if (eventRunId) activeRuns.delete(eventRunId);
|
|
2470
|
+
else activeRuns.clear();
|
|
2471
|
+
const siblingsActive = activeRuns.size > 0;
|
|
2446
2472
|
// In verbose mode each sub-agent tool already rendered a full
|
|
2447
2473
|
// transcript card live — flushing the fold would list every tool a
|
|
2448
2474
|
// second time. The fold batch is the durable record ONLY when tools
|
|
@@ -2733,17 +2759,18 @@ function renderEvent(event) {
|
|
|
2733
2759
|
// late events land correctly instead of corrupting fresh state —
|
|
2734
2760
|
// but only for ONE turn boundary: if they're still around at the
|
|
2735
2761
|
// next complete with no closure, force-clean to avoid stale lanes.
|
|
2736
|
-
const
|
|
2762
|
+
const activeRuns = activeSubAgentRunsMap();
|
|
2763
|
+
const lanesLive = activeRuns.size > 0;
|
|
2737
2764
|
if (lanesLive && !session._lanesPreservedAtTurnEnd) {
|
|
2738
2765
|
session._lanesPreservedAtTurnEnd = true;
|
|
2739
|
-
const n =
|
|
2766
|
+
const n = activeRuns.size;
|
|
2740
2767
|
process.stderr.write(` ${c.dim(`${n} agent run${n === 1 ? '' : 's'} still active in background — progress continues below`)}\n`);
|
|
2741
2768
|
} else {
|
|
2742
2769
|
session._lanesPreservedAtTurnEnd = false;
|
|
2743
2770
|
if (showSubAgentTools(getVerbosity())) resetFoldedSubAgentTools();
|
|
2744
2771
|
else flushFoldedSubAgentTools();
|
|
2745
2772
|
resetSubAgents();
|
|
2746
|
-
|
|
2773
|
+
activeRuns.clear();
|
|
2747
2774
|
setSubAgentWindowActive(false);
|
|
2748
2775
|
}
|
|
2749
2776
|
session.inSubAgent = false;
|
|
@@ -4298,11 +4325,13 @@ export async function startTerminalRepl() {
|
|
|
4298
4325
|
options: Array.isArray(req?.options) ? req.options : [],
|
|
4299
4326
|
context: req?.context || '',
|
|
4300
4327
|
});
|
|
4328
|
+
if (isInputDockMounted()) redrawDockInput();
|
|
4301
4329
|
if (res?.answer) {
|
|
4302
4330
|
process.stderr.write(` ${c.green('✓')} ${c.dim('answered:')} ${c.brand(res.answer)}\n`);
|
|
4303
4331
|
} else {
|
|
4304
4332
|
process.stderr.write(` ${c.dim('Question declined — agent proceeds with its own judgment.')}\n`);
|
|
4305
4333
|
}
|
|
4334
|
+
if (isInputDockMounted()) redrawDockInput();
|
|
4306
4335
|
return res;
|
|
4307
4336
|
};
|
|
4308
4337
|
|
package/src/ui/input-dock.mjs
CHANGED
|
@@ -518,6 +518,21 @@ function drawInputLines(lines) {
|
|
|
518
518
|
// No save/restore — caller parks cursor via focusDockInput.
|
|
519
519
|
}
|
|
520
520
|
|
|
521
|
+
function terminalCellWidthFromColumn(text, startColumn = 1) {
|
|
522
|
+
let col = Math.max(1, Math.floor(Number(startColumn) || 1));
|
|
523
|
+
const start = col;
|
|
524
|
+
for (const ch of queue.stripSequences(String(text ?? ''))) {
|
|
525
|
+
const cp = ch.codePointAt(0);
|
|
526
|
+
if (cp === 0x09) {
|
|
527
|
+
col = (Math.floor((col - 1) / 8) + 1) * 8 + 1;
|
|
528
|
+
continue;
|
|
529
|
+
}
|
|
530
|
+
if (cp === 0x0a || cp === 0x0d) continue;
|
|
531
|
+
col += queue.cellWidth(ch);
|
|
532
|
+
}
|
|
533
|
+
return Math.max(0, col - start);
|
|
534
|
+
}
|
|
535
|
+
|
|
521
536
|
export function isInputDockMounted() {
|
|
522
537
|
return mounted;
|
|
523
538
|
}
|
|
@@ -650,6 +665,20 @@ export function redrawDockFrame() {
|
|
|
650
665
|
return true;
|
|
651
666
|
}
|
|
652
667
|
|
|
668
|
+
export function redrawDockInput() {
|
|
669
|
+
if (!mounted) return false;
|
|
670
|
+
contentTrackingActive = false;
|
|
671
|
+
renderFrame(lastFrame);
|
|
672
|
+
if (Array.isArray(lastFrame.overlayLines)) {
|
|
673
|
+
drawInputLines(lastFrame.overlayLines);
|
|
674
|
+
} else {
|
|
675
|
+
const layout = layoutInput(lastFrame.prefix, lastFrame.value);
|
|
676
|
+
drawInputLines(layout.lines);
|
|
677
|
+
}
|
|
678
|
+
parkCursorAtInput();
|
|
679
|
+
return true;
|
|
680
|
+
}
|
|
681
|
+
|
|
653
682
|
export function prepareInputPrompt({ context = '', tips = '', meta = '' } = {}) {
|
|
654
683
|
if (!mounted) return false;
|
|
655
684
|
contentTrackingActive = false;
|
|
@@ -725,28 +754,35 @@ export function renderDockOverlay({
|
|
|
725
754
|
export function focusDockInput(prefix, value = '', cursorInValue = null) {
|
|
726
755
|
if (!mounted) return false;
|
|
727
756
|
contentTrackingActive = false;
|
|
757
|
+
const target = cursorTargetForInput(prefix, value, cursorInValue);
|
|
758
|
+
if (queue.isActive()) {
|
|
759
|
+
// Record the park position — every queue op re-parks here so readline
|
|
760
|
+
// echoes always land in the input row, even mid-stream.
|
|
761
|
+
queue.park(target.row, target.col);
|
|
762
|
+
return true;
|
|
763
|
+
}
|
|
764
|
+
moveTo(target.row, target.col);
|
|
765
|
+
return true;
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
function cursorTargetForInput(prefix, value = '', cursorInValue = null) {
|
|
728
769
|
const layout = layoutInput(prefix, value);
|
|
729
770
|
const valueStr = String(value || '');
|
|
730
771
|
const rawCursor = cursorInValue == null
|
|
731
772
|
? valueStr.length
|
|
732
773
|
: Math.max(0, Math.min(valueStr.length, Math.floor(cursorInValue)));
|
|
733
774
|
const cursorSlice = valueStr.slice(0, rawCursor);
|
|
734
|
-
const
|
|
735
|
-
const
|
|
775
|
+
const inputColumn = INPUT_INDENT + 1;
|
|
776
|
+
const measureInputLine = (line) => terminalCellWidthFromColumn(line, inputColumn);
|
|
777
|
+
const offset = terminalCellWidthFromColumn(`${prefix || ''}${cursorSlice}`, inputColumn);
|
|
778
|
+
const pos = cursorPositionInLines(layout.wrapped, offset, measureInputLine);
|
|
736
779
|
const visibleRowIdx = Math.max(
|
|
737
780
|
0,
|
|
738
781
|
Math.min(inputRows - 1, pos.row - Math.max(0, layout.wrapped.length - inputRows)),
|
|
739
782
|
);
|
|
740
783
|
const row = inputRowStart() + visibleRowIdx;
|
|
741
784
|
const col = Math.min(cols(), INPUT_INDENT + 1 + Math.max(0, pos.col));
|
|
742
|
-
|
|
743
|
-
// Record the park position — every queue op re-parks here so readline
|
|
744
|
-
// echoes always land in the input row, even mid-stream.
|
|
745
|
-
queue.park(row, col);
|
|
746
|
-
return true;
|
|
747
|
-
}
|
|
748
|
-
moveTo(row, col);
|
|
749
|
-
return true;
|
|
785
|
+
return { row, col };
|
|
750
786
|
}
|
|
751
787
|
|
|
752
788
|
export function inputRowColumn() {
|
|
@@ -766,6 +802,8 @@ export function _internals() {
|
|
|
766
802
|
drawableColumns,
|
|
767
803
|
resetContentCursor,
|
|
768
804
|
contentCursor: () => ({ row: contentCursorRow, col: contentCursorCol, active: contentTrackingActive }),
|
|
805
|
+
cursorTargetForInput,
|
|
806
|
+
terminalCellWidthFromColumn,
|
|
769
807
|
overlayRowsForWrapped,
|
|
770
808
|
FIXED_ROWS,
|
|
771
809
|
MAX_INPUT_ROWS_CAP,
|
package/src/ui/text-layout.mjs
CHANGED
|
@@ -76,19 +76,20 @@ export function tailWithEllipsis(lines, maxRows, ellipsis = '… ') {
|
|
|
76
76
|
*
|
|
77
77
|
* Used to place the terminal cursor after rendering the input buffer.
|
|
78
78
|
*/
|
|
79
|
-
export function cursorPositionInLines(visibleLines, offset) {
|
|
79
|
+
export function cursorPositionInLines(visibleLines, offset, measure = visibleWidth) {
|
|
80
80
|
const arr = Array.isArray(visibleLines) ? visibleLines : [];
|
|
81
|
+
const widthOf = typeof measure === 'function' ? measure : visibleWidth;
|
|
81
82
|
let remaining = Math.max(0, Math.floor(offset));
|
|
82
83
|
for (let row = 0; row < arr.length; row++) {
|
|
83
84
|
const line = arr[row] || '';
|
|
84
|
-
const w =
|
|
85
|
+
const w = widthOf(line);
|
|
85
86
|
if (remaining <= w) return { row, col: remaining };
|
|
86
87
|
remaining -= w;
|
|
87
88
|
// Newline between wrapped lines doesn't count as a visible column,
|
|
88
89
|
// but consumes zero of the remaining offset either.
|
|
89
90
|
}
|
|
90
91
|
const lastRow = Math.max(0, arr.length - 1);
|
|
91
|
-
return { row: lastRow, col:
|
|
92
|
+
return { row: lastRow, col: widthOf(arr[lastRow] || '') };
|
|
92
93
|
}
|
|
93
94
|
|
|
94
95
|
// ── internals ────────────────────────────────────────────────────────────
|