@brainervirus/workit-core 8.32.0 → 8.33.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/package.json
CHANGED
|
@@ -567,10 +567,16 @@ function invalidOperation(
|
|
|
567
567
|
const request = isObject(input) ? input : {};
|
|
568
568
|
const action = typeof request.action === "string" ? request.action : "";
|
|
569
569
|
const actions = OPERATION_ACTIONS[family];
|
|
570
|
-
if (!actions.includes(action))
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
570
|
+
if (!actions.includes(action)) {
|
|
571
|
+
// The family's read (list, else the first action) is the safe corrected call.
|
|
572
|
+
const common = actions.find((name) => READ_ONLY_ACTIONS.has(name)) ?? actions[0];
|
|
573
|
+
const example = JSON.stringify(exampleOperation(family, common));
|
|
574
|
+
return failure(
|
|
575
|
+
"invalid_input",
|
|
576
|
+
`${family}: action must be one of ${oneOf(actions)}. Example: ${example}`,
|
|
577
|
+
{ fields: [{ path: "action", reason: `got ${JSON.stringify(request.action ?? null)}` }] },
|
|
578
|
+
);
|
|
579
|
+
}
|
|
574
580
|
const own = actionKeys(family, action)!;
|
|
575
581
|
const fields: { path: string; reason: string }[] = [];
|
|
576
582
|
const sentence: string[] = [];
|
package/src/hooks/handle.ts
CHANGED
|
@@ -30,7 +30,8 @@ import {
|
|
|
30
30
|
} from "./skill-nudge";
|
|
31
31
|
import { spawnDecision } from "./spawn-gate";
|
|
32
32
|
import { seedStanding, standingDelta } from "./standing";
|
|
33
|
-
import { trackJudge } from "./session-marker";
|
|
33
|
+
import { modelSwitchedOnDisk, trackJudge } from "./session-marker";
|
|
34
|
+
import { invariantBootstrap } from "../core/methods";
|
|
34
35
|
import { stopDecision } from "./stop";
|
|
35
36
|
import { liveOwner } from "../core/live-session";
|
|
36
37
|
import { liveTargets, shellGate, writeGate } from "./write-gate";
|
|
@@ -166,7 +167,12 @@ function decide(input: HookInput, deps: HookDeps): HookDecision {
|
|
|
166
167
|
case "context.turn": {
|
|
167
168
|
if (!usable(descriptor.context.perTurn)) return NONE;
|
|
168
169
|
const current = turnContextText(input, descriptor);
|
|
169
|
-
const
|
|
170
|
+
const turn = resendsOnChange(descriptor) ? changedTurnContext(input, current) : current;
|
|
171
|
+
// A model switch (#435): the compact contract again, once.
|
|
172
|
+
const text =
|
|
173
|
+
input.model && modelSwitchedOnDisk(input)
|
|
174
|
+
? [invariantBootstrap(), turn].filter(Boolean).join("\n")
|
|
175
|
+
: turn;
|
|
170
176
|
return withContextLine(
|
|
171
177
|
text ? { kind: "context", text } : NONE,
|
|
172
178
|
promptNudge(input, event.prompt),
|
package/src/hooks/hosts/codex.ts
CHANGED
package/src/hooks/protocol.ts
CHANGED
|
@@ -17,6 +17,8 @@ export type SessionMarker = {
|
|
|
17
17
|
stops: string[];
|
|
18
18
|
/** Verifier and reviewer subagents of this lead that started and have not stopped. */
|
|
19
19
|
judges: string[];
|
|
20
|
+
/** The last model the session reported (Codex #435): a change re-sends the contract. */
|
|
21
|
+
model: string | null;
|
|
20
22
|
};
|
|
21
23
|
|
|
22
24
|
/**
|
|
@@ -42,7 +44,14 @@ export const sessionMarkerFile = (
|
|
|
42
44
|
const list = (value: unknown): string[] =>
|
|
43
45
|
Array.isArray(value) ? value.filter((item): item is string => typeof item === "string") : [];
|
|
44
46
|
|
|
45
|
-
const EMPTY: SessionMarker = {
|
|
47
|
+
const EMPTY: SessionMarker = {
|
|
48
|
+
nudged: [],
|
|
49
|
+
loaded: [],
|
|
50
|
+
raw: [],
|
|
51
|
+
stops: [],
|
|
52
|
+
judges: [],
|
|
53
|
+
model: null,
|
|
54
|
+
};
|
|
46
55
|
|
|
47
56
|
const readRaw = (file: string): string => {
|
|
48
57
|
try {
|
|
@@ -61,6 +70,7 @@ const parseMarker = (text: string): SessionMarker => {
|
|
|
61
70
|
raw: list(value.raw),
|
|
62
71
|
stops: list(value.stops),
|
|
63
72
|
judges: list(value.judges),
|
|
73
|
+
model: typeof value.model === "string" ? value.model : null,
|
|
64
74
|
};
|
|
65
75
|
} catch {
|
|
66
76
|
return { ...EMPTY };
|
|
@@ -123,3 +133,19 @@ export const trackJudge = (input: HookInput, agentId: string | null, running: bo
|
|
|
123
133
|
return { ...marker, judges: running ? [...rest, agentId] : rest };
|
|
124
134
|
});
|
|
125
135
|
};
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Record the session's model and say whether it differs from the last one
|
|
139
|
+
* recorded (the hook is a fresh process each time). The first model is a
|
|
140
|
+
* start, not a switch. Fails open: no store or no model reads as no switch.
|
|
141
|
+
*/
|
|
142
|
+
export const modelSwitchedOnDisk = (input: HookInput): boolean => {
|
|
143
|
+
const session = input.session.id;
|
|
144
|
+
const model = input.model;
|
|
145
|
+
const file = session && model ? sessionMarkerFile(input, session) : null;
|
|
146
|
+
if (!file || !model) return false;
|
|
147
|
+
const previous = readSessionMarker(file).model;
|
|
148
|
+
if (previous === model) return false;
|
|
149
|
+
updateSessionMarker(file, (marker) => ({ ...marker, model }));
|
|
150
|
+
return previous !== null;
|
|
151
|
+
};
|