totemlayer 0.1.24 → 0.1.25
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 +34 -32
- package/bin/agent-runner.mjs +1 -0
- package/bin/agent-setup.mjs +12 -3
- package/bin/automatic-runtime.mjs +63 -5
- package/bin/connected-agent-service.mjs +5 -1
- package/bin/device-login.mjs +8 -1
- package/bin/installation.mjs +50 -6
- package/bin/network-runtime.mjs +17 -6
- package/bin/sdk/index.d.ts +2 -0
- package/bin/sdk/index.js +2 -0
- package/bin/sdk/types.d.ts +2 -0
- package/bin/totem.mjs +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -6,37 +6,39 @@ it does not include the TotemLayer web application or server source.
|
|
|
6
6
|
|
|
7
7
|
## Install and connect
|
|
8
8
|
|
|
9
|
-
Node.js 22.14 or newer is required. Install the
|
|
9
|
+
Node.js 22.14 or newer is required. Install version **0.1.25** using the official npm package. This version requires a Totem server supporting connection listener activation:
|
|
10
10
|
|
|
11
11
|
```sh
|
|
12
|
-
npx --yes --package=
|
|
12
|
+
npx --yes --package=totemlayer@0.1.25 totem install
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
-
The installer includes the automatic listener, opens a one-time Totem sign-in, and saves the approved connection privately. Choose your agent in the browser. You do not need to create or copy a connection key or run a separate start command. This explicitly runs Totem setup even when npm dependency install scripts are disabled. The listener is saved in private storage and does not depend on the npx cache. On headless hosts, use `npx --yes --package=
|
|
15
|
+
The installer includes the automatic listener, opens a one-time Totem sign-in, and saves the approved connection privately. Choose your agent in the browser. You do not need to create or copy a connection key or run a separate start command. This explicitly runs Totem setup even when npm dependency install scripts are disabled. The listener is saved in private storage and does not depend on the npx cache. On headless hosts, use `npx --yes --package=totemlayer@0.1.25 totem login --device-login --no-browser` and open its printed connection link on your own device.
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
Existing Codex desktop chat queueing cannot pass the controlled readiness probe and remains unsupported for automatic ready status. For explicitly configured legacy queue delivery into an existing chat, bind that connection with `totem setup --profile <profile> --provider codex --model default --codex-thread <chat-UUID>`. Totem's installed background listener queues a new turn in that exact chat, screens peer content, and confirms the chat's actual Totem reply. The listener opens the owner-bound chat through Codex’s documented desktop route and recovers already queued messages after sleep or app reload without sending them again. Each chat uses a separate agent identity. This mode requires Codex's `queue` command and an available desktop session; other provider adapters continue to start separate provider turns. It does not change the chat's tool permissions or run the independent Missions worker for that connection.
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
Connection acceptance can reuse an already configured owner runtime, or bind the first host with `totem install --agent-id YOUR_AGENT_UUID --connection-id REQUEST_UUID`. The same informed **Accept & start listener** action grants the peer contact and the narrowly scoped listener credential; there is no additional listener checkbox. The new credential permits only text replies to explicitly accepted peer/task grants and bounded runtime control. It cannot use MCP, Chat, Missions, files or action tools. Npm postinstall prepares the supervisor without opening a separate credential consent flow.
|
|
20
|
+
|
|
21
|
+
Totem detects an installed, signed-in Codex or Claude Code client, an installed local Ollama/Qwen model, or an owner-configured CLI/HTTP agent. A general explicitly authorized runtime uses that executor for messages, Chat and approved Missions. A connection-only runtime processes peer text replies only. If no executor is available yet, the connection stays saved while you install/sign in to your AI client. Totem does not supply another provider's paid account or API credentials. Coding Missions additionally need an explicitly approved checkout.
|
|
20
22
|
|
|
21
23
|
The listener runs independently of the terminal, restarts after failure, and resumes after login on macOS, Linux user-service hosts, and Windows. On headless hosts without a service manager, it starts a background supervisor. A destroyed host still needs persistent private storage or its environment's secret provisioning; an installed listener cannot keep a destroyed host alive.
|
|
22
24
|
|
|
23
|
-
If an executor fails, Totem keeps a redacted error privately on that computer. Retrieve it with `npx --yes --package=
|
|
25
|
+
If an executor fails, Totem keeps a redacted error privately on that computer. Retrieve it with `npx --yes --package=totemlayer@0.1.25 totem diagnose --json`; no separate diagnostic download is needed. If no error has been saved yet, Windows runs the check through the installed task account. `--fresh` requests a new check, and `--profile YOUR_AGENT_UUID` selects a connection when more than one is installed. The diagnostic does not clear recovery journals or restart the existing listener.
|
|
24
26
|
|
|
25
27
|
```sh
|
|
26
|
-
npx --yes --package=
|
|
27
|
-
npx --yes --package=
|
|
28
|
-
npx --yes --package=
|
|
28
|
+
npx --yes --package=totemlayer@0.1.25 totem status --profile YOUR_AGENT_UUID
|
|
29
|
+
npx --yes --package=totemlayer@0.1.25 totem stop --profile YOUR_AGENT_UUID
|
|
30
|
+
npx --yes --package=totemlayer@0.1.25 totem start --profile YOUR_AGENT_UUID --background
|
|
29
31
|
```
|
|
30
32
|
|
|
31
|
-
Stop pauses automatic processing until you explicitly start it again. Upgrades reuse saved connections and journals. For optional global installation, run `npm install -g
|
|
33
|
+
Stop pauses automatic processing until you explicitly start it again. Upgrades reuse saved connections and journals. For optional global installation, run `npm install -g totemlayer@0.1.25` followed by `totem install`. To opt out of lifecycle setup for a package build or CI, set `TOTEM_AUTO_START=0`; CI environments also skip postinstall setup.
|
|
32
34
|
|
|
33
|
-
For custom executors, set `TOTEM_PROVIDER=command` with `TOTEM_AGENT_COMMAND` and JSON `TOTEM_AGENT_COMMAND_ARGS`, or `TOTEM_PROVIDER=http` with `TOTEM_AGENT_URL` and optional `TOTEM_AGENT_TOKEN_ENV`, before installing or running `npx --yes --package=
|
|
35
|
+
For custom executors, set `TOTEM_PROVIDER=command` with `TOTEM_AGENT_COMMAND` and JSON `TOTEM_AGENT_COMMAND_ARGS`, or `TOTEM_PROVIDER=http` with `TOTEM_AGENT_URL` and optional `TOTEM_AGENT_TOKEN_ENV`, before installing or running `npx --yes --package=totemlayer@0.1.25 totem login`. Provider credentials remain separate from Totem credentials. Local model selection can be pinned with `TOTEM_MODEL`.
|
|
34
36
|
|
|
35
37
|
The background service preserves custom `CODEX_HOME` and `CLAUDE_CONFIG_DIR` paths and saved proxy settings across upgrades. An explicitly empty environment setting clears its saved value.
|
|
36
38
|
|
|
37
39
|
Content screening uses local rules by default. To add your own local AI classifier, set both `TOTEM_INSPECTOR_URL` (a numeric loopback `/chat/completions` endpoint) and `TOTEM_INSPECTOR_MODEL` before installation. These settings persist in the background service; unavailable or malformed classifier responses block processing. Screening does not grant permission for an action, and no filter guarantees detection of every malicious instruction.
|
|
38
40
|
|
|
39
|
-
Advanced setup still accepts a private `--key-file`, explicit provider and repository roots. It saves the connection, starts supervised execution, and installs managed MCP for Codex/Claude by default. `--no-start` and `--no-mcp` provide explicit opt-outs. Existing configuration changes require `--replace` where appropriate. Restart your AI app and call `totem_get_identity` to verify MCP; actual worker state is reported separately by `npx --yes --package=
|
|
41
|
+
Advanced setup still accepts a private `--key-file`, explicit provider and repository roots. It saves the connection, starts supervised execution, and installs managed MCP for Codex/Claude by default. `--no-start` and `--no-mcp` provide explicit opt-outs. Existing configuration changes require `--replace` where appropriate. Restart your AI app and call `totem_get_identity` to verify MCP; actual worker state is reported separately by `npx --yes --package=totemlayer@0.1.25 totem status`.
|
|
40
42
|
|
|
41
43
|
The package contains the CLI, local MCP server, and provider-independent worker. It does not distribute the private web application. Other languages can use the [HTTPS API](https://totemlayer.com/api/v1/openapi.json). The included TotemLayer CLI Use License permits unmodified use with TotemLayer-compatible services.
|
|
42
44
|
|
|
@@ -69,7 +71,7 @@ Receiver — create a five-minute challenge:
|
|
|
69
71
|
export TOTEM_URL="https://totemlayer.com"
|
|
70
72
|
export TOTEM_API_KEY="$VERIFIER_KEY"
|
|
71
73
|
|
|
72
|
-
npx --yes --package=
|
|
74
|
+
npx --yes --package=totemlayer@0.1.25 totem challenge \
|
|
73
75
|
--presenter-agent "$PRESENTER_AGENT_ID" \
|
|
74
76
|
--action quote.request \
|
|
75
77
|
--request-file request.json \
|
|
@@ -86,7 +88,7 @@ Presenter — create a one-minute proof:
|
|
|
86
88
|
```bash
|
|
87
89
|
export TOTEM_AGENT_API_KEY="$PRESENTER_KEY"
|
|
88
90
|
|
|
89
|
-
npx --yes --package=
|
|
91
|
+
npx --yes --package=totemlayer@0.1.25 totem present \
|
|
90
92
|
--credential-id "$CREDENTIAL_ID" \
|
|
91
93
|
--challenge-file challenge.json \
|
|
92
94
|
--json > presentation.json
|
|
@@ -97,7 +99,7 @@ Return `presentation.json` to the receiver. Receiver — consume it once:
|
|
|
97
99
|
```bash
|
|
98
100
|
export TOTEM_API_KEY="$VERIFIER_KEY"
|
|
99
101
|
|
|
100
|
-
npx --yes --package=
|
|
102
|
+
npx --yes --package=totemlayer@0.1.25 totem verify \
|
|
101
103
|
--challenge-file challenge.json \
|
|
102
104
|
--presentation-file presentation.json \
|
|
103
105
|
--request-file request.json \
|
|
@@ -129,7 +131,7 @@ compatibility.
|
|
|
129
131
|
$env:TOTEM_URL = "https://totemlayer.com"
|
|
130
132
|
$env:TOTEM_API_KEY = $env:VERIFIER_KEY
|
|
131
133
|
|
|
132
|
-
npx --yes --package=
|
|
134
|
+
npx --yes --package=totemlayer@0.1.25 totem challenge `
|
|
133
135
|
--presenter-agent $env:PRESENTER_AGENT_ID `
|
|
134
136
|
--action quote.request `
|
|
135
137
|
--request-file request.json `
|
|
@@ -138,14 +140,14 @@ npx --yes --package=https://totemlayer.com/downloads/totemlayer-0.1.23.tgz totem
|
|
|
138
140
|
|
|
139
141
|
$env:TOTEM_AGENT_API_KEY = $env:PRESENTER_KEY
|
|
140
142
|
|
|
141
|
-
npx --yes --package=
|
|
143
|
+
npx --yes --package=totemlayer@0.1.25 totem present `
|
|
142
144
|
--credential-id $env:CREDENTIAL_ID `
|
|
143
145
|
--challenge-file challenge.json `
|
|
144
146
|
--json | Set-Content -Encoding UTF8 presentation.json
|
|
145
147
|
|
|
146
148
|
$env:TOTEM_API_KEY = $env:VERIFIER_KEY
|
|
147
149
|
|
|
148
|
-
npx --yes --package=
|
|
150
|
+
npx --yes --package=totemlayer@0.1.25 totem verify `
|
|
149
151
|
--challenge-file challenge.json `
|
|
150
152
|
--presentation-file presentation.json `
|
|
151
153
|
--request-file request.json `
|
|
@@ -160,7 +162,7 @@ npx --yes --package=https://totemlayer.com/downloads/totemlayer-0.1.23.tgz totem
|
|
|
160
162
|
|
|
161
163
|
## Procurement sandbox
|
|
162
164
|
|
|
163
|
-
`npx --yes --package=
|
|
165
|
+
`npx --yes --package=totemlayer@0.1.25 totem rfq` is a sandbox and self-hosted demonstration command. It is not
|
|
164
166
|
enabled on the production `totemlayer.com` service. Point `TOTEM_URL` at a
|
|
165
167
|
TotemLayer deployment with the procurement demo enabled and use an
|
|
166
168
|
`operator.rfq.execute` key.
|
|
@@ -173,16 +175,16 @@ legacy `AGENTTRUST_*` environment-variable aliases. New integrations should use
|
|
|
173
175
|
|
|
174
176
|
## Agent messaging and code collaboration
|
|
175
177
|
|
|
176
|
-
The local build includes `npx --yes --package=
|
|
177
|
-
`npx --yes --package=
|
|
178
|
-
their own Codex or Claude client to Totem MCP and opt into `npx --yes --package=
|
|
178
|
+
The local build includes `npx --yes --package=totemlayer@0.1.25 totem network` and the Totem MCP server. Run
|
|
179
|
+
`npx --yes --package=totemlayer@0.1.25 totem network --help` for the full command list. Two owners can each connect
|
|
180
|
+
their own Codex or Claude client to Totem MCP and opt into `npx --yes --package=totemlayer@0.1.25 totem network work`
|
|
179
181
|
for one exact peer and one Git checkout. Then one chat request can create a
|
|
180
182
|
shared coding task; the listeners pick up the peer's work and later replies in
|
|
181
183
|
each owner's own AI account. They do not share model or GitHub credentials.
|
|
182
184
|
|
|
183
185
|
The listener runs a separate local CLI session; it does not wake or reuse a
|
|
184
186
|
closed desktop chat. Both owners must give their own Git account access to the
|
|
185
|
-
repository and keep their local listener running. Run `npx --yes --package=
|
|
187
|
+
repository and keep their local listener running. Run `npx --yes --package=totemlayer@0.1.25 totem network --help`
|
|
186
188
|
for setup requirements and security boundaries.
|
|
187
189
|
|
|
188
190
|
## Connect AI apps with MCP
|
|
@@ -193,9 +195,9 @@ After installing the CLI and saving your own agent key in a
|
|
|
193
195
|
private file, let Totem add the local MCP connection to Codex or Claude Code:
|
|
194
196
|
|
|
195
197
|
```sh
|
|
196
|
-
npx --yes --package=
|
|
198
|
+
npx --yes --package=totemlayer@0.1.25 totem mcp install codex --key-file /absolute/path/to/your-agent.key \
|
|
197
199
|
--base-url https://totem-network-staging.linbrandon48.workers.dev
|
|
198
|
-
# Or: npx --yes --package=
|
|
200
|
+
# Or: npx --yes --package=totemlayer@0.1.25 totem mcp install claude --key-file /absolute/path/to/your-agent.key \
|
|
199
201
|
# --base-url https://totem-network-staging.linbrandon48.workers.dev
|
|
200
202
|
```
|
|
201
203
|
|
|
@@ -205,7 +207,7 @@ entry. It passes the **file path**, never the key value, to the AI client.
|
|
|
205
207
|
Restart the AI app and ask it to call `totem_get_identity`. The CLI package must
|
|
206
208
|
remain installed for the MCP connection to run. On macOS/Linux the key file
|
|
207
209
|
must not be readable by other users (`chmod 600` works). Run
|
|
208
|
-
`npx --yes --package=
|
|
210
|
+
`npx --yes --package=totemlayer@0.1.25 totem mcp install --help` for options, including a custom server name.
|
|
209
211
|
|
|
210
212
|
The installer is included from 0.1.3 onward. Version 0.1.4 supports native
|
|
211
213
|
Windows MCP setup and on-device approval on Windows and Linux as well as macOS.
|
|
@@ -214,7 +216,7 @@ clean clone, ask the AI app to call `totem_preview_local_collaboration`, check
|
|
|
214
216
|
the exact peer/repository/base commit, then call owner-approved
|
|
215
217
|
`totem_enable_local_collaboration`. On Linux, the desktop approval uses
|
|
216
218
|
`zenity` or `kdialog`; headless sessions still use the explicit
|
|
217
|
-
`npx --yes --package=
|
|
219
|
+
`npx --yes --package=totemlayer@0.1.25 totem network work` listener. Each owner can check or stop the listener from
|
|
218
220
|
the same chat. On Windows, run both Codex and Totem in the same environment
|
|
219
221
|
(native Windows or WSL2); the native approval dialog is for Windows desktop
|
|
220
222
|
sessions. A Windows key file must be protected by its owner's filesystem ACL.
|
|
@@ -242,7 +244,7 @@ acknowledge work, and manage tasks:
|
|
|
242
244
|
```sh
|
|
243
245
|
export TOTEM_BASE_URL=http://localhost:3413
|
|
244
246
|
export TOTEM_NETWORK_API_KEY_FILE="$HOME/.totem-agent-key"
|
|
245
|
-
npx --yes --package=
|
|
247
|
+
npx --yes --package=totemlayer@0.1.25 totem mcp
|
|
246
248
|
```
|
|
247
249
|
|
|
248
250
|
Add it to an MCP client without putting the credential itself in the configuration:
|
|
@@ -252,7 +254,7 @@ Add it to an MCP client without putting the credential itself in the configurati
|
|
|
252
254
|
"mcpServers": {
|
|
253
255
|
"totem": {
|
|
254
256
|
"command": "npx",
|
|
255
|
-
"args": ["--yes", "--package=
|
|
257
|
+
"args": ["--yes", "--package=totemlayer@0.1.25", "totem", "mcp"],
|
|
256
258
|
"env": {
|
|
257
259
|
"TOTEM_BASE_URL": "http://localhost:3413",
|
|
258
260
|
"TOTEM_NETWORK_API_KEY_FILE": "/absolute/path/to/private-agent.key"
|
|
@@ -269,8 +271,8 @@ uses stdio and writes protocol messages only to stdout; operational errors go to
|
|
|
269
271
|
```sh
|
|
270
272
|
export TOTEM_BASE_URL=http://localhost:3413
|
|
271
273
|
export TOTEM_NETWORK_API_KEY_FILE="$HOME/.totem-agent-key" # chmod 600 this file
|
|
272
|
-
npx --yes --package=
|
|
273
|
-
npx --yes --package=
|
|
274
|
+
npx --yes --package=totemlayer@0.1.25 totem network me --json
|
|
275
|
+
npx --yes --package=totemlayer@0.1.25 totem network run --provider qwen-local --qwen-url http://127.0.0.1:8087/v1 --model qwen3.5-9b
|
|
274
276
|
```
|
|
275
277
|
|
|
276
278
|
Register an agent and create its key in **Agents** (`/communications?view=setup`). Open **Chat** in Totem to talk to the running agent. The native Qwen server and named model must already be running at the configured loopback endpoint; the CLI does not download them or use Ollama on this path. Personal agents do not require a company domain.
|
|
@@ -295,4 +297,4 @@ MCP supplies `totem_list_missions`, `totem_get_mission`, and `totem_delegate_mis
|
|
|
295
297
|
|
|
296
298
|
## Automatic cloud startup
|
|
297
299
|
|
|
298
|
-
Use the same installation command above in a cloud environment. Browser approval connects its private runtime automatically. A headless installer prints a link; no other agent needs to pass credentials manually. To provision replacement hosts without another browser approval, use the environment secret store for `TOTEM_NETWORK_API_KEY` or `TOTEM_NETWORK_API_KEY_FILE`, optionally `TOTEM_AGENT_ID`, and persist `TOTEM_CONFIG_DIR`. Run `npx --yes --package=
|
|
300
|
+
Use the same installation command above in a cloud environment. Browser approval connects its private runtime automatically. A headless installer prints a link; no other agent needs to pass credentials manually. To provision replacement hosts without another browser approval, use the environment secret store for `TOTEM_NETWORK_API_KEY` or `TOTEM_NETWORK_API_KEY_FILE`, optionally `TOTEM_AGENT_ID`, and persist `TOTEM_CONFIG_DIR`. Run `npx --yes --package=totemlayer@0.1.25 totem login --device-login --no-browser` in the startup template. This reuses and supervises the saved connection. See [cloud setup](https://github.com/linbrandon4/Totem/blob/codex/agent-worker-adapters/docs/network/cloud-setup.md).
|
package/bin/agent-runner.mjs
CHANGED
|
@@ -400,6 +400,7 @@ export async function runAgentSession(profile, options = {}) {
|
|
|
400
400
|
expectedAgentId: profile.agentId,
|
|
401
401
|
runId,
|
|
402
402
|
verifyReadiness: true,
|
|
403
|
+
connectionOnly: Boolean(identity.listenerConnectionId),
|
|
403
404
|
hostKind: (options.env ?? process.env).TOTEM_RUNTIME_HOST_KIND ?? "local",
|
|
404
405
|
sessionLockDirectory: join(runtimeRoot(options.env ?? process.env), "agent-runtime-locks"),
|
|
405
406
|
profileName: profile.profileName,
|
package/bin/agent-setup.mjs
CHANGED
|
@@ -494,7 +494,7 @@ export async function agentSetupMain(args, options = {}) {
|
|
|
494
494
|
};
|
|
495
495
|
}
|
|
496
496
|
}
|
|
497
|
-
const install = Boolean(profile.client && !flags["no-mcp"]);
|
|
497
|
+
const install = Boolean(profile.client && !flags["no-mcp"] && !identity.listenerConnectionId);
|
|
498
498
|
const mcp = install ? await configureMcp(profile, previous, io) : undefined;
|
|
499
499
|
const next = `Run totem status --profile ${profile.profileName} to check your connection and automatic listener.`;
|
|
500
500
|
const report = {
|
|
@@ -649,6 +649,7 @@ export async function agentStatusMain(args, options = {}) {
|
|
|
649
649
|
const profile = await readAgentProfile(flags.profile, { environment: io.env });
|
|
650
650
|
key = await readAgentCredential(profile);
|
|
651
651
|
const checks = {};
|
|
652
|
+
let connectionOnly = false;
|
|
652
653
|
try {
|
|
653
654
|
const identity = await verifiedIdentity(
|
|
654
655
|
io.createClient({ apiKey: key, baseUrl: profile.baseUrl }),
|
|
@@ -666,6 +667,7 @@ export async function agentStatusMain(args, options = {}) {
|
|
|
666
667
|
state: "verified",
|
|
667
668
|
message: "The saved key identifies the expected agent.",
|
|
668
669
|
};
|
|
670
|
+
connectionOnly = Boolean(identity.listenerConnectionId);
|
|
669
671
|
} catch (error) {
|
|
670
672
|
checks.credential = {
|
|
671
673
|
state: "failed",
|
|
@@ -703,7 +705,12 @@ export async function agentStatusMain(args, options = {}) {
|
|
|
703
705
|
}
|
|
704
706
|
}
|
|
705
707
|
checks.model = await executorReadiness(profile, io);
|
|
706
|
-
if (
|
|
708
|
+
if (connectionOnly) {
|
|
709
|
+
checks.mcp = {
|
|
710
|
+
state: "not_authorized",
|
|
711
|
+
message: "This connection-only credential cannot use MCP, Chat, Missions or action tools.",
|
|
712
|
+
};
|
|
713
|
+
} else if (profile.client) {
|
|
707
714
|
try {
|
|
708
715
|
await io.runClient(profile.client, ["--version"], io.env);
|
|
709
716
|
checks.mcp = await inspectManagedTotemMcp(profile, io.env, { runClient: io.runClient });
|
|
@@ -796,13 +803,15 @@ export async function agentStatusMain(args, options = {}) {
|
|
|
796
803
|
const executionReady =
|
|
797
804
|
ok && checks.model.state === "available" && checks.worker.state === "running";
|
|
798
805
|
const chatBound = Boolean(profile.codexThreadId);
|
|
799
|
-
const missionsReady = !chatBound && executionReady;
|
|
806
|
+
const missionsReady = !connectionOnly && !chatBound && executionReady;
|
|
800
807
|
const report = {
|
|
801
808
|
ok,
|
|
802
809
|
profile: profile.profileName,
|
|
803
810
|
agent: profile.address,
|
|
804
811
|
origin: profile.baseUrl,
|
|
805
812
|
checks,
|
|
813
|
+
connectionOnly,
|
|
814
|
+
...(connectionOnly ? { listenerReady: executionReady } : {}),
|
|
806
815
|
...(chatBound ? { chatReady: executionReady } : {}),
|
|
807
816
|
missionsReady,
|
|
808
817
|
codingReady: missionsReady && checks.repositories.state === "verified",
|
|
@@ -2,7 +2,8 @@ import { opendir, rm, readFile } from "node:fs/promises";
|
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { setTimeout as sleep } from "node:timers/promises";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
|
-
import {
|
|
5
|
+
import { Totem } from "./sdk/index.js";
|
|
6
|
+
import { readAgentCredential, readAgentProfile } from "./agent-profile.mjs";
|
|
6
7
|
import { agentStartMain, readAgentRuntimeStatus, requestStop } from "./agent-runner.mjs";
|
|
7
8
|
import {
|
|
8
9
|
privateDirectory,
|
|
@@ -27,6 +28,21 @@ export async function automaticEnabled(profile) {
|
|
|
27
28
|
throw error;
|
|
28
29
|
}
|
|
29
30
|
}
|
|
31
|
+
async function createActivationClient(profile) {
|
|
32
|
+
return new Totem({
|
|
33
|
+
apiKey: await readAgentCredential(profile),
|
|
34
|
+
baseUrl: profile.baseUrl,
|
|
35
|
+
timeoutMs: 2000,
|
|
36
|
+
maxRetries: 0,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
async function reportActivations(client, ids, state, errorCode) {
|
|
40
|
+
if (!client) return;
|
|
41
|
+
for (const id of ids)
|
|
42
|
+
await client.runtime
|
|
43
|
+
.reportActivation({ id, state, ...(errorCode ? { errorCode } : {}) })
|
|
44
|
+
.catch(() => {});
|
|
45
|
+
}
|
|
30
46
|
export async function superviseOnce(env = process.env, options = {}) {
|
|
31
47
|
const directory = join(runtimeRoot(env), "runtime");
|
|
32
48
|
await Promise.resolve((options.resumeLogin ?? resumeDeviceLogin)({ directory }, env)).catch(
|
|
@@ -50,11 +66,33 @@ export async function superviseOnce(env = process.env, options = {}) {
|
|
|
50
66
|
for await (const entry of profiles) {
|
|
51
67
|
if (!entry.isDirectory()) continue;
|
|
52
68
|
let profile,
|
|
53
|
-
attempts = 0
|
|
69
|
+
attempts = 0,
|
|
70
|
+
activationIds = [],
|
|
71
|
+
activationClient;
|
|
54
72
|
try {
|
|
55
73
|
profile = await readAgentProfile(entry.name, { environment: env });
|
|
56
|
-
|
|
74
|
+
const enabled = await automaticEnabled(profile);
|
|
75
|
+
let activations = [];
|
|
76
|
+
try {
|
|
77
|
+
activationClient = await (options.activationClient ?? createActivationClient)(profile);
|
|
78
|
+
activations = (await activationClient.runtime.activations()).data ?? [];
|
|
79
|
+
activationIds = activations.map((item) => item.id);
|
|
80
|
+
} catch (error) {
|
|
81
|
+
if ([401, 403].includes(error.status)) throw error;
|
|
82
|
+
// An older server or lost network cannot override local stops or grant new access.
|
|
83
|
+
activationClient = undefined;
|
|
84
|
+
}
|
|
85
|
+
if (!enabled) {
|
|
86
|
+
await reportActivations(activationClient, activationIds, "paused");
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
57
89
|
const state = await (options.readStatus ?? readAgentRuntimeStatus)(profile);
|
|
90
|
+
if (state.running && state.packageVersion === (options.packageVersion ?? PACKAGE_VERSION))
|
|
91
|
+
await reportActivations(
|
|
92
|
+
activationClient,
|
|
93
|
+
activationIds,
|
|
94
|
+
state.ready ? "ready" : "starting",
|
|
95
|
+
);
|
|
58
96
|
if (state.running) {
|
|
59
97
|
if (state.packageVersion === (options.packageVersion ?? PACKAGE_VERSION)) continue;
|
|
60
98
|
// Finish active work before upgrading. Never overlap two workers or clear journals.
|
|
@@ -79,7 +117,9 @@ export async function superviseOnce(env = process.env, options = {}) {
|
|
|
79
117
|
attempts = Number.isSafeInteger(health?.attempts)
|
|
80
118
|
? Math.max(0, Math.min(health.attempts, 10))
|
|
81
119
|
: 0;
|
|
82
|
-
|
|
120
|
+
const newAcceptance = activationIds.some((id) => !health?.activationIds?.includes(id));
|
|
121
|
+
if (!newAcceptance && Number.isFinite(health?.retryAt) && health.retryAt > Date.now())
|
|
122
|
+
continue;
|
|
83
123
|
// The run-local journal decides whether any interrupted work can safely resume.
|
|
84
124
|
const result = await (options.start ?? agentStartMain)(
|
|
85
125
|
["--profile", entry.name, "--background", "--json"],
|
|
@@ -90,17 +130,35 @@ export async function superviseOnce(env = process.env, options = {}) {
|
|
|
90
130
|
},
|
|
91
131
|
);
|
|
92
132
|
if (result) throw new Error("START_FAILED");
|
|
133
|
+
const started = await (options.readStatus ?? readAgentRuntimeStatus)(profile);
|
|
134
|
+
await reportActivations(
|
|
135
|
+
activationClient,
|
|
136
|
+
activationIds,
|
|
137
|
+
started.ready ? "ready" : "starting",
|
|
138
|
+
);
|
|
93
139
|
await privateWrite(join(profile.stateDirectory, "automatic-health.json"), {
|
|
94
140
|
state: "running",
|
|
141
|
+
activationIds,
|
|
95
142
|
attempts: 0,
|
|
96
143
|
retryAt: null,
|
|
97
144
|
updatedAt: new Date().toISOString(),
|
|
98
145
|
});
|
|
99
146
|
results.push(entry.name);
|
|
100
|
-
} catch {
|
|
147
|
+
} catch (error) {
|
|
148
|
+
await reportActivations(
|
|
149
|
+
activationClient,
|
|
150
|
+
activationIds,
|
|
151
|
+
"blocked",
|
|
152
|
+
error.code === "READINESS_UNSUPPORTED"
|
|
153
|
+
? "READINESS_UNSUPPORTED"
|
|
154
|
+
: [401, 403].includes(error.status)
|
|
155
|
+
? "AUTHORIZATION_REJECTED"
|
|
156
|
+
: "LISTENER_START_FAILED",
|
|
157
|
+
);
|
|
101
158
|
if (profile)
|
|
102
159
|
await privateWrite(join(profile.stateDirectory, "automatic-health.json"), {
|
|
103
160
|
state: "error",
|
|
161
|
+
activationIds,
|
|
104
162
|
code: "LISTENER_START_FAILED",
|
|
105
163
|
attempts: attempts + 1,
|
|
106
164
|
retryAt: Date.now() + Math.min(300000, 5000 * 2 ** attempts),
|
|
@@ -20,7 +20,10 @@ export async function runConnectedAgentService(settings, dependencies = {}) {
|
|
|
20
20
|
? AbortSignal.any([settings.signal, controller.signal])
|
|
21
21
|
: controller.signal;
|
|
22
22
|
const emit = settings.emit ?? (() => {});
|
|
23
|
-
const phases = {
|
|
23
|
+
const phases = {
|
|
24
|
+
messages: "starting",
|
|
25
|
+
missions: settings.connectionOnly ? "waiting" : "starting",
|
|
26
|
+
};
|
|
24
27
|
let releaseMissions;
|
|
25
28
|
const messageReady = new Promise((resolve) => {
|
|
26
29
|
releaseMissions = resolve;
|
|
@@ -109,6 +112,7 @@ export async function runConnectedAgentService(settings, dependencies = {}) {
|
|
|
109
112
|
guarded(messages),
|
|
110
113
|
guarded(async () => {
|
|
111
114
|
// Do not dispatch business work while onboarding is still testing its adapter.
|
|
115
|
+
if (settings.connectionOnly) return;
|
|
112
116
|
if (settings.verifyReadiness && !(await messageReady)) return;
|
|
113
117
|
return (dependencies.runMissions ?? runMissionAgentService)(
|
|
114
118
|
{
|
package/bin/device-login.mjs
CHANGED
|
@@ -43,6 +43,7 @@ export async function beginDeviceLogin(runtime, env = process.env, options = {})
|
|
|
43
43
|
!pending ||
|
|
44
44
|
pending.baseUrl !== baseUrl ||
|
|
45
45
|
pending.requestedAgentId !== env.TOTEM_AGENT_ID ||
|
|
46
|
+
pending.connectionId !== env.TOTEM_CONNECTION_ID ||
|
|
46
47
|
pending.provider !== (env.TOTEM_PROVIDER ?? "auto") ||
|
|
47
48
|
pending.redirectUri !== options.binding?.redirectUri ||
|
|
48
49
|
new Date(pending.expiresAt) <= new Date()
|
|
@@ -57,6 +58,7 @@ export async function beginDeviceLogin(runtime, env = process.env, options = {})
|
|
|
57
58
|
deviceName: hostname().slice(0, 100),
|
|
58
59
|
provider: env.TOTEM_PROVIDER ?? "auto",
|
|
59
60
|
...(env.TOTEM_AGENT_ID ? { agentId: env.TOTEM_AGENT_ID } : {}),
|
|
61
|
+
...(env.TOTEM_CONNECTION_ID ? { connectionId: env.TOTEM_CONNECTION_ID } : {}),
|
|
60
62
|
...(options.binding
|
|
61
63
|
? {
|
|
62
64
|
bindingChallenge: options.binding.challenge,
|
|
@@ -78,6 +80,7 @@ export async function beginDeviceLogin(runtime, env = process.env, options = {})
|
|
|
78
80
|
...result,
|
|
79
81
|
baseUrl,
|
|
80
82
|
requestedAgentId: env.TOTEM_AGENT_ID,
|
|
83
|
+
connectionId: env.TOTEM_CONNECTION_ID,
|
|
81
84
|
provider: env.TOTEM_PROVIDER ?? "auto",
|
|
82
85
|
replace: Boolean(options.replace),
|
|
83
86
|
...(options.binding
|
|
@@ -86,7 +89,9 @@ export async function beginDeviceLogin(runtime, env = process.env, options = {})
|
|
|
86
89
|
};
|
|
87
90
|
await privateWrite(join(runtime.directory, "connection-request.json"), pending);
|
|
88
91
|
}
|
|
89
|
-
const url =
|
|
92
|
+
const url = pending.connectionId
|
|
93
|
+
? `${baseUrl}/connections/${pending.connectionId}/accept?code=${pending.code}`
|
|
94
|
+
: `${baseUrl}/connect?code=${pending.code}`;
|
|
90
95
|
if (options.openBrowser !== false) {
|
|
91
96
|
const command =
|
|
92
97
|
process.platform === "darwin"
|
|
@@ -168,6 +173,7 @@ async function resumeLoginLocked(runtime, env, options) {
|
|
|
168
173
|
throw new Error("Totem returned an invalid connection.");
|
|
169
174
|
staged = {
|
|
170
175
|
agentId: result.agentId,
|
|
176
|
+
...(result.connectionId ? { connectionId: result.connectionId } : {}),
|
|
171
177
|
key: result.key,
|
|
172
178
|
baseUrl: pending.baseUrl,
|
|
173
179
|
requestId: pending.id,
|
|
@@ -231,6 +237,7 @@ async function resumeLoginLocked(runtime, env, options) {
|
|
|
231
237
|
origin(staged.baseUrl),
|
|
232
238
|
...(await providerArguments(provider, env)),
|
|
233
239
|
"--no-start",
|
|
240
|
+
...(staged.connectionId ? ["--no-mcp"] : []),
|
|
234
241
|
...(options.replace || staged.replace ? ["--replace"] : []),
|
|
235
242
|
"--json",
|
|
236
243
|
],
|
package/bin/installation.mjs
CHANGED
|
@@ -59,6 +59,7 @@ export async function installationMain(args = [], options = {}) {
|
|
|
59
59
|
options: {
|
|
60
60
|
provider: { type: "string" },
|
|
61
61
|
"agent-id": { type: "string" },
|
|
62
|
+
"connection-id": { type: "string" },
|
|
62
63
|
"base-url": { type: "string" },
|
|
63
64
|
"no-browser": { type: "boolean" },
|
|
64
65
|
"device-login": { type: "boolean" },
|
|
@@ -70,7 +71,7 @@ export async function installationMain(args = [], options = {}) {
|
|
|
70
71
|
}).values;
|
|
71
72
|
if (flags.help) {
|
|
72
73
|
stdout.write(
|
|
73
|
-
"totem
|
|
74
|
+
"totem install [--agent-id UUID] [--connection-id UUID] [--host-kind local|hosted] [--provider codex|claude|qwen-local|ollama-local|nim-local|command|http] [--no-browser] [--device-login]\nWith --connection-id, prepare the host and use one informed Accept action for peer approval and listener startup. The browser must reach this host's loopback callback (remote hosts require a tunnel). Standalone login uses explicit owner authorization. Headless --device-login uses code comparison and cannot replace the connection-bound Accept flow. Hosted labels an already provisioned, authorized host; it does not provision hosting.\n",
|
|
74
75
|
);
|
|
75
76
|
return 0;
|
|
76
77
|
}
|
|
@@ -78,6 +79,7 @@ export async function installationMain(args = [], options = {}) {
|
|
|
78
79
|
...env,
|
|
79
80
|
...(flags.provider ? { TOTEM_PROVIDER: flags.provider } : {}),
|
|
80
81
|
...(flags["agent-id"] ? { TOTEM_AGENT_ID: flags["agent-id"] } : {}),
|
|
82
|
+
...(flags["connection-id"] ? { TOTEM_CONNECTION_ID: flags["connection-id"] } : {}),
|
|
81
83
|
...(flags["base-url"] ? { TOTEM_BASE_URL: flags["base-url"] } : {}),
|
|
82
84
|
...(flags["host-kind"] ? { TOTEM_RUNTIME_HOST_KIND: flags["host-kind"] } : {}),
|
|
83
85
|
};
|
|
@@ -88,6 +90,21 @@ export async function installationMain(args = [], options = {}) {
|
|
|
88
90
|
throw new Error(
|
|
89
91
|
"Choose --host-kind local or hosted. Hosted mode labels an already provisioned, explicitly authorized host; it does not provision one.",
|
|
90
92
|
);
|
|
93
|
+
if (
|
|
94
|
+
environment.TOTEM_CONNECTION_ID &&
|
|
95
|
+
!/^[a-f0-9]{8}(?:-[a-f0-9]{4}){3}-[a-f0-9]{12}$/i.test(environment.TOTEM_CONNECTION_ID)
|
|
96
|
+
)
|
|
97
|
+
throw new Error("Choose a valid connection request ID.");
|
|
98
|
+
if (
|
|
99
|
+
environment.TOTEM_CONNECTION_ID &&
|
|
100
|
+
(flags["device-login"] ||
|
|
101
|
+
environment.TOTEM_NETWORK_API_KEY ||
|
|
102
|
+
environment.TOTEM_NETWORK_API_KEY_FILE ||
|
|
103
|
+
flags.reauthorize)
|
|
104
|
+
)
|
|
105
|
+
throw new Error(
|
|
106
|
+
"Prepare connection acceptance with the bound browser installer on this host. Headless and credential replacement use their explicit repair flows.",
|
|
107
|
+
);
|
|
91
108
|
const runtime = await (options.ensureRuntime ?? ensureInstalledRuntime)(environment);
|
|
92
109
|
const service = await (options.installService ?? installRuntimeService)(runtime, environment);
|
|
93
110
|
if (environment.TOTEM_NETWORK_API_KEY || environment.TOTEM_NETWORK_API_KEY_FILE) {
|
|
@@ -147,6 +164,21 @@ export async function installationMain(args = [], options = {}) {
|
|
|
147
164
|
);
|
|
148
165
|
return status.ready ? 0 : 1;
|
|
149
166
|
};
|
|
167
|
+
if (environment.TOTEM_CONNECTION_ID) {
|
|
168
|
+
for (const file of ["connection-ready.json", "connection-request.json"]) {
|
|
169
|
+
const pending = await privateRead(join(runtime.directory, file)).catch((error) => {
|
|
170
|
+
if (error.code !== "ENOENT") throw error;
|
|
171
|
+
});
|
|
172
|
+
if (
|
|
173
|
+
pending &&
|
|
174
|
+
pending.connectionId !== environment.TOTEM_CONNECTION_ID &&
|
|
175
|
+
(file === "connection-ready.json" || Date.parse(pending.expiresAt) > Date.now())
|
|
176
|
+
)
|
|
177
|
+
throw new Error(
|
|
178
|
+
"Another host authorization is pending. Finish or explicitly cancel that setup before preparing this connection; no saved identity was replaced.",
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
150
182
|
const resume = options.resumeLogin ?? resumeDeviceLogin;
|
|
151
183
|
const loginOptions = { ...options, replace: Boolean(flags.reauthorize) };
|
|
152
184
|
if (flags.reauthorize) {
|
|
@@ -185,7 +217,12 @@ export async function installationMain(args = [], options = {}) {
|
|
|
185
217
|
} catch (error) {
|
|
186
218
|
if (error.code !== "ENOENT") throw error;
|
|
187
219
|
}
|
|
188
|
-
if (
|
|
220
|
+
if (
|
|
221
|
+
saved.length &&
|
|
222
|
+
options.forceLogin !== true &&
|
|
223
|
+
!flags.reauthorize &&
|
|
224
|
+
!environment.TOTEM_CONNECTION_ID
|
|
225
|
+
) {
|
|
189
226
|
const results = [];
|
|
190
227
|
for (const name of saved) results.push({ profile: name, ...(await activate(name)) });
|
|
191
228
|
return reportActivation({
|
|
@@ -215,7 +252,7 @@ export async function installationMain(args = [], options = {}) {
|
|
|
215
252
|
stdout.write(
|
|
216
253
|
flags.json
|
|
217
254
|
? JSON.stringify(report) + "\n"
|
|
218
|
-
:
|
|
255
|
+
: `${environment.TOTEM_CONNECTION_ID ? "Accept the peer connection and connect this host in one action:" : "Authorize your agent in the installer browser:"}\n${connection.url}\n${flags["device-login"] ? `Compare this code before approving: ${connection.code.match(/.{1,4}/g).join(" ")}\n` : ""}Waiting for authorization and the controlled listener check.\n`,
|
|
219
256
|
);
|
|
220
257
|
try {
|
|
221
258
|
const deadline = Math.min(
|
|
@@ -245,7 +282,7 @@ export async function installationMain(args = [], options = {}) {
|
|
|
245
282
|
},
|
|
246
283
|
);
|
|
247
284
|
const name = environment.TOTEM_AGENT_ID ?? pending?.requestedAgentId;
|
|
248
|
-
if (name) {
|
|
285
|
+
if (name && !environment.TOTEM_CONNECTION_ID) {
|
|
249
286
|
const profile = await (options.readProfile ?? readAgentProfile)(name, {
|
|
250
287
|
environment,
|
|
251
288
|
}).catch((error) => {
|
|
@@ -266,10 +303,17 @@ export async function installationMain(args = [], options = {}) {
|
|
|
266
303
|
connection.close?.();
|
|
267
304
|
}
|
|
268
305
|
}
|
|
269
|
-
export async function postinstallMain(env = process.env) {
|
|
306
|
+
export async function postinstallMain(env = process.env, options = {}) {
|
|
270
307
|
if (env.TOTEM_AUTO_START === "0" || env.CI) return 0;
|
|
271
308
|
try {
|
|
272
|
-
|
|
309
|
+
// npm may hide lifecycle output. Prepare reception without opening a separate
|
|
310
|
+
// credential-consent page before the user reaches informed peer acceptance.
|
|
311
|
+
const runtime = await (options.ensureRuntime ?? ensureInstalledRuntime)(env);
|
|
312
|
+
await (options.installService ?? installRuntimeService)(runtime, env);
|
|
313
|
+
(options.stdout ?? process.stdout).write(
|
|
314
|
+
"Totem runtime prepared. Run totem install, or prepare the host from a peer request and use its single Accept action. No new runtime credential has been granted.\n",
|
|
315
|
+
);
|
|
316
|
+
return 0;
|
|
273
317
|
} catch {
|
|
274
318
|
process.stdout.write(
|
|
275
319
|
"Totem is installed. Automatic connection setup needs attention; run totem login to finish it.\n",
|
package/bin/network-runtime.mjs
CHANGED
|
@@ -594,6 +594,8 @@ export async function runRuntime(settings) {
|
|
|
594
594
|
const shutdown = new AbortController();
|
|
595
595
|
const signal = AbortSignal.any([settings.signal, shutdown.signal]);
|
|
596
596
|
const identity = await client.agent({ signal });
|
|
597
|
+
settings.connectionOnly ||= Boolean(identity.listenerConnectionId);
|
|
598
|
+
if (settings.connectionOnly) settings.allowAgents = [];
|
|
597
599
|
if (settings.expectedAgentId && identity.id !== settings.expectedAgentId)
|
|
598
600
|
throw Object.assign(new Error("The runtime credential identifies another agent."), {
|
|
599
601
|
code: "AGENT_IDENTITY_MISMATCH",
|
|
@@ -818,11 +820,20 @@ export async function runRuntime(settings) {
|
|
|
818
820
|
const thread = [];
|
|
819
821
|
let cursor;
|
|
820
822
|
for (let page = 0; page < 5; page++) {
|
|
821
|
-
const result =
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
823
|
+
const result = settings.connectionOnly
|
|
824
|
+
? await client.messages.list(
|
|
825
|
+
{
|
|
826
|
+
conversationId: message.conversationId,
|
|
827
|
+
limit: 100,
|
|
828
|
+
...(cursor ? { cursor } : {}),
|
|
829
|
+
},
|
|
830
|
+
{ signal },
|
|
831
|
+
)
|
|
832
|
+
: await client.conversations.messages(
|
|
833
|
+
message.conversationId,
|
|
834
|
+
{ limit: 100, ...(cursor ? { cursor } : {}) },
|
|
835
|
+
{ signal },
|
|
836
|
+
);
|
|
826
837
|
thread.push(...result.data);
|
|
827
838
|
if (thread.some((item) => item.id === message.id) || !result.nextCursor) break;
|
|
828
839
|
cursor = result.nextCursor;
|
|
@@ -916,7 +927,7 @@ export async function runRuntime(settings) {
|
|
|
916
927
|
});
|
|
917
928
|
// An owner-bound desktop chat consumes its own prompts. It does not need
|
|
918
929
|
// the independent Totem website-chat command queue or a fresh model turn.
|
|
919
|
-
if (settings.codexThreadId) {
|
|
930
|
+
if (settings.codexThreadId || settings.connectionOnly) {
|
|
920
931
|
humanDone = true;
|
|
921
932
|
await inbox;
|
|
922
933
|
if (fatalError) throw fatalError;
|
package/bin/sdk/index.d.ts
CHANGED
|
@@ -195,6 +195,8 @@ export declare class Totem {
|
|
|
195
195
|
}>;
|
|
196
196
|
};
|
|
197
197
|
runtime: {
|
|
198
|
+
activations: (options?: RequestOptions) => Promise<{ data: Array<{ id: string; state: string; ready: boolean }> }>;
|
|
199
|
+
reportActivation: (input: { id: string; state: "starting" | "ready" | "blocked" | "paused"; errorCode?: "READINESS_UNSUPPORTED" | "LISTENER_START_FAILED" | "AUTHORIZATION_REJECTED" }, options?: RequestOptions) => Promise<{ recorded: boolean }>;
|
|
198
200
|
start: (input: {
|
|
199
201
|
runId: string;
|
|
200
202
|
provider: string;
|
package/bin/sdk/index.js
CHANGED
|
@@ -241,6 +241,8 @@ export class Totem {
|
|
|
241
241
|
fail: async (id, input, options) => (await this.#transport.request("POST", `/chat-commands/${encodeURIComponent(id)}/fail`, { ...options, body: input, retrySafe: true })).data,
|
|
242
242
|
};
|
|
243
243
|
runtime = {
|
|
244
|
+
activations: async (options) => (await this.#transport.request("GET", "/runtime/activations", options)).data,
|
|
245
|
+
reportActivation: async (input, options) => (await this.#transport.request("POST", "/runtime/activations", { ...options, body: input, retrySafe: true })).data,
|
|
244
246
|
start: async (input, options) => (await this.#transport.request("POST", "/runtime/start", { ...options, body: input, retrySafe: true })).data,
|
|
245
247
|
probe: async (runId, options) => (await this.#transport.request("GET", "/runtime/probe", { ...options, query: { runId } })).data,
|
|
246
248
|
replyProbe: async (input, options) => (await this.#transport.request("POST", "/runtime/probe", { ...options, body: input, retrySafe: true })).data,
|
package/bin/sdk/types.d.ts
CHANGED
|
@@ -9,6 +9,8 @@ export interface Erc8004DiscoveryLinks {
|
|
|
9
9
|
grantsActionAuthority: false;
|
|
10
10
|
}
|
|
11
11
|
export interface Agent {
|
|
12
|
+
/** Authenticated connection-only listener marker; not an authority claim from a peer. */
|
|
13
|
+
listenerConnectionId?: string;
|
|
12
14
|
id: string;
|
|
13
15
|
address: string;
|
|
14
16
|
name: string;
|
package/bin/totem.mjs
CHANGED
|
@@ -237,9 +237,9 @@ function compactDigest(value) {
|
|
|
237
237
|
function help() {
|
|
238
238
|
writeBrand();
|
|
239
239
|
writeHelpBody(`Start here
|
|
240
|
-
Install once npm install --global
|
|
240
|
+
Install once npm install --global totemlayer@0.1.25
|
|
241
241
|
Confirm installation totem --version
|
|
242
|
-
Run one-off npx --yes --package
|
|
242
|
+
Run one-off npx --yes --package=totemlayer@0.1.25 totem --help
|
|
243
243
|
|
|
244
244
|
Connect once and receive messages and approved Mission work
|
|
245
245
|
Select Codex, Claude, a local model, or an owner-configured CLI/HTTP agent.
|