@adonis-agora/agent 0.76.3 → 0.77.1
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/CHANGELOG.md +22 -0
- package/dist/providers/agent_provider.d.ts.map +1 -1
- package/dist/providers/agent_provider.js +9 -0
- package/dist/providers/agent_provider.js.map +1 -1
- package/dist/providers/mcp_provider.d.ts.map +1 -1
- package/dist/providers/mcp_provider.js +46 -10
- package/dist/providers/mcp_provider.js.map +1 -1
- package/dist/providers/poppy_connections_provider.d.ts +17 -0
- package/dist/providers/poppy_connections_provider.d.ts.map +1 -0
- package/dist/providers/poppy_connections_provider.js +186 -0
- package/dist/providers/poppy_connections_provider.js.map +1 -0
- package/dist/providers/poppy_provider.d.ts +12 -0
- package/dist/providers/poppy_provider.d.ts.map +1 -0
- package/dist/providers/poppy_provider.js +159 -0
- package/dist/providers/poppy_provider.js.map +1 -0
- package/dist/src/a2a/handler.d.ts.map +1 -1
- package/dist/src/a2a/handler.js +4 -2
- package/dist/src/a2a/handler.js.map +1 -1
- package/dist/src/a2a/turn.d.ts +27 -4
- package/dist/src/a2a/turn.d.ts.map +1 -1
- package/dist/src/a2a/turn.js +66 -12
- package/dist/src/a2a/turn.js.map +1 -1
- package/dist/src/action-proposal-approval.d.ts.map +1 -1
- package/dist/src/action-proposal-approval.js +3 -3
- package/dist/src/action-proposal-approval.js.map +1 -1
- package/dist/src/action-proposal-listeners.d.ts +12 -0
- package/dist/src/action-proposal-listeners.d.ts.map +1 -0
- package/dist/src/action-proposal-listeners.js +27 -0
- package/dist/src/action-proposal-listeners.js.map +1 -0
- package/dist/src/action-proposal-receipt.d.ts +4 -0
- package/dist/src/action-proposal-receipt.d.ts.map +1 -1
- package/dist/src/action-proposal-receipt.js +3 -1
- package/dist/src/action-proposal-receipt.js.map +1 -1
- package/dist/src/action-proposal-worker.d.ts +6 -1
- package/dist/src/action-proposal-worker.d.ts.map +1 -1
- package/dist/src/action-proposal-worker.js +10 -1
- package/dist/src/action-proposal-worker.js.map +1 -1
- package/dist/src/agent-loop.d.ts.map +1 -1
- package/dist/src/agent-loop.js +8 -5
- package/dist/src/agent-loop.js.map +1 -1
- package/dist/src/channels/handler.d.ts +10 -0
- package/dist/src/channels/handler.d.ts.map +1 -1
- package/dist/src/channels/handler.js +47 -1
- package/dist/src/channels/handler.js.map +1 -1
- package/dist/src/channels/texts.d.ts +9 -0
- package/dist/src/channels/texts.d.ts.map +1 -1
- package/dist/src/channels/texts.js +4 -0
- package/dist/src/channels/texts.js.map +1 -1
- package/dist/src/client/sse.d.ts.map +1 -1
- package/dist/src/client/sse.js +3 -0
- package/dist/src/client/sse.js.map +1 -1
- package/dist/src/define_config.d.ts +3 -0
- package/dist/src/define_config.d.ts.map +1 -1
- package/dist/src/define_config.js.map +1 -1
- package/dist/src/genui/channels.d.ts +5 -0
- package/dist/src/genui/channels.d.ts.map +1 -1
- package/dist/src/genui/channels.js +11 -0
- package/dist/src/genui/channels.js.map +1 -1
- package/dist/src/index.d.ts +2 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +2 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/mcp/auth.d.ts +17 -0
- package/dist/src/mcp/auth.d.ts.map +1 -1
- package/dist/src/mcp/auth.js +56 -1
- package/dist/src/mcp/auth.js.map +1 -1
- package/dist/src/mcp/discovery.d.ts +13 -2
- package/dist/src/mcp/discovery.d.ts.map +1 -1
- package/dist/src/mcp/discovery.js +17 -2
- package/dist/src/mcp/discovery.js.map +1 -1
- package/dist/src/mcp/index.d.ts +1 -1
- package/dist/src/mcp/index.d.ts.map +1 -1
- package/dist/src/mcp/index.js.map +1 -1
- package/dist/src/mcp/scope.d.ts +13 -0
- package/dist/src/mcp/scope.d.ts.map +1 -0
- package/dist/src/mcp/scope.js +24 -0
- package/dist/src/mcp/scope.js.map +1 -0
- package/dist/src/mcp/server.d.ts +6 -1
- package/dist/src/mcp/server.d.ts.map +1 -1
- package/dist/src/mcp/server.js +1 -1
- package/dist/src/mcp/server.js.map +1 -1
- package/dist/src/poppy/auth.d.ts +77 -0
- package/dist/src/poppy/auth.d.ts.map +1 -0
- package/dist/src/poppy/auth.js +16 -0
- package/dist/src/poppy/auth.js.map +1 -0
- package/dist/src/poppy/conversations.d.ts +249 -0
- package/dist/src/poppy/conversations.d.ts.map +1 -0
- package/dist/src/poppy/conversations.js +1017 -0
- package/dist/src/poppy/conversations.js.map +1 -0
- package/dist/src/poppy/define_config.d.ts +98 -0
- package/dist/src/poppy/define_config.d.ts.map +1 -0
- package/dist/src/poppy/define_config.js +11 -0
- package/dist/src/poppy/define_config.js.map +1 -0
- package/dist/src/poppy/endpoints.d.ts +25 -0
- package/dist/src/poppy/endpoints.d.ts.map +1 -0
- package/dist/src/poppy/endpoints.js +31 -0
- package/dist/src/poppy/endpoints.js.map +1 -0
- package/dist/src/poppy/handler.d.ts +80 -0
- package/dist/src/poppy/handler.d.ts.map +1 -0
- package/dist/src/poppy/handler.js +362 -0
- package/dist/src/poppy/handler.js.map +1 -0
- package/dist/src/poppy/index.d.ts +17 -0
- package/dist/src/poppy/index.d.ts.map +1 -0
- package/dist/src/poppy/index.js +9 -0
- package/dist/src/poppy/index.js.map +1 -0
- package/dist/src/poppy/protocol.d.ts +132 -0
- package/dist/src/poppy/protocol.d.ts.map +1 -0
- package/dist/src/poppy/protocol.js +178 -0
- package/dist/src/poppy/protocol.js.map +1 -0
- package/dist/src/poppy/redis-store.d.ts +27 -0
- package/dist/src/poppy/redis-store.d.ts.map +1 -0
- package/dist/src/poppy/redis-store.js +148 -0
- package/dist/src/poppy/redis-store.js.map +1 -0
- package/dist/src/poppy/runtime.d.ts +6 -0
- package/dist/src/poppy/runtime.d.ts.map +1 -0
- package/dist/src/poppy/runtime.js +12 -0
- package/dist/src/poppy/runtime.js.map +1 -0
- package/dist/src/poppy/server_middleware.d.ts +11 -0
- package/dist/src/poppy/server_middleware.d.ts.map +1 -0
- package/dist/src/poppy/server_middleware.js +16 -0
- package/dist/src/poppy/server_middleware.js.map +1 -0
- package/dist/src/poppy/store.d.ts +207 -0
- package/dist/src/poppy/store.d.ts.map +1 -0
- package/dist/src/poppy/store.js +444 -0
- package/dist/src/poppy/store.js.map +1 -0
- package/dist/src/poppy-client/api.d.ts +36 -0
- package/dist/src/poppy-client/api.d.ts.map +1 -0
- package/dist/src/poppy-client/api.js +77 -0
- package/dist/src/poppy-client/api.js.map +1 -0
- package/dist/src/poppy-client/bin/poppy-agent.d.ts +3 -0
- package/dist/src/poppy-client/bin/poppy-agent.d.ts.map +1 -0
- package/dist/src/poppy-client/bin/poppy-agent.js +9 -0
- package/dist/src/poppy-client/bin/poppy-agent.js.map +1 -0
- package/dist/src/poppy-client/browser.d.ts +24 -0
- package/dist/src/poppy-client/browser.d.ts.map +1 -0
- package/dist/src/poppy-client/browser.js +83 -0
- package/dist/src/poppy-client/browser.js.map +1 -0
- package/dist/src/poppy-client/cli/main.d.ts +2 -0
- package/dist/src/poppy-client/cli/main.d.ts.map +1 -0
- package/dist/src/poppy-client/cli/main.js +776 -0
- package/dist/src/poppy-client/cli/main.js.map +1 -0
- package/dist/src/poppy-client/cli/prompt.d.ts +13 -0
- package/dist/src/poppy-client/cli/prompt.d.ts.map +1 -0
- package/dist/src/poppy-client/cli/prompt.js +50 -0
- package/dist/src/poppy-client/cli/prompt.js.map +1 -0
- package/dist/src/poppy-client/company.d.ts +87 -0
- package/dist/src/poppy-client/company.d.ts.map +1 -0
- package/dist/src/poppy-client/company.js +336 -0
- package/dist/src/poppy-client/company.js.map +1 -0
- package/dist/src/poppy-client/conversations.d.ts +89 -0
- package/dist/src/poppy-client/conversations.d.ts.map +1 -0
- package/dist/src/poppy-client/conversations.js +332 -0
- package/dist/src/poppy-client/conversations.js.map +1 -0
- package/dist/src/poppy-client/discovery.d.ts +41 -0
- package/dist/src/poppy-client/discovery.d.ts.map +1 -0
- package/dist/src/poppy-client/discovery.js +217 -0
- package/dist/src/poppy-client/discovery.js.map +1 -0
- package/dist/src/poppy-client/dpop.d.ts +19 -0
- package/dist/src/poppy-client/dpop.d.ts.map +1 -0
- package/dist/src/poppy-client/dpop.js +43 -0
- package/dist/src/poppy-client/dpop.js.map +1 -0
- package/dist/src/poppy-client/errors.d.ts +50 -0
- package/dist/src/poppy-client/errors.d.ts.map +1 -0
- package/dist/src/poppy-client/errors.js +55 -0
- package/dist/src/poppy-client/errors.js.map +1 -0
- package/dist/src/poppy-client/http.d.ts +40 -0
- package/dist/src/poppy-client/http.d.ts.map +1 -0
- package/dist/src/poppy-client/http.js +95 -0
- package/dist/src/poppy-client/http.js.map +1 -0
- package/dist/src/poppy-client/identity.d.ts +61 -0
- package/dist/src/poppy-client/identity.d.ts.map +1 -0
- package/dist/src/poppy-client/identity.js +111 -0
- package/dist/src/poppy-client/identity.js.map +1 -0
- package/dist/src/poppy-client/identity_server.d.ts +29 -0
- package/dist/src/poppy-client/identity_server.d.ts.map +1 -0
- package/dist/src/poppy-client/identity_server.js +84 -0
- package/dist/src/poppy-client/identity_server.js.map +1 -0
- package/dist/src/poppy-client/index.d.ts +19 -0
- package/dist/src/poppy-client/index.d.ts.map +1 -0
- package/dist/src/poppy-client/index.js +18 -0
- package/dist/src/poppy-client/index.js.map +1 -0
- package/dist/src/poppy-client/keys.d.ts +13 -0
- package/dist/src/poppy-client/keys.d.ts.map +1 -0
- package/dist/src/poppy-client/keys.js +31 -0
- package/dist/src/poppy-client/keys.js.map +1 -0
- package/dist/src/poppy-client/llm.d.ts +31 -0
- package/dist/src/poppy-client/llm.d.ts.map +1 -0
- package/dist/src/poppy-client/llm.js +230 -0
- package/dist/src/poppy-client/llm.js.map +1 -0
- package/dist/src/poppy-client/mcp.d.ts +16 -0
- package/dist/src/poppy-client/mcp.d.ts.map +1 -0
- package/dist/src/poppy-client/mcp.js +57 -0
- package/dist/src/poppy-client/mcp.js.map +1 -0
- package/dist/src/poppy-client/open_url.d.ts +3 -0
- package/dist/src/poppy-client/open_url.d.ts.map +1 -0
- package/dist/src/poppy-client/open_url.js +13 -0
- package/dist/src/poppy-client/open_url.js.map +1 -0
- package/dist/src/poppy-client/signin.d.ts +59 -0
- package/dist/src/poppy-client/signin.d.ts.map +1 -0
- package/dist/src/poppy-client/signin.js +210 -0
- package/dist/src/poppy-client/signin.js.map +1 -0
- package/dist/src/poppy-client/sse.d.ts +15 -0
- package/dist/src/poppy-client/sse.d.ts.map +1 -0
- package/dist/src/poppy-client/sse.js +74 -0
- package/dist/src/poppy-client/sse.js.map +1 -0
- package/dist/src/poppy-client/store.d.ts +89 -0
- package/dist/src/poppy-client/store.d.ts.map +1 -0
- package/dist/src/poppy-client/store.js +79 -0
- package/dist/src/poppy-client/store.js.map +1 -0
- package/dist/src/poppy-client/token_client.d.ts +69 -0
- package/dist/src/poppy-client/token_client.d.ts.map +1 -0
- package/dist/src/poppy-client/token_client.js +154 -0
- package/dist/src/poppy-client/token_client.js.map +1 -0
- package/dist/src/poppy-client/types.d.ts +184 -0
- package/dist/src/poppy-client/types.d.ts.map +1 -0
- package/dist/src/poppy-client/types.js +8 -0
- package/dist/src/poppy-client/types.js.map +1 -0
- package/dist/src/poppy-client/util.d.ts +42 -0
- package/dist/src/poppy-client/util.d.ts.map +1 -0
- package/dist/src/poppy-client/util.js +122 -0
- package/dist/src/poppy-client/util.js.map +1 -0
- package/dist/src/poppy-connections/config.d.ts +134 -0
- package/dist/src/poppy-connections/config.d.ts.map +1 -0
- package/dist/src/poppy-connections/config.js +44 -0
- package/dist/src/poppy-connections/config.js.map +1 -0
- package/dist/src/poppy-connections/crypto.d.ts +12 -0
- package/dist/src/poppy-connections/crypto.d.ts.map +1 -0
- package/dist/src/poppy-connections/crypto.js +36 -0
- package/dist/src/poppy-connections/crypto.js.map +1 -0
- package/dist/src/poppy-connections/http.d.ts +63 -0
- package/dist/src/poppy-connections/http.d.ts.map +1 -0
- package/dist/src/poppy-connections/http.js +217 -0
- package/dist/src/poppy-connections/http.js.map +1 -0
- package/dist/src/poppy-connections/index.d.ts +14 -0
- package/dist/src/poppy-connections/index.d.ts.map +1 -0
- package/dist/src/poppy-connections/index.js +17 -0
- package/dist/src/poppy-connections/index.js.map +1 -0
- package/dist/src/poppy-connections/resume.d.ts +46 -0
- package/dist/src/poppy-connections/resume.d.ts.map +1 -0
- package/dist/src/poppy-connections/resume.js +109 -0
- package/dist/src/poppy-connections/resume.js.map +1 -0
- package/dist/src/poppy-connections/runtime.d.ts +4 -0
- package/dist/src/poppy-connections/runtime.d.ts.map +1 -0
- package/dist/src/poppy-connections/runtime.js +9 -0
- package/dist/src/poppy-connections/runtime.js.map +1 -0
- package/dist/src/poppy-connections/server_middleware.d.ts +10 -0
- package/dist/src/poppy-connections/server_middleware.d.ts.map +1 -0
- package/dist/src/poppy-connections/server_middleware.js +14 -0
- package/dist/src/poppy-connections/server_middleware.js.map +1 -0
- package/dist/src/poppy-connections/service.d.ts +179 -0
- package/dist/src/poppy-connections/service.d.ts.map +1 -0
- package/dist/src/poppy-connections/service.js +648 -0
- package/dist/src/poppy-connections/service.js.map +1 -0
- package/dist/src/poppy-connections/store.d.ts +139 -0
- package/dist/src/poppy-connections/store.d.ts.map +1 -0
- package/dist/src/poppy-connections/store.js +260 -0
- package/dist/src/poppy-connections/store.js.map +1 -0
- package/dist/src/poppy-connections/tools.d.ts +65 -0
- package/dist/src/poppy-connections/tools.d.ts.map +1 -0
- package/dist/src/poppy-connections/tools.js +574 -0
- package/dist/src/poppy-connections/tools.js.map +1 -0
- package/dist/src/react/core/approvals/use-action-proposals.d.ts +6 -1
- package/dist/src/react/core/approvals/use-action-proposals.d.ts.map +1 -1
- package/dist/src/react/core/approvals/use-action-proposals.js +18 -3
- package/dist/src/react/core/approvals/use-action-proposals.js.map +1 -1
- package/dist/src/react/core/use-agent-chat.d.ts +6 -0
- package/dist/src/react/core/use-agent-chat.d.ts.map +1 -1
- package/dist/src/react/core/use-agent-chat.js +47 -1
- package/dist/src/react/core/use-agent-chat.js.map +1 -1
- package/dist/src/tool-presentation.d.ts +6 -0
- package/dist/src/tool-presentation.d.ts.map +1 -1
- package/dist/src/tool-presentation.js +1 -0
- package/dist/src/tool-presentation.js.map +1 -1
- package/dist/src/types.d.ts +6 -0
- package/dist/src/types.d.ts.map +1 -1
- package/package.json +41 -2
- package/skills/agent-governance/SKILL.md +13 -2
- package/skills/agent-personas-agents/SKILL.md +1 -1
- package/skills/agent-tools/SKILL.md +20 -3
|
@@ -0,0 +1,776 @@
|
|
|
1
|
+
import { parseArgs } from 'node:util';
|
|
2
|
+
import { callOpenApi, listApis, loadOpenApi } from '../api.js';
|
|
3
|
+
import { autoSubmitFormHtml, createBrowserAssertion, serveOneShotPage } from '../browser.js';
|
|
4
|
+
import { PoppyAgent } from '../company.js';
|
|
5
|
+
import { ConversationClient, newMessageId, poppyProtocol } from '../conversations.js';
|
|
6
|
+
import { discover } from '../discovery.js';
|
|
7
|
+
import { PoppyError, ResourceAuthError } from '../errors.js';
|
|
8
|
+
import { AgentIdentity } from '../identity.js';
|
|
9
|
+
import { startIdentityServer, waitForRelayedCallback } from '../identity_server.js';
|
|
10
|
+
import { generateEs256Key } from '../keys.js';
|
|
11
|
+
import { ask, DEFAULT_MODEL } from '../llm.js';
|
|
12
|
+
import { connectMcp } from '../mcp.js';
|
|
13
|
+
import { openInBrowser } from '../open_url.js';
|
|
14
|
+
import { completeDirectSignIn, deviceSignIn, eligibleSignInTypes, mediatedSignIn, startDirectSignIn, submitMediatedCode, } from '../signin.js';
|
|
15
|
+
import { StateStore } from '../store.js';
|
|
16
|
+
import { isLoopbackHost, requireSecureUrl } from '../util.js';
|
|
17
|
+
import { Prompter } from './prompt.js';
|
|
18
|
+
const HELP = `poppy-agent — reference Personal Agent for the Personal Agent Protocol (Poppy)
|
|
19
|
+
|
|
20
|
+
EXPERIMENTAL. Implements spec Draft 0.1 (https://personalagentprotocol.org/docs/spec).
|
|
21
|
+
The spec is still in development; this client tracks it and WILL change, possibly in
|
|
22
|
+
breaking ways, as the spec evolves.
|
|
23
|
+
|
|
24
|
+
Usage: poppy-agent <command> [args] [options]
|
|
25
|
+
|
|
26
|
+
Identity (4.1)
|
|
27
|
+
init --base-url <https-url> [--name <client_name>] [--logo-uri <url>] [--rotate-keys]
|
|
28
|
+
identity print client metadata + JWKS
|
|
29
|
+
serve-identity [--base-url <url>] [--port <n>] [--host <addr>]
|
|
30
|
+
serve agent.json, jwks.json, logo, /oauth/callback
|
|
31
|
+
Discovery (3)
|
|
32
|
+
discover <domain>
|
|
33
|
+
Sessions & sign-in (4)
|
|
34
|
+
session <domain> [--new] [--scope <s>] [--resource <url>]
|
|
35
|
+
signin <domain> [--method direct|device|mediated] [--scope "<scopes>"] [--no-open]
|
|
36
|
+
status [domain]
|
|
37
|
+
disconnect <domain> revoke the Account Token (RFC 7009)
|
|
38
|
+
APIs (6)
|
|
39
|
+
apis <domain> list APIs (and OpenAPI operations)
|
|
40
|
+
api <domain> <METHOD> <path> [--data <json>] [--api <n>] [--scope <s>]
|
|
41
|
+
mcp tools <domain> [--api <n>]
|
|
42
|
+
mcp call <domain> <tool> [<json-args>] [--api <n>]
|
|
43
|
+
Conversations (7)
|
|
44
|
+
chat <domain> [--conversation <id>] [--as agent|human] [--no-stream]
|
|
45
|
+
Web browsing (5)
|
|
46
|
+
open <domain> <return_to> [--print] form-POST a poppy-browser+jwt assertion
|
|
47
|
+
LLM mode
|
|
48
|
+
ask <domain> "<task>" [--model <id>] [--max-steps <n>] [--yes]
|
|
49
|
+
needs OPENROUTER_API_KEY (default model ${DEFAULT_MODEL})
|
|
50
|
+
|
|
51
|
+
Global options
|
|
52
|
+
--state <file> state file (default $POPPY_AGENT_STATE or ~/.config/poppy-agent/state.json), mode 0600
|
|
53
|
+
--profile <name> local user to act for (default "default"); each gets its own User IDs
|
|
54
|
+
--client-assertion-aud token_endpoint|issuer aud of private_key_jwt (default token_endpoint)
|
|
55
|
+
--insecure-dev NON-SPEC: allow http:// URLs (client_id, discovery, endpoints). Local testing only.
|
|
56
|
+
Also: POPPY_AGENT_INSECURE_DEV=1
|
|
57
|
+
`;
|
|
58
|
+
const out = (s = '') => process.stdout.write(`${s}\n`);
|
|
59
|
+
const err = (s) => process.stderr.write(`${s}\n`);
|
|
60
|
+
const dim = (s) => (process.stdout.isTTY ? `\x1b[2m${s}\x1b[0m` : s);
|
|
61
|
+
const bold = (s) => (process.stdout.isTTY ? `\x1b[1m${s}\x1b[0m` : s);
|
|
62
|
+
function insecureBanner() {
|
|
63
|
+
const red = (s) => (process.stderr.isTTY ? `\x1b[41;97;1m${s}\x1b[0m` : s);
|
|
64
|
+
err(red('!!! --insecure-dev: NON-SPEC MODE !!!'));
|
|
65
|
+
err(red(' http:// is accepted for client_id, poppy.json, issuer, endpoints and redirects.'));
|
|
66
|
+
err(red(' The Poppy spec requires HTTPS everywhere. NEVER use this against a real Company.'));
|
|
67
|
+
}
|
|
68
|
+
function loadIdentity(ctx) {
|
|
69
|
+
const id = ctx.store.data.identity;
|
|
70
|
+
if (!id)
|
|
71
|
+
throw new PoppyError('no_identity', 'no agent identity yet: run `poppy-agent init --base-url <url>`');
|
|
72
|
+
return new AgentIdentity(id, { insecureDev: ctx.insecureDev });
|
|
73
|
+
}
|
|
74
|
+
function makeAgent(ctx) {
|
|
75
|
+
const aud = ctx.values['client-assertion-aud'];
|
|
76
|
+
return new PoppyAgent({
|
|
77
|
+
store: ctx.store,
|
|
78
|
+
identity: loadIdentity(ctx),
|
|
79
|
+
profile: ctx.profile,
|
|
80
|
+
insecureDev: ctx.insecureDev,
|
|
81
|
+
log: (l) => err(dim(`· ${l}`)),
|
|
82
|
+
tokenClient: { clientAssertionAudience: aud === 'issuer' ? 'issuer' : 'token_endpoint' },
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
async function company(ctx, domain) {
|
|
86
|
+
if (!domain)
|
|
87
|
+
throw new PoppyError('usage', 'missing <domain>');
|
|
88
|
+
return makeAgent(ctx).company(domain);
|
|
89
|
+
}
|
|
90
|
+
function str(v) {
|
|
91
|
+
return typeof v === 'string' ? v : undefined;
|
|
92
|
+
}
|
|
93
|
+
export async function runCli(argv) {
|
|
94
|
+
const { values, positionals } = parseArgs({
|
|
95
|
+
args: argv,
|
|
96
|
+
allowPositionals: true,
|
|
97
|
+
strict: false,
|
|
98
|
+
options: {
|
|
99
|
+
help: { type: 'boolean', short: 'h' },
|
|
100
|
+
state: { type: 'string' },
|
|
101
|
+
profile: { type: 'string' },
|
|
102
|
+
'insecure-dev': { type: 'boolean' },
|
|
103
|
+
'base-url': { type: 'string' },
|
|
104
|
+
name: { type: 'string' },
|
|
105
|
+
'logo-uri': { type: 'string' },
|
|
106
|
+
'rotate-keys': { type: 'boolean' },
|
|
107
|
+
port: { type: 'string' },
|
|
108
|
+
host: { type: 'string' },
|
|
109
|
+
new: { type: 'boolean' },
|
|
110
|
+
scope: { type: 'string' },
|
|
111
|
+
resource: { type: 'string' },
|
|
112
|
+
method: { type: 'string' },
|
|
113
|
+
'no-open': { type: 'boolean' },
|
|
114
|
+
data: { type: 'string' },
|
|
115
|
+
api: { type: 'string' },
|
|
116
|
+
conversation: { type: 'string' },
|
|
117
|
+
as: { type: 'string' },
|
|
118
|
+
'no-stream': { type: 'boolean' },
|
|
119
|
+
print: { type: 'boolean' },
|
|
120
|
+
model: { type: 'string' },
|
|
121
|
+
'max-steps': { type: 'string' },
|
|
122
|
+
yes: { type: 'boolean' },
|
|
123
|
+
'client-assertion-aud': { type: 'string' },
|
|
124
|
+
},
|
|
125
|
+
});
|
|
126
|
+
const [command, ...args] = positionals;
|
|
127
|
+
if (!command || values.help) {
|
|
128
|
+
out(HELP);
|
|
129
|
+
return command || values.help ? 0 : 1;
|
|
130
|
+
}
|
|
131
|
+
const insecureDev = values['insecure-dev'] === true || process.env.POPPY_AGENT_INSECURE_DEV === '1';
|
|
132
|
+
if (insecureDev)
|
|
133
|
+
insecureBanner();
|
|
134
|
+
const ctx = {
|
|
135
|
+
store: await StateStore.open(str(values.state)),
|
|
136
|
+
insecureDev,
|
|
137
|
+
profile: str(values.profile) ?? 'default',
|
|
138
|
+
values: values,
|
|
139
|
+
prompter: new Prompter(),
|
|
140
|
+
};
|
|
141
|
+
try {
|
|
142
|
+
return await dispatch(ctx, command, args);
|
|
143
|
+
}
|
|
144
|
+
catch (e) {
|
|
145
|
+
if (e instanceof ResourceAuthError && e.code === 'insufficient_scope') {
|
|
146
|
+
err(`error: insufficient_scope — the Company needs "${e.scope ?? '?'}". Run: poppy-agent signin <domain> --scope "${e.scope ?? ''}"`);
|
|
147
|
+
}
|
|
148
|
+
else if (e instanceof ResourceAuthError && e.code === 'sign_in_required') {
|
|
149
|
+
err('error: sign_in_required — run: poppy-agent signin <domain>');
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
err(`error: ${e.message}`);
|
|
153
|
+
}
|
|
154
|
+
return 1;
|
|
155
|
+
}
|
|
156
|
+
finally {
|
|
157
|
+
ctx.prompter.close();
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
async function dispatch(ctx, command, args) {
|
|
161
|
+
const v = ctx.values;
|
|
162
|
+
switch (command) {
|
|
163
|
+
case 'init':
|
|
164
|
+
return cmdInit(ctx);
|
|
165
|
+
case 'identity': {
|
|
166
|
+
const id = loadIdentity(ctx);
|
|
167
|
+
out(JSON.stringify({ metadata: id.metadata(), jwks: id.jwks() }, null, 2));
|
|
168
|
+
return 0;
|
|
169
|
+
}
|
|
170
|
+
case 'serve-identity':
|
|
171
|
+
return cmdServeIdentity(ctx);
|
|
172
|
+
case 'discover': {
|
|
173
|
+
if (!args[0])
|
|
174
|
+
throw new PoppyError('usage', 'discover <domain>');
|
|
175
|
+
const d = await discover(args[0], { insecureDev: ctx.insecureDev });
|
|
176
|
+
out(JSON.stringify({ domain: d.domain, poppy: d.document, metadata: d.metadata }, null, 2));
|
|
177
|
+
return 0;
|
|
178
|
+
}
|
|
179
|
+
case 'session': {
|
|
180
|
+
const c = await company(ctx, args[0]);
|
|
181
|
+
if (v.new)
|
|
182
|
+
await c.newSession();
|
|
183
|
+
const t = await c.token({ scope: str(v.scope), resource: str(v.resource) });
|
|
184
|
+
out(JSON.stringify({
|
|
185
|
+
user_id: c.userId,
|
|
186
|
+
session_id: t.sessionId,
|
|
187
|
+
signed_in: t.signedIn,
|
|
188
|
+
scope: t.scope,
|
|
189
|
+
token_type: t.tokenType,
|
|
190
|
+
expires_at: new Date(t.expiresAt).toISOString(),
|
|
191
|
+
account_token: c.accountToken
|
|
192
|
+
? { scope: c.accountToken.scope, via: c.accountToken.via }
|
|
193
|
+
: null,
|
|
194
|
+
}, null, 2));
|
|
195
|
+
return 0;
|
|
196
|
+
}
|
|
197
|
+
case 'signin': {
|
|
198
|
+
const c = await company(ctx, args[0]);
|
|
199
|
+
const r = await signIn(ctx, c, str(v.method), str(v.scope));
|
|
200
|
+
return r ? 0 : 1;
|
|
201
|
+
}
|
|
202
|
+
case 'status':
|
|
203
|
+
return cmdStatus(ctx, args[0]);
|
|
204
|
+
case 'disconnect': {
|
|
205
|
+
const c = await company(ctx, args[0]);
|
|
206
|
+
const revoked = await c.signOut();
|
|
207
|
+
out(revoked
|
|
208
|
+
? 'Account Token revoked; Sessions continue signed out.'
|
|
209
|
+
: 'No Account Token for this Company.');
|
|
210
|
+
return 0;
|
|
211
|
+
}
|
|
212
|
+
case 'apis': {
|
|
213
|
+
const c = await company(ctx, args[0]);
|
|
214
|
+
for (const a of listApis(c)) {
|
|
215
|
+
out(`${bold(`[${a.type}]`)} ${a.url}${a.resource ? ` (resource ${a.resource})` : ''} — ${a.description ?? ''}`);
|
|
216
|
+
if (a.type === 'openapi') {
|
|
217
|
+
try {
|
|
218
|
+
const doc = await loadOpenApi(c, a);
|
|
219
|
+
for (const [path, ops] of Object.entries(doc.paths ?? {})) {
|
|
220
|
+
for (const [m, op] of Object.entries(ops ?? {})) {
|
|
221
|
+
if (!/^(get|post|put|patch|delete)$/i.test(m))
|
|
222
|
+
continue;
|
|
223
|
+
out(` ${m.toUpperCase().padEnd(6)} ${path}${op?.summary ? dim(` ${op.summary}`) : ''}`);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
catch (e) {
|
|
228
|
+
out(dim(` (could not load: ${e.message})`));
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
return 0;
|
|
233
|
+
}
|
|
234
|
+
case 'api': {
|
|
235
|
+
const [domain, method, path] = args;
|
|
236
|
+
if (!domain || !method || !path)
|
|
237
|
+
throw new PoppyError('usage', 'api <domain> <METHOD> <path> [--data json]');
|
|
238
|
+
const c = await company(ctx, domain);
|
|
239
|
+
const r = await callOpenApi(c, {
|
|
240
|
+
method,
|
|
241
|
+
path,
|
|
242
|
+
body: str(v.data) !== undefined ? JSON.parse(str(v.data)) : undefined,
|
|
243
|
+
apiIndex: v.api ? Number(v.api) : undefined,
|
|
244
|
+
scope: str(v.scope),
|
|
245
|
+
});
|
|
246
|
+
err(dim(`HTTP ${r.status}`));
|
|
247
|
+
out(typeof r.body === 'string' ? r.body : JSON.stringify(r.body, null, 2));
|
|
248
|
+
return r.status < 400 ? 0 : 1;
|
|
249
|
+
}
|
|
250
|
+
case 'mcp':
|
|
251
|
+
return cmdMcp(ctx, args);
|
|
252
|
+
case 'chat':
|
|
253
|
+
return cmdChat(ctx, args[0]);
|
|
254
|
+
case 'open':
|
|
255
|
+
return cmdOpen(ctx, args[0], args[1]);
|
|
256
|
+
case 'ask':
|
|
257
|
+
return cmdAsk(ctx, args[0], args.slice(1).join(' '));
|
|
258
|
+
default:
|
|
259
|
+
err(`unknown command: ${command}\n`);
|
|
260
|
+
out(HELP);
|
|
261
|
+
return 1;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
/* --------------------------------------------------------------------------- identity */
|
|
265
|
+
async function cmdInit(ctx) {
|
|
266
|
+
const v = ctx.values;
|
|
267
|
+
const existing = ctx.store.data.identity;
|
|
268
|
+
const baseUrl = str(v['base-url']) ?? existing?.baseUrl;
|
|
269
|
+
if (!baseUrl)
|
|
270
|
+
throw new PoppyError('usage', 'init --base-url <https-url>');
|
|
271
|
+
requireSecureUrl(baseUrl, 'base URL', { insecureDev: ctx.insecureDev });
|
|
272
|
+
const keys = !existing || v['rotate-keys']
|
|
273
|
+
? [await generateEs256Key(), ...(existing?.keys ?? []).slice(0, 1)]
|
|
274
|
+
: existing.keys;
|
|
275
|
+
ctx.store.data.identity = {
|
|
276
|
+
baseUrl,
|
|
277
|
+
clientName: str(v.name) ?? existing?.clientName ?? 'Poppy Reference Agent',
|
|
278
|
+
logoUri: str(v['logo-uri']) ?? existing?.logoUri,
|
|
279
|
+
keys,
|
|
280
|
+
};
|
|
281
|
+
await ctx.store.save();
|
|
282
|
+
const id = loadIdentity(ctx);
|
|
283
|
+
out(`client_id: ${id.clientId}`);
|
|
284
|
+
out(`jwks_uri: ${id.jwksUri}`);
|
|
285
|
+
out(`redirect: ${id.redirectUri}`);
|
|
286
|
+
out(`state: ${ctx.store.path} (0600)`);
|
|
287
|
+
return 0;
|
|
288
|
+
}
|
|
289
|
+
async function cmdServeIdentity(ctx) {
|
|
290
|
+
const v = ctx.values;
|
|
291
|
+
if (str(v['base-url']) || !ctx.store.data.identity) {
|
|
292
|
+
const code = await cmdInit(ctx);
|
|
293
|
+
if (code)
|
|
294
|
+
return code;
|
|
295
|
+
}
|
|
296
|
+
const identity = loadIdentity(ctx);
|
|
297
|
+
const base = new URL(identity.baseUrl);
|
|
298
|
+
const local = isLoopbackHost(base.hostname);
|
|
299
|
+
const port = v.port ? Number(v.port) : local && base.port ? Number(base.port) : 7777;
|
|
300
|
+
const host = str(v.host) ?? (local && base.hostname === 'localhost' ? 'localhost' : '127.0.0.1');
|
|
301
|
+
const srv = await startIdentityServer(identity, {
|
|
302
|
+
host,
|
|
303
|
+
port,
|
|
304
|
+
relayDir: ctx.store.dir,
|
|
305
|
+
log: (l) => err(dim(l)),
|
|
306
|
+
});
|
|
307
|
+
out(`Serving the agent identity on ${srv.url}`);
|
|
308
|
+
out(` client_id ${identity.clientId}`);
|
|
309
|
+
out(` jwks_uri ${identity.jwksUri}`);
|
|
310
|
+
out(` redirect_uri ${identity.redirectUri}`);
|
|
311
|
+
if (!local)
|
|
312
|
+
out(`Expose ${srv.url} at ${identity.baseUrl} (e.g. cloudflared / tailscale funnel).`);
|
|
313
|
+
out('Ctrl-C to stop.');
|
|
314
|
+
await new Promise((resolve) => {
|
|
315
|
+
process.once('SIGINT', resolve);
|
|
316
|
+
process.once('SIGTERM', resolve);
|
|
317
|
+
});
|
|
318
|
+
await srv.close();
|
|
319
|
+
return 0;
|
|
320
|
+
}
|
|
321
|
+
/* --------------------------------------------------------------------------- sign-in */
|
|
322
|
+
function defaultScope(c, method) {
|
|
323
|
+
const offered = c.document.auth?.[method]?.scopes ?? [];
|
|
324
|
+
const broad = offered.filter((s) => s === 'poppy:read' || s === 'poppy:write');
|
|
325
|
+
return (broad.length ? broad : offered).join(' ');
|
|
326
|
+
}
|
|
327
|
+
async function signIn(ctx, c, method, scope) {
|
|
328
|
+
const eligible = scope
|
|
329
|
+
? eligibleSignInTypes(c, scope)
|
|
330
|
+
: ['direct', 'device', 'mediated'].filter((t) => c.document.auth?.[t]);
|
|
331
|
+
const chosen = method ?? eligible[0];
|
|
332
|
+
if (!chosen) {
|
|
333
|
+
err(scope
|
|
334
|
+
? `No sign-in type offered by this Company can grant "${scope}".`
|
|
335
|
+
: 'This Company offers no sign-in.');
|
|
336
|
+
return undefined;
|
|
337
|
+
}
|
|
338
|
+
const wanted = scope ?? defaultScope(c, chosen);
|
|
339
|
+
out(`Signing in with ${bold(chosen)} sign-in, scope "${wanted}"`);
|
|
340
|
+
let result;
|
|
341
|
+
if (chosen === 'direct')
|
|
342
|
+
result = await directFlow(ctx, c, wanted);
|
|
343
|
+
else if (chosen === 'device') {
|
|
344
|
+
result = await deviceSignIn(c, wanted, {
|
|
345
|
+
display: (i) => {
|
|
346
|
+
out(`Open ${bold(i.verificationUriComplete ?? i.verificationUri)} and enter the code ${bold(i.userCode)}`);
|
|
347
|
+
out(dim(`(expires in ${i.expiresIn}s; waiting…)`));
|
|
348
|
+
},
|
|
349
|
+
onPoll: (s, n) => s === 'slow_down' && err(dim(`slow_down: polling every ${n}s`)),
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
else
|
|
353
|
+
result = await mediatedFlow(ctx, c, wanted);
|
|
354
|
+
if (result) {
|
|
355
|
+
out(`Signed in. Granted scope: "${result.grantedScope}" (session ${result.sessionId})`);
|
|
356
|
+
if (result.missingScopes.length)
|
|
357
|
+
out(`Note: the User did not grant ${result.missingScopes.join(', ')}`);
|
|
358
|
+
}
|
|
359
|
+
return result;
|
|
360
|
+
}
|
|
361
|
+
async function reachable(url) {
|
|
362
|
+
try {
|
|
363
|
+
const r = await fetch(url, { signal: AbortSignal.timeout(2000) });
|
|
364
|
+
return r.ok;
|
|
365
|
+
}
|
|
366
|
+
catch {
|
|
367
|
+
return false;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
async function directFlow(ctx, c, scope) {
|
|
371
|
+
const identity = c.agent.identity;
|
|
372
|
+
const pending = await startDirectSignIn(c, scope);
|
|
373
|
+
const ac = new AbortController();
|
|
374
|
+
const waits = [
|
|
375
|
+
waitForRelayedCallback(ctx.store.dir, pending.state, { signal: ac.signal }),
|
|
376
|
+
];
|
|
377
|
+
let inproc;
|
|
378
|
+
if (!(await reachable(identity.clientId))) {
|
|
379
|
+
const base = new URL(identity.baseUrl);
|
|
380
|
+
if (isLoopbackHost(base.hostname)) {
|
|
381
|
+
let resolveCb;
|
|
382
|
+
waits.push(new Promise((r) => (resolveCb = r)));
|
|
383
|
+
inproc = await startIdentityServer(identity, {
|
|
384
|
+
host: base.hostname === 'localhost' ? 'localhost' : base.hostname,
|
|
385
|
+
port: Number(base.port || 80),
|
|
386
|
+
onCallback: (p) => p.get('state') === pending.state && resolveCb(p),
|
|
387
|
+
});
|
|
388
|
+
err(dim(`serving the agent identity in-process at ${identity.baseUrl}`));
|
|
389
|
+
}
|
|
390
|
+
else {
|
|
391
|
+
err(`warning: ${identity.clientId} is not reachable. Run \`poppy-agent serve-identity\` behind your tunnel.`);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
out(`Open this URL to sign in:\n ${pending.url}`);
|
|
395
|
+
if (!ctx.values['no-open'])
|
|
396
|
+
openInBrowser(pending.url);
|
|
397
|
+
if (ctx.prompter.interactive) {
|
|
398
|
+
waits.push(ctx.prompter
|
|
399
|
+
.ask('(waiting for the redirect… or paste the full redirect URL here)\n', ac.signal)
|
|
400
|
+
.then((line) => new URL(line.trim()).searchParams));
|
|
401
|
+
}
|
|
402
|
+
try {
|
|
403
|
+
const params = await Promise.race(waits);
|
|
404
|
+
return await completeDirectSignIn(c, pending, params);
|
|
405
|
+
}
|
|
406
|
+
finally {
|
|
407
|
+
ac.abort();
|
|
408
|
+
for (const w of waits)
|
|
409
|
+
w.catch(() => { });
|
|
410
|
+
await inproc?.close();
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
async function mediatedFlow(ctx, c, scope) {
|
|
414
|
+
const mediated = c.document.auth.mediated;
|
|
415
|
+
const credentials = {};
|
|
416
|
+
for (const f of mediated.fields) {
|
|
417
|
+
credentials[f.name] = f.secret
|
|
418
|
+
? await ctx.prompter.askSecret(`${f.label}: `)
|
|
419
|
+
: await ctx.prompter.ask(`${f.label}: `);
|
|
420
|
+
}
|
|
421
|
+
let outcome = await mediatedSignIn(c, scope, credentials);
|
|
422
|
+
for (const k of Object.keys(credentials))
|
|
423
|
+
credentials[k] = '';
|
|
424
|
+
while (outcome.status === 'code_required') {
|
|
425
|
+
const code = await ctx.prompter.ask(`One-time code${outcome.sentTo ? ` (${outcome.sentTo})` : ''}${outcome.expiresAt ? `, before ${outcome.expiresAt}` : ''}: `);
|
|
426
|
+
outcome = await submitMediatedCode(c, scope, outcome.signInId, code.trim());
|
|
427
|
+
}
|
|
428
|
+
if (outcome.status === 'complete')
|
|
429
|
+
return outcome.result;
|
|
430
|
+
err(`Mediated Sign-In ${outcome.status}.`);
|
|
431
|
+
return undefined;
|
|
432
|
+
}
|
|
433
|
+
/* --------------------------------------------------------------------------- status */
|
|
434
|
+
async function cmdStatus(ctx, domain) {
|
|
435
|
+
const companies = ctx.store.profile(ctx.profile).companies;
|
|
436
|
+
const id = ctx.store.data.identity;
|
|
437
|
+
out(`identity: ${id ? `${id.baseUrl}/agent.json` : '(none: run init)'} profile: ${ctx.profile}`);
|
|
438
|
+
for (const s of Object.values(companies)) {
|
|
439
|
+
if (domain && !s.domains.includes(domain.toLowerCase().replace(/^www\./, '')))
|
|
440
|
+
continue;
|
|
441
|
+
const t = s.tokens.dpop;
|
|
442
|
+
out(`${bold(s.issuer)} (${s.domains.join(', ')})`);
|
|
443
|
+
out(` user_id ${s.userId} session ${s.sessionId ?? '-'} ${t ? `signed_in=${t.signedIn} scope="${t.scope}" expires ${new Date(t.expiresAt).toISOString()}` : ''}`);
|
|
444
|
+
out(` account token: ${s.accountToken ? `yes (scope "${s.accountToken.scope}", via ${s.accountToken.via})` : 'no'}`);
|
|
445
|
+
const convs = Object.entries(s.conversations).filter(([, c]) => !c.closed);
|
|
446
|
+
if (convs.length)
|
|
447
|
+
out(` open conversations: ${convs.map(([k]) => k).join(', ')}`);
|
|
448
|
+
}
|
|
449
|
+
return 0;
|
|
450
|
+
}
|
|
451
|
+
/* --------------------------------------------------------------------------- mcp */
|
|
452
|
+
async function cmdMcp(ctx, args) {
|
|
453
|
+
const [sub, domain, tool, json] = args;
|
|
454
|
+
if ((sub !== 'tools' && sub !== 'call') || !domain || (sub === 'call' && !tool)) {
|
|
455
|
+
throw new PoppyError('usage', 'mcp tools <domain> | mcp call <domain> <tool> [json]');
|
|
456
|
+
}
|
|
457
|
+
const c = await company(ctx, domain);
|
|
458
|
+
const conn = await connectMcp(c, ctx.values.api ? Number(ctx.values.api) : undefined);
|
|
459
|
+
try {
|
|
460
|
+
if (sub === 'tools') {
|
|
461
|
+
const { tools } = await conn.client.listTools();
|
|
462
|
+
for (const t of tools) {
|
|
463
|
+
out(`${bold(t.name)}${t.annotations?.readOnlyHint ? dim(' (read-only)') : ''} — ${t.description ?? ''}`);
|
|
464
|
+
out(dim(` input: ${JSON.stringify(t.inputSchema)}`));
|
|
465
|
+
}
|
|
466
|
+
return 0;
|
|
467
|
+
}
|
|
468
|
+
const r = await conn.client.callTool({ name: tool, arguments: json ? JSON.parse(json) : {} });
|
|
469
|
+
out(JSON.stringify(r, null, 2));
|
|
470
|
+
return r.isError ? 1 : 0;
|
|
471
|
+
}
|
|
472
|
+
finally {
|
|
473
|
+
await conn.close();
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
/* --------------------------------------------------------------------------- open */
|
|
477
|
+
async function cmdOpen(ctx, domain, returnTo) {
|
|
478
|
+
if (!domain || !returnTo)
|
|
479
|
+
throw new PoppyError('usage', 'open <domain> <return_to>');
|
|
480
|
+
const c = await company(ctx, domain);
|
|
481
|
+
const { endpoint, assertion } = await createBrowserAssertion(c, returnTo);
|
|
482
|
+
const page = await serveOneShotPage(autoSubmitFormHtml(endpoint, assertion));
|
|
483
|
+
err(dim("Spec 5: browser assertions MUST be posted only from a browser the Personal Agent controls, never from the User's own browser. Use a dedicated/automation browser profile."));
|
|
484
|
+
out(`Load this page (once, within 60s) in the agent-controlled browser:\n ${page.url}`);
|
|
485
|
+
out(dim(`It form-POSTs the assertion to ${endpoint}, which sets the Session cookie and 303s to return_to.`));
|
|
486
|
+
if (!ctx.values.print)
|
|
487
|
+
openInBrowser(page.url);
|
|
488
|
+
await Promise.race([page.served, new Promise((r) => setTimeout(r, 60_000))]);
|
|
489
|
+
page.close();
|
|
490
|
+
return 0;
|
|
491
|
+
}
|
|
492
|
+
/* --------------------------------------------------------------------------- ask */
|
|
493
|
+
async function cmdAsk(ctx, domain, task) {
|
|
494
|
+
if (!domain || !task)
|
|
495
|
+
throw new PoppyError('usage', 'ask <domain> "<task>"');
|
|
496
|
+
const apiKey = process.env.OPENROUTER_API_KEY;
|
|
497
|
+
if (!apiKey)
|
|
498
|
+
throw new PoppyError('usage', 'set OPENROUTER_API_KEY');
|
|
499
|
+
const c = await company(ctx, domain);
|
|
500
|
+
const r = await ask(c, task, {
|
|
501
|
+
apiKey,
|
|
502
|
+
model: str(ctx.values.model) ?? process.env.POPPY_AGENT_MODEL ?? DEFAULT_MODEL,
|
|
503
|
+
maxSteps: ctx.values['max-steps'] ? Number(ctx.values['max-steps']) : 8,
|
|
504
|
+
baseUrl: process.env.POPPY_AGENT_LLM_URL,
|
|
505
|
+
allowWrites: ctx.values.yes === true,
|
|
506
|
+
confirm: (q) => ctx.prompter.confirm(q),
|
|
507
|
+
log: (l) => out(l),
|
|
508
|
+
context: localContext(),
|
|
509
|
+
});
|
|
510
|
+
out();
|
|
511
|
+
out(bold('Answer:'));
|
|
512
|
+
out(r.answer ?? '(no answer)');
|
|
513
|
+
return r.answer === undefined ? 1 : 0;
|
|
514
|
+
}
|
|
515
|
+
/* --------------------------------------------------------------------------- chat */
|
|
516
|
+
function localContext() {
|
|
517
|
+
const ro = Intl.DateTimeFormat().resolvedOptions();
|
|
518
|
+
return { locale: ro.locale, time_zone: ro.timeZone };
|
|
519
|
+
}
|
|
520
|
+
const CHAT_HELP = `Type a message to send it. Commands:
|
|
521
|
+
/data <json> send a message with a data part
|
|
522
|
+
/context k=v ... send a context-only update (user_available=true|false, locale=…, time_zone=…)
|
|
523
|
+
/as agent|human who wrote what you type (default agent; human = the User's own words, 7.8)
|
|
524
|
+
/handoff ask for a person at the Company (7.9)
|
|
525
|
+
/signin [method] [scope] sign in (e.g. after an authorization event)
|
|
526
|
+
/direct [text] open a Direct Conversation (7.10); you then talk as the User
|
|
527
|
+
/decline decline the Company's request to talk to the User
|
|
528
|
+
/leave close the Direct Conversation
|
|
529
|
+
/status show status/responder
|
|
530
|
+
/close close the conversation (7.12)
|
|
531
|
+
/quit leave (the conversation stays open)`;
|
|
532
|
+
async function cmdChat(ctx, domain) {
|
|
533
|
+
const c = await company(ctx, domain);
|
|
534
|
+
if (!poppyProtocol(c))
|
|
535
|
+
throw new PoppyError('no_conversation_protocol', 'Company lists no `poppy` conversation protocol');
|
|
536
|
+
const conv = new ConversationClient(c);
|
|
537
|
+
const stream = !ctx.values['no-stream'];
|
|
538
|
+
let sender = ctx.values.as === 'human' ? 'human' : 'agent';
|
|
539
|
+
let mainId = str(ctx.values.conversation);
|
|
540
|
+
let directId;
|
|
541
|
+
let pendingAuth;
|
|
542
|
+
let pendingUserRequest;
|
|
543
|
+
let status = '';
|
|
544
|
+
const followers = new Map();
|
|
545
|
+
const provisional = new Map();
|
|
546
|
+
const rl = ctx.prompter.interface;
|
|
547
|
+
const print = (line) => {
|
|
548
|
+
if (process.stdout.isTTY)
|
|
549
|
+
process.stdout.write('\r\x1b[2K');
|
|
550
|
+
out(line);
|
|
551
|
+
if (process.stdout.isTTY)
|
|
552
|
+
rl.prompt(true);
|
|
553
|
+
};
|
|
554
|
+
const showProvisional = (id) => {
|
|
555
|
+
if (!process.stdout.isTTY)
|
|
556
|
+
return;
|
|
557
|
+
const text = (provisional.get(id) ?? '').split('\n').pop() ?? '';
|
|
558
|
+
process.stdout.write(`\r\x1b[2K${dim(`… ${text.slice(-((process.stdout.columns || 80) - 4))}`)}`);
|
|
559
|
+
};
|
|
560
|
+
const render = (convId, ev) => {
|
|
561
|
+
const tag = directId && convId !== directId ? dim('[parent] ') : '';
|
|
562
|
+
switch (ev.type) {
|
|
563
|
+
case 'message': {
|
|
564
|
+
const m = ev.message;
|
|
565
|
+
provisional.delete(m.id);
|
|
566
|
+
if (m.role === 'user')
|
|
567
|
+
return; // our own message, echoed back
|
|
568
|
+
let line = `${tag}${bold(`company[${m.sender}]`)}: ${m.text ?? ''}`;
|
|
569
|
+
if (m.data)
|
|
570
|
+
line += `\n${dim(JSON.stringify(m.data, null, 2))}`;
|
|
571
|
+
print(line);
|
|
572
|
+
return;
|
|
573
|
+
}
|
|
574
|
+
case 'state':
|
|
575
|
+
status = `${ev.status}/${ev.responder}`;
|
|
576
|
+
print(dim(`${tag}[${status}]`));
|
|
577
|
+
return;
|
|
578
|
+
case 'authorization':
|
|
579
|
+
pendingAuth = ev;
|
|
580
|
+
print(`${tag}${bold('Authorization needed')}: ${ev.error}${ev.scope ? ` (scope "${ev.scope}")` : ''}. Type /signin to sign in, then keep chatting.`);
|
|
581
|
+
return;
|
|
582
|
+
case 'user_requested':
|
|
583
|
+
pendingUserRequest = ev;
|
|
584
|
+
print(`${tag}${bold('The Company asks to talk with you directly')}: ${ev.reason ?? ''}\n /direct to accept, /decline to decline.`);
|
|
585
|
+
return;
|
|
586
|
+
case 'direct_opened':
|
|
587
|
+
print(dim(`${tag}[direct conversation ${ev.conversation_id} opened]`));
|
|
588
|
+
return;
|
|
589
|
+
case 'direct_closed':
|
|
590
|
+
print(dim(`${tag}[direct conversation ${ev.conversation_id} closed]`));
|
|
591
|
+
if (directId === ev.conversation_id)
|
|
592
|
+
directId = undefined;
|
|
593
|
+
return;
|
|
594
|
+
default:
|
|
595
|
+
return; // MUST skip unknown event types (7.5)
|
|
596
|
+
}
|
|
597
|
+
};
|
|
598
|
+
const follow = (convId) => {
|
|
599
|
+
if (followers.has(convId))
|
|
600
|
+
return;
|
|
601
|
+
const ac = new AbortController();
|
|
602
|
+
followers.set(convId, ac);
|
|
603
|
+
(async () => {
|
|
604
|
+
try {
|
|
605
|
+
for await (const item of conv.follow(convId, { signal: ac.signal, stream })) {
|
|
606
|
+
if (item.kind === 'delta') {
|
|
607
|
+
provisional.set(item.delta.message_id, (provisional.get(item.delta.message_id) ?? '') + item.delta.text);
|
|
608
|
+
showProvisional(item.delta.message_id);
|
|
609
|
+
}
|
|
610
|
+
else if (item.kind === 'reconnected') {
|
|
611
|
+
provisional.clear(); // 7.6: drop pieces of messages not received in full
|
|
612
|
+
}
|
|
613
|
+
else if (item.kind === 'history_gap') {
|
|
614
|
+
print(dim('[part of this conversation is no longer available; reading from the start]'));
|
|
615
|
+
}
|
|
616
|
+
else
|
|
617
|
+
render(convId, item.event);
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
catch (e) {
|
|
621
|
+
if (!ac.signal.aborted)
|
|
622
|
+
print(`! event stream for ${convId} stopped: ${e.message}`);
|
|
623
|
+
}
|
|
624
|
+
finally {
|
|
625
|
+
followers.delete(convId);
|
|
626
|
+
}
|
|
627
|
+
})();
|
|
628
|
+
};
|
|
629
|
+
const send = async (msg) => {
|
|
630
|
+
const message = { id: newMessageId(), ...msg };
|
|
631
|
+
const target = directId ?? mainId;
|
|
632
|
+
if (!target) {
|
|
633
|
+
message.context = { ...localContext(), user_available: true, ...message.context };
|
|
634
|
+
const r = await conv.start(message);
|
|
635
|
+
mainId = r.conversation_id;
|
|
636
|
+
print(dim(`[conversation ${mainId} — ${r.status}/${r.responder}]`));
|
|
637
|
+
follow(mainId);
|
|
638
|
+
}
|
|
639
|
+
else {
|
|
640
|
+
const r = await conv.send(target, message);
|
|
641
|
+
if (r?.status)
|
|
642
|
+
status = `${r.status}/${r.responder}`;
|
|
643
|
+
}
|
|
644
|
+
};
|
|
645
|
+
out(`Chatting with ${bold(c.document.organization.name)} as ${sender}. /help for commands.`);
|
|
646
|
+
if (mainId)
|
|
647
|
+
follow(mainId);
|
|
648
|
+
rl.setPrompt('> ');
|
|
649
|
+
for (;;) {
|
|
650
|
+
let line;
|
|
651
|
+
try {
|
|
652
|
+
line = (await rl.question(directId ? 'you (direct)> ' : '> ')).trim();
|
|
653
|
+
}
|
|
654
|
+
catch {
|
|
655
|
+
break;
|
|
656
|
+
}
|
|
657
|
+
if (!line)
|
|
658
|
+
continue;
|
|
659
|
+
try {
|
|
660
|
+
if (!line.startsWith('/')) {
|
|
661
|
+
await send({ sender: directId ? 'human' : sender, text: line });
|
|
662
|
+
continue;
|
|
663
|
+
}
|
|
664
|
+
const [cmd, ...rest] = line.split(' ');
|
|
665
|
+
const restText = rest.join(' ').trim();
|
|
666
|
+
if (cmd === '/quit' || cmd === '/exit')
|
|
667
|
+
break;
|
|
668
|
+
if (cmd === '/help')
|
|
669
|
+
out(CHAT_HELP);
|
|
670
|
+
else if (cmd === '/as') {
|
|
671
|
+
sender = restText === 'human' ? 'human' : 'agent';
|
|
672
|
+
out(dim(`now sending as ${sender}`));
|
|
673
|
+
}
|
|
674
|
+
else if (cmd === '/data') {
|
|
675
|
+
await send({ sender: directId ? 'human' : sender, data: JSON.parse(restText) });
|
|
676
|
+
}
|
|
677
|
+
else if (cmd === '/context') {
|
|
678
|
+
const context = {};
|
|
679
|
+
for (const kv of rest) {
|
|
680
|
+
const [k, val] = kv.split('=');
|
|
681
|
+
if (!k)
|
|
682
|
+
continue;
|
|
683
|
+
context[k] = val === 'true' ? true : val === 'false' ? false : val;
|
|
684
|
+
}
|
|
685
|
+
await send({ sender: 'agent', context });
|
|
686
|
+
}
|
|
687
|
+
else if (cmd === '/status') {
|
|
688
|
+
if (!mainId)
|
|
689
|
+
out('no conversation yet');
|
|
690
|
+
else {
|
|
691
|
+
const page = await conv.read(directId ?? mainId, {
|
|
692
|
+
cursor: c.state.conversations[directId ?? mainId]?.cursor || undefined,
|
|
693
|
+
});
|
|
694
|
+
out(`${directId ?? mainId}: ${page.status}/${page.responder}`);
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
else if (cmd === '/handoff') {
|
|
698
|
+
if (!mainId)
|
|
699
|
+
out('no conversation yet');
|
|
700
|
+
else {
|
|
701
|
+
const r = await conv.handoff(mainId);
|
|
702
|
+
out(dim(`[${r.status}/${r.responder}]`));
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
else if (cmd === '/signin') {
|
|
706
|
+
const [method, ...scopeParts] = rest;
|
|
707
|
+
const scope = scopeParts.join(' ') || pendingAuth?.scope || undefined;
|
|
708
|
+
const m = method === 'direct' || method === 'device' || method === 'mediated' ? method : undefined;
|
|
709
|
+
const r = await signIn(ctx, c, m, m ? scope : [method, ...scopeParts].filter(Boolean).join(' ') || scope);
|
|
710
|
+
if (r) {
|
|
711
|
+
pendingAuth = undefined;
|
|
712
|
+
out(dim('Continue the conversation; the new Session Token is used from now on.'));
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
else if (cmd === '/direct') {
|
|
716
|
+
if (!mainId)
|
|
717
|
+
out('no conversation yet');
|
|
718
|
+
else {
|
|
719
|
+
const message = {
|
|
720
|
+
id: newMessageId(),
|
|
721
|
+
sender: 'human',
|
|
722
|
+
text: restText || 'Hi, I am here.',
|
|
723
|
+
context: { user_available: true },
|
|
724
|
+
};
|
|
725
|
+
const r = await conv.start(message, { parentConversationId: mainId });
|
|
726
|
+
directId = r.conversation_id;
|
|
727
|
+
pendingUserRequest = undefined;
|
|
728
|
+
print(dim(`[direct conversation ${directId} — you are now talking as the User]`));
|
|
729
|
+
follow(directId);
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
else if (cmd === '/decline') {
|
|
733
|
+
if (!mainId)
|
|
734
|
+
out('no conversation yet');
|
|
735
|
+
else {
|
|
736
|
+
await conv.send(mainId, {
|
|
737
|
+
id: newMessageId(),
|
|
738
|
+
sender: 'agent',
|
|
739
|
+
text: pendingUserRequest
|
|
740
|
+
? 'The user declined to talk directly right now.'
|
|
741
|
+
: 'The user is not available.',
|
|
742
|
+
context: { user_available: false },
|
|
743
|
+
});
|
|
744
|
+
pendingUserRequest = undefined;
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
else if (cmd === '/leave') {
|
|
748
|
+
if (!directId)
|
|
749
|
+
out('not in a direct conversation');
|
|
750
|
+
else {
|
|
751
|
+
const id = directId;
|
|
752
|
+
await conv.close(id);
|
|
753
|
+
followers.get(id)?.abort();
|
|
754
|
+
directId = undefined;
|
|
755
|
+
out(dim(`[direct conversation ${id} closed]`));
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
else if (cmd === '/close') {
|
|
759
|
+
if (!mainId)
|
|
760
|
+
break;
|
|
761
|
+
const r = await conv.close(mainId);
|
|
762
|
+
out(dim(`[${r.status}/${r.responder}]`));
|
|
763
|
+
break;
|
|
764
|
+
}
|
|
765
|
+
else
|
|
766
|
+
out(`unknown command ${cmd}; /help`);
|
|
767
|
+
}
|
|
768
|
+
catch (e) {
|
|
769
|
+
out(`! ${e.message}`);
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
for (const ac of followers.values())
|
|
773
|
+
ac.abort();
|
|
774
|
+
return 0;
|
|
775
|
+
}
|
|
776
|
+
//# sourceMappingURL=main.js.map
|