@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,213 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2026 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import * as Schema from 'effect/Schema';
|
|
6
|
+
|
|
7
|
+
import { AiSession } from '@dxos/assistant';
|
|
8
|
+
import { type Message } from '@dxos/types';
|
|
9
|
+
import { trim } from '@dxos/util';
|
|
10
|
+
|
|
11
|
+
/** PostHog event emitted when the reviewer finds a turn the agent struggled through. */
|
|
12
|
+
export const STRUGGLE_EVENT = 'agent_struggle';
|
|
13
|
+
|
|
14
|
+
/** Small enough to be cheap per turn, strong enough to judge an agentic transcript. */
|
|
15
|
+
export const REVIEW_MODEL = 'com.anthropic.model.claude-sonnet-5.default';
|
|
16
|
+
|
|
17
|
+
/** Bounds the transcript the reviewer reads; the uploaded trajectory is not cut. */
|
|
18
|
+
const MAX_TRANSCRIPT_CHARS = 120_000;
|
|
19
|
+
const MAX_BLOCK_CHARS = 4_000;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Why the agent struggled. Only the first four are reported: they are the ones fixable on our side
|
|
23
|
+
* (prompting or tooling); the model's own reasoning and an unclear user request are not.
|
|
24
|
+
*/
|
|
25
|
+
export const StruggleCause = Schema.Literals([
|
|
26
|
+
'system_prompt_missing',
|
|
27
|
+
'system_prompt_wrong',
|
|
28
|
+
'tool_missing',
|
|
29
|
+
'tool_faulty',
|
|
30
|
+
'model',
|
|
31
|
+
'user',
|
|
32
|
+
'none',
|
|
33
|
+
]);
|
|
34
|
+
export type StruggleCause = Schema.Schema.Type<typeof StruggleCause>;
|
|
35
|
+
|
|
36
|
+
export const TurnVerdict = Schema.Struct({
|
|
37
|
+
struggled: Schema.Boolean.annotate({
|
|
38
|
+
description: 'Whether the agent visibly struggled in the latest turn.',
|
|
39
|
+
}),
|
|
40
|
+
cause: StruggleCause.annotate({
|
|
41
|
+
description: 'The root cause of the struggle, or "none".',
|
|
42
|
+
}),
|
|
43
|
+
severity: Schema.Literals(['low', 'medium', 'high']).annotate({
|
|
44
|
+
description: 'low: recovered quickly; medium: wasted several steps; high: failed the request.',
|
|
45
|
+
}),
|
|
46
|
+
summary: Schema.String.annotate({
|
|
47
|
+
description: 'One sentence naming what went wrong and what instruction or tool change would fix it.',
|
|
48
|
+
}),
|
|
49
|
+
tools: Schema.Array(Schema.String).annotate({
|
|
50
|
+
description: 'Names of the tools implicated, if any.',
|
|
51
|
+
}),
|
|
52
|
+
});
|
|
53
|
+
export type TurnVerdict = Schema.Schema.Type<typeof TurnVerdict>;
|
|
54
|
+
|
|
55
|
+
export type CauseGroup = 'system_prompt' | 'tooling';
|
|
56
|
+
|
|
57
|
+
/** The dashboard's top-level split; `undefined` for a cause that is not reported. */
|
|
58
|
+
export const causeGroup = (cause: StruggleCause): CauseGroup | undefined => {
|
|
59
|
+
switch (cause) {
|
|
60
|
+
case 'system_prompt_missing':
|
|
61
|
+
case 'system_prompt_wrong':
|
|
62
|
+
return 'system_prompt';
|
|
63
|
+
case 'tool_missing':
|
|
64
|
+
case 'tool_faulty':
|
|
65
|
+
return 'tooling';
|
|
66
|
+
default:
|
|
67
|
+
return undefined;
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
export const isReportable = (verdict: TurnVerdict): boolean =>
|
|
72
|
+
verdict.struggled && causeGroup(verdict.cause) !== undefined;
|
|
73
|
+
|
|
74
|
+
export const REVIEW_SYSTEM_PROMPT = trim`
|
|
75
|
+
You review transcripts of an AI agent working inside Composer, a collaborative workspace app.
|
|
76
|
+
Decide whether the agent struggled in the LATEST TURN (messages inside the <latest_turn> tags).
|
|
77
|
+
Earlier messages are context only.
|
|
78
|
+
|
|
79
|
+
Signs of struggling: repeated or looping tool calls, tool errors, calling tools with invalid
|
|
80
|
+
arguments, guessing at tool names or object ids, apologizing and retrying, giving up, asking the
|
|
81
|
+
user for information it should have been able to obtain, or giving an incomplete answer.
|
|
82
|
+
|
|
83
|
+
Classify the root cause:
|
|
84
|
+
- system_prompt_missing: the agent lacked instructions or context it needed (e.g. how a feature
|
|
85
|
+
works, which tool to use for a task, conventions of the app).
|
|
86
|
+
- system_prompt_wrong: instructions were present but misleading, contradictory, or outdated.
|
|
87
|
+
- tool_missing: no tool existed for an action the request clearly required.
|
|
88
|
+
- tool_faulty: a tool errored, returned unusable output, had a confusing schema or description.
|
|
89
|
+
- model: the tools and instructions were adequate; the model reasoned poorly.
|
|
90
|
+
- user: the request was ambiguous or impossible.
|
|
91
|
+
- none: the agent did not struggle.
|
|
92
|
+
|
|
93
|
+
Be conservative: a single quickly-recovered tool error is not a struggle. When unsure, answer
|
|
94
|
+
struggled=false with cause=none.
|
|
95
|
+
`;
|
|
96
|
+
|
|
97
|
+
const clip = (text: string, max: number): string =>
|
|
98
|
+
text.length <= max ? text : `${text.slice(0, max)}… [${text.length - max} chars truncated]`;
|
|
99
|
+
|
|
100
|
+
const stringify = (value: unknown): string => (typeof value === 'string' ? value : JSON.stringify(value));
|
|
101
|
+
|
|
102
|
+
/** One line per block, so the reviewer sees the shape of the turn without a tool result drowning it. */
|
|
103
|
+
const formatMessage = (message: Message.Message): string => {
|
|
104
|
+
const lines = message.blocks.map((block) => {
|
|
105
|
+
switch (block._tag) {
|
|
106
|
+
case 'text':
|
|
107
|
+
return clip(block.text, MAX_BLOCK_CHARS);
|
|
108
|
+
case 'toolCall':
|
|
109
|
+
return `[tool call ${block.name}] ${clip(stringify(block.input), MAX_BLOCK_CHARS)}`;
|
|
110
|
+
case 'toolResult':
|
|
111
|
+
return block.error
|
|
112
|
+
? `[tool error ${block.name}] ${clip(stringify(block.error), MAX_BLOCK_CHARS)}`
|
|
113
|
+
: `[tool result ${block.name}] ${clip(stringify(block.result), MAX_BLOCK_CHARS)}`;
|
|
114
|
+
default:
|
|
115
|
+
return `[${block._tag}]`;
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
return `${(message.sender.role ?? 'unknown').toUpperCase()}:\n${lines.join('\n')}`;
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Splits the history at the turn that just ended: it starts at the last user message, which is what
|
|
123
|
+
* the agent was answering. Hooks fire per request, so everything after it is this turn's work.
|
|
124
|
+
*/
|
|
125
|
+
export const splitTurn = (history: readonly Message.Message[]) => {
|
|
126
|
+
const index = history.findLastIndex((message) => message.sender.role === 'user');
|
|
127
|
+
return index === -1
|
|
128
|
+
? { context: [] as readonly Message.Message[], turn: history }
|
|
129
|
+
: { context: history.slice(0, index), turn: history.slice(index) };
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
/** The reviewer's prompt; context is trimmed from the front when the transcript runs long. */
|
|
133
|
+
export const formatReviewPrompt = (history: readonly Message.Message[]): string => {
|
|
134
|
+
const { context, turn } = splitTurn(history);
|
|
135
|
+
const turnText = turn.map(formatMessage).join('\n\n');
|
|
136
|
+
const budget = Math.max(0, MAX_TRANSCRIPT_CHARS - turnText.length);
|
|
137
|
+
const contextText = context.map(formatMessage).join('\n\n');
|
|
138
|
+
const clippedContext =
|
|
139
|
+
contextText.length > budget ? `…${contextText.slice(contextText.length - budget)}` : contextText;
|
|
140
|
+
return [clippedContext, '<latest_turn>', clip(turnText, MAX_TRANSCRIPT_CHARS), '</latest_turn>']
|
|
141
|
+
.filter((part) => part.length > 0)
|
|
142
|
+
.join('\n\n');
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
export const countToolCalls = (messages: readonly Message.Message[]) => {
|
|
146
|
+
let calls = 0;
|
|
147
|
+
let errors = 0;
|
|
148
|
+
for (const message of messages) {
|
|
149
|
+
for (const block of message.blocks) {
|
|
150
|
+
if (block._tag === 'toolCall') {
|
|
151
|
+
calls++;
|
|
152
|
+
} else if (block._tag === 'toolResult' && block.error) {
|
|
153
|
+
errors++;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return { calls, errors };
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
export type TrajectoryHeader = {
|
|
161
|
+
sessionId: string;
|
|
162
|
+
model?: string;
|
|
163
|
+
codeMode: boolean;
|
|
164
|
+
skills: readonly string[];
|
|
165
|
+
verdict: TurnVerdict;
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
/** A header line then one line per message, in the span serialization the LLM analytics traces use. */
|
|
169
|
+
export const toTrajectoryNdjson = (header: TrajectoryHeader, history: readonly Message.Message[]): string =>
|
|
170
|
+
[
|
|
171
|
+
JSON.stringify({ type: 'header', ...header }),
|
|
172
|
+
...history.map((message) =>
|
|
173
|
+
JSON.stringify({ type: 'message', created: message.created, ...(AiSession.serializeMessage(message) as object) }),
|
|
174
|
+
),
|
|
175
|
+
].join('\n');
|
|
176
|
+
|
|
177
|
+
export type StruggleEventInput = TrajectoryHeader & {
|
|
178
|
+
history: readonly Message.Message[];
|
|
179
|
+
trajectoryKey?: string;
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Flat properties so every one is a PostHog breakdown; lists are comma-joined for the same reason.
|
|
184
|
+
* `$ai_session_id` joins the event to the conversation's LLM analytics traces.
|
|
185
|
+
*/
|
|
186
|
+
export const toStruggleEventProperties = ({
|
|
187
|
+
sessionId,
|
|
188
|
+
model,
|
|
189
|
+
codeMode,
|
|
190
|
+
skills,
|
|
191
|
+
verdict,
|
|
192
|
+
history,
|
|
193
|
+
trajectoryKey,
|
|
194
|
+
}: StruggleEventInput): Record<string, unknown> => {
|
|
195
|
+
const { turn } = splitTurn(history);
|
|
196
|
+
const { calls, errors } = countToolCalls(turn);
|
|
197
|
+
return {
|
|
198
|
+
$ai_session_id: sessionId,
|
|
199
|
+
model,
|
|
200
|
+
code_mode: codeMode,
|
|
201
|
+
skills: skills.join(','),
|
|
202
|
+
cause: verdict.cause,
|
|
203
|
+
cause_group: causeGroup(verdict.cause),
|
|
204
|
+
severity: verdict.severity,
|
|
205
|
+
summary: verdict.summary,
|
|
206
|
+
tools: verdict.tools.join(','),
|
|
207
|
+
turn_message_count: turn.length,
|
|
208
|
+
turn_tool_call_count: calls,
|
|
209
|
+
turn_tool_error_count: errors,
|
|
210
|
+
review_model: REVIEW_MODEL,
|
|
211
|
+
trajectory_key: trajectoryKey,
|
|
212
|
+
};
|
|
213
|
+
};
|
package/src/skills/index.ts
CHANGED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2026 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
// @import-as-namespace
|
|
6
|
+
|
|
7
|
+
import * as Schema from 'effect/Schema';
|
|
8
|
+
|
|
9
|
+
import { AiService } from '@dxos/ai';
|
|
10
|
+
import * as Capability from '@dxos/app-framework/Capability';
|
|
11
|
+
import * as Harness from '@dxos/assistant/Harness';
|
|
12
|
+
import * as Operation from '@dxos/compute/Operation';
|
|
13
|
+
import * as Skill from '@dxos/compute/Skill';
|
|
14
|
+
import { Database, DXN, Ref } from '@dxos/echo';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* The skill's end-request hook: asks a small model whether the agent struggled in the turn that just
|
|
18
|
+
* ended because of its prompting or tooling, and if so reports the trajectory. Defined beside the
|
|
19
|
+
* skill rather than in `#types` because the harness service it needs carries the session runtime,
|
|
20
|
+
* which the UI must not load.
|
|
21
|
+
*/
|
|
22
|
+
export const AnalyzeTurn = Operation.make({
|
|
23
|
+
meta: {
|
|
24
|
+
key: DXN.make('org.dxos.operation.assistant.analyzeTurn'),
|
|
25
|
+
name: 'Analyze Agent Turn',
|
|
26
|
+
icon: 'ph--magnifying-glass--regular',
|
|
27
|
+
},
|
|
28
|
+
services: [Harness.HarnessService, Database.Service, AiService.AiService, Capability.Service],
|
|
29
|
+
input: Schema.Struct({}),
|
|
30
|
+
output: Schema.Void,
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
export const key = 'org.dxos.skill.assistant.turnReview';
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Bound to a chat by the chat processor while the user has opted into struggle reports. It adds no
|
|
37
|
+
* tools or instructions; it exists only for its hook, which runs in the background so the user never
|
|
38
|
+
* waits on the review.
|
|
39
|
+
*/
|
|
40
|
+
export const make = (): Skill.Skill =>
|
|
41
|
+
Skill.make({
|
|
42
|
+
key,
|
|
43
|
+
name: 'Turn Review',
|
|
44
|
+
description: 'Reports agent turns that struggled because of their instructions or tools (opt-in telemetry).',
|
|
45
|
+
hooks: [{ spec: { _tag: 'end-request' }, async: true, function: Ref.make(Operation.serialize(AnalyzeTurn)) }],
|
|
46
|
+
});
|
package/src/translations.ts
CHANGED
|
@@ -192,6 +192,8 @@ export const translations: Theme.Resource[] = [
|
|
|
192
192
|
'options.mcp.title': 'MCP',
|
|
193
193
|
'options.chat-model.title': 'Models',
|
|
194
194
|
'options.environment.title': 'Environment',
|
|
195
|
+
'options.agent.title': 'Agent',
|
|
196
|
+
'agent-locked.message': 'The agent is chosen before the first message. Start a new chat to use another.',
|
|
195
197
|
'remove-object.label': 'Remove object',
|
|
196
198
|
|
|
197
199
|
'chat-view.title': 'View',
|
|
@@ -9,7 +9,7 @@ import type * as Atom from 'effect/reactivity/Atom';
|
|
|
9
9
|
import * as Schema from 'effect/Schema';
|
|
10
10
|
import * as Struct from 'effect/Struct';
|
|
11
11
|
|
|
12
|
-
import type { MakeTurnProducer } from '@dxos/agent-runtime';
|
|
12
|
+
import type { AgentProcessDefinition, MakeTurnProducer } from '@dxos/agent-runtime';
|
|
13
13
|
import * as Capability from '@dxos/app-framework/Capability';
|
|
14
14
|
import type { AiContext } from '@dxos/assistant';
|
|
15
15
|
import type * as Chat from '@dxos/assistant/Chat';
|
|
@@ -107,6 +107,13 @@ export type Agent = {
|
|
|
107
107
|
|
|
108
108
|
export const Agent = Capability.make<Agent>()(`${meta.profile.key}.capability.agent`);
|
|
109
109
|
|
|
110
|
+
/**
|
|
111
|
+
* A durable process a chat can run on in place of the assistant's own agent process, named by its
|
|
112
|
+
* key in `chat.session.process`. The agent service spawns it for such a chat; a chat that names
|
|
113
|
+
* none runs on the assistant's.
|
|
114
|
+
*/
|
|
115
|
+
export const AgentProcess = Capability.make<AgentProcessDefinition>()(`${meta.profile.key}.capability.agentProcess`);
|
|
116
|
+
|
|
110
117
|
/** Context a chat receives when it runs against a subject object. */
|
|
111
118
|
export type SubjectBindings = AiContext.BindingProps & {
|
|
112
119
|
/** Applied to `chat.instructions` only when the chat has none; reaches the model via the system prompt. */
|
package/src/types/Settings.ts
CHANGED
|
@@ -81,6 +81,13 @@ export const Settings = Schema.Struct({
|
|
|
81
81
|
'Agent that one-click "Assign to agent" hands tasks to, by id: composer, or another installed agent such as claude-code. Falls back to Composer where that agent is unavailable.',
|
|
82
82
|
}),
|
|
83
83
|
),
|
|
84
|
+
reportStruggles: Schema.optional(
|
|
85
|
+
Schema.Boolean.annotate({
|
|
86
|
+
title: 'Report agent struggles',
|
|
87
|
+
description:
|
|
88
|
+
'After each agent turn, a small model reviews the conversation. If the agent struggled because of its instructions or tools, the conversation (including content from your space) is sent to the DXOS team to improve Composer. Requires telemetry to be enabled.',
|
|
89
|
+
}),
|
|
90
|
+
),
|
|
84
91
|
tracePanelDebug: Schema.optional(
|
|
85
92
|
Schema.Boolean.annotate({
|
|
86
93
|
title: 'Trace panel debug',
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2026 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { describe, test } from 'vitest';
|
|
6
|
+
|
|
7
|
+
import { SessionConfig } from '@dxos/ai';
|
|
8
|
+
|
|
9
|
+
import { NOT_INSTALLED, agentOptions } from './agent-options.ts';
|
|
10
|
+
|
|
11
|
+
const composer = { id: SessionConfig.COMPOSER_HARNESS, label: 'Composer', icon: 'ph--sparkle--regular' };
|
|
12
|
+
const local = { id: 'claude-code', label: 'Claude Code', icon: 'px--anthropic--regular' };
|
|
13
|
+
const cloud = { id: 'claude-code-edge', label: 'Claude Code (cloud)', icon: 'px--anthropic--regular' };
|
|
14
|
+
|
|
15
|
+
describe('agentOptions', () => {
|
|
16
|
+
test('lists Composer first, then the other agents in registration order', ({ expect }) => {
|
|
17
|
+
const options = agentOptions({
|
|
18
|
+
agents: [local, composer, cloud],
|
|
19
|
+
availability: [{ available: true }, { available: true }, { available: true }],
|
|
20
|
+
current: SessionConfig.COMPOSER_HARNESS,
|
|
21
|
+
});
|
|
22
|
+
expect(options.map(({ value }) => value)).toEqual([composer.id, local.id, cloud.id]);
|
|
23
|
+
expect(options.every(({ disabled }) => !disabled)).toBe(true);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test('disables an unavailable agent with its reason', ({ expect }) => {
|
|
27
|
+
const options = agentOptions({
|
|
28
|
+
agents: [composer, local, cloud],
|
|
29
|
+
availability: [
|
|
30
|
+
{ available: true },
|
|
31
|
+
{ available: false, reason: 'needs the Composer desktop app' },
|
|
32
|
+
{ available: true },
|
|
33
|
+
],
|
|
34
|
+
current: SessionConfig.COMPOSER_HARNESS,
|
|
35
|
+
});
|
|
36
|
+
expect(options[1]).toEqual({
|
|
37
|
+
value: local.id,
|
|
38
|
+
label: local.label,
|
|
39
|
+
icon: local.icon,
|
|
40
|
+
disabled: true,
|
|
41
|
+
description: 'needs the Composer desktop app',
|
|
42
|
+
});
|
|
43
|
+
expect(options[2].disabled).toBe(false);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test('keeps an agent the chat names that this device does not register', ({ expect }) => {
|
|
47
|
+
const options = agentOptions({
|
|
48
|
+
agents: [composer],
|
|
49
|
+
availability: [{ available: true }],
|
|
50
|
+
current: 'claude-code-edge',
|
|
51
|
+
});
|
|
52
|
+
expect(options.at(-1)).toMatchObject({ value: 'claude-code-edge', disabled: true, description: NOT_INSTALLED });
|
|
53
|
+
});
|
|
54
|
+
});
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2026 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { SessionConfig } from '@dxos/ai';
|
|
6
|
+
|
|
7
|
+
import type * as AssistantCapabilities from '../types/AssistantCapabilities.ts';
|
|
8
|
+
|
|
9
|
+
/** One row of the chat's agent picker. */
|
|
10
|
+
export type AgentOption = {
|
|
11
|
+
value: string;
|
|
12
|
+
label: string;
|
|
13
|
+
icon: string;
|
|
14
|
+
disabled: boolean;
|
|
15
|
+
/** Why the row cannot be picked, shown under its name. */
|
|
16
|
+
description?: string;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export type AgentOptionsProps = {
|
|
20
|
+
agents: readonly Pick<AssistantCapabilities.Agent, 'id' | 'label' | 'icon'>[];
|
|
21
|
+
/** Availability of each agent, by index into `agents`. */
|
|
22
|
+
availability: readonly AssistantCapabilities.AgentAvailability[];
|
|
23
|
+
/** The agent the chat names (`chat.session.harness`). */
|
|
24
|
+
current: string;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/** Reason shown for an agent a chat names that no plugin registers on this device. */
|
|
28
|
+
export const NOT_INSTALLED = 'not installed here';
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* The rows of the agent picker: Composer's own loop first, as the default, then the rest in
|
|
32
|
+
* registration order, with an unavailable agent disabled and its reason as the description. An
|
|
33
|
+
* agent the chat already names but this device does not register is kept, so the picker still
|
|
34
|
+
* shows what the chat runs on.
|
|
35
|
+
*/
|
|
36
|
+
export const agentOptions = ({ agents, availability, current }: AgentOptionsProps): AgentOption[] => {
|
|
37
|
+
const options = agents.map((agent, index): AgentOption => {
|
|
38
|
+
const status = availability[index];
|
|
39
|
+
return status && !status.available
|
|
40
|
+
? { value: agent.id, label: agent.label, icon: agent.icon, disabled: true, description: status.reason }
|
|
41
|
+
: { value: agent.id, label: agent.label, icon: agent.icon, disabled: false };
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const sorted = [
|
|
45
|
+
...options.filter(({ value }) => value === SessionConfig.COMPOSER_HARNESS),
|
|
46
|
+
...options.filter(({ value }) => value !== SessionConfig.COMPOSER_HARNESS),
|
|
47
|
+
];
|
|
48
|
+
|
|
49
|
+
if (!sorted.some(({ value }) => value === current)) {
|
|
50
|
+
sorted.push({
|
|
51
|
+
value: current,
|
|
52
|
+
label: current,
|
|
53
|
+
icon: 'ph--robot--regular',
|
|
54
|
+
disabled: true,
|
|
55
|
+
description: NOT_INSTALLED,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return sorted;
|
|
60
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2026 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { type AiContext } from '@dxos/assistant';
|
|
6
|
+
import * as AlarmSkill from '@dxos/assistant-toolkit/AlarmSkill';
|
|
7
|
+
import * as ChatContextSkill from '@dxos/assistant-toolkit/ChatContextSkill';
|
|
8
|
+
import * as PlanningSkill from '@dxos/assistant-toolkit/PlanningSkill';
|
|
9
|
+
import * as SkillManagerSkill from '@dxos/assistant-toolkit/SkillManagerSkill';
|
|
10
|
+
import type * as Chat from '@dxos/assistant/Chat';
|
|
11
|
+
import * as Skill from '@dxos/compute/Skill';
|
|
12
|
+
import { Ref } from '@dxos/echo';
|
|
13
|
+
import * as DatabaseSkill from '@dxos/plugin-space/DatabaseSkill';
|
|
14
|
+
|
|
15
|
+
import { AssistantSkill, PluginManagerSkill } from '#skills';
|
|
16
|
+
|
|
17
|
+
/** Only an extensible host contributes the plugin-manager skill; elsewhere its tools cannot run. */
|
|
18
|
+
export const contributesPluginManager = (contributed: readonly Pick<Skill.Definition, 'key'>[]): boolean =>
|
|
19
|
+
contributed.some(({ key }) => key === PluginManagerSkill.key);
|
|
20
|
+
|
|
21
|
+
const defaultChatSkills = (pluginManager: boolean): Ref.Ref<Skill.Skill>[] =>
|
|
22
|
+
[
|
|
23
|
+
AssistantSkill,
|
|
24
|
+
DatabaseSkill,
|
|
25
|
+
ChatContextSkill,
|
|
26
|
+
SkillManagerSkill,
|
|
27
|
+
AlarmSkill,
|
|
28
|
+
PlanningSkill,
|
|
29
|
+
...(pluginManager ? [PluginManagerSkill] : []),
|
|
30
|
+
].map(({ key }) => Ref.fromURI(Skill.registryURI(key)));
|
|
31
|
+
|
|
32
|
+
export const bindChatDefaults = (
|
|
33
|
+
binder: AiContext.Binder,
|
|
34
|
+
{ chat, pluginManager }: { chat: Chat.Chat; pluginManager: boolean },
|
|
35
|
+
): Promise<void> => binder.bind({ skills: defaultChatSkills(pluginManager), objects: [Ref.make(chat)] });
|