nfo-cli 0.0.4-improve-prompting → 0.0.5
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/claude-command.js +6 -1
- package/dist/claude-command.js.map +1 -1
- package/dist/claude-trust.js +46 -0
- package/dist/claude-trust.js.map +1 -0
- package/dist/cli.js +0 -0
- package/dist/mcp/handlers.js +5 -0
- package/dist/mcp/handlers.js.map +1 -1
- package/dist/mcp/tool-defs.js +10 -0
- package/dist/mcp/tool-defs.js.map +1 -1
- package/dist/musicians/dismiss.js +1 -1
- package/dist/musicians/dismiss.js.map +1 -1
- package/dist/musicians/roles.js +15 -0
- package/dist/musicians/roles.js.map +1 -0
- package/dist/musicians/spawn.js +53 -18
- package/dist/musicians/spawn.js.map +1 -1
- package/dist/permission.js +6 -0
- package/dist/permission.js.map +1 -1
- package/dist/prompts/musician-role.js +2 -1
- package/dist/prompts/musician-role.js.map +1 -1
- package/dist/prompts/orchestrator-role.js +18 -6
- package/dist/prompts/orchestrator-role.js.map +1 -1
- package/dist/prompts/tool-discipline.js +7 -3
- package/dist/prompts/tool-discipline.js.map +1 -1
- package/package.json +8 -1
- package/assets/agent-screen.png +0 -0
- package/assets/main-screen.png +0 -0
- package/assets/orche-clawd.png +0 -0
- package/dist/tui/App.js +0 -428
- package/dist/tui/App.js.map +0 -1
- package/dist/tui/AppView.js +0 -13
- package/dist/tui/AppView.js.map +0 -1
- package/dist/tui/Auditorium.js +0 -17
- package/dist/tui/Auditorium.js.map +0 -1
- package/dist/tui/ConcertHall.js +0 -11
- package/dist/tui/ConcertHall.js.map +0 -1
- package/dist/tui/Help.js +0 -49
- package/dist/tui/Help.js.map +0 -1
- package/dist/tui/OrchestratorPane.js +0 -34
- package/dist/tui/OrchestratorPane.js.map +0 -1
- package/dist/tui/SidebarHeader.js +0 -6
- package/dist/tui/SidebarHeader.js.map +0 -1
- package/dist/tui/StatusBar.js +0 -6
- package/dist/tui/StatusBar.js.map +0 -1
- package/docs/plans/2026-05-29-nfo-phase-1-bootstrap.md +0 -2152
- package/docs/plans/2026-05-29-nfo-phase-2-mcp-musicians.md +0 -2467
- package/docs/plans/2026-05-29-nfo-phase-3-ink-tui.md +0 -1611
- package/docs/plans/2026-05-29-nfo-phase-4-permission-prompts.md +0 -460
- package/docs/plans/2026-05-29-nfo-phase-5-help-and-notify.md +0 -933
- package/docs/specs/2026-05-29-nfo-design.md +0 -468
- package/plan-explorer-musician-hardening.md +0 -56
- package/src/claude-command.ts +0 -35
- package/src/claude-detect.ts +0 -42
- package/src/cli.ts +0 -197
- package/src/commands/attach.ts +0 -24
- package/src/commands/dashboard-window.ts +0 -33
- package/src/commands/kill.ts +0 -50
- package/src/commands/launch.ts +0 -134
- package/src/commands/list.ts +0 -43
- package/src/commands/mcp-server.ts +0 -18
- package/src/commands/notes.ts +0 -18
- package/src/commands/restore.ts +0 -153
- package/src/commands/tui.tsx +0 -22
- package/src/config.ts +0 -44
- package/src/dashboard.ts +0 -1
- package/src/mcp/config.ts +0 -39
- package/src/mcp/handlers.ts +0 -141
- package/src/mcp/server.ts +0 -50
- package/src/mcp/tool-defs.ts +0 -151
- package/src/musicians/dismiss.ts +0 -60
- package/src/musicians/ids.ts +0 -21
- package/src/musicians/lookup.ts +0 -13
- package/src/musicians/message-log.ts +0 -152
- package/src/musicians/message.ts +0 -99
- package/src/musicians/query.ts +0 -19
- package/src/musicians/spawn.ts +0 -139
- package/src/notes.ts +0 -39
- package/src/notify.ts +0 -62
- package/src/orchestrator/report-back.ts +0 -33
- package/src/permission.ts +0 -30
- package/src/project-key.ts +0 -12
- package/src/prompts/musician-role.ts +0 -22
- package/src/prompts/orchestrator-role.ts +0 -84
- package/src/prompts/tool-discipline.ts +0 -41
- package/src/repo.ts +0 -14
- package/src/shell-quote.ts +0 -7
- package/src/state-updaters.ts +0 -132
- package/src/state.ts +0 -49
- package/src/state.types.ts +0 -67
- package/src/tmux.ts +0 -226
- package/src/tui/activity-line.ts +0 -16
- package/src/tui/components/App.tsx +0 -534
- package/src/tui/components/AppView.tsx +0 -98
- package/src/tui/components/Auditorium.tsx +0 -56
- package/src/tui/components/ConcertHall.tsx +0 -31
- package/src/tui/components/Help.tsx +0 -63
- package/src/tui/components/OrchestratorPane.tsx +0 -98
- package/src/tui/components/SidebarHeader.tsx +0 -34
- package/src/tui/components/StatusBar.tsx +0 -42
- package/src/tui/detect-permission.ts +0 -93
- package/src/tui/embedded-session-lifecycle.ts +0 -44
- package/src/tui/embedded-terminal.ts +0 -325
- package/src/tui/format-time.ts +0 -25
- package/src/tui/keymap.ts +0 -104
- package/src/tui/poll-activity.ts +0 -25
- package/src/tui/poll-idle.ts +0 -149
- package/src/tui/poll-permission.ts +0 -50
- package/src/tui/status-icon.ts +0 -35
- package/src/tui/terminal-input.ts +0 -136
- package/src/tui/watch-state.ts +0 -43
- package/src/worktree.ts +0 -41
- package/tests/claude-command.test.ts +0 -30
- package/tests/claude-detect.test.ts +0 -14
- package/tests/commands/attach.test.ts +0 -60
- package/tests/commands/kill.test.ts +0 -66
- package/tests/commands/launch.test.ts +0 -75
- package/tests/commands/list.test.ts +0 -47
- package/tests/commands/notes.test.ts +0 -53
- package/tests/commands/restore.test.ts +0 -126
- package/tests/helpers/tmp-config.ts +0 -16
- package/tests/helpers/tmp-repo.ts +0 -29
- package/tests/integration/orchestrator-spawn.test.ts +0 -108
- package/tests/mcp/handlers.test.ts +0 -163
- package/tests/mcp/tool-defs.test.ts +0 -35
- package/tests/musicians/dismiss.test.ts +0 -102
- package/tests/musicians/message.test.ts +0 -159
- package/tests/musicians/query.test.ts +0 -65
- package/tests/musicians/spawn.test.ts +0 -125
- package/tests/notes.test.ts +0 -56
- package/tests/notify.test.ts +0 -80
- package/tests/orchestrator/report-back.test.ts +0 -18
- package/tests/permission.test.ts +0 -39
- package/tests/project-key.test.ts +0 -33
- package/tests/prompts/tool-discipline.test.ts +0 -25
- package/tests/repo.test.ts +0 -38
- package/tests/state-updaters.test.ts +0 -126
- package/tests/state.test.ts +0 -85
- package/tests/tmux.test.ts +0 -126
- package/tests/tui/AppView.test.tsx +0 -92
- package/tests/tui/Auditorium.test.tsx +0 -67
- package/tests/tui/ConcertHall.test.tsx +0 -22
- package/tests/tui/Help.test.tsx +0 -38
- package/tests/tui/OrchestratorPane.test.ts +0 -30
- package/tests/tui/SidebarHeader.test.tsx +0 -20
- package/tests/tui/StatusBar.test.tsx +0 -51
- package/tests/tui/activity-line.test.ts +0 -21
- package/tests/tui/detect-permission.test.ts +0 -92
- package/tests/tui/embedded-session-lifecycle.test.ts +0 -55
- package/tests/tui/embedded-terminal.test.ts +0 -80
- package/tests/tui/format-time.test.ts +0 -25
- package/tests/tui/keymap.test.ts +0 -93
- package/tests/tui/poll-activity.test.ts +0 -81
- package/tests/tui/poll-idle.test.ts +0 -159
- package/tests/tui/poll-permission.test.ts +0 -222
- package/tests/tui/status-icon.test.ts +0 -27
- package/tests/tui/terminal-input.test.ts +0 -113
- package/tests/tui/watch-state.test.ts +0 -54
- package/tests/worktree.test.ts +0 -73
- package/tsconfig.json +0 -19
- package/vitest.config.ts +0 -12
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import type { ReactElement } from "react";
|
|
2
|
-
import { Box, Text } from "ink";
|
|
3
|
-
import type { OrchestraSummary } from "../../commands/list.js";
|
|
4
|
-
|
|
5
|
-
export interface ConcertHallProps {
|
|
6
|
-
orchestras: OrchestraSummary[];
|
|
7
|
-
currentId: string;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export function ConcertHall(props: ConcertHallProps): ReactElement {
|
|
11
|
-
return (
|
|
12
|
-
<Box
|
|
13
|
-
flexDirection="column"
|
|
14
|
-
borderStyle="single"
|
|
15
|
-
borderBottom={true}
|
|
16
|
-
paddingX={1}
|
|
17
|
-
>
|
|
18
|
-
<Text bold={true}>Concert Hall</Text>
|
|
19
|
-
{props.orchestras.map((o) => {
|
|
20
|
-
const current = o.id === props.currentId;
|
|
21
|
-
const marker = current ? "▸" : " ";
|
|
22
|
-
const dot = o.running ? "●" : "○";
|
|
23
|
-
return (
|
|
24
|
-
<Text key={o.id} bold={current}>
|
|
25
|
-
{marker} {dot} {o.id} ({o.musician_count})
|
|
26
|
-
</Text>
|
|
27
|
-
);
|
|
28
|
-
})}
|
|
29
|
-
</Box>
|
|
30
|
-
);
|
|
31
|
-
}
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import type { ReactElement } from "react";
|
|
2
|
-
import { Box, Text } from "ink";
|
|
3
|
-
|
|
4
|
-
interface Row {
|
|
5
|
-
key: string;
|
|
6
|
-
label: string;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
const ROWS: Row[] = [
|
|
10
|
-
{ key: "↑ / k", label: "move selection up" },
|
|
11
|
-
{ key: "↓ / j", label: "move selection down" },
|
|
12
|
-
{ key: "Enter", label: "open the selected target in the left pane" },
|
|
13
|
-
{ key: "n", label: "open notes for this orchestra" },
|
|
14
|
-
{
|
|
15
|
-
key: "d",
|
|
16
|
-
label:
|
|
17
|
-
"arm dismiss for selected Musician (press d again / y / Enter to confirm)",
|
|
18
|
-
},
|
|
19
|
-
{ key: "n / Esc", label: "cancel pending dismiss confirmation" },
|
|
20
|
-
{ key: "p", label: "jump to next Musician awaiting permission" },
|
|
21
|
-
{
|
|
22
|
-
key: "q",
|
|
23
|
-
label:
|
|
24
|
-
"detach this tmux client from NFO without killing the orchestra session",
|
|
25
|
-
},
|
|
26
|
-
{
|
|
27
|
-
key: "Ctrl+g",
|
|
28
|
-
label: "switch focus between the sidebar and the embedded Claude terminal",
|
|
29
|
-
},
|
|
30
|
-
{
|
|
31
|
-
key: "typed keys",
|
|
32
|
-
label:
|
|
33
|
-
"go directly to the currently open tmux terminal while the left pane is focused",
|
|
34
|
-
},
|
|
35
|
-
{
|
|
36
|
-
key: "Alt+Enter / Shift+Enter / Ctrl+J",
|
|
37
|
-
label:
|
|
38
|
-
"insert a newline in the focused terminal without treating it like Enter",
|
|
39
|
-
},
|
|
40
|
-
{
|
|
41
|
-
key: "Mouse wheel",
|
|
42
|
-
label:
|
|
43
|
-
"scroll the left terminal through local scrollback when the pointer is over that pane",
|
|
44
|
-
},
|
|
45
|
-
{ key: "?", label: "toggle this help / close" },
|
|
46
|
-
];
|
|
47
|
-
|
|
48
|
-
export function Help(): ReactElement {
|
|
49
|
-
return (
|
|
50
|
-
<Box flexDirection="column" paddingX={1}>
|
|
51
|
-
<Text bold={true}>Keybindings</Text>
|
|
52
|
-
{ROWS.map((row) => {
|
|
53
|
-
return (
|
|
54
|
-
<Text key={row.key}>
|
|
55
|
-
<Text color="cyan">{row.key.padEnd(8)}</Text>
|
|
56
|
-
<Text> {row.label}</Text>
|
|
57
|
-
</Text>
|
|
58
|
-
);
|
|
59
|
-
})}
|
|
60
|
-
<Text dimColor={true}>Press ? to close.</Text>
|
|
61
|
-
</Box>
|
|
62
|
-
);
|
|
63
|
-
}
|
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
import type { ReactElement } from "react";
|
|
2
|
-
import { Box, Text } from "ink";
|
|
3
|
-
import type {
|
|
4
|
-
EmbeddedTerminalLine,
|
|
5
|
-
EmbeddedTerminalSpan,
|
|
6
|
-
} from "../embedded-terminal.js";
|
|
7
|
-
|
|
8
|
-
export interface OrchestratorPaneProps {
|
|
9
|
-
title: string;
|
|
10
|
-
lines: EmbeddedTerminalLine[];
|
|
11
|
-
focused: boolean;
|
|
12
|
-
connected: boolean;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export function resolveSpanStyle(
|
|
16
|
-
span: EmbeddedTerminalSpan,
|
|
17
|
-
focused: boolean,
|
|
18
|
-
): Omit<EmbeddedTerminalSpan, "text" | "cursor"> {
|
|
19
|
-
const style: Omit<EmbeddedTerminalSpan, "text" | "cursor"> = {
|
|
20
|
-
color: span.color,
|
|
21
|
-
backgroundColor: span.backgroundColor,
|
|
22
|
-
dimColor: span.dimColor,
|
|
23
|
-
bold: span.bold,
|
|
24
|
-
italic: span.italic,
|
|
25
|
-
underline: span.underline,
|
|
26
|
-
strikethrough: span.strikethrough,
|
|
27
|
-
inverse: span.inverse,
|
|
28
|
-
};
|
|
29
|
-
if (span.cursor === true && focused) {
|
|
30
|
-
style.color = "black";
|
|
31
|
-
style.backgroundColor = "white";
|
|
32
|
-
style.inverse = false;
|
|
33
|
-
}
|
|
34
|
-
return style;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function renderSpan(
|
|
38
|
-
span: EmbeddedTerminalSpan,
|
|
39
|
-
index: number,
|
|
40
|
-
focused: boolean,
|
|
41
|
-
): ReactElement {
|
|
42
|
-
const style = resolveSpanStyle(span, focused);
|
|
43
|
-
return (
|
|
44
|
-
<Text
|
|
45
|
-
key={`${index}:${span.text}`}
|
|
46
|
-
color={style.color}
|
|
47
|
-
backgroundColor={style.backgroundColor}
|
|
48
|
-
dimColor={style.dimColor}
|
|
49
|
-
bold={style.bold}
|
|
50
|
-
italic={style.italic}
|
|
51
|
-
underline={style.underline}
|
|
52
|
-
strikethrough={style.strikethrough}
|
|
53
|
-
inverse={style.inverse}
|
|
54
|
-
>
|
|
55
|
-
{span.text}
|
|
56
|
-
</Text>
|
|
57
|
-
);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
export function OrchestratorPane(props: OrchestratorPaneProps): ReactElement {
|
|
61
|
-
return (
|
|
62
|
-
<Box
|
|
63
|
-
flexGrow={1}
|
|
64
|
-
flexDirection="column"
|
|
65
|
-
borderStyle="single"
|
|
66
|
-
marginRight={1}
|
|
67
|
-
minHeight={16}
|
|
68
|
-
paddingX={1}
|
|
69
|
-
>
|
|
70
|
-
<Text bold={true}>{props.title}</Text>
|
|
71
|
-
<Box flexDirection="column" flexGrow={1}>
|
|
72
|
-
{props.lines.length > 0 ? (
|
|
73
|
-
props.lines.map((line, index) => {
|
|
74
|
-
return (
|
|
75
|
-
<Text key={String(index)} wrap="truncate-end">
|
|
76
|
-
{line.spans.length > 0
|
|
77
|
-
? line.spans.map((span, spanIndex) =>
|
|
78
|
-
renderSpan(span, spanIndex, props.focused),
|
|
79
|
-
)
|
|
80
|
-
: " "}
|
|
81
|
-
</Text>
|
|
82
|
-
);
|
|
83
|
-
})
|
|
84
|
-
) : (
|
|
85
|
-
<Text dimColor={true}>Waiting for Claude terminal…</Text>
|
|
86
|
-
)}
|
|
87
|
-
</Box>
|
|
88
|
-
<Text color={props.focused ? "cyan" : "gray"} wrap="truncate-end">
|
|
89
|
-
{props.focused
|
|
90
|
-
? "[Ctrl+g] sidebar · [Mouse wheel] scroll"
|
|
91
|
-
: "[Ctrl+g] focus left terminal"}
|
|
92
|
-
</Text>
|
|
93
|
-
{!props.connected ? (
|
|
94
|
-
<Text color="yellow">Embedded tmux client disconnected.</Text>
|
|
95
|
-
) : null}
|
|
96
|
-
</Box>
|
|
97
|
-
);
|
|
98
|
-
}
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import type { ReactElement } from "react";
|
|
2
|
-
import { Box, Text } from "ink";
|
|
3
|
-
|
|
4
|
-
export interface SidebarHeaderProps {
|
|
5
|
-
orchestraId: string;
|
|
6
|
-
musicianCount: number;
|
|
7
|
-
pendingCount: number;
|
|
8
|
-
version: string;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export function SidebarHeader(props: SidebarHeaderProps): ReactElement {
|
|
12
|
-
return (
|
|
13
|
-
<Box
|
|
14
|
-
flexDirection="column"
|
|
15
|
-
borderStyle="round"
|
|
16
|
-
borderBottom={true}
|
|
17
|
-
paddingX={1}
|
|
18
|
-
>
|
|
19
|
-
<Text bold={true}>No Fluff Orchestra · {props.orchestraId}</Text>
|
|
20
|
-
<Text bold={true}>v.{props.version}</Text>
|
|
21
|
-
{props.pendingCount > 0 ? (
|
|
22
|
-
<Text color="yellow">
|
|
23
|
-
{props.musicianCount} musicians · {props.pendingCount} awaiting
|
|
24
|
-
permission
|
|
25
|
-
</Text>
|
|
26
|
-
) : (
|
|
27
|
-
<Text dimColor={true}>
|
|
28
|
-
{props.musicianCount} musicians · {props.pendingCount} awaiting
|
|
29
|
-
permission
|
|
30
|
-
</Text>
|
|
31
|
-
)}
|
|
32
|
-
</Box>
|
|
33
|
-
);
|
|
34
|
-
}
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import type { ReactElement } from "react";
|
|
2
|
-
import { Box, Text } from "ink";
|
|
3
|
-
|
|
4
|
-
export interface StatusBarProps {
|
|
5
|
-
permissionLevel: string;
|
|
6
|
-
tokenHint: string;
|
|
7
|
-
pendingCount: number;
|
|
8
|
-
dismissConfirmation?: string | null;
|
|
9
|
-
orchestratorFocused: boolean;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export function StatusBar(props: StatusBarProps): ReactElement {
|
|
13
|
-
return (
|
|
14
|
-
<Box
|
|
15
|
-
flexDirection="column"
|
|
16
|
-
borderStyle="single"
|
|
17
|
-
borderTop={true}
|
|
18
|
-
paddingX={1}
|
|
19
|
-
>
|
|
20
|
-
{props.pendingCount > 0 ? (
|
|
21
|
-
<Text color="yellow">
|
|
22
|
-
⚠ {props.pendingCount} awaiting permission · [p] jump to next
|
|
23
|
-
</Text>
|
|
24
|
-
) : null}
|
|
25
|
-
{props.dismissConfirmation ? (
|
|
26
|
-
<Text color="red">{props.dismissConfirmation}</Text>
|
|
27
|
-
) : null}
|
|
28
|
-
<Text>
|
|
29
|
-
{props.permissionLevel} · {props.tokenHint}
|
|
30
|
-
</Text>
|
|
31
|
-
{props.orchestratorFocused ? (
|
|
32
|
-
<Text dimColor={true}>[type] active terminal [Ctrl+g] sidebar</Text>
|
|
33
|
-
) : (
|
|
34
|
-
<Text dimColor={true}>
|
|
35
|
-
[↑↓] nav [⏎] open left pane [d] dismiss [p] pending [n] notes [Ctrl+g]
|
|
36
|
-
terminal
|
|
37
|
-
</Text>
|
|
38
|
-
)}
|
|
39
|
-
<Text dimColor={true}>[q] detach [?] help</Text>
|
|
40
|
-
</Box>
|
|
41
|
-
);
|
|
42
|
-
}
|
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
const LAST_N_LINES = 20;
|
|
2
|
-
const MAX_TOOL_LEN = 60;
|
|
3
|
-
|
|
4
|
-
const INTRO_PATTERNS: RegExp[] = [
|
|
5
|
-
/allow\s+\S+/i,
|
|
6
|
-
/do you want to/i,
|
|
7
|
-
/permission required/i,
|
|
8
|
-
/use this tool/i,
|
|
9
|
-
];
|
|
10
|
-
|
|
11
|
-
export interface PermissionDetection {
|
|
12
|
-
pending: boolean;
|
|
13
|
-
tool: string | null;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
function hasYesLine(lines: string[]): boolean {
|
|
17
|
-
for (const line of lines) {
|
|
18
|
-
const trimmed = line.trimStart();
|
|
19
|
-
if (trimmed.startsWith('1.') || trimmed.startsWith('1)')) {
|
|
20
|
-
return true;
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
return false;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function hasNoLine(lines: string[]): boolean {
|
|
27
|
-
for (const line of lines) {
|
|
28
|
-
const trimmed = line.trimStart();
|
|
29
|
-
const startsWithSmallDigit =
|
|
30
|
-
trimmed.startsWith('2.') ||
|
|
31
|
-
trimmed.startsWith('2)') ||
|
|
32
|
-
trimmed.startsWith('3.') ||
|
|
33
|
-
trimmed.startsWith('3)');
|
|
34
|
-
if (startsWithSmallDigit && trimmed.includes('No')) {
|
|
35
|
-
return true;
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
return false;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function hasIntroPattern(lines: string[]): boolean {
|
|
42
|
-
const block = lines.join('\n');
|
|
43
|
-
for (const pattern of INTRO_PATTERNS) {
|
|
44
|
-
if (pattern.test(block)) {
|
|
45
|
-
return true;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
return false;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
function extractTool(lines: string[]): string | null {
|
|
52
|
-
try {
|
|
53
|
-
const block = lines.join('\n');
|
|
54
|
-
const nameMatch = /^Allow ([A-Z][A-Za-z]+)/m.exec(block);
|
|
55
|
-
if (!nameMatch) {
|
|
56
|
-
return null;
|
|
57
|
-
}
|
|
58
|
-
const toolName = nameMatch[1];
|
|
59
|
-
// Find the full line that contains the Allow … match, to search for backticks.
|
|
60
|
-
const matchIndex = nameMatch.index;
|
|
61
|
-
const lineStart = matchIndex;
|
|
62
|
-
const lineEnd = block.indexOf('\n', matchIndex);
|
|
63
|
-
const fullLine = lineEnd === -1 ? block.slice(lineStart) : block.slice(lineStart, lineEnd);
|
|
64
|
-
const backtickMatch = /`([^`]*)`/.exec(fullLine);
|
|
65
|
-
let result: string;
|
|
66
|
-
if (backtickMatch) {
|
|
67
|
-
result = `${toolName}: \`${backtickMatch[1]}\``;
|
|
68
|
-
} else {
|
|
69
|
-
result = toolName;
|
|
70
|
-
}
|
|
71
|
-
if (result.length > MAX_TOOL_LEN) {
|
|
72
|
-
return result.slice(0, MAX_TOOL_LEN - 1) + '…';
|
|
73
|
-
}
|
|
74
|
-
return result;
|
|
75
|
-
} catch {
|
|
76
|
-
return null;
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
export function detectPermissionPrompt(paneText: string): PermissionDetection {
|
|
81
|
-
const allLines = paneText.split('\n');
|
|
82
|
-
const nonEmpty = allLines.filter((line) => { return line.trim().length > 0; });
|
|
83
|
-
const lines = nonEmpty.slice(-LAST_N_LINES);
|
|
84
|
-
|
|
85
|
-
const pending = hasYesLine(lines) && hasNoLine(lines) && hasIntroPattern(lines);
|
|
86
|
-
|
|
87
|
-
if (!pending) {
|
|
88
|
-
return { pending: false, tool: null };
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
const tool = extractTool(lines);
|
|
92
|
-
return { pending: true, tool };
|
|
93
|
-
}
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
export interface EmbeddedSessionLease {
|
|
2
|
-
readonly sessionName: string;
|
|
3
|
-
readonly token: number;
|
|
4
|
-
}
|
|
5
|
-
|
|
6
|
-
const embeddedSessionTokens = new Map<string, number>();
|
|
7
|
-
const embeddedSessionQueues = new Map<string, Promise<void>>();
|
|
8
|
-
|
|
9
|
-
export function claimEmbeddedSessionLease(
|
|
10
|
-
sessionName: string,
|
|
11
|
-
): EmbeddedSessionLease {
|
|
12
|
-
const token = (embeddedSessionTokens.get(sessionName) ?? 0) + 1;
|
|
13
|
-
embeddedSessionTokens.set(sessionName, token);
|
|
14
|
-
return { sessionName, token };
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export function embeddedSessionLeaseIsCurrent(
|
|
18
|
-
lease: EmbeddedSessionLease,
|
|
19
|
-
): boolean {
|
|
20
|
-
return embeddedSessionTokens.get(lease.sessionName) === lease.token;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export async function runEmbeddedSessionOperation<T>(
|
|
24
|
-
sessionName: string,
|
|
25
|
-
operation: () => Promise<T>,
|
|
26
|
-
): Promise<T> {
|
|
27
|
-
const previous = embeddedSessionQueues.get(sessionName) ?? Promise.resolve();
|
|
28
|
-
let releaseQueue: (() => void) | undefined;
|
|
29
|
-
const current = new Promise<void>((resolve) => {
|
|
30
|
-
releaseQueue = resolve;
|
|
31
|
-
});
|
|
32
|
-
embeddedSessionQueues.set(sessionName, current);
|
|
33
|
-
|
|
34
|
-
await previous;
|
|
35
|
-
|
|
36
|
-
try {
|
|
37
|
-
return await operation();
|
|
38
|
-
} finally {
|
|
39
|
-
releaseQueue?.();
|
|
40
|
-
if (embeddedSessionQueues.get(sessionName) === current) {
|
|
41
|
-
embeddedSessionQueues.delete(sessionName);
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
}
|