@checkstack/ai-backend 0.1.3 → 0.1.5
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/CHANGELOG.md +95 -0
- package/package.json +7 -7
- package/src/agent-runner.test.ts +50 -0
- package/src/agent-runner.ts +13 -3
- package/src/chat/chat-handler.ts +6 -0
- package/src/chat/chat-service.ts +13 -18
- package/src/chat/classifier.logic.test.ts +11 -0
- package/src/chat/classifier.logic.ts +16 -9
- package/src/chat/model-schema.test.ts +264 -0
- package/src/chat/model-schema.ts +334 -0
- package/src/chat/sdk-tools.ts +32 -35
- package/src/chat/system-prompt.test.ts +113 -0
- package/src/chat/system-prompt.ts +146 -0
- package/src/generated/docs-index.ts +6 -5
- package/src/projection.test.ts +3 -1
- package/src/registry-wiring.test.ts +3 -1
- package/src/serializer.test.ts +22 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,100 @@
|
|
|
1
1
|
# @checkstack/ai-backend
|
|
2
2
|
|
|
3
|
+
## 0.1.5
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 56e7c75: Hide navigation, actions and links that the current user cannot use, so anonymous
|
|
8
|
+
and read-only users no longer see entries that lead to "Access Denied" or to
|
|
9
|
+
actions the server would reject.
|
|
10
|
+
|
|
11
|
+
- **Sidebar**: a nav entry can now declare a dynamic `nav.isVisible({ accessRules, isAuthenticated })` predicate (in addition to the static `accessRule`). A group whose every entry is filtered out is no longer rendered. The filtering/grouping logic is extracted to a pure, unit-tested helper.
|
|
12
|
+
- **Infrastructure**: its sidebar entry is shown only when the user can READ at least one contributed tab (queue, cache, …), instead of always (it previously had no static rule because tabs are contributed at runtime).
|
|
13
|
+
- **Notification Settings**: hidden from anonymous users - notifications are per-user, so an anonymous visitor can't have any.
|
|
14
|
+
- **Anomaly Mute / Suppress**: the "Mute" / "Mute all" controls (a per-user preference) are hidden from anonymous visitors; the "Suppress" control is gated on `anomalyAccess.feed.manage`. Both were previously always visible.
|
|
15
|
+
- **Dashboard**: the "Open Catalog" actions (which open the manage-only Catalog config page) are hidden from users without `catalogAccess.system.manage`, and the "View catalog" link is gated on `catalogAccess.system.read`.
|
|
16
|
+
- **Dashboard status signals**: the per-system status rows contributed by plugins (`SystemSignalsSlot`) now render as a LINK only when the user can open the target, and as plain text otherwise. `SystemSignal` gains an optional `accessRule`; the healthcheck, anomaly, and dependency fillers set it for their gated targets (check-history / assignments / dependency-map). Signals pointing at ungated pages (incident / maintenance / SLO detail) stay links.
|
|
17
|
+
- **Plugin Manager**: the "Install plugin" button (which opens the install-gated page) is hidden from users with only `plugin` view access.
|
|
18
|
+
- **Satellites**: the page is entirely manage-gated, but its route/sidebar entry was gated on `read`, so read-only users saw the nav item and hit "Access Denied" on click. The route and nav entry now require `satellite.manage`.
|
|
19
|
+
|
|
20
|
+
The `@checkstack/ai-backend` bump is only the regenerated bundled docs index
|
|
21
|
+
(the frontend routing guide gained the `nav.isVisible` section); no code change.
|
|
22
|
+
|
|
23
|
+
**BREAKING (`@checkstack/frontend-api`):** the `AccessApi` interface gains a
|
|
24
|
+
required `useIsAuthenticated()` method. Custom `AccessApi` implementations must
|
|
25
|
+
add it (it returns `{ loading, isAuthenticated }`). The built-in auth
|
|
26
|
+
implementation and the no-auth fallback already do. `NavEntry` also gains an
|
|
27
|
+
optional `isVisible` predicate (purely additive).
|
|
28
|
+
|
|
29
|
+
- Updated dependencies [0626782]
|
|
30
|
+
- Updated dependencies [56e7c75]
|
|
31
|
+
- @checkstack/backend-api@0.21.5
|
|
32
|
+
- @checkstack/common@0.15.0
|
|
33
|
+
- @checkstack/ai-common@0.1.3
|
|
34
|
+
- @checkstack/integration-backend@0.4.5
|
|
35
|
+
- @checkstack/sdk@0.100.1
|
|
36
|
+
|
|
37
|
+
## 0.1.4
|
|
38
|
+
|
|
39
|
+
### Patch Changes
|
|
40
|
+
|
|
41
|
+
- b50916d: Fix "Date cannot be represented in JSON Schema" crashing the AI chat. Zod v4's
|
|
42
|
+
`toJSONSchema()` throws on `z.date()` (and even `z.coerce.date()`) by default,
|
|
43
|
+
and the chat hit this in TWO places:
|
|
44
|
+
|
|
45
|
+
- **`@checkstack/backend-api`** `toJsonSchema()` (the OpenAPI generator and AI
|
|
46
|
+
tool-introspection / MCP substrate) called it with no options.
|
|
47
|
+
- **`@checkstack/ai-backend`** the agent loop hands the Vercel AI SDK the raw
|
|
48
|
+
Zod tool input, and the SDK runs its OWN `toJSONSchema()` (throwing) to build
|
|
49
|
+
the model-facing tool schema - so a single date field in any tool input
|
|
50
|
+
crashed every chat turn (the whole tool list is projected before the model is
|
|
51
|
+
called).
|
|
52
|
+
|
|
53
|
+
Both now render dates as `{ type: "string", format: "date-time" }` (their wire
|
|
54
|
+
shape) and degrade other unrepresentable types to `{}` instead of throwing.
|
|
55
|
+
|
|
56
|
+
For the model boundary, a single `dateSafeModelSchema()` helper hands the SDK a
|
|
57
|
+
ready-made date-safe schema plus a validator that COERCES the ISO strings the
|
|
58
|
+
model emits back into real `Date`s before parsing with the original schema
|
|
59
|
+
(refinements and the downstream RPC client, which expects `Date`s, keep
|
|
60
|
+
working). A single `toModelSchema()` entry point applies this at EVERY point a
|
|
61
|
+
schema is handed to the model - chat tool inputs, the headless agent runner's
|
|
62
|
+
tool inputs (the automation "AI Action"), and `generateObject` structured
|
|
63
|
+
output - gated so non-date schemas are untouched, so individual tool / agent
|
|
64
|
+
definitions never special-case dates. Regression tests cover the converter, the
|
|
65
|
+
AI tool serializer, and the model-schema generation + coercion helper, including
|
|
66
|
+
the full inbound round-trip with the exact ISO shape a live model emits
|
|
67
|
+
(`...T22:00:00Z`, no milliseconds).
|
|
68
|
+
|
|
69
|
+
**Timezone correctness.** Because the model produces dates as text, the chat now
|
|
70
|
+
enforces an unambiguous wire contract: a date-time tool argument MUST be RFC 3339
|
|
71
|
+
with an explicit timezone offset. Zone-less (`2026-07-01T22:00:00`) and date-only
|
|
72
|
+
(`2026-07-01`) values are rejected with a model-readable error (the model
|
|
73
|
+
self-repairs), instead of being silently interpreted in the pod's local zone -
|
|
74
|
+
which would resolve the same string to different instants across pods. To resolve
|
|
75
|
+
an operator's bare "22:00", the browser's IANA timezone is sent with every chat
|
|
76
|
+
turn and folded into the system prompt, so each operator's times are interpreted
|
|
77
|
+
in their own zone by default. When no browser zone is available (a headless
|
|
78
|
+
automation AI Action), the reference zone falls back to the host/container
|
|
79
|
+
timezone (`TZ`), not UTC. A format-matrix test covers every common shape a model
|
|
80
|
+
might emit. The chat UI shows the operator which timezone is in use, and the
|
|
81
|
+
`TZ` override is documented for operators.
|
|
82
|
+
|
|
83
|
+
**Current time in context.** The model has no clock, so the system prompt now
|
|
84
|
+
includes the current instant (UTC plus the reference-zone wall clock), letting it
|
|
85
|
+
resolve relative dates like "today at 10:00" without asking. Applied to both the
|
|
86
|
+
chat and the headless agent runner, computed per turn/run so it is never stale.
|
|
87
|
+
|
|
88
|
+
**Less-strict topic classifier.** The chat's off-topic pre-classifier was
|
|
89
|
+
refusing legitimate requests like "create a maintenance" because maintenances
|
|
90
|
+
(and several other domains) were not listed. The classifier now enumerates the
|
|
91
|
+
full domain set and treats any create/list/update/delete action on a platform
|
|
92
|
+
resource as on-topic by default.
|
|
93
|
+
|
|
94
|
+
- Updated dependencies [b50916d]
|
|
95
|
+
- @checkstack/backend-api@0.21.4
|
|
96
|
+
- @checkstack/integration-backend@0.4.4
|
|
97
|
+
|
|
3
98
|
## 0.1.3
|
|
4
99
|
|
|
5
100
|
### Patch Changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@checkstack/ai-backend",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"license": "Elastic-2.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.ts",
|
|
@@ -16,12 +16,12 @@
|
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
18
|
"@ai-sdk/openai-compatible": "^2.0.48",
|
|
19
|
-
"@checkstack/ai-common": "0.1.
|
|
20
|
-
"@checkstack/backend-api": "0.21.
|
|
21
|
-
"@checkstack/common": "0.
|
|
19
|
+
"@checkstack/ai-common": "0.1.3",
|
|
20
|
+
"@checkstack/backend-api": "0.21.5",
|
|
21
|
+
"@checkstack/common": "0.15.0",
|
|
22
22
|
"@checkstack/drizzle-helper": "0.0.5",
|
|
23
|
-
"@checkstack/integration-backend": "0.4.
|
|
24
|
-
"@checkstack/sdk": "0.
|
|
23
|
+
"@checkstack/integration-backend": "0.4.5",
|
|
24
|
+
"@checkstack/sdk": "0.100.1",
|
|
25
25
|
"@orpc/client": "^1.14.4",
|
|
26
26
|
"@orpc/contract": "^1.14.4",
|
|
27
27
|
"@orpc/server": "^1.14.4",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"zod": "^4.2.1"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
|
-
"@checkstack/scripts": "0.6.
|
|
34
|
+
"@checkstack/scripts": "0.6.1",
|
|
35
35
|
"@checkstack/tsconfig": "0.0.7",
|
|
36
36
|
"@types/node": "^20.0.0",
|
|
37
37
|
"@types/pg": "^8.20.0",
|
package/src/agent-runner.test.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { describe, expect, it, mock } from "bun:test";
|
|
2
|
+
import { asSchema } from "ai";
|
|
2
3
|
import { z } from "zod";
|
|
3
4
|
import type { AuthUser, RpcClient } from "@checkstack/backend-api";
|
|
4
5
|
import type { OpenAiCompatibleConnection } from "@checkstack/ai-common";
|
|
@@ -108,6 +109,55 @@ describe("createAgentRunner", () => {
|
|
|
108
109
|
expect(result.toolCalls).toEqual([{ tool: "plugin.read", ok: true }]);
|
|
109
110
|
});
|
|
110
111
|
|
|
112
|
+
it("hands the model a date-safe schema for tools with Date inputs (no throw)", async () => {
|
|
113
|
+
// Regression: the AI Action (headless agent runner) builds its OWN tools.
|
|
114
|
+
// A `z.date()` input would make the SDK's Zod->JSON-Schema conversion throw
|
|
115
|
+
// "Date cannot be represented...", crashing the action - the same bug as the
|
|
116
|
+
// chat. The runner must gate date inputs through dateSafeModelSchema too.
|
|
117
|
+
const registry = createAiToolRegistry();
|
|
118
|
+
registry.register({
|
|
119
|
+
name: "plugin.history",
|
|
120
|
+
description: "history",
|
|
121
|
+
effect: "read",
|
|
122
|
+
input: z.object({ since: z.date() }),
|
|
123
|
+
requiredAccessRules: [],
|
|
124
|
+
execute: async () => ({ ok: true }),
|
|
125
|
+
} as RegisteredAiTool);
|
|
126
|
+
const resolver = createAiToolResolver({ registry });
|
|
127
|
+
|
|
128
|
+
let offeredSchema: unknown;
|
|
129
|
+
const generateText = mock(
|
|
130
|
+
async (args: {
|
|
131
|
+
tools?: Record<string, { inputSchema: unknown }>;
|
|
132
|
+
}) => {
|
|
133
|
+
const t = (args.tools ?? {})["plugin.history"];
|
|
134
|
+
// Exactly what the SDK does internally to build the model request; this
|
|
135
|
+
// threw before the fix.
|
|
136
|
+
offeredSchema = await asSchema(t.inputSchema as never).jsonSchema;
|
|
137
|
+
return { text: "ok", usage: {} };
|
|
138
|
+
},
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
const runner = createAgentRunner({
|
|
142
|
+
resolver,
|
|
143
|
+
resolveConnection: async () => connection,
|
|
144
|
+
modelFns: { generateText: generateText as never },
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
await runner({
|
|
148
|
+
principal,
|
|
149
|
+
rpcClient,
|
|
150
|
+
connectionId: "conn-1",
|
|
151
|
+
prompt: "go",
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
const props = (
|
|
155
|
+
offeredSchema as { properties: Record<string, Record<string, unknown>> }
|
|
156
|
+
).properties;
|
|
157
|
+
expect(props.since?.type).toBe("string");
|
|
158
|
+
expect(props.since?.format).toBe("date-time");
|
|
159
|
+
});
|
|
160
|
+
|
|
111
161
|
it("offers a projected read tool and routes it through the principal's client", async () => {
|
|
112
162
|
const registry = createAiToolRegistry();
|
|
113
163
|
registry.register({
|
package/src/agent-runner.ts
CHANGED
|
@@ -30,6 +30,8 @@ import {
|
|
|
30
30
|
type LanguageModel,
|
|
31
31
|
} from "ai";
|
|
32
32
|
import { z } from "zod";
|
|
33
|
+
import { toModelSchema } from "./chat/model-schema";
|
|
34
|
+
import { buildDateTimeContext } from "./chat/system-prompt";
|
|
33
35
|
import {
|
|
34
36
|
createServiceRef,
|
|
35
37
|
type AuthUser,
|
|
@@ -201,7 +203,8 @@ export function createAgentRunner({
|
|
|
201
203
|
|
|
202
204
|
sdkTools[t.name] = aiTool({
|
|
203
205
|
description: t.description,
|
|
204
|
-
|
|
206
|
+
// Single model-boundary date handling, same as the chat tool path.
|
|
207
|
+
inputSchema: toModelSchema(t.input as z.ZodType),
|
|
205
208
|
execute: async (input: unknown) => {
|
|
206
209
|
try {
|
|
207
210
|
const result = await invoke(input);
|
|
@@ -237,9 +240,13 @@ export function createAgentRunner({
|
|
|
237
240
|
});
|
|
238
241
|
}
|
|
239
242
|
|
|
243
|
+
// Append the date/time context at call time (NOT module load) so the model
|
|
244
|
+
// gets the CURRENT instant and the host-zone wire contract. Headless: no
|
|
245
|
+
// operator, so the reference zone is the host/container TZ.
|
|
246
|
+
const dateContext = buildDateTimeContext({ audience: "headless" });
|
|
240
247
|
const { text } = await gen({
|
|
241
248
|
model: languageModel,
|
|
242
|
-
system: systemPrompt ?? DEFAULT_SYSTEM_PROMPT
|
|
249
|
+
system: `${systemPrompt ?? DEFAULT_SYSTEM_PROMPT} ${dateContext}`,
|
|
243
250
|
prompt,
|
|
244
251
|
tools: sdkTools,
|
|
245
252
|
stopWhen: stepCountIs(maxSteps ?? DEFAULT_MAX_STEPS),
|
|
@@ -249,7 +256,10 @@ export function createAgentRunner({
|
|
|
249
256
|
if (outputSchema) {
|
|
250
257
|
const res = await genObj({
|
|
251
258
|
model: languageModel,
|
|
252
|
-
|
|
259
|
+
// Same single model-boundary date handling as the tool path: the
|
|
260
|
+
// structured-output schema's dates must serialize AND the model's ISO
|
|
261
|
+
// strings coerce back to Date.
|
|
262
|
+
schema: toModelSchema(outputSchema),
|
|
253
263
|
system:
|
|
254
264
|
"Produce the structured result from the analysis below. Use only information present in it; do not invent values.",
|
|
255
265
|
prompt: `Task: ${prompt}\n\n--- Analysis ---\n${text}`,
|
package/src/chat/chat-handler.ts
CHANGED
|
@@ -10,6 +10,8 @@ const ChatTurnBodySchema = z.object({
|
|
|
10
10
|
connectionId: z.string(),
|
|
11
11
|
model: z.string().optional(),
|
|
12
12
|
message: z.string().min(1),
|
|
13
|
+
/** Browser IANA timezone, used to resolve bare times the operator types. */
|
|
14
|
+
timeZone: z.string().optional(),
|
|
13
15
|
});
|
|
14
16
|
|
|
15
17
|
/**
|
|
@@ -25,6 +27,8 @@ const ChatDecisionBodySchema = z.object({
|
|
|
25
27
|
token: z.string().min(1),
|
|
26
28
|
kind: z.enum(["apply", "decline"]),
|
|
27
29
|
}),
|
|
30
|
+
/** Browser IANA timezone, used to resolve bare times the operator types. */
|
|
31
|
+
timeZone: z.string().optional(),
|
|
28
32
|
});
|
|
29
33
|
|
|
30
34
|
/** A /chat POST is either a new user turn or a confirm-card decision turn. */
|
|
@@ -91,6 +95,7 @@ export function createChatRequestHandler({
|
|
|
91
95
|
forwardHeaders,
|
|
92
96
|
token: body.decision.token,
|
|
93
97
|
decision: body.decision.kind,
|
|
98
|
+
timeZone: body.timeZone,
|
|
94
99
|
});
|
|
95
100
|
}
|
|
96
101
|
return await chatService.streamTurn({
|
|
@@ -100,6 +105,7 @@ export function createChatRequestHandler({
|
|
|
100
105
|
model: body.model,
|
|
101
106
|
forwardHeaders,
|
|
102
107
|
userText: body.message,
|
|
108
|
+
timeZone: body.timeZone,
|
|
103
109
|
});
|
|
104
110
|
} catch (error) {
|
|
105
111
|
return Response.json(
|
package/src/chat/chat-service.ts
CHANGED
|
@@ -41,6 +41,7 @@ import {
|
|
|
41
41
|
type AgentToolCallbacks,
|
|
42
42
|
} from "./sdk-tools";
|
|
43
43
|
import type { ChatReadInvoker } from "./read-invoker";
|
|
44
|
+
import { buildChatSystemPrompt } from "./system-prompt";
|
|
44
45
|
import { createUserScopedRpcClient } from "../user-rpc-client";
|
|
45
46
|
|
|
46
47
|
type AiDatabase = SafeDatabase<typeof schema>;
|
|
@@ -200,6 +201,8 @@ export interface ChatTurnInput {
|
|
|
200
201
|
forwardHeaders: Record<string, string>;
|
|
201
202
|
/** The user's new message text. */
|
|
202
203
|
userText: string;
|
|
204
|
+
/** The operator's IANA timezone (browser-detected) for resolving bare times. */
|
|
205
|
+
timeZone?: string;
|
|
203
206
|
}
|
|
204
207
|
|
|
205
208
|
/**
|
|
@@ -220,25 +223,10 @@ export interface ChatDecisionInput {
|
|
|
220
223
|
token: string;
|
|
221
224
|
/** Whether the operator applied or declined the card. */
|
|
222
225
|
decision: DecisionKind;
|
|
226
|
+
/** The operator's IANA timezone (browser-detected) for resolving bare times. */
|
|
227
|
+
timeZone?: string;
|
|
223
228
|
}
|
|
224
229
|
|
|
225
|
-
const SYSTEM_PROMPT =
|
|
226
|
-
"You are Checkstack's built-in assistant. You ONLY help operators run " +
|
|
227
|
-
"Checkstack: incidents, health checks, anomalies, automations, and the " +
|
|
228
|
-
"monitoring and operations of THIS platform. Use the provided tools to read " +
|
|
229
|
-
"live data. For any change to the platform, call the appropriate tool: " +
|
|
230
|
-
"depending on the conversation's permission mode it either returns a " +
|
|
231
|
-
"confirmation card the operator must approve, or applies immediately and " +
|
|
232
|
-
"returns the applied result. Never claim a change took effect until the tool " +
|
|
233
|
-
"result confirms it (an applied result, or the operator approving the card). " +
|
|
234
|
-
"Call each change tool ONCE per request: a confirm-card result means the " +
|
|
235
|
-
"proposal succeeded and is awaiting the operator - do NOT call the tool again " +
|
|
236
|
-
"to retry; just tell the operator you are waiting for their decision. " +
|
|
237
|
-
"Politely DECLINE anything unrelated to operating Checkstack " +
|
|
238
|
-
"(general coding help, writing, or general knowledge) with a one-line " +
|
|
239
|
-
"redirect back to Checkstack monitoring and operations. Be concise and " +
|
|
240
|
-
"engineering-focused.";
|
|
241
|
-
|
|
242
230
|
/** Max agent steps (tool-call round trips) per turn. */
|
|
243
231
|
const MAX_STEPS = 8;
|
|
244
232
|
|
|
@@ -532,6 +520,7 @@ export function createChatService({
|
|
|
532
520
|
languageModel,
|
|
533
521
|
recordUsage,
|
|
534
522
|
modelMessages,
|
|
523
|
+
timeZone,
|
|
535
524
|
}: {
|
|
536
525
|
principal: AuthUser;
|
|
537
526
|
conversation: { permissionMode: AiPermissionMode };
|
|
@@ -541,6 +530,8 @@ export function createChatService({
|
|
|
541
530
|
languageModel: ReturnType<typeof buildLanguageModel>;
|
|
542
531
|
recordUsage: (usage: LanguageModelUsage) => Promise<void>;
|
|
543
532
|
modelMessages: ModelMessage[];
|
|
533
|
+
/** The operator's IANA timezone (from the browser), folded into the prompt. */
|
|
534
|
+
timeZone?: string;
|
|
544
535
|
}): Response => {
|
|
545
536
|
// Build the SDK tools from the resolver-allowed set only. The model is never
|
|
546
537
|
// offered a tool the principal cannot use. Tool callbacks (budget + audit +
|
|
@@ -568,7 +559,7 @@ export function createChatService({
|
|
|
568
559
|
|
|
569
560
|
const result = streamText({
|
|
570
561
|
model: languageModel,
|
|
571
|
-
system:
|
|
562
|
+
system: buildChatSystemPrompt({ timeZone }),
|
|
572
563
|
// Defensively normalize: drop empty-content rows and merge consecutive
|
|
573
564
|
// same-role messages so a failed prior turn (which persists no assistant
|
|
574
565
|
// reply, leaving consecutive `user` rows) cannot poison the history into a
|
|
@@ -680,6 +671,7 @@ export function createChatService({
|
|
|
680
671
|
model,
|
|
681
672
|
forwardHeaders,
|
|
682
673
|
userText,
|
|
674
|
+
timeZone,
|
|
683
675
|
} = input;
|
|
684
676
|
|
|
685
677
|
// Ownership: the conversation MUST belong to the principal.
|
|
@@ -810,6 +802,7 @@ export function createChatService({
|
|
|
810
802
|
languageModel,
|
|
811
803
|
recordUsage,
|
|
812
804
|
modelMessages,
|
|
805
|
+
timeZone,
|
|
813
806
|
});
|
|
814
807
|
},
|
|
815
808
|
|
|
@@ -831,6 +824,7 @@ export function createChatService({
|
|
|
831
824
|
forwardHeaders,
|
|
832
825
|
token,
|
|
833
826
|
decision,
|
|
827
|
+
timeZone,
|
|
834
828
|
} = input;
|
|
835
829
|
|
|
836
830
|
const conversation = await loadOwnedConversation({
|
|
@@ -915,6 +909,7 @@ export function createChatService({
|
|
|
915
909
|
languageModel,
|
|
916
910
|
recordUsage,
|
|
917
911
|
modelMessages,
|
|
912
|
+
timeZone,
|
|
918
913
|
});
|
|
919
914
|
},
|
|
920
915
|
};
|
|
@@ -48,6 +48,17 @@ describe("buildClassifierPrompt", () => {
|
|
|
48
48
|
expect(system).toMatch(/clearly unrelated|CLEARLY unrelated/i);
|
|
49
49
|
});
|
|
50
50
|
|
|
51
|
+
test("system prompt names maintenances and a CRUD-action allowance as ON_TOPIC", () => {
|
|
52
|
+
// Regression for the real bug: "Create a maintenance" was refused because
|
|
53
|
+
// maintenances were not listed and there was no generic action allowance.
|
|
54
|
+
const { system } = buildClassifierPrompt({
|
|
55
|
+
userText: "Create a maintenance",
|
|
56
|
+
});
|
|
57
|
+
expect(system.toLowerCase()).toContain("maintenance");
|
|
58
|
+
// Any create/list/update/delete request must be ON_TOPIC by default.
|
|
59
|
+
expect(system).toMatch(/create[^.]*list[^.]*update[^.]*delete/i);
|
|
60
|
+
});
|
|
61
|
+
|
|
51
62
|
test("system prompt retains the 'when in doubt' ON_TOPIC default", () => {
|
|
52
63
|
const { system } = buildClassifierPrompt({ userText: "???" });
|
|
53
64
|
expect(system).toMatch(/when in doubt.*on_topic/i);
|
|
@@ -19,18 +19,25 @@ export type ClassifierVerdict = "ON_TOPIC" | "OFF_TOPIC";
|
|
|
19
19
|
* against any decoration regardless.
|
|
20
20
|
*/
|
|
21
21
|
const CLASSIFIER_SYSTEM_PROMPT =
|
|
22
|
-
"You are a topical classifier for Checkstack, an
|
|
23
|
-
"
|
|
22
|
+
"You are a topical classifier for Checkstack, an operations platform covering " +
|
|
23
|
+
"incidents, health checks, anomalies, automations, maintenances/maintenance " +
|
|
24
|
+
"windows, dependencies, systems and services, notifications, SLOs, " +
|
|
25
|
+
"integrations, on-call, and general monitoring/operations. Decide whether the " +
|
|
24
26
|
"user's message is ON_TOPIC or OFF_TOPIC. " +
|
|
25
|
-
"ON_TOPIC includes: operating or reasoning about Checkstack
|
|
26
|
-
"
|
|
27
|
-
"
|
|
28
|
-
"
|
|
29
|
-
"greetings and conversational openers (\"hi\", \"hello\", \"hey\"); " +
|
|
27
|
+
"ON_TOPIC includes: operating or reasoning about Checkstack or any of its " +
|
|
28
|
+
"resources and configuration; meta/capability questions about the assistant " +
|
|
29
|
+
"itself (\"what can you do\", \"who are you\", \"help\", \"what features do you " +
|
|
30
|
+
"have\"); greetings and conversational openers (\"hi\", \"hello\", \"hey\"); " +
|
|
30
31
|
"how-to or conceptual questions about using Checkstack features or workflows " +
|
|
31
32
|
"(\"how do health checks work\", \"how do I create an automation\"). " +
|
|
32
|
-
"
|
|
33
|
-
"
|
|
33
|
+
"IMPORTANT: any request to create, add, list, show, view, find, update, edit, " +
|
|
34
|
+
"schedule, start, stop, resolve, acknowledge, or delete something is ON_TOPIC " +
|
|
35
|
+
"by default - it is almost certainly an action on a platform resource (e.g. " +
|
|
36
|
+
"\"create a maintenance\", \"list incidents\", \"schedule downtime\"), EVEN IF " +
|
|
37
|
+
"the resource type is not named in the list above. " +
|
|
38
|
+
"OFF_TOPIC means ONLY requests that are CLEARLY unrelated to operating this " +
|
|
39
|
+
"platform: general-purpose coding help, creative writing, math homework, and " +
|
|
40
|
+
"general trivia or knowledge questions. " +
|
|
34
41
|
"When in doubt, reply ON_TOPIC. Reply with the token only.";
|
|
35
42
|
|
|
36
43
|
/**
|