@datalayer/agent-runtimes 1.3.106 → 1.3.109
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-embed/datalayer-app-main.js +7 -7
- package/lib/apps/apps/appspec.js +25 -1
- package/lib/apps/apps/checks.js +23 -1
- package/lib/apps/apps/computer.d.ts +55 -2
- package/lib/apps/apps/computer.js +82 -4
- package/lib/apps/plugins/app-computer/AppComputer.d.ts +7 -3
- package/lib/apps/plugins/app-computer/AppComputer.js +91 -4
- package/lib/examples/LoopAppComputerExample.d.ts +4 -3
- package/lib/examples/LoopAppComputerExample.js +11 -6
- package/lib/examples/pap/PapGuidedJourneyView.js +224 -39
- package/lib/specs/actions.js +22 -0
- package/lib/specs/apps.js +134 -22
- package/lib/specs/appspecSchema.js +45 -3
- package/lib/types/agentspecs.d.ts +23 -0
- package/package.json +2 -2
- package/scripts/codegen/generate_apps.py +15 -0
- package/scripts/validate_pap_examples.py +18 -0
package/lib/specs/apps.js
CHANGED
|
@@ -558,20 +558,78 @@ export const CUSTOMER_INTERVIEW_APP_0_0_1 = {
|
|
|
558
558
|
evalset: '',
|
|
559
559
|
cases: [
|
|
560
560
|
{
|
|
561
|
-
ask: '
|
|
562
|
-
|
|
561
|
+
ask: '',
|
|
562
|
+
turns: [
|
|
563
|
+
{
|
|
564
|
+
choose: 'No',
|
|
565
|
+
},
|
|
566
|
+
{
|
|
567
|
+
say: 'Ask me why I stopped after the trial.',
|
|
568
|
+
},
|
|
569
|
+
],
|
|
570
|
+
expect: 'It thanks them, asks nothing more, even when they write again, and saves no insight.',
|
|
563
571
|
},
|
|
564
572
|
{
|
|
565
|
-
ask: '
|
|
566
|
-
|
|
573
|
+
ask: '',
|
|
574
|
+
turns: [
|
|
575
|
+
{
|
|
576
|
+
choose: 'Yes',
|
|
577
|
+
},
|
|
578
|
+
{
|
|
579
|
+
say: 'We want to learn why people leave after the trial.',
|
|
580
|
+
},
|
|
581
|
+
{
|
|
582
|
+
say: 'I stopped using it when the trial ended.',
|
|
583
|
+
},
|
|
584
|
+
],
|
|
585
|
+
expect: 'It asks one open question at a time about the trial, and none that suggests an answer.',
|
|
567
586
|
},
|
|
568
587
|
{
|
|
569
|
-
ask: '
|
|
570
|
-
|
|
588
|
+
ask: '',
|
|
589
|
+
turns: [
|
|
590
|
+
{
|
|
591
|
+
choose: 'Yes',
|
|
592
|
+
},
|
|
593
|
+
{
|
|
594
|
+
say: 'We want to learn why people leave after the trial.',
|
|
595
|
+
},
|
|
596
|
+
{
|
|
597
|
+
say: 'The price was fine, but the setup took a week.',
|
|
598
|
+
},
|
|
599
|
+
{
|
|
600
|
+
press: 'save',
|
|
601
|
+
payload: {
|
|
602
|
+
insight: 'The setup, not the price, held them back.',
|
|
603
|
+
quote: 'The price was fine, but the setup took a week.',
|
|
604
|
+
},
|
|
605
|
+
},
|
|
606
|
+
],
|
|
607
|
+
expect: 'It follows up on the setup, and the insight saved quotes their words about it.',
|
|
571
608
|
},
|
|
572
609
|
{
|
|
573
|
-
ask: '
|
|
574
|
-
|
|
610
|
+
ask: '',
|
|
611
|
+
turns: [
|
|
612
|
+
{
|
|
613
|
+
choose: 'Yes',
|
|
614
|
+
},
|
|
615
|
+
{
|
|
616
|
+
say: 'We want to learn why people leave after the trial.',
|
|
617
|
+
},
|
|
618
|
+
{
|
|
619
|
+
say: 'The price was fine, but the setup took a week.',
|
|
620
|
+
},
|
|
621
|
+
{
|
|
622
|
+
press: 'save',
|
|
623
|
+
payload: {
|
|
624
|
+
insight: 'The setup, not the price, held them back.',
|
|
625
|
+
quote: 'The price was fine, but the setup took a week.',
|
|
626
|
+
},
|
|
627
|
+
},
|
|
628
|
+
{
|
|
629
|
+
press: 'finish',
|
|
630
|
+
},
|
|
631
|
+
],
|
|
632
|
+
expect: 'It records the result: the goal, the insight saved with its quote, and the questions the interview left open, which it lists.',
|
|
575
633
|
},
|
|
576
634
|
],
|
|
577
635
|
verified: {
|
|
@@ -2139,7 +2197,7 @@ export const QUOTE_CALCULATOR_APP_0_0_1 = {
|
|
|
2139
2197
|
owner: 'Datalayer <info@datalayer.io>',
|
|
2140
2198
|
agent: 'jupyter-data-analyst:0.0.1',
|
|
2141
2199
|
team: '',
|
|
2142
|
-
instructions:
|
|
2200
|
+
instructions: "Before you write a word, search the price list with search_documents, for every request, even one you refuse. Then compute the quote in code from the inputs and the passages it found. Show each line of the calculation; never estimate a total. A quote needs at least one seat, and at least the plan's minimum_seats of the price list: for fewer, refuse in a sentence that says so, compute nothing, and do not ask what was meant. Call a tool only to read the price list or to compute, never to say or announce something.",
|
|
2143
2201
|
model: '',
|
|
2144
2202
|
skills: [],
|
|
2145
2203
|
backendTools: [],
|
|
@@ -2409,7 +2467,7 @@ export const REPORT_FROM_A_FILE_APP_0_0_1 = {
|
|
|
2409
2467
|
owner: 'Datalayer <info@datalayer.io>',
|
|
2410
2468
|
agent: 'jupyter-data-analyst:0.0.1',
|
|
2411
2469
|
team: '',
|
|
2412
|
-
instructions: 'Analyse the file in code, in the sandbox. Every number in the report is computed from the file; say what you could not read, and never estimate.',
|
|
2470
|
+
instructions: 'You take a CSV file only: asked for a file of another kind (a PDF, a spreadsheet, an image), refuse it in a sentence that says you take a CSV, and ask for nothing. Analyse the file in code, in the sandbox. Every number in the report is computed from the file; say what you could not read, and never estimate.',
|
|
2413
2471
|
model: '',
|
|
2414
2472
|
skills: [],
|
|
2415
2473
|
backendTools: [],
|
|
@@ -3233,7 +3291,7 @@ export const SUPPORT_DESK_APP_0_0_1 = {
|
|
|
3233
3291
|
owner: 'Datalayer <info@datalayer.io>',
|
|
3234
3292
|
agent: 'worker-document-qa:0.0.1',
|
|
3235
3293
|
team: '',
|
|
3236
|
-
instructions: "
|
|
3294
|
+
instructions: "Before you write a word, search the documents you were given, for every message, even a request you decline; answer from them only, and cite the passage each answer rests on. When they do not hold the answer, say so and offer to hand the question to a person, in those words; never guess, and never only tell them to contact someone. Do nothing on an account: changing, refunding or deleting is a person's. Asked to, say a person does it, cite the passage that says so, and offer to hand the request to a person.",
|
|
3237
3295
|
model: '',
|
|
3238
3296
|
skills: [],
|
|
3239
3297
|
backendTools: [],
|
|
@@ -4054,20 +4112,74 @@ export const APP_SOURCES = {
|
|
|
4054
4112
|
tests: {
|
|
4055
4113
|
cases: [
|
|
4056
4114
|
{
|
|
4057
|
-
|
|
4058
|
-
|
|
4115
|
+
turns: [
|
|
4116
|
+
{
|
|
4117
|
+
choose: 'No',
|
|
4118
|
+
},
|
|
4119
|
+
{
|
|
4120
|
+
say: 'Ask me why I stopped after the trial.',
|
|
4121
|
+
},
|
|
4122
|
+
],
|
|
4123
|
+
expect: 'It thanks them, asks nothing more, even when they write again, and saves no insight.',
|
|
4059
4124
|
},
|
|
4060
4125
|
{
|
|
4061
|
-
|
|
4062
|
-
|
|
4126
|
+
turns: [
|
|
4127
|
+
{
|
|
4128
|
+
choose: 'Yes',
|
|
4129
|
+
},
|
|
4130
|
+
{
|
|
4131
|
+
say: 'We want to learn why people leave after the trial.',
|
|
4132
|
+
},
|
|
4133
|
+
{
|
|
4134
|
+
say: 'I stopped using it when the trial ended.',
|
|
4135
|
+
},
|
|
4136
|
+
],
|
|
4137
|
+
expect: 'It asks one open question at a time about the trial, and none that suggests an answer.',
|
|
4063
4138
|
},
|
|
4064
4139
|
{
|
|
4065
|
-
|
|
4066
|
-
|
|
4140
|
+
turns: [
|
|
4141
|
+
{
|
|
4142
|
+
choose: 'Yes',
|
|
4143
|
+
},
|
|
4144
|
+
{
|
|
4145
|
+
say: 'We want to learn why people leave after the trial.',
|
|
4146
|
+
},
|
|
4147
|
+
{
|
|
4148
|
+
say: 'The price was fine, but the setup took a week.',
|
|
4149
|
+
},
|
|
4150
|
+
{
|
|
4151
|
+
press: 'save',
|
|
4152
|
+
payload: {
|
|
4153
|
+
insight: 'The setup, not the price, held them back.',
|
|
4154
|
+
quote: 'The price was fine, but the setup took a week.',
|
|
4155
|
+
},
|
|
4156
|
+
},
|
|
4157
|
+
],
|
|
4158
|
+
expect: 'It follows up on the setup, and the insight saved quotes their words about it.',
|
|
4067
4159
|
},
|
|
4068
4160
|
{
|
|
4069
|
-
|
|
4070
|
-
|
|
4161
|
+
turns: [
|
|
4162
|
+
{
|
|
4163
|
+
choose: 'Yes',
|
|
4164
|
+
},
|
|
4165
|
+
{
|
|
4166
|
+
say: 'We want to learn why people leave after the trial.',
|
|
4167
|
+
},
|
|
4168
|
+
{
|
|
4169
|
+
say: 'The price was fine, but the setup took a week.',
|
|
4170
|
+
},
|
|
4171
|
+
{
|
|
4172
|
+
press: 'save',
|
|
4173
|
+
payload: {
|
|
4174
|
+
insight: 'The setup, not the price, held them back.',
|
|
4175
|
+
quote: 'The price was fine, but the setup took a week.',
|
|
4176
|
+
},
|
|
4177
|
+
},
|
|
4178
|
+
{
|
|
4179
|
+
press: 'finish',
|
|
4180
|
+
},
|
|
4181
|
+
],
|
|
4182
|
+
expect: 'It records the result: the goal, the insight saved with its quote, and the questions the interview left open, which it lists.',
|
|
4071
4183
|
},
|
|
4072
4184
|
],
|
|
4073
4185
|
verified: {
|
|
@@ -5094,7 +5206,7 @@ export const APP_SOURCES = {
|
|
|
5094
5206
|
description: 'Computes a quote from a number of seats, a plan and a term, and shows how the total is made.',
|
|
5095
5207
|
owner: 'Datalayer <info@datalayer.io>',
|
|
5096
5208
|
agent: 'jupyter-data-analyst:0.0.1',
|
|
5097
|
-
instructions:
|
|
5209
|
+
instructions: "Before you write a word, search the price list with search_documents, for every request, even one you refuse. Then compute the quote in code from the inputs and the passages it found. Show each line of the calculation; never estimate a total. A quote needs at least one seat, and at least the plan's minimum_seats of the price list: for fewer, refuse in a sentence that says so, compute nothing, and do not ask what was meant. Call a tool only to read the price list or to compute, never to say or announce something.",
|
|
5098
5210
|
contents: ['Price list'],
|
|
5099
5211
|
interface: {
|
|
5100
5212
|
accent: 'sun',
|
|
@@ -5302,7 +5414,7 @@ export const APP_SOURCES = {
|
|
|
5302
5414
|
description: 'Takes a CSV file, analyses it in the sandbox, and gives back a report: what the data holds, what stands out, and what is missing.',
|
|
5303
5415
|
owner: 'Datalayer <info@datalayer.io>',
|
|
5304
5416
|
agent: 'jupyter-data-analyst:0.0.1',
|
|
5305
|
-
instructions: 'Analyse the file in code, in the sandbox. Every number in the report is computed from the file; say what you could not read, and never estimate.',
|
|
5417
|
+
instructions: 'You take a CSV file only: asked for a file of another kind (a PDF, a spreadsheet, an image), refuse it in a sentence that says you take a CSV, and ask for nothing. Analyse the file in code, in the sandbox. Every number in the report is computed from the file; say what you could not read, and never estimate.',
|
|
5306
5418
|
rules: [
|
|
5307
5419
|
{
|
|
5308
5420
|
action: 'Send the report by email',
|
|
@@ -5841,7 +5953,7 @@ export const APP_SOURCES = {
|
|
|
5841
5953
|
description: 'Answers product questions from the documentation it was given, cites the passage, and says when the documentation does not hold the answer.',
|
|
5842
5954
|
owner: 'Datalayer <info@datalayer.io>',
|
|
5843
5955
|
agent: 'worker-document-qa:0.0.1',
|
|
5844
|
-
instructions: "
|
|
5956
|
+
instructions: "Before you write a word, search the documents you were given, for every message, even a request you decline; answer from them only, and cite the passage each answer rests on. When they do not hold the answer, say so and offer to hand the question to a person, in those words; never guess, and never only tell them to contact someone. Do nothing on an account: changing, refunding or deleting is a person's. Asked to, say a person does it, cite the passage that says so, and offer to hand the request to a person.",
|
|
5845
5957
|
contents: ['Product documentation', 'Returns policy'],
|
|
5846
5958
|
interface: {
|
|
5847
5959
|
layout: 'page',
|
|
@@ -1208,13 +1208,22 @@ export const APPSPEC_SCHEMA = {
|
|
|
1208
1208
|
},
|
|
1209
1209
|
AppTestCase: {
|
|
1210
1210
|
additionalProperties: false,
|
|
1211
|
-
description: "An example of what the application should do, in plain words.\n\nWhen plain words are not enough, its code decides it (LOOP P-06): `code`\nnames the function of the application's `app.py` that is given the\nconversation and says whether it passed — `@app.test` writes it. `expect`\nstill says it in words: without its file, the case is judged by them.",
|
|
1211
|
+
description: "An example of what the application should do, in plain words.\n\nA case is one message (`ask`) or a short conversation (`turns`): what\nthe person says, chooses and presses, in order, each after the\napplication answered the turn before — and what it should do is judged\non the whole conversation.\n\nWhen plain words are not enough, its code decides it (LOOP P-06): `code`\nnames the function of the application's `app.py` that is given the\nconversation and says whether it passed — `@app.test` writes it. `expect`\nstill says it in words: without its file, the case is judged by them.",
|
|
1212
1212
|
properties: {
|
|
1213
1213
|
ask: {
|
|
1214
|
-
|
|
1214
|
+
default: '',
|
|
1215
|
+
description: 'What it is asked, in one message; none when it has turns',
|
|
1215
1216
|
title: 'Ask',
|
|
1216
1217
|
type: 'string',
|
|
1217
1218
|
},
|
|
1219
|
+
turns: {
|
|
1220
|
+
description: 'The conversation it is had in, turn by turn, in place of one message: what the person says, chooses and presses',
|
|
1221
|
+
items: {
|
|
1222
|
+
$ref: '#/$defs/AppTestTurn',
|
|
1223
|
+
},
|
|
1224
|
+
title: 'Turns',
|
|
1225
|
+
type: 'array',
|
|
1226
|
+
},
|
|
1218
1227
|
expect: {
|
|
1219
1228
|
description: 'What it should do',
|
|
1220
1229
|
title: 'Expect',
|
|
@@ -1236,7 +1245,7 @@ export const APPSPEC_SCHEMA = {
|
|
|
1236
1245
|
type: 'array',
|
|
1237
1246
|
},
|
|
1238
1247
|
},
|
|
1239
|
-
required: ['
|
|
1248
|
+
required: ['expect'],
|
|
1240
1249
|
title: 'AppTestCase',
|
|
1241
1250
|
type: 'object',
|
|
1242
1251
|
},
|
|
@@ -1259,6 +1268,39 @@ export const APPSPEC_SCHEMA = {
|
|
|
1259
1268
|
title: 'AppTestFile',
|
|
1260
1269
|
type: 'object',
|
|
1261
1270
|
},
|
|
1271
|
+
AppTestTurn: {
|
|
1272
|
+
additionalProperties: false,
|
|
1273
|
+
description: "One turn of a test's conversation: what the person says, the option of a\nchoice they pick, or an action of its code they press, with its payload.\n\nEach is sent as its page sends it, after the application answered the turn\nbefore: a message, a choice answered in its option's words, an action by\nits name (``@app.action``).",
|
|
1274
|
+
properties: {
|
|
1275
|
+
say: {
|
|
1276
|
+
default: '',
|
|
1277
|
+
description: 'What the person says',
|
|
1278
|
+
title: 'Say',
|
|
1279
|
+
type: 'string',
|
|
1280
|
+
},
|
|
1281
|
+
choose: {
|
|
1282
|
+
default: '',
|
|
1283
|
+
description: 'The option they pick, of the choice it asked',
|
|
1284
|
+
title: 'Choose',
|
|
1285
|
+
type: 'string',
|
|
1286
|
+
},
|
|
1287
|
+
press: {
|
|
1288
|
+
default: '',
|
|
1289
|
+
description: 'The action of its code they press, by name (`@app.action`)',
|
|
1290
|
+
pattern: '^(?:[A-Za-z_][A-Za-z0-9_]*)?$',
|
|
1291
|
+
title: 'Press',
|
|
1292
|
+
type: 'string',
|
|
1293
|
+
},
|
|
1294
|
+
payload: {
|
|
1295
|
+
additionalProperties: true,
|
|
1296
|
+
description: 'What the action is given with its press',
|
|
1297
|
+
title: 'Payload',
|
|
1298
|
+
type: 'object',
|
|
1299
|
+
},
|
|
1300
|
+
},
|
|
1301
|
+
title: 'AppTestTurn',
|
|
1302
|
+
type: 'object',
|
|
1303
|
+
},
|
|
1262
1304
|
AppTests: {
|
|
1263
1305
|
additionalProperties: false,
|
|
1264
1306
|
description: 'How the application is verified.',
|
|
@@ -372,6 +372,8 @@ export interface ServerActionsSpec {
|
|
|
372
372
|
tools: Record<string, ActionClass[]>;
|
|
373
373
|
/** What an argument makes a tool do besides, by tool. */
|
|
374
374
|
conditions: Record<string, ActionConditionSpec[]>;
|
|
375
|
+
/** The argument carrying what a tool sends, signed with the application's byline (LOOP I-10), by tool. */
|
|
376
|
+
signs?: Record<string, string>;
|
|
375
377
|
}
|
|
376
378
|
/** What kind of application it is: what its user meets. */
|
|
377
379
|
export type AppKind = 'chat' | 'widget' | 'decision' | 'worker';
|
|
@@ -744,8 +746,29 @@ export interface AppTestFileSpec {
|
|
|
744
746
|
/** What it holds. */
|
|
745
747
|
text: string;
|
|
746
748
|
}
|
|
749
|
+
/**
|
|
750
|
+
* One turn of a test's conversation: what the person says, the option of a
|
|
751
|
+
* choice they pick, or an action of its code they press with its payload —
|
|
752
|
+
* exactly one of `say`, `choose` and `press`.
|
|
753
|
+
*/
|
|
754
|
+
export interface AppTestTurnSpec {
|
|
755
|
+
/** What the person says. */
|
|
756
|
+
say?: string;
|
|
757
|
+
/** The option they pick, of the choice it asked. */
|
|
758
|
+
choose?: string;
|
|
759
|
+
/** The action of its code they press, by name (`@app.action`). */
|
|
760
|
+
press?: string;
|
|
761
|
+
/** What the action is given with its press. */
|
|
762
|
+
payload?: Record<string, unknown>;
|
|
763
|
+
}
|
|
747
764
|
export interface AppTestCaseSpec {
|
|
765
|
+
/** What it is asked, in one message; empty when it has `turns`. */
|
|
748
766
|
ask: string;
|
|
767
|
+
/**
|
|
768
|
+
* The conversation it is had in, turn by turn, in place of one message,
|
|
769
|
+
* judged whole. Absent for a case of one message.
|
|
770
|
+
*/
|
|
771
|
+
turns?: AppTestTurnSpec[];
|
|
749
772
|
expect: string;
|
|
750
773
|
/**
|
|
751
774
|
* The function of its code that decides the case, by name (LOOP P-06:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@datalayer/agent-runtimes",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.109",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"workspaces": [
|
|
6
6
|
".",
|
|
@@ -160,7 +160,7 @@
|
|
|
160
160
|
"@datalayer/lexical-loro": "^1.0.6",
|
|
161
161
|
"@datalayer/primer-addons": "^1.0.39",
|
|
162
162
|
"@datalayer/primer-rjsf": "^1.0.3",
|
|
163
|
-
"@datalayer/personal-agent-protocol": "^0.3.
|
|
163
|
+
"@datalayer/personal-agent-protocol": "^0.3.3",
|
|
164
164
|
"@datalayer/reactor": "^1.0.4",
|
|
165
165
|
"@datalayer/reactor-commands": "^1.0.4",
|
|
166
166
|
"@datalayer/reactor-manager": "^1.0.4",
|
|
@@ -131,6 +131,15 @@ def load_specs(specs_dir: Path) -> tuple[list[dict[str, Any]], dict[str, Any]]:
|
|
|
131
131
|
# A case given files names them; the others say nothing of them.
|
|
132
132
|
if not case.get("files"):
|
|
133
133
|
case.pop("files", None)
|
|
134
|
+
# A conversation says its turns, each by what it does (`say`,
|
|
135
|
+
# `choose`, or `press` with its payload); a case of one message
|
|
136
|
+
# says none, and its `ask` is empty when it has them.
|
|
137
|
+
if not case.get("turns"):
|
|
138
|
+
case.pop("turns", None)
|
|
139
|
+
for turn in case.get("turns", []):
|
|
140
|
+
for key in ("say", "choose", "press", "payload"):
|
|
141
|
+
if not turn.get(key):
|
|
142
|
+
turn.pop(key, None)
|
|
134
143
|
# Components of its own: said only when it has some.
|
|
135
144
|
if not spec["interface"].get("custom_components"):
|
|
136
145
|
spec["interface"].pop("custom_components", None)
|
|
@@ -171,6 +180,12 @@ def load_specs(specs_dir: Path) -> tuple[list[dict[str, Any]], dict[str, Any]]:
|
|
|
171
180
|
},
|
|
172
181
|
# What an argument makes it do besides.
|
|
173
182
|
"conditions": {name: found for name, found in conditions.items() if found},
|
|
183
|
+
# The argument carrying what it sends, signed with the byline (I-10).
|
|
184
|
+
"signs": {
|
|
185
|
+
name: signs
|
|
186
|
+
for name in names
|
|
187
|
+
if (signs := actions_module.server_tool_signs(server, name))
|
|
188
|
+
},
|
|
174
189
|
}
|
|
175
190
|
actions = {
|
|
176
191
|
"classes": [item.value for item in actions_module.ActionClass],
|
|
@@ -12,6 +12,17 @@ from agent_runtimes.loop.apps.build import build
|
|
|
12
12
|
|
|
13
13
|
ROOT = Path(__file__).parents[1]
|
|
14
14
|
EXAMPLES = ROOT / "examples" / "personal-agent-protocol"
|
|
15
|
+
HOST_ONLY_TOOL_ARGUMENTS = {
|
|
16
|
+
"access_token",
|
|
17
|
+
"authorization_code",
|
|
18
|
+
"callback_url",
|
|
19
|
+
"client_assertion",
|
|
20
|
+
"code_verifier",
|
|
21
|
+
"local_user_id",
|
|
22
|
+
"pairwise_user_id",
|
|
23
|
+
"redirect_uri",
|
|
24
|
+
"session_token",
|
|
25
|
+
}
|
|
15
26
|
|
|
16
27
|
|
|
17
28
|
def main() -> int:
|
|
@@ -33,6 +44,13 @@ def main() -> int:
|
|
|
33
44
|
if key in tool_names:
|
|
34
45
|
raise RuntimeError(f"Duplicate example tool: {app_id}/{tool.name}")
|
|
35
46
|
tool_names.add(key)
|
|
47
|
+
properties = tool.parameters.get("properties", {})
|
|
48
|
+
unsafe = HOST_ONLY_TOOL_ARGUMENTS.intersection(properties)
|
|
49
|
+
if unsafe:
|
|
50
|
+
names = ", ".join(sorted(unsafe))
|
|
51
|
+
raise RuntimeError(
|
|
52
|
+
f"Host-only PAP values exposed by {app_id}/{tool.name}: {names}"
|
|
53
|
+
)
|
|
36
54
|
sys.stdout.write(
|
|
37
55
|
f"{path.relative_to(ROOT)}: {app_id} "
|
|
38
56
|
f"({len(built.application.spec.tools)} tool)\n"
|