crewly 1.20.144 → 1.20.146

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.
Files changed (106) hide show
  1. package/README.md +5 -1
  2. package/config/roles/orchestrator/prompt.md +9 -0
  3. package/config/skills/orchestrator/harness-login/SKILL.md +88 -0
  4. package/config/skills/orchestrator/harness-login/execute.sh +75 -0
  5. package/config/skills/orchestrator/harness-login/execute.test.sh +72 -0
  6. package/dist/backend/backend/src/constants.d.ts +12 -0
  7. package/dist/backend/backend/src/constants.d.ts.map +1 -1
  8. package/dist/backend/backend/src/constants.js +12 -0
  9. package/dist/backend/backend/src/constants.js.map +1 -1
  10. package/dist/backend/backend/src/controllers/harness/harness.routes.d.ts +1 -0
  11. package/dist/backend/backend/src/controllers/harness/harness.routes.d.ts.map +1 -1
  12. package/dist/backend/backend/src/controllers/harness/harness.routes.js +3 -0
  13. package/dist/backend/backend/src/controllers/harness/harness.routes.js.map +1 -1
  14. package/dist/backend/backend/src/controllers/harness/owner-login.controller.d.ts +61 -0
  15. package/dist/backend/backend/src/controllers/harness/owner-login.controller.d.ts.map +1 -0
  16. package/dist/backend/backend/src/controllers/harness/owner-login.controller.js +133 -0
  17. package/dist/backend/backend/src/controllers/harness/owner-login.controller.js.map +1 -0
  18. package/dist/backend/backend/src/index.d.ts.map +1 -1
  19. package/dist/backend/backend/src/index.js +4 -1
  20. package/dist/backend/backend/src/index.js.map +1 -1
  21. package/dist/backend/backend/src/services/agent/agent-registration.service.d.ts.map +1 -1
  22. package/dist/backend/backend/src/services/agent/agent-registration.service.js +23 -1
  23. package/dist/backend/backend/src/services/agent/agent-registration.service.js.map +1 -1
  24. package/dist/backend/backend/src/services/agent/codex-runtime.service.d.ts +11 -1
  25. package/dist/backend/backend/src/services/agent/codex-runtime.service.d.ts.map +1 -1
  26. package/dist/backend/backend/src/services/agent/codex-runtime.service.js +18 -0
  27. package/dist/backend/backend/src/services/agent/codex-runtime.service.js.map +1 -1
  28. package/dist/backend/backend/src/services/agent/crewly-agent/crewly-agent-external-runtime.service.d.ts +12 -0
  29. package/dist/backend/backend/src/services/agent/crewly-agent/crewly-agent-external-runtime.service.d.ts.map +1 -1
  30. package/dist/backend/backend/src/services/agent/crewly-agent/crewly-agent-external-runtime.service.js +18 -3
  31. package/dist/backend/backend/src/services/agent/crewly-agent/crewly-agent-external-runtime.service.js.map +1 -1
  32. package/dist/backend/backend/src/services/agent/runtime-agent.service.abstract.d.ts +29 -0
  33. package/dist/backend/backend/src/services/agent/runtime-agent.service.abstract.d.ts.map +1 -1
  34. package/dist/backend/backend/src/services/agent/runtime-agent.service.abstract.js +48 -0
  35. package/dist/backend/backend/src/services/agent/runtime-agent.service.abstract.js.map +1 -1
  36. package/dist/backend/backend/src/services/ai/prompt-modules/skills-reference.module.d.ts.map +1 -1
  37. package/dist/backend/backend/src/services/ai/prompt-modules/skills-reference.module.js +1 -1
  38. package/dist/backend/backend/src/services/ai/prompt-modules/skills-reference.module.js.map +1 -1
  39. package/dist/backend/backend/src/services/cloud/cloud-sync.service.d.ts +14 -0
  40. package/dist/backend/backend/src/services/cloud/cloud-sync.service.d.ts.map +1 -1
  41. package/dist/backend/backend/src/services/cloud/cloud-sync.service.js +18 -2
  42. package/dist/backend/backend/src/services/cloud/cloud-sync.service.js.map +1 -1
  43. package/dist/backend/backend/src/services/harness/harness-relogin.service.d.ts +147 -5
  44. package/dist/backend/backend/src/services/harness/harness-relogin.service.d.ts.map +1 -1
  45. package/dist/backend/backend/src/services/harness/harness-relogin.service.js +210 -20
  46. package/dist/backend/backend/src/services/harness/harness-relogin.service.js.map +1 -1
  47. package/dist/backend/backend/src/services/harness/owner-login-request.d.ts +87 -0
  48. package/dist/backend/backend/src/services/harness/owner-login-request.d.ts.map +1 -0
  49. package/dist/backend/backend/src/services/harness/owner-login-request.js +191 -0
  50. package/dist/backend/backend/src/services/harness/owner-login-request.js.map +1 -0
  51. package/dist/backend/backend/src/services/slack/slack-relogin-dm.service.d.ts +68 -9
  52. package/dist/backend/backend/src/services/slack/slack-relogin-dm.service.d.ts.map +1 -1
  53. package/dist/backend/backend/src/services/slack/slack-relogin-dm.service.js +121 -11
  54. package/dist/backend/backend/src/services/slack/slack-relogin-dm.service.js.map +1 -1
  55. package/dist/backend/backend/src/utils/agent-reply-filter.utils.d.ts +75 -0
  56. package/dist/backend/backend/src/utils/agent-reply-filter.utils.d.ts.map +1 -0
  57. package/dist/backend/backend/src/utils/agent-reply-filter.utils.js +127 -0
  58. package/dist/backend/backend/src/utils/agent-reply-filter.utils.js.map +1 -0
  59. package/dist/backend/build-info.json +2 -2
  60. package/dist/cli/backend/src/constants.d.ts +12 -0
  61. package/dist/cli/backend/src/constants.d.ts.map +1 -1
  62. package/dist/cli/backend/src/constants.js +12 -0
  63. package/dist/cli/backend/src/constants.js.map +1 -1
  64. package/dist/cli/backend/src/services/agent/agent-registration.service.d.ts.map +1 -1
  65. package/dist/cli/backend/src/services/agent/agent-registration.service.js +23 -1
  66. package/dist/cli/backend/src/services/agent/agent-registration.service.js.map +1 -1
  67. package/dist/cli/backend/src/services/agent/codex-runtime.service.d.ts +11 -1
  68. package/dist/cli/backend/src/services/agent/codex-runtime.service.d.ts.map +1 -1
  69. package/dist/cli/backend/src/services/agent/codex-runtime.service.js +18 -0
  70. package/dist/cli/backend/src/services/agent/codex-runtime.service.js.map +1 -1
  71. package/dist/cli/backend/src/services/agent/crewly-agent/crewly-agent-external-runtime.service.d.ts +12 -0
  72. package/dist/cli/backend/src/services/agent/crewly-agent/crewly-agent-external-runtime.service.d.ts.map +1 -1
  73. package/dist/cli/backend/src/services/agent/crewly-agent/crewly-agent-external-runtime.service.js +18 -3
  74. package/dist/cli/backend/src/services/agent/crewly-agent/crewly-agent-external-runtime.service.js.map +1 -1
  75. package/dist/cli/backend/src/services/agent/runtime-agent.service.abstract.d.ts +29 -0
  76. package/dist/cli/backend/src/services/agent/runtime-agent.service.abstract.d.ts.map +1 -1
  77. package/dist/cli/backend/src/services/agent/runtime-agent.service.abstract.js +48 -0
  78. package/dist/cli/backend/src/services/agent/runtime-agent.service.abstract.js.map +1 -1
  79. package/dist/cli/backend/src/services/ai/prompt-modules/skills-reference.module.d.ts.map +1 -1
  80. package/dist/cli/backend/src/services/ai/prompt-modules/skills-reference.module.js +1 -1
  81. package/dist/cli/backend/src/services/ai/prompt-modules/skills-reference.module.js.map +1 -1
  82. package/dist/cli/backend/src/services/cloud/cloud-sync.service.d.ts +14 -0
  83. package/dist/cli/backend/src/services/cloud/cloud-sync.service.d.ts.map +1 -1
  84. package/dist/cli/backend/src/services/cloud/cloud-sync.service.js +18 -2
  85. package/dist/cli/backend/src/services/cloud/cloud-sync.service.js.map +1 -1
  86. package/dist/cli/backend/src/services/harness/harness-relogin.service.d.ts +147 -5
  87. package/dist/cli/backend/src/services/harness/harness-relogin.service.d.ts.map +1 -1
  88. package/dist/cli/backend/src/services/harness/harness-relogin.service.js +210 -20
  89. package/dist/cli/backend/src/services/harness/harness-relogin.service.js.map +1 -1
  90. package/dist/cli/backend/src/services/harness/owner-login-request.d.ts +87 -0
  91. package/dist/cli/backend/src/services/harness/owner-login-request.d.ts.map +1 -0
  92. package/dist/cli/backend/src/services/harness/owner-login-request.js +191 -0
  93. package/dist/cli/backend/src/services/harness/owner-login-request.js.map +1 -0
  94. package/dist/cli/backend/src/utils/agent-reply-filter.utils.d.ts +75 -0
  95. package/dist/cli/backend/src/utils/agent-reply-filter.utils.d.ts.map +1 -0
  96. package/dist/cli/backend/src/utils/agent-reply-filter.utils.js +127 -0
  97. package/dist/cli/backend/src/utils/agent-reply-filter.utils.js.map +1 -0
  98. package/package.json +1 -1
  99. package/packages/crewly-agent/src/runtime/agent-runner.service.test.ts +39 -0
  100. package/packages/crewly-agent/src/runtime/agent-runner.service.ts +17 -17
  101. package/packages/crewly-agent/src/runtime/interactive-login-guard.test.ts +60 -0
  102. package/packages/crewly-agent/src/runtime/interactive-login-guard.ts +76 -0
  103. package/packages/crewly-agent/src/runtime/reply-tool-calls.test.ts +53 -0
  104. package/packages/crewly-agent/src/runtime/reply-tool-calls.ts +76 -0
  105. package/packages/crewly-agent/src/runtime/tool-registry.test.ts +8 -0
  106. package/packages/crewly-agent/src/runtime/tool-registry.ts +14 -0
@@ -0,0 +1,191 @@
1
+ /**
2
+ * Owner login requests — recognising "log harness X in" in plain words.
3
+ *
4
+ * Two consumers:
5
+ * - {@link parseOwnerLoginRequest}: the Slack DM interceptor. An owner who
6
+ * writes 「重新登录 claude」, 「换个账号登录 claude」 or "relogin codex" in the
7
+ * orchestrator's DM gets the login flow directly, without the LLM. The
8
+ * match is deliberately strict — the whole message must be the request —
9
+ * so a sentence that merely mentions a login still goes to the orc.
10
+ * - {@link isOwnerLoginRequestEvidence}: the orchestrator's `harness-login`
11
+ * skill. The orc may start a login only when a real owner message asked for
12
+ * one; this is the evidence check over the owner's recent messages.
13
+ *
14
+ * Incident 2026-09-26: the orc ran `claude setup-token` in its one-shot bash
15
+ * tool (the process died with the call, every pasted code went stale) and
16
+ * then claimed several times that it had sent a new link. The broker-backed
17
+ * flow started from here keeps the login alive in its own PTY.
18
+ *
19
+ * @module services/harness/owner-login-request
20
+ */
21
+ import { HARNESS_CONSTANTS } from '../../constants.js';
22
+ /** Harness names and aliases → id, longest first so "claude code" wins over "claude". */
23
+ const HARNESS_NAME_PATTERNS = [
24
+ { re: /^claude[\s_-]*code$/, id: HARNESS_CONSTANTS.IDS.CLAUDE_CODE },
25
+ { re: /^claude$/, id: HARNESS_CONSTANTS.IDS.CLAUDE_CODE },
26
+ { re: /^codex(?:[\s_-]*cli)?$/, id: HARNESS_CONSTANTS.IDS.CODEX_CLI },
27
+ { re: /^antigravity(?:[\s_-]*cli)?$/, id: HARNESS_CONSTANTS.IDS.ANTIGRAVITY_CLI },
28
+ { re: /^agy$/, id: HARNESS_CONSTANTS.IDS.ANTIGRAVITY_CLI },
29
+ { re: /^gemini(?:[\s_-]*cli)?$/, id: HARNESS_CONSTANTS.IDS.GEMINI_CLI },
30
+ ];
31
+ /** Any harness mention inside a longer message (evidence check). */
32
+ const HARNESS_MENTIONS = [
33
+ { re: /claude/i, id: HARNESS_CONSTANTS.IDS.CLAUDE_CODE },
34
+ { re: /codex/i, id: HARNESS_CONSTANTS.IDS.CODEX_CLI },
35
+ { re: /antigravity|\bagy\b/i, id: HARNESS_CONSTANTS.IDS.ANTIGRAVITY_CLI },
36
+ { re: /gemini/i, id: HARNESS_CONSTANTS.IDS.GEMINI_CLI },
37
+ ];
38
+ /**
39
+ * Names that are coding assistants Crewly does not run. "登录 cursor" is
40
+ * answered with the supported list; any other unknown word after a login
41
+ * verb ("登录 gmail") is not a harness request and goes to the orc.
42
+ */
43
+ const OTHER_CODING_TOOLS = /^(?:cursor|copilot|github\s*copilot|aider|opencode|cline|windsurf|kimi(?:\s*cli)?|qwen(?:\s*code)?|amp|goose|kiro|trae)$/;
44
+ /** Generic words for "the coding assistant" — a request for a harness without naming one. */
45
+ const GENERIC_HARNESS_WORDS = /^(?:harness|编程助手|编码助手|代码助手|ai\s*harness|cli)$/;
46
+ /** Chinese login verbs. The account-switch forms come first so they win. */
47
+ const ZH_VERB = '(?:换(?:一)?个(?:新)?账号(?:重新)?登[录陆]|换(?:一)?个号(?:重新)?登[录陆]|换账号(?:重新)?登[录陆]|换号(?:重新)?登[录陆]|用(?:另|别)(?:一)?个账号(?:重新)?登[录陆]|切换账号|换(?:一)?个(?:新)?账号|换账号|换号|重新登[录陆入]|重登[录陆]?|再登[录陆]|登[录陆入])';
48
+ /** English login verbs. */
49
+ const EN_VERB = '(?:re-?log\\s*in(?:\\s+to)?|re-?login(?:\\s+to)?|log\\s*in(?:\\s+to)?|login(?:\\s+to)?|sign\\s*in(?:\\s+to)?|re-?auth(?:enticate)?|switch\\s+(?:the\\s+)?accounts?(?:\\s+(?:for|of|on))?)';
50
+ /** Polite lead-ins that do not change the request. */
51
+ const PREFIX = '(?:(?:please|pls|can\\s+you|could\\s+you)\\s+)?(?:帮我|帮忙|麻烦(?:你)?|请(?:你)?|给我|你)?\\s*(?:(?:把|给|帮)\\s*)?';
52
+ /** Harmless endings. No 吗/了: "登录 claude 了吗" asks about a login, it does not request one. */
53
+ const SUFFIX = '\\s*(?:一下|下|吧)?\\s*[.。!!~~]*';
54
+ /** A name after or before the verb: up to three ASCII words, fewest first ("claude code", not "claude account"). */
55
+ const NAME = '([a-z][a-z0-9_-]*(?:\\s+[a-z][a-z0-9_-]*){0,2}?|编程助手|编码助手|代码助手)';
56
+ /** Optional "account" noun after the name ("claude 的账号", "claude account"). */
57
+ const ACCOUNT_NOUN = '(?:\\s*(?:的)?(?:账号|帐号|account))?';
58
+ /** "登录 claude" / "relogin to codex" / "换个账号登录 claude code". */
59
+ const VERB_FIRST = new RegExp(`^${PREFIX}(${ZH_VERB}|${EN_VERB})\\s*(?:到\\s*)?${NAME}${ACCOUNT_NOUN}${SUFFIX}$`, 'i');
60
+ /** "claude 重新登录" / "codex login" / "把 claude 换个账号登录". */
61
+ const NAME_FIRST = new RegExp(`^${PREFIX}${NAME}${ACCOUNT_NOUN}\\s*(?:重新)?(${ZH_VERB}|${EN_VERB})${SUFFIX}$`, 'i');
62
+ /** "给 claude 换个账号" / "switch claude account" / "switch claude to another account". */
63
+ const SWITCH_NAME = new RegExp(`^${PREFIX}(?:switch|change)\\s+${NAME}\\s+(?:to\\s+(?:another|a\\s+different|a\\s+new)\\s+)?accounts?${SUFFIX}$`, 'i');
64
+ /** A bare verb ("重新登录", "login") — a request with no harness named. */
65
+ const BARE_VERB = new RegExp(`^${PREFIX}(${ZH_VERB}|${EN_VERB})${SUFFIX}$`, 'i');
66
+ /** Words that mark an account switch. */
67
+ const SWITCH_WORDS = /换|切换|switch|another\s+account|different\s+account|other\s+account|另一个账号|别的账号|其他账号/i;
68
+ /**
69
+ * Normalise a DM for matching: drop Slack mention/link markup, collapse
70
+ * whitespace, lower-case, trim.
71
+ *
72
+ * @param text - Raw message text
73
+ * @returns Normalised text
74
+ */
75
+ export function normaliseLoginText(text) {
76
+ return text
77
+ .replace(/<@[A-Z0-9]+>/g, ' ')
78
+ .replace(/<([^|>]+)\|([^>]+)>/g, '$2')
79
+ .replace(/[「」“”"'`]/g, ' ')
80
+ .replace(/\s+/g, ' ')
81
+ .trim()
82
+ .toLowerCase();
83
+ }
84
+ /**
85
+ * Resolve a harness name or alias as written by a person.
86
+ *
87
+ * @param name - e.g. "claude code", "Claude", "agy", "codex cli"
88
+ * @returns The harness id, or null
89
+ *
90
+ * @example
91
+ * ```ts
92
+ * resolveHarnessName('claude code'); // 'claude-code'
93
+ * ```
94
+ */
95
+ export function resolveHarnessName(name) {
96
+ const key = name.trim().toLowerCase().replace(/\s+/g, ' ');
97
+ for (const { re, id } of HARNESS_NAME_PATTERNS) {
98
+ if (re.test(key))
99
+ return id;
100
+ }
101
+ return null;
102
+ }
103
+ /**
104
+ * Turn a matched name into a request, or null when the name is not about a
105
+ * coding assistant at all ("登录 gmail" is for the orc, not for us).
106
+ *
107
+ * @param rawName - Name as matched
108
+ * @param switchAccount - Whether the owner asked for a different account
109
+ * @returns The request or null
110
+ */
111
+ function requestForName(rawName, switchAccount) {
112
+ const name = rawName.trim();
113
+ const harnessId = resolveHarnessName(name);
114
+ if (harnessId)
115
+ return { kind: 'harness', harnessId, switchAccount };
116
+ if (GENERIC_HARNESS_WORDS.test(name))
117
+ return { kind: 'unknown', name: null, switchAccount };
118
+ if (OTHER_CODING_TOOLS.test(name))
119
+ return { kind: 'unknown', name, switchAccount };
120
+ return null;
121
+ }
122
+ /**
123
+ * Recognise an owner's DM that asks Crewly to log a harness in (or switch
124
+ * its account). The whole message must be the request; anything longer or
125
+ * different returns null and is handled by the orchestrator as usual.
126
+ *
127
+ * @param text - Owner DM text
128
+ * @returns The request, or null when the message is not a login request
129
+ *
130
+ * @example
131
+ * ```ts
132
+ * parseOwnerLoginRequest('换个账号登录 claude');
133
+ * // { kind: 'harness', harnessId: 'claude-code', switchAccount: true }
134
+ * parseOwnerLoginRequest('重新登录');
135
+ * // { kind: 'unknown', name: null, switchAccount: false }
136
+ * ```
137
+ */
138
+ export function parseOwnerLoginRequest(text) {
139
+ if (typeof text !== 'string')
140
+ return null;
141
+ const normalised = normaliseLoginText(text);
142
+ if (!normalised || normalised.length > HARNESS_CONSTANTS.OWNER_LOGIN.MAX_TRIGGER_LENGTH)
143
+ return null;
144
+ const switchAccount = SWITCH_WORDS.test(normalised);
145
+ // Each form in turn; a form whose "name" is not a harness ("relogin" read
146
+ // as name "re" + verb "login") does not end the search.
147
+ const verbFirst = VERB_FIRST.exec(normalised);
148
+ const fromVerbFirst = verbFirst ? requestForName(verbFirst[2], switchAccount) : null;
149
+ if (fromVerbFirst)
150
+ return fromVerbFirst;
151
+ const nameFirst = NAME_FIRST.exec(normalised);
152
+ const fromNameFirst = nameFirst ? requestForName(nameFirst[1], switchAccount) : null;
153
+ if (fromNameFirst)
154
+ return fromNameFirst;
155
+ const switchName = SWITCH_NAME.exec(normalised);
156
+ const fromSwitch = switchName ? requestForName(switchName[1], true) : null;
157
+ if (fromSwitch)
158
+ return fromSwitch;
159
+ if (BARE_VERB.test(normalised))
160
+ return { kind: 'unknown', name: null, switchAccount };
161
+ return null;
162
+ }
163
+ /** A login verb anywhere in a message (evidence check; broader than the trigger). */
164
+ const LOGIN_MENTION = /登[录陆入]|重登|换(?:一)?个?账号|切换账号|换号|re-?log\s*in|re-?login|\blog\s*in\b|\blog\s+(?:\S+\s+){1,2}in\b|\blogin\b|sign\s*in|setup-token|\/login|switch\s+(?:the\s+)?accounts?|login\s+link|登[录陆]链接/i;
165
+ /** Status questions — asking whether it is logged in is not asking to log in. */
166
+ const STATUS_QUESTION = /登[录陆]了吗|登[录陆]状态|是否(?:已经)?登[录陆]|有没有登[录陆]|登[录陆]过期了吗|(?:is|are)\s+\S+\s+logged\s+in|login\s+status|still\s+logged\s+in/i;
167
+ /**
168
+ * Whether one owner message asks for a (re-)login of a harness. Mirrors the
169
+ * install-skill approval check: the orchestrator's claim is not trusted, the
170
+ * owner's own words are.
171
+ *
172
+ * Counts a message with a login verb that either names this harness or names
173
+ * none (「不 我要重新登陆一个账号」 in a conversation about Claude). A message
174
+ * naming only other harnesses, or a status question, does not count.
175
+ *
176
+ * @param message - Owner message text
177
+ * @param harnessId - Harness the orchestrator wants to log in
178
+ * @returns True when the message is evidence for that login
179
+ */
180
+ export function isOwnerLoginRequestEvidence(message, harnessId) {
181
+ if (typeof message !== 'string' || !message.trim())
182
+ return false;
183
+ const parsed = parseOwnerLoginRequest(message);
184
+ if (parsed?.kind === 'harness')
185
+ return parsed.harnessId === harnessId;
186
+ if (!LOGIN_MENTION.test(message) || STATUS_QUESTION.test(message))
187
+ return false;
188
+ const named = HARNESS_MENTIONS.filter(({ re }) => re.test(message)).map(({ id }) => id);
189
+ return named.length === 0 || named.includes(harnessId);
190
+ }
191
+ //# sourceMappingURL=owner-login-request.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"owner-login-request.js","sourceRoot":"","sources":["../../../../../../backend/src/services/harness/owner-login-request.ts"],"names":[],"mappings":"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"}
@@ -7,13 +7,18 @@
7
7
  * known, falls back to the owner-notification path
8
8
  * (`SlackService.sendNotification`, the most recent master-bot DM).
9
9
  * - {@link SlackReloginDmService.isOwnerDmReply}: whether an inbound message
10
- * is the owner writing in that DM, so the Slack bridge may offer it to the
11
- * re-login coordinator before anything else (logging, thread store, orc).
10
+ * is the owner writing in that DM — or in their DM with the orchestrator's
11
+ * own bot ("Crewly Orc"), where they usually talk to the orc — so the Slack
12
+ * bridge may offer it to the re-login coordinator before anything else
13
+ * (logging, thread store, orc).
14
+ * - An owner-requested login (「重新登录 claude」) answers in the conversation
15
+ * and thread it was asked in, under the bot that conversation belongs to
16
+ * ({@link ReloginReplyTarget}); the master-bot DM is the fallback.
12
17
  *
13
18
  * @module services/slack/slack-relogin-dm.service
14
19
  */
15
20
  import type { SlackIncomingMessage, SlackNotification, SlackOutgoingMessage } from '../../types/slack.types.js';
16
- import type { HarnessReloginService, ReloginOwnerNotifier } from '../harness/harness-relogin.service.js';
21
+ import type { HarnessReloginService, ReloginOwnerNotifier, ReloginReplyTarget } from '../harness/harness-relogin.service.js';
17
22
  /** The slice of SlackService this adapter uses. */
18
23
  export interface ReloginDmSlackApi {
19
24
  isConnected(): boolean;
@@ -32,40 +37,94 @@ export interface ReloginDmSlackApi {
32
37
  * @returns Text safe for `chat.postMessage`
33
38
  */
34
39
  export declare function escapeSlackText(text: string): string;
40
+ /** Resolves an agent's own Slack bot token (null when it has no bot). */
41
+ export type AgentBotTokenResolver = (agentSession: string) => string | null;
35
42
  /** DMs the owner for the re-login coordinator and recognises their replies. */
36
43
  export declare class SlackReloginDmService implements ReloginOwnerNotifier {
37
44
  private readonly getSlack;
38
45
  private readonly now;
46
+ private readonly getAgentBotToken;
39
47
  private readonly logger;
40
48
  /** DM channel the last re-login message went to */
41
49
  private dmChannelId;
42
50
  /**
43
51
  * @param getSlack - Slack service accessor (resolved per call; the connection can come up late)
44
52
  * @param now - Clock
53
+ * @param getAgentBotToken - Bot token of an agent's own Slack app (for answering in the orc's DM)
45
54
  */
46
- constructor(getSlack: () => ReloginDmSlackApi, now?: () => Date);
55
+ constructor(getSlack: () => ReloginDmSlackApi, now?: () => Date, getAgentBotToken?: AgentBotTokenResolver);
56
+ /**
57
+ * Whether Slack is connected, so a DM can go out now.
58
+ *
59
+ * @returns True when connected
60
+ */
61
+ isAvailable(): boolean;
62
+ /**
63
+ * Where to answer a message: its conversation and thread, and the agent
64
+ * whose bot owns the conversation (for the orc's own-bot DM).
65
+ *
66
+ * @param message - Inbound Slack message
67
+ * @returns Reply target
68
+ */
69
+ replyTargetOf(message: SlackIncomingMessage): ReloginReplyTarget;
47
70
  /**
48
71
  * DM the owner. Never logs the text.
49
72
  *
50
73
  * @param text - Message (plain text with Slack mrkdwn emphasis; escaped here)
74
+ * @param target - Conversation to answer in; absent/unusable = the master-bot DM
51
75
  * @returns True when Slack accepted it
52
76
  */
53
- sendToOwner(text: string): Promise<boolean>;
77
+ sendToOwner(text: string, target?: ReloginReplyTarget | null): Promise<boolean>;
78
+ /**
79
+ * Post into a specific conversation (the thread the owner asked in).
80
+ *
81
+ * @param slack - Slack API
82
+ * @param escaped - Escaped text
83
+ * @param target - Conversation, thread and owning agent
84
+ * @returns True when posted
85
+ */
86
+ private sendToTarget;
54
87
  /**
55
88
  * Whether a message is the owner writing in their DM with the master bot
56
- * (the DM the re-login messages went to, when known).
89
+ * (the DM the re-login messages went to, when known) or in their DM with
90
+ * the orchestrator's own bot.
57
91
  *
58
92
  * @param message - Inbound Slack message
59
93
  * @returns True for the owner's DM reply
60
94
  */
61
95
  isOwnerDmReply(message: SlackIncomingMessage): boolean;
62
96
  }
97
+ /** The slice of the orc's turn origin the reply-target resolver reads. */
98
+ export interface OrcTurnOriginLike {
99
+ conversationId: string;
100
+ slackChannelId?: string;
101
+ slackThreadTs?: string;
102
+ }
103
+ /** Link between a chat-v2 DM channel and a Slack DM with an agent's bot. */
104
+ export interface AgentDmLinkLike {
105
+ agentSession: string;
106
+ slackChannelId: string;
107
+ replyThreadTs?: string;
108
+ }
109
+ /**
110
+ * Where the orchestrator's current conversation is on Slack — the answer
111
+ * target for a login the orc starts with its `harness-login` skill, so the
112
+ * link lands in the thread the owner asked in.
113
+ *
114
+ * @param origin - The orc's fresh turn origin, if any
115
+ * @param agentDm - Lookups into the agent-DM links (orc bot DM ↔ chat channel)
116
+ * @returns The Slack target, or null (then the master-bot DM is used)
117
+ */
118
+ export declare function resolveOrcTurnReplyTarget(origin: OrcTurnOriginLike | undefined, agentDm: {
119
+ findBySlackChannelId(slackChannelId: string): AgentDmLinkLike | null;
120
+ findByChatChannelId(chatChannelId: string): AgentDmLinkLike | null;
121
+ } | null): ReloginReplyTarget | null;
63
122
  /**
64
123
  * Build the Slack bridge's inbound interceptor for the re-login: an owner's
65
124
  * DM reply (text only) is offered to the coordinator, which consumes it only
66
125
  * when it belongs to a login (a code, the retry keyword, a reply to an
67
- * unrecognised screen). A consumed message must not be logged, stored or
68
- * forwarded to the orchestrator.
126
+ * unrecognised screen) or asks for one (「重新登录 claude」). A consumed
127
+ * message must not be logged, stored or forwarded to the orchestrator.
69
128
  *
70
129
  * @param dm - Owner-DM recogniser
71
130
  * @param coordinator - Re-login coordinator
@@ -76,5 +135,5 @@ export declare class SlackReloginDmService implements ReloginOwnerNotifier {
76
135
  * bridge.setInboundInterceptor(createReloginReplyInterceptor(dm, getHarnessReloginService()));
77
136
  * ```
78
137
  */
79
- export declare function createReloginReplyInterceptor(dm: Pick<SlackReloginDmService, 'isOwnerDmReply'>, coordinator: Pick<HarnessReloginService, 'handleOwnerReply'>): (message: SlackIncomingMessage) => boolean;
138
+ export declare function createReloginReplyInterceptor(dm: Pick<SlackReloginDmService, 'isOwnerDmReply' | 'replyTargetOf'>, coordinator: Pick<HarnessReloginService, 'handleOwnerReply'>): (message: SlackIncomingMessage) => boolean;
80
139
  //# sourceMappingURL=slack-relogin-dm.service.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"slack-relogin-dm.service.d.ts","sourceRoot":"","sources":["../../../../../../backend/src/services/slack/slack-relogin-dm.service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAC;AAChH,OAAO,KAAK,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,MAAM,uCAAuC,CAAC;AAMzG,mDAAmD;AACnD,MAAM,WAAW,iBAAiB;IACjC,WAAW,IAAI,OAAO,CAAC;IACvB,cAAc,EAAE,CAAC,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC;IAC7C,wBAAwB,EAAE,CAAC,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,GAAG,IAAI,CAAC;IAClE,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACnD,WAAW,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC5D,gBAAgB,CAAC,YAAY,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACjE;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEpD;AAED,+EAA+E;AAC/E,qBAAa,qBAAsB,YAAW,oBAAoB;IAUhE,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,GAAG;IAVrB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAkB;IACzC,mDAAmD;IACnD,OAAO,CAAC,WAAW,CAAuB;IAE1C;;;OAGG;gBAEe,QAAQ,EAAE,MAAM,iBAAiB,EACjC,GAAG,GAAE,MAAM,IAAuB;IAKpD;;;;;OAKG;IACG,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IA0BjD;;;;;;OAMG;IACH,cAAc,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO;CAUtD;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,6BAA6B,CAC5C,EAAE,EAAE,IAAI,CAAC,qBAAqB,EAAE,gBAAgB,CAAC,EACjD,WAAW,EAAE,IAAI,CAAC,qBAAqB,EAAE,kBAAkB,CAAC,GAC1D,CAAC,OAAO,EAAE,oBAAoB,KAAK,OAAO,CAM5C"}
1
+ {"version":3,"file":"slack-relogin-dm.service.d.ts","sourceRoot":"","sources":["../../../../../../backend/src/services/slack/slack-relogin-dm.service.ts"],"names":[],"mappings":"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"}
@@ -7,12 +7,18 @@
7
7
  * known, falls back to the owner-notification path
8
8
  * (`SlackService.sendNotification`, the most recent master-bot DM).
9
9
  * - {@link SlackReloginDmService.isOwnerDmReply}: whether an inbound message
10
- * is the owner writing in that DM, so the Slack bridge may offer it to the
11
- * re-login coordinator before anything else (logging, thread store, orc).
10
+ * is the owner writing in that DM — or in their DM with the orchestrator's
11
+ * own bot ("Crewly Orc"), where they usually talk to the orc — so the Slack
12
+ * bridge may offer it to the re-login coordinator before anything else
13
+ * (logging, thread store, orc).
14
+ * - An owner-requested login (「重新登录 claude」) answers in the conversation
15
+ * and thread it was asked in, under the bot that conversation belongs to
16
+ * ({@link ReloginReplyTarget}); the master-bot DM is the fallback.
12
17
  *
13
18
  * @module services/slack/slack-relogin-dm.service
14
19
  */
15
20
  import { LoggerService } from '../core/logger.service.js';
21
+ import { ORCHESTRATOR_SESSION_NAME } from '../../constants.js';
16
22
  /** Prefix of Slack direct-message channel ids. */
17
23
  const DM_CHANNEL_PREFIX = 'D';
18
24
  /**
@@ -30,29 +36,63 @@ export function escapeSlackText(text) {
30
36
  export class SlackReloginDmService {
31
37
  getSlack;
32
38
  now;
39
+ getAgentBotToken;
33
40
  logger;
34
41
  /** DM channel the last re-login message went to */
35
42
  dmChannelId = null;
36
43
  /**
37
44
  * @param getSlack - Slack service accessor (resolved per call; the connection can come up late)
38
45
  * @param now - Clock
46
+ * @param getAgentBotToken - Bot token of an agent's own Slack app (for answering in the orc's DM)
39
47
  */
40
- constructor(getSlack, now = () => new Date()) {
48
+ constructor(getSlack, now = () => new Date(), getAgentBotToken = () => null) {
41
49
  this.getSlack = getSlack;
42
50
  this.now = now;
51
+ this.getAgentBotToken = getAgentBotToken;
43
52
  this.logger = LoggerService.getInstance().createComponentLogger('SlackReloginDm');
44
53
  }
54
+ /**
55
+ * Whether Slack is connected, so a DM can go out now.
56
+ *
57
+ * @returns True when connected
58
+ */
59
+ isAvailable() {
60
+ try {
61
+ return this.getSlack().isConnected();
62
+ }
63
+ catch {
64
+ return false;
65
+ }
66
+ }
67
+ /**
68
+ * Where to answer a message: its conversation and thread, and the agent
69
+ * whose bot owns the conversation (for the orc's own-bot DM).
70
+ *
71
+ * @param message - Inbound Slack message
72
+ * @returns Reply target
73
+ */
74
+ replyTargetOf(message) {
75
+ const threadTs = message.threadTs || message.ts;
76
+ return {
77
+ channelId: message.channelId,
78
+ ...(threadTs ? { threadTs } : {}),
79
+ ...(message.agentSession ? { agentSession: message.agentSession } : {}),
80
+ };
81
+ }
45
82
  /**
46
83
  * DM the owner. Never logs the text.
47
84
  *
48
85
  * @param text - Message (plain text with Slack mrkdwn emphasis; escaped here)
86
+ * @param target - Conversation to answer in; absent/unusable = the master-bot DM
49
87
  * @returns True when Slack accepted it
50
88
  */
51
- async sendToOwner(text) {
89
+ async sendToOwner(text, target) {
52
90
  const slack = this.getSlack();
53
91
  if (!slack.isConnected())
54
92
  return false;
55
93
  const escaped = escapeSlackText(text);
94
+ if (target && (await this.sendToTarget(slack, escaped, target)))
95
+ return true;
56
96
  const ownerId = slack.getOwnerUserId?.() ?? null;
57
97
  if (ownerId) {
58
98
  try {
@@ -76,35 +116,105 @@ export class SlackReloginDmService {
76
116
  return false;
77
117
  }
78
118
  }
119
+ /**
120
+ * Post into a specific conversation (the thread the owner asked in).
121
+ *
122
+ * @param slack - Slack API
123
+ * @param escaped - Escaped text
124
+ * @param target - Conversation, thread and owning agent
125
+ * @returns True when posted
126
+ */
127
+ async sendToTarget(slack, escaped, target) {
128
+ // A DM with an agent's own bot is only reachable with that bot's token.
129
+ const botToken = target.agentSession ? this.getAgentBotToken(target.agentSession) : null;
130
+ if (target.agentSession && !botToken)
131
+ return false;
132
+ try {
133
+ await slack.sendMessage({
134
+ channelId: target.channelId,
135
+ text: escaped,
136
+ ...(target.threadTs ? { threadTs: target.threadTs } : {}),
137
+ ...(botToken ? { botToken } : {}),
138
+ unfurlLinks: false,
139
+ unfurlMedia: false,
140
+ skipChatV2Mirror: true,
141
+ });
142
+ return true;
143
+ }
144
+ catch (error) {
145
+ this.logger.warn('Could not answer in the conversation the login was asked in; using the owner DM', {
146
+ channelId: target.channelId,
147
+ error: error instanceof Error ? error.message : String(error),
148
+ });
149
+ return false;
150
+ }
151
+ }
79
152
  /**
80
153
  * Whether a message is the owner writing in their DM with the master bot
81
- * (the DM the re-login messages went to, when known).
154
+ * (the DM the re-login messages went to, when known) or in their DM with
155
+ * the orchestrator's own bot.
82
156
  *
83
157
  * @param message - Inbound Slack message
84
158
  * @returns True for the owner's DM reply
85
159
  */
86
160
  isOwnerDmReply(message) {
87
- if (message.agentSession || message.authorAgentSession || message.handoffTo)
161
+ if (message.authorAgentSession || message.handoffTo)
88
162
  return false;
89
163
  if (!message.channelId?.startsWith(DM_CHANNEL_PREFIX))
90
164
  return false;
91
165
  const slack = this.getSlack();
92
- if (slack.isAgentOwnedConversation?.(message.channelId))
93
- return false;
94
166
  const ownerId = slack.getOwnerUserId?.() ?? null;
95
167
  if (ownerId && message.userId !== ownerId)
96
168
  return false;
169
+ // The owner's DM with the orc's own bot: where they talk to the orc
170
+ // (2026-09-26: 「帮我重新登陆claude code」 was written there). Other
171
+ // agents' DMs stay theirs.
172
+ if (message.agentSession)
173
+ return message.agentSession === ORCHESTRATOR_SESSION_NAME;
174
+ if (slack.isAgentOwnedConversation?.(message.channelId))
175
+ return false;
97
176
  if (this.dmChannelId && message.channelId !== this.dmChannelId)
98
177
  return false;
99
178
  return true;
100
179
  }
101
180
  }
181
+ /**
182
+ * Where the orchestrator's current conversation is on Slack — the answer
183
+ * target for a login the orc starts with its `harness-login` skill, so the
184
+ * link lands in the thread the owner asked in.
185
+ *
186
+ * @param origin - The orc's fresh turn origin, if any
187
+ * @param agentDm - Lookups into the agent-DM links (orc bot DM ↔ chat channel)
188
+ * @returns The Slack target, or null (then the master-bot DM is used)
189
+ */
190
+ export function resolveOrcTurnReplyTarget(origin, agentDm) {
191
+ if (!origin)
192
+ return null;
193
+ if (origin.slackChannelId) {
194
+ if (!origin.slackChannelId.startsWith(DM_CHANNEL_PREFIX))
195
+ return null;
196
+ const link = agentDm?.findBySlackChannelId(origin.slackChannelId) ?? null;
197
+ return {
198
+ channelId: origin.slackChannelId,
199
+ ...(origin.slackThreadTs ? { threadTs: origin.slackThreadTs } : {}),
200
+ ...(link ? { agentSession: link.agentSession } : {}),
201
+ };
202
+ }
203
+ const link = agentDm?.findByChatChannelId(origin.conversationId) ?? null;
204
+ if (!link)
205
+ return null;
206
+ return {
207
+ channelId: link.slackChannelId,
208
+ ...(link.replyThreadTs ? { threadTs: link.replyThreadTs } : {}),
209
+ agentSession: link.agentSession,
210
+ };
211
+ }
102
212
  /**
103
213
  * Build the Slack bridge's inbound interceptor for the re-login: an owner's
104
214
  * DM reply (text only) is offered to the coordinator, which consumes it only
105
215
  * when it belongs to a login (a code, the retry keyword, a reply to an
106
- * unrecognised screen). A consumed message must not be logged, stored or
107
- * forwarded to the orchestrator.
216
+ * unrecognised screen) or asks for one (「重新登录 claude」). A consumed
217
+ * message must not be logged, stored or forwarded to the orchestrator.
108
218
  *
109
219
  * @param dm - Owner-DM recogniser
110
220
  * @param coordinator - Re-login coordinator
@@ -121,7 +231,7 @@ export function createReloginReplyInterceptor(dm, coordinator) {
121
231
  return false;
122
232
  if (!dm.isOwnerDmReply(message))
123
233
  return false;
124
- return coordinator.handleOwnerReply(message.text);
234
+ return coordinator.handleOwnerReply(message.text, dm.replyTargetOf(message));
125
235
  };
126
236
  }
127
237
  //# sourceMappingURL=slack-relogin-dm.service.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"slack-relogin-dm.service.js","sourceRoot":"","sources":["../../../../../../backend/src/services/slack/slack-relogin-dm.service.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"slack-relogin-dm.service.js","sourceRoot":"","sources":["../../../../../../backend/src/services/slack/slack-relogin-dm.service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Filter for an in-process agent's final turn text before it is posted to a
3
+ * person (chat, Slack DM).
4
+ *
5
+ * Incident 2026-09-26 (owner's Slack DM with the orchestrator): after each
6
+ * turn the owner got an English meta report — "I've sent the reply. Here's
7
+ * my status: *What the user asked* … *What I did* … *Next step* … *Note on
8
+ * the ticket instruction*". Several turns in a row had made no tool call at
9
+ * all and still said "I generated a fresh link and sent it via reply-chat":
10
+ * nothing had been sent. Both kinds of text are noise at best and false at
11
+ * worst, so they are not posted:
12
+ *
13
+ * - {@link isMetaStatusReport}: the text reports on the turn instead of
14
+ * answering ("Here's my status", or self-referential section headers such
15
+ * as "What I did" plus another report header).
16
+ * - {@link claimsSentReply} with no reply tool call in the turn: the text
17
+ * says a reply / link was sent to the person, but nothing sent one. Text
18
+ * that carries a URL itself is kept — posting it delivers what it claims.
19
+ *
20
+ * A genuine answer — even one mentioning "next steps" or "I sent the task
21
+ * to Joe" — passes.
22
+ *
23
+ * @module utils/agent-reply-filter
24
+ */
25
+ /** The slice of a tool call record the filter reads. */
26
+ export interface TurnToolCall {
27
+ toolName: string;
28
+ args?: Record<string, unknown>;
29
+ }
30
+ /** Why a turn's text was not posted. */
31
+ export type SuppressedReplyReason = 'meta_status_report' | 'unsent_reply_claim';
32
+ /** Result of {@link filterAgentTurnReply}. */
33
+ export interface FilteredTurnReply {
34
+ /** Text to post ('' when suppressed) */
35
+ text: string;
36
+ /** Why it was suppressed, or null when it passes */
37
+ suppressed: SuppressedReplyReason | null;
38
+ }
39
+ /**
40
+ * Whether a tool call sent a reply to a person.
41
+ *
42
+ * @param call - Tool call record
43
+ * @returns True for a reply tool, or a shell call that runs a reply skill
44
+ */
45
+ export declare function isReplyToolCall(call: TurnToolCall): boolean;
46
+ /**
47
+ * Whether a text is a report on the turn rather than an answer.
48
+ *
49
+ * @param text - Final turn text
50
+ * @returns True for "Here's my status" openers, or a self-report header plus
51
+ * at least one more report header
52
+ *
53
+ * @example
54
+ * ```ts
55
+ * isMetaStatusReport("I've sent the reply. Here's my status:\n**What I did:** …"); // true
56
+ * isMetaStatusReport('部署好了:https://ce.example.com'); // false
57
+ * ```
58
+ */
59
+ export declare function isMetaStatusReport(text: string): boolean;
60
+ /**
61
+ * Whether a text claims a reply, link or message was sent to the person.
62
+ *
63
+ * @param text - Final turn text
64
+ * @returns True for "I've sent the link", "The link has been sent", 「链接已经发给你了」 …
65
+ */
66
+ export declare function claimsSentReply(text: string): boolean;
67
+ /**
68
+ * Decide whether an in-process agent's final text may be posted.
69
+ *
70
+ * @param text - Final turn text (already stripped of tool-call markup)
71
+ * @param toolCalls - The turn's tool calls
72
+ * @returns The text to post, or '' with the reason it was suppressed
73
+ */
74
+ export declare function filterAgentTurnReply(text: string, toolCalls: readonly TurnToolCall[] | undefined): FilteredTurnReply;
75
+ //# sourceMappingURL=agent-reply-filter.utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-reply-filter.utils.d.ts","sourceRoot":"","sources":["../../../../../backend/src/utils/agent-reply-filter.utils.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,wDAAwD;AACxD,MAAM,WAAW,YAAY;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC/B;AAED,wCAAwC;AACxC,MAAM,MAAM,qBAAqB,GAAG,oBAAoB,GAAG,oBAAoB,CAAC;AAEhF,8CAA8C;AAC9C,MAAM,WAAW,iBAAiB;IACjC,wCAAwC;IACxC,IAAI,EAAE,MAAM,CAAC;IACb,oDAAoD;IACpD,UAAU,EAAE,qBAAqB,GAAG,IAAI,CAAC;CACzC;AA2CD;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,YAAY,GAAG,OAAO,CAK3D;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAWxD;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAGrD;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,YAAY,EAAE,GAAG,SAAS,GAAG,iBAAiB,CAQpH"}