@geteigen/cli 0.0.0-stage → 3.1.0
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/README.md +154 -2
- package/dist/agent.d.ts +32 -0
- package/dist/agent.js +246 -0
- package/dist/auth.d.ts +5 -0
- package/dist/auth.js +33 -0
- package/dist/cli/src/agent.d.ts +24 -0
- package/dist/cli/src/agent.js +263 -0
- package/dist/cli/src/agentTools.d.ts +6 -0
- package/dist/cli/src/agentTools.js +104 -0
- package/dist/cli/src/auth.d.ts +5 -0
- package/dist/cli/src/auth.js +33 -0
- package/dist/cli/src/cliEnv.d.ts +6 -0
- package/dist/cli/src/cliEnv.js +26 -0
- package/dist/cli/src/config.d.ts +11 -0
- package/dist/cli/src/config.js +51 -0
- package/dist/cli/src/cursorAuth.d.ts +10 -0
- package/dist/cli/src/cursorAuth.js +92 -0
- package/dist/cli/src/gitAuthEnv.d.ts +5 -0
- package/dist/cli/src/gitAuthEnv.js +35 -0
- package/dist/cli/src/index.d.ts +2 -0
- package/dist/cli/src/index.js +244 -0
- package/dist/cli/src/listModels.d.ts +14 -0
- package/dist/cli/src/listModels.js +196 -0
- package/dist/cli/src/listSessions.d.ts +9 -0
- package/dist/cli/src/listSessions.js +37 -0
- package/dist/cli/src/machineConfig.d.ts +20 -0
- package/dist/cli/src/machineConfig.js +66 -0
- package/dist/cli/src/pickFolder.d.ts +5 -0
- package/dist/cli/src/pickFolder.js +65 -0
- package/dist/cli/src/proc.d.ts +31 -0
- package/dist/cli/src/proc.js +97 -0
- package/dist/cli/src/service.d.ts +17 -0
- package/dist/cli/src/service.js +255 -0
- package/dist/cli/src/workRoots.d.ts +30 -0
- package/dist/cli/src/workRoots.js +228 -0
- package/dist/cli/src/worker.d.ts +8 -0
- package/dist/cli/src/worker.js +51 -0
- package/dist/cli/src/workerCore.d.ts +47 -0
- package/dist/cli/src/workerCore.js +721 -0
- package/dist/cli/src/workspace.d.ts +10 -0
- package/dist/cli/src/workspace.js +329 -0
- package/dist/config.d.ts +11 -0
- package/dist/config.js +51 -0
- package/dist/cursorAuth.d.ts +10 -0
- package/dist/cursorAuth.js +95 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +108 -0
- package/dist/listModels.d.ts +6 -0
- package/dist/listModels.js +62 -0
- package/dist/listSessions.d.ts +9 -0
- package/dist/listSessions.js +37 -0
- package/dist/pickFolder.d.ts +5 -0
- package/dist/pickFolder.js +65 -0
- package/dist/shared/actionDrafts.d.ts +83 -0
- package/dist/shared/actionDrafts.js +169 -0
- package/dist/shared/agentArtifacts.d.ts +49 -0
- package/dist/shared/agentArtifacts.js +169 -0
- package/dist/shared/agentLabel.d.ts +18 -0
- package/dist/shared/agentLabel.js +22 -0
- package/dist/shared/agentMentions.d.ts +38 -0
- package/dist/shared/agentMentions.js +110 -0
- package/dist/shared/agentRepos.d.ts +28 -0
- package/dist/shared/agentRepos.js +60 -0
- package/dist/shared/agentRunStatus.d.ts +12 -0
- package/dist/shared/agentRunStatus.js +17 -0
- package/dist/shared/agentUsage.d.ts +24 -0
- package/dist/shared/agentUsage.js +139 -0
- package/dist/shared/approvals.d.ts +44 -0
- package/dist/shared/approvals.js +97 -0
- package/dist/shared/askEigen.d.ts +190 -0
- package/dist/shared/askEigen.js +76 -0
- package/dist/shared/attribution.d.ts +25 -0
- package/dist/shared/attribution.js +63 -0
- package/dist/shared/automations.d.ts +135 -0
- package/dist/shared/automations.js +553 -0
- package/dist/shared/backends/claudeCode.d.ts +44 -0
- package/dist/shared/backends/claudeCode.js +548 -0
- package/dist/shared/backends/codex.d.ts +15 -0
- package/dist/shared/backends/codex.js +287 -0
- package/dist/shared/backends/cursor.d.ts +111 -0
- package/dist/shared/backends/cursor.js +1216 -0
- package/dist/shared/backends/index.d.ts +18 -0
- package/dist/shared/backends/index.js +58 -0
- package/dist/shared/backends/opencode.d.ts +43 -0
- package/dist/shared/backends/opencode.js +415 -0
- package/dist/shared/backends/types.d.ts +171 -0
- package/dist/shared/backends/types.js +19 -0
- package/dist/shared/backends/usage.d.ts +8 -0
- package/dist/shared/backends/usage.js +71 -0
- package/dist/shared/billingPlans.d.ts +60 -0
- package/dist/shared/billingPlans.js +262 -0
- package/dist/shared/brain.d.ts +353 -0
- package/dist/shared/brain.js +487 -0
- package/dist/shared/clarifyingQuestions.d.ts +13 -0
- package/dist/shared/clarifyingQuestions.js +207 -0
- package/dist/shared/claudeModels.d.ts +10 -0
- package/dist/shared/claudeModels.js +52 -0
- package/dist/shared/cloudBackends.d.ts +21 -0
- package/dist/shared/cloudBackends.js +29 -0
- package/dist/shared/codexModels.d.ts +10 -0
- package/dist/shared/codexModels.js +28 -0
- package/dist/shared/contextWindows.d.ts +2 -0
- package/dist/shared/contextWindows.js +30 -0
- package/dist/shared/cursorMcpConfig.d.ts +14 -0
- package/dist/shared/cursorMcpConfig.js +43 -0
- package/dist/shared/cursorProjectMcp.d.ts +10 -0
- package/dist/shared/cursorProjectMcp.js +42 -0
- package/dist/shared/docs.d.ts +59 -0
- package/dist/shared/docs.js +87 -0
- package/dist/shared/events.d.ts +932 -0
- package/dist/shared/events.js +57 -0
- package/dist/shared/integrations.d.ts +266 -0
- package/dist/shared/integrations.js +449 -0
- package/dist/shared/issueTranscript.d.ts +64 -0
- package/dist/shared/issueTranscript.js +387 -0
- package/dist/shared/issues.d.ts +139 -0
- package/dist/shared/issues.js +217 -0
- package/dist/shared/machines.d.ts +119 -0
- package/dist/shared/machines.js +58 -0
- package/dist/shared/markdownMedia.d.ts +13 -0
- package/dist/shared/markdownMedia.js +41 -0
- package/dist/shared/notificationText.d.ts +6 -0
- package/dist/shared/notificationText.js +38 -0
- package/dist/shared/opencodeModels.d.ts +19 -0
- package/dist/shared/opencodeModels.js +62 -0
- package/dist/shared/opencodeSession.d.ts +7 -0
- package/dist/shared/opencodeSession.js +30 -0
- package/dist/shared/orchestration.d.ts +58 -0
- package/dist/shared/orchestration.js +12 -0
- package/dist/shared/orgs.d.ts +47 -0
- package/dist/shared/orgs.js +49 -0
- package/dist/shared/personalOrg.d.ts +5 -0
- package/dist/shared/personalOrg.js +12 -0
- package/dist/shared/plans.d.ts +3 -0
- package/dist/shared/plans.js +19 -0
- package/dist/shared/providerModels.d.ts +40 -0
- package/dist/shared/providerModels.js +503 -0
- package/dist/shared/roomAttention.d.ts +17 -0
- package/dist/shared/roomAttention.js +36 -0
- package/dist/shared/roomContext.d.ts +120 -0
- package/dist/shared/roomContext.js +133 -0
- package/dist/shared/roomPermissions.d.ts +56 -0
- package/dist/shared/roomPermissions.js +133 -0
- package/dist/shared/sessionSeats.d.ts +29 -0
- package/dist/shared/sessionSeats.js +57 -0
- package/dist/shared/slashCommands.d.ts +39 -0
- package/dist/shared/slashCommands.js +166 -0
- package/dist/shared/space.d.ts +33 -0
- package/dist/shared/space.js +54 -0
- package/dist/shared/spaceFeed.d.ts +78 -0
- package/dist/shared/spaceFeed.js +194 -0
- package/dist/shared/stringifyUnknown.d.ts +1 -0
- package/dist/shared/stringifyUnknown.js +1 -0
- package/dist/shared/swarm.d.ts +320 -0
- package/dist/shared/swarm.js +328 -0
- package/dist/shared/swarmVisual.d.ts +79 -0
- package/dist/shared/swarmVisual.js +212 -0
- package/dist/shared/terminalFilter.d.ts +13 -0
- package/dist/shared/terminalFilter.js +46 -0
- package/dist/shared/toolIntegration.d.ts +56 -0
- package/dist/shared/toolIntegration.js +180 -0
- package/dist/shared/typing.d.ts +9 -0
- package/dist/shared/typing.js +37 -0
- package/dist/shared/uploads.d.ts +31 -0
- package/dist/shared/uploads.js +65 -0
- package/dist/shared/voiceApprovals.d.ts +36 -0
- package/dist/shared/voiceApprovals.js +229 -0
- package/dist/shared/workspace.d.ts +49 -0
- package/dist/shared/workspace.js +53 -0
- package/dist/worker.d.ts +1 -0
- package/dist/worker.js +266 -0
- package/package.json +51 -4
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which integration an MCP tool call went through. Resolved where the agent
|
|
3
|
+
* event is parsed (tool args are available there), then stored on the chat
|
|
4
|
+
* message so the UI never has to guess from display text.
|
|
5
|
+
*/
|
|
6
|
+
export interface ToolIntegrationRef {
|
|
7
|
+
/** MCP server the call went through, e.g. `steer_hub` or a user's own server. */
|
|
8
|
+
server: string;
|
|
9
|
+
/**
|
|
10
|
+
* Integration id for the logo/label: the hub provider (`gmail`, `slack`, …),
|
|
11
|
+
* `steer` for hub meta-tools with no provider, or the server name for
|
|
12
|
+
* non-hub MCP servers.
|
|
13
|
+
*/
|
|
14
|
+
provider: string;
|
|
15
|
+
/** Tool within the provider, e.g. `send_email`. */
|
|
16
|
+
tool: string;
|
|
17
|
+
}
|
|
18
|
+
/** Provider id used for Integrations Hub meta-tools that name no app. */
|
|
19
|
+
export declare const STEER_HUB_PROVIDER = "steer";
|
|
20
|
+
export declare const HUB_SEARCH_TOOLS = "search_tools";
|
|
21
|
+
export declare const HUB_CALL_TOOL = "call_tool";
|
|
22
|
+
/** Hub server name, including the pre-rename one and client-added prefixes. */
|
|
23
|
+
export declare function isSteerHubServer(server: string): boolean;
|
|
24
|
+
/** Resolve the integration behind one MCP call on `server` → `tool(args)`. */
|
|
25
|
+
export declare function resolveMcpToolRef(serverRaw: string, toolRaw: string, argsRaw?: unknown): ToolIntegrationRef | undefined;
|
|
26
|
+
/** Claude Code names MCP tools `mcp__<server>__<tool>`. */
|
|
27
|
+
export declare function parseClaudeMcpToolName(name: string): {
|
|
28
|
+
server: string;
|
|
29
|
+
tool: string;
|
|
30
|
+
} | null;
|
|
31
|
+
/** Claude Code tool_use → integration, or undefined for built-in tools. */
|
|
32
|
+
export declare function claudeToolIntegration(name: string, input?: unknown): ToolIntegrationRef | undefined;
|
|
33
|
+
/** Cursor `mcpToolCall` args: `{ providerIdentifier, toolName, args }`. */
|
|
34
|
+
export declare function cursorToolIntegration(name: string, args?: Record<string, unknown>): ToolIntegrationRef | undefined;
|
|
35
|
+
/** MCP server name for Eigen's room/run tools (`steer_upload_file`, orchestration, memory). */
|
|
36
|
+
export declare const ROOM_MCP_SERVER_NAME = "steer_room";
|
|
37
|
+
/**
|
|
38
|
+
* OpenCode names MCP tools `<server>_<tool>` (and may also expose the bare
|
|
39
|
+
* tool name when it is unique). Older builds used Claude's `mcp__` form.
|
|
40
|
+
*/
|
|
41
|
+
export declare function parseOpencodeMcpToolName(name: string): {
|
|
42
|
+
server: string;
|
|
43
|
+
tool: string;
|
|
44
|
+
} | null;
|
|
45
|
+
/** OpenCode MCP tool_use → integration, or undefined for built-in tools. */
|
|
46
|
+
export declare function opencodeToolIntegration(name: string, input?: Record<string, unknown>): ToolIntegrationRef | undefined;
|
|
47
|
+
/** Codex `mcp_tool_call` item: `{ server, tool, arguments }`. */
|
|
48
|
+
export declare function codexToolIntegration(item: Record<string, unknown>): ToolIntegrationRef | undefined;
|
|
49
|
+
/** Stored `messages.integration_json` → ref (ignores malformed rows). */
|
|
50
|
+
export declare function parseToolIntegrationJson(raw: unknown): ToolIntegrationRef | undefined;
|
|
51
|
+
/**
|
|
52
|
+
* Cursor CLI stream-json `tool_call` line → integration. Applied in
|
|
53
|
+
* backends/index.ts (cursor.ts is bundled into the web app and must not
|
|
54
|
+
* import runtime modules).
|
|
55
|
+
*/
|
|
56
|
+
export declare function cursorStreamLineIntegration(json: unknown): ToolIntegrationRef | undefined;
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/** Provider id used for Integrations Hub meta-tools that name no app. */
|
|
2
|
+
export const STEER_HUB_PROVIDER = "steer";
|
|
3
|
+
export const HUB_SEARCH_TOOLS = "search_tools";
|
|
4
|
+
export const HUB_CALL_TOOL = "call_tool";
|
|
5
|
+
/** Hub server name, including the pre-rename one and client-added prefixes. */
|
|
6
|
+
export function isSteerHubServer(server) {
|
|
7
|
+
return /steer[_-](hub|integrations)/i.test(server);
|
|
8
|
+
}
|
|
9
|
+
function asRecord(value) {
|
|
10
|
+
if (typeof value === "string") {
|
|
11
|
+
try {
|
|
12
|
+
value = JSON.parse(value);
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return undefined;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return value && typeof value === "object" && !Array.isArray(value)
|
|
19
|
+
? value
|
|
20
|
+
: undefined;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* `<provider>__<tool>` → parts. Mirrors splitBundledToolName in
|
|
24
|
+
* integrations.ts; kept local because the web bundle imports this file and
|
|
25
|
+
* Turbopack can't follow runtime `.js` → `.ts` imports.
|
|
26
|
+
*/
|
|
27
|
+
function splitBundledToolName(name) {
|
|
28
|
+
const i = name.indexOf("__");
|
|
29
|
+
if (i <= 0)
|
|
30
|
+
return null;
|
|
31
|
+
return { provider: name.slice(0, i), tool: name.slice(i + 2) };
|
|
32
|
+
}
|
|
33
|
+
function str(value) {
|
|
34
|
+
return typeof value === "string" ? value.trim() : "";
|
|
35
|
+
}
|
|
36
|
+
/** Resolve the integration behind one MCP call on `server` → `tool(args)`. */
|
|
37
|
+
export function resolveMcpToolRef(serverRaw, toolRaw, argsRaw) {
|
|
38
|
+
const server = serverRaw.trim();
|
|
39
|
+
const tool = toolRaw.trim();
|
|
40
|
+
if (!server || !tool)
|
|
41
|
+
return undefined;
|
|
42
|
+
if (!isSteerHubServer(server)) {
|
|
43
|
+
// Eigen's own servers (room memory, swarm board) are Steer, not an app.
|
|
44
|
+
const provider = /^steer[_-]/i.test(server) ? STEER_HUB_PROVIDER : server.toLowerCase();
|
|
45
|
+
return { server, provider, tool };
|
|
46
|
+
}
|
|
47
|
+
const args = asRecord(argsRaw);
|
|
48
|
+
if (tool === HUB_CALL_TOOL) {
|
|
49
|
+
const split = splitBundledToolName(str(args?.name));
|
|
50
|
+
return split
|
|
51
|
+
? { server, provider: split.provider.toLowerCase(), tool: split.tool }
|
|
52
|
+
: { server, provider: STEER_HUB_PROVIDER, tool };
|
|
53
|
+
}
|
|
54
|
+
if (tool === HUB_SEARCH_TOOLS) {
|
|
55
|
+
return { server, provider: str(args?.provider).toLowerCase() || STEER_HUB_PROVIDER, tool };
|
|
56
|
+
}
|
|
57
|
+
const split = splitBundledToolName(tool);
|
|
58
|
+
return split
|
|
59
|
+
? { server, provider: split.provider.toLowerCase(), tool: split.tool }
|
|
60
|
+
: { server, provider: STEER_HUB_PROVIDER, tool };
|
|
61
|
+
}
|
|
62
|
+
/** Claude Code names MCP tools `mcp__<server>__<tool>`. */
|
|
63
|
+
export function parseClaudeMcpToolName(name) {
|
|
64
|
+
const match = /^mcp__(.+?)__(.+)$/.exec(name.trim());
|
|
65
|
+
return match ? { server: match[1], tool: match[2] } : null;
|
|
66
|
+
}
|
|
67
|
+
/** Claude Code tool_use → integration, or undefined for built-in tools. */
|
|
68
|
+
export function claudeToolIntegration(name, input) {
|
|
69
|
+
const parsed = parseClaudeMcpToolName(name);
|
|
70
|
+
return parsed ? resolveMcpToolRef(parsed.server, parsed.tool, input) : undefined;
|
|
71
|
+
}
|
|
72
|
+
/** Cursor `mcpToolCall` args: `{ providerIdentifier, toolName, args }`. */
|
|
73
|
+
export function cursorToolIntegration(name, args) {
|
|
74
|
+
if (name.replace(/ToolCall$/i, "").toLowerCase() !== "mcp" || !args)
|
|
75
|
+
return undefined;
|
|
76
|
+
return resolveMcpToolRef(str(args.providerIdentifier), str(args.toolName), args.args);
|
|
77
|
+
}
|
|
78
|
+
/** MCP server name for Eigen's room/run tools (`steer_upload_file`, orchestration, memory). */
|
|
79
|
+
export const ROOM_MCP_SERVER_NAME = "steer_room";
|
|
80
|
+
/** Eigen's MCP servers, as OpenCode prefixes their tool names. */
|
|
81
|
+
const OPENCODE_STEER_SERVERS = ["steer_room", "steer_hub", "steer_swarm"];
|
|
82
|
+
/** Swarm board tools that OpenCode may expose without the server prefix. */
|
|
83
|
+
const OPENCODE_SWARM_TOOLS = new Set([
|
|
84
|
+
"team_list",
|
|
85
|
+
"board_digest",
|
|
86
|
+
"board_read",
|
|
87
|
+
"board_post",
|
|
88
|
+
"ask_orchestrator",
|
|
89
|
+
"request_human",
|
|
90
|
+
"task_list",
|
|
91
|
+
"task_claim",
|
|
92
|
+
"task_complete",
|
|
93
|
+
"notes_write",
|
|
94
|
+
"ledger_read",
|
|
95
|
+
"artifact_put",
|
|
96
|
+
"artifact_list",
|
|
97
|
+
"artifact_get",
|
|
98
|
+
"hypothesis_list",
|
|
99
|
+
"hypothesis_propose",
|
|
100
|
+
"hypothesis_critique",
|
|
101
|
+
"record_match",
|
|
102
|
+
"verify_result",
|
|
103
|
+
"create_task",
|
|
104
|
+
"update_task",
|
|
105
|
+
"spawn_worker",
|
|
106
|
+
"retire_worker",
|
|
107
|
+
"update_ledger",
|
|
108
|
+
"set_hypothesis_status",
|
|
109
|
+
"request_phase_change",
|
|
110
|
+
"declare_done",
|
|
111
|
+
]);
|
|
112
|
+
const OPENCODE_HUB_META_TOOLS = new Set([HUB_SEARCH_TOOLS, HUB_CALL_TOOL]);
|
|
113
|
+
/**
|
|
114
|
+
* OpenCode names MCP tools `<server>_<tool>` (and may also expose the bare
|
|
115
|
+
* tool name when it is unique). Older builds used Claude's `mcp__` form.
|
|
116
|
+
*/
|
|
117
|
+
export function parseOpencodeMcpToolName(name) {
|
|
118
|
+
const trimmed = name.trim();
|
|
119
|
+
const legacy = parseClaudeMcpToolName(trimmed);
|
|
120
|
+
if (legacy)
|
|
121
|
+
return legacy;
|
|
122
|
+
for (const server of OPENCODE_STEER_SERVERS) {
|
|
123
|
+
const prefix = `${server}_`;
|
|
124
|
+
if (trimmed.startsWith(prefix) && trimmed.length > prefix.length) {
|
|
125
|
+
return { server, tool: trimmed.slice(prefix.length) };
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
// Hub aliases keep `<provider>__<tool>` without their `steer_hub_` prefix.
|
|
129
|
+
if (OPENCODE_HUB_META_TOOLS.has(trimmed) || splitBundledToolName(trimmed)) {
|
|
130
|
+
return { server: "steer_hub", tool: trimmed };
|
|
131
|
+
}
|
|
132
|
+
if (OPENCODE_SWARM_TOOLS.has(trimmed)) {
|
|
133
|
+
return { server: "steer_swarm", tool: trimmed };
|
|
134
|
+
}
|
|
135
|
+
// Room MCP tool names already start with `steer_`.
|
|
136
|
+
if (/^steer_/i.test(trimmed)) {
|
|
137
|
+
return { server: "steer_room", tool: trimmed };
|
|
138
|
+
}
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
/** OpenCode MCP tool_use → integration, or undefined for built-in tools. */
|
|
142
|
+
export function opencodeToolIntegration(name, input) {
|
|
143
|
+
const parsed = parseOpencodeMcpToolName(name);
|
|
144
|
+
return parsed ? resolveMcpToolRef(parsed.server, parsed.tool, input) : undefined;
|
|
145
|
+
}
|
|
146
|
+
/** Codex `mcp_tool_call` item: `{ server, tool, arguments }`. */
|
|
147
|
+
export function codexToolIntegration(item) {
|
|
148
|
+
return resolveMcpToolRef(str(item.server), str(item.tool), item.arguments);
|
|
149
|
+
}
|
|
150
|
+
/** Stored `messages.integration_json` → ref (ignores malformed rows). */
|
|
151
|
+
export function parseToolIntegrationJson(raw) {
|
|
152
|
+
if (typeof raw !== "string" || !raw)
|
|
153
|
+
return undefined;
|
|
154
|
+
try {
|
|
155
|
+
const parsed = JSON.parse(raw);
|
|
156
|
+
return typeof parsed.server === "string" &&
|
|
157
|
+
typeof parsed.provider === "string" &&
|
|
158
|
+
typeof parsed.tool === "string"
|
|
159
|
+
? { server: parsed.server, provider: parsed.provider, tool: parsed.tool }
|
|
160
|
+
: undefined;
|
|
161
|
+
}
|
|
162
|
+
catch {
|
|
163
|
+
return undefined;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Cursor CLI stream-json `tool_call` line → integration. Applied in
|
|
168
|
+
* backends/index.ts (cursor.ts is bundled into the web app and must not
|
|
169
|
+
* import runtime modules).
|
|
170
|
+
*/
|
|
171
|
+
export function cursorStreamLineIntegration(json) {
|
|
172
|
+
const ev = asRecord(json);
|
|
173
|
+
if (ev?.type !== "tool_call")
|
|
174
|
+
return undefined;
|
|
175
|
+
const toolCall = asRecord(ev.tool_call);
|
|
176
|
+
const mcp = asRecord(toolCall?.mcpToolCall);
|
|
177
|
+
if (!mcp)
|
|
178
|
+
return undefined;
|
|
179
|
+
return cursorToolIntegration("mcp", asRecord(mcp.args ?? mcp.input ?? mcp.parameters));
|
|
180
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** Format a chat-style typing line scoped to one agent. */
|
|
2
|
+
export declare function formatTypingIndicator(names: string[], agentLabel: string): string;
|
|
3
|
+
/** Aggregate typing across agents for the “All agents” chat view. */
|
|
4
|
+
export declare function formatTypingIndicatorAll(typingByAgent: Record<string, Array<{
|
|
5
|
+
name: string;
|
|
6
|
+
}>>, agents: Array<{
|
|
7
|
+
id: string;
|
|
8
|
+
label: string;
|
|
9
|
+
}>): string;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/** Format a chat-style typing line scoped to one agent. */
|
|
2
|
+
export function formatTypingIndicator(names, agentLabel) {
|
|
3
|
+
const clean = names.map((n) => n.trim()).filter(Boolean);
|
|
4
|
+
if (clean.length === 0)
|
|
5
|
+
return "";
|
|
6
|
+
const target = agentLabel.trim() || "the agent";
|
|
7
|
+
if (clean.length === 1)
|
|
8
|
+
return `${clean[0]} is typing to ${target}…`;
|
|
9
|
+
if (clean.length === 2) {
|
|
10
|
+
return `${clean[0]} and ${clean[1]} are typing to ${target}…`;
|
|
11
|
+
}
|
|
12
|
+
return `${clean[0]} and ${clean.length - 1} others are typing to ${target}…`;
|
|
13
|
+
}
|
|
14
|
+
/** Aggregate typing across agents for the “All agents” chat view. */
|
|
15
|
+
export function formatTypingIndicatorAll(typingByAgent, agents) {
|
|
16
|
+
const labelById = new Map(agents.map((a) => [a.id, a.label]));
|
|
17
|
+
const entries = [];
|
|
18
|
+
for (const [agentId, users] of Object.entries(typingByAgent)) {
|
|
19
|
+
const agentLabel = labelById.get(agentId) || "agent";
|
|
20
|
+
for (const u of users || []) {
|
|
21
|
+
const name = (u.name || "").trim();
|
|
22
|
+
if (name)
|
|
23
|
+
entries.push({ name, agentLabel });
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
if (entries.length === 0)
|
|
27
|
+
return "";
|
|
28
|
+
if (entries.length === 1) {
|
|
29
|
+
return formatTypingIndicator([entries[0].name], entries[0].agentLabel);
|
|
30
|
+
}
|
|
31
|
+
const names = [...new Set(entries.map((e) => e.name))];
|
|
32
|
+
if (names.length === 1)
|
|
33
|
+
return `${names[0]} is typing…`;
|
|
34
|
+
if (names.length === 2)
|
|
35
|
+
return `${names[0]} and ${names[1]} are typing…`;
|
|
36
|
+
return `${names[0]} and ${names.length - 1} others are typing…`;
|
|
37
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/** Workspace-relative folder where Steer drops user attachments for local agents. */
|
|
2
|
+
export declare const ATTACHMENT_DIR = ".steer-uploads";
|
|
3
|
+
/** True when the attachment URL can be loaded without API auth (e.g. R2 public bucket). */
|
|
4
|
+
export declare function isPublicHttpUrl(url: string): boolean;
|
|
5
|
+
/**
|
|
6
|
+
* Public object URL (Cloudflare R2 / CDN), not the authenticated Render upload API.
|
|
7
|
+
* Those API paths look like `/api/rooms/:id/uploads/:fileId` even when absolutized.
|
|
8
|
+
*/
|
|
9
|
+
export declare function isDirectFileUrl(url: string): boolean;
|
|
10
|
+
/** Return the URL only when it should be used as the file's public address. */
|
|
11
|
+
export declare function directFileUrl(url: string | undefined | null): string | undefined;
|
|
12
|
+
export declare function safeAttachmentFileName(name: string): string;
|
|
13
|
+
export declare function contentDispositionInline(name: string): string;
|
|
14
|
+
export declare function contentDispositionAttachment(name: string): string;
|
|
15
|
+
export declare function safeContentType(mime: string, fallback?: string): string;
|
|
16
|
+
/** Path the agent should Read, relative to the run cwd. */
|
|
17
|
+
export declare function attachmentWorkspaceRelPath(id: string, name: string): string;
|
|
18
|
+
/**
|
|
19
|
+
* Curl a composer file into the workspace. The Cursor cloud VM already has
|
|
20
|
+
* `STEER_UPLOAD_URL` and `STEER_UPLOAD_TOKEN`; the token is not written here.
|
|
21
|
+
*/
|
|
22
|
+
export declare function cursorCloudAttachmentCurl(fileId: string, relPath: string): string;
|
|
23
|
+
export declare function isSafeAttachmentRelPath(relPath: string): boolean;
|
|
24
|
+
export interface WorkerPromptAttachment {
|
|
25
|
+
id: string;
|
|
26
|
+
name: string;
|
|
27
|
+
mime: string;
|
|
28
|
+
relPath: string;
|
|
29
|
+
/** Base64 file bytes. */
|
|
30
|
+
data: string;
|
|
31
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/** Workspace-relative folder where Steer drops user attachments for local agents. */
|
|
2
|
+
export const ATTACHMENT_DIR = ".steer-uploads";
|
|
3
|
+
/** True when the attachment URL can be loaded without API auth (e.g. R2 public bucket). */
|
|
4
|
+
export function isPublicHttpUrl(url) {
|
|
5
|
+
return /^https?:\/\//i.test(url.trim());
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Public object URL (Cloudflare R2 / CDN), not the authenticated Render upload API.
|
|
9
|
+
* Those API paths look like `/api/rooms/:id/uploads/:fileId` even when absolutized.
|
|
10
|
+
*/
|
|
11
|
+
export function isDirectFileUrl(url) {
|
|
12
|
+
const trimmed = url.trim();
|
|
13
|
+
if (!isPublicHttpUrl(trimmed))
|
|
14
|
+
return false;
|
|
15
|
+
try {
|
|
16
|
+
const path = new URL(trimmed).pathname;
|
|
17
|
+
return (!/^\/api\/rooms\/[^/]+\/uploads\//i.test(path) &&
|
|
18
|
+
!/^\/api\/issues\/[^/]+\/attachments\//i.test(path));
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
/** Return the URL only when it should be used as the file's public address. */
|
|
25
|
+
export function directFileUrl(url) {
|
|
26
|
+
return url && isDirectFileUrl(url) ? url.trim() : undefined;
|
|
27
|
+
}
|
|
28
|
+
export function safeAttachmentFileName(name) {
|
|
29
|
+
return name.replace(/[^a-zA-Z0-9._-]+/g, "_").slice(0, 120) || "file";
|
|
30
|
+
}
|
|
31
|
+
/** HTTP header-safe Content-Disposition. Node rejects unicode in filename=. */
|
|
32
|
+
function contentDisposition(kind, name) {
|
|
33
|
+
const fallback = safeAttachmentFileName(name);
|
|
34
|
+
const encoded = encodeURIComponent(name.replace(/[\r\n\0]+/g, " ").trim() || fallback);
|
|
35
|
+
return `${kind}; filename="${fallback}"; filename*=UTF-8''${encoded}`;
|
|
36
|
+
}
|
|
37
|
+
export function contentDispositionInline(name) {
|
|
38
|
+
return contentDisposition("inline", name);
|
|
39
|
+
}
|
|
40
|
+
export function contentDispositionAttachment(name) {
|
|
41
|
+
return contentDisposition("attachment", name);
|
|
42
|
+
}
|
|
43
|
+
export function safeContentType(mime, fallback = "application/octet-stream") {
|
|
44
|
+
const trimmed = mime.trim().toLowerCase();
|
|
45
|
+
return /^[\w.+-]+\/[\w.+-]+$/.test(trimmed) ? trimmed : fallback;
|
|
46
|
+
}
|
|
47
|
+
/** Path the agent should Read, relative to the run cwd. */
|
|
48
|
+
export function attachmentWorkspaceRelPath(id, name) {
|
|
49
|
+
return `${ATTACHMENT_DIR}/${id}-${safeAttachmentFileName(name)}`.replace(/\\/g, "/");
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Curl a composer file into the workspace. The Cursor cloud VM already has
|
|
53
|
+
* `STEER_UPLOAD_URL` and `STEER_UPLOAD_TOKEN`; the token is not written here.
|
|
54
|
+
*/
|
|
55
|
+
export function cursorCloudAttachmentCurl(fileId, relPath) {
|
|
56
|
+
const dest = relPath.replace(/"/g, "");
|
|
57
|
+
return (`mkdir -p .steer-uploads && curl -fsS -H "Authorization: Bearer $STEER_UPLOAD_TOKEN" ` +
|
|
58
|
+
`-o "${dest}" "\${STEER_UPLOAD_URL%/api/agent-artifacts}/api/agent-uploads/${encodeURIComponent(fileId)}"`);
|
|
59
|
+
}
|
|
60
|
+
export function isSafeAttachmentRelPath(relPath) {
|
|
61
|
+
const normalized = relPath.replace(/\\/g, "/");
|
|
62
|
+
return (normalized.startsWith(`${ATTACHMENT_DIR}/`) &&
|
|
63
|
+
!normalized.includes("..") &&
|
|
64
|
+
!normalized.startsWith("/"));
|
|
65
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phone-call approvals via Sarvam Instant Outbound.
|
|
3
|
+
* The in-room Approve/Deny button stays the source of truth; voice is a second channel.
|
|
4
|
+
*/
|
|
5
|
+
export declare const VOICE_DECISIONS: readonly ["approved", "denied"];
|
|
6
|
+
export type VoiceDecision = (typeof VOICE_DECISIONS)[number];
|
|
7
|
+
export type VoiceCallStatus = "dialing" | "connected" | "no_answer" | "busy" | "failed" | "completed";
|
|
8
|
+
export interface VoiceApprovalSettings {
|
|
9
|
+
phoneE164: string | null;
|
|
10
|
+
phoneMasked: string | null;
|
|
11
|
+
optIn: boolean;
|
|
12
|
+
callingConfigured: boolean;
|
|
13
|
+
}
|
|
14
|
+
export interface VoiceApprovalDecisionBody {
|
|
15
|
+
approvalId: string;
|
|
16
|
+
decision: VoiceDecision;
|
|
17
|
+
token?: string;
|
|
18
|
+
}
|
|
19
|
+
/** Normalize user-entered phones to E.164. Bare 10-digit numbers are treated as India (+91). */
|
|
20
|
+
export declare function normalizePhoneE164(raw: unknown): string | null;
|
|
21
|
+
export declare function maskPhoneE164(phone: string | null | undefined): string | null;
|
|
22
|
+
export declare function parseVoiceDecision(raw: unknown): VoiceDecision | null;
|
|
23
|
+
/** Unwrap Sarvam API-tool envelopes and JSON-as-text bodies. */
|
|
24
|
+
export declare function flattenVoiceWebhookBody(body: unknown): Record<string, unknown>;
|
|
25
|
+
export declare function pickRawVoiceDecision(body: Record<string, unknown>, query?: Record<string, unknown>): unknown;
|
|
26
|
+
/**
|
|
27
|
+
* Instant Outbound's terminal webhook (`status: connected`) includes the
|
|
28
|
+
* transcript. Use that when the mid-call API tool never posts /decision.
|
|
29
|
+
*/
|
|
30
|
+
export declare function extractCallWebhookDecision(body: Record<string, unknown>): {
|
|
31
|
+
decision: VoiceDecision | null;
|
|
32
|
+
source: "variables" | "transcript" | null;
|
|
33
|
+
userTurns: string[];
|
|
34
|
+
};
|
|
35
|
+
export declare function parseVoiceCallStatus(raw: unknown): VoiceCallStatus | null;
|
|
36
|
+
export declare function isVoiceCallInFlight(status: string | null | undefined): boolean;
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phone-call approvals via Sarvam Instant Outbound.
|
|
3
|
+
* The in-room Approve/Deny button stays the source of truth; voice is a second channel.
|
|
4
|
+
*/
|
|
5
|
+
export const VOICE_DECISIONS = ["approved", "denied"];
|
|
6
|
+
const E164_RE = /^\+[1-9]\d{7,14}$/;
|
|
7
|
+
/** Normalize user-entered phones to E.164. Bare 10-digit numbers are treated as India (+91). */
|
|
8
|
+
export function normalizePhoneE164(raw) {
|
|
9
|
+
if (typeof raw !== "string")
|
|
10
|
+
return null;
|
|
11
|
+
let s = raw.trim();
|
|
12
|
+
if (!s)
|
|
13
|
+
return null;
|
|
14
|
+
s = s.replace(/[()\s.\-]/g, "");
|
|
15
|
+
if (s.startsWith("00"))
|
|
16
|
+
s = `+${s.slice(2)}`;
|
|
17
|
+
if (s.startsWith("0") && /^\d{11}$/.test(s)) {
|
|
18
|
+
s = `+91${s.slice(1)}`;
|
|
19
|
+
}
|
|
20
|
+
if (/^\d{10}$/.test(s))
|
|
21
|
+
s = `+91${s}`;
|
|
22
|
+
if (/^\d{11,15}$/.test(s))
|
|
23
|
+
s = `+${s}`;
|
|
24
|
+
if (!s.startsWith("+"))
|
|
25
|
+
return null;
|
|
26
|
+
if (!E164_RE.test(s))
|
|
27
|
+
return null;
|
|
28
|
+
return s;
|
|
29
|
+
}
|
|
30
|
+
export function maskPhoneE164(phone) {
|
|
31
|
+
const p = phone?.trim() || "";
|
|
32
|
+
if (!p)
|
|
33
|
+
return null;
|
|
34
|
+
if (p.length < 6)
|
|
35
|
+
return "••••";
|
|
36
|
+
return `${p.slice(0, 3)}••••${p.slice(-4)}`;
|
|
37
|
+
}
|
|
38
|
+
export function parseVoiceDecision(raw) {
|
|
39
|
+
if (typeof raw === "boolean")
|
|
40
|
+
return raw ? "approved" : "denied";
|
|
41
|
+
if (typeof raw === "number") {
|
|
42
|
+
if (raw === 1)
|
|
43
|
+
return "approved";
|
|
44
|
+
if (raw === 0)
|
|
45
|
+
return "denied";
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
const original = String(raw ?? "").trim();
|
|
49
|
+
if (!original)
|
|
50
|
+
return null;
|
|
51
|
+
const compact = original
|
|
52
|
+
.toLowerCase()
|
|
53
|
+
.replace(/[_\s-]+/g, "")
|
|
54
|
+
.replace(/[^a-z0-9]+/g, "");
|
|
55
|
+
if (compact === "approved" ||
|
|
56
|
+
compact === "approve" ||
|
|
57
|
+
compact === "yes" ||
|
|
58
|
+
compact === "y" ||
|
|
59
|
+
compact === "true" ||
|
|
60
|
+
compact === "ok" ||
|
|
61
|
+
compact === "okay" ||
|
|
62
|
+
compact === "goahead") {
|
|
63
|
+
return "approved";
|
|
64
|
+
}
|
|
65
|
+
if (compact === "denied" ||
|
|
66
|
+
compact === "deny" ||
|
|
67
|
+
compact === "no" ||
|
|
68
|
+
compact === "n" ||
|
|
69
|
+
compact === "false" ||
|
|
70
|
+
compact === "reject" ||
|
|
71
|
+
compact === "rejected" ||
|
|
72
|
+
compact === "stop") {
|
|
73
|
+
return "denied";
|
|
74
|
+
}
|
|
75
|
+
const text = original.toLowerCase();
|
|
76
|
+
const yes = /\b(approved?|yes|yeah|yep|yup|ok(?:ay)?|go\s*ahead|proceed|confirm(?:ed)?)\b/.test(text);
|
|
77
|
+
const no = /\b(den(?:y|ied)|no(?:pe)?|reject(?:ed)?|stop|cancel(?:led)?|don'?t)\b/.test(text);
|
|
78
|
+
if (yes && !no)
|
|
79
|
+
return "approved";
|
|
80
|
+
if (no && !yes)
|
|
81
|
+
return "denied";
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
const NESTED_BODY_KEYS = [
|
|
85
|
+
"data",
|
|
86
|
+
"payload",
|
|
87
|
+
"parameters",
|
|
88
|
+
"params",
|
|
89
|
+
"arguments",
|
|
90
|
+
"args",
|
|
91
|
+
"input",
|
|
92
|
+
"body",
|
|
93
|
+
"variables",
|
|
94
|
+
"agent_variables",
|
|
95
|
+
"tool",
|
|
96
|
+
"request",
|
|
97
|
+
];
|
|
98
|
+
const DECISION_KEYS = [
|
|
99
|
+
"decision",
|
|
100
|
+
"approved",
|
|
101
|
+
"result",
|
|
102
|
+
"choice",
|
|
103
|
+
"action",
|
|
104
|
+
"intent",
|
|
105
|
+
"verdict",
|
|
106
|
+
"outcome",
|
|
107
|
+
"user_decision",
|
|
108
|
+
"spoken_decision",
|
|
109
|
+
"answer",
|
|
110
|
+
"status",
|
|
111
|
+
];
|
|
112
|
+
const TRANSCRIPT_KEYS = [
|
|
113
|
+
"transcript",
|
|
114
|
+
"call_transcript",
|
|
115
|
+
"callTranscript",
|
|
116
|
+
"conversation",
|
|
117
|
+
"user_response",
|
|
118
|
+
"message",
|
|
119
|
+
"text",
|
|
120
|
+
"utterance",
|
|
121
|
+
];
|
|
122
|
+
/** Unwrap Sarvam API-tool envelopes and JSON-as-text bodies. */
|
|
123
|
+
export function flattenVoiceWebhookBody(body) {
|
|
124
|
+
if (typeof body === "string") {
|
|
125
|
+
const trimmed = body.trim();
|
|
126
|
+
if (!trimmed)
|
|
127
|
+
return {};
|
|
128
|
+
try {
|
|
129
|
+
return flattenVoiceWebhookBody(JSON.parse(trimmed));
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
return { decision: trimmed };
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
if (!body || typeof body !== "object" || Array.isArray(body))
|
|
136
|
+
return {};
|
|
137
|
+
const rec = body;
|
|
138
|
+
let out = { ...rec };
|
|
139
|
+
for (const key of NESTED_BODY_KEYS) {
|
|
140
|
+
const inner = rec[key];
|
|
141
|
+
if (inner && typeof inner === "object" && !Array.isArray(inner)) {
|
|
142
|
+
out = { ...inner, ...out };
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return out;
|
|
146
|
+
}
|
|
147
|
+
export function pickRawVoiceDecision(body, query) {
|
|
148
|
+
for (const key of DECISION_KEYS) {
|
|
149
|
+
const value = body[key] ?? query?.[key];
|
|
150
|
+
if (value === undefined || value === null || String(value).trim() === "") {
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
if (key === "status" && parseVoiceCallStatus(value))
|
|
154
|
+
continue;
|
|
155
|
+
return value;
|
|
156
|
+
}
|
|
157
|
+
for (const key of TRANSCRIPT_KEYS) {
|
|
158
|
+
const value = body[key];
|
|
159
|
+
if (typeof value === "string" && value.trim())
|
|
160
|
+
return value;
|
|
161
|
+
}
|
|
162
|
+
return undefined;
|
|
163
|
+
}
|
|
164
|
+
function userTranscriptTurns(raw) {
|
|
165
|
+
if (typeof raw === "string" && raw.trim())
|
|
166
|
+
return [raw.trim()];
|
|
167
|
+
if (!Array.isArray(raw))
|
|
168
|
+
return [];
|
|
169
|
+
const texts = [];
|
|
170
|
+
for (const turn of raw) {
|
|
171
|
+
if (!turn || typeof turn !== "object")
|
|
172
|
+
continue;
|
|
173
|
+
const rec = turn;
|
|
174
|
+
const role = String(rec.role || rec.speaker || "").toLowerCase();
|
|
175
|
+
if (role && role !== "user" && role !== "customer" && role !== "human") {
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
const text = String(rec.en_text || rec.text || rec.content || rec.transcript || "").trim();
|
|
179
|
+
if (text)
|
|
180
|
+
texts.push(text);
|
|
181
|
+
}
|
|
182
|
+
return texts;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Instant Outbound's terminal webhook (`status: connected`) includes the
|
|
186
|
+
* transcript. Use that when the mid-call API tool never posts /decision.
|
|
187
|
+
*/
|
|
188
|
+
export function extractCallWebhookDecision(body) {
|
|
189
|
+
const variableBags = [
|
|
190
|
+
body.final_agent_variables,
|
|
191
|
+
body.final_agent,
|
|
192
|
+
body.collected_variables,
|
|
193
|
+
body.extracted_data,
|
|
194
|
+
];
|
|
195
|
+
for (const bag of variableBags) {
|
|
196
|
+
if (!bag || typeof bag !== "object" || Array.isArray(bag))
|
|
197
|
+
continue;
|
|
198
|
+
const raw = pickRawVoiceDecision(bag);
|
|
199
|
+
const decision = parseVoiceDecision(raw);
|
|
200
|
+
if (decision) {
|
|
201
|
+
return { decision, source: "variables", userTurns: [] };
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
const userTurns = userTranscriptTurns(body.interaction_transcript ?? body.transcript ?? body.turns);
|
|
205
|
+
for (let i = userTurns.length - 1; i >= 0; i--) {
|
|
206
|
+
const decision = parseVoiceDecision(userTurns[i]);
|
|
207
|
+
if (decision) {
|
|
208
|
+
return { decision, source: "transcript", userTurns };
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
return { decision: null, source: null, userTurns };
|
|
212
|
+
}
|
|
213
|
+
export function parseVoiceCallStatus(raw) {
|
|
214
|
+
const v = String(raw ?? "")
|
|
215
|
+
.trim()
|
|
216
|
+
.toLowerCase();
|
|
217
|
+
if (v === "dialing" ||
|
|
218
|
+
v === "connected" ||
|
|
219
|
+
v === "no_answer" ||
|
|
220
|
+
v === "busy" ||
|
|
221
|
+
v === "failed" ||
|
|
222
|
+
v === "completed") {
|
|
223
|
+
return v;
|
|
224
|
+
}
|
|
225
|
+
return null;
|
|
226
|
+
}
|
|
227
|
+
export function isVoiceCallInFlight(status) {
|
|
228
|
+
return status === "dialing" || status === "connected";
|
|
229
|
+
}
|