@sellable/install 0.1.589 → 0.1.590-wip.phase187.20260811.5
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.
|
@@ -20,6 +20,7 @@ import { createHash } from "node:crypto";
|
|
|
20
20
|
|
|
21
21
|
const SHA256 = /^[a-f0-9]{64}$/;
|
|
22
22
|
const EXACT_VERSION = /^\d+\.\d+\.\d+(?:[-.A-Za-z0-9]+)?$/;
|
|
23
|
+
const PACKAGE_DISPOSITIONS = new Set(["changed", "reused"]);
|
|
23
24
|
const PUBLIC_ACTIONS = new Set(["INSTANTIATE", "REFRESH"]);
|
|
24
25
|
const RESTORATION_ACTIONS = new Set([
|
|
25
26
|
"START",
|
|
@@ -64,14 +65,13 @@ export function validateAdminRuntimeTuple(tuple) {
|
|
|
64
65
|
}
|
|
65
66
|
for (const name of ADMIN_RUNTIME_TUPLE_MEMBERS) {
|
|
66
67
|
const item = tuple.packages[name];
|
|
67
|
-
const expectedDisposition = name === "@sellable/mcp" ? "reused" : "changed";
|
|
68
68
|
if (
|
|
69
69
|
!item ||
|
|
70
70
|
item.name !== name ||
|
|
71
71
|
!EXACT_VERSION.test(item.version ?? "") ||
|
|
72
72
|
typeof item.integrity !== "string" ||
|
|
73
73
|
!/^sha512-[A-Za-z0-9+/]{80,}={0,2}$/.test(item.integrity) ||
|
|
74
|
-
item.disposition
|
|
74
|
+
!PACKAGE_DISPOSITIONS.has(item.disposition)
|
|
75
75
|
) {
|
|
76
76
|
return {
|
|
77
77
|
ok: false,
|
|
@@ -67,6 +67,7 @@ import {
|
|
|
67
67
|
HERMES_SNAPSHOT_HOOKS,
|
|
68
68
|
HERMES_SNAPSHOT_HOOK_ALLOWLIST_BYTES,
|
|
69
69
|
applyHermesHostedBackgroundReviewPolicy,
|
|
70
|
+
ensureHermesOpportunitySequencesCron,
|
|
70
71
|
ensureHermesSnapshotCron,
|
|
71
72
|
markHermesSnapshotDirty,
|
|
72
73
|
mergeHermesState,
|
|
@@ -696,6 +697,22 @@ const ADMIN_LIVE_SKILLS_CONFIG_KEYS = Object.freeze([
|
|
|
696
697
|
"disabled",
|
|
697
698
|
"platform_disabled",
|
|
698
699
|
]);
|
|
700
|
+
function packagedAdminSkillDirectories() {
|
|
701
|
+
const skillsRoot = join(ADMIN_INSTALL_ASSET_ROOT, "mcp", "skills");
|
|
702
|
+
const directories = new Set(["sellable"]);
|
|
703
|
+
for (const entry of readdirSync(skillsRoot, { withFileTypes: true })) {
|
|
704
|
+
if (entry.isSymbolicLink() || !entry.isDirectory()) {
|
|
705
|
+
throw new Error("admin_runtime_packaged_skills_inventory_rejected");
|
|
706
|
+
}
|
|
707
|
+
directories.add(entry.name);
|
|
708
|
+
}
|
|
709
|
+
return directories;
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
// Candidate run 31458025989 proved that a fresh profile must not classify
|
|
713
|
+
// package-backed Admin skills as operator state: doing so made both allowed
|
|
714
|
+
// SOUL restage attempts compare an empty live inventory with the sealed stage.
|
|
715
|
+
const ADMIN_SEALED_SKILL_DIRECTORIES = packagedAdminSkillDirectories();
|
|
699
716
|
|
|
700
717
|
function adminPreservedSkillsIdentity(profileRoot) {
|
|
701
718
|
if (!existsSync(profileRoot)) return null;
|
|
@@ -704,7 +721,7 @@ function adminPreservedSkillsIdentity(profileRoot) {
|
|
|
704
721
|
const walk = (directory, relativePath = "") => {
|
|
705
722
|
for (const name of readdirSync(directory).sort()) {
|
|
706
723
|
const nextRelative = relativePath ? `${relativePath}/${name}` : name;
|
|
707
|
-
if (nextRelative
|
|
724
|
+
if (ADMIN_SEALED_SKILL_DIRECTORIES.has(nextRelative)) continue;
|
|
708
725
|
const path = join(directory, name);
|
|
709
726
|
const stat = lstatSync(path);
|
|
710
727
|
if (stat.isSymbolicLink()) {
|
|
@@ -961,12 +978,22 @@ async function materializeAdminProfile({
|
|
|
961
978
|
hermesCli: HERMES,
|
|
962
979
|
});
|
|
963
980
|
if (!memoryCron.ok) throw new Error(memoryCron.code);
|
|
981
|
+
const opportunitySequencesCron = await ensureHermesOpportunitySequencesCron({
|
|
982
|
+
profileId: profile,
|
|
983
|
+
profileRoot: profileHome,
|
|
984
|
+
hermesRoot: DATA_ROOT,
|
|
985
|
+
statePath: layout.statePath,
|
|
986
|
+
});
|
|
987
|
+
if (!opportunitySequencesCron.ok) {
|
|
988
|
+
throw new Error(opportunitySequencesCron.code);
|
|
989
|
+
}
|
|
964
990
|
return {
|
|
965
991
|
stage,
|
|
966
992
|
promotion: promoted.promotion,
|
|
967
993
|
profile,
|
|
968
994
|
profileHome,
|
|
969
995
|
adminHome,
|
|
996
|
+
opportunitySequencesCron,
|
|
970
997
|
observedSoulRawSha256: promoted.observedSoulRawSha256,
|
|
971
998
|
servers: [serverProofs.sellable, serverProofs["sellable-admin"]].map(
|
|
972
999
|
({ tools: _tools, ...proof }) => proof
|
|
@@ -55,9 +55,20 @@ export const HERMES_SNAPSHOT_CRON = Object.freeze({
|
|
|
55
55
|
installedScript: "scripts/hermes-memory-reconcile.sh",
|
|
56
56
|
noAgent: true,
|
|
57
57
|
});
|
|
58
|
+
export const HERMES_OPPORTUNITY_SEQUENCES_CRON = Object.freeze({
|
|
59
|
+
name: "sellable-opportunity-sequences",
|
|
60
|
+
schedule: "every 30m",
|
|
61
|
+
deliver: "slack",
|
|
62
|
+
skills: Object.freeze(["opportunity-sequences"]),
|
|
63
|
+
enabledToolsets: Object.freeze(["safe", "sellable", "mcp"]),
|
|
64
|
+
noAgent: false,
|
|
65
|
+
});
|
|
66
|
+
export const HERMES_OPPORTUNITY_SEQUENCES_JOURNAL_ROOT =
|
|
67
|
+
"/opt/data/.sellable-agent/admin-runtime/opportunity-sequences";
|
|
58
68
|
const LEGACY_HERMES_SNAPSHOT_SCRIPT = "scripts/hermes-memory-reconcile.sh";
|
|
59
69
|
|
|
60
70
|
const HERMES = "/opt/hermes/.venv/bin/hermes";
|
|
71
|
+
const HERMES_PYTHON = "/opt/hermes/.venv/bin/python";
|
|
61
72
|
const VERSION = "0.20.0";
|
|
62
73
|
const ID = /^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$/;
|
|
63
74
|
const SHA256 = /^[a-f0-9]{64}$/;
|
|
@@ -259,6 +270,68 @@ function cronJobs(profileRoot) {
|
|
|
259
270
|
return jobs;
|
|
260
271
|
}
|
|
261
272
|
|
|
273
|
+
const sameJson = (left, right) =>
|
|
274
|
+
JSON.stringify(left) === JSON.stringify(right);
|
|
275
|
+
|
|
276
|
+
export async function ensureHermesNamedCron({
|
|
277
|
+
profileRoot,
|
|
278
|
+
name,
|
|
279
|
+
isExpected,
|
|
280
|
+
isUpdatable = () => true,
|
|
281
|
+
create,
|
|
282
|
+
update,
|
|
283
|
+
codePrefix = "hermes_named_cron",
|
|
284
|
+
}) {
|
|
285
|
+
if (
|
|
286
|
+
!isAbsolute(profileRoot) ||
|
|
287
|
+
!ID.test(name ?? "") ||
|
|
288
|
+
typeof isExpected !== "function" ||
|
|
289
|
+
typeof create !== "function" ||
|
|
290
|
+
typeof update !== "function"
|
|
291
|
+
) {
|
|
292
|
+
return failed(`${codePrefix}_identity_rejected`);
|
|
293
|
+
}
|
|
294
|
+
try {
|
|
295
|
+
const before = cronJobs(profileRoot);
|
|
296
|
+
const named = before.filter((job) => job?.name === name);
|
|
297
|
+
if (named.length > 1 || (named[0] && !isUpdatable(named[0]))) {
|
|
298
|
+
return failed(`${codePrefix}_inventory_rejected`);
|
|
299
|
+
}
|
|
300
|
+
if (named[0] && isExpected(named[0])) {
|
|
301
|
+
return {
|
|
302
|
+
ok: true,
|
|
303
|
+
status: "REUSED",
|
|
304
|
+
jobId: named[0].id,
|
|
305
|
+
job: named[0],
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
const siblings = before.filter((job) => job?.name !== name);
|
|
309
|
+
const mutation = named[0] ? await update(named[0]) : await create();
|
|
310
|
+
if (childFailed(mutation)) {
|
|
311
|
+
return failed(`${codePrefix}_${named[0] ? "update" : "create"}_failed`);
|
|
312
|
+
}
|
|
313
|
+
const after = cronJobs(profileRoot);
|
|
314
|
+
const readback = after.filter((job) => job?.name === name);
|
|
315
|
+
const afterSiblings = after.filter((job) => job?.name !== name);
|
|
316
|
+
if (!sameJson(afterSiblings, siblings)) {
|
|
317
|
+
return failed(`${codePrefix}_sibling_readback_rejected`);
|
|
318
|
+
}
|
|
319
|
+
if (readback.length !== 1 || !isExpected(readback[0])) {
|
|
320
|
+
return failed(
|
|
321
|
+
`${codePrefix}_${named[0] ? "update_readback" : "readback"}_rejected`
|
|
322
|
+
);
|
|
323
|
+
}
|
|
324
|
+
return {
|
|
325
|
+
ok: true,
|
|
326
|
+
status: named[0] ? "UPDATED" : "CREATED",
|
|
327
|
+
jobId: readback[0].id,
|
|
328
|
+
job: readback[0],
|
|
329
|
+
};
|
|
330
|
+
} catch {
|
|
331
|
+
return failed(`${codePrefix}_install_failed`);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
262
335
|
function isCron(job) {
|
|
263
336
|
return Boolean(
|
|
264
337
|
job?.id &&
|
|
@@ -430,25 +503,23 @@ export async function ensureHermesSnapshotCron({
|
|
|
430
503
|
readFileSync(reconcileTemplatePath),
|
|
431
504
|
0o700
|
|
432
505
|
);
|
|
433
|
-
const
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
}
|
|
442
|
-
if (before[0]) {
|
|
443
|
-
if (isLegacyCron(before[0])) {
|
|
444
|
-
const migrated = await childRunner(runChild)(
|
|
506
|
+
const result = await ensureHermesNamedCron({
|
|
507
|
+
profileRoot,
|
|
508
|
+
name: HERMES_SNAPSHOT_CRON.name,
|
|
509
|
+
isExpected: isCron,
|
|
510
|
+
isUpdatable: (job) => isCron(job) || isLegacyCron(job),
|
|
511
|
+
codePrefix: "hermes_snapshot_cron",
|
|
512
|
+
create: () =>
|
|
513
|
+
childRunner(runChild)(
|
|
445
514
|
invocation(
|
|
446
515
|
[
|
|
447
516
|
"--profile",
|
|
448
517
|
profileId,
|
|
449
518
|
"cron",
|
|
450
|
-
"
|
|
451
|
-
|
|
519
|
+
"create",
|
|
520
|
+
HERMES_SNAPSHOT_CRON.schedule,
|
|
521
|
+
"--name",
|
|
522
|
+
HERMES_SNAPSHOT_CRON.name,
|
|
452
523
|
"--script",
|
|
453
524
|
HERMES_SNAPSHOT_CRON.script,
|
|
454
525
|
"--no-agent",
|
|
@@ -456,53 +527,211 @@ export async function ensureHermesSnapshotCron({
|
|
|
456
527
|
hermesRoot,
|
|
457
528
|
hermesCli
|
|
458
529
|
)
|
|
459
|
-
)
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
);
|
|
466
|
-
return afterMigration.length === 1 && isCron(afterMigration[0])
|
|
467
|
-
? {
|
|
468
|
-
ok: true,
|
|
469
|
-
status: "MIGRATED",
|
|
470
|
-
jobId: afterMigration[0].id,
|
|
530
|
+
),
|
|
531
|
+
update: (job) =>
|
|
532
|
+
childRunner(runChild)(
|
|
533
|
+
invocation(
|
|
534
|
+
[
|
|
535
|
+
"--profile",
|
|
471
536
|
profileId,
|
|
472
|
-
|
|
473
|
-
|
|
537
|
+
"cron",
|
|
538
|
+
"edit",
|
|
539
|
+
job.id,
|
|
540
|
+
"--script",
|
|
541
|
+
HERMES_SNAPSHOT_CRON.script,
|
|
542
|
+
"--no-agent",
|
|
543
|
+
],
|
|
544
|
+
hermesRoot,
|
|
545
|
+
hermesCli
|
|
546
|
+
)
|
|
547
|
+
),
|
|
548
|
+
});
|
|
549
|
+
if (!result.ok) {
|
|
550
|
+
if (result.code === "hermes_snapshot_cron_update_failed") {
|
|
551
|
+
return failed("hermes_snapshot_cron_migrate_failed");
|
|
552
|
+
}
|
|
553
|
+
if (result.code === "hermes_snapshot_cron_update_readback_rejected") {
|
|
554
|
+
return failed("hermes_snapshot_cron_migrate_readback_rejected");
|
|
474
555
|
}
|
|
475
|
-
return
|
|
556
|
+
return result;
|
|
476
557
|
}
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
558
|
+
return {
|
|
559
|
+
ok: true,
|
|
560
|
+
status: result.status === "UPDATED" ? "MIGRATED" : result.status,
|
|
561
|
+
jobId: result.jobId,
|
|
562
|
+
profileId,
|
|
563
|
+
};
|
|
564
|
+
} catch {
|
|
565
|
+
return failed("hermes_snapshot_cron_install_failed");
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
const OPPORTUNITY_SEQUENCE_ASSETS = Object.freeze([
|
|
570
|
+
"SKILL.md",
|
|
571
|
+
"references/outbound-multichannel-v1.md",
|
|
572
|
+
"references/positive-reply-to-agreed-meeting-v1.md",
|
|
573
|
+
]);
|
|
574
|
+
const SLACK_USER_ID = /^U[A-Z0-9]{8,20}$/;
|
|
575
|
+
const OPPORTUNITY_SEQUENCE_CRON_PYTHON = [
|
|
576
|
+
"import json,sys",
|
|
577
|
+
"from cron.jobs import create_job, update_job",
|
|
578
|
+
"value=json.loads(sys.argv[1])",
|
|
579
|
+
"job_id=value.pop('jobId', None)",
|
|
580
|
+
"enabled=value.pop('enabled')",
|
|
581
|
+
"result=update_job(job_id, {**value, 'enabled': enabled}) if job_id else create_job(**value)",
|
|
582
|
+
"print(json.dumps({'id': result.get('id') if result else None}))",
|
|
583
|
+
].join("\n");
|
|
584
|
+
|
|
585
|
+
function opportunitySequenceIdentity(profileRoot) {
|
|
586
|
+
const root = join(profileRoot, "skills", "opportunity-sequences");
|
|
587
|
+
return Object.fromEntries(
|
|
588
|
+
OPPORTUNITY_SEQUENCE_ASSETS.map((relative) => {
|
|
589
|
+
const path = join(root, relative);
|
|
590
|
+
regularFile(path);
|
|
591
|
+
return [relative, sha256(readFileSync(path))];
|
|
592
|
+
})
|
|
593
|
+
);
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
function opportunitySequencePrompt({
|
|
597
|
+
notificationSlackUserId,
|
|
598
|
+
journalRoot,
|
|
599
|
+
hashes,
|
|
600
|
+
}) {
|
|
601
|
+
return [
|
|
602
|
+
"Run the installed opportunity-sequences skill for one bounded page.",
|
|
603
|
+
`Use the private journal at ${journalRoot}.`,
|
|
604
|
+
`Require these installed asset hashes: ${JSON.stringify(hashes)}.`,
|
|
605
|
+
`End with a compact native Slack summary addressed to <@${notificationSlackUserId}>.`,
|
|
606
|
+
"Never send prospect outreach; never claim exactly-once delivery; never use a direct Slack or Lightfield transport outside the installed skill and Product MCP.",
|
|
607
|
+
].join(" ");
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
export async function ensureHermesOpportunitySequencesCron({
|
|
611
|
+
profileId,
|
|
612
|
+
profileRoot,
|
|
613
|
+
hermesRoot,
|
|
614
|
+
statePath,
|
|
615
|
+
journalRoot = HERMES_OPPORTUNITY_SEQUENCES_JOURNAL_ROOT,
|
|
616
|
+
hermesPython = HERMES_PYTHON,
|
|
617
|
+
runChild,
|
|
618
|
+
}) {
|
|
619
|
+
if (
|
|
620
|
+
!validProfilePath(profileId, profileRoot, hermesRoot) ||
|
|
621
|
+
!existsSync(profileRoot) ||
|
|
622
|
+
!isAbsolute(statePath) ||
|
|
623
|
+
!isAbsolute(journalRoot)
|
|
624
|
+
) {
|
|
625
|
+
return failed("hermes_opportunity_sequences_cron_identity_rejected");
|
|
626
|
+
}
|
|
627
|
+
try {
|
|
628
|
+
const stateStat = regularFile(statePath);
|
|
629
|
+
if ((stateStat.mode & 0o077) !== 0) {
|
|
630
|
+
return failed("hermes_opportunity_sequences_cron_state_mode_rejected");
|
|
631
|
+
}
|
|
632
|
+
const state = JSON.parse(readFileSync(statePath, "utf8"));
|
|
633
|
+
const notificationSlackUserId =
|
|
634
|
+
state?.opportunitySequences?.notificationSlackUserId;
|
|
635
|
+
if (!SLACK_USER_ID.test(notificationSlackUserId ?? "")) {
|
|
636
|
+
return failed("hermes_opportunity_sequences_cron_notification_rejected");
|
|
637
|
+
}
|
|
638
|
+
mkdirSync(journalRoot, { recursive: true, mode: 0o700 });
|
|
639
|
+
chmodSync(journalRoot, 0o700);
|
|
640
|
+
const journalStat = lstatSync(journalRoot);
|
|
641
|
+
if (
|
|
642
|
+
journalStat.isSymbolicLink() ||
|
|
643
|
+
!journalStat.isDirectory() ||
|
|
644
|
+
(journalStat.mode & 0o077) !== 0
|
|
645
|
+
) {
|
|
646
|
+
return failed("hermes_opportunity_sequences_cron_journal_rejected");
|
|
647
|
+
}
|
|
648
|
+
const hashes = opportunitySequenceIdentity(profileRoot);
|
|
649
|
+
const prompt = opportunitySequencePrompt({
|
|
650
|
+
notificationSlackUserId,
|
|
651
|
+
journalRoot,
|
|
652
|
+
hashes,
|
|
653
|
+
});
|
|
654
|
+
const expected = (job) =>
|
|
655
|
+
Boolean(
|
|
656
|
+
job?.id &&
|
|
657
|
+
job.name === HERMES_OPPORTUNITY_SEQUENCES_CRON.name &&
|
|
658
|
+
job.prompt === prompt &&
|
|
659
|
+
job.no_agent === false &&
|
|
660
|
+
job.enabled === true &&
|
|
661
|
+
job.deliver === HERMES_OPPORTUNITY_SEQUENCES_CRON.deliver &&
|
|
662
|
+
sameJson(job.skills, HERMES_OPPORTUNITY_SEQUENCES_CRON.skills) &&
|
|
663
|
+
sameJson(
|
|
664
|
+
job.enabled_toolsets,
|
|
665
|
+
HERMES_OPPORTUNITY_SEQUENCES_CRON.enabledToolsets
|
|
666
|
+
) &&
|
|
667
|
+
job.schedule?.kind === "interval" &&
|
|
668
|
+
job.schedule?.minutes === 30
|
|
669
|
+
);
|
|
670
|
+
const mutate = (jobId = null) => {
|
|
671
|
+
const spec = {
|
|
672
|
+
prompt,
|
|
673
|
+
schedule: HERMES_OPPORTUNITY_SEQUENCES_CRON.schedule,
|
|
674
|
+
name: HERMES_OPPORTUNITY_SEQUENCES_CRON.name,
|
|
675
|
+
deliver: HERMES_OPPORTUNITY_SEQUENCES_CRON.deliver,
|
|
676
|
+
skills: [...HERMES_OPPORTUNITY_SEQUENCES_CRON.skills],
|
|
677
|
+
enabled_toolsets: [
|
|
678
|
+
...HERMES_OPPORTUNITY_SEQUENCES_CRON.enabledToolsets,
|
|
490
679
|
],
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
680
|
+
no_agent: false,
|
|
681
|
+
enabled: true,
|
|
682
|
+
...(jobId ? { jobId } : {}),
|
|
683
|
+
};
|
|
684
|
+
return childRunner(runChild)({
|
|
685
|
+
command: hermesPython,
|
|
686
|
+
args: ["-c", OPPORTUNITY_SEQUENCE_CRON_PYTHON, JSON.stringify(spec)],
|
|
687
|
+
env: {
|
|
688
|
+
...process.env,
|
|
689
|
+
HOME: hermesRoot,
|
|
690
|
+
HERMES_HOME: profileRoot,
|
|
691
|
+
HERMES_PROFILE: profileId,
|
|
692
|
+
NO_COLOR: "1",
|
|
693
|
+
},
|
|
694
|
+
shell: false,
|
|
695
|
+
timeoutMs: NATIVE_COMMAND_TIMEOUT_MS,
|
|
696
|
+
maxOutputBytes: 64 * 1024,
|
|
697
|
+
});
|
|
698
|
+
};
|
|
699
|
+
const result = await ensureHermesNamedCron({
|
|
700
|
+
profileRoot,
|
|
701
|
+
name: HERMES_OPPORTUNITY_SEQUENCES_CRON.name,
|
|
702
|
+
isExpected: expected,
|
|
703
|
+
create: () => mutate(),
|
|
704
|
+
update: (job) => mutate(job.id),
|
|
705
|
+
codePrefix: "hermes_opportunity_sequences_cron",
|
|
706
|
+
});
|
|
707
|
+
if (!result.ok) return result;
|
|
708
|
+
const jobsMode =
|
|
709
|
+
lstatSync(join(profileRoot, "cron", "jobs.json")).mode & 0o777;
|
|
710
|
+
if ((jobsMode & 0o077) !== 0) {
|
|
711
|
+
return failed("hermes_opportunity_sequences_cron_jobs_mode_rejected");
|
|
497
712
|
}
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
:
|
|
713
|
+
return {
|
|
714
|
+
ok: true,
|
|
715
|
+
status: result.status,
|
|
716
|
+
jobId: result.jobId,
|
|
717
|
+
name: result.job.name,
|
|
718
|
+
prompt: result.job.prompt,
|
|
719
|
+
schedule: result.job.schedule,
|
|
720
|
+
profileId,
|
|
721
|
+
enabledToolsets: result.job.enabled_toolsets,
|
|
722
|
+
skills: result.job.skills,
|
|
723
|
+
assetHashes: hashes,
|
|
724
|
+
notificationSlackUserIdSource:
|
|
725
|
+
"config-state.json:opportunitySequences.notificationSlackUserId",
|
|
726
|
+
journalRoot,
|
|
727
|
+
modes: {
|
|
728
|
+
state: stateStat.mode & 0o777,
|
|
729
|
+
journal: journalStat.mode & 0o777,
|
|
730
|
+
jobs: jobsMode,
|
|
731
|
+
},
|
|
732
|
+
};
|
|
504
733
|
} catch {
|
|
505
|
-
return failed("
|
|
734
|
+
return failed("hermes_opportunity_sequences_cron_install_failed");
|
|
506
735
|
}
|
|
507
736
|
}
|
|
508
737
|
|