@jskit-ai/assistant-core 0.2.6 → 0.2.8
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 +5 -5
- package/package.json +18 -7
- package/src/client/browser.js +21 -0
- package/src/client/conversation/AssistantAttachmentQueue.vue +25 -108
- package/src/client/conversation/AssistantComposerActions.vue +1 -1
- package/src/client/conversation/AssistantConversationElement.vue +14 -52
- package/src/client/conversation/AssistantGoalControl.vue +18 -12
- package/src/client/conversation/AssistantPromptInput.vue +6 -91
- package/src/client/conversation/AssistantTranscript.css +505 -0
- package/src/client/conversation/AssistantTranscript.vue +24 -1197
- package/src/client/conversation/attachmentFiles.js +158 -0
- package/src/client/conversation/attachmentQueueItems.js +121 -0
- package/src/client/conversation/attachmentUploadRows.js +265 -0
- package/src/client/conversation/conversationDisplay.js +54 -0
- package/src/client/conversation/messageDelivery.js +13 -9
- package/src/client/conversation/retainedConversation.js +14 -3
- package/src/client/conversation/submitText.js +13 -2
- package/src/client/conversation/transcriptEntries.js +225 -0
- package/src/client/conversation/useAssistantAttachments.js +86 -388
- package/src/client/conversation/useAssistantQuestions.js +16 -3
- package/src/client/conversation/useAssistantSuggestions.js +16 -2
- package/src/client/conversation/useScrollToBottom.js +17 -19
- package/src/client/conversation/useTextareaAutoGrow.js +115 -0
- package/src/client/conversation/useTranscriptScrollFollow.js +525 -0
- package/src/client/lib/assistantApi.js +192 -161
- package/src/client/types.js +110 -0
- package/src/server/conversation/attachments.js +21 -6
- package/src/server/conversation/boundedToolLoop.js +1 -0
- package/src/server/conversation/claudeClient.js +71 -20
- package/src/server/conversation/claudeCommandRun.js +191 -0
- package/src/server/conversation/claudeHistory.js +7 -0
- package/src/server/conversation/claudeNativeTurn.js +708 -0
- package/src/server/conversation/claudeProcess.js +186 -112
- package/src/server/conversation/claudeProcessStart.js +214 -0
- package/src/server/conversation/claudePromptDelivery.js +183 -0
- package/src/server/conversation/claudeSessionCleanup.js +248 -0
- package/src/server/conversation/claudeTools.js +15 -3
- package/src/server/conversation/claudeTurn.js +212 -1388
- package/src/server/conversation/codexAccountReader.js +168 -0
- package/src/server/conversation/codexAuthPreflight.js +5 -4
- package/src/server/conversation/codexClient.js +18 -6
- package/src/server/conversation/codexConfiguration.js +8 -844
- package/src/server/conversation/codexConversationStops.js +377 -0
- package/src/server/conversation/codexConversationStore.js +119 -85
- package/src/server/conversation/codexConversationTurns.js +378 -0
- package/src/server/conversation/codexConversations.js +134 -866
- package/src/server/conversation/codexDetachedConversation.js +203 -0
- package/src/server/conversation/codexDetachedTurn.js +145 -104
- package/src/server/conversation/codexEvents.js +5 -200
- package/src/server/conversation/codexGoalCommands.js +14 -4
- package/src/server/conversation/codexHelperExecution.js +57 -57
- package/src/server/conversation/codexHelperThreadLedger.js +30 -288
- package/src/server/conversation/codexHelperThreadRecord.js +304 -0
- package/src/server/conversation/codexHelperThreadRecovery.js +54 -76
- package/src/server/conversation/codexHelperThreads.js +94 -93
- package/src/server/conversation/codexHistoryAdapter.js +13 -8
- package/src/server/conversation/codexIsolation.js +672 -0
- package/src/server/conversation/codexLiveItems.js +379 -0
- package/src/server/conversation/codexMessageCommands.js +68 -286
- package/src/server/conversation/codexMessageDelivery.js +46 -37
- package/src/server/conversation/codexModelCatalog.js +86 -7
- package/src/server/conversation/codexNotificationObserver.js +81 -88
- package/src/server/conversation/codexNotificationQueue.js +21 -10
- package/src/server/conversation/codexObservationControl.js +105 -89
- package/src/server/conversation/codexOutput.js +84 -419
- package/src/server/conversation/codexProcess.js +29 -1053
- package/src/server/conversation/codexProcessLeader.js +1 -0
- package/src/server/conversation/codexPromptDispatch.js +241 -0
- package/src/server/conversation/codexProvider.js +147 -1807
- package/src/server/conversation/codexProviderAuth.js +348 -0
- package/src/server/conversation/codexProviderInvalidation.js +371 -0
- package/src/server/conversation/codexProviderInventories.js +437 -0
- package/src/server/conversation/codexProviderOwner.js +149 -957
- package/src/server/conversation/codexProviderRelease.js +465 -0
- package/src/server/conversation/codexProviderSelection.js +26 -15
- package/src/server/conversation/codexProviderThreads.js +698 -0
- package/src/server/conversation/codexProviderTurns.js +453 -0
- package/src/server/conversation/codexReasoning.js +32 -23
- package/src/server/conversation/codexRenewalCommands.js +18 -6
- package/src/server/conversation/codexRenewalThreads.js +7 -22
- package/src/server/conversation/codexResultSettlement.js +70 -88
- package/src/server/conversation/codexRunJournal.js +91 -97
- package/src/server/conversation/codexRuntime.js +426 -0
- package/src/server/conversation/codexRuntimeInventory.js +113 -0
- package/src/server/conversation/codexRuntimeLifecycle.js +39 -13
- package/src/server/conversation/codexRuntimeStart.js +431 -0
- package/src/server/conversation/codexRuntimeStop.js +324 -0
- package/src/server/conversation/codexSessionCleanup.js +25 -10
- package/src/server/conversation/codexSessionReconciliation.js +21 -7
- package/src/server/conversation/codexThreadReadiness.js +49 -52
- package/src/server/conversation/codexThreadSnapshot.js +210 -0
- package/src/server/conversation/codexThreadStatus.js +48 -62
- package/src/server/conversation/codexTurn.js +171 -55
- package/src/server/conversation/codexTurnActivity.js +210 -0
- package/src/server/conversation/codexTurnControl.js +44 -48
- package/src/server/conversation/codexTurnEndings.js +610 -0
- package/src/server/conversation/codexTurnRecovery.js +97 -848
- package/src/server/conversation/codexTurnState.js +138 -111
- package/src/server/conversation/completedResponses.js +227 -0
- package/src/server/conversation/configuration.js +2 -2
- package/src/server/conversation/continuity.js +43 -6
- package/src/server/conversation/conversationDelivery.js +394 -0
- package/src/server/conversation/conversationEntries.js +365 -0
- package/src/server/conversation/conversationEvents.js +192 -0
- package/src/server/conversation/conversationGoals.js +187 -0
- package/src/server/conversation/conversationHandles.js +237 -0
- package/src/server/conversation/conversationReads.js +206 -0
- package/src/server/conversation/conversationRefusals.js +58 -0
- package/src/server/conversation/conversationReplacement.js +254 -0
- package/src/server/conversation/conversationSending.js +273 -0
- package/src/server/conversation/conversationStopping.js +121 -0
- package/src/server/conversation/conversationTurnDelivery.js +413 -0
- package/src/server/conversation/conversationTurns.js +511 -0
- package/src/server/conversation/deliveryTiming.js +25 -3
- package/src/server/conversation/fileStorage.js +11 -6
- package/src/server/conversation/hookBridge.js +14 -3
- package/src/server/conversation/index.js +1 -1
- package/src/server/conversation/localExecution.js +82 -625
- package/src/server/conversation/nativeCommandCallbacks.js +10 -0
- package/src/server/conversation/nativeConversationLifecycle.js +379 -0
- package/src/server/conversation/nativeHistoryExport.js +27 -18
- package/src/server/conversation/normalize.js +9 -0
- package/src/server/conversation/openCodeClient.js +295 -366
- package/src/server/conversation/openCodeNativeHistory.js +141 -0
- package/src/server/conversation/openCodeNativeSession.js +79 -0
- package/src/server/conversation/openCodeProcess.js +109 -53
- package/src/server/conversation/openCodeProcessOwner.js +565 -0
- package/src/server/conversation/openCodeRuntime.js +111 -1814
- package/src/server/conversation/openCodeSessionTurns.js +547 -0
- package/src/server/conversation/openCodeTemporaryConversations.js +394 -0
- package/src/server/conversation/openCodeTools.js +28 -14
- package/src/server/conversation/openCodeTurn.js +80 -74
- package/src/server/conversation/openCodeTurnMonitor.js +388 -0
- package/src/server/conversation/processIdentity.js +582 -0
- package/src/server/conversation/provider.js +21 -6
- package/src/server/conversation/providers/api.js +268 -107
- package/src/server/conversation/providers/claude.js +29 -6
- package/src/server/conversation/providers/claudeDriver.js +325 -250
- package/src/server/conversation/providers/codex.js +69 -33
- package/src/server/conversation/providers/codexDriver.js +503 -1081
- package/src/server/conversation/providers/codexDriverConnection.js +331 -0
- package/src/server/conversation/providers/codexDriverTurns.js +464 -0
- package/src/server/conversation/providers/opencode.js +30 -4
- package/src/server/conversation/providers/opencodeDriver.js +321 -659
- package/src/server/conversation/providers/opencodeStandalone.js +475 -0
- package/src/server/conversation/providers/scoped.js +64 -19
- package/src/server/conversation/runtime.js +171 -2863
- package/src/server/conversation/storage.js +91 -50
- package/src/server/conversation/streams.js +15 -2
- package/src/server/conversation/structuredOutput.js +7 -11
- package/src/server/conversation/systemPrompt.js +13 -1
- package/src/server/conversation/tools.js +47 -15
- package/src/server/conversation/transcript.js +49 -20
- package/src/server/hosts/claudeTurn.js +2 -4
- package/src/server/hosts/codexConfiguration.js +6 -8
- package/src/server/hosts/codexEvents.js +4 -2
- package/src/server/hosts/codexProcess.js +1 -1
- package/src/server/hosts/codexProvider.js +11 -9
- package/src/server/hosts/codexTurn.js +3 -3
- package/src/server/hosts/openCodeClient.js +4 -2
- package/src/server/index.js +0 -2
- package/src/server/lib/aiConnectionClient.js +35 -25
- package/src/server/lib/ndjson.js +1 -0
- package/src/server/lib/providers/anthropicClient.js +15 -6
- package/src/server/lib/providers/common.js +10 -6
- package/src/server/lib/providers/openAiCompatibleClient.js +40 -23
- package/src/server/lib/serviceToolCatalog.js +29 -283
- package/src/server/lib/serviceToolEntries.js +270 -0
- package/src/server/types.js +78 -0
- package/src/shared/assistantResource.js +0 -23
- package/src/shared/conversation/answerChoiceSugar.js +2 -14
- package/src/shared/conversation/messageInputSugar.js +18 -0
- package/src/shared/conversation/numberedQuestionSugar.js +6 -16
- package/src/shared/conversation/pagination.js +1 -1
- package/src/shared/conversation/questions.js +1 -0
- package/src/shared/conversation/streaming.js +1 -0
- package/src/shared/index.js +0 -1
- package/src/testing/conversationStorageContract.js +20 -0
- package/test/aiConnectionClient.test.js +42 -1
- package/test/apiConversationDriver.test.js +217 -38
- package/test/assistantBoundedToolLoop.test.js +1 -1
- package/test/claudeConversationSession.test.js +15 -11
- package/test/claudeInstructionResume.test.js +1 -1
- package/test/claudeProcess.test.js +9 -9
- package/test/claudeTurn.test.js +21 -8
- package/test/codexAccountChange.test.js +5 -3
- package/test/codexConversationSession.test.js +22 -21
- package/test/codexEvents.test.js +3 -1
- package/test/codexHelperDeletion.test.js +3 -7
- package/test/codexIsolation.test.js +6 -6
- package/test/codexModelCatalog.test.js +6 -7
- package/test/codexProcess.test.js +3 -3
- package/test/codexThreadSelection.test.js +4 -1
- package/test/codexTurn.test.js +29 -20
- package/test/conversationApiFacilities.test.js +11 -9
- package/test/conversationAttachments.test.js +3 -3
- package/test/conversationConfiguration.test.js +4 -4
- package/test/conversationNativeStorage.test.js +11 -10
- package/test/conversationReplacement.test.js +13 -13
- package/test/conversationRuntime.test.js +9 -5
- package/test/conversationSession.test.js +78 -22
- package/test/conversationTools.test.js +47 -26
- package/test/fileConversationStorage.test.js +10 -3
- package/test/fixtures/codexHelperThreadLedger.js +3 -3
- package/test/frameworkActions.test.js +33 -18
- package/test/localConversationExecution.test.js +4 -4
- package/test/localExecution.test.js +1 -1
- package/test/openCodeConnectionChange.test.js +3 -2
- package/test/openCodeRuntime.test.js +8 -7
- package/test/openCodeTools.test.js +1 -1
- package/test/opencodeConversationSession.test.js +4 -3
- package/test/serviceToolCatalog.test.js +3 -8
- package/src/server/lib/assistantCompletion.js +0 -190
- package/src/server/lib/assistantToolLoop.js +0 -259
|
@@ -9,6 +9,20 @@ import { createAssistantActions } from "../../assistant-runtime/src/server/actio
|
|
|
9
9
|
import { accountSettingsResponseFormatter } from "../../users-core/src/server/common/formatters/accountSettingsResponseFormatter.js";
|
|
10
10
|
import { DEFAULT_USER_SETTINGS } from "../../users-core/src/shared/settings.js";
|
|
11
11
|
|
|
12
|
+
// Each action's definition with a run(service, input, context) that executes
|
|
13
|
+
// it against the service a test supplies.
|
|
14
|
+
function specificationsOf(createActions) {
|
|
15
|
+
let service = null;
|
|
16
|
+
const forwarding = new Proxy({}, { get: (_target, method) => (...args) => service[method](...args) });
|
|
17
|
+
return createActions(new Proxy({}, { get: () => forwarding })).map(({ execute, ...definition }) => ({
|
|
18
|
+
...definition,
|
|
19
|
+
run(nextService, input, context) {
|
|
20
|
+
service = nextService;
|
|
21
|
+
return execute(input, context);
|
|
22
|
+
}
|
|
23
|
+
}));
|
|
24
|
+
}
|
|
25
|
+
|
|
12
26
|
const specifications = [];
|
|
13
27
|
for (const owner of ["users-core", "workspaces-core", "console-core"]) {
|
|
14
28
|
const directory = new URL(`../../${owner}/src/server/`, import.meta.url);
|
|
@@ -16,7 +30,7 @@ for (const owner of ["users-core", "workspaces-core", "console-core"]) {
|
|
|
16
30
|
if (!path.endsWith("Actions.js")) continue;
|
|
17
31
|
const module = await import(new URL(path, directory));
|
|
18
32
|
for (const [name, value] of Object.entries(module)) {
|
|
19
|
-
if (
|
|
33
|
+
if (/^create\w+Actions$/u.test(name)) specifications.push(...specificationsOf(value));
|
|
20
34
|
}
|
|
21
35
|
}
|
|
22
36
|
}
|
|
@@ -30,8 +44,9 @@ const roleCatalog = { collaborationEnabled: true, defaultInviteRole: "member", r
|
|
|
30
44
|
const memberResult = { workspace, members: [], roleCatalog };
|
|
31
45
|
const invite = { id: "31", email: "invite@example.test", roleSid: "member", status: "pending", expiresAt: "2026-10-01" };
|
|
32
46
|
const invitesResult = { workspace, invites: [invite], roleCatalog };
|
|
47
|
+
// An auth service without the optional settings-profile operation: email is managed by auth.
|
|
33
48
|
const settings = accountSettingsResponseFormatter({
|
|
34
|
-
profile: { id: "7", displayName: "Alice", email: "alice@example.test" }, settings: DEFAULT_USER_SETTINGS
|
|
49
|
+
authService: {}, profile: { id: "7", displayName: "Alice", email: "alice@example.test" }, settings: DEFAULT_USER_SETTINGS
|
|
35
50
|
});
|
|
36
51
|
const workspaceSettings = {
|
|
37
52
|
workspace: { id: "11", slug: "acme", ownerUserId: "7" }, roleCatalog,
|
|
@@ -49,24 +64,24 @@ const fixtures = {
|
|
|
49
64
|
"settings.security.password_method.toggle": [{ enabled: true }, returnJsonApiData({ securityStatus: settings.security, settings, session: "SECRET" })],
|
|
50
65
|
"settings.security.oauth.unlink": [{ provider: "google" }, returnJsonApiData({ securityStatus: settings.security, providerMessageId: "SECRET" })],
|
|
51
66
|
"settings.security.sessions.logout_others": [{}, null],
|
|
52
|
-
"workspace.workspaces.create": [{ name: "Acme" }, { ...workspace, isPersonal: false, createdAt: "2026-09-13", updatedAt: "2026-09-13", deletedAt: null }],
|
|
53
|
-
"workspace.workspaces.list": [{}, [{ ...workspace, roleSid: "owner", isAccessible: true, membershipStatus: "active", createdAt: "2026-09-13" }]],
|
|
54
|
-
"workspace.workspaces.read": [{ workspaceSlug: "evil" }, { ...workspace, isPersonal: false, createdAt: "2026-09-13" }],
|
|
55
|
-
"workspace.workspaces.update": [{ workspaceSlug: "evil", name: "Acme" }, workspace],
|
|
56
|
-
"workspace.settings.read": [{ workspaceSlug: "evil" }, workspaceSettings],
|
|
57
|
-
"workspace.settings.update": [{ workspaceSlug: "evil", invitesEnabled: false }, workspaceSettings],
|
|
58
|
-
"workspace.roles.list": [{ workspaceSlug: "evil" }, roleCatalog],
|
|
59
|
-
"workspace.members.list": [{ workspaceSlug: "evil" }, memberResult],
|
|
60
|
-
"workspace.member.role.update": [{ workspaceSlug: "evil", memberUserId: "8", roleSid: "member" }, memberResult],
|
|
61
|
-
"workspace.member.remove": [{ workspaceSlug: "evil", memberUserId: "8" }, memberResult],
|
|
62
|
-
"workspace.invites.list": [{ workspaceSlug: "evil" }, invitesResult],
|
|
63
|
-
"workspace.invite.create": [{ workspaceSlug: "evil", email: invite.email, roleSid: "member" }, {
|
|
67
|
+
"workspace.workspaces.create": [{ name: "Acme" }, returnJsonApiData({ ...workspace, isPersonal: false, createdAt: "2026-09-13", updatedAt: "2026-09-13", deletedAt: null })],
|
|
68
|
+
"workspace.workspaces.list": [{}, returnJsonApiData({ items: [{ ...workspace, roleSid: "owner", isAccessible: true, membershipStatus: "active", createdAt: "2026-09-13" }], nextCursor: null })],
|
|
69
|
+
"workspace.workspaces.read": [{ workspaceSlug: "evil" }, returnJsonApiData({ ...workspace, isPersonal: false, createdAt: "2026-09-13" })],
|
|
70
|
+
"workspace.workspaces.update": [{ workspaceSlug: "evil", name: "Acme" }, returnJsonApiData(workspace)],
|
|
71
|
+
"workspace.settings.read": [{ workspaceSlug: "evil" }, returnJsonApiData(workspaceSettings)],
|
|
72
|
+
"workspace.settings.update": [{ workspaceSlug: "evil", invitesEnabled: false }, returnJsonApiData(workspaceSettings)],
|
|
73
|
+
"workspace.roles.list": [{ workspaceSlug: "evil" }, returnJsonApiData(roleCatalog)],
|
|
74
|
+
"workspace.members.list": [{ workspaceSlug: "evil" }, returnJsonApiData(memberResult)],
|
|
75
|
+
"workspace.member.role.update": [{ workspaceSlug: "evil", memberUserId: "8", roleSid: "member" }, returnJsonApiData(memberResult)],
|
|
76
|
+
"workspace.member.remove": [{ workspaceSlug: "evil", memberUserId: "8" }, returnJsonApiData(memberResult)],
|
|
77
|
+
"workspace.invites.list": [{ workspaceSlug: "evil" }, returnJsonApiData(invitesResult)],
|
|
78
|
+
"workspace.invite.create": [{ workspaceSlug: "evil", email: invite.email, roleSid: "member" }, returnJsonApiData({
|
|
64
79
|
...invitesResult, createdInviteId: "31", inviteTokenPreview: "SECRET", inviteUrl: "https://example.test/invite?token=SECRET",
|
|
65
80
|
inviteDelivery: { status: "sent", message: "SECRET", providerMessageId: "SECRET" }
|
|
66
|
-
}],
|
|
67
|
-
"workspace.invite.revoke": [{ workspaceSlug: "evil", inviteId: "31" }, { ...invitesResult, revokedInviteId: "31" }],
|
|
68
|
-
"workspace.invitations.pending.list": [{}, [{ id: "31", workspaceId: "11", workspaceSlug: "acme", workspaceName: "Acme",
|
|
69
|
-
workspaceAvatarUrl: "", roleSid: "member", status: "pending", expiresAt: "2026-10-01", token: "SECRET" }]]
|
|
81
|
+
})],
|
|
82
|
+
"workspace.invite.revoke": [{ workspaceSlug: "evil", inviteId: "31" }, returnJsonApiData({ ...invitesResult, revokedInviteId: "31" })],
|
|
83
|
+
"workspace.invitations.pending.list": [{}, returnJsonApiData({ pendingInvites: [{ id: "31", workspaceId: "11", workspaceSlug: "acme", workspaceName: "Acme",
|
|
84
|
+
workspaceAvatarUrl: "", roleSid: "member", status: "pending", expiresAt: "2026-10-01", token: "SECRET" }] })]
|
|
70
85
|
};
|
|
71
86
|
|
|
72
87
|
function catalogFor(spec, result, observe = () => {}) {
|
|
@@ -12,7 +12,7 @@ function host(t) {
|
|
|
12
12
|
t.after(() => execution.close());
|
|
13
13
|
return execution;
|
|
14
14
|
}
|
|
15
|
-
const spec = (code) => ({ command: process.execPath, args: ["-e", code], cwd: process.cwd(), stream: true });
|
|
15
|
+
const spec = (code) => ({ command: process.execPath, args: ["-e", code], cwd: process.cwd(), env: process.env, stream: true });
|
|
16
16
|
|
|
17
17
|
test("the supplied execution host carries native protocol bytes and proves owned cleanup", options, async (t) => {
|
|
18
18
|
const execution = host(t);
|
|
@@ -59,14 +59,14 @@ test("native command requests use the same execution owner and retain bounded ca
|
|
|
59
59
|
t.after(() => rm(directory, { recursive: true, force: true }));
|
|
60
60
|
const captured = await execution.run({ command: process.execPath,
|
|
61
61
|
args: ["-e", 'process.stdout.write("x".repeat(70000)); process.stderr.write("finished");'],
|
|
62
|
-
cwd: directory, mode: "capture" });
|
|
62
|
+
cwd: directory, baseEnv: process.env, mode: "capture" });
|
|
63
63
|
assert.equal(captured.ok, true);
|
|
64
64
|
assert.equal(Buffer.byteLength(captured.output), 64 * 1024);
|
|
65
65
|
assert.ok(captured.output.endsWith("finished"));
|
|
66
66
|
const logPath = join(directory, "detached.log");
|
|
67
67
|
const child = await execution.run({ command: process.execPath,
|
|
68
68
|
args: ["-e", 'process.stderr.write("ready"); setInterval(() => {}, 1000);'],
|
|
69
|
-
cwd: directory, mode: "detached", logPath });
|
|
69
|
+
cwd: directory, baseEnv: process.env, mode: "detached", logPath });
|
|
70
70
|
assert.equal(child.ok, true);
|
|
71
71
|
assert.ok(Number.isSafeInteger(child.pid) && child.pid > 0);
|
|
72
72
|
const deadline = Date.now() + 3000;
|
|
@@ -83,7 +83,7 @@ test("native capture timeout drains its execution before returning", options, as
|
|
|
83
83
|
const execution = host(t);
|
|
84
84
|
const result = await execution.run({ command: process.execPath,
|
|
85
85
|
args: ["-e", 'process.stdout.write(String(process.pid)); setInterval(() => {}, 1000);'],
|
|
86
|
-
cwd: process.cwd(), mode: "capture", timeout: 500 });
|
|
86
|
+
cwd: process.cwd(), baseEnv: process.env, mode: "capture", timeout: 500 });
|
|
87
87
|
assert.equal(result.ok, false);
|
|
88
88
|
assert.equal(result.timedOut, true);
|
|
89
89
|
assert.throws(() => process.kill(Number(result.output), 0), { code: "ESRCH" });
|
|
@@ -2,7 +2,7 @@ import assert from "node:assert/strict";
|
|
|
2
2
|
import { spawn } from "node:child_process";
|
|
3
3
|
import { randomUUID } from "node:crypto";
|
|
4
4
|
import { test } from "node:test";
|
|
5
|
-
import { createConversationProcessIdentity } from "../src/server/conversation/
|
|
5
|
+
import { createConversationProcessIdentity } from "../src/server/conversation/processIdentity.js";
|
|
6
6
|
|
|
7
7
|
const TOKEN_ENV = "JSKIT_EXECUTION_RUNTIME_TOKEN";
|
|
8
8
|
const HASH_ENV = "JSKIT_EXECUTION_COMMAND_HASH";
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
2
|
import test from "node:test";
|
|
3
3
|
import { setTimeout as delay } from "node:timers/promises";
|
|
4
|
-
import { createOpenCodeSharedRuntime
|
|
4
|
+
import { createOpenCodeSharedRuntime } from "../src/server/conversation/openCodeRuntime.js";
|
|
5
|
+
import { openCodeServerForDirectory } from "../src/server/conversation/openCodeNativeSession.js";
|
|
5
6
|
|
|
6
7
|
// One shared server whose native directories record what OpenCode is asked to do.
|
|
7
8
|
function sharedServer() {
|
|
@@ -31,7 +32,7 @@ function sharedServer() {
|
|
|
31
32
|
const deepseek = (fingerprint, apiKey) => ({ modelProviderId: "deepseek", canonicalUrl: "", endpointCode: "native", fingerprint, apiKey });
|
|
32
33
|
|
|
33
34
|
async function startedRuntime(saved) {
|
|
34
|
-
const runtime = createOpenCodeSharedRuntime();
|
|
35
|
+
const runtime = createOpenCodeSharedRuntime({});
|
|
35
36
|
const native = sharedServer();
|
|
36
37
|
const shared = await runtime.ensure(saved[0], async () => ({ server: native.server, connections: [...saved] }), async () => saved);
|
|
37
38
|
return { runtime, native, shared };
|
|
@@ -4,11 +4,12 @@ import os from "node:os";
|
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import test from "node:test";
|
|
6
6
|
import { setTimeout as delay } from "node:timers/promises";
|
|
7
|
-
import { createOpenCodeSharedRuntime,
|
|
7
|
+
import { createOpenCodeSharedRuntime, readOpenCodeEnvironments } from "../src/server/conversation/openCodeRuntime.js";
|
|
8
|
+
import { ensureOpenCodeSession, openCodeServerForDirectory } from "../src/server/conversation/openCodeNativeSession.js";
|
|
8
9
|
import { createOpenCodeServerClient } from "../src/server/conversation/openCodeClient.js";
|
|
9
10
|
|
|
10
11
|
function fixture(options = {}) {
|
|
11
|
-
const runtime = createOpenCodeSharedRuntime();
|
|
12
|
+
const runtime = createOpenCodeSharedRuntime({});
|
|
12
13
|
const starts = [], stops = [];
|
|
13
14
|
const connection = { modelProviderId: "provider", canonicalUrl: "https://provider.invalid", fingerprint: "account", endpointCode: "api" };
|
|
14
15
|
const start = async selected => {
|
|
@@ -37,7 +38,7 @@ function fixture(options = {}) {
|
|
|
37
38
|
}
|
|
38
39
|
|
|
39
40
|
test("history projection supplies ordered reasoning facts without native part envelopes", async () => {
|
|
40
|
-
const runtime = createOpenCodeSharedRuntime();
|
|
41
|
+
const runtime = createOpenCodeSharedRuntime({});
|
|
41
42
|
const message = { id: "reply", type: "assistant", time: { created: 1_000 }, content: [
|
|
42
43
|
{ type: "tool", id: "tool", text: "Not reasoning" },
|
|
43
44
|
{ type: "reasoning", id: "first", text: " First thought\n", time: { end: 0 }, nativeOnly: true },
|
|
@@ -250,7 +251,7 @@ test("deleting a scoped OpenCode conversation returns its scope's exit proof and
|
|
|
250
251
|
});
|
|
251
252
|
|
|
252
253
|
test("an active OpenCode turn with missing native identity still fails instead of claiming Stop", async () => {
|
|
253
|
-
const runtime = createOpenCodeSharedRuntime();
|
|
254
|
+
const runtime = createOpenCodeSharedRuntime({});
|
|
254
255
|
const client = createOpenCodeServerClient({ baseUrl: "http://127.0.0.1:1",
|
|
255
256
|
fetchImpl: async () => assert.fail("Invalid identity must fail before transport.") });
|
|
256
257
|
const turn = { active: true, state: "inProgress" };
|
|
@@ -447,7 +448,7 @@ test("OpenCode bindings share atomic publication without overwriting another con
|
|
|
447
448
|
const root = await mkdtemp(path.join(os.tmpdir(), "jskit-opencode-bindings-"));
|
|
448
449
|
t.after(() => rm(root, { force: true, recursive: true }));
|
|
449
450
|
const registry = path.join(root, "private", "session-environments.json");
|
|
450
|
-
const runtime = createOpenCodeSharedRuntime();
|
|
451
|
+
const runtime = createOpenCodeSharedRuntime({});
|
|
451
452
|
const main = { upstreamSessionId: "main", workdir: "/work/main", env: { OWNER: "main" } };
|
|
452
453
|
const colleague = { upstreamSessionId: "colleague", workdir: "/work/colleague", systemPrompt: "Current instructions" };
|
|
453
454
|
await Promise.all([
|
|
@@ -467,7 +468,7 @@ test("OpenCode bindings share atomic publication without overwriting another con
|
|
|
467
468
|
|
|
468
469
|
|
|
469
470
|
test("native output identity is opt-in for every existing OpenCode projection writer", async () => {
|
|
470
|
-
const runtime = createOpenCodeSharedRuntime();
|
|
471
|
+
const runtime = createOpenCodeSharedRuntime({});
|
|
471
472
|
for (const configured of [false, true]) {
|
|
472
473
|
const writes = [];
|
|
473
474
|
const streams = [];
|
|
@@ -545,7 +546,7 @@ async function finalReaderMonitorFixture(t, { publication, checkpoint, recovery
|
|
|
545
546
|
}
|
|
546
547
|
|
|
547
548
|
test("native final projection opt-in returns only its original published receipt without changing default fields", async () => {
|
|
548
|
-
const runtime = createOpenCodeSharedRuntime();
|
|
549
|
+
const runtime = createOpenCodeSharedRuntime({});
|
|
549
550
|
const originalTurn = { turnId: "authored", assistant: { messageId: "real-final:assistant",
|
|
550
551
|
outputId: "real-final:assistant", role: "assistant", text: "Actual final." } };
|
|
551
552
|
const rows = [{ id: "accepted", type: "user", time: { created: 1 } },
|
|
@@ -118,7 +118,7 @@ test("one native plugin routes current instructions, command policy and tools by
|
|
|
118
118
|
parameters: { type: "object", properties: { query: { type: "string" } } } } }];
|
|
119
119
|
process.env.JSKIT_OPENCODE_ENV_REGISTRY = registry;
|
|
120
120
|
process.env.JSKIT_OPENCODE_TOOL_SCHEMAS = JSON.stringify(schemas);
|
|
121
|
-
const owner = createOpenCodeSharedRuntime();
|
|
121
|
+
const owner = createOpenCodeSharedRuntime({});
|
|
122
122
|
const calls = [];
|
|
123
123
|
const bindings = [];
|
|
124
124
|
for (const id of ["first", "second"]) {
|
|
@@ -525,7 +525,7 @@ function applicationActions(execute) {
|
|
|
525
525
|
const actions = createActionCatalogue();
|
|
526
526
|
actions.register({ contributorId: "test.tools", domain: "numbers", actions: [{
|
|
527
527
|
id: "numbers.read", version: 1, kind: "query", channels: ["automation"], surfaces: ["app"],
|
|
528
|
-
permission: { require: "all", permissions: ["numbers.read"] },
|
|
528
|
+
permission: { require: "all", permissions: ["numbers.read"] },
|
|
529
529
|
input: { schema: createSchema({}), mode: "replace" },
|
|
530
530
|
output: { schema: createSchema({ value: { type: "number", required: true } }), mode: "replace" }, execute
|
|
531
531
|
}] });
|
|
@@ -894,7 +894,7 @@ for (const change of ["model", "effort"]) {
|
|
|
894
894
|
if (change === "model") model = "other-model";
|
|
895
895
|
const selection = { operationId: `fresh-${change}`, expectedSegmentId: before.segmentId, engine: "opencode",
|
|
896
896
|
configuration: { ...configuration, model, effort: change === "effort" ? "high" : "low" }, retireNative: true };
|
|
897
|
-
await assert.rejects(f.conversation.select({ ...selection, retireNative: "yes" }), { code: "
|
|
897
|
+
await assert.rejects(f.conversation.select({ ...selection, retireNative: "yes" }), { code: "conversation_replacement_request_invalid" });
|
|
898
898
|
assert.deepEqual(await f.binding(), original);
|
|
899
899
|
const selected = await f.conversation.select(selection);
|
|
900
900
|
assert.notEqual(selected.segmentId, before.segmentId);
|
|
@@ -1098,7 +1098,8 @@ for (const savedStatus of ["cancelled", "failed", "unknown-tool", "interrupted"]
|
|
|
1098
1098
|
// The original standalone fixture above remains byte-for-byte unchanged.
|
|
1099
1099
|
async function boundToolFixture(t, { runInContext, monitorRunInContext, monitorContext } = {}) {
|
|
1100
1100
|
const { createOpenCodeConversationServer, openCodeApplicationToolSchemas } = await import("../src/server/conversation/openCodeProcess.js");
|
|
1101
|
-
const { createOpenCodeSharedRuntime
|
|
1101
|
+
const { createOpenCodeSharedRuntime } = await import("../src/server/conversation/openCodeRuntime.js");
|
|
1102
|
+
const { ensureOpenCodeSession } = await import("../src/server/conversation/openCodeNativeSession.js");
|
|
1102
1103
|
const { observeOpenCodeEvents } = await import("../src/server/conversation/openCodeTurn.js");
|
|
1103
1104
|
let owner;
|
|
1104
1105
|
const handles = [];
|
|
@@ -40,7 +40,6 @@ function action({
|
|
|
40
40
|
permission,
|
|
41
41
|
input,
|
|
42
42
|
output,
|
|
43
|
-
idempotency: "none",
|
|
44
43
|
extensions,
|
|
45
44
|
execute
|
|
46
45
|
};
|
|
@@ -284,8 +283,7 @@ test("large authorized catalogs use compact paged discovery, exact contracts, an
|
|
|
284
283
|
input: workspaceInput,
|
|
285
284
|
extensions: {
|
|
286
285
|
assistant: {
|
|
287
|
-
alwaysAvailable: true
|
|
288
|
-
preflight: ["CURRENT-TIME", "current-time"]
|
|
286
|
+
alwaysAvailable: true
|
|
289
287
|
}
|
|
290
288
|
},
|
|
291
289
|
execute: async (input, context) => {
|
|
@@ -327,7 +325,6 @@ test("large authorized catalogs use compact paged discovery, exact contracts, an
|
|
|
327
325
|
"demo_clock_read"
|
|
328
326
|
]);
|
|
329
327
|
const clockTool = toolSet.tools.find((entry) => entry.actionId === "demo.clock.read");
|
|
330
|
-
assert.deepEqual(clockTool.preflight, ["current-time"]);
|
|
331
328
|
assert.equal(clockTool.alwaysAvailable, true);
|
|
332
329
|
assert.equal(clockTool.parameters.required?.includes("workspaceSlug") || false, false);
|
|
333
330
|
assert.deepEqual(await catalog.executeToolCall({
|
|
@@ -489,8 +486,7 @@ test("discovery mode bounds authorized always-available tools", () => {
|
|
|
489
486
|
id: `demo.clock-${String(index + 1).padStart(2, "0")}.read`,
|
|
490
487
|
extensions: {
|
|
491
488
|
assistant: {
|
|
492
|
-
alwaysAvailable: true
|
|
493
|
-
preflight: ["current-time"]
|
|
489
|
+
alwaysAvailable: true
|
|
494
490
|
}
|
|
495
491
|
}
|
|
496
492
|
}));
|
|
@@ -499,8 +495,7 @@ test("discovery mode bounds authorized always-available tools", () => {
|
|
|
499
495
|
permission: { require: "all", permissions: ["clock.read"] },
|
|
500
496
|
extensions: {
|
|
501
497
|
assistant: {
|
|
502
|
-
alwaysAvailable: true
|
|
503
|
-
preflight: ["current-time"]
|
|
498
|
+
alwaysAvailable: true
|
|
504
499
|
}
|
|
505
500
|
}
|
|
506
501
|
}));
|
|
@@ -1,190 +0,0 @@
|
|
|
1
|
-
import { normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
|
|
2
|
-
|
|
3
|
-
function extractTextDelta(deltaContent) {
|
|
4
|
-
if (typeof deltaContent === "string") {
|
|
5
|
-
return deltaContent;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
if (!Array.isArray(deltaContent)) {
|
|
9
|
-
return "";
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
return deltaContent
|
|
13
|
-
.map((entry) => {
|
|
14
|
-
if (typeof entry === "string") {
|
|
15
|
-
return entry;
|
|
16
|
-
}
|
|
17
|
-
if (!entry || typeof entry !== "object") {
|
|
18
|
-
return "";
|
|
19
|
-
}
|
|
20
|
-
return String(entry.text || "");
|
|
21
|
-
})
|
|
22
|
-
.join("");
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function sanitizeAssistantMessageText(value, { preserveWhitespace = false } = {}) {
|
|
26
|
-
let source = String(value || "");
|
|
27
|
-
if (!source) {
|
|
28
|
-
return "";
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const blockPatterns = [
|
|
32
|
-
/<[^>\n]*function_calls[^>\n]*>[\s\S]*?(?:<\/[^>\n]*function_calls>|$)/gi,
|
|
33
|
-
/<[^>\n]*tool_calls?[^>\n]*>[\s\S]*?(?:<\/[^>\n]*tool_calls?[^>\n]*>|$)/gi,
|
|
34
|
-
/<[^>\n]*invoke\b[^>\n]*>[\s\S]*?(?:<\/[^>\n]*invoke>|$)/gi,
|
|
35
|
-
/<(?:analysis|reasoning|think)>[\s\S]*?(?:<\/(?:analysis|reasoning|think)>|$)/gi
|
|
36
|
-
];
|
|
37
|
-
for (const pattern of blockPatterns) {
|
|
38
|
-
source = source.replace(pattern, " ");
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const inlineTagPatterns = [
|
|
42
|
-
/<[^>\n]*invoke\b[^>]*\/>/gi,
|
|
43
|
-
/<\/?[^>\n]*invoke[^>\n]*>/gi,
|
|
44
|
-
/<\/?[^>\n]*function_calls[^>\n]*>/gi,
|
|
45
|
-
/<\/?[^>\n]*tool_calls?[^>\n]*>/gi,
|
|
46
|
-
/<\/?[^>\n]*DSML[^>\n]*>/gi
|
|
47
|
-
];
|
|
48
|
-
for (const pattern of inlineTagPatterns) {
|
|
49
|
-
source = source.replace(pattern, " ");
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
if (preserveWhitespace) return source;
|
|
53
|
-
|
|
54
|
-
return source
|
|
55
|
-
.split(/\r?\n/)
|
|
56
|
-
.map((line) => line.trim())
|
|
57
|
-
.filter(Boolean)
|
|
58
|
-
.join("\n");
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function parseDsmlToolCallsFromText(value = "") {
|
|
62
|
-
const source = String(value || "");
|
|
63
|
-
if (!source) {
|
|
64
|
-
return [];
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
const functionCallsMatch = source.match(
|
|
68
|
-
/<[^>\n]*function_calls[^>\n]*>([\s\S]*?)<\/[^>\n]*function_calls>/i
|
|
69
|
-
);
|
|
70
|
-
if (!functionCallsMatch) {
|
|
71
|
-
return [];
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
const blockText = String(functionCallsMatch[1] || "");
|
|
75
|
-
if (!blockText) {
|
|
76
|
-
return [];
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
const calls = [];
|
|
80
|
-
const invokePattern = /<[^>\n]*invoke\b([^>]*)>([\s\S]*?)<\/[^>\n]*invoke>/gi;
|
|
81
|
-
let match = invokePattern.exec(blockText);
|
|
82
|
-
while (match) {
|
|
83
|
-
const attributes = String(match[1] || "");
|
|
84
|
-
const body = normalizeText(String(match[2] || ""));
|
|
85
|
-
const quotedNameMatch =
|
|
86
|
-
attributes.match(/\bname\s*=\s*"([^"]+)"/i) || attributes.match(/\bname\s*=\s*'([^']+)'/i);
|
|
87
|
-
const bareNameMatch = attributes.match(/\bname\s*=\s*([^\s"'/>]+)/i);
|
|
88
|
-
const name = normalizeText(quotedNameMatch?.[1] || bareNameMatch?.[1]);
|
|
89
|
-
if (name) {
|
|
90
|
-
calls.push({
|
|
91
|
-
id: `dsml_tool_call_${calls.length + 1}`,
|
|
92
|
-
name,
|
|
93
|
-
arguments: body && /^[\[{]/.test(body) ? body : "{}"
|
|
94
|
-
});
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
match = invokePattern.exec(blockText);
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
return calls;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
async function consumeCompletionStream(stream, onText = () => {}, {
|
|
104
|
-
signal, limits, preserveWhitespace = false, requireCallIds = false, callIdPrefix = ""
|
|
105
|
-
} = {}) {
|
|
106
|
-
let assistantText = "";
|
|
107
|
-
let finishReason;
|
|
108
|
-
const toolCallsByIndex = new Map();
|
|
109
|
-
|
|
110
|
-
function checkToolLimits(call, count) {
|
|
111
|
-
if (!limits) return;
|
|
112
|
-
if (limits.maxToolCalls === 0) throw new Error("This conversation has no configured application tools.");
|
|
113
|
-
if (count > limits.maxToolCalls || call.name.length > 256 || call.id.length > 256 ||
|
|
114
|
-
Buffer.byteLength(call.arguments) > limits.maxToolArgumentBytes) {
|
|
115
|
-
throw new Error("The model exceeded the configured application tool-call limits.");
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
for await (const chunk of stream) {
|
|
120
|
-
signal?.throwIfAborted();
|
|
121
|
-
const choice = chunk?.choices?.[0] || {};
|
|
122
|
-
const delta = choice?.delta || {};
|
|
123
|
-
finishReason = choice.finish_reason || finishReason;
|
|
124
|
-
|
|
125
|
-
const textDelta = extractTextDelta(delta.content);
|
|
126
|
-
if (textDelta) {
|
|
127
|
-
assistantText += textDelta;
|
|
128
|
-
if (limits && assistantText.length > limits.maxOutputCharacters) {
|
|
129
|
-
throw new Error("The assistant exceeded the configured response limit.");
|
|
130
|
-
}
|
|
131
|
-
// An opening provider tag can be split between chunks. Keep it private
|
|
132
|
-
// until its boundary is known; complete internal blocks are stripped below.
|
|
133
|
-
const tagStart = assistantText.lastIndexOf("<");
|
|
134
|
-
const safeText = tagStart > assistantText.lastIndexOf(">") ? assistantText.slice(0, tagStart) : assistantText;
|
|
135
|
-
await onText(sanitizeAssistantMessageText(safeText, { preserveWhitespace }));
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
const toolCalls = Array.isArray(delta.tool_calls) ? delta.tool_calls : [];
|
|
139
|
-
for (const partialToolCall of toolCalls) {
|
|
140
|
-
const index = Number(partialToolCall?.index || 0);
|
|
141
|
-
const existing =
|
|
142
|
-
toolCallsByIndex.get(index) ||
|
|
143
|
-
{
|
|
144
|
-
id: normalizeText(partialToolCall?.id) || (requireCallIds ? "" : `tool_call_${index + 1}`),
|
|
145
|
-
name: "",
|
|
146
|
-
arguments: ""
|
|
147
|
-
};
|
|
148
|
-
|
|
149
|
-
if (partialToolCall?.id) {
|
|
150
|
-
existing.id = normalizeText(partialToolCall.id) || existing.id;
|
|
151
|
-
}
|
|
152
|
-
if (partialToolCall?.function?.name) {
|
|
153
|
-
existing.name += String(partialToolCall.function.name || "");
|
|
154
|
-
}
|
|
155
|
-
if (partialToolCall?.function?.arguments) {
|
|
156
|
-
existing.arguments += String(partialToolCall.function.arguments || "");
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
toolCallsByIndex.set(index, existing);
|
|
160
|
-
checkToolLimits(existing, toolCallsByIndex.size);
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
let toolCalls = [...toolCallsByIndex.values()].map((toolCall, index) => ({
|
|
165
|
-
id: normalizeText(toolCall.id) || (requireCallIds ? "" : `tool_call_${index + 1}`),
|
|
166
|
-
name: normalizeText(toolCall.name),
|
|
167
|
-
arguments: String(toolCall.arguments || "")
|
|
168
|
-
}));
|
|
169
|
-
|
|
170
|
-
let toolCallSource = toolCalls.length ? "structured" : "";
|
|
171
|
-
if (toolCalls.length < 1) {
|
|
172
|
-
const parsedDsmlCalls = parseDsmlToolCallsFromText(assistantText);
|
|
173
|
-
if (parsedDsmlCalls.length > 0) {
|
|
174
|
-
toolCallSource = "text";
|
|
175
|
-
toolCalls = parsedDsmlCalls.map(call => ({ ...call, id: `${callIdPrefix}${call.id}` }));
|
|
176
|
-
for (const call of toolCalls) checkToolLimits(call, toolCalls.length);
|
|
177
|
-
const visible = sanitizeAssistantMessageText(assistantText, { preserveWhitespace });
|
|
178
|
-
assistantText = preserveWhitespace ? visible : normalizeText(visible);
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
return {
|
|
183
|
-
assistantText,
|
|
184
|
-
toolCalls,
|
|
185
|
-
toolCallSource,
|
|
186
|
-
finishReason
|
|
187
|
-
};
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
export { consumeCompletionStream, sanitizeAssistantMessageText };
|