@intentic/sandbox-contract 1.329.0 → 1.331.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/chores/probes.d.ts.map +1 -1
- package/dist/chores/probes.js +1 -2
- package/dist/chores/probes.js.map +1 -1
- package/dist/contracts/accounts.contract.d.ts +4 -0
- package/dist/contracts/accounts.contract.d.ts.map +1 -1
- package/dist/contracts/agent.contract.d.ts +1 -0
- package/dist/contracts/agent.contract.d.ts.map +1 -1
- package/dist/contracts/agents.contract.d.ts +69 -0
- package/dist/contracts/agents.contract.d.ts.map +1 -1
- package/dist/contracts/device.contract.d.ts +6 -0
- package/dist/contracts/device.contract.d.ts.map +1 -1
- package/dist/contracts/device.contract.js +2 -1
- package/dist/contracts/device.contract.js.map +1 -1
- package/dist/contracts/runner.contract.d.ts +3 -0
- package/dist/contracts/runner.contract.d.ts.map +1 -1
- package/dist/contracts/runs.contract.d.ts +35 -0
- package/dist/contracts/runs.contract.d.ts.map +1 -0
- package/dist/contracts/runs.contract.js +22 -0
- package/dist/contracts/runs.contract.js.map +1 -0
- package/dist/contracts/settings.contract.d.ts +10 -0
- package/dist/contracts/settings.contract.d.ts.map +1 -1
- package/dist/contracts/system.contract.d.ts +45 -30
- package/dist/contracts/system.contract.d.ts.map +1 -1
- package/dist/contracts/system.contract.js +10 -1
- package/dist/contracts/system.contract.js.map +1 -1
- package/dist/contracts/translator.contract.d.ts +4 -0
- package/dist/contracts/translator.contract.d.ts.map +1 -1
- package/dist/contracts/workspace.contract.d.ts +37 -0
- package/dist/contracts/workspace.contract.d.ts.map +1 -1
- package/dist/contracts/workspace.contract.js +11 -1
- package/dist/contracts/workspace.contract.js.map +1 -1
- package/dist/events/agent-events.d.ts +1 -0
- package/dist/events/agent-events.d.ts.map +1 -1
- package/dist/events/system-events.d.ts +10 -0
- package/dist/events/system-events.d.ts.map +1 -1
- package/dist/index.d.ts +179 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -1
- package/dist/index.js.map +1 -1
- package/dist/models/model-roles.d.ts +8 -0
- package/dist/models/model-roles.d.ts.map +1 -1
- package/dist/models/model-roles.js +8 -0
- package/dist/models/model-roles.js.map +1 -1
- package/dist/models/plan-pools.d.ts +2 -0
- package/dist/models/plan-pools.d.ts.map +1 -1
- package/dist/models/plan-pools.js +23 -7
- package/dist/models/plan-pools.js.map +1 -1
- package/dist/protocol/doors/listener-protocol.d.ts +0 -1
- package/dist/protocol/doors/listener-protocol.d.ts.map +1 -1
- package/dist/protocol/doors/listener-protocol.js +3 -2
- package/dist/protocol/doors/listener-protocol.js.map +1 -1
- package/dist/protocol/raw/raw-routes.d.ts +25 -0
- package/dist/protocol/raw/raw-routes.d.ts.map +1 -1
- package/dist/protocol/raw/raw-routes.js +7 -0
- package/dist/protocol/raw/raw-routes.js.map +1 -1
- package/dist/schemas/agent.d.ts +3 -0
- package/dist/schemas/agent.d.ts.map +1 -1
- package/dist/schemas/agents-list.d.ts +4 -0
- package/dist/schemas/agents-list.d.ts.map +1 -1
- package/dist/schemas/agents.d.ts +13 -0
- package/dist/schemas/agents.d.ts.map +1 -1
- package/dist/schemas/agents.js +4 -3
- package/dist/schemas/agents.js.map +1 -1
- package/dist/schemas/device-artifacts.d.ts +13 -0
- package/dist/schemas/device-artifacts.d.ts.map +1 -1
- package/dist/schemas/device-artifacts.js +13 -0
- package/dist/schemas/device-artifacts.js.map +1 -1
- package/dist/schemas/environment.d.ts +30 -0
- package/dist/schemas/environment.d.ts.map +1 -1
- package/dist/schemas/environment.js +13 -0
- package/dist/schemas/environment.js.map +1 -1
- package/dist/schemas/providers/plan-limits.d.ts +7 -0
- package/dist/schemas/providers/plan-limits.d.ts.map +1 -1
- package/dist/schemas/providers/plan-limits.js +5 -1
- package/dist/schemas/providers/plan-limits.js.map +1 -1
- package/dist/schemas/providers/provider-oauth.d.ts +4 -0
- package/dist/schemas/providers/provider-oauth.d.ts.map +1 -1
- package/dist/schemas/run-targets.d.ts +82 -0
- package/dist/schemas/run-targets.d.ts.map +1 -0
- package/dist/schemas/run-targets.js +49 -0
- package/dist/schemas/run-targets.js.map +1 -0
- package/dist/schemas/settings-history.d.ts +6 -1
- package/dist/schemas/settings-history.d.ts.map +1 -1
- package/dist/schemas/settings-history.js +4 -1
- package/dist/schemas/settings-history.js.map +1 -1
- package/dist/schemas/settings.d.ts +10 -0
- package/dist/schemas/settings.d.ts.map +1 -1
- package/dist/schemas/settings.js +2 -1
- package/dist/schemas/settings.js.map +1 -1
- package/dist/schemas/speech.d.ts +75 -0
- package/dist/schemas/speech.d.ts.map +1 -0
- package/dist/schemas/speech.js +66 -0
- package/dist/schemas/speech.js.map +1 -0
- package/dist/schemas/terminal.d.ts +4 -0
- package/dist/schemas/terminal.d.ts.map +1 -1
- package/dist/schemas/terminal.js +4 -0
- package/dist/schemas/terminal.js.map +1 -1
- package/dist/schemas/turn-break.d.ts +7 -1
- package/dist/schemas/turn-break.d.ts.map +1 -1
- package/dist/schemas/turn-break.js +4 -2
- package/dist/schemas/turn-break.js.map +1 -1
- package/dist/schemas/workspace/workspace-graph.d.ts +133 -0
- package/dist/schemas/workspace/workspace-graph.d.ts.map +1 -0
- package/dist/schemas/workspace/workspace-graph.js +83 -0
- package/dist/schemas/workspace/workspace-graph.js.map +1 -0
- package/dist/schemas/workspace/workspace-repos.d.ts +0 -39
- package/dist/schemas/workspace/workspace-repos.d.ts.map +1 -1
- package/dist/schemas/workspace/workspace-repos.js +0 -15
- package/dist/schemas/workspace/workspace-repos.js.map +1 -1
- package/dist/state/definition.d.ts +8 -4
- package/dist/state/definition.d.ts.map +1 -1
- package/dist/state/workspace-state.d.ts +2 -1
- package/dist/state/workspace-state.d.ts.map +1 -1
- package/dist/state/workspace-state.js +5 -1
- package/dist/state/workspace-state.js.map +1 -1
- package/dist/text/transcript-page.d.ts +5 -0
- package/dist/text/transcript-page.d.ts.map +1 -0
- package/dist/text/transcript-page.js +17 -0
- package/dist/text/transcript-page.js.map +1 -0
- package/package.json +5 -5
- package/src/chores/probes.ts +1 -2
- package/src/contracts/device.contract.ts +6 -1
- package/src/contracts/runs.contract.ts +26 -0
- package/src/contracts/system.contract.ts +13 -0
- package/src/contracts/workspace.contract.ts +12 -1
- package/src/index.ts +7 -1
- package/src/models/agent-runtimes.ts +2 -2
- package/src/models/model-roles.ts +8 -0
- package/src/models/plan-pools.test.ts +27 -0
- package/src/models/plan-pools.ts +38 -10
- package/src/protocol/doors/listener-protocol.ts +5 -4
- package/src/protocol/raw/raw-routes.ts +12 -0
- package/src/schemas/agents.ts +6 -3
- package/src/schemas/device-artifacts.ts +21 -0
- package/src/schemas/environment.ts +25 -0
- package/src/schemas/providers/plan-limits.ts +9 -1
- package/src/schemas/run-targets.ts +93 -0
- package/src/schemas/settings-history.ts +15 -1
- package/src/schemas/settings.test.ts +25 -0
- package/src/schemas/settings.ts +6 -1
- package/src/schemas/speech.ts +96 -0
- package/src/schemas/terminal.ts +4 -0
- package/src/schemas/turn-break.ts +18 -9
- package/src/schemas/workspace/workspace-graph.ts +116 -0
- package/src/schemas/workspace/workspace-repos.ts +0 -22
- package/src/state/workspace-state.test.ts +42 -1
- package/src/state/workspace-state.ts +12 -1
- package/src/text/transcript-page.test.ts +39 -0
- package/src/text/transcript-page.ts +37 -0
- package/dist/text/whisper.d.ts +0 -3
- package/dist/text/whisper.d.ts.map +0 -1
- package/dist/text/whisper.js +0 -11
- package/dist/text/whisper.js.map +0 -1
- package/src/text/whisper.test.ts +0 -18
- package/src/text/whisper.ts +0 -17
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { ArtifactNameSchema } from "./device-artifacts.js";
|
|
3
|
+
|
|
4
|
+
// WHAT A REPOSITORY SAYS IT CAN BE RUN AS, ON ONE OF THE OWNER'S COMPUTERS (`<repo>/.intentic/run.json`). Beside the
|
|
5
|
+
// build scripts it names and tracked with them, like `.intentic/checks.json`. One file serves two doors that must do the
|
|
6
|
+
// same thing: an agent's `devices run <target>` in its own shell, and the editor's "Run on <device>" button, which runs
|
|
7
|
+
// that very command in a terminal the owner watches. Each run builds in the sandbox, carries the build to the device
|
|
8
|
+
// (`devices push`), opens the device ports the target says the sandbox needs (`devices reach`), and starts the program
|
|
9
|
+
// there (the device's app_start). Nothing here runs by itself: a target runs when someone runs it.
|
|
10
|
+
//
|
|
11
|
+
// {
|
|
12
|
+
// "targets": [{
|
|
13
|
+
// "name": "desktop",
|
|
14
|
+
// "device": "rog",
|
|
15
|
+
// "build": "bash _tools/scripts/desktop/build-desktop.sh 0.0.0 --windows-only",
|
|
16
|
+
// "artifact": {
|
|
17
|
+
// "intentic-desktop.exe": "_editor/desktop-app/src-tauri/target/x86_64-pc-windows-msvc/release/intentic-desktop.exe",
|
|
18
|
+
// "intentic-files.exe": "_editor/desktop-app/src-tauri/binaries/intentic-files-x86_64-pc-windows-msvc.exe"
|
|
19
|
+
// },
|
|
20
|
+
// "program": "intentic-desktop.exe"
|
|
21
|
+
// }]
|
|
22
|
+
// }
|
|
23
|
+
|
|
24
|
+
export const RUN_TARGETS_FILE = ".intentic/run.json";
|
|
25
|
+
|
|
26
|
+
const RepoPathSchema = z
|
|
27
|
+
.string()
|
|
28
|
+
.min(1)
|
|
29
|
+
.max(500)
|
|
30
|
+
.refine((path) => !path.startsWith("/") && !path.split(/[\\/]/).includes(".."), "a path inside the repository, relative to its root");
|
|
31
|
+
const BundleNameSchema = z.string().regex(/^[A-Za-z0-9][A-Za-z0-9 ._()+-]{0,199}(\/[A-Za-z0-9][A-Za-z0-9 ._()+-]{0,199})*$/, "a relative name, folders by /");
|
|
32
|
+
|
|
33
|
+
export const RunTargetSchema = z
|
|
34
|
+
.object({
|
|
35
|
+
// What `devices run <name>` and the button call it.
|
|
36
|
+
name: ArtifactNameSchema,
|
|
37
|
+
description: z.string().max(300).optional(),
|
|
38
|
+
// The computer it runs on unless one is named at the run; the device's name as its tools are mounted.
|
|
39
|
+
device: z.string().min(1).max(100).optional(),
|
|
40
|
+
// A shell command run from the repository's root, in the sandbox, before every run; absent means the artifact
|
|
41
|
+
// is already there.
|
|
42
|
+
build: z.string().min(1).max(4000).optional(),
|
|
43
|
+
// What is carried: one file or one folder, or a set of files and folders gathered into one folder under names of
|
|
44
|
+
// their own (an .exe beside the sidecars, DLLs and resource folders it looks for next to itself).
|
|
45
|
+
artifact: z.union([RepoPathSchema, z.record(BundleNameSchema, RepoPathSchema)]),
|
|
46
|
+
// Which file to start when the artifact is a folder or a set; a lone file is started as itself.
|
|
47
|
+
program: BundleNameSchema.optional(),
|
|
48
|
+
args: z.array(z.string().max(4000)).max(100).default([]),
|
|
49
|
+
env: z.record(z.string().regex(/^[A-Za-z_][A-Za-z0-9_]*$/), z.string().max(4000)).default({}),
|
|
50
|
+
// Run it inside Windows Sandbox, a throwaway VM that sees the build read-only and nothing else of the machine.
|
|
51
|
+
isolated: z.boolean().default(false),
|
|
52
|
+
// The device's own loopback ports the sandbox needs to reach while it runs (its local API, a dev server), each
|
|
53
|
+
// opened at the same port in the sandbox.
|
|
54
|
+
reach: z.array(z.int().min(1).max(65535)).max(16).default([]),
|
|
55
|
+
})
|
|
56
|
+
.strict();
|
|
57
|
+
export type RunTarget = z.infer<typeof RunTargetSchema>;
|
|
58
|
+
|
|
59
|
+
export const RunTargetsFileSchema = z
|
|
60
|
+
.object({
|
|
61
|
+
targets: z
|
|
62
|
+
.array(RunTargetSchema)
|
|
63
|
+
.min(1)
|
|
64
|
+
.max(50)
|
|
65
|
+
.refine((targets) => new Set(targets.map((target) => target.name)).size === targets.length, "two targets share a name"),
|
|
66
|
+
})
|
|
67
|
+
.strict();
|
|
68
|
+
|
|
69
|
+
// One repository's file as the editor lists it: its targets, or what is wrong with the file.
|
|
70
|
+
export const RepoRunTargetsSchema = z.object({
|
|
71
|
+
repo: z.string(),
|
|
72
|
+
path: z.string(),
|
|
73
|
+
targets: z.array(RunTargetSchema),
|
|
74
|
+
error: z.string().optional(),
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
export const RunTargetsListSchema = z.object({
|
|
78
|
+
repos: z.array(RepoRunTargetsSchema),
|
|
79
|
+
// The owner's computers: whether each is connected, whether its agent is new enough to take a program, and whether
|
|
80
|
+
// its card's "Run programs this sandbox sends" switch is on. A run needs all three.
|
|
81
|
+
devices: z.array(z.object({ id: z.string(), online: z.boolean(), programs: z.boolean(), allowed: z.boolean() })),
|
|
82
|
+
});
|
|
83
|
+
export type RunTargetsList = z.infer<typeof RunTargetsListSchema>;
|
|
84
|
+
|
|
85
|
+
export const RunStartSchema = z.object({
|
|
86
|
+
repo: z.string().min(1),
|
|
87
|
+
target: ArtifactNameSchema,
|
|
88
|
+
device: z.string().min(1).max(100),
|
|
89
|
+
});
|
|
90
|
+
export type RunStart = z.infer<typeof RunStartSchema>;
|
|
91
|
+
|
|
92
|
+
// The terminal session the run is in, for the editor to open.
|
|
93
|
+
export const RunStartedSchema = z.object({ session: z.string() });
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import { at, drop, dropAll, fold, isJsonObject, mapValue, retype } from "../documents/conversions.js";
|
|
2
|
+
import { at, drop, dropAll, fold, isJsonObject, type JsonObject, mapValue, retype, transform } from "../documents/conversions.js";
|
|
3
3
|
import type { KeepWarmSettings, RuleSchema } from "./settings.js";
|
|
4
4
|
|
|
5
5
|
// The conversions the settings shape has had, oldest first: what brings any earlier release's settings to
|
|
@@ -63,6 +63,19 @@ const keepWarmObject = fold("folds keepWarm, keepWarmHours, keepWarmMinTokens an
|
|
|
63
63
|
},
|
|
64
64
|
});
|
|
65
65
|
|
|
66
|
+
// The Refactors job (2026-10-09): a refactor started from a repository's Health tab used to name no job and opened on
|
|
67
|
+
// whatever model the composer last held. A file from before it inherits the Maintenance chores list, the job nearest in
|
|
68
|
+
// kind, so the owner's choice for work they start carries over instead of the new job opening empty. Only while the key
|
|
69
|
+
// is absent: an owner who later empties it stores [], which this leaves alone.
|
|
70
|
+
type RolesWithChores = JsonObject & { readonly "maintenance-chore": readonly unknown[] };
|
|
71
|
+
const holdsChoresWithoutRefactors = (roles: JsonObject): roles is RolesWithChores =>
|
|
72
|
+
!Object.hasOwn(roles, "refactor-run") && Array.isArray(roles["maintenance-chore"]) && roles["maintenance-chore"].length > 0;
|
|
73
|
+
const refactorsFromChores = transform(
|
|
74
|
+
"gives the new Refactors job the Maintenance chores list",
|
|
75
|
+
holdsChoresWithoutRefactors,
|
|
76
|
+
(roles): JsonObject => ({ ...roles, "refactor-run": roles["maintenance-chore"] }),
|
|
77
|
+
);
|
|
78
|
+
|
|
66
79
|
export const SETTINGS_HISTORY = [
|
|
67
80
|
// Every setting a release since 2026-08-10 had and this one does not (read off the contract lock's history):
|
|
68
81
|
// retired, so passthrough stops carrying them, a definition naming one still applies, and no later setting may
|
|
@@ -112,4 +125,5 @@ export const SETTINGS_HISTORY = [
|
|
|
112
125
|
at("modelRoles", drop("pre-push-fix")),
|
|
113
126
|
// The short guidance form (2026-10-06): it lost its A/B to the full one, which every turn now gets.
|
|
114
127
|
...dropAll(["leanGuidance", "leanGuidanceHoldout"]),
|
|
128
|
+
at("modelRoles", refactorsFromChores),
|
|
115
129
|
] as const;
|
|
@@ -129,3 +129,28 @@ describe(`the retired pre-push fix role`, () => {
|
|
|
129
129
|
expect(convertDocument(SETTINGS_HISTORY, `object`, { modelRoles: { "pipeline-fix": [pin] } }, true).changes).toEqual([]);
|
|
130
130
|
});
|
|
131
131
|
});
|
|
132
|
+
|
|
133
|
+
// The Refactors job arrived on 2026-10-09; a file from before it carries the owner's Maintenance chores list over, so a
|
|
134
|
+
// refactor started from Health opens on a model they chose rather than on an empty job.
|
|
135
|
+
describe(`the new Refactors job`, () => {
|
|
136
|
+
const opus = { provider: `claude`, model: `claude-opus-5-5` };
|
|
137
|
+
const sol = { provider: `codex`, model: `gpt-sol-6.1` };
|
|
138
|
+
|
|
139
|
+
test(`takes the Maintenance chores list in a file from before it, and every other list stays`, () => {
|
|
140
|
+
const stored = { modelRoles: { "maintenance-chore": [opus, sol], "pipeline-fix": [sol] } };
|
|
141
|
+
const converted = convertDocument(SETTINGS_HISTORY, `object`, stored, true);
|
|
142
|
+
expect(converted.value).toEqual({ modelRoles: { "maintenance-chore": [opus, sol], "pipeline-fix": [sol], "refactor-run": [opus, sol] } });
|
|
143
|
+
expect(SandboxSettingsSchema.parse(converted.value).modelRoles[`refactor-run`]).toEqual([opus, sol]);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
test(`leaves a list the owner set, even an emptied one, and a file with no chores list`, () => {
|
|
147
|
+
for (const modelRoles of [
|
|
148
|
+
{ "maintenance-chore": [opus], "refactor-run": [sol] },
|
|
149
|
+
{ "maintenance-chore": [opus], "refactor-run": [] },
|
|
150
|
+
{ "pipeline-fix": [opus] },
|
|
151
|
+
{ "maintenance-chore": [] },
|
|
152
|
+
]) {
|
|
153
|
+
expect(convertDocument(SETTINGS_HISTORY, `object`, { modelRoles }, true).changes).toEqual([]);
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
});
|
package/src/schemas/settings.ts
CHANGED
|
@@ -5,7 +5,7 @@ import { z } from "zod";
|
|
|
5
5
|
import { CommandJudgeModeSchema, ProjectInstallModeSchema } from "../policy/safety-policy.js";
|
|
6
6
|
import { ModelRoleSchema } from "../models/model-roles.js";
|
|
7
7
|
import { AdmissionPolicySchema, AdmissionRuleSchema, ModelPinSchema } from "./agent.js";
|
|
8
|
-
import { LimitPolicySchema, RetryPolicySchema } from "./turn-break.js";
|
|
8
|
+
import { LimitPolicySchema, RetryPolicySchema, RoomPolicySchema } from "./turn-break.js";
|
|
9
9
|
import { ZoneSchema } from "../time/zone.js";
|
|
10
10
|
// Which prompt base the agent runs before this turn composes anything on top: Intentic's own (default), Claude Code's
|
|
11
11
|
// preset, or the owner's text. Declared out here since both the daemon and the browser branch on it.
|
|
@@ -452,6 +452,11 @@ export const SandboxSettingsSchema = z.object({
|
|
|
452
452
|
stopPolicy: RetryPolicySchema.default("wait").describe(
|
|
453
453
|
"What happens to a turn that stopped short with nothing to repair — a hung runtime, a crashed harness. `wait` holds it for a press. `retry` re-runs the held turn on a short ladder, standing down after three tries that got nowhere rather than looping forever.",
|
|
454
454
|
),
|
|
455
|
+
// The one ending that defaults to going by itself: nothing ran, so sending it once there is room spends nothing twice,
|
|
456
|
+
// and it is what the person meant when they pressed Send.
|
|
457
|
+
memoryPolicy: RoomPolicySchema.default("resend").describe(
|
|
458
|
+
"What happens to a message the sandbox held because it was short of memory: nothing ran, so nothing was spent. `resend` sends it by itself once memory has stayed free for a while, one held conversation at a time. `wait` holds it until somebody presses Send anyway. The sandbox-wide default; any one conversation can say otherwise.",
|
|
459
|
+
),
|
|
455
460
|
limitMoveCarryUnder: z
|
|
456
461
|
.number()
|
|
457
462
|
.int()
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
// Dictation on this sandbox: which local model hears a language, how far it is from ready, and the live wire the
|
|
4
|
+
// composer streams a spoken phrase over. The daemon's engine is src/speech; the composer's half is
|
|
5
|
+
// _editor/web/src/features/chat/composer/voice.
|
|
6
|
+
|
|
7
|
+
// The models a sandbox can hear with, by what they are for:
|
|
8
|
+
// - `parakeet` (NVIDIA Parakeet TDT 0.6B v3): 25 European languages, punctuated, ~30x faster than real time on two
|
|
9
|
+
// cores. Baked into the standard image, and the default for any language it covers.
|
|
10
|
+
// - `whisper` (OpenAI Whisper large-v3-turbo): the other ~75 languages, slower, fetched the first time one is asked for.
|
|
11
|
+
export const SpeechEngineSchema = z.enum(["parakeet", "whisper"]);
|
|
12
|
+
export type SpeechEngine = z.infer<typeof SpeechEngineSchema>;
|
|
13
|
+
|
|
14
|
+
// The 25 Parakeet hears, as ISO 639-1 primary subtags: it detects which on its own, so a browser in English and a
|
|
15
|
+
// speaker in Polish still land on it.
|
|
16
|
+
export const PARAKEET_LANGUAGES: ReadonlySet<string> = new Set([
|
|
17
|
+
"bg",
|
|
18
|
+
"cs",
|
|
19
|
+
"da",
|
|
20
|
+
"de",
|
|
21
|
+
"el",
|
|
22
|
+
"en",
|
|
23
|
+
"es",
|
|
24
|
+
"et",
|
|
25
|
+
"fi",
|
|
26
|
+
"fr",
|
|
27
|
+
"hr",
|
|
28
|
+
"hu",
|
|
29
|
+
"it",
|
|
30
|
+
"lt",
|
|
31
|
+
"lv",
|
|
32
|
+
"mt",
|
|
33
|
+
"nl",
|
|
34
|
+
"pl",
|
|
35
|
+
"pt",
|
|
36
|
+
"ro",
|
|
37
|
+
"ru",
|
|
38
|
+
"sk",
|
|
39
|
+
"sl",
|
|
40
|
+
"sv",
|
|
41
|
+
"uk",
|
|
42
|
+
]);
|
|
43
|
+
|
|
44
|
+
// A locale's primary subtag, lowercased, or undefined for anything that is not one (`""`, `*`, `auto`).
|
|
45
|
+
export const speechLanguage = (locale: string | undefined): string | undefined => {
|
|
46
|
+
const primary = (locale ?? "").trim().toLowerCase().split(/[-_]/u)[0] ?? "";
|
|
47
|
+
return /^[a-z]{2,3}$/u.test(primary) && primary !== "auto" ? primary : undefined;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
// Which model hears a locale: Parakeet for its own languages and for anything unreadable, Whisper for the rest.
|
|
51
|
+
export const speechEngineFor = (locale: string | undefined): SpeechEngine => {
|
|
52
|
+
const language = speechLanguage(locale);
|
|
53
|
+
return language === undefined || PARAKEET_LANGUAGES.has(language) ? "parakeet" : "whisper";
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export const SpeechStatusSchema = z.object({
|
|
57
|
+
provisioned: z.boolean().describe("This sandbox can run speech recognition at all."),
|
|
58
|
+
model: z
|
|
59
|
+
.enum(["absent", "downloading", "ready", "failed"])
|
|
60
|
+
.describe("Whether the model for the asked language is on disk: baked into the image, fetched, being fetched, or its fetch failed."),
|
|
61
|
+
engine: SpeechEngineSchema.optional().describe("Which model hears the asked language."),
|
|
62
|
+
loaded: z.boolean().optional().describe("The model is in memory, so the next phrase is heard without a load first."),
|
|
63
|
+
received: z.number().optional().describe("Bytes of the model fetched so far, while it downloads."),
|
|
64
|
+
total: z.number().optional().describe("The model's whole size in bytes, while it downloads."),
|
|
65
|
+
error: z.string().optional().describe("Why the last fetch failed, when it did."),
|
|
66
|
+
});
|
|
67
|
+
export type SpeechStatus = z.infer<typeof SpeechStatusSchema>;
|
|
68
|
+
|
|
69
|
+
// GET /speech/stream, a WebSocket: the composer cuts the microphone into phrases itself (its silence segmenter) and
|
|
70
|
+
// streams each one as it is spoken. A text frame `begin` opens phrase `id`; binary frames that follow are its audio,
|
|
71
|
+
// 16 kHz mono s16le; `end` closes it, keeping its first `keep` samples (the trailing pause trimmed off); `drop`
|
|
72
|
+
// abandons it. The daemon answers with `partial` text while the phrase is still being spoken, one `final` per ended
|
|
73
|
+
// phrase, in order, and `status` whenever the model's readiness moves. The language is the socket's `lang` query.
|
|
74
|
+
export const SpeechClientMessageSchema = z.discriminatedUnion("type", [
|
|
75
|
+
z.object({ type: z.literal("begin"), id: z.number() }),
|
|
76
|
+
z.object({ type: z.literal("end"), id: z.number(), keep: z.number() }),
|
|
77
|
+
z.object({ type: z.literal("drop"), id: z.number() }),
|
|
78
|
+
]);
|
|
79
|
+
export type SpeechClientMessage = z.infer<typeof SpeechClientMessageSchema>;
|
|
80
|
+
|
|
81
|
+
export const SpeechServerMessageSchema = z.discriminatedUnion("type", [
|
|
82
|
+
z.object({ type: z.literal("status"), status: SpeechStatusSchema }),
|
|
83
|
+
z.object({ type: z.literal("partial"), id: z.number(), text: z.string() }),
|
|
84
|
+
z.object({
|
|
85
|
+
type: z.literal("final"),
|
|
86
|
+
id: z.number(),
|
|
87
|
+
text: z.string().describe("Empty when the phrase held no words."),
|
|
88
|
+
decodeMs: z.number().describe("How long hearing it took once its audio was in."),
|
|
89
|
+
}),
|
|
90
|
+
z.object({ type: z.literal("error"), id: z.number().optional(), message: z.string() }),
|
|
91
|
+
]);
|
|
92
|
+
export type SpeechServerMessage = z.infer<typeof SpeechServerMessageSchema>;
|
|
93
|
+
|
|
94
|
+
// The longest phrase either door takes: a minute at 16 kHz. The composer's segmenter cuts a monologue at the same
|
|
95
|
+
// length, so nothing it sends is ever refused.
|
|
96
|
+
export const MAX_SPEECH_SAMPLES = 60 * 16_000;
|
package/src/schemas/terminal.ts
CHANGED
|
@@ -140,6 +140,10 @@ export const BrowsersListSchema = z.object({
|
|
|
140
140
|
});
|
|
141
141
|
export type BrowsersList = z.infer<typeof BrowsersListSchema>;
|
|
142
142
|
export const BrowserNameParamSchema = z.object({ name: z.string().describe("Which browser.") });
|
|
143
|
+
export const BrowserPageParamSchema = z.object({
|
|
144
|
+
name: z.string().describe("Which browser."),
|
|
145
|
+
pageId: z.string().describe("Which of its tabs: a page id from the roster."),
|
|
146
|
+
});
|
|
143
147
|
export const OpenBrowserInputSchema = z.object({
|
|
144
148
|
url: z.string().optional().describe("Where the new tab goes. Absent opens a blank tab, the way a browser's own new tab does."),
|
|
145
149
|
});
|
|
@@ -7,20 +7,23 @@
|
|
|
7
7
|
|
|
8
8
|
import { z } from "zod";
|
|
9
9
|
|
|
10
|
-
// The
|
|
11
|
-
// `TurnEndingSchema.reason` spells them, so the ending a surface reads and the
|
|
12
|
-
//
|
|
13
|
-
|
|
10
|
+
// The walls a turn can stop at that take a standing answer. The first three end a turn that leaves finished work behind
|
|
11
|
+
// a live session, spelled exactly as `TurnEndingSchema.reason` spells them, so the ending a surface reads and the
|
|
12
|
+
// question it asks are one word. `memory` is the door: the sandbox too short of memory to start the turn at all, which
|
|
13
|
+
// holds the message (or the turn the sandbox kept) until a press or room comes back. A restart is handled apart
|
|
14
|
+
// (SandboxSettings.autoResumeOnRestart): nobody is watching a restart, so it has no in-chat question.
|
|
15
|
+
export const TurnBreakSchema = z.enum(["limit", "outage", "stopped", "memory"]);
|
|
14
16
|
export type TurnBreak = z.infer<typeof TurnBreakSchema>;
|
|
15
17
|
|
|
16
|
-
// Every ending that leaves a held turn behind:
|
|
17
|
-
// partway. That one has no standing answer and never re-runs by itself: the
|
|
18
|
-
// model again and another one (2026-09-30, the owner's call over an automatic
|
|
19
|
-
|
|
18
|
+
// Every ending that leaves a held turn behind: a spent allowance, an outage, a turn that stopped short, plus the
|
|
19
|
+
// provider's safety classifier stopping a turn partway. That one has no standing answer and never re-runs by itself: the
|
|
20
|
+
// person picks, each time, between the same model again and another one (2026-09-30, the owner's call over an automatic
|
|
21
|
+
// model switch). A low-memory hold is no ending: nothing ran, so it is never one of these.
|
|
22
|
+
export const HeldEndingSchema = z.enum(["limit", "outage", "stopped", "flagged"]);
|
|
20
23
|
export type HeldEnding = z.infer<typeof HeldEndingSchema>;
|
|
21
24
|
|
|
22
25
|
/** Whether this ending takes a standing answer (turn-break policy), which `flagged` never does. */
|
|
23
|
-
export const isTurnBreak = (ending: HeldEnding): ending is
|
|
26
|
+
export const isTurnBreak = (ending: HeldEnding): ending is Exclude<HeldEnding, "flagged"> => ending !== "flagged";
|
|
24
27
|
|
|
25
28
|
// `move` implies `resend`: an account with room is tried at once, and the reset appointment stands as its fallback.
|
|
26
29
|
// There is deliberately no "move, else hold" — a reader willing to spend a second account is willing to wait.
|
|
@@ -31,6 +34,11 @@ export type LimitPolicy = z.infer<typeof LimitPolicySchema>;
|
|
|
31
34
|
export const RetryPolicySchema = z.enum(["wait", "retry"]);
|
|
32
35
|
export type RetryPolicy = z.infer<typeof RetryPolicySchema>;
|
|
33
36
|
|
|
37
|
+
// The memory wall's two answers: hold for a press, or send once the sandbox has room again. Nothing ran, so sending it
|
|
38
|
+
// then spends nothing twice.
|
|
39
|
+
export const RoomPolicySchema = z.enum(["wait", "resend"]);
|
|
40
|
+
export type RoomPolicy = z.infer<typeof RoomPolicySchema>;
|
|
41
|
+
|
|
34
42
|
// One answer, whichever ending asked. Read against the ending: only `limit` can be `move`.
|
|
35
43
|
export const TurnBreakPolicySchema = z.enum(["wait", "resend", "move", "retry"]);
|
|
36
44
|
export type TurnBreakPolicy = z.infer<typeof TurnBreakPolicySchema>;
|
|
@@ -40,6 +48,7 @@ export const TURN_BREAK_POLICIES: Readonly<Record<TurnBreak, readonly TurnBreakP
|
|
|
40
48
|
limit: ["wait", "resend", "move"],
|
|
41
49
|
outage: ["wait", "retry"],
|
|
42
50
|
stopped: ["wait", "retry"],
|
|
51
|
+
memory: ["wait", "resend"],
|
|
43
52
|
};
|
|
44
53
|
|
|
45
54
|
/** Whether this ending may be answered this way; the one gate every writer goes through. */
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
// A monorepo's architecture as its files state it: the packages pnpm-workspace.yaml names, which siblings each declares
|
|
4
|
+
// and what its own files do with them, and, inside one package, how its modules import each other. Facts only: what to
|
|
5
|
+
// draw, and what counts as a finding, is the reader's.
|
|
6
|
+
|
|
7
|
+
// A component of the repository's map, docs/architecture/repo.json, which the documentation extension writes.
|
|
8
|
+
export const WorkspaceComponentSchema = z.object({
|
|
9
|
+
id: z.string().describe("The component's id."),
|
|
10
|
+
name: z.string().describe("What the map calls it."),
|
|
11
|
+
accent: z.string().optional().describe('The map\'s colour slot for it: "1" to "5", or "neutral".'),
|
|
12
|
+
});
|
|
13
|
+
export type WorkspaceComponent = z.infer<typeof WorkspaceComponentSchema>;
|
|
14
|
+
|
|
15
|
+
// A pnpm-workspace package, discovered from pnpm-workspace.yaml's globs. group is the top-level dir segment; component
|
|
16
|
+
// is the repo.json component listing its dir, when the repository has a map.
|
|
17
|
+
export const WorkspacePackageSchema = z.object({
|
|
18
|
+
name: z.string().describe("The name the package declares."),
|
|
19
|
+
dir: z.string().describe("Where it lives, relative to the repository."),
|
|
20
|
+
group: z.string().describe("The top-level folder it sits under, which is what a diagram colours by."),
|
|
21
|
+
component: z.string().optional().describe("The component of docs/architecture/repo.json that lists it, by id. Absent without a map."),
|
|
22
|
+
});
|
|
23
|
+
export type WorkspacePackage = z.infer<typeof WorkspacePackageSchema>;
|
|
24
|
+
export const WorkspaceDepTypeSchema = z.enum(["prod", "dev", "peer"]);
|
|
25
|
+
export type WorkspaceDepType = z.infer<typeof WorkspaceDepTypeSchema>;
|
|
26
|
+
|
|
27
|
+
// What the dependent's own files do with a sibling it declares, read from them: the strongest use found wins.
|
|
28
|
+
export const WorkspaceDepUsageSchema = z
|
|
29
|
+
.enum(["code", "types", "tooling", "reference", "none"])
|
|
30
|
+
.describe(
|
|
31
|
+
"What the dependent's own files do with it: `code`, a shipped file imports a value from it; `types`, shipped files import only its types; `tooling`, only its tests, scripts and config files use it; `reference`, a shipped file names it without importing it (a Dockerfile copying its build, a manifest listing its files); `none`, nothing in the dependent names it.",
|
|
32
|
+
);
|
|
33
|
+
export type WorkspaceDepUsage = z.infer<typeof WorkspaceDepUsageSchema>;
|
|
34
|
+
|
|
35
|
+
// A workspace-internal dependency edge: from depends on to, typed by which dependency block declared it.
|
|
36
|
+
export const WorkspaceDepEdgeSchema = z.object({
|
|
37
|
+
from: z.string().describe("The package that depends."),
|
|
38
|
+
to: z.string().describe("The package it depends on."),
|
|
39
|
+
type: WorkspaceDepTypeSchema.describe("Which kind of dependency declared it."),
|
|
40
|
+
usage: WorkspaceDepUsageSchema.optional(),
|
|
41
|
+
});
|
|
42
|
+
export type WorkspaceDepEdge = z.infer<typeof WorkspaceDepEdgeSchema>;
|
|
43
|
+
export const WorkspaceGraphSchema = z.object({
|
|
44
|
+
packages: z.array(WorkspacePackageSchema).describe("Every package in the repository."),
|
|
45
|
+
edges: z.array(WorkspaceDepEdgeSchema).describe("Which of them use which. Pure data: how to lay it out is yours to decide."),
|
|
46
|
+
components: z
|
|
47
|
+
.array(WorkspaceComponentSchema)
|
|
48
|
+
.optional()
|
|
49
|
+
.describe("The components of docs/architecture/repo.json, in the map's order. Absent when the repository has no map."),
|
|
50
|
+
});
|
|
51
|
+
export type WorkspaceGraph = z.infer<typeof WorkspaceGraphSchema>;
|
|
52
|
+
|
|
53
|
+
// layers.json beside a package's package.json: the layers its modules sit in, lowest first. A module may import its own
|
|
54
|
+
// layer and any below it; an import reaching higher is a finding, and so is a cycle between units of one layer.
|
|
55
|
+
export const PackageLayerSchema = z.object({
|
|
56
|
+
name: z.string().min(1).describe("The layer's name."),
|
|
57
|
+
about: z.string().optional().describe("What sits in it, in a sentence."),
|
|
58
|
+
units: z
|
|
59
|
+
.array(z.string().min(1))
|
|
60
|
+
.describe(
|
|
61
|
+
"Path prefixes under the root it holds: a directory, a subdirectory, or a module named without its extension. A file belongs to the layer whose unit matches it longest.",
|
|
62
|
+
),
|
|
63
|
+
});
|
|
64
|
+
export type PackageLayer = z.infer<typeof PackageLayerSchema>;
|
|
65
|
+
export const PackageLayersFileSchema = z.object({
|
|
66
|
+
root: z.string().optional().describe('The directory the units are relative to, from the package. Default "src".'),
|
|
67
|
+
shelves: z.array(z.string()).optional().describe("Top-level directories whose every subdirectory is a unit of its own, such as one per feature."),
|
|
68
|
+
surface: z
|
|
69
|
+
.array(z.string())
|
|
70
|
+
.optional()
|
|
71
|
+
.describe('File-name patterns, `*` a wildcard, of modules that sit above every layer, such as "*.routes.ts". Nothing below may import one.'),
|
|
72
|
+
layers: z.array(PackageLayerSchema).min(1).describe("The layers, lowest first."),
|
|
73
|
+
});
|
|
74
|
+
export type PackageLayersFile = z.infer<typeof PackageLayersFileSchema>;
|
|
75
|
+
|
|
76
|
+
export const PackageModulesParamSchema = z.object({
|
|
77
|
+
repo: z.string().describe("Which repository."),
|
|
78
|
+
package: z.string().min(1).describe("Which of its workspace packages, by the name it declares."),
|
|
79
|
+
});
|
|
80
|
+
export const PackageUnitSchema = z.object({
|
|
81
|
+
id: z
|
|
82
|
+
.string()
|
|
83
|
+
.describe(
|
|
84
|
+
'The unit: a directory under the root, a shelf\'s subdirectory ("features/chat"), a unit layers.json names, "(surface)" for every surface module, or "(root)" for the modules directly in the root, which no rule judges.',
|
|
85
|
+
),
|
|
86
|
+
modules: z.number().int().nonnegative().describe("How many source modules it holds."),
|
|
87
|
+
layer: z
|
|
88
|
+
.number()
|
|
89
|
+
.int()
|
|
90
|
+
.nonnegative()
|
|
91
|
+
.optional()
|
|
92
|
+
.describe("The declared layer it sits in, by index, lowest 0. Absent when the package declares no layers, or none places it."),
|
|
93
|
+
surface: z.literal(true).optional().describe("The surface modules' unit, above every layer."),
|
|
94
|
+
});
|
|
95
|
+
export type PackageUnit = z.infer<typeof PackageUnitSchema>;
|
|
96
|
+
export const PackageUnitEdgeSchema = z.object({
|
|
97
|
+
from: z.string().describe("The importing unit."),
|
|
98
|
+
to: z.string().describe("The unit it imports."),
|
|
99
|
+
imports: z.number().int().positive().describe("How many value imports make the edge."),
|
|
100
|
+
sites: z.array(z.string()).describe("Up to five of them, as `path:line` relative to the root."),
|
|
101
|
+
});
|
|
102
|
+
export type PackageUnitEdge = z.infer<typeof PackageUnitEdgeSchema>;
|
|
103
|
+
export const PackageModulesSchema = z.object({
|
|
104
|
+
package: z.string().describe("The package."),
|
|
105
|
+
root: z.string().describe("The directory read, relative to the repository."),
|
|
106
|
+
layers: z
|
|
107
|
+
.array(PackageLayerSchema.pick({ name: true, about: true }))
|
|
108
|
+
.optional()
|
|
109
|
+
.describe("Its declared layers, lowest first. Absent when it has no layers.json."),
|
|
110
|
+
invalid: z.string().optional().describe("Why its layers.json could not be read, when it has one that does not parse."),
|
|
111
|
+
units: z.array(PackageUnitSchema).describe("Every unit with a module in it."),
|
|
112
|
+
edges: z
|
|
113
|
+
.array(PackageUnitEdgeSchema)
|
|
114
|
+
.describe("Value imports between units: type-only imports erase and tests import their subject, so neither counts."),
|
|
115
|
+
});
|
|
116
|
+
export type PackageModules = z.infer<typeof PackageModulesSchema>;
|
|
@@ -91,28 +91,6 @@ export const RepoAppSchema = z.object({
|
|
|
91
91
|
export type RepoApp = z.infer<typeof RepoAppSchema>;
|
|
92
92
|
export const AppsListSchema = z.object({ apps: z.array(RepoAppSchema).describe("The apps in this repository.") });
|
|
93
93
|
export type AppsList = z.infer<typeof AppsListSchema>;
|
|
94
|
-
// A pnpm-workspace package, discovered from pnpm-workspace.yaml's globs. group is the top-level dir segment, the
|
|
95
|
-
// dependencies view's coloring axis.
|
|
96
|
-
export const WorkspacePackageSchema = z.object({
|
|
97
|
-
name: z.string().describe("The name the package declares."),
|
|
98
|
-
dir: z.string().describe("Where it lives, relative to the repository."),
|
|
99
|
-
group: z.string().describe("The top-level folder it sits under, which is what a diagram colours by."),
|
|
100
|
-
});
|
|
101
|
-
export type WorkspacePackage = z.infer<typeof WorkspacePackageSchema>;
|
|
102
|
-
export const WorkspaceDepTypeSchema = z.enum(["prod", "dev", "peer"]);
|
|
103
|
-
export type WorkspaceDepType = z.infer<typeof WorkspaceDepTypeSchema>;
|
|
104
|
-
// A workspace-internal dependency edge: from depends on to, typed by which dependency block declared it.
|
|
105
|
-
export const WorkspaceDepEdgeSchema = z.object({
|
|
106
|
-
from: z.string().describe("The package that depends."),
|
|
107
|
-
to: z.string().describe("The package it depends on."),
|
|
108
|
-
type: WorkspaceDepTypeSchema.describe("Which kind of dependency declared it."),
|
|
109
|
-
});
|
|
110
|
-
export type WorkspaceDepEdge = z.infer<typeof WorkspaceDepEdgeSchema>;
|
|
111
|
-
export const WorkspaceGraphSchema = z.object({
|
|
112
|
-
packages: z.array(WorkspacePackageSchema).describe("Every package in the repository."),
|
|
113
|
-
edges: z.array(WorkspaceDepEdgeSchema).describe("Which of them use which. Pure data: how to lay it out is yours to decide."),
|
|
114
|
-
});
|
|
115
|
-
export type WorkspaceGraph = z.infer<typeof WorkspaceGraphSchema>;
|
|
116
94
|
// Path params for the per-repo apps routes: repo names the monorepo, validated like PanelRepoParam.
|
|
117
95
|
export const RepoAppsParamSchema = z.object({ repo: z.string().describe("Which repository.") });
|
|
118
96
|
export const AppParamSchema = z.object({
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
isLockedWorkspacePath,
|
|
6
6
|
isReportedManifest,
|
|
7
7
|
isReviewableLockedPath,
|
|
8
|
+
isUnversionedStatePath,
|
|
8
9
|
LOCKED_STATE_ENTRIES,
|
|
9
10
|
lockedWorkspaceEntry,
|
|
10
11
|
PLAN_DOCUMENTS_DIR,
|
|
@@ -29,7 +30,13 @@ const AUTOMATIONS: readonly FileContribution[] = [
|
|
|
29
30
|
|
|
30
31
|
describe(`staleQueryKeys`, () => {
|
|
31
32
|
it(`maps a manifest write to the queries it makes stale`, () => {
|
|
32
|
-
expect(staleQueryKeys([`.intentic/config/capabilities.json`], [])).toEqual([
|
|
33
|
+
expect(staleQueryKeys([`.intentic/config/capabilities.json`], [])).toEqual([
|
|
34
|
+
`capabilities`,
|
|
35
|
+
`extensions`,
|
|
36
|
+
`environment`,
|
|
37
|
+
`panels`,
|
|
38
|
+
`manifests`,
|
|
39
|
+
]);
|
|
33
40
|
});
|
|
34
41
|
|
|
35
42
|
it(`refreshes the unreadable-manifest notice for the four files a person hand-edits`, () => {
|
|
@@ -299,6 +306,40 @@ describe(`isReviewableLockedPath`, () => {
|
|
|
299
306
|
});
|
|
300
307
|
});
|
|
301
308
|
|
|
309
|
+
// What lets a git-reading reaction skip a batch: the daemon's own churn under `.intentic` must not rescan every repo,
|
|
310
|
+
// while a tracked config file still has to.
|
|
311
|
+
describe(`isUnversionedStatePath`, () => {
|
|
312
|
+
it(`holds the machine state the daemon rewrites constantly`, () => {
|
|
313
|
+
expect(isUnversionedStatePath(`.intentic/local/privacy-log.json`)).toBe(true);
|
|
314
|
+
expect(isUnversionedStatePath(`.intentic/local/.privacy-log.json.55.3275.tmp`)).toBe(true);
|
|
315
|
+
expect(isUnversionedStatePath(`.intentic/records/extension-usage.json`)).toBe(true);
|
|
316
|
+
expect(isUnversionedStatePath(`.intentic/records/artifacts/browser/page.png`)).toBe(true);
|
|
317
|
+
// An unversioned file beside versioned ones in config is still invisible to git.
|
|
318
|
+
expect(isUnversionedStatePath(`.intentic/config/docs/index.json`)).toBe(true);
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
it(`lets every versioned entry through, files and directories alike`, () => {
|
|
322
|
+
for (const path of VERSIONED_STATE_PATHS) {
|
|
323
|
+
const inside = path.endsWith(`/`) ? `${path}nested/file.json` : path;
|
|
324
|
+
expect([inside, isUnversionedStatePath(inside)]).toEqual([inside, false]);
|
|
325
|
+
}
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
it(`never claims a path outside the workspace's own state dir`, () => {
|
|
329
|
+
expect(isUnversionedStatePath(`intentic/_sandbox/sandbox/src/main.ts`)).toBe(false);
|
|
330
|
+
expect(isUnversionedStatePath(`README.md`)).toBe(false);
|
|
331
|
+
// A repo's nested state dir is its project's content, which git does track.
|
|
332
|
+
expect(isUnversionedStatePath(`myrepo/.intentic/local/privacy-log.json`)).toBe(false);
|
|
333
|
+
expect(isUnversionedStatePath(`.intentic`)).toBe(false);
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
it(`reads a platform path and a dot-relative one the same as a posix one`, () => {
|
|
337
|
+
expect(isUnversionedStatePath(`.intentic\\local\\privacy-log.json`)).toBe(true);
|
|
338
|
+
expect(isUnversionedStatePath(`./.intentic/local/privacy-log.json`)).toBe(true);
|
|
339
|
+
expect(isUnversionedStatePath(`./.intentic/config/capabilities.json`)).toBe(false);
|
|
340
|
+
});
|
|
341
|
+
});
|
|
342
|
+
|
|
302
343
|
describe(`VERSIONED_STATE_PATHS`, () => {
|
|
303
344
|
// versioned exempts an entry from the `.intentic` exclusion; a mismarked credential can't be unmarked once
|
|
304
345
|
// committed.
|
|
@@ -397,7 +397,7 @@ const STATE_FILES = [
|
|
|
397
397
|
{
|
|
398
398
|
path: ".intentic/local/cache/",
|
|
399
399
|
invalidates: [],
|
|
400
|
-
why: "Rebuildable indexes and caches, the iq index and its vector sidecar, the
|
|
400
|
+
why: "Rebuildable indexes and caches, the iq index and its vector sidecar, the speech models an image does not bake, fileq's derived/ markdown shadows of binary files; ignored by the watcher and recreated from carried workspace content.",
|
|
401
401
|
portability: "derived",
|
|
402
402
|
},
|
|
403
403
|
// Extension runtime scratch, one directory per extension via `extensionStateDir` below (the only way an extension
|
|
@@ -769,6 +769,17 @@ export const isReviewableLockedPath = (relPath: string): boolean => {
|
|
|
769
769
|
return isLockedWorkspacePath(rel) && VERSIONED_STATE_PATHS.some((path) => (path.endsWith("/") ? rel.startsWith(path) : rel === path));
|
|
770
770
|
};
|
|
771
771
|
|
|
772
|
+
// Machine state the root repo never tracks: everything under the workspace's own `.intentic/` but the `versioned`
|
|
773
|
+
// entries history.ts carves back out of the wholesale exclusion. A write here moves no git status, no landing and no
|
|
774
|
+
// review, so a reaction that exists to re-read git can pass over a batch made only of these. The daemon rewrites
|
|
775
|
+
// some of them every few seconds under load (the privacy ledger, extension usage, browser screenshots), and each
|
|
776
|
+
// one used to rescan every repo. Root-level only, like the exclusion it mirrors: a repo's own nested `.intentic` is
|
|
777
|
+
// its project's content. Accepts either slash.
|
|
778
|
+
export const isUnversionedStatePath = (relPath: string): boolean => {
|
|
779
|
+
const rel = relPath.replaceAll("\\", "/").replace(/^\.\//, "");
|
|
780
|
+
return rel.startsWith(`${STATE_DIR}/`) && !VERSIONED_STATE_PATHS.some((path) => (path.endsWith("/") ? rel.startsWith(path) : rel === path));
|
|
781
|
+
};
|
|
782
|
+
|
|
772
783
|
// Every path this table declares, as a type; the daemon joins paths only through `statePath`
|
|
773
784
|
// (workspace/state-paths.ts), which accepts one of these and nothing else, so renaming a store's file is a compile
|
|
774
785
|
// error everywhere it's named rather than a view that silently stops refreshing.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { TranscriptRow, TranscriptTool } from "../events/transcript.js";
|
|
2
|
+
import { fitNestedTool, fitPageRow, PAGE_TEXT_CAP } from "./transcript-page.js";
|
|
3
|
+
|
|
4
|
+
// What a page carries of a row, which the daemon serves and the editor mirrors: the two have to agree to the byte.
|
|
5
|
+
|
|
6
|
+
const long = `x`.repeat(PAGE_TEXT_CAP + 500);
|
|
7
|
+
const call = (id: string, extra: Partial<TranscriptTool> = {}): TranscriptTool => ({
|
|
8
|
+
id,
|
|
9
|
+
name: `Bash`,
|
|
10
|
+
category: `execute`,
|
|
11
|
+
status: `completed`,
|
|
12
|
+
...extra,
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
describe(`fitPageRow`, () => {
|
|
16
|
+
it(`hands back a row with no calls as it is`, () => {
|
|
17
|
+
const row: TranscriptRow = { role: `assistant`, text: `done` };
|
|
18
|
+
expect(fitPageRow(row)).toBe(row);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it(`keeps each output's start and counts a delegation's calls instead of carrying them`, () => {
|
|
22
|
+
const row: TranscriptRow = {
|
|
23
|
+
role: `assistant`,
|
|
24
|
+
text: ``,
|
|
25
|
+
tools: [call(`a`, { content: [{ type: `text`, text: long }] }), call(`b`, { name: `Agent`, children: [call(`c`), call(`d`)] })],
|
|
26
|
+
};
|
|
27
|
+
const fitted = fitPageRow(row);
|
|
28
|
+
expect(fitted.tools?.[0]?.content).toEqual([{ type: `text`, text: long.slice(0, PAGE_TEXT_CAP) }]);
|
|
29
|
+
expect(fitted.tools?.[1]?.children).toBeUndefined();
|
|
30
|
+
expect(fitted.tools?.[1]?.nested).toBe(2);
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
describe(`fitNestedTool`, () => {
|
|
35
|
+
it(`keeps every call under it, each output fitted at every depth`, () => {
|
|
36
|
+
const fitted = fitNestedTool(call(`a`, { children: [call(`b`, { content: [{ type: `text`, text: long }] })] }));
|
|
37
|
+
expect(fitted.children?.[0]?.content).toEqual([{ type: `text`, text: long.slice(0, PAGE_TEXT_CAP) }]);
|
|
38
|
+
});
|
|
39
|
+
});
|