@intentic/sandbox-contract 1.159.0 → 1.161.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 (111) hide show
  1. package/dist/agent-catalog.d.ts +1 -0
  2. package/dist/agent-catalog.d.ts.map +1 -1
  3. package/dist/agent-catalog.js +1 -0
  4. package/dist/agent-catalog.js.map +1 -1
  5. package/dist/contracts/agent.contract.d.ts +50 -4
  6. package/dist/contracts/agent.contract.d.ts.map +1 -1
  7. package/dist/contracts/agent.contract.js +2 -1
  8. package/dist/contracts/agent.contract.js.map +1 -1
  9. package/dist/contracts/agents.contract.d.ts +195 -0
  10. package/dist/contracts/agents.contract.d.ts.map +1 -1
  11. package/dist/contracts/agents.contract.js +7 -2
  12. package/dist/contracts/agents.contract.js.map +1 -1
  13. package/dist/contracts/claude.contract.d.ts +26 -0
  14. package/dist/contracts/claude.contract.d.ts.map +1 -1
  15. package/dist/contracts/claude.contract.js +2 -1
  16. package/dist/contracts/claude.contract.js.map +1 -1
  17. package/dist/contracts/extensions.contract.d.ts +1 -0
  18. package/dist/contracts/extensions.contract.d.ts.map +1 -1
  19. package/dist/contracts/git.contract.d.ts +13 -0
  20. package/dist/contracts/git.contract.d.ts.map +1 -1
  21. package/dist/contracts/git.contract.js +2 -1
  22. package/dist/contracts/git.contract.js.map +1 -1
  23. package/dist/contracts/grok.contract.d.ts +2 -0
  24. package/dist/contracts/grok.contract.d.ts.map +1 -1
  25. package/dist/contracts/kimi.contract.d.ts +26 -0
  26. package/dist/contracts/kimi.contract.d.ts.map +1 -1
  27. package/dist/contracts/kimi.contract.js +2 -1
  28. package/dist/contracts/kimi.contract.js.map +1 -1
  29. package/dist/contracts/memory.contract.d.ts +34 -0
  30. package/dist/contracts/memory.contract.d.ts.map +1 -0
  31. package/dist/contracts/memory.contract.js +9 -0
  32. package/dist/contracts/memory.contract.js.map +1 -0
  33. package/dist/contracts/panels.contract.d.ts +1 -0
  34. package/dist/contracts/panels.contract.d.ts.map +1 -1
  35. package/dist/contracts/push.contract.d.ts +1 -1
  36. package/dist/contracts/push.contract.js +2 -2
  37. package/dist/contracts/push.contract.js.map +1 -1
  38. package/dist/contracts/sessions.contract.d.ts +2 -0
  39. package/dist/contracts/sessions.contract.d.ts.map +1 -1
  40. package/dist/contracts/settings.contract.d.ts +72 -15
  41. package/dist/contracts/settings.contract.d.ts.map +1 -1
  42. package/dist/contracts/settings.contract.js +3 -2
  43. package/dist/contracts/settings.contract.js.map +1 -1
  44. package/dist/contracts/system.contract.d.ts +15 -2
  45. package/dist/contracts/system.contract.d.ts.map +1 -1
  46. package/dist/contracts/system.contract.js +2 -1
  47. package/dist/contracts/system.contract.js.map +1 -1
  48. package/dist/contracts/translator.contract.d.ts +13 -3
  49. package/dist/contracts/translator.contract.d.ts.map +1 -1
  50. package/dist/contracts/translator.contract.js +1 -1
  51. package/dist/contracts/translator.contract.js.map +1 -1
  52. package/dist/contracts/usage.contract.js +2 -2
  53. package/dist/contracts/usage.contract.js.map +1 -1
  54. package/dist/contracts/workspace.contract.d.ts +38 -5
  55. package/dist/contracts/workspace.contract.d.ts.map +1 -1
  56. package/dist/contracts/workspace.contract.js +2 -1
  57. package/dist/contracts/workspace.contract.js.map +1 -1
  58. package/dist/events.d.ts +124 -2
  59. package/dist/events.d.ts.map +1 -1
  60. package/dist/events.js +21 -3
  61. package/dist/events.js.map +1 -1
  62. package/dist/index.d.ts +491 -30
  63. package/dist/index.d.ts.map +1 -1
  64. package/dist/index.js +5 -0
  65. package/dist/index.js.map +1 -1
  66. package/dist/model-order.d.ts +1 -0
  67. package/dist/model-order.d.ts.map +1 -1
  68. package/dist/model-order.js +1 -0
  69. package/dist/model-order.js.map +1 -1
  70. package/dist/path-refs.d.ts +1 -0
  71. package/dist/path-refs.d.ts.map +1 -1
  72. package/dist/path-refs.js +3 -0
  73. package/dist/path-refs.js.map +1 -1
  74. package/dist/quick-model.d.ts +13 -0
  75. package/dist/quick-model.d.ts.map +1 -0
  76. package/dist/quick-model.js +33 -0
  77. package/dist/quick-model.js.map +1 -0
  78. package/dist/schemas.d.ts +374 -14
  79. package/dist/schemas.d.ts.map +1 -1
  80. package/dist/schemas.js +145 -15
  81. package/dist/schemas.js.map +1 -1
  82. package/dist/title.d.ts +6 -0
  83. package/dist/title.d.ts.map +1 -0
  84. package/dist/title.js +149 -0
  85. package/dist/title.js.map +1 -0
  86. package/package.json +2 -2
  87. package/src/agent-catalog.ts +6 -0
  88. package/src/contracts/agent.contract.ts +14 -1
  89. package/src/contracts/agents.contract.ts +25 -1
  90. package/src/contracts/claude.contract.ts +4 -1
  91. package/src/contracts/git.contract.ts +6 -0
  92. package/src/contracts/kimi.contract.ts +13 -2
  93. package/src/contracts/memory.contract.ts +13 -0
  94. package/src/contracts/push.contract.ts +2 -2
  95. package/src/contracts/settings.contract.ts +16 -4
  96. package/src/contracts/system.contract.ts +4 -0
  97. package/src/contracts/translator.contract.ts +3 -2
  98. package/src/contracts/usage.contract.ts +2 -2
  99. package/src/contracts/workspace.contract.ts +6 -0
  100. package/src/events.ts +85 -6
  101. package/src/index.ts +7 -1
  102. package/src/model-order.test.ts +40 -1
  103. package/src/model-order.ts +15 -0
  104. package/src/path-refs.test.ts +27 -1
  105. package/src/path-refs.ts +16 -0
  106. package/src/quick-model.test.ts +88 -0
  107. package/src/quick-model.ts +103 -0
  108. package/src/schemas.test.ts +30 -1
  109. package/src/schemas.ts +500 -42
  110. package/src/title.test.ts +130 -0
  111. package/src/title.ts +254 -0
@@ -0,0 +1,103 @@
1
+ import { ACCESS_COST, accessFor, PROVIDERS } from "./agent-catalog.js";
2
+ import { compareCheapestFirst, familyOf, tierRankOf } from "./model-order.js";
3
+ import type { NativeProvider } from "./schemas.js";
4
+
5
+ /* THE QUICK MODEL — the cheap, fast model a one-click helper spends instead of the frontier model the chat runs
6
+ * on. Today that is the commit box's autofill; anything else of that shape (a branch name, a PR description)
7
+ * reads the same answer, which is the reason this is a `quickModel` setting rather than a commit-message one.
8
+ *
9
+ * The rule lives in the contract because BOTH sides need the same answer for different jobs: the daemon runs
10
+ * the model, and the browser has to NAME it — in the sparkle's tooltip and in the settings row's "Auto (…)"
11
+ * label — before anything has been run. Two implementations would drift precisely where it matters most, since
12
+ * a tooltip promising Haiku while the daemon bills Opus is worse than no tooltip.
13
+ *
14
+ * The default is DERIVED, NEVER STORED. `quickModel` ships as "" and that empty string means "work it out from
15
+ * whatever is connected right now", so connecting a Google account tomorrow improves the default by itself and
16
+ * disconnecting the pinned provider degrades to Auto instead of to a dead button. Same instinct as the rest of
17
+ * this repo's model handling: model-order.ts derives tier and recency from the id and curates nothing, and the
18
+ * web's defaultModelFor reads the live catalog rather than naming an id that a release will falsify. */
19
+
20
+ // One provider's standing in the decision: whether a turn on it can be sent at all, and what its catalog holds.
21
+ // ACP agents are deliberately not expressible here — an ACP row's model id is empty because the agent owns its
22
+ // own model, so there is no cheap rung to point it at.
23
+ export interface QuickModelSource {
24
+ readonly provider: NativeProvider;
25
+ // The same connection predicate every other surface gates on (access.ts web-side, the daemon's own account
26
+ // stores daemon-side). A catalog is never empty by construction, so "has rows" says nothing about "can send".
27
+ readonly ready: boolean;
28
+ readonly models: readonly string[];
29
+ }
30
+
31
+ export interface QuickModelChoice {
32
+ readonly provider: NativeProvider;
33
+ readonly model: string;
34
+ }
35
+
36
+ // A pinned selection on the wire: `${provider}:${modelId}`, the same key shape the model picker already mints
37
+ // for its entries (PickerEntry.key). Empty ⇒ Auto.
38
+ export const quickModelKey = (choice: QuickModelChoice): string => `${choice.provider}:${choice.model}`;
39
+
40
+ // Split on the FIRST colon only: a provider id never contains one and a model id might.
41
+ const parsePinned = (pinned: string): QuickModelChoice | undefined => {
42
+ const separator = pinned.indexOf(`:`);
43
+ if (separator <= 0 || separator === pinned.length - 1) {
44
+ return undefined;
45
+ }
46
+ return { provider: pinned.slice(0, separator) as NativeProvider, model: pinned.slice(separator + 1) };
47
+ };
48
+
49
+ // The cheapest row a provider publishes — its whole catalog read from the cheap end. Undefined for a catalog
50
+ // that hasn't loaded yet, which is a real state: every provider serves a floor, but only once something has
51
+ // asked it.
52
+ const cheapestOf = (source: QuickModelSource): string | undefined => source.models.toSorted(compareCheapestFirst)[0];
53
+
54
+ // Where a provider's cheapest row sits on the shared tier scale, and therefore how well it answers the question
55
+ // this whole module asks. UNRANKED (-1) is a genuine last place: it means the id carries no tier word we know,
56
+ // so the row is the provider's base line rather than its budget one.
57
+ const tierOf = (model: string): number => tierRankOf(familyOf(model));
58
+
59
+ // PROVIDERS order, as the final tiebreak. Arbitrary, but the SAME arbitrary answer on every read — the property
60
+ // compareUnrankedModelIds exists to guarantee, and the one a default actually needs.
61
+ const providerOrder = (provider: NativeProvider): number => PROVIDERS.findIndex((entry) => entry.value === provider);
62
+
63
+ // How much a call on this provider costs at the margin. Every native provider declares an access kind, so the
64
+ // fallback is unreachable — it exists because AgentProvider is a bare string on the wire.
65
+ const costOf = (provider: NativeProvider): number => {
66
+ const access = accessFor(provider);
67
+ return access === undefined ? ACCESS_COST.key : ACCESS_COST[access.kind];
68
+ };
69
+
70
+ /* Which model a quick helper runs, given what this sandbox has connected. `pinned` is the stored setting.
71
+ *
72
+ * A pin only holds while its provider is READY: an account the user disconnected would otherwise leave the
73
+ * button failing on every click with a credential error, when the sandbox can plainly still answer. Auto taking
74
+ * over is the same move the composer already makes when a live catalog stops offering the selected model.
75
+ *
76
+ * Auto ranks candidates on TIER FIRST, then cost. That order is the point of the feature: the helper exists to
77
+ * not be the frontier model, so a free flagship is still the wrong tool, while a free Haiku-class row and a
78
+ * subscription Haiku-class row differ only in whose quota they spend. Cost then breaks that tie towards the
79
+ * channel the user is not paying per token for — and against the one they are.
80
+ *
81
+ * Undefined when nothing is connected: the caller renders a disabled control that says so, rather than a live
82
+ * button that fails on click. */
83
+ export const resolveQuickModel = (sources: readonly QuickModelSource[], pinned: string): QuickModelChoice | undefined => {
84
+ const requested = parsePinned(pinned);
85
+ if (requested !== undefined && sources.some((source) => source.provider === requested.provider && source.ready)) {
86
+ // Taken verbatim, unvalidated against the catalog on purpose: the picker already offers a custom-id
87
+ // escape hatch for a model a catalog hasn't caught up with, and second-guessing the user's own id here
88
+ // would silently run a different model than the settings row names.
89
+ return requested;
90
+ }
91
+ const candidates = sources
92
+ .filter((source) => source.ready)
93
+ .flatMap((source) => {
94
+ const model = cheapestOf(source);
95
+ return model === undefined ? [] : [{ provider: source.provider, model }];
96
+ });
97
+ return candidates.toSorted(
98
+ (left, right) =>
99
+ tierOf(right.model) - tierOf(left.model) ||
100
+ costOf(left.provider) - costOf(right.provider) ||
101
+ providerOrder(left.provider) - providerOrder(right.provider),
102
+ )[0];
103
+ };
@@ -18,7 +18,18 @@ test("a payload from a build that predates a toggle parses, with the new toggle
18
18
  outputCleaners: "-cap",
19
19
  outputHoldout: 0.1,
20
20
  };
21
- expect(SandboxSettingsSchema.parse(older)).toEqual({ ...older, filterBackend: "native", agentRetentionDays: 3 });
21
+ expect(SandboxSettingsSchema.parse(older)).toEqual({
22
+ ...older,
23
+ terseHoldout: 0,
24
+ filterBackend: "native",
25
+ systemPromptMode: "intentic",
26
+ systemPrompt: "",
27
+ quickModel: "",
28
+ agentRetentionDays: 3,
29
+ autoLand: true,
30
+ autoResumeOnLimit: false,
31
+ resumeAfterOutage: true,
32
+ });
22
33
  });
23
34
 
24
35
  test("an empty object is the full default settings object", () => {
@@ -27,17 +38,35 @@ test("an empty object is the full default settings object", () => {
27
38
  skills: [],
28
39
  hashlineEdits: false,
29
40
  terseOutput: false,
41
+ // Off: the steer's turn-level control spends the tokens it measures, so measuring is opt-in.
42
+ terseHoldout: 0,
30
43
  iqSearch: false,
31
44
  outputCleaners: "off",
32
45
  outputHoldout: 0,
33
46
  filterBackend: "native",
47
+ // The default base is Intentic's own prompt; the text field is only read under "custom".
48
+ systemPromptMode: "intentic",
49
+ systemPrompt: "",
50
+ // Empty is not "no quick model" — it is Auto, resolved from the connected accounts on every read
51
+ // (quick-model.ts). Storing a resolved id as the default would name a provider a fresh sandbox has no
52
+ // credential for, and would go stale the moment one is connected.
53
+ quickModel: "",
34
54
  // The one default that isn't "off": the fleet board's Finished lane has no exit of its own, and each
35
55
  // card it holds is a worktree checkout. Opting INTO cleanup would mean shipping a leak by default.
36
56
  agentRetentionDays: 3,
57
+ // On because it is the historical behaviour — defaulting off would silently hold every existing
58
+ // sandbox's finished work on branches nobody is watching.
59
+ autoLand: true,
60
+ autoResumeOnLimit: false,
61
+ // On, unlike the limit resume beside it: an outage resume spends nothing the dead turn hadn't already
62
+ // committed, and the turns it saves are the unattended ones nobody is watching to restart by hand.
63
+ resumeAfterOutage: true,
37
64
  });
38
65
  });
39
66
 
40
67
  test("a key of the wrong type is still a parse failure — tolerance is for absence, not for garbage", () => {
41
68
  expect(SandboxSettingsSchema.safeParse({ iqSearch: "yes" }).success).toBe(false);
42
69
  expect(SandboxSettingsSchema.safeParse({ outputHoldout: 4 }).success).toBe(false);
70
+ // The prompt cap is a real bound, not advice: the text IS the system prompt, and every turn pays for it.
71
+ expect(SandboxSettingsSchema.safeParse({ systemPrompt: "x".repeat(20001) }).success).toBe(false);
43
72
  });