@usenaive-sdk/cli 0.15.1 → 1.0.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/README.md +43 -402
- package/dist/index.js +51796 -502
- package/package.json +22 -18
- package/dist/client.d.ts +0 -108
- package/dist/client.js +0 -458
- package/dist/client.js.map +0 -1
- package/dist/commands/account-kits.d.ts +0 -2
- package/dist/commands/account-kits.js +0 -71
- package/dist/commands/account-kits.js.map +0 -1
- package/dist/commands/aeo.d.ts +0 -2
- package/dist/commands/aeo.js +0 -451
- package/dist/commands/aeo.js.map +0 -1
- package/dist/commands/agent-profiles.d.ts +0 -11
- package/dist/commands/agent-profiles.js +0 -120
- package/dist/commands/agent-profiles.js.map +0 -1
- package/dist/commands/agents.d.ts +0 -2
- package/dist/commands/agents.js +0 -916
- package/dist/commands/agents.js.map +0 -1
- package/dist/commands/app-data.d.ts +0 -2
- package/dist/commands/app-data.js +0 -445
- package/dist/commands/app-data.js.map +0 -1
- package/dist/commands/approvals.d.ts +0 -2
- package/dist/commands/approvals.js +0 -242
- package/dist/commands/approvals.js.map +0 -1
- package/dist/commands/apps.d.ts +0 -9
- package/dist/commands/apps.js +0 -844
- package/dist/commands/apps.js.map +0 -1
- package/dist/commands/audio.d.ts +0 -9
- package/dist/commands/audio.js +0 -391
- package/dist/commands/audio.js.map +0 -1
- package/dist/commands/auth.d.ts +0 -31
- package/dist/commands/auth.js +0 -428
- package/dist/commands/auth.js.map +0 -1
- package/dist/commands/billing.d.ts +0 -2
- package/dist/commands/billing.js +0 -250
- package/dist/commands/billing.js.map +0 -1
- package/dist/commands/brain.d.ts +0 -2
- package/dist/commands/brain.js +0 -1300
- package/dist/commands/brain.js.map +0 -1
- package/dist/commands/browser.d.ts +0 -2
- package/dist/commands/browser.js +0 -378
- package/dist/commands/browser.js.map +0 -1
- package/dist/commands/business.d.ts +0 -2
- package/dist/commands/business.js +0 -566
- package/dist/commands/business.js.map +0 -1
- package/dist/commands/cards.d.ts +0 -2
- package/dist/commands/cards.js +0 -419
- package/dist/commands/cards.js.map +0 -1
- package/dist/commands/ceo.d.ts +0 -2
- package/dist/commands/ceo.js +0 -369
- package/dist/commands/ceo.js.map +0 -1
- package/dist/commands/clone.d.ts +0 -2
- package/dist/commands/clone.js +0 -203
- package/dist/commands/clone.js.map +0 -1
- package/dist/commands/companies.d.ts +0 -10
- package/dist/commands/companies.js +0 -283
- package/dist/commands/companies.js.map +0 -1
- package/dist/commands/company-data.d.ts +0 -2
- package/dist/commands/company-data.js +0 -168
- package/dist/commands/company-data.js.map +0 -1
- package/dist/commands/compute.d.ts +0 -2
- package/dist/commands/compute.js +0 -498
- package/dist/commands/compute.js.map +0 -1
- package/dist/commands/config.d.ts +0 -2
- package/dist/commands/config.js +0 -36
- package/dist/commands/config.js.map +0 -1
- package/dist/commands/connections.d.ts +0 -8
- package/dist/commands/connections.js +0 -137
- package/dist/commands/connections.js.map +0 -1
- package/dist/commands/cron-jobs.d.ts +0 -2
- package/dist/commands/cron-jobs.js +0 -245
- package/dist/commands/cron-jobs.js.map +0 -1
- package/dist/commands/domains.d.ts +0 -2
- package/dist/commands/domains.js +0 -474
- package/dist/commands/domains.js.map +0 -1
- package/dist/commands/ecommerce.d.ts +0 -2
- package/dist/commands/ecommerce.js +0 -277
- package/dist/commands/ecommerce.js.map +0 -1
- package/dist/commands/email.d.ts +0 -2
- package/dist/commands/email.js +0 -336
- package/dist/commands/email.js.map +0 -1
- package/dist/commands/employees.d.ts +0 -2
- package/dist/commands/employees.js +0 -190
- package/dist/commands/employees.js.map +0 -1
- package/dist/commands/env.d.ts +0 -7
- package/dist/commands/env.js +0 -112
- package/dist/commands/env.js.map +0 -1
- package/dist/commands/formation.d.ts +0 -2
- package/dist/commands/formation.js +0 -273
- package/dist/commands/formation.js.map +0 -1
- package/dist/commands/identity.d.ts +0 -2
- package/dist/commands/identity.js +0 -110
- package/dist/commands/identity.js.map +0 -1
- package/dist/commands/images.d.ts +0 -2
- package/dist/commands/images.js +0 -371
- package/dist/commands/images.js.map +0 -1
- package/dist/commands/init.d.ts +0 -5
- package/dist/commands/init.js +0 -43
- package/dist/commands/init.js.map +0 -1
- package/dist/commands/jobs.d.ts +0 -2
- package/dist/commands/jobs.js +0 -144
- package/dist/commands/jobs.js.map +0 -1
- package/dist/commands/keys.d.ts +0 -2
- package/dist/commands/keys.js +0 -156
- package/dist/commands/keys.js.map +0 -1
- package/dist/commands/legacy-auth.d.ts +0 -27
- package/dist/commands/legacy-auth.js +0 -64
- package/dist/commands/legacy-auth.js.map +0 -1
- package/dist/commands/llm.d.ts +0 -2
- package/dist/commands/llm.js +0 -125
- package/dist/commands/llm.js.map +0 -1
- package/dist/commands/login.d.ts +0 -2
- package/dist/commands/login.js +0 -65
- package/dist/commands/login.js.map +0 -1
- package/dist/commands/logout.d.ts +0 -7
- package/dist/commands/logout.js +0 -55
- package/dist/commands/logout.js.map +0 -1
- package/dist/commands/logs.d.ts +0 -2
- package/dist/commands/logs.js +0 -42
- package/dist/commands/logs.js.map +0 -1
- package/dist/commands/loops.d.ts +0 -2
- package/dist/commands/loops.js +0 -260
- package/dist/commands/loops.js.map +0 -1
- package/dist/commands/media.d.ts +0 -2
- package/dist/commands/media.js +0 -183
- package/dist/commands/media.js.map +0 -1
- package/dist/commands/memory.d.ts +0 -2
- package/dist/commands/memory.js +0 -151
- package/dist/commands/memory.js.map +0 -1
- package/dist/commands/mobile.d.ts +0 -4
- package/dist/commands/mobile.js +0 -382
- package/dist/commands/mobile.js.map +0 -1
- package/dist/commands/module.d.ts +0 -8
- package/dist/commands/module.js +0 -48
- package/dist/commands/module.js.map +0 -1
- package/dist/commands/objectives.d.ts +0 -2
- package/dist/commands/objectives.js +0 -243
- package/dist/commands/objectives.js.map +0 -1
- package/dist/commands/payments.d.ts +0 -2
- package/dist/commands/payments.js +0 -131
- package/dist/commands/payments.js.map +0 -1
- package/dist/commands/people.d.ts +0 -2
- package/dist/commands/people.js +0 -197
- package/dist/commands/people.js.map +0 -1
- package/dist/commands/phone.d.ts +0 -2
- package/dist/commands/phone.js +0 -289
- package/dist/commands/phone.js.map +0 -1
- package/dist/commands/projects.d.ts +0 -13
- package/dist/commands/projects.js +0 -220
- package/dist/commands/projects.js.map +0 -1
- package/dist/commands/queue.d.ts +0 -2
- package/dist/commands/queue.js +0 -121
- package/dist/commands/queue.js.map +0 -1
- package/dist/commands/register.d.ts +0 -2
- package/dist/commands/register.js +0 -88
- package/dist/commands/register.js.map +0 -1
- package/dist/commands/report.d.ts +0 -17
- package/dist/commands/report.js +0 -109
- package/dist/commands/report.js.map +0 -1
- package/dist/commands/sandbox.d.ts +0 -2
- package/dist/commands/sandbox.js +0 -558
- package/dist/commands/sandbox.js.map +0 -1
- package/dist/commands/search.d.ts +0 -2
- package/dist/commands/search.js +0 -223
- package/dist/commands/search.js.map +0 -1
- package/dist/commands/seo.d.ts +0 -2
- package/dist/commands/seo.js +0 -528
- package/dist/commands/seo.js.map +0 -1
- package/dist/commands/sessions.d.ts +0 -6
- package/dist/commands/sessions.js +0 -109
- package/dist/commands/sessions.js.map +0 -1
- package/dist/commands/social-data.d.ts +0 -2
- package/dist/commands/social-data.js +0 -245
- package/dist/commands/social-data.js.map +0 -1
- package/dist/commands/social.d.ts +0 -2
- package/dist/commands/social.js +0 -407
- package/dist/commands/social.js.map +0 -1
- package/dist/commands/status.d.ts +0 -2
- package/dist/commands/status.js +0 -79
- package/dist/commands/status.js.map +0 -1
- package/dist/commands/support.d.ts +0 -2
- package/dist/commands/support.js +0 -134
- package/dist/commands/support.js.map +0 -1
- package/dist/commands/tasks.d.ts +0 -2
- package/dist/commands/tasks.js +0 -409
- package/dist/commands/tasks.js.map +0 -1
- package/dist/commands/teams.d.ts +0 -69
- package/dist/commands/teams.js +0 -503
- package/dist/commands/teams.js.map +0 -1
- package/dist/commands/trading.d.ts +0 -2
- package/dist/commands/trading.js +0 -333
- package/dist/commands/trading.js.map +0 -1
- package/dist/commands/up.d.ts +0 -551
- package/dist/commands/up.js +0 -1149
- package/dist/commands/up.js.map +0 -1
- package/dist/commands/usage.d.ts +0 -2
- package/dist/commands/usage.js +0 -73
- package/dist/commands/usage.js.map +0 -1
- package/dist/commands/use.d.ts +0 -12
- package/dist/commands/use.js +0 -96
- package/dist/commands/use.js.map +0 -1
- package/dist/commands/users.d.ts +0 -10
- package/dist/commands/users.js +0 -82
- package/dist/commands/users.js.map +0 -1
- package/dist/commands/vault.d.ts +0 -2
- package/dist/commands/vault.js +0 -101
- package/dist/commands/vault.js.map +0 -1
- package/dist/commands/verification.d.ts +0 -2
- package/dist/commands/verification.js +0 -152
- package/dist/commands/verification.js.map +0 -1
- package/dist/commands/verify-email.d.ts +0 -19
- package/dist/commands/verify-email.js +0 -130
- package/dist/commands/verify-email.js.map +0 -1
- package/dist/commands/video.d.ts +0 -2
- package/dist/commands/video.js +0 -417
- package/dist/commands/video.js.map +0 -1
- package/dist/commands/voice.d.ts +0 -2
- package/dist/commands/voice.js +0 -516
- package/dist/commands/voice.js.map +0 -1
- package/dist/commands/wallet.d.ts +0 -2
- package/dist/commands/wallet.js +0 -244
- package/dist/commands/wallet.js.map +0 -1
- package/dist/commands/webhooks.d.ts +0 -13
- package/dist/commands/webhooks.js +0 -119
- package/dist/commands/webhooks.js.map +0 -1
- package/dist/commands/whoami.d.ts +0 -2
- package/dist/commands/whoami.js +0 -53
- package/dist/commands/whoami.js.map +0 -1
- package/dist/config.d.ts +0 -42
- package/dist/config.js +0 -83
- package/dist/config.js.map +0 -1
- package/dist/index.d.ts +0 -3
- package/dist/index.js.map +0 -1
- package/dist/lib/brain-select.d.ts +0 -193
- package/dist/lib/brain-select.js +0 -403
- package/dist/lib/brain-select.js.map +0 -1
- package/dist/lib/cli-validation.d.ts +0 -12
- package/dist/lib/cli-validation.js +0 -46
- package/dist/lib/cli-validation.js.map +0 -1
- package/dist/lib/config-errors.d.ts +0 -4
- package/dist/lib/config-errors.js +0 -13
- package/dist/lib/config-errors.js.map +0 -1
- package/dist/lib/deprecation.d.ts +0 -158
- package/dist/lib/deprecation.js +0 -140
- package/dist/lib/deprecation.js.map +0 -1
- package/dist/lib/duration.d.ts +0 -8
- package/dist/lib/duration.js +0 -11
- package/dist/lib/duration.js.map +0 -1
- package/dist/lib/http-response.d.ts +0 -11
- package/dist/lib/http-response.js +0 -96
- package/dist/lib/http-response.js.map +0 -1
- package/dist/lib/llm-paywall.d.ts +0 -82
- package/dist/lib/llm-paywall.js +0 -231
- package/dist/lib/llm-paywall.js.map +0 -1
- package/dist/lib/pricing.d.ts +0 -132
- package/dist/lib/pricing.js +0 -148
- package/dist/lib/pricing.js.map +0 -1
- package/dist/lib/starter-config.d.ts +0 -13
- package/dist/lib/starter-config.js +0 -71
- package/dist/lib/starter-config.js.map +0 -1
- package/dist/lib/stream-frames.d.ts +0 -114
- package/dist/lib/stream-frames.js +0 -169
- package/dist/lib/stream-frames.js.map +0 -1
- package/dist/lib/teams-catalog.d.ts +0 -141
- package/dist/lib/teams-catalog.js +0 -841
- package/dist/lib/teams-catalog.js.map +0 -1
- package/dist/lib/unknown-command.d.ts +0 -71
- package/dist/lib/unknown-command.js +0 -132
- package/dist/lib/unknown-command.js.map +0 -1
- package/dist/lib/version-check.d.ts +0 -26
- package/dist/lib/version-check.js +0 -21
- package/dist/lib/version-check.js.map +0 -1
- package/dist/output.d.ts +0 -246
- package/dist/output.js +0 -539
- package/dist/output.js.map +0 -1
- package/dist/prompt.d.ts +0 -17
- package/dist/prompt.js +0 -70
- package/dist/prompt.js.map +0 -1
- package/dist/version.d.ts +0 -2
- package/dist/version.js +0 -14
- package/dist/version.js.map +0 -1
package/dist/commands/brain.js
DELETED
|
@@ -1,1300 +0,0 @@
|
|
|
1
|
-
import { Command } from "commander";
|
|
2
|
-
import { readFileSync } from "node:fs";
|
|
3
|
-
import { basename, extname } from "node:path";
|
|
4
|
-
import { apiRequest, handleApiError, handlePendingApproval } from "../client.js";
|
|
5
|
-
import { agentError, agentOutput } from "../output.js";
|
|
6
|
-
import { BRAIN_BINDING_KEY, BRAIN_CONNECT_PATH, bindingNextSteps, boundBrainId, listBrains, resolveAgent, resolveBrainId, selectBrainId, setAgentBrain, tryListAgents, } from "../lib/brain-select.js";
|
|
7
|
-
/**
|
|
8
|
-
* Normalise a command's brain-selection flags into a selector.
|
|
9
|
-
*
|
|
10
|
-
* `|| undefined` and not `?? undefined` ON PURPOSE: every one of these
|
|
11
|
-
* subcommands previously wrote `...(opts.kb && { knowledge_base_id: opts.kb })`,
|
|
12
|
-
* so an EMPTY `--kb ""` was silently dropped and the call fell through to the
|
|
13
|
-
* company default. Mapping "" to undefined keeps that exact behaviour instead of
|
|
14
|
-
* turning a pathological-but-working invocation into an error.
|
|
15
|
-
*/
|
|
16
|
-
function brainSelector(opts) {
|
|
17
|
-
return { brain: opts.brain || undefined, kb: opts.kb || undefined, agent: opts.agent || undefined };
|
|
18
|
-
}
|
|
19
|
-
/**
|
|
20
|
-
* The three flags that name a brain, added to every subcommand that already
|
|
21
|
-
* scoped its call with `--kb`.
|
|
22
|
-
*
|
|
23
|
-
* `--kb <id>` is UNCHANGED and stays first in the help: a UUID passes straight
|
|
24
|
-
* through with no extra request, exactly as it always has. `--brain` is the same
|
|
25
|
-
* concept spelled the way the command group is (`naive brain …`) and additionally
|
|
26
|
-
* accepts a name. `--agent` means "whatever brain that agent is connected to".
|
|
27
|
-
*/
|
|
28
|
-
function withBrainFlags(cmd) {
|
|
29
|
-
return cmd
|
|
30
|
-
.option("--brain <name|id>", "Use this brain (by name or id)")
|
|
31
|
-
.option("--agent <id|name>", "Use the brain this agent is connected to");
|
|
32
|
-
}
|
|
33
|
-
/** Trivial extension -> MIME map; anything else omits content_type and lets the API decide. */
|
|
34
|
-
const CONTENT_TYPES = {
|
|
35
|
-
".pdf": "application/pdf",
|
|
36
|
-
".txt": "text/plain",
|
|
37
|
-
".md": "text/markdown",
|
|
38
|
-
".csv": "text/csv",
|
|
39
|
-
".json": "application/json",
|
|
40
|
-
".html": "text/html",
|
|
41
|
-
".htm": "text/html",
|
|
42
|
-
".png": "image/png",
|
|
43
|
-
".jpg": "image/jpeg",
|
|
44
|
-
".jpeg": "image/jpeg",
|
|
45
|
-
".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
46
|
-
};
|
|
47
|
-
export const brainCmd = new Command("brain")
|
|
48
|
-
.description("Naive Brain — centralized knowledge base + RAG. Ingest docs, then ask grounded questions")
|
|
49
|
-
.addHelpText("after", `
|
|
50
|
-
Subcommands:
|
|
51
|
-
naive brain query <question> Ask the knowledge base (grounded answer + citations)
|
|
52
|
-
naive brain recall <query> Recall structured semantic memory before work
|
|
53
|
-
naive brain remember <fact> Write a durable semantic memory episode after work
|
|
54
|
-
naive brain think <query> Synthesize from memory + docs with gap notes
|
|
55
|
-
naive brain graph Explore the entity/relation knowledge graph
|
|
56
|
-
naive brain timeline View memory events/facts in chronological order
|
|
57
|
-
naive brain forget <scope> <ref> Tombstone/forget a semantic memory scope
|
|
58
|
-
naive brain add --file <path> Ingest a file into the knowledge base
|
|
59
|
-
naive brain add --text "..." Ingest raw text
|
|
60
|
-
naive brain add --url <url> Ingest a web page
|
|
61
|
-
naive brain note <fact> Ingest a short fact as a document (see note vs remember below)
|
|
62
|
-
naive brain replace-doc <id> Replace/refresh a document in place
|
|
63
|
-
naive brain docs List ingested documents + processing status
|
|
64
|
-
naive brain doc <id> Get one document (refreshed processing status)
|
|
65
|
-
naive brain rm-doc <id> Delete an ingested document (permanent)
|
|
66
|
-
naive brain list Every brain: which is default, and who is connected
|
|
67
|
-
naive brain create <name> Create a new knowledge base
|
|
68
|
-
naive brain default <brain> Make an existing brain the company default
|
|
69
|
-
naive brain rm <id> Delete a knowledge base + its documents (permanent)
|
|
70
|
-
naive brain connect <brain> --agent <a> Connect an agent to a brain
|
|
71
|
-
naive brain disconnect --agent <a> Disconnect it again
|
|
72
|
-
|
|
73
|
-
A company can have SEVERAL brains (the schema has always allowed it: one row per
|
|
74
|
-
brain, one flagged default). Every read/write subcommand takes the brain three ways:
|
|
75
|
-
--kb <id> the shipped spelling — an id, passed straight through
|
|
76
|
-
--brain <name|id> the same thing, and it accepts a NAME
|
|
77
|
-
--agent <id|name> whatever brain that agent is connected to
|
|
78
|
-
Pass none and you get the company default, exactly as before.
|
|
79
|
-
|
|
80
|
-
CHOOSING THE DEFAULT:
|
|
81
|
-
naive brain default <name|id> Promote an existing brain. One request, no new
|
|
82
|
-
brain, idempotent, and only an ACTIVE brain can
|
|
83
|
-
be promoted.
|
|
84
|
-
naive brain create X --default Create one AND make it the default.
|
|
85
|
-
naive brain rm <the default> Succeeds, and promotes the oldest survivor in
|
|
86
|
-
the same transaction — the empty 204 cannot say
|
|
87
|
-
which, so run 'naive brain list' after.
|
|
88
|
-
|
|
89
|
-
The run loop (attach → work → consolidate → decide):
|
|
90
|
-
naive brain attach <goal> What the brain already knows, BEFORE the work
|
|
91
|
-
naive brain consolidate Run-end debrief: learned / decided / left open
|
|
92
|
-
naive brain proposals [id] The proposal queue; read one, or decide one
|
|
93
|
-
naive brain writebacks [id] The envelopes a run produced
|
|
94
|
-
naive brain status Operator view of the brain runtime (this company)
|
|
95
|
-
naive brain metrics Nervous-system counters (PROCESS-GLOBAL, not per-company)
|
|
96
|
-
|
|
97
|
-
A debrief PROPOSES. Nothing it says is a belief until a proposal is accepted:
|
|
98
|
-
$ naive brain consolidate --objective "refund audit" --learned "acme|refund window|30 days"
|
|
99
|
-
$ naive brain proposals --status pending
|
|
100
|
-
$ naive brain proposals prp_01J --accept --because "confirmed against the contract"
|
|
101
|
-
|
|
102
|
-
note vs remember:
|
|
103
|
-
'remember' writes a durable SEMANTIC MEMORY episode (facts/entities/claims are
|
|
104
|
-
extracted and recallable via 'recall'/'think'/'graph'/'timeline').
|
|
105
|
-
'note' ingests the text as a DOCUMENT into the knowledge base (chunked +
|
|
106
|
-
embedded, answerable via 'query'). Use 'remember' for durable facts and
|
|
107
|
-
'note'/'add' for source material.
|
|
108
|
-
|
|
109
|
-
Workflow:
|
|
110
|
-
1. naive brain add --file ./handbook.pdf → ingest a document
|
|
111
|
-
2. naive brain add --text "Our refund policy is 30 days"
|
|
112
|
-
3. naive brain docs → confirm processing is 'ready'
|
|
113
|
-
4. naive brain query "What is our refund policy?"
|
|
114
|
-
|
|
115
|
-
Naive Brain is your company's central, shared knowledge system. Both your own
|
|
116
|
-
agents and Naive agents can read from it via the CLI, SDK, or MCP. Use it for
|
|
117
|
-
company-specific knowledge (docs, policies, decisions) — for the open web use
|
|
118
|
-
'naive search' instead.
|
|
119
|
-
|
|
120
|
-
Examples:
|
|
121
|
-
$ naive brain add --file ./product-spec.md
|
|
122
|
-
$ naive brain query "What are the P0 requirements in the spec?"
|
|
123
|
-
$ naive brain query "And the P1s?" --session <session_id from the previous answer>
|
|
124
|
-
$ naive brain docs
|
|
125
|
-
`);
|
|
126
|
-
withBrainFlags(brainCmd
|
|
127
|
-
.command("recall <query>")
|
|
128
|
-
.description("Recall structured semantic memory: facts, entities, and relevant episodes")
|
|
129
|
-
.option("--kb <id>", "Recall from a specific knowledge base")
|
|
130
|
-
.option("--entity <name>", "Filter to one entity/subject")
|
|
131
|
-
.option("--limit <n>", "Maximum memories to return", (v) => Number.parseInt(v, 10)))
|
|
132
|
-
.action(async (query, opts) => {
|
|
133
|
-
const kbId = await selectBrainId("brain.recall", brainSelector(opts));
|
|
134
|
-
const resp = await apiRequest("POST", "/v1/brain/recall", {
|
|
135
|
-
query,
|
|
136
|
-
...(kbId && { knowledge_base_id: kbId }),
|
|
137
|
-
...(opts.entity && { entity: opts.entity }),
|
|
138
|
-
...(opts.limit && { limit: opts.limit }),
|
|
139
|
-
});
|
|
140
|
-
handleApiError("brain.recall", resp);
|
|
141
|
-
agentOutput({
|
|
142
|
-
action: "brain.recall",
|
|
143
|
-
result: resp.data,
|
|
144
|
-
next_steps: [
|
|
145
|
-
{ command: `naive brain think "${query}"`, description: "Synthesize memory with grounded document context" },
|
|
146
|
-
{ command: 'naive brain remember "..."', description: "Record a durable fact after work" },
|
|
147
|
-
],
|
|
148
|
-
hints: ["Structured memory recalled from the company brain."],
|
|
149
|
-
related_commands: ["naive brain think", "naive brain remember", "naive brain query"],
|
|
150
|
-
});
|
|
151
|
-
});
|
|
152
|
-
withBrainFlags(brainCmd
|
|
153
|
-
.command("remember <content>")
|
|
154
|
-
.alias("mem")
|
|
155
|
-
.description("Write a durable semantic memory episode into the shared company brain")
|
|
156
|
-
.option("--title <title>", "Optional title")
|
|
157
|
-
.option("--source <source>", "Provenance tag (default: cli)")
|
|
158
|
-
.option("--confidence <n>", "0-1 confidence", (v) => Number.parseFloat(v))
|
|
159
|
-
.option("--kb <id>", "Target a specific knowledge base"))
|
|
160
|
-
.action(async (content, opts) => {
|
|
161
|
-
const kbId = await selectBrainId("brain.remember", brainSelector(opts));
|
|
162
|
-
const resp = await apiRequest("POST", "/v1/brain/remember", {
|
|
163
|
-
content,
|
|
164
|
-
...(opts.title && { title: opts.title }),
|
|
165
|
-
source: opts.source ?? "cli",
|
|
166
|
-
...(opts.confidence !== undefined && { confidence: opts.confidence }),
|
|
167
|
-
...(kbId && { knowledge_base_id: kbId }),
|
|
168
|
-
});
|
|
169
|
-
handleApiError("brain.remember", resp);
|
|
170
|
-
agentOutput({
|
|
171
|
-
action: "brain.remember",
|
|
172
|
-
result: resp.data,
|
|
173
|
-
next_steps: [{ command: `naive brain recall "${content.slice(0, 80)}"`, description: "Verify the memory is recallable" }],
|
|
174
|
-
hints: ["Durable semantic memory recorded."],
|
|
175
|
-
related_commands: ["naive brain recall", "naive brain think"],
|
|
176
|
-
});
|
|
177
|
-
});
|
|
178
|
-
withBrainFlags(brainCmd
|
|
179
|
-
.command("think <query>")
|
|
180
|
-
.description("Synthesize from semantic memory and grounded Brain documents with gap notes")
|
|
181
|
-
.option("--kb <id>", "Use a specific knowledge base")
|
|
182
|
-
.option("--session <id>", "Continue a query session")
|
|
183
|
-
.option("--limit <n>", "Maximum memories to include", (v) => Number.parseInt(v, 10)))
|
|
184
|
-
.action(async (query, opts) => {
|
|
185
|
-
const kbId = await selectBrainId("brain.think", brainSelector(opts));
|
|
186
|
-
const resp = await apiRequest("POST", "/v1/brain/think", {
|
|
187
|
-
query,
|
|
188
|
-
...(kbId && { knowledge_base_id: kbId }),
|
|
189
|
-
...(opts.session && { session_id: opts.session }),
|
|
190
|
-
...(opts.limit && { limit: opts.limit }),
|
|
191
|
-
});
|
|
192
|
-
handleApiError("brain.think", resp);
|
|
193
|
-
agentOutput({
|
|
194
|
-
action: "brain.think",
|
|
195
|
-
result: resp.data,
|
|
196
|
-
next_steps: [{ command: 'naive brain remember "..."', description: "Record any durable outcome from this work" }],
|
|
197
|
-
hints: ["Answer synthesized from semantic memory and grounded documents."],
|
|
198
|
-
related_commands: ["naive brain recall", "naive brain query", "naive brain remember"],
|
|
199
|
-
});
|
|
200
|
-
});
|
|
201
|
-
withBrainFlags(brainCmd
|
|
202
|
-
.command("graph")
|
|
203
|
-
.description("Explore the company brain's entity/relation knowledge graph")
|
|
204
|
-
.option("--kb <id>", "Use a specific knowledge base")
|
|
205
|
-
.option("--entity <name>", "Center the graph on one entity/subject")
|
|
206
|
-
.option("--limit <n>", "Maximum nodes/edges to return", (v) => Number.parseInt(v, 10)))
|
|
207
|
-
.action(async (opts) => {
|
|
208
|
-
const kbId = await selectBrainId("brain.graph", brainSelector(opts));
|
|
209
|
-
const resp = await apiRequest("POST", "/v1/brain/graph", {
|
|
210
|
-
...(kbId && { knowledge_base_id: kbId }),
|
|
211
|
-
...(opts.entity && { entity: opts.entity }),
|
|
212
|
-
...(opts.limit && { limit: opts.limit }),
|
|
213
|
-
});
|
|
214
|
-
handleApiError("brain.graph", resp);
|
|
215
|
-
agentOutput({
|
|
216
|
-
action: "brain.graph",
|
|
217
|
-
result: resp.data,
|
|
218
|
-
next_steps: [
|
|
219
|
-
{ command: "naive brain timeline", description: "See how these facts evolved over time" },
|
|
220
|
-
{ command: 'naive brain recall "..."', description: "Recall structured memory for an entity" },
|
|
221
|
-
],
|
|
222
|
-
hints: ["Entity/relation graph derived from the company brain's semantic memory."],
|
|
223
|
-
related_commands: ["naive brain timeline", "naive brain recall", "naive brain think"],
|
|
224
|
-
});
|
|
225
|
-
});
|
|
226
|
-
withBrainFlags(brainCmd
|
|
227
|
-
.command("timeline")
|
|
228
|
-
.description("View the company brain's memory events and facts in chronological order")
|
|
229
|
-
.option("--kb <id>", "Use a specific knowledge base")
|
|
230
|
-
.option("--entity <name>", "Filter to one entity/subject")
|
|
231
|
-
.option("--limit <n>", "Maximum entries to return", (v) => Number.parseInt(v, 10)))
|
|
232
|
-
.action(async (opts) => {
|
|
233
|
-
const kbId = await selectBrainId("brain.timeline", brainSelector(opts));
|
|
234
|
-
const resp = await apiRequest("POST", "/v1/brain/timeline", {
|
|
235
|
-
...(kbId && { knowledge_base_id: kbId }),
|
|
236
|
-
...(opts.entity && { entity: opts.entity }),
|
|
237
|
-
...(opts.limit && { limit: opts.limit }),
|
|
238
|
-
});
|
|
239
|
-
handleApiError("brain.timeline", resp);
|
|
240
|
-
agentOutput({
|
|
241
|
-
action: "brain.timeline",
|
|
242
|
-
result: resp.data,
|
|
243
|
-
next_steps: [
|
|
244
|
-
{ command: "naive brain graph", description: "Explore the entity/relation graph" },
|
|
245
|
-
{ command: 'naive brain think "..."', description: "Synthesize memory + docs with gap notes" },
|
|
246
|
-
],
|
|
247
|
-
hints: ["Chronological memory events/facts from the company brain."],
|
|
248
|
-
related_commands: ["naive brain graph", "naive brain recall", "naive brain think"],
|
|
249
|
-
});
|
|
250
|
-
});
|
|
251
|
-
withBrainFlags(brainCmd
|
|
252
|
-
.command("forget <scope> <scope_ref>")
|
|
253
|
-
.description("Tombstone/forget a semantic memory scope")
|
|
254
|
-
.option("--kb <id>", "Knowledge base id")
|
|
255
|
-
.option("--reason <reason>", "Reason for the tombstone"))
|
|
256
|
-
.action(async (scope, scope_ref, opts) => {
|
|
257
|
-
const kbId = await selectBrainId("brain.forget", brainSelector(opts));
|
|
258
|
-
const resp = await apiRequest("POST", "/v1/brain/forget", {
|
|
259
|
-
scope,
|
|
260
|
-
scope_ref,
|
|
261
|
-
...(kbId && { knowledge_base_id: kbId }),
|
|
262
|
-
...(opts.reason && { reason: opts.reason }),
|
|
263
|
-
});
|
|
264
|
-
handleApiError("brain.forget", resp);
|
|
265
|
-
if (handlePendingApproval("brain.forget", resp))
|
|
266
|
-
return;
|
|
267
|
-
agentOutput({
|
|
268
|
-
action: "brain.forget",
|
|
269
|
-
result: resp.data,
|
|
270
|
-
next_steps: [{ command: "naive brain recall <query>", description: "Confirm forgotten memory no longer appears" }],
|
|
271
|
-
hints: ["Semantic memory tombstone written; derived projections will purge the scope."],
|
|
272
|
-
related_commands: ["naive brain recall", "naive brain docs"],
|
|
273
|
-
});
|
|
274
|
-
});
|
|
275
|
-
withBrainFlags(brainCmd
|
|
276
|
-
.command("query <question>")
|
|
277
|
-
.description("Ask the knowledge base a question; returns a grounded answer with citations")
|
|
278
|
-
.option("--kb <id>", "Query a specific knowledge base (defaults to the company brain)")
|
|
279
|
-
.option("--session <id>", "Continue a previous conversation (session_id from an earlier answer)"))
|
|
280
|
-
.action(async (question, opts) => {
|
|
281
|
-
const kbId = await selectBrainId("brain.query", brainSelector(opts));
|
|
282
|
-
const resp = await apiRequest("POST", "/v1/brain/query", {
|
|
283
|
-
question,
|
|
284
|
-
...(kbId && { knowledge_base_id: kbId }),
|
|
285
|
-
...(opts.session && { session_id: opts.session }),
|
|
286
|
-
});
|
|
287
|
-
handleApiError("brain.query", resp);
|
|
288
|
-
const data = resp.data;
|
|
289
|
-
agentOutput({
|
|
290
|
-
action: "brain.query",
|
|
291
|
-
result: resp.data,
|
|
292
|
-
next_steps: [
|
|
293
|
-
...(data.session_id
|
|
294
|
-
? [{ command: `naive brain query "<follow-up>" --session ${data.session_id}`, description: "Ask a follow-up in the same conversation" }]
|
|
295
|
-
: []),
|
|
296
|
-
{ command: "naive brain add --file <path>", description: "Add more knowledge to improve answers" },
|
|
297
|
-
{ command: "naive brain docs", description: "See what's in the knowledge base" },
|
|
298
|
-
],
|
|
299
|
-
hints: [
|
|
300
|
-
data.answer ? "Answer grounded in the company knowledge base." : "No answer returned — the brain may be empty.",
|
|
301
|
-
`${data.citations?.length ?? 0} citation(s).`,
|
|
302
|
-
],
|
|
303
|
-
related_commands: ["naive brain add", "naive brain docs"],
|
|
304
|
-
});
|
|
305
|
-
});
|
|
306
|
-
withBrainFlags(brainCmd
|
|
307
|
-
.command("add")
|
|
308
|
-
.description("Ingest a document into the knowledge base (--file, --text, or --url)")
|
|
309
|
-
.option("--file <path>", "Ingest a local file")
|
|
310
|
-
.option("--text <text>", "Ingest raw text")
|
|
311
|
-
.option("--url <url>", "Ingest the content at a URL")
|
|
312
|
-
.option("--name <filename>", "Override the stored filename/label")
|
|
313
|
-
.option("--source <source>", "Provenance tag (e.g. upload, web, agent:researcher)")
|
|
314
|
-
.option("--kb <id>", "Target a specific knowledge base"))
|
|
315
|
-
.action(async (opts) => {
|
|
316
|
-
const provided = [opts.file, opts.text, opts.url].filter((v) => v !== undefined).length;
|
|
317
|
-
if (provided !== 1) {
|
|
318
|
-
console.log(JSON.stringify({
|
|
319
|
-
success: false,
|
|
320
|
-
action: "brain.add",
|
|
321
|
-
error: { code: "missing_argument", message: "Provide exactly one of --file, --text, or --url" },
|
|
322
|
-
recovery_steps: [
|
|
323
|
-
{ command: "naive brain add --file ./doc.pdf", description: "Ingest a file" },
|
|
324
|
-
{ command: 'naive brain add --text "..."', description: "Ingest raw text" },
|
|
325
|
-
{ command: "naive brain add --url https://example.com", description: "Ingest a URL" },
|
|
326
|
-
],
|
|
327
|
-
}, null, 2));
|
|
328
|
-
process.exit(1);
|
|
329
|
-
}
|
|
330
|
-
const kbId = await selectBrainId("brain.add", brainSelector(opts));
|
|
331
|
-
const body = {};
|
|
332
|
-
if (kbId)
|
|
333
|
-
body.knowledge_base_id = kbId;
|
|
334
|
-
if (opts.source)
|
|
335
|
-
body.source = opts.source;
|
|
336
|
-
if (opts.file) {
|
|
337
|
-
const buf = readFileSync(opts.file);
|
|
338
|
-
body.file_base64 = buf.toString("base64");
|
|
339
|
-
body.filename = opts.name ?? basename(opts.file);
|
|
340
|
-
const contentType = CONTENT_TYPES[extname(opts.file).toLowerCase()];
|
|
341
|
-
if (contentType)
|
|
342
|
-
body.content_type = contentType;
|
|
343
|
-
}
|
|
344
|
-
else if (opts.url) {
|
|
345
|
-
body.url = opts.url;
|
|
346
|
-
if (opts.name)
|
|
347
|
-
body.filename = opts.name;
|
|
348
|
-
}
|
|
349
|
-
else {
|
|
350
|
-
body.text = opts.text;
|
|
351
|
-
if (opts.name)
|
|
352
|
-
body.filename = opts.name;
|
|
353
|
-
}
|
|
354
|
-
const resp = await apiRequest("POST", "/v1/brain/documents", body);
|
|
355
|
-
handleApiError("brain.add", resp);
|
|
356
|
-
const data = resp.data;
|
|
357
|
-
agentOutput({
|
|
358
|
-
action: "brain.add",
|
|
359
|
-
result: resp.data,
|
|
360
|
-
next_steps: [
|
|
361
|
-
...(data.id ? [{ command: `naive brain doc ${data.id}`, description: "Check this document's processing status" }] : []),
|
|
362
|
-
{ command: "naive brain docs", description: "List all documents + statuses" },
|
|
363
|
-
{ command: 'naive brain query "..."', description: "Ask a question once processing is 'ready'" },
|
|
364
|
-
],
|
|
365
|
-
hints: [
|
|
366
|
-
`Document '${data.filename ?? "?"}' ingested (status: ${data.status ?? "processing"}).`,
|
|
367
|
-
"Large documents may take a moment to finish processing before they're queryable.",
|
|
368
|
-
],
|
|
369
|
-
related_commands: ["naive brain query", "naive brain docs", "naive brain doc"],
|
|
370
|
-
});
|
|
371
|
-
});
|
|
372
|
-
withBrainFlags(brainCmd
|
|
373
|
-
.command("note <fact>")
|
|
374
|
-
.description("Ingest a short fact as a DOCUMENT (queryable). For semantic memory use 'remember' instead")
|
|
375
|
-
.option("--source <source>", "Who/what recorded it (default: note)")
|
|
376
|
-
.option("--kb <id>", "Target a specific knowledge base"))
|
|
377
|
-
.action(async (fact, opts) => {
|
|
378
|
-
const kbId = await selectBrainId("brain.note", brainSelector(opts));
|
|
379
|
-
const body = { text: fact, source: opts.source ?? "note" };
|
|
380
|
-
if (kbId)
|
|
381
|
-
body.knowledge_base_id = kbId;
|
|
382
|
-
// Document ingest is NOT approval-gated (unlike deletes/forget), so there is
|
|
383
|
-
// no pending-approval path to handle here.
|
|
384
|
-
const resp = await apiRequest("POST", "/v1/brain/documents", body);
|
|
385
|
-
handleApiError("brain.note", resp);
|
|
386
|
-
agentOutput({
|
|
387
|
-
action: "brain.note",
|
|
388
|
-
result: resp.data,
|
|
389
|
-
next_steps: [
|
|
390
|
-
{ command: 'naive brain query "..."', description: "Ask the company brain a question" },
|
|
391
|
-
{ command: 'naive brain remember "..."', description: "Record a durable semantic memory instead" },
|
|
392
|
-
],
|
|
393
|
-
hints: ["Fact ingested as a document in the company brain — queryable via 'naive brain query'."],
|
|
394
|
-
related_commands: ["naive brain query", "naive brain remember", "naive brain docs"],
|
|
395
|
-
});
|
|
396
|
-
});
|
|
397
|
-
brainCmd
|
|
398
|
-
.command("replace-doc <id>")
|
|
399
|
-
.description("Replace/refresh an existing document in place (--file, --text, or --url)")
|
|
400
|
-
.option("--file <path>", "Replace with a local file")
|
|
401
|
-
.option("--text <text>", "Replace with raw text")
|
|
402
|
-
.option("--url <url>", "Replace with the content at a URL")
|
|
403
|
-
.option("--name <filename>", "Override the stored filename/label")
|
|
404
|
-
.action(async (id, opts) => {
|
|
405
|
-
const provided = [opts.file, opts.text, opts.url].filter((v) => v !== undefined).length;
|
|
406
|
-
if (provided !== 1) {
|
|
407
|
-
console.log(JSON.stringify({
|
|
408
|
-
success: false,
|
|
409
|
-
action: "brain.replace_doc",
|
|
410
|
-
error: { code: "missing_argument", message: "Provide exactly one of --file, --text, or --url" },
|
|
411
|
-
recovery_steps: [
|
|
412
|
-
{ command: `naive brain replace-doc ${id} --file ./doc.pdf`, description: "Replace with a file" },
|
|
413
|
-
{ command: `naive brain replace-doc ${id} --text "..."`, description: "Replace with raw text" },
|
|
414
|
-
{ command: `naive brain replace-doc ${id} --url https://example.com`, description: "Replace with a URL" },
|
|
415
|
-
],
|
|
416
|
-
}, null, 2));
|
|
417
|
-
process.exit(1);
|
|
418
|
-
}
|
|
419
|
-
const body = {};
|
|
420
|
-
if (opts.file) {
|
|
421
|
-
const buf = readFileSync(opts.file);
|
|
422
|
-
body.file_base64 = buf.toString("base64");
|
|
423
|
-
body.filename = opts.name ?? basename(opts.file);
|
|
424
|
-
const contentType = CONTENT_TYPES[extname(opts.file).toLowerCase()];
|
|
425
|
-
if (contentType)
|
|
426
|
-
body.content_type = contentType;
|
|
427
|
-
}
|
|
428
|
-
else if (opts.url) {
|
|
429
|
-
body.url = opts.url;
|
|
430
|
-
if (opts.name)
|
|
431
|
-
body.filename = opts.name;
|
|
432
|
-
}
|
|
433
|
-
else {
|
|
434
|
-
body.text = opts.text;
|
|
435
|
-
if (opts.name)
|
|
436
|
-
body.filename = opts.name;
|
|
437
|
-
}
|
|
438
|
-
const resp = await apiRequest("PUT", `/v1/brain/documents/${id}`, body);
|
|
439
|
-
handleApiError("brain.replace_doc", resp);
|
|
440
|
-
const data = resp.data;
|
|
441
|
-
agentOutput({
|
|
442
|
-
action: "brain.replace_doc",
|
|
443
|
-
result: resp.data,
|
|
444
|
-
next_steps: [
|
|
445
|
-
{ command: `naive brain doc ${data.id ?? id}`, description: "Check this document's processing status" },
|
|
446
|
-
{ command: "naive brain docs", description: "List all documents + statuses" },
|
|
447
|
-
{ command: 'naive brain query "..."', description: "Ask a question once processing is 'ready'" },
|
|
448
|
-
],
|
|
449
|
-
hints: [
|
|
450
|
-
`Document '${data.filename ?? "?"}' replaced in place (status: ${data.status ?? "processing"}).`,
|
|
451
|
-
"The document keeps the same Naive id while the provider file is swapped.",
|
|
452
|
-
],
|
|
453
|
-
related_commands: ["naive brain doc", "naive brain docs", "naive brain query"],
|
|
454
|
-
});
|
|
455
|
-
});
|
|
456
|
-
withBrainFlags(brainCmd
|
|
457
|
-
.command("docs")
|
|
458
|
-
.description("List ingested documents and their processing status")
|
|
459
|
-
.option("--kb <id>", "Filter by knowledge base"))
|
|
460
|
-
.action(async (opts) => {
|
|
461
|
-
const kbId = await selectBrainId("brain.docs", brainSelector(opts));
|
|
462
|
-
const qs = kbId ? `?knowledge_base_id=${encodeURIComponent(kbId)}` : "";
|
|
463
|
-
const resp = await apiRequest("GET", `/v1/brain/documents${qs}`);
|
|
464
|
-
handleApiError("brain.docs", resp);
|
|
465
|
-
const data = resp.data;
|
|
466
|
-
agentOutput({
|
|
467
|
-
action: "brain.docs",
|
|
468
|
-
result: resp.data,
|
|
469
|
-
next_steps: [
|
|
470
|
-
...(data.documents?.length
|
|
471
|
-
? [{ command: `naive brain doc ${data.documents[0].id}`, description: "Get a document's refreshed status" }]
|
|
472
|
-
: []),
|
|
473
|
-
{ command: "naive brain add --file <path>", description: "Ingest another document" },
|
|
474
|
-
{ command: 'naive brain query "..."', description: "Ask the knowledge base a question" },
|
|
475
|
-
],
|
|
476
|
-
hints: [`${data.count ?? 0} document(s) in the knowledge base.`],
|
|
477
|
-
related_commands: ["naive brain add", "naive brain query", "naive brain doc"],
|
|
478
|
-
});
|
|
479
|
-
});
|
|
480
|
-
brainCmd
|
|
481
|
-
.command("doc <id>")
|
|
482
|
-
.description("Get a single ingested document (refreshes processing status)")
|
|
483
|
-
.action(async (id) => {
|
|
484
|
-
const resp = await apiRequest("GET", `/v1/brain/documents/${id}`);
|
|
485
|
-
handleApiError("brain.doc", resp);
|
|
486
|
-
const data = resp.data;
|
|
487
|
-
agentOutput({
|
|
488
|
-
action: "brain.doc",
|
|
489
|
-
result: resp.data,
|
|
490
|
-
next_steps: data.status === "processing"
|
|
491
|
-
? [{ command: `naive brain doc ${id}`, description: "Check again — still processing" }]
|
|
492
|
-
: [{ command: 'naive brain query "..."', description: "Ask the knowledge base a question" }],
|
|
493
|
-
hints: [
|
|
494
|
-
`Document '${data.filename ?? "?"}' is ${data.status ?? "unknown"}.`,
|
|
495
|
-
],
|
|
496
|
-
related_commands: ["naive brain docs", "naive brain rm-doc", "naive brain query"],
|
|
497
|
-
});
|
|
498
|
-
});
|
|
499
|
-
brainCmd
|
|
500
|
-
.command("rm-doc <id>")
|
|
501
|
-
.description("Delete an ingested document from the knowledge base (permanent)")
|
|
502
|
-
.action(async (id) => {
|
|
503
|
-
const resp = await apiRequest("DELETE", `/v1/brain/documents/${id}`);
|
|
504
|
-
handleApiError("brain.rm_doc", resp);
|
|
505
|
-
if (handlePendingApproval("brain.rm_doc", resp))
|
|
506
|
-
return;
|
|
507
|
-
agentOutput({
|
|
508
|
-
action: "brain.rm_doc",
|
|
509
|
-
result: resp.data ?? { deleted: true, id },
|
|
510
|
-
next_steps: [{ command: "naive brain docs", description: "List remaining documents" }],
|
|
511
|
-
hints: ["Document deleted from the knowledge base."],
|
|
512
|
-
related_commands: ["naive brain docs", "naive brain add"],
|
|
513
|
-
});
|
|
514
|
-
});
|
|
515
|
-
brainCmd
|
|
516
|
-
.command("list")
|
|
517
|
-
.description("Every brain the company has: which is the default, and which agents are connected")
|
|
518
|
-
.option("--no-agents", "Skip the roster read and report brains only")
|
|
519
|
-
.action(async (opts) => {
|
|
520
|
-
const resp = await apiRequest("GET", "/v1/brain");
|
|
521
|
-
handleApiError("brain.list", resp);
|
|
522
|
-
const data = resp.data;
|
|
523
|
-
const brains = data.knowledge_bases ?? [];
|
|
524
|
-
// THE BINDINGS COLUMN. It is an enrichment of a DIFFERENT surface
|
|
525
|
-
// (the agent roster, behind a different primitive), so a failure to
|
|
526
|
-
// read it must never cost the caller the brain list. The reason rides the
|
|
527
|
-
// envelope; a bare empty column would read as "nothing is connected".
|
|
528
|
-
let bindings = {};
|
|
529
|
-
let unavailable;
|
|
530
|
-
let orphaned = [];
|
|
531
|
-
if (opts.agents === false) {
|
|
532
|
-
unavailable = "not read: --no-agents was passed";
|
|
533
|
-
}
|
|
534
|
-
else {
|
|
535
|
-
const roster = await tryListAgents();
|
|
536
|
-
if (!roster.ok) {
|
|
537
|
-
unavailable = roster.because;
|
|
538
|
-
}
|
|
539
|
-
else {
|
|
540
|
-
const known = new Set(brains.map((b) => b.id));
|
|
541
|
-
for (const a of roster.agents) {
|
|
542
|
-
const bound = boundBrainId(a);
|
|
543
|
-
if (!bound)
|
|
544
|
-
continue;
|
|
545
|
-
if (!known.has(bound)) {
|
|
546
|
-
// A binding that points at a deleted brain is a real state, and
|
|
547
|
-
// silently dropping it would hide it forever: `brain rm` cannot
|
|
548
|
-
// reach into the roster, so the key outlives the row.
|
|
549
|
-
orphaned.push({ agent_id: a.id, agent_name: a.name, knowledge_base_id: bound });
|
|
550
|
-
continue;
|
|
551
|
-
}
|
|
552
|
-
(bindings[bound] ??= []).push({ id: a.id, name: a.name });
|
|
553
|
-
}
|
|
554
|
-
}
|
|
555
|
-
}
|
|
556
|
-
const knowledge_bases = brains.map((b) => ({
|
|
557
|
-
...b,
|
|
558
|
-
...(unavailable ? {} : { agents: bindings[b.id] ?? [] }),
|
|
559
|
-
}));
|
|
560
|
-
const defaultBrain = brains.find((b) => b.is_default);
|
|
561
|
-
// Two brains may share a name — the schema has no unique index on
|
|
562
|
-
// (company_id, name) — and when they do, `--brain <that name>` refuses as
|
|
563
|
-
// ambiguous. Reported here because this is the command that already has the
|
|
564
|
-
// whole list in hand; `brain create` deliberately does not pre-flight for it.
|
|
565
|
-
const byName = new Map();
|
|
566
|
-
for (const b of brains) {
|
|
567
|
-
const key = (b.name ?? "").trim().toLowerCase();
|
|
568
|
-
byName.set(key, [...(byName.get(key) ?? []), b.id]);
|
|
569
|
-
}
|
|
570
|
-
const ambiguous = [...byName.entries()]
|
|
571
|
-
.filter(([, ids]) => ids.length > 1)
|
|
572
|
-
.map(([key, ids]) => ({ name: brains.find((b) => b.id === ids[0]).name, ids, normalized: key }));
|
|
573
|
-
agentOutput({
|
|
574
|
-
action: "brain.list",
|
|
575
|
-
result: {
|
|
576
|
-
knowledge_bases,
|
|
577
|
-
count: data.count ?? brains.length,
|
|
578
|
-
default_knowledge_base_id: defaultBrain?.id ?? null,
|
|
579
|
-
...(unavailable ? { agents_unavailable_because: unavailable } : {}),
|
|
580
|
-
...(orphaned.length > 0 ? { orphaned_bindings: orphaned } : {}),
|
|
581
|
-
...(ambiguous.length > 0 ? { ambiguous_names: ambiguous } : {}),
|
|
582
|
-
},
|
|
583
|
-
next_steps: [
|
|
584
|
-
{ command: "naive brain create <name>", description: "Create another knowledge base" },
|
|
585
|
-
{ command: "naive brain connect <brain> --agent <agent>", description: "Connect an agent to one of them" },
|
|
586
|
-
{ command: 'naive brain query "..." --brain <name>', description: "Ask a specific brain a question" },
|
|
587
|
-
],
|
|
588
|
-
hints: [
|
|
589
|
-
`${data.count ?? brains.length} knowledge base(s).`,
|
|
590
|
-
// "No default" has TWO causes and they need different actions. A company
|
|
591
|
-
// with no brains has simply not needed one — the API mints it on the first
|
|
592
|
-
// call. A company WITH brains and none flagged has rows it can promote in
|
|
593
|
-
// one request, and letting the lazy mint happen instead adds a brain nobody
|
|
594
|
-
// asked for. (Deleting the default no longer produces this state: measured
|
|
595
|
-
// on a booted app, the delete promotes the oldest survivor in the same
|
|
596
|
-
// transaction. An earlier run, before that landed, left zero defaults.)
|
|
597
|
-
defaultBrain
|
|
598
|
-
? `Default: "${defaultBrain.name}" (${defaultBrain.id}) — used whenever no --brain/--kb/--agent is passed.`
|
|
599
|
-
: brains.length === 0
|
|
600
|
-
? "No brain is flagged default yet; the API provisions one on the first call that needs it."
|
|
601
|
-
: `No brain is flagged default, but ${brains.length} exist. The next call that names no brain will CREATE one called "Default Brain" and make it the default — run \`naive brain default <name|id>\` first to promote one of the brains above instead.`,
|
|
602
|
-
...(unavailable ? [`Connected agents not shown: ${unavailable}.`] : []),
|
|
603
|
-
...(orphaned.length > 0
|
|
604
|
-
? [`${orphaned.length} agent(s) are connected to a knowledge base that no longer exists — see orphaned_bindings.`]
|
|
605
|
-
: []),
|
|
606
|
-
...(ambiguous.length > 0
|
|
607
|
-
? [
|
|
608
|
-
`${ambiguous.length} name(s) are shared by more than one brain (${ambiguous
|
|
609
|
-
.map((a) => `"${a.name}" ×${a.ids.length}`)
|
|
610
|
-
.join(", ")}). --brain <name> refuses those and asks for an id.`,
|
|
611
|
-
]
|
|
612
|
-
: []),
|
|
613
|
-
],
|
|
614
|
-
related_commands: ["naive brain create", "naive brain connect", "naive brain add", "naive brain query"],
|
|
615
|
-
});
|
|
616
|
-
});
|
|
617
|
-
brainCmd
|
|
618
|
-
.command("create [name]")
|
|
619
|
-
.description("Create (and provision) a new knowledge base")
|
|
620
|
-
// `--name` SHIPPED FIRST and stays. The positional is the ergonomic form
|
|
621
|
-
// (`naive brain create "Support"`); passing both is refused rather than
|
|
622
|
-
// guessed at, and passing `--name` alone behaves exactly as it always did.
|
|
623
|
-
.option("--name <name>", "Name for the knowledge base (1-120 chars)")
|
|
624
|
-
.option("--default", "Make this the company's default knowledge base (to promote one that already exists, use `naive brain default`)")
|
|
625
|
-
.action(async (nameArg, opts) => {
|
|
626
|
-
if (nameArg && opts.name && nameArg !== opts.name) {
|
|
627
|
-
agentError({
|
|
628
|
-
action: "brain.create",
|
|
629
|
-
error: {
|
|
630
|
-
code: "invalid_input",
|
|
631
|
-
message: `A brain gets one name. Got "${nameArg}" as an argument and "${opts.name}" as --name.`,
|
|
632
|
-
},
|
|
633
|
-
recovery_steps: [
|
|
634
|
-
{ command: `naive brain create "${nameArg}"`, description: "Use the argument" },
|
|
635
|
-
{ command: `naive brain create --name "${opts.name}"`, description: "Use the flag" },
|
|
636
|
-
],
|
|
637
|
-
});
|
|
638
|
-
return;
|
|
639
|
-
}
|
|
640
|
-
const name = nameArg ?? opts.name;
|
|
641
|
-
// NO PRE-FLIGHT LOOKUP HERE, ON PURPOSE. A duplicate-name warning would be
|
|
642
|
-
// worth having — `brain_knowledge_bases` has no unique index on
|
|
643
|
-
// (company_id, name), so two brains may share one and `--brain <name>` then
|
|
644
|
-
// refuses as ambiguous — but detecting it costs a `GET /v1/brain` that
|
|
645
|
-
// `naive brain create --name X` has never made. The invariant test
|
|
646
|
-
// (__tests__/brain-multi.test.ts) compares the WHOLE request stream and
|
|
647
|
-
// catches exactly that. The same fact is reported by `naive brain list` as
|
|
648
|
-
// `ambiguous_names`, which already reads the list.
|
|
649
|
-
const resp = await apiRequest("POST", "/v1/brain", {
|
|
650
|
-
...(name && { name }),
|
|
651
|
-
...(opts.default && { is_default: true }),
|
|
652
|
-
});
|
|
653
|
-
// 🔴 A 409 FROM THIS ROUTE DOES NOT MEAN NOTHING WAS CREATED, AND THE FIX IS
|
|
654
|
-
// `naive brain default`, NOT A RETRY.
|
|
655
|
-
//
|
|
656
|
-
// With `is_default: true` the route inserts and provisions the row FIRST and
|
|
657
|
-
// swaps the default AFTER. The swap now runs under a company advisory lock, so
|
|
658
|
-
// concurrent creates no longer lose the race — six concurrent creates with
|
|
659
|
-
// `is_default: true` all returned 201 when measured against a booted app over a
|
|
660
|
-
// real Postgres (scratchpad/default-probe.mts FACT 1, second run). What is left
|
|
661
|
-
// is the narrow case where the SWAP fails: the brain exists, and the server says
|
|
662
|
-
// so — `retryable: false`, `details.knowledge_base_id` naming it, and prose
|
|
663
|
-
// telling the caller to promote it with `PATCH /v1/brain/{id}`.
|
|
664
|
-
//
|
|
665
|
-
// Passing that through verbatim would leave a CLI user holding a raw HTTP call.
|
|
666
|
-
// So this branch keeps the server's code, message and details EXACTLY as sent
|
|
667
|
-
// and only turns the instruction into a command that can be run. Nothing else on
|
|
668
|
-
// this command is intercepted; `handleApiError` still handles every other status.
|
|
669
|
-
const created = resp.data;
|
|
670
|
-
const strandedId = created.error?.details?.knowledge_base_id;
|
|
671
|
-
if (resp.status === 409 && created.error?.code === "duplicate_record" && typeof strandedId === "string") {
|
|
672
|
-
agentError({
|
|
673
|
-
action: "brain.create",
|
|
674
|
-
error: {
|
|
675
|
-
code: "duplicate_record",
|
|
676
|
-
message: created.error.message ?? "The knowledge base was created but could not be made the default.",
|
|
677
|
-
...(created.error.details ? { details: created.error.details } : {}),
|
|
678
|
-
},
|
|
679
|
-
recovery_steps: [
|
|
680
|
-
{
|
|
681
|
-
command: `naive brain default ${strandedId}`,
|
|
682
|
-
description: "THE BRAIN EXISTS. Only the default swap failed, so promote it — one request, no new brain.",
|
|
683
|
-
},
|
|
684
|
-
{
|
|
685
|
-
command: "naive brain list",
|
|
686
|
-
description: "Confirm it is there and see which brain currently holds the default.",
|
|
687
|
-
},
|
|
688
|
-
],
|
|
689
|
-
});
|
|
690
|
-
return;
|
|
691
|
-
}
|
|
692
|
-
handleApiError("brain.create", resp);
|
|
693
|
-
const data = resp.data;
|
|
694
|
-
agentOutput({
|
|
695
|
-
action: "brain.create",
|
|
696
|
-
result: resp.data,
|
|
697
|
-
next_steps: [
|
|
698
|
-
...(data.id
|
|
699
|
-
? [
|
|
700
|
-
// The ID, not the name: names are not unique, and a next step that
|
|
701
|
-
// may be ambiguous the moment a second brain shares the name is
|
|
702
|
-
// not a next step.
|
|
703
|
-
{ command: `naive brain add --file <path> --brain ${data.id}`, description: "Ingest a document into this knowledge base" },
|
|
704
|
-
{ command: `naive brain connect ${data.id} --agent <agent>`, description: "Connect an agent to it" },
|
|
705
|
-
]
|
|
706
|
-
: []),
|
|
707
|
-
{ command: "naive brain list", description: "List all knowledge bases" },
|
|
708
|
-
],
|
|
709
|
-
hints: [
|
|
710
|
-
`Knowledge base '${data.name ?? "?"}' created (status: ${data.status ?? "provisioning"}).`,
|
|
711
|
-
...(data.is_default ? ["This is now the company's default knowledge base."] : []),
|
|
712
|
-
],
|
|
713
|
-
related_commands: ["naive brain list", "naive brain default", "naive brain connect", "naive brain add", "naive brain rm"],
|
|
714
|
-
});
|
|
715
|
-
});
|
|
716
|
-
/* ══════════════════════════════════════════════════════════════════════════
|
|
717
|
-
* CHOOSING THE DEFAULT — the operation that makes a wrong default recoverable
|
|
718
|
-
*
|
|
719
|
-
* Until `PATCH /v1/brain/:id` landed, `is_default` was settable at create and
|
|
720
|
-
* nowhere else, so an operator who ended up with the wrong default could only
|
|
721
|
-
* create ANOTHER brain to move it — and never re-point at one they already had.
|
|
722
|
-
* Measured then, against a booted app with a key that read `GET /v1/brain` 200:
|
|
723
|
-
* PATCH/PUT /v1/brain/{id}, POST /v1/brain/{id}/default, POST /v1/brain/default
|
|
724
|
-
* and PATCH /v1/brain all answered 404. Re-measured after the route landed: the
|
|
725
|
-
* PATCH answers 200 and the other four still 404.
|
|
726
|
-
*
|
|
727
|
-
* WHY A POSITIONAL AND NOT `--default` ON SOMETHING ELSE. This is one act on one
|
|
728
|
-
* brain, and the brain is the subject: `naive brain default "Support Brain"`.
|
|
729
|
-
* It takes the same `<name|id>` every other subcommand's `--brain` takes, through
|
|
730
|
-
* the same resolver, so a name that two brains share is refused with both ids
|
|
731
|
-
* rather than resolved to one of them.
|
|
732
|
-
*
|
|
733
|
-
* NO `--because`. The route takes exactly one body property (`is_default: true`)
|
|
734
|
-
* and the server writes its own audit event (`brain.kb.default_changed`, carrying
|
|
735
|
-
* the previous id); a reason this CLI invented would be dropped on the floor.
|
|
736
|
-
* ══════════════════════════════════════════════════════════════════════════ */
|
|
737
|
-
brainCmd
|
|
738
|
-
.command("default <brain>")
|
|
739
|
-
.description("Make an existing brain the company default (by name or id)")
|
|
740
|
-
.action(async (ref) => {
|
|
741
|
-
const id = await resolveBrainId("brain.default", ref);
|
|
742
|
-
const resp = await apiRequest("PATCH", `/v1/brain/${id}`, { is_default: true });
|
|
743
|
-
handleApiError("brain.default", resp);
|
|
744
|
-
const data = resp.data;
|
|
745
|
-
agentOutput({
|
|
746
|
-
action: "brain.default",
|
|
747
|
-
result: resp.data,
|
|
748
|
-
next_steps: [
|
|
749
|
-
{ command: "naive brain list", description: "Confirm which brain is now the default" },
|
|
750
|
-
{ command: `naive brain query "..." --brain ${data.id ?? id}`, description: "Ask this brain directly" },
|
|
751
|
-
],
|
|
752
|
-
hints: [
|
|
753
|
-
`"${data.name ?? ref}" (${data.id ?? id}) is now the company default — used whenever no --brain/--kb/--agent is passed.`,
|
|
754
|
-
// Both of these are the server's rules, not this CLI's, and both are
|
|
755
|
-
// states a caller can reach: promoting the current default is a no-op,
|
|
756
|
-
// and a brain still provisioning is refused rather than promoted half-built.
|
|
757
|
-
"Promoting the brain that is already the default is a no-op and writes no audit event.",
|
|
758
|
-
"Only an active brain can be promoted; a provisioning or errored one is refused with invalid_input.",
|
|
759
|
-
],
|
|
760
|
-
related_commands: ["naive brain list", "naive brain create", "naive brain connect"],
|
|
761
|
-
});
|
|
762
|
-
});
|
|
763
|
-
/* ══════════════════════════════════════════════════════════════════════════
|
|
764
|
-
* CONNECTING A BRAIN TO AN AGENT
|
|
765
|
-
*
|
|
766
|
-
* WHAT THIS IS, EXACTLY — because the difference matters and nothing else in
|
|
767
|
-
* the product states it. MEASURED in this tree:
|
|
768
|
-
*
|
|
769
|
-
* - NO TABLE binds an agent to a `brain_knowledge_bases` row. The
|
|
770
|
-
* `knowledge_base_id` FK appears on documents, episodes, entities, claims
|
|
771
|
-
* and ops rows — never on an agent, a profile or a tenant user. The binding
|
|
772
|
-
* is a KEY IN `agents.metadata`, not a column.
|
|
773
|
-
* - THREE ROUTES own it, all under the `brain` primitive gate:
|
|
774
|
-
* `POST /v1/brain/connect`, `DELETE /v1/brain/connections/:agentId` and
|
|
775
|
-
* `GET /v1/brain/connections` (packages/api/src/routes/brain.ts:1251-1277,
|
|
776
|
-
* of 42 `brainRouter` routes).
|
|
777
|
-
* - The one place the platform accepts "this work uses that brain" per RUN is
|
|
778
|
-
* `POST /v1/runs`, whose body carries `brain_knowledge_base_id`
|
|
779
|
-
* (contracts/brain-ops.ts:98) next to `agent_id`. The connect routes write
|
|
780
|
-
* the key that surface already consumes, rather than minting a second name.
|
|
781
|
-
*
|
|
782
|
-
* 🔴 WHAT THIS COMMAND USED TO DO, AND WHY IT DOES NOT ANY MORE. It wrote the
|
|
783
|
-
* binding with `PATCH {@link ROSTER_PATH}/:id {metadata:{…}}` — the roster route,
|
|
784
|
-
* mounted on the ROSTER's own primitive gate. That is a SECOND DOOR to
|
|
785
|
-
* brain-owned data past the brain gate, which is precisely what
|
|
786
|
-
* `packages/api/src/routes/brain.ts:17-22` says must not exist. Measured live on
|
|
787
|
-
* one tenant, one agent, one field: `POST …/brain/connect` → 403
|
|
788
|
-
* `subprocessor_consent_required`; the roster PATCH of the same key on the same
|
|
789
|
-
* agent → 200. Three things were also silently skipped by the roster door — the
|
|
790
|
-
* `brain.agent.connected` `activity_events` row, the `brain_connected_at` stamp
|
|
791
|
-
* (so `GET /v1/brain/connections` reported a timestamp from an EARLIER binding
|
|
792
|
-
* to a DIFFERENT brain), and atomicity (it read-modify-wrote the whole metadata
|
|
793
|
-
* bag). All three come back with the gated route.
|
|
794
|
-
*
|
|
795
|
-
* AND HERE IS WHAT IT IS NOT: it is not an enforcement point. Nothing in the
|
|
796
|
-
* API reads this key to CONSTRAIN what an agent may recall — an agent key that
|
|
797
|
-
* can reach `/v1/brain` can still name any of its company's knowledge bases.
|
|
798
|
-
* The binding is a DEFAULT, honoured by every `naive brain … --agent <a>` call
|
|
799
|
-
* in this CLI. Saying otherwise would describe governance this build does not
|
|
800
|
-
* have. Governance of the brain is the AccountKit's `brain` primitive and the
|
|
801
|
-
* statute gates on `brain.kb.delete` / `brain.forget` / `brain.proposal.accept`,
|
|
802
|
-
* and this command changes neither.
|
|
803
|
-
* ════════════════════════════════════════════════════════════════════════ */
|
|
804
|
-
brainCmd
|
|
805
|
-
.command("connect <brain>")
|
|
806
|
-
.description("Connect an agent to a brain — that agent's default knowledge base")
|
|
807
|
-
.requiredOption("--agent <id|name>", "The agent to connect (id, display name, or runtime profile name)")
|
|
808
|
-
.addHelpText("after", `
|
|
809
|
-
Examples:
|
|
810
|
-
$ naive brain connect "Support Brain" --agent "Support Sam"
|
|
811
|
-
$ naive brain connect 00a21080-e247-4935-8efb-a6c1051c189b --agent c62c69e3-69aa-4136-b571-fded6ce5a28b
|
|
812
|
-
|
|
813
|
-
Then, anywhere a brain is named:
|
|
814
|
-
$ naive brain attach "refund audit" --agent "Support Sam"
|
|
815
|
-
$ naive brain recall "refunds" --agent "Support Sam"
|
|
816
|
-
|
|
817
|
-
The binding is a DEFAULT, not a fence: it decides which brain this CLI reads for
|
|
818
|
-
that agent. It does not stop an agent key from naming another knowledge base —
|
|
819
|
-
that is the AccountKit's job, and this command does not touch it.
|
|
820
|
-
`)
|
|
821
|
-
.action(async (brainRef, opts) => {
|
|
822
|
-
const action = "brain.connect";
|
|
823
|
-
const kbId = await resolveBrainId(action, brainRef);
|
|
824
|
-
const brain = (await listBrains(action)).find((b) => b.id === kbId);
|
|
825
|
-
const agent = await resolveAgent(action, opts.agent);
|
|
826
|
-
const previous = boundBrainId(agent);
|
|
827
|
-
const { agent: updated, connection } = await setAgentBrain(action, agent, kbId);
|
|
828
|
-
agentOutput({
|
|
829
|
-
action,
|
|
830
|
-
result: {
|
|
831
|
-
agent: { id: agent.id, name: agent.name },
|
|
832
|
-
knowledge_base: { id: kbId, name: connection?.knowledge_base_name ?? brain?.name ?? null },
|
|
833
|
-
previous_knowledge_base_id: previous ?? null,
|
|
834
|
-
binding_key: BRAIN_BINDING_KEY,
|
|
835
|
-
// The route's own timestamp, not one this CLI invented. Reported because
|
|
836
|
-
// it is what `naive brain list` and `GET /v1/brain/connections` show, and
|
|
837
|
-
// because the roster PATCH this replaced left it stale or null.
|
|
838
|
-
connected_at: connection?.connected_at ?? null,
|
|
839
|
-
metadata: updated.metadata ?? null,
|
|
840
|
-
},
|
|
841
|
-
next_steps: bindingNextSteps(brain, agent.id),
|
|
842
|
-
hints: [
|
|
843
|
-
`${agent.name} → "${connection?.knowledge_base_name ?? brain?.name ?? kbId}".`,
|
|
844
|
-
...(previous && previous !== kbId ? [`Replaced its previous binding (${previous}).`] : []),
|
|
845
|
-
...(previous === kbId ? ["It was already connected to this brain; the binding was re-pointed and `connected_at` refreshed."] : []),
|
|
846
|
-
`Written by POST ${BRAIN_CONNECT_PATH}, which carries the brain primitive gate and records a \`brain.agent.connected\` ledger event.`,
|
|
847
|
-
`Stored on the agent as metadata.${BRAIN_BINDING_KEY} — the same field POST /v1/runs accepts to point a run's brain attach.`,
|
|
848
|
-
"This is the default this CLI uses for --agent. It is not a permission boundary.",
|
|
849
|
-
],
|
|
850
|
-
related_commands: ["naive brain list", "naive brain disconnect", "naive brain attach"],
|
|
851
|
-
});
|
|
852
|
-
});
|
|
853
|
-
brainCmd
|
|
854
|
-
.command("disconnect [brain]")
|
|
855
|
-
.description("Disconnect an agent from its brain (the inverse of 'connect')")
|
|
856
|
-
.requiredOption("--agent <id|name>", "The agent to disconnect (id, display name, or runtime profile name)")
|
|
857
|
-
.addHelpText("after", `
|
|
858
|
-
Examples:
|
|
859
|
-
$ naive brain disconnect --agent "Support Sam"
|
|
860
|
-
$ naive brain disconnect "Support Brain" --agent "Support Sam" # refuses if it is bound elsewhere
|
|
861
|
-
|
|
862
|
-
Naming the brain is optional and is an ASSERTION: if the agent is connected to a
|
|
863
|
-
different one, the command refuses rather than clearing a binding you did not
|
|
864
|
-
mean to clear. Afterwards the agent falls back to the company default brain.
|
|
865
|
-
`)
|
|
866
|
-
.action(async (brainRef, opts) => {
|
|
867
|
-
const action = "brain.disconnect";
|
|
868
|
-
const agent = await resolveAgent(action, opts.agent);
|
|
869
|
-
const bound = boundBrainId(agent);
|
|
870
|
-
if (!bound) {
|
|
871
|
-
agentOutput({
|
|
872
|
-
action,
|
|
873
|
-
result: { agent: { id: agent.id, name: agent.name }, knowledge_base: null, changed: false },
|
|
874
|
-
next_steps: [{ command: `naive brain connect <brain> --agent ${agent.id}`, description: "Connect it to a brain" }],
|
|
875
|
-
hints: [`${agent.name} was not connected to a brain. Nothing to do.`],
|
|
876
|
-
related_commands: ["naive brain list", "naive brain connect"],
|
|
877
|
-
});
|
|
878
|
-
return;
|
|
879
|
-
}
|
|
880
|
-
if (brainRef) {
|
|
881
|
-
const asserted = await resolveBrainId(action, brainRef);
|
|
882
|
-
if (asserted !== bound) {
|
|
883
|
-
const brains = await listBrains(action);
|
|
884
|
-
const actual = brains.find((b) => b.id === bound);
|
|
885
|
-
agentError({
|
|
886
|
-
action,
|
|
887
|
-
error: {
|
|
888
|
-
code: "invalid_input",
|
|
889
|
-
message: `${agent.name} is connected to ${actual ? `"${actual.name}" (${bound})` : bound}, not to "${brainRef}". Nothing was changed.`,
|
|
890
|
-
agent: { id: agent.id, name: agent.name },
|
|
891
|
-
knowledge_base_id: bound,
|
|
892
|
-
},
|
|
893
|
-
recovery_steps: [
|
|
894
|
-
{ command: `naive brain disconnect --agent ${agent.id}`, description: "Disconnect it from whatever it is bound to" },
|
|
895
|
-
{ command: "naive brain list", description: "See every brain and what is bound to each" },
|
|
896
|
-
],
|
|
897
|
-
});
|
|
898
|
-
return;
|
|
899
|
-
}
|
|
900
|
-
}
|
|
901
|
-
const brains = await listBrains(action);
|
|
902
|
-
const was = brains.find((b) => b.id === bound);
|
|
903
|
-
const { agent: updated } = await setAgentBrain(action, agent, null);
|
|
904
|
-
agentOutput({
|
|
905
|
-
action,
|
|
906
|
-
result: {
|
|
907
|
-
agent: { id: agent.id, name: agent.name },
|
|
908
|
-
disconnected_from: { id: bound, name: was?.name ?? null },
|
|
909
|
-
knowledge_base: null,
|
|
910
|
-
changed: true,
|
|
911
|
-
metadata: updated.metadata ?? null,
|
|
912
|
-
},
|
|
913
|
-
next_steps: [
|
|
914
|
-
{ command: `naive brain connect <brain> --agent ${agent.id}`, description: "Connect it to a different brain" },
|
|
915
|
-
{ command: "naive brain list", description: "See every brain and what is bound to each" },
|
|
916
|
-
],
|
|
917
|
-
hints: [
|
|
918
|
-
`${agent.name} is no longer connected to "${was?.name ?? bound}".`,
|
|
919
|
-
"Its brain calls fall back to the company default, exactly as before it was connected.",
|
|
920
|
-
"The knowledge base itself is untouched — this removed a binding, not a brain. Use 'naive brain rm <id>' to delete one.",
|
|
921
|
-
],
|
|
922
|
-
related_commands: ["naive brain connect", "naive brain list", "naive brain rm"],
|
|
923
|
-
});
|
|
924
|
-
});
|
|
925
|
-
brainCmd
|
|
926
|
-
.command("rm <id>")
|
|
927
|
-
.description("Delete a knowledge base and all its documents (permanent)")
|
|
928
|
-
// STILL NO PRE-FLIGHT READ, for the reason `create` gives: a shipped
|
|
929
|
-
// invocation that made one request may not start making two. What changed is
|
|
930
|
-
// what the RESULT says. The route answers 204 with an empty body, and two of
|
|
931
|
-
// its consequences are invisible in that emptiness — both measured, both named
|
|
932
|
-
// here, and both reported by `naive brain list`, which is already the next step.
|
|
933
|
-
.action(async (id) => {
|
|
934
|
-
const resp = await apiRequest("DELETE", `/v1/brain/${id}`);
|
|
935
|
-
handleApiError("brain.rm", resp);
|
|
936
|
-
if (handlePendingApproval("brain.rm", resp))
|
|
937
|
-
return;
|
|
938
|
-
agentOutput({
|
|
939
|
-
action: "brain.rm",
|
|
940
|
-
result: resp.data ?? { deleted: true, id },
|
|
941
|
-
next_steps: [
|
|
942
|
-
{
|
|
943
|
-
command: "naive brain list",
|
|
944
|
-
description: "List remaining knowledge bases — and check `default_knowledge_base_id` and `orphaned_bindings`, " +
|
|
945
|
-
"which are the two things this delete can change without saying so.",
|
|
946
|
-
},
|
|
947
|
-
],
|
|
948
|
-
hints: [
|
|
949
|
-
"Knowledge base and its documents deleted.",
|
|
950
|
-
// MEASURED on a booted app over a real Postgres (default-probe.mts FACT 3,
|
|
951
|
-
// second run): deleting the default answered 204 and the company still held
|
|
952
|
-
// exactly one default — the delete promotes the oldest surviving brain in
|
|
953
|
-
// the same transaction. An earlier run of the same probe, before that
|
|
954
|
-
// landed, left ZERO defaults and the next unscoped call minted a brain.
|
|
955
|
-
// The 204 body is empty and names no successor, so the only way to learn
|
|
956
|
-
// which brain it is, is to look.
|
|
957
|
-
"If this was the DEFAULT brain, another was promoted in the same transaction — the oldest surviving one. The 204 cannot say which: `naive brain list` shows it, and `naive brain default <brain>` picks a different one.",
|
|
958
|
-
// MEASURED this session: DELETE does not clear
|
|
959
|
-
// agents.metadata.brain_knowledge_base_id, so a connected agent keeps
|
|
960
|
-
// pointing at the dead id and its next scoped call 404s.
|
|
961
|
-
"Agents connected to it are NOT disconnected — they keep pointing at the deleted id and their scoped calls will 404. `naive brain list` reports them as `orphaned_bindings`; `naive brain disconnect --agent <id>` clears one.",
|
|
962
|
-
],
|
|
963
|
-
related_commands: ["naive brain list", "naive brain create", "naive brain disconnect"],
|
|
964
|
-
});
|
|
965
|
-
});
|
|
966
|
-
/* ========================================================================== */
|
|
967
|
-
/* The durable-runtime era additions (06 §1.7). */
|
|
968
|
-
/* */
|
|
969
|
-
/* §1.7 specifies FOURTEEN new subcommands: */
|
|
970
|
-
/* attach beliefs belief reaffirm lessons learn proposals writebacks */
|
|
971
|
-
/* retention levels status metrics consolidate decisions */
|
|
972
|
-
/* */
|
|
973
|
-
/* SIX are shipped here. MEASURED against packages/api/src/routes/brain.ts in */
|
|
974
|
-
/* this tree (44 `brainRouter.<verb>(` handlers), these six and only these six */
|
|
975
|
-
/* have a route to reach: */
|
|
976
|
-
/* */
|
|
977
|
-
/* attach POST /v1/brain/attach */
|
|
978
|
-
/* consolidate POST /v1/brain/consolidate */
|
|
979
|
-
/* proposals GET /v1/brain/proposals (+ /proposals/:id) */
|
|
980
|
-
/* writebacks GET /v1/brain/writebacks (+ /writebacks/:id) */
|
|
981
|
-
/* status GET /v1/brain/status */
|
|
982
|
-
/* metrics GET /v1/brain/metrics */
|
|
983
|
-
/* */
|
|
984
|
-
/* The per-route LINE NUMBERS that used to be on these six are deliberately */
|
|
985
|
-
/* gone, and the handler count is corrected from 37 to 44. Both had drifted */
|
|
986
|
-
/* far enough to mislead — `status` was cited at :350 against a real :428 — */
|
|
987
|
-
/* and re-measuring them proved the point rather than fixing it: the numbers */
|
|
988
|
-
/* moved another +142 within the same change, because an edit anywhere above */
|
|
989
|
-
/* a handler renumbers every handler below it. A citation that is wrong more */
|
|
990
|
-
/* often than it is right stops being evidence and starts being noise. */
|
|
991
|
-
/* */
|
|
992
|
-
/* What this block actually asserts is a SET — "exactly these six of the */
|
|
993
|
-
/* fourteen have a mount" — and a METHOD+PATH is a stable, greppable key for */
|
|
994
|
-
/* that claim where a line number is not. The count stays because it is the */
|
|
995
|
-
/* one number that is cheap to re-check and that changes only when a route is */
|
|
996
|
-
/* genuinely added or removed, which is the event this block cares about. */
|
|
997
|
-
/* */
|
|
998
|
-
/* The other EIGHT — beliefs, belief, reaffirm, lessons, learn, retention, */
|
|
999
|
-
/* levels, decisions — have no handler on `brainRouter` and no table behind */
|
|
1000
|
-
/* them. They are deliberately NOT shipped. `naive brain` is a working group */
|
|
1001
|
-
/* with sixteen working subcommands; adding eight that can only ever 404 */
|
|
1002
|
-
/* degrades a live surface, and a subcommand whose only possible outcome is an */
|
|
1003
|
-
/* error is not a command, it is a promise. They land with their routes. */
|
|
1004
|
-
/* */
|
|
1005
|
-
/* `proposals [id]` is one name at two scopes rather than a `proposals` / */
|
|
1006
|
-
/* `proposal` pair — the same shape the group already uses for a list and its */
|
|
1007
|
-
/* member elsewhere, and one fewer name to keep in step. */
|
|
1008
|
-
/* ========================================================================== */
|
|
1009
|
-
/** Collect a repeatable option into an array (commander's documented idiom). */
|
|
1010
|
-
function collect(value, previous = []) {
|
|
1011
|
-
return [...previous, value];
|
|
1012
|
-
}
|
|
1013
|
-
withBrainFlags(brainCmd
|
|
1014
|
-
.command("attach [goal]")
|
|
1015
|
-
.description("Attach a read capsule for a goal — what the brain already knows, before the work starts")
|
|
1016
|
-
.option("--goal <goal>", "The goal (alternative to the positional argument)")
|
|
1017
|
-
.option("--entity <name>", "An entity the work touches (repeatable, max 20)", collect)
|
|
1018
|
-
.option("--artifact <ref>", "An artifact the work touches (repeatable, max 50)", collect)
|
|
1019
|
-
.option("--since <iso8601>", "Only what changed since this watermark")
|
|
1020
|
-
.option("--kb <id>", "Attach from a specific knowledge base"))
|
|
1021
|
-
.action(async (goalArg, opts) => {
|
|
1022
|
-
const goal = goalArg ?? opts.goal;
|
|
1023
|
-
if (!goal) {
|
|
1024
|
-
agentError({
|
|
1025
|
-
action: "brain.attach",
|
|
1026
|
-
error: { code: "invalid_input", message: "A goal is required — pass it positionally or with --goal" },
|
|
1027
|
-
recovery_steps: [
|
|
1028
|
-
{ command: 'naive brain attach "refund audit"', description: "Positional form" },
|
|
1029
|
-
{ command: 'naive brain attach --goal "refund audit"', description: "Flag form (same call)" },
|
|
1030
|
-
],
|
|
1031
|
-
});
|
|
1032
|
-
return;
|
|
1033
|
-
}
|
|
1034
|
-
// `--agent <a>` here is the payoff of `brain connect`: the run loop's read
|
|
1035
|
-
// half runs against the brain that agent is bound to, without the caller
|
|
1036
|
-
// having to carry the id.
|
|
1037
|
-
const kbId = await selectBrainId("brain.attach", brainSelector(opts));
|
|
1038
|
-
const resp = await apiRequest("POST", "/v1/brain/attach", {
|
|
1039
|
-
goal,
|
|
1040
|
-
...(opts.entity && { entities: opts.entity }),
|
|
1041
|
-
...(opts.artifact && { artifacts_touched: opts.artifact }),
|
|
1042
|
-
...(opts.since && { since_watermark: opts.since }),
|
|
1043
|
-
...(kbId && { knowledge_base_id: kbId }),
|
|
1044
|
-
});
|
|
1045
|
-
handleApiError("brain.attach", resp);
|
|
1046
|
-
agentOutput({
|
|
1047
|
-
action: "brain.attach",
|
|
1048
|
-
result: resp.data,
|
|
1049
|
-
next_steps: [
|
|
1050
|
-
{ command: 'naive brain consolidate --objective "..."', description: "Debrief at the end of the run" },
|
|
1051
|
-
{ command: "naive brain proposals", description: "See what the run proposed writing back" },
|
|
1052
|
-
],
|
|
1053
|
-
hints: ["A capsule is what the brain already knows about this goal. Read it before working, not after."],
|
|
1054
|
-
related_commands: ["naive brain consolidate", "naive brain recall", "naive brain proposals"],
|
|
1055
|
-
});
|
|
1056
|
-
});
|
|
1057
|
-
withBrainFlags(brainCmd
|
|
1058
|
-
.command("consolidate")
|
|
1059
|
-
.description("Run-end debrief — the write half. What was learned, decided and left open")
|
|
1060
|
-
.option("--objective <text>", "What the run was for")
|
|
1061
|
-
.option("--learned <s|p|o>", "A learned fact as subject|predicate|object (repeatable, max 100)", collect)
|
|
1062
|
-
.option("--decided <title>", "A decision the run made (repeatable, max 50)", collect)
|
|
1063
|
-
.option("--open <question>", "A question the run left open (repeatable, max 50)", collect)
|
|
1064
|
-
.option("--kb <id>", "Consolidate into a specific knowledge base"))
|
|
1065
|
-
.action(async (opts) => {
|
|
1066
|
-
// `--learned` is pipe-separated because the API's shape is a TRIPLE, and a
|
|
1067
|
-
// free-text sentence would have to be parsed into one by something. Refusing
|
|
1068
|
-
// a malformed triple here is better than posting a fact with an empty object.
|
|
1069
|
-
const learned = (opts.learned ?? []).map((raw) => {
|
|
1070
|
-
const [subject, predicate, object] = raw.split("|");
|
|
1071
|
-
if (!subject || !predicate || !object) {
|
|
1072
|
-
agentError({
|
|
1073
|
-
action: "brain.consolidate",
|
|
1074
|
-
error: {
|
|
1075
|
-
code: "invalid_input",
|
|
1076
|
-
message: `--learned expects subject|predicate|object, got: ${raw}`,
|
|
1077
|
-
},
|
|
1078
|
-
recovery_steps: [
|
|
1079
|
-
{ command: 'naive brain consolidate --learned "acme|refund window|30 days"', description: "Use the triple form" },
|
|
1080
|
-
],
|
|
1081
|
-
});
|
|
1082
|
-
}
|
|
1083
|
-
return { subject: subject, predicate: predicate, object: object };
|
|
1084
|
-
});
|
|
1085
|
-
const kbId = await selectBrainId("brain.consolidate", brainSelector(opts));
|
|
1086
|
-
const resp = await apiRequest("POST", "/v1/brain/consolidate", {
|
|
1087
|
-
...(opts.objective && { objective: opts.objective }),
|
|
1088
|
-
...(learned.length > 0 && { learned }),
|
|
1089
|
-
...(opts.decided && { decided: opts.decided.map((title) => ({ title })) }),
|
|
1090
|
-
...(opts.open && { open: opts.open }),
|
|
1091
|
-
...(kbId && { knowledge_base_id: kbId }),
|
|
1092
|
-
});
|
|
1093
|
-
handleApiError("brain.consolidate", resp);
|
|
1094
|
-
const data = resp.data;
|
|
1095
|
-
agentOutput({
|
|
1096
|
-
action: "brain.consolidate",
|
|
1097
|
-
result: resp.data,
|
|
1098
|
-
next_steps: [
|
|
1099
|
-
{ command: "naive brain proposals --status pending", description: "Review what this debrief proposed" },
|
|
1100
|
-
{ command: "naive brain writebacks", description: "See the envelope this created" },
|
|
1101
|
-
],
|
|
1102
|
-
hints: [
|
|
1103
|
-
`Accepted (202). Gateway mode: ${data.mode ?? "unknown"}; ${data.proposals?.length ?? 0} proposal(s).`,
|
|
1104
|
-
"A debrief PROPOSES; it does not commit. Nothing here is a belief until a proposal is accepted.",
|
|
1105
|
-
],
|
|
1106
|
-
related_commands: ["naive brain proposals", "naive brain writebacks", "naive brain attach"],
|
|
1107
|
-
});
|
|
1108
|
-
});
|
|
1109
|
-
brainCmd
|
|
1110
|
-
.command("proposals [id]")
|
|
1111
|
-
.description("The proposal queue — list it, read one, or decide one")
|
|
1112
|
-
.option("--status <status>", "Filter by proposal status")
|
|
1113
|
-
.option("--type <objectType>", "Filter by proposed object type")
|
|
1114
|
-
.option("--run <id>", "Filter to one run")
|
|
1115
|
-
.option("--limit <n>", "Maximum proposals to return (1-200)", (v) => Number.parseInt(v, 10))
|
|
1116
|
-
.option("--events", "With <id>: show the proposal's event history instead of the proposal")
|
|
1117
|
-
.option("--accept", "With <id>: accept it (may require human approval)")
|
|
1118
|
-
.option("--reject", "With <id>: reject it")
|
|
1119
|
-
.option("--merge <targetId>", "With <id>: merge it into an existing object")
|
|
1120
|
-
.option("--quarantine", "With <id>: quarantine it")
|
|
1121
|
-
.option("--because <why>", "Why you decided as you did. Recorded on the proposal")
|
|
1122
|
-
.action(async (id, opts) => {
|
|
1123
|
-
const decisions = [
|
|
1124
|
-
opts.accept && "accept",
|
|
1125
|
-
opts.reject && "reject",
|
|
1126
|
-
opts.merge && "merge",
|
|
1127
|
-
opts.quarantine && "quarantine",
|
|
1128
|
-
].filter(Boolean);
|
|
1129
|
-
if (decisions.length > 0 && !id) {
|
|
1130
|
-
agentError({
|
|
1131
|
-
action: "brain.proposals",
|
|
1132
|
-
error: { code: "invalid_input", message: `--${decisions[0]} needs a proposal id: naive brain proposals <id> --${decisions[0]}` },
|
|
1133
|
-
recovery_steps: [{ command: "naive brain proposals --status pending", description: "List proposals awaiting a decision" }],
|
|
1134
|
-
});
|
|
1135
|
-
return;
|
|
1136
|
-
}
|
|
1137
|
-
if (decisions.length > 1) {
|
|
1138
|
-
agentError({
|
|
1139
|
-
action: "brain.proposals",
|
|
1140
|
-
error: { code: "invalid_input", message: `A proposal gets one decision, not ${decisions.length}: ${decisions.join(", ")}` },
|
|
1141
|
-
recovery_steps: [{ command: "naive brain proposals <id>", description: "Read the proposal before deciding" }],
|
|
1142
|
-
});
|
|
1143
|
-
return;
|
|
1144
|
-
}
|
|
1145
|
-
if (decisions.length === 1) {
|
|
1146
|
-
const verb = decisions[0];
|
|
1147
|
-
const resp = await apiRequest("POST", `/v1/brain/proposals/${encodeURIComponent(id)}/${verb}`, {
|
|
1148
|
-
...(opts.because && { reason: opts.because }),
|
|
1149
|
-
...(opts.merge && { merge_target_id: opts.merge }),
|
|
1150
|
-
});
|
|
1151
|
-
handleApiError(`brain.proposals.${verb}`, resp);
|
|
1152
|
-
if (handlePendingApproval(`brain.proposals.${verb}`, resp))
|
|
1153
|
-
return;
|
|
1154
|
-
agentOutput({
|
|
1155
|
-
action: `brain.proposals.${verb}`,
|
|
1156
|
-
result: resp.data,
|
|
1157
|
-
next_steps: [{ command: "naive brain proposals --status pending", description: "Review the rest of the queue" }],
|
|
1158
|
-
hints: [
|
|
1159
|
-
`Proposal ${id} ${verb}ed.`,
|
|
1160
|
-
...(verb === "accept" ? ["Accepting is a governed action — it may return pending_approval instead of promoting."] : []),
|
|
1161
|
-
],
|
|
1162
|
-
related_commands: ["naive brain proposals", "naive brain writebacks"],
|
|
1163
|
-
});
|
|
1164
|
-
return;
|
|
1165
|
-
}
|
|
1166
|
-
if (id) {
|
|
1167
|
-
const suffix = opts.events ? "/events" : "";
|
|
1168
|
-
const resp = await apiRequest("GET", `/v1/brain/proposals/${encodeURIComponent(id)}${suffix}`);
|
|
1169
|
-
handleApiError("brain.proposals", resp);
|
|
1170
|
-
agentOutput({
|
|
1171
|
-
action: "brain.proposals",
|
|
1172
|
-
result: resp.data,
|
|
1173
|
-
next_steps: [
|
|
1174
|
-
{ command: `naive brain proposals ${id} --accept --because "..."`, description: "Accept it" },
|
|
1175
|
-
{ command: `naive brain proposals ${id} --reject --because "..."`, description: "Reject it" },
|
|
1176
|
-
{ command: `naive brain proposals ${id} --events`, description: "See its history" },
|
|
1177
|
-
],
|
|
1178
|
-
related_commands: ["naive brain proposals", "naive brain writebacks"],
|
|
1179
|
-
});
|
|
1180
|
-
return;
|
|
1181
|
-
}
|
|
1182
|
-
const params = new URLSearchParams();
|
|
1183
|
-
if (opts.status)
|
|
1184
|
-
params.set("status", opts.status);
|
|
1185
|
-
if (opts.type)
|
|
1186
|
-
params.set("object_type", opts.type);
|
|
1187
|
-
if (opts.run)
|
|
1188
|
-
params.set("run_id", opts.run);
|
|
1189
|
-
if (opts.limit !== undefined)
|
|
1190
|
-
params.set("limit", String(opts.limit));
|
|
1191
|
-
const qs = params.toString();
|
|
1192
|
-
const resp = await apiRequest("GET", `/v1/brain/proposals${qs ? `?${qs}` : ""}`);
|
|
1193
|
-
handleApiError("brain.proposals", resp);
|
|
1194
|
-
const data = resp.data;
|
|
1195
|
-
agentOutput({
|
|
1196
|
-
action: "brain.proposals",
|
|
1197
|
-
result: resp.data,
|
|
1198
|
-
next_steps: [
|
|
1199
|
-
{ command: "naive brain proposals <id>", description: "Read one proposal" },
|
|
1200
|
-
{ command: 'naive brain proposals <id> --accept --because "..."', description: "Accept one" },
|
|
1201
|
-
],
|
|
1202
|
-
hints: [`${data.count ?? 0} proposal(s).`],
|
|
1203
|
-
related_commands: ["naive brain writebacks", "naive brain consolidate"],
|
|
1204
|
-
});
|
|
1205
|
-
});
|
|
1206
|
-
brainCmd
|
|
1207
|
-
.command("writebacks [id]")
|
|
1208
|
-
.description("Writeback envelopes — what a run asked the brain to record")
|
|
1209
|
-
.option("--run <id>", "Filter to one run")
|
|
1210
|
-
.option("--source <source>", "Filter by source")
|
|
1211
|
-
.option("--status <status>", "Filter by envelope status")
|
|
1212
|
-
.option("--limit <n>", "Maximum envelopes to return (1-200)", (v) => Number.parseInt(v, 10))
|
|
1213
|
-
.action(async (id, opts) => {
|
|
1214
|
-
if (id) {
|
|
1215
|
-
const resp = await apiRequest("GET", `/v1/brain/writebacks/${encodeURIComponent(id)}`);
|
|
1216
|
-
handleApiError("brain.writebacks", resp);
|
|
1217
|
-
agentOutput({
|
|
1218
|
-
action: "brain.writebacks",
|
|
1219
|
-
result: resp.data,
|
|
1220
|
-
next_steps: [{ command: "naive brain proposals --status pending", description: "Decide the proposals it raised" }],
|
|
1221
|
-
related_commands: ["naive brain proposals", "naive brain consolidate"],
|
|
1222
|
-
});
|
|
1223
|
-
return;
|
|
1224
|
-
}
|
|
1225
|
-
const params = new URLSearchParams();
|
|
1226
|
-
if (opts.run)
|
|
1227
|
-
params.set("run_id", opts.run);
|
|
1228
|
-
if (opts.source)
|
|
1229
|
-
params.set("source", opts.source);
|
|
1230
|
-
if (opts.status)
|
|
1231
|
-
params.set("status", opts.status);
|
|
1232
|
-
if (opts.limit !== undefined)
|
|
1233
|
-
params.set("limit", String(opts.limit));
|
|
1234
|
-
const qs = params.toString();
|
|
1235
|
-
const resp = await apiRequest("GET", `/v1/brain/writebacks${qs ? `?${qs}` : ""}`);
|
|
1236
|
-
handleApiError("brain.writebacks", resp);
|
|
1237
|
-
const data = resp.data;
|
|
1238
|
-
agentOutput({
|
|
1239
|
-
action: "brain.writebacks",
|
|
1240
|
-
result: resp.data,
|
|
1241
|
-
next_steps: [{ command: "naive brain writebacks <id>", description: "Read one envelope and its proposals" }],
|
|
1242
|
-
hints: [`${data.count ?? 0} envelope(s).`],
|
|
1243
|
-
related_commands: ["naive brain proposals", "naive brain consolidate"],
|
|
1244
|
-
});
|
|
1245
|
-
});
|
|
1246
|
-
brainCmd
|
|
1247
|
-
.command("status")
|
|
1248
|
-
.description("Operator view of the brain runtime for this company")
|
|
1249
|
-
.action(async () => {
|
|
1250
|
-
const resp = await apiRequest("GET", "/v1/brain/status");
|
|
1251
|
-
handleApiError("brain.status", resp);
|
|
1252
|
-
agentOutput({
|
|
1253
|
-
action: "brain.status",
|
|
1254
|
-
result: resp.data,
|
|
1255
|
-
next_steps: [
|
|
1256
|
-
{ command: "naive brain metrics", description: "Process-global nervous-system counters" },
|
|
1257
|
-
{ command: "naive brain list", description: "The company's knowledge bases" },
|
|
1258
|
-
],
|
|
1259
|
-
// This command is a pure passthrough of the route body, so every field the
|
|
1260
|
-
// API adds shows up here for free — but the REMEDIATION does not, and a
|
|
1261
|
-
// `configured: false` with no next move is where an operator stalls. These
|
|
1262
|
-
// hints name what each provider resolves from, which is the one thing the
|
|
1263
|
-
// payload cannot say for a provider that resolved to nothing.
|
|
1264
|
-
//
|
|
1265
|
-
// Two rungs each, not one: the resolvers accept EITHER the explicit override
|
|
1266
|
-
// OR the hosted key already present on the API
|
|
1267
|
-
// (platform/brain/src/providers/embedder.ts:133 `resolveEmbedderTarget`,
|
|
1268
|
-
// answerer.ts:240 `resolveAnswererTarget`). Naming only BRAIN_EMBED_BASE —
|
|
1269
|
-
// as every doc and error string did until this change — sent operators to
|
|
1270
|
-
// provision an embedding endpoint they did not need.
|
|
1271
|
-
hints: [
|
|
1272
|
-
"Read `services.<leg>.source` first: it names WHICH key answered — `explicit` (a BRAIN_*_BASE override), `openai-default`/`openrouter-default` (the hosted key), or `mock`. A leg carrying `optional: true` is one that `ready` ignores.",
|
|
1273
|
-
"Embeddings (ingest + document recall) resolve from BRAIN_EMBED_BASE if set, else OPENAI_API_KEY, else nothing — and with nothing, ingest refuses at the door rather than writing a broken document.",
|
|
1274
|
-
"Answer synthesis resolves from BRAIN_ANSWER_BASE if set, else OPENROUTER_API_KEY, else nothing. Unlike the embedder its absence is NOT an error: `brain query` falls back to returning grounded chunks.",
|
|
1275
|
-
"A reranker is opt-in only (BRAIN_RERANK_BASE, no hosted fallback) and the GBrain semantic engine needs BOTH BRAIN_SEMANTIC_ENGINE=gbrain AND GBRAIN_BASE_URL. Neither absence is a failure: retrieval falls back to RRF order, reads fall back to the Postgres spine. `ready` never counts the reranker at all, and counts GBrain health only when BOTH gbrain terms are set — so `ready: true` with both reported unconfigured is the correct reading, not a false pass.",
|
|
1276
|
-
],
|
|
1277
|
-
related_commands: ["naive brain metrics", "naive brain list", "naive brain docs"],
|
|
1278
|
-
});
|
|
1279
|
-
});
|
|
1280
|
-
brainCmd
|
|
1281
|
-
.command("metrics")
|
|
1282
|
-
.description("Nervous-system counters. NOT company-scoped — see the hint")
|
|
1283
|
-
.action(async () => {
|
|
1284
|
-
const resp = await apiRequest("GET", "/v1/brain/metrics");
|
|
1285
|
-
handleApiError("brain.metrics", resp);
|
|
1286
|
-
agentOutput({
|
|
1287
|
-
action: "brain.metrics",
|
|
1288
|
-
result: resp.data,
|
|
1289
|
-
next_steps: [{ command: "naive brain status", description: "The company-scoped view" }],
|
|
1290
|
-
hints: [
|
|
1291
|
-
// MEASURED: routes/brain.ts:750 calls getCompanyId(req) as an AUTH GATE ONLY and
|
|
1292
|
-
// then returns snapshotBrainMetrics(), which takes no company and reads
|
|
1293
|
-
// process-wide counters (platform/brain/src/brain-metrics.ts:73). Saying so is
|
|
1294
|
-
// the difference between a number an operator can act on and one they cannot.
|
|
1295
|
-
"These counters are PROCESS-GLOBAL, not company-scoped: they aggregate every tenant served by the API process that answered. Use `naive brain status` for this company.",
|
|
1296
|
-
],
|
|
1297
|
-
related_commands: ["naive brain status"],
|
|
1298
|
-
});
|
|
1299
|
-
});
|
|
1300
|
-
//# sourceMappingURL=brain.js.map
|