@schlenkr/ragents 0.1.8 → 0.1.9
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/apps/server/src/config-definition.ts +24 -2
- package/apps/server/src/config.ts +0 -6
- package/apps/server/src/host-api.json +3 -10
- package/apps/server/src/host-api.ts +6 -7
- package/apps/server/src/plugin-build/build.ts +28 -5
- package/apps/server/src/plugin-build/plugin-description.ts +1 -1
- package/apps/server/src/plugin-build/typecheck.ts +2 -2
- package/apps/server/src/plugin-support/language-server/plugin.ts +8 -7
- package/apps/server/src/plugin-support/language-server/tools.ts +7 -6
- package/apps/server/src/plugin-support/model-aliases.ts +55 -25
- package/apps/server/src/plugin-support/product-relay.ts +10 -2
- package/apps/server/src/plugin-support/workspace-sandbox-host.ts +18 -9
- package/apps/server/src/profile/bundle-manifest.ts +7 -3
- package/apps/server/src/profile/compose.ts +6 -0
- package/apps/server/src/profile/plugin-discovery.ts +6 -1
- package/apps/server/src/profile/provisioning.ts +6 -5
- package/apps/server/src/ragents/engine.ts +0 -1
- package/apps/server/src/ragents/host-services.ts +4 -0
- package/apps/server/src/ragents/session.ts +1 -1
- package/apps/server/src/server.ts +1 -0
- package/apps/server/tsconfig.json +2 -0
- package/apps/web/dist/help/guide-clients.html +14 -6
- package/apps/web/dist/help/guide-clients.md +14 -6
- package/apps/web/dist/help/guide-distributed.html +12 -4
- package/apps/web/dist/help/guide-distributed.md +12 -4
- package/apps/web/dist/help/guide-getting-started.html +12 -8
- package/apps/web/dist/help/guide-getting-started.md +12 -8
- package/apps/web/dist/help/guide-plugins.html +6 -2
- package/apps/web/dist/help/guide-plugins.md +6 -2
- package/apps/web/dist/help/guide-runtime.html +2 -1
- package/apps/web/dist/help/guide-runtime.md +2 -1
- package/apps/web/dist/help/index.html +3328 -1135
- package/apps/web/dist/host-web.json +3 -3
- package/bundles/ragents.activity/ragents-bundle.json +3 -3
- package/bundles/ragents.actor-programs/ragents-bundle.json +3 -3
- package/bundles/ragents.ask/ragents-bundle.json +4 -4
- package/bundles/ragents.ask/server/index.js +42 -11
- package/bundles/ragents.ask/server/index.js.map +3 -3
- package/bundles/ragents.ask/web/chunks/chunk-WFN4374U.js +2 -0
- package/bundles/ragents.ask/web/chunks/chunk-WFN4374U.js.map +7 -0
- package/bundles/ragents.ask/web/classes.json +1 -1
- package/bundles/ragents.ask/web/exports/ask-payload.js +1 -1
- package/bundles/ragents.ask/web/index.js +1 -1
- package/bundles/ragents.ask/web/index.js.map +3 -3
- package/bundles/ragents.browser/executor/index.mjs +357 -0
- package/bundles/ragents.browser/executor/index.mjs.map +7 -0
- package/bundles/ragents.browser/ragents-bundle.json +5 -8
- package/bundles/ragents.browser/server/chunks/{chunk-2GKLLCBE.js → chunk-CWLX4VA5.js} +1 -1
- package/bundles/ragents.browser/server/chunks/chunk-CWLX4VA5.js.map +7 -0
- package/bundles/ragents.browser/server/exports/server/contract.js +1 -1
- package/bundles/ragents.browser/server/index.js +20 -9
- package/bundles/ragents.browser/server/index.js.map +4 -4
- package/bundles/ragents.documents/ragents-bundle.json +3 -3
- package/bundles/ragents.lsp-fsharp/executor/index.mjs +68 -0
- package/bundles/ragents.lsp-fsharp/executor/index.mjs.map +7 -0
- package/bundles/ragents.lsp-fsharp/ragents-bundle.json +5 -9
- package/bundles/ragents.lsp-fsharp/server/index.js +21 -6
- package/bundles/ragents.lsp-fsharp/server/index.js.map +4 -4
- package/bundles/ragents.lsp-roslyn/executor/index.mjs +68 -0
- package/bundles/ragents.lsp-roslyn/executor/index.mjs.map +7 -0
- package/bundles/ragents.lsp-roslyn/ragents-bundle.json +6 -8
- package/bundles/ragents.lsp-roslyn/server/index.js +54 -15
- package/bundles/ragents.lsp-roslyn/server/index.js.map +4 -4
- package/bundles/ragents.lsp-typescript/executor/index.mjs +49 -0
- package/bundles/ragents.lsp-typescript/executor/index.mjs.map +7 -0
- package/bundles/ragents.lsp-typescript/ragents-bundle.json +5 -7
- package/bundles/ragents.lsp-typescript/server/index.js +22 -2
- package/bundles/ragents.lsp-typescript/server/index.js.map +3 -3
- package/bundles/ragents.model-relay/ragents-bundle.json +4 -4
- package/bundles/ragents.model-relay/server/index.js +3 -2
- package/bundles/ragents.model-relay/server/index.js.map +2 -2
- package/bundles/ragents.orchestration/ragents-bundle.json +3 -3
- package/bundles/ragents.overseer/ragents-bundle.json +3 -3
- package/bundles/ragents.processes/ragents-bundle.json +3 -3
- package/bundles/ragents.product/ragents-bundle.json +3 -3
- package/bundles/ragents.profile-distribution/ragents-bundle.json +4 -4
- package/bundles/ragents.profile-distribution/server/index.js +1 -1
- package/bundles/ragents.profile-distribution/server/index.js.map +2 -2
- package/bundles/ragents.reference/ragents-bundle.json +3 -3
- package/bundles/ragents.todo/ragents-bundle.json +3 -3
- package/bundles/ragents.transcript/ragents-bundle.json +3 -3
- package/bundles/ragents.watch/ragents-bundle.json +3 -3
- package/bundles/ragents.workspace/ragents-bundle.json +11 -5
- package/bundles/ragents.workspace/server/index.js +101 -28
- package/bundles/ragents.workspace/server/index.js.map +3 -3
- package/bundles/ragents.workspace/web/classes.json +1 -1
- package/bundles/ragents.workspace/web/index.js +1 -1
- package/bundles/ragents.workspace/web/index.js.map +3 -3
- package/package.json +2 -2
- package/packages/agent/dist/core/agent-settings.d.ts +1 -7
- package/packages/agent/dist/core/compaction/compaction.d.ts +17 -13
- package/packages/agent/dist/core/model-runtime.d.ts +5 -4
- package/packages/agent/dist/core/provider-composer.d.ts +2 -1
- package/packages/agent/dist/core/tools/bash.d.ts +9 -3
- package/packages/agent/dist/index.d.ts +1 -1
- package/packages/agent/src/core/agent-settings.ts +1 -4
- package/packages/agent/src/core/compaction/compaction.ts +68 -30
- package/packages/agent/src/core/model-runtime.ts +12 -5
- package/packages/agent/src/core/provider-composer.ts +5 -0
- package/packages/agent/src/core/tools/bash.ts +36 -30
- package/packages/agent/src/index.ts +3 -0
- package/packages/ai/dist/types.d.ts +11 -0
- package/packages/ai/src/types.ts +12 -0
- package/packages/ragents/src/domain/event-semantics.ts +3 -0
- package/packages/ragents/src/domain/event-validation.ts +15 -2
- package/packages/ragents/src/domain/events.ts +3 -0
- package/packages/ragents/src/domain/model.ts +2 -0
- package/packages/ragents/src/domain/projection.ts +1 -0
- package/packages/ragents/src/drivers/agent-runtime.ts +1 -1
- package/packages/ragents/src/drivers/agent-turn.ts +8 -6
- package/packages/ragents/src/runtime/decisions/inputs.ts +6 -2
- package/packages/ragents/src/runtime/journal-storage.ts +5 -4
- package/packages/workspace-executor/package.json +0 -1
- package/packages/workspace-executor/src/bash-launch.ts +56 -10
- package/packages/workspace-executor/src/context.ts +4 -0
- package/packages/workspace-executor/src/contributions.ts +150 -0
- package/packages/workspace-executor/src/executor.ts +1 -1
- package/packages/workspace-executor/src/index.ts +16 -19
- package/packages/workspace-executor/src/language-server/host.ts +3 -0
- package/packages/workspace-executor/src/language-server/module.ts +12 -2
- package/packages/workspace-executor/src/language-server/roots.ts +0 -5
- package/packages/workspace-executor/src/modules.ts +7 -6
- package/packages/workspace-executor/src/sandbox-tools.ts +3 -9
- package/packages/workspace-executor/src/shell-platform.ts +14 -3
- package/packages/workspace-executor/src/tools.ts +0 -3
- package/plugins/ragents.ask/ask-payload.ts +9 -0
- package/plugins/ragents.ask/server/ask-service.ts +53 -13
- package/plugins/ragents.ask/web/AskActionView.tsx +3 -2
- package/plugins/ragents.browser/executor/index.ts +5 -0
- package/{packages/workspace-executor/src/browser → plugins/ragents.browser/executor}/module.ts +25 -31
- package/{packages/workspace-executor/src/browser → plugins/ragents.browser/executor}/playwright.ts +9 -23
- package/plugins/ragents.browser/provision.ts +1 -1
- package/plugins/ragents.browser/server/browser.ts +3 -3
- package/plugins/ragents.browser/server/contract.ts +2 -2
- package/plugins/ragents.browser/server/index.ts +1 -1
- package/plugins/ragents.lsp-fsharp/executor.ts +80 -0
- package/plugins/ragents.lsp-fsharp/provision.ts +1 -1
- package/plugins/ragents.lsp-fsharp/server/index.ts +2 -2
- package/plugins/ragents.lsp-roslyn/executor.ts +64 -0
- package/plugins/ragents.lsp-roslyn/provision.ts +1 -1
- package/plugins/ragents.lsp-roslyn/server/index.ts +28 -5
- package/plugins/ragents.lsp-roslyn/server/solution-on-start.ts +18 -6
- package/plugins/ragents.lsp-typescript/executor.ts +50 -0
- package/plugins/ragents.lsp-typescript/server/index.ts +2 -2
- package/plugins/ragents.model-relay/server/config.ts +1 -1
- package/plugins/ragents.model-relay/server/relay.ts +4 -2
- package/plugins/ragents.profile-distribution/server/archive.ts +2 -1
- package/plugins/ragents.workspace/client/workspace-client.ts +111 -28
- package/plugins/ragents.workspace/contract.ts +33 -2
- package/plugins/ragents.workspace/server/binding.ts +10 -4
- package/plugins/ragents.workspace/server/clients.ts +56 -9
- package/plugins/ragents.workspace/server/config.ts +9 -0
- package/plugins/ragents.workspace/server/index.ts +13 -4
- package/plugins/ragents.workspace/server/runtime.ts +10 -0
- package/plugins/ragents.workspace/server/shell-platform.ts +6 -5
- package/plugins/ragents.workspace/web/WorkspaceBinding.tsx +1 -1
- package/ragents.config.core.ts +4 -1
- package/ragents.config.showcase.ts +4 -1
- package/scripts/agent/journal.ts +3 -1
- package/scripts/package/build-package.ts +3 -1
- package/scripts/provision/run-provision.ts +2 -2
- package/scripts/workspace-client/run-workspace-client.ts +7 -4
- package/apps/web/dist/help/screenshots/release-checklist-vscode.png +0 -0
- package/apps/web/dist/help/screenshots/release-checklist-web.png +0 -0
- package/bundles/ragents.ask/web/chunks/chunk-HGFDGTNC.js +0 -2
- package/bundles/ragents.ask/web/chunks/chunk-HGFDGTNC.js.map +0 -7
- package/bundles/ragents.browser/server/chunks/chunk-2GKLLCBE.js.map +0 -7
- package/packages/workspace-executor/src/language-server/adapters/fsharp.ts +0 -60
- package/packages/workspace-executor/src/language-server/adapters/index.ts +0 -7
- package/packages/workspace-executor/src/language-server/adapters/roslyn.ts +0 -56
- package/packages/workspace-executor/src/language-server/adapters/typescript.ts +0 -74
- package/packages/workspace-executor/src/language-server/solution.ts +0 -13
- /package/{packages/workspace-executor/src/browser → plugins/ragents.browser/executor}/contract.ts +0 -0
- /package/{packages/workspace-executor/src/browser → plugins/ragents.browser/executor}/pages.ts +0 -0
|
@@ -520,6 +520,15 @@ export interface ModelCost extends ModelCostRates {
|
|
|
520
520
|
/** Request-wide pricing tiers. The highest matching input threshold applies to the full request. */
|
|
521
521
|
tiers?: ModelCostTier[];
|
|
522
522
|
}
|
|
523
|
+
/** When an agent compacts its context on a model and how much it keeps, in absolute tokens of that model. */
|
|
524
|
+
export interface ModelCompaction {
|
|
525
|
+
/** Context tokens above which the agent compacts. */
|
|
526
|
+
threshold: number;
|
|
527
|
+
/** Tokens of the newest context the compaction keeps verbatim. */
|
|
528
|
+
keepRecentTokens: number;
|
|
529
|
+
/** Output budget of the summary. */
|
|
530
|
+
summaryTokens: number;
|
|
531
|
+
}
|
|
523
532
|
export interface Model<TApi extends Api> {
|
|
524
533
|
id: string;
|
|
525
534
|
name: string;
|
|
@@ -536,6 +545,8 @@ export interface Model<TApi extends Api> {
|
|
|
536
545
|
cost: ModelCost;
|
|
537
546
|
contextWindow: number;
|
|
538
547
|
maxTokens: number;
|
|
548
|
+
/** The model's own compaction values; without them the agent applies the catalog standard of its context window. */
|
|
549
|
+
compaction?: ModelCompaction;
|
|
539
550
|
headers?: Record<string, string>;
|
|
540
551
|
/** Compatibility overrides for OpenAI-compatible APIs. If not set, auto-detected from baseUrl. */
|
|
541
552
|
compat?: TApi extends "openai-completions" ? OpenAICompletionsCompat : TApi extends "openai-responses" | "openai-codex-responses" ? OpenAIResponsesCompat : TApi extends "anthropic-messages" ? AnthropicMessagesCompat : never;
|
package/packages/ai/src/types.ts
CHANGED
|
@@ -561,6 +561,16 @@ export interface ModelCost extends ModelCostRates {
|
|
|
561
561
|
tiers?: ModelCostTier[];
|
|
562
562
|
}
|
|
563
563
|
|
|
564
|
+
/** When an agent compacts its context on a model and how much it keeps, in absolute tokens of that model. */
|
|
565
|
+
export interface ModelCompaction {
|
|
566
|
+
/** Context tokens above which the agent compacts. */
|
|
567
|
+
threshold: number;
|
|
568
|
+
/** Tokens of the newest context the compaction keeps verbatim. */
|
|
569
|
+
keepRecentTokens: number;
|
|
570
|
+
/** Output budget of the summary. */
|
|
571
|
+
summaryTokens: number;
|
|
572
|
+
}
|
|
573
|
+
|
|
564
574
|
// Model interface for the unified model system
|
|
565
575
|
export interface Model<TApi extends Api> {
|
|
566
576
|
id: string;
|
|
@@ -578,6 +588,8 @@ export interface Model<TApi extends Api> {
|
|
|
578
588
|
cost: ModelCost;
|
|
579
589
|
contextWindow: number;
|
|
580
590
|
maxTokens: number;
|
|
591
|
+
/** The model's own compaction values; without them the agent applies the catalog standard of its context window. */
|
|
592
|
+
compaction?: ModelCompaction;
|
|
581
593
|
headers?: Record<string, string>;
|
|
582
594
|
/** Compatibility overrides for OpenAI-compatible APIs. If not set, auto-detected from baseUrl. */
|
|
583
595
|
compat?: TApi extends "openai-completions"
|
|
@@ -358,6 +358,9 @@ export const assertEventSemantics = (
|
|
|
358
358
|
for (const artifactId of event.payload.artifactIds)
|
|
359
359
|
assertArtifactRead(state, event.actorId, artifactId);
|
|
360
360
|
|
|
361
|
+
if (event.payload.origin === "human" && existingActorOf(state, event.actorId).kind !== "human")
|
|
362
|
+
throw new Error(`Input ${event.payload.inputId} of human origin was not enqueued by a human actor.`);
|
|
363
|
+
|
|
361
364
|
if (event.payload.subscriptionId) {
|
|
362
365
|
const subscription = state.subscriptions.get(event.payload.subscriptionId);
|
|
363
366
|
|
|
@@ -431,9 +431,13 @@ const payloadOf = (type: EventType, value: unknown, path: string) => {
|
|
|
431
431
|
"artifactIds",
|
|
432
432
|
"sourceEventIds",
|
|
433
433
|
"subscriptionId",
|
|
434
|
-
], ["presentation"]);
|
|
434
|
+
], ["presentation", "origin"]);
|
|
435
435
|
if (payload.presentation !== undefined && payload.presentation !== "background")
|
|
436
436
|
fail(`${path}.presentation`, "must be background");
|
|
437
|
+
if (payload.origin !== undefined && payload.origin !== "human")
|
|
438
|
+
fail(`${path}.origin`, "must be human");
|
|
439
|
+
if (subscriptionInput && Object.hasOwn(payload, "origin"))
|
|
440
|
+
fail(`${path}.origin`, "is not supported for a subscription input");
|
|
437
441
|
stringOf(payload.inputId, `${path}.inputId`);
|
|
438
442
|
actorIdOf(payload.actorId, `${path}.actorId`);
|
|
439
443
|
stringArrayOf(payload.artifactIds, `${path}.artifactIds`);
|
|
@@ -545,7 +549,7 @@ const payloadOf = (type: EventType, value: unknown, path: string) => {
|
|
|
545
549
|
"model",
|
|
546
550
|
"readFiles",
|
|
547
551
|
"modifiedFiles",
|
|
548
|
-
]);
|
|
552
|
+
], ["threshold"]);
|
|
549
553
|
stringOf(payload.turnId, `${path}.turnId`);
|
|
550
554
|
nonEmptyStringOf(payload.summary, `${path}.summary`);
|
|
551
555
|
nonEmptyStringOf(payload.firstKeptEventId, `${path}.firstKeptEventId`);
|
|
@@ -554,6 +558,15 @@ const payloadOf = (type: EventType, value: unknown, path: string) => {
|
|
|
554
558
|
nonEmptyStringOf(payload.model, `${path}.model`);
|
|
555
559
|
stringArrayOf(payload.readFiles, `${path}.readFiles`);
|
|
556
560
|
stringArrayOf(payload.modifiedFiles, `${path}.modifiedFiles`);
|
|
561
|
+
|
|
562
|
+
if (Object.hasOwn(payload, "threshold")) {
|
|
563
|
+
const threshold = exactObject(payload.threshold, `${path}.threshold`, ["tokens", "source"]);
|
|
564
|
+
if (!Number.isSafeInteger(threshold.tokens))
|
|
565
|
+
fail(`${path}.threshold.tokens`, "must be a safe integer");
|
|
566
|
+
if (threshold.source !== "model" && threshold.source !== "catalog")
|
|
567
|
+
fail(`${path}.threshold.source`, "must be model or catalog");
|
|
568
|
+
}
|
|
569
|
+
|
|
557
570
|
return;
|
|
558
571
|
}
|
|
559
572
|
|
|
@@ -93,6 +93,7 @@ export type EventPayloads = {
|
|
|
93
93
|
actorId: ActorId;
|
|
94
94
|
artifactIds: string[];
|
|
95
95
|
presentation?: "background";
|
|
96
|
+
origin?: "human";
|
|
96
97
|
} & (
|
|
97
98
|
| { subscriptionId: null; sourceEventIds: string[]; content: string }
|
|
98
99
|
| { subscriptionId: SubscriptionId; sourceEventIds: [string]; content?: never }
|
|
@@ -151,6 +152,8 @@ export type EventPayloads = {
|
|
|
151
152
|
model: string;
|
|
152
153
|
readFiles: string[];
|
|
153
154
|
modifiedFiles: string[];
|
|
155
|
+
/** The compaction threshold of the model and whether it is the model's own or the catalog standard; absent before file format 9. */
|
|
156
|
+
threshold?: { tokens: number; source: "model" | "catalog" };
|
|
154
157
|
};
|
|
155
158
|
"model.output.completed": {
|
|
156
159
|
turnId: TurnId;
|
|
@@ -89,6 +89,8 @@ export type ActorInput = {
|
|
|
89
89
|
actorId: ActorId;
|
|
90
90
|
content: string;
|
|
91
91
|
presentation?: "background";
|
|
92
|
+
/** Set only on a person's own message, because system inputs are enqueued under the owner too. */
|
|
93
|
+
origin?: "human";
|
|
92
94
|
artifactIds: readonly ArtifactId[];
|
|
93
95
|
enqueuedBy: ActorId;
|
|
94
96
|
enqueuedAt: string;
|
|
@@ -222,6 +222,7 @@ export function applyEvent(
|
|
|
222
222
|
actorId: event.payload.actorId,
|
|
223
223
|
content: event.payload.subscriptionId === null ? event.payload.content : JSON.stringify(source),
|
|
224
224
|
...(event.payload.presentation ? { presentation: event.payload.presentation } : {}),
|
|
225
|
+
...(event.payload.origin ? { origin: event.payload.origin } : {}),
|
|
225
226
|
artifactIds: event.payload.artifactIds,
|
|
226
227
|
enqueuedBy: event.actorId,
|
|
227
228
|
enqueuedAt: event.occurredAt,
|
|
@@ -36,7 +36,7 @@ export type AgentRuntimeManagerOptions = {
|
|
|
36
36
|
context: AgentRuntimeContext,
|
|
37
37
|
signal: AbortSignal,
|
|
38
38
|
) => readonly AgentHook[] | Promise<readonly AgentHook[]>;
|
|
39
|
-
/**
|
|
39
|
+
/** Retry and provider request limits; unset values keep the defaults. Compaction comes from the model. */
|
|
40
40
|
settings?: AgentSettingsInput;
|
|
41
41
|
turnAbortTimeoutMs?: number;
|
|
42
42
|
onDiagnostic?: (diagnostic: AgentRuntimeDiagnostic) => void;
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
type AgentTool,
|
|
11
11
|
calculateContextTokens,
|
|
12
12
|
compact,
|
|
13
|
+
compactionOf,
|
|
13
14
|
contextMessages,
|
|
14
15
|
convertToLlm,
|
|
15
16
|
EMPTY_RESPONSE_NUDGE,
|
|
@@ -626,12 +627,11 @@ export class AgentTurn {
|
|
|
626
627
|
return true;
|
|
627
628
|
}
|
|
628
629
|
|
|
629
|
-
/** After a context overflow compacts and retries once; over the threshold compacts without a retry. */
|
|
630
|
+
/** After a context overflow compacts and retries once; over the threshold of the turn's model compacts without a retry. */
|
|
630
631
|
async #checkCompaction(assistant: AssistantMessage | undefined, skipAborted = true): Promise<boolean> {
|
|
631
|
-
const settings = this.#options.settings.compaction;
|
|
632
632
|
const { model } = this.#options;
|
|
633
633
|
|
|
634
|
-
if (!assistant ||
|
|
634
|
+
if (!assistant || (skipAborted && assistant.stopReason === "aborted"))
|
|
635
635
|
return false;
|
|
636
636
|
|
|
637
637
|
const contextWindow = model.contextWindow ?? 0;
|
|
@@ -672,12 +672,13 @@ export class AgentTurn {
|
|
|
672
672
|
contextTokens = estimate.tokens;
|
|
673
673
|
}
|
|
674
674
|
|
|
675
|
-
return shouldCompact(contextTokens,
|
|
675
|
+
return shouldCompact(contextTokens, compactionOf(model).values) ? this.#compact(false) : false;
|
|
676
676
|
}
|
|
677
677
|
|
|
678
678
|
async #compact(retry: boolean): Promise<boolean> {
|
|
679
|
-
const { model, modelRuntime, thinkingLevel
|
|
680
|
-
const
|
|
679
|
+
const { model, modelRuntime, thinkingLevel } = this.#options;
|
|
680
|
+
const compaction = compactionOf(model);
|
|
681
|
+
const preparation = prepareCompaction([...this.#request.modelContext().entries], compaction.values);
|
|
681
682
|
|
|
682
683
|
if (!preparation)
|
|
683
684
|
return false;
|
|
@@ -715,6 +716,7 @@ export class AgentTurn {
|
|
|
715
716
|
model: model.id,
|
|
716
717
|
readFiles: result.readFiles,
|
|
717
718
|
modifiedFiles: result.modifiedFiles,
|
|
719
|
+
threshold: { tokens: compaction.values.threshold, source: compaction.source },
|
|
718
720
|
},
|
|
719
721
|
});
|
|
720
722
|
const messages = this.#request.modelContext().messages;
|
|
@@ -17,8 +17,8 @@ export type EnqueueInput = {
|
|
|
17
17
|
artifactIds?: readonly string[];
|
|
18
18
|
presentation?: "background";
|
|
19
19
|
} & (
|
|
20
|
-
| { content: string; sourceEventIds?: readonly string[]; subscriptionId?: null }
|
|
21
|
-
| { content?: never; sourceEventIds: readonly [string]; subscriptionId: string }
|
|
20
|
+
| { content: string; sourceEventIds?: readonly string[]; subscriptionId?: null; origin?: "human" }
|
|
21
|
+
| { content?: never; sourceEventIds: readonly [string]; subscriptionId: string; origin?: never }
|
|
22
22
|
);
|
|
23
23
|
|
|
24
24
|
export const enqueueInput =
|
|
@@ -43,6 +43,9 @@ export const enqueueInput =
|
|
|
43
43
|
assertCapability(caller, "actor.input", { kind: "run" });
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
if (input.origin === "human" && caller.kind !== "human")
|
|
47
|
+
throw new DomainError("input-origin-invalid", "Only a human can enqueue an input of human origin.", 403);
|
|
48
|
+
|
|
46
49
|
const artifactIds = [...new Set(input.artifactIds ?? [])];
|
|
47
50
|
|
|
48
51
|
for (const artifactId of artifactIds)
|
|
@@ -55,6 +58,7 @@ export const enqueueInput =
|
|
|
55
58
|
actorId: target.id,
|
|
56
59
|
artifactIds,
|
|
57
60
|
...(input.presentation !== undefined ? { presentation: input.presentation } : {}),
|
|
61
|
+
...(input.origin !== undefined ? { origin: input.origin } : {}),
|
|
58
62
|
};
|
|
59
63
|
|
|
60
64
|
if (subscriptionId && firstSourceEventId === undefined)
|
|
@@ -8,9 +8,10 @@ import { isRunId } from "../domain/portable-id.ts";
|
|
|
8
8
|
import { syncDirectory } from "./durable-fs.ts";
|
|
9
9
|
import type { CommandRecord, JournalCommand } from "./journal.ts";
|
|
10
10
|
|
|
11
|
-
/** Rises whenever an older stand would reject newly written lines;
|
|
12
|
-
export const journalStorageVersion =
|
|
13
|
-
const
|
|
11
|
+
/** Rises whenever an older stand would reject newly written lines; formats 7 and 8 stay readable because they only lack the input origin and the compaction threshold. */
|
|
12
|
+
export const journalStorageVersion = 9;
|
|
13
|
+
const oldestReadableJournalStorageVersion = 7;
|
|
14
|
+
const readableJournalStorageVersions: readonly unknown[] = [oldestReadableJournalStorageVersion, 8, journalStorageVersion];
|
|
14
15
|
export const journalPayloadThresholdBytes = 4096;
|
|
15
16
|
|
|
16
17
|
type PayloadReference = { sha256: string; bytes: number };
|
|
@@ -162,7 +163,7 @@ export const parseJournalRecord = (line: string, runDirectory: string, location:
|
|
|
162
163
|
|
|
163
164
|
if (!readableJournalStorageVersions.includes(record.formatVersion))
|
|
164
165
|
throw new Error(`${location} has unsupported journal format version ${String(record.formatVersion)}.${
|
|
165
|
-
typeof record.formatVersion === "number" && record.formatVersion <
|
|
166
|
+
typeof record.formatVersion === "number" && record.formatVersion < oldestReadableJournalStorageVersion
|
|
166
167
|
? " Journals before format 7 hold no model context and are not migrated."
|
|
167
168
|
: ""}`);
|
|
168
169
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { existsSync } from "node:fs";
|
|
1
|
+
import { accessSync, constants, existsSync, statSync } from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { getShellConfig } from "@ragents/agent";
|
|
4
4
|
|
|
@@ -9,6 +9,12 @@ export interface BashLaunch {
|
|
|
9
9
|
readonly env: NodeJS.ProcessEnv;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
+
/** Was der Executor dem Werkzeug bash mitgibt: die Bash selbst und rg; ohne Angabe gilt jeweils das des Systems. */
|
|
13
|
+
export interface BashPrograms {
|
|
14
|
+
readonly bash: string | undefined;
|
|
15
|
+
readonly rg: string | undefined;
|
|
16
|
+
}
|
|
17
|
+
|
|
12
18
|
/** Unter Windows sucht RAgents keine Bash; der Executor nennt die mitgebrachte. */
|
|
13
19
|
const missingWindowsBash = (): Error => new Error(
|
|
14
20
|
"Unter Windows arbeitet das Werkzeug bash nur mit der Bash, die RAgents mitbringt, und für diesen Executor ist keine genannt. "
|
|
@@ -16,17 +22,51 @@ const missingWindowsBash = (): Error => new Error(
|
|
|
16
22
|
+ "und reicht sie an Arbeitsplatz und lokalen Host weiter; ein Host oder Arbeitsplatz ohne Erweiterung braucht RAGENTS_BASH mit dem Pfad dieser bash.exe.",
|
|
17
23
|
);
|
|
18
24
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
+
const missingRipgrep = (rg: string): Error =>
|
|
26
|
+
new Error(`Das rg dieses Executors fehlt: ${rg}. Es gehört zur Erweiterung (dist/rg/<plattform>) oder steht in RAGENTS_RG.`);
|
|
27
|
+
|
|
28
|
+
const pathModule = (platform: NodeJS.Platform): path.PlatformPath => platform === "win32" ? path.win32 : path.posix;
|
|
29
|
+
|
|
30
|
+
/** Die Namen der PATH-Variablen; unter Windows zählt jede Schreibweise (`Path`). */
|
|
31
|
+
const pathNames = (env: NodeJS.ProcessEnv, platform: NodeJS.Platform): readonly string[] =>
|
|
32
|
+
platform === "win32" ? Object.keys(env).filter((name) => name.toUpperCase() === "PATH") : ["PATH"];
|
|
33
|
+
|
|
34
|
+
const currentPath = (env: NodeJS.ProcessEnv, platform: NodeJS.Platform): string | undefined =>
|
|
35
|
+
pathNames(env, platform).map((name) => env[name]).find((value) => value !== undefined && value !== "");
|
|
36
|
+
|
|
37
|
+
/** Die Ordner vorn im PATH, als eine Variable PATH; MSYSTEM gehört unter Windows einer Git-Bash-Anmeldung und fällt weg. */
|
|
38
|
+
const withPathFront = (env: NodeJS.ProcessEnv, directories: readonly string[], platform: NodeJS.Platform): NodeJS.ProcessEnv => {
|
|
39
|
+
if (directories.length === 0) return env;
|
|
40
|
+
const names = pathNames(env, platform);
|
|
41
|
+
const current = currentPath(env, platform);
|
|
42
|
+
const kept = Object.entries(env).filter(([name]) => !names.includes(name) && (platform !== "win32" || name !== "MSYSTEM"));
|
|
43
|
+
const delimiter = pathModule(platform).delimiter;
|
|
44
|
+
return { ...Object.fromEntries(kept), PATH: [...directories, ...(current ? [current] : [])].join(delimiter) };
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const isExecutableFile = (file: string, platform: NodeJS.Platform): boolean => {
|
|
48
|
+
try {
|
|
49
|
+
if (platform !== "win32") accessSync(file, constants.X_OK);
|
|
50
|
+
return statSync(file).isFile();
|
|
51
|
+
} catch {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
/** Ob die Bash dieses Executors rg findet: das genannte, sonst eines im PATH; ein genanntes, das fehlt, ist ein Fehler. */
|
|
57
|
+
export const ripgrepAvailable = (rg: string | undefined, env: NodeJS.ProcessEnv, platform: NodeJS.Platform = process.platform): boolean => {
|
|
58
|
+
if (rg !== undefined) {
|
|
59
|
+
if (!existsSync(rg)) throw missingRipgrep(rg);
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
const file = platform === "win32" ? "rg.exe" : "rg";
|
|
63
|
+
return (currentPath(env, platform) ?? "").split(pathModule(platform).delimiter).filter(Boolean)
|
|
64
|
+
.some((directory) => isExecutableFile(path.join(directory, file), platform));
|
|
25
65
|
};
|
|
26
66
|
|
|
27
|
-
/** Wie ein Befehl in der Bash dieses Executors startet
|
|
67
|
+
/** Wie ein Befehl in der Bash dieses Executors startet: das Verzeichnis von rg vorn im PATH, unter Windows dahinter das usr/bin der Bash, damit find.exe und sort.exe aus System32 nicht gewinnen. */
|
|
28
68
|
export const bashLaunch = (
|
|
29
|
-
bash
|
|
69
|
+
{ bash, rg }: BashPrograms,
|
|
30
70
|
command: string,
|
|
31
71
|
env: NodeJS.ProcessEnv,
|
|
32
72
|
platform: NodeJS.Platform = process.platform,
|
|
@@ -35,11 +75,17 @@ export const bashLaunch = (
|
|
|
35
75
|
if (bash !== undefined && !existsSync(bash)) {
|
|
36
76
|
throw new Error(`Die Bash dieses Executors fehlt: ${bash}. Unter Windows gehört sie zur Erweiterung (dist/bash/<plattform>) oder steht in RAGENTS_BASH.`);
|
|
37
77
|
}
|
|
78
|
+
if (rg !== undefined && !existsSync(rg)) throw missingRipgrep(rg);
|
|
38
79
|
const shell = getShellConfig(bash, platform);
|
|
39
80
|
if (!/bash(\.exe)?$/i.test(shell.shell)) throw new Error(`${shell.shell} ist keine Bash; das Werkzeug heißt bash und der Prompt beschreibt bash`);
|
|
81
|
+
const paths = pathModule(platform);
|
|
82
|
+
const front = [
|
|
83
|
+
...(rg === undefined ? [] : [paths.dirname(rg)]),
|
|
84
|
+
...(platform === "win32" ? [paths.dirname(shell.shell)] : []),
|
|
85
|
+
];
|
|
40
86
|
return {
|
|
41
87
|
command: shell.shell,
|
|
42
88
|
args: [...shell.args, command],
|
|
43
|
-
env:
|
|
89
|
+
env: withPathFront(env, front, platform),
|
|
44
90
|
};
|
|
45
91
|
};
|
|
@@ -27,6 +27,8 @@ export interface WorkspaceProcessContext {
|
|
|
27
27
|
sandbox?: ProcessSandbox;
|
|
28
28
|
/** Die Bash, mit der das Werkzeug bash startet; unter Windows Pflicht, sonst gilt ohne Angabe die des Systems. */
|
|
29
29
|
bash?: string;
|
|
30
|
+
/** Das rg, dessen Ordner das Werkzeug bash vorn im PATH hat; ohne Angabe gilt eines im PATH, falls es eines gibt. */
|
|
31
|
+
rg?: string;
|
|
30
32
|
}
|
|
31
33
|
|
|
32
34
|
export interface SandboxHomeEnvironment {
|
|
@@ -103,6 +105,7 @@ export interface WorkspaceContextOptions {
|
|
|
103
105
|
source?: NodeJS.ProcessEnv;
|
|
104
106
|
baseEnvironment?: BaseEnvironment;
|
|
105
107
|
bash?: string;
|
|
108
|
+
rg?: string;
|
|
106
109
|
}
|
|
107
110
|
|
|
108
111
|
const namedEntries = (
|
|
@@ -151,5 +154,6 @@ export const workspaceProcessContext = (options: WorkspaceContextOptions): Works
|
|
|
151
154
|
...(options.runOperation === undefined ? {} : { runOperation: options.runOperation }),
|
|
152
155
|
...(options.sandbox === undefined ? {} : { sandbox: options.sandbox }),
|
|
153
156
|
...(options.bash === undefined ? {} : { bash: options.bash }),
|
|
157
|
+
...(options.rg === undefined ? {} : { rg: options.rg }),
|
|
154
158
|
};
|
|
155
159
|
};
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
import { createRequire } from "node:module";
|
|
4
|
+
import { homedir } from "node:os";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { pathToFileURL } from "node:url";
|
|
7
|
+
import { WorkspaceOperationError } from "./errors.js";
|
|
8
|
+
import type { LanguageServerAdapter } from "./language-server/host.js";
|
|
9
|
+
import { resolveRootDirectory, resolveRootFile } from "./language-server/roots.js";
|
|
10
|
+
import type { WorkspaceModuleFactory } from "./module.js";
|
|
11
|
+
import { RUN_MARKER_ENV } from "./run-marker.js";
|
|
12
|
+
import { safeProcessEnvironment } from "./safe-environment.js";
|
|
13
|
+
|
|
14
|
+
/** Wo ein Bundle seinen Beitrag zum Executor trägt: eine in sich geschlossene Datei, die jeder Node-Prozess ohne Host-Auflösung lädt. */
|
|
15
|
+
export const EXECUTOR_CONTRIBUTION_FILE = "executor/index.mjs";
|
|
16
|
+
|
|
17
|
+
/** Was der Executor einer Maschine einem Beitrag über sie sagt; ein Beitrag importiert vom Host nur Typen, alles andere kommt hierüber. */
|
|
18
|
+
export interface WorkspaceExecutorMachine {
|
|
19
|
+
/** Der Werkzeugordner des Plugins auf dieser Maschine, wohin seine Provisionierung lädt. */
|
|
20
|
+
readonly toolsDirectory: string;
|
|
21
|
+
/** Eine Datei aus einem Paket in den node_modules des Hosts dieser Maschine; ohne Host oder Paket scheitert der Aufruf mit Ursache. */
|
|
22
|
+
readonly hostPackageFile: (hostRoot: string | undefined, specifier: string) => string;
|
|
23
|
+
/** Eine Datei mit einer der Endungen im Arbeitsbereich, aufgelöst und geprüft. */
|
|
24
|
+
readonly resolveRootFile: (workspaceRoot: string, requested: string, extensions: readonly string[]) => Promise<string>;
|
|
25
|
+
/** Ein Ordner im Arbeitsbereich, aufgelöst und geprüft. */
|
|
26
|
+
readonly resolveRootDirectory: (workspaceRoot: string, requested: string) => Promise<string>;
|
|
27
|
+
/** Ein fachlicher Fehler einer Operation mit Kennung und Status; beim Aufrufer kommt er als derselbe Fehler an wie einer des Executors. */
|
|
28
|
+
readonly operationError: (code: string, message: string, status: number) => Error;
|
|
29
|
+
/** Die Umgebung eines Prozesses, den ein Beitrag selbst startet: die sichere Auswahl dieser Maschine, ihr HOME und der Marker des Runs für die Prozessanzeige. */
|
|
30
|
+
readonly processEnvironment: (runId: string) => NodeJS.ProcessEnv;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Was ein Beitrag dem Executor einer Maschine hinzufügt. */
|
|
34
|
+
export interface WorkspaceExecutorParts {
|
|
35
|
+
readonly languageServers?: readonly LanguageServerAdapter[];
|
|
36
|
+
/** Module mit eigenen Operationen; jede Operation gehört genau einem Modul. */
|
|
37
|
+
readonly modules?: readonly WorkspaceModuleFactory[];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Was ein Plugin-Bundle als `executor` exportiert; es läuft in jedem Executor, im Server wie auf jedem Arbeitsplatz, und berührt beim Bauen weder Platte noch Netz. */
|
|
41
|
+
export type WorkspaceExecutorContribution = (machine: WorkspaceExecutorMachine) => WorkspaceExecutorParts;
|
|
42
|
+
|
|
43
|
+
/** Wessen Beitrag in welchem Stand; Server und Arbeitsplatz verlangen dieselben. */
|
|
44
|
+
export interface ExecutorContributionStand {
|
|
45
|
+
readonly plugin: string;
|
|
46
|
+
/** SHA-256 der Datei des Beitrags. */
|
|
47
|
+
readonly stand: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface LoadedExecutorContribution extends ExecutorContributionStand {
|
|
51
|
+
readonly contribution: WorkspaceExecutorContribution;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Ein Beitrag, gebaut für diese Maschine. */
|
|
55
|
+
export interface PreparedExecutorContribution extends ExecutorContributionStand {
|
|
56
|
+
readonly parts: WorkspaceExecutorParts;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const LANGUAGE_SERVER_ID = /^[a-z][a-z0-9]{0,39}$/;
|
|
60
|
+
const PART_KEYS = new Set(["languageServers", "modules"]);
|
|
61
|
+
|
|
62
|
+
const messageOf = (cause: unknown): string => cause instanceof Error ? cause.message : String(cause);
|
|
63
|
+
|
|
64
|
+
/** Der Paketname eines Bezeichners wie acme-server/lib/cli.mjs oder @scope/name/datei. */
|
|
65
|
+
const packageOf = (specifier: string): string =>
|
|
66
|
+
specifier.split("/").slice(0, specifier.startsWith("@") ? 2 : 1).join("/");
|
|
67
|
+
|
|
68
|
+
const hostPackageFile = (hostRoot: string | undefined, specifier: string): string => {
|
|
69
|
+
if (!hostRoot) {
|
|
70
|
+
throw new Error(`Auf diesem Rechner ist kein Host bekannt, aus dem sich ${packageOf(specifier)} auflösen ließe. Ein Arbeitsplatz `
|
|
71
|
+
+ "bekommt ihn mit der ersten Verbindung zu einem verteilenden Server oder über die Einstellung ragents.hostPath");
|
|
72
|
+
}
|
|
73
|
+
try {
|
|
74
|
+
return createRequire(path.join(hostRoot, "package.json")).resolve(specifier);
|
|
75
|
+
} catch (cause) {
|
|
76
|
+
throw new Error(`${packageOf(specifier)} liegt nicht im Host ${hostRoot}: ${messageOf(cause)}`);
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
export const executorMachine = (toolsDirectory: string): WorkspaceExecutorMachine => ({
|
|
81
|
+
toolsDirectory,
|
|
82
|
+
hostPackageFile,
|
|
83
|
+
resolveRootFile,
|
|
84
|
+
resolveRootDirectory,
|
|
85
|
+
operationError: (code, message, status) => new WorkspaceOperationError(code, message, status),
|
|
86
|
+
processEnvironment: (runId) => ({ ...safeProcessEnvironment(process.env), HOME: homedir(), [RUN_MARKER_ENV]: runId }),
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
/** Lädt die Datei eines Beitrags, mit `expected` nur in diesem Stand; der Stand steht in ihrer Adresse, damit ein neu gebauter Beitrag nie aus dem Modulcache kommt. */
|
|
90
|
+
export const loadExecutorContribution = async (plugin: string, file: string, expected?: string): Promise<LoadedExecutorContribution> => {
|
|
91
|
+
const content = await readFile(file).catch((cause: unknown) => {
|
|
92
|
+
throw new Error(`Der Executor-Beitrag von ${plugin} fehlt unter ${file}: ${messageOf(cause)}`);
|
|
93
|
+
});
|
|
94
|
+
const stand = createHash("sha256").update(content).digest("hex");
|
|
95
|
+
if (expected !== undefined && stand !== expected) {
|
|
96
|
+
throw new Error(`Der Executor-Beitrag von ${plugin} unter ${file} hat den Stand ${stand.slice(0, 12)}, verlangt ist ${expected.slice(0, 12)}`);
|
|
97
|
+
}
|
|
98
|
+
const exported = await import(`${pathToFileURL(file).href}?stand=${stand}`).catch((cause: unknown) => {
|
|
99
|
+
throw new Error(`Der Executor-Beitrag von ${plugin} (${file}) lädt nicht: ${messageOf(cause)}`, { cause });
|
|
100
|
+
}) as Readonly<Record<string, unknown>>;
|
|
101
|
+
const contribution = exported.executor;
|
|
102
|
+
if (typeof contribution !== "function") {
|
|
103
|
+
throw new Error(`Das Bundle ${plugin} exportiert in ${file} keinen gültigen Executor-Beitrag; erwartet wird `
|
|
104
|
+
+ "\"export const executor: WorkspaceExecutorContribution\" als Funktion der Maschine");
|
|
105
|
+
}
|
|
106
|
+
return { plugin, stand, contribution: contribution as WorkspaceExecutorContribution };
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const isRecord = (value: unknown): value is Readonly<Record<string, unknown>> =>
|
|
110
|
+
typeof value === "object" && value !== null && !Array.isArray(value);
|
|
111
|
+
|
|
112
|
+
const isStringList = (value: unknown): value is readonly string[] =>
|
|
113
|
+
Array.isArray(value) && value.every((entry) => typeof entry === "string");
|
|
114
|
+
|
|
115
|
+
/** Was an einem Sprachserver fehlt oder die falsche Form hat; leer, wenn er taugt. */
|
|
116
|
+
const adapterProblems = (value: Readonly<Record<string, unknown>>): readonly string[] => [
|
|
117
|
+
...typeof value.id === "string" && LANGUAGE_SERVER_ID.test(value.id) ? [] : ["id aus Kleinbuchstaben und Ziffern"],
|
|
118
|
+
...typeof value.label === "string" ? [] : ["label"],
|
|
119
|
+
...isRecord(value.languages) && Object.values(value.languages).every((entry) => typeof entry === "string") ? [] : ["languages"],
|
|
120
|
+
...typeof value.rootDescription === "string" ? [] : ["rootDescription"],
|
|
121
|
+
...value.solutionExtensions === undefined || isStringList(value.solutionExtensions) ? [] : ["solutionExtensions"],
|
|
122
|
+
...(["resolveRoot", "rootDirectory", "launch", "open"] as const).filter((name) => typeof value[name] !== "function"),
|
|
123
|
+
];
|
|
124
|
+
|
|
125
|
+
/** Baut einen geladenen Beitrag für diese Maschine und prüft, was er liefert; eine falsche Form ist ein Fehler mit Ursache. */
|
|
126
|
+
export const prepareExecutorContribution = (loaded: LoadedExecutorContribution, toolsDirectory: string): PreparedExecutorContribution => {
|
|
127
|
+
const invalid = (detail: string): Error => new Error(`Der Executor-Beitrag von ${loaded.plugin} ist ungültig: ${detail}`);
|
|
128
|
+
const parts = ((): unknown => {
|
|
129
|
+
try {
|
|
130
|
+
return loaded.contribution(executorMachine(toolsDirectory));
|
|
131
|
+
} catch (cause) {
|
|
132
|
+
throw invalid(`die Funktion wirft: ${messageOf(cause)}`);
|
|
133
|
+
}
|
|
134
|
+
})();
|
|
135
|
+
if (!isRecord(parts)) throw invalid("die Funktion liefert kein Objekt");
|
|
136
|
+
const unknown = Object.keys(parts).filter((key) => !PART_KEYS.has(key));
|
|
137
|
+
if (unknown.length > 0) throw invalid(`unbekannte Teile ${unknown.join(", ")}; erlaubt sind ${[...PART_KEYS].join(", ")}`);
|
|
138
|
+
const modules = parts.modules;
|
|
139
|
+
if (modules !== undefined && (!Array.isArray(modules) || !modules.every((module) => typeof module === "function"))) {
|
|
140
|
+
throw invalid("modules ist keine Liste von Modulfabriken");
|
|
141
|
+
}
|
|
142
|
+
const servers = parts.languageServers;
|
|
143
|
+
if (servers !== undefined && !Array.isArray(servers)) throw invalid("languageServers ist keine Liste");
|
|
144
|
+
for (const [index, server] of (servers ?? []).entries()) {
|
|
145
|
+
if (!isRecord(server)) throw invalid(`der Sprachserver ${index + 1} ist kein Objekt`);
|
|
146
|
+
const problems = adapterProblems(server);
|
|
147
|
+
if (problems.length > 0) throw invalid(`der Sprachserver ${index + 1} braucht ${problems.join(", ")}`);
|
|
148
|
+
}
|
|
149
|
+
return { plugin: loaded.plugin, stand: loaded.stand, parts: parts as WorkspaceExecutorParts };
|
|
150
|
+
};
|
|
@@ -4,7 +4,7 @@ import type { WorkspaceExecutorModule, WorkspaceModuleFactory, WorkspaceOperatio
|
|
|
4
4
|
import { NO_ROOTS, type OperationFootprint } from "./paths.js";
|
|
5
5
|
|
|
6
6
|
/** Der Stand des Executors; Server und Arbeitsplatz müssen denselben tragen. */
|
|
7
|
-
export const WORKSPACE_EXECUTOR_VERSION = "
|
|
7
|
+
export const WORKSPACE_EXECUTOR_VERSION = "7";
|
|
8
8
|
|
|
9
9
|
export interface WorkspaceExecuteOptions {
|
|
10
10
|
toolCallId?: string;
|
|
@@ -18,6 +18,18 @@ export {
|
|
|
18
18
|
type WorkspaceModuleFactory,
|
|
19
19
|
} from "./module.js";
|
|
20
20
|
export { workspaceExecutorModules, type WorkspaceExecutorModuleOptions } from "./modules.js";
|
|
21
|
+
export {
|
|
22
|
+
EXECUTOR_CONTRIBUTION_FILE,
|
|
23
|
+
executorMachine,
|
|
24
|
+
loadExecutorContribution,
|
|
25
|
+
prepareExecutorContribution,
|
|
26
|
+
type ExecutorContributionStand,
|
|
27
|
+
type LoadedExecutorContribution,
|
|
28
|
+
type PreparedExecutorContribution,
|
|
29
|
+
type WorkspaceExecutorContribution,
|
|
30
|
+
type WorkspaceExecutorMachine,
|
|
31
|
+
type WorkspaceExecutorParts,
|
|
32
|
+
} from "./contributions.js";
|
|
21
33
|
export { RUN_FOLDER_OPERATIONS, runFolderModule, type RunFolderCreated } from "./run-folders.js";
|
|
22
34
|
export {
|
|
23
35
|
FILE_OPERATIONS,
|
|
@@ -37,18 +49,6 @@ export {
|
|
|
37
49
|
type CommandResult,
|
|
38
50
|
type CommandRunInput,
|
|
39
51
|
} from "./commands.js";
|
|
40
|
-
export {
|
|
41
|
-
BROWSER_EXECUTABLE_VARIABLE,
|
|
42
|
-
BROWSER_OPERATIONS,
|
|
43
|
-
type BrowserCheck,
|
|
44
|
-
type BrowserCheckResult,
|
|
45
|
-
type BrowserPageState,
|
|
46
|
-
type BrowserSnapshot,
|
|
47
|
-
type BrowserStep,
|
|
48
|
-
type BrowserTarget,
|
|
49
|
-
type BrowserViewport,
|
|
50
|
-
} from "./browser/contract.js";
|
|
51
|
-
export { browserModule, type BrowserModuleOptions } from "./browser/module.js";
|
|
52
52
|
export { processGroupExists, runManagedProcess, startManagedService, type ManagedService } from "./managed-process.js";
|
|
53
53
|
export {
|
|
54
54
|
containsWorkspacePath,
|
|
@@ -81,15 +81,16 @@ export { labelOf, runProcessesFrom } from "./processes/snapshot.js";
|
|
|
81
81
|
export { RunProcessTerminator } from "./processes/terminator.js";
|
|
82
82
|
export { RUN_MARKER_ENV } from "./run-marker.js";
|
|
83
83
|
export { editorFreeEnvironment, inheritedProcessEnvironment, safeProcessEnvironment } from "./safe-environment.js";
|
|
84
|
-
export { bashLaunch, type BashLaunch } from "./bash-launch.js";
|
|
84
|
+
export { bashLaunch, ripgrepAvailable, type BashLaunch, type BashPrograms } from "./bash-launch.js";
|
|
85
85
|
export { createSandboxTools, sandboxToolsModule, withAnnotation, type SeenFile } from "./sandbox-tools.js";
|
|
86
86
|
export { type SessionIdent } from "./session-ident.js";
|
|
87
|
-
export { shellPlatformText } from "./shell-platform.js";
|
|
88
|
-
export { ragentsDataRoot, workspaceDataDirectory } from "./tools.js";
|
|
87
|
+
export { shellPlatformText, type ShellTools } from "./shell-platform.js";
|
|
88
|
+
export { hostDataDirectory, pluginToolsDirectory, ragentsDataRoot, workspaceDataDirectory } from "./tools.js";
|
|
89
89
|
export {
|
|
90
90
|
LanguageServerHost,
|
|
91
91
|
changedWorkspaceFiles,
|
|
92
92
|
type LanguageServerAdapter,
|
|
93
|
+
type LanguageServerDescription,
|
|
93
94
|
type LanguageServerHostOptions,
|
|
94
95
|
} from "./language-server/host.js";
|
|
95
96
|
export {
|
|
@@ -112,7 +113,3 @@ export {
|
|
|
112
113
|
} from "./language-server/module.js";
|
|
113
114
|
export { resolveRootDirectory } from "./language-server/roots.js";
|
|
114
115
|
export { LanguageServerSession, withTimeout } from "./language-server/session.js";
|
|
115
|
-
export { solutionProjects } from "./language-server/solution.js";
|
|
116
|
-
export { fsharpAdapter, FSHARP_SERVER_FILE, FSHARP_SERVER_VARIABLE } from "./language-server/adapters/fsharp.js";
|
|
117
|
-
export { roslynAdapter, ROSLYN_SERVER_FILE, ROSLYN_SERVER_VARIABLE } from "./language-server/adapters/roslyn.js";
|
|
118
|
-
export { typescriptAdapter } from "./language-server/adapters/typescript.js";
|
|
@@ -27,6 +27,9 @@ export interface LanguageServerAdapter {
|
|
|
27
27
|
open: (session: LanguageServerSession, root: string, timeoutMs: number) => Promise<string>;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
/** Was der Server über einen Sprachserver wissen muss, um Werkzeuge und Reiter zu beschreiben; gestartet wird er nur im Executor. */
|
|
31
|
+
export type LanguageServerDescription = Pick<LanguageServerAdapter, "id" | "label" | "languages" | "rootDescription" | "solutionExtensions">;
|
|
32
|
+
|
|
30
33
|
export interface LanguageServerHostOptions {
|
|
31
34
|
idleMs?: number;
|
|
32
35
|
openTimeoutMs?: number;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { WorkspaceOperationError } from "../errors.js";
|
|
2
|
-
import type { WorkspaceModuleFactory, WorkspaceOperation } from "../module.js";
|
|
2
|
+
import type { WorkspaceExecutorModule, WorkspaceModuleFactory, WorkspaceModuleHost, WorkspaceOperation } from "../module.js";
|
|
3
3
|
import { rootsOfFields, type OperationFootprint } from "../paths.js";
|
|
4
4
|
import { LanguageServerHost, type LanguageServerAdapter, type LanguageServerHostOptions } from "./host.js";
|
|
5
5
|
|
|
@@ -89,7 +89,17 @@ const footprintsOf = (server: LanguageServerHost): Array<readonly [string, (inpu
|
|
|
89
89
|
export const languageServerModule = (
|
|
90
90
|
adapters: readonly LanguageServerAdapter[],
|
|
91
91
|
options: LanguageServerHostOptions = {},
|
|
92
|
-
): WorkspaceModuleFactory =>
|
|
92
|
+
): WorkspaceModuleFactory => {
|
|
93
|
+
const duplicate = adapters.find((adapter, index) => adapters.findIndex((other) => other.id === adapter.id) !== index);
|
|
94
|
+
if (duplicate) throw new Error(`Der Sprachserver ${duplicate.id} kommt im Executor zweimal vor; seine Operationen gehören genau einem Beitrag`);
|
|
95
|
+
return (host) => languageServersOn(host, adapters, options);
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const languageServersOn = (
|
|
99
|
+
host: WorkspaceModuleHost,
|
|
100
|
+
adapters: readonly LanguageServerAdapter[],
|
|
101
|
+
options: LanguageServerHostOptions,
|
|
102
|
+
): WorkspaceExecutorModule => {
|
|
93
103
|
const servers = adapters.map((adapter) => new LanguageServerHost(adapter, host.contextFor, options));
|
|
94
104
|
return {
|
|
95
105
|
operations: Object.fromEntries(servers.flatMap(operationsOf)),
|