@agent-native/core 0.228.0-nightly-20261111212500 → 0.228.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/dist/agent/engine/builder-engine.js +5 -4
- package/dist/agent/engine/error-detail.d.ts +1 -0
- package/dist/agent/engine/error-detail.js +5 -0
- package/dist/agent/production-agent.js +2 -2
- package/dist/app-config/onboarding.d.ts +1 -0
- package/dist/app-config/onboarding.js +1 -0
- package/dist/app-config/schema.d.ts +1 -0
- package/dist/cli/index.d.ts +7 -1
- package/dist/cli/index.js +19 -3
- package/dist/client/chat/agentkit-agent-native.js +145 -0
- package/dist/client/chat/agentkit-protocol.js +8 -2
- package/dist/client/error-format.js +5 -1
- package/dist/coding-tools/run-code-result.d.ts +11 -0
- package/dist/coding-tools/run-code-result.js +39 -0
- package/dist/coding-tools/run-code.js +6 -6
- package/dist/coding-tools/sandbox/local-child-process-adapter.js +20 -0
- package/dist/collab/routes.d.ts +1 -1
- package/dist/decisions/actions/decide.d.ts +1 -0
- package/dist/decisions/actions/decide.js +1 -0
- package/dist/decisions/decide.js +2 -1
- package/dist/decisions/types.d.ts +1 -0
- package/dist/extensions/routes.js +1 -1
- package/dist/framework-package-versions.json +11 -11
- package/dist/integrations/slack-input-request.d.ts +2 -0
- package/dist/integrations/slack-input-request.js +87 -0
- package/dist/integrations/webhook-handler.js +10 -58
- package/dist/provider-api/actions/custom-provider-registration.d.ts +12 -12
- package/dist/provider-api/actions/provider-api.d.ts +12 -12
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/scripts/db/safety.js +1 -0
- package/dist/server/agent-chat/context-tools.js +59 -23
- package/dist/server/builder-signup-experiment.js +4 -1
- package/dist/shared/ssr-session-bootstrap.js +5 -1
- package/dist/templates/chat/app/components/chat/ChatRouteContent.tsx +10 -3
- package/dist/templates/chat/app/global.css +0 -5
- package/dist/vite/client.js +17 -0
- package/docs/content/dispatch.mdx +1 -1
- package/docs/content/multi-app-workspace.mdx +1 -1
- package/docs/content/security.mdx +1 -1
- package/docs/content/template-dispatch-reference.mdx +1 -1
- package/docs/content/workspace-management.mdx +0 -1
- package/package.json +4 -4
- package/src/templates/chat/app/components/chat/ChatRouteContent.tsx +10 -3
- package/src/templates/chat/app/global.css +0 -5
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/** One `ask-question` call's input as a Slack message, or null if Slack cannot show it. */
|
|
2
|
+
export function slackInputRequestText(input) {
|
|
3
|
+
let items = [input];
|
|
4
|
+
// Absent, null or empty `questions` means the single-question form, as in the tool.
|
|
5
|
+
if (input?.questions !== undefined &&
|
|
6
|
+
input.questions !== null &&
|
|
7
|
+
input.questions !== "") {
|
|
8
|
+
try {
|
|
9
|
+
const parsed = Array.isArray(input.questions)
|
|
10
|
+
? input.questions
|
|
11
|
+
: JSON.parse(stringifyInboundValue(input.questions));
|
|
12
|
+
if (!Array.isArray(parsed))
|
|
13
|
+
return null;
|
|
14
|
+
items = parsed.slice(0, 4);
|
|
15
|
+
// coercion-ok: questions Slack cannot read mean no input request; the run's own reply is sent instead.
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
const parsedBlocks = items.map((item) => slackQuestionBlock(item));
|
|
22
|
+
// A question Slack cannot show would leave the others unanswerable.
|
|
23
|
+
if (!parsedBlocks.length || parsedBlocks.includes(null))
|
|
24
|
+
return null;
|
|
25
|
+
const blocks = parsedBlocks.filter((block) => block !== null);
|
|
26
|
+
const allowFreeText = blocks.some((block) => block.allowFreeText);
|
|
27
|
+
const severalChoices = blocks.length > 1 || blocks.some((block) => block.allowMultiple);
|
|
28
|
+
return [
|
|
29
|
+
...blocks.flatMap((block, blockIndex) => [
|
|
30
|
+
...(blockIndex > 0 ? [""] : []),
|
|
31
|
+
...block.lines,
|
|
32
|
+
]),
|
|
33
|
+
"",
|
|
34
|
+
`Reply in this thread with your choice${severalChoices ? "s" : ""}${allowFreeText ? " or a short answer" : ""}.`,
|
|
35
|
+
].join("\n");
|
|
36
|
+
}
|
|
37
|
+
function stringifyInboundValue(value) {
|
|
38
|
+
return typeof value === "string" ? value : JSON.stringify(value);
|
|
39
|
+
}
|
|
40
|
+
function slackQuestionBlock(input) {
|
|
41
|
+
const question = typeof input?.question === "string" ? input.question.trim() : "";
|
|
42
|
+
if (!question)
|
|
43
|
+
return null;
|
|
44
|
+
let rawOptions;
|
|
45
|
+
try {
|
|
46
|
+
rawOptions = Array.isArray(input?.options)
|
|
47
|
+
? input.options
|
|
48
|
+
: JSON.parse(stringifyInboundValue(input?.options ?? "[]"));
|
|
49
|
+
// coercion-ok: options Slack cannot read mean no input request; the run's own reply is sent instead.
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
if (!Array.isArray(rawOptions) || rawOptions.length === 0)
|
|
55
|
+
return null;
|
|
56
|
+
const options = rawOptions
|
|
57
|
+
.slice(0, 4)
|
|
58
|
+
.map((option) => {
|
|
59
|
+
const value = option;
|
|
60
|
+
const label = typeof value?.label === "string"
|
|
61
|
+
? value.label.trim()
|
|
62
|
+
: typeof value?.value === "string"
|
|
63
|
+
? value.value.trim()
|
|
64
|
+
: "";
|
|
65
|
+
if (!label)
|
|
66
|
+
return null;
|
|
67
|
+
const description = typeof value?.description === "string" ? value.description.trim() : "";
|
|
68
|
+
return {
|
|
69
|
+
label: label.slice(0, 200),
|
|
70
|
+
description: description.slice(0, 400),
|
|
71
|
+
};
|
|
72
|
+
})
|
|
73
|
+
.filter((option) => option !== null);
|
|
74
|
+
if (!options.length)
|
|
75
|
+
return null;
|
|
76
|
+
const header = typeof input?.header === "string" ? input.header.trim().slice(0, 80) : "";
|
|
77
|
+
return {
|
|
78
|
+
allowFreeText: stringifyInboundValue(input?.allowFreeText ?? "true") !== "false",
|
|
79
|
+
allowMultiple: stringifyInboundValue(input?.allowMultiple ?? "false") === "true",
|
|
80
|
+
lines: [
|
|
81
|
+
...(header ? [`*${header}*`] : []),
|
|
82
|
+
question.slice(0, 1_500),
|
|
83
|
+
"",
|
|
84
|
+
...options.map((option, optionIndex) => `${optionIndex + 1}. ${option.label}${option.description ? ` — ${option.description}` : ""}`),
|
|
85
|
+
],
|
|
86
|
+
};
|
|
87
|
+
}
|
|
@@ -31,6 +31,7 @@ import { loadIntegrationMemoryPrompt } from "./integration-memory.js";
|
|
|
31
31
|
import { insertPendingTask, isDuplicateEventError, stageTaskDeliveryPayload, } from "./pending-tasks-store.js";
|
|
32
32
|
import { integrationRequestContext, resolveIntegrationRequestScope, } from "./request-scope.js";
|
|
33
33
|
import { integrationScopeSubjectKey } from "./scope-store.js";
|
|
34
|
+
import { slackInputRequestText } from "./slack-input-request.js";
|
|
34
35
|
import { getThreadMapping, saveThreadMapping } from "./thread-mapping-store.js";
|
|
35
36
|
import { listIntegrationUsageBudgets, releaseIntegrationUsageBudget, reserveIntegrationUsageBudget, settleIntegrationUsageBudget, } from "./usage-budget-store.js";
|
|
36
37
|
const PROCESSOR_DISPATCH_SETTLE_WAIT_MS = 1_500;
|
|
@@ -1457,64 +1458,15 @@ function extractSlackInputRequest(completedRun) {
|
|
|
1457
1458
|
if (!delivered)
|
|
1458
1459
|
return null;
|
|
1459
1460
|
const deliveredId = delivered.type === "tool_done" ? delivered.id : "";
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
if (!question)
|
|
1470
|
-
return null;
|
|
1471
|
-
let rawOptions;
|
|
1472
|
-
try {
|
|
1473
|
-
rawOptions = JSON.parse(stringifyInboundValue(input?.options ?? "[]"));
|
|
1474
|
-
}
|
|
1475
|
-
catch {
|
|
1476
|
-
return null;
|
|
1477
|
-
}
|
|
1478
|
-
if (!Array.isArray(rawOptions) || rawOptions.length === 0)
|
|
1479
|
-
return null;
|
|
1480
|
-
const options = rawOptions
|
|
1481
|
-
.slice(0, 4)
|
|
1482
|
-
.map((option) => {
|
|
1483
|
-
const value = option;
|
|
1484
|
-
const label = typeof value?.label === "string"
|
|
1485
|
-
? value.label.trim()
|
|
1486
|
-
: typeof value?.value === "string"
|
|
1487
|
-
? value.value.trim()
|
|
1488
|
-
: "";
|
|
1489
|
-
if (!label)
|
|
1490
|
-
return null;
|
|
1491
|
-
const description = typeof value?.description === "string"
|
|
1492
|
-
? value.description.trim()
|
|
1493
|
-
: "";
|
|
1494
|
-
return {
|
|
1495
|
-
label: label.slice(0, 200),
|
|
1496
|
-
description: description.slice(0, 400),
|
|
1497
|
-
};
|
|
1498
|
-
})
|
|
1499
|
-
.filter((option) => option !== null);
|
|
1500
|
-
if (!options.length)
|
|
1501
|
-
return null;
|
|
1502
|
-
const header = typeof input?.header === "string" ? input.header.trim().slice(0, 80) : "";
|
|
1503
|
-
const allowFreeText = stringifyInboundValue(input?.allowFreeText ?? "true") !== "false";
|
|
1504
|
-
return {
|
|
1505
|
-
text: [
|
|
1506
|
-
header ? `*${header}*` : null,
|
|
1507
|
-
question.slice(0, 1_500),
|
|
1508
|
-
"",
|
|
1509
|
-
...options.map((option, optionIndex) => `${optionIndex + 1}. ${option.label}${option.description ? ` — ${option.description}` : ""}`),
|
|
1510
|
-
"",
|
|
1511
|
-
`Reply in this thread with your choice${allowFreeText ? " or a short answer" : ""}.`,
|
|
1512
|
-
]
|
|
1513
|
-
.filter((line) => line !== null)
|
|
1514
|
-
.join("\n"),
|
|
1515
|
-
};
|
|
1516
|
-
}
|
|
1517
|
-
return null;
|
|
1461
|
+
const started = [...events]
|
|
1462
|
+
.reverse()
|
|
1463
|
+
.find((event) => event.type === "tool_start" &&
|
|
1464
|
+
event.tool === "ask-question" &&
|
|
1465
|
+
event.id === deliveredId);
|
|
1466
|
+
if (started?.type !== "tool_start")
|
|
1467
|
+
return null;
|
|
1468
|
+
const text = slackInputRequestText(started.input);
|
|
1469
|
+
return text === null ? null : { text };
|
|
1518
1470
|
}
|
|
1519
1471
|
function extractRecoverableA2AArtifactToolResult(completedRun) {
|
|
1520
1472
|
for (let i = completedRun.events.length - 1; i >= 0; i--) {
|
|
@@ -75,9 +75,7 @@ export declare function createCustomProviderRegistrationAction<TSchema extends Z
|
|
|
75
75
|
user: "user";
|
|
76
76
|
}>>;
|
|
77
77
|
}, z.core.$strip>>, {
|
|
78
|
-
deleted?: undefined;
|
|
79
78
|
id?: undefined;
|
|
80
|
-
provider?: undefined;
|
|
81
79
|
message?: undefined;
|
|
82
80
|
found?: undefined;
|
|
83
81
|
providers: {
|
|
@@ -90,44 +88,46 @@ export declare function createCustomProviderRegistrationAction<TSchema extends Z
|
|
|
90
88
|
updatedAt: number;
|
|
91
89
|
}[];
|
|
92
90
|
count: number;
|
|
91
|
+
provider?: undefined;
|
|
92
|
+
deleted?: undefined;
|
|
93
93
|
registered?: undefined;
|
|
94
94
|
label?: undefined;
|
|
95
95
|
} | {
|
|
96
|
-
deleted?: undefined;
|
|
97
96
|
id?: undefined;
|
|
97
|
+
count?: undefined;
|
|
98
98
|
message?: undefined;
|
|
99
99
|
providers?: undefined;
|
|
100
|
-
count?: undefined;
|
|
101
100
|
found: boolean;
|
|
102
101
|
provider: import("../custom-registry.js").CustomProviderConfig;
|
|
102
|
+
deleted?: undefined;
|
|
103
103
|
registered?: undefined;
|
|
104
104
|
label?: undefined;
|
|
105
105
|
} | {
|
|
106
|
-
|
|
107
|
-
provider?: undefined;
|
|
106
|
+
count?: undefined;
|
|
108
107
|
message?: undefined;
|
|
109
108
|
providers?: undefined;
|
|
110
|
-
|
|
109
|
+
provider?: undefined;
|
|
111
110
|
found: boolean;
|
|
112
111
|
id: string;
|
|
112
|
+
deleted?: undefined;
|
|
113
113
|
registered?: undefined;
|
|
114
114
|
label?: undefined;
|
|
115
115
|
} | {
|
|
116
|
-
|
|
116
|
+
count?: undefined;
|
|
117
117
|
message?: undefined;
|
|
118
118
|
found?: undefined;
|
|
119
119
|
providers?: undefined;
|
|
120
|
-
|
|
120
|
+
provider?: undefined;
|
|
121
121
|
deleted: boolean;
|
|
122
122
|
id: string;
|
|
123
123
|
registered?: undefined;
|
|
124
124
|
label?: undefined;
|
|
125
125
|
} | {
|
|
126
|
-
|
|
127
|
-
provider?: undefined;
|
|
126
|
+
count?: undefined;
|
|
128
127
|
found?: undefined;
|
|
129
128
|
providers?: undefined;
|
|
130
|
-
|
|
129
|
+
provider?: undefined;
|
|
130
|
+
deleted?: undefined;
|
|
131
131
|
registered: boolean;
|
|
132
132
|
id: string;
|
|
133
133
|
label: string;
|
|
@@ -316,7 +316,9 @@ export declare function createProviderApiActions(runtime: Pick<ProviderApiRuntim
|
|
|
316
316
|
notes?: string | undefined;
|
|
317
317
|
scope?: "org" | "user" | undefined;
|
|
318
318
|
}, {
|
|
319
|
+
deleted?: undefined;
|
|
319
320
|
id?: undefined;
|
|
321
|
+
provider?: undefined;
|
|
320
322
|
found?: undefined;
|
|
321
323
|
message?: undefined;
|
|
322
324
|
providers: {
|
|
@@ -329,46 +331,44 @@ export declare function createProviderApiActions(runtime: Pick<ProviderApiRuntim
|
|
|
329
331
|
updatedAt: number;
|
|
330
332
|
}[];
|
|
331
333
|
count: number;
|
|
332
|
-
provider?: undefined;
|
|
333
|
-
deleted?: undefined;
|
|
334
334
|
registered?: undefined;
|
|
335
335
|
label?: undefined;
|
|
336
336
|
} | {
|
|
337
|
+
deleted?: undefined;
|
|
337
338
|
id?: undefined;
|
|
338
|
-
count?: undefined;
|
|
339
339
|
message?: undefined;
|
|
340
340
|
providers?: undefined;
|
|
341
|
+
count?: undefined;
|
|
341
342
|
found: boolean;
|
|
342
343
|
provider: import("../custom-registry.js").CustomProviderConfig;
|
|
343
|
-
deleted?: undefined;
|
|
344
344
|
registered?: undefined;
|
|
345
345
|
label?: undefined;
|
|
346
346
|
} | {
|
|
347
|
-
|
|
347
|
+
deleted?: undefined;
|
|
348
|
+
provider?: undefined;
|
|
348
349
|
message?: undefined;
|
|
349
350
|
providers?: undefined;
|
|
350
|
-
|
|
351
|
+
count?: undefined;
|
|
351
352
|
found: boolean;
|
|
352
353
|
id: string;
|
|
353
|
-
deleted?: undefined;
|
|
354
354
|
registered?: undefined;
|
|
355
355
|
label?: undefined;
|
|
356
356
|
} | {
|
|
357
|
-
|
|
357
|
+
provider?: undefined;
|
|
358
358
|
found?: undefined;
|
|
359
359
|
message?: undefined;
|
|
360
360
|
providers?: undefined;
|
|
361
|
-
|
|
361
|
+
count?: undefined;
|
|
362
362
|
deleted: boolean;
|
|
363
363
|
id: string;
|
|
364
364
|
registered?: undefined;
|
|
365
365
|
label?: undefined;
|
|
366
366
|
} | {
|
|
367
|
-
|
|
367
|
+
deleted?: undefined;
|
|
368
|
+
provider?: undefined;
|
|
368
369
|
found?: undefined;
|
|
369
370
|
providers?: undefined;
|
|
370
|
-
|
|
371
|
-
deleted?: undefined;
|
|
371
|
+
count?: undefined;
|
|
372
372
|
registered: boolean;
|
|
373
373
|
id: string;
|
|
374
374
|
label: string;
|
|
@@ -49,6 +49,7 @@ export declare function handleUploadResource(event: any): Promise<import("./stor
|
|
|
49
49
|
error: string;
|
|
50
50
|
storageSetupRequired?: undefined;
|
|
51
51
|
} | {
|
|
52
|
+
error?: undefined;
|
|
52
53
|
id: string;
|
|
53
54
|
path: string;
|
|
54
55
|
owner: string;
|
|
@@ -63,7 +64,6 @@ export declare function handleUploadResource(event: any): Promise<import("./stor
|
|
|
63
64
|
runId: string | null;
|
|
64
65
|
expiresAt: number | null;
|
|
65
66
|
metadata: string | null;
|
|
66
|
-
error?: undefined;
|
|
67
67
|
url: string;
|
|
68
68
|
provider: string;
|
|
69
69
|
storageSetupRequired?: undefined;
|
|
@@ -469,62 +469,98 @@ function parseAskQuestion(args) {
|
|
|
469
469
|
}
|
|
470
470
|
return { question, header, allowMultiple, allowFreeText, options };
|
|
471
471
|
}
|
|
472
|
+
const MAX_ASK_QUESTIONS = 4;
|
|
473
|
+
function parseAskQuestions(args) {
|
|
474
|
+
const raw = typeof args === "object" && args !== null
|
|
475
|
+
? args.questions
|
|
476
|
+
: undefined;
|
|
477
|
+
if (raw === undefined || raw === null || raw === "") {
|
|
478
|
+
return { questions: [parseAskQuestion(args)] };
|
|
479
|
+
}
|
|
480
|
+
let parsed = raw;
|
|
481
|
+
if (!Array.isArray(raw)) {
|
|
482
|
+
try {
|
|
483
|
+
parsed = JSON.parse(String(raw));
|
|
484
|
+
}
|
|
485
|
+
catch {
|
|
486
|
+
throw new ActionInputValidationError("'questions' must be a JSON array of { question, header?, options, allowMultiple?, allowFreeText? }.");
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
if (!Array.isArray(parsed) ||
|
|
490
|
+
parsed.length === 0 ||
|
|
491
|
+
parsed.length > MAX_ASK_QUESTIONS) {
|
|
492
|
+
throw new ActionInputValidationError(`'questions' must be a JSON array of 1 to ${MAX_ASK_QUESTIONS} questions.`);
|
|
493
|
+
}
|
|
494
|
+
return {
|
|
495
|
+
questions: parsed.map((item) => {
|
|
496
|
+
const fields = (item ?? {});
|
|
497
|
+
return parseAskQuestion({
|
|
498
|
+
...fields,
|
|
499
|
+
options: Array.isArray(fields.options)
|
|
500
|
+
? JSON.stringify(fields.options)
|
|
501
|
+
: fields.options,
|
|
502
|
+
});
|
|
503
|
+
}),
|
|
504
|
+
};
|
|
505
|
+
}
|
|
472
506
|
function createAskQuestionTool() {
|
|
473
507
|
const entry = {
|
|
474
508
|
endsTurn: true,
|
|
475
509
|
tool: {
|
|
476
|
-
description: "Ask
|
|
510
|
+
description: "Ask 1-4 multiple-choice clarifying questions in a card above the message box. Use ONLY when blocked on a costly decision you cannot resolve from context (an ambiguous metric, date range or grain; a real fork in approach), never for confirmations, things already specified, or easy work. One question: `question` and `options`; several: `questions`, answered together in one message. Mark the likely option recommended. Ends the turn: other tool calls in the same response will not run.",
|
|
477
511
|
parameters: {
|
|
478
512
|
type: "object",
|
|
479
513
|
properties: {
|
|
480
514
|
question: {
|
|
481
515
|
type: "string",
|
|
482
|
-
description: "The
|
|
516
|
+
description: "The question, with the context needed to choose, ending in a question mark. Omit it with `questions`.",
|
|
517
|
+
},
|
|
518
|
+
questions: {
|
|
519
|
+
type: "string",
|
|
520
|
+
description: 'Instead of `question` and `options`: a JSON array of 1-4 `{ "question", "header"?, "options", "allowMultiple"?, "allowFreeText"? }`, each field as below.',
|
|
483
521
|
},
|
|
484
522
|
header: {
|
|
485
523
|
type: "string",
|
|
486
|
-
description: 'Optional
|
|
524
|
+
description: 'Optional short chip label (about 12 characters), e.g. "Date range".',
|
|
487
525
|
},
|
|
488
526
|
options: {
|
|
489
527
|
type: "string",
|
|
490
|
-
description: 'A JSON array of 2-4 distinct
|
|
528
|
+
description: 'A JSON array of 2-4 distinct options (exclusive unless `allowMultiple`), each `{ "label", "value"?, "recommended"? }`. The user sees only the label, on one line, so it says what you would do, specific to this case, in 3-10 words ("Checker fix only", not "Option A"); context every option shares goes in the question. `value` defaults to `label`. Add no "Other" option: `allowFreeText` provides one.',
|
|
491
529
|
},
|
|
492
530
|
allowFreeText: {
|
|
493
531
|
type: "string",
|
|
494
|
-
description: '
|
|
532
|
+
description: 'Allow a typed "Other" answer. Keep "true" (the default) unless the workflow accepts only the listed values.',
|
|
495
533
|
enum: ["true", "false"],
|
|
496
534
|
},
|
|
497
535
|
allowMultiple: {
|
|
498
536
|
type: "string",
|
|
499
|
-
description: '
|
|
537
|
+
description: 'Allow more than one option. "true" or "false" (the default).',
|
|
500
538
|
enum: ["true", "false"],
|
|
501
539
|
},
|
|
502
540
|
},
|
|
503
|
-
required: [
|
|
541
|
+
required: [],
|
|
504
542
|
},
|
|
505
543
|
},
|
|
506
544
|
run: async (args) => {
|
|
507
|
-
const {
|
|
545
|
+
const { questions } = args;
|
|
508
546
|
const askingRunCtx = getRequestRunContext();
|
|
509
547
|
const askingThreadId = askingRunCtx?.threadId && askingRunCtx.threadId !== askingRunCtx.runId
|
|
510
548
|
? askingRunCtx.threadId
|
|
511
549
|
: undefined;
|
|
512
550
|
const payload = {
|
|
513
551
|
...(askingThreadId ? { threadId: askingThreadId } : {}),
|
|
514
|
-
questions:
|
|
515
|
-
{
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
},
|
|
527
|
-
],
|
|
552
|
+
questions: questions.map(({ question, header, allowMultiple, allowFreeText, options }, index) => ({
|
|
553
|
+
id: `q${index + 1}`,
|
|
554
|
+
type: "text-options",
|
|
555
|
+
question,
|
|
556
|
+
...(header ? { header } : {}),
|
|
557
|
+
required: !allowFreeText,
|
|
558
|
+
multiSelect: allowMultiple,
|
|
559
|
+
allowOther: allowFreeText,
|
|
560
|
+
includeExplore: false,
|
|
561
|
+
includeDecide: false,
|
|
562
|
+
options,
|
|
563
|
+
})),
|
|
528
564
|
};
|
|
529
565
|
const { writeAppState } = await import("../../application-state/script-helpers.js");
|
|
530
566
|
await writeAppState(appStateKeyForBrowserTab("guided-questions", getRequestRunContext()?.browserTabId), payload);
|
|
@@ -533,7 +569,7 @@ function createAskQuestionTool() {
|
|
|
533
569
|
};
|
|
534
570
|
const action = defineAction({
|
|
535
571
|
description: entry.tool.description,
|
|
536
|
-
schema: z.unknown().transform(
|
|
572
|
+
schema: z.unknown().transform(parseAskQuestions),
|
|
537
573
|
endsTurn: true,
|
|
538
574
|
run: entry.run,
|
|
539
575
|
});
|
|
@@ -330,9 +330,12 @@ export function createBuilderSignupExperiment(dependencies) {
|
|
|
330
330
|
if (session)
|
|
331
331
|
return { status: "disabled" };
|
|
332
332
|
if (!assignment) {
|
|
333
|
+
const { builderSignupAutomaticPercent } = getAppConfig().onboarding;
|
|
333
334
|
assignment = {
|
|
334
335
|
id: randomBytes(16).toString("hex"),
|
|
335
|
-
variant: randomInt(
|
|
336
|
+
variant: randomInt(100) < builderSignupAutomaticPercent
|
|
337
|
+
? "automatic"
|
|
338
|
+
: "control",
|
|
336
339
|
issuedAt: Date.now(),
|
|
337
340
|
policy: BUILDER_SIGNUP_POLICY,
|
|
338
341
|
};
|
|
@@ -68,8 +68,12 @@ export function isSessionNavigationPending() {
|
|
|
68
68
|
typeof window[SESSION_NAVIGATION_FLAG] === "string");
|
|
69
69
|
}
|
|
70
70
|
function releaseSessionNavigation(href) {
|
|
71
|
-
|
|
71
|
+
// The stall timer can outlive window: a test environment tears it down
|
|
72
|
+
// while the timer is still pending.
|
|
73
|
+
if (typeof window === "undefined" ||
|
|
74
|
+
window[SESSION_NAVIGATION_FLAG] !== href) {
|
|
72
75
|
return;
|
|
76
|
+
}
|
|
73
77
|
delete window[SESSION_NAVIGATION_FLAG];
|
|
74
78
|
window.dispatchEvent?.(new Event(SESSION_NAVIGATION_RELEASED_EVENT));
|
|
75
79
|
}
|
|
@@ -39,8 +39,9 @@ import {
|
|
|
39
39
|
} from "@agent-native/toolkit/app/chat/agentkit-chat/connections";
|
|
40
40
|
import { CoreAgentKitRoot } from "@agent-native/toolkit/app/chat/agentkit-chat/index";
|
|
41
41
|
import {
|
|
42
|
-
|
|
42
|
+
GuidedQuestionCard,
|
|
43
43
|
useGuidedQuestionFlow,
|
|
44
|
+
useRefetchGuidedQuestionsWhenAsked,
|
|
44
45
|
} from "@agent-native/toolkit/app/chat/agentkit-chat/questions";
|
|
45
46
|
import { isClientDraftThread } from "@agent-native/toolkit/app/chat/agentkit-chat/rail";
|
|
46
47
|
import {
|
|
@@ -899,6 +900,8 @@ function ChatInitialMessage({
|
|
|
899
900
|
control.sendMessage({
|
|
900
901
|
text: requestText,
|
|
901
902
|
attachments: [...(composerOptions?.uploadedAttachments ?? [])],
|
|
903
|
+
// The run reads mentions from here; metadata only records them.
|
|
904
|
+
...(references.length ? { references: [...references] } : {}),
|
|
902
905
|
options: runOptions,
|
|
903
906
|
metadata,
|
|
904
907
|
queueWhileRunning: metadata.custom.agentNativeQueueWhileRunning,
|
|
@@ -1048,6 +1051,7 @@ function ChatAgentFooter({ children }: { children: ReactNode }) {
|
|
|
1048
1051
|
retryProviderStatus,
|
|
1049
1052
|
handleSubmit,
|
|
1050
1053
|
handleSkip,
|
|
1054
|
+
refetchPendingQuestion,
|
|
1051
1055
|
} = useGuidedQuestionFlow({
|
|
1052
1056
|
stateKey: "guided-questions",
|
|
1053
1057
|
queryKey: ["guided-questions", "agentkit"],
|
|
@@ -1058,12 +1062,16 @@ function ChatAgentFooter({ children }: { children: ReactNode }) {
|
|
|
1058
1062
|
onSubmitMessage: submitAnswers,
|
|
1059
1063
|
onSkipMessage: skipQuestions,
|
|
1060
1064
|
});
|
|
1065
|
+
useRefetchGuidedQuestionsWhenAsked({
|
|
1066
|
+
tools: thread.tools,
|
|
1067
|
+
refetch: refetchPendingQuestion,
|
|
1068
|
+
});
|
|
1061
1069
|
|
|
1062
1070
|
return (
|
|
1063
1071
|
<div className="agent-kit-chat-footer-stack">
|
|
1064
1072
|
{questions?.length ? (
|
|
1065
1073
|
<div className="agent-kit-chat-guided-question">
|
|
1066
|
-
<
|
|
1074
|
+
<GuidedQuestionCard
|
|
1067
1075
|
questions={questions}
|
|
1068
1076
|
onSubmit={handleSubmit}
|
|
1069
1077
|
onSkip={handleSkip}
|
|
@@ -1074,7 +1082,6 @@ function ChatAgentFooter({ children }: { children: ReactNode }) {
|
|
|
1074
1082
|
isSubmissionBlocked={isSubmissionBlocked}
|
|
1075
1083
|
providerStatus={providerStatus}
|
|
1076
1084
|
onRetryProviderStatus={retryProviderStatus}
|
|
1077
|
-
className="h-auto items-stretch justify-stretch bg-transparent"
|
|
1078
1085
|
/>
|
|
1079
1086
|
</div>
|
|
1080
1087
|
) : null}
|
|
@@ -83,11 +83,6 @@
|
|
|
83
83
|
.agent-kit-chat-guided-question {
|
|
84
84
|
max-block-size: min(32rem, 58dvh);
|
|
85
85
|
overflow: auto;
|
|
86
|
-
margin-block-end: 0.5rem;
|
|
87
|
-
border: 1px solid var(--agentkit-border);
|
|
88
|
-
border-radius: var(--agentkit-radius);
|
|
89
|
-
background: var(--agentkit-surface);
|
|
90
|
-
box-shadow: var(--agentkit-elevation);
|
|
91
86
|
}
|
|
92
87
|
|
|
93
88
|
.chat-sidebar-history .an-chat-history__list {
|
package/dist/vite/client.js
CHANGED
|
@@ -946,6 +946,22 @@ const disableDepSourcemapsPlugin = {
|
|
|
946
946
|
name: "agent-native:no-dep-prebundle-sourcemaps",
|
|
947
947
|
outputOptions: (options) => ({ ...options, sourcemap: false }),
|
|
948
948
|
};
|
|
949
|
+
const disableServedDepSourcemapsPlugin = {
|
|
950
|
+
name: "agent-native:no-served-dep-sourcemaps",
|
|
951
|
+
apply: "serve",
|
|
952
|
+
enforce: "post",
|
|
953
|
+
transform(code, id) {
|
|
954
|
+
if (process.env.AGENT_NATIVE_DEP_SOURCEMAPS === "1" ||
|
|
955
|
+
this.environment.name !== "client" ||
|
|
956
|
+
this.environment.mode !== "dev" ||
|
|
957
|
+
!this.environment.depsOptimizer?.isOptimizedDepFile(id)) {
|
|
958
|
+
return;
|
|
959
|
+
}
|
|
960
|
+
// Vite injects an identity map when no map is present. It only takes the
|
|
961
|
+
// transformed map when the code changes, so add a trailing newline.
|
|
962
|
+
return { code: `${code}\n`, map: { mappings: "" } };
|
|
963
|
+
},
|
|
964
|
+
};
|
|
949
965
|
function getDefaultOptimizeDeps(cwd) {
|
|
950
966
|
const inMonorepo = findCoreSrcDir(cwd) !== null;
|
|
951
967
|
const entries = [
|
|
@@ -3729,6 +3745,7 @@ function createAgentNativePlugins(options, { command, includeReactTransform, use
|
|
|
3729
3745
|
clientOptionalPeerStubPlugin(cwd),
|
|
3730
3746
|
...userPlugins,
|
|
3731
3747
|
externalStoreShimPlugin(),
|
|
3748
|
+
disableServedDepSourcemapsPlugin,
|
|
3732
3749
|
appChangelogRawPlugin(),
|
|
3733
3750
|
actionTypesPlugin(),
|
|
3734
3751
|
agentsBundlePlugin({ agentNativeConfig: options.agentNativeConfig }),
|
|
@@ -109,7 +109,7 @@ Slack, email, Telegram, and WhatsApp all flow into Dispatch's agent loop. Connec
|
|
|
109
109
|
|
|
110
110
|
### Secret vault
|
|
111
111
|
|
|
112
|
-
Store credentials once in Dispatch's vault. By default, vault access is **all apps**: every saved key is available to every
|
|
112
|
+
Store credentials once in Dispatch's vault. The value is encrypted into the shared credential store; the vault itself keeps only the key name, label, and grants, never a plaintext copy. By default, vault access is **all apps**: every saved key is available to every app that shares Dispatch's database. Workspaces that need stricter separation can switch the vault to **manual** mode, where explicit per-app grants are required. Non-admins can **request** a secret for an app; admins **approve**, which creates the secret and, in manual workflows, the grant. Every read, grant, and rotation is captured in an audit log. This is what makes "rotate the OpenAI key" a one-click operation across ten apps instead of ten PRs.
|
|
113
113
|
|
|
114
114
|
### Cross-app delegation
|
|
115
115
|
|
|
@@ -295,7 +295,7 @@ See [MCP Clients](/docs/mcp-clients) for the config schema, precedence rules, an
|
|
|
295
295
|
|
|
296
296
|
The workspace root `.env` is loaded into every app automatically for deployment-level configuration such as `A2A_SECRET`, `BETTER_AUTH_SECRET`, and `DATABASE_URL`. User, org, and workspace API keys should be saved as scoped DB secrets instead of being written into `.env`. Per-app deploy-time overrides can still go in `apps/<name>/.env` and win on conflict.
|
|
297
297
|
|
|
298
|
-
For runtime app credentials, prefer the Dispatch vault over hand-editing `.env` files. The vault defaults to all-apps access, so every saved vault key is available to every workspace app
|
|
298
|
+
For runtime app credentials, prefer the Dispatch vault over hand-editing `.env` files. The vault defaults to all-apps access, so every saved vault key is available to every workspace app that shares its database. Switch the vault to manual mode only when apps need explicit per-key grants.
|
|
299
299
|
|
|
300
300
|
```text
|
|
301
301
|
my-company-platform/
|
|
@@ -322,7 +322,7 @@ pnpm action db-check-scoping --require-org # Also require org_id
|
|
|
322
322
|
| OAuth tokens (Google, GitHub) | `oauth_tokens` store via `saveOAuthTokens()` |
|
|
323
323
|
| Session tokens | Automatic (Better Auth handles this) |
|
|
324
324
|
|
|
325
|
-
Per-user/per-org credentials and the vault are encrypted at rest with AES-256-GCM. For a per-user/per-org credential, the key precedence is `<APP_NAME>_SECRETS_ENCRYPTION_KEY` (app-scoped — useful in a multi-app workspace where apps shouldn't share one key) → `SECRETS_ENCRYPTION_KEY` → `BETTER_AUTH_SECRET`; production refuses to start without one of these set. The shared workspace vault (`app_secrets`, meant to decrypt the same in sibling apps) prefers `WORKSPACE_SECRETS_ENCRYPTION_KEY`, then the legacy combined `SECRETS_ENCRYPTION_KEY`, then material derived from `A2A_SECRET`, with `BETTER_AUTH_SECRET` and the app-scoped key retained as compatibility candidates. Successful fallback reads compare-and-swap re-encrypt the shared column under the preferred key. During a workspace-key rotation, set `WORKSPACE_SECRETS_ENCRYPTION_KEY_PREVIOUS` to the old value until reads have migrated existing rows. To encrypt any pre-existing plaintext credential rows in place, run `pnpm action db-migrate-encrypt-credentials` (idempotent, non-destructive).
|
|
325
|
+
Per-user/per-org credentials and the vault are encrypted at rest with AES-256-GCM. The Dispatch vault keeps no plaintext copy: `vault_secrets` holds names and metadata, and each value lives only in `app_secrets`. For a per-user/per-org credential, the key precedence is `<APP_NAME>_SECRETS_ENCRYPTION_KEY` (app-scoped — useful in a multi-app workspace where apps shouldn't share one key) → `SECRETS_ENCRYPTION_KEY` → `BETTER_AUTH_SECRET`; production refuses to start without one of these set. The shared workspace vault (`app_secrets`, meant to decrypt the same in sibling apps) prefers `WORKSPACE_SECRETS_ENCRYPTION_KEY`, then the legacy combined `SECRETS_ENCRYPTION_KEY`, then material derived from `A2A_SECRET`, with `BETTER_AUTH_SECRET` and the app-scoped key retained as compatibility candidates. Successful fallback reads compare-and-swap re-encrypt the shared column under the preferred key. During a workspace-key rotation, set `WORKSPACE_SECRETS_ENCRYPTION_KEY_PREVIOUS` to the old value until reads have migrated existing rows. To encrypt any pre-existing plaintext credential rows in place, run `pnpm action db-migrate-encrypt-credentials` (idempotent, non-destructive).
|
|
326
326
|
|
|
327
327
|
Never store secrets in `settings`, `application_state`, source code, or action responses. Use the credential / vault APIs above — they handle both encryption and per-user scoping.
|
|
328
328
|
|
|
@@ -44,7 +44,7 @@ Dispatch ships close to a hundred actions. Most of the UI-facing ones are alread
|
|
|
44
44
|
| `create-vault-secret` / `update-vault-secret` / `delete-vault-secret` | Manage a stored credential. |
|
|
45
45
|
| `create-vault-grant` / `revoke-vault-grant` / `list-vault-grants` | Grant, revoke, or list per-app secret access. |
|
|
46
46
|
| `request-vault-secret` / `approve-vault-request` / `deny-vault-request` / `list-vault-requests` | The request-and-review workflow for a new secret. |
|
|
47
|
-
| `grant-vault-secrets-to-app`
|
|
47
|
+
| `grant-vault-secrets-to-app` | Grant several vault keys to one app in manual mode. |
|
|
48
48
|
| `list-vault-secrets` / `list-vault-secret-options` / `list-vault-audit` | Read secret metadata (never values) and the audit trail. |
|
|
49
49
|
| `get-vault-access-settings` / `set-vault-access-settings` | Read or change the all-apps vs. manual access mode. |
|
|
50
50
|
|
|
@@ -230,7 +230,6 @@ For a new workspace, after running `npx --yes @agent-native/core@latest create`:
|
|
|
230
230
|
|
|
231
231
|
- [ ] Add shared secrets to the vault (API keys, OAuth credentials, etc.)
|
|
232
232
|
- [ ] Keep the default all-apps vault policy or switch to manual per-app grants
|
|
233
|
-
- [ ] Sync vault secrets to push them to apps
|
|
234
233
|
- [ ] Register reusable workspace connections for shared provider accounts, then
|
|
235
234
|
grant apps such as Brain, Analytics, Mail, or Dispatch only when they need
|
|
236
235
|
that account
|