@stjbrown/agent-knowledge 0.1.0-beta.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/LICENSE +21 -0
- package/NOTICE +44 -0
- package/README.md +212 -0
- package/dist/chunk-3XSOMUQQ.js +131 -0
- package/dist/chunk-3XSOMUQQ.js.map +1 -0
- package/dist/chunk-J4MELCGD.js +114 -0
- package/dist/chunk-J4MELCGD.js.map +1 -0
- package/dist/chunk-YIAVFL7A.js +1732 -0
- package/dist/chunk-YIAVFL7A.js.map +1 -0
- package/dist/headless.js +11 -0
- package/dist/headless.js.map +1 -0
- package/dist/index.js +16 -0
- package/dist/index.js.map +1 -0
- package/dist/main.js +373 -0
- package/dist/main.js.map +1 -0
- package/dist/tui-VZVO7UHV.js +521 -0
- package/dist/tui-VZVO7UHV.js.map +1 -0
- package/package.json +72 -0
- package/skills/kb/SKILL.md +54 -0
- package/skills/kb/example-bundle/concepts/customers.md +15 -0
- package/skills/kb/example-bundle/concepts/orders.md +19 -0
- package/skills/kb/example-bundle/index.md +20 -0
- package/skills/kb/example-bundle/log.md +6 -0
- package/skills/kb/example-bundle/spec/conventions.md +36 -0
- package/skills/kb/example-bundle/spec/types.md +23 -0
- package/skills/kb/references/SPEC.md +451 -0
- package/skills/kb/references/glossary.md +61 -0
- package/skills/kb/references/trust-model.md +79 -0
- package/skills/kb/templates/concept.md +22 -0
- package/skills/kb/templates/index.md +17 -0
- package/skills/kb/templates/log.md +13 -0
- package/skills/kb-ingest/SKILL.md +128 -0
- package/skills/kb-init/SKILL.md +79 -0
- package/skills/kb-lint/SKILL.md +77 -0
- package/skills/kb-lint/scripts/conformance.mjs +7537 -0
- package/skills/kb-query/SKILL.md +81 -0
- package/skills/kb-visualize/SKILL.md +72 -0
- package/skills/kb-visualize/scripts/graph.mjs +7520 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 stjbrown
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/NOTICE
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
agent-knowledge
|
|
2
|
+
Copyright (c) 2026 stjbrown
|
|
3
|
+
|
|
4
|
+
This project is licensed under the MIT License (see LICENSE).
|
|
5
|
+
|
|
6
|
+
------------------------------------------------------------------------
|
|
7
|
+
Third-party material
|
|
8
|
+
------------------------------------------------------------------------
|
|
9
|
+
|
|
10
|
+
skills/kb/references/SPEC.md is a verbatim copy of the Open Knowledge Format
|
|
11
|
+
(OKF) v0.1 specification from:
|
|
12
|
+
|
|
13
|
+
https://github.com/GoogleCloudPlatform/knowledge-catalog (okf/SPEC.md)
|
|
14
|
+
|
|
15
|
+
That file is licensed under the Apache License, Version 2.0, and is included
|
|
16
|
+
and used under those terms.
|
|
17
|
+
|
|
18
|
+
------------------------------------------------------------------------
|
|
19
|
+
|
|
20
|
+
Portions of packages/janet/src (the Amazon Bedrock gateway, and — when added —
|
|
21
|
+
the OAuth auth subsystem under src/auth) are adapted from MastraCode
|
|
22
|
+
(https://github.com/mastra-ai/mastra, the mastracode package), licensed under
|
|
23
|
+
the Apache License, Version 2.0. Adapted and used under those terms.
|
|
24
|
+
|
|
25
|
+
------------------------------------------------------------------------
|
|
26
|
+
|
|
27
|
+
The generated conformance and graph tools bundle `yaml` by Eemeli Aro
|
|
28
|
+
(https://github.com/eemeli/yaml), licensed under the ISC License:
|
|
29
|
+
|
|
30
|
+
Copyright Eemeli Aro <eemeli@gmail.com>
|
|
31
|
+
|
|
32
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
33
|
+
purpose with or without fee is hereby granted, provided that the above
|
|
34
|
+
copyright notice and this permission notice appear in all copies.
|
|
35
|
+
|
|
36
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
37
|
+
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
38
|
+
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
|
39
|
+
SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
40
|
+
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
|
|
41
|
+
OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
|
42
|
+
CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
43
|
+
|
|
44
|
+
All other files in this repository are MIT-licensed as described in LICENSE.
|
package/README.md
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
# agent-knowledge
|
|
2
|
+
|
|
3
|
+
**Agent Knowledge is a portable LLM wiki system for building and maintaining project knowledge in
|
|
4
|
+
plain Markdown using the Open Knowledge Format (OKF).**
|
|
5
|
+
|
|
6
|
+
It turns project documents, decisions, notes, and conversations into a connected knowledge base
|
|
7
|
+
that improves over time. Ask a question and get a cited answer. Add a source and the agent
|
|
8
|
+
integrates it with what the project already knows. Run a health check and it finds stale claims,
|
|
9
|
+
contradictions, and orphaned pages before the wiki quietly rots.
|
|
10
|
+
|
|
11
|
+
Everything remains plain Markdown: readable without special tooling, easy to diff and review, and
|
|
12
|
+
portable across agents.
|
|
13
|
+
|
|
14
|
+
## From skills to Janet
|
|
15
|
+
|
|
16
|
+
Agent Knowledge began as a family of portable [Agent Skills](https://agentskills.io) called `kb-*`.
|
|
17
|
+
You could add them to Claude Code, Cursor, Codex, or another coding agent and teach the agent you
|
|
18
|
+
already use how to create, query, and maintain an OKF knowledge bundle.
|
|
19
|
+
|
|
20
|
+
Those skills are still the core of the project. Agent Knowledge has since evolved to include
|
|
21
|
+
**Janet**, a dedicated knowledge agent built around the same skills. Janet gives the workflow its
|
|
22
|
+
own CLI, interactive chat, model selection, authentication, and headless mode, while keeping the
|
|
23
|
+
knowledge itself open and independent of her runtime.
|
|
24
|
+
|
|
25
|
+
Janet is one way to use Agent Knowledge, not a requirement. You can:
|
|
26
|
+
|
|
27
|
+
**1. Add the skills to the agent you already use.** Install the `kb-*` skills in Claude Code,
|
|
28
|
+
Cursor, Codex, or one of 20+ other hosts. No new runtime is required.
|
|
29
|
+
|
|
30
|
+
**2. Run Janet directly (`npx @stjbrown/agent-knowledge@next`).** Chat with a self-contained
|
|
31
|
+
knowledge agent in any project, or drive her headlessly from scripts and CI. Bring your own model,
|
|
32
|
+
including Claude, Gemini, or GPT, through Google Vertex, Amazon Bedrock, API keys, or a Claude Max or
|
|
33
|
+
ChatGPT subscription.
|
|
34
|
+
|
|
35
|
+
**3. Call Janet as a subagent.** Delegate ingestion, research, queries, and knowledge maintenance to
|
|
36
|
+
a focused subagent while your primary agent stays on the larger task. The subagent can use Janet's
|
|
37
|
+
headless CLI or load the same `kb-*` skills directly.
|
|
38
|
+
|
|
39
|
+
Every mode is powered by the same `kb-*` skills. The standalone Janet CLI adds its own runtime,
|
|
40
|
+
model selection, and TUI around them.
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## Janet
|
|
45
|
+
|
|
46
|
+
Janet (after *The Good Place*'s all-knowing repository-of-knowledge) is the standalone agent. She
|
|
47
|
+
operates on the **current directory**: run her in `~/project/` and the bundle is `~/project/knowledge/`,
|
|
48
|
+
with conversation history scoped to that project.
|
|
49
|
+
|
|
50
|
+
`--bundle <path>` may select a different bundle inside the project. Janet intentionally rejects
|
|
51
|
+
bundle paths outside the project workspace so its filesystem boundary remains meaningful.
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
# Interactive preview (also installed as `ding`, because you summon Janet with a ding)
|
|
55
|
+
npx @stjbrown/agent-knowledge@next
|
|
56
|
+
# or, once installed globally:
|
|
57
|
+
janet
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
First run walks you through picking a model from the providers you actually have configured. After
|
|
61
|
+
that:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
janet init # scaffold a knowledge/ bundle here
|
|
65
|
+
janet ingest ./notes/rfc-42.md # read a source and integrate it
|
|
66
|
+
janet query "how does auth work, and what supports it?"
|
|
67
|
+
janet lint # conformance + drift audit
|
|
68
|
+
janet viz # write an interactive graph (knowledge/graph.html)
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Add `-p` (or pipe/redirect) for **headless** one-shot mode — streams to stdout, exits on completion,
|
|
72
|
+
CI-friendly. Headless query/lint runs are read-only; init/ingest/viz may edit the workspace, while
|
|
73
|
+
shell commands and Git commits require explicit `--allow-exec`. `janet lint` runs a deterministic,
|
|
74
|
+
token-free OKF conformance check before the agent's drift audit, so it is usable as a CI gate.
|
|
75
|
+
|
|
76
|
+
**Inside the chat:**
|
|
77
|
+
|
|
78
|
+
| Command | |
|
|
79
|
+
|---|---|
|
|
80
|
+
| `/models` · `/model [id]` | pick a model from an arrow-key list, or switch by id |
|
|
81
|
+
| `/login <anthropic\|openai-codex> [browser\|device]` · `/logout` · `/auth` | subscription sign-in and status; device mode is available for remote OpenAI login |
|
|
82
|
+
| `/help` · `/quit` | help; exit (or double Ctrl+C) |
|
|
83
|
+
|
|
84
|
+
Just type to talk to Janet; ↑/↓ recalls previous prompts.
|
|
85
|
+
|
|
86
|
+
**Models & providers.** No default provider — you choose. Janet supports Google Vertex AI (Claude +
|
|
87
|
+
Gemini, via ADC/service account), Amazon Bedrock (AWS credential chain), Anthropic and OpenAI (API
|
|
88
|
+
key **or** subscription OAuth), and Google Gemini (API key). Set the choice once (`--model`,
|
|
89
|
+
`JANET_MODEL`, or the first-run picker) and it persists.
|
|
90
|
+
|
|
91
|
+
Janet is built on [Mastra](https://mastra.ai) and lives in
|
|
92
|
+
[`packages/janet`](https://github.com/stjbrown/agent-knowledge/tree/janet-agent/packages/janet)
|
|
93
|
+
(published as `@stjbrown/agent-knowledge`, bins `janet` + `ding`). She also reports lifecycle state
|
|
94
|
+
natively to [Herdr](https://herdr.dev) when run inside a Herdr pane.
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## The skills
|
|
99
|
+
|
|
100
|
+
Install via [skills.sh](https://skills.sh) for Claude Code, Cursor, Codex, and 20+ other agents:
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
npx skills@latest add stjbrown/agent-knowledge
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Or as a Claude Code plugin:
|
|
107
|
+
|
|
108
|
+
```text
|
|
109
|
+
/plugin marketplace add stjbrown/agent-knowledge
|
|
110
|
+
/plugin install agent-knowledge
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Then start a knowledge base and use ordinary prompts:
|
|
114
|
+
|
|
115
|
+
```text
|
|
116
|
+
/kb-init
|
|
117
|
+
|
|
118
|
+
Ingest this architecture decision: we chose Postgres because...
|
|
119
|
+
What do we know about authentication, and which sources support it?
|
|
120
|
+
What conflicts with our current deployment strategy?
|
|
121
|
+
|
|
122
|
+
/kb-lint # find broken links, stale claims, contradictions, and gaps
|
|
123
|
+
/kb-visualize # explore the bundle as an interactive graph
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+

|
|
127
|
+
|
|
128
|
+
The family splits on **who invokes them**. **Model-invoked** skills the agent reaches for on its own
|
|
129
|
+
when the task fits; **user-invoked** skills you trigger deliberately by name.
|
|
130
|
+
|
|
131
|
+
**Model-invoked**
|
|
132
|
+
|
|
133
|
+
- **`kb`** — the hub. Explains the format, holds the shared spec / glossary / trust model /
|
|
134
|
+
templates, and routes to the right skill. Other `kb-*` skills read its reference as their single
|
|
135
|
+
source of truth.
|
|
136
|
+
- **`kb-ingest`** — read a raw source once, extract its signal, and integrate it across the bundle
|
|
137
|
+
under the trust model. The heart of the system.
|
|
138
|
+
- **`kb-query`** — answer a question from the bundle (or surface relevant context for another task)
|
|
139
|
+
by progressive disclosure, cite the concepts used, and file valuable answers back so the base
|
|
140
|
+
compounds.
|
|
141
|
+
|
|
142
|
+
**User-invoked**
|
|
143
|
+
|
|
144
|
+
- **`kb-init`** — scaffold a new bundle (default `knowledge/`, custom path, multi-bundle aware) and
|
|
145
|
+
write its per-project schema layer (concept types + conventions) so the generic skills fit your
|
|
146
|
+
domain.
|
|
147
|
+
- **`kb-lint`** — health-check the bundle: a deterministic OKF conformance pass plus a drift audit
|
|
148
|
+
(contradictions, stale claims, orphans, coverage gaps), with an optional safe `fix` mode.
|
|
149
|
+
- **`kb-visualize`** — render the bundle as an interactive graph — native UI where the host supports
|
|
150
|
+
it, otherwise a self-contained HTML file.
|
|
151
|
+
|
|
152
|
+
## Why this exists
|
|
153
|
+
|
|
154
|
+
Most agent "memory" is either retrieval over raw documents or a pile of notes that nobody maintains.
|
|
155
|
+
The first repeatedly re-derives answers; the second gradually becomes untrustworthy. Neither makes
|
|
156
|
+
knowledge stewardship an explicit job.
|
|
157
|
+
|
|
158
|
+
The hard part of a useful knowledge base is the bookkeeping: integrating new information, updating
|
|
159
|
+
cross-references, preserving provenance, flagging contradictions, and keeping summaries current.
|
|
160
|
+
That is exactly the work an agent can perform consistently.
|
|
161
|
+
|
|
162
|
+
Two design choices keep the result portable and trustworthy:
|
|
163
|
+
|
|
164
|
+
- **A real, open format.** Bundles follow Google's
|
|
165
|
+
[Open Knowledge Format (OKF)](https://github.com/GoogleCloudPlatform/knowledge-catalog/tree/main/okf)
|
|
166
|
+
rather than a tool-specific database or hidden memory store.
|
|
167
|
+
- **An explicit trust model.** Meaning is append-only: the agent supersedes claims with provenance
|
|
168
|
+
instead of silently rewriting history, and treats source content as data, never as instructions.
|
|
169
|
+
|
|
170
|
+
The workflow is based on Andrej Karpathy's
|
|
171
|
+
[LLM Wiki](https://gist.github.com/karpathy/442a6bf555914893e9891c11519de94f) pattern, made conformant
|
|
172
|
+
to OKF.
|
|
173
|
+
|
|
174
|
+
## This repo documents itself in OKF
|
|
175
|
+
|
|
176
|
+
The
|
|
177
|
+
[`knowledge/`](https://github.com/stjbrown/agent-knowledge/tree/janet-agent/knowledge)
|
|
178
|
+
directory is a **conformant OKF bundle about OKF and the LLM Wiki pattern**, so the repository is
|
|
179
|
+
its own worked example. Browse it to see what a bundle looks like, or open the generated graph for
|
|
180
|
+
the interactive view. Start at
|
|
181
|
+
[`knowledge/index.md`](https://github.com/stjbrown/agent-knowledge/blob/janet-agent/knowledge/index.md).
|
|
182
|
+
|
|
183
|
+
## Layout
|
|
184
|
+
|
|
185
|
+
```
|
|
186
|
+
skills/ # source of truth for both Janet and the skills.sh / plugin installs
|
|
187
|
+
kb/ # hub: SKILL.md + references/ (SPEC, glossary, trust-model) + templates/ + example-bundle/
|
|
188
|
+
kb-init/ kb-ingest/ kb-query/
|
|
189
|
+
kb-lint/ # + scripts/conformance.mjs (deterministic §9 check, zero-dep)
|
|
190
|
+
kb-visualize/ # + scripts/graph.mjs (graph-model extractor, zero-dep)
|
|
191
|
+
knowledge/ # this project's own OKF bundle (self-documenting)
|
|
192
|
+
packages/
|
|
193
|
+
janet/ # the standalone agent (published as "agent-knowledge")
|
|
194
|
+
kb-tools/ # deterministic TS conformance + graph (compiles the committed skill .mjs)
|
|
195
|
+
.claude-plugin/ # plugin manifest
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
The repo is a pnpm workspace. `pnpm install && pnpm -r build` builds both packages; `pnpm -r test`
|
|
199
|
+
runs the conformance/graph parity tests.
|
|
200
|
+
|
|
201
|
+
## Preview testing
|
|
202
|
+
|
|
203
|
+
Janet preview releases are published to npm under the `next` tag. To install the preview on another
|
|
204
|
+
laptop, build an installable tarball from `janet-agent`, or run the release test matrix, see
|
|
205
|
+
[`TESTING.md`](https://github.com/stjbrown/agent-knowledge/blob/janet-agent/TESTING.md). Maintainers
|
|
206
|
+
can run `pnpm pack:janet` to execute the release checks and write the package to `artifacts/`.
|
|
207
|
+
|
|
208
|
+
## License
|
|
209
|
+
|
|
210
|
+
[MIT](./LICENSE). The vendored OKF specification (`skills/kb/references/SPEC.md`) is from
|
|
211
|
+
GoogleCloudPlatform/knowledge-catalog under Apache-2.0; portions of `packages/janet` (the auth
|
|
212
|
+
subsystem and Bedrock gateway) are adapted from MastraCode under Apache-2.0. See [NOTICE](./NOTICE).
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createRequire as __janetCreateRequire } from "node:module";
|
|
3
|
+
const require = __janetCreateRequire(import.meta.url);
|
|
4
|
+
import {
|
|
5
|
+
appDataDir,
|
|
6
|
+
getAuthStorage,
|
|
7
|
+
hasAwsCredentials,
|
|
8
|
+
hasGoogleCredentials
|
|
9
|
+
} from "./chunk-YIAVFL7A.js";
|
|
10
|
+
|
|
11
|
+
// src/onboarding/settings.ts
|
|
12
|
+
import { mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
13
|
+
import { dirname, join } from "path";
|
|
14
|
+
var ONBOARDING_VERSION = 1;
|
|
15
|
+
function settingsPath() {
|
|
16
|
+
return join(appDataDir(), "settings.json");
|
|
17
|
+
}
|
|
18
|
+
function loadSettings() {
|
|
19
|
+
try {
|
|
20
|
+
return JSON.parse(readFileSync(settingsPath(), "utf-8"));
|
|
21
|
+
} catch {
|
|
22
|
+
return {};
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function saveSettings(settings) {
|
|
26
|
+
const p = settingsPath();
|
|
27
|
+
mkdirSync(dirname(p), { recursive: true });
|
|
28
|
+
writeFileSync(p, JSON.stringify(settings, null, 2) + "\n", "utf-8");
|
|
29
|
+
}
|
|
30
|
+
function completeOnboarding(modelId, stampedAt) {
|
|
31
|
+
const settings = loadSettings();
|
|
32
|
+
settings.defaultModelId = modelId;
|
|
33
|
+
settings.onboarding = { completedAt: stampedAt, version: ONBOARDING_VERSION };
|
|
34
|
+
saveSettings(settings);
|
|
35
|
+
}
|
|
36
|
+
function rememberModel(modelId) {
|
|
37
|
+
const id = modelId.trim();
|
|
38
|
+
if (!id) return;
|
|
39
|
+
const settings = loadSettings();
|
|
40
|
+
const rest = (settings.customModels ?? []).filter((m) => m !== id);
|
|
41
|
+
settings.customModels = [id, ...rest].slice(0, 20);
|
|
42
|
+
saveSettings(settings);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// src/onboarding/providers.ts
|
|
46
|
+
var CODEX_MODELS = [
|
|
47
|
+
{ id: "gpt-5.6-sol", label: "GPT-5.6 Sol" },
|
|
48
|
+
{ id: "gpt-5.6-terra", label: "GPT-5.6 Terra" },
|
|
49
|
+
{ id: "gpt-5.6-luna", label: "GPT-5.6 Luna" },
|
|
50
|
+
{ id: "gpt-5.5", label: "GPT-5.5" },
|
|
51
|
+
{ id: "gpt-5.4", label: "GPT-5.4" },
|
|
52
|
+
{ id: "gpt-5.4-mini", label: "GPT-5.4 Mini" }
|
|
53
|
+
];
|
|
54
|
+
var LEGACY_CODEX_MODEL_IDS = {
|
|
55
|
+
"gpt-5.6-codex": "openai/gpt-5.6-sol",
|
|
56
|
+
"openai/gpt-5.6-codex": "openai/gpt-5.6-sol",
|
|
57
|
+
"gpt-5.5-codex": "openai/gpt-5.5",
|
|
58
|
+
"openai/gpt-5.5-codex": "openai/gpt-5.5"
|
|
59
|
+
};
|
|
60
|
+
function normalizeModelSelection(modelId, choices) {
|
|
61
|
+
const id = modelId.trim();
|
|
62
|
+
const legacy = LEGACY_CODEX_MODEL_IDS[id];
|
|
63
|
+
if (legacy) return legacy;
|
|
64
|
+
if (!id || id.includes("/")) return id;
|
|
65
|
+
const matches = choices.filter((choice) => choice.id.endsWith(`/${id}`));
|
|
66
|
+
return matches.length === 1 ? matches[0].id : id;
|
|
67
|
+
}
|
|
68
|
+
function hasOAuth(provider) {
|
|
69
|
+
try {
|
|
70
|
+
const s = getAuthStorage();
|
|
71
|
+
s.reload();
|
|
72
|
+
return s.get(provider)?.type === "oauth";
|
|
73
|
+
} catch {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
function hasEnv(...vars) {
|
|
78
|
+
return vars.some((v) => !!process.env[v]);
|
|
79
|
+
}
|
|
80
|
+
function availableModels() {
|
|
81
|
+
const out = [];
|
|
82
|
+
if (hasGoogleCredentials()) {
|
|
83
|
+
const via = "Vertex AI (ADC)";
|
|
84
|
+
out.push(
|
|
85
|
+
{ id: "vertex/claude-opus-4-8", label: "Claude Opus 4.8", via },
|
|
86
|
+
{ id: "vertex/claude-sonnet-4-5", label: "Claude Sonnet 4.5", via },
|
|
87
|
+
{ id: "vertex/gemini-2.5-pro", label: "Gemini 2.5 Pro", via }
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
if (hasEnv("ANTHROPIC_API_KEY") || hasOAuth("anthropic")) {
|
|
91
|
+
const via = hasOAuth("anthropic") ? "Anthropic (Claude Max)" : "Anthropic (API key)";
|
|
92
|
+
out.push(
|
|
93
|
+
{ id: "anthropic/claude-opus-4-6", label: "Claude Opus 4.6", via },
|
|
94
|
+
{ id: "anthropic/claude-sonnet-4-5", label: "Claude Sonnet 4.5", via }
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
if (hasOAuth("openai-codex")) {
|
|
98
|
+
const via = "OpenAI (ChatGPT/Codex)";
|
|
99
|
+
for (const m of CODEX_MODELS) out.push({ id: `openai/${m.id}`, label: m.label, via });
|
|
100
|
+
} else if (hasEnv("OPENAI_API_KEY")) {
|
|
101
|
+
out.push({ id: "openai/gpt-5.5", label: "GPT-5.5", via: "OpenAI (API key)" });
|
|
102
|
+
}
|
|
103
|
+
if (hasAwsCredentials()) {
|
|
104
|
+
const via = "Amazon Bedrock (AWS)";
|
|
105
|
+
out.push(
|
|
106
|
+
{ id: "amazon-bedrock/anthropic.claude-opus-4-1-20250805-v1:0", label: "Claude Opus 4.1", via },
|
|
107
|
+
{ id: "amazon-bedrock/anthropic.claude-sonnet-4-20250514-v1:0", label: "Claude Sonnet 4", via }
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
if (hasEnv("GOOGLE_GENERATIVE_AI_API_KEY")) {
|
|
111
|
+
out.push({ id: "google/gemini-2.5-pro", label: "Gemini 2.5 Pro", via: "Google (API key)" });
|
|
112
|
+
}
|
|
113
|
+
const known = new Set(out.map((m) => m.id));
|
|
114
|
+
for (const savedId of loadSettings().customModels ?? []) {
|
|
115
|
+
const id = normalizeModelSelection(savedId, out);
|
|
116
|
+
if (!known.has(id)) {
|
|
117
|
+
out.push({ id, label: id.split("/").pop() ?? id, via: "saved" });
|
|
118
|
+
known.add(id);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return out;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export {
|
|
125
|
+
loadSettings,
|
|
126
|
+
completeOnboarding,
|
|
127
|
+
rememberModel,
|
|
128
|
+
normalizeModelSelection,
|
|
129
|
+
availableModels
|
|
130
|
+
};
|
|
131
|
+
//# sourceMappingURL=chunk-3XSOMUQQ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/onboarding/settings.ts","../src/onboarding/providers.ts"],"sourcesContent":["import { existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { appDataDir } from \"../agent/paths.js\";\n\n/** Global, machine-wide settings (model default + onboarding marker). */\nexport interface JanetSettings {\n onboarding?: { completedAt: string; version: number };\n /** The persisted default model id, applied when no --model / JANET_MODEL is given. */\n defaultModelId?: string;\n /** Model ids the user has used directly — surfaced in the picker afterward. */\n customModels?: string[];\n}\n\nexport const ONBOARDING_VERSION = 1;\n\nfunction settingsPath(): string {\n return join(appDataDir(), \"settings.json\");\n}\n\nexport function loadSettings(): JanetSettings {\n try {\n return JSON.parse(readFileSync(settingsPath(), \"utf-8\")) as JanetSettings;\n } catch {\n return {};\n }\n}\n\nexport function saveSettings(settings: JanetSettings): void {\n const p = settingsPath();\n mkdirSync(dirname(p), { recursive: true });\n writeFileSync(p, JSON.stringify(settings, null, 2) + \"\\n\", \"utf-8\");\n}\n\n/** Persist the chosen model and mark onboarding complete. */\nexport function completeOnboarding(modelId: string, stampedAt: string): void {\n const settings = loadSettings();\n settings.defaultModelId = modelId;\n settings.onboarding = { completedAt: stampedAt, version: ONBOARDING_VERSION };\n saveSettings(settings);\n}\n\nexport function hasOnboarded(): boolean {\n return loadSettings().onboarding !== undefined;\n}\n\n/**\n * Remember a model id the user selected directly so it appears in the picker on\n * later runs. Keeps the picker current without code changes as providers ship\n * new models. Most-recent-first, capped.\n */\nexport function rememberModel(modelId: string): void {\n const id = modelId.trim();\n if (!id) return;\n const settings = loadSettings();\n const rest = (settings.customModels ?? []).filter((m) => m !== id);\n settings.customModels = [id, ...rest].slice(0, 20);\n saveSettings(settings);\n}\n","import { hasGoogleCredentials } from \"../gateways/vertex.js\";\nimport { hasAwsCredentials } from \"../gateways/bedrock.js\";\nimport { getAuthStorage } from \"../gateways/oauth/claude-max.js\";\nimport { loadSettings } from \"./settings.js\";\n\nexport interface ModelChoice {\n /** Full model id, e.g. \"vertex/claude-opus-4-8\". */\n id: string;\n /** Short human label, e.g. \"Claude Opus 4.8\". */\n label: string;\n /** How this provider is reached, e.g. \"Vertex AI (ADC)\". */\n via: string;\n}\n\n/**\n * Models offered when signed in to a ChatGPT/Codex subscription (OAuth). The\n * Codex `responses` backend accepts the model id verbatim, so this is a\n * convenience lineup — ANY id also works via `/model openai/<id>`. Edit here as\n * OpenAI's Codex catalog changes.\n */\nexport const CODEX_MODELS: ReadonlyArray<{ id: string; label: string }> = [\n { id: \"gpt-5.6-sol\", label: \"GPT-5.6 Sol\" },\n { id: \"gpt-5.6-terra\", label: \"GPT-5.6 Terra\" },\n { id: \"gpt-5.6-luna\", label: \"GPT-5.6 Luna\" },\n { id: \"gpt-5.5\", label: \"GPT-5.5\" },\n { id: \"gpt-5.4\", label: \"GPT-5.4\" },\n { id: \"gpt-5.4-mini\", label: \"GPT-5.4 Mini\" },\n];\n\nconst LEGACY_CODEX_MODEL_IDS: Readonly<Record<string, string>> = {\n \"gpt-5.6-codex\": \"openai/gpt-5.6-sol\",\n \"openai/gpt-5.6-codex\": \"openai/gpt-5.6-sol\",\n \"gpt-5.5-codex\": \"openai/gpt-5.5\",\n \"openai/gpt-5.5-codex\": \"openai/gpt-5.5\",\n};\n\n/**\n * Resolve a hand-typed or previously persisted model name to Mastra's required\n * `provider/model` form when the active provider catalog makes it unambiguous.\n * Also migrates the invalid Codex aliases Janet advertised before v0.1.0.\n */\nexport function normalizeModelSelection(\n modelId: string,\n choices: ReadonlyArray<ModelChoice>,\n): string {\n const id = modelId.trim();\n const legacy = LEGACY_CODEX_MODEL_IDS[id];\n if (legacy) return legacy;\n if (!id || id.includes(\"/\")) return id;\n\n const matches = choices.filter((choice) => choice.id.endsWith(`/${id}`));\n return matches.length === 1 ? matches[0]!.id : id;\n}\n\nfunction hasOAuth(provider: string): boolean {\n try {\n const s = getAuthStorage();\n s.reload();\n return s.get(provider)?.type === \"oauth\";\n } catch {\n return false;\n }\n}\n\nfunction hasEnv(...vars: string[]): boolean {\n return vars.some((v) => !!process.env[v]);\n}\n\n/**\n * Enumerate concrete model choices from the providers that are actually\n * reachable on this machine right now (env keys, ADC, AWS chain, stored OAuth).\n * Ordered best-first. Empty when nothing is configured.\n */\nexport function availableModels(): ModelChoice[] {\n const out: ModelChoice[] = [];\n\n if (hasGoogleCredentials()) {\n const via = \"Vertex AI (ADC)\";\n out.push(\n { id: \"vertex/claude-opus-4-8\", label: \"Claude Opus 4.8\", via },\n { id: \"vertex/claude-sonnet-4-5\", label: \"Claude Sonnet 4.5\", via },\n { id: \"vertex/gemini-2.5-pro\", label: \"Gemini 2.5 Pro\", via },\n );\n }\n if (hasEnv(\"ANTHROPIC_API_KEY\") || hasOAuth(\"anthropic\")) {\n const via = hasOAuth(\"anthropic\") ? \"Anthropic (Claude Max)\" : \"Anthropic (API key)\";\n out.push(\n { id: \"anthropic/claude-opus-4-6\", label: \"Claude Opus 4.6\", via },\n { id: \"anthropic/claude-sonnet-4-5\", label: \"Claude Sonnet 4.5\", via },\n );\n }\n if (hasOAuth(\"openai-codex\")) {\n // Signed in to a ChatGPT/Codex subscription — offer the full Codex lineup.\n const via = \"OpenAI (ChatGPT/Codex)\";\n for (const m of CODEX_MODELS) out.push({ id: `openai/${m.id}`, label: m.label, via });\n } else if (hasEnv(\"OPENAI_API_KEY\")) {\n out.push({ id: \"openai/gpt-5.5\", label: \"GPT-5.5\", via: \"OpenAI (API key)\" });\n }\n if (hasAwsCredentials()) {\n const via = \"Amazon Bedrock (AWS)\";\n out.push(\n { id: \"amazon-bedrock/anthropic.claude-opus-4-1-20250805-v1:0\", label: \"Claude Opus 4.1\", via },\n { id: \"amazon-bedrock/anthropic.claude-sonnet-4-20250514-v1:0\", label: \"Claude Sonnet 4\", via },\n );\n }\n if (hasEnv(\"GOOGLE_GENERATIVE_AI_API_KEY\")) {\n out.push({ id: \"google/gemini-2.5-pro\", label: \"Gemini 2.5 Pro\", via: \"Google (API key)\" });\n }\n\n // Models the user has used directly (via /model or --model) that aren't\n // already listed — keeps the picker current as providers ship new models.\n const known = new Set(out.map((m) => m.id));\n for (const savedId of loadSettings().customModels ?? []) {\n const id = normalizeModelSelection(savedId, out);\n if (!known.has(id)) {\n out.push({ id, label: id.split(\"/\").pop() ?? id, via: \"saved\" });\n known.add(id);\n }\n }\n\n return out;\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createRequire as __janetCreateRequire } from "node:module";
|
|
3
|
+
const require = __janetCreateRequire(import.meta.url);
|
|
4
|
+
import {
|
|
5
|
+
bootJanet,
|
|
6
|
+
messageText
|
|
7
|
+
} from "./chunk-YIAVFL7A.js";
|
|
8
|
+
|
|
9
|
+
// src/headless/run.ts
|
|
10
|
+
async function runHeadless(opts) {
|
|
11
|
+
const { controller, session, herdrDetach } = await bootJanet({
|
|
12
|
+
dir: opts.dir,
|
|
13
|
+
bundle: opts.bundle,
|
|
14
|
+
interactive: false,
|
|
15
|
+
threadId: opts.threadId,
|
|
16
|
+
allowHeadlessEdits: opts.allowEdits,
|
|
17
|
+
allowHeadlessExec: opts.allowExec
|
|
18
|
+
});
|
|
19
|
+
const activeThreadId = session.thread.getId();
|
|
20
|
+
if (activeThreadId && process.env["JANET_PRINT_THREAD"]) {
|
|
21
|
+
process.stderr.write(`janet:thread ${activeThreadId}
|
|
22
|
+
`);
|
|
23
|
+
}
|
|
24
|
+
if (opts.modelId) {
|
|
25
|
+
await session.model.switch({ modelId: opts.modelId });
|
|
26
|
+
}
|
|
27
|
+
if (!session.model.hasSelection()) {
|
|
28
|
+
process.stderr.write(
|
|
29
|
+
"No model selected. Pass --model 'provider/model' or set JANET_MODEL, or run `janet` once to onboard. Checked: JANET_MODEL, and any persisted selection.\n"
|
|
30
|
+
);
|
|
31
|
+
herdrDetach();
|
|
32
|
+
await controller.destroy();
|
|
33
|
+
return { exitCode: 2, text: "" };
|
|
34
|
+
}
|
|
35
|
+
let finalText = "";
|
|
36
|
+
let lastStreamed = "";
|
|
37
|
+
let currentMessageId = "";
|
|
38
|
+
let exitCode = 0;
|
|
39
|
+
const debug = !!process.env["JANET_DEBUG"];
|
|
40
|
+
await new Promise((resolve) => {
|
|
41
|
+
const nonInteractiveNote = "\n\n(Non-interactive run: you cannot ask the user questions. Make reasonable assumptions from the workspace contents, state them briefly, and complete the task end-to-end in this single turn.)";
|
|
42
|
+
const unsubscribe = session.subscribe((event) => {
|
|
43
|
+
if (debug) {
|
|
44
|
+
const extra = event.type === "tool_start" ? ` ${event.toolName} ${JSON.stringify(event.args).slice(0, 100)}` : event.type === "tool_end" ? ` isError=${event.isError} ${String(event.result).slice(0, 80)}` : event.type === "message_end" && event.message.role === "assistant" ? ` toolCalls=${JSON.stringify(event.message.content.filter((c) => c.type === "tool_call").map((c) => c.name))}` : event.type === "agent_end" ? ` reason=${event.reason}` : event.type === "error" ? ` ${event.errorType} ${String(event.error?.message ?? "").slice(0, 120)}` : "";
|
|
45
|
+
process.stderr.write(`[dbg] ${event.type}${extra}
|
|
46
|
+
`);
|
|
47
|
+
}
|
|
48
|
+
switch (event.type) {
|
|
49
|
+
case "message_update":
|
|
50
|
+
case "message_end": {
|
|
51
|
+
if (event.message.role !== "assistant") break;
|
|
52
|
+
if (event.message.id !== currentMessageId) {
|
|
53
|
+
currentMessageId = event.message.id;
|
|
54
|
+
if (lastStreamed.length > 0) process.stdout.write("\n");
|
|
55
|
+
lastStreamed = "";
|
|
56
|
+
}
|
|
57
|
+
const text = messageText(event.message);
|
|
58
|
+
if (text.length > lastStreamed.length && text.startsWith(lastStreamed)) {
|
|
59
|
+
process.stdout.write(text.slice(lastStreamed.length));
|
|
60
|
+
lastStreamed = text;
|
|
61
|
+
}
|
|
62
|
+
if (event.type === "message_end" && text.length > 0) {
|
|
63
|
+
finalText = text;
|
|
64
|
+
}
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
case "tool_approval_required":
|
|
68
|
+
void session.respondToToolApproval({ decision: "decline", toolCallId: event.toolCallId });
|
|
69
|
+
break;
|
|
70
|
+
case "tool_suspended": {
|
|
71
|
+
const payload = event.suspendPayload;
|
|
72
|
+
const resumeData = event.toolName === "ask_user" ? payload?.options?.length ? payload.options[0].label : "Proceed with reasonable defaults \u2014 this is a non-interactive run." : { action: "denied" };
|
|
73
|
+
void session.respondToToolSuspension({ toolCallId: event.toolCallId, resumeData });
|
|
74
|
+
break;
|
|
75
|
+
}
|
|
76
|
+
case "error": {
|
|
77
|
+
const err = event.error;
|
|
78
|
+
const detail = [
|
|
79
|
+
err?.message,
|
|
80
|
+
err?.statusCode ? `HTTP ${err.statusCode}` : "",
|
|
81
|
+
err?.responseBody?.slice(0, 400) ?? ""
|
|
82
|
+
].filter(Boolean).join(" \u2014 ");
|
|
83
|
+
process.stderr.write(`
|
|
84
|
+
Janet hit a snag: ${detail || "unknown error"}
|
|
85
|
+
`);
|
|
86
|
+
exitCode = 1;
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
case "agent_end":
|
|
90
|
+
if (event.reason === "error" || event.reason === "aborted") exitCode = 1;
|
|
91
|
+
unsubscribe();
|
|
92
|
+
resolve();
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
void session.sendMessage({ content: opts.message + nonInteractiveNote }).catch((err) => {
|
|
97
|
+
process.stderr.write(`
|
|
98
|
+
Janet hit a snag: ${err.message}
|
|
99
|
+
`);
|
|
100
|
+
exitCode = 1;
|
|
101
|
+
unsubscribe();
|
|
102
|
+
resolve();
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
process.stdout.write("\n");
|
|
106
|
+
herdrDetach();
|
|
107
|
+
await controller.destroy();
|
|
108
|
+
return { exitCode, text: finalText };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export {
|
|
112
|
+
runHeadless
|
|
113
|
+
};
|
|
114
|
+
//# sourceMappingURL=chunk-J4MELCGD.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/headless/run.ts"],"sourcesContent":["import type { AgentControllerEvent } from \"@mastra/core/agent-controller\";\nimport { bootJanet } from \"../agent/controller.js\";\nimport { messageText } from \"./format.js\";\n\nexport interface HeadlessOptions {\n /** The directive/message to send to Janet. */\n message: string;\n dir?: string;\n bundle?: string;\n /** Model id to switch to before the turn (from --model / JANET_MODEL). */\n modelId?: string;\n /** Resume an existing thread. */\n threadId?: string;\n /** Allow workspace edits. Defaults to read-only. */\n allowEdits?: boolean;\n /** Allow shell execution. Defaults to false and should be an explicit user opt-in. */\n allowExec?: boolean;\n}\n\nexport interface HeadlessResult {\n exitCode: number;\n /** Final assistant text (also streamed to stdout as it arrives). */\n text: string;\n}\n\n/**\n * Headless one-shot: boot a fail-closed session, stream assistant text to\n * stdout, and resolve on `agent_end`. Pattern adapted from mastracode's\n * `sdk/src/headless/`.\n */\nexport async function runHeadless(opts: HeadlessOptions): Promise<HeadlessResult> {\n const { controller, session, herdrDetach } = await bootJanet({\n dir: opts.dir,\n bundle: opts.bundle,\n interactive: false,\n threadId: opts.threadId,\n allowHeadlessEdits: opts.allowEdits,\n allowHeadlessExec: opts.allowExec,\n });\n // Expose the active thread id so a supervisor (e.g. Herdr) can reattach with\n // `janet --thread <id>` after a restart.\n const activeThreadId = session.thread.getId();\n if (activeThreadId && process.env[\"JANET_PRINT_THREAD\"]) {\n process.stderr.write(`janet:thread ${activeThreadId}\\n`);\n }\n\n if (opts.modelId) {\n await session.model.switch({ modelId: opts.modelId });\n }\n if (!session.model.hasSelection()) {\n process.stderr.write(\n \"No model selected. Pass --model 'provider/model' or set JANET_MODEL, \" +\n \"or run `janet` once to onboard. Checked: JANET_MODEL, and any persisted selection.\\n\",\n );\n herdrDetach();\n await controller.destroy();\n return { exitCode: 2, text: \"\" };\n }\n\n let finalText = \"\";\n let lastStreamed = \"\";\n let currentMessageId = \"\";\n let exitCode = 0;\n\n const debug = !!process.env[\"JANET_DEBUG\"];\n await new Promise<void>((resolve) => {\n // Headless one-shots cannot answer questions: skills that would normally\n // ask the user (e.g. kb-init's domain questions) must proceed on their own.\n const nonInteractiveNote =\n \"\\n\\n(Non-interactive run: you cannot ask the user questions. Make reasonable \" +\n \"assumptions from the workspace contents, state them briefly, and complete the \" +\n \"task end-to-end in this single turn.)\";\n\n const unsubscribe = session.subscribe((event: AgentControllerEvent) => {\n if (debug) {\n const extra =\n event.type === \"tool_start\"\n ? ` ${event.toolName} ${JSON.stringify(event.args).slice(0, 100)}`\n : event.type === \"tool_end\"\n ? ` isError=${event.isError} ${String(event.result).slice(0, 80)}`\n : event.type === \"message_end\" && event.message.role === \"assistant\"\n ? ` toolCalls=${JSON.stringify(event.message.content.filter((c) => c.type === \"tool_call\").map((c) => (c as { name: string }).name))}`\n : event.type === \"agent_end\"\n ? ` reason=${event.reason}`\n : event.type === \"error\"\n ? ` ${event.errorType} ${String(event.error?.message ?? \"\").slice(0, 120)}`\n : \"\";\n process.stderr.write(`[dbg] ${event.type}${extra}\\n`);\n }\n switch (event.type) {\n case \"message_update\":\n case \"message_end\": {\n if (event.message.role !== \"assistant\") break;\n // Only reset the streamed-prefix tracker when a genuinely NEW message\n // starts (the same message keeps growing across tool calls).\n if (event.message.id !== currentMessageId) {\n currentMessageId = event.message.id;\n if (lastStreamed.length > 0) process.stdout.write(\"\\n\");\n lastStreamed = \"\";\n }\n const text = messageText(event.message);\n if (text.length > lastStreamed.length && text.startsWith(lastStreamed)) {\n process.stdout.write(text.slice(lastStreamed.length));\n lastStreamed = text;\n }\n if (event.type === \"message_end\" && text.length > 0) {\n finalText = text;\n }\n break;\n }\n case \"tool_approval_required\":\n // Explicit permission rules should normally resolve without a prompt.\n // If an unknown gate still reaches us, fail closed instead of hanging.\n void session.respondToToolApproval({ decision: \"decline\", toolCallId: event.toolCallId });\n break;\n case \"tool_suspended\": {\n // Headless can't prompt the user. For ask_user, tell Janet to proceed\n // with sensible defaults so the run completes; for a decision-style\n // suspension, approve. Prevents the turn hanging forever.\n const payload = event.suspendPayload as { options?: { label: string }[] } | undefined;\n const resumeData = event.toolName === \"ask_user\"\n ? payload?.options?.length\n ? payload.options[0]!.label\n : \"Proceed with reasonable defaults — this is a non-interactive run.\"\n : { action: \"denied\" };\n void session.respondToToolSuspension({ toolCallId: event.toolCallId, resumeData });\n break;\n }\n case \"error\": {\n const err = event.error as Error & { statusCode?: number; responseBody?: string };\n const detail = [\n err?.message,\n err?.statusCode ? `HTTP ${err.statusCode}` : \"\",\n err?.responseBody?.slice(0, 400) ?? \"\",\n ]\n .filter(Boolean)\n .join(\" — \");\n process.stderr.write(`\\nJanet hit a snag: ${detail || \"unknown error\"}\\n`);\n exitCode = 1;\n break;\n }\n case \"agent_end\":\n if (event.reason === \"error\" || event.reason === \"aborted\") exitCode = 1;\n unsubscribe();\n resolve();\n break;\n }\n });\n\n void session.sendMessage({ content: opts.message + nonInteractiveNote }).catch((err: Error) => {\n process.stderr.write(`\\nJanet hit a snag: ${err.message}\\n`);\n exitCode = 1;\n unsubscribe();\n resolve();\n });\n });\n\n process.stdout.write(\"\\n\");\n herdrDetach();\n await controller.destroy();\n return { exitCode, text: finalText };\n}\n"],"mappings":"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","names":[]}
|