@intentic/sandbox-contract 1.158.0 → 1.160.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent-catalog.d.ts +10 -0
- package/dist/agent-catalog.d.ts.map +1 -1
- package/dist/agent-catalog.js +14 -1
- package/dist/agent-catalog.js.map +1 -1
- package/dist/contracts/agent.contract.d.ts +74 -4
- package/dist/contracts/agent.contract.d.ts.map +1 -1
- package/dist/contracts/agent.contract.js +2 -1
- package/dist/contracts/agent.contract.js.map +1 -1
- package/dist/contracts/agents.contract.d.ts +248 -0
- package/dist/contracts/agents.contract.d.ts.map +1 -1
- package/dist/contracts/agents.contract.js +7 -2
- package/dist/contracts/agents.contract.js.map +1 -1
- package/dist/contracts/automations.contract.d.ts +7 -0
- package/dist/contracts/automations.contract.d.ts.map +1 -1
- package/dist/contracts/claude.contract.d.ts +26 -0
- package/dist/contracts/claude.contract.d.ts.map +1 -1
- package/dist/contracts/claude.contract.js +2 -1
- package/dist/contracts/claude.contract.js.map +1 -1
- package/dist/contracts/extensions.contract.d.ts +1 -0
- package/dist/contracts/extensions.contract.d.ts.map +1 -1
- package/dist/contracts/git.contract.d.ts +13 -0
- package/dist/contracts/git.contract.d.ts.map +1 -1
- package/dist/contracts/git.contract.js +2 -1
- package/dist/contracts/git.contract.js.map +1 -1
- package/dist/contracts/grok.contract.d.ts +2 -0
- package/dist/contracts/grok.contract.d.ts.map +1 -1
- package/dist/contracts/kimi.contract.d.ts +26 -0
- package/dist/contracts/kimi.contract.d.ts.map +1 -1
- package/dist/contracts/kimi.contract.js +2 -1
- package/dist/contracts/kimi.contract.js.map +1 -1
- package/dist/contracts/memory.contract.d.ts +34 -0
- package/dist/contracts/memory.contract.d.ts.map +1 -0
- package/dist/contracts/memory.contract.js +9 -0
- package/dist/contracts/memory.contract.js.map +1 -0
- package/dist/contracts/panels.contract.d.ts +1 -0
- package/dist/contracts/panels.contract.d.ts.map +1 -1
- package/dist/contracts/push.contract.d.ts +1 -1
- package/dist/contracts/push.contract.js +2 -2
- package/dist/contracts/push.contract.js.map +1 -1
- package/dist/contracts/sessions.contract.d.ts +2 -0
- package/dist/contracts/sessions.contract.d.ts.map +1 -1
- package/dist/contracts/settings.contract.d.ts +32 -0
- package/dist/contracts/settings.contract.d.ts.map +1 -1
- package/dist/contracts/settings.contract.js +2 -1
- package/dist/contracts/settings.contract.js.map +1 -1
- package/dist/contracts/system.contract.d.ts +35 -6
- package/dist/contracts/system.contract.d.ts.map +1 -1
- package/dist/contracts/system.contract.js +2 -1
- package/dist/contracts/system.contract.js.map +1 -1
- package/dist/contracts/translator.contract.d.ts +13 -3
- package/dist/contracts/translator.contract.d.ts.map +1 -1
- package/dist/contracts/translator.contract.js +1 -1
- package/dist/contracts/translator.contract.js.map +1 -1
- package/dist/contracts/workspace.contract.d.ts +43 -5
- package/dist/contracts/workspace.contract.d.ts.map +1 -1
- package/dist/contracts/workspace.contract.js +3 -1
- package/dist/contracts/workspace.contract.js.map +1 -1
- package/dist/events.d.ts +176 -2
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js +16 -5
- package/dist/events.js.map +1 -1
- package/dist/hostnames.d.ts +2 -0
- package/dist/hostnames.d.ts.map +1 -1
- package/dist/hostnames.js +27 -1
- package/dist/hostnames.js.map +1 -1
- package/dist/index.d.ts +566 -19
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -1
- package/dist/model-order.d.ts +2 -0
- package/dist/model-order.d.ts.map +1 -1
- package/dist/model-order.js +3 -0
- package/dist/model-order.js.map +1 -1
- package/dist/path-refs.d.ts +5 -0
- package/dist/path-refs.d.ts.map +1 -0
- package/dist/path-refs.js +21 -0
- package/dist/path-refs.js.map +1 -0
- package/dist/quick-model.d.ts +13 -0
- package/dist/quick-model.d.ts.map +1 -0
- package/dist/quick-model.js +33 -0
- package/dist/quick-model.js.map +1 -0
- package/dist/routes.d.ts +8 -0
- package/dist/routes.d.ts.map +1 -0
- package/dist/routes.js +39 -0
- package/dist/routes.js.map +1 -0
- package/dist/schemas.d.ts +351 -10
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +143 -13
- package/dist/schemas.js.map +1 -1
- package/dist/title.d.ts +6 -0
- package/dist/title.d.ts.map +1 -0
- package/dist/title.js +149 -0
- package/dist/title.js.map +1 -0
- package/package.json +2 -2
- package/src/agent-catalog.ts +51 -1
- package/src/contracts/agent.contract.ts +14 -1
- package/src/contracts/agents.contract.ts +25 -1
- package/src/contracts/claude.contract.ts +4 -1
- package/src/contracts/git.contract.ts +6 -0
- package/src/contracts/kimi.contract.ts +13 -2
- package/src/contracts/memory.contract.ts +13 -0
- package/src/contracts/push.contract.ts +2 -2
- package/src/contracts/settings.contract.ts +5 -1
- package/src/contracts/system.contract.ts +4 -0
- package/src/contracts/translator.contract.ts +3 -2
- package/src/contracts/workspace.contract.ts +12 -0
- package/src/events.ts +89 -11
- package/src/hostnames.test.ts +22 -0
- package/src/hostnames.ts +40 -1
- package/src/index.ts +20 -0
- package/src/model-order.test.ts +73 -1
- package/src/model-order.ts +31 -0
- package/src/path-refs.test.ts +68 -0
- package/src/path-refs.ts +59 -0
- package/src/quick-model.test.ts +88 -0
- package/src/quick-model.ts +103 -0
- package/src/routes.test.ts +83 -0
- package/src/routes.ts +84 -0
- package/src/schemas.test.ts +23 -1
- package/src/schemas.ts +461 -38
- package/src/title.test.ts +130 -0
- package/src/title.ts +254 -0
|
@@ -1,13 +1,19 @@
|
|
|
1
1
|
import { oc } from "@orpc/contract";
|
|
2
|
+
import { AgentTranscriptSchema } from "../events.js";
|
|
2
3
|
import {
|
|
3
4
|
AgentArchiveSchema,
|
|
5
|
+
AgentAutoLandSchema,
|
|
4
6
|
AgentChangesSchema,
|
|
5
7
|
AgentFileDiffQuerySchema,
|
|
6
8
|
AgentIdSchema,
|
|
7
9
|
AgentIdsSchema,
|
|
10
|
+
AgentLandSchema,
|
|
8
11
|
AgentRenameSchema,
|
|
12
|
+
AgentSearchQuerySchema,
|
|
13
|
+
AgentSearchResultSchema,
|
|
9
14
|
AgentsListSchema,
|
|
10
15
|
AgentsMovedSchema,
|
|
16
|
+
AgentsRemovedSchema,
|
|
11
17
|
AgentSummarySchema,
|
|
12
18
|
FileDiffSchema,
|
|
13
19
|
LandResultSchema,
|
|
@@ -29,17 +35,35 @@ import {
|
|
|
29
35
|
// per repo per agent) and keeps the branch, the entry, and the transcript. `list` then stops carrying it and
|
|
30
36
|
// `archived` does; `unarchive` puts it back, and either way the next turn re-attaches a worktree from the
|
|
31
37
|
// surviving branch. Archiving a running agent is CONFLICT, same as land/discard.
|
|
38
|
+
//
|
|
39
|
+
// PURGE empties the archive, and it is `discard` applied to every agent already in there: worktree remnants,
|
|
40
|
+
// branches and entries all go. Deliberately the whole archive and not a list of ids — the archive is the pile
|
|
41
|
+
// of agents the user has already decided are over, so "clean it up" is one act with one confirmation, and a
|
|
42
|
+
// per-id purge would be `discard`, which already exists. Never touches a running agent (a turn un-archives its
|
|
43
|
+
// own agent, so there should be none) and answers with what it actually deleted: a teardown that fails on one
|
|
44
|
+
// agent's repo leaves that one behind rather than taking the batch down with it.
|
|
32
45
|
export const agentsContract = {
|
|
33
46
|
list: oc.route({ method: "GET", path: "/agents" }).output(AgentsListSchema),
|
|
34
47
|
archived: oc.route({ method: "GET", path: "/agents/archived" }).output(AgentsListSchema),
|
|
48
|
+
// The board's filter. Answers over BOTH halves of the fleet — the live roster and the archive — because
|
|
49
|
+
// the board hides by design (the Finished lane windows to a handful, archived agents are off the roster
|
|
50
|
+
// entirely), and a filter that reports "no matches" while the agent sits one click away is a lie. The
|
|
51
|
+
// never-carded conversations that are neither are `sessions.list`'s query, which matches by the same rule.
|
|
52
|
+
search: oc.route({ method: "GET", path: "/agents/search" }).input(AgentSearchQuerySchema).output(AgentSearchResultSchema),
|
|
35
53
|
get: oc.route({ method: "GET", path: "/agents/{id}" }).input(AgentIdSchema).output(AgentSummarySchema),
|
|
54
|
+
transcript: oc.route({ method: "GET", path: "/agents/{id}/transcript" }).input(AgentIdSchema).output(AgentTranscriptSchema),
|
|
36
55
|
rename: oc.route({ method: "POST", path: "/agents/{id}/rename" }).input(AgentRenameSchema).output(AgentSummarySchema),
|
|
56
|
+
// This agent's own land-at-completion posture — an override of the sandbox-wide `autoLand` setting; null
|
|
57
|
+
// clears it back to "inherit". Legal mid-turn on purpose: the setting is read at turn COMPLETION, so
|
|
58
|
+
// flipping it while the agent works is exactly "hold THIS turn's work for review", the press that matters.
|
|
59
|
+
autoLand: oc.route({ method: "POST", path: "/agents/{id}/auto-land" }).input(AgentAutoLandSchema).output(AgentSummarySchema),
|
|
37
60
|
seen: oc.route({ method: "POST", path: "/agents/{id}/seen" }).input(AgentIdSchema).output(AgentSummarySchema),
|
|
38
61
|
seenAll: oc.route({ method: "POST", path: "/agents/seen" }).output(AgentsListSchema),
|
|
39
62
|
diff: oc.route({ method: "GET", path: "/agents/{id}/diff" }).input(AgentIdSchema).output(AgentChangesSchema),
|
|
40
63
|
fileDiff: oc.route({ method: "GET", path: "/agents/{id}/{repo}/file-diff" }).input(AgentFileDiffQuerySchema).output(FileDiffSchema),
|
|
41
|
-
land: oc.route({ method: "POST", path: "/agents/{id}/land" }).input(
|
|
64
|
+
land: oc.route({ method: "POST", path: "/agents/{id}/land" }).input(AgentLandSchema).output(LandResultSchema),
|
|
42
65
|
discard: oc.route({ method: "POST", path: "/agents/{id}/discard" }).input(AgentIdSchema).output(OkSchema),
|
|
43
66
|
archive: oc.route({ method: "POST", path: "/agents/archive" }).input(AgentArchiveSchema).output(AgentsMovedSchema),
|
|
44
67
|
unarchive: oc.route({ method: "POST", path: "/agents/unarchive" }).input(AgentIdsSchema).output(AgentsMovedSchema),
|
|
68
|
+
purge: oc.route({ method: "POST", path: "/agents/purge" }).output(AgentsRemovedSchema),
|
|
45
69
|
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { oc } from "@orpc/contract";
|
|
2
2
|
import {
|
|
3
3
|
AccountIdSchema,
|
|
4
|
+
AccountRenameSchema,
|
|
4
5
|
AuthorizeChallengeSchema,
|
|
5
6
|
ModelsSchema,
|
|
6
7
|
OauthAccountListSchema,
|
|
@@ -11,12 +12,14 @@ import {
|
|
|
11
12
|
|
|
12
13
|
// Claude subscription OAuth — the sandbox owns the credential. `start` hands the browser the authorize URL +
|
|
13
14
|
// PKCE material; `exchange` stores the resulting tokens as a new account and returns it; `accounts` lists the
|
|
14
|
-
// connected accounts; `disconnect` clears the one named by id. A sandbox can hold several
|
|
15
|
+
// connected accounts; `rename` renames one; `disconnect` clears the one named by id. A sandbox can hold several
|
|
16
|
+
// Claude accounts.
|
|
15
17
|
export const claudeContract = {
|
|
16
18
|
start: oc.route({ method: "POST", path: "/claude/oauth/start" }).output(AuthorizeChallengeSchema),
|
|
17
19
|
exchange: oc.route({ method: "POST", path: "/claude/oauth/exchange" }).input(OauthExchangeSchema).output(OauthAccountSchema),
|
|
18
20
|
// Claude's available models for the picker, from the Agent SDK's supportedModels() (see claude-models.ts).
|
|
19
21
|
models: oc.route({ method: "GET", path: "/claude/models" }).output(ModelsSchema),
|
|
20
22
|
accounts: oc.route({ method: "GET", path: "/claude/accounts" }).output(OauthAccountListSchema),
|
|
23
|
+
rename: oc.route({ method: "POST", path: "/claude/account/rename" }).input(AccountRenameSchema).output(OauthAccountSchema),
|
|
21
24
|
disconnect: oc.route({ method: "POST", path: "/claude/account/disconnect" }).input(AccountIdSchema).output(OkSchema),
|
|
22
25
|
};
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { oc } from "@orpc/contract";
|
|
2
2
|
import {
|
|
3
|
+
CommitMessageDraftSchema,
|
|
4
|
+
CommitMessageSchema,
|
|
3
5
|
CommitResultSchema,
|
|
4
6
|
CommitSchema,
|
|
5
7
|
DiscardSchema,
|
|
@@ -39,6 +41,10 @@ import {
|
|
|
39
41
|
// Changes panel renders; commit/discard take optional `paths` for per-file actions.
|
|
40
42
|
export const gitContract = {
|
|
41
43
|
changes: oc.route({ method: "GET", path: "/git/changes" }).output(GitChangesSchema),
|
|
44
|
+
// Drafts a commit message for what the commit box is about to record, on the sandbox's quick model (the
|
|
45
|
+
// cheap rung — see quick-model.ts). Workspace-wide like `changes` and for the same reason: one commit box,
|
|
46
|
+
// one message, every staged repo. POST because it spends a model call, not because it writes anything.
|
|
47
|
+
commitMessage: oc.route({ method: "POST", path: "/git/commit-message" }).input(CommitMessageDraftSchema).output(CommitMessageSchema),
|
|
42
48
|
// The git-history graph over one repo's real commits: the repo list (for the tree affordance + switcher),
|
|
43
49
|
// one repo's commit log, and lazy per-commit detail (changed files, then a file's before/after AT the
|
|
44
50
|
// commit). Read-only — commit/discard on the working tree stay the write path (above).
|
|
@@ -1,15 +1,26 @@
|
|
|
1
1
|
import { oc } from "@orpc/contract";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
AccountIdSchema,
|
|
4
|
+
AccountRenameSchema,
|
|
5
|
+
KimiConnectSchema,
|
|
6
|
+
ModelsSchema,
|
|
7
|
+
OauthAccountListSchema,
|
|
8
|
+
OauthAccountSchema,
|
|
9
|
+
OkSchema,
|
|
10
|
+
} from "../schemas.js";
|
|
3
11
|
|
|
4
12
|
// Kimi Code (Moonshot) — the sandbox owns the credential, an API key rather than an OAuth grant. Kimi speaks the
|
|
5
13
|
// Anthropic Messages protocol, so a Kimi turn runs on the SAME Claude Code harness as Claude, with the harness
|
|
6
14
|
// pointed at Moonshot's Anthropic-compatible endpoint and authenticated with the stored key (see agent.routes).
|
|
7
15
|
// `connect` stores a pasted key as a new account and returns it; `models` is the live catalog for the picker;
|
|
8
|
-
// `accounts` lists connected keys (tokens never ride back); `
|
|
16
|
+
// `accounts` lists connected keys (tokens never ride back); `rename` renames one (a pasted key carries no
|
|
17
|
+
// identity to derive a name from, so this is the ONLY way a Kimi row gets a distinguishing name);
|
|
18
|
+
// `disconnect` clears the one named by id.
|
|
9
19
|
export const kimiContract = {
|
|
10
20
|
connect: oc.route({ method: "POST", path: "/kimi/account/connect" }).input(KimiConnectSchema).output(OauthAccountSchema),
|
|
11
21
|
// Kimi/Moonshot's live models for the picker — the source of valid ids (see kimi-models.ts).
|
|
12
22
|
models: oc.route({ method: "GET", path: "/kimi/models" }).output(ModelsSchema),
|
|
13
23
|
accounts: oc.route({ method: "GET", path: "/kimi/accounts" }).output(OauthAccountListSchema),
|
|
24
|
+
rename: oc.route({ method: "POST", path: "/kimi/account/rename" }).input(AccountRenameSchema).output(OauthAccountSchema),
|
|
14
25
|
disconnect: oc.route({ method: "POST", path: "/kimi/account/disconnect" }).input(AccountIdSchema).output(OkSchema),
|
|
15
26
|
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { oc } from "@orpc/contract";
|
|
2
|
+
import { MemoryFileQuerySchema, MemoryFileSchema, MemoryListSchema, MemoryWriteSchema, OkSchema } from "../schemas.js";
|
|
3
|
+
|
|
4
|
+
// The agent's persistent memory notes (.intentic/claude/projects/<project>/memory) — the one browser surface
|
|
5
|
+
// for that control-plane subtree (see schemas.ts). Read for the memory panel; write/delete so the owner can
|
|
6
|
+
// curate what the agent remembers (fix a stale fact, drop a wrong one). oRPC's OpenAPI codec reads non-GET
|
|
7
|
+
// input from the JSON body, so write and delete send {project, name} in the body (same as /workspace/entry).
|
|
8
|
+
export const memoryContract = {
|
|
9
|
+
list: oc.route({ method: "GET", path: "/memory" }).output(MemoryListSchema),
|
|
10
|
+
read: oc.route({ method: "GET", path: "/memory/file" }).input(MemoryFileQuerySchema).output(MemoryFileSchema),
|
|
11
|
+
write: oc.route({ method: "PUT", path: "/memory/file" }).input(MemoryWriteSchema).output(OkSchema),
|
|
12
|
+
delete: oc.route({ method: "DELETE", path: "/memory/file" }).input(MemoryFileQuerySchema).output(OkSchema),
|
|
13
|
+
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { oc } from "@orpc/contract";
|
|
2
|
-
import { OkSchema, PushConfigQuerySchema, PushConfigSchema, PushEndpointSchema, PushSubscriptionSchema } from "../schemas.js";
|
|
2
|
+
import { OkSchema, PushConfigQuerySchema, PushConfigSchema, PushEndpointSchema, PushSubscriptionSchema, PushTestSchema } from "../schemas.js";
|
|
3
3
|
|
|
4
4
|
// Web-push notifications for this sandbox. The daemon owns the VAPID keypair and the subscription list (see
|
|
5
5
|
// push/push-store.ts for why the key lives on the history volume), and sends on the three moments where the
|
|
@@ -13,5 +13,5 @@ export const pushContract = {
|
|
|
13
13
|
config: oc.route({ method: "GET", path: "/push/config" }).input(PushConfigQuerySchema).output(PushConfigSchema),
|
|
14
14
|
subscribe: oc.route({ method: "POST", path: "/push/subscribe" }).input(PushSubscriptionSchema).output(OkSchema),
|
|
15
15
|
unsubscribe: oc.route({ method: "POST", path: "/push/unsubscribe" }).input(PushEndpointSchema).output(OkSchema),
|
|
16
|
-
test: oc.route({ method: "POST", path: "/push/test" }).output(
|
|
16
|
+
test: oc.route({ method: "POST", path: "/push/test" }).output(PushTestSchema),
|
|
17
17
|
};
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import { oc } from "@orpc/contract";
|
|
2
|
-
import { CleanerSavingsSchema, OkSchema, SandboxSettingsSchema } from "../schemas.js";
|
|
2
|
+
import { BuiltinPromptSchema, BuiltinPromptTextSchema, CleanerSavingsSchema, OkSchema, SandboxSettingsSchema } from "../schemas.js";
|
|
3
3
|
|
|
4
4
|
// Per-sandbox agent settings (.intentic/settings.json). `get` returns the current flags with defaults applied
|
|
5
5
|
// when the file is absent; `set` overwrites them. `savings` reports the output-cleaner token savings aggregated
|
|
6
6
|
// from the live filter-stats ledger (the rtk-`gain` surface — read-only, so a UI card can show what's working).
|
|
7
|
+
// `builtinPrompt` returns one of the two built-in system prompts as text — Intentic's own, or Claude Code's
|
|
8
|
+
// read out of the installed CLI — so the settings page can SHOW the prompt behind a mode instead of asking the
|
|
9
|
+
// user to trust a description of it, and can fork either into a custom one.
|
|
7
10
|
export const settingsContract = {
|
|
8
11
|
get: oc.route({ method: "GET", path: "/settings" }).output(SandboxSettingsSchema),
|
|
9
12
|
set: oc.route({ method: "POST", path: "/settings" }).input(SandboxSettingsSchema).output(OkSchema),
|
|
10
13
|
savings: oc.route({ method: "GET", path: "/settings/savings" }).output(CleanerSavingsSchema),
|
|
14
|
+
builtinPrompt: oc.route({ method: "GET", path: "/settings/system-prompt/{base}" }).input(BuiltinPromptSchema).output(BuiltinPromptTextSchema),
|
|
11
15
|
};
|
|
@@ -2,6 +2,7 @@ import { eventIterator, oc } from "@orpc/contract";
|
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { SystemEventSchema } from "../events.js";
|
|
4
4
|
import {
|
|
5
|
+
DaemonSessionSchema,
|
|
5
6
|
HostTunnelInputSchema,
|
|
6
7
|
HostTunnelSchema,
|
|
7
8
|
InfoSchema,
|
|
@@ -19,6 +20,9 @@ import {
|
|
|
19
20
|
// connection simply never joins the roster.
|
|
20
21
|
export const systemContract = {
|
|
21
22
|
info: oc.route({ method: "GET", path: "/info" }).output(InfoSchema),
|
|
23
|
+
// Exchange the request's verified bearer (a Google ID token — or a still-valid session, which makes this
|
|
24
|
+
// route sliding renewal) for a daemon-minted session, the credential every steady-state call presents.
|
|
25
|
+
session: oc.route({ method: "POST", path: "/system/session" }).output(DaemonSessionSchema),
|
|
22
26
|
events: oc
|
|
23
27
|
.route({ method: "GET", path: "/events" })
|
|
24
28
|
.input(z.object({ clientId: z.string().optional() }))
|
|
@@ -4,7 +4,8 @@ import { KeyedProviderSchema, OkSchema, TranslatorAccountsSchema, TranslatorComp
|
|
|
4
4
|
|
|
5
5
|
// Routed-provider subscriptions (Sandbox ▸ Agent). The bundled translator (CLIProxyAPI) runs a non-Claude model
|
|
6
6
|
// UNDER the Claude Code harness on the user's SUBSCRIPTION, so each provider connects via an OAuth login rather
|
|
7
|
-
// than an API key.
|
|
7
|
+
// than an API key. A provider can hold several accounts side by side (the translator balances across them);
|
|
8
|
+
// `accounts` lists what's connected per provider and `disconnect` clears ONE account by its auth-file `name`.
|
|
8
9
|
//
|
|
9
10
|
// Two login shapes ride one pair of routes. Codex and Grok mint a one-time device `code`: the user enters it at
|
|
10
11
|
// the provider's site and the translator polls to completion in the background, so the UI just polls `accounts`
|
|
@@ -21,6 +22,6 @@ export const translatorContract = {
|
|
|
21
22
|
complete: oc.route({ method: "POST", path: "/translator/{provider}/complete" }).input(TranslatorCompleteSchema).output(OkSchema),
|
|
22
23
|
disconnect: oc
|
|
23
24
|
.route({ method: "POST", path: "/translator/{provider}/disconnect" })
|
|
24
|
-
.input(z.object({ provider: KeyedProviderSchema }))
|
|
25
|
+
.input(z.object({ provider: KeyedProviderSchema, name: z.string().min(1) }))
|
|
25
26
|
.output(OkSchema),
|
|
26
27
|
};
|
|
@@ -19,9 +19,13 @@ import {
|
|
|
19
19
|
WorkspaceFileQuerySchema,
|
|
20
20
|
WorkspaceFileSchema,
|
|
21
21
|
WorkspaceGraphSchema,
|
|
22
|
+
WorkspaceHealthQuerySchema,
|
|
23
|
+
WorkspaceHealthSchema,
|
|
22
24
|
WorkspaceInstallResultSchema,
|
|
23
25
|
WorkspaceInstallSchema,
|
|
24
26
|
WorkspaceMoveSchema,
|
|
27
|
+
WorkspaceResolveQuerySchema,
|
|
28
|
+
WorkspaceResolveSchema,
|
|
25
29
|
WorkspaceSetupSchema,
|
|
26
30
|
WorkspaceSyncSchema,
|
|
27
31
|
WorkspaceTreeSchema,
|
|
@@ -36,9 +40,17 @@ export const workspaceContract = {
|
|
|
36
40
|
// and the client fetches them here on expand so a giant node_modules can't blow the tree walk's entry budget.
|
|
37
41
|
children: oc.route({ method: "GET", path: "/workspace/children" }).input(WorkspaceChildrenQuerySchema).output(WorkspaceChildrenSchema),
|
|
38
42
|
file: oc.route({ method: "GET", path: "/workspace/file" }).input(WorkspaceFileQuerySchema).output(WorkspaceFileSchema),
|
|
43
|
+
// Which file a NAMED reference means — the lookup behind every clickable path in the UI. A path an agent
|
|
44
|
+
// wrote in prose is often only a suffix of the real one, so it is matched against the workspace tree rather
|
|
45
|
+
// than trusted as root-relative.
|
|
46
|
+
resolve: oc.route({ method: "GET", path: "/workspace/resolve" }).input(WorkspaceResolveQuerySchema).output(WorkspaceResolveSchema),
|
|
39
47
|
// Ranked groups, match-reason tags, freshness, resumable cursor. `mode` narrows to one verb; default is
|
|
40
48
|
// auto-mode fusion. (Implementation detail: the daemon backs this with a resident in-process iq engine.)
|
|
41
49
|
search: oc.route({ method: "GET", path: "/workspace/search" }).input(WorkspaceSearchQuerySchema).output(WorkspaceSearchResultSchema),
|
|
50
|
+
// One repository's health in numbers: churn × complexity per file, index totals, and the import graph's
|
|
51
|
+
// top modules — the `hotspots` and `map` rankings the CLI prints, shaped for a panel. Repo-scoped, because
|
|
52
|
+
// "the codebase" is a repo, not the whole /work drop.
|
|
53
|
+
health: oc.route({ method: "GET", path: "/workspace/health" }).input(WorkspaceHealthQuerySchema).output(WorkspaceHealthSchema),
|
|
42
54
|
// Deterministic, no-LLM classification of the dropped workspace into coarse buckets (repositories / documents
|
|
43
55
|
// / media / archives / other). Read-only proposal: the browser renders it and applies accepted moves via the
|
|
44
56
|
// existing /workspace/move route — this route never touches the tree.
|
package/src/events.ts
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
AgentProviderSchema,
|
|
4
|
+
AgentReplySchema,
|
|
5
|
+
AgentSummarySchema,
|
|
6
|
+
LandConflictSchema,
|
|
7
|
+
PermissionModeSchema,
|
|
8
|
+
RateLimitInfoSchema,
|
|
9
|
+
UsageWindowSchema,
|
|
10
|
+
} from "./schemas.js";
|
|
3
11
|
|
|
4
12
|
// The wire shapes streamed from the daemon's event-iterator procedures. This is their canonical home: the
|
|
5
13
|
// daemon yields them and the browser client consumes them from the same schema, so the two can't drift (they
|
|
@@ -72,7 +80,7 @@ export const ContextUsageSchema = z.object({
|
|
|
72
80
|
export type ContextUsage = z.infer<typeof ContextUsageSchema>;
|
|
73
81
|
|
|
74
82
|
// ACP-aligned tool taxonomy (Agent Client Protocol's ToolKind, verbatim): what a tool call *does*, driving
|
|
75
|
-
// the card icon and
|
|
83
|
+
// the card icon and the live-writes bookkeeping regardless of which backend named the tool.
|
|
76
84
|
export const ToolKindSchema = z.enum(["read", "edit", "delete", "move", "search", "execute", "think", "fetch", "other"]);
|
|
77
85
|
export type ToolKind = z.infer<typeof ToolKindSchema>;
|
|
78
86
|
|
|
@@ -129,31 +137,46 @@ export type RestoredToolCall = z.infer<typeof RestoredToolCallSchema>;
|
|
|
129
137
|
export const RestoredMessageSchema = z.object({
|
|
130
138
|
role: z.enum(["user", "assistant"]),
|
|
131
139
|
text: z.string(),
|
|
140
|
+
// Files the user attached to this turn (user bubbles only) as workspace-relative paths, recovered from
|
|
141
|
+
// the stored prompt's attachment note — so a reopened tab redraws chips, not the injected protocol text.
|
|
142
|
+
attachments: z.array(z.string()).optional(),
|
|
132
143
|
thinking: z.string().optional(),
|
|
133
144
|
tools: z.array(RestoredToolCallSchema).optional(),
|
|
134
145
|
});
|
|
135
146
|
export type RestoredMessage = z.infer<typeof RestoredMessageSchema>;
|
|
136
147
|
|
|
137
148
|
export const SessionTranscriptSchema = z.object({ messages: z.array(RestoredMessageSchema) });
|
|
149
|
+
export const AgentTranscriptSchema = SessionTranscriptSchema.extend({ sessionId: z.string().optional() });
|
|
138
150
|
|
|
139
151
|
// One frame from an agent turn, relayed to the UI. `kind`-discriminated. The daemon normalizes the SDK's
|
|
140
152
|
// ~40 SDKMessage types down to this union: high-value block types get a dedicated frame
|
|
141
153
|
// (delta/thinking/tool_call/tool_call_update/todos/usage/rate_limit_info/account_usage/context_usage/init/compact); any SDK message
|
|
142
154
|
// without a UI mapping is dropped. `plan`/`question`/`permission` pause the turn until the user answers on the
|
|
143
|
-
// `POST /agent/reply` side channel
|
|
155
|
+
// `POST /agent/reply` side channel, and `resolved` releases the one it names; `mode` reports the live
|
|
156
|
+
// permission posture as the agent changes it.
|
|
144
157
|
// `parentToolUseId` tags frames produced inside a subagent (Task tool).
|
|
145
158
|
export const AgentEventSchema = z.discriminatedUnion("kind", [
|
|
146
159
|
z.object({ kind: z.literal("session"), sessionId: z.string() }),
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
160
|
+
/* First frame of an isolated turn: the conversation's worktree identity — its branch (agent/<id>) and the
|
|
161
|
+
* ROOT repo's short base sha (the checkout moment). Emitted before any provider frames.
|
|
162
|
+
*
|
|
163
|
+
* `unenforced` marks the degraded container: no CAP_SYS_ADMIN, so the turn's worktree could not be
|
|
164
|
+
* bind-mounted over the workspace root and the harness is rewriting tool paths into it instead. That
|
|
165
|
+
* fallback covers what arrives as tool input and not what a subprocess computes for itself, so the
|
|
166
|
+
* operator needs to know — this state used to be one line in the daemon log at boot, and the way it got
|
|
167
|
+
* noticed was files appearing in the main tree from agents that were supposed to be on branches. */
|
|
168
|
+
z.object({ kind: z.literal("worktree"), branch: z.string(), base: z.string(), unenforced: z.boolean().optional() }),
|
|
150
169
|
// Emitted after a clean isolated turn whose delta auto-landed (or failed to): landed ⇒ the work is now
|
|
151
170
|
// UNCOMMITTED changes in the main tree (the Changes panel is the review); conflicts ⇒ it stayed safely in
|
|
152
|
-
// the worktree
|
|
171
|
+
// the worktree, and each named path carries WHY it would not apply (see LandConflictSchema) so the report
|
|
172
|
+
// can say whether the user's own copy is at risk or the main line simply moved on underneath the agent.
|
|
173
|
+
// held ⇒ auto-land is off for this agent: nothing was applied and nothing failed — the delta is waiting
|
|
174
|
+
// on the branch for a deliberate Land (landed is false, conflicts absent).
|
|
153
175
|
z.object({
|
|
154
176
|
kind: z.literal("landed"),
|
|
155
177
|
landed: z.boolean(),
|
|
156
|
-
conflicts: z.array(
|
|
178
|
+
conflicts: z.array(LandConflictSchema).optional(),
|
|
179
|
+
held: z.boolean().optional(),
|
|
157
180
|
}),
|
|
158
181
|
// The SDK's init handshake; carries the model it actually resolved for the turn.
|
|
159
182
|
z.object({ kind: z.literal("init"), model: z.string() }),
|
|
@@ -227,6 +250,21 @@ export const AgentEventSchema = z.discriminatedUnion("kind", [
|
|
|
227
250
|
z.object({ kind: z.literal("plan"), requestId: z.string(), text: z.string() }),
|
|
228
251
|
z.object({ kind: z.literal("question"), requestId: z.string(), questions: z.array(AskQuestionSchema) }),
|
|
229
252
|
PermissionAskSchema.extend({ kind: z.literal("permission"), requestId: z.string() }),
|
|
253
|
+
// The card above named by `requestId` is released — the user answered (or dismissed it, or the turn was
|
|
254
|
+
// stopped out from under it), so the turn is executing again. Emitted by whoever parked, the moment its
|
|
255
|
+
// waiter settles, because the park's END is otherwise invisible on this stream: nothing else here says
|
|
256
|
+
// "that card is done", and it cannot be inferred from the next frame that happens along. Frames DO arrive
|
|
257
|
+
// while a turn is parked — the pausing tool's own `tool_call` regularly trails its card (the SDK queues
|
|
258
|
+
// stream messages while dispatching an in-process MCP tool straight off the transport), and a card raised
|
|
259
|
+
// beside a parallel tool call sits through that tool's whole life. See agents-registry.ts, which reads
|
|
260
|
+
// this pair as the fleet's "needs you" state.
|
|
261
|
+
//
|
|
262
|
+
// `reply` says HOW it settled, and is what a transcript rebuilt from this log freezes the card with: a
|
|
263
|
+
// reload replays the run from seq 0 and a second window renders it live, so both would otherwise restore
|
|
264
|
+
// the card pending — offering buttons on a requestId nothing holds any more, under a transcript that has
|
|
265
|
+
// already moved on. It rides verbatim, exactly as the client POSTed it; absent, nobody answered (the turn
|
|
266
|
+
// was stopped, or died under the card), which is not a decision and must not replay as one.
|
|
267
|
+
z.object({ kind: z.literal("resolved"), requestId: z.string(), reply: AgentReplySchema.optional() }),
|
|
230
268
|
// The turn's permission mode, whenever it changes — the user's pick at turn start, then every move the
|
|
231
269
|
// AGENT makes on its own (EnterPlanMode on a request that needs thinking through, ExitPlanMode once the
|
|
232
270
|
// user approves). The composer's mode selector follows this, so the UI never lies about the live posture.
|
|
@@ -240,13 +278,41 @@ export const AgentEventSchema = z.discriminatedUnion("kind", [
|
|
|
240
278
|
.enum([
|
|
241
279
|
"session-not-found",
|
|
242
280
|
"rate_limit",
|
|
281
|
+
// Codex ran the turn but warned about it (fallback model metadata) — a notice, not a failure.
|
|
282
|
+
"codex-advisory",
|
|
243
283
|
"codex-reauth",
|
|
284
|
+
// The Claude subscription credential is dead (revoked, or its refresh token rejected) and only a
|
|
285
|
+
// reconnect fixes it. Distinct from "no account connected": the account IS there, so the UI can
|
|
286
|
+
// offer reconnect where the user already is and replay the message that bounced.
|
|
287
|
+
"claude-reauth",
|
|
288
|
+
// The API refused this turn's token MID-FLIGHT — nearly always one superseded by a rotation,
|
|
289
|
+
// which Anthropic retires the moment its successor is minted. Distinct from claude-reauth: the
|
|
290
|
+
// account is fine and the daemon re-mints on the spot, so this frame is a notice about a turn
|
|
291
|
+
// that resumed itself, not a request for the user to do anything. It only reaches the client
|
|
292
|
+
// when the resume could NOT start, which is when reconnecting really is the fix.
|
|
293
|
+
"claude-token-refused",
|
|
294
|
+
// The harness read the message as a slash command it doesn't have, and discarded everything
|
|
295
|
+
// after the name — the model never saw the message. Nothing was processed, so the client holds
|
|
296
|
+
// the text back instead of leaving the user to retype it (same treatment as claude-reauth).
|
|
297
|
+
"unknown-command",
|
|
244
298
|
"grok-model-invalid",
|
|
245
299
|
"codex-model-invalid",
|
|
246
300
|
"subscription-required",
|
|
247
301
|
"agent-busy",
|
|
248
302
|
])
|
|
249
303
|
.optional(),
|
|
304
|
+
// rate_limit only: when the exhausted window reopens (epoch seconds, from the stream's own
|
|
305
|
+
// rate_limit_event or the account's persisted usage windows), and what the daemon did about it —
|
|
306
|
+
// "scheduled" = auto-resume is on and this turn re-runs itself a minute after `resetsAt`;
|
|
307
|
+
// "available" = the daemon remembered the failed turn and enabling the autoResumeOnLimit setting
|
|
308
|
+
// arms that same resume, which is what the chat's offer banner hangs off. Absent together when the
|
|
309
|
+
// reset instant is unknown (nothing to schedule against).
|
|
310
|
+
resetsAt: z.number().optional(),
|
|
311
|
+
autoResume: z.enum(["scheduled", "available"]).optional(),
|
|
312
|
+
// rate_limit only: the account whose allowance is spent, as the DAEMON resolved it (the client's own
|
|
313
|
+
// selection can be empty, which means "the provider's first"). It is what lets the chat offer the
|
|
314
|
+
// provider's OTHER accounts as a resume-now instead of a wait — see /agent/resume-limit.
|
|
315
|
+
account: z.string().optional(),
|
|
250
316
|
}),
|
|
251
317
|
z.object({ kind: z.literal("done") }),
|
|
252
318
|
]);
|
|
@@ -283,7 +349,15 @@ export type Heartbeat = z.infer<typeof HeartbeatSchema>;
|
|
|
283
349
|
// browser remembers it per sandbox id and drops that sandbox's persisted query cache when it changes — a wiped
|
|
284
350
|
// and recreated workspace (cleanup.sh + reconnect keeps the same sandbox id) must not be painted from the
|
|
285
351
|
// previous workspace's cache.
|
|
286
|
-
|
|
352
|
+
//
|
|
353
|
+
// It also advertises `routes` — the contract route names (`vpn.list`, `kimi.models`) this daemon actually
|
|
354
|
+
// implements, from ITS build of the contract. A browser is routinely newer than the daemon it talks to (a
|
|
355
|
+
// released app plane serves whatever image each user last pulled; in local dev the web app is always ahead of
|
|
356
|
+
// the last `pnpm build:sandbox`), and that stays fully supported — the browser just compares the two sets so a
|
|
357
|
+
// route the daemon predates surfaces as a named, explained gap instead of a bare 404 nobody can attribute.
|
|
358
|
+
// Optional: a daemon built before this field simply advertises nothing, and every route is assumed present,
|
|
359
|
+
// which is exactly the pre-existing behaviour.
|
|
360
|
+
export const HelloSchema = z.object({ kind: z.literal("hello"), workspaceId: z.string(), routes: z.array(z.string()).optional() });
|
|
287
361
|
export type Hello = z.infer<typeof HelloSchema>;
|
|
288
362
|
|
|
289
363
|
// The FULL discovered repo set (sorted root-relative ids), pushed whenever it changes — a clone, a scaffold,
|
|
@@ -325,8 +399,12 @@ export const PresenceSchema = z.object({ kind: z.literal("presence"), users: z.a
|
|
|
325
399
|
export type Presence = z.infer<typeof PresenceSchema>;
|
|
326
400
|
|
|
327
401
|
// The FULL fleet roster, broadcast on every registry change — same snapshot-not-diff contract as presence:
|
|
328
|
-
// a reconnecting browser is consistent from its first frame
|
|
329
|
-
|
|
402
|
+
// a reconnecting browser is consistent from its first frame. NOT simply "last frame wins", though: `rev` is the
|
|
403
|
+
// registry revision the snapshot was taken at, and the browser applies a frame only if it is newer than the one
|
|
404
|
+
// it already holds. Snapshots race two other sources of the same fact — an explicit GET /agents and the
|
|
405
|
+
// browser's own optimistic writes — and an unordered full replace lets the slowest of them win, which is how an
|
|
406
|
+
// archived card came back. See AgentsListSchema and useAgents.ts.
|
|
407
|
+
export const AgentsSchema = z.object({ kind: z.literal("agents"), agents: z.array(AgentSummarySchema), rev: z.number() });
|
|
330
408
|
export type Agents = z.infer<typeof AgentsSchema>;
|
|
331
409
|
|
|
332
410
|
// The /events stream union: the hello identity frame, then liveness heartbeats interleaved with
|
package/src/hostnames.test.ts
CHANGED
|
@@ -9,6 +9,9 @@ import {
|
|
|
9
9
|
previewHostname,
|
|
10
10
|
previewLabel,
|
|
11
11
|
previewUrl,
|
|
12
|
+
sandboxHostname,
|
|
13
|
+
sandboxIdFromUrl,
|
|
14
|
+
syncFolder,
|
|
12
15
|
} from "./hostnames.js";
|
|
13
16
|
|
|
14
17
|
const ID = "abc123def456";
|
|
@@ -35,6 +38,25 @@ test("labels are the mintable unit: hostname = <label>-<id>.<zone> for both sche
|
|
|
35
38
|
expect(labelHostname(portLabel("a"), ID, "example.com")).toBe(portHostname("a", ID, "example.com"));
|
|
36
39
|
});
|
|
37
40
|
|
|
41
|
+
test("the local sync folder carries the very id the sandbox's URL does", () => {
|
|
42
|
+
const url = `https://${sandboxHostname(ID, "intentic.dev")}`;
|
|
43
|
+
expect(sandboxIdFromUrl(url)).toBe(ID);
|
|
44
|
+
expect(syncFolder("Radarsu Local", url)).toBe(`~/intentic/radarsu-local-${ID}`);
|
|
45
|
+
// A bare host (no scheme) and a port are both accepted — the daemon/CLI and the browser pass different shapes.
|
|
46
|
+
expect(sandboxIdFromUrl(`${sandboxHostname(ID, "intentic.dev")}:443`)).toBe(ID);
|
|
47
|
+
// Own-Cloudflare: the owner's chosen subdomain IS the id, so the folder still matches what they browse to.
|
|
48
|
+
expect(syncFolder("shop", "https://dev-box.example.com")).toBe("~/intentic/shop-dev-box");
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test("sandbox ids that can't be read off a URL leave the folder named for the sandbox alone", () => {
|
|
52
|
+
expect(sandboxIdFromUrl(undefined)).toBeUndefined();
|
|
53
|
+
expect(sandboxIdFromUrl("")).toBeUndefined();
|
|
54
|
+
expect(sandboxIdFromUrl("not a url")).toBeUndefined();
|
|
55
|
+
expect(sandboxIdFromUrl("https://sandbox-.intentic.dev")).toBeUndefined();
|
|
56
|
+
expect(syncFolder("My Repo", undefined)).toBe("~/intentic/my-repo");
|
|
57
|
+
expect(syncFolder("", undefined)).toBe("~/intentic/sandbox");
|
|
58
|
+
});
|
|
59
|
+
|
|
38
60
|
test("preview/port URLs require both a zone and a sandbox id", () => {
|
|
39
61
|
expect(previewUrl("app", "example.com", ID)).toBe(`https://preview-app-${ID}.example.com`);
|
|
40
62
|
expect(portUrl("a", "example.com", ID)).toBe(`https://port-a-${ID}.example.com`);
|
package/src/hostnames.ts
CHANGED
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
// the zone's SOA TTL. This module is that agreement.
|
|
8
8
|
|
|
9
9
|
// The sandbox daemon's subdomain + hostname: `sandbox-<id>` / `sandbox-<id>.<zone>`.
|
|
10
|
-
|
|
10
|
+
const SANDBOX_PREFIX = "sandbox-";
|
|
11
|
+
export const sandboxSubdomain = (id: string): string => `${SANDBOX_PREFIX}${id}`;
|
|
11
12
|
export const sandboxHostname = (id: string, zone: string): string => `${sandboxSubdomain(id)}.${zone}`;
|
|
12
13
|
|
|
13
14
|
// The container sshd hostname the desktop-sync (Mutagen) reaches over the sandbox tunnel: `ssh-<id>.<zone>`.
|
|
@@ -73,6 +74,44 @@ export const panelFromHost = (hostHeader: string | undefined, sandboxId: string
|
|
|
73
74
|
export const portSlotFromHost = (hostHeader: string | undefined, sandboxId: string | undefined): string | undefined =>
|
|
74
75
|
keyFromHost("port-", hostHeader, sandboxId);
|
|
75
76
|
|
|
77
|
+
// The sandbox's identity AS THE USER SEES IT: the leading DNS label of its public URL, minus the `sandbox-`
|
|
78
|
+
// prefix — `https://sandbox-0f310c3c4db4.intentic.dev` → `0f310c3c4db4`, i.e. sandboxIdFromToken's digest read
|
|
79
|
+
// back off the wire by anyone holding only the URL. On the own-Cloudflare path the label is whatever subdomain
|
|
80
|
+
// the owner chose, so that is the id there. undefined until the sandbox has a URL at all.
|
|
81
|
+
export const sandboxIdFromUrl = (url: string | undefined): string | undefined => {
|
|
82
|
+
if (url === undefined || url === "") {
|
|
83
|
+
return undefined;
|
|
84
|
+
}
|
|
85
|
+
const withScheme = /^[a-z][a-z0-9+.-]*:\/\//i.test(url) ? url : `https://${url}`;
|
|
86
|
+
let label: string;
|
|
87
|
+
try {
|
|
88
|
+
label = new URL(withScheme).hostname.split(".")[0] ?? "";
|
|
89
|
+
} catch {
|
|
90
|
+
return undefined;
|
|
91
|
+
}
|
|
92
|
+
if (label === "" || label === SANDBOX_PREFIX) {
|
|
93
|
+
return undefined;
|
|
94
|
+
}
|
|
95
|
+
return label.startsWith(SANDBOX_PREFIX) ? label.slice(SANDBOX_PREFIX.length) : label;
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
// The LOCAL folder desktop sync mirrors /work into: `~/intentic/<sandbox name>-<sandboxIdFromUrl>`. Both halves
|
|
99
|
+
// are strings the user already has in front of them — the name in the sandbox switcher, the id in the address
|
|
100
|
+
// bar — so the folder on disk and the sandbox it mirrors read as ONE identity: `~/intentic/shop-0f310c3c4db4`
|
|
101
|
+
// belongs to `https://sandbox-0f310c3c4db4.intentic.dev` and nothing else. Keyed on the URL rather than the
|
|
102
|
+
// name alone for the same reason the hostname is: a torn-down sandbox recreated under the same name gets a new
|
|
103
|
+
// id, hence its own fresh folder instead of reusing the dead one's (which cleanup never deletes) and colliding
|
|
104
|
+
// on the two-way sync. Lives here, beside the hostname builders, because that match IS the contract.
|
|
105
|
+
export const syncFolder = (name: string, url: string | undefined): string => {
|
|
106
|
+
const slug =
|
|
107
|
+
name
|
|
108
|
+
.replace(/[^a-zA-Z0-9]+/g, "-")
|
|
109
|
+
.replace(/^-+|-+$/g, "")
|
|
110
|
+
.toLowerCase() || "sandbox";
|
|
111
|
+
const id = sandboxIdFromUrl(url);
|
|
112
|
+
return `~/intentic/${slug}${id === undefined ? "" : `-${id}`}`;
|
|
113
|
+
};
|
|
114
|
+
|
|
76
115
|
// The Cloudflare zone from a sandbox public URL (https://sandbox-<id>.<zone> → <zone>): the hostname minus its
|
|
77
116
|
// first DNS label. undefined when the URL is unparsable OR the hostname has fewer than three labels (no zone
|
|
78
117
|
// suffix to strip — e.g. a 2-label host would otherwise yield a bare TLD). Accepts scheme-less input too, so it
|
package/src/index.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { ContractRoute } from "./routes.js";
|
|
2
|
+
import { contractRoutes, routeNameForRequest } from "./routes.js";
|
|
1
3
|
import { activityContract } from "./contracts/activity.contract.js";
|
|
2
4
|
import { agentContract } from "./contracts/agent.contract.js";
|
|
3
5
|
import { agentsContract } from "./contracts/agents.contract.js";
|
|
@@ -15,6 +17,7 @@ import { intenticContract } from "./contracts/intentic.contract.js";
|
|
|
15
17
|
import { inventoryContract } from "./contracts/inventory.contract.js";
|
|
16
18
|
import { kimiContract } from "./contracts/kimi.contract.js";
|
|
17
19
|
import { logsContract } from "./contracts/logs.contract.js";
|
|
20
|
+
import { memoryContract } from "./contracts/memory.contract.js";
|
|
18
21
|
import { panelsContract } from "./contracts/panels.contract.js";
|
|
19
22
|
import { portsContract } from "./contracts/ports.contract.js";
|
|
20
23
|
import { pushContract } from "./contracts/push.contract.js";
|
|
@@ -44,6 +47,7 @@ export { intenticContract } from "./contracts/intentic.contract.js";
|
|
|
44
47
|
export { inventoryContract } from "./contracts/inventory.contract.js";
|
|
45
48
|
export { kimiContract } from "./contracts/kimi.contract.js";
|
|
46
49
|
export { logsContract } from "./contracts/logs.contract.js";
|
|
50
|
+
export { memoryContract } from "./contracts/memory.contract.js";
|
|
47
51
|
export { panelsContract } from "./contracts/panels.contract.js";
|
|
48
52
|
export { portsContract } from "./contracts/ports.contract.js";
|
|
49
53
|
export { pushContract } from "./contracts/push.contract.js";
|
|
@@ -58,11 +62,15 @@ export { workspaceContract } from "./contracts/workspace.contract.js";
|
|
|
58
62
|
export * from "./effects.js";
|
|
59
63
|
export * from "./events.js";
|
|
60
64
|
export * from "./sse.js";
|
|
65
|
+
export * from "./routes.js";
|
|
61
66
|
export * from "./agent-catalog.js";
|
|
62
67
|
export * from "./hostnames.js";
|
|
63
68
|
export * from "./model-order.js";
|
|
69
|
+
export * from "./path-refs.js";
|
|
70
|
+
export * from "./quick-model.js";
|
|
64
71
|
export * from "./schemas.js";
|
|
65
72
|
export * from "./terminal-protocol.js";
|
|
73
|
+
export * from "./title.js";
|
|
66
74
|
|
|
67
75
|
// The aggregated contract — implemented on the server by the per-domain route factories and consumed by the
|
|
68
76
|
// browser's typed oRPC client (ContractRouterClient<typeof sandboxContract>). The wire paths it declares are
|
|
@@ -88,6 +96,7 @@ export const sandboxContract = {
|
|
|
88
96
|
workspace: workspaceContract,
|
|
89
97
|
inventory: inventoryContract,
|
|
90
98
|
logs: logsContract,
|
|
99
|
+
memory: memoryContract,
|
|
91
100
|
panels: panelsContract,
|
|
92
101
|
ports: portsContract,
|
|
93
102
|
push: pushContract,
|
|
@@ -97,3 +106,14 @@ export const sandboxContract = {
|
|
|
97
106
|
usage: usageContract,
|
|
98
107
|
vpn: vpnContract,
|
|
99
108
|
};
|
|
109
|
+
|
|
110
|
+
// Every route in THIS build of the contract, and the names the daemon advertises on its hello frame. Bound here
|
|
111
|
+
// rather than in routes.ts so that module stays a pure function of whatever contract it is handed — importing
|
|
112
|
+
// `sandboxContract` from there would close a load-time cycle back through this file. See routes.ts for why a
|
|
113
|
+
// daemon advertises its route surface at all.
|
|
114
|
+
export const SANDBOX_ROUTES: readonly ContractRoute[] = contractRoutes(sandboxContract);
|
|
115
|
+
export const SANDBOX_ROUTE_NAMES: readonly string[] = SANDBOX_ROUTES.map((route) => route.name);
|
|
116
|
+
|
|
117
|
+
// The contract route a concrete browser request belongs to, bound to this build's route table.
|
|
118
|
+
export const sandboxRouteName = (method: string, pathWithQuery: string): string | undefined =>
|
|
119
|
+
routeNameForRequest(SANDBOX_ROUTES, method, pathWithQuery);
|