@intentic/sandbox-contract 1.226.0 → 1.227.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/activity.contract.d.ts.map +1 -1
- package/dist/contracts/activity.contract.js +17 -2
- package/dist/contracts/activity.contract.js.map +1 -1
- package/dist/contracts/agent.contract.d.ts +19 -2
- package/dist/contracts/agent.contract.d.ts.map +1 -1
- package/dist/contracts/agent.contract.js +71 -8
- package/dist/contracts/agent.contract.js.map +1 -1
- package/dist/contracts/agents.contract.d.ts +59 -0
- package/dist/contracts/agents.contract.d.ts.map +1 -1
- package/dist/contracts/agents.contract.js +164 -19
- package/dist/contracts/agents.contract.js.map +1 -1
- package/dist/contracts/automations.contract.d.ts.map +1 -1
- package/dist/contracts/automations.contract.js +78 -9
- package/dist/contracts/automations.contract.js.map +1 -1
- package/dist/contracts/capabilities.contract.d.ts.map +1 -1
- package/dist/contracts/capabilities.contract.js +98 -11
- package/dist/contracts/capabilities.contract.js.map +1 -1
- package/dist/contracts/chores.contract.d.ts.map +1 -1
- package/dist/contracts/chores.contract.js +26 -3
- package/dist/contracts/chores.contract.js.map +1 -1
- package/dist/contracts/ci.contract.d.ts.map +1 -1
- package/dist/contracts/ci.contract.js +52 -6
- package/dist/contracts/ci.contract.js.map +1 -1
- package/dist/contracts/claude.contract.d.ts.map +1 -1
- package/dist/contracts/claude.contract.js +44 -5
- package/dist/contracts/claude.contract.js.map +1 -1
- package/dist/contracts/drafts.contract.d.ts.map +1 -1
- package/dist/contracts/drafts.contract.js +26 -3
- package/dist/contracts/drafts.contract.js.map +1 -1
- package/dist/contracts/endpoints.contract.d.ts +1 -0
- package/dist/contracts/endpoints.contract.d.ts.map +1 -1
- package/dist/contracts/endpoints.contract.js +17 -2
- package/dist/contracts/endpoints.contract.js.map +1 -1
- package/dist/contracts/exit.contract.d.ts.map +1 -1
- package/dist/contracts/exit.contract.js +62 -7
- package/dist/contracts/exit.contract.js.map +1 -1
- package/dist/contracts/extensions.contract.d.ts +1 -0
- package/dist/contracts/extensions.contract.d.ts.map +1 -1
- package/dist/contracts/extensions.contract.js +124 -15
- package/dist/contracts/extensions.contract.js.map +1 -1
- package/dist/contracts/git.contract.d.ts.map +1 -1
- package/dist/contracts/git.contract.js +384 -43
- package/dist/contracts/git.contract.js.map +1 -1
- package/dist/contracts/grok.contract.d.ts.map +1 -1
- package/dist/contracts/grok.contract.js +25 -3
- package/dist/contracts/grok.contract.js.map +1 -1
- package/dist/contracts/history.contract.d.ts.map +1 -1
- package/dist/contracts/history.contract.js +35 -4
- package/dist/contracts/history.contract.js.map +1 -1
- package/dist/contracts/intentic.contract.d.ts.map +1 -1
- package/dist/contracts/intentic.contract.js +25 -3
- package/dist/contracts/intentic.contract.js.map +1 -1
- package/dist/contracts/inventory.contract.d.ts.map +1 -1
- package/dist/contracts/inventory.contract.js +26 -3
- package/dist/contracts/inventory.contract.js.map +1 -1
- package/dist/contracts/logs.contract.d.ts +17 -0
- package/dist/contracts/logs.contract.d.ts.map +1 -1
- package/dist/contracts/logs.contract.js +27 -3
- package/dist/contracts/logs.contract.js.map +1 -1
- package/dist/contracts/loops.contract.d.ts.map +1 -1
- package/dist/contracts/loops.contract.js +52 -6
- package/dist/contracts/loops.contract.js.map +1 -1
- package/dist/contracts/panels.contract.d.ts.map +1 -1
- package/dist/contracts/panels.contract.js +26 -3
- package/dist/contracts/panels.contract.js.map +1 -1
- package/dist/contracts/personas.contract.d.ts.map +1 -1
- package/dist/contracts/personas.contract.js +71 -8
- package/dist/contracts/personas.contract.js.map +1 -1
- package/dist/contracts/ports.contract.d.ts.map +1 -1
- package/dist/contracts/ports.contract.js +26 -3
- package/dist/contracts/ports.contract.js.map +1 -1
- package/dist/contracts/prepush.contract.d.ts.map +1 -1
- package/dist/contracts/prepush.contract.js +24 -3
- package/dist/contracts/prepush.contract.js.map +1 -1
- package/dist/contracts/providers.contract.d.ts +1 -0
- package/dist/contracts/providers.contract.d.ts.map +1 -1
- package/dist/contracts/providers.contract.js +9 -1
- package/dist/contracts/providers.contract.js.map +1 -1
- package/dist/contracts/public.contract.d.ts.map +1 -1
- package/dist/contracts/public.contract.js +26 -3
- package/dist/contracts/public.contract.js.map +1 -1
- package/dist/contracts/push.contract.d.ts.map +1 -1
- package/dist/contracts/push.contract.js +35 -4
- package/dist/contracts/push.contract.js.map +1 -1
- package/dist/contracts/secrets.contract.d.ts.map +1 -1
- package/dist/contracts/secrets.contract.js +43 -5
- package/dist/contracts/secrets.contract.js.map +1 -1
- package/dist/contracts/sessions.contract.d.ts +3 -0
- package/dist/contracts/sessions.contract.d.ts.map +1 -1
- package/dist/contracts/sessions.contract.js +15 -2
- package/dist/contracts/sessions.contract.js.map +1 -1
- package/dist/contracts/settings.contract.d.ts +19 -0
- package/dist/contracts/settings.contract.d.ts.map +1 -1
- package/dist/contracts/settings.contract.js +43 -5
- package/dist/contracts/settings.contract.js.map +1 -1
- package/dist/contracts/share.contract.d.ts.map +1 -1
- package/dist/contracts/share.contract.js +35 -4
- package/dist/contracts/share.contract.js.map +1 -1
- package/dist/contracts/skills.contract.d.ts.map +1 -1
- package/dist/contracts/skills.contract.js +35 -4
- package/dist/contracts/skills.contract.js.map +1 -1
- package/dist/contracts/system.contract.d.ts +7 -0
- package/dist/contracts/system.contract.d.ts.map +1 -1
- package/dist/contracts/system.contract.js +107 -14
- package/dist/contracts/system.contract.js.map +1 -1
- package/dist/contracts/translator.contract.d.ts.map +1 -1
- package/dist/contracts/translator.contract.js +29 -4
- package/dist/contracts/translator.contract.js.map +1 -1
- package/dist/contracts/usage.contract.d.ts.map +1 -1
- package/dist/contracts/usage.contract.js +9 -1
- package/dist/contracts/usage.contract.js.map +1 -1
- package/dist/contracts/vpn.contract.d.ts.map +1 -1
- package/dist/contracts/vpn.contract.js +32 -4
- package/dist/contracts/vpn.contract.js.map +1 -1
- package/dist/contracts/workflows.contract.d.ts.map +1 -1
- package/dist/contracts/workflows.contract.js +70 -8
- package/dist/contracts/workflows.contract.js.map +1 -1
- package/dist/contracts/workspace.contract.d.ts.map +1 -1
- package/dist/contracts/workspace.contract.js +216 -25
- package/dist/contracts/workspace.contract.js.map +1 -1
- package/dist/events.d.ts +46 -0
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js +193 -88
- package/dist/events.js.map +1 -1
- package/dist/history-state.d.ts.map +1 -1
- package/dist/history-state.js +1 -0
- package/dist/history-state.js.map +1 -1
- package/dist/index.d.ts +197 -70
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/prompt-complexity.d.ts +9 -1
- package/dist/prompt-complexity.d.ts.map +1 -1
- package/dist/prompt-complexity.js +13 -5
- package/dist/prompt-complexity.js.map +1 -1
- package/dist/request-id.d.ts +3 -0
- package/dist/request-id.d.ts.map +1 -0
- package/dist/request-id.js +3 -0
- package/dist/request-id.js.map +1 -0
- package/dist/schemas.d.ts +89 -0
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +2912 -1276
- package/dist/schemas.js.map +1 -1
- package/dist/starter.d.ts +3 -0
- package/dist/starter.d.ts.map +1 -0
- package/dist/starter.js +3 -0
- package/dist/starter.js.map +1 -0
- package/package.json +5 -4
- package/src/contract-lock.test.ts +8 -2
- package/src/contracts/activity.contract.ts +18 -2
- package/src/contracts/agent.contract.ts +78 -8
- package/src/contracts/agents.contract.ts +180 -19
- package/src/contracts/automations.contract.ts +80 -9
- package/src/contracts/capabilities.contract.ts +106 -11
- package/src/contracts/chores.contract.ts +29 -3
- package/src/contracts/ci.contract.ts +55 -6
- package/src/contracts/claude.contract.ts +47 -5
- package/src/contracts/drafts.contract.ts +26 -3
- package/src/contracts/endpoints.contract.ts +19 -2
- package/src/contracts/exit.contract.ts +69 -7
- package/src/contracts/extensions.contract.ts +134 -15
- package/src/contracts/git.contract.ts +421 -43
- package/src/contracts/grok.contract.ts +26 -3
- package/src/contracts/history.contract.ts +37 -4
- package/src/contracts/intentic.contract.ts +28 -3
- package/src/contracts/inventory.contract.ts +27 -3
- package/src/contracts/logs.contract.ts +41 -4
- package/src/contracts/loops.contract.ts +58 -6
- package/src/contracts/panels.contract.ts +26 -3
- package/src/contracts/personas.contract.ts +77 -8
- package/src/contracts/ports.contract.ts +27 -3
- package/src/contracts/prepush.contract.ts +26 -3
- package/src/contracts/providers.contract.ts +10 -1
- package/src/contracts/public.contract.ts +29 -3
- package/src/contracts/push.contract.ts +37 -4
- package/src/contracts/secrets.contract.ts +46 -5
- package/src/contracts/sessions.contract.ts +16 -2
- package/src/contracts/settings.contract.ts +46 -5
- package/src/contracts/share.contract.ts +37 -4
- package/src/contracts/skills.contract.ts +39 -4
- package/src/contracts/system.contract.ts +119 -14
- package/src/contracts/translator.contract.ts +32 -4
- package/src/contracts/usage.contract.ts +10 -1
- package/src/contracts/vpn.contract.ts +35 -4
- package/src/contracts/workflows.contract.ts +77 -8
- package/src/contracts/workspace.contract.ts +231 -25
- package/src/events.ts +259 -89
- package/src/history-state.ts +6 -0
- package/src/index.ts +2 -0
- package/src/prompt-complexity.test.ts +69 -0
- package/src/prompt-complexity.ts +102 -39
- package/src/request-id.ts +41 -0
- package/src/schemas.ts +3700 -1260
- package/src/starter.ts +13 -0
|
@@ -158,3 +158,72 @@ test("the score never leaves 0..1, so a stored row is always comparable against
|
|
|
158
158
|
expect(floor.score).toBeGreaterThanOrEqual(0);
|
|
159
159
|
expect(ceiling.score).toBeLessThanOrEqual(1);
|
|
160
160
|
});
|
|
161
|
+
|
|
162
|
+
// --- the one dial, and the property it may not move ------------------------------------------------------
|
|
163
|
+
|
|
164
|
+
/* The owner can move the cutoff (settings.autoTierEagerness) because "err toward my model or toward the cheap
|
|
165
|
+
* one" is a preference nobody else can hold for them. What these pin is that the dial moves the cutoff and
|
|
166
|
+
* NOTHING else, in particular not the rule that a downgrade needs something positively easy to have been said,
|
|
167
|
+
* which was the property the old ceiling held only by arithmetic coincidence. */
|
|
168
|
+
|
|
169
|
+
test("the dial widens what counts as simple, in the direction it says", () => {
|
|
170
|
+
// A question about a real file: eased by its words, held back by naming a path. The middle stop keeps it on
|
|
171
|
+
// the user's pick, and the eager stop is precisely the choice to let it through.
|
|
172
|
+
const aboutAFile = `explain what src/app.ts does`;
|
|
173
|
+
|
|
174
|
+
expect(tierOf(aboutAFile, { eagerness: `balanced` })).toBe(`standard`);
|
|
175
|
+
expect(tierOf(aboutAFile, { eagerness: `eager` })).toBe(`fast`);
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
test("the cautious stop wants every easing signal at once, not merely an easy word", () => {
|
|
179
|
+
// Its whole content: leave no room for doubt. A statement in easy words still qualifies at the default,
|
|
180
|
+
// and does not here; only a short bare question naming no file survives every stop.
|
|
181
|
+
expect(tierOf(`explain closures`, { eagerness: `balanced` })).toBe(`fast`);
|
|
182
|
+
expect(tierOf(`explain closures`, { eagerness: `cautious` })).toBe(`standard`);
|
|
183
|
+
expect(tierOf(`what is a closure?`, { eagerness: `cautious` })).toBe(`fast`);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
test("an absent dial is the balanced stop, so every row recorded before it existed still compares", () => {
|
|
187
|
+
const bare = judgeComplexity(turn(`what is this?`));
|
|
188
|
+
|
|
189
|
+
expect(bare.ceiling).toBe(FAST_CEILING);
|
|
190
|
+
expect(bare.tier).toBe(judgeComplexity(turn(`what is this?`, { eagerness: `balanced` })).tier);
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
test("no setting of the dial can downgrade a short vague request", () => {
|
|
194
|
+
// The whole safety argument for offering an eager stop at all. Absence of complexity is not evidence of
|
|
195
|
+
// simplicity at ANY cutoff, so this is a rule in the judge rather than a sum that happens to clear it.
|
|
196
|
+
for (const eagerness of [`cautious`, `balanced`, `eager`] as const) {
|
|
197
|
+
expect(tierOf(`fix the bug`, { eagerness })).toBe(`standard`);
|
|
198
|
+
expect(tierOf(`have a look at the thing we discussed`, { eagerness })).toBe(`standard`);
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
test("the deceptive follow-up is standard at every stop, because it never says anything easy", () => {
|
|
203
|
+
/* "now do the same for the other file" is the case the whole afterHardTurn signal was built for, and the
|
|
204
|
+
* easing rule turns out to answer it more strongly than any cutoff can: those words make no positive claim
|
|
205
|
+
* of ease, so no setting of the dial reaches them. The weight still does its own job one test down. */
|
|
206
|
+
for (const eagerness of [`cautious`, `balanced`, `eager`] as const) {
|
|
207
|
+
expect(tierOf(`now do the same for the other file`, { eagerness, afterHardTurn: true })).toBe(`standard`);
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
test("a turn following hard work has to clear a higher bar, and at the default an eased one no longer does", () => {
|
|
212
|
+
// It raises the bar rather than locking the door (see ComplexityInput.afterHardTurn), so this is a shift of
|
|
213
|
+
// one stop's worth, not a gate: the same words that route in a fresh conversation stay put in a hard one.
|
|
214
|
+
expect(tierOf(`explain closures`)).toBe(`fast`);
|
|
215
|
+
expect(tierOf(`explain closures`, { afterHardTurn: true })).toBe(`standard`);
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
test("the verdict carries the cutoff it was judged against, because a score alone stopped being an answer", () => {
|
|
219
|
+
// With the cutoff an owner setting, the same 0.35 is standard on one stop and fast on another. A ledger of
|
|
220
|
+
// bare scores could not tell those two rows apart; the ceiling beside each is what keeps a refit honest.
|
|
221
|
+
const cautious = judgeComplexity(turn(`explain closures`, { eagerness: `cautious` }));
|
|
222
|
+
const eager = judgeComplexity(turn(`explain closures`, { eagerness: `eager` }));
|
|
223
|
+
|
|
224
|
+
expect(cautious.ceiling).toBeLessThan(FAST_CEILING);
|
|
225
|
+
expect(eager.ceiling).toBeGreaterThan(FAST_CEILING);
|
|
226
|
+
// Same words, same score, opposite verdicts: the pair that a column of bare scores could not have told apart.
|
|
227
|
+
expect(cautious.score).toBe(eager.score);
|
|
228
|
+
expect([cautious.tier, eager.tier]).toEqual([`standard`, `fast`]);
|
|
229
|
+
});
|
package/src/prompt-complexity.ts
CHANGED
|
@@ -89,6 +89,10 @@ export interface ComplexityInput {
|
|
|
89
89
|
* or the feature switched off) says nothing about the difficulty of the work, and reading it as escalation
|
|
90
90
|
* would make the sandbox's configuration leak into its opinion about a sentence. */
|
|
91
91
|
readonly afterHardTurn: boolean;
|
|
92
|
+
/* HOW EAGER THE OWNER ASKED THIS TO BE (settings.autoTierEagerness), the one preference the judge takes.
|
|
93
|
+
* Absent ⇒ `balanced`, which is the stop every verdict recorded before the knob existed was judged
|
|
94
|
+
* against, so an absent value and an old row mean the same thing. See FAST_CEILINGS. */
|
|
95
|
+
readonly eagerness?: TierEagerness;
|
|
92
96
|
}
|
|
93
97
|
|
|
94
98
|
export interface ComplexityVerdict {
|
|
@@ -99,6 +103,10 @@ export interface ComplexityVerdict {
|
|
|
99
103
|
// Every rule that fired, in declaration order. Empty is legal and means "nothing distinctive": the score is
|
|
100
104
|
// the base, which sits above the fast ceiling, so an unremarkable turn stays on the user's own pick.
|
|
101
105
|
readonly rules: readonly ComplexityRule[];
|
|
106
|
+
/* The cutoff this score was judged against, carried out so the caller can record it beside the score.
|
|
107
|
+
* A score is only half a verdict once the ceiling is a setting: 0.35 was standard yesterday and is fast
|
|
108
|
+
* today, and a refit reading a column of bare scores could not tell those rows apart. */
|
|
109
|
+
readonly ceiling: number;
|
|
102
110
|
}
|
|
103
111
|
|
|
104
112
|
/* WHERE AN UNREMARKABLE TURN STARTS, and it starts ABOVE the fast ceiling on purpose: a prompt that matches no
|
|
@@ -107,10 +115,35 @@ export interface ComplexityVerdict {
|
|
|
107
115
|
* escalation, and a user who stops trusting the feature. */
|
|
108
116
|
const BASE_SCORE = 0.5;
|
|
109
117
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
118
|
+
/* HOW EAGER THE JUDGE IS, the one knob this feature exposes, and deliberately the only one.
|
|
119
|
+
*
|
|
120
|
+
* The routing literature's own answer to "how do you tune a router in public" is a single aggressiveness
|
|
121
|
+
* threshold (RouteLLM ships it in the model name, `router-mf-0.3` against `router-mf-0.7`); everything else it
|
|
122
|
+
* learns stays inside. Same here: the weights below are a hypothesis with a ledger under them and are nobody's
|
|
123
|
+
* business, while "should this err toward my model or toward the cheap one" is a preference only the owner can
|
|
124
|
+
* hold, and the shadow numbers are useless without a way to act on what they say.
|
|
125
|
+
*
|
|
126
|
+
* THREE NAMED STOPS, not a slider, because the page has no slider idiom and, more to the point, a continuous
|
|
127
|
+
* control here invites fiddling with a number whose meaning nobody can feel. Each stop is a sentence about
|
|
128
|
+
* which turns move:
|
|
129
|
+
* cautious — every easing signal at once and nothing pulling the other way: a short bare question, in easy
|
|
130
|
+
* words, naming no file. "what is a closure?" and very little else. The zero is not a disabled
|
|
131
|
+
* state, it is the floor the score clamps to, so it means exactly "leave no room for doubt".
|
|
132
|
+
* balanced — the shipped default, and what every stored verdict before this knob existed was judged against.
|
|
133
|
+
* Easy words carry a turn on their own; naming a file still holds it back.
|
|
134
|
+
* eager — an easy-worded question about real code goes too ("explain what this file does").
|
|
135
|
+
*
|
|
136
|
+
* NONE OF THEM CAN REACH THE ABSENCE FEATURES, at any setting, because that property is enforced structurally
|
|
137
|
+
* now rather than by the weights happening to sum above the ceiling (see `easing` below). That is what makes an
|
|
138
|
+
* eager stop safe to offer at all: raising a bare number would, at 0.3, have started downgrading every short
|
|
139
|
+
* vague request in the product, which is the single worst population to be wrong about. */
|
|
140
|
+
export const FAST_CEILINGS = { cautious: 0, balanced: 0.25, eager: 0.4 } as const;
|
|
141
|
+
export type TierEagerness = keyof typeof FAST_CEILINGS;
|
|
142
|
+
|
|
143
|
+
// The stop a turn is judged against when nobody has chosen one, and the one every verdict recorded before the
|
|
144
|
+
// knob existed was judged against. Exported because a reader of a stored score needs the ceiling behind it, and
|
|
145
|
+
// a row written before ceilings were recorded was written against exactly this.
|
|
146
|
+
export const FAST_CEILING = FAST_CEILINGS.balanced;
|
|
114
147
|
|
|
115
148
|
// Characters, not tokens: nothing here can tokenize, and for a threshold the constant cancels. ~600 chars is
|
|
116
149
|
// where a request stops being a sentence and starts being a brief; ~2400 is where it is carrying pasted
|
|
@@ -215,38 +248,56 @@ const forcing = (input: ComplexityInput, text: string): ComplexityRule[] => {
|
|
|
215
248
|
* nothing, which is exactly why the mechanism ships in shadow first — see docs/model-routing-design.md §4. They
|
|
216
249
|
* are not a claim, they are a hypothesis with a ledger under it.
|
|
217
250
|
*
|
|
218
|
-
* ONE PROPERTY IS NOT A HYPOTHESIS AND MUST SURVIVE ANY REFIT
|
|
219
|
-
*
|
|
220
|
-
*
|
|
221
|
-
*
|
|
222
|
-
*
|
|
223
|
-
*
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
251
|
+
* ONE PROPERTY IS NOT A HYPOTHESIS AND MUST SURVIVE ANY REFIT, and it is a RULE here rather than an accident of
|
|
252
|
+
* arithmetic: absence of complexity is not evidence of simplicity. "fix the bug" is four words naming no file
|
|
253
|
+
* and is not a cheap turn, so a downgrade always requires something POSITIVE to have been said — an easy word,
|
|
254
|
+
* or a bare question. Those two are marked `easing`, and `judgeComplexity` refuses a fast verdict without one
|
|
255
|
+
* whatever the score says.
|
|
256
|
+
*
|
|
257
|
+
* It used to hold only because the two ABSENCE features (`short-prompt`, `no-workspace-reference`) summed to
|
|
258
|
+
* 0.3 against a ceiling of 0.25, which is a coincidence of two numbers rather than a property, and the moment
|
|
259
|
+
* the ceiling became a setting it was one click from being false. Weighted the obvious way, the judge
|
|
260
|
+
* downgraded every short vague request in the product, which is the single worst population to be wrong
|
|
261
|
+
* about. */
|
|
262
|
+
interface GradedFeature {
|
|
263
|
+
readonly rule: ComplexityRule;
|
|
264
|
+
readonly weight: number;
|
|
265
|
+
readonly of: (input: ComplexityInput, text: string) => boolean;
|
|
266
|
+
// A POSITIVE reason to think this is easy, as opposed to the mere absence of reasons to think it is hard.
|
|
267
|
+
// At least one has to fire before any turn is called fast; see the note above.
|
|
268
|
+
readonly easing?: true;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const GRADED: readonly GradedFeature[] = [
|
|
272
|
+
{ rule: "medium-prompt", weight: +0.2, of: (_input, text) => text.length > MEDIUM_PROMPT_CHARS },
|
|
273
|
+
{ rule: "attachment", weight: +0.15, of: (input) => input.attachments > 0 },
|
|
274
|
+
{ rule: "editor-context", weight: +0.1, of: (input) => input.editorContext },
|
|
275
|
+
// Enough on its own to hold an easy-worded question at standard on the middle stop: "explain what this file
|
|
276
|
+
// does" is a question about real code in this repo, and the cheap rung's failures on real code are the
|
|
277
|
+
// silent kind. The `eager` stop is precisely the choice to let that one through.
|
|
278
|
+
{ rule: "paths", weight: +0.15, of: (_input, text) => PATH_LIKE.test(text) },
|
|
279
|
+
{
|
|
280
|
+
rule: "many-verbs",
|
|
281
|
+
weight: +0.15,
|
|
282
|
+
of: (_input, text) => new Set((text.match(VERBS) ?? []).map((verb) => verb.toLowerCase())).size >= MANY_VERBS,
|
|
283
|
+
},
|
|
284
|
+
// The heaviest single weight, because it is the only feature that can see past the words. See
|
|
285
|
+
// ComplexityInput.afterHardTurn for why it is a weight rather than the gate it was designed as. Heavy enough
|
|
286
|
+
// that even the eager stop cannot route a follow-up to hard work on easy words alone.
|
|
287
|
+
{ rule: "after-hard-turn", weight: +0.25, of: (input) => input.afterHardTurn },
|
|
288
|
+
/* The easing half, in the order it has always been declared, because that order is the order a verdict
|
|
289
|
+
* lists its rules in and the ledger has rows written against it. `easing` marks the two POSITIVE ones, one
|
|
290
|
+
* of which every fast verdict must carry; the other two are the absence features, deliberately light and,
|
|
291
|
+
* by the rule above, never enough by themselves. */
|
|
292
|
+
{ rule: "short-prompt", weight: -0.1, of: (_input, text) => text.length <= SHORT_PROMPT_CHARS },
|
|
293
|
+
{ rule: "easy-words", weight: -0.25, easing: true, of: (_input, text) => EASY_WORDS.test(text) },
|
|
294
|
+
{ rule: "bare-question", weight: -0.15, easing: true, of: (_input, text) => BARE_QUESTION.test(text) },
|
|
295
|
+
{
|
|
296
|
+
rule: "no-workspace-reference",
|
|
297
|
+
weight: -0.1,
|
|
298
|
+
of: (input, text) => input.attachments === 0 && !input.editorContext && !PATH_LIKE.test(text),
|
|
299
|
+
},
|
|
300
|
+
];
|
|
250
301
|
|
|
251
302
|
// Three places, so a stored score is a value rather than a float artefact and two rows written by the same
|
|
252
303
|
// rules compare equal.
|
|
@@ -258,14 +309,26 @@ const round3 = (value: number): number => Math.round(value * 1000) / 1000;
|
|
|
258
309
|
* up a tier, never silently move a different set of turns down.
|
|
259
310
|
*
|
|
260
311
|
* Only what survives all of that gets scored, which keeps the graded layer doing the one job it is good at:
|
|
261
|
-
* separating "explain this" from "wire this up" among requests that look alike.
|
|
312
|
+
* separating "explain this" from "wire this up" among requests that look alike.
|
|
313
|
+
*
|
|
314
|
+
* TWO CONDITIONS FOR FAST, not one, and the second is the one that does not move: the score has to clear the
|
|
315
|
+
* owner's chosen ceiling AND something POSITIVE has to have been said (a `easing` feature). The ceiling is a
|
|
316
|
+
* preference and belongs to whoever pays the bill; "we downgraded it because you didn't say much" is not a
|
|
317
|
+
* preference, it is a bug, and it stays impossible at every stop of the knob. */
|
|
262
318
|
export const judgeComplexity = (input: ComplexityInput): ComplexityVerdict => {
|
|
319
|
+
const ceiling = FAST_CEILINGS[input.eagerness ?? "balanced"];
|
|
263
320
|
const text = input.prompt.trim();
|
|
264
321
|
const forced = forcing(input, text);
|
|
265
322
|
if (forced.length > 0) {
|
|
266
|
-
return { tier: "standard", score: 1, rules: forced };
|
|
323
|
+
return { tier: "standard", score: 1, rules: forced, ceiling };
|
|
267
324
|
}
|
|
268
325
|
const hits = GRADED.filter((feature) => feature.of(input, text));
|
|
269
326
|
const score = Math.min(1, Math.max(0, BASE_SCORE + hits.reduce((total, feature) => total + feature.weight, 0)));
|
|
270
|
-
|
|
327
|
+
const eased = hits.some((feature) => feature.easing === true);
|
|
328
|
+
return {
|
|
329
|
+
tier: eased && score <= ceiling ? "fast" : "standard",
|
|
330
|
+
score: round3(score),
|
|
331
|
+
rules: hits.map((feature) => feature.rule),
|
|
332
|
+
ceiling,
|
|
333
|
+
};
|
|
271
334
|
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/* THE ONE HEADER THAT JOINS A BROWSER CALL TO THE DAEMON LINE THAT SERVED IT.
|
|
2
|
+
*
|
|
3
|
+
* Both halves of a slow interaction were already measured and could not be put together. The browser times
|
|
4
|
+
* `rpc.request`, what the user actually waited for; the daemon times `http.request`, what it served; and on a
|
|
5
|
+
* sandbox answering several calls a second the only way to pair them was by timestamp and hope. So "the panel
|
|
6
|
+
* stuttered" stayed unattributable even with both numbers in hand, which is the gap the daemon's own perf module
|
|
7
|
+
* says it exists to close and could only close on its own side of the wire.
|
|
8
|
+
*
|
|
9
|
+
* Here rather than as a literal on each side, because a header name that disagrees across the wire fails
|
|
10
|
+
* SILENTLY: the browser sends a field nobody reads and the daemon logs nothing, which looks exactly like a quiet
|
|
11
|
+
* system. Both sides import this.
|
|
12
|
+
*
|
|
13
|
+
* The NAME only. The browser mints the value with the app's own `composables/uuid.ts`, which already solves the
|
|
14
|
+
* one hard part (`crypto.randomUUID` is a secure-context api and a self-hosted instance on a LAN address does
|
|
15
|
+
* not have it), and the daemon only ever reads what it is given. A generator here would be a second answer to a
|
|
16
|
+
* question that already has a better one. The value is a correlation token and never a security boundary: the
|
|
17
|
+
* daemon records it and decides nothing on it, so a caller repeating one confuses a log and nothing else. */
|
|
18
|
+
export const REQUEST_ID_HEADER = "x-intentic-request-id";
|
|
19
|
+
|
|
20
|
+
/* WHEN A BROWSER MAY SEND IT, and this is not the additive change it looks like.
|
|
21
|
+
*
|
|
22
|
+
* A custom request header is the one wire addition that is NOT backward compatible, because the browser does not
|
|
23
|
+
* get to decide it: a header outside the CORS safelist forces a preflight, and a daemon whose `allowHeaders`
|
|
24
|
+
* predates this name answers a preflight that omits it. The browser then fails the whole request rather than
|
|
25
|
+
* dropping the header. Sent unconditionally, this turns "one field the daemon never logs" into EVERY typed call
|
|
26
|
+
* to that daemon failing at the transport, `system.events` included, so the stream never opens, the connection
|
|
27
|
+
* never reaches `online`, and the app settles on "Busy, catching up" forever against a sandbox that is up and
|
|
28
|
+
* healthy and answering `/health` in a millisecond.
|
|
29
|
+
*
|
|
30
|
+
* That state is not exotic. A browser newer than its daemon is the NORMAL case and a supported one, both in
|
|
31
|
+
* production (every user's sandbox runs whatever image they last chose to pull, and COMPATIBILITY.md's second
|
|
32
|
+
* promise is that no update is ever forced) and in development (the web app runs from the working tree, the
|
|
33
|
+
* daemon is baked into the last `pnpm build:sandbox`). See the web app's useDaemonRoutes.ts, which exists to say
|
|
34
|
+
* exactly this.
|
|
35
|
+
*
|
|
36
|
+
* So the header ships gated on POSITIVE evidence, and this names the evidence: `logs.report` is the route that
|
|
37
|
+
* landed in the same commit as the `allowHeaders` entry, so a daemon advertising it on its hello frame is a
|
|
38
|
+
* daemon whose CORS accepts the header. Note the polarity is the opposite of `supportsRoute`'s: there, no
|
|
39
|
+
* evidence means assume-supported, because hiding a working feature is the greater harm; here, no evidence must
|
|
40
|
+
* mean DO NOT SEND, because sending costs the entire connection and the header buys a log field. */
|
|
41
|
+
export const REQUEST_ID_EVIDENCE_ROUTE = "logs.report";
|