@jskit-ai/assistant-core 0.2.8 → 0.2.10
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/examples/conversation/package.json +3 -3
- package/locales/en.json +1 -0
- package/package.json +1 -1
- package/src/server/conversation/claudePromptDelivery.js +5 -3
- package/src/server/conversation/codexTurnState.js +13 -5
- package/src/server/conversation/providers/claude.js +7 -1
- package/test/claudeTurn.test.js +30 -0
- package/test/codexConversationSession.test.js +22 -0
- package/test/codexTurn.test.js +13 -0
|
@@ -8,14 +8,14 @@
|
|
|
8
8
|
"build": "vite build"
|
|
9
9
|
},
|
|
10
10
|
"dependencies": {
|
|
11
|
-
"@jskit-ai/assistant-core": "0.2.
|
|
11
|
+
"@jskit-ai/assistant-core": "0.2.10",
|
|
12
12
|
"@jskit-ai/http-runtime": "0.2.4",
|
|
13
13
|
"@jskit-ai/kernel": "0.2.4",
|
|
14
14
|
"@mdi/js": "^7.4.47",
|
|
15
15
|
"vue": "^3.5.13",
|
|
16
16
|
"vuetify": "^4.0.0",
|
|
17
|
-
"@jskit-ai/connectors-core": "0.2.
|
|
18
|
-
"@jskit-ai/connectors-catalog": "0.2.
|
|
17
|
+
"@jskit-ai/connectors-core": "0.2.5",
|
|
18
|
+
"@jskit-ai/connectors-catalog": "0.2.5"
|
|
19
19
|
},
|
|
20
20
|
"devDependencies": {
|
|
21
21
|
"@vitejs/plugin-vue": "^6.0.7",
|
package/locales/en.json
CHANGED
|
@@ -333,6 +333,7 @@
|
|
|
333
333
|
"compactWhileWorking": "Claude is still working. Wait for it to finish before freeing its context.",
|
|
334
334
|
"noActiveTurn": "Claude has no active turn for this message.",
|
|
335
335
|
"steeringUnavailable": "This Claude turn cannot accept steering now.",
|
|
336
|
+
"settingsChangeWhileWorking": "Stop the current Claude turn before changing its settings.",
|
|
336
337
|
"notConnected": "Claude Code is not connected.",
|
|
337
338
|
"accountStatusInvalid": "Claude Code returned an invalid account status."
|
|
338
339
|
},
|
package/package.json
CHANGED
|
@@ -56,9 +56,11 @@ export function createClaudePromptDelivery({
|
|
|
56
56
|
}
|
|
57
57
|
let native;
|
|
58
58
|
try {
|
|
59
|
-
if (!command && entry.
|
|
60
|
-
//
|
|
61
|
-
//
|
|
59
|
+
if (!command && entry.turn?.active && entry.process) {
|
|
60
|
+
// Steering keeps the active instruction installation, just like command
|
|
61
|
+
// steering, in Main and in every other conversation: guidance changed
|
|
62
|
+
// by the running work (a repair editing the project's Genesis files)
|
|
63
|
+
// applies at the next idle admission and never refuses the message.
|
|
62
64
|
// Account replacement must still confirm cleanup before any new send.
|
|
63
65
|
if (preparation.account) await bindAccount(entry);
|
|
64
66
|
native = entry.process || await ensureProcess(entry, input);
|
|
@@ -163,7 +163,14 @@ export function codexAppServerProcessedResultEvent(session = {}, threadId = "",
|
|
|
163
163
|
)) || null;
|
|
164
164
|
}
|
|
165
165
|
|
|
166
|
-
|
|
166
|
+
/**
|
|
167
|
+
* The turn a saved Codex run reports. No saved run (null or undefined) is no turn: it is idle. A saved run
|
|
168
|
+
* whose state is not written yet is starting, which counts as active.
|
|
169
|
+
* @param {object | null | undefined} run
|
|
170
|
+
* @param {(state: unknown) => string} [normalizeRunState]
|
|
171
|
+
*/
|
|
172
|
+
export function codexAppServerTurnStateFromAgentRun(run, normalizeRunState = normalizeCodexAppServerRunState) {
|
|
173
|
+
if (run == null) return codexAppServerIdleTurnState();
|
|
167
174
|
const runState = normalizeRunState(run.state);
|
|
168
175
|
const active = codexAppServerRunStateIsActive(runState);
|
|
169
176
|
const state = runState === CODEX_APP_SERVER_RUN_STATE.FINALIZING
|
|
@@ -194,10 +201,11 @@ export function codexAppServerTurnStateFromAgentRun(run = {}, normalizeRunState
|
|
|
194
201
|
}
|
|
195
202
|
|
|
196
203
|
export function codexAppServerTurnState(session = {}, normalizeRunState = normalizeCodexAppServerRunState) {
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
204
|
+
return codexAppServerTurnStateFromAgentRun(codexAppServerAgentRun(session), normalizeRunState);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/** The turn of a session or binding that has no saved Codex run. */
|
|
208
|
+
function codexAppServerIdleTurnState() {
|
|
201
209
|
return {
|
|
202
210
|
active: false,
|
|
203
211
|
completedAt: "",
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { AppError } from "@jskit-ai/kernel/server/runtime/errors";
|
|
1
2
|
import { createConversationSystemPrompt } from "../systemPrompt.js";
|
|
2
3
|
|
|
3
4
|
/**
|
|
@@ -44,7 +45,12 @@ export function createClaudeConversationAdapter({ getProcess, isActive, startPro
|
|
|
44
45
|
const canUpdate = getProcess() && installedPrompt === systemPrompt && installedMode === instructionMode &&
|
|
45
46
|
liveUpdateIdentity !== undefined && updateIdentity === liveUpdateIdentity;
|
|
46
47
|
if (getProcess() && isActive() && !canUpdate && !instructions.isCurrent({ systemPrompt, contextIdentity })) {
|
|
47
|
-
|
|
48
|
+
// Steering never gets here: it keeps the running installation. This is an
|
|
49
|
+
// explicit settings or command preparation while work is running.
|
|
50
|
+
throw new AppError(409, "Stop the current Claude turn before changing its settings.", {
|
|
51
|
+
code: "conversation_claude_settings_change_while_working",
|
|
52
|
+
messageCode: "assistant.claude.settingsChangeWhileWorking"
|
|
53
|
+
});
|
|
48
54
|
}
|
|
49
55
|
return instructions.ensure({
|
|
50
56
|
systemPrompt,
|
package/test/claudeTurn.test.js
CHANGED
|
@@ -949,6 +949,36 @@ test("Claude status retains active instructions and applies changed guidance onc
|
|
|
949
949
|
assert.equal(f.processes.length, 2, "Idle readiness installs the changed instructions");
|
|
950
950
|
});
|
|
951
951
|
|
|
952
|
+
// A repair chat edits the project's Genesis files while it works, so its
|
|
953
|
+
// composed instructions change mid-turn. A message typed then is steering.
|
|
954
|
+
for (const main of [true, false]) test(`Claude steering keeps the running instructions after guidance changed mid-turn and idle admission applies them, main=${main}`, async t => {
|
|
955
|
+
const f = await publishedClaudeFixture(t);
|
|
956
|
+
const entry = main ? f.entry : await f.owner.open(f.context, {
|
|
957
|
+
conversationId: "87654321-4321-4321-8321-cba987654321", mainId: f.conversationId, create: true
|
|
958
|
+
});
|
|
959
|
+
assert.equal(entry.main, main);
|
|
960
|
+
await f.owner.startTurn(entry, { message: "Repair the setup", messageId: "repair", persistent: true });
|
|
961
|
+
const running = entry.process;
|
|
962
|
+
f.behavior.systemPrompt = "Guidance the repair changed during its own turn.";
|
|
963
|
+
const steered = await f.owner.startTurn(entry, { message: "The saved version already has this", messageId: "midturn", steer: true });
|
|
964
|
+
assert.deepEqual([steered.ok, entry.nativeTurn.read().active], [true, true]);
|
|
965
|
+
assert.equal(entry.process, running, "the steer keeps the running process and its instructions");
|
|
966
|
+
assert.equal(f.processes.length, 1);
|
|
967
|
+
assert.equal(f.behavior.nativeInputs.at(-1), "The saved version already has this", "the steer reached the running Claude");
|
|
968
|
+
await entry.process.options.onEvent({ session_id: entry.id, type: "result", subtype: "success", result: "Done" });
|
|
969
|
+
await f.owner.startTurn(entry, { message: "Next request", messageId: "next" });
|
|
970
|
+
assert.equal(f.processes.length, 2, "the next idle admission installs the changed guidance");
|
|
971
|
+
});
|
|
972
|
+
|
|
973
|
+
test("an explicit Claude settings change while work runs is refused with its own code", async t => {
|
|
974
|
+
const f = await publishedClaudeFixture(t);
|
|
975
|
+
await f.send();
|
|
976
|
+
await assert.rejects(f.entry.nativeTurn.prepare({ systemPrompt: "Changed guidance", settings: {}, model: "sonnet" }), error =>
|
|
977
|
+
error.code === "conversation_claude_settings_change_while_working" && error.statusCode === 409 &&
|
|
978
|
+
error.messageCode === "assistant.claude.settingsChangeWhileWorking");
|
|
979
|
+
assert.equal(f.processes.length, 1);
|
|
980
|
+
});
|
|
981
|
+
|
|
952
982
|
test("Claude active status keeps a running turn across an account change; idle admission applies it", async t => {
|
|
953
983
|
const f = await publishedClaudeFixture(t);
|
|
954
984
|
await f.send();
|
|
@@ -1740,6 +1740,28 @@ test("Codex cancellation drains owned work, and a deadline saves a failed turn",
|
|
|
1740
1740
|
assert.equal(trace.filter(row => row.args).length, 1);
|
|
1741
1741
|
});
|
|
1742
1742
|
|
|
1743
|
+
// An upgraded binding keeps its native thread but has no saved Codex run. With
|
|
1744
|
+
// its Codex account gone, reading, Stop and selection still start nothing.
|
|
1745
|
+
test("a Codex binding with no saved run reads ready, and Stop and selection start no Codex work", async t => {
|
|
1746
|
+
const f = await fixture(t, apiOptions());
|
|
1747
|
+
await f.first.close();
|
|
1748
|
+
await f.storage.write("conversation", async transaction => {
|
|
1749
|
+
const metadata = await transaction.readMetadata();
|
|
1750
|
+
assert.equal(Object.hasOwn(metadata.runtime.binding, "codexAppServerRun"), false);
|
|
1751
|
+
metadata.runtime.binding = { ...metadata.runtime.binding, threadId: "imported-thread", processInstanceId: "" };
|
|
1752
|
+
await transaction.writeMetadata(metadata);
|
|
1753
|
+
});
|
|
1754
|
+
await rm(f.account);
|
|
1755
|
+
const conversation = await f.runtime().open({ id: "conversation" });
|
|
1756
|
+
const state = await conversation.read();
|
|
1757
|
+
assert.equal(state.status, "ready");
|
|
1758
|
+
assert.equal(state.phase, "");
|
|
1759
|
+
assert.deepEqual(await conversation.cancel(), { stopped: false });
|
|
1760
|
+
await select(conversation, "api", "imported-to-api");
|
|
1761
|
+
assert.equal((await conversation.read()).engine, "api");
|
|
1762
|
+
await assert.rejects(f.trace(), { code: "ENOENT" }, "No Codex process was started");
|
|
1763
|
+
});
|
|
1764
|
+
|
|
1743
1765
|
test("lost Codex acknowledgement stays uncertain until native history proves the exact message", async t => {
|
|
1744
1766
|
const f = await fixture(t);
|
|
1745
1767
|
const lost = { messageId: "lost-id", text: "lost" };
|
package/test/codexTurn.test.js
CHANGED
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
codexAppServerAgentRunPatch,
|
|
20
20
|
codexAppServerPendingUserMessageOwnership,
|
|
21
21
|
codexAppServerTurnState,
|
|
22
|
+
codexAppServerTurnStateFromAgentRun,
|
|
22
23
|
CODEX_APP_SERVER_RUN_STATE
|
|
23
24
|
} from "../src/server/conversation/codexTurn.js";
|
|
24
25
|
|
|
@@ -563,6 +564,18 @@ test("original pending ownership and terminal outer identity are preserved witho
|
|
|
563
564
|
error => error === hostError);
|
|
564
565
|
});
|
|
565
566
|
|
|
567
|
+
test("no saved Codex run is no turn, while a saved run without a state is starting", () => {
|
|
568
|
+
for (const run of [undefined, null]) {
|
|
569
|
+
const turn = codexAppServerTurnStateFromAgentRun(run);
|
|
570
|
+
assert.equal(turn.active, false);
|
|
571
|
+
assert.equal(turn.state, "idle");
|
|
572
|
+
assert.deepEqual(turn, codexAppServerTurnState({ agentRuns: [] }));
|
|
573
|
+
}
|
|
574
|
+
const starting = codexAppServerTurnStateFromAgentRun({});
|
|
575
|
+
assert.equal(starting.active, true);
|
|
576
|
+
assert.equal(starting.state, "starting");
|
|
577
|
+
});
|
|
578
|
+
|
|
566
579
|
test("native notification queues merge only waiting stream deltas and retain receipt order", async () => {
|
|
567
580
|
const owner = createCodexAppServerNotificationQueue();
|
|
568
581
|
const context = { sessionId: "session-1", sessionKey: "session-1", threadId: "thread-1", turnId: "turn-1" };
|