@datalayer/agent-runtimes 1.3.58 → 1.3.60

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.
Files changed (58) hide show
  1. package/lib/chat/ChatFloating.d.ts +11 -1
  2. package/lib/chat/ChatFloating.js +6 -2
  3. package/lib/chat/assistant/AssistantStage.d.ts +7 -1
  4. package/lib/chat/assistant/AssistantStage.js +5 -3
  5. package/lib/chat/assistant/DecisionAsk.d.ts +31 -0
  6. package/lib/chat/assistant/DecisionAsk.js +96 -0
  7. package/lib/chat/assistant/SpeechBalloon.d.ts +11 -2
  8. package/lib/chat/assistant/SpeechBalloon.js +5 -4
  9. package/lib/chat/assistant/decisions.d.ts +104 -0
  10. package/lib/chat/assistant/decisions.js +161 -0
  11. package/lib/chat/marks/toolMarks.js +2 -2
  12. package/lib/config/AgentConfiguration.d.ts +2 -1
  13. package/lib/config/AgentConfiguration.js +2 -2
  14. package/lib/examples/AgentDecideExample.d.ts +24 -0
  15. package/lib/examples/AgentDecideExample.js +73 -0
  16. package/lib/examples/AgentNotificationsExample.js +1 -1
  17. package/lib/examples/AgentSkillsExample.js +1 -1
  18. package/lib/examples/AgentspecsExample.js +3 -3
  19. package/lib/examples/ChatAssistantGalleryExample.d.ts +12 -0
  20. package/lib/examples/ChatAssistantGalleryExample.js +181 -0
  21. package/lib/examples/example-selector.js +2 -0
  22. package/lib/examples/index.d.ts +2 -0
  23. package/lib/examples/index.js +2 -0
  24. package/lib/examples/main.js +5 -0
  25. package/lib/examples/utils/AssistantGalleryGrid.d.ts +53 -0
  26. package/lib/examples/utils/AssistantGalleryGrid.js +123 -0
  27. package/lib/examples/utils/assistantGallery.d.ts +42 -0
  28. package/lib/examples/utils/assistantGallery.js +102 -0
  29. package/lib/examples/utils/testSpriteCharacter.d.ts +9 -0
  30. package/lib/examples/utils/testSpriteCharacter.js +60 -0
  31. package/lib/loop/apps/appspec.js +4 -4
  32. package/lib/loop/apps/checks.js +6 -6
  33. package/lib/loop/apps/rules.js +2 -2
  34. package/lib/loop/plugins/assistant/index.d.ts +14 -2
  35. package/lib/loop/plugins/assistant/index.js +13 -4
  36. package/lib/specs/actions.d.ts +1 -1
  37. package/lib/specs/actions.js +1 -1
  38. package/lib/specs/agents/agents.js +197 -192
  39. package/lib/specs/apps.js +13 -13
  40. package/lib/specs/appspecSchema.js +3 -3
  41. package/lib/specs/backendTools.d.ts +31 -0
  42. package/lib/specs/{tools.js → backendTools.js} +48 -48
  43. package/lib/specs/cogs.js +8 -7
  44. package/lib/specs/frames.js +5 -5
  45. package/lib/specs/index.d.ts +1 -1
  46. package/lib/specs/index.js +1 -1
  47. package/lib/stores/agentRuntimeStore.js +2 -2
  48. package/lib/stories/loop/pictures-main.js +16 -1
  49. package/lib/types/agentspecs.d.ts +7 -5
  50. package/lib/types/config.d.ts +2 -2
  51. package/lib/types/tools.d.ts +3 -3
  52. package/package.json +1 -1
  53. package/scripts/codegen/generate_agents.py +23 -12
  54. package/scripts/codegen/generate_apps.py +3 -2
  55. package/scripts/codegen/{generate_tools.py → generate_backend_tools.py} +29 -29
  56. package/scripts/codegen/generate_cogs.py +2 -2
  57. package/scripts/codegen/generate_frames.py +2 -2
  58. package/lib/specs/tools.d.ts +0 -31
@@ -0,0 +1,102 @@
1
+ /*
2
+ * Copyright (c) 2025-2026 Datalayer, Inc.
3
+ * Distributed under the terms of the Modified BSD License.
4
+ */
5
+ import { ASSISTANT_WORDS, latestSaying, } from '../../chat/assistant/state';
6
+ /** Every state, in the order an assistant lives them, then stepped aside. */
7
+ export const GALLERY_POSES = [
8
+ 'idle',
9
+ 'greeting',
10
+ 'thinking',
11
+ 'working',
12
+ 'waiting',
13
+ 'speaking',
14
+ 'paused',
15
+ 'aside',
16
+ 'goodbye',
17
+ ];
18
+ /** How the gallery names each pose. */
19
+ export const GALLERY_POSE_LABELS = {
20
+ idle: 'Idle',
21
+ greeting: 'Greeting',
22
+ thinking: 'Thinking',
23
+ working: 'Working',
24
+ waiting: 'Waiting',
25
+ speaking: 'Speaking',
26
+ paused: 'Paused',
27
+ aside: 'Stepped aside',
28
+ goodbye: 'Goodbye',
29
+ };
30
+ /** The state the stage acts for a pose: aside, it is idle under what covers it. */
31
+ export function stateOfPose(pose) {
32
+ return pose === 'aside' ? 'idle' : pose;
33
+ }
34
+ /**
35
+ * What the agent said, as a conversation would hold it: each entry is read
36
+ * by `latestSaying`, as the floating assistant reads the chat — Markdown made
37
+ * plain, a long answer cut with *Open the conversation*.
38
+ */
39
+ export const SAMPLE_CONVERSATIONS = [
40
+ [
41
+ { id: 'u1', role: 'user', content: 'Who wrote about late deliveries?' },
42
+ {
43
+ id: 'a1',
44
+ role: 'assistant',
45
+ content: 'Three customers wrote about **late deliveries** this week: Ada, Grace and Alan.',
46
+ },
47
+ ],
48
+ [
49
+ { id: 'u2', role: 'user', content: 'Is the notebook up to date?' },
50
+ { id: 't2', role: 'assistant', toolName: 'readCell', content: '' },
51
+ {
52
+ id: 'a2',
53
+ role: 'assistant',
54
+ content: 'Yes — the last cell ran at 9:40 and its [chart](#chart) shows the new week.',
55
+ },
56
+ ],
57
+ [
58
+ { id: 'u3', role: 'user', content: 'Summarise the quarter.' },
59
+ {
60
+ id: 'a3',
61
+ role: 'assistant',
62
+ content: 'Revenue grew 12% on the quarter, carried by the renewals in March. Churn held at 2.1%, the same as the last two quarters, and the new plan took 140 accounts in its first six weeks. Support tickets fell by a fifth after the onboarding change, and the slowest region, the north, closed half its gap with the others. The full table, with each region and month, is in the notebook.',
63
+ },
64
+ ],
65
+ ];
66
+ /** The saying of sample `index` (wrapping), as the balloon shows it. */
67
+ export function sampleSaying(index) {
68
+ const conversation = SAMPLE_CONVERSATIONS[((index % SAMPLE_CONVERSATIONS.length) + SAMPLE_CONVERSATIONS.length) %
69
+ SAMPLE_CONVERSATIONS.length];
70
+ const saying = latestSaying(conversation);
71
+ if (!saying) {
72
+ throw new Error(`The sample conversation ${index} says nothing.`);
73
+ }
74
+ return { text: saying.text, more: saying.more };
75
+ }
76
+ /** The approval the gallery waits on (T-23): answered in the balloon. */
77
+ export function sampleApproval(onApprove, onDeny) {
78
+ return {
79
+ id: 'gallery-approval',
80
+ asks: 'send_reply',
81
+ why: 'Send a reply to a customer: ask me first',
82
+ others: 1,
83
+ onApprove,
84
+ onDeny,
85
+ };
86
+ }
87
+ /**
88
+ * The balloon for a pose: waiting holds the approval, paused says so, and
89
+ * any other pose says the sample's words when asked to (`saying`).
90
+ */
91
+ export function balloonForPose(pose, { saying, approval, }) {
92
+ if (pose === 'waiting') {
93
+ return { text: ASSISTANT_WORDS.approval, approval };
94
+ }
95
+ if (pose === 'paused') {
96
+ return { text: ASSISTANT_WORDS.paused };
97
+ }
98
+ if (pose === 'goodbye' || pose === 'aside') {
99
+ return undefined;
100
+ }
101
+ return saying;
102
+ }
@@ -0,0 +1,9 @@
1
+ import type { AssistantCharacterData } from '../../chat/assistant/formats/types';
2
+ /** The character's `agent.js`, as clippy.js registers one. */
3
+ export declare const TEST_SPRITE_AGENT_JS = "\nclippy.ready('Pixel', {\n overlayCount: 1,\n framesize: [48, 48],\n animations: {\n Idle1_1: { frames: [{ duration: 1200, images: [[0, 0]] }] },\n Thinking: { frames: [{ duration: 1200, images: [[48, 0]] }] },\n Processing: { frames: [\n { duration: 240, images: [[96, 0]] },\n { duration: 240, images: [[144, 0]] },\n ] },\n GetAttention: { frames: [{ duration: 900, images: [[192, 0]] }] },\n Sleep: { frames: [{ duration: 1600, images: [[240, 0]] }] },\n Greeting: { frames: [{ duration: 900, images: [[288, 0]] }] },\n Explain: { frames: [\n { duration: 200, images: [[336, 0]] },\n { duration: 200, images: [[384, 0]] },\n ] },\n GoodBye: { frames: [{ duration: 900, images: [[432, 0]] }] },\n },\n});\n";
4
+ /** Its sprite sheet, `map.png` (480×48), base64. */
5
+ export declare const TEST_SPRITE_MAP_PNG_BASE64 = "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";
6
+ /** The sprite sheet's bytes. */
7
+ export declare function testSpriteMapPng(): Uint8Array;
8
+ /** The test sprite, read as a person's clippy.js files are. */
9
+ export declare function readTestSpriteCharacter(): Promise<AssistantCharacterData>;
@@ -0,0 +1,60 @@
1
+ /*
2
+ * Copyright (c) 2025-2026 Datalayer, Inc.
3
+ * Distributed under the terms of the Modified BSD License.
4
+ */
5
+ /**
6
+ * A small character of our own in the clippy.js shape (LOOP T-26), for the
7
+ * floating assistant's gallery: an `agent.js` and a `map.png` sprite sheet
8
+ * of ten 48×48 cells, read through the same reader a person's files go
9
+ * through (`readClippyCharacter`). It is a test sprite drawn for Datalayer —
10
+ * no file of Microsoft's is in the repository (§6.9).
11
+ *
12
+ * Its animations carry the names the Office characters use, so the reader's
13
+ * `stateAnimations` gives every state of the assistant one of its own:
14
+ * `Idle1_1` idles, `Thinking`, `Processing`, `GetAttention`, `Sleep`,
15
+ * `Greeting`, `Explain` and `GoodBye`.
16
+ *
17
+ * @module examples/utils/testSpriteCharacter
18
+ */
19
+ import { readClippyCharacter } from '../../chat/assistant/formats/clippy';
20
+ /** The character's `agent.js`, as clippy.js registers one. */
21
+ export const TEST_SPRITE_AGENT_JS = `
22
+ clippy.ready('Pixel', {
23
+ overlayCount: 1,
24
+ framesize: [48, 48],
25
+ animations: {
26
+ Idle1_1: { frames: [{ duration: 1200, images: [[0, 0]] }] },
27
+ Thinking: { frames: [{ duration: 1200, images: [[48, 0]] }] },
28
+ Processing: { frames: [
29
+ { duration: 240, images: [[96, 0]] },
30
+ { duration: 240, images: [[144, 0]] },
31
+ ] },
32
+ GetAttention: { frames: [{ duration: 900, images: [[192, 0]] }] },
33
+ Sleep: { frames: [{ duration: 1600, images: [[240, 0]] }] },
34
+ Greeting: { frames: [{ duration: 900, images: [[288, 0]] }] },
35
+ Explain: { frames: [
36
+ { duration: 200, images: [[336, 0]] },
37
+ { duration: 200, images: [[384, 0]] },
38
+ ] },
39
+ GoodBye: { frames: [{ duration: 900, images: [[432, 0]] }] },
40
+ },
41
+ });
42
+ `;
43
+ /** Its sprite sheet, `map.png` (480×48), base64. */
44
+ export const TEST_SPRITE_MAP_PNG_BASE64 = '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';
45
+ /** The sprite sheet's bytes. */
46
+ export function testSpriteMapPng() {
47
+ const binary = atob(TEST_SPRITE_MAP_PNG_BASE64);
48
+ const bytes = new Uint8Array(binary.length);
49
+ for (let index = 0; index < binary.length; index += 1) {
50
+ bytes[index] = binary.charCodeAt(index);
51
+ }
52
+ return bytes;
53
+ }
54
+ /** The test sprite, read as a person's clippy.js files are. */
55
+ export function readTestSpriteCharacter() {
56
+ return readClippyCharacter({
57
+ agentJs: TEST_SPRITE_AGENT_JS,
58
+ mapPng: new Blob([testSpriteMapPng()], { type: 'image/png' }),
59
+ });
60
+ }
@@ -89,7 +89,7 @@ export function emptyAppspec(kind = 'chat') {
89
89
  instructions: '',
90
90
  model: '',
91
91
  skills: [],
92
- tools: [],
92
+ backendTools: [],
93
93
  context: [],
94
94
  contents: [],
95
95
  connections: [],
@@ -147,7 +147,7 @@ const KNOWN_KEYS = [
147
147
  'instructions',
148
148
  'model',
149
149
  'skills',
150
- 'tools',
150
+ 'backend_tools',
151
151
  'context',
152
152
  'contents',
153
153
  'connections',
@@ -341,7 +341,7 @@ export function parseAppspec(document) {
341
341
  instructions: text(data.instructions),
342
342
  model: text(data.model),
343
343
  skills: texts(data.skills),
344
- tools: texts(data.tools),
344
+ backendTools: texts(data.backend_tools),
345
345
  context: texts(data.context),
346
346
  contents: texts(data.contents),
347
347
  connections: records(data.connections).map(parseConnection),
@@ -541,7 +541,7 @@ export function dumpAppspec(app) {
541
541
  .text('instructions', app.instructions)
542
542
  .text('model', app.model)
543
543
  .list('skills', app.skills)
544
- .list('tools', app.tools)
544
+ .list('backend_tools', app.backendTools)
545
545
  .list('context', app.context)
546
546
  .list('contents', app.contents)
547
547
  .list('connections', app.connections.map(connection => new Writer()
@@ -36,7 +36,7 @@ import { getModel } from '../../specs/models';
36
36
  import { getNotificationSpec } from '../../specs/notifications';
37
37
  import { getSkillSpec } from '../../specs/skills';
38
38
  import { getTeamSpec } from '../../specs/teams';
39
- import { getToolSpec } from '../../specs/tools';
39
+ import { getBackendToolSpec } from '../../specs/backendTools';
40
40
  import { getTrack } from '../../specs/tracks';
41
41
  import { pluginsOffSetupNotes } from '../plugins/canvas-blocks';
42
42
  import { isAssistantCharacterId, parseAppspec } from './appspec';
@@ -173,8 +173,8 @@ function referenceProblems(app, organizationFrames) {
173
173
  if (!getSkillSpec(idOf(ref)))
174
174
  missing('skill', ref);
175
175
  }
176
- for (const ref of app.tools) {
177
- if (!getToolSpec(idOf(ref)))
176
+ for (const ref of app.backendTools) {
177
+ if (!getBackendToolSpec(idOf(ref)))
178
178
  missing('tool', ref);
179
179
  }
180
180
  for (const ref of app.checks.guards) {
@@ -241,7 +241,7 @@ function referenceProblems(app, organizationFrames) {
241
241
  continue;
242
242
  const [server, name] = splitRef(target);
243
243
  if (server === undefined) {
244
- if (!getToolSpec(name)) {
244
+ if (!getBackendToolSpec(name)) {
245
245
  problems.push(`The rule “${rule.action}” names the tool “${target}”, which the catalogue does not have.`);
246
246
  }
247
247
  continue;
@@ -339,8 +339,8 @@ function setupNotes(app) {
339
339
  }
340
340
  for (const ref of app.skills)
341
341
  note('skill', ref, getSkillSpec(idOf(ref)));
342
- for (const ref of app.tools)
343
- note('tool', ref, getToolSpec(idOf(ref)));
342
+ for (const ref of app.backendTools)
343
+ note('tool', ref, getBackendToolSpec(idOf(ref)));
344
344
  for (const ref of app.checks.guards)
345
345
  note('Guard', ref, own(GUARD_CATALOGUE, idOf(ref)));
346
346
  for (const ref of app.checks.gates)
@@ -34,7 +34,7 @@
34
34
  *
35
35
  * @module loop/apps/rules
36
36
  */
37
- import { SERVER_ACTIONS, TOOL_ACTIONS } from '../../specs/actions';
37
+ import { SERVER_ACTIONS, BACKEND_TOOL_ACTIONS } from '../../specs/actions';
38
38
  /** The four behaviours, from the freest to the most restricted. */
39
39
  export const BEHAVIOURS = [
40
40
  'do_it',
@@ -138,7 +138,7 @@ function entryOf(server, name) {
138
138
  export function classesOf(ref, args) {
139
139
  const [server, name] = splitRef(ref);
140
140
  if (server === undefined) {
141
- return [...(own(TOOL_ACTIONS, name) ?? [])];
141
+ return [...(own(BACKEND_TOOL_ACTIONS, name) ?? [])];
142
142
  }
143
143
  const [classes, conditions] = entryOf(server, name);
144
144
  for (const condition of conditions) {
@@ -9,7 +9,9 @@
9
9
  * while the conversation is out of sight, holds there the approval it waits
10
10
  * on, answered with *Approve* or *Deny* on the path every approval of it is
11
11
  * answered on (T-23, U-19), opens the conversation when clicked, and is
12
- * dragged about and sent away as the chat's own assistant is (T-27).
12
+ * dragged about and sent away as the chat's own assistant is (T-27). Given
13
+ * the runtime to ask (`decisions`), its balloon offers *Ask a decision*: a
14
+ * typed decision asked of Jev through the runtime, answered there.
13
15
  *
14
16
  * The workspace's chat is the conversation: on a page layout, its side panel
15
17
  * is opened and closed; where the conversation is always on screen, a click
@@ -33,6 +35,16 @@ export type LoopAssistantConfig = {
33
35
  * the balloon says an approval waits and the conversation answers it.
34
36
  */
35
37
  appId?: string;
38
+ /**
39
+ * The runtime decisions are asked at — its
40
+ * `/api/v1/configure/inference/decisions`, as the `decide` tool asks — and
41
+ * the token to ask with, if it needs one: the balloon offers *Ask a
42
+ * decision*. Without it, it does not.
43
+ */
44
+ decisions?: {
45
+ serverUrl: string;
46
+ token?: string;
47
+ };
36
48
  };
37
49
  /**
38
50
  * What the character acts out (T-22), from what the application is doing as
@@ -43,6 +55,6 @@ export declare function assistantStateOfTurn(turn: ChatTurnSnapshot, presence: P
43
55
  arriving?: boolean;
44
56
  leaving?: boolean;
45
57
  }): AssistantState;
46
- export declare function LoopAssistant({ app, person, appId, }: LoopAssistantConfig): JSX.Element | null;
58
+ export declare function LoopAssistant({ app, person, appId, decisions, }: LoopAssistantConfig): JSX.Element | null;
47
59
  export declare const LoopAssistantPlugin: import("@datalayer/reactor").ReactorPlugin<LoopAssistantConfig, unknown, unknown>;
48
60
  export default LoopAssistantPlugin;
@@ -7,6 +7,7 @@ import { Box } from '@datalayer/primer-addons';
7
7
  import { useIAMStore } from '@datalayer/core/lib/state/substates/IAMState';
8
8
  import { pageLayoutPanelOpen, togglePagePanel, } from '@datalayer/primer-addons/lib/reactor';
9
9
  import { AssistantStage } from '../../../chat/assistant/AssistantStage';
10
+ import { decisionsAskerAt } from '../../../chat/assistant/decisions';
10
11
  import { ASSISTANT_WORDS, assistantStateOf, keepAway, keptAway, latestSaying, } from '../../../chat/assistant/state';
11
12
  import { useApproveToolRequest, useRejectToolRequest, useToolApprovalsQuery, } from '../../../hooks/useToolApprovals';
12
13
  import { approvalsOfApp, ruleOfApproval } from '../app-rules/AppRulesCard';
@@ -67,7 +68,7 @@ function focusComposer() {
67
68
  const composer = document.querySelector('[data-chat-composer] textarea, [data-chat-composer] [contenteditable="true"]');
68
69
  composer?.focus();
69
70
  }
70
- export function LoopAssistant({ app, person, appId, }) {
71
+ export function LoopAssistant({ app, person, appId, decisions, }) {
71
72
  const contributed = useContributions(LoopAssistantCharacter).map(entry => entry.value);
72
73
  const chosen = assistantCharacterFor(contributed, { app, person });
73
74
  const turnEntries = useContributions(LoopChatTurn);
@@ -110,6 +111,9 @@ export function LoopAssistant({ app, person, appId, }) {
110
111
  }, [open, saying?.id]);
111
112
  const stageRef = useRef(null);
112
113
  const drag = useViewportDrag(stageRef, { whole: true });
114
+ const decide = useMemo(() => decisions?.serverUrl
115
+ ? decisionsAskerAt(decisions.serverUrl, decisions.token)
116
+ : undefined, [decisions?.serverUrl, decisions?.token]);
113
117
  if (away !== 'none') {
114
118
  return null;
115
119
  }
@@ -162,16 +166,21 @@ export function LoopAssistant({ app, person, appId, }) {
162
166
  };
163
167
  return (_jsxs(_Fragment, { children: [appId && signedIn ? (_jsx(WaitingApprovals, { appId: appId, onWaiting: setApproval })) : null, _jsx(AssistantStage, { character: chosen.character, state: state, size: SIZE, place: drag.position
164
168
  ? { left: `${drag.position.left}px`, top: `${drag.position.top}px` }
165
- : { right: '24px', bottom: '24px' }, stageRef: stageRef, onDragStart: drag.onHandlePointerDown, open: open, onToggle: onToggle, balloon: balloon, insist: insist, onDismiss: onDismiss })] }));
169
+ : { right: '24px', bottom: '24px' }, stageRef: stageRef, onDragStart: drag.onHandlePointerDown, open: open, onToggle: onToggle, balloon: balloon, insist: insist, onDismiss: onDismiss, decide: decide })] }));
166
170
  }
167
171
  export const LoopAssistantPlugin = definePlugin({
168
172
  name: LOOP_ASSISTANT_PLUGIN_NAME,
169
- config: { app: undefined, person: undefined, appId: undefined },
173
+ config: {
174
+ app: undefined,
175
+ person: undefined,
176
+ appId: undefined,
177
+ decisions: undefined,
178
+ },
170
179
  displayName: 'Floating assistant',
171
180
  description: 'The chat as a character on the page: the application’s own, or the one the person chose, from the characters the enabled plugins contribute.',
172
181
  octicon: 'paperclip',
173
182
  build: ({ config }) => {
174
- const Configured = () => (_jsx(LoopAssistant, { app: config.app, person: config.person, appId: config.appId }));
183
+ const Configured = () => (_jsx(LoopAssistant, { app: config.app, person: config.person, appId: config.appId, decisions: config.decisions }));
175
184
  return {
176
185
  components: [
177
186
  { id: 'loop-assistant', slot: LoopSlots.root, Component: Configured },
@@ -11,7 +11,7 @@ import type { ActionClass, AppBehaviour, AppEscalation, ServerActionsSpec } from
11
11
  /** The classes of action, from the one that changes nothing. */
12
12
  export declare const ACTION_CLASSES: ActionClass[];
13
13
  /** What each tool of the tools catalogue does, by id. */
14
- export declare const TOOL_ACTIONS: Record<string, ActionClass[]>;
14
+ export declare const BACKEND_TOOL_ACTIONS: Record<string, ActionClass[]>;
15
15
  /**
16
16
  * What each MCP server's tools do, by server id. `checked` is the day the
17
17
  * names were read off the running server; absent when nobody looked.
@@ -12,7 +12,7 @@ export const ACTION_CLASSES = [
12
12
  'publish',
13
13
  ];
14
14
  /** What each tool of the tools catalogue does, by id. */
15
- export const TOOL_ACTIONS = {
15
+ export const BACKEND_TOOL_ACTIONS = {
16
16
  'create-plan': ['read'],
17
17
  'current-time': ['read'],
18
18
  decide: ['read'],