@dxos/plugin-assistant 0.13.0 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/PLUGIN.mdl +25 -0
- package/dist/lib/AssistantCapabilities.mjs +8 -1
- package/dist/lib/AssistantCapabilities.mjs.map +1 -1
- package/dist/lib/Settings.mjs +4 -0
- package/dist/lib/Settings.mjs.map +1 -1
- package/dist/lib/TurnReviewSkill.mjs +55 -0
- package/dist/lib/TurnReviewSkill.mjs.map +1 -0
- package/dist/lib/chunk-AgentArticle.mjs +1 -1
- package/dist/lib/chunk-AssistantSettings.mjs +1 -1
- package/dist/lib/chunk-AssistantSettings.mjs.map +1 -1
- package/dist/lib/chunk-Chat.mjs +322 -174
- package/dist/lib/chunk-Chat.mjs.map +1 -1
- package/dist/lib/chunk-ChatArticle.mjs +14 -7
- package/dist/lib/chunk-ChatArticle.mjs.map +1 -1
- package/dist/lib/chunk-ChatDialog.mjs +5 -4
- package/dist/lib/chunk-ChatDialog.mjs.map +1 -1
- package/dist/lib/chunk-IntegrationPrompt.mjs +1 -1
- package/dist/lib/chunk-PLUGIN.mjs +1 -1
- package/dist/lib/chunk-PluginUrlPrompt.mjs +1 -1
- package/dist/lib/chunk-QuestionSurface.mjs +1 -1
- package/dist/lib/chunk-SpaceHomePrompt.mjs +116 -60
- package/dist/lib/chunk-SpaceHomePrompt.mjs.map +1 -1
- package/dist/lib/chunk-TriggerStatus.mjs +1 -1
- package/dist/lib/chunk-agent-service.mjs +3 -1
- package/dist/lib/chunk-agent-service.mjs.map +1 -1
- package/dist/lib/chunk-analyze-turn.mjs +220 -0
- package/dist/lib/chunk-analyze-turn.mjs.map +1 -0
- package/dist/lib/{chunk-processor.mjs → chunk-chat-model.mjs} +95 -43
- package/dist/lib/chunk-chat-model.mjs.map +1 -0
- package/dist/lib/chunk-context.mjs.map +1 -1
- package/dist/lib/chunk-create-chat.mjs +5 -19
- package/dist/lib/chunk-create-chat.mjs.map +1 -1
- package/dist/lib/chunk-create-object.mjs +1 -1
- package/dist/lib/chunk-default-skills.mjs +28 -0
- package/dist/lib/chunk-default-skills.mjs.map +1 -0
- package/dist/lib/chunk-generate-home-suggestions.mjs +8 -6
- package/dist/lib/chunk-generate-home-suggestions.mjs.map +1 -1
- package/dist/lib/chunk-presets.mjs +1 -1
- package/dist/lib/chunk-presets.mjs.map +1 -1
- package/dist/lib/chunk-question-resumer.mjs +1 -1
- package/dist/lib/chunk-react-surface.mjs +1 -1
- package/dist/lib/chunk-run-prompt-in-chat.mjs +2 -1
- package/dist/lib/chunk-run-prompt-in-chat.mjs.map +1 -1
- package/dist/lib/chunk-run-prompt-in-new-chat.mjs +1 -1
- package/dist/lib/chunk-schema-defs.mjs +2 -2
- package/dist/lib/chunk-skill-definition.mjs +3 -2
- package/dist/lib/chunk-skill-definition.mjs.map +1 -1
- package/dist/lib/components.mjs +13 -13
- package/dist/lib/components.mjs.map +1 -1
- package/dist/lib/hooks.mjs +40 -31
- package/dist/lib/hooks.mjs.map +1 -1
- package/dist/lib/operations.mjs +3 -1
- package/dist/lib/operations.mjs.map +1 -1
- package/dist/lib/skills.mjs +2 -1
- package/dist/lib/translations.mjs +4 -2
- package/dist/lib/translations.mjs.map +1 -1
- package/dist/types/src/Chat.d.ts +1 -1
- package/dist/types/src/capabilities/agent-service.d.ts.map +1 -1
- package/dist/types/src/capabilities/skill-definition.d.ts.map +1 -1
- package/dist/types/src/{processor/processor.d.ts → chat-model/chat-model.d.ts} +24 -22
- package/dist/types/src/chat-model/chat-model.d.ts.map +1 -0
- package/dist/types/src/chat-model/chat-model.edge.test.d.ts +2 -0
- package/dist/types/src/chat-model/chat-model.edge.test.d.ts.map +1 -0
- package/dist/types/src/chat-model/chat-model.node.test.d.ts +2 -0
- package/dist/types/src/chat-model/chat-model.node.test.d.ts.map +1 -0
- package/dist/types/src/chat-model/chat-model.scripted.node.test.d.ts +2 -0
- package/dist/types/src/chat-model/chat-model.scripted.node.test.d.ts.map +1 -0
- package/dist/types/src/{processor → chat-model}/index.d.ts +1 -1
- package/dist/types/src/chat-model/index.d.ts.map +1 -0
- package/dist/types/src/chat-model/outbox.d.ts.map +1 -0
- package/dist/types/src/chat-model/outbox.test.d.ts.map +1 -0
- package/dist/types/src/chat-model/presets.d.ts.map +1 -0
- package/dist/types/src/chat-model/presets.test.d.ts.map +1 -0
- package/dist/types/src/{processor → chat-model}/prompt.d.ts +2 -2
- package/dist/types/src/chat-model/prompt.d.ts.map +1 -0
- package/dist/types/src/chat-model/prompt.test.d.ts.map +1 -0
- package/dist/types/src/chat-model/streaming.node.test.d.ts.map +1 -0
- package/dist/types/src/chat-model/testing/chat-model-recorder.d.ts +58 -0
- package/dist/types/src/chat-model/testing/chat-model-recorder.d.ts.map +1 -0
- package/dist/types/src/chat-model/testing/harness.d.ts +22 -0
- package/dist/types/src/chat-model/testing/harness.d.ts.map +1 -0
- package/dist/types/src/chat-model/thread.d.ts.map +1 -0
- package/dist/types/src/chat-model/thread.test.d.ts.map +1 -0
- package/dist/types/src/components/Chat/Chat.d.ts +6 -6
- package/dist/types/src/components/Chat/Chat.d.ts.map +1 -1
- package/dist/types/src/components/Chat/context.d.ts +5 -3
- package/dist/types/src/components/Chat/context.d.ts.map +1 -1
- package/dist/types/src/components/ChatPrompt/ChatActions.d.ts +1 -1
- package/dist/types/src/components/ChatPrompt/ChatActions.d.ts.map +1 -1
- package/dist/types/src/components/ChatPrompt/ChatMcpErrors.d.ts +3 -3
- package/dist/types/src/components/ChatPrompt/ChatMcpErrors.d.ts.map +1 -1
- package/dist/types/src/components/ChatPrompt/ChatOptions.d.ts +3 -1
- package/dist/types/src/components/ChatPrompt/ChatOptions.d.ts.map +1 -1
- package/dist/types/src/components/ChatPrompt/ChatOptions.stories.d.ts +4 -3
- package/dist/types/src/components/ChatPrompt/ChatOptions.stories.d.ts.map +1 -1
- package/dist/types/src/components/ChatPrompt/ChatPrompt.d.ts +15 -5
- package/dist/types/src/components/ChatPrompt/ChatPrompt.d.ts.map +1 -1
- package/dist/types/src/components/ChatPrompt/ChatPrompt.stories.d.ts +1 -1
- package/dist/types/src/components/ChatPrompt/ChatStatus.d.ts +1 -1
- package/dist/types/src/components/ChatPrompt/ChatStatus.d.ts.map +1 -1
- package/dist/types/src/components/ConnectorAuthMenu/ConnectorAuthMenu.stories.d.ts +9 -0
- package/dist/types/src/components/ConnectorAuthMenu/ConnectorAuthMenu.stories.d.ts.map +1 -1
- package/dist/types/src/components/Toolbox/Toolbox.d.ts +3 -3
- package/dist/types/src/components/Toolbox/Toolbox.d.ts.map +1 -1
- package/dist/types/src/containers/ChatArticle/ChatArticle.d.ts.map +1 -1
- package/dist/types/src/containers/ChatArticle/ChatArticle.stories.d.ts +1 -1
- package/dist/types/src/containers/SpaceHomePrompt/SpaceHomePrompt.d.ts.map +1 -1
- package/dist/types/src/hooks/index.d.ts +2 -2
- package/dist/types/src/hooks/index.d.ts.map +1 -1
- package/dist/types/src/hooks/{useChatProcessor.d.ts → useChatModel.d.ts} +6 -6
- package/dist/types/src/hooks/useChatModel.d.ts.map +1 -0
- package/dist/types/src/hooks/useDebug.d.ts +3 -3
- package/dist/types/src/hooks/useDebug.d.ts.map +1 -1
- package/dist/types/src/hooks/useHomeSuggestions.d.ts +3 -3
- package/dist/types/src/hooks/useHomeSuggestions.d.ts.map +1 -1
- package/dist/types/src/hooks/usePresets.d.ts +1 -1
- package/dist/types/src/hooks/usePresets.d.ts.map +1 -1
- package/dist/types/src/hooks/useSelectionContext.d.ts +3 -3
- package/dist/types/src/hooks/useSelectionContext.d.ts.map +1 -1
- package/dist/types/src/operations/analyze-turn.d.ts +5 -0
- package/dist/types/src/operations/analyze-turn.d.ts.map +1 -0
- package/dist/types/src/operations/analyze-turn.test.d.ts +2 -0
- package/dist/types/src/operations/analyze-turn.test.d.ts.map +1 -0
- package/dist/types/src/operations/create-chat.d.ts.map +1 -1
- package/dist/types/src/operations/generate-home-suggestions.d.ts.map +1 -1
- package/dist/types/src/operations/index.d.ts.map +1 -1
- package/dist/types/src/operations/run-prompt-in-chat.d.ts.map +1 -1
- package/dist/types/src/review/turn-review.d.ts +58 -0
- package/dist/types/src/review/turn-review.d.ts.map +1 -0
- package/dist/types/src/review/turn-review.test.d.ts +2 -0
- package/dist/types/src/review/turn-review.test.d.ts.map +1 -0
- package/dist/types/src/skills/index.d.ts +1 -0
- package/dist/types/src/skills/index.d.ts.map +1 -1
- package/dist/types/src/skills/turn-review/TurnReviewSkill.d.ts +21 -0
- package/dist/types/src/skills/turn-review/TurnReviewSkill.d.ts.map +1 -0
- package/dist/types/src/skills/turn-review/index.d.ts +2 -0
- package/dist/types/src/skills/turn-review/index.d.ts.map +1 -0
- package/dist/types/src/translations.d.ts.map +1 -1
- package/dist/types/src/types/Assistant.d.ts +1 -0
- package/dist/types/src/types/Assistant.d.ts.map +1 -1
- package/dist/types/src/types/AssistantCapabilities.d.ts +7 -1
- package/dist/types/src/types/AssistantCapabilities.d.ts.map +1 -1
- package/dist/types/src/types/Settings.d.ts +1 -0
- package/dist/types/src/types/Settings.d.ts.map +1 -1
- package/dist/types/src/util/agent-options.d.ts +27 -0
- package/dist/types/src/util/agent-options.d.ts.map +1 -0
- package/dist/types/src/util/agent-options.test.d.ts +2 -0
- package/dist/types/src/util/agent-options.test.d.ts.map +1 -0
- package/dist/types/src/util/default-skills.d.ts +10 -0
- package/dist/types/src/util/default-skills.d.ts.map +1 -0
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +89 -80
- package/src/capabilities/agent-service.ts +2 -1
- package/src/capabilities/skill-definition.ts +2 -1
- package/src/chat-model/chat-model.edge.test.ts +301 -0
- package/src/{processor/processor.node.test.ts → chat-model/chat-model.node.test.ts} +19 -9
- package/src/chat-model/chat-model.scripted.node.test.ts +441 -0
- package/src/{processor/processor.ts → chat-model/chat-model.ts} +140 -45
- package/src/{processor → chat-model}/index.ts +1 -1
- package/src/{processor → chat-model}/prompt.ts +2 -2
- package/src/{processor → chat-model}/streaming.node.test.ts +108 -116
- package/src/chat-model/testing/chat-model-recorder.ts +249 -0
- package/src/chat-model/testing/harness.ts +81 -0
- package/src/{processor → chat-model}/thread.test.ts +1 -1
- package/src/components/Chat/Chat.tsx +44 -38
- package/src/components/Chat/Error.stories.tsx +1 -1
- package/src/components/Chat/context.ts +5 -3
- package/src/components/ChatPrompt/ChatActions.tsx +1 -1
- package/src/components/ChatPrompt/ChatMcpErrors.tsx +6 -6
- package/src/components/ChatPrompt/ChatOptions.stories.tsx +37 -3
- package/src/components/ChatPrompt/ChatOptions.tsx +118 -10
- package/src/components/ChatPrompt/ChatPrompt.stories.tsx +10 -10
- package/src/components/ChatPrompt/ChatPrompt.tsx +44 -27
- package/src/components/ChatPrompt/ChatStatus.tsx +8 -7
- package/src/components/Toolbox/Toolbox.tsx +12 -13
- package/src/containers/AssistantSettings/AssistantSettings.tsx +1 -1
- package/src/containers/ChatArticle/ChatArticle.stories.tsx +1 -1
- package/src/containers/ChatArticle/ChatArticle.tsx +16 -6
- package/src/containers/ChatDialog/ChatDialog.tsx +4 -4
- package/src/containers/SpaceHomePrompt/SpaceHomePrompt.tsx +110 -57
- package/src/hooks/index.ts +2 -2
- package/src/hooks/{useChatProcessor.ts → useChatModel.ts} +16 -14
- package/src/hooks/useDebug.ts +8 -8
- package/src/hooks/useHomeSuggestions.ts +14 -9
- package/src/hooks/usePresets.ts +2 -2
- package/src/hooks/useProcessEphemeralStatus.ts +1 -1
- package/src/hooks/useSelectionContext.ts +3 -5
- package/src/operations/analyze-turn.test.ts +194 -0
- package/src/operations/analyze-turn.ts +96 -0
- package/src/operations/create-chat.ts +4 -36
- package/src/operations/generate-home-suggestions.test.ts +1 -2
- package/src/operations/generate-home-suggestions.ts +17 -9
- package/src/operations/index.ts +2 -0
- package/src/operations/run-prompt-in-chat.ts +3 -2
- package/src/review/turn-review.test.ts +92 -0
- package/src/review/turn-review.ts +213 -0
- package/src/skills/index.ts +1 -0
- package/src/skills/turn-review/TurnReviewSkill.ts +46 -0
- package/src/skills/turn-review/index.ts +5 -0
- package/src/translations.ts +2 -0
- package/src/types/AssistantCapabilities.ts +8 -1
- package/src/types/Settings.ts +7 -0
- package/src/util/agent-options.test.ts +54 -0
- package/src/util/agent-options.ts +60 -0
- package/src/util/default-skills.ts +35 -0
- package/dist/lib/chunk-processor.mjs.map +0 -1
- package/dist/types/src/hooks/useChatProcessor.d.ts.map +0 -1
- package/dist/types/src/processor/index.d.ts.map +0 -1
- package/dist/types/src/processor/outbox.d.ts.map +0 -1
- package/dist/types/src/processor/outbox.test.d.ts.map +0 -1
- package/dist/types/src/processor/presets.d.ts.map +0 -1
- package/dist/types/src/processor/presets.test.d.ts.map +0 -1
- package/dist/types/src/processor/processor.d.ts.map +0 -1
- package/dist/types/src/processor/processor.node.test.d.ts +0 -2
- package/dist/types/src/processor/processor.node.test.d.ts.map +0 -1
- package/dist/types/src/processor/prompt.d.ts.map +0 -1
- package/dist/types/src/processor/prompt.test.d.ts.map +0 -1
- package/dist/types/src/processor/streaming.node.test.d.ts.map +0 -1
- package/dist/types/src/processor/thread.d.ts.map +0 -1
- package/dist/types/src/processor/thread.test.d.ts.map +0 -1
- /package/dist/types/src/{processor → chat-model}/outbox.d.ts +0 -0
- /package/dist/types/src/{processor → chat-model}/outbox.test.d.ts +0 -0
- /package/dist/types/src/{processor → chat-model}/presets.d.ts +0 -0
- /package/dist/types/src/{processor → chat-model}/presets.test.d.ts +0 -0
- /package/dist/types/src/{processor → chat-model}/prompt.test.d.ts +0 -0
- /package/dist/types/src/{processor → chat-model}/streaming.node.test.d.ts +0 -0
- /package/dist/types/src/{processor → chat-model}/thread.d.ts +0 -0
- /package/dist/types/src/{processor → chat-model}/thread.test.d.ts +0 -0
- /package/src/{processor → chat-model}/outbox.test.ts +0 -0
- /package/src/{processor → chat-model}/outbox.ts +0 -0
- /package/src/{processor → chat-model}/presets.test.ts +0 -0
- /package/src/{processor → chat-model}/presets.ts +0 -0
- /package/src/{processor → chat-model}/prompt.test.ts +0 -0
- /package/src/{processor → chat-model}/thread.ts +0 -0
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import * as Capability from "@dxos/app-framework/Capability";
|
|
2
|
+
import { AssistantCapabilities } from "#types";
|
|
3
|
+
import * as Capabilities from "@dxos/app-framework/Capabilities";
|
|
4
|
+
import * as Option from "effect/Option";
|
|
5
|
+
import { Database, Obj } from "@dxos/echo";
|
|
6
|
+
import { AiService } from "@dxos/ai";
|
|
7
|
+
import { AiSession } from "@dxos/assistant";
|
|
8
|
+
import { log } from "@dxos/log";
|
|
9
|
+
import { trim } from "@dxos/util";
|
|
10
|
+
import * as Effect from "effect/Effect";
|
|
11
|
+
import * as Operation from "@dxos/compute/Operation";
|
|
12
|
+
import { TurnReviewSkill } from "#skills";
|
|
13
|
+
import * as Schema from "effect/Schema";
|
|
14
|
+
import * as Harness$1 from "@dxos/assistant/Harness";
|
|
15
|
+
import * as LanguageModel from "effect/ai/LanguageModel";
|
|
16
|
+
import * as Prompt from "effect/ai/Prompt";
|
|
17
|
+
import { contentCaptureAllowed } from "@dxos/observability/AiObservability";
|
|
18
|
+
import * as ObservabilityCapabilities from "@dxos/plugin-observability/ObservabilityCapabilities";
|
|
19
|
+
//#region src/review/turn-review.ts
|
|
20
|
+
/** PostHog event emitted when the reviewer finds a turn the agent struggled through. */
|
|
21
|
+
var STRUGGLE_EVENT = "agent_struggle";
|
|
22
|
+
/** Small enough to be cheap per turn, strong enough to judge an agentic transcript. */
|
|
23
|
+
var REVIEW_MODEL = "com.anthropic.model.claude-sonnet-5.default";
|
|
24
|
+
/** Bounds the transcript the reviewer reads; the uploaded trajectory is not cut. */
|
|
25
|
+
var MAX_TRANSCRIPT_CHARS = 12e4;
|
|
26
|
+
var MAX_BLOCK_CHARS = 4e3;
|
|
27
|
+
/**
|
|
28
|
+
* Why the agent struggled. Only the first four are reported: they are the ones fixable on our side
|
|
29
|
+
* (prompting or tooling); the model's own reasoning and an unclear user request are not.
|
|
30
|
+
*/
|
|
31
|
+
var StruggleCause = Schema.Literals([
|
|
32
|
+
"system_prompt_missing",
|
|
33
|
+
"system_prompt_wrong",
|
|
34
|
+
"tool_missing",
|
|
35
|
+
"tool_faulty",
|
|
36
|
+
"model",
|
|
37
|
+
"user",
|
|
38
|
+
"none"
|
|
39
|
+
]);
|
|
40
|
+
var TurnVerdict = Schema.Struct({
|
|
41
|
+
struggled: Schema.Boolean.annotate({ description: "Whether the agent visibly struggled in the latest turn." }),
|
|
42
|
+
cause: StruggleCause.annotate({ description: "The root cause of the struggle, or \"none\"." }),
|
|
43
|
+
severity: Schema.Literals([
|
|
44
|
+
"low",
|
|
45
|
+
"medium",
|
|
46
|
+
"high"
|
|
47
|
+
]).annotate({ description: "low: recovered quickly; medium: wasted several steps; high: failed the request." }),
|
|
48
|
+
summary: Schema.String.annotate({ description: "One sentence naming what went wrong and what instruction or tool change would fix it." }),
|
|
49
|
+
tools: Schema.Array(Schema.String).annotate({ description: "Names of the tools implicated, if any." })
|
|
50
|
+
});
|
|
51
|
+
/** The dashboard's top-level split; `undefined` for a cause that is not reported. */
|
|
52
|
+
var causeGroup = (cause) => {
|
|
53
|
+
switch (cause) {
|
|
54
|
+
case "system_prompt_missing":
|
|
55
|
+
case "system_prompt_wrong": return "system_prompt";
|
|
56
|
+
case "tool_missing":
|
|
57
|
+
case "tool_faulty": return "tooling";
|
|
58
|
+
default: return;
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
var isReportable = (verdict) => verdict.struggled && causeGroup(verdict.cause) !== void 0;
|
|
62
|
+
var REVIEW_SYSTEM_PROMPT = trim`
|
|
63
|
+
You review transcripts of an AI agent working inside Composer, a collaborative workspace app.
|
|
64
|
+
Decide whether the agent struggled in the LATEST TURN (messages inside the <latest_turn> tags).
|
|
65
|
+
Earlier messages are context only.
|
|
66
|
+
|
|
67
|
+
Signs of struggling: repeated or looping tool calls, tool errors, calling tools with invalid
|
|
68
|
+
arguments, guessing at tool names or object ids, apologizing and retrying, giving up, asking the
|
|
69
|
+
user for information it should have been able to obtain, or giving an incomplete answer.
|
|
70
|
+
|
|
71
|
+
Classify the root cause:
|
|
72
|
+
- system_prompt_missing: the agent lacked instructions or context it needed (e.g. how a feature
|
|
73
|
+
works, which tool to use for a task, conventions of the app).
|
|
74
|
+
- system_prompt_wrong: instructions were present but misleading, contradictory, or outdated.
|
|
75
|
+
- tool_missing: no tool existed for an action the request clearly required.
|
|
76
|
+
- tool_faulty: a tool errored, returned unusable output, had a confusing schema or description.
|
|
77
|
+
- model: the tools and instructions were adequate; the model reasoned poorly.
|
|
78
|
+
- user: the request was ambiguous or impossible.
|
|
79
|
+
- none: the agent did not struggle.
|
|
80
|
+
|
|
81
|
+
Be conservative: a single quickly-recovered tool error is not a struggle. When unsure, answer
|
|
82
|
+
struggled=false with cause=none.
|
|
83
|
+
`;
|
|
84
|
+
var clip = (text, max) => text.length <= max ? text : `${text.slice(0, max)}… [${text.length - max} chars truncated]`;
|
|
85
|
+
var stringify = (value) => typeof value === "string" ? value : JSON.stringify(value);
|
|
86
|
+
/** One line per block, so the reviewer sees the shape of the turn without a tool result drowning it. */
|
|
87
|
+
var formatMessage = (message) => {
|
|
88
|
+
const lines = message.blocks.map((block) => {
|
|
89
|
+
switch (block._tag) {
|
|
90
|
+
case "text": return clip(block.text, MAX_BLOCK_CHARS);
|
|
91
|
+
case "toolCall": return `[tool call ${block.name}] ${clip(stringify(block.input), MAX_BLOCK_CHARS)}`;
|
|
92
|
+
case "toolResult": return block.error ? `[tool error ${block.name}] ${clip(stringify(block.error), MAX_BLOCK_CHARS)}` : `[tool result ${block.name}] ${clip(stringify(block.result), MAX_BLOCK_CHARS)}`;
|
|
93
|
+
default: return `[${block._tag}]`;
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
return `${(message.sender.role ?? "unknown").toUpperCase()}:\n${lines.join("\n")}`;
|
|
97
|
+
};
|
|
98
|
+
/**
|
|
99
|
+
* Splits the history at the turn that just ended: it starts at the last user message, which is what
|
|
100
|
+
* the agent was answering. Hooks fire per request, so everything after it is this turn's work.
|
|
101
|
+
*/
|
|
102
|
+
var splitTurn = (history) => {
|
|
103
|
+
const index = history.findLastIndex((message) => message.sender.role === "user");
|
|
104
|
+
return index === -1 ? {
|
|
105
|
+
context: [],
|
|
106
|
+
turn: history
|
|
107
|
+
} : {
|
|
108
|
+
context: history.slice(0, index),
|
|
109
|
+
turn: history.slice(index)
|
|
110
|
+
};
|
|
111
|
+
};
|
|
112
|
+
/** The reviewer's prompt; context is trimmed from the front when the transcript runs long. */
|
|
113
|
+
var formatReviewPrompt = (history) => {
|
|
114
|
+
const { context, turn } = splitTurn(history);
|
|
115
|
+
const turnText = turn.map(formatMessage).join("\n\n");
|
|
116
|
+
const budget = Math.max(0, MAX_TRANSCRIPT_CHARS - turnText.length);
|
|
117
|
+
const contextText = context.map(formatMessage).join("\n\n");
|
|
118
|
+
return [
|
|
119
|
+
contextText.length > budget ? `…${contextText.slice(contextText.length - budget)}` : contextText,
|
|
120
|
+
"<latest_turn>",
|
|
121
|
+
clip(turnText, MAX_TRANSCRIPT_CHARS),
|
|
122
|
+
"</latest_turn>"
|
|
123
|
+
].filter((part) => part.length > 0).join("\n\n");
|
|
124
|
+
};
|
|
125
|
+
var countToolCalls = (messages) => {
|
|
126
|
+
let calls = 0;
|
|
127
|
+
let errors = 0;
|
|
128
|
+
for (const message of messages) for (const block of message.blocks) if (block._tag === "toolCall") calls++;
|
|
129
|
+
else if (block._tag === "toolResult" && block.error) errors++;
|
|
130
|
+
return {
|
|
131
|
+
calls,
|
|
132
|
+
errors
|
|
133
|
+
};
|
|
134
|
+
};
|
|
135
|
+
/** A header line then one line per message, in the span serialization the LLM analytics traces use. */
|
|
136
|
+
var toTrajectoryNdjson = (header, history) => [JSON.stringify({
|
|
137
|
+
type: "header",
|
|
138
|
+
...header
|
|
139
|
+
}), ...history.map((message) => JSON.stringify({
|
|
140
|
+
type: "message",
|
|
141
|
+
created: message.created,
|
|
142
|
+
...AiSession.serializeMessage(message)
|
|
143
|
+
}))].join("\n");
|
|
144
|
+
/**
|
|
145
|
+
* Flat properties so every one is a PostHog breakdown; lists are comma-joined for the same reason.
|
|
146
|
+
* `$ai_session_id` joins the event to the conversation's LLM analytics traces.
|
|
147
|
+
*/
|
|
148
|
+
var toStruggleEventProperties = ({ sessionId, model, codeMode, skills, verdict, history, trajectoryKey }) => {
|
|
149
|
+
const { turn } = splitTurn(history);
|
|
150
|
+
const { calls, errors } = countToolCalls(turn);
|
|
151
|
+
return {
|
|
152
|
+
$ai_session_id: sessionId,
|
|
153
|
+
model,
|
|
154
|
+
code_mode: codeMode,
|
|
155
|
+
skills: skills.join(","),
|
|
156
|
+
cause: verdict.cause,
|
|
157
|
+
cause_group: causeGroup(verdict.cause),
|
|
158
|
+
severity: verdict.severity,
|
|
159
|
+
summary: verdict.summary,
|
|
160
|
+
tools: verdict.tools.join(","),
|
|
161
|
+
turn_message_count: turn.length,
|
|
162
|
+
turn_tool_call_count: calls,
|
|
163
|
+
turn_tool_error_count: errors,
|
|
164
|
+
review_model: REVIEW_MODEL,
|
|
165
|
+
trajectory_key: trajectoryKey
|
|
166
|
+
};
|
|
167
|
+
};
|
|
168
|
+
//#endregion
|
|
169
|
+
//#region src/operations/analyze-turn.ts
|
|
170
|
+
var __dxlog_file = "/__w/dxos/dxos/packages/plugins/plugin-assistant/src/operations/analyze-turn.ts";
|
|
171
|
+
var handler = TurnReviewSkill.AnalyzeTurn.pipe(Operation.withHandler(Effect.fnUntraced(function* () {
|
|
172
|
+
const settings = Option.getOrUndefined(yield* Capabilities.getAtomValueOption(AssistantCapabilities.Settings));
|
|
173
|
+
const observability = Option.getOrUndefined(yield* Capability.getOption(ObservabilityCapabilities.Observability));
|
|
174
|
+
if (!settings?.reportStruggles || !observability?.enabled) return;
|
|
175
|
+
const chat = yield* Harness$1.getChat;
|
|
176
|
+
const spaceId = Obj.getDatabase(chat)?.spaceId;
|
|
177
|
+
if (!spaceId || !contentCaptureAllowed(spaceId)) return;
|
|
178
|
+
const history = yield* Harness$1.history;
|
|
179
|
+
if (history.length === 0) return;
|
|
180
|
+
const { value: verdict } = yield* LanguageModel.generateObject({
|
|
181
|
+
schema: TurnVerdict,
|
|
182
|
+
objectName: "turn_verdict",
|
|
183
|
+
prompt: Prompt.setSystem(Prompt.make(formatReviewPrompt(history)), REVIEW_SYSTEM_PROMPT)
|
|
184
|
+
}).pipe(Effect.provide(AiService.languageModel(REVIEW_MODEL)));
|
|
185
|
+
log.info("turn reviewed", {
|
|
186
|
+
chat: chat.id,
|
|
187
|
+
struggled: verdict.struggled,
|
|
188
|
+
cause: verdict.cause
|
|
189
|
+
}, {
|
|
190
|
+
"~LogMeta": "~LogMeta",
|
|
191
|
+
F: __dxlog_file,
|
|
192
|
+
L: 66,
|
|
193
|
+
S: this
|
|
194
|
+
});
|
|
195
|
+
if (!isReportable(verdict)) return;
|
|
196
|
+
const binder = yield* Harness$1.binder;
|
|
197
|
+
const feed = yield* Database.load(chat.feed);
|
|
198
|
+
const header = {
|
|
199
|
+
sessionId: Obj.getURI(feed, { prefer: "absolute" }),
|
|
200
|
+
model: chat.session?.model,
|
|
201
|
+
codeMode: settings.codeMode ?? false,
|
|
202
|
+
skills: binder.getSkills().filter((skill) => Obj.getMeta(skill).key !== TurnReviewSkill.key).map((skill) => skill.name),
|
|
203
|
+
verdict
|
|
204
|
+
};
|
|
205
|
+
const trajectoryKey = yield* Effect.promise(() => observability.support.uploadNdjson(toTrajectoryNdjson(header, history), "trajectory"));
|
|
206
|
+
observability.events.captureEvent(STRUGGLE_EVENT, toStruggleEventProperties({
|
|
207
|
+
...header,
|
|
208
|
+
history,
|
|
209
|
+
trajectoryKey
|
|
210
|
+
}));
|
|
211
|
+
}, Effect.catchCause((cause) => Effect.sync(() => log.warn("turn review failed", { cause }, {
|
|
212
|
+
"~LogMeta": "~LogMeta",
|
|
213
|
+
F: __dxlog_file,
|
|
214
|
+
L: 91,
|
|
215
|
+
S: void 0
|
|
216
|
+
}))))));
|
|
217
|
+
//#endregion
|
|
218
|
+
export { handler as default };
|
|
219
|
+
|
|
220
|
+
//# sourceMappingURL=chunk-analyze-turn.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chunk-analyze-turn.mjs","names":[],"sources":["../../src/review/turn-review.ts","../../src/operations/analyze-turn.ts"],"sourcesContent":["//\n// Copyright 2026 DXOS.org\n//\n\nimport * as Schema from 'effect/Schema';\n\nimport { AiSession } from '@dxos/assistant';\nimport { type Message } from '@dxos/types';\nimport { trim } from '@dxos/util';\n\n/** PostHog event emitted when the reviewer finds a turn the agent struggled through. */\nexport const STRUGGLE_EVENT = 'agent_struggle';\n\n/** Small enough to be cheap per turn, strong enough to judge an agentic transcript. */\nexport const REVIEW_MODEL = 'com.anthropic.model.claude-sonnet-5.default';\n\n/** Bounds the transcript the reviewer reads; the uploaded trajectory is not cut. */\nconst MAX_TRANSCRIPT_CHARS = 120_000;\nconst MAX_BLOCK_CHARS = 4_000;\n\n/**\n * Why the agent struggled. Only the first four are reported: they are the ones fixable on our side\n * (prompting or tooling); the model's own reasoning and an unclear user request are not.\n */\nexport const StruggleCause = Schema.Literals([\n 'system_prompt_missing',\n 'system_prompt_wrong',\n 'tool_missing',\n 'tool_faulty',\n 'model',\n 'user',\n 'none',\n]);\nexport type StruggleCause = Schema.Schema.Type<typeof StruggleCause>;\n\nexport const TurnVerdict = Schema.Struct({\n struggled: Schema.Boolean.annotate({\n description: 'Whether the agent visibly struggled in the latest turn.',\n }),\n cause: StruggleCause.annotate({\n description: 'The root cause of the struggle, or \"none\".',\n }),\n severity: Schema.Literals(['low', 'medium', 'high']).annotate({\n description: 'low: recovered quickly; medium: wasted several steps; high: failed the request.',\n }),\n summary: Schema.String.annotate({\n description: 'One sentence naming what went wrong and what instruction or tool change would fix it.',\n }),\n tools: Schema.Array(Schema.String).annotate({\n description: 'Names of the tools implicated, if any.',\n }),\n});\nexport type TurnVerdict = Schema.Schema.Type<typeof TurnVerdict>;\n\nexport type CauseGroup = 'system_prompt' | 'tooling';\n\n/** The dashboard's top-level split; `undefined` for a cause that is not reported. */\nexport const causeGroup = (cause: StruggleCause): CauseGroup | undefined => {\n switch (cause) {\n case 'system_prompt_missing':\n case 'system_prompt_wrong':\n return 'system_prompt';\n case 'tool_missing':\n case 'tool_faulty':\n return 'tooling';\n default:\n return undefined;\n }\n};\n\nexport const isReportable = (verdict: TurnVerdict): boolean =>\n verdict.struggled && causeGroup(verdict.cause) !== undefined;\n\nexport const REVIEW_SYSTEM_PROMPT = trim`\n You review transcripts of an AI agent working inside Composer, a collaborative workspace app.\n Decide whether the agent struggled in the LATEST TURN (messages inside the <latest_turn> tags).\n Earlier messages are context only.\n\n Signs of struggling: repeated or looping tool calls, tool errors, calling tools with invalid\n arguments, guessing at tool names or object ids, apologizing and retrying, giving up, asking the\n user for information it should have been able to obtain, or giving an incomplete answer.\n\n Classify the root cause:\n - system_prompt_missing: the agent lacked instructions or context it needed (e.g. how a feature\n works, which tool to use for a task, conventions of the app).\n - system_prompt_wrong: instructions were present but misleading, contradictory, or outdated.\n - tool_missing: no tool existed for an action the request clearly required.\n - tool_faulty: a tool errored, returned unusable output, had a confusing schema or description.\n - model: the tools and instructions were adequate; the model reasoned poorly.\n - user: the request was ambiguous or impossible.\n - none: the agent did not struggle.\n\n Be conservative: a single quickly-recovered tool error is not a struggle. When unsure, answer\n struggled=false with cause=none.\n`;\n\nconst clip = (text: string, max: number): string =>\n text.length <= max ? text : `${text.slice(0, max)}… [${text.length - max} chars truncated]`;\n\nconst stringify = (value: unknown): string => (typeof value === 'string' ? value : JSON.stringify(value));\n\n/** One line per block, so the reviewer sees the shape of the turn without a tool result drowning it. */\nconst formatMessage = (message: Message.Message): string => {\n const lines = message.blocks.map((block) => {\n switch (block._tag) {\n case 'text':\n return clip(block.text, MAX_BLOCK_CHARS);\n case 'toolCall':\n return `[tool call ${block.name}] ${clip(stringify(block.input), MAX_BLOCK_CHARS)}`;\n case 'toolResult':\n return block.error\n ? `[tool error ${block.name}] ${clip(stringify(block.error), MAX_BLOCK_CHARS)}`\n : `[tool result ${block.name}] ${clip(stringify(block.result), MAX_BLOCK_CHARS)}`;\n default:\n return `[${block._tag}]`;\n }\n });\n return `${(message.sender.role ?? 'unknown').toUpperCase()}:\\n${lines.join('\\n')}`;\n};\n\n/**\n * Splits the history at the turn that just ended: it starts at the last user message, which is what\n * the agent was answering. Hooks fire per request, so everything after it is this turn's work.\n */\nexport const splitTurn = (history: readonly Message.Message[]) => {\n const index = history.findLastIndex((message) => message.sender.role === 'user');\n return index === -1\n ? { context: [] as readonly Message.Message[], turn: history }\n : { context: history.slice(0, index), turn: history.slice(index) };\n};\n\n/** The reviewer's prompt; context is trimmed from the front when the transcript runs long. */\nexport const formatReviewPrompt = (history: readonly Message.Message[]): string => {\n const { context, turn } = splitTurn(history);\n const turnText = turn.map(formatMessage).join('\\n\\n');\n const budget = Math.max(0, MAX_TRANSCRIPT_CHARS - turnText.length);\n const contextText = context.map(formatMessage).join('\\n\\n');\n const clippedContext =\n contextText.length > budget ? `…${contextText.slice(contextText.length - budget)}` : contextText;\n return [clippedContext, '<latest_turn>', clip(turnText, MAX_TRANSCRIPT_CHARS), '</latest_turn>']\n .filter((part) => part.length > 0)\n .join('\\n\\n');\n};\n\nexport const countToolCalls = (messages: readonly Message.Message[]) => {\n let calls = 0;\n let errors = 0;\n for (const message of messages) {\n for (const block of message.blocks) {\n if (block._tag === 'toolCall') {\n calls++;\n } else if (block._tag === 'toolResult' && block.error) {\n errors++;\n }\n }\n }\n return { calls, errors };\n};\n\nexport type TrajectoryHeader = {\n sessionId: string;\n model?: string;\n codeMode: boolean;\n skills: readonly string[];\n verdict: TurnVerdict;\n};\n\n/** A header line then one line per message, in the span serialization the LLM analytics traces use. */\nexport const toTrajectoryNdjson = (header: TrajectoryHeader, history: readonly Message.Message[]): string =>\n [\n JSON.stringify({ type: 'header', ...header }),\n ...history.map((message) =>\n JSON.stringify({ type: 'message', created: message.created, ...(AiSession.serializeMessage(message) as object) }),\n ),\n ].join('\\n');\n\nexport type StruggleEventInput = TrajectoryHeader & {\n history: readonly Message.Message[];\n trajectoryKey?: string;\n};\n\n/**\n * Flat properties so every one is a PostHog breakdown; lists are comma-joined for the same reason.\n * `$ai_session_id` joins the event to the conversation's LLM analytics traces.\n */\nexport const toStruggleEventProperties = ({\n sessionId,\n model,\n codeMode,\n skills,\n verdict,\n history,\n trajectoryKey,\n}: StruggleEventInput): Record<string, unknown> => {\n const { turn } = splitTurn(history);\n const { calls, errors } = countToolCalls(turn);\n return {\n $ai_session_id: sessionId,\n model,\n code_mode: codeMode,\n skills: skills.join(','),\n cause: verdict.cause,\n cause_group: causeGroup(verdict.cause),\n severity: verdict.severity,\n summary: verdict.summary,\n tools: verdict.tools.join(','),\n turn_message_count: turn.length,\n turn_tool_call_count: calls,\n turn_tool_error_count: errors,\n review_model: REVIEW_MODEL,\n trajectory_key: trajectoryKey,\n };\n};\n","//\n// Copyright 2026 DXOS.org\n//\n\nimport * as LanguageModel from 'effect/ai/LanguageModel';\nimport * as Prompt from 'effect/ai/Prompt';\nimport * as Effect from 'effect/Effect';\nimport * as Option from 'effect/Option';\n\nimport { AiService } from '@dxos/ai';\nimport * as Capabilities from '@dxos/app-framework/Capabilities';\nimport * as Capability from '@dxos/app-framework/Capability';\nimport * as Harness from '@dxos/assistant/Harness';\nimport * as Operation from '@dxos/compute/Operation';\nimport { Database, Obj } from '@dxos/echo';\nimport { log } from '@dxos/log';\nimport { contentCaptureAllowed } from '@dxos/observability/AiObservability';\nimport * as ObservabilityCapabilities from '@dxos/plugin-observability/ObservabilityCapabilities';\n\nimport { TurnReviewSkill } from '#skills';\nimport { AssistantCapabilities } from '#types';\n\nimport {\n REVIEW_MODEL,\n REVIEW_SYSTEM_PROMPT,\n STRUGGLE_EVENT,\n type TrajectoryHeader,\n TurnVerdict,\n formatReviewPrompt,\n isReportable,\n toStruggleEventProperties,\n toTrajectoryNdjson,\n} from '../review/turn-review.ts';\n\n// Never fails: it runs as a background hook after the user's turn, so a review that cannot complete\n// is only logged.\nconst handler: Operation.WithHandler<typeof TurnReviewSkill.AnalyzeTurn> = TurnReviewSkill.AnalyzeTurn.pipe(\n Operation.withHandler(\n Effect.fnUntraced(\n function* () {\n const settings = Option.getOrUndefined(yield* Capabilities.getAtomValueOption(AssistantCapabilities.Settings));\n const observability = Option.getOrUndefined(\n yield* Capability.getOption(ObservabilityCapabilities.Observability),\n );\n // Both opt-ins are re-read here: the skill stays bound to a chat after the user opts out.\n if (!settings?.reportStruggles || !observability?.enabled) {\n return;\n }\n\n const chat = yield* Harness.getChat;\n const spaceId = Obj.getDatabase(chat)?.spaceId;\n if (!spaceId || !contentCaptureAllowed(spaceId)) {\n return;\n }\n\n const history = yield* Harness.history;\n if (history.length === 0) {\n return;\n }\n\n const { value: verdict } = yield* LanguageModel.generateObject({\n schema: TurnVerdict,\n objectName: 'turn_verdict',\n prompt: Prompt.setSystem(Prompt.make(formatReviewPrompt(history)), REVIEW_SYSTEM_PROMPT),\n }).pipe(Effect.provide(AiService.languageModel(REVIEW_MODEL)));\n log.info('turn reviewed', { chat: chat.id, struggled: verdict.struggled, cause: verdict.cause });\n if (!isReportable(verdict)) {\n return;\n }\n\n const binder = yield* Harness.binder;\n const feed = yield* Database.load(chat.feed);\n const header: TrajectoryHeader = {\n sessionId: Obj.getURI(feed, { prefer: 'absolute' }),\n model: chat.session?.model,\n codeMode: settings.codeMode ?? false,\n skills: binder\n .getSkills()\n .filter((skill) => Obj.getMeta(skill).key !== TurnReviewSkill.key)\n .map((skill) => skill.name),\n verdict,\n };\n const trajectoryKey = yield* Effect.promise(() =>\n observability.support.uploadNdjson(toTrajectoryNdjson(header, history), 'trajectory'),\n );\n observability.events.captureEvent(\n STRUGGLE_EVENT,\n toStruggleEventProperties({ ...header, history, trajectoryKey }),\n );\n },\n Effect.catchCause((cause) => Effect.sync(() => log.warn('turn review failed', { cause }))),\n ),\n ),\n);\n\nexport default handler;\n"],"mappings":"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"}
|
|
@@ -2,7 +2,7 @@ import { a as providerForModel } from "./chunk-presets.mjs";
|
|
|
2
2
|
import { AssistantOperation } from "#types";
|
|
3
3
|
import * as Option from "effect/Option";
|
|
4
4
|
import * as Atom from "effect/reactivity/Atom";
|
|
5
|
-
import { Annotation, Database, Feed, Filter, Obj, Query } from "@dxos/echo";
|
|
5
|
+
import { Annotation, Database, Feed, Filter, Obj, Query, Ref } from "@dxos/echo";
|
|
6
6
|
import { AiModelNotAvailableError, Model } from "@dxos/ai";
|
|
7
7
|
import { Harness, ToolExecutionServices, createSystemPrompt, formatSystemPrompt, isConsumed, isInFlight, isQueued } from "@dxos/assistant";
|
|
8
8
|
import { log } from "@dxos/log";
|
|
@@ -16,16 +16,19 @@ import * as Exit from "effect/Exit";
|
|
|
16
16
|
import * as Fiber from "effect/Fiber";
|
|
17
17
|
import * as AtomRegistry from "effect/reactivity/AtomRegistry";
|
|
18
18
|
import * as Stream from "effect/Stream";
|
|
19
|
+
import * as Agent from "@dxos/assistant/Agent";
|
|
19
20
|
import * as AgentService from "@dxos/compute/AgentService";
|
|
20
21
|
import * as Operation from "@dxos/compute/Operation";
|
|
22
|
+
import * as Skill from "@dxos/compute/Skill";
|
|
21
23
|
import * as Trace from "@dxos/compute/Trace";
|
|
22
24
|
import { UsageQuotaExceededError } from "@dxos/edge-client";
|
|
23
25
|
import * as EffectEx from "@dxos/effect/EffectEx";
|
|
26
|
+
import { TurnReviewSkill } from "#skills";
|
|
24
27
|
import * as Array from "effect/Array";
|
|
25
28
|
import * as Order$1 from "effect/Order";
|
|
26
29
|
import { DeliveryAnnotation } from "@dxos/react-ui-assistant/types";
|
|
27
|
-
//#region src/
|
|
28
|
-
var __dxlog_file$1 = "/__w/dxos/dxos/packages/plugins/plugin-assistant/src/
|
|
30
|
+
//#region src/chat-model/outbox.ts
|
|
31
|
+
var __dxlog_file$1 = "/__w/dxos/dxos/packages/plugins/plugin-assistant/src/chat-model/outbox.ts";
|
|
29
32
|
/** A dispatch stopped before the prompt reached the agent, by the reader: the prompt is dropped, not failed. */
|
|
30
33
|
var PromptCancelledError = class extends BaseError.extend("PromptCancelledError", "Prompt cancelled before it was sent.") {};
|
|
31
34
|
/**
|
|
@@ -117,7 +120,7 @@ var findInCause = (error, predicate) => {
|
|
|
117
120
|
}
|
|
118
121
|
};
|
|
119
122
|
//#endregion
|
|
120
|
-
//#region src/
|
|
123
|
+
//#region src/chat-model/prompt.ts
|
|
121
124
|
/**
|
|
122
125
|
* Prompt content for a request: the bare message, or — when the request carries selection context —
|
|
123
126
|
* a synthetic block (system-generated user-turn content, hidden from the summary view) ahead of it.
|
|
@@ -142,7 +145,7 @@ var createPromptContent = (request) => {
|
|
|
142
145
|
}), ContentBlock.Text.make({ text: request.message })];
|
|
143
146
|
};
|
|
144
147
|
//#endregion
|
|
145
|
-
//#region src/
|
|
148
|
+
//#region src/chat-model/thread.ts
|
|
146
149
|
/**
|
|
147
150
|
* Append order for {@link Feed.history}, which walks lineage positionally rather than by time.
|
|
148
151
|
*
|
|
@@ -373,8 +376,8 @@ var resolveRewind = (messages, messageId) => {
|
|
|
373
376
|
};
|
|
374
377
|
};
|
|
375
378
|
//#endregion
|
|
376
|
-
//#region src/
|
|
377
|
-
var __dxlog_file = "/__w/dxos/dxos/packages/plugins/plugin-assistant/src/
|
|
379
|
+
//#region src/chat-model/chat-model.ts
|
|
380
|
+
var __dxlog_file = "/__w/dxos/dxos/packages/plugins/plugin-assistant/src/chat-model/chat-model.ts";
|
|
378
381
|
var defaultOptions = { model: Model.DEFAULT_EDGE };
|
|
379
382
|
/** User-facing message shown when an AI request is rejected for exceeding the account usage quota (HTTP 429). */
|
|
380
383
|
var QUOTA_EXCEEDED_MESSAGE = "You have reached your AI usage limit for this period.";
|
|
@@ -410,6 +413,20 @@ var describeAiError = (err) => ("description" in err.reason ? err.reason.descrip
|
|
|
410
413
|
* which drops nested causes — so detection also reads the message it stringifies to.
|
|
411
414
|
*/
|
|
412
415
|
var MODEL_UNAVAILABLE_PATTERN = /AI Model not available:\s*(\S+?):?(?=\s|$)/i;
|
|
416
|
+
/**
|
|
417
|
+
* Matches the first line of a stringified {@link AiError.AiError} ("…AiError: Module.method: detail"), the
|
|
418
|
+
* form a provider failure takes once it has crossed the agent-process boundary.
|
|
419
|
+
*/
|
|
420
|
+
var AI_ERROR_PATTERN = /AiError: [\w$.]+: ([^\n]+)/;
|
|
421
|
+
/** The provider's own description of a failure that arrived stringified, anywhere in the cause chain. */
|
|
422
|
+
var stringifiedAiErrorText = (err) => {
|
|
423
|
+
const visited = /* @__PURE__ */ new Set();
|
|
424
|
+
for (let current = err; current && !visited.has(current); current = current instanceof Error ? current.cause : void 0) {
|
|
425
|
+
visited.add(current);
|
|
426
|
+
const detail = (typeof current === "string" ? current : current instanceof Error ? current.message : "").match(AI_ERROR_PATTERN)?.[1]?.trim();
|
|
427
|
+
if (detail) return detail;
|
|
428
|
+
}
|
|
429
|
+
};
|
|
413
430
|
/** The displayable text of a failure, which reaches the chat either typed or already stringified. */
|
|
414
431
|
var errorText = (err) => typeof err === "string" ? err : err instanceof Error ? err.message : "";
|
|
415
432
|
/** The model of a model-unavailable rejection, by typed context or message text. */
|
|
@@ -441,6 +458,7 @@ var parseError = (err) => {
|
|
|
441
458
|
const [, model] = err.match(/model\s+'([^']+)'\s+not\s+found/i) || [];
|
|
442
459
|
if (model) message = `The model is not available: ${model}`;
|
|
443
460
|
}
|
|
461
|
+
if (!message) message = stringifiedAiErrorText(err);
|
|
444
462
|
if (!message) message = "An unexpected error occurred.";
|
|
445
463
|
return new Error(message, { cause: err });
|
|
446
464
|
};
|
|
@@ -448,7 +466,7 @@ var parseError = (err) => {
|
|
|
448
466
|
* Handles interactions with the AI service.
|
|
449
467
|
* Uses AgentService to spawn a process-backed agent and subscribes to ephemeral trace events for streaming.
|
|
450
468
|
*/
|
|
451
|
-
var
|
|
469
|
+
var ChatModel = class {
|
|
452
470
|
_conversation;
|
|
453
471
|
_runtime;
|
|
454
472
|
_feed;
|
|
@@ -461,9 +479,12 @@ var AiChatProcessor = class {
|
|
|
461
479
|
#streaming = Atom.make([]);
|
|
462
480
|
/** Set of message IDs that have been finalized (non-pending delivered via ephemeral). */
|
|
463
481
|
#finalizedIds = /* @__PURE__ */ new Set();
|
|
482
|
+
/** Timestamp of the newest trace event applied this turn, overall and per streamed message. */
|
|
483
|
+
#appliedAt = 0;
|
|
484
|
+
#appliedAtByMessage = /* @__PURE__ */ new Map();
|
|
464
485
|
/** Currently active request fiber. */
|
|
465
486
|
#requestFiber;
|
|
466
|
-
/** Fiber following a turn this
|
|
487
|
+
/** Fiber following a turn this chat model did not issue ({@link adopt}). */
|
|
467
488
|
#observeFiber;
|
|
468
489
|
/** Last request (for retries). */
|
|
469
490
|
#lastRequest;
|
|
@@ -625,6 +646,7 @@ var AiChatProcessor = class {
|
|
|
625
646
|
this.#registry.set(this.mcpErrors, []);
|
|
626
647
|
this.#registry.set(this.activity, { phase: "starting" });
|
|
627
648
|
this.#registry.set(this.active, true);
|
|
649
|
+
await this.#ensureTurnReview();
|
|
628
650
|
const effect = Effect.gen({ self: this }, function* () {
|
|
629
651
|
log.info("init agent session", {
|
|
630
652
|
feed: Obj.getURI(this._feed),
|
|
@@ -633,25 +655,25 @@ var AiChatProcessor = class {
|
|
|
633
655
|
}, {
|
|
634
656
|
"~LogMeta": "~LogMeta",
|
|
635
657
|
F: __dxlog_file,
|
|
636
|
-
L:
|
|
658
|
+
L: 478,
|
|
637
659
|
S: this
|
|
638
660
|
});
|
|
639
661
|
const session = yield* this.#getSession();
|
|
640
662
|
markWork("chat.session-ready");
|
|
641
|
-
yield* this.#forkEphemeralCollector(session);
|
|
642
|
-
log("chat
|
|
663
|
+
yield* this.#forkEphemeralCollector(session, { replay: false });
|
|
664
|
+
log("chat model submitting prompt", { length: requestProp.message.length }, {
|
|
643
665
|
"~LogMeta": "~LogMeta",
|
|
644
666
|
F: __dxlog_file,
|
|
645
|
-
L:
|
|
667
|
+
L: 489,
|
|
646
668
|
S: this
|
|
647
669
|
});
|
|
648
670
|
yield* session.submitPrompt(createPromptContent(requestProp), { sender: this._options.sender });
|
|
649
671
|
markWork("chat.prompt-submitted");
|
|
650
672
|
submission?.resolve();
|
|
651
|
-
log("chat
|
|
673
|
+
log("chat model submitPrompt returned, waiting for agent", {}, {
|
|
652
674
|
"~LogMeta": "~LogMeta",
|
|
653
675
|
F: __dxlog_file,
|
|
654
|
-
L:
|
|
676
|
+
L: 493,
|
|
655
677
|
S: this
|
|
656
678
|
});
|
|
657
679
|
if (!this._options.chat?.target?.name && requestProp.disposition !== "synthetic") yield* this.#updateChatName(requestProp.message);
|
|
@@ -659,7 +681,7 @@ var AiChatProcessor = class {
|
|
|
659
681
|
log.info("session complete", void 0, {
|
|
660
682
|
"~LogMeta": "~LogMeta",
|
|
661
683
|
F: __dxlog_file,
|
|
662
|
-
L:
|
|
684
|
+
L: 503,
|
|
663
685
|
S: this
|
|
664
686
|
});
|
|
665
687
|
this.#flushStreaming();
|
|
@@ -681,7 +703,7 @@ var AiChatProcessor = class {
|
|
|
681
703
|
log.error("request failed", { error: err }, {
|
|
682
704
|
"~LogMeta": "~LogMeta",
|
|
683
705
|
F: __dxlog_file,
|
|
684
|
-
L:
|
|
706
|
+
L: 530,
|
|
685
707
|
S: this
|
|
686
708
|
});
|
|
687
709
|
submission?.reject(err);
|
|
@@ -690,7 +712,7 @@ var AiChatProcessor = class {
|
|
|
690
712
|
log.info("setting active to false", void 0, {
|
|
691
713
|
"~LogMeta": "~LogMeta",
|
|
692
714
|
F: __dxlog_file,
|
|
693
|
-
L:
|
|
715
|
+
L: 535,
|
|
694
716
|
S: this
|
|
695
717
|
});
|
|
696
718
|
this.#registry.set(this.active, false);
|
|
@@ -714,7 +736,7 @@ var AiChatProcessor = class {
|
|
|
714
736
|
log.error("enqueue failed", { error: err }, {
|
|
715
737
|
"~LogMeta": "~LogMeta",
|
|
716
738
|
F: __dxlog_file,
|
|
717
|
-
L:
|
|
739
|
+
L: 555,
|
|
718
740
|
S: this
|
|
719
741
|
});
|
|
720
742
|
this.#registry.set(this.error, Option.some(parseError(err)));
|
|
@@ -727,8 +749,8 @@ var AiChatProcessor = class {
|
|
|
727
749
|
}).pipe(Effect.provide(this._spaceLayer)));
|
|
728
750
|
}
|
|
729
751
|
/**
|
|
730
|
-
* Mirrors turns this
|
|
731
|
-
* per-
|
|
752
|
+
* Mirrors turns this chat model did not initiate into its own state: active/streaming state is
|
|
753
|
+
* per-chat model ({@link useChatModel} builds one per mount) while the agent process outlives
|
|
732
754
|
* the mount, so a chat remounted mid-turn would otherwise render as idle.
|
|
733
755
|
*
|
|
734
756
|
* Returns a disposer that stops observing.
|
|
@@ -744,7 +766,7 @@ var AiChatProcessor = class {
|
|
|
744
766
|
}).catch((err) => log.warn("failed to attach to agent session", { error: err }, {
|
|
745
767
|
"~LogMeta": "~LogMeta",
|
|
746
768
|
F: __dxlog_file,
|
|
747
|
-
L:
|
|
769
|
+
L: 598,
|
|
748
770
|
S: this
|
|
749
771
|
}));
|
|
750
772
|
return () => {
|
|
@@ -759,20 +781,21 @@ var AiChatProcessor = class {
|
|
|
759
781
|
}
|
|
760
782
|
/**
|
|
761
783
|
* Follows a turn started elsewhere to completion, surfacing its streamed blocks here.
|
|
762
|
-
* A turn this
|
|
784
|
+
* A turn this chat model issued is owned by {@link request}, which reports its own errors.
|
|
763
785
|
*/
|
|
764
786
|
async #observe(session) {
|
|
765
787
|
if (this.#requestFiber || this.#observeFiber || this.#registry.get(this.active)) return;
|
|
766
788
|
log.info("observing agent turn", { feed: Obj.getURI(this._feed) }, {
|
|
767
789
|
"~LogMeta": "~LogMeta",
|
|
768
790
|
F: __dxlog_file,
|
|
769
|
-
L:
|
|
791
|
+
L: 622,
|
|
770
792
|
S: this
|
|
771
793
|
});
|
|
772
794
|
try {
|
|
795
|
+
this.#registry.set(this.activity, { phase: "starting" });
|
|
773
796
|
this.#registry.set(this.active, true);
|
|
774
797
|
const effect = Effect.gen({ self: this }, function* () {
|
|
775
|
-
yield* this.#forkEphemeralCollector(session);
|
|
798
|
+
yield* this.#forkEphemeralCollector(session, { replay: true });
|
|
776
799
|
yield* session.waitForCompletion();
|
|
777
800
|
this.#flushStreaming();
|
|
778
801
|
});
|
|
@@ -783,7 +806,7 @@ var AiChatProcessor = class {
|
|
|
783
806
|
log.warn("failed to observe agent turn", { error: err }, {
|
|
784
807
|
"~LogMeta": "~LogMeta",
|
|
785
808
|
F: __dxlog_file,
|
|
786
|
-
L:
|
|
809
|
+
L: 643,
|
|
787
810
|
S: this
|
|
788
811
|
});
|
|
789
812
|
} finally {
|
|
@@ -799,7 +822,7 @@ var AiChatProcessor = class {
|
|
|
799
822
|
#getSession() {
|
|
800
823
|
return Effect.gen({ self: this }, function* () {
|
|
801
824
|
const chat = this._options.chat?.target;
|
|
802
|
-
if (!chat) return yield* Effect.die(/* @__PURE__ */ new Error("Chat
|
|
825
|
+
if (!chat) return yield* Effect.die(/* @__PURE__ */ new Error("Chat model requires a chat."));
|
|
803
826
|
const selected = this._options.model;
|
|
804
827
|
if (!chat.session?.model && selected) Obj.update(chat, (chat) => {
|
|
805
828
|
chat.session = {
|
|
@@ -810,7 +833,7 @@ var AiChatProcessor = class {
|
|
|
810
833
|
const model = chat.session?.model ?? selected;
|
|
811
834
|
return yield* AgentService.getSession(chat, {
|
|
812
835
|
provider: model ? providerForModel(model, this._options.provider) : this._options.provider,
|
|
813
|
-
location: chat
|
|
836
|
+
location: Agent.chatLocation(chat)
|
|
814
837
|
});
|
|
815
838
|
});
|
|
816
839
|
}
|
|
@@ -818,11 +841,16 @@ var AiChatProcessor = class {
|
|
|
818
841
|
* Forks the collector for the session's ephemeral trace events (streaming blocks and MCP
|
|
819
842
|
* failures) as a child of the calling fiber.
|
|
820
843
|
*/
|
|
821
|
-
#forkEphemeralCollector(session) {
|
|
822
|
-
return session.subscribeEphemeral().pipe(Stream.runForEach((message) => Effect.sync(() => {
|
|
823
|
-
for (const event of message.events) if (Trace.isOfType(Trace.PartialBlock, event))
|
|
824
|
-
|
|
825
|
-
|
|
844
|
+
#forkEphemeralCollector(session, options) {
|
|
845
|
+
return session.subscribeEphemeral(options).pipe(Stream.runForEach((message) => Effect.sync(() => {
|
|
846
|
+
for (const event of message.events) if (Trace.isOfType(Trace.PartialBlock, event)) {
|
|
847
|
+
const current = event.timestamp >= this.#appliedAt;
|
|
848
|
+
if (this.#isStale(event.timestamp, event.data.messageId)) continue;
|
|
849
|
+
this.#handleEphemeralMessage(event.data, current);
|
|
850
|
+
} else if (Trace.isOfType(Trace.RequestPhase, event)) {
|
|
851
|
+
if (this.#isStale(event.timestamp)) continue;
|
|
852
|
+
this.#registry.set(this.activity, event.data);
|
|
853
|
+
} else if (Trace.isOfType(Trace.McpServerError, event)) this.#handleMcpError(event.data);
|
|
826
854
|
})), Effect.forkChild, Effect.asVoid);
|
|
827
855
|
}
|
|
828
856
|
/**
|
|
@@ -833,7 +861,7 @@ var AiChatProcessor = class {
|
|
|
833
861
|
log.info("cancelling request", { fiber: this.#requestFiber }, {
|
|
834
862
|
"~LogMeta": "~LogMeta",
|
|
835
863
|
F: __dxlog_file,
|
|
836
|
-
L:
|
|
864
|
+
L: 723,
|
|
837
865
|
S: this
|
|
838
866
|
});
|
|
839
867
|
if (this.#requestFiber) yield* Fiber.interrupt(this.#requestFiber);
|
|
@@ -864,13 +892,23 @@ var AiChatProcessor = class {
|
|
|
864
892
|
EffectEx.unwrapExit(await this._runtime.runPromiseExit(Operation.invoke(AssistantOperation.UpdateChatName, { chat }, { spaceId }).pipe(Effect.provide(this._spaceLayer))));
|
|
865
893
|
}
|
|
866
894
|
/**
|
|
895
|
+
* Whether a trace event is older than one already applied (per message for a streamed block, whose
|
|
896
|
+
* text is the whole reply so far), recording it as the newest when it is not.
|
|
897
|
+
*/
|
|
898
|
+
#isStale(timestamp, messageId) {
|
|
899
|
+
if (timestamp < (messageId === void 0 ? this.#appliedAt : this.#appliedAtByMessage.get(messageId) ?? 0)) return true;
|
|
900
|
+
this.#appliedAt = Math.max(this.#appliedAt, timestamp);
|
|
901
|
+
if (messageId !== void 0) this.#appliedAtByMessage.set(messageId, timestamp);
|
|
902
|
+
return false;
|
|
903
|
+
}
|
|
904
|
+
/**
|
|
867
905
|
* Handles an ephemeral message from the agent process.
|
|
868
906
|
* Both pending and completed blocks arrive here. Completed blocks are deduped
|
|
869
907
|
* against messages already written to the feed queue to handle the race between
|
|
870
908
|
* ephemeral delivery and feed replication.
|
|
871
909
|
*/
|
|
872
|
-
#handleEphemeralMessage(event) {
|
|
873
|
-
this.#registry.set(this.activity, { phase: "generating" });
|
|
910
|
+
#handleEphemeralMessage(event, current = true) {
|
|
911
|
+
if (current) this.#registry.set(this.activity, { phase: "generating" });
|
|
874
912
|
const isPending = event.block.pending;
|
|
875
913
|
const message = Obj.make(Message.Message, {
|
|
876
914
|
id: event.messageId,
|
|
@@ -906,7 +944,7 @@ var AiChatProcessor = class {
|
|
|
906
944
|
log.warn("MCP server error", event, {
|
|
907
945
|
"~LogMeta": "~LogMeta",
|
|
908
946
|
F: __dxlog_file,
|
|
909
|
-
L:
|
|
947
|
+
L: 841,
|
|
910
948
|
S: this
|
|
911
949
|
});
|
|
912
950
|
this.#registry.update(this.mcpErrors, (errors) => {
|
|
@@ -921,18 +959,32 @@ var AiChatProcessor = class {
|
|
|
921
959
|
this.#registry.set(this.#streaming, []);
|
|
922
960
|
this.#registry.set(this.activity, void 0);
|
|
923
961
|
this.#finalizedIds.clear();
|
|
962
|
+
this.#resetApplied();
|
|
924
963
|
}
|
|
925
964
|
/**
|
|
926
965
|
* Move remaining streaming messages to pending (called when agent completes).
|
|
927
966
|
*/
|
|
928
967
|
#flushStreaming() {
|
|
929
|
-
this.#registry.set(this.activity, void 0);
|
|
930
968
|
const remaining = this.#registry.get(this.#streaming);
|
|
931
969
|
if (remaining.length > 0) {
|
|
932
970
|
this.#registry.update(this.#pending, (pending) => [...pending, ...remaining]);
|
|
933
971
|
this.#registry.set(this.#streaming, []);
|
|
934
972
|
}
|
|
935
973
|
this.#finalizedIds.clear();
|
|
974
|
+
this.#resetApplied();
|
|
975
|
+
}
|
|
976
|
+
#resetApplied() {
|
|
977
|
+
this.#appliedAt = 0;
|
|
978
|
+
this.#appliedAtByMessage.clear();
|
|
979
|
+
}
|
|
980
|
+
/**
|
|
981
|
+
* Binds the turn-review skill before a request while the user is opted in; its background
|
|
982
|
+
* end-request hook does the review, so nothing here waits on it. Left bound after an opt-out,
|
|
983
|
+
* since the hook re-checks the setting.
|
|
984
|
+
*/
|
|
985
|
+
async #ensureTurnReview() {
|
|
986
|
+
if (!this._options.reportStruggles) return;
|
|
987
|
+
if (!this.context.getSkills().some((skill) => Obj.getMeta(skill).key === TurnReviewSkill.key)) await this.context.bind({ skills: [Ref.fromURI(Skill.registryURI(TurnReviewSkill.key))] });
|
|
936
988
|
}
|
|
937
989
|
/**
|
|
938
990
|
* Schedule a chat name update as a detached (fire-and-forget) operation.
|
|
@@ -946,7 +998,7 @@ var AiChatProcessor = class {
|
|
|
946
998
|
log.info("scheduling chat name update", void 0, {
|
|
947
999
|
"~LogMeta": "~LogMeta",
|
|
948
1000
|
F: __dxlog_file,
|
|
949
|
-
L:
|
|
1001
|
+
L: 910,
|
|
950
1002
|
S: this
|
|
951
1003
|
});
|
|
952
1004
|
return Operation.schedule(AssistantOperation.UpdateChatName, {
|
|
@@ -955,7 +1007,7 @@ var AiChatProcessor = class {
|
|
|
955
1007
|
}, { spaceId });
|
|
956
1008
|
}
|
|
957
1009
|
};
|
|
958
|
-
var
|
|
1010
|
+
var idleChatModelState = {
|
|
959
1011
|
streaming: Atom.make(false),
|
|
960
1012
|
active: Atom.make(false),
|
|
961
1013
|
thread: Atom.make(EMPTY_THREAD),
|
|
@@ -963,8 +1015,8 @@ var idleProcessorState = {
|
|
|
963
1015
|
mcpErrors: Atom.make([]),
|
|
964
1016
|
activity: Atom.make(void 0)
|
|
965
1017
|
};
|
|
966
|
-
var
|
|
1018
|
+
var getChatModelState = (chatModel) => chatModel ?? idleChatModelState;
|
|
967
1019
|
//#endregion
|
|
968
|
-
export { projectSelfWakes as a, projectAlarms as i,
|
|
1020
|
+
export { projectSelfWakes as a, projectAlarms as i, ChatModel as n, resolveRewind as o, getChatModelState as r, AiUsageQuotaError as t };
|
|
969
1021
|
|
|
970
|
-
//# sourceMappingURL=chunk-
|
|
1022
|
+
//# sourceMappingURL=chunk-chat-model.mjs.map
|