@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.
- package/dist-embed/datalayer-app-main.js +2 -2
- package/lib/apps/apps/sessionRules.d.ts +14 -1
- package/lib/apps/apps/sessionRules.js +68 -2
- package/lib/apps/core/canvasParts.d.ts +7 -1
- package/lib/apps/plugins/app-rules/AppRulesCard.js +2 -2
- package/lib/apps/plugins/canvas-parts/index.d.ts +9 -1
- package/lib/apps/plugins/canvas-parts/index.js +28 -0
- package/lib/examples/ShieldHarnessExample.d.ts +26 -0
- package/lib/examples/ShieldHarnessExample.js +51 -0
- package/lib/examples/ShieldPolicyExample.d.ts +18 -0
- package/lib/examples/ShieldPolicyExample.js +50 -0
- package/lib/examples/example-selector.js +3 -0
- package/lib/examples/exampleGroups.d.ts +1 -1
- package/lib/examples/exampleGroups.js +4 -0
- package/lib/examples/shield/shieldHarness.d.ts +14 -0
- package/lib/examples/shield/shieldHarness.js +21 -0
- package/lib/examples/shield/shieldPolicyDemo.d.ts +85 -0
- package/lib/examples/shield/shieldPolicyDemo.js +112 -0
- package/lib/specs/harnesses.d.ts +9 -1
- package/lib/specs/harnesses.js +513 -0
- package/lib/specs/policies.js +2 -1
- package/lib/specs/teams/teams.d.ts +5 -4
- package/lib/specs/teams/teams.js +84 -12
- package/lib/types/agentspecs.d.ts +47 -1
- package/lib/types/teams.d.ts +7 -1
- package/package.json +1 -1
- package/scripts/codegen/generate_harnesses.py +41 -3
- package/scripts/codegen/generate_teams.py +3 -0
|
@@ -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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
|
|
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
|
+
}>;
|