granola-toolkit 0.51.0 → 0.52.0
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/cli.js +403 -39
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -3745,6 +3745,7 @@ function cloneAction$1(action) {
|
|
|
3745
3745
|
switch (action.kind) {
|
|
3746
3746
|
case "agent": return {
|
|
3747
3747
|
...action,
|
|
3748
|
+
approvalMode: action.approvalMode,
|
|
3748
3749
|
fallbackHarnessIds: action.fallbackHarnessIds ? [...action.fallbackHarnessIds] : void 0,
|
|
3749
3750
|
pipeline: action.pipeline ? { ...action.pipeline } : void 0
|
|
3750
3751
|
};
|
|
@@ -3756,23 +3757,50 @@ function cloneAction$1(action) {
|
|
|
3756
3757
|
};
|
|
3757
3758
|
case "export-notes":
|
|
3758
3759
|
case "export-transcript": return { ...action };
|
|
3760
|
+
case "slack-message": return { ...action };
|
|
3761
|
+
case "webhook": return {
|
|
3762
|
+
...action,
|
|
3763
|
+
headers: action.headers ? { ...action.headers } : void 0
|
|
3764
|
+
};
|
|
3765
|
+
case "write-file": return { ...action };
|
|
3759
3766
|
}
|
|
3760
3767
|
}
|
|
3761
3768
|
function automationActionName(action) {
|
|
3762
3769
|
return action.name || action.id;
|
|
3763
3770
|
}
|
|
3771
|
+
function automationActionTrigger(action) {
|
|
3772
|
+
switch (action.kind) {
|
|
3773
|
+
case "command":
|
|
3774
|
+
case "slack-message":
|
|
3775
|
+
case "webhook":
|
|
3776
|
+
case "write-file": return action.trigger ?? "match";
|
|
3777
|
+
default: return "match";
|
|
3778
|
+
}
|
|
3779
|
+
}
|
|
3764
3780
|
function buildAutomationActionRunId(match, actionId) {
|
|
3765
3781
|
return `${match.id}:${actionId}`;
|
|
3766
3782
|
}
|
|
3767
|
-
function
|
|
3768
|
-
return
|
|
3783
|
+
function buildAutomationApprovalActionRunId(artefact, actionId) {
|
|
3784
|
+
return `approval:${artefact.id}:${actionId}`;
|
|
3785
|
+
}
|
|
3786
|
+
function enabledAutomationActions(rule, options = {}) {
|
|
3787
|
+
return (rule.actions ?? []).filter((action) => action.enabled !== false).filter((action) => automationActionTrigger(action) === (options.trigger ?? "match")).filter((action) => {
|
|
3788
|
+
if (options.trigger !== "approval") return true;
|
|
3789
|
+
switch (action.kind) {
|
|
3790
|
+
case "command":
|
|
3791
|
+
case "slack-message":
|
|
3792
|
+
case "webhook":
|
|
3793
|
+
case "write-file": return !options.sourceActionId || action.sourceActionId === options.sourceActionId;
|
|
3794
|
+
default: return false;
|
|
3795
|
+
}
|
|
3796
|
+
}).map((action) => cloneAction$1(action));
|
|
3769
3797
|
}
|
|
3770
|
-
function baseRun(match, rule, action, startedAt, options = {}) {
|
|
3798
|
+
function baseRun(match, rule, action, startedAt, context, options = {}) {
|
|
3771
3799
|
return {
|
|
3772
3800
|
actionId: action.id,
|
|
3773
3801
|
actionKind: action.kind,
|
|
3774
3802
|
actionName: automationActionName(action),
|
|
3775
|
-
artefactIds: void 0,
|
|
3803
|
+
artefactIds: context.artefact ? [context.artefact.id] : void 0,
|
|
3776
3804
|
eventId: match.eventId,
|
|
3777
3805
|
eventKind: match.eventKind,
|
|
3778
3806
|
folders: match.folders.map((folder) => ({ ...folder })),
|
|
@@ -3780,6 +3808,10 @@ function baseRun(match, rule, action, startedAt, options = {}) {
|
|
|
3780
3808
|
matchId: match.id,
|
|
3781
3809
|
matchedAt: match.matchedAt,
|
|
3782
3810
|
meetingId: match.meetingId,
|
|
3811
|
+
meta: {
|
|
3812
|
+
sourceActionId: action.kind === "command" || action.kind === "slack-message" || action.kind === "webhook" || action.kind === "write-file" ? action.sourceActionId : void 0,
|
|
3813
|
+
trigger: context.trigger
|
|
3814
|
+
},
|
|
3783
3815
|
ruleId: rule.id,
|
|
3784
3816
|
ruleName: rule.name,
|
|
3785
3817
|
rerunOfId: options.rerunOfId,
|
|
@@ -3815,7 +3847,8 @@ function skippedRun(run, finishedAt, reason) {
|
|
|
3815
3847
|
};
|
|
3816
3848
|
}
|
|
3817
3849
|
async function executeAutomationAction(match, rule, action, handlers, options = {}) {
|
|
3818
|
-
const
|
|
3850
|
+
const context = options.context ?? { trigger: automationActionTrigger(action) };
|
|
3851
|
+
const run = baseRun(match, rule, action, handlers.nowIso(), context, options);
|
|
3819
3852
|
switch (action.kind) {
|
|
3820
3853
|
case "agent": try {
|
|
3821
3854
|
const result = await handlers.runAgent(match, rule, action, run);
|
|
@@ -3845,9 +3878,10 @@ async function executeAutomationAction(match, rule, action, handlers, options =
|
|
|
3845
3878
|
status: "pending"
|
|
3846
3879
|
};
|
|
3847
3880
|
case "command": try {
|
|
3848
|
-
const result = await handlers.runCommand(match, rule, action);
|
|
3881
|
+
const result = await handlers.runCommand(match, rule, action, context);
|
|
3849
3882
|
return completedRun(run, handlers.nowIso(), {
|
|
3850
3883
|
meta: {
|
|
3884
|
+
...run.meta ? structuredClone(run.meta) : {},
|
|
3851
3885
|
command: result.command,
|
|
3852
3886
|
cwd: result.cwd
|
|
3853
3887
|
},
|
|
@@ -3886,8 +3920,162 @@ async function executeAutomationAction(match, rule, action, handlers, options =
|
|
|
3886
3920
|
} catch (error) {
|
|
3887
3921
|
return failedRun(run, handlers.nowIso(), error);
|
|
3888
3922
|
}
|
|
3923
|
+
case "slack-message": try {
|
|
3924
|
+
const result = await handlers.runSlackMessage(match, rule, action, context);
|
|
3925
|
+
return completedRun(run, handlers.nowIso(), {
|
|
3926
|
+
meta: {
|
|
3927
|
+
...run.meta ? structuredClone(run.meta) : {},
|
|
3928
|
+
status: result.status,
|
|
3929
|
+
text: result.text,
|
|
3930
|
+
url: result.url
|
|
3931
|
+
},
|
|
3932
|
+
result: result.output ?? `Posted Slack message (${result.status})`
|
|
3933
|
+
});
|
|
3934
|
+
} catch (error) {
|
|
3935
|
+
return failedRun(run, handlers.nowIso(), error);
|
|
3936
|
+
}
|
|
3937
|
+
case "webhook": try {
|
|
3938
|
+
const result = await handlers.runWebhook(match, rule, action, context);
|
|
3939
|
+
return completedRun(run, handlers.nowIso(), {
|
|
3940
|
+
meta: {
|
|
3941
|
+
...run.meta ? structuredClone(run.meta) : {},
|
|
3942
|
+
status: result.status,
|
|
3943
|
+
url: result.url
|
|
3944
|
+
},
|
|
3945
|
+
result: result.output ?? `Posted webhook (${result.status})`
|
|
3946
|
+
});
|
|
3947
|
+
} catch (error) {
|
|
3948
|
+
return failedRun(run, handlers.nowIso(), error);
|
|
3949
|
+
}
|
|
3950
|
+
case "write-file": try {
|
|
3951
|
+
const result = await handlers.writeFile(match, rule, action, context);
|
|
3952
|
+
return completedRun(run, handlers.nowIso(), {
|
|
3953
|
+
meta: {
|
|
3954
|
+
...run.meta ? structuredClone(run.meta) : {},
|
|
3955
|
+
bytes: result.bytes,
|
|
3956
|
+
filePath: result.filePath,
|
|
3957
|
+
format: result.format
|
|
3958
|
+
},
|
|
3959
|
+
result: `Wrote ${result.format} file to ${result.filePath}`
|
|
3960
|
+
});
|
|
3961
|
+
} catch (error) {
|
|
3962
|
+
return failedRun(run, handlers.nowIso(), error);
|
|
3963
|
+
}
|
|
3964
|
+
}
|
|
3965
|
+
}
|
|
3966
|
+
//#endregion
|
|
3967
|
+
//#region src/automation-delivery.ts
|
|
3968
|
+
function getTemplateValue(record, path) {
|
|
3969
|
+
let current = record;
|
|
3970
|
+
for (const segment of path.split(".")) {
|
|
3971
|
+
if (!segment) return;
|
|
3972
|
+
if (!current || typeof current !== "object" || Array.isArray(current)) return;
|
|
3973
|
+
current = current[segment];
|
|
3974
|
+
}
|
|
3975
|
+
return current;
|
|
3976
|
+
}
|
|
3977
|
+
function templateValueAsString(value) {
|
|
3978
|
+
if (value == null) return "";
|
|
3979
|
+
if (typeof value === "string") return value;
|
|
3980
|
+
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
|
3981
|
+
return JSON.stringify(value);
|
|
3982
|
+
}
|
|
3983
|
+
function renderAutomationTemplate(template, payload) {
|
|
3984
|
+
return template.replace(/\{\{\s*([a-zA-Z0-9_.-]+)\s*\}\}/g, (_, path) => templateValueAsString(getTemplateValue(payload, path)));
|
|
3985
|
+
}
|
|
3986
|
+
function defaultMeetingTitle(bundle, match) {
|
|
3987
|
+
return bundle?.meeting.meeting.title || bundle?.document.title || match.title;
|
|
3988
|
+
}
|
|
3989
|
+
function buildAutomationDeliveryPayload(context) {
|
|
3990
|
+
return {
|
|
3991
|
+
action: {
|
|
3992
|
+
id: context.action.id,
|
|
3993
|
+
kind: context.action.kind,
|
|
3994
|
+
name: context.action.name || context.action.id
|
|
3995
|
+
},
|
|
3996
|
+
approval: context.trigger === "approval" && context.artefact && context.decision ? {
|
|
3997
|
+
artefactId: context.artefact.id,
|
|
3998
|
+
decidedAt: context.generatedAt,
|
|
3999
|
+
decision: context.decision,
|
|
4000
|
+
note: context.note?.trim() || void 0
|
|
4001
|
+
} : void 0,
|
|
4002
|
+
artefact: context.artefact ? {
|
|
4003
|
+
actionId: context.artefact.actionId,
|
|
4004
|
+
id: context.artefact.id,
|
|
4005
|
+
kind: context.artefact.kind,
|
|
4006
|
+
markdown: context.artefact.structured.markdown,
|
|
4007
|
+
metadata: context.artefact.structured.metadata,
|
|
4008
|
+
model: context.artefact.model,
|
|
4009
|
+
prompt: context.artefact.prompt,
|
|
4010
|
+
provider: context.artefact.provider,
|
|
4011
|
+
status: context.artefact.status,
|
|
4012
|
+
summary: context.artefact.structured.summary,
|
|
4013
|
+
title: context.artefact.structured.title
|
|
4014
|
+
} : void 0,
|
|
4015
|
+
generatedAt: context.generatedAt,
|
|
4016
|
+
match: {
|
|
4017
|
+
...context.match,
|
|
4018
|
+
folders: context.match.folders.map((folder) => ({ ...folder })),
|
|
4019
|
+
tags: [...context.match.tags]
|
|
4020
|
+
},
|
|
4021
|
+
meeting: context.bundle ? {
|
|
4022
|
+
document: context.bundle.document,
|
|
4023
|
+
id: context.bundle.document.id,
|
|
4024
|
+
meeting: context.bundle.meeting,
|
|
4025
|
+
title: defaultMeetingTitle(context.bundle, context.match)
|
|
4026
|
+
} : void 0,
|
|
4027
|
+
rule: {
|
|
4028
|
+
id: context.rule.id,
|
|
4029
|
+
name: context.rule.name
|
|
4030
|
+
}
|
|
4031
|
+
};
|
|
4032
|
+
}
|
|
4033
|
+
function defaultDeliveryText(payload) {
|
|
4034
|
+
if (payload.artefact?.summary?.trim()) return payload.artefact.summary.trim();
|
|
4035
|
+
if (payload.artefact?.markdown?.trim()) return payload.artefact.markdown.trim();
|
|
4036
|
+
return `${payload.action.name} for ${payload.meeting?.title || payload.match.title}`;
|
|
4037
|
+
}
|
|
4038
|
+
function renderSlackMessageText(action, payload) {
|
|
4039
|
+
const text = action.text?.trim();
|
|
4040
|
+
if (text) return renderAutomationTemplate(text, payload).trim();
|
|
4041
|
+
return defaultDeliveryText(payload);
|
|
4042
|
+
}
|
|
4043
|
+
function renderWebhookBody(action, payload) {
|
|
4044
|
+
const format = action.payload ?? "json";
|
|
4045
|
+
if (format === "json") return {
|
|
4046
|
+
body: JSON.stringify(payload, null, 2),
|
|
4047
|
+
contentType: "application/json"
|
|
4048
|
+
};
|
|
4049
|
+
const template = action.bodyTemplate?.trim();
|
|
4050
|
+
return {
|
|
4051
|
+
body: template ? renderAutomationTemplate(template, payload).trim() : defaultDeliveryText(payload),
|
|
4052
|
+
contentType: format === "markdown" ? "text/markdown; charset=utf-8" : "text/plain; charset=utf-8"
|
|
4053
|
+
};
|
|
4054
|
+
}
|
|
4055
|
+
function defaultWriteFileExtension(format) {
|
|
4056
|
+
switch (format) {
|
|
4057
|
+
case "json": return "json";
|
|
4058
|
+
case "text": return "txt";
|
|
4059
|
+
default: return "md";
|
|
3889
4060
|
}
|
|
3890
4061
|
}
|
|
4062
|
+
function renderWriteFileName(action, payload) {
|
|
4063
|
+
const format = action.format ?? "markdown";
|
|
4064
|
+
const template = action.filenameTemplate?.trim();
|
|
4065
|
+
if (template) return sanitiseFilename(renderAutomationTemplate(template, payload), payload.action.id);
|
|
4066
|
+
return `${sanitiseFilename(`${payload.meeting?.title || payload.match.title}-${payload.artefact?.kind || payload.action.id}`)}.${defaultWriteFileExtension(format)}`;
|
|
4067
|
+
}
|
|
4068
|
+
function resolveWriteFilePath(action, payload) {
|
|
4069
|
+
return resolve(action.outputDir, renderWriteFileName(action, payload));
|
|
4070
|
+
}
|
|
4071
|
+
function renderWriteFileContent(action, payload) {
|
|
4072
|
+
const format = action.format ?? "markdown";
|
|
4073
|
+
const template = action.contentTemplate?.trim();
|
|
4074
|
+
if (template) return renderAutomationTemplate(template, payload);
|
|
4075
|
+
if (format === "json") return JSON.stringify(payload, null, 2);
|
|
4076
|
+
if (format === "text") return `${defaultDeliveryText(payload)}\n`;
|
|
4077
|
+
return `${payload.artefact?.markdown?.trim() || defaultDeliveryText(payload)}\n`;
|
|
4078
|
+
}
|
|
3891
4079
|
//#endregion
|
|
3892
4080
|
//#region src/automation-matches.ts
|
|
3893
4081
|
function cloneMatch(match) {
|
|
@@ -3996,6 +4184,7 @@ function cloneAction(action) {
|
|
|
3996
4184
|
switch (action.kind) {
|
|
3997
4185
|
case "agent": return {
|
|
3998
4186
|
...action,
|
|
4187
|
+
approvalMode: action.approvalMode,
|
|
3999
4188
|
fallbackHarnessIds: action.fallbackHarnessIds ? [...action.fallbackHarnessIds] : void 0,
|
|
4000
4189
|
pipeline: action.pipeline ? { ...action.pipeline } : void 0
|
|
4001
4190
|
};
|
|
@@ -4007,6 +4196,12 @@ function cloneAction(action) {
|
|
|
4007
4196
|
};
|
|
4008
4197
|
case "export-notes":
|
|
4009
4198
|
case "export-transcript": return { ...action };
|
|
4199
|
+
case "slack-message": return { ...action };
|
|
4200
|
+
case "webhook": return {
|
|
4201
|
+
...action,
|
|
4202
|
+
headers: action.headers ? { ...action.headers } : void 0
|
|
4203
|
+
};
|
|
4204
|
+
case "write-file": return { ...action };
|
|
4010
4205
|
}
|
|
4011
4206
|
}
|
|
4012
4207
|
function stringArray(value) {
|
|
@@ -4027,6 +4222,9 @@ function parsePipeline(value) {
|
|
|
4027
4222
|
const kind = record ? stringValue(record.kind).trim() : typeof value === "string" ? value.trim() : "";
|
|
4028
4223
|
return kind === "enrichment" || kind === "notes" ? { kind } : void 0;
|
|
4029
4224
|
}
|
|
4225
|
+
function parseTrigger(value) {
|
|
4226
|
+
if (value === "approval" || value === "match") return value;
|
|
4227
|
+
}
|
|
4030
4228
|
function parseAction(value, index) {
|
|
4031
4229
|
if (!value || typeof value !== "object" || Array.isArray(value)) return;
|
|
4032
4230
|
const record = value;
|
|
@@ -4046,6 +4244,7 @@ function parseAction(value, index) {
|
|
|
4046
4244
|
const systemPromptFile = typeof record.systemPromptFile === "string" && record.systemPromptFile.trim() ? record.systemPromptFile.trim() : void 0;
|
|
4047
4245
|
if (!prompt && !promptFile && !harnessId) return;
|
|
4048
4246
|
return {
|
|
4247
|
+
approvalMode: record.approvalMode === "auto" || record.approvalMode === "manual" ? record.approvalMode : void 0,
|
|
4049
4248
|
cwd: typeof record.cwd === "string" && record.cwd.trim() ? record.cwd.trim() : void 0,
|
|
4050
4249
|
dryRun: typeof record.dryRun === "boolean" ? record.dryRun : void 0,
|
|
4051
4250
|
enabled,
|
|
@@ -4089,8 +4288,10 @@ function parseAction(value, index) {
|
|
|
4089
4288
|
id,
|
|
4090
4289
|
kind,
|
|
4091
4290
|
name,
|
|
4291
|
+
sourceActionId: typeof record.sourceActionId === "string" && record.sourceActionId.trim() ? record.sourceActionId.trim() : void 0,
|
|
4092
4292
|
stdin: record.stdin === "json" || record.stdin === "none" ? record.stdin : void 0,
|
|
4093
|
-
timeoutMs: typeof record.timeoutMs === "number" && Number.isFinite(record.timeoutMs) ? record.timeoutMs : typeof record.timeoutMs === "string" && /^\d+$/.test(record.timeoutMs) ? Number(record.timeoutMs) : void 0
|
|
4293
|
+
timeoutMs: typeof record.timeoutMs === "number" && Number.isFinite(record.timeoutMs) ? record.timeoutMs : typeof record.timeoutMs === "string" && /^\d+$/.test(record.timeoutMs) ? Number(record.timeoutMs) : void 0,
|
|
4294
|
+
trigger: parseTrigger(record.trigger)
|
|
4094
4295
|
};
|
|
4095
4296
|
}
|
|
4096
4297
|
case "export-notes":
|
|
@@ -4115,6 +4316,52 @@ function parseAction(value, index) {
|
|
|
4115
4316
|
outputDir: typeof record.outputDir === "string" && record.outputDir.trim() ? record.outputDir.trim() : void 0,
|
|
4116
4317
|
scopedOutput: typeof record.scopedOutput === "boolean" ? record.scopedOutput : void 0
|
|
4117
4318
|
};
|
|
4319
|
+
case "slack-message":
|
|
4320
|
+
if (!id) return;
|
|
4321
|
+
return {
|
|
4322
|
+
enabled,
|
|
4323
|
+
id,
|
|
4324
|
+
kind,
|
|
4325
|
+
name,
|
|
4326
|
+
sourceActionId: typeof record.sourceActionId === "string" && record.sourceActionId.trim() ? record.sourceActionId.trim() : void 0,
|
|
4327
|
+
text: typeof record.text === "string" && record.text.trim() ? record.text.trim() : void 0,
|
|
4328
|
+
trigger: parseTrigger(record.trigger),
|
|
4329
|
+
webhookUrl: typeof record.webhookUrl === "string" && record.webhookUrl.trim() ? record.webhookUrl.trim() : void 0,
|
|
4330
|
+
webhookUrlEnv: typeof record.webhookUrlEnv === "string" && record.webhookUrlEnv.trim() ? record.webhookUrlEnv.trim() : void 0
|
|
4331
|
+
};
|
|
4332
|
+
case "webhook":
|
|
4333
|
+
if (!id) return;
|
|
4334
|
+
return {
|
|
4335
|
+
bodyTemplate: typeof record.bodyTemplate === "string" && record.bodyTemplate.trim() ? record.bodyTemplate.trim() : void 0,
|
|
4336
|
+
enabled,
|
|
4337
|
+
headers: stringRecord(record.headers),
|
|
4338
|
+
id,
|
|
4339
|
+
kind,
|
|
4340
|
+
method: typeof record.method === "string" && record.method.trim() ? record.method.trim().toUpperCase() : void 0,
|
|
4341
|
+
name,
|
|
4342
|
+
payload: record.payload === "json" || record.payload === "markdown" || record.payload === "text" ? record.payload : void 0,
|
|
4343
|
+
sourceActionId: typeof record.sourceActionId === "string" && record.sourceActionId.trim() ? record.sourceActionId.trim() : void 0,
|
|
4344
|
+
trigger: parseTrigger(record.trigger),
|
|
4345
|
+
url: typeof record.url === "string" && record.url.trim() ? record.url.trim() : void 0,
|
|
4346
|
+
urlEnv: typeof record.urlEnv === "string" && record.urlEnv.trim() ? record.urlEnv.trim() : void 0
|
|
4347
|
+
};
|
|
4348
|
+
case "write-file": {
|
|
4349
|
+
const outputDir = typeof record.outputDir === "string" && record.outputDir.trim() ? record.outputDir.trim() : void 0;
|
|
4350
|
+
if (!id || !outputDir) return;
|
|
4351
|
+
return {
|
|
4352
|
+
contentTemplate: typeof record.contentTemplate === "string" && record.contentTemplate.trim() ? record.contentTemplate.trim() : void 0,
|
|
4353
|
+
enabled,
|
|
4354
|
+
filenameTemplate: typeof record.filenameTemplate === "string" && record.filenameTemplate.trim() ? record.filenameTemplate.trim() : void 0,
|
|
4355
|
+
format: record.format === "json" || record.format === "markdown" || record.format === "text" ? record.format : void 0,
|
|
4356
|
+
id,
|
|
4357
|
+
kind,
|
|
4358
|
+
name,
|
|
4359
|
+
outputDir,
|
|
4360
|
+
overwrite: typeof record.overwrite === "boolean" ? record.overwrite : void 0,
|
|
4361
|
+
sourceActionId: typeof record.sourceActionId === "string" && record.sourceActionId.trim() ? record.sourceActionId.trim() : void 0,
|
|
4362
|
+
trigger: parseTrigger(record.trigger)
|
|
4363
|
+
};
|
|
4364
|
+
}
|
|
4118
4365
|
default: return;
|
|
4119
4366
|
}
|
|
4120
4367
|
}
|
|
@@ -6017,6 +6264,12 @@ var GranolaApp = class {
|
|
|
6017
6264
|
};
|
|
6018
6265
|
case "export-notes":
|
|
6019
6266
|
case "export-transcript": return { ...action };
|
|
6267
|
+
case "slack-message": return { ...action };
|
|
6268
|
+
case "webhook": return {
|
|
6269
|
+
...action,
|
|
6270
|
+
headers: action.headers ? { ...action.headers } : void 0
|
|
6271
|
+
};
|
|
6272
|
+
case "write-file": return { ...action };
|
|
6020
6273
|
}
|
|
6021
6274
|
}),
|
|
6022
6275
|
when: {
|
|
@@ -6145,7 +6398,10 @@ var GranolaApp = class {
|
|
|
6145
6398
|
exportTranscripts: async (nextMatch, nextAction) => await this.runAutomationTranscriptAction(nextMatch, nextAction),
|
|
6146
6399
|
nowIso: () => this.nowIso(),
|
|
6147
6400
|
runAgent: async (nextMatch, nextRule, nextAction, run) => await this.runAutomationAgent(nextMatch, nextRule, nextAction, run),
|
|
6148
|
-
runCommand: async (nextMatch, nextRule, nextAction) => await this.runAutomationCommand(nextMatch, nextRule, nextAction)
|
|
6401
|
+
runCommand: async (nextMatch, nextRule, nextAction, context) => await this.runAutomationCommand(nextMatch, nextRule, nextAction, context),
|
|
6402
|
+
runSlackMessage: async (nextMatch, nextRule, nextAction, context) => await this.runAutomationSlackMessage(nextMatch, nextRule, nextAction, context),
|
|
6403
|
+
runWebhook: async (nextMatch, nextRule, nextAction, context) => await this.runAutomationWebhook(nextMatch, nextRule, nextAction, context),
|
|
6404
|
+
writeFile: async (nextMatch, nextRule, nextAction, context) => await this.runAutomationWriteFile(nextMatch, nextRule, nextAction, context)
|
|
6149
6405
|
};
|
|
6150
6406
|
}
|
|
6151
6407
|
async currentMeetingSummariesForProcessing() {
|
|
@@ -6482,17 +6738,60 @@ var GranolaApp = class {
|
|
|
6482
6738
|
this.emitStateUpdate();
|
|
6483
6739
|
return this.cloneAutomationRun(resolved);
|
|
6484
6740
|
}
|
|
6741
|
+
async readAutomationMatchById(id) {
|
|
6742
|
+
return (this.deps.automationMatchStore ? (await this.deps.automationMatchStore.readMatches(0)).find((candidate) => candidate.id === id) : void 0) ?? this.#automationMatches.find((candidate) => candidate.id === id);
|
|
6743
|
+
}
|
|
6744
|
+
pipelineApprovalMode(action) {
|
|
6745
|
+
return action.approvalMode ?? "manual";
|
|
6746
|
+
}
|
|
6747
|
+
async runPostApprovalActions(artefact, options) {
|
|
6748
|
+
if (options.decision !== "approve") return [];
|
|
6749
|
+
const rule = (await this.loadAutomationRules({ forceRefresh: true })).find((candidate) => candidate.id === artefact.ruleId);
|
|
6750
|
+
if (!rule) return [];
|
|
6751
|
+
const match = await this.readAutomationMatchById(artefact.matchId);
|
|
6752
|
+
if (!match) return [];
|
|
6753
|
+
const actions = enabledAutomationActions(rule, {
|
|
6754
|
+
sourceActionId: artefact.actionId,
|
|
6755
|
+
trigger: "approval"
|
|
6756
|
+
});
|
|
6757
|
+
if (actions.length === 0) return [];
|
|
6758
|
+
const existingRunIds = new Set(this.#automationActionRuns.map((run) => run.id));
|
|
6759
|
+
const runs = [];
|
|
6760
|
+
for (const action of actions) {
|
|
6761
|
+
const runId = buildAutomationApprovalActionRunId(artefact, action.id);
|
|
6762
|
+
if (existingRunIds.has(runId)) continue;
|
|
6763
|
+
existingRunIds.add(runId);
|
|
6764
|
+
runs.push(await executeAutomationAction(this.cloneAutomationMatch(match), rule, action, this.automationActionHandlers(), {
|
|
6765
|
+
context: {
|
|
6766
|
+
artefact: this.cloneAutomationArtefact(artefact),
|
|
6767
|
+
decision: options.decision,
|
|
6768
|
+
note: options.note,
|
|
6769
|
+
trigger: "approval"
|
|
6770
|
+
},
|
|
6771
|
+
runId
|
|
6772
|
+
}));
|
|
6773
|
+
}
|
|
6774
|
+
await this.appendAutomationRuns(runs);
|
|
6775
|
+
this.emitStateUpdate();
|
|
6776
|
+
return runs.map((run) => this.cloneAutomationRun(run));
|
|
6777
|
+
}
|
|
6485
6778
|
async resolveAutomationArtefact(id, decision, options = {}) {
|
|
6486
6779
|
const current = await this.readAutomationArtefactById(id);
|
|
6487
6780
|
if (!current) throw new Error(`automation artefact not found: ${id}`);
|
|
6488
6781
|
this.assertMutableAutomationArtefact(current);
|
|
6782
|
+
const shouldRunPostApproval = decision === "approve" && current.status !== "approved";
|
|
6489
6783
|
const nextArtefact = {
|
|
6490
6784
|
...this.cloneAutomationArtefact(current),
|
|
6491
6785
|
history: [...current.history.map((entry) => ({ ...entry })), this.buildAutomationArtefactHistoryEntry(decision === "approve" ? "approved" : "rejected", options.note)],
|
|
6492
6786
|
status: decision === "approve" ? "approved" : "rejected",
|
|
6493
6787
|
updatedAt: this.nowIso()
|
|
6494
6788
|
};
|
|
6495
|
-
|
|
6789
|
+
const replaced = await this.replaceAutomationArtefact(nextArtefact);
|
|
6790
|
+
if (shouldRunPostApproval) await this.runPostApprovalActions(replaced, {
|
|
6791
|
+
decision,
|
|
6792
|
+
note: options.note
|
|
6793
|
+
});
|
|
6794
|
+
return replaced;
|
|
6496
6795
|
}
|
|
6497
6796
|
async updateAutomationArtefact(id, patch) {
|
|
6498
6797
|
const current = await this.readAutomationArtefactById(id);
|
|
@@ -6589,13 +6888,7 @@ var GranolaApp = class {
|
|
|
6589
6888
|
if (!action || action.kind !== "agent" || !action.pipeline) throw new Error(`automation artefact is not rerunnable: ${id}`);
|
|
6590
6889
|
const match = (this.deps.automationMatchStore ? (await this.deps.automationMatchStore.readMatches(0)).find((candidate) => candidate.id === current.matchId) : void 0) ?? this.#automationMatches.find((candidate) => candidate.id === current.matchId);
|
|
6591
6890
|
if (!match) throw new Error(`automation match not found: ${current.matchId}`);
|
|
6592
|
-
const nextRun = await executeAutomationAction(this.cloneAutomationMatch(match), rule, action, {
|
|
6593
|
-
exportNotes: async (nextMatch, nextAction) => await this.runAutomationNotesAction(nextMatch, nextAction),
|
|
6594
|
-
exportTranscripts: async (nextMatch, nextAction) => await this.runAutomationTranscriptAction(nextMatch, nextAction),
|
|
6595
|
-
nowIso: () => this.nowIso(),
|
|
6596
|
-
runAgent: async (nextMatch, nextRule, nextAction, run) => await this.runAutomationAgent(nextMatch, nextRule, nextAction, run),
|
|
6597
|
-
runCommand: async (nextMatch, nextRule, nextAction) => await this.runAutomationCommand(nextMatch, nextRule, nextAction)
|
|
6598
|
-
}, {
|
|
6891
|
+
const nextRun = await executeAutomationAction(this.cloneAutomationMatch(match), rule, action, this.automationActionHandlers(), {
|
|
6599
6892
|
rerunOfId: current.runId,
|
|
6600
6893
|
runId: `${current.runId}:rerun:${this.nowIso().replaceAll(/[-:.]/g, "").replace("T", "").replace("Z", "")}`
|
|
6601
6894
|
});
|
|
@@ -6720,26 +7013,33 @@ var GranolaApp = class {
|
|
|
6720
7013
|
written: result.written
|
|
6721
7014
|
};
|
|
6722
7015
|
}
|
|
6723
|
-
async
|
|
6724
|
-
|
|
7016
|
+
async buildAutomationExecutionBundle(match) {
|
|
7017
|
+
if (match.eventKind === "meeting.removed") return;
|
|
7018
|
+
return await this.maybeReadMeetingBundleById(match.meetingId, { requireCache: false });
|
|
7019
|
+
}
|
|
7020
|
+
buildAutomationDeliveryPayloadForAction(match, rule, action, context, bundle) {
|
|
7021
|
+
return buildAutomationDeliveryPayload({
|
|
7022
|
+
action,
|
|
7023
|
+
artefact: context.artefact ? this.cloneAutomationArtefact(context.artefact) : void 0,
|
|
7024
|
+
bundle,
|
|
7025
|
+
decision: context.decision,
|
|
7026
|
+
generatedAt: this.nowIso(),
|
|
7027
|
+
match: this.cloneAutomationMatch(match),
|
|
7028
|
+
note: context.note,
|
|
7029
|
+
rule,
|
|
7030
|
+
trigger: context.trigger
|
|
7031
|
+
});
|
|
7032
|
+
}
|
|
7033
|
+
async runAutomationCommand(match, rule, action, context) {
|
|
7034
|
+
const bundle = await this.buildAutomationExecutionBundle(match);
|
|
6725
7035
|
const cwd = action.cwd ? resolve(action.cwd) : process.cwd();
|
|
6726
7036
|
const payload = JSON.stringify({
|
|
6727
|
-
|
|
7037
|
+
...this.buildAutomationDeliveryPayloadForAction(match, rule, {
|
|
6728
7038
|
id: action.id,
|
|
6729
7039
|
kind: "command",
|
|
6730
7040
|
name: automationActionName(action)
|
|
6731
|
-
},
|
|
6732
|
-
authMode: this.#state.auth.mode
|
|
6733
|
-
generatedAt: this.nowIso(),
|
|
6734
|
-
match: this.cloneAutomationMatch(match),
|
|
6735
|
-
meeting: bundle ? {
|
|
6736
|
-
document: bundle.document,
|
|
6737
|
-
meeting: bundle.meeting
|
|
6738
|
-
} : void 0,
|
|
6739
|
-
rule: {
|
|
6740
|
-
id: rule.id,
|
|
6741
|
-
name: rule.name
|
|
6742
|
-
}
|
|
7041
|
+
}, context, bundle),
|
|
7042
|
+
authMode: this.#state.auth.mode
|
|
6743
7043
|
}, null, 2);
|
|
6744
7044
|
return await new Promise((resolve, reject) => {
|
|
6745
7045
|
const child = spawn(action.command, action.args ?? [], {
|
|
@@ -6748,6 +7048,9 @@ var GranolaApp = class {
|
|
|
6748
7048
|
...process.env,
|
|
6749
7049
|
...action.env,
|
|
6750
7050
|
GRANOLA_ACTION_KIND: "command",
|
|
7051
|
+
GRANOLA_ACTION_TRIGGER: context.trigger,
|
|
7052
|
+
GRANOLA_APPROVAL_DECISION: context.decision,
|
|
7053
|
+
GRANOLA_ARTEFACT_ID: context.artefact?.id,
|
|
6751
7054
|
GRANOLA_EVENT_ID: match.eventId,
|
|
6752
7055
|
GRANOLA_EVENT_KIND: match.eventKind,
|
|
6753
7056
|
GRANOLA_MATCH_ID: match.id,
|
|
@@ -6800,6 +7103,73 @@ var GranolaApp = class {
|
|
|
6800
7103
|
child.stdin.end();
|
|
6801
7104
|
});
|
|
6802
7105
|
}
|
|
7106
|
+
async runAutomationWebhook(match, rule, action, context) {
|
|
7107
|
+
const bundle = await this.buildAutomationExecutionBundle(match);
|
|
7108
|
+
const payload = this.buildAutomationDeliveryPayloadForAction(match, rule, {
|
|
7109
|
+
id: action.id,
|
|
7110
|
+
kind: "webhook",
|
|
7111
|
+
name: automationActionName(action)
|
|
7112
|
+
}, context, bundle);
|
|
7113
|
+
const url = action.url?.trim() || (action.urlEnv ? process.env[action.urlEnv]?.trim() : "");
|
|
7114
|
+
if (!url) throw new Error(`automation webhook action ${action.id} is missing a URL`);
|
|
7115
|
+
const rendered = renderWebhookBody(action, payload);
|
|
7116
|
+
const response = await fetch(url, {
|
|
7117
|
+
body: rendered.body,
|
|
7118
|
+
headers: {
|
|
7119
|
+
"content-type": rendered.contentType,
|
|
7120
|
+
...action.headers
|
|
7121
|
+
},
|
|
7122
|
+
method: action.method ?? "POST"
|
|
7123
|
+
});
|
|
7124
|
+
const output = (await response.text()).trim() || void 0;
|
|
7125
|
+
if (!response.ok) throw new Error(output || `automation webhook failed with status ${response.status}`);
|
|
7126
|
+
return {
|
|
7127
|
+
output,
|
|
7128
|
+
status: response.status,
|
|
7129
|
+
url
|
|
7130
|
+
};
|
|
7131
|
+
}
|
|
7132
|
+
async runAutomationSlackMessage(match, rule, action, context) {
|
|
7133
|
+
const bundle = await this.buildAutomationExecutionBundle(match);
|
|
7134
|
+
const payload = this.buildAutomationDeliveryPayloadForAction(match, rule, {
|
|
7135
|
+
id: action.id,
|
|
7136
|
+
kind: "slack-message",
|
|
7137
|
+
name: automationActionName(action)
|
|
7138
|
+
}, context, bundle);
|
|
7139
|
+
const url = action.webhookUrl?.trim() || (action.webhookUrlEnv ? process.env[action.webhookUrlEnv]?.trim() : process.env.SLACK_WEBHOOK_URL?.trim());
|
|
7140
|
+
if (!url) throw new Error(`automation Slack action ${action.id} is missing a webhook URL`);
|
|
7141
|
+
const text = renderSlackMessageText(action, payload);
|
|
7142
|
+
const response = await fetch(url, {
|
|
7143
|
+
body: JSON.stringify({ text }),
|
|
7144
|
+
headers: { "content-type": "application/json" },
|
|
7145
|
+
method: "POST"
|
|
7146
|
+
});
|
|
7147
|
+
const output = (await response.text()).trim() || void 0;
|
|
7148
|
+
if (!response.ok) throw new Error(output || `automation Slack action failed with status ${response.status}`);
|
|
7149
|
+
return {
|
|
7150
|
+
output,
|
|
7151
|
+
status: response.status,
|
|
7152
|
+
text,
|
|
7153
|
+
url
|
|
7154
|
+
};
|
|
7155
|
+
}
|
|
7156
|
+
async runAutomationWriteFile(match, rule, action, context) {
|
|
7157
|
+
const bundle = await this.buildAutomationExecutionBundle(match);
|
|
7158
|
+
const payload = this.buildAutomationDeliveryPayloadForAction(match, rule, {
|
|
7159
|
+
id: action.id,
|
|
7160
|
+
kind: "write-file",
|
|
7161
|
+
name: automationActionName(action)
|
|
7162
|
+
}, context, bundle);
|
|
7163
|
+
const filePath = resolveWriteFilePath(action, payload);
|
|
7164
|
+
if (existsSync(filePath) && action.overwrite === false) throw new Error(`automation write-file target already exists: ${filePath}`);
|
|
7165
|
+
const content = renderWriteFileContent(action, payload);
|
|
7166
|
+
await writeTextFile(filePath, content);
|
|
7167
|
+
return {
|
|
7168
|
+
bytes: Buffer.byteLength(content, "utf8"),
|
|
7169
|
+
filePath,
|
|
7170
|
+
format: action.format ?? "markdown"
|
|
7171
|
+
};
|
|
7172
|
+
}
|
|
6803
7173
|
async buildAutomationAgentAttempt(match, rule, action, bundle, harness) {
|
|
6804
7174
|
const harnessCwd = harness?.cwd;
|
|
6805
7175
|
const promptFile = await readOptionalActionFile(action.promptFile, action.cwd ?? harnessCwd);
|
|
@@ -6887,7 +7257,7 @@ var GranolaApp = class {
|
|
|
6887
7257
|
};
|
|
6888
7258
|
await this.writeAutomationArtefacts([artefact, ...this.#automationArtefacts]);
|
|
6889
7259
|
return {
|
|
6890
|
-
artefactIds: [artefact.id],
|
|
7260
|
+
artefactIds: [(this.pipelineApprovalMode(action) === "auto" ? await this.resolveAutomationArtefact(artefact.id, "approve", { note: "Auto-approved by automation rule" }) : artefact).id],
|
|
6891
7261
|
attempts: attemptMeta,
|
|
6892
7262
|
command: result.command,
|
|
6893
7263
|
dryRun: result.dryRun,
|
|
@@ -6931,13 +7301,7 @@ var GranolaApp = class {
|
|
|
6931
7301
|
const runId = buildAutomationActionRunId(match, action.id);
|
|
6932
7302
|
if (existingRunIds.has(runId)) continue;
|
|
6933
7303
|
existingRunIds.add(runId);
|
|
6934
|
-
runs.push(await executeAutomationAction(match, rule, action,
|
|
6935
|
-
exportNotes: async (nextMatch, nextAction) => await this.runAutomationNotesAction(nextMatch, nextAction),
|
|
6936
|
-
exportTranscripts: async (nextMatch, nextAction) => await this.runAutomationTranscriptAction(nextMatch, nextAction),
|
|
6937
|
-
nowIso: () => this.nowIso(),
|
|
6938
|
-
runAgent: async (nextMatch, nextRule, nextAction, run) => await this.runAutomationAgent(nextMatch, nextRule, nextAction, run),
|
|
6939
|
-
runCommand: async (nextMatch, nextRule, nextAction) => await this.runAutomationCommand(nextMatch, nextRule, nextAction)
|
|
6940
|
-
}));
|
|
7304
|
+
runs.push(await executeAutomationAction(match, rule, action, this.automationActionHandlers()));
|
|
6941
7305
|
}
|
|
6942
7306
|
}
|
|
6943
7307
|
await this.appendAutomationRuns(runs);
|