@pensar/apex 0.0.83-canary.623a32d3 → 0.0.83
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/build/index.js +455 -54
- package/package.json +1 -1
package/build/index.js
CHANGED
|
@@ -31956,7 +31956,7 @@ var package_default2;
|
|
|
31956
31956
|
var init_package = __esm(() => {
|
|
31957
31957
|
package_default2 = {
|
|
31958
31958
|
name: "@pensar/apex",
|
|
31959
|
-
version: "0.0.83
|
|
31959
|
+
version: "0.0.83",
|
|
31960
31960
|
description: "AI-powered penetration testing CLI tool with terminal UI",
|
|
31961
31961
|
module: "src/tui/index.tsx",
|
|
31962
31962
|
main: "build/index.js",
|
|
@@ -84374,6 +84374,47 @@ function parseAnthropicResponse(response, usage) {
|
|
|
84374
84374
|
return { content, finishReason, usage: finalUsage };
|
|
84375
84375
|
}
|
|
84376
84376
|
|
|
84377
|
+
// src/core/ai/providers/pensarSSE.ts
|
|
84378
|
+
async function* parseSSE(stream) {
|
|
84379
|
+
const reader = stream.getReader();
|
|
84380
|
+
const decoder2 = new TextDecoder;
|
|
84381
|
+
let buffer = "";
|
|
84382
|
+
let currentEvent = "message";
|
|
84383
|
+
let currentData = [];
|
|
84384
|
+
try {
|
|
84385
|
+
while (true) {
|
|
84386
|
+
const { done, value } = await reader.read();
|
|
84387
|
+
if (done)
|
|
84388
|
+
break;
|
|
84389
|
+
buffer += decoder2.decode(value, { stream: true });
|
|
84390
|
+
const lines = buffer.split(`
|
|
84391
|
+
`);
|
|
84392
|
+
buffer = lines.pop() ?? "";
|
|
84393
|
+
for (let line of lines) {
|
|
84394
|
+
line = line.replace(/\r$/, "");
|
|
84395
|
+
if (line === "") {
|
|
84396
|
+
if (currentData.length > 0) {
|
|
84397
|
+
yield { event: currentEvent, data: currentData.join(`
|
|
84398
|
+
`) };
|
|
84399
|
+
}
|
|
84400
|
+
currentEvent = "message";
|
|
84401
|
+
currentData = [];
|
|
84402
|
+
} else if (line.startsWith("event:")) {
|
|
84403
|
+
currentEvent = line.slice(6).trim();
|
|
84404
|
+
} else if (line.startsWith("data:")) {
|
|
84405
|
+
currentData.push(line.slice(5).trim());
|
|
84406
|
+
}
|
|
84407
|
+
}
|
|
84408
|
+
}
|
|
84409
|
+
if (currentData.length > 0) {
|
|
84410
|
+
yield { event: currentEvent, data: currentData.join(`
|
|
84411
|
+
`) };
|
|
84412
|
+
}
|
|
84413
|
+
} finally {
|
|
84414
|
+
reader.releaseLock();
|
|
84415
|
+
}
|
|
84416
|
+
}
|
|
84417
|
+
|
|
84377
84418
|
// src/core/ai/providers/pensar.ts
|
|
84378
84419
|
function log(...args) {
|
|
84379
84420
|
if (DEBUG)
|
|
@@ -84472,63 +84513,248 @@ function createPensarModel(bedrockModelId, config2) {
|
|
|
84472
84513
|
};
|
|
84473
84514
|
},
|
|
84474
84515
|
async doStream(options) {
|
|
84475
|
-
|
|
84476
|
-
const
|
|
84477
|
-
|
|
84478
|
-
|
|
84479
|
-
|
|
84480
|
-
|
|
84481
|
-
|
|
84482
|
-
|
|
84483
|
-
|
|
84484
|
-
|
|
84485
|
-
|
|
84486
|
-
|
|
84487
|
-
|
|
84488
|
-
|
|
84489
|
-
|
|
84490
|
-
|
|
84491
|
-
|
|
84492
|
-
|
|
84493
|
-
|
|
84494
|
-
|
|
84495
|
-
|
|
84496
|
-
|
|
84497
|
-
|
|
84498
|
-
|
|
84499
|
-
|
|
84500
|
-
|
|
84501
|
-
|
|
84502
|
-
|
|
84503
|
-
|
|
84504
|
-
|
|
84505
|
-
|
|
84506
|
-
});
|
|
84516
|
+
const body = convertToBedrockFormat(bedrockModelId, options);
|
|
84517
|
+
const url2 = `${config2.baseUrl}/bedrock/stream`;
|
|
84518
|
+
log(`doStream → SSE streaming for ${bedrockModelId}`);
|
|
84519
|
+
log(` URL: ${url2}`);
|
|
84520
|
+
const headers = await buildHeaders();
|
|
84521
|
+
let response;
|
|
84522
|
+
try {
|
|
84523
|
+
response = await fetch(url2, {
|
|
84524
|
+
method: "POST",
|
|
84525
|
+
headers,
|
|
84526
|
+
signal: options.abortSignal,
|
|
84527
|
+
body: JSON.stringify({
|
|
84528
|
+
modelId: bedrockModelId,
|
|
84529
|
+
body
|
|
84530
|
+
})
|
|
84531
|
+
});
|
|
84532
|
+
} catch (err) {
|
|
84533
|
+
logError(` SSE fetch failed, falling back to doGenerate:`, err);
|
|
84534
|
+
return doStreamFallback(model, options);
|
|
84535
|
+
}
|
|
84536
|
+
if (!response.ok) {
|
|
84537
|
+
const errorBody = await response.text();
|
|
84538
|
+
let errorMessage;
|
|
84539
|
+
try {
|
|
84540
|
+
const parsed = JSON.parse(errorBody);
|
|
84541
|
+
errorMessage = parsed.error || parsed.message || errorBody;
|
|
84542
|
+
} catch {
|
|
84543
|
+
errorMessage = errorBody;
|
|
84544
|
+
}
|
|
84545
|
+
logError(` SSE FAILED ${response.status}: ${errorMessage}`);
|
|
84546
|
+
if (response.status === 402) {
|
|
84547
|
+
throw new Error(`Insufficient Pensar credits: ${errorMessage}. ` + `Top up at https://console.pensar.dev`);
|
|
84507
84548
|
}
|
|
84549
|
+
throw new Error(`Pensar streaming error (${response.status}): ${errorMessage}`);
|
|
84508
84550
|
}
|
|
84509
|
-
|
|
84510
|
-
|
|
84511
|
-
|
|
84512
|
-
|
|
84513
|
-
|
|
84551
|
+
if (!response.body) {
|
|
84552
|
+
logError(` SSE response has no body, falling back to doGenerate`);
|
|
84553
|
+
return doStreamFallback(model, options);
|
|
84554
|
+
}
|
|
84555
|
+
const sseStream = response.body;
|
|
84514
84556
|
const stream = new ReadableStream({
|
|
84515
|
-
start(controller) {
|
|
84516
|
-
|
|
84517
|
-
|
|
84557
|
+
async start(controller) {
|
|
84558
|
+
let inputTokens = 0;
|
|
84559
|
+
let outputTokens = 0;
|
|
84560
|
+
let finishReason = "unknown";
|
|
84561
|
+
let startEmitted = false;
|
|
84562
|
+
let activeTextId = null;
|
|
84563
|
+
let activeToolId = null;
|
|
84564
|
+
let activeToolName = null;
|
|
84565
|
+
let activeToolInput = "";
|
|
84566
|
+
try {
|
|
84567
|
+
for await (const sse of parseSSE(sseStream)) {
|
|
84568
|
+
let parsed;
|
|
84569
|
+
try {
|
|
84570
|
+
parsed = JSON.parse(sse.data);
|
|
84571
|
+
} catch {
|
|
84572
|
+
continue;
|
|
84573
|
+
}
|
|
84574
|
+
if (sse.event === "error") {
|
|
84575
|
+
const msg = parsed.error ?? "Unknown streaming error";
|
|
84576
|
+
controller.error(new Error(msg));
|
|
84577
|
+
return;
|
|
84578
|
+
}
|
|
84579
|
+
if (sse.event === "pensar:usage") {
|
|
84580
|
+
if (parsed.totalCost != null) {
|
|
84581
|
+
log(` cost: $${Number(parsed.totalCost).toFixed(6)}`);
|
|
84582
|
+
}
|
|
84583
|
+
continue;
|
|
84584
|
+
}
|
|
84585
|
+
const type = parsed.type;
|
|
84586
|
+
switch (type) {
|
|
84587
|
+
case "message_start": {
|
|
84588
|
+
if (!startEmitted) {
|
|
84589
|
+
controller.enqueue({ type: "stream-start", warnings: [] });
|
|
84590
|
+
startEmitted = true;
|
|
84591
|
+
}
|
|
84592
|
+
const msg = parsed.message;
|
|
84593
|
+
const usage = msg?.usage;
|
|
84594
|
+
if (usage?.input_tokens) {
|
|
84595
|
+
inputTokens = usage.input_tokens;
|
|
84596
|
+
}
|
|
84597
|
+
break;
|
|
84598
|
+
}
|
|
84599
|
+
case "content_block_start": {
|
|
84600
|
+
if (!startEmitted) {
|
|
84601
|
+
controller.enqueue({ type: "stream-start", warnings: [] });
|
|
84602
|
+
startEmitted = true;
|
|
84603
|
+
}
|
|
84604
|
+
const cb = parsed.content_block;
|
|
84605
|
+
const cbType = cb?.type;
|
|
84606
|
+
if (cbType === "text") {
|
|
84607
|
+
activeTextId = `text-${Date.now()}-${parsed.index}`;
|
|
84608
|
+
controller.enqueue({
|
|
84609
|
+
type: "text-start",
|
|
84610
|
+
id: activeTextId
|
|
84611
|
+
});
|
|
84612
|
+
} else if (cbType === "tool_use") {
|
|
84613
|
+
activeToolId = cb?.id ?? `tool-${Date.now()}`;
|
|
84614
|
+
activeToolName = cb?.name ?? "unknown";
|
|
84615
|
+
activeToolInput = "";
|
|
84616
|
+
controller.enqueue({
|
|
84617
|
+
type: "tool-input-start",
|
|
84618
|
+
id: activeToolId,
|
|
84619
|
+
toolName: activeToolName
|
|
84620
|
+
});
|
|
84621
|
+
}
|
|
84622
|
+
break;
|
|
84623
|
+
}
|
|
84624
|
+
case "content_block_delta": {
|
|
84625
|
+
const delta = parsed.delta;
|
|
84626
|
+
const deltaType = delta?.type;
|
|
84627
|
+
if (deltaType === "text_delta" && activeTextId) {
|
|
84628
|
+
controller.enqueue({
|
|
84629
|
+
type: "text-delta",
|
|
84630
|
+
id: activeTextId,
|
|
84631
|
+
delta: delta?.text ?? ""
|
|
84632
|
+
});
|
|
84633
|
+
} else if (deltaType === "input_json_delta" && activeToolId) {
|
|
84634
|
+
const jsonDelta = delta?.partial_json ?? "";
|
|
84635
|
+
activeToolInput += jsonDelta;
|
|
84636
|
+
controller.enqueue({
|
|
84637
|
+
type: "tool-input-delta",
|
|
84638
|
+
id: activeToolId,
|
|
84639
|
+
delta: jsonDelta
|
|
84640
|
+
});
|
|
84641
|
+
}
|
|
84642
|
+
break;
|
|
84643
|
+
}
|
|
84644
|
+
case "content_block_stop": {
|
|
84645
|
+
if (activeTextId) {
|
|
84646
|
+
controller.enqueue({ type: "text-end", id: activeTextId });
|
|
84647
|
+
activeTextId = null;
|
|
84648
|
+
} else if (activeToolId && activeToolName) {
|
|
84649
|
+
controller.enqueue({
|
|
84650
|
+
type: "tool-input-end",
|
|
84651
|
+
id: activeToolId
|
|
84652
|
+
});
|
|
84653
|
+
controller.enqueue({
|
|
84654
|
+
type: "tool-call",
|
|
84655
|
+
toolCallId: activeToolId,
|
|
84656
|
+
toolName: activeToolName,
|
|
84657
|
+
input: activeToolInput
|
|
84658
|
+
});
|
|
84659
|
+
activeToolId = null;
|
|
84660
|
+
activeToolName = null;
|
|
84661
|
+
activeToolInput = "";
|
|
84662
|
+
}
|
|
84663
|
+
break;
|
|
84664
|
+
}
|
|
84665
|
+
case "message_delta": {
|
|
84666
|
+
const delta = parsed.delta;
|
|
84667
|
+
const stopReason = delta?.stop_reason;
|
|
84668
|
+
if (stopReason) {
|
|
84669
|
+
finishReason = mapStopReason(stopReason);
|
|
84670
|
+
}
|
|
84671
|
+
const usage = parsed.usage;
|
|
84672
|
+
if (usage?.output_tokens) {
|
|
84673
|
+
outputTokens = usage.output_tokens;
|
|
84674
|
+
}
|
|
84675
|
+
break;
|
|
84676
|
+
}
|
|
84677
|
+
case "message_stop": {
|
|
84678
|
+
break;
|
|
84679
|
+
}
|
|
84680
|
+
}
|
|
84681
|
+
}
|
|
84682
|
+
log(` stream done: ${finishReason}, ${inputTokens}in/${outputTokens}out`);
|
|
84683
|
+
controller.enqueue({
|
|
84684
|
+
type: "finish",
|
|
84685
|
+
finishReason,
|
|
84686
|
+
usage: {
|
|
84687
|
+
inputTokens,
|
|
84688
|
+
outputTokens,
|
|
84689
|
+
totalTokens: inputTokens + outputTokens
|
|
84690
|
+
}
|
|
84691
|
+
});
|
|
84692
|
+
controller.close();
|
|
84693
|
+
} catch (err) {
|
|
84694
|
+
controller.error(err);
|
|
84518
84695
|
}
|
|
84519
|
-
controller.close();
|
|
84520
84696
|
}
|
|
84521
84697
|
});
|
|
84522
84698
|
return {
|
|
84523
84699
|
stream,
|
|
84524
|
-
request: {
|
|
84525
|
-
body: generateResult.request?.body
|
|
84526
|
-
}
|
|
84700
|
+
request: { body }
|
|
84527
84701
|
};
|
|
84528
84702
|
}
|
|
84529
84703
|
};
|
|
84530
84704
|
return model;
|
|
84531
84705
|
}
|
|
84706
|
+
function mapStopReason(reason) {
|
|
84707
|
+
switch (reason) {
|
|
84708
|
+
case "end_turn":
|
|
84709
|
+
return "stop";
|
|
84710
|
+
case "max_tokens":
|
|
84711
|
+
return "length";
|
|
84712
|
+
case "tool_use":
|
|
84713
|
+
return "tool-calls";
|
|
84714
|
+
case "stop_sequence":
|
|
84715
|
+
return "stop";
|
|
84716
|
+
default:
|
|
84717
|
+
return "unknown";
|
|
84718
|
+
}
|
|
84719
|
+
}
|
|
84720
|
+
async function doStreamFallback(model, options) {
|
|
84721
|
+
const generateResult = await model.doGenerate(options);
|
|
84722
|
+
const parts = [];
|
|
84723
|
+
parts.push({ type: "stream-start", warnings: generateResult.warnings });
|
|
84724
|
+
for (const item of generateResult.content) {
|
|
84725
|
+
if (item.type === "text") {
|
|
84726
|
+
const id = `text-${Date.now()}`;
|
|
84727
|
+
parts.push({ type: "text-start", id });
|
|
84728
|
+
parts.push({ type: "text-delta", id, delta: item.text });
|
|
84729
|
+
parts.push({ type: "text-end", id });
|
|
84730
|
+
} else if (item.type === "tool-call") {
|
|
84731
|
+
const id = item.toolCallId;
|
|
84732
|
+
parts.push({ type: "tool-input-start", id, toolName: item.toolName });
|
|
84733
|
+
parts.push({ type: "tool-input-delta", id, delta: item.input });
|
|
84734
|
+
parts.push({ type: "tool-input-end", id });
|
|
84735
|
+
parts.push({
|
|
84736
|
+
type: "tool-call",
|
|
84737
|
+
toolCallId: id,
|
|
84738
|
+
toolName: item.toolName,
|
|
84739
|
+
input: item.input
|
|
84740
|
+
});
|
|
84741
|
+
}
|
|
84742
|
+
}
|
|
84743
|
+
parts.push({
|
|
84744
|
+
type: "finish",
|
|
84745
|
+
finishReason: generateResult.finishReason,
|
|
84746
|
+
usage: generateResult.usage
|
|
84747
|
+
});
|
|
84748
|
+
const stream = new ReadableStream({
|
|
84749
|
+
start(controller) {
|
|
84750
|
+
for (const part of parts) {
|
|
84751
|
+
controller.enqueue(part);
|
|
84752
|
+
}
|
|
84753
|
+
controller.close();
|
|
84754
|
+
}
|
|
84755
|
+
});
|
|
84756
|
+
return { stream, request: { body: generateResult.request?.body } };
|
|
84757
|
+
}
|
|
84532
84758
|
var DEBUG;
|
|
84533
84759
|
var init_pensar2 = __esm(() => {
|
|
84534
84760
|
DEBUG = process.env.PENSAR_DEBUG === "1" || process.env.PENSAR_DEBUG === "true";
|
|
@@ -107948,6 +108174,11 @@ They are organised into categories:
|
|
|
107948
108174
|
- "framework" — framework-specific notes (e.g. Rails tricks, Django patterns)
|
|
107949
108175
|
- "general" — catch-all (default when category is omitted)
|
|
107950
108176
|
|
|
108177
|
+
IMPORTANT: Before adding a new memory, always use list_memories first to check
|
|
108178
|
+
for existing memories that overlap with what you intend to save. If a relevant
|
|
108179
|
+
memory already exists, update it (delete + re-add) rather than creating a
|
|
108180
|
+
duplicate. This keeps the memory store clean and avoids conflicting entries.
|
|
108181
|
+
|
|
107951
108182
|
Use this to record reusable techniques, target-specific notes, credential
|
|
107952
108183
|
patterns, useful payloads, or any information worth remembering for future
|
|
107953
108184
|
engagements.`,
|
|
@@ -189828,6 +190059,7 @@ You can perform the full lifecycle of a penetration test and support a wide rang
|
|
|
189828
190059
|
4. **Use the right level of automation.** For large tasks (e.g., "pentest this entire application"), use orchestration tools like \`run_attack_surface\` and \`spawn_pentest_swarm\` to parallelize the work. For focused tasks (e.g., "check if this parameter is vulnerable to XSS"), work directly with \`http_request\`, \`execute_command\`, or the browser tools.
|
|
189829
190060
|
5. **Authenticate when needed.** If the user provides credentials or auth instructions, use them. Try \`authenticate_session\` first; fall back to \`delegate_to_auth_subagent\` for complex flows (OAuth, SAML, CSRF-protected SPAs).
|
|
189830
190061
|
6. **Document as you go.** Call \`document_asset\` when you discover assets. Call \`document_finding\` when you confirm vulnerabilities. Create PoCs with \`create_poc\` to demonstrate exploitability. Don't defer documentation to the end.
|
|
190062
|
+
7. **Consult memories first.** When you begin testing a specific application, framework, or path, call \`list_memories\` to check for saved knowledge from previous sessions — past findings, useful payloads, endpoint patterns, or target-specific notes. Use relevant memories to inform your approach before starting from scratch.
|
|
189831
190063
|
|
|
189832
190064
|
# Command Execution
|
|
189833
190065
|
|
|
@@ -270639,6 +270871,7 @@ var PromptInput = import_react29.forwardRef(function PromptInput2({
|
|
|
270639
270871
|
enableCommands = false,
|
|
270640
270872
|
onCommandExecute,
|
|
270641
270873
|
commandHistory = [],
|
|
270874
|
+
disableHistoryNavigation = false,
|
|
270642
270875
|
showPromptIndicator = false
|
|
270643
270876
|
}, ref) {
|
|
270644
270877
|
const { colors: colors2 } = useTheme();
|
|
@@ -270713,6 +270946,8 @@ var PromptInput = import_react29.forwardRef(function PromptInput2({
|
|
|
270713
270946
|
}
|
|
270714
270947
|
return;
|
|
270715
270948
|
}
|
|
270949
|
+
if (disableHistoryNavigation)
|
|
270950
|
+
return;
|
|
270716
270951
|
const history = historyRef.current;
|
|
270717
270952
|
const currentState = {
|
|
270718
270953
|
historyIndex,
|
|
@@ -280690,13 +280925,13 @@ function NormalInputAreaInner({
|
|
|
280690
280925
|
autocompleteOptions = [],
|
|
280691
280926
|
enableCommands = false,
|
|
280692
280927
|
onCommandExecute,
|
|
280693
|
-
commandHistory = []
|
|
280928
|
+
commandHistory = [],
|
|
280929
|
+
disableHistoryNavigation = false
|
|
280694
280930
|
}) {
|
|
280695
280931
|
const { colors: colors2 } = useTheme();
|
|
280696
280932
|
const { inputValue, setInputValue } = useInput();
|
|
280697
280933
|
const promptRef = import_react77.useRef(null);
|
|
280698
280934
|
const isExternalUpdate = import_react77.useRef(false);
|
|
280699
|
-
const isDisabled = status === "running";
|
|
280700
280935
|
import_react77.useEffect(() => {
|
|
280701
280936
|
if (value !== inputValue) {
|
|
280702
280937
|
isExternalUpdate.current = true;
|
|
@@ -280733,7 +280968,7 @@ function NormalInputAreaInner({
|
|
|
280733
280968
|
backgroundColor: "transparent",
|
|
280734
280969
|
children: [
|
|
280735
280970
|
/* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
|
|
280736
|
-
fg:
|
|
280971
|
+
fg: !focused ? colors2.textMuted : colors2.primary,
|
|
280737
280972
|
children: ">"
|
|
280738
280973
|
}, undefined, false, undefined, this),
|
|
280739
280974
|
/* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV(PromptInput, {
|
|
@@ -280742,14 +280977,15 @@ function NormalInputAreaInner({
|
|
|
280742
280977
|
minHeight: 1,
|
|
280743
280978
|
maxHeight: 3,
|
|
280744
280979
|
textColor: colors2.text,
|
|
280745
|
-
focused
|
|
280746
|
-
placeholder
|
|
280980
|
+
focused,
|
|
280981
|
+
placeholder,
|
|
280747
280982
|
onSubmit: handleSubmit,
|
|
280748
280983
|
enableAutocomplete,
|
|
280749
280984
|
autocompleteOptions,
|
|
280750
280985
|
enableCommands,
|
|
280751
280986
|
onCommandExecute,
|
|
280752
|
-
commandHistory
|
|
280987
|
+
commandHistory,
|
|
280988
|
+
disableHistoryNavigation
|
|
280753
280989
|
}, undefined, false, undefined, this)
|
|
280754
280990
|
]
|
|
280755
280991
|
}, undefined, true, undefined, this),
|
|
@@ -280836,6 +281072,7 @@ function InputArea(props) {
|
|
|
280836
281072
|
enableCommands,
|
|
280837
281073
|
onCommandExecute,
|
|
280838
281074
|
commandHistory,
|
|
281075
|
+
disableHistoryNavigation,
|
|
280839
281076
|
...normalProps
|
|
280840
281077
|
} = props;
|
|
280841
281078
|
if (pendingApproval) {
|
|
@@ -280859,6 +281096,7 @@ function InputArea(props) {
|
|
|
280859
281096
|
enableCommands,
|
|
280860
281097
|
onCommandExecute,
|
|
280861
281098
|
commandHistory,
|
|
281099
|
+
disableHistoryNavigation,
|
|
280862
281100
|
...normalProps
|
|
280863
281101
|
}, undefined, false, undefined, this)
|
|
280864
281102
|
}, undefined, false, undefined, this);
|
|
@@ -281074,6 +281312,89 @@ function accumulateTokenUsage(current, stepInputTokens, stepOutputTokens) {
|
|
|
281074
281312
|
};
|
|
281075
281313
|
}
|
|
281076
281314
|
|
|
281315
|
+
// src/tui/components/operator-dashboard/queued-messages.tsx
|
|
281316
|
+
function QueuedMessages({
|
|
281317
|
+
messages,
|
|
281318
|
+
selectedIndex
|
|
281319
|
+
}) {
|
|
281320
|
+
const { colors: colors2 } = useTheme();
|
|
281321
|
+
if (messages.length === 0)
|
|
281322
|
+
return null;
|
|
281323
|
+
return /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
|
|
281324
|
+
flexDirection: "column",
|
|
281325
|
+
paddingLeft: 2,
|
|
281326
|
+
paddingRight: 2,
|
|
281327
|
+
paddingBottom: 1,
|
|
281328
|
+
flexShrink: 0,
|
|
281329
|
+
children: [
|
|
281330
|
+
/* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
|
|
281331
|
+
flexDirection: "row",
|
|
281332
|
+
gap: 1,
|
|
281333
|
+
marginBottom: 0,
|
|
281334
|
+
children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
|
|
281335
|
+
fg: colors2.textMuted,
|
|
281336
|
+
children: [
|
|
281337
|
+
"Queued (",
|
|
281338
|
+
messages.length,
|
|
281339
|
+
")"
|
|
281340
|
+
]
|
|
281341
|
+
}, undefined, true, undefined, this)
|
|
281342
|
+
}, undefined, false, undefined, this),
|
|
281343
|
+
messages.map((msg, index) => {
|
|
281344
|
+
const isSelected = index === selectedIndex;
|
|
281345
|
+
const displayText = msg.length > 80 ? msg.slice(0, 77) + "…" : msg;
|
|
281346
|
+
return /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
|
|
281347
|
+
flexDirection: "row",
|
|
281348
|
+
gap: 1,
|
|
281349
|
+
children: [
|
|
281350
|
+
/* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
|
|
281351
|
+
fg: isSelected ? colors2.primary : colors2.textMuted,
|
|
281352
|
+
children: isSelected ? "▸" : " "
|
|
281353
|
+
}, undefined, false, undefined, this),
|
|
281354
|
+
/* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
|
|
281355
|
+
fg: isSelected ? colors2.text : colors2.textMuted,
|
|
281356
|
+
children: displayText
|
|
281357
|
+
}, undefined, false, undefined, this)
|
|
281358
|
+
]
|
|
281359
|
+
}, `q-${index}`, true, undefined, this);
|
|
281360
|
+
}),
|
|
281361
|
+
/* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
|
|
281362
|
+
flexDirection: "row",
|
|
281363
|
+
gap: 2,
|
|
281364
|
+
marginTop: 0,
|
|
281365
|
+
children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
|
|
281366
|
+
fg: colors2.textMuted,
|
|
281367
|
+
children: "↑↓ select • Enter send now • ⌫ delete • e edit"
|
|
281368
|
+
}, undefined, false, undefined, this)
|
|
281369
|
+
}, undefined, false, undefined, this)
|
|
281370
|
+
]
|
|
281371
|
+
}, undefined, true, undefined, this);
|
|
281372
|
+
}
|
|
281373
|
+
|
|
281374
|
+
// src/tui/components/operator-dashboard/queue.ts
|
|
281375
|
+
function selectionAfterRemove(prevQueueLength, selectedIndex) {
|
|
281376
|
+
const newLen = prevQueueLength - 1;
|
|
281377
|
+
if (newLen === 0)
|
|
281378
|
+
return -1;
|
|
281379
|
+
return Math.min(selectedIndex, newLen - 1);
|
|
281380
|
+
}
|
|
281381
|
+
function navigateUp(selectedIndex, queueLength) {
|
|
281382
|
+
if (queueLength === 0)
|
|
281383
|
+
return -1;
|
|
281384
|
+
if (selectedIndex === -1)
|
|
281385
|
+
return queueLength - 1;
|
|
281386
|
+
if (selectedIndex === 0)
|
|
281387
|
+
return 0;
|
|
281388
|
+
return selectedIndex - 1;
|
|
281389
|
+
}
|
|
281390
|
+
function navigateDown(selectedIndex, queueLength) {
|
|
281391
|
+
if (selectedIndex === -1)
|
|
281392
|
+
return -1;
|
|
281393
|
+
if (selectedIndex >= queueLength - 1)
|
|
281394
|
+
return -1;
|
|
281395
|
+
return selectedIndex + 1;
|
|
281396
|
+
}
|
|
281397
|
+
|
|
281077
281398
|
// src/tui/components/operator-dashboard/index.tsx
|
|
281078
281399
|
import { existsSync as existsSync26, readFileSync as readFileSync13 } from "fs";
|
|
281079
281400
|
import { join as join27 } from "path";
|
|
@@ -281127,6 +281448,17 @@ function OperatorDashboard({
|
|
|
281127
281448
|
const textRef = import_react79.useRef("");
|
|
281128
281449
|
const conversationRef = import_react79.useRef([]);
|
|
281129
281450
|
const [inputValue, setInputValue] = import_react79.useState("");
|
|
281451
|
+
const [queuedMessages, setQueuedMessages] = import_react79.useState([]);
|
|
281452
|
+
const [selectedQueueIndex, setSelectedQueueIndex] = import_react79.useState(-1);
|
|
281453
|
+
const queuedMessagesRef = import_react79.useRef([]);
|
|
281454
|
+
import_react79.useEffect(() => {
|
|
281455
|
+
queuedMessagesRef.current = queuedMessages;
|
|
281456
|
+
if (queuedMessages.length === 0) {
|
|
281457
|
+
setSelectedQueueIndex(-1);
|
|
281458
|
+
} else if (selectedQueueIndex >= queuedMessages.length) {
|
|
281459
|
+
setSelectedQueueIndex(queuedMessages.length - 1);
|
|
281460
|
+
}
|
|
281461
|
+
}, [queuedMessages, selectedQueueIndex]);
|
|
281130
281462
|
const [operatorState, setOperatorState] = import_react79.useState(() => createInitialOperatorState("manual", true));
|
|
281131
281463
|
const approvalGateRef = import_react79.useRef(new ApprovalGate({ requireApproval: true }));
|
|
281132
281464
|
const [pendingApprovals, setPendingApprovals] = import_react79.useState([]);
|
|
@@ -281616,6 +281948,11 @@ function OperatorDashboard({
|
|
|
281616
281948
|
approvalGateRef.current.deny(p.id);
|
|
281617
281949
|
}
|
|
281618
281950
|
}
|
|
281951
|
+
if (result.action === "blocked" && value.trim()) {
|
|
281952
|
+
setQueuedMessages((prev) => [...prev, value.trim()]);
|
|
281953
|
+
setInputValue("");
|
|
281954
|
+
return;
|
|
281955
|
+
}
|
|
281619
281956
|
if (result.action !== "run" || !result.prompt)
|
|
281620
281957
|
return;
|
|
281621
281958
|
setInputValue("");
|
|
@@ -281630,6 +281967,17 @@ function OperatorDashboard({
|
|
|
281630
281967
|
runAgentRef.current(initialMessage);
|
|
281631
281968
|
}
|
|
281632
281969
|
}, [loading, initialMessage]);
|
|
281970
|
+
import_react79.useEffect(() => {
|
|
281971
|
+
if (status !== "idle")
|
|
281972
|
+
return;
|
|
281973
|
+
const queue = queuedMessagesRef.current;
|
|
281974
|
+
if (queue.length === 0)
|
|
281975
|
+
return;
|
|
281976
|
+
const next = queue[0];
|
|
281977
|
+
setQueuedMessages((prev) => prev.slice(1));
|
|
281978
|
+
setSelectedQueueIndex(-1);
|
|
281979
|
+
runAgentRef.current(next);
|
|
281980
|
+
}, [status]);
|
|
281633
281981
|
const showModelPicker = import_react79.useCallback(() => {
|
|
281634
281982
|
setDialogSize("large");
|
|
281635
281983
|
showDialog(/* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
|
|
@@ -281730,6 +282078,9 @@ function OperatorDashboard({
|
|
|
281730
282078
|
abortControllerRef.current.abort();
|
|
281731
282079
|
abortControllerRef.current = null;
|
|
281732
282080
|
commandCancelledRef.current = false;
|
|
282081
|
+
setQueuedMessages([]);
|
|
282082
|
+
queuedMessagesRef.current = [];
|
|
282083
|
+
setSelectedQueueIndex(-1);
|
|
281733
282084
|
setStatus("idle");
|
|
281734
282085
|
setThinking(false);
|
|
281735
282086
|
setIsExecuting(false);
|
|
@@ -281765,6 +282116,51 @@ function OperatorDashboard({
|
|
|
281765
282116
|
});
|
|
281766
282117
|
}, []);
|
|
281767
282118
|
useKeyboard((key) => {
|
|
282119
|
+
if (status === "running" && queuedMessages.length > 0 && !inputValue.trim()) {
|
|
282120
|
+
if (key.name === "up") {
|
|
282121
|
+
key.preventDefault?.();
|
|
282122
|
+
setSelectedQueueIndex((prev) => navigateUp(prev, queuedMessages.length));
|
|
282123
|
+
return;
|
|
282124
|
+
}
|
|
282125
|
+
if (selectedQueueIndex >= 0) {
|
|
282126
|
+
if (key.name === "down") {
|
|
282127
|
+
key.preventDefault?.();
|
|
282128
|
+
setSelectedQueueIndex((prev) => navigateDown(prev, queuedMessages.length));
|
|
282129
|
+
return;
|
|
282130
|
+
}
|
|
282131
|
+
if (key.name === "backspace" || key.name === "delete") {
|
|
282132
|
+
key.preventDefault?.();
|
|
282133
|
+
const removeIdx = selectedQueueIndex;
|
|
282134
|
+
setQueuedMessages((prev) => prev.filter((_3, i2) => i2 !== removeIdx));
|
|
282135
|
+
setSelectedQueueIndex(() => selectionAfterRemove(queuedMessages.length, removeIdx));
|
|
282136
|
+
return;
|
|
282137
|
+
}
|
|
282138
|
+
if (key.name === "return") {
|
|
282139
|
+
key.preventDefault?.();
|
|
282140
|
+
const msg = queuedMessages[selectedQueueIndex];
|
|
282141
|
+
const removeIdx = selectedQueueIndex;
|
|
282142
|
+
setQueuedMessages((prev) => prev.filter((_3, i2) => i2 !== removeIdx));
|
|
282143
|
+
setSelectedQueueIndex(-1);
|
|
282144
|
+
flushCommandOutput();
|
|
282145
|
+
if (cmdFlushTimerRef.current) {
|
|
282146
|
+
clearInterval(cmdFlushTimerRef.current);
|
|
282147
|
+
cmdFlushTimerRef.current = null;
|
|
282148
|
+
}
|
|
282149
|
+
setMessages((prev) => prev.map((m4) => isToolMessage(m4) && m4.status === "pending" ? { ...m4, status: "error", result: "Interrupted" } : m4));
|
|
282150
|
+
runAgentRef.current(msg);
|
|
282151
|
+
return;
|
|
282152
|
+
}
|
|
282153
|
+
if (key.raw === "e" || key.raw === "E") {
|
|
282154
|
+
key.preventDefault?.();
|
|
282155
|
+
const msg = queuedMessages[selectedQueueIndex];
|
|
282156
|
+
const removeIdx = selectedQueueIndex;
|
|
282157
|
+
setQueuedMessages((prev) => prev.filter((_3, i2) => i2 !== removeIdx));
|
|
282158
|
+
setInputValue(msg);
|
|
282159
|
+
setSelectedQueueIndex(-1);
|
|
282160
|
+
return;
|
|
282161
|
+
}
|
|
282162
|
+
}
|
|
282163
|
+
}
|
|
281768
282164
|
const dialogOpen = stack.length > 0 || externalDialogOpen;
|
|
281769
282165
|
const action = resolveKeyboardShortcut(key, status, inputValue, pendingApprovals.length > 0, dialogOpen);
|
|
281770
282166
|
switch (action.type) {
|
|
@@ -281916,12 +282312,16 @@ function OperatorDashboard({
|
|
|
281916
282312
|
pendingApprovals,
|
|
281917
282313
|
lastApprovedAction
|
|
281918
282314
|
}, undefined, false, undefined, this),
|
|
282315
|
+
/* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV(QueuedMessages, {
|
|
282316
|
+
messages: queuedMessages,
|
|
282317
|
+
selectedIndex: selectedQueueIndex
|
|
282318
|
+
}, undefined, false, undefined, this),
|
|
281919
282319
|
/* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV(InputArea, {
|
|
281920
282320
|
value: inputValue,
|
|
281921
282321
|
onChange: setInputValue,
|
|
281922
282322
|
onSubmit: handleSubmit,
|
|
281923
|
-
placeholder: status === "running" ? "
|
|
281924
|
-
focused: resolveInputFocused(status, stack.length, externalDialogOpen),
|
|
282323
|
+
placeholder: status === "running" ? "Queue a follow-up message..." : status === "waiting" ? "Type to redirect agent, or Y/A to approve..." : "Enter directive or / for commands & skills...",
|
|
282324
|
+
focused: status === "running" ? selectedQueueIndex < 0 : resolveInputFocused(status, stack.length, externalDialogOpen),
|
|
281925
282325
|
status: status === "waiting" ? "running" : status,
|
|
281926
282326
|
mode: "operator",
|
|
281927
282327
|
operatorMode: operatorState.mode,
|
|
@@ -281933,7 +282333,8 @@ function OperatorDashboard({
|
|
|
281933
282333
|
enableAutocomplete: true,
|
|
281934
282334
|
autocompleteOptions,
|
|
281935
282335
|
enableCommands: true,
|
|
281936
|
-
onCommandExecute: handleCommandExecute
|
|
282336
|
+
onCommandExecute: handleCommandExecute,
|
|
282337
|
+
disableHistoryNavigation: status === "running" && queuedMessages.length > 0
|
|
281937
282338
|
}, undefined, false, undefined, this)
|
|
281938
282339
|
]
|
|
281939
282340
|
}, undefined, true, undefined, this);
|