@audienti/cli 0.2.0-exora.29 → 0.2.0-exora.30
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/cli/audienti.mjs +3 -1
- package/cli/intent-operations.mjs +12 -3
- package/cli/intent.mjs +30 -6
- package/cli/playbook-help.mjs +1 -1
- package/cli/playbook-operations.mjs +3 -2
- package/cli/work-operations.mjs +1 -1
- package/cli/workspaces.mjs +2 -2
- package/package.json +1 -1
- package/skills/getting-started/references/replies.md +19 -1
- package/source-manifest.json +10 -10
package/cli/audienti.mjs
CHANGED
|
@@ -81,9 +81,10 @@ Discovery: prospects list|get ID|timeline ID [--limit N]|observation create|cita
|
|
|
81
81
|
budgets alerts --workspace ID [--limit N]
|
|
82
82
|
usage breakdown [--workspace ID] [--since month|7d|30d|90d] [--by tool|provider|model|caller|day] [--format json|csv]
|
|
83
83
|
workspace usage summary|callers|calls [--after ID] [--caller user:ID|agent:ID|key:ID] [--status all|succeeded|failed|pending]|call ID [--workspace ID] (paid tool use, last 30 days, and single calls)
|
|
84
|
-
intent list [--workspace ID] (the closed set of message labels: name, direction, event, channels, typed detail; output is JSON)
|
|
84
|
+
intent list [--playbook ID] [--workspace ID] (the closed set of message labels: name, direction, event, channels, typed detail; --playbook adds that playbook's own labels as customLabels; output is JSON)
|
|
85
85
|
intent suggest (--text TEXT | --text-file PATH) --channel email|linkedin [--direction inbound|outbound] [--context TEXT] [--request-id UUID] [--workspace ID] (paid: what a message may mean, read by a model; prints the price per 1M input tokens first; suggests labels with a quote and reason, records nothing; keep one with intent record)
|
|
86
86
|
intent record --person ID --evidence ID --label NAME --quote TEXT --reason TEXT [--confidence high|medium|low] [--request-id UUID] [--participation ID] [--workspace ID] (what a stored message means; get EVIDENCE from prospects conversation append|list; --file LABELS.json for several labels or a typed detail such as a stop result or a meeting time; every label is checked and a refusal says what to fix; a message you sent that asks for a meeting takes --label meeting_requested on its outbound evidence)
|
|
87
|
+
intent correct --person ID --event ID (--label NAME --quote TEXT --reason TEXT [--confidence high|medium|low] | --file LABEL.json | --retract) [--request-id UUID] [--workspace ID] (a person changes or withdraws one label; the old one stays in the history; a change that routes the person elsewhere opens a decision and a change away from a stop never lifts it until a person confirms; an agent token is refused)
|
|
87
88
|
decision list [--state open|decided|all] [--person ID] [--participation ID] [--limit N] [--workspace ID] (what Operator asks the person when the rules do not decide; an open decision holds its person)
|
|
88
89
|
decision options ID [--workspace ID] (one decision with the options the playbook allows)
|
|
89
90
|
decision ask --participation ID [--reason TEXT] [--request-id UUID] [--workspace ID] (ask the person to decide about one running person)
|
|
@@ -132,6 +133,7 @@ Errors name the MCP tool to call next, not the command.`;
|
|
|
132
133
|
const examples = {
|
|
133
134
|
'decision prepare': { recommended: 'snooze_30', options: [{ id: 'snooze_30', analysis: 'They said ask again in March, so wait a month.' }, { id: 'continue', analysis: 'Keep the plan; a follow-up may go this week.' }] },
|
|
134
135
|
'prospects conversation append': { requestId: 'UUID', item: { source: { provider: 'email', accountRef: 'you@yourcompany.com', itemId: 'PROVIDER_MESSAGE_ID', threadId: null }, payload: { direction: 'outbound', conversationType: 'direct', occurredAt: '2026-11-06T14:00:00Z', subject: 'Ten minutes on late invoices?', body: 'Hi Jordan, could we find 20 minutes next week?', participants: null } } },
|
|
136
|
+
'intent correct': { label: 'not_interested', quote: 'not a fit for us right now', reason: 'They decline, so this is not interest.' },
|
|
135
137
|
'intent record': [{ label: 'stop', quote: 'please stop writing to me', reason: 'They ask us to stop.', result: { type: 'reply.stop', scope: 'person' } }],
|
|
136
138
|
'prospects observation create': { requestId: 'UUID', kind: 'person', source: { provider: 'linkedin', url: 'https://www.linkedin.com/in/jane-doe' }, fields: { name: 'Jane Doe', title: 'VP Sales', imageUrl: 'https://media.licdn.com/dms/image/PHOTO_ID' }, provenance: { url: 'https://example.com/team/jane' }, observedAt: '2026-01-01T00:00:00Z', skill: { id: 'signal-prospect-research', version: '1' } },
|
|
137
139
|
'prospects citation create': { requestId: 'UUID', source: { kind: 'prospect', id: 'PERSON_PROSPECT_ID' }, target: { kind: 'prospect', id: 'COMPANY_PROSPECT_ID' }, edgeType: 'works_at', roles: [{ roleId: 'role-1', title: 'VP Sales', current: true, observedAt: '2026-01-01T00:00:00Z' }] },
|
|
@@ -8,7 +8,7 @@ const resultInput = z.discriminatedUnion('type', [
|
|
|
8
8
|
]);
|
|
9
9
|
const uuid = z.string().uuid();
|
|
10
10
|
const label = z.object({
|
|
11
|
-
label: z.string().describe(
|
|
11
|
+
label: z.string().describe("A name from intent_labels_list that fits the message direction and channel, or custom.<name> for a label the person's running playbook declares (intent_labels_list with playbookId shows them)."),
|
|
12
12
|
quote: z.string().meta({ minLength: 1, maxLength: 300 }).describe('The words in the message that support the label. Copy them from the message; case and spacing do not matter.'),
|
|
13
13
|
reason: z.string().meta({ minLength: 1, maxLength: 200 }).describe('One plain sentence: why the quote means this label. A person reads it, so no IDs, error codes or status numbers.'),
|
|
14
14
|
confidence: z.enum(['high', 'medium', 'low']).optional().describe('How sure you are. Optional. It is stored with the label when given. A low-confidence label does not fire its rule: it holds the person and opens a decision, except stop and bounce, which always act.'),
|
|
@@ -19,8 +19,8 @@ const label = z.object({
|
|
|
19
19
|
}).strict();
|
|
20
20
|
export const intentOperations = {
|
|
21
21
|
intent_labels_list: {
|
|
22
|
-
schema: z.object({ workspaceId: uuid.optional() }).strict(),
|
|
23
|
-
method: 'GET', path: a => `/workspaces/${a.workspaceId}/intent-labels`, scoped: true,
|
|
22
|
+
schema: z.object({ workspaceId: uuid.optional(), playbookId: uuid.optional().describe("Also list this playbook's own labels (customLabels), each with whether a rule of the playbook names it. Record one as custom.<name>.") }).strict(),
|
|
23
|
+
method: 'GET', path: a => `/workspaces/${a.workspaceId}/intent-labels${a.playbookId ? `?playbookId=${a.playbookId}` : ''}`, scoped: true,
|
|
24
24
|
description: 'List the closed set of message labels: each name with its direction (inbound or outbound), the event it starts, the channels it fits, the typed detail it needs, whether it is locked or must stand alone, and whether it is only planned. Read it to learn which names exist, then record one with intent_record.',
|
|
25
25
|
},
|
|
26
26
|
intent_record: {
|
|
@@ -32,4 +32,13 @@ export const intentOperations = {
|
|
|
32
32
|
method: 'POST', path: a => `/workspaces/${a.workspaceId}/intents`, scoped: true, body: ({ workspaceId, ...rest }) => rest,
|
|
33
33
|
description: 'Record what a message means: one to three labels, each with the quote that supports it and a plain reason. The message must already be stored as conversation evidence with its text. Every label is checked first and nothing is written if one fails: the name is known and fits the direction and channel, the quote is in the message, the reason is plain words, the labels fit together (stop, bounce and out_of_office stand alone), and the typed detail a stop, bounce, referral or booked meeting needs is present. A refusal names the field, the allowed values and the next step. The same requestId with the same body returns the first answer. The matching events land on the person\'s running campaigns, so the playbook rules act on them. Replies set records the same labels when you give it an intent block. A message you sent can be labeled too: store it first as outbound conversation evidence, then record meeting_requested when it asks for or offers a meeting. That puts the request on the meeting timeline and starts the meeting follow-ups without a reply.',
|
|
34
34
|
},
|
|
35
|
+
intent_correct: {
|
|
36
|
+
schema: z.object({
|
|
37
|
+
workspaceId: uuid.optional(), prospectId: uuid, eventId: uuid.describe('The label event to change: the id the label shows in workspace_reply_get, prospect_timeline and workspace_pipeline_get.'), requestId: uuid,
|
|
38
|
+
label: label.optional().describe('The label that replaces it, with its quote and reason. Send this or retract.'),
|
|
39
|
+
retract: z.literal(true).optional().describe('Withdraw the label and put nothing in its place. Send this or label.'),
|
|
40
|
+
}).strict(),
|
|
41
|
+
method: 'POST', path: a => `/workspaces/${a.workspaceId}/intents/corrections`, scoped: true, body: ({ workspaceId, ...rest }) => rest,
|
|
42
|
+
description: 'A person changes one label of a message, or withdraws it. Only a person can: an agent token is refused with human_only, so ask your user to change it in the app or with audienti intent correct. The new label passes the checks a first recording passes. Nothing is edited: the old label stays in the history, and the labels in force are the originals no correction replaced plus the replacements. A change that would route the person differently never moves them: it opens a decision (Move back to stage X?) and the person picks. A change away from a stop never lifts it: it opens a confirmation decision. A bounce and a company-wide stop cannot be corrected. The same requestId with the same body returns the first answer.',
|
|
43
|
+
},
|
|
35
44
|
};
|
package/cli/intent.mjs
CHANGED
|
@@ -4,11 +4,12 @@ import { readFileSync, statSync } from 'node:fs';
|
|
|
4
4
|
import { readJsonFile } from './json-file.mjs';
|
|
5
5
|
import { printPaid } from './tools.mjs';
|
|
6
6
|
|
|
7
|
-
const RECORD_FLAGS = ['workspace', 'profile', 'person', 'evidence', 'label', 'quote', 'reason', 'confidence', 'request-id', 'participation', 'file', 'text', 'text-file', 'direction', 'channel', 'context'];
|
|
7
|
+
const RECORD_FLAGS = ['workspace', 'profile', 'person', 'evidence', 'label', 'quote', 'reason', 'confidence', 'request-id', 'participation', 'playbook', 'file', 'text', 'text-file', 'direction', 'channel', 'context', 'event', 'retract'];
|
|
8
8
|
const SUGGEST_FLAGS = ['workspace', 'profile', 'text', 'text-file', 'direction', 'channel', 'context', 'request-id'];
|
|
9
9
|
const TEXT_MAX_BYTES = 16384;
|
|
10
10
|
const TEXT_MAX_CHARACTERS = 4000;
|
|
11
11
|
const CONTEXT_MAX_CHARACTERS = 2000;
|
|
12
|
+
const CORRECT_OK = ['workspace', 'profile', 'person', 'event', 'label', 'quote', 'reason', 'confidence', 'request-id', 'file', 'retract'];
|
|
12
13
|
const RECORD_ONLY_OK = ['workspace', 'profile', 'person', 'evidence', 'label', 'quote', 'reason', 'confidence', 'request-id', 'participation', 'file'];
|
|
13
14
|
|
|
14
15
|
function readText(path) {
|
|
@@ -44,7 +45,28 @@ async function suggest(flags) {
|
|
|
44
45
|
await printPaid('tools_intent_label', { workspaceId: flags.workspace, requestId, input: { text, direction, channel: flags.channel, ...(flags.context ? { context: flags.context } : {}) } }, { profile: flags.profile, explain: explain(requestId) });
|
|
45
46
|
}
|
|
46
47
|
const SUGGEST_USAGE = 'Use: intent suggest (--text TEXT | --text-file PATH) --channel email|linkedin [--direction inbound|outbound] [--context TEXT] [--request-id UUID] [--workspace ID].';
|
|
47
|
-
const USAGE = 'Use intent list [--workspace ID], intent suggest (--text TEXT | --text-file PATH) --channel email|linkedin [--direction inbound|outbound] [--context TEXT] [--request-id UUID] [--workspace ID], or intent record --person ID --evidence ID (--label NAME --quote TEXT --reason TEXT [--confidence high|medium|low] | --file LABELS.json) [--request-id UUID] [--participation ID] [--workspace ID].';
|
|
48
|
+
const USAGE = 'Use intent list [--playbook ID] [--workspace ID], intent suggest (--text TEXT | --text-file PATH) --channel email|linkedin [--direction inbound|outbound] [--context TEXT] [--request-id UUID] [--workspace ID], or intent record --person ID --evidence ID (--label NAME --quote TEXT --reason TEXT [--confidence high|medium|low] | --file LABELS.json) [--request-id UUID] [--participation ID] [--workspace ID], or intent correct --person ID --event ID (--label NAME --quote TEXT --reason TEXT [--confidence high|medium|low] | --file LABEL.json | --retract) [--request-id UUID] [--workspace ID].';
|
|
49
|
+
|
|
50
|
+
/** Change or withdraw one label of a message. A person only: the server refuses an agent credential with human_only. */
|
|
51
|
+
async function correct(flags) {
|
|
52
|
+
if (Object.keys(flags).some(key => !CORRECT_OK.includes(key))) throw Error('Option does not apply to intent correct.');
|
|
53
|
+
if (!flags.person || !flags.event) throw Error(`--person ID and --event ID are required. Read the event ID from replies list or the person history. ${USAGE}`);
|
|
54
|
+
const single = ['label', 'quote', 'reason', 'confidence'].filter(key => flags[key] !== undefined);
|
|
55
|
+
const ways = [flags.retract ? 'retract' : null, flags.file ? 'file' : null, single.length ? 'flags' : null].filter(Boolean);
|
|
56
|
+
if (ways.length !== 1) throw Error(`Give one change: --retract, one label with --label, --quote and --reason, or a label in --file. ${USAGE}`);
|
|
57
|
+
let label;
|
|
58
|
+
if (flags.file) {
|
|
59
|
+
const body = readJsonFile(flags.file, 65536, 'Label file');
|
|
60
|
+
if (!body || typeof body !== 'object' || Array.isArray(body)) throw Error('The label file is one JSON object: { "label": "...", "quote": "...", "reason": "...", "result": {...} }.');
|
|
61
|
+
label = body;
|
|
62
|
+
} else if (single.length) {
|
|
63
|
+
if (!flags.label || !flags.quote || !flags.reason) throw Error(`--label, --quote and --reason are required together. ${USAGE}`);
|
|
64
|
+
label = { label: flags.label, quote: flags.quote, reason: flags.reason, ...(flags.confidence ? { confidence: flags.confidence } : {}) };
|
|
65
|
+
}
|
|
66
|
+
console.log(JSON.stringify(await callWorkspace('intent_correct', {
|
|
67
|
+
workspaceId: flags.workspace, prospectId: flags.person, eventId: flags.event, requestId: flags['request-id'] ?? randomUUID(), ...(label ? { label } : { retract: true }),
|
|
68
|
+
}, configuration(flags.profile))));
|
|
69
|
+
}
|
|
48
70
|
|
|
49
71
|
export async function intentCli(args) {
|
|
50
72
|
const flags = {}, positions = [];
|
|
@@ -53,17 +75,19 @@ export async function intentCli(args) {
|
|
|
53
75
|
if (arg === '--json') continue;
|
|
54
76
|
if (!arg.startsWith('--')) { positions.push(arg); continue; }
|
|
55
77
|
const key = arg.slice(2);
|
|
56
|
-
if (
|
|
78
|
+
if (key === 'retract' && !Object.hasOwn(flags, key)) { flags.retract = true; continue; }
|
|
79
|
+
if (!RECORD_FLAGS.includes(key) || key === 'retract' || Object.hasOwn(flags, key) || i + 1 >= args.length) throw Error(`Invalid flag --${key}. ${USAGE}`);
|
|
57
80
|
flags[key] = args[++i];
|
|
58
81
|
}
|
|
59
82
|
const command = positions[1];
|
|
60
|
-
if (positions.length !== 2 || positions[0] !== 'intent' || !['list', 'suggest', 'record'].includes(command)) throw Error(USAGE);
|
|
83
|
+
if (positions.length !== 2 || positions[0] !== 'intent' || !['list', 'suggest', 'record', 'correct'].includes(command)) throw Error(USAGE);
|
|
61
84
|
if (command === 'list') {
|
|
62
|
-
if (Object.keys(flags).some(key => !['workspace', 'profile'].includes(key))) throw Error('Option does not apply to intent list.');
|
|
63
|
-
console.log(JSON.stringify(await callWorkspace('intent_labels_list', { workspaceId: flags.workspace }, configuration(flags.profile))));
|
|
85
|
+
if (Object.keys(flags).some(key => !['workspace', 'profile', 'playbook'].includes(key))) throw Error('Option does not apply to intent list.');
|
|
86
|
+
console.log(JSON.stringify(await callWorkspace('intent_labels_list', { workspaceId: flags.workspace, ...(flags.playbook ? { playbookId: flags.playbook } : {}) }, configuration(flags.profile))));
|
|
64
87
|
return;
|
|
65
88
|
}
|
|
66
89
|
if (command === 'suggest') { await suggest(flags); return; }
|
|
90
|
+
if (command === 'correct') { await correct(flags); return; }
|
|
67
91
|
if (Object.keys(flags).some(key => !RECORD_ONLY_OK.includes(key))) throw Error('Option does not apply to intent record.');
|
|
68
92
|
if (!flags.person || !flags.evidence) throw Error(`--person ID and --evidence ID are required. ${USAGE}`);
|
|
69
93
|
const single = ['label', 'quote', 'reason', 'confidence'].filter(key => flags[key] !== undefined);
|
package/cli/playbook-help.mjs
CHANGED
|
@@ -16,7 +16,7 @@ export const playbookCommands = {
|
|
|
16
16
|
},
|
|
17
17
|
save: {
|
|
18
18
|
usage: 'playbook save --file REQUEST.json [--workspace ID] [--profile PATH]',
|
|
19
|
-
about: 'Create a playbook or save a new revision. Step action ids and channels: audienti playbook actions. The file is {"requestId": UUID, "expectedRevision": N, "id": PLAYBOOK_ID, "fields": {...}}. requestId: a fresh UUID; reuse it only to retry the same save. id: leave it out for a new playbook. expectedRevision: 0 for a new playbook, otherwise currentRevision from playbook show. A save from a stale revision is refused with revision_conflict and the response gives currentRevision. A playbook that cannot work (no off-ramp, or a stage nobody can reach) is refused with playbook_cannot_work and the list in errors. A saved playbook comes back with warnings, the Playbook check, for things that are legal but probably wrong. fields: name, stages (2 or more, each with a UUID key), goalKey (a stage key after the first stage), offRamps, and optionally eventRules (the example shows one). The actionId of a step is one of the ids from playbook actions. Field rules: audienti skills read playbook-design references/fields.md',
|
|
19
|
+
about: 'Create a playbook or save a new revision. Step action ids and channels: audienti playbook actions. The file is {"requestId": UUID, "expectedRevision": N, "id": PLAYBOOK_ID, "fields": {...}}. requestId: a fresh UUID; reuse it only to retry the same save. id: leave it out for a new playbook. expectedRevision: 0 for a new playbook, otherwise currentRevision from playbook show. A save from a stale revision is refused with revision_conflict and the response gives currentRevision. A playbook that cannot work (no off-ramp, or a stage nobody can reach) is refused with playbook_cannot_work and the list in errors. A saved playbook comes back with warnings, the Playbook check, for things that are legal but probably wrong. fields: name, stages (2 or more, each with a UUID key), goalKey (a stage key after the first stage), offRamps, and optionally eventRules (the example shows one). The actionId of a step is one of the ids from playbook actions. Field rules: audienti skills read playbook-design references/fields.md Your own reply labels: add customLabels [{"name":"asked_pricing","meaning":"They ask what it costs."}] and a rule with when custom.asked_pricing; a label with no rule opens a decision. Read them with audienti intent list --playbook ID.',
|
|
20
20
|
example: {
|
|
21
21
|
requestId: 'UUID',
|
|
22
22
|
expectedRevision: 0,
|
|
@@ -5,7 +5,8 @@ const text=z.string().trim().max(5000);
|
|
|
5
5
|
const identity=z.object({key:z.string().uuid(),name:label}).strict();
|
|
6
6
|
const routeTarget=z.discriminatedUnion('kind',[z.object({kind:z.literal('stage'),stageKey:z.string().uuid()}).strict(),z.object({kind:z.literal('off_ramp'),offRampKey:z.string().uuid()}).strict(),z.object({kind:z.literal('step'),stepKey:z.string().uuid()}).strict()]);
|
|
7
7
|
// An event rule (#350 T3): what happens when an event reaches a person. The server checks the event and action ids against the outreach rulebook.
|
|
8
|
-
const
|
|
8
|
+
const customLabel=z.object({name:z.string().regex(/^[a-z][a-z0-9_]{1,39}$/).describe('Lower case letters, digits and underscores, 2 to 40 characters, starting with a letter. It cannot be the name of a core label (run intent_labels_list).'),direction:z.enum(['inbound']).optional().describe('Only inbound: a message the person sent. Default inbound.'),meaning:z.string().trim().min(1).max(160).describe('One line: what a message with this label says.')}).strict();
|
|
9
|
+
const eventRule=z.object({key:z.string().uuid(),when:z.string().min(1).max(60).describe('An event id from the outreach rulebook, for example accepted, replied, wait_timeout or reply.out_of_office, or custom.<name> for a label this playbook declares in customLabels.'),do:z.array(z.enum(['stop_wait','snooze','redirect_to_named_person','ask_who_is_right','tell_me','hand_to_user','withdraw_pending_request'])).max(7).optional(),snoozeDays:z.number().int().min(1).max(90).optional(),goto:z.discriminatedUnion('kind',[z.object({kind:z.literal('stay')}).strict(),z.object({kind:z.literal('gate')}).strict(),...routeTarget.options]).optional()}).strict();
|
|
9
10
|
const stepRoutes=z.object({accept:routeTarget.optional(),reply:routeTarget.optional(),meeting:routeTarget.optional(),timeout:routeTarget.optional()}).strict().describe("Where an event that reaches the person after this step ran sends them. A route wins over the stage's jump list; an event with no route follows the stage. Going to an earlier stage needs rules.allowBackward.");
|
|
10
11
|
const playbookStepSchema=z.object({key:z.string().uuid(),channel:z.enum(['LinkedIn','Email','Custom']),action:label,condition:z.enum(['Always','Only if viable','Only if connected']).optional().describe('Left out: Only if connected for a LinkedIn message (it can only reach a connection), Always for everything else. Only if connected skips the step for a person who was asked to connect and has not accepted. Only if viable needs viability.'),viability:text.optional(),instructions:text.optional().describe('What the agent should write or do. Left out, one coaching sentence for the kind of action.'),wait:z.object({until:z.enum(['accept','reply','meeting','timeout','event']),days:z.number().int().min(1).max(60),event:label.optional().describe('Custom steps only, with until event: the name of the event a person records to end the wait, for example Gift delivered.'),followUps:z.number().int().min(0).max(10).optional().describe('Meeting waits only: how many follow-ups your agent writes, one after each wait, before the meeting is marked No answer. Default 3.')}).strict().nullable().optional().describe('How long the step waits and for what. Left out on a new step that sends: a reply wait of 3 days (a connection request waits 7 days for the accept). null means no wait. A step the playbook already has keeps what it has.'),waitDays:z.number().int().min(1).max(60).optional().describe('Older form of wait: the same as wait {until: timeout, days}.'),side:z.boolean().optional(),onTimeout:z.enum(['next','off_ramp']).optional(),humanize:z.boolean().optional().describe('Email only. Rewrite the email text before approval so it reads as written by a person. On by default; set false to send the text as written.'),actionId:z.string().max(80).optional().describe('An action id from the outreach rulebook, for example linkedin.message.send.'),routes:stepRoutes.optional(),eventRules:z.array(eventRule).max(10).optional()}).strict();
|
|
11
12
|
const completion=z.discriminatedUnion('kind',[z.object({kind:z.literal('next')}).strict(),z.object({kind:z.literal('stay')}).strict(),z.object({kind:z.literal('off_ramp'),offRampKey:z.string().uuid()}).strict()]);
|
|
@@ -16,7 +17,7 @@ const dropKeys=keys=>value=>{if(!value||typeof value!=='object'||Array.isArray(v
|
|
|
16
17
|
const dropRetiredStage=value=>{const rest=dropKeys(['optional','movesInWhen'])(value);if(!rest||typeof rest!=='object'||Array.isArray(rest)||!Array.isArray(rest.on))return rest;return {...rest,on:rest.on.filter(event=>event!=='manual')};};
|
|
17
18
|
const stage=z.preprocess(dropRetiredStage,stageFields);
|
|
18
19
|
const playbookRulesSchema=z.preprocess(dropKeys(['autoMove','flagOverdue','emailWindow','nurtureDays']),z.object({allowBackward:z.boolean().optional().describe('Let a move go to an earlier stage, for example when a meeting is cancelled. Default false.')}).strict());
|
|
19
|
-
const playbookFieldsSchema=z.object({name:label,stages:z.array(stage).min(2).max(30).optional().describe('The stages, in order. Leave it out for the default skeleton: Ready, Outreach underway (no steps; people who finish it leave as Unresponsive), In conversation and Meeting booked as the goal. Then add steps with a later save.'),goalKey:z.string().uuid().optional().describe('The key of the goal stage, after the first stage. Leave it out with stages to use the last stage; it is ignored when you leave stages out.'),offRamps:z.array(identity.extend({reason:z.enum(['rejected','not_fit','nurture','unresponsive','other','taken_over','bounced','skipped']).optional().describe('How Analytics counts people who leave here. Left out, it is guessed from the name (Not interested is rejected, Not a fit is not_fit, Unresponsive is unresponsive), else other.')})).max(30).optional().describe('Where people leave the playbook. Left out, the three standard off-ramps: Unresponsive, Not interested and Not a fit.'),voice:text.optional().describe('One sentence of how the agent writes. Left out, a plain, short default.'),rules:playbookRulesSchema.optional(),eventRules:z.array(eventRule).max(30).optional()}).strict();
|
|
20
|
+
const playbookFieldsSchema=z.object({name:label,stages:z.array(stage).min(2).max(30).optional().describe('The stages, in order. Leave it out for the default skeleton: Ready, Outreach underway (no steps; people who finish it leave as Unresponsive), In conversation and Meeting booked as the goal. Then add steps with a later save.'),goalKey:z.string().uuid().optional().describe('The key of the goal stage, after the first stage. Leave it out with stages to use the last stage; it is ignored when you leave stages out.'),offRamps:z.array(identity.extend({reason:z.enum(['rejected','not_fit','nurture','unresponsive','other','taken_over','bounced','skipped']).optional().describe('How Analytics counts people who leave here. Left out, it is guessed from the name (Not interested is rejected, Not a fit is not_fit, Unresponsive is unresponsive), else other.')})).max(30).optional().describe('Where people leave the playbook. Left out, the three standard off-ramps: Unresponsive, Not interested and Not a fit.'),voice:text.optional().describe('One sentence of how the agent writes. Left out, a plain, short default.'),rules:playbookRulesSchema.optional(),eventRules:z.array(eventRule).max(30).optional(),customLabels:z.array(customLabel).max(20).optional().describe("Labels of this playbook's own, for what a reply means that the core labels do not say. Each has a name, a direction and a one-line meaning. A rule names one as its event, custom.<name>; an agent records one with intent_record as custom.<name>. A label no rule names opens a decision for the person.")}).strict();
|
|
20
21
|
const fields=playbookFieldsSchema;
|
|
21
22
|
const base=a=>`/workspaces/${a.workspaceId}`,query=(a,skip=[])=>new URLSearchParams(Object.entries(a).filter(([k,v])=>k!=='workspaceId'&&!skip.includes(k)&&v!==undefined).map(([k,v])=>[k,String(v)]));
|
|
22
23
|
export const playbookOperations={
|
package/cli/work-operations.mjs
CHANGED
|
@@ -18,7 +18,7 @@ const omit = (a, keys) => Object.fromEntries(Object.entries(a).filter(([k])=>!ke
|
|
|
18
18
|
const body = a => omit(a,['workspaceId','planId','clientActionId']);
|
|
19
19
|
const qs = values => { const p=new URLSearchParams(); for(const [k,v] of Object.entries(values)) if(v!==undefined)p.set(k,String(v));return p.toString(); };
|
|
20
20
|
export const workOperations = {
|
|
21
|
-
workspace_work_activity_list: {description:'Read recorded workspace Activity, newest first: actor, fact, person, time and recorded status. Human tokens require workspace:access; connected agents require workspace:read and workspace policy access, acting as their user. Stage decisions follow Admin/current-assignee visibility. Fixed pages of 50; reuse search and both cursor fields. Read only, no decision or inferred send success.',schema:z.object({...workspace,search:z.string().trim().max(120).optional(),cursor:z.object({at:z.string().datetime(),id:z.string().max(160)}).strict().optional()}).strict(),method:'GET',path:a=>`${root(a)}/operator/activity?${qs({search:a.search,cursorAt:a.cursor?.at,cursorId:a.cursor?.id})}`,scoped:true},
|
|
21
|
+
workspace_work_activity_list: {description:'Read recorded workspace Activity, newest first: actor, fact, person, time and recorded status. A stop, bounce or referral row also carries label: the label that caused it, with its quote, reason, confidence and who recorded it (null on other rows). Human tokens require workspace:access; connected agents require workspace:read and workspace policy access, acting as their user. Stage decisions follow Admin/current-assignee visibility. Fixed pages of 50; reuse search and both cursor fields. Read only, no decision or inferred send success.',schema:z.object({...workspace,search:z.string().trim().max(120).optional(),cursor:z.object({at:z.string().datetime(),id:z.string().max(160)}).strict().optional()}).strict(),method:'GET',path:a=>`${root(a)}/operator/activity?${qs({search:a.search,cursorAt:a.cursor?.at,cursorId:a.cursor?.id})}`,scoped:true},
|
|
22
22
|
workspace_work_action_plan_save: {schema:z.object({...workspace,planId:uuid.nullable(),prospectId:uuid,campaignId:uuid.nullable(),campaignRevision:z.number().int().positive().nullable(),expectedRevision:version,requestId:uuid,origin:z.discriminatedUnion('kind',[z.object({kind:z.literal('agent'),skill:z.object({id:z.string(),version:z.string()}).strict()}).strict(),z.object({kind:z.literal('human'),editReason:z.string()}).strict()]),rationale:z.string(),evidenceIds:evidence,actions:z.array(z.discriminatedUnion('type',[email,linkedin])).min(1).max(50),cadence:z.array(cadence).max(50)}).strict(), method:'POST',path:a=>`${root(a)}/action-plans`,body:a=>omit(a,['workspaceId']),scoped:true},
|
|
23
23
|
workspace_work_operator_list: {description:'Read workspace proposals. order=priority matches the Operator queue: open replies first, then oldest proposals, with head-page totals. Omit order for legacy oldest-first order. Keep order and filters unchanged while paging. Read only; grants no execution authority.',schema:z.object({...workspace,order:z.enum(['oldest','priority']).optional(),page:page.optional(),filter:z.object({state:z.enum(['open','cancelled','terminal','invalidated']).optional(),reviewStatus:z.enum(['awaiting_revision','invalidated','pending_review','approved','in_progress','reconciliation_required','completed','skipped','cancelled']).optional(),search:z.string().optional(),queue:z.enum(['review','blocked','activity']).optional()}).strict().optional()}).strict(),method:'GET',path:a=>`${root(a)}/operator/work?${qs({...a.page,...a.filter,order:a.order})}`,scoped:true},
|
|
24
24
|
workspace_work_operator_get: {schema:z.object({...workspace,planId:uuid,revision:z.number().int().positive().optional(),history:page.optional()}).strict(),method:'GET',path:a=>`${plan(a)}?${qs({revision:a.revision,historyCursor:a.history?.cursor,historyLimit:a.history?.limit})}`,scoped:true},
|
package/cli/workspaces.mjs
CHANGED
|
@@ -180,7 +180,7 @@ export function localGuidance(name, issues, args) {
|
|
|
180
180
|
// #519: a refused label already says in plain words what is wrong; the code and the next step travel beside it.
|
|
181
181
|
// Refusals of the decision calls (#522). Their messages tell the person what to do next, so the CLI shows them.
|
|
182
182
|
const DECISION_REFUSALS = new Set(['agent_required', 'human_only', 'not_assigned_owner', 'participation_ended', 'decision_not_found', 'decision_closed', 'decision_superseded', 'already_decided', 'option_not_allowed', 'option_count', 'duplicate_option', 'analysis_required', 'analysis_too_long', 'analysis_not_plain', 'workspace_not_found']);
|
|
183
|
-
const INTENT_REFUSALS = new Set(['prospect_not_found', 'conversation_evidence_not_found', 'participation_not_found', 'payload_too_large', 'idempotency_conflict', 'evidence_has_no_body', 'too_many_labels', 'unknown_label', 'already_recorded', 'label_not_available', 'label_direction', 'label_channel', 'duplicate_label', 'must_stand_alone', 'labels_conflict', 'result_required', 'result_mismatch', 'quote_required', 'quote_not_in_message', 'reason_required', 'reason_not_plain', 'detail_not_allowed']);
|
|
183
|
+
const INTENT_REFUSALS = new Set(['human_only', 'label_event_not_found', 'label_not_in_force', 'no_change', 'original_has_no_message', 'company_stop', 'bounce_not_correctable', 'correction_needs_one_change', 'prospect_not_found', 'conversation_evidence_not_found', 'participation_not_found', 'payload_too_large', 'idempotency_conflict', 'evidence_has_no_body', 'too_many_labels', 'unknown_label', 'already_recorded', 'label_not_available', 'label_direction', 'label_channel', 'duplicate_label', 'must_stand_alone', 'labels_conflict', 'result_required', 'result_mismatch', 'quote_required', 'quote_not_in_message', 'reason_required', 'reason_not_plain', 'detail_not_allowed']);
|
|
184
184
|
export function errorGuidance(error, token, config) {
|
|
185
185
|
const clean = text => typeof text === 'string' && !/[\u0000-\u001f\u007f]/.test(text) && !(token && text.includes(token));
|
|
186
186
|
return {
|
|
@@ -215,7 +215,7 @@ export async function callWorkspace(name, args, config) {
|
|
|
215
215
|
const retryAt = typeof result?.error?.retryAt === 'string' && /^\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d\.\d{3}Z$/.test(result.error.retryAt) ? result.error.retryAt : undefined;
|
|
216
216
|
const detail = (INTENT_REFUSALS.has(code) || (name.startsWith('decision_') && DECISION_REFUSALS.has(code)) || (response.status===400 && (code==='invalid_fields' || code==='validation_error' || code==='secret_rejected')) || (response.status===403 && ((code==='insufficient_scope' && /^(This API token can read but not change|This command is for an agent, and this API token is read-only)\./.test(result?.error?.message ?? '')) || code==='agent_required')) || (response.status===404 && (code==='not_found' || code==='no_draft')) || (response.status===422 && code==='playbook_cannot_work') || (response.status===409 && (code==='revision_conflict' || code==='draft_conflict' || code==='playbook_in_use' || code==='definition_archived' || code==='playbook_archived' || code==='starter_playbook' || code==='identity_conflict' || code==='task_not_for_you' || code==='version_conflict' || code==='request_conflict' || code==='campaign_not_ready' || code==='campaign_not_paced' || code==='wrong_event_name' || code==='mark_task_done_first' || code==='no_event' || code==='prepare_first' || code==='task_closed' || code==='not_claimed'))) && typeof result?.error?.message==='string' && result.error.message.length<=(code==='playbook_cannot_work'?2000:1000) && !/[\u0000-\u001f\u007f]/.test(result.error.message) && !/^Workspace request failed/.test(result.error.message) && !(config.token && result.error.message.includes(config.token)) ? result.error.message.trim() : '';
|
|
217
217
|
const blockers = (code==='execution_blocked' || code==='campaign_not_ready') && Array.isArray(result?.error?.blockers) && result.error.blockers.length<=28 && result.error.blockers.every(v=>typeof v==='string' && /^[a-z_]+$/.test(v)) ? result.error.blockers : undefined;
|
|
218
|
-
const error = new WorkspaceRequestError(response.status, code, name==='intent_record' && (INTENT_REFUSALS.has(code) || code==='invalid_fields') && detail ? surfaceWording(detail, config) : `Workspace request failed: ${code} (HTTP ${response.status}).${detail ? ` ${surfaceWording(detail, config)}` : ''}${retryAt ? ` Retry after ${retryAt}.` : ''}`, retryAt, blockers, requestTraceId, errorGuidance(result?.error, config.token, config));
|
|
218
|
+
const error = new WorkspaceRequestError(response.status, code, (name==='intent_record' || name==='intent_correct') && (INTENT_REFUSALS.has(code) || code==='invalid_fields') && detail ? surfaceWording(detail, config) : `Workspace request failed: ${code} (HTTP ${response.status}).${detail ? ` ${surfaceWording(detail, config)}` : ''}${retryAt ? ` Retry after ${retryAt}.` : ''}`, retryAt, blockers, requestTraceId, errorGuidance(result?.error, config.token, config));
|
|
219
219
|
if(code==='playbook_cannot_work'&&Array.isArray(result?.error?.errors)&&result.error.errors.length<=30)error.errors=result.error.errors.filter(item=>item&&typeof item.id==='string'&&typeof item.message==='string').map(item=>({id:item.id,level:item.level,message:item.message,...(typeof item.stageKey==='string'?{stageKey:item.stageKey}:{}),...(item.fix&&typeof item.fix.label==='string'?{fix:{id:item.fix.id,label:item.fix.label}}:{})}));
|
|
220
220
|
if(code==='draft_conflict'&&(result?.error?.currentUpdatedAt===null||/^\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d\.\d{3}Z$/.test(result?.error?.currentUpdatedAt??'')))error.currentUpdatedAt=result.error.currentUpdatedAt;
|
|
221
221
|
if(code==='revision_conflict'&&Number.isInteger(result?.error?.currentRevision)&&result.error.currentRevision>=0)error.currentRevision=result.error.currentRevision;
|
package/package.json
CHANGED
|
@@ -97,7 +97,7 @@ asks for everything to end looks like this:
|
|
|
97
97
|
"result": {"type": "reply.stop", "scope": "person"}}]
|
|
98
98
|
```
|
|
99
99
|
|
|
100
|
-
`audienti intent --help` prints this example. Give the same `--request-id` to repeat a call safely. Add
|
|
100
|
+
`audienti intent record --help` prints this example. Give the same `--request-id` to repeat a call safely. Add
|
|
101
101
|
`confidence` (`high`, `medium` or `low`) when you are unsure. A low-confidence label does not fire its rule:
|
|
102
102
|
the person is held and a decision opens for your user to pick from (see below). `stop` and `bounce` always
|
|
103
103
|
act, whatever the confidence. A message keeps the labels it has: a second request that repeats
|
|
@@ -127,6 +127,24 @@ a suggestion, then record it with `intent record`, which runs every check above
|
|
|
127
127
|
`replies set` records the same labels. Add `"intent": {"quote": "...", "reason": "..."}` next to `classification`
|
|
128
128
|
or `meetingState` and it runs the same checks and stores the words on the event. Without `intent` it works as before.
|
|
129
129
|
|
|
130
|
+
## A label your playbook defines
|
|
131
|
+
|
|
132
|
+
A playbook may declare labels of its own, such as "asked about pricing". See them with `audienti intent list --playbook PLAYBOOK_ID`: each shows its name
|
|
133
|
+
(`custom.asked_pricing`), what it means and whether a rule uses it. Record one like any other label:
|
|
134
|
+
`audienti intent record --person PERSON --evidence EVIDENCE --label custom.asked_pricing --quote "..." --reason "..."`. The same checks run, and
|
|
135
|
+
the label fits only the people whose running playbook declares it. If a rule names the label, the rule acts. If none does, the person is held and a
|
|
136
|
+
decision opens for your user, with the trigger `custom_label_no_rule`. A custom label is never a stop or a bounce, and `replies set` takes core labels only.
|
|
137
|
+
|
|
138
|
+
## A label is wrong
|
|
139
|
+
|
|
140
|
+
A person corrects a label; an agent cannot. If you see a wrong label, tell your user and say which one. They change it
|
|
141
|
+
with the "Change label" menu on the reply card or the person's page, or with `audienti intent correct --person PERSON
|
|
142
|
+
--event EVENT --label NAME --quote "..." --reason "..."`. `--retract` withdraws it. The `EVENT` is the `id` the label
|
|
143
|
+
shows. Nothing is edited: the old label stays in the history, and the labels in force are the ones no correction replaced.
|
|
144
|
+
The new label passes the same checks as a first recording. If it would send the person somewhere else, nothing moves:
|
|
145
|
+
Operator asks "Move back to ..." and the person picks. A change away from a stop never lifts the stop. A decision
|
|
146
|
+
asks "Lift the stop on this person?", and the stop stays until a person picks to lift it. An agent token gets `human_only`.
|
|
147
|
+
|
|
130
148
|
## Label a message you sent
|
|
131
149
|
|
|
132
150
|
A message you sent can carry a label too. Today one fits: `meeting_requested`, for a message that asks for a
|
package/source-manifest.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"repository": "https://github.com/omalab/exora",
|
|
3
|
-
"sourceCommit": "
|
|
3
|
+
"sourceCommit": "580ab43854ad3b285cfdd05463e46cb2ab3acd00",
|
|
4
4
|
"files": {
|
|
5
5
|
"LICENSE": "220f297cfcd5c38eb64e6b4b4a77cab4ebc7f4686d4d99c6260a475d02ca11e5",
|
|
6
6
|
"README.md": "7ff32cc8ac89dd54b64d7b39c0b41b1f37e154b7ea10ad5a7bceefc3df77d87f",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"cli/attachments.mjs": "2fee6b4abbf82da9847d4a927a3d4d3ca947a198a903a78927596a2ea858cbf4",
|
|
17
17
|
"cli/audience-operations.mjs": "133b72dc6b9e3a6b4f173ce28f1524163f9c7b049505b885115f83b9e4af7170",
|
|
18
18
|
"cli/audiences.mjs": "3c377346efc1a8bdde432a16c2d19a3f51f34f3333d344d3a2bfa94c188a017d",
|
|
19
|
-
"cli/audienti.mjs": "
|
|
19
|
+
"cli/audienti.mjs": "7d7103637f69824a263f47a7c3873293103db75483c8271398968f68dcd9147a",
|
|
20
20
|
"cli/audit-operations.mjs": "afaf87d592a1e5f6bff2a8649a6a64a8f471950b7d99e98995d9205aa5910398",
|
|
21
21
|
"cli/audit.mjs": "a7becaed67c58b063e668b5e66844b6dc15c11a63b428faa171b542ad922c001",
|
|
22
22
|
"cli/budget-operations.mjs": "d74916551ef393e29734c6e13cf9021251e0ed126eeaa529e18cf2332146e1e0",
|
|
@@ -40,8 +40,8 @@
|
|
|
40
40
|
"cli/guardrails.mjs": "e8b9c0a19956a9eab84475d3c56a4b6c77baf7ed9a55e8686d660ef1ad562aa8",
|
|
41
41
|
"cli/hand-task-operations.mjs": "473473fe95324c8c8733e037feab5ac468c07b0658fabd739cfd7d400a7faa83",
|
|
42
42
|
"cli/hand-tasks.mjs": "2c895cf6b30c594a7e27cc4ec4b7eddeb39f941cd195b9255c6e0d6b4a56371f",
|
|
43
|
-
"cli/intent-operations.mjs": "
|
|
44
|
-
"cli/intent.mjs": "
|
|
43
|
+
"cli/intent-operations.mjs": "a51de06eb4775840834651e689c62946a30609edbebf53f76d98370153762c5d",
|
|
44
|
+
"cli/intent.mjs": "295375c527d864679e7692f46e4e6c78e42b46c1b7b6d95775def7486dcd1653",
|
|
45
45
|
"cli/json-file.mjs": "96431b18215834c2531f5901e2526875c634dd2e85a9465fcffc937fbec52150",
|
|
46
46
|
"cli/kv-operations.mjs": "505d79d0d535d3441883f8a37817c30c73367216a35dddb07181f0f0e799df79",
|
|
47
47
|
"cli/kv.mjs": "05783610001dd002af07a4bb8f26e64d74aa4981bbe38a2100552b810cb3bc72",
|
|
@@ -54,8 +54,8 @@
|
|
|
54
54
|
"cli/outcome-operations.mjs": "cf133097799f240a006264a38a4c6bd4094078d6dec78a71679700ac3bdeacd5",
|
|
55
55
|
"cli/outcomes.mjs": "765ccb112a90fc4d228b2a41e5e19c5e1f3e2fe14bc1d8128b0090fc95cdbf3f",
|
|
56
56
|
"cli/owner-account-client.mjs": "aa2790fd3c16714b1a82922fe13012f261cced99025d692bca5d0c605a717cd1",
|
|
57
|
-
"cli/playbook-help.mjs": "
|
|
58
|
-
"cli/playbook-operations.mjs": "
|
|
57
|
+
"cli/playbook-help.mjs": "664114538865a91dbcbd96bdd2a628c78505f078c9e6c7eb542e51d376fed60a",
|
|
58
|
+
"cli/playbook-operations.mjs": "be94b1a9e1c84fa2f56169c2af7c972ea8336cf8402150e4b57b08e354e7b4a9",
|
|
59
59
|
"cli/profiles.mjs": "45929b224ffc916b6dbd7a94e06ac09a1ff7feec430633b10d160c1608fc2d32",
|
|
60
60
|
"cli/progress.mjs": "6908f2937adb87a83ebefc1f4324ed18adac65228547b0524a474ac3513b5c67",
|
|
61
61
|
"cli/prospecting-settings-operations.mjs": "1829eb2fe7208f58601988143756072429adbfe00f8deb5a52613bf650a9eacc",
|
|
@@ -85,10 +85,10 @@
|
|
|
85
85
|
"cli/work-execution.mjs": "4b74923417eaa70dee0ba8806b64d0acf7c884fb77b76d5e86d567cf92e51b01",
|
|
86
86
|
"cli/work-feedback-operations.mjs": "a998f2b2dbe38ceb2bad74f7fa61c31f87f842755449248b7eb0c04185c0ee3d",
|
|
87
87
|
"cli/work-feedback.mjs": "f3b77ecfd7ea1aba9c9f03c9d894137547e9c5575d9daa359ed94505ad025852",
|
|
88
|
-
"cli/work-operations.mjs": "
|
|
88
|
+
"cli/work-operations.mjs": "66a6155964d1af264b4aa3851cf43b9373155887ee2f9bcac3eb97d9f4680157",
|
|
89
89
|
"cli/work.mjs": "751107ecc3de5a6be9db4a068cddc41f3ad8e77c87e59686eaf522862caf9e9f",
|
|
90
|
-
"cli/workspaces.mjs": "
|
|
91
|
-
"package.json": "
|
|
90
|
+
"cli/workspaces.mjs": "7de635a78f80d75421447cc62686ab4cde7779a9dd7d0bb55e48143a5e4c8365",
|
|
91
|
+
"package.json": "145b9fae8227341aec956281dd53ce9bea07e4e7693dd4a47e5790c5b3fd7e39",
|
|
92
92
|
"skills/agent-rooms/SKILL.md": "08f917ff85fee1d23499a4499056ce845b39ce6727029b104819dad41ee87cc2",
|
|
93
93
|
"skills/agent-rooms/references/commands.md": "ef5afa5543ca9da967a147f638774647998ea1c1a2a821959f3bf87d54c292bd",
|
|
94
94
|
"skills/audienti/SKILL.md": "8f2124065bfc3a005b44e194e7dcce9bbf14062ab332e7db66d441eeccb518ed",
|
|
@@ -104,7 +104,7 @@
|
|
|
104
104
|
"skills/getting-started/references/capabilities.md": "982e3583a47ba4ac832efae2f6af3a4d8c0c18cace159c76b2a4ad1a8d8e910e",
|
|
105
105
|
"skills/getting-started/references/mailbox-check.md": "113574bb5a573125620dcde4e65ebf44164f7b0727cc936fb41234f5d57bba73",
|
|
106
106
|
"skills/getting-started/references/outcome-checks.md": "094d09d231b38d4935aa9eb8ac089e46ba8e698489852fa97040e6b5eac77a9b",
|
|
107
|
-
"skills/getting-started/references/replies.md": "
|
|
107
|
+
"skills/getting-started/references/replies.md": "e543f13b9fae2ad3a7a07b6289ca03258c75e64df60cd3b36d4686ac9298d799",
|
|
108
108
|
"skills/getting-started/references/reply-checks.md": "85fe692ad6b7bc73e204de313b27d225be3da1a0af71e8e7bb9945ae511a0dc3",
|
|
109
109
|
"skills/playbook-design/SKILL.md": "56aa5692c1bbe3843f11819f0f9270988ddd0424b16f38db72a854864155407f",
|
|
110
110
|
"skills/playbook-design/references/fields.md": "c7ba7a0bf8ac1325e70a5e4d98734716d752c311588d064bf2aba849925357aa"
|