@intentic/sandbox-contract 1.348.0 → 1.350.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/contracts/agents.contract.d.ts +493 -0
- package/dist/contracts/agents.contract.d.ts.map +1 -1
- package/dist/contracts/agents.contract.js +19 -1
- package/dist/contracts/agents.contract.js.map +1 -1
- package/dist/contracts/device.contract.d.ts +7 -0
- package/dist/contracts/device.contract.d.ts.map +1 -1
- package/dist/contracts/system.contract.d.ts +13 -0
- package/dist/contracts/system.contract.d.ts.map +1 -1
- package/dist/events/system-events.d.ts +4 -0
- package/dist/events/system-events.d.ts.map +1 -1
- package/dist/ids/conversation-ids.d.ts +3 -0
- package/dist/ids/conversation-ids.d.ts.map +1 -1
- package/dist/ids/conversation-ids.js +4 -0
- package/dist/ids/conversation-ids.js.map +1 -1
- package/dist/index.d.ts +506 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/protocol/raw/raw-json-routes.d.ts +7 -0
- package/dist/protocol/raw/raw-json-routes.d.ts.map +1 -1
- package/dist/schemas/agent-people.d.ts +23 -0
- package/dist/schemas/agent-people.d.ts.map +1 -0
- package/dist/schemas/agent-people.js +22 -0
- package/dist/schemas/agent-people.js.map +1 -0
- package/dist/schemas/agents-list.d.ts +456 -0
- package/dist/schemas/agents-list.d.ts.map +1 -1
- package/dist/schemas/agents-list.js +8 -0
- package/dist/schemas/agents-list.js.map +1 -1
- package/dist/schemas/agents.d.ts +7 -21
- package/dist/schemas/agents.d.ts.map +1 -1
- package/dist/schemas/agents.js +6 -20
- package/dist/schemas/agents.js.map +1 -1
- package/dist/schemas/conversation-links.d.ts +1 -0
- package/dist/schemas/conversation-links.d.ts.map +1 -1
- package/dist/schemas/conversation-links.js +1 -0
- package/dist/schemas/conversation-links.js.map +1 -1
- package/dist/schemas/device-folder.d.ts +8 -0
- package/dist/schemas/device-folder.d.ts.map +1 -1
- package/dist/schemas/device-folder.js +7 -0
- package/dist/schemas/device-folder.js.map +1 -1
- package/dist/schemas/devices.d.ts +37 -1
- package/dist/schemas/devices.d.ts.map +1 -1
- package/dist/schemas/devices.js +4 -2
- package/dist/schemas/devices.js.map +1 -1
- package/dist/state/workspace-state.d.ts +6 -0
- package/dist/state/workspace-state.d.ts.map +1 -1
- package/dist/state/workspace-state.js +2 -6
- package/dist/state/workspace-state.js.map +1 -1
- package/package.json +5 -5
- package/src/contracts/agents.contract.ts +24 -1
- package/src/ids/conversation-ids.test.ts +15 -0
- package/src/ids/conversation-ids.ts +11 -0
- package/src/schemas/agent-people.ts +36 -0
- package/src/schemas/agents-list.ts +16 -0
- package/src/schemas/agents.ts +10 -31
- package/src/schemas/conversation-links.ts +5 -0
- package/src/schemas/device-folder.ts +14 -0
- package/src/schemas/devices.ts +9 -9
- package/src/state/workspace-state.test.ts +2 -0
- package/src/state/workspace-state.ts +5 -6
|
@@ -4,7 +4,7 @@ import { AgentToolChildrenSchema, SessionTranscriptSchema } from "../events/tran
|
|
|
4
4
|
import { AgentTranscriptSchema } from "../events/agent-transcript.js";
|
|
5
5
|
import { AgentAssignSchema, AgentAutoLandSchema, AgentBreakPolicySchema, AgentFileDiffQuerySchema, AgentIdSchema, AgentToolChildrenQuerySchema, AgentTranscriptQuerySchema, AgentIdsSchema, AgentLandSchema, AgentReactSchema, AgentRenameSchema, AgentStopJobSchema, AgentStopWatchingSchema, AgentSubagentQuerySchema, AgentsArchivedSchema, AgentsMovedSchema, AgentsRemovedSchema, AgentSummarySchema, AgentUnsentSchema, LandResultSchema } from "../schemas/agents.js";
|
|
6
6
|
import { AgentSearchQuerySchema, AgentSearchResultSchema } from "../schemas/agent-search.js";
|
|
7
|
-
import { AgentsAttentionSchema, AgentsListSchema } from "../schemas/agents-list.js";
|
|
7
|
+
import { AgentsAttentionSchema, AgentsListSchema, HomeConversationSchema } from "../schemas/agents-list.js";
|
|
8
8
|
import { AgentChangesSchema, AgentConflictsSchema, AgentIgnoredSchema, AgentScratchSchema, AgentHistorySchema } from "../schemas/git/git.js";
|
|
9
9
|
import { FileDiffSchema } from "../schemas/history.js";
|
|
10
10
|
import { OkSchema } from "../schemas/shared.js";
|
|
@@ -43,6 +43,29 @@ export const agentsContract = {
|
|
|
43
43
|
})
|
|
44
44
|
.meta({ floor: "owner", control: "never", panel: false })
|
|
45
45
|
.output(AgentsAttentionSchema),
|
|
46
|
+
// Every member's own, guests' too: the count a rail chip of the master view wears for this sandbox, over the caller's
|
|
47
|
+
// own conversations rather than the fleet, so a member's chip never counts somebody else's waiting work.
|
|
48
|
+
attentionMine: procedure
|
|
49
|
+
.route({
|
|
50
|
+
method: "GET",
|
|
51
|
+
path: "/agents/attention/mine",
|
|
52
|
+
summary: "What needs you, and what is working, of your own conversations",
|
|
53
|
+
description:
|
|
54
|
+
"The same two counts as `attention`, over only the conversations you answer for (and their subagents, through their family's one card). What a sandbox's icon shows in the master view without reading the whole fleet.",
|
|
55
|
+
})
|
|
56
|
+
.meta({ guest: true })
|
|
57
|
+
.output(AgentsAttentionSchema),
|
|
58
|
+
// The id is minted here, from the verified caller, never from a body: a client deriving it could name somebody else's.
|
|
59
|
+
home: procedure
|
|
60
|
+
.route({
|
|
61
|
+
method: "GET",
|
|
62
|
+
path: "/agents/home",
|
|
63
|
+
summary: "Your home conversation in this sandbox",
|
|
64
|
+
description:
|
|
65
|
+
"The one long-running conversation the master view opens on this sandbox, yours alone among its members: its id, its card once it has started, and whether the work it accepts lands by itself. It orchestrates subagents, judges their work with review and test sessions, and merges what it accepts.",
|
|
66
|
+
})
|
|
67
|
+
.meta({ guest: true })
|
|
68
|
+
.output(HomeConversationSchema),
|
|
46
69
|
archived: procedure
|
|
47
70
|
.route({
|
|
48
71
|
method: "GET",
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
2
|
CI_FIX_PREFIX,
|
|
3
|
+
CONVERSATION_ID,
|
|
4
|
+
homeConversationId,
|
|
5
|
+
isHomeConversationId,
|
|
3
6
|
ciFixConversationId,
|
|
4
7
|
fixAttemptId,
|
|
5
8
|
fixAttemptOf,
|
|
@@ -136,3 +139,15 @@ test("an attempt id passes the guard and fits, even for the widest run id a forg
|
|
|
136
139
|
expect(id.length).toBeLessThanOrEqual(64);
|
|
137
140
|
}
|
|
138
141
|
});
|
|
142
|
+
|
|
143
|
+
// One home conversation per member, derived from who they are: the same in every browser and in the daemon, whatever the
|
|
144
|
+
// email's case, a valid conversation id, and never the email itself.
|
|
145
|
+
test("a home conversation id is its member's alone, stable and folded, and says nothing of the address", () => {
|
|
146
|
+
const ada = homeConversationId("ada@example.com");
|
|
147
|
+
expect(homeConversationId(" Ada@Example.COM ")).toBe(ada);
|
|
148
|
+
expect(homeConversationId("bob@example.com")).not.toBe(ada);
|
|
149
|
+
expect(CONVERSATION_ID.test(ada)).toBe(true);
|
|
150
|
+
expect(isHomeConversationId(ada)).toBe(true);
|
|
151
|
+
expect(isHomeConversationId(newConversationId())).toBe(false);
|
|
152
|
+
expect(ada).not.toContain("ada");
|
|
153
|
+
});
|
|
@@ -180,6 +180,17 @@ export const fnvDigest = (text: string): string => {
|
|
|
180
180
|
return (hash >>> 0).toString(36).padStart(7, "0");
|
|
181
181
|
};
|
|
182
182
|
|
|
183
|
+
// A PERSON'S HOME CONVERSATION in one sandbox: the one long-running chat the master view opens on it, one per member,
|
|
184
|
+
// so two people with the same sandbox connected never share it. Derived from the email rather than drawn, so every
|
|
185
|
+
// browser and the daemon name the same conversation without asking, and an email never sits in an id other members read
|
|
186
|
+
// on the board. Two independent FNV digests (64 bits between them): a sandbox's members never come near a collision.
|
|
187
|
+
// The daemon is the one that says which id is whose (`GET /agents/home`); a client computing it is only a shortcut.
|
|
188
|
+
export const HOME_PREFIX = "home-";
|
|
189
|
+
const homeKey = (email: string): string => email.trim().toLowerCase();
|
|
190
|
+
export const homeConversationId = (email: string): string => `${HOME_PREFIX}${fnvDigest(homeKey(email))}${fnvDigest(`home:${homeKey(email)}`)}`;
|
|
191
|
+
// Whether an id is shaped like somebody's home conversation; whose it is takes the email (`homeConversationId`).
|
|
192
|
+
export const isHomeConversationId = (id: string): boolean => id.startsWith(HOME_PREFIX);
|
|
193
|
+
|
|
183
194
|
// A failing main-line check that no conversation still holding the work could take: keyed by the project and the moment
|
|
184
195
|
// it began failing, so every attempt at one streak shares a base and a later streak in the same project starts over.
|
|
185
196
|
// Seconds in base36 keep it short; the workspace root is spelled `workspace`, since its project folder is empty.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
// THE PEOPLE ON A CONVERSATION'S CARD: who answers for it, and who marked it. Their own module so agents.ts, the fleet's
|
|
4
|
+
// summary, stays within its size; agents.ts re-exports every name, so importers keep reading them from there.
|
|
5
|
+
|
|
6
|
+
// One person behind one mark. The instant rides along so the group can be ordered by who was first, which is also the
|
|
7
|
+
// order the names read in.
|
|
8
|
+
export const ReactorSchema = z.object({
|
|
9
|
+
email: z.string().describe("Who it was, as the sandbox verified them."),
|
|
10
|
+
name: z.string().optional().describe("What to call them, when their sign-in carried a name. Absent leaves the address to stand for them."),
|
|
11
|
+
at: z.number().describe("When they marked it, in milliseconds."),
|
|
12
|
+
});
|
|
13
|
+
export type Reactor = z.infer<typeof ReactorSchema>;
|
|
14
|
+
// The member answerable for a conversation: whose questions its cards are, who a reviewer asks about its work. Derived
|
|
15
|
+
// once from the starter, moved only by `agents.assign`; never a program, which is what `startedBy` is for.
|
|
16
|
+
export const SessionOwnerSchema = z.object({
|
|
17
|
+
email: z.string().describe("Who answers for this conversation, as the sandbox verified them."),
|
|
18
|
+
name: z
|
|
19
|
+
.string()
|
|
20
|
+
.optional()
|
|
21
|
+
.describe("What to call them, when the sign-in that made them its owner carried a name. Absent leaves the address to stand for them."),
|
|
22
|
+
since: z.number().describe("When they became its owner, in milliseconds."),
|
|
23
|
+
});
|
|
24
|
+
export type SessionOwner = z.infer<typeof SessionOwnerSchema>;
|
|
25
|
+
// Grouped by emoji on the wire, not as a flat list of presses: every surface draws one chip per emoji, and grouping
|
|
26
|
+
// here is what stops three of them each grouping it differently.
|
|
27
|
+
export const AgentReactionSchema = z.object({
|
|
28
|
+
emoji: z.string().describe("The mark itself, one emoji, carried as the character rather than as a name that would need a table on both sides."),
|
|
29
|
+
by: z
|
|
30
|
+
.array(ReactorSchema)
|
|
31
|
+
.min(1)
|
|
32
|
+
.describe(
|
|
33
|
+
"Everyone wearing this mark, oldest first, each by name. Carried whole rather than as a count, because a chip reading 3 that cannot say whose is a number nobody can answer. Never empty: the last person taking theirs back takes the whole chip with it.",
|
|
34
|
+
),
|
|
35
|
+
});
|
|
36
|
+
export type AgentReaction = z.infer<typeof AgentReactionSchema>;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { AgentSummarySchema } from "./agents.js";
|
|
3
|
+
import { ConversationIdSchema } from "./agent.js";
|
|
3
4
|
import { AutomationApprovalSchema } from "./automations.js";
|
|
4
5
|
|
|
5
6
|
// The fleet list: its own module, since it joins conversations (agents.ts) with held automation wakes (automations.ts),
|
|
@@ -35,3 +36,18 @@ export const AgentsAttentionSchema = z.object({
|
|
|
35
36
|
working: z.number().int().nonnegative().describe("How many conversations are producing something right now."),
|
|
36
37
|
});
|
|
37
38
|
export type AgentsAttention = z.infer<typeof AgentsAttentionSchema>;
|
|
39
|
+
|
|
40
|
+
// The caller's home conversation in this sandbox (ids/conversation-ids.ts homeConversationId), as the master view opens it:
|
|
41
|
+
// the id is the daemon's answer to whose it is, so no client has to derive it, and the card once its first turn opened it.
|
|
42
|
+
export const HomeConversationSchema = z.object({
|
|
43
|
+
conversationId: ConversationIdSchema.describe(
|
|
44
|
+
"Your home conversation's id here. Send to it as to any conversation (`POST /agent`, isolated): its first turn opens it as your home.",
|
|
45
|
+
),
|
|
46
|
+
agent: AgentSummarySchema.optional().describe("Its card, once a first turn has opened it. Absent until then."),
|
|
47
|
+
lands: z
|
|
48
|
+
.enum(["itself", "asks"])
|
|
49
|
+
.describe(
|
|
50
|
+
"What becomes of the work it accepts: `itself`, it lands and commits when its turn ends, because you may merge here; `asks`, it waits for a maintainer, because you may not.",
|
|
51
|
+
),
|
|
52
|
+
});
|
|
53
|
+
export type HomeConversation = z.infer<typeof HomeConversationSchema>;
|
package/src/schemas/agents.ts
CHANGED
|
@@ -10,6 +10,8 @@ import { AgentNeedSchema } from "./needs.js";
|
|
|
10
10
|
import { EMOJI_MAX_LENGTH, isSingleEmoji } from "../text/emoji.js";
|
|
11
11
|
import { ProgramAskSchema } from "../events/requests.js";
|
|
12
12
|
import { ReviewTallySchema, TurnProofSchema } from "./work-assurance.js";
|
|
13
|
+
import { AgentReactionSchema, SessionOwnerSchema } from "./agent-people.js";
|
|
14
|
+
export { AgentReactionSchema, ReactorSchema, SessionOwnerSchema, type AgentReaction, type Reactor, type SessionOwner } from "./agent-people.js";
|
|
13
15
|
// A fleet agent is any conversation with a registry entry, keyed by conversationId. Isolated ones own a git worktree
|
|
14
16
|
// (branch agent/<id>); workspace conversations have none, but both share one status/activity/cost lifecycle.
|
|
15
17
|
|
|
@@ -237,37 +239,6 @@ export type LandedMessageDraft = z.infer<typeof LandedMessageDraftSchema>;
|
|
|
237
239
|
// offer the press that clears a refusal without knowing whose refusal it is.
|
|
238
240
|
export const LandConflictReasonSchema = z.enum(["workspace", "diverged", "binary"]);
|
|
239
241
|
export type LandConflictReason = z.infer<typeof LandConflictReasonSchema>;
|
|
240
|
-
// One person behind one mark. The instant rides along so the group can be ordered by who was first, which is also the
|
|
241
|
-
// order the names read in.
|
|
242
|
-
export const ReactorSchema = z.object({
|
|
243
|
-
email: z.string().describe("Who it was, as the sandbox verified them."),
|
|
244
|
-
name: z.string().optional().describe("What to call them, when their sign-in carried a name. Absent leaves the address to stand for them."),
|
|
245
|
-
at: z.number().describe("When they marked it, in milliseconds."),
|
|
246
|
-
});
|
|
247
|
-
export type Reactor = z.infer<typeof ReactorSchema>;
|
|
248
|
-
// The member answerable for a conversation: whose questions its cards are, who a reviewer asks about its work. Derived
|
|
249
|
-
// once from the starter, moved only by `agents.assign`; never a program, which is what `startedBy` is for.
|
|
250
|
-
export const SessionOwnerSchema = z.object({
|
|
251
|
-
email: z.string().describe("Who answers for this conversation, as the sandbox verified them."),
|
|
252
|
-
name: z
|
|
253
|
-
.string()
|
|
254
|
-
.optional()
|
|
255
|
-
.describe("What to call them, when the sign-in that made them its owner carried a name. Absent leaves the address to stand for them."),
|
|
256
|
-
since: z.number().describe("When they became its owner, in milliseconds."),
|
|
257
|
-
});
|
|
258
|
-
export type SessionOwner = z.infer<typeof SessionOwnerSchema>;
|
|
259
|
-
// Grouped by emoji on the wire, not as a flat list of presses: every surface draws one chip per emoji, and grouping
|
|
260
|
-
// here is what stops three of them each grouping it differently.
|
|
261
|
-
export const AgentReactionSchema = z.object({
|
|
262
|
-
emoji: z.string().describe("The mark itself, one emoji, carried as the character rather than as a name that would need a table on both sides."),
|
|
263
|
-
by: z
|
|
264
|
-
.array(ReactorSchema)
|
|
265
|
-
.min(1)
|
|
266
|
-
.describe(
|
|
267
|
-
"Everyone wearing this mark, oldest first, each by name. Carried whole rather than as a count, because a chip reading 3 that cannot say whose is a number nobody can answer. Never empty: the last person taking theirs back takes the whole chip with it.",
|
|
268
|
-
),
|
|
269
|
-
});
|
|
270
|
-
export type AgentReaction = z.infer<typeof AgentReactionSchema>;
|
|
271
242
|
export const AgentSummarySchema = z.object({
|
|
272
243
|
id: z.string().describe("The conversation id, which is how every other call addresses it."),
|
|
273
244
|
sessionId: z.string().optional().describe("The provider session behind the last turn. It is retired whenever the model or account changes."),
|
|
@@ -412,6 +383,14 @@ export const AgentSummarySchema = z.object({
|
|
|
412
383
|
fresh: FreshSessionSchema.optional().describe(
|
|
413
384
|
"Set on a conversation started on another conversation's work without its messages (a code review, a test pass, a person's own ask): which one, for what, and when. Absent for every other conversation.",
|
|
414
385
|
),
|
|
386
|
+
// Latched on the opening turn (ids/conversation-ids.ts homeConversationId). Rides the summary because it decides who
|
|
387
|
+
// may see the row at all (auth/fleet-scope.ts visibleTo): its owner, and the sandbox's owner.
|
|
388
|
+
home: z
|
|
389
|
+
.literal(true)
|
|
390
|
+
.optional()
|
|
391
|
+
.describe(
|
|
392
|
+
"This is its owner's home conversation in this sandbox: the one long-running chat the master view opens, which orchestrates its own subagents, holds their work until it accepts it, and lands and commits by itself. Only its owner and the sandbox's owner see it. Absent for every other conversation.",
|
|
393
|
+
),
|
|
415
394
|
// Root repo's short base sha; per-repo bases stay daemon-internal.
|
|
416
395
|
base: z.string().optional().describe("The commit its private copy started from, shortened."),
|
|
417
396
|
costUsd: z.number().optional().describe("What it has cost so far, in dollars. A subagent's spend is its own and is not folded in here."),
|
|
@@ -36,6 +36,11 @@ export const FreshSessionSchema = z.object({
|
|
|
36
36
|
of: ConversationIdSchema.describe("The conversation whose work it started from."),
|
|
37
37
|
kind: FreshKindSchema.describe("What it was opened for: a code review, a test pass, or the person's own ask."),
|
|
38
38
|
at: z.number().describe("When it started, in milliseconds. Work its origin changed after this is work it has not seen."),
|
|
39
|
+
// An orchestrator judging its own subagent's work (spawn with `freshOf`): the session is still the origin's child, cut
|
|
40
|
+
// from its work and landing back into it, but it reports to the agent that opened it, which supervises it.
|
|
41
|
+
by: ConversationIdSchema.optional().describe(
|
|
42
|
+
"The agent that opened it, when an agent did rather than a person: an orchestrator judging the work of a subagent it spawned. Its report goes to that agent. Absent for a person's session.",
|
|
43
|
+
),
|
|
39
44
|
});
|
|
40
45
|
export type FreshSession = z.infer<typeof FreshSessionSchema>;
|
|
41
46
|
|
|
@@ -42,6 +42,20 @@ export const clearableOnDevice = (conflict: DeviceConflict): boolean =>
|
|
|
42
42
|
export const DeviceSyncHoldSchema = z.enum(["owner", "gone", "container-missing", "container-trashed", "swap", "unreachable", "low-disk", "transfer"]);
|
|
43
43
|
export type DeviceSyncHold = z.infer<typeof DeviceSyncHoldSchema>;
|
|
44
44
|
|
|
45
|
+
// HOW FAR A FOLDER'S COPY IS (2026-10-21), in Mutagen's own counts, so nothing walks the folder a second time to say
|
|
46
|
+
// it: what the sending side's last scan holds (`files`, `bytes`, ignored paths left out as the sync leaves them), and
|
|
47
|
+
// while the receiving side stages a pass, how many of that pass's files it has (`stagedFiles` of `stagingFiles`) and
|
|
48
|
+
// how many bytes have crossed (`stagedBytes`). A folder's first copy is one such pass over everything. Each is absent
|
|
49
|
+
// until Mutagen has said it; the whole is absent from an agent older than it, and from a pairing with no session.
|
|
50
|
+
export const DeviceCopySchema = z.object({
|
|
51
|
+
files: z.number().int().nonnegative().optional(),
|
|
52
|
+
bytes: z.number().nonnegative().optional(),
|
|
53
|
+
stagedFiles: z.number().int().nonnegative().optional(),
|
|
54
|
+
stagingFiles: z.number().int().nonnegative().optional(),
|
|
55
|
+
stagedBytes: z.number().nonnegative().optional(),
|
|
56
|
+
});
|
|
57
|
+
export type DeviceCopy = z.infer<typeof DeviceCopySchema>;
|
|
58
|
+
|
|
45
59
|
// The drive reading that paused a pairing for want of room: when (epoch ms), and how much was free of how much.
|
|
46
60
|
export const DeviceLowDiskSchema = z.object({
|
|
47
61
|
since: z.number(),
|
package/src/schemas/devices.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// What one of the user's own machines is running.
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { PortNumberSchema } from "./device-commands.js";
|
|
4
|
-
import { DeviceBridgeHoldSchema, DeviceConflictSchema, DeviceLowDiskSchema } from "./device-folder.js";
|
|
4
|
+
import { DeviceBridgeHoldSchema, DeviceConflictSchema, DeviceCopySchema, DeviceLowDiskSchema } from "./device-folder.js";
|
|
5
5
|
import { parseHostConnection, type DeviceFacts, DeviceFactsSchema, MachineIdentitySchema, MachineIdSchema, userDistrosOf, WslEnvironmentSchema } from "./hosts.js";
|
|
6
6
|
import { RollbackTargetSchema, UpdateOutcomeSchema } from "./updates.js";
|
|
7
7
|
import { DEV_VERSION } from "../state/versions.js";
|
|
@@ -249,15 +249,15 @@ export const DevicePairingSchema = z.object({
|
|
|
249
249
|
remoteDir: z.string().optional(),
|
|
250
250
|
// Whether landed work in this project folder is delivered to `localDir` by itself (`deliverProject`). A project
|
|
251
251
|
// sandbox's folder says "auto"; absent means Bring back by hand, as before. (2026-10-21) The folderless
|
|
252
|
-
// `projectsHost` pairing of the
|
|
253
|
-
// that still carries it parses, the key dropped.
|
|
252
|
+
// `projectsHost` pairing of the one sandbox per computer went with it; an older report carrying it parses, key dropped.
|
|
254
253
|
deliver: z.enum(["auto", "off"]).optional(),
|
|
255
254
|
// (2026-10-20) The slug this machine's ic knows the sandbox by: how the desktop app starts or forgets a folder's sandbox.
|
|
256
255
|
slug: z.string().optional(),
|
|
257
256
|
// Machine-owned switch (agent's own `sync mirror off`); absent reads as "on", not merely unknown.
|
|
258
257
|
mirroring: z.enum(["on", "off"]).optional(),
|
|
259
|
-
// Mutagen's own status word, kept verbatim rather than reduced to a traffic light.
|
|
258
|
+
// Mutagen's own status word, kept verbatim rather than reduced to a traffic light, and how far its copy is.
|
|
260
259
|
mutagenStatus: z.string().optional(),
|
|
260
|
+
copy: DeviceCopySchema.optional(),
|
|
261
261
|
// Whole conflict count, including any Mutagen's own state truncates (`excludedConflicts`).
|
|
262
262
|
conflicts: z.number().int().nonnegative().optional(),
|
|
263
263
|
// Conflicted paths, capped at `CONFLICT_PATHS_MAX`; `conflicts` above is the true, uncapped count.
|
|
@@ -304,8 +304,7 @@ export const DevicePortSchema = z.object({
|
|
|
304
304
|
command: z.string().optional(),
|
|
305
305
|
});
|
|
306
306
|
export type DevicePort = z.infer<typeof DevicePortSchema>;
|
|
307
|
-
// The device's one agent process
|
|
308
|
-
// current, a dead loop leaves every other row reading as it did before it died.
|
|
307
|
+
// The device's one agent process; `running` gates whether the rest of a report is current (a dead loop freezes it).
|
|
309
308
|
export const DeviceAgentSchema = z.object({
|
|
310
309
|
running: z.boolean(),
|
|
311
310
|
pid: z.number().int().optional(),
|
|
@@ -313,12 +312,13 @@ export const DeviceAgentSchema = z.object({
|
|
|
313
312
|
installed: z.string().optional(),
|
|
314
313
|
// Build actually serving, stamped in the pidfile; can lag `installed` indefinitely (see `agentBuildSkew`).
|
|
315
314
|
build: z.string().optional(),
|
|
316
|
-
// When the loop last
|
|
315
|
+
// When the sync loop last moved; absent while nothing is paired (no loop runs). A live process can have a dead loop.
|
|
317
316
|
lastTickAt: z.number().optional(),
|
|
318
317
|
});
|
|
319
318
|
export type DeviceAgent = z.infer<typeof DeviceAgentSchema>;
|
|
320
|
-
//
|
|
321
|
-
export const
|
|
319
|
+
// Stalled only once the agent's own restart of a loop that stopped moving (machine sync/mirror.ts) is overdue.
|
|
320
|
+
export const WATCHER_STALL_MS = 20 * 60_000;
|
|
321
|
+
export const AGENT_STALL_AFTER_MS = WATCHER_STALL_MS + 2 * 60_000;
|
|
322
322
|
export const agentStalled = (agent: DeviceAgent, now: number): boolean =>
|
|
323
323
|
agent.running && agent.lastTickAt !== undefined && now - agent.lastTickAt > AGENT_STALL_AFTER_MS;
|
|
324
324
|
export const DeviceReportSchema = z.object({
|
|
@@ -398,6 +398,8 @@ describe(`VERSIONED_STATE_PATHS`, () => {
|
|
|
398
398
|
// Which commands count as heavy, and how many may run at once; a shared-box decision worth a `git log`
|
|
399
399
|
// line.
|
|
400
400
|
`${STATE_DIR}/config/heavy-commands.json`,
|
|
401
|
+
// What a home conversation reviews, merges and asks a person about; same reason as the safety policy.
|
|
402
|
+
`${STATE_DIR}/config/home.md`,
|
|
401
403
|
`${STATE_DIR}/config/loop-designs.json`,
|
|
402
404
|
`${STATE_DIR}/config/personas.json`,
|
|
403
405
|
// A persona's prompt and reachable skills: the text that decides how it behaves, same reason as its card.
|
|
@@ -100,6 +100,10 @@ const STATE_FILES = [
|
|
|
100
100
|
// may do unasked, so a fresh sandbox should start already governed and changes should show in review. Absent falls
|
|
101
101
|
// back to `DEFAULT_SAFETY_POLICY`, not unconfigured.
|
|
102
102
|
{ path: ".intentic/config/safety.md", invalidates: ["safety-policy"], portability: "carry", versioned: true },
|
|
103
|
+
// What every home conversation works by (conversations/home/home-policy.ts): when it reviews, tests, merges and asks a
|
|
104
|
+
// person. `carry`+`versioned` like the safety policy, since it governs what an orchestrator lands unasked. Absent falls
|
|
105
|
+
// back to `DEFAULT_HOME_POLICY`.
|
|
106
|
+
{ path: ".intentic/config/home.md", invalidates: [], portability: "carry", versioned: true, why: "Read per home turn; no view shows it." },
|
|
103
107
|
// Verdicts the policy above produced, newest first. `derived`/`local`: evidence about this machine, self-trimming,
|
|
104
108
|
// not carried. Own query key, separate from the policy's, since a verdict must not invalidate the document someone
|
|
105
109
|
// is editing.
|
|
@@ -305,12 +309,7 @@ const STATE_FILES = [
|
|
|
305
309
|
// Unlike the settings file, this switch is observed by the Extensions tab's query, so a flip elsewhere shows up
|
|
306
310
|
// live. Toggling doesn't itself rerun the host, that happens via the loader's reconcile, triggered by the tab's own
|
|
307
311
|
// toggle.
|
|
308
|
-
{
|
|
309
|
-
path: ".intentic/config/extension-enablement.json",
|
|
310
|
-
invalidates: ["extensions"],
|
|
311
|
-
portability: "carry",
|
|
312
|
-
versioned: true,
|
|
313
|
-
},
|
|
312
|
+
{ path: ".intentic/config/extension-enablement.json", invalidates: ["extensions"], portability: "carry", versioned: true },
|
|
314
313
|
// Workspace extensions: one directory per extension, written directly by the agent's file tools and live
|
|
315
314
|
// immediately (no clone, no install step). `authored`, unlike `.intentic/extensions/` below which holds clones.
|
|
316
315
|
// `versioned`: every other load path is already reviewable (a git-installed extension's sha, a baked one's image);
|