@jskit-ai/assistant-core 0.1.225 → 0.1.226
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
|
@@ -180,13 +180,15 @@ export async function stopClaudeCodeProcess({ process: native, executionId, stop
|
|
|
180
180
|
return { ...proof, exited: true };
|
|
181
181
|
}
|
|
182
182
|
|
|
183
|
-
export async function bindClaudeConversationAccount(entry, { identity, providerId, stop, save }) {
|
|
183
|
+
export async function bindClaudeConversationAccount(entry, { identity, providerId, allowAccountChange = false, stop, save }) {
|
|
184
184
|
entry.accountIdentities ||= {};
|
|
185
185
|
if (!nativeAiProvider(providerId) && entry.accountIdentities[providerId] && entry.accountIdentities[providerId] !== identity) {
|
|
186
186
|
await stop(entry, "The signed-in Claude account changed.");
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
187
|
+
if (!allowAccountChange) {
|
|
188
|
+
throw Object.assign(new Error("This conversation belongs to another Claude account. Reconnect that account or start a new session."), {
|
|
189
|
+
code: "claude_account_changed"
|
|
190
|
+
});
|
|
191
|
+
}
|
|
190
192
|
}
|
|
191
193
|
if (entry.accountIdentity !== identity) {
|
|
192
194
|
entry.accountIdentity = identity;
|
|
@@ -126,8 +126,8 @@ export function createClaudeConversationGoals({ conversationId, readHistory, rea
|
|
|
126
126
|
if (!objective || objective.length > 4000 || /^(clear|stop|off|reset|none|cancel)$/iu.test(objective)) {
|
|
127
127
|
throw goalError("Enter a goal condition of up to 4,000 characters.");
|
|
128
128
|
}
|
|
129
|
-
if (
|
|
130
|
-
throw goalError("
|
|
129
|
+
if (action === "set" && goal && goal.status !== "complete") {
|
|
130
|
+
throw goalError("Cancel the existing goal before setting another.");
|
|
131
131
|
}
|
|
132
132
|
await sendGoal({ message: `/goal ${objective}` });
|
|
133
133
|
} else throw goalError("Unknown Claude goal action.");
|
|
@@ -264,7 +264,7 @@ export function createClaudeConversationOwner({
|
|
|
264
264
|
const providerId = account.providerId;
|
|
265
265
|
if (entry.accountIdentity !== identity) entry.nativeTurn.invalidateFinalAssistantResult();
|
|
266
266
|
try {
|
|
267
|
-
await bindClaudeConversationAccount(entry, { identity, providerId,
|
|
267
|
+
await bindClaudeConversationAccount(entry, { identity, providerId, allowAccountChange: account.allowAccountChange === true,
|
|
268
268
|
stop: (entry, reason) => entry.nativeTurn.stopProcess(reason, { preparing: true }), save: saveAccount });
|
|
269
269
|
} catch (failure) {
|
|
270
270
|
if (failure.code !== "claude_account_changed") throw failure;
|
|
@@ -1434,6 +1434,11 @@ export function createClaudeConversationTurn({ conversationId, onEvent = async (
|
|
|
1434
1434
|
// A completed main loop can still own background work. There is no
|
|
1435
1435
|
// generation left to interrupt or second terminal result to wait for.
|
|
1436
1436
|
if (outcome && inFlight.size === 0) {
|
|
1437
|
+
// The next input owns completion now. A task finishing during host
|
|
1438
|
+
// preparation must not settle this turn using the previous result.
|
|
1439
|
+
outcome = null;
|
|
1440
|
+
acceptedInput = null;
|
|
1441
|
+
clearFinalAssistantResult();
|
|
1437
1442
|
await publish({ type: "diagnostic", stage: "steering_background_only", backgroundTasks: tasks.size });
|
|
1438
1443
|
return;
|
|
1439
1444
|
}
|
|
@@ -1059,6 +1059,25 @@ test("Claude native goal commands use ordinary admission, pause retains the goal
|
|
|
1059
1059
|
await assert.rejects(f.conversation.updateGoal({ ...start, messageId: "budget", tokenBudget: 100 }), /token budget/u);
|
|
1060
1060
|
});
|
|
1061
1061
|
|
|
1062
|
+
test("Claude accepts a goal during an active turn through the existing steering path", async t => {
|
|
1063
|
+
const f = await fixture(t);
|
|
1064
|
+
await f.conversation.send({ messageId: "working", text: "wait" });
|
|
1065
|
+
const { segmentId } = await f.conversation.read();
|
|
1066
|
+
assert.equal(await f.conversation.readGoal(), null);
|
|
1067
|
+
const command = { action: "set", objective: "Finish the current work", messageId: "working-goal", expectedSegmentId: segmentId };
|
|
1068
|
+
assert.equal((await f.conversation.updateGoal(command)).status, "accepted");
|
|
1069
|
+
const goal = await f.conversation.readGoal();
|
|
1070
|
+
assert.equal(goal.objective, command.objective);
|
|
1071
|
+
assert.equal(goal.status, "active");
|
|
1072
|
+
assert.equal((await f.conversation.updateGoal(command)).duplicate, true);
|
|
1073
|
+
await assert.rejects(f.conversation.updateGoal({ ...command, messageId: "another-goal", objective: "Replace it" }), /cancel the existing goal/i);
|
|
1074
|
+
const trace = await f.trace();
|
|
1075
|
+
assert.equal(trace.filter(row => row.args?.includes("--print")).length, 1);
|
|
1076
|
+
assert.deepEqual(trace.filter(row => row.frame?.type === "user").map(row => row.frame.message.content),
|
|
1077
|
+
["wait", "/goal Finish the current work"]);
|
|
1078
|
+
assert.equal(trace.filter(row => row.frame?.request?.subtype === "interrupt").length, 1);
|
|
1079
|
+
});
|
|
1080
|
+
|
|
1062
1081
|
test("Claude rejects reserved goals and waits for ordinary Send to deliver a replacement briefing", async t => {
|
|
1063
1082
|
const f = await fixture(t);
|
|
1064
1083
|
let state = await f.conversation.read();
|
package/test/claudeTurn.test.js
CHANGED
|
@@ -135,6 +135,21 @@ test("interruption does not claim process exit or complete work on a control ack
|
|
|
135
135
|
assert.equal(turn.read().text, "Resumed");
|
|
136
136
|
});
|
|
137
137
|
|
|
138
|
+
test("Claude steering survives background completion while the follow-up is being prepared", async () => {
|
|
139
|
+
const { turn, send, client, events } = fixture();
|
|
140
|
+
await send();
|
|
141
|
+
await turn.receive({ type: "system", subtype: "task_started", task_id: "build", task_type: "local_bash" });
|
|
142
|
+
await turn.receive({ type: "result", subtype: "success", result: "Build started" });
|
|
143
|
+
client.interrupt = () => assert.fail("The generation has already completed");
|
|
144
|
+
await turn.interruptGeneration(client);
|
|
145
|
+
await turn.receive({ type: "system", subtype: "task_notification", task_id: "build", status: "completed" });
|
|
146
|
+
assert.equal(events.some(event => event.type === "settled"), false, "The reserved steering input still needs admission");
|
|
147
|
+
await send("follow-up");
|
|
148
|
+
await turn.receive({ type: "result", subtype: "success", result: "Revised answer" });
|
|
149
|
+
assert.equal(turn.read().status, "completed");
|
|
150
|
+
assert.equal(turn.read().text, "Revised answer");
|
|
151
|
+
});
|
|
152
|
+
|
|
138
153
|
test("lost acknowledgement stays uncertain and stop rejects pending admission", async () => {
|
|
139
154
|
const { turn, client, send } = fixture();
|
|
140
155
|
client.send = async () => {};
|