crewly 1.16.0 → 1.17.1
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/README.md +33 -1
- package/config/roles/orchestrator/prompt.md +20 -5
- package/config/skills/agent/core/calendar-create/SKILL.md +62 -0
- package/config/skills/agent/core/calendar-create/execute.sh +91 -0
- package/config/skills/agent/core/calendar-create/execute.test.sh +80 -0
- package/config/skills/agent/core/calendar-list/SKILL.md +58 -0
- package/config/skills/agent/core/calendar-list/execute.sh +88 -0
- package/config/skills/agent/core/calendar-list/execute.test.sh +71 -0
- package/config/skills/agent/core/gmail-read/SKILL.md +61 -0
- package/config/skills/agent/core/gmail-read/execute.sh +62 -0
- package/config/skills/agent/core/gmail-read/execute.test.sh +68 -0
- package/config/skills/agent/core/gmail-search/SKILL.md +64 -0
- package/config/skills/agent/core/gmail-search/execute.sh +70 -0
- package/config/skills/agent/core/gmail-search/execute.test.sh +67 -0
- package/config/skills/agent/core/gmail-send/SKILL.md +69 -0
- package/config/skills/agent/core/gmail-send/execute.sh +114 -0
- package/config/skills/agent/core/gmail-send/execute.test.sh +83 -0
- package/config/skills/orchestrator/list-missions/SKILL.md +47 -0
- package/config/skills/orchestrator/list-missions/execute.sh +34 -0
- package/config/skills/orchestrator/list-missions/execute.test.sh +33 -0
- package/config/slack-master-app-manifest.json +64 -0
- package/dist/backend/backend/src/constants.d.ts +125 -0
- package/dist/backend/backend/src/constants.d.ts.map +1 -1
- package/dist/backend/backend/src/constants.js +125 -0
- package/dist/backend/backend/src/constants.js.map +1 -1
- package/dist/backend/backend/src/controllers/cloud/cloud.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/cloud/cloud.controller.js +3 -1
- package/dist/backend/backend/src/controllers/cloud/cloud.controller.js.map +1 -1
- package/dist/backend/backend/src/controllers/google/google.controller.d.ts +100 -0
- package/dist/backend/backend/src/controllers/google/google.controller.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/google/google.controller.js +293 -0
- package/dist/backend/backend/src/controllers/google/google.controller.js.map +1 -0
- package/dist/backend/backend/src/controllers/google/google.routes.d.ts +26 -0
- package/dist/backend/backend/src/controllers/google/google.routes.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/google/google.routes.js +38 -0
- package/dist/backend/backend/src/controllers/google/google.routes.js.map +1 -0
- package/dist/backend/backend/src/controllers/slack/slack.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/slack/slack.controller.js +209 -2
- package/dist/backend/backend/src/controllers/slack/slack.controller.js.map +1 -1
- package/dist/backend/backend/src/index.d.ts.map +1 -1
- package/dist/backend/backend/src/index.js +58 -8
- package/dist/backend/backend/src/index.js.map +1 -1
- package/dist/backend/backend/src/routes/api.routes.d.ts.map +1 -1
- package/dist/backend/backend/src/routes/api.routes.js +4 -0
- package/dist/backend/backend/src/routes/api.routes.js.map +1 -1
- package/dist/backend/backend/src/services/cloud/cloud-initializer.d.ts +8 -0
- package/dist/backend/backend/src/services/cloud/cloud-initializer.d.ts.map +1 -1
- package/dist/backend/backend/src/services/cloud/cloud-initializer.js +25 -0
- package/dist/backend/backend/src/services/cloud/cloud-initializer.js.map +1 -1
- package/dist/backend/backend/src/services/cloud/cloud-sync.types.d.ts +1 -1
- package/dist/backend/backend/src/services/cloud/cloud-sync.types.d.ts.map +1 -1
- package/dist/backend/backend/src/services/cloud/cloud-sync.types.js +1 -0
- package/dist/backend/backend/src/services/cloud/cloud-sync.types.js.map +1 -1
- package/dist/backend/backend/src/services/core/crewly-home.utils.d.ts +49 -0
- package/dist/backend/backend/src/services/core/crewly-home.utils.d.ts.map +1 -1
- package/dist/backend/backend/src/services/core/crewly-home.utils.js +86 -0
- package/dist/backend/backend/src/services/core/crewly-home.utils.js.map +1 -1
- package/dist/backend/backend/src/services/google/calendar.service.d.ts +98 -0
- package/dist/backend/backend/src/services/google/calendar.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/google/calendar.service.js +125 -0
- package/dist/backend/backend/src/services/google/calendar.service.js.map +1 -0
- package/dist/backend/backend/src/services/google/gmail.service.d.ts +165 -0
- package/dist/backend/backend/src/services/google/gmail.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/google/gmail.service.js +296 -0
- package/dist/backend/backend/src/services/google/gmail.service.js.map +1 -0
- package/dist/backend/backend/src/services/google/google-api.client.d.ts +44 -0
- package/dist/backend/backend/src/services/google/google-api.client.d.ts.map +1 -0
- package/dist/backend/backend/src/services/google/google-api.client.js +104 -0
- package/dist/backend/backend/src/services/google/google-api.client.js.map +1 -0
- package/dist/backend/backend/src/services/google/google-workspace-token.service.d.ts +159 -0
- package/dist/backend/backend/src/services/google/google-workspace-token.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/google/google-workspace-token.service.js +274 -0
- package/dist/backend/backend/src/services/google/google-workspace-token.service.js.map +1 -0
- package/dist/backend/backend/src/services/memory/mission-context.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/memory/mission-context.service.js +2 -1
- package/dist/backend/backend/src/services/memory/mission-context.service.js.map +1 -1
- package/dist/backend/backend/src/services/memory/project-memory.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/memory/project-memory.service.js +3 -2
- package/dist/backend/backend/src/services/memory/project-memory.service.js.map +1 -1
- package/dist/backend/backend/src/services/memory/session-memory.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/memory/session-memory.service.js +2 -1
- package/dist/backend/backend/src/services/memory/session-memory.service.js.map +1 -1
- package/dist/backend/backend/src/services/project/task.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/project/task.service.js +2 -3
- package/dist/backend/backend/src/services/project/task.service.js.map +1 -1
- package/dist/backend/backend/src/services/project/ticket-editor.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/project/ticket-editor.service.js +2 -3
- package/dist/backend/backend/src/services/project/ticket-editor.service.js.map +1 -1
- package/dist/backend/backend/src/services/slack/slack-agent-identity.service.d.ts +12 -1
- package/dist/backend/backend/src/services/slack/slack-agent-identity.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/slack/slack-agent-identity.service.js +30 -0
- package/dist/backend/backend/src/services/slack/slack-agent-identity.service.js.map +1 -1
- package/dist/backend/backend/src/services/slack/slack-cloud-config.service.d.ts +186 -0
- package/dist/backend/backend/src/services/slack/slack-cloud-config.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/slack/slack-cloud-config.service.js +409 -0
- package/dist/backend/backend/src/services/slack/slack-cloud-config.service.js.map +1 -0
- package/dist/backend/backend/src/services/slack/slack-initializer.d.ts +95 -1
- package/dist/backend/backend/src/services/slack/slack-initializer.d.ts.map +1 -1
- package/dist/backend/backend/src/services/slack/slack-initializer.js +284 -4
- package/dist/backend/backend/src/services/slack/slack-initializer.js.map +1 -1
- package/dist/backend/backend/src/services/slack/slack-instance-registry.service.d.ts +178 -0
- package/dist/backend/backend/src/services/slack/slack-instance-registry.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/slack/slack-instance-registry.service.js +392 -0
- package/dist/backend/backend/src/services/slack/slack-instance-registry.service.js.map +1 -0
- package/dist/backend/backend/src/services/slack/slack.service.d.ts +76 -1
- package/dist/backend/backend/src/services/slack/slack.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/slack/slack.service.js +234 -47
- package/dist/backend/backend/src/services/slack/slack.service.js.map +1 -1
- package/dist/backend/backend/src/services/v3/escalation-router.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/v3/escalation-router.service.js +2 -1
- package/dist/backend/backend/src/services/v3/escalation-router.service.js.map +1 -1
- package/dist/backend/backend/src/services/v3/mission-paths.d.ts +17 -0
- package/dist/backend/backend/src/services/v3/mission-paths.d.ts.map +1 -1
- package/dist/backend/backend/src/services/v3/mission-paths.js +21 -2
- package/dist/backend/backend/src/services/v3/mission-paths.js.map +1 -1
- package/dist/backend/backend/src/services/v3/mission-reminder.service.d.ts +4 -0
- package/dist/backend/backend/src/services/v3/mission-reminder.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/v3/mission-reminder.service.js +17 -0
- package/dist/backend/backend/src/services/v3/mission-reminder.service.js.map +1 -1
- package/dist/backend/backend/src/services/v3/okr-owner-guidance.service.d.ts +116 -0
- package/dist/backend/backend/src/services/v3/okr-owner-guidance.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/v3/okr-owner-guidance.service.js +254 -0
- package/dist/backend/backend/src/services/v3/okr-owner-guidance.service.js.map +1 -0
- package/dist/backend/backend/src/services/v3/request.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/v3/request.service.js +2 -1
- package/dist/backend/backend/src/services/v3/request.service.js.map +1 -1
- package/dist/backend/backend/src/services/v3/task-projection.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/v3/task-projection.service.js +2 -1
- package/dist/backend/backend/src/services/v3/task-projection.service.js.map +1 -1
- package/dist/backend/backend/src/services/v3/trigger-engine.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/v3/trigger-engine.service.js +2 -1
- package/dist/backend/backend/src/services/v3/trigger-engine.service.js.map +1 -1
- package/dist/backend/backend/src/services/v3/v3-data.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/v3/v3-data.service.js +2 -1
- package/dist/backend/backend/src/services/v3/v3-data.service.js.map +1 -1
- package/dist/backend/backend/src/services/wiki/wiki-bookkeep-trigger.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/wiki/wiki-bookkeep-trigger.service.js +6 -1
- package/dist/backend/backend/src/services/wiki/wiki-bookkeep-trigger.service.js.map +1 -1
- package/dist/backend/backend/src/services/wiki/wiki-workitem-bridge.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/wiki/wiki-workitem-bridge.service.js +3 -1
- package/dist/backend/backend/src/services/wiki/wiki-workitem-bridge.service.js.map +1 -1
- package/dist/backend/backend/src/types/slack.types.d.ts +141 -2
- package/dist/backend/backend/src/types/slack.types.d.ts.map +1 -1
- package/dist/backend/backend/src/types/slack.types.js.map +1 -1
- package/dist/backend/backend/src/types/v2/mission.types.d.ts +2 -0
- package/dist/backend/backend/src/types/v2/mission.types.d.ts.map +1 -1
- package/dist/backend/backend/src/types/v2/mission.types.js.map +1 -1
- package/dist/backend/build-info.json +2 -2
- package/dist/cli/backend/src/constants.d.ts +125 -0
- package/dist/cli/backend/src/constants.d.ts.map +1 -1
- package/dist/cli/backend/src/constants.js +125 -0
- package/dist/cli/backend/src/constants.js.map +1 -1
- package/dist/cli/backend/src/services/cloud/cloud-sync.types.d.ts +1 -1
- package/dist/cli/backend/src/services/cloud/cloud-sync.types.d.ts.map +1 -1
- package/dist/cli/backend/src/services/cloud/cloud-sync.types.js +1 -0
- package/dist/cli/backend/src/services/cloud/cloud-sync.types.js.map +1 -1
- package/dist/cli/backend/src/services/core/crewly-home.utils.d.ts +49 -0
- package/dist/cli/backend/src/services/core/crewly-home.utils.d.ts.map +1 -1
- package/dist/cli/backend/src/services/core/crewly-home.utils.js +86 -0
- package/dist/cli/backend/src/services/core/crewly-home.utils.js.map +1 -1
- package/dist/cli/backend/src/services/memory/mission-context.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/memory/mission-context.service.js +2 -1
- package/dist/cli/backend/src/services/memory/mission-context.service.js.map +1 -1
- package/dist/cli/backend/src/services/memory/project-memory.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/memory/project-memory.service.js +3 -2
- package/dist/cli/backend/src/services/memory/project-memory.service.js.map +1 -1
- package/dist/cli/backend/src/services/memory/session-memory.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/memory/session-memory.service.js +2 -1
- package/dist/cli/backend/src/services/memory/session-memory.service.js.map +1 -1
- package/dist/cli/backend/src/services/slack/slack-agent-identity.service.d.ts +12 -1
- package/dist/cli/backend/src/services/slack/slack-agent-identity.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/slack/slack-agent-identity.service.js +30 -0
- package/dist/cli/backend/src/services/slack/slack-agent-identity.service.js.map +1 -1
- package/dist/cli/backend/src/services/slack/slack.service.d.ts +76 -1
- package/dist/cli/backend/src/services/slack/slack.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/slack/slack.service.js +234 -47
- package/dist/cli/backend/src/services/slack/slack.service.js.map +1 -1
- package/dist/cli/backend/src/services/v3/escalation-router.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/v3/escalation-router.service.js +2 -1
- package/dist/cli/backend/src/services/v3/escalation-router.service.js.map +1 -1
- package/dist/cli/backend/src/services/v3/mission-paths.d.ts +17 -0
- package/dist/cli/backend/src/services/v3/mission-paths.d.ts.map +1 -1
- package/dist/cli/backend/src/services/v3/mission-paths.js +21 -2
- package/dist/cli/backend/src/services/v3/mission-paths.js.map +1 -1
- package/dist/cli/backend/src/services/v3/request.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/v3/request.service.js +2 -1
- package/dist/cli/backend/src/services/v3/request.service.js.map +1 -1
- package/dist/cli/backend/src/services/v3/v3-data.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/v3/v3-data.service.js +2 -1
- package/dist/cli/backend/src/services/v3/v3-data.service.js.map +1 -1
- package/dist/cli/backend/src/types/slack.types.d.ts +141 -2
- package/dist/cli/backend/src/types/slack.types.d.ts.map +1 -1
- package/dist/cli/backend/src/types/slack.types.js.map +1 -1
- package/dist/cli/backend/src/types/v2/mission.types.d.ts +2 -0
- package/dist/cli/backend/src/types/v2/mission.types.d.ts.map +1 -1
- package/dist/cli/backend/src/types/v2/mission.types.js.map +1 -1
- package/frontend/dist/assets/{index-019817e2.js → index-c87332bf.js} +273 -273
- package/frontend/dist/assets/{index-a56613e7.css → index-f6b4230e.css} +1 -1
- package/frontend/dist/index.html +2 -2
- package/package.json +1 -1
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slack Cloud Config Service — the Cloud-owned Slack setup, cached locally.
|
|
3
|
+
*
|
|
4
|
+
* Slack v3 ("Crewly Cloud owns Slack"): the owner installs the Crewly Slack
|
|
5
|
+
* app once from the portal; every OSS instance signed in to the same Crewly
|
|
6
|
+
* account then gets the workspace bot token, bot user id and the installed
|
|
7
|
+
* per-agent identities from `GET /api/cloud/slack/config` — no manual
|
|
8
|
+
* tokens. This service:
|
|
9
|
+
*
|
|
10
|
+
* - fetches that config on boot and every 10 minutes while signed in;
|
|
11
|
+
* - caches it at `~/.crewly/slack-cloud-config.json` (mode 0600) so a
|
|
12
|
+
* restart without Cloud reachability still connects;
|
|
13
|
+
* - exposes the master bot as a {@link SlackConfig} with the `cloud`
|
|
14
|
+
* transport for `SlackService`, and the agents for
|
|
15
|
+
* `SlackAgentIdentityService`;
|
|
16
|
+
* - notifies listeners when the config appears, changes or goes away, so
|
|
17
|
+
* the initializer can (re)connect without a restart.
|
|
18
|
+
*
|
|
19
|
+
* Source precedence lives in {@link resolveSlackSourceMode}: unset → Cloud
|
|
20
|
+
* wins when both exist (logged once), `CREWLY_SLACK_SOURCE=env` → never
|
|
21
|
+
* touch Cloud, `CREWLY_SLACK_SOURCE=cloud` → never use local tokens.
|
|
22
|
+
*
|
|
23
|
+
* @module services/slack/slack-cloud-config.service
|
|
24
|
+
*/
|
|
25
|
+
import type { SlackCloudAgentConfig, SlackCloudConfig, SlackConfig } from '../../types/slack.types.js';
|
|
26
|
+
import { type IdentityCloudClient } from './slack-agent-identity.service.js';
|
|
27
|
+
/** Where the Slack tokens come from. */
|
|
28
|
+
export type SlackSourceMode = 'env' | 'cloud' | 'auto';
|
|
29
|
+
/** Constructor dependencies. */
|
|
30
|
+
export interface SlackCloudConfigServiceDeps {
|
|
31
|
+
cloud: IdentityCloudClient;
|
|
32
|
+
/** Cache path; defaults to `<CREWLY_HOME>/slack-cloud-config.json`. */
|
|
33
|
+
storePath?: string;
|
|
34
|
+
fetchImpl?: typeof fetch;
|
|
35
|
+
now?: () => number;
|
|
36
|
+
/** Timer overrides for tests. */
|
|
37
|
+
setInterval?: typeof setInterval;
|
|
38
|
+
clearInterval?: typeof clearInterval;
|
|
39
|
+
/** Environment override for tests. */
|
|
40
|
+
env?: NodeJS.ProcessEnv;
|
|
41
|
+
}
|
|
42
|
+
/** Listener for config changes (`null` = Cloud has no workspace any more). */
|
|
43
|
+
export type SlackCloudConfigListener = (config: SlackCloudConfig | null) => void;
|
|
44
|
+
/**
|
|
45
|
+
* Read `CREWLY_SLACK_SOURCE` into a mode. Anything but `env` / `cloud`
|
|
46
|
+
* (case-insensitive) is `auto`.
|
|
47
|
+
*
|
|
48
|
+
* @param env - Environment to read (defaults to `process.env`)
|
|
49
|
+
* @returns The mode
|
|
50
|
+
*/
|
|
51
|
+
export declare function resolveSlackSourceMode(env?: NodeJS.ProcessEnv): SlackSourceMode;
|
|
52
|
+
/**
|
|
53
|
+
* Service — see module docs.
|
|
54
|
+
*/
|
|
55
|
+
export declare class SlackCloudConfigService {
|
|
56
|
+
private readonly logger;
|
|
57
|
+
private readonly deps;
|
|
58
|
+
private readonly storePath;
|
|
59
|
+
private readonly fetchImpl;
|
|
60
|
+
private config;
|
|
61
|
+
private fetchedAt;
|
|
62
|
+
private loading;
|
|
63
|
+
private refreshTimer;
|
|
64
|
+
private lastError;
|
|
65
|
+
private readonly listeners;
|
|
66
|
+
constructor(deps: SlackCloudConfigServiceDeps);
|
|
67
|
+
/**
|
|
68
|
+
* Whether Cloud can be asked (signed in, url + token known) and the source
|
|
69
|
+
* mode does not forbid it.
|
|
70
|
+
*
|
|
71
|
+
* @returns True when `refresh()` can reach Cloud
|
|
72
|
+
*/
|
|
73
|
+
isAvailable(): boolean;
|
|
74
|
+
/**
|
|
75
|
+
* The configured source mode (`CREWLY_SLACK_SOURCE`).
|
|
76
|
+
*
|
|
77
|
+
* @returns `env`, `cloud` or `auto`
|
|
78
|
+
*/
|
|
79
|
+
getSourceMode(): SlackSourceMode;
|
|
80
|
+
/**
|
|
81
|
+
* Load the cached config from disk once. Never touches Cloud.
|
|
82
|
+
*
|
|
83
|
+
* @returns The cached config or null
|
|
84
|
+
*/
|
|
85
|
+
load(): Promise<SlackCloudConfig | null>;
|
|
86
|
+
/**
|
|
87
|
+
* Fetch `GET /api/cloud/slack/config`, update the cache and notify
|
|
88
|
+
* listeners when the config changed. A 404 means the account has no
|
|
89
|
+
* workspace installed: the cache is cleared. A network / Cloud failure
|
|
90
|
+
* keeps whatever is cached and is reported via {@link getLastError}.
|
|
91
|
+
*
|
|
92
|
+
* @returns The current config after the refresh (cached on failure)
|
|
93
|
+
*/
|
|
94
|
+
refresh(): Promise<SlackCloudConfig | null>;
|
|
95
|
+
/**
|
|
96
|
+
* Convenience for boot: cached copy first, then a Cloud refresh when
|
|
97
|
+
* possible. Returns the best available config.
|
|
98
|
+
*
|
|
99
|
+
* @returns The config or null
|
|
100
|
+
*/
|
|
101
|
+
loadOrRefresh(): Promise<SlackCloudConfig | null>;
|
|
102
|
+
/**
|
|
103
|
+
* Forget the cached config (workspace removed). Notifies listeners.
|
|
104
|
+
*/
|
|
105
|
+
clear(): Promise<void>;
|
|
106
|
+
/**
|
|
107
|
+
* Remove the account's Slack workspace on Cloud (`DELETE
|
|
108
|
+
* /api/cloud/slack/workspace`) and forget the local cache. Every instance
|
|
109
|
+
* of the account loses Slack on its next refresh.
|
|
110
|
+
*
|
|
111
|
+
* @returns True when Cloud removed a workspace
|
|
112
|
+
* @throws {SlackIdentityCloudError} on a Cloud failure
|
|
113
|
+
*/
|
|
114
|
+
removeWorkspace(): Promise<boolean>;
|
|
115
|
+
/**
|
|
116
|
+
* Periodic refresh (every 10 min). Idempotent; never keeps the process
|
|
117
|
+
* alive on its own.
|
|
118
|
+
*/
|
|
119
|
+
start(): void;
|
|
120
|
+
/** Stop the periodic refresh. */
|
|
121
|
+
stop(): void;
|
|
122
|
+
/**
|
|
123
|
+
* The in-memory config (call {@link load} / {@link refresh} first).
|
|
124
|
+
*
|
|
125
|
+
* @returns The config or null
|
|
126
|
+
*/
|
|
127
|
+
getConfig(): SlackCloudConfig | null;
|
|
128
|
+
/**
|
|
129
|
+
* ISO timestamp of the last successful Cloud fetch (or the cache's).
|
|
130
|
+
*
|
|
131
|
+
* @returns Timestamp or null
|
|
132
|
+
*/
|
|
133
|
+
getFetchedAt(): string | null;
|
|
134
|
+
/**
|
|
135
|
+
* Last refresh failure, if the most recent attempt failed.
|
|
136
|
+
*
|
|
137
|
+
* @returns Error message or null
|
|
138
|
+
*/
|
|
139
|
+
getLastError(): string | null;
|
|
140
|
+
/**
|
|
141
|
+
* The master workspace as a {@link SlackConfig} for `SlackService`
|
|
142
|
+
* (`transport: 'cloud'`, no socket credentials). Optional env knobs
|
|
143
|
+
* (`SLACK_DEFAULT_CHANNEL`, `SLACK_ALLOWED_USERS`) still apply.
|
|
144
|
+
*
|
|
145
|
+
* @returns The config, or null without a workspace
|
|
146
|
+
*/
|
|
147
|
+
toSlackConfig(): SlackConfig | null;
|
|
148
|
+
/**
|
|
149
|
+
* Installed per-agent identities from the config.
|
|
150
|
+
*
|
|
151
|
+
* @returns Copies (tokens included — never log them)
|
|
152
|
+
*/
|
|
153
|
+
getAgents(): SlackCloudAgentConfig[];
|
|
154
|
+
/**
|
|
155
|
+
* Register a change listener. Fired after the cache is written.
|
|
156
|
+
*
|
|
157
|
+
* @param listener - Called with the new config (null when removed)
|
|
158
|
+
* @returns Unsubscribe
|
|
159
|
+
*/
|
|
160
|
+
onChange(listener: SlackCloudConfigListener): () => void;
|
|
161
|
+
/** Persist + notify when the config differs from the current one. */
|
|
162
|
+
private apply;
|
|
163
|
+
/** `GET /api/cloud/slack/config`; null on 404 (not connected). */
|
|
164
|
+
private fetchFromCloud;
|
|
165
|
+
private now;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Runtime guard for the Cloud config payload.
|
|
169
|
+
*
|
|
170
|
+
* @param value - Parsed JSON
|
|
171
|
+
* @returns True when it has a workspace with a bot token and an agents list
|
|
172
|
+
*/
|
|
173
|
+
export declare function isCloudConfig(value: unknown): value is SlackCloudConfig;
|
|
174
|
+
/**
|
|
175
|
+
* Install the process-wide instance (composition root).
|
|
176
|
+
*
|
|
177
|
+
* @param service - The service or null
|
|
178
|
+
*/
|
|
179
|
+
export declare function setSlackCloudConfigService(service: SlackCloudConfigService | null): void;
|
|
180
|
+
/**
|
|
181
|
+
* The process-wide instance, or null before wiring.
|
|
182
|
+
*
|
|
183
|
+
* @returns The service or null
|
|
184
|
+
*/
|
|
185
|
+
export declare function getSlackCloudConfigService(): SlackCloudConfigService | null;
|
|
186
|
+
//# sourceMappingURL=slack-cloud-config.service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"slack-cloud-config.service.d.ts","sourceRoot":"","sources":["../../../../../../backend/src/services/slack/slack-cloud-config.service.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slack Cloud Config Service — the Cloud-owned Slack setup, cached locally.
|
|
3
|
+
*
|
|
4
|
+
* Slack v3 ("Crewly Cloud owns Slack"): the owner installs the Crewly Slack
|
|
5
|
+
* app once from the portal; every OSS instance signed in to the same Crewly
|
|
6
|
+
* account then gets the workspace bot token, bot user id and the installed
|
|
7
|
+
* per-agent identities from `GET /api/cloud/slack/config` — no manual
|
|
8
|
+
* tokens. This service:
|
|
9
|
+
*
|
|
10
|
+
* - fetches that config on boot and every 10 minutes while signed in;
|
|
11
|
+
* - caches it at `~/.crewly/slack-cloud-config.json` (mode 0600) so a
|
|
12
|
+
* restart without Cloud reachability still connects;
|
|
13
|
+
* - exposes the master bot as a {@link SlackConfig} with the `cloud`
|
|
14
|
+
* transport for `SlackService`, and the agents for
|
|
15
|
+
* `SlackAgentIdentityService`;
|
|
16
|
+
* - notifies listeners when the config appears, changes or goes away, so
|
|
17
|
+
* the initializer can (re)connect without a restart.
|
|
18
|
+
*
|
|
19
|
+
* Source precedence lives in {@link resolveSlackSourceMode}: unset → Cloud
|
|
20
|
+
* wins when both exist (logged once), `CREWLY_SLACK_SOURCE=env` → never
|
|
21
|
+
* touch Cloud, `CREWLY_SLACK_SOURCE=cloud` → never use local tokens.
|
|
22
|
+
*
|
|
23
|
+
* @module services/slack/slack-cloud-config.service
|
|
24
|
+
*/
|
|
25
|
+
import * as path from 'path';
|
|
26
|
+
import { promises as fs } from 'fs';
|
|
27
|
+
import { getCrewlyHomePath } from '../core/crewly-home.utils.js';
|
|
28
|
+
import { atomicWriteJson, safeReadJson } from '../../utils/file-io.utils.js';
|
|
29
|
+
import { LoggerService } from '../core/logger.service.js';
|
|
30
|
+
import { SLACK_CLOUD_CONSTANTS } from '../../constants.js';
|
|
31
|
+
import { SlackIdentityCloudError } from './slack-agent-identity.service.js';
|
|
32
|
+
/**
|
|
33
|
+
* Read `CREWLY_SLACK_SOURCE` into a mode. Anything but `env` / `cloud`
|
|
34
|
+
* (case-insensitive) is `auto`.
|
|
35
|
+
*
|
|
36
|
+
* @param env - Environment to read (defaults to `process.env`)
|
|
37
|
+
* @returns The mode
|
|
38
|
+
*/
|
|
39
|
+
export function resolveSlackSourceMode(env = process.env) {
|
|
40
|
+
const raw = (env[SLACK_CLOUD_CONSTANTS.SOURCE_ENV_VAR] ?? '').trim().toLowerCase();
|
|
41
|
+
if (raw === 'env' || raw === 'cloud')
|
|
42
|
+
return raw;
|
|
43
|
+
return 'auto';
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Service — see module docs.
|
|
47
|
+
*/
|
|
48
|
+
export class SlackCloudConfigService {
|
|
49
|
+
logger;
|
|
50
|
+
deps;
|
|
51
|
+
storePath;
|
|
52
|
+
fetchImpl;
|
|
53
|
+
config = null;
|
|
54
|
+
fetchedAt = null;
|
|
55
|
+
loading = null;
|
|
56
|
+
refreshTimer = null;
|
|
57
|
+
lastError = null;
|
|
58
|
+
listeners = new Set();
|
|
59
|
+
constructor(deps) {
|
|
60
|
+
this.deps = deps;
|
|
61
|
+
this.storePath =
|
|
62
|
+
deps.storePath ?? path.join(getCrewlyHomePath(), SLACK_CLOUD_CONSTANTS.CONFIG_CACHE_FILENAME);
|
|
63
|
+
this.fetchImpl = deps.fetchImpl ?? fetch;
|
|
64
|
+
this.logger = LoggerService.getInstance().createComponentLogger('SlackCloudConfig');
|
|
65
|
+
}
|
|
66
|
+
// -------------------------------------------------------------------------
|
|
67
|
+
// Availability / mode
|
|
68
|
+
// -------------------------------------------------------------------------
|
|
69
|
+
/**
|
|
70
|
+
* Whether Cloud can be asked (signed in, url + token known) and the source
|
|
71
|
+
* mode does not forbid it.
|
|
72
|
+
*
|
|
73
|
+
* @returns True when `refresh()` can reach Cloud
|
|
74
|
+
*/
|
|
75
|
+
isAvailable() {
|
|
76
|
+
if (this.getSourceMode() === 'env')
|
|
77
|
+
return false;
|
|
78
|
+
return this.deps.cloud.isConnected() && !!this.deps.cloud.getToken() && !!this.deps.cloud.getCloudUrl();
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* The configured source mode (`CREWLY_SLACK_SOURCE`).
|
|
82
|
+
*
|
|
83
|
+
* @returns `env`, `cloud` or `auto`
|
|
84
|
+
*/
|
|
85
|
+
getSourceMode() {
|
|
86
|
+
return resolveSlackSourceMode(this.deps.env ?? process.env);
|
|
87
|
+
}
|
|
88
|
+
// -------------------------------------------------------------------------
|
|
89
|
+
// Cache + Cloud
|
|
90
|
+
// -------------------------------------------------------------------------
|
|
91
|
+
/**
|
|
92
|
+
* Load the cached config from disk once. Never touches Cloud.
|
|
93
|
+
*
|
|
94
|
+
* @returns The cached config or null
|
|
95
|
+
*/
|
|
96
|
+
async load() {
|
|
97
|
+
if (this.config)
|
|
98
|
+
return this.config;
|
|
99
|
+
if (!this.loading) {
|
|
100
|
+
this.loading = safeReadJson(this.storePath, null).then((raw) => {
|
|
101
|
+
if (raw && isCloudConfig(raw.config)) {
|
|
102
|
+
this.config = raw.config;
|
|
103
|
+
this.fetchedAt = typeof raw.fetchedAt === 'string' ? raw.fetchedAt : null;
|
|
104
|
+
}
|
|
105
|
+
return this.config;
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
return this.loading;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Fetch `GET /api/cloud/slack/config`, update the cache and notify
|
|
112
|
+
* listeners when the config changed. A 404 means the account has no
|
|
113
|
+
* workspace installed: the cache is cleared. A network / Cloud failure
|
|
114
|
+
* keeps whatever is cached and is reported via {@link getLastError}.
|
|
115
|
+
*
|
|
116
|
+
* @returns The current config after the refresh (cached on failure)
|
|
117
|
+
*/
|
|
118
|
+
async refresh() {
|
|
119
|
+
await this.load();
|
|
120
|
+
if (!this.isAvailable())
|
|
121
|
+
return this.config;
|
|
122
|
+
try {
|
|
123
|
+
const fetched = await this.fetchFromCloud();
|
|
124
|
+
this.lastError = null;
|
|
125
|
+
await this.apply(fetched);
|
|
126
|
+
}
|
|
127
|
+
catch (err) {
|
|
128
|
+
this.lastError = err instanceof Error ? err.message : String(err);
|
|
129
|
+
this.logger.warn('Cloud Slack config refresh failed — keeping cached copy', {
|
|
130
|
+
error: this.lastError,
|
|
131
|
+
cached: !!this.config,
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
return this.config;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Convenience for boot: cached copy first, then a Cloud refresh when
|
|
138
|
+
* possible. Returns the best available config.
|
|
139
|
+
*
|
|
140
|
+
* @returns The config or null
|
|
141
|
+
*/
|
|
142
|
+
async loadOrRefresh() {
|
|
143
|
+
await this.load();
|
|
144
|
+
if (this.isAvailable())
|
|
145
|
+
await this.refresh();
|
|
146
|
+
return this.config;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Forget the cached config (workspace removed). Notifies listeners.
|
|
150
|
+
*/
|
|
151
|
+
async clear() {
|
|
152
|
+
await this.apply(null);
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Remove the account's Slack workspace on Cloud (`DELETE
|
|
156
|
+
* /api/cloud/slack/workspace`) and forget the local cache. Every instance
|
|
157
|
+
* of the account loses Slack on its next refresh.
|
|
158
|
+
*
|
|
159
|
+
* @returns True when Cloud removed a workspace
|
|
160
|
+
* @throws {SlackIdentityCloudError} on a Cloud failure
|
|
161
|
+
*/
|
|
162
|
+
async removeWorkspace() {
|
|
163
|
+
const token = this.deps.cloud.getToken();
|
|
164
|
+
const base = this.deps.cloud.getCloudUrl();
|
|
165
|
+
if (!token || !base) {
|
|
166
|
+
throw new SlackIdentityCloudError(401, 'not_logged_in', 'Not logged in to Crewly Cloud');
|
|
167
|
+
}
|
|
168
|
+
const url = `${base.replace(/\/$/, '')}${SLACK_CLOUD_CONSTANTS.CLOUD_PATH}${SLACK_CLOUD_CONSTANTS.WORKSPACE_PATH}`;
|
|
169
|
+
const controller = new AbortController();
|
|
170
|
+
const timer = setTimeout(() => controller.abort(), SLACK_CLOUD_CONSTANTS.REQUEST_TIMEOUT_MS);
|
|
171
|
+
let removed = false;
|
|
172
|
+
try {
|
|
173
|
+
const res = await this.fetchImpl(url, {
|
|
174
|
+
method: 'DELETE',
|
|
175
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
176
|
+
signal: controller.signal,
|
|
177
|
+
});
|
|
178
|
+
const text = await res.text();
|
|
179
|
+
let parsed = {};
|
|
180
|
+
try {
|
|
181
|
+
parsed = JSON.parse(text);
|
|
182
|
+
}
|
|
183
|
+
catch {
|
|
184
|
+
parsed = {};
|
|
185
|
+
}
|
|
186
|
+
if (res.status !== 404 && (!res.ok || parsed.success !== true)) {
|
|
187
|
+
throw new SlackIdentityCloudError(res.status, parsed.code ?? `http_${res.status}`, parsed.error ?? `Cloud request failed (${res.status})`);
|
|
188
|
+
}
|
|
189
|
+
removed = res.ok && parsed.data?.removed !== false;
|
|
190
|
+
}
|
|
191
|
+
catch (err) {
|
|
192
|
+
if (err instanceof SlackIdentityCloudError)
|
|
193
|
+
throw err;
|
|
194
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
195
|
+
throw new SlackIdentityCloudError(502, 'network', `Cloud unreachable: ${message}`);
|
|
196
|
+
}
|
|
197
|
+
finally {
|
|
198
|
+
clearTimeout(timer);
|
|
199
|
+
}
|
|
200
|
+
await this.clear();
|
|
201
|
+
return removed;
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Periodic refresh (every 10 min). Idempotent; never keeps the process
|
|
205
|
+
* alive on its own.
|
|
206
|
+
*/
|
|
207
|
+
start() {
|
|
208
|
+
if (this.refreshTimer)
|
|
209
|
+
return;
|
|
210
|
+
const setI = this.deps.setInterval ?? setInterval;
|
|
211
|
+
this.refreshTimer = setI(() => {
|
|
212
|
+
void this.refresh();
|
|
213
|
+
}, SLACK_CLOUD_CONSTANTS.CONFIG_REFRESH_INTERVAL_MS);
|
|
214
|
+
this.refreshTimer.unref?.();
|
|
215
|
+
}
|
|
216
|
+
/** Stop the periodic refresh. */
|
|
217
|
+
stop() {
|
|
218
|
+
if (!this.refreshTimer)
|
|
219
|
+
return;
|
|
220
|
+
const clearI = this.deps.clearInterval ?? clearInterval;
|
|
221
|
+
clearI(this.refreshTimer);
|
|
222
|
+
this.refreshTimer = null;
|
|
223
|
+
}
|
|
224
|
+
// -------------------------------------------------------------------------
|
|
225
|
+
// Read model
|
|
226
|
+
// -------------------------------------------------------------------------
|
|
227
|
+
/**
|
|
228
|
+
* The in-memory config (call {@link load} / {@link refresh} first).
|
|
229
|
+
*
|
|
230
|
+
* @returns The config or null
|
|
231
|
+
*/
|
|
232
|
+
getConfig() {
|
|
233
|
+
return this.config;
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* ISO timestamp of the last successful Cloud fetch (or the cache's).
|
|
237
|
+
*
|
|
238
|
+
* @returns Timestamp or null
|
|
239
|
+
*/
|
|
240
|
+
getFetchedAt() {
|
|
241
|
+
return this.fetchedAt;
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Last refresh failure, if the most recent attempt failed.
|
|
245
|
+
*
|
|
246
|
+
* @returns Error message or null
|
|
247
|
+
*/
|
|
248
|
+
getLastError() {
|
|
249
|
+
return this.lastError;
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* The master workspace as a {@link SlackConfig} for `SlackService`
|
|
253
|
+
* (`transport: 'cloud'`, no socket credentials). Optional env knobs
|
|
254
|
+
* (`SLACK_DEFAULT_CHANNEL`, `SLACK_ALLOWED_USERS`) still apply.
|
|
255
|
+
*
|
|
256
|
+
* @returns The config, or null without a workspace
|
|
257
|
+
*/
|
|
258
|
+
toSlackConfig() {
|
|
259
|
+
const workspace = this.config?.workspace;
|
|
260
|
+
if (!workspace?.botToken)
|
|
261
|
+
return null;
|
|
262
|
+
const env = this.deps.env ?? process.env;
|
|
263
|
+
return {
|
|
264
|
+
botToken: workspace.botToken,
|
|
265
|
+
appToken: '',
|
|
266
|
+
signingSecret: '',
|
|
267
|
+
socketMode: false,
|
|
268
|
+
transport: 'cloud',
|
|
269
|
+
botUserId: workspace.botUserId,
|
|
270
|
+
defaultChannelId: env.SLACK_DEFAULT_CHANNEL,
|
|
271
|
+
allowedUserIds: env.SLACK_ALLOWED_USERS?.split(',').filter(Boolean),
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Installed per-agent identities from the config.
|
|
276
|
+
*
|
|
277
|
+
* @returns Copies (tokens included — never log them)
|
|
278
|
+
*/
|
|
279
|
+
getAgents() {
|
|
280
|
+
return (this.config?.agents ?? []).map((a) => ({ ...a }));
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Register a change listener. Fired after the cache is written.
|
|
284
|
+
*
|
|
285
|
+
* @param listener - Called with the new config (null when removed)
|
|
286
|
+
* @returns Unsubscribe
|
|
287
|
+
*/
|
|
288
|
+
onChange(listener) {
|
|
289
|
+
this.listeners.add(listener);
|
|
290
|
+
return () => this.listeners.delete(listener);
|
|
291
|
+
}
|
|
292
|
+
// -------------------------------------------------------------------------
|
|
293
|
+
// Internals
|
|
294
|
+
// -------------------------------------------------------------------------
|
|
295
|
+
/** Persist + notify when the config differs from the current one. */
|
|
296
|
+
async apply(next) {
|
|
297
|
+
const changed = JSON.stringify(next) !== JSON.stringify(this.config);
|
|
298
|
+
this.config = next;
|
|
299
|
+
this.fetchedAt = new Date(this.now()).toISOString();
|
|
300
|
+
if (next) {
|
|
301
|
+
const file = { version: 1, fetchedAt: this.fetchedAt, config: next };
|
|
302
|
+
await fs.mkdir(path.dirname(this.storePath), { recursive: true });
|
|
303
|
+
await atomicWriteJson(this.storePath, file);
|
|
304
|
+
await fs.chmod(this.storePath, 0o600).catch(() => undefined);
|
|
305
|
+
}
|
|
306
|
+
else {
|
|
307
|
+
await fs.unlink(this.storePath).catch(() => undefined);
|
|
308
|
+
}
|
|
309
|
+
if (!changed)
|
|
310
|
+
return;
|
|
311
|
+
this.logger.info(next ? 'Cloud Slack config updated' : 'Cloud Slack config removed', {
|
|
312
|
+
workspace: next?.workspace.slackTeamName,
|
|
313
|
+
agents: next?.agents.length ?? 0,
|
|
314
|
+
});
|
|
315
|
+
for (const listener of this.listeners) {
|
|
316
|
+
try {
|
|
317
|
+
listener(next ? { ...next } : null);
|
|
318
|
+
}
|
|
319
|
+
catch (err) {
|
|
320
|
+
this.logger.warn('onChange listener threw', { error: err instanceof Error ? err.message : String(err) });
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
/** `GET /api/cloud/slack/config`; null on 404 (not connected). */
|
|
325
|
+
async fetchFromCloud() {
|
|
326
|
+
const token = this.deps.cloud.getToken();
|
|
327
|
+
const base = this.deps.cloud.getCloudUrl();
|
|
328
|
+
if (!token || !base) {
|
|
329
|
+
throw new SlackIdentityCloudError(401, 'not_logged_in', 'Not logged in to Crewly Cloud');
|
|
330
|
+
}
|
|
331
|
+
const url = `${base.replace(/\/$/, '')}${SLACK_CLOUD_CONSTANTS.CLOUD_PATH}${SLACK_CLOUD_CONSTANTS.CONFIG_PATH}`;
|
|
332
|
+
const controller = new AbortController();
|
|
333
|
+
const timer = setTimeout(() => controller.abort(), SLACK_CLOUD_CONSTANTS.REQUEST_TIMEOUT_MS);
|
|
334
|
+
try {
|
|
335
|
+
const res = await this.fetchImpl(url, {
|
|
336
|
+
method: 'GET',
|
|
337
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
338
|
+
signal: controller.signal,
|
|
339
|
+
});
|
|
340
|
+
if (res.status === 404)
|
|
341
|
+
return null;
|
|
342
|
+
const text = await res.text();
|
|
343
|
+
let parsed = {};
|
|
344
|
+
try {
|
|
345
|
+
parsed = JSON.parse(text);
|
|
346
|
+
}
|
|
347
|
+
catch {
|
|
348
|
+
parsed = {};
|
|
349
|
+
}
|
|
350
|
+
if (!res.ok || parsed.success !== true) {
|
|
351
|
+
throw new SlackIdentityCloudError(res.status, parsed.code ?? `http_${res.status}`, parsed.error ?? `Cloud request failed (${res.status})`);
|
|
352
|
+
}
|
|
353
|
+
if (!isCloudConfig(parsed.data)) {
|
|
354
|
+
throw new SlackIdentityCloudError(502, 'bad_config', 'Cloud returned a malformed Slack config');
|
|
355
|
+
}
|
|
356
|
+
return parsed.data;
|
|
357
|
+
}
|
|
358
|
+
catch (err) {
|
|
359
|
+
if (err instanceof SlackIdentityCloudError)
|
|
360
|
+
throw err;
|
|
361
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
362
|
+
throw new SlackIdentityCloudError(502, 'network', `Cloud unreachable: ${message}`);
|
|
363
|
+
}
|
|
364
|
+
finally {
|
|
365
|
+
clearTimeout(timer);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
now() {
|
|
369
|
+
return this.deps.now?.() ?? Date.now();
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* Runtime guard for the Cloud config payload.
|
|
374
|
+
*
|
|
375
|
+
* @param value - Parsed JSON
|
|
376
|
+
* @returns True when it has a workspace with a bot token and an agents list
|
|
377
|
+
*/
|
|
378
|
+
export function isCloudConfig(value) {
|
|
379
|
+
const v = value;
|
|
380
|
+
if (!v || typeof v !== 'object')
|
|
381
|
+
return false;
|
|
382
|
+
const w = v.workspace;
|
|
383
|
+
if (!w || typeof w.botToken !== 'string' || !w.botToken || typeof w.slackTeamId !== 'string')
|
|
384
|
+
return false;
|
|
385
|
+
if (!Array.isArray(v.agents))
|
|
386
|
+
return false;
|
|
387
|
+
return v.agents.every((a) => a &&
|
|
388
|
+
typeof a.agentSession === 'string' &&
|
|
389
|
+
typeof a.botToken === 'string' &&
|
|
390
|
+
typeof a.botUserId === 'string');
|
|
391
|
+
}
|
|
392
|
+
let instance = null;
|
|
393
|
+
/**
|
|
394
|
+
* Install the process-wide instance (composition root).
|
|
395
|
+
*
|
|
396
|
+
* @param service - The service or null
|
|
397
|
+
*/
|
|
398
|
+
export function setSlackCloudConfigService(service) {
|
|
399
|
+
instance = service;
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* The process-wide instance, or null before wiring.
|
|
403
|
+
*
|
|
404
|
+
* @returns The service or null
|
|
405
|
+
*/
|
|
406
|
+
export function getSlackCloudConfigService() {
|
|
407
|
+
return instance;
|
|
408
|
+
}
|
|
409
|
+
//# sourceMappingURL=slack-cloud-config.service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"slack-cloud-config.service.js","sourceRoot":"","sources":["../../../../../../backend/src/services/slack/slack-cloud-config.service.ts"],"names":[],"mappings":"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"}
|