@datalayer/agent-runtimes 1.5.41 → 1.5.43

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.
@@ -61,8 +61,21 @@ export declare const SESSION_RULES_WORDS: {
61
61
  readonly goneWithSession: "In force at once, for this session only; gone when it ends.";
62
62
  readonly unavailable: (sentence: string) => string;
63
63
  };
64
- /** A rule in words: *writing, sending: leave it to me*. */
64
+ /** Where a file rule holds, in words (SHIELD decision 5). */
65
+ export declare const WHERE_WORDS: Record<'inside' | 'outside', string>;
66
+ /**
67
+ * A rule in words: *writing, sending: leave it to me*; with decision 5's file
68
+ * and shell wording when it says where or what (SHIELD SH-43) — *writing
69
+ * inside the workspace (src/**): do it*, *running `git push`: ask me first*.
70
+ */
65
71
  export declare function sessionRuleInWords(rule: SessionRule): string;
72
+ /**
73
+ * Decision 5's file and shell rules, offered on a running session in the
74
+ * Shield's own words: each one a rule a person adds as it stands, and the
75
+ * runtime refuses one that would loosen what the organization says
76
+ * (SHIELD §6.2, §6.3).
77
+ */
78
+ export declare const FILE_AND_SHELL_RULES: readonly SessionRule[];
66
79
  /** What the person typed, as the rule the runtime takes; a sentence when it cannot be one. */
67
80
  export declare function sessionRuleOf(input: {
68
81
  name: string;
@@ -54,12 +54,78 @@ export const SESSION_RULES_WORDS = {
54
54
  goneWithSession: 'In force at once, for this session only; gone when it ends.',
55
55
  unavailable: (sentence) => `Nothing is decided right now: ${sentence}`,
56
56
  };
57
- /** A rule in words: *writing, sending: leave it to me*. */
57
+ /** Where a file rule holds, in words (SHIELD decision 5). */
58
+ export const WHERE_WORDS = {
59
+ inside: 'inside the workspace',
60
+ outside: 'outside the workspace',
61
+ };
62
+ /**
63
+ * A rule in words: *writing, sending: leave it to me*; with decision 5's file
64
+ * and shell wording when it says where or what (SHIELD SH-43) — *writing
65
+ * inside the workspace (src/**): do it*, *running `git push`: ask me first*.
66
+ */
58
67
  export function sessionRuleInWords(rule) {
59
68
  const classes = (rule.classes ?? []).map(item => SESSION_RULES_WORDS.classes[item] ?? item);
60
- const covers = classes.length > 0 ? classes.join(', ') : 'everything';
69
+ let covers = classes.length > 0 ? classes.join(', ') : 'everything';
70
+ if (rule.where === 'inside' || rule.where === 'outside') {
71
+ covers = `${covers} ${WHERE_WORDS[rule.where]}`;
72
+ }
73
+ if (rule.paths && rule.paths.length > 0) {
74
+ covers = `${covers} (${rule.paths.join(', ')})`;
75
+ }
76
+ if (rule.commands && rule.commands.length > 0) {
77
+ covers = `${covers} matching ${rule.commands.map(command => `\`${command}\``).join(' or ')}`;
78
+ }
61
79
  return `${covers}: ${SESSION_RULES_WORDS.decisions[rule.decision]}`;
62
80
  }
81
+ /**
82
+ * Decision 5's file and shell rules, offered on a running session in the
83
+ * Shield's own words: each one a rule a person adds as it stands, and the
84
+ * runtime refuses one that would loosen what the organization says
85
+ * (SHIELD §6.2, §6.3).
86
+ */
87
+ export const FILE_AND_SHELL_RULES = [
88
+ {
89
+ name: 'Edit inside the workspace',
90
+ decision: 'allow',
91
+ classes: ['write'],
92
+ where: 'inside',
93
+ },
94
+ {
95
+ name: 'Ask before deleting a file',
96
+ decision: 'ask',
97
+ classes: ['delete'],
98
+ },
99
+ {
100
+ name: 'Ask before running a command',
101
+ decision: 'ask',
102
+ classes: ['execute'],
103
+ },
104
+ {
105
+ name: 'Leave files outside the workspace to me',
106
+ decision: 'deny',
107
+ classes: ['read', 'write', 'delete'],
108
+ where: 'outside',
109
+ },
110
+ {
111
+ name: 'Ask before git push',
112
+ decision: 'ask',
113
+ classes: ['execute'],
114
+ commands: ['^git\\s+push\\b'],
115
+ },
116
+ {
117
+ name: 'Ask before installing a package',
118
+ decision: 'ask',
119
+ classes: ['execute'],
120
+ commands: ['\\b(pip|npm|uv|yarn|apt(-get)?)\\s+(install|add)\\b'],
121
+ },
122
+ {
123
+ name: 'Leave sudo to me',
124
+ decision: 'deny',
125
+ classes: ['execute'],
126
+ commands: ['^sudo\\b'],
127
+ },
128
+ ];
63
129
  /** What the person typed, as the rule the runtime takes; a sentence when it cannot be one. */
64
130
  export function sessionRuleOf(input) {
65
131
  const name = input.name.trim();
@@ -1,5 +1,5 @@
1
1
  /** The kinds of part an agent is built from on the Canvas. */
2
- export type CanvasPartKind = 'connection' | 'rule' | 'skill' | 'test' | 'event' | 'model' | 'protocol' | 'ui' | 'character';
2
+ export type CanvasPartKind = 'agent' | 'connection' | 'rule' | 'skill' | 'test' | 'event' | 'model' | 'protocol' | 'ui' | 'character';
3
3
  /**
4
4
  * A part of an agent the Canvas may place (LOOP C-20): a connection to a
5
5
  * server of the catalogue, a skill, a model, a rule, a test, an event, a
@@ -18,6 +18,12 @@ export type CanvasPartContribution = {
18
18
  /** One line of what it is. */
19
19
  says: string;
20
20
  emoji?: string;
21
+ /**
22
+ * The harness it runs on, for an agent part (SHIELD SH-43): the catalogue
23
+ * harness's id, whose terms, credential and what it honours the Properties
24
+ * panel reads from `specs/harnesses`.
25
+ */
26
+ harness?: string;
21
27
  /**
22
28
  * What it arrives with, spelled as the Appspec's text spells it: a
23
29
  * connection's, a rule's, a test's or an event's object, a skill's, a
@@ -5,7 +5,7 @@ import { Box } from '@datalayer/primer-addons';
5
5
  import { useIAMStore } from '@datalayer/core/lib/state/substates/IAMState';
6
6
  import { agentServerOf } from '../../core';
7
7
  import { BEHAVIOUR_WORDS, coverOf } from '../../apps/rules';
8
- import { SESSION_RULES_WORDS, SESSION_RULE_CLASSES, SESSION_RULE_DECISIONS, addSessionRule, readSessionRules, removeSessionRule, sessionRuleInWords, sessionRuleOf, } from '../../apps/sessionRules';
8
+ import { FILE_AND_SHELL_RULES, SESSION_RULES_WORDS, SESSION_RULE_CLASSES, SESSION_RULE_DECISIONS, addSessionRule, readSessionRules, removeSessionRule, sessionRuleInWords, sessionRuleOf, } from '../../apps/sessionRules';
9
9
  import { SAVE_WORDS, draftOfApproval } from '../../apps/saved';
10
10
  import { useApproveToolRequest, useRejectToolRequest, useToolApprovalsQuery, } from '../../../hooks/useToolApprovals';
11
11
  /** What the card says. */
@@ -105,7 +105,7 @@ export function SessionRules({ agentServerUrl, sessionUid, token, }) {
105
105
  }
106
106
  void act(() => addSessionRule(agentServerUrl, token, sessionUid, rule)).then(() => setName(''));
107
107
  };
108
- return (_jsxs(Box, { "data-testid": "session-rules", mt: 3, children: [_jsx(Heading, { as: "h4", sx: { fontSize: 1, mb: 1 }, children: SESSION_RULES_WORDS.title }), inForce?.unavailable ? (_jsx(Text, { as: "p", role: "alert", sx: { fontSize: 0, m: 0, mb: 1 }, children: SESSION_RULES_WORDS.unavailable(inForce.unavailable) })) : null, inForce && inForce.session_rules.length === 0 ? (_jsx(Text, { as: "p", sx: { fontSize: 1, color: 'fg.muted', m: 0 }, children: SESSION_RULES_WORDS.none })) : (_jsx(Box, { as: "ul", listStyle: "none", p: 0, m: 0, children: (inForce?.session_rules ?? []).map(rule => (_jsxs(Box, { as: "li", py: 1, fontSize: 1, display: "flex", gap: 2, alignItems: "center", children: [_jsx(Text, { sx: { fontWeight: 'semibold' }, children: rule.name }), _jsx(Text, { sx: { color: 'fg.muted', fontSize: 0 }, children: sessionRuleInWords(rule) }), _jsx(Button, { size: "small", variant: "invisible", disabled: busy, onClick: () => void act(() => removeSessionRule(agentServerUrl, token, sessionUid, rule.name)), children: SESSION_RULES_WORDS.remove })] }, rule.name))) })), _jsxs(Box, { display: "flex", gap: 1, mt: 1, flexWrap: "wrap", alignItems: "center", children: [_jsx(TextInput, { size: "small", value: name, placeholder: SESSION_RULES_WORDS.namePlaceholder, "aria-label": "The rule's name", onChange: event => setName(event.target.value) }), _jsx(Select, { size: "small", value: kind, "aria-label": "What it covers", onChange: event => setKind(event.target.value), children: SESSION_RULE_CLASSES.map(item => (_jsx(Select.Option, { value: item, children: SESSION_RULES_WORDS.classes[item] }, item))) }), _jsx(Select, { size: "small", value: decision, "aria-label": "What it says", onChange: event => setDecision(event.target.value), children: SESSION_RULE_DECISIONS.map(item => (_jsx(Select.Option, { value: item, children: SESSION_RULES_WORDS.decisions[item] }, item))) }), _jsx(Button, { size: "small", disabled: busy, onClick: add, children: SESSION_RULES_WORDS.add })] }), problem ? (_jsx(Text, { as: "p", role: "alert", sx: { fontSize: 0, m: 0, mt: 1 }, children: problem })) : null, _jsx(Text, { as: "p", sx: { fontSize: 0, color: 'fg.muted', mt: 1, mb: 0 }, children: SESSION_RULES_WORDS.goneWithSession })] }));
108
+ return (_jsxs(Box, { "data-testid": "session-rules", mt: 3, children: [_jsx(Heading, { as: "h4", sx: { fontSize: 1, mb: 1 }, children: SESSION_RULES_WORDS.title }), inForce?.unavailable ? (_jsx(Text, { as: "p", role: "alert", sx: { fontSize: 0, m: 0, mb: 1 }, children: SESSION_RULES_WORDS.unavailable(inForce.unavailable) })) : null, inForce && inForce.session_rules.length === 0 ? (_jsx(Text, { as: "p", sx: { fontSize: 1, color: 'fg.muted', m: 0 }, children: SESSION_RULES_WORDS.none })) : (_jsx(Box, { as: "ul", listStyle: "none", p: 0, m: 0, children: (inForce?.session_rules ?? []).map(rule => (_jsxs(Box, { as: "li", py: 1, fontSize: 1, display: "flex", gap: 2, alignItems: "center", children: [_jsx(Text, { sx: { fontWeight: 'semibold' }, children: rule.name }), _jsx(Text, { sx: { color: 'fg.muted', fontSize: 0 }, children: sessionRuleInWords(rule) }), _jsx(Button, { size: "small", variant: "invisible", disabled: busy, onClick: () => void act(() => removeSessionRule(agentServerUrl, token, sessionUid, rule.name)), children: SESSION_RULES_WORDS.remove })] }, rule.name))) })), _jsxs(Box, { display: "flex", gap: 1, mt: 1, flexWrap: "wrap", alignItems: "center", children: [_jsx(TextInput, { size: "small", value: name, placeholder: SESSION_RULES_WORDS.namePlaceholder, "aria-label": "The rule's name", onChange: event => setName(event.target.value) }), _jsx(Select, { size: "small", value: kind, "aria-label": "What it covers", onChange: event => setKind(event.target.value), children: SESSION_RULE_CLASSES.map(item => (_jsx(Select.Option, { value: item, children: SESSION_RULES_WORDS.classes[item] }, item))) }), _jsx(Select, { size: "small", value: decision, "aria-label": "What it says", onChange: event => setDecision(event.target.value), children: SESSION_RULE_DECISIONS.map(item => (_jsx(Select.Option, { value: item, children: SESSION_RULES_WORDS.decisions[item] }, item))) }), _jsx(Button, { size: "small", disabled: busy, onClick: add, children: SESSION_RULES_WORDS.add })] }), _jsx(Box, { display: "flex", gap: 1, mt: 1, flexWrap: "wrap", "data-testid": "session-rules-file-and-shell", children: FILE_AND_SHELL_RULES.map(rule => (_jsx(Button, { size: "small", variant: "invisible", disabled: busy, title: sessionRuleInWords(rule), onClick: () => void act(() => addSessionRule(agentServerUrl, token, sessionUid, rule)), children: rule.name }, rule.name))) }), problem ? (_jsx(Text, { as: "p", role: "alert", sx: { fontSize: 0, m: 0, mt: 1 }, children: problem })) : null, _jsx(Text, { as: "p", sx: { fontSize: 0, color: 'fg.muted', mt: 1, mb: 0 }, children: SESSION_RULES_WORDS.goneWithSession })] }));
109
109
  }
110
110
  export function AppRulesCard({ app, workspace, }) {
111
111
  const token = useIAMStore(state => state.token) ?? '';
@@ -15,9 +15,17 @@ type PartsReader = {
15
15
  }>;
16
16
  };
17
17
  /** The short ids an organization turns off, and the plugin each names. */
18
- export declare const CANVAS_PART_SOURCES: readonly ["connections", "skills", "models", "rules", "tests", "events", "protocols", "ui", "characters"];
18
+ export declare const CANVAS_PART_SOURCES: readonly ["agents", "connections", "skills", "models", "rules", "tests", "events", "protocols", "ui", "characters"];
19
19
  export type CanvasPartSource = (typeof CANVAS_PART_SOURCES)[number];
20
20
  export declare const canvasPartsPluginName: (source: CanvasPartSource) => string;
21
+ /**
22
+ * The catalogue's harnesses as the agent (SHIELD SH-43): each harness an
23
+ * agent program over ACP behind the Shield, offered by the catalogue agent
24
+ * that runs on it — dropped on the surface, it is the application's agent.
25
+ * A harness no agent of the catalogue runs yet is offered off, and says so;
26
+ * `custom` is an organization's own and never offered here.
27
+ */
28
+ export declare const AGENT_PARTS: CanvasPartContribution[];
21
29
  /** A connection to each server of the catalogue: read-only, as its owner, every tool. */
22
30
  export declare const CONNECTION_PARTS: CanvasPartContribution[];
23
31
  /** Each skill of the catalogue. */
@@ -13,6 +13,8 @@
13
13
  */
14
14
  import { buildReactorFromPlugins, contribution, definePlugin, } from '@datalayer/reactor';
15
15
  import { MCP_SERVER_LIBRARY } from '../../../specs/mcpServers';
16
+ import { listAgentspecs } from '../../../specs/agents';
17
+ import { listHarnesses } from '../../../specs/harnesses';
16
18
  import { listChatModels } from '../../../specs/models';
17
19
  import { getSkillSpecs } from '../../../specs/skills';
18
20
  import { listProtocolSpecs } from '../../../specs/protocols';
@@ -21,6 +23,7 @@ import { ASSISTANT_CHARACTERS } from '../../../chat/assistant/characters';
21
23
  import { LoopCanvasPart, } from '../../core/canvasParts';
22
24
  /** The short ids an organization turns off, and the plugin each names. */
23
25
  export const CANVAS_PART_SOURCES = [
26
+ 'agents',
24
27
  'connections',
25
28
  'skills',
26
29
  'models',
@@ -39,6 +42,30 @@ const partsPlugin = (source, displayName, parts) => definePlugin({
39
42
  octicon: 'package',
40
43
  contributes: parts.map(part => contribution(LoopCanvasPart, part, { id: part.id })),
41
44
  });
45
+ /**
46
+ * The catalogue's harnesses as the agent (SHIELD SH-43): each harness an
47
+ * agent program over ACP behind the Shield, offered by the catalogue agent
48
+ * that runs on it — dropped on the surface, it is the application's agent.
49
+ * A harness no agent of the catalogue runs yet is offered off, and says so;
50
+ * `custom` is an organization's own and never offered here.
51
+ */
52
+ export const AGENT_PARTS = listHarnesses()
53
+ .filter(harness => harness.id !== 'custom')
54
+ .map(harness => {
55
+ const agent = listAgentspecs().find(spec => spec.harness === harness.id);
56
+ return {
57
+ id: `agent:${harness.id}`,
58
+ kind: 'agent',
59
+ label: harness.name,
60
+ says: agent
61
+ ? `${agent.name}: ${harness.description}`
62
+ : `${harness.description} No agent of the catalogue runs on it yet.`,
63
+ emoji: '🛡️',
64
+ harness: harness.id,
65
+ example: agent?.id ?? '',
66
+ enabled: Boolean(agent) && harness.enabled,
67
+ };
68
+ });
42
69
  /** A connection to each server of the catalogue: read-only, as its owner, every tool. */
43
70
  export const CONNECTION_PARTS = Object.values(MCP_SERVER_LIBRARY).map(server => ({
44
71
  id: `connection:${server.id}`,
@@ -210,6 +237,7 @@ export const CHARACTER_PARTS = ASSISTANT_CHARACTERS.map(character => ({
210
237
  enabled: true,
211
238
  }));
212
239
  export const CANVAS_PART_PLUGINS = [
240
+ partsPlugin('agents', 'Agents', AGENT_PARTS),
213
241
  partsPlugin('connections', 'Connections', CONNECTION_PARTS),
214
242
  partsPlugin('skills', 'Skills', SKILL_PARTS),
215
243
  partsPlugin('models', 'Models', MODEL_PARTS),
@@ -0,0 +1,26 @@
1
+ /**
2
+ * A coding agent behind the Shield, in the web chat (SHIELD §4.3, SH-16; SH-23).
3
+ *
4
+ * The chat talks AG-UI to a local runtime whose agent is a harness: what the
5
+ * agent changed is drawn as a **diff** in the file's language, what it ran
6
+ * as a **terminal** with its output, through the `shield-displays` plugin
7
+ * the Loop preset mounts (here, registered on its own); an action a rule
8
+ * asks first about waits in the chat's approval banner, answered there.
9
+ *
10
+ * It needs the local runtime with a harness, which this page does not
11
+ * start. From agent-runtimes' `examples/shield`:
12
+ *
13
+ * python harness_on_the_runtime.py --serve
14
+ *
15
+ * serves the fake agent (no model, no key) as the agent `chat` on
16
+ * http://127.0.0.1:8765, its workspace a fresh directory; with
17
+ * `--harness claude-code` and a funded `ANTHROPIC_API_KEY`, Claude Code
18
+ * instead. The openers below are the fake agent's words: `edit` writes
19
+ * `hello.txt` (do it, shown as a diff), `run echo hello` asks first (the
20
+ * banner), `outside` reads outside the workspace (refused in a sentence).
21
+ *
22
+ * @module examples/ShieldHarnessExample
23
+ */
24
+ import React from 'react';
25
+ declare const ShieldHarnessExample: React.FC;
26
+ export default ShieldHarnessExample;
@@ -0,0 +1,51 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /*
3
+ * Copyright (c) 2025-2026 Datalayer, Inc.
4
+ * Distributed under the terms of the Modified BSD License.
5
+ */
6
+ /**
7
+ * A coding agent behind the Shield, in the web chat (SHIELD §4.3, SH-16; SH-23).
8
+ *
9
+ * The chat talks AG-UI to a local runtime whose agent is a harness: what the
10
+ * agent changed is drawn as a **diff** in the file's language, what it ran
11
+ * as a **terminal** with its output, through the `shield-displays` plugin
12
+ * the Loop preset mounts (here, registered on its own); an action a rule
13
+ * asks first about waits in the chat's approval banner, answered there.
14
+ *
15
+ * It needs the local runtime with a harness, which this page does not
16
+ * start. From agent-runtimes' `examples/shield`:
17
+ *
18
+ * python harness_on_the_runtime.py --serve
19
+ *
20
+ * serves the fake agent (no model, no key) as the agent `chat` on
21
+ * http://127.0.0.1:8765, its workspace a fresh directory; with
22
+ * `--harness claude-code` and a funded `ANTHROPIC_API_KEY`, Claude Code
23
+ * instead. The openers below are the fake agent's words: `edit` writes
24
+ * `hello.txt` (do it, shown as a diff), `run echo hello` asks first (the
25
+ * banner), `outside` reads outside the workspace (refused in a sentence).
26
+ *
27
+ * @module examples/ShieldHarnessExample
28
+ */
29
+ import { useEffect, useMemo } from 'react';
30
+ import { Text } from '@primer/react';
31
+ import { Box } from '@datalayer/primer-addons';
32
+ import { buildReactorFromPlugins } from '@datalayer/reactor';
33
+ import { registerReactor } from '@datalayer/reactor/react';
34
+ import { ThemedProvider } from './utils/themedProvider';
35
+ import { resolveExampleAgentRuntimesUrl } from './utils/useExampleAgentRuntimesUrl';
36
+ import { Chat } from '../chat';
37
+ import { createShieldDisplaysPlugin } from '../apps/plugins/shield-displays';
38
+ import { SHIELD_HARNESS_AGENT, SHIELD_HARNESS_OPENERS, } from './shield/shieldHarness';
39
+ const ShieldHarnessExample = () => {
40
+ const baseUrl = resolveExampleAgentRuntimesUrl('local');
41
+ // The diff and terminal renderers, registered for the chat to resolve
42
+ // `_meta["reactor.ui/display"]` with — what the Loop preset does.
43
+ const reactor = useMemo(() => buildReactorFromPlugins([createShieldDisplaysPlugin()]), []);
44
+ useEffect(() => {
45
+ reactor.start();
46
+ registerReactor(reactor);
47
+ return () => registerReactor(null);
48
+ }, [reactor]);
49
+ return (_jsx(ThemedProvider, { children: _jsxs(Box, { display: "flex", flexDirection: "column", height: "100vh", minHeight: 0, children: [_jsx(Box, { p: 3, borderBottom: "1px solid", borderColor: "border.default", children: _jsxs(Text, { as: "p", sx: { m: 0, fontSize: 1, color: 'fg.muted' }, children: ["Needs the local runtime with a harness:", ' ', _jsx("code", { children: "python examples/shield/harness_on_the_runtime.py --serve" }), ' ', "(the fake agent on ", baseUrl, ", no key)."] }) }), _jsx(Box, { flex: 1, minHeight: 0, children: _jsx(Chat, { protocol: "ag-ui", baseUrl: baseUrl, agentId: SHIELD_HARNESS_AGENT, title: "A coding agent behind the Shield", description: "Its changes as diffs, its commands as terminals, what a rule asks first in the banner.", showToolApprovalBanner: true, suggestions: SHIELD_HARNESS_OPENERS, height: "100%" }) })] }) }));
50
+ };
51
+ export default ShieldHarnessExample;
@@ -0,0 +1,18 @@
1
+ /**
2
+ * The Shield's policy, from TypeScript (SHIELD §5.1, SH-53; SH-23).
3
+ *
4
+ * An organization's document typed by `@datalayer/agent-shield` (the types
5
+ * generated from the schema the Python package publishes), what it decides
6
+ * for each class, how ACP's tool kinds are classed, the limits soft then
7
+ * hard, and decisions in the words `describeDecision` gives every card and
8
+ * row. Needs nothing running: it reads the package only.
9
+ *
10
+ * `@datalayer/agent-shield` is not on npm yet: the page reads it from the
11
+ * sibling checkout `ai/agent-shield` beside agent-runtimes (see
12
+ * `shield/shieldPolicyDemo`), and says so where it is not there.
13
+ *
14
+ * @module examples/ShieldPolicyExample
15
+ */
16
+ import React from 'react';
17
+ declare const ShieldPolicyExample: React.FC;
18
+ export default ShieldPolicyExample;
@@ -0,0 +1,50 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ /*
3
+ * Copyright (c) 2025-2026 Datalayer, Inc.
4
+ * Distributed under the terms of the Modified BSD License.
5
+ */
6
+ /**
7
+ * The Shield's policy, from TypeScript (SHIELD §5.1, SH-53; SH-23).
8
+ *
9
+ * An organization's document typed by `@datalayer/agent-shield` (the types
10
+ * generated from the schema the Python package publishes), what it decides
11
+ * for each class, how ACP's tool kinds are classed, the limits soft then
12
+ * hard, and decisions in the words `describeDecision` gives every card and
13
+ * row. Needs nothing running: it reads the package only.
14
+ *
15
+ * `@datalayer/agent-shield` is not on npm yet: the page reads it from the
16
+ * sibling checkout `ai/agent-shield` beside agent-runtimes (see
17
+ * `shield/shieldPolicyDemo`), and says so where it is not there.
18
+ *
19
+ * @module examples/ShieldPolicyExample
20
+ */
21
+ import { useEffect, useState } from 'react';
22
+ import { Heading, Text } from '@primer/react';
23
+ import { Box } from '@datalayer/primer-addons';
24
+ import { ThemedProvider } from './utils/themedProvider';
25
+ import { AGENT_SHIELD_MISSING, ORGANIZATION_POLICY, classRows, decisionRows, kindRows, limitRows, loadAgentShield, } from './shield/shieldPolicyDemo';
26
+ const cell = {
27
+ borderBottom: '1px solid',
28
+ borderColor: 'border.muted',
29
+ py: 1,
30
+ pr: 3,
31
+ };
32
+ function Table({ head, rows, }) {
33
+ return (_jsxs(Box, { as: "table", sx: { borderCollapse: 'collapse', fontSize: 1, mb: 4 }, children: [_jsx("thead", { children: _jsx("tr", { children: head.map(title => (_jsx(Box, { as: "th", sx: { ...cell, textAlign: 'left' }, children: title }, title))) }) }), _jsx("tbody", { children: rows.map(row => (_jsx("tr", { children: row.map((value, index) => (_jsx(Box, { as: "td", sx: cell, children: value }, index))) }, row.join('|')))) })] }));
34
+ }
35
+ function Tables({ shield, }) {
36
+ return (_jsxs(_Fragment, { children: [_jsx(Heading, { as: "h2", sx: { fontSize: 2, mb: 2 }, children: "By class" }), _jsx(Table, { head: ['Class', `The ${ORGANIZATION_POLICY.scope}'s document says`], rows: classRows(shield).map(row => [row.actionClass, row.says]) }), _jsx(Heading, { as: "h2", sx: { fontSize: 2, mb: 2 }, children: "A coding agent's tool kinds" }), _jsx(Table, { head: ['ACP kind', 'Decided as'], rows: kindRows(shield).map(row => [row.kind, row.actionClass]) }), _jsx(Heading, { as: "h2", sx: { fontSize: 2, mb: 2 }, children: "Limits, soft then hard" }), _jsx(Table, { head: ['Limit', 'Soft: asks once', 'Hard: refuses'], rows: limitRows(shield).map(row => [row.limit, row.soft, row.hard]) }), _jsx(Heading, { as: "h2", sx: { fontSize: 2, mb: 2 }, children: "Decisions, in their words" }), _jsx(Table, { head: ['The Shield said', 'describeDecision'], rows: decisionRows(shield).map(row => [row.sentence, row.words]) })] }));
37
+ }
38
+ const ShieldPolicyExample = () => {
39
+ // `undefined` while reading, `null` without the sibling checkout.
40
+ const [shield, setShield] = useState(undefined);
41
+ useEffect(() => {
42
+ let live = true;
43
+ void loadAgentShield().then(loaded => live && setShield(loaded));
44
+ return () => {
45
+ live = false;
46
+ };
47
+ }, []);
48
+ return (_jsx(ThemedProvider, { children: _jsxs(Box, { p: 4, maxWidth: "900px", mx: "auto", "data-testid": "shield-policy-example", children: [_jsx(Heading, { as: "h1", sx: { fontSize: 4, mb: 2 }, children: "The Shield's policy, typed" }), _jsx(Text, { as: "p", sx: { color: 'fg.muted', mb: 4 }, children: "An organization's document in `@datalayer/agent-shield`'s shape, and what it decides. Three levels decide every action \u2014 the organization's, the application's, the session's \u2014 strictest first; a lower level only tightens." }), shield === undefined ? (_jsx(Text, { as: "p", children: "Reading @datalayer/agent-shield\u2026" })) : shield === null ? (_jsx(Text, { as: "p", sx: { color: 'attention.fg' }, children: AGENT_SHIELD_MISSING })) : (_jsx(Tables, { shield: shield }))] }) }));
49
+ };
50
+ export default ShieldPolicyExample;
@@ -133,6 +133,9 @@ export const EXAMPLE_ENTRIES = [
133
133
  makeEntry('AgentA2ATeamExample', () => import('./AgentA2ATeamExample'), 'Two applications as a team over A2A: Sales in the browser (@a2a-js/sdk) asks Accounting on a runtime (fasta2a, Odoo read only), each an Office Assistant character.'),
134
134
  makeEntry('ProtocolSignedRequestsExample', () => import('./ProtocolSignedRequestsExample'), 'Web Bot Auth in the browser: a key made in the page signs a request — its three headers and the signature base — verified, then a replay and a changed path refused.'),
135
135
  makeEntry('ProtocolUcpCheckoutExample', () => import('./ProtocolUcpCheckoutExample'), 'A UCP checkout at a test store in the page: the exact purchase proposed, approved on a card once, bought once.'),
136
+ // The Shield (SHIELD.md SH-23): a coding agent behind it, and its policy's types.
137
+ makeEntry('ShieldHarnessExample', () => import('./ShieldHarnessExample'), 'A coding agent behind the Shield in the web chat: its changes as diffs, its commands as terminals, what a rule asks first in the approval banner. Needs `python examples/shield/harness_on_the_runtime.py --serve` (the fake agent, no key).', ['example', 'shield', 'harness', 'acp', 'ag-ui', 'chat']),
138
+ makeEntry('ShieldPolicyExample', () => import('./ShieldPolicyExample'), 'The Shield’s policy from TypeScript: an organization’s document typed by @datalayer/agent-shield, what it decides by class, the tool kinds classed, the limits soft then hard, decisions in describeDecision’s words.', ['example', 'shield', 'policy']),
136
139
  makeEntry('PapCompanyDiscoveryExample', () => import('./PapCompanyDiscoveryExample'), 'Validate a PAP company document and reduce it to the public capabilities safe for an agent or UI.'),
137
140
  makeEntry('PapAgentIdentityExample', () => import('./PapAgentIdentityExample'), 'Verify personal-agent client metadata and signing policy without exposing JWK coordinates.'),
138
141
  makeEntry('PapAuthorizationBoundaryExample', () => import('./PapAuthorizationBoundaryExample'), 'Generate real Session and Direct Sign-In security material while showing only safe policy status.'),
@@ -5,5 +5,5 @@
5
5
  *
6
6
  * @module examples/exampleGroups
7
7
  */
8
- export declare const EXAMPLE_GROUP_ORDER: readonly ["Apps", "Assistant", "A2UI", "A2A", "Personal Agent Protocol", "Agent protocols", "AG-UI", "Chat", "Document", "Notebook", "Capabilities", "Cell", "CopilotKit"];
8
+ export declare const EXAMPLE_GROUP_ORDER: readonly ["Apps", "Assistant", "A2UI", "A2A", "Personal Agent Protocol", "Agent protocols", "Shield", "AG-UI", "Chat", "Document", "Notebook", "Capabilities", "Cell", "CopilotKit"];
9
9
  export declare const getExampleGroup: (id: string) => string;
@@ -16,6 +16,7 @@ export const EXAMPLE_GROUP_ORDER = [
16
16
  'A2A',
17
17
  'Personal Agent Protocol',
18
18
  'Agent protocols',
19
+ 'Shield',
19
20
  'AG-UI',
20
21
  'Chat',
21
22
  'Document',
@@ -43,6 +44,9 @@ export const getExampleGroup = (id) => {
43
44
  // The other agent protocols' examples: signed requests, commerce (AGENT_PROTOCOLS EX-10).
44
45
  if (id.startsWith('Protocol'))
45
46
  return 'Agent protocols';
47
+ // The Shield: a coding agent behind it, and its policy (SHIELD.md SH-23).
48
+ if (id.startsWith('Shield'))
49
+ return 'Shield';
46
50
  if (id.startsWith('AgUi'))
47
51
  return 'AG-UI';
48
52
  if (id.startsWith('CopilotKit'))
@@ -0,0 +1,14 @@
1
+ /**
2
+ * What `ShieldHarnessExample` connects to and offers (SHIELD SH-23).
3
+ *
4
+ * `examples/shield/harness_on_the_runtime.py --serve` names the harness's
5
+ * agent `chat`, as `loop --harness` does with `AGENT_RUNTIMES_AGENT_NAME`;
6
+ * the openers are the fake agent's words (its docstring says what each does).
7
+ *
8
+ * @module examples/shield/shieldHarness
9
+ */
10
+ import type { Suggestion } from '../../types/chat';
11
+ /** The agent the local runtime serves the harness as. */
12
+ export declare const SHIELD_HARNESS_AGENT = "chat";
13
+ /** What to ask the fake agent: one of each decision. */
14
+ export declare const SHIELD_HARNESS_OPENERS: Suggestion[];
@@ -0,0 +1,21 @@
1
+ /*
2
+ * Copyright (c) 2025-2026 Datalayer, Inc.
3
+ * Distributed under the terms of the Modified BSD License.
4
+ */
5
+ /** The agent the local runtime serves the harness as. */
6
+ export const SHIELD_HARNESS_AGENT = 'chat';
7
+ /** What to ask the fake agent: one of each decision. */
8
+ export const SHIELD_HARNESS_OPENERS = [
9
+ {
10
+ title: 'Edit a file',
11
+ message: 'edit and read README.md',
12
+ },
13
+ {
14
+ title: 'Run a command',
15
+ message: 'run echo hello',
16
+ },
17
+ {
18
+ title: 'Read outside the workspace',
19
+ message: 'outside',
20
+ },
21
+ ];
@@ -0,0 +1,85 @@
1
+ /**
2
+ * What `ShieldPolicyExample` draws, as data (SHIELD §5.1, SH-53; SH-23).
3
+ *
4
+ * `@datalayer/agent-shield` is the Shield's TypeScript: the policy
5
+ * document's types generated from the schema the Python package publishes,
6
+ * the classes and the verdicts, ACP's tool kinds mapped to the classes, the
7
+ * limits' words, and `describeDecision` — the one place a decision's words
8
+ * are written. It is not on npm yet (npm's latest is an old scaffold), so
9
+ * agent-runtimes does not depend on it: this page reads it from the sibling
10
+ * checkout, `ai/agent-shield/src/policy.ts` beside `ai/agent-runtimes`, the
11
+ * way the monorepo lays them out — through Vite's `import.meta.glob`, which
12
+ * finds nothing (and the page says so) where the sibling is not there, as in
13
+ * agent-runtimes' own CI. The shapes below name only what the page reads;
14
+ * the types themselves are the package's.
15
+ *
16
+ * @module examples/shield/shieldPolicyDemo
17
+ */
18
+ export type Verdict = 'allow' | 'ask' | 'deny';
19
+ /** A decision as the Shield returns it (the package's `Decision`). */
20
+ export type Decision = {
21
+ verdict: Verdict;
22
+ sentence: string;
23
+ scope?: 'organization' | 'team' | 'session' | null;
24
+ rule?: string | null;
25
+ decided_by: 'rule' | 'limit' | 'network' | 'default';
26
+ };
27
+ /** The part of a policy document the page draws (the package's `Policy`). */
28
+ export type PolicyDocument = {
29
+ scope: 'organization' | 'team' | 'session';
30
+ rules?: Array<{
31
+ name: string;
32
+ decision: Verdict;
33
+ classes?: string[];
34
+ commands?: string[];
35
+ where?: 'inside' | 'outside' | 'any';
36
+ reason?: string;
37
+ }>;
38
+ limits?: Record<string, {
39
+ soft: number;
40
+ hard: number;
41
+ } | null | undefined>;
42
+ harnesses?: {
43
+ admit?: string[];
44
+ credentials?: 'account' | 'platform' | 'either';
45
+ };
46
+ };
47
+ /** What the page reads from `@datalayer/agent-shield`'s `policy.ts`. */
48
+ export type AgentShieldPolicyModule = {
49
+ ACTION_CLASSES: readonly string[];
50
+ TOOL_KINDS: readonly string[];
51
+ KIND_CLASS: Record<string, string | null>;
52
+ LIMIT_NAMES: readonly string[];
53
+ LIMIT_WORDS: Record<string, string>;
54
+ VERDICT_WORDS: Record<Verdict, string>;
55
+ describeDecision: (decision: Decision) => string;
56
+ };
57
+ /** What the page says when the sibling checkout is not beside this one. */
58
+ export declare const AGENT_SHIELD_MISSING = "This page reads @datalayer/agent-shield from the sibling checkout ai/agent-shield, which is not beside this one: clone github.com/datalayer/agent-shield next to agent-runtimes (the package is not on npm yet).";
59
+ /** `@datalayer/agent-shield`'s policy module, or `null` without the sibling checkout. */
60
+ export declare function loadAgentShield(): Promise<AgentShieldPolicyModule | null>;
61
+ /** An organization's document, in the package's shape: what an administrator writes. */
62
+ export declare const ORGANIZATION_POLICY: PolicyDocument;
63
+ /** One row per class: what the organization's document decides for it, in words. */
64
+ export declare function classRows(shield: AgentShieldPolicyModule, policy?: PolicyDocument): Array<{
65
+ actionClass: string;
66
+ says: string;
67
+ }>;
68
+ /** ACP's tool kinds and the class each is decided as (§4.5). */
69
+ export declare function kindRows(shield: AgentShieldPolicyModule): Array<{
70
+ kind: string;
71
+ actionClass: string;
72
+ }>;
73
+ /** The limits a document sets, soft then hard, in the sentences' words. */
74
+ export declare function limitRows(shield: AgentShieldPolicyModule, policy?: PolicyDocument): Array<{
75
+ limit: string;
76
+ soft: string;
77
+ hard: string;
78
+ }>;
79
+ /** Decisions as the Shield returns them. */
80
+ export declare const DECISIONS: Decision[];
81
+ /** Each decision and its words through `describeDecision`. */
82
+ export declare function decisionRows(shield: AgentShieldPolicyModule): Array<{
83
+ sentence: string;
84
+ words: string;
85
+ }>;