@intentic/sandbox-contract 1.337.0 → 1.339.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.
Files changed (316) hide show
  1. package/README.md +41 -0
  2. package/dist/chores/probes.d.ts.map +1 -1
  3. package/dist/chores/probes.js +4 -7
  4. package/dist/chores/probes.js.map +1 -1
  5. package/dist/contracts/agent.contract.d.ts +32 -23
  6. package/dist/contracts/agent.contract.d.ts.map +1 -1
  7. package/dist/contracts/agents.contract.d.ts +6020 -5994
  8. package/dist/contracts/agents.contract.d.ts.map +1 -1
  9. package/dist/contracts/agents.contract.js +13 -1
  10. package/dist/contracts/agents.contract.js.map +1 -1
  11. package/dist/contracts/approvals.contract.d.ts +8 -0
  12. package/dist/contracts/approvals.contract.d.ts.map +1 -1
  13. package/dist/contracts/automations.contract.d.ts +3 -3
  14. package/dist/contracts/capabilities.contract.d.ts +28 -0
  15. package/dist/contracts/capabilities.contract.d.ts.map +1 -1
  16. package/dist/contracts/capabilities.contract.js +11 -1
  17. package/dist/contracts/capabilities.contract.js.map +1 -1
  18. package/dist/contracts/device.contract.d.ts +225 -0
  19. package/dist/contracts/device.contract.d.ts.map +1 -1
  20. package/dist/contracts/device.contract.js +10 -0
  21. package/dist/contracts/device.contract.js.map +1 -1
  22. package/dist/contracts/extensions.contract.d.ts +16 -0
  23. package/dist/contracts/extensions.contract.d.ts.map +1 -1
  24. package/dist/contracts/needs.contract.d.ts +22 -7
  25. package/dist/contracts/needs.contract.d.ts.map +1 -1
  26. package/dist/contracts/org.contract.d.ts +35 -0
  27. package/dist/contracts/org.contract.d.ts.map +1 -0
  28. package/dist/contracts/org.contract.js +14 -0
  29. package/dist/contracts/org.contract.js.map +1 -0
  30. package/dist/contracts/phone.contract.d.ts +1 -0
  31. package/dist/contracts/phone.contract.d.ts.map +1 -1
  32. package/dist/contracts/privacy.contract.d.ts +115 -0
  33. package/dist/contracts/privacy.contract.d.ts.map +1 -1
  34. package/dist/contracts/privacy.contract.js +11 -2
  35. package/dist/contracts/privacy.contract.js.map +1 -1
  36. package/dist/contracts/runner.contract.d.ts +11 -7
  37. package/dist/contracts/runner.contract.d.ts.map +1 -1
  38. package/dist/contracts/secrets.contract.d.ts +85 -0
  39. package/dist/contracts/secrets.contract.d.ts.map +1 -1
  40. package/dist/contracts/secrets.contract.js +48 -1
  41. package/dist/contracts/secrets.contract.js.map +1 -1
  42. package/dist/contracts/sessions.contract.d.ts +4 -1
  43. package/dist/contracts/sessions.contract.d.ts.map +1 -1
  44. package/dist/contracts/settings.contract.d.ts +11 -3
  45. package/dist/contracts/settings.contract.d.ts.map +1 -1
  46. package/dist/contracts/system.contract.d.ts +23 -2
  47. package/dist/contracts/system.contract.d.ts.map +1 -1
  48. package/dist/events/agent-events.d.ts +17 -4
  49. package/dist/events/agent-events.d.ts.map +1 -1
  50. package/dist/events/requests.d.ts +4 -0
  51. package/dist/events/requests.d.ts.map +1 -1
  52. package/dist/events/requests.js +1 -1
  53. package/dist/events/requests.js.map +1 -1
  54. package/dist/events/system-events.d.ts +4 -0
  55. package/dist/events/system-events.d.ts.map +1 -1
  56. package/dist/events/system-events.js +2 -0
  57. package/dist/events/system-events.js.map +1 -1
  58. package/dist/events/transcript.d.ts +27 -6
  59. package/dist/events/transcript.d.ts.map +1 -1
  60. package/dist/index.d.ts +7562 -7426
  61. package/dist/index.d.ts.map +1 -1
  62. package/dist/index.js +31 -257
  63. package/dist/index.js.map +1 -1
  64. package/dist/models/agent-lanes.d.ts +30 -0
  65. package/dist/models/agent-lanes.d.ts.map +1 -0
  66. package/dist/models/agent-lanes.js +67 -0
  67. package/dist/models/agent-lanes.js.map +1 -0
  68. package/dist/models/agent-standing.d.ts +55 -0
  69. package/dist/models/agent-standing.d.ts.map +1 -0
  70. package/dist/models/agent-standing.js +82 -0
  71. package/dist/models/agent-standing.js.map +1 -0
  72. package/dist/models/agent-starter.d.ts +10 -0
  73. package/dist/models/agent-starter.d.ts.map +1 -0
  74. package/dist/models/agent-starter.js +16 -0
  75. package/dist/models/agent-starter.js.map +1 -0
  76. package/dist/netd/generated/wire.d.ts +1 -1
  77. package/dist/netd/generated/wire.d.ts.map +1 -1
  78. package/dist/policy/identity-assertion.d.ts.map +1 -1
  79. package/dist/policy/identity-assertion.js +2 -8
  80. package/dist/policy/identity-assertion.js.map +1 -1
  81. package/dist/policy/keyring-seal.d.ts +48 -0
  82. package/dist/policy/keyring-seal.d.ts.map +1 -0
  83. package/dist/policy/keyring-seal.js +94 -0
  84. package/dist/policy/keyring-seal.js.map +1 -0
  85. package/dist/policy/message-limits.d.ts +2 -0
  86. package/dist/policy/message-limits.d.ts.map +1 -1
  87. package/dist/policy/message-limits.js +2 -0
  88. package/dist/policy/message-limits.js.map +1 -1
  89. package/dist/policy/org-locks.d.ts +58 -0
  90. package/dist/policy/org-locks.d.ts.map +1 -0
  91. package/dist/policy/org-locks.js +113 -0
  92. package/dist/policy/org-locks.js.map +1 -0
  93. package/dist/policy/persona-capability.d.ts +14 -0
  94. package/dist/policy/persona-capability.d.ts.map +1 -0
  95. package/dist/policy/persona-capability.js +23 -0
  96. package/dist/policy/persona-capability.js.map +1 -0
  97. package/dist/policy/reserved-servers.d.ts.map +1 -1
  98. package/dist/policy/reserved-servers.js +1 -1
  99. package/dist/policy/reserved-servers.js.map +1 -1
  100. package/dist/protocol/doors/phone-links.d.ts +5 -0
  101. package/dist/protocol/doors/phone-links.d.ts.map +1 -1
  102. package/dist/protocol/doors/phone-links.js +3 -1
  103. package/dist/protocol/doors/phone-links.js.map +1 -1
  104. package/dist/protocol/doors/webext-links.d.ts +3 -0
  105. package/dist/protocol/doors/webext-links.d.ts.map +1 -1
  106. package/dist/protocol/doors/webext-links.js +3 -0
  107. package/dist/protocol/doors/webext-links.js.map +1 -1
  108. package/dist/protocol/raw/raw-json-routes.d.ts +270 -0
  109. package/dist/protocol/raw/raw-json-routes.d.ts.map +1 -1
  110. package/dist/protocol/raw/raw-json-routes.js +14 -1
  111. package/dist/protocol/raw/raw-json-routes.js.map +1 -1
  112. package/dist/protocol/raw/raw-routes.d.ts +70 -0
  113. package/dist/protocol/raw/raw-routes.d.ts.map +1 -1
  114. package/dist/protocol/raw/raw-routes.js +18 -0
  115. package/dist/protocol/raw/raw-routes.js.map +1 -1
  116. package/dist/protocol/route-meta.d.ts.map +1 -1
  117. package/dist/protocol/route-meta.js.map +1 -1
  118. package/dist/protocol/routes.d.ts +4 -0
  119. package/dist/protocol/routes.d.ts.map +1 -1
  120. package/dist/protocol/routes.js +12 -0
  121. package/dist/protocol/routes.js.map +1 -1
  122. package/dist/schemas/agents-list.d.ts +5 -0
  123. package/dist/schemas/agents-list.d.ts.map +1 -1
  124. package/dist/schemas/agents-list.js +8 -0
  125. package/dist/schemas/agents-list.js.map +1 -1
  126. package/dist/schemas/agents.d.ts +1 -0
  127. package/dist/schemas/agents.d.ts.map +1 -1
  128. package/dist/schemas/agents.js +1 -0
  129. package/dist/schemas/agents.js.map +1 -1
  130. package/dist/schemas/approvals.d.ts +20 -0
  131. package/dist/schemas/approvals.d.ts.map +1 -1
  132. package/dist/schemas/approvals.js +10 -0
  133. package/dist/schemas/approvals.js.map +1 -1
  134. package/dist/schemas/capabilities.d.ts +26 -0
  135. package/dist/schemas/capabilities.d.ts.map +1 -1
  136. package/dist/schemas/capabilities.js +17 -0
  137. package/dist/schemas/capabilities.js.map +1 -1
  138. package/dist/schemas/data-protection.d.ts +5 -0
  139. package/dist/schemas/data-protection.d.ts.map +1 -1
  140. package/dist/schemas/data-protection.js +29 -0
  141. package/dist/schemas/data-protection.js.map +1 -1
  142. package/dist/schemas/devices.d.ts +20 -0
  143. package/dist/schemas/devices.d.ts.map +1 -1
  144. package/dist/schemas/devices.js +5 -1
  145. package/dist/schemas/devices.js.map +1 -1
  146. package/dist/schemas/extension-updates.d.ts +32 -0
  147. package/dist/schemas/extension-updates.d.ts.map +1 -1
  148. package/dist/schemas/git/git.d.ts +19 -0
  149. package/dist/schemas/git/git.d.ts.map +1 -1
  150. package/dist/schemas/git/git.js +12 -1
  151. package/dist/schemas/git/git.js.map +1 -1
  152. package/dist/schemas/hosts.d.ts +2 -0
  153. package/dist/schemas/hosts.d.ts.map +1 -1
  154. package/dist/schemas/hosts.js +11 -1
  155. package/dist/schemas/hosts.js.map +1 -1
  156. package/dist/schemas/intentic-os.d.ts +218 -0
  157. package/dist/schemas/intentic-os.d.ts.map +1 -1
  158. package/dist/schemas/intentic-os.js +166 -0
  159. package/dist/schemas/intentic-os.js.map +1 -1
  160. package/dist/schemas/keyring.d.ts +421 -0
  161. package/dist/schemas/keyring.d.ts.map +1 -0
  162. package/dist/schemas/keyring.js +176 -0
  163. package/dist/schemas/keyring.js.map +1 -0
  164. package/dist/schemas/links.d.ts +208 -0
  165. package/dist/schemas/links.d.ts.map +1 -0
  166. package/dist/schemas/links.js +116 -0
  167. package/dist/schemas/links.js.map +1 -0
  168. package/dist/schemas/maintenance.d.ts.map +1 -1
  169. package/dist/schemas/maintenance.js.map +1 -1
  170. package/dist/schemas/members.d.ts.map +1 -1
  171. package/dist/schemas/members.js +2 -0
  172. package/dist/schemas/members.js.map +1 -1
  173. package/dist/schemas/needs.d.ts +25 -8
  174. package/dist/schemas/needs.d.ts.map +1 -1
  175. package/dist/schemas/needs.js +15 -3
  176. package/dist/schemas/needs.js.map +1 -1
  177. package/dist/schemas/organisation.d.ts +106 -0
  178. package/dist/schemas/organisation.d.ts.map +1 -0
  179. package/dist/schemas/organisation.js +52 -0
  180. package/dist/schemas/organisation.js.map +1 -0
  181. package/dist/schemas/phone.d.ts +3 -0
  182. package/dist/schemas/phone.d.ts.map +1 -1
  183. package/dist/schemas/phone.js +1 -0
  184. package/dist/schemas/phone.js.map +1 -1
  185. package/dist/schemas/privacy.d.ts +232 -0
  186. package/dist/schemas/privacy.d.ts.map +1 -1
  187. package/dist/schemas/privacy.js +154 -4
  188. package/dist/schemas/privacy.js.map +1 -1
  189. package/dist/schemas/secrets.d.ts +151 -0
  190. package/dist/schemas/secrets.d.ts.map +1 -1
  191. package/dist/schemas/secrets.js +71 -8
  192. package/dist/schemas/secrets.js.map +1 -1
  193. package/dist/schemas/settings.d.ts +8 -0
  194. package/dist/schemas/settings.d.ts.map +1 -1
  195. package/dist/schemas/settings.js +4 -0
  196. package/dist/schemas/settings.js.map +1 -1
  197. package/dist/schemas/speech.d.ts +12 -0
  198. package/dist/schemas/speech.d.ts.map +1 -1
  199. package/dist/schemas/speech.js +23 -0
  200. package/dist/schemas/speech.js.map +1 -1
  201. package/dist/schemas/system.d.ts +2 -0
  202. package/dist/schemas/system.d.ts.map +1 -1
  203. package/dist/schemas/system.js +1 -1
  204. package/dist/schemas/system.js.map +1 -1
  205. package/dist/schemas/terminal.d.ts +15 -0
  206. package/dist/schemas/terminal.d.ts.map +1 -1
  207. package/dist/schemas/terminal.js +11 -2
  208. package/dist/schemas/terminal.js.map +1 -1
  209. package/dist/schemas/voice.d.ts +3 -0
  210. package/dist/schemas/voice.d.ts.map +1 -1
  211. package/dist/schemas/voice.js +7 -0
  212. package/dist/schemas/voice.js.map +1 -1
  213. package/dist/schemas/webext.d.ts +17 -0
  214. package/dist/schemas/webext.d.ts.map +1 -1
  215. package/dist/schemas/webext.js +7 -1
  216. package/dist/schemas/webext.js.map +1 -1
  217. package/dist/state/contract-lock.d.ts +3 -1
  218. package/dist/state/contract-lock.d.ts.map +1 -1
  219. package/dist/state/contract-lock.js +34 -9
  220. package/dist/state/contract-lock.js.map +1 -1
  221. package/dist/state/contract-modules.d.ts +4 -0
  222. package/dist/state/contract-modules.d.ts.map +1 -0
  223. package/dist/state/contract-modules.js +28 -0
  224. package/dist/state/contract-modules.js.map +1 -0
  225. package/dist/state/daemon-floor.d.ts +4 -0
  226. package/dist/state/daemon-floor.d.ts.map +1 -0
  227. package/dist/state/daemon-floor.js +8 -0
  228. package/dist/state/daemon-floor.js.map +1 -0
  229. package/dist/state/definition.d.ts +34 -0
  230. package/dist/state/definition.d.ts.map +1 -1
  231. package/dist/state/history-state.d.ts.map +1 -1
  232. package/dist/state/history-state.js +8 -0
  233. package/dist/state/history-state.js.map +1 -1
  234. package/dist/text/documents.d.ts +3 -1
  235. package/dist/text/documents.d.ts.map +1 -1
  236. package/dist/text/documents.js +30 -0
  237. package/dist/text/documents.js.map +1 -1
  238. package/dist/text/pages.d.ts.map +1 -1
  239. package/dist/text/pages.js +3 -7
  240. package/dist/text/pages.js.map +1 -1
  241. package/package.json +27 -5
  242. package/src/chores/probes.ts +4 -6
  243. package/src/contracts/agents.contract.ts +18 -1
  244. package/src/contracts/capabilities.contract.ts +14 -0
  245. package/src/contracts/device.contract.ts +30 -0
  246. package/src/contracts/org.contract.ts +19 -0
  247. package/src/contracts/privacy.contract.ts +13 -1
  248. package/src/contracts/secrets.contract.ts +64 -0
  249. package/src/events/requests.ts +3 -1
  250. package/src/events/system-events.ts +9 -0
  251. package/src/index.ts +41 -279
  252. package/src/models/agent-lanes.ts +152 -0
  253. package/src/models/agent-standing.ts +279 -0
  254. package/src/models/agent-starter.ts +26 -0
  255. package/src/netd/generated/netd-wire.json +2 -1
  256. package/src/netd/generated/wire.ts +1 -1
  257. package/src/policy/identity-assertion.ts +2 -8
  258. package/src/policy/keyring-seal.test.ts +93 -0
  259. package/src/policy/keyring-seal.ts +148 -0
  260. package/src/policy/message-limits.ts +3 -0
  261. package/src/policy/org-locks.ts +173 -0
  262. package/src/policy/persona-capability.ts +57 -0
  263. package/src/policy/reserved-servers.ts +3 -2
  264. package/src/policy/tests/org-locks.test.ts +92 -0
  265. package/src/policy/tests/reserved-servers.test.ts +3 -2
  266. package/src/protocol/doors/listener-protocol.ts +1 -1
  267. package/src/protocol/doors/phone-links.ts +11 -2
  268. package/src/protocol/doors/phone-protocol.test.ts +8 -1
  269. package/src/protocol/doors/webext-links.ts +13 -0
  270. package/src/protocol/ingress-contract.fixture.json +2 -0
  271. package/src/protocol/pairing-redeem.ts +1 -1
  272. package/src/protocol/peer-dial.test.ts +6 -6
  273. package/src/protocol/peer-mcp-server.test.ts +2 -1
  274. package/src/protocol/raw/raw-json-routes.ts +31 -1
  275. package/src/protocol/raw/raw-routes.ts +35 -1
  276. package/src/protocol/route-meta.ts +2 -1
  277. package/src/protocol/routes.test.ts +33 -1
  278. package/src/protocol/routes.ts +18 -0
  279. package/src/protocol/sse.ts +1 -1
  280. package/src/schemas/agent.ts +1 -1
  281. package/src/schemas/agents-list.ts +13 -0
  282. package/src/schemas/agents.ts +1 -0
  283. package/src/schemas/approvals.ts +14 -0
  284. package/src/schemas/capabilities.ts +25 -0
  285. package/src/schemas/data-protection.ts +47 -0
  286. package/src/schemas/devices.ts +15 -1
  287. package/src/schemas/git/git.ts +22 -2
  288. package/src/schemas/hosts.ts +14 -1
  289. package/src/schemas/intentic-os.test.ts +148 -0
  290. package/src/schemas/intentic-os.ts +259 -0
  291. package/src/schemas/keyring.ts +277 -0
  292. package/src/schemas/links.ts +180 -0
  293. package/src/schemas/maintenance.ts +1 -0
  294. package/src/schemas/members.ts +4 -0
  295. package/src/schemas/needs.ts +21 -5
  296. package/src/schemas/organisation.ts +110 -0
  297. package/src/schemas/phone.ts +3 -0
  298. package/src/schemas/privacy.test.ts +63 -0
  299. package/src/schemas/privacy.ts +213 -5
  300. package/src/schemas/secrets.ts +96 -8
  301. package/src/schemas/settings.ts +8 -0
  302. package/src/schemas/speech.ts +38 -1
  303. package/src/schemas/system.ts +5 -2
  304. package/src/schemas/terminal.ts +13 -2
  305. package/src/schemas/voice.ts +11 -0
  306. package/src/schemas/webext.ts +16 -1
  307. package/src/state/contract-lock.test.ts +12 -12
  308. package/src/state/contract-lock.ts +43 -12
  309. package/src/state/contract-modules.ts +41 -0
  310. package/src/state/daemon-floor.test.ts +81 -0
  311. package/src/state/daemon-floor.ts +32 -0
  312. package/src/state/history-state.ts +17 -0
  313. package/src/state/workspace-state.ts +1 -1
  314. package/src/text/documents.test.ts +30 -1
  315. package/src/text/documents.ts +43 -1
  316. package/src/text/pages.ts +5 -7
@@ -44,6 +44,183 @@ export const PRIVACY_TRUSTED_MAX = 200;
44
44
  export const PRIVACY_ALLOW_MAX = 1000;
45
45
  export const PRIVACY_CONVERSATIONS_MAX = 200;
46
46
 
47
+ // The owner's own additions to what the shield finds. The policy travels whole on every write, so each is kept to what a
48
+ // person types or pastes; a dataset of thousands of values is taught to the vault instead (privacy learn).
49
+ export const PRIVACY_OWN_LISTS_MAX = 20;
50
+ export const PRIVACY_OWN_LIST_WORDS_MAX = 2000;
51
+ export const PRIVACY_WORD_MAX = 60;
52
+ export const PRIVACY_RULES_MAX = 50;
53
+ export const PRIVACY_RULE_TERMS_MAX = 1000;
54
+ export const PRIVACY_TERM_MAX = 200;
55
+ export const PRIVACY_PATTERN_MAX = 500;
56
+ export const PRIVACY_TITLE_MAX = 80;
57
+
58
+ // What a word on a name list says about a name: a first name or a surname (a name when the two stand together), a word
59
+ // that is a name only beside other evidence, a title after which a capitalized word is a person, or never part of one.
60
+ export const PrivacyNameListKindSchema = z.enum(["first-name", "surname", "ambiguous", "title", "never"]);
61
+ export type PrivacyNameListKind = z.infer<typeof PrivacyNameListKindSchema>;
62
+
63
+ // inflected: every Polish grammatical form of a listed word ("Kasia", "Kasi", "Kasię"); as-written: only as listed.
64
+ export const PrivacyNameMatchingSchema = z.enum(["inflected", "as-written"]);
65
+ export type PrivacyNameMatching = z.infer<typeof PrivacyNameMatchingSchema>;
66
+
67
+ // Ids the editor mints for the owner's lists and rules: stable across renames, so a write never confuses two of them.
68
+ const OwnIdSchema = z.string().regex(/^[a-z0-9][a-z0-9-]{0,39}$/u);
69
+
70
+ export const PrivacyOwnNameListSchema = z.object({
71
+ id: OwnIdSchema,
72
+ title: z.string().min(1).max(PRIVACY_TITLE_MAX),
73
+ kind: PrivacyNameListKindSchema,
74
+ matching: PrivacyNameMatchingSchema.default("as-written"),
75
+ words: z
76
+ .array(z.string().min(1).max(PRIVACY_WORD_MAX))
77
+ .max(PRIVACY_OWN_LIST_WORDS_MAX)
78
+ .default([])
79
+ .describe("One word each, in any case: the list is read as the built-in lists are."),
80
+ on: z.boolean().default(true),
81
+ });
82
+ export type PrivacyOwnNameList = z.infer<typeof PrivacyOwnNameListSchema>;
83
+
84
+ export const PrivacyDictionaryPolicySchema = z.object({
85
+ off: z.array(z.string().min(1).max(64)).max(64).default([]).describe("Built-in name lists switched off, by id (privacy.dictionary names them)."),
86
+ lists: z
87
+ .array(PrivacyOwnNameListSchema)
88
+ .max(PRIVACY_OWN_LISTS_MAX)
89
+ .default([])
90
+ .describe("The owner's own name lists, read beside the built-in ones."),
91
+ });
92
+ export type PrivacyDictionaryPolicy = z.infer<typeof PrivacyDictionaryPolicySchema>;
93
+
94
+ // One of the owner's own rules: what it matches is masked wherever it stands, with no context asked for, as the kind it
95
+ // names. terms: each word or phrase as a whole word, in any case unless matchCase. pattern: a regular expression (with the
96
+ // `u` flag, `i` unless matchCase); a group named `value` masks only that part of a match.
97
+ export const PrivacyRuleMatchSchema = z.enum(["terms", "pattern"]);
98
+ export type PrivacyRuleMatch = z.infer<typeof PrivacyRuleMatchSchema>;
99
+
100
+ export const PrivacyRuleSchema = z.object({
101
+ id: OwnIdSchema,
102
+ title: z.string().min(1).max(PRIVACY_TITLE_MAX),
103
+ on: z.boolean().default(true),
104
+ class: PersonalDataClassSchema.describe("The kind what it matches is masked as; switching that kind off stops the rule too."),
105
+ match: PrivacyRuleMatchSchema,
106
+ terms: z.array(z.string().min(1).max(PRIVACY_TERM_MAX)).max(PRIVACY_RULE_TERMS_MAX).default([]),
107
+ // Only its size is held here: whether a pattern is safe to run is judged when it is saved (patternProblem), so a
108
+ // stored one never turns unreadable because a later build judges more strictly.
109
+ pattern: z.string().max(PRIVACY_PATTERN_MAX).default(""),
110
+ matchCase: z.boolean().default(false),
111
+ });
112
+ export type PrivacyRule = z.infer<typeof PrivacyRuleSchema>;
113
+
114
+ // Why a pattern cannot be saved. Owner-written patterns run on every string bound for an untrusted provider, in the
115
+ // daemon's one thread, so beside not compiling: one that matches empty text masks nothing and only costs, and a repeat
116
+ // inside a repeat (`(a+)+`, `(\w+\s?)*`) can take minutes on one line that almost matches.
117
+ export type PrivacyPatternProblem = "invalid" | "tooLong" | "empty" | "nested";
118
+
119
+ // The length of a quantifier at `at`, and whether it repeats a variable number of times (`*`, `+`, `{2,}`, `{2,5}`); `?`
120
+ // and `{3}` do not. A lazy `?` after it is part of it.
121
+ const quantifierAt = (source: string, at: number): { readonly length: number; readonly varies: boolean } | undefined => {
122
+ const char = source[at];
123
+ let length: number;
124
+ let varies: boolean;
125
+ if (char === "*" || char === "+" || char === "?") {
126
+ length = 1;
127
+ varies = char !== "?";
128
+ } else if (char === "{") {
129
+ const braces = /^\{(\d+)(?:(,)(\d*))?\}/u.exec(source.slice(at));
130
+ if (braces === null) {
131
+ return undefined;
132
+ }
133
+ const [whole, min = "0", comma, max] = braces;
134
+ length = whole.length;
135
+ varies = comma !== undefined && (max === undefined || max === "" || Number(max) > Number(min));
136
+ } else {
137
+ return undefined;
138
+ }
139
+ return { length: source[at + length] === "?" ? length + 1 : length, varies };
140
+ };
141
+
142
+ // One step of the walk below: where the next one starts, and what this one was.
143
+ interface PatternStep {
144
+ readonly next: number;
145
+ readonly kind: "open" | "close" | "repeat" | "other";
146
+ }
147
+
148
+ // Past a character class, escapes inside it included.
149
+ const classEnd = (source: string, at: number): number => {
150
+ let end = at + 1;
151
+ while (end < source.length && source[end] !== "]") {
152
+ end += source[end] === "\\" ? 2 : 1;
153
+ }
154
+ return end + 1;
155
+ };
156
+
157
+ // Past a group's opening and its own prefix (`?:`, `?=`, `?<name>`), which is no quantifier.
158
+ const groupStart = (source: string, at: number): number => {
159
+ if (source[at + 1] !== "?") {
160
+ return at + 1;
161
+ }
162
+ const name = /^\?<(?![=!])[^>]*>/u.exec(source.slice(at + 1));
163
+ return at + 1 + (name === null ? 2 : name[0].length);
164
+ };
165
+
166
+ const stepAt = (source: string, at: number): PatternStep => {
167
+ const char = source[at];
168
+ if (char === "\\") {
169
+ return { next: at + 2, kind: "other" };
170
+ }
171
+ if (char === "[") {
172
+ return { next: classEnd(source, at), kind: "other" };
173
+ }
174
+ if (char === "(") {
175
+ return { next: groupStart(source, at), kind: "open" };
176
+ }
177
+ if (char === ")") {
178
+ return { next: at + 1, kind: "close" };
179
+ }
180
+ const quantifier = quantifierAt(source, at);
181
+ return quantifier === undefined
182
+ ? { next: at + 1, kind: "other" }
183
+ : { next: at + quantifier.length, kind: quantifier.varies ? "repeat" : "other" };
184
+ };
185
+
186
+ // Whether a group that repeats a variable number of times holds such a repeat itself: a star height above one.
187
+ const nestedRepeat = (source: string): boolean => {
188
+ // Per open group, whether something inside it repeats.
189
+ const open: boolean[] = [];
190
+ // Whether the step before was the close of a group that held a repeat.
191
+ let closedRepeating = false;
192
+ for (let at = 0; at < source.length;) {
193
+ const step = stepAt(source, at);
194
+ at = step.next;
195
+ if (step.kind === "open") {
196
+ open.push(false);
197
+ } else if (step.kind === "repeat") {
198
+ if (closedRepeating) {
199
+ return true;
200
+ }
201
+ open.fill(true);
202
+ }
203
+ closedRepeating = step.kind === "close" && (open.pop() ?? false);
204
+ }
205
+ return false;
206
+ };
207
+
208
+ export const patternProblem = (source: string): PrivacyPatternProblem | undefined => {
209
+ if (source.length > PRIVACY_PATTERN_MAX) {
210
+ return "tooLong";
211
+ }
212
+ let compiled: RegExp;
213
+ try {
214
+ compiled = new RegExp(source, "u");
215
+ } catch {
216
+ return "invalid";
217
+ }
218
+ if (source.trim() === "" || compiled.test("")) {
219
+ return "empty";
220
+ }
221
+ return nestedRepeat(source) ? "nested" : undefined;
222
+ };
223
+
47
224
  // One conversation the owner let an untrusted provider read as it is, granted from that conversation's own composer:
48
225
  // narrower than the trusted list, and the way on for a provider whose runtime the gateway cannot sit in front of
49
226
  // (Cursor's own wire, an ACP agent, Pi), which the shield otherwise refuses outright.
@@ -78,6 +255,14 @@ export const PrivacyShieldPolicySchema = z.object({
78
255
  .max(PRIVACY_CONVERSATIONS_MAX)
79
256
  .default([])
80
257
  .describe("Providers that may read one conversation's personal data as it is, each granted from that conversation; oldest first."),
258
+ dictionary: PrivacyDictionaryPolicySchema.default({ off: [], lists: [] }).describe(
259
+ "The name dictionary as the owner adjusted it: built-in lists switched off, and lists of their own.",
260
+ ),
261
+ rules: z
262
+ .array(PrivacyRuleSchema)
263
+ .max(PRIVACY_RULES_MAX)
264
+ .default([])
265
+ .describe("The owner's own rules: words, phrases and patterns masked wherever they stand."),
81
266
  });
82
267
  export type PrivacyShieldPolicy = z.infer<typeof PrivacyShieldPolicySchema>;
83
268
 
@@ -204,18 +389,21 @@ export const PrivacyKnownSourceSchema = z.object({
204
389
  export type PrivacyKnownSource = z.infer<typeof PrivacyKnownSourceSchema>;
205
390
 
206
391
  // The name lists the shield finds names by when no model is asked for (and alongside one when it is): what each list
207
- // holds, how many, and where it came from.
392
+ // holds, how many, and where it came from; the owner's own lists among them.
208
393
  export const PrivacyNameListSchema = z.object({
209
394
  id: z.string().describe("Stable id of the list."),
210
- kind: z.enum(["first-name", "surname", "ambiguous", "title", "never"]).describe("What a word on it says about a name."),
395
+ kind: PrivacyNameListKindSchema.describe("What a word on it says about a name."),
211
396
  languages: z.array(z.enum(["pl", "en", "multi"])).describe("The languages its words come from; multi: many languages at once."),
212
397
  count: z.number().int().describe("How many words it holds."),
213
- matching: z
214
- .enum(["inflected", "as-written"])
215
- .describe("inflected: matched in every grammatical form of a listed word; as-written: matched only exactly as listed."),
398
+ matching: PrivacyNameMatchingSchema.describe(
399
+ "inflected: matched in every grammatical form of a listed word; as-written: matched only exactly as listed.",
400
+ ),
216
401
  source: z.string().describe("Where the words come from: the register or dataset, or that they were written by hand."),
217
402
  url: z.string().optional().describe("The source's page, where it has one."),
218
403
  license: z.string().optional(),
404
+ on: z.boolean().describe("Read by the shield; the owner can switch any list off."),
405
+ own: z.boolean().optional().describe("One of the owner's own lists (policy.dictionary.lists), titled by them."),
406
+ title: z.string().optional().describe("An own list's title."),
219
407
  });
220
408
  export type PrivacyNameList = z.infer<typeof PrivacyNameListSchema>;
221
409
 
@@ -255,3 +443,23 @@ export const PrivacyDictionarySchema = z.object({
255
443
  lookup: PrivacyNameLookupSchema.optional(),
256
444
  });
257
445
  export type PrivacyDictionary = z.infer<typeof PrivacyDictionarySchema>;
446
+
447
+ // What the shield would mask in a text the owner tries it on, with the policy as saved: where each find sits, its kind,
448
+ // and what found it. Nothing is given a token and nothing is kept, so trying a value teaches the vault nothing.
449
+ export const PRIVACY_PREVIEW_MAX = 4000;
450
+
451
+ export const PrivacyPreviewSpanSchema = z.object({
452
+ start: z.number().int(),
453
+ end: z.number().int().describe("Exclusive."),
454
+ class: PersonalDataClassSchema,
455
+ by: z
456
+ .enum(["detector", "rule", "learned", "model"])
457
+ .describe(
458
+ "detector: the built-in detectors and name lists, the owner's lists included; rule: one of the owner's rules; learned: a value the vault knows; model: the local name model.",
459
+ ),
460
+ rule: z.string().optional().describe("The id of the owner's rule that found it."),
461
+ });
462
+ export type PrivacyPreviewSpan = z.infer<typeof PrivacyPreviewSpanSchema>;
463
+
464
+ export const PrivacyPreviewSchema = z.object({ spans: z.array(PrivacyPreviewSpanSchema) });
465
+ export type PrivacyPreview = z.infer<typeof PrivacyPreviewSchema>;
@@ -1,6 +1,7 @@
1
1
  // secrets: user-supplied env-var secrets the daemon writes to desired-state/.env
2
2
  import { BrokerRuleSchema } from "@intentic/extension-manifest";
3
3
  import { z } from "zod";
4
+ import { KEYRING_PROGRAMS_MAX, KeyringPackageSchema, KeyringProgramSchema, KeyringShareModeSchema, KeyringSourceSchema } from "./keyring.js";
4
5
  import { SECRET_HOST_MAX, SECRET_HOST_PATTERN_RE, SECRET_HOSTS_MAX } from "../policy/secret-hosts.js";
5
6
  // Straight to the sandbox daemon, never through the platform; `apply` reloads .env with no restart. `list` returns keys
6
7
  // only; `reveal` is the one owner-only exception that returns a value.
@@ -61,9 +62,9 @@ export const CredentialGateKindSchema = z
61
62
  export type CredentialGateKind = z.infer<typeof CredentialGateKindSchema>;
62
63
 
63
64
  export const CredentialLaneSchema = z
64
- .enum(["shell", "code", "browser", "session", "otp", "gateway", "ssh"])
65
+ .enum(["shell", "code", "browser", "session", "otp", "gateway", "ssh", "device", "desktop"])
65
66
  .describe(
66
- "What the credential was about to be used for: a shell command, a script, typing into a page, mounting a connected account, one one-time code, a request the credential gateway attaches it to, or a signature the sandbox's ssh agent makes with a held key.",
67
+ "What the credential was about to be used for: a shell command, a script, typing into a page, mounting a connected account, one one-time code, a request the credential gateway attaches it to, a signature the sandbox's ssh agent makes with a held key, typing into a window or phone app on one of the owner's computers, or typing into a window on the sandbox's own desktop.",
67
68
  );
68
69
  export type CredentialLane = z.infer<typeof CredentialLaneSchema>;
69
70
 
@@ -121,9 +122,9 @@ export const SecretHostSchema = z
121
122
  );
122
123
 
123
124
  export const SecretHostSourceSchema = z
124
- .enum(["owner", "connector"])
125
+ .enum(["owner", "connector", "keyring"])
125
126
  .describe(
126
- "Who set it: the owner, or the connector the credential belongs to, whose guard is on with its own service's hosts until the owner changes it.",
127
+ "Who set it: the owner, the connector the credential belongs to (whose guard is on with its own service's hosts until the owner changes it), or the keyring on the computer that holds the secret, whose list is changed there.",
127
128
  );
128
129
  export type SecretHostSource = z.infer<typeof SecretHostSourceSchema>;
129
130
 
@@ -208,12 +209,84 @@ export const CredentialPolicySetSchema = z.object({
208
209
  });
209
210
  export type CredentialPolicySet = z.infer<typeof CredentialPolicySetSchema>;
210
211
 
212
+ // The program guard: the host guard's counterpart for typing into windows. A sandbox-held secret typed into a window on
213
+ // one of the owner's computers goes unasked only into a program on its list and, unless `anyField`, only into a field the
214
+ // program marks as a password field; anywhere else it waits for a person. A keyring entry carries its own list, kept on
215
+ // that computer (schemas/keyring.ts), which the sandbox can show and never widen.
216
+ export const SecretProgramsSchema = z.object({
217
+ programs: z.array(KeyringProgramSchema).max(KEYRING_PROGRAMS_MAX).describe("Programs, by executable name or whole path, it may be typed into unasked."),
218
+ packages: z.array(KeyringPackageSchema).max(KEYRING_PROGRAMS_MAX).describe("Android apps, by package name, it may be typed into unasked."),
219
+ anyField: z.boolean().describe("Whether it may go into a field not marked as a password field without asking."),
220
+ source: z.enum(["owner", "keyring"]).describe("Who keeps the list: the owner, here, or the keyring on the computer that holds the secret."),
221
+ });
222
+ export type SecretPrograms = z.infer<typeof SecretProgramsSchema>;
223
+
224
+ export const SecretProgramGuardSchema = SecretProgramsSchema.omit({ source: true }).extend({
225
+ subject: z.string().min(1).describe("Which secret, by the name its reference carries."),
226
+ });
227
+ export type SecretProgramGuard = z.infer<typeof SecretProgramGuardSchema>;
228
+
229
+ export const SecretProgramGuardsSchema = z.object({
230
+ guards: z.array(SecretProgramGuardSchema).describe("Every sandbox-held secret whose program list has been set. Names and programs only, never a value."),
231
+ });
232
+
233
+ export const SecretProgramGuardSetSchema = SecretProgramGuardSchema.extend({
234
+ conversationId: z.string().optional().describe("Which conversation to ask the owner in, when the change needs them. The CLI fills this from the running turn."),
235
+ });
236
+
237
+ export const SecretProgramGuardSetResultSchema = SecretProgramsSchema.omit({ source: true }).extend({
238
+ approvedBy: z.string().optional().describe("Who approved the change, when it needed the owner's click. Absent when nobody had to."),
239
+ });
240
+
241
+ // The owner's Type into…: one secret typed into whatever has focus on one of their computers, after a countdown that
242
+ // leaves time to put the cursor there. Through that computer's keyring, which still asks when its entry's mode says to.
243
+ export const SecretTypeIntoSchema = z.object({
244
+ key: secretKey.describe("Which secret, by name."),
245
+ device: z.string().min(1).describe("Which computer, by its device card (`rog`, or `rog::wsl:archlinux` for a distro's own keyring)."),
246
+ countdownSeconds: z.number().int().min(0).max(10).default(3).describe("How long to wait before typing, for the cursor to be put in place."),
247
+ });
248
+ export const SecretTypeIntoResultSchema = z.object({
249
+ program: z.string().describe("What it was typed into."),
250
+ field: z.enum(["password", "text"]).describe("Whether the focused field was a password field."),
251
+ asked: z.boolean().describe("Whether the person at the computer was asked."),
252
+ });
253
+
254
+ // The keyrings on the owner's computers this sandbox can reach: where a secret can be kept or typed, and which two can be
255
+ // paired to keep one set of entries.
256
+ export const SandboxKeyringSchema = z.object({
257
+ machine: z.string().describe("The computer, by its device card."),
258
+ online: z.boolean().describe("Whether it answers now."),
259
+ locked: z.boolean().optional().describe("Whether its keyring is locked, as last heard. Absent while offline."),
260
+ version: z.string().optional().describe("The keyring's release, as last heard."),
261
+ pendingInbox: z.number().int().nonnegative().optional().describe("Values sent to be kept there that wait for its next unlock."),
262
+ shared: z.number().int().nonnegative().describe("How many of its entries are shared with this sandbox."),
263
+ sync: z
264
+ .object({
265
+ fingerprint: z.string().describe("Its sync key's fingerprint, to compare with what the other computer shows when pairing."),
266
+ peers: z.array(z.string()).describe("The computers it is paired with, by device card."),
267
+ })
268
+ .optional()
269
+ .describe("Its pairing with the owner's other keyrings. Absent while offline or before it was heard."),
270
+ });
271
+ export type SandboxKeyring = z.infer<typeof SandboxKeyringSchema>;
272
+ export const SandboxKeyringsSchema = z.object({ keyrings: z.array(SandboxKeyringSchema) });
273
+
274
+ export const SandboxKeyringPairSchema = z.object({
275
+ a: z.string().min(1).describe("One computer, by its device card."),
276
+ b: z.string().min(1).describe("The other."),
277
+ });
278
+ export const SandboxKeyringPairResultSchema = z.object({
279
+ fingerprints: z.object({ a: z.string(), b: z.string() }).describe("Each keyring's sync-key fingerprint, as both prompts showed them."),
280
+ });
281
+
211
282
  export const SecretInventoryEntrySchema = z.object({
212
283
  // Env-var key for env|generated; `<provider>:<accountId>` for provider; capability instance id otherwise.
213
284
  key: z.string().describe("What identifies it. Unique across the whole inventory, so several accounts of one provider each get their own entry."),
214
285
  kind: z
215
- .enum(["env", "generated", "capability", "provider"])
216
- .describe("Where it came from: you set it, the sandbox generated it, a connection needs it, or it is a model account's credential."),
286
+ .enum(["env", "generated", "capability", "provider", "device"])
287
+ .describe(
288
+ "Where it came from: you set it, the sandbox generated it, a connection needs it, it is a model account's credential, or it lives in the keyring on one of your computers and this sandbox holds only a pointer to it.",
289
+ ),
217
290
  // e.g. "<ProviderName> · <accountLabel>" for provider entries; absent on env/generated.
218
291
  label: z.string().optional().describe("A friendlier name, for entries that have one."),
219
292
  status: z.enum(["missing", "set", "connected"]).describe("Whether it exists and, for a connection, whether it is working."),
@@ -237,9 +310,9 @@ export const SecretInventoryEntrySchema = z.object({
237
310
  .object({
238
311
  at: z.number().describe("When, in milliseconds."),
239
312
  lane: z
240
- .enum(["shell", "code", "browser", "gateway", "ssh"])
313
+ .enum(["shell", "code", "browser", "gateway", "ssh", "device", "desktop"])
241
314
  .describe(
242
- "How it was used: a command, a script, typed into a page, attached by the credential gateway to a request the agent sent, or a key the sandbox's ssh agent signed with for the agent.",
315
+ "How it was used: a command, a script, typed into a page, attached by the credential gateway to a request the agent sent, a key the sandbox's ssh agent signed with for the agent, typed into a window or app on one of the owner's computers, or typed into a window on the sandbox's desktop.",
243
316
  ),
244
317
  detail: z
245
318
  .string()
@@ -272,6 +345,21 @@ export const SecretInventoryEntrySchema = z.object({
272
345
  })
273
346
  .optional()
274
347
  .describe("Its host guard. Absent when none was ever set and no connector sets one: the guard is off."),
348
+ // Joined from the program guards: which windows and phone apps a use may type it into unasked.
349
+ programs: SecretProgramsSchema.optional().describe(
350
+ "Which programs on the owner's computers, and which phone apps, it may be typed into without asking. Absent when none was set: typing it into any window asks first.",
351
+ ),
352
+ // A device row only: the keyring entry the pointer names. Everything here is the keyring's to change, on that computer.
353
+ device: z
354
+ .object({
355
+ machine: z.string().describe("Which computer's keyring holds it, by its device card."),
356
+ mode: KeyringShareModeSchema,
357
+ online: z.boolean().describe("Whether that computer answers now. Offline, an `each-use` or `while-here` secret cannot be used until it does."),
358
+ locked: z.boolean().optional().describe("Whether the keyring there is locked, as last heard. Absent while the computer is offline."),
359
+ source: KeyringSourceSchema,
360
+ })
361
+ .optional()
362
+ .describe("Where a secret held on one of your computers lives and how it is shared. Present on `device` rows only."),
275
363
  });
276
364
  export type SecretInventoryEntry = z.infer<typeof SecretInventoryEntrySchema>;
277
365
  export const SecretInventorySchema = z.object({
@@ -298,6 +298,14 @@ export const SandboxSettingsSchema = z.object({
298
298
  .describe(
299
299
  "Let the agent answer with a page drawn right in the chat: a chart, a table, a diagram, a mock-up, or a small form you answer it on. Off, the agent is not offered the tools at all, so it spends nothing on them.",
300
300
  ),
301
+ // The daemon's shadow keeper (derived/shadow-keeper.ts) and the ripgrep config agents' shells get with it. On by
302
+ // default: it renders without asking any model (OCR excepted, on this machine), one low-priority process at a time.
303
+ documentSearch: z
304
+ .boolean()
305
+ .default(true)
306
+ .describe(
307
+ "Keep the text of every document in the workspace (pdf, Word, OpenDocument, spreadsheets, presentations, epub, and scans where OCR is installed) ready to search. In the background, one low-priority process at a time, each document is rendered to text when it appears or changes, so `rg` and iq find what documents say under their own paths, cited by page. Off, nothing renders until someone opens a document, and searches skip them.",
308
+ ),
301
309
  hashlineEdits: z
302
310
  .boolean()
303
311
  .default(false)
@@ -74,7 +74,9 @@ export type SpeechTranscript = z.infer<typeof SpeechTranscriptSchema>;
74
74
  // streams each one as it is spoken. A text frame `begin` opens phrase `id`; binary frames that follow are its audio,
75
75
  // 16 kHz mono s16le; `end` closes it, keeping its first `keep` samples (the trailing pause trimmed off); `drop`
76
76
  // abandons it. The daemon answers with `partial` text while the phrase is still being spoken, one `final` per ended
77
- // phrase, in order, and `status` whenever the model's readiness moves. The language is the socket's `lang` query.
77
+ // phrase, in order, and `status` whenever the model's readiness moves. The language is the socket's `lang` query;
78
+ // `guesses=0` asks for finals alone, which the wake listener does: nobody reads a running guess at a room's talk, and
79
+ // each one is a decode.
78
80
  export const SpeechClientMessageSchema = z.discriminatedUnion("type", [
79
81
  z.object({ type: z.literal("begin"), id: z.number() }),
80
82
  z.object({ type: z.literal("end"), id: z.number(), keep: z.number() }),
@@ -98,3 +100,38 @@ export type SpeechServerMessage = z.infer<typeof SpeechServerMessageSchema>;
98
100
  // The longest phrase either door takes: a minute at 16 kHz. The composer's segmenter cuts a monologue at the same
99
101
  // length, so nothing it sends is ever refused.
100
102
  export const MAX_SPEECH_SAMPLES = 60 * 16_000;
103
+
104
+ // THE WAKE WORD: what a person says to the editor across the room to be heard without touching it ("Leon, fix the
105
+ // build"). The editor listens for it on a device the person switched it on for, and hears it with this sandbox's own
106
+ // model, so it is a word the model writes down: letters (any script), with a space, hyphen or apostrophe between them.
107
+ export const WAKE_WORD_DEFAULT = "Leon";
108
+ export const WakeWordSchema = z
109
+ .string()
110
+ .trim()
111
+ .regex(/^[\p{L}\p{M}](?:[\p{L}\p{M}' -]{0,38}[\p{L}\p{M}])?$/u)
112
+ .refine((word) => [...word].length >= 2, { message: "a wake word has at least two letters" });
113
+
114
+ // POST /speech/wake: one device's claim on a wake it just heard. Every device listening for the same person hears the
115
+ // same "Leon", and each would open its own session; the first to claim within a few seconds is granted it and the rest
116
+ // stand down. A device keeps its own claims, so its next wake a moment later is never refused by its last one, and a
117
+ // device in the middle of an exchange holds it longer, so a follow-up the other devices also hear stays with it.
118
+ export const WAKE_HOLD_MAX_MS = 10 * 60_000;
119
+ export const SpeechWakeClaimSchema = z.object({
120
+ device: z
121
+ .string()
122
+ .regex(/^[A-Za-z0-9_-]{8,64}$/u)
123
+ .describe("A random id this browser or app minted for itself once, so its own claims are told from another device's."),
124
+ holdMs: z
125
+ .number()
126
+ .int()
127
+ .min(0)
128
+ .max(WAKE_HOLD_MAX_MS)
129
+ .optional()
130
+ .describe("Keep the person's other devices off for this long rather than the few seconds a lone wake holds: an exchange this device is in the middle of."),
131
+ });
132
+ export type SpeechWakeClaim = z.infer<typeof SpeechWakeClaimSchema>;
133
+
134
+ export const SpeechWakeGrantSchema = z.object({
135
+ granted: z.boolean().describe("This device acts on the wake; false when another device of the same person claimed it first."),
136
+ });
137
+ export type SpeechWakeGrant = z.infer<typeof SpeechWakeGrantSchema>;
@@ -208,8 +208,11 @@ export type DaemonSession = z.infer<typeof DaemonSessionSchema>;
208
208
  // How a session's identity was proven. A session carries the set it was minted from; the require-passkey policy reads
209
209
  // it on every request, so a session minted before the switch stops working on its next call. `email` is the platform's
210
210
  // signed word that the person controls the address (policy/identity-assertion.ts), accepted only where the owner opted
211
- // that person in, and on hosted machines.
212
- export const ProofMethodSchema = z.enum(["google", "ticket", "passkey", "recovery", "email"]);
211
+ // that person in, and on hosted machines. `host` is the machine that runs the sandbox: a session `ic sandbox session`
212
+ // minted through `docker exec` for the bound owner, so the desktop app and Intentic OS reach their own sandboxes with no
213
+ // network (the daemon's auth/host/host-session.ts). It is never a sign-in a browser can offer, so no daemon lists it in
214
+ // `signIn.proofs`.
215
+ export const ProofMethodSchema = z.enum(["google", "ticket", "passkey", "recovery", "email", "host"]);
213
216
  export type ProofMethod = z.infer<typeof ProofMethodSchema>;
214
217
 
215
218
  // The `reason` a daemon's refusal of an identity assertion carries beside its `error`, so the editor can say what to do:
@@ -191,7 +191,8 @@ export type DesktopState = z.infer<typeof DesktopStateSchema>;
191
191
  // turn, be steered, and land its own work.
192
192
  // id is the spawning tool call's id for an in-process one, its own conversation id for a spawned one. The kind changes
193
193
  // only where its record is read from and what else can be done with it, never how it looks.
194
- export const SubagentKindSchema = z.enum(["subagent", "spawned"]);
194
+ // `linked`: a task handed to another of the owner's sandboxes over a link; it runs and lands there, never in this checkout.
195
+ export const SubagentKindSchema = z.enum(["subagent", "spawned", "linked"]);
195
196
  export type SubagentKind = z.infer<typeof SubagentKindSchema>;
196
197
  // pending/running/blocked/paused are live, the rest terminal. Uses the SDK's own task vocabulary rather than AgentStatus.
197
198
  // `blocked` is one addition: a spawned child's question/permission/plan card raises it. `paused` the other: a spawned
@@ -225,7 +226,7 @@ export const SubagentSessionSchema = z.object({
225
226
  "The id of the tool call that started it (an SDK child) or the child's own conversation id (a spawned one); either way both sides already hold it, so a card links to its subagent with the id it has and the subagent points back the same way.",
226
227
  ),
227
228
  kind: SubagentKindSchema.describe(
228
- "How it was started: in-process by the runtime's own Agent/Task tool, or spawned by the daemon as a conversation of its own, on any provider. It changes where its record is read from and what else can be done with it, never what it is.",
229
+ "How it was started: in-process by the runtime's own Agent/Task tool, spawned by the daemon as a conversation of its own, on any provider, or handed to another of the owner's sandboxes over a link, where it runs and lands. It changes where its record is read from and what else can be done with it, never what it is.",
229
230
  ),
230
231
  // The conversation whose turn started it; how a card links back to the chat it belongs to.
231
232
  conversationId: z.string().describe("The conversation whose turn started it, and the way back to the chat it belongs to."),
@@ -279,6 +280,16 @@ export const SubagentSessionSchema = z.object({
279
280
  error: z.string().optional().describe("Why it failed, when it did."),
280
281
  // Filled once the subagent ends; a standing read mid-work would judge a job not yet done.
281
282
  verification: SubagentVerificationSchema.optional().describe("Whether anything proved the work its report describes."),
283
+ // A `linked` one's far end: where its conversation lives, so a row opens it there rather than here.
284
+ linked: z
285
+ .object({
286
+ link: z.string().describe("The link card it went through: the agent's name for that sandbox."),
287
+ sandbox: z.string().describe("What that sandbox is called."),
288
+ sandboxId: z.string().optional().describe("That sandbox's id on the owner's account, when the card knows it."),
289
+ conversationId: z.string().describe("The conversation it opened there."),
290
+ })
291
+ .optional()
292
+ .describe("For a task handed to another sandbox: which one, and the conversation it runs in there."),
282
293
  });
283
294
  export type SubagentSession = z.infer<typeof SubagentSessionSchema>;
284
295
  export const SubagentsListSchema = z.object({
@@ -1,4 +1,5 @@
1
1
  import { z } from "zod";
2
+ import { WAKE_WORD_DEFAULT, WakeWordSchema } from "./speech.js";
2
3
 
3
4
  // Speaking a reply aloud: which voices a sandbox can speak with, which one speaks a language, and the live wire the
4
5
  // editor plays speech over. The daemon's engine is src/speech/voice; the editor's half is
@@ -60,6 +61,16 @@ export const VoiceSettingsSchema = z.object({
60
61
  cloud: CloudVoiceSchema.optional().describe(
61
62
  "A speech provider of your own, used for every language it speaks unless you picked a voice on this sandbox for it. Its key is a stored secret; text it speaks leaves the sandbox for that provider.",
62
63
  ),
64
+ wake: z
65
+ .object({
66
+ word: WakeWordSchema.default(WAKE_WORD_DEFAULT).describe(
67
+ "The name you call the agent by. Two or three syllables wake it reliably; a short or everyday word wakes it by mistake.",
68
+ ),
69
+ })
70
+ .prefault({})
71
+ .describe(
72
+ "Talking to the agent from across the room: say this word and what you want, on a device where listening for it is switched on. Each device is switched on by its own reader, since it is that device's microphone.",
73
+ ),
63
74
  });
64
75
  export type VoiceSettings = z.infer<typeof VoiceSettingsSchema>;
65
76
 
@@ -1,7 +1,7 @@
1
1
  // webext: the user's own browser (the `webext` capability's live half)
2
2
  // Named imports rather than the `z` namespace: this module is bundled into the browser extension, where the
3
3
  // namespace keeps zod's 60 locales (~250 kB) that esbuild can otherwise drop. _devices/webext/scripts/size-budget.mjs holds the ceiling.
4
- import { array, boolean, enum as zEnum, number, object, string } from "zod";
4
+ import { array, boolean, enum as zEnum, literal, number, object, string, union } from "zod";
5
5
  import type * as z from "zod";
6
6
  // The card's switches, here rather than in capabilities.ts because the extension bundles this module and would
7
7
  // otherwise ship every other capability's schema to the store.
@@ -105,3 +105,18 @@ export const WebExtSessionExportResultSchema = object({
105
105
  cookies: array(WebExtCookieSchema).optional(),
106
106
  });
107
107
  export type WebExtSessionExportResult = z.infer<typeof WebExtSessionExportResultSchema>;
108
+
109
+ // The extension collecting a secret it was handed a ticket to type (webextSecretUrl): the ticket, and the page it is
110
+ // about to type into, which the daemon's host guard judges. The value never rides a request, only the answer.
111
+ export const WebExtSecretRequestSchema = object({
112
+ ticket: string().min(1),
113
+ url: string().min(1),
114
+ // A password input or any other text field, which an approval card names.
115
+ field: zEnum(["password", "text"]),
116
+ });
117
+ export type WebExtSecretRequest = z.infer<typeof WebExtSecretRequestSchema>;
118
+
119
+ // The value for that one field, read only by the extension and written straight into the page; or the sentence why
120
+ // not, which reaches the agent as the tool's refusal.
121
+ export const WebExtSecretAnswerSchema = union([object({ ok: literal(true), value: string() }), object({ ok: literal(false), message: string() })]);
122
+ export type WebExtSecretAnswer = z.infer<typeof WebExtSecretAnswerSchema>;
@@ -18,9 +18,9 @@ const ACCESS_MOVED =
18
18
  // Needs 30s: serializing ~500 schemas can run past the unit hang detector under load.
19
19
  test(
20
20
  "contract.lock.json matches the schemas this package exports",
21
- () => {
21
+ async () => {
22
22
  const committed = committedText();
23
- const current = currentLock();
23
+ const current = await currentLock();
24
24
  // Named first, so a reach change says which route before the whole-lock diff does.
25
25
  expect(accessChanges(committed, serializeLock(current)), ACCESS_MOVED).toEqual([]);
26
26
  expect(
@@ -35,8 +35,8 @@ test(
35
35
 
36
36
  // The wire outside oRPC is pinned the same way: a changed tunnel path, a gone control message or a renamed verdict is a
37
37
  // shrink the push gate and the landing draft both flag, where before nothing could see it.
38
- test("the lock flags a change to the wire no oRPC route carries", () => {
39
- const base = currentLock() as Record<string, Record<string, unknown>>;
38
+ test("the lock flags a change to the wire no oRPC route carries", async () => {
39
+ const base = (await currentLock()) as Record<string, Record<string, unknown>>;
40
40
  const moved = structuredClone(base);
41
41
  (moved["wire:tunnel"] as { path: string }).path = "/tunnel/v3";
42
42
  delete (moved["wire:browser-wire"] as { webTransport?: unknown }).webTransport;
@@ -45,7 +45,7 @@ test("the lock flags a change to the wire no oRPC route carries", () => {
45
45
  });
46
46
 
47
47
  // The lock as it would be with one raw route declared otherwise: the declaration is swapped in place, and back.
48
- const lockWith = (name: string, change: (declared: RouteMeta) => RouteMeta): Record<string, unknown> => {
48
+ const lockWith = async (name: string, change: (declared: RouteMeta) => RouteMeta): Promise<Record<string, unknown>> => {
49
49
  const route = RAW_ROUTE_LIST.find((candidate) => candidate.name === name);
50
50
  if (route === undefined) {
51
51
  throw new Error(`${name} is gone; pick another raw route with a maintainer floor and no grants`);
@@ -53,7 +53,7 @@ const lockWith = (name: string, change: (declared: RouteMeta) => RouteMeta): Rec
53
53
  const declared = route.meta;
54
54
  Reflect.set(route, "meta", change(declared));
55
55
  try {
56
- return currentLock();
56
+ return await currentLock();
57
57
  } finally {
58
58
  Reflect.set(route, "meta", declared);
59
59
  }
@@ -63,18 +63,18 @@ const ROUTE = "POST /system/sync/report";
63
63
 
64
64
  // A lowered floor on any route, not only the ones route-reach.test.ts pins: the lock check names it, and the push gate and
65
65
  // the landing draft read it as a shrink that needs a declared break.
66
- test("lowering one route's floor fails the lock check, naming the route", () => {
67
- const committed = currentLock();
68
- const lowered = lockWith(ROUTE, (declared) => ({ ...declared, floor: "viewer" }));
66
+ test("lowering one route's floor fails the lock check, naming the route", async () => {
67
+ const committed = await currentLock();
68
+ const lowered = await lockWith(ROUTE, (declared) => ({ ...declared, floor: "viewer" }));
69
69
  expect(accessChanges(serializeLock(committed), serializeLock(lowered))).toEqual([`${ROUTE}: floor "maintainer" → "viewer"`]);
70
70
  expect(shrunkSurfaces(committed, lowered)).toEqual([`access:${ROUTE}.floor`]);
71
71
  });
72
72
 
73
73
  // A grant a route never declared still has a value in the lock, so granting it is a changed value, not growth that
74
74
  // passes; taking one away reads the same way, since it locks out a caller that relied on it.
75
- test("granting guest or agent reach reads as a shrink, and so does taking it back", () => {
76
- const committed = currentLock();
77
- const granted = lockWith(ROUTE, (declared) => ({ ...declared, guest: true, agent: true }));
75
+ test("granting guest or agent reach reads as a shrink, and so does taking it back", async () => {
76
+ const committed = await currentLock();
77
+ const granted = await lockWith(ROUTE, (declared) => ({ ...declared, guest: true, agent: true }));
78
78
  expect(accessChanges(serializeLock(committed), serializeLock(granted))).toEqual([`${ROUTE}: agent false → true, guest false → true`]);
79
79
  expect(shrunkSurfaces(committed, granted)).toEqual([`access:${ROUTE}.agent`, `access:${ROUTE}.guest`]);
80
80
  expect(shrunkSurfaces(granted, committed)).toEqual([`access:${ROUTE}.agent`, `access:${ROUTE}.guest`]);