@bahulam/code 0.1.12 → 0.1.14
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 +382 -0
- package/src/commands/plugin-manage.mjs +467 -89
- package/src/config/model-catalog-default.json +0 -4
- package/src/config/settings-loader.mjs +15 -0
- package/src/core/resume-mode.mjs +0 -1
- package/src/core/stream-client.mjs +18 -0
- package/src/core/tool-executor.mjs +47 -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/manifest.mjs +3 -0
- package/src/plugins/npm-install.mjs +138 -0
- package/src/plugins/pi-compat/loader-hook.mjs +45 -0
- package/src/plugins/pi-compat/probe.mjs +294 -0
- package/src/plugins/pi-compat/requirements.mjs +465 -0
- package/src/plugins/pi-compat/scaffold.mjs +563 -0
- package/src/plugins/pi-compat/shim.mjs +162 -0
- package/src/plugins/pi-compose.mjs +147 -0
- package/src/plugins/preflight.mjs +29 -2
- package/src/plugins/registry.mjs +6 -0
- package/src/terminal/main.mjs +33 -6
- package/src/terminal/paste-input.mjs +23 -0
- package/src/terminal/repl.mjs +104 -30
- package/src/tools/registry.mjs +19 -0
- package/src/ui/input-dock.mjs +53 -12
- package/src/ui/text-layout.mjs +4 -3
package/src/terminal/repl.mjs
CHANGED
|
@@ -71,6 +71,7 @@ import { PluginRegistry } from '../plugins/registry.mjs';
|
|
|
71
71
|
import { SessionManager } from '../core/session-manager.mjs';
|
|
72
72
|
import { parseArgs } from '../config/cli-args.mjs';
|
|
73
73
|
import { pickModelOverridesForm } from './repl-model-form.mjs';
|
|
74
|
+
import { isRawMultilinePasteChunk, normalizePastedText, pastedTextLabel } from './paste-input.mjs';
|
|
74
75
|
import {
|
|
75
76
|
MODEL_CATEGORY_ORDER,
|
|
76
77
|
formatCategoryBadge,
|
|
@@ -183,6 +184,7 @@ import {
|
|
|
183
184
|
moveToContent,
|
|
184
185
|
prepareInputPrompt,
|
|
185
186
|
redrawDockFrame,
|
|
187
|
+
redrawDockInput,
|
|
186
188
|
renderDockInput,
|
|
187
189
|
unmountInputDock,
|
|
188
190
|
} from '../ui/input-dock.mjs';
|
|
@@ -1558,11 +1560,35 @@ function subAgentRunId(data = {}) {
|
|
|
1558
1560
|
return data?.run_id || data?.sub_agent_run_id || null;
|
|
1559
1561
|
}
|
|
1560
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
|
+
|
|
1561
1587
|
function normalizeSubAgentRunData(data = {}) {
|
|
1562
1588
|
if (!data || typeof data !== 'object') return data;
|
|
1563
1589
|
const runId = subAgentRunId(data);
|
|
1564
1590
|
if (!runId) return data;
|
|
1565
|
-
const laneRun =
|
|
1591
|
+
const laneRun = activeSubAgentRunsMap().get(runId);
|
|
1566
1592
|
const patch = {};
|
|
1567
1593
|
if (!data.run_id) patch.run_id = runId;
|
|
1568
1594
|
if (laneRun?.type && !data.sub_agent) patch.sub_agent = laneRun.type;
|
|
@@ -1590,7 +1616,7 @@ function ensureFoldedSubAgentTools(agentType, key = agentType, data = {}) {
|
|
|
1590
1616
|
let fold = runtime.foldedSubAgentToolMap.get(key);
|
|
1591
1617
|
if (!fold) {
|
|
1592
1618
|
const runId = subAgentRunId(data);
|
|
1593
|
-
const laneRun = runId ?
|
|
1619
|
+
const laneRun = runId ? activeSubAgentRunsMap().get(runId) : null;
|
|
1594
1620
|
fold = {
|
|
1595
1621
|
key,
|
|
1596
1622
|
runId,
|
|
@@ -1603,7 +1629,7 @@ function ensureFoldedSubAgentTools(agentType, key = agentType, data = {}) {
|
|
|
1603
1629
|
runtime.foldedSubAgentToolMap.set(key, fold);
|
|
1604
1630
|
} else {
|
|
1605
1631
|
const runId = subAgentRunId(data);
|
|
1606
|
-
const laneRun = runId ?
|
|
1632
|
+
const laneRun = runId ? activeSubAgentRunsMap().get(runId) : null;
|
|
1607
1633
|
if (runId && !fold.runId) fold.runId = runId;
|
|
1608
1634
|
if (laneRun) fold.label = subAgentLaneLabel(laneRun);
|
|
1609
1635
|
if (laneRun?.query && !fold.query) fold.query = laneRun.query;
|
|
@@ -1613,8 +1639,8 @@ function ensureFoldedSubAgentTools(agentType, key = agentType, data = {}) {
|
|
|
1613
1639
|
}
|
|
1614
1640
|
|
|
1615
1641
|
function createSubAgentLane(agentType, query, runId, data = {}) {
|
|
1616
|
-
|
|
1617
|
-
const sameTypeOrdinals = [...
|
|
1642
|
+
const activeRuns = activeSubAgentRunsMap();
|
|
1643
|
+
const sameTypeOrdinals = [...activeRuns.values()]
|
|
1618
1644
|
.filter(run => run.type === agentType)
|
|
1619
1645
|
.map(run => Number(run.ordinal || 1));
|
|
1620
1646
|
const ordinal = sameTypeOrdinals.length ? Math.max(...sameTypeOrdinals) + 1 : 1;
|
|
@@ -1630,16 +1656,14 @@ function createSubAgentLane(agentType, query, runId, data = {}) {
|
|
|
1630
1656
|
// the close line agree even for the first run of a batch.
|
|
1631
1657
|
forceOrdinal: Number(data?.parallel_batch) > 1,
|
|
1632
1658
|
};
|
|
1633
|
-
|
|
1659
|
+
activeRuns.set(runId, lane);
|
|
1634
1660
|
ensureFoldedSubAgentTools(agentType, runId, { type: agentType, query, run_id: runId });
|
|
1635
1661
|
_syncSubAgentWindow();
|
|
1636
1662
|
return lane;
|
|
1637
1663
|
}
|
|
1638
1664
|
|
|
1639
1665
|
function activeSubAgentLanes() {
|
|
1640
|
-
return
|
|
1641
|
-
? [...session.activeSubAgentRuns.values()]
|
|
1642
|
-
: [];
|
|
1666
|
+
return [...activeSubAgentRunsMap().values()];
|
|
1643
1667
|
}
|
|
1644
1668
|
|
|
1645
1669
|
function subAgentLaneLabel(lane, lanes = activeSubAgentLanes()) {
|
|
@@ -2321,8 +2345,8 @@ function renderEvent(event) {
|
|
|
2321
2345
|
// concurrent runs of the same type are only distinguishable by the
|
|
2322
2346
|
// backend-issued run_id; label lanes explore#1 / explore#2 when
|
|
2323
2347
|
// more than one run is active. Solo runs render exactly as before.
|
|
2324
|
-
|
|
2325
|
-
const hadActiveRuns =
|
|
2348
|
+
const activeRuns = activeSubAgentRunsMap();
|
|
2349
|
+
const hadActiveRuns = activeRuns.size > 0;
|
|
2326
2350
|
if (!hadActiveRuns) {
|
|
2327
2351
|
stopSpinner();
|
|
2328
2352
|
clearPendingHead();
|
|
@@ -2330,7 +2354,8 @@ function renderEvent(event) {
|
|
|
2330
2354
|
}
|
|
2331
2355
|
const runId = data?.run_id || `${agentType}:${Date.now().toString(36)}`;
|
|
2332
2356
|
const lane = createSubAgentLane(agentType, query, runId, data);
|
|
2333
|
-
const
|
|
2357
|
+
const activeRunsAfterStart = activeSubAgentRunsMap();
|
|
2358
|
+
const parallel = activeRunsAfterStart.size > 1 || lane.forceOrdinal;
|
|
2334
2359
|
const label = subAgentLaneLabel(lane);
|
|
2335
2360
|
renderBlockBoundary('subagent');
|
|
2336
2361
|
process.stderr.write(renderSubAgentOpen({ id: runId, type: label, query, parentDepth: parallel ? 0 : undefined }).replace(/^\n/, '') + '\n');
|
|
@@ -2341,7 +2366,7 @@ function renderEvent(event) {
|
|
|
2341
2366
|
// inner tool calls stream here instead of flooding the transcript.
|
|
2342
2367
|
setSubAgentWindowActive(true);
|
|
2343
2368
|
if (hadActiveRuns) {
|
|
2344
|
-
updateSpinner(`${
|
|
2369
|
+
updateSpinner(`${activeRunsAfterStart.size} agents running`);
|
|
2345
2370
|
} else {
|
|
2346
2371
|
// Phase per sub-agent run: the status line counts elapsed time and
|
|
2347
2372
|
// tool calls live ("plan agent · 4 calls · 32s") for the whole run.
|
|
@@ -2366,16 +2391,17 @@ function renderEvent(event) {
|
|
|
2366
2391
|
if (!eventRunId && hasParallelSubAgentRuns()) {
|
|
2367
2392
|
break;
|
|
2368
2393
|
}
|
|
2369
|
-
const laneRun =
|
|
2394
|
+
const laneRun = activeSubAgentRunsMap().get(eventRunId);
|
|
2370
2395
|
if (laneRun) laneRun.tools++;
|
|
2371
2396
|
foldSubAgentToolProgress(eventData);
|
|
2372
|
-
const
|
|
2397
|
+
const activeRuns = activeSubAgentRunsMap();
|
|
2398
|
+
const laneParallel = activeRuns.size > 1;
|
|
2373
2399
|
if (laneParallel) {
|
|
2374
2400
|
bumpSpinnerProgress();
|
|
2375
2401
|
const activeLanes = activeSubAgentLanes();
|
|
2376
2402
|
const lanes = activeLanes
|
|
2377
2403
|
.map(r => `${subAgentLaneLabel(r, activeLanes)} ${r.tools}`).join(' · ');
|
|
2378
|
-
updateSpinner(`${
|
|
2404
|
+
updateSpinner(`${activeRuns.size} agents · ${lanes}`);
|
|
2379
2405
|
break;
|
|
2380
2406
|
}
|
|
2381
2407
|
// Feed the live window from THIS event — it always fires (55/55 in
|
|
@@ -2432,16 +2458,17 @@ function renderEvent(event) {
|
|
|
2432
2458
|
// Retire this run's display lane; while sibling runs are still
|
|
2433
2459
|
// active keep the shared window/spinner alive for them.
|
|
2434
2460
|
const eventRunId = subAgentRunId(eventData);
|
|
2435
|
-
const
|
|
2461
|
+
const activeRuns = activeSubAgentRunsMap();
|
|
2462
|
+
const doneRun = activeRuns.get(eventRunId);
|
|
2436
2463
|
const doneLabel = doneRun ? subAgentLaneLabel(doneRun) : agentType;
|
|
2437
2464
|
const doneFold = eventRunId ? removeFoldedSubAgentTools(eventRunId) : null;
|
|
2438
2465
|
if (doneFold) {
|
|
2439
2466
|
doneFold.agentType = doneLabel;
|
|
2440
2467
|
doneFold.label = doneLabel;
|
|
2441
2468
|
}
|
|
2442
|
-
if (eventRunId)
|
|
2443
|
-
else
|
|
2444
|
-
const siblingsActive =
|
|
2469
|
+
if (eventRunId) activeRuns.delete(eventRunId);
|
|
2470
|
+
else activeRuns.clear();
|
|
2471
|
+
const siblingsActive = activeRuns.size > 0;
|
|
2445
2472
|
// In verbose mode each sub-agent tool already rendered a full
|
|
2446
2473
|
// transcript card live — flushing the fold would list every tool a
|
|
2447
2474
|
// second time. The fold batch is the durable record ONLY when tools
|
|
@@ -2732,17 +2759,18 @@ function renderEvent(event) {
|
|
|
2732
2759
|
// late events land correctly instead of corrupting fresh state —
|
|
2733
2760
|
// but only for ONE turn boundary: if they're still around at the
|
|
2734
2761
|
// next complete with no closure, force-clean to avoid stale lanes.
|
|
2735
|
-
const
|
|
2762
|
+
const activeRuns = activeSubAgentRunsMap();
|
|
2763
|
+
const lanesLive = activeRuns.size > 0;
|
|
2736
2764
|
if (lanesLive && !session._lanesPreservedAtTurnEnd) {
|
|
2737
2765
|
session._lanesPreservedAtTurnEnd = true;
|
|
2738
|
-
const n =
|
|
2766
|
+
const n = activeRuns.size;
|
|
2739
2767
|
process.stderr.write(` ${c.dim(`${n} agent run${n === 1 ? '' : 's'} still active in background — progress continues below`)}\n`);
|
|
2740
2768
|
} else {
|
|
2741
2769
|
session._lanesPreservedAtTurnEnd = false;
|
|
2742
2770
|
if (showSubAgentTools(getVerbosity())) resetFoldedSubAgentTools();
|
|
2743
2771
|
else flushFoldedSubAgentTools();
|
|
2744
2772
|
resetSubAgents();
|
|
2745
|
-
|
|
2773
|
+
activeRuns.clear();
|
|
2746
2774
|
setSubAgentWindowActive(false);
|
|
2747
2775
|
}
|
|
2748
2776
|
session.inSubAgent = false;
|
|
@@ -4297,11 +4325,13 @@ export async function startTerminalRepl() {
|
|
|
4297
4325
|
options: Array.isArray(req?.options) ? req.options : [],
|
|
4298
4326
|
context: req?.context || '',
|
|
4299
4327
|
});
|
|
4328
|
+
if (isInputDockMounted()) redrawDockInput();
|
|
4300
4329
|
if (res?.answer) {
|
|
4301
4330
|
process.stderr.write(` ${c.green('✓')} ${c.dim('answered:')} ${c.brand(res.answer)}\n`);
|
|
4302
4331
|
} else {
|
|
4303
4332
|
process.stderr.write(` ${c.dim('Question declined — agent proceeds with its own judgment.')}\n`);
|
|
4304
4333
|
}
|
|
4334
|
+
if (isInputDockMounted()) redrawDockInput();
|
|
4305
4335
|
return res;
|
|
4306
4336
|
};
|
|
4307
4337
|
|
|
@@ -4857,6 +4887,9 @@ export async function startTerminalRepl() {
|
|
|
4857
4887
|
let _bracketedPasteStartLine = '';
|
|
4858
4888
|
let _bracketedPasteStartCursor = 0;
|
|
4859
4889
|
let _promptHasInsertedPaste = false;
|
|
4890
|
+
let _suppressRawPasteLines = false;
|
|
4891
|
+
let _pastedInputValue = '';
|
|
4892
|
+
let _pastedInputLabel = '';
|
|
4860
4893
|
const _pasteEndListeners = new Set();
|
|
4861
4894
|
function onBracketedPasteEnd(cb) { _pasteEndListeners.add(cb); return () => _pasteEndListeners.delete(cb); }
|
|
4862
4895
|
function isInBracketedPaste() { return _inBracketedPaste; }
|
|
@@ -4876,7 +4909,25 @@ export async function startTerminalRepl() {
|
|
|
4876
4909
|
while (i < s.length) {
|
|
4877
4910
|
if (!_inBracketedPaste) {
|
|
4878
4911
|
const start = s.indexOf(PASTE_BEGIN, i);
|
|
4879
|
-
if (start === -1)
|
|
4912
|
+
if (start === -1) {
|
|
4913
|
+
if (isRawMultilinePasteChunk(s)) {
|
|
4914
|
+
_suppressRawPasteLines = true;
|
|
4915
|
+
const baseLine = String(rl?.line || '');
|
|
4916
|
+
const baseCursor = typeof rl?.cursor === 'number' ? rl.cursor : baseLine.length;
|
|
4917
|
+
setImmediate(() => {
|
|
4918
|
+
try {
|
|
4919
|
+
insertPromptText(normalizePastedText(s), {
|
|
4920
|
+
baseLine,
|
|
4921
|
+
baseCursor,
|
|
4922
|
+
fromPaste: true,
|
|
4923
|
+
});
|
|
4924
|
+
} finally {
|
|
4925
|
+
_suppressRawPasteLines = false;
|
|
4926
|
+
}
|
|
4927
|
+
});
|
|
4928
|
+
}
|
|
4929
|
+
return;
|
|
4930
|
+
}
|
|
4880
4931
|
_inBracketedPaste = true;
|
|
4881
4932
|
_bracketedPasteBuffer = '';
|
|
4882
4933
|
_suppressBracketedPasteLines = true;
|
|
@@ -5138,7 +5189,11 @@ export async function startTerminalRepl() {
|
|
|
5138
5189
|
const line = String(baseLine || '');
|
|
5139
5190
|
const cursor = typeof baseCursor === 'number' ? Math.max(0, Math.min(line.length, baseCursor)) : line.length;
|
|
5140
5191
|
const next = `${line.slice(0, cursor)}${payload}${line.slice(cursor)}`;
|
|
5141
|
-
if (fromPaste)
|
|
5192
|
+
if (fromPaste) {
|
|
5193
|
+
_promptHasInsertedPaste = true;
|
|
5194
|
+
_pastedInputValue = next;
|
|
5195
|
+
_pastedInputLabel = pastedTextLabel(payload);
|
|
5196
|
+
}
|
|
5142
5197
|
replaceReadlineLine(next, cursor + payload.length);
|
|
5143
5198
|
renderIdleDockInput();
|
|
5144
5199
|
}
|
|
@@ -5178,15 +5233,28 @@ export async function startTerminalRepl() {
|
|
|
5178
5233
|
|
|
5179
5234
|
function renderIdleDockInput() {
|
|
5180
5235
|
if (!isInputDockMounted()) return false;
|
|
5236
|
+
const line = rl.line || '';
|
|
5237
|
+
let displayLine = line;
|
|
5238
|
+
let displayCursor = typeof rl.cursor === 'number' ? rl.cursor : null;
|
|
5239
|
+
let fixedRows = null;
|
|
5240
|
+
if (_pastedInputValue && line === _pastedInputValue) {
|
|
5241
|
+
displayLine = _pastedInputLabel;
|
|
5242
|
+
displayCursor = _pastedInputLabel.length;
|
|
5243
|
+
fixedRows = 1;
|
|
5244
|
+
} else if (_pastedInputValue) {
|
|
5245
|
+
_pastedInputValue = '';
|
|
5246
|
+
_pastedInputLabel = '';
|
|
5247
|
+
}
|
|
5181
5248
|
// rl.cursor is readline's byte offset within rl.line. Threading it
|
|
5182
5249
|
// through to focusDockInput makes arrow-key navigation visually move
|
|
5183
5250
|
// the terminal cursor within the buffer instead of always landing at
|
|
5184
5251
|
// the end of the string.
|
|
5185
|
-
return renderDockInput(userPrompt(),
|
|
5252
|
+
return renderDockInput(userPrompt(), displayLine, {
|
|
5186
5253
|
context: buildContextStrip(),
|
|
5187
5254
|
meta: buildDockMeta(),
|
|
5188
5255
|
tips: idleInputTips(),
|
|
5189
|
-
cursor:
|
|
5256
|
+
cursor: displayCursor,
|
|
5257
|
+
fixedRows,
|
|
5190
5258
|
});
|
|
5191
5259
|
}
|
|
5192
5260
|
|
|
@@ -5232,7 +5300,7 @@ export async function startTerminalRepl() {
|
|
|
5232
5300
|
readline.emitKeypressEvents(process.stdin, rl);
|
|
5233
5301
|
process.stdin.on('keypress', (_str, key = {}) => {
|
|
5234
5302
|
if (!inputActive) return;
|
|
5235
|
-
if (_inBracketedPaste || _suppressBracketedPasteLines) return;
|
|
5303
|
+
if (_inBracketedPaste || _suppressBracketedPasteLines || _suppressRawPasteLines) return;
|
|
5236
5304
|
if (key.name === 'return' || key.name === 'enter') return;
|
|
5237
5305
|
if (key.name === 'f2') {
|
|
5238
5306
|
clearSlashHint();
|
|
@@ -5243,7 +5311,7 @@ export async function startTerminalRepl() {
|
|
|
5243
5311
|
}
|
|
5244
5312
|
setImmediate(() => {
|
|
5245
5313
|
if (!inputActive) return;
|
|
5246
|
-
if (_inBracketedPaste || _suppressBracketedPasteLines) return;
|
|
5314
|
+
if (_inBracketedPaste || _suppressBracketedPasteLines || _suppressRawPasteLines) return;
|
|
5247
5315
|
if (slashHintVisible && key.name === 'tab' && acceptSlashHint()) return;
|
|
5248
5316
|
if (slashHintVisible && key.name === 'down' && moveSlashHintSelection(1)) return;
|
|
5249
5317
|
if (slashHintVisible && key.name === 'up' && moveSlashHintSelection(-1)) return;
|
|
@@ -5305,12 +5373,16 @@ export async function startTerminalRepl() {
|
|
|
5305
5373
|
if (pastedLines.length > 1 || trailing) {
|
|
5306
5374
|
const text = [...pastedLines, trailing].join('\n');
|
|
5307
5375
|
_promptHasInsertedPaste = true;
|
|
5376
|
+
_pastedInputValue = text;
|
|
5377
|
+
_pastedInputLabel = pastedTextLabel(text);
|
|
5308
5378
|
replaceReadlineLine(text);
|
|
5309
5379
|
renderIdleDockInput();
|
|
5310
5380
|
return;
|
|
5311
5381
|
}
|
|
5312
5382
|
const line = pastedLines.join('\n');
|
|
5313
5383
|
_promptHasInsertedPaste = false;
|
|
5384
|
+
_pastedInputValue = '';
|
|
5385
|
+
_pastedInputLabel = '';
|
|
5314
5386
|
queueOrRunLine(line);
|
|
5315
5387
|
}
|
|
5316
5388
|
|
|
@@ -5320,7 +5392,7 @@ export async function startTerminalRepl() {
|
|
|
5320
5392
|
// or the user pressed Enter normally), the debounce falls back to old
|
|
5321
5393
|
// behavior — a single Enter flushes almost instantly.
|
|
5322
5394
|
rl.on('line', async (line) => {
|
|
5323
|
-
if (_suppressBracketedPasteLines) {
|
|
5395
|
+
if (_suppressBracketedPasteLines || _suppressRawPasteLines) {
|
|
5324
5396
|
_pasteLines = [];
|
|
5325
5397
|
if (_pasteFlushTimer) {
|
|
5326
5398
|
clearTimeout(_pasteFlushTimer);
|
|
@@ -5369,6 +5441,8 @@ export async function startTerminalRepl() {
|
|
|
5369
5441
|
let input = line.trim();
|
|
5370
5442
|
const selectedSlashCommand = selectedSlashCommandFor(input);
|
|
5371
5443
|
inputActive = false;
|
|
5444
|
+
_pastedInputValue = '';
|
|
5445
|
+
_pastedInputLabel = '';
|
|
5372
5446
|
clearSlashHint();
|
|
5373
5447
|
if (selectedSlashCommand) input = selectedSlashCommand;
|
|
5374
5448
|
if (!input) {
|
package/src/tools/registry.mjs
CHANGED
|
@@ -122,6 +122,25 @@ export function createToolRegistry({
|
|
|
122
122
|
const name = String(toolDef.name || '').trim();
|
|
123
123
|
if (!name || tools.has(name)) continue;
|
|
124
124
|
const pluginName = toolDef._plugin_name || toolDef.plugin_name || null;
|
|
125
|
+
if (toolDef._composed?.kind === 'pi') {
|
|
126
|
+
tools.set(name, {
|
|
127
|
+
name,
|
|
128
|
+
description: toolDef.description || '',
|
|
129
|
+
inputSchema: toolDef.input_schema || toolDef.parameters || { type: 'object', properties: {} },
|
|
130
|
+
validateInput() { return []; },
|
|
131
|
+
async call() {
|
|
132
|
+
return {
|
|
133
|
+
success: false,
|
|
134
|
+
output: `Composed pi tool '${name}' is registered but pi runtime execution is not wired yet`,
|
|
135
|
+
_tool: name,
|
|
136
|
+
_plugin: pluginName,
|
|
137
|
+
_composed: toolDef._composed,
|
|
138
|
+
_blocked: true,
|
|
139
|
+
};
|
|
140
|
+
},
|
|
141
|
+
});
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
125
144
|
tools.set(name, {
|
|
126
145
|
name,
|
|
127
146
|
description: toolDef.description || '',
|
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;
|
|
@@ -672,10 +701,13 @@ export function clearInputPrompt() {
|
|
|
672
701
|
return true;
|
|
673
702
|
}
|
|
674
703
|
|
|
675
|
-
export function renderDockInput(prefix, value, { context = '', tips = '', meta = '', cursor = null } = {}) {
|
|
704
|
+
export function renderDockInput(prefix, value, { context = '', tips = '', meta = '', cursor = null, fixedRows = null } = {}) {
|
|
676
705
|
if (!mounted) return false;
|
|
677
706
|
contentTrackingActive = false;
|
|
678
|
-
|
|
707
|
+
const requestedRows = fixedRows == null
|
|
708
|
+
? computeInputRowsForBuffer(prefix, value)
|
|
709
|
+
: Math.max(MIN_INPUT_ROWS, Math.min(inputRowsMax, Math.floor(Number(fixedRows) || MIN_INPUT_ROWS)));
|
|
710
|
+
setInputRowsTo(requestedRows);
|
|
679
711
|
renderFrame({ context, tips, meta, prefix, value, cursor, overlayLines: null });
|
|
680
712
|
const layout = layoutInput(prefix, value);
|
|
681
713
|
drawInputLines(layout.lines);
|
|
@@ -722,28 +754,35 @@ export function renderDockOverlay({
|
|
|
722
754
|
export function focusDockInput(prefix, value = '', cursorInValue = null) {
|
|
723
755
|
if (!mounted) return false;
|
|
724
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) {
|
|
725
769
|
const layout = layoutInput(prefix, value);
|
|
726
770
|
const valueStr = String(value || '');
|
|
727
771
|
const rawCursor = cursorInValue == null
|
|
728
772
|
? valueStr.length
|
|
729
773
|
: Math.max(0, Math.min(valueStr.length, Math.floor(cursorInValue)));
|
|
730
774
|
const cursorSlice = valueStr.slice(0, rawCursor);
|
|
731
|
-
const
|
|
732
|
-
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);
|
|
733
779
|
const visibleRowIdx = Math.max(
|
|
734
780
|
0,
|
|
735
781
|
Math.min(inputRows - 1, pos.row - Math.max(0, layout.wrapped.length - inputRows)),
|
|
736
782
|
);
|
|
737
783
|
const row = inputRowStart() + visibleRowIdx;
|
|
738
784
|
const col = Math.min(cols(), INPUT_INDENT + 1 + Math.max(0, pos.col));
|
|
739
|
-
|
|
740
|
-
// Record the park position — every queue op re-parks here so readline
|
|
741
|
-
// echoes always land in the input row, even mid-stream.
|
|
742
|
-
queue.park(row, col);
|
|
743
|
-
return true;
|
|
744
|
-
}
|
|
745
|
-
moveTo(row, col);
|
|
746
|
-
return true;
|
|
785
|
+
return { row, col };
|
|
747
786
|
}
|
|
748
787
|
|
|
749
788
|
export function inputRowColumn() {
|
|
@@ -763,6 +802,8 @@ export function _internals() {
|
|
|
763
802
|
drawableColumns,
|
|
764
803
|
resetContentCursor,
|
|
765
804
|
contentCursor: () => ({ row: contentCursorRow, col: contentCursorCol, active: contentTrackingActive }),
|
|
805
|
+
cursorTargetForInput,
|
|
806
|
+
terminalCellWidthFromColumn,
|
|
766
807
|
overlayRowsForWrapped,
|
|
767
808
|
FIXED_ROWS,
|
|
768
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 ────────────────────────────────────────────────────────────
|