allaboard 0.0.0-stage → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (141) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +43 -2
  3. package/dist/agent/claude/backend.js +124 -0
  4. package/dist/agent/claude/capabilities.js +30 -0
  5. package/dist/agent/claude/chrome.js +42 -0
  6. package/dist/agent/claude/hooks.js +37 -0
  7. package/dist/agent/claude/info.js +61 -0
  8. package/dist/agent/claude/listing.js +230 -0
  9. package/dist/agent/claude/module.js +82 -0
  10. package/dist/agent/claude/prompt.js +24 -0
  11. package/dist/agent/claude/record.js +9 -0
  12. package/dist/agent/claude/requests.js +122 -0
  13. package/dist/agent/claude/session.js +441 -0
  14. package/dist/agent/claude/translate.js +581 -0
  15. package/dist/agent/claude/trust.js +576 -0
  16. package/dist/agent/claude/usage-probe.js +103 -0
  17. package/dist/agent/claude/usage.js +110 -0
  18. package/dist/agent/claude/wire.js +22 -0
  19. package/dist/agent/seam.js +21 -0
  20. package/dist/chat/seam.js +10 -0
  21. package/dist/chat/slack/app/answers.js +209 -0
  22. package/dist/chat/slack/app/app.js +262 -0
  23. package/dist/chat/slack/app/clip.js +191 -0
  24. package/dist/chat/slack/app/guards.js +162 -0
  25. package/dist/chat/slack/app/home-controls.js +91 -0
  26. package/dist/chat/slack/app/messages.js +181 -0
  27. package/dist/chat/slack/app/open.js +392 -0
  28. package/dist/chat/slack/app/order.js +37 -0
  29. package/dist/chat/slack/app/requests.js +482 -0
  30. package/dist/chat/slack/app/resume-bind.js +71 -0
  31. package/dist/chat/slack/app/wire.js +83 -0
  32. package/dist/chat/slack/attachments.js +348 -0
  33. package/dist/chat/slack/bind.js +67 -0
  34. package/dist/chat/slack/channel.js +67 -0
  35. package/dist/chat/slack/channels.js +43 -0
  36. package/dist/chat/slack/delete.js +278 -0
  37. package/dist/chat/slack/footer.js +43 -0
  38. package/dist/chat/slack/hold.js +37 -0
  39. package/dist/chat/slack/home.js +1069 -0
  40. package/dist/chat/slack/module.js +174 -0
  41. package/dist/chat/slack/notice.js +64 -0
  42. package/dist/chat/slack/openfile/modal.js +362 -0
  43. package/dist/chat/slack/openfile/read.js +29 -0
  44. package/dist/chat/slack/quiet-logger.js +52 -0
  45. package/dist/chat/slack/repair.js +123 -0
  46. package/dist/chat/slack/reply/blocks.js +372 -0
  47. package/dist/chat/slack/reply/chars.js +59 -0
  48. package/dist/chat/slack/reply/clients.js +171 -0
  49. package/dist/chat/slack/reply/errors.js +125 -0
  50. package/dist/chat/slack/reply/escape.js +25 -0
  51. package/dist/chat/slack/reply/limiter.js +86 -0
  52. package/dist/chat/slack/reply/markdown.js +210 -0
  53. package/dist/chat/slack/reply/places.js +262 -0
  54. package/dist/chat/slack/reply/sinks.js +2403 -0
  55. package/dist/chat/slack/reply/status.js +433 -0
  56. package/dist/chat/slack/reply/tasks.js +11 -0
  57. package/dist/chat/slack/requests.js +552 -0
  58. package/dist/chat/slack/resume.js +127 -0
  59. package/dist/chat/slack/setup.js +114 -0
  60. package/dist/chat/slack/texts.js +185 -0
  61. package/dist/chat/slack/thread.js +537 -0
  62. package/dist/chat/slack/update.js +71 -0
  63. package/dist/chat/slack/voice.js +84 -0
  64. package/dist/cli.js +326 -0
  65. package/dist/clock.js +21 -0
  66. package/dist/core/cleanup.js +145 -0
  67. package/dist/core/commands.js +172 -0
  68. package/dist/core/config.js +174 -0
  69. package/dist/core/files/changed.js +125 -0
  70. package/dist/core/files/listing.js +382 -0
  71. package/dist/core/files/paths.js +9 -0
  72. package/dist/core/files/read.js +191 -0
  73. package/dist/core/files/waiting.js +34 -0
  74. package/dist/core/flows/answers.js +133 -0
  75. package/dist/core/flows/ask-owner.js +95 -0
  76. package/dist/core/flows/asking.js +14 -0
  77. package/dist/core/flows/bind.js +95 -0
  78. package/dist/core/flows/contract.js +1 -0
  79. package/dist/core/flows/flows.js +35 -0
  80. package/dist/core/flows/hold.js +22 -0
  81. package/dist/core/flows/inbound.js +24 -0
  82. package/dist/core/flows/lists.js +21 -0
  83. package/dist/core/flows/messages.js +293 -0
  84. package/dist/core/flows/notices.js +51 -0
  85. package/dist/core/flows/repair.js +106 -0
  86. package/dist/core/flows/resume.js +163 -0
  87. package/dist/core/flows/setup.js +130 -0
  88. package/dist/core/flows/status.js +98 -0
  89. package/dist/core/flows/talk.js +207 -0
  90. package/dist/core/flows/update.js +370 -0
  91. package/dist/core/flows/words.js +171 -0
  92. package/dist/core/folders.js +93 -0
  93. package/dist/core/footer.js +313 -0
  94. package/dist/core/guide.js +50 -0
  95. package/dist/core/hold.js +126 -0
  96. package/dist/core/instance.js +46 -0
  97. package/dist/core/lock.js +412 -0
  98. package/dist/core/models.js +10 -0
  99. package/dist/core/paths.js +75 -0
  100. package/dist/core/port/file-flows.js +1 -0
  101. package/dist/core/port/home.js +1 -0
  102. package/dist/core/port/requests.js +1 -0
  103. package/dist/core/port/threads.js +1 -0
  104. package/dist/core/port/updates.js +1 -0
  105. package/dist/core/port.js +17 -0
  106. package/dist/core/reply/fold.js +175 -0
  107. package/dist/core/reply/previews.js +258 -0
  108. package/dist/core/reply/renderer.js +846 -0
  109. package/dist/core/reply/tail.js +24 -0
  110. package/dist/core/reply/words.js +32 -0
  111. package/dist/core/requests.js +158 -0
  112. package/dist/core/sessions/constants.js +61 -0
  113. package/dist/core/sessions/deps.js +1 -0
  114. package/dist/core/sessions/directory.js +86 -0
  115. package/dist/core/sessions/errors.js +79 -0
  116. package/dist/core/sessions/manager.js +378 -0
  117. package/dist/core/sessions/session.js +3297 -0
  118. package/dist/core/sessions/tasks.js +266 -0
  119. package/dist/core/sessions/turn.js +45 -0
  120. package/dist/core/setup.js +28 -0
  121. package/dist/core/state.js +724 -0
  122. package/dist/core/texts.js +451 -0
  123. package/dist/core/updates.js +332 -0
  124. package/dist/daemon.js +566 -0
  125. package/dist/host/npm.js +134 -0
  126. package/dist/host/program.js +49 -0
  127. package/dist/host/run.js +52 -0
  128. package/dist/host/service/launchd.js +385 -0
  129. package/dist/host/service/listing.js +42 -0
  130. package/dist/host/service/seam.js +1 -0
  131. package/dist/log.js +37 -0
  132. package/dist/main.js +29 -0
  133. package/dist/node-version.js +40 -0
  134. package/dist/registry.js +12 -0
  135. package/dist/semver.js +53 -0
  136. package/dist/text.js +89 -0
  137. package/dist/updates-host.js +113 -0
  138. package/dist/values.js +30 -0
  139. package/dist/version.js +54 -0
  140. package/package.json +32 -4
  141. package/slack-app-manifest.json +50 -0
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Session setup in Slack: the message asked before the first prompt of a new session, with the
3
+ * model, the effort and bypass the request offers, and the owner's choice read back from
4
+ * `state.values`. Pure functions: the handlers post and wait, the session applies the choice.
5
+ */
6
+ import { choiceOf, DEFAULT, effortLevels, texts } from "../../core/port.js";
7
+ import * as slackTexts from "./texts.js";
8
+ export const SETUP_BLOCK = "setup"; // the one `actions` block that holds every control
9
+ export const SETUP_MODEL = "setup_model";
10
+ export const SETUP_EFFORT = "setup_effort";
11
+ export const SETUP_BYPASS = "setup_bypass";
12
+ export const SETUP_START = "setup_start";
13
+ const BYPASS_ON = "on";
14
+ const OPTION_TEXT_LIMIT = 75; // a plain_text option's own limit in Slack
15
+ /** The first `limit` characters, counted in code points as Slack counts them. */
16
+ function firstChars(text, limit) {
17
+ return Array.from(text).slice(0, limit).join("");
18
+ }
19
+ function option(label, value, description = null) {
20
+ const entry = {
21
+ text: { type: "plain_text", text: firstChars(label, OPTION_TEXT_LIMIT) },
22
+ value,
23
+ };
24
+ if (description !== null)
25
+ entry.description = description;
26
+ return entry;
27
+ }
28
+ function modelOptions(models) {
29
+ return models.map((m) => option(m.displayName, m.value,
30
+ // The CLI's own words, shown under the name (plain_text, at most 75 characters).
31
+ m.description === null
32
+ ? null
33
+ : { type: "plain_text", text: firstChars(m.description, OPTION_TEXT_LIMIT) }));
34
+ }
35
+ function effortOptions(levels) {
36
+ return [DEFAULT, ...levels].map((level) => option(texts.fill(slackTexts.SETUP_EFFORT_OPTION, { level }), level));
37
+ }
38
+ function initial(options, value) {
39
+ return options.find((o) => o.value === value) ?? options[0];
40
+ }
41
+ function select(actionId, options, value) {
42
+ return {
43
+ type: "static_select",
44
+ action_id: actionId,
45
+ options,
46
+ initial_option: initial(options, value),
47
+ };
48
+ }
49
+ /**
50
+ * The setup message: a header and one row of controls (Slack wraps it on a narrow screen): a
51
+ * Model select (left out when the request lists no model), an Effort select (the chosen model's
52
+ * levels, or the request's `efforts` when it lists no model; left out when there are none), the
53
+ * Bypass checkbox (left out when the request does not offer it) and Start. `choice` is what each
54
+ * control shows; Start carries `setupId`, the one thing that resolves the question.
55
+ */
56
+ export function setupBlocks(setupId, draw, choice) {
57
+ const { models, efforts, bypassOffered } = draw;
58
+ const elements = [];
59
+ if (models.length > 0) {
60
+ elements.push(select(SETUP_MODEL, modelOptions(models), choice.model));
61
+ }
62
+ const levels = models.length > 0 ? effortLevels(models, choice.model) : efforts;
63
+ if (models.length > 0 || levels.length > 0) {
64
+ elements.push(select(SETUP_EFFORT, effortOptions(levels), choice.effort));
65
+ }
66
+ if (bypassOffered) {
67
+ const bypass = option(slackTexts.SETUP_BYPASS_OPTION, BYPASS_ON, {
68
+ type: "mrkdwn",
69
+ text: slackTexts.SETUP_BYPASS_DESCRIPTION,
70
+ });
71
+ const checkboxes = { type: "checkboxes", action_id: SETUP_BYPASS, options: [bypass] };
72
+ if (choice.bypass)
73
+ checkboxes.initial_options = [bypass];
74
+ elements.push(checkboxes);
75
+ }
76
+ elements.push({
77
+ type: "button",
78
+ action_id: SETUP_START,
79
+ value: setupId,
80
+ style: "primary",
81
+ text: { type: "plain_text", text: slackTexts.SETUP_START_BUTTON },
82
+ });
83
+ return [
84
+ { type: "section", text: { type: "mrkdwn", text: slackTexts.SETUP_HEADER } },
85
+ { type: "actions", block_id: SETUP_BLOCK, elements },
86
+ ];
87
+ }
88
+ function record(value) {
89
+ return typeof value === "object" && value !== null && !Array.isArray(value)
90
+ ? value
91
+ : {};
92
+ }
93
+ /**
94
+ * What the controls hold in a payload's `state.values`, as they stand: a control with no
95
+ * selection, or one the message does not draw, reads as `DEFAULT` (bypass off). The core checks
96
+ * it against what the request offered.
97
+ */
98
+ export function readSelection(values) {
99
+ const row = record(record(values)[SETUP_BLOCK]);
100
+ const selected = (action) => {
101
+ const value = record(record(row[action]).selected_option).value;
102
+ return typeof value === "string" && value !== "" ? value : DEFAULT;
103
+ };
104
+ const picked = record(row[SETUP_BYPASS]).selected_options;
105
+ const bypass = Array.isArray(picked) && picked.some((o) => record(o).value === BYPASS_ON);
106
+ return { model: selected(SETUP_MODEL), effort: selected(SETUP_EFFORT), bypass };
107
+ }
108
+ /**
109
+ * The owner's choice from a payload's `state.values`. A control with no selection, or one naming
110
+ * something the agent did not list, reads as its default (`choiceOf`).
111
+ */
112
+ export function readChoice(values, models) {
113
+ return choiceOf(models, readSelection(values));
114
+ }
@@ -0,0 +1,185 @@
1
+ /**
2
+ * The Slack provider's own words: what its controls, its forms, its Home tab and its file viewer
3
+ * say, and why it refuses a channel, a file or a clip. English only. The sentences every chat
4
+ * shows are the core's (`texts` of the port).
5
+ */
6
+ import { Named } from "../../core/port.js";
7
+ import { setupGuide } from "../../version.js";
8
+ function naming(template) {
9
+ return new Named(template);
10
+ }
11
+ export const CHANNEL_REFUSED = "allaboard does not work in this channel: {reason}. It answers only in a private " +
12
+ "channel whose only members are you and the bot.";
13
+ export const REASON_NOT_PRIVATE = "it is not a private channel";
14
+ export const REASON_SHARED = "it is shared with another workspace";
15
+ export const REASON_MEMBERS = "it has members other than you and the bot";
16
+ export const REASON_UNREADABLE = "the bot cannot read its details";
17
+ export const OPEN_FALLBACK = "Open a file";
18
+ export const OPEN_TITLE = "*Open a file*";
19
+ export const OPEN_BUTTON = "Choose a file";
20
+ export const OPEN_BY_NAME = "Or type `!open setup` to open a file by name.";
21
+ export const OPEN_NO_MATCH = "No file matches `{words}`.";
22
+ export const OPEN_NO_MATCH_PARTIAL = "No file matching `{words}` was found, but the folder could not be listed in full.";
23
+ export const OPEN_MATCHES = "*{count} files match* `{words}`";
24
+ export const OPEN_MATCHES_ONE = "*1 file matches* `{words}`";
25
+ export const OPEN_PARTIAL = "The folder could not be listed in full, so some files may be missing here.";
26
+ export const OPEN_MATCHES_CAPPED = "The first {shown} of {count} are listed: type more of the name to narrow it.";
27
+ export const OPEN_MATCHES_TOO_LONG = "None of their paths is short enough to list: open one with `!open <path>`.";
28
+ export const OPEN_MODAL_TITLE = "Open a file";
29
+ export const OPEN_MODAL_SUBMIT = "Open";
30
+ export const OPEN_MODAL_CLOSE = "Close";
31
+ export const OPEN_QUERY_LABEL = "Search any file";
32
+ export const OPEN_QUERY_HINT = "Type part of a name";
33
+ export const OPEN_ROWS_CHANGED = "Changed in this session ({count}), newest first";
34
+ export const OPEN_ROWS_MATCH = "{count} files match";
35
+ export const OPEN_ROWS_MATCH_ONE = "1 file matches";
36
+ export const OPEN_TYPE_A_NAME = "No changed files to show. Type part of a name to find a file.";
37
+ export const OPEN_LOADING = "Looking for files…";
38
+ export const OPEN_NONE_CHOSEN = "Choose a file first.";
39
+ export const OPEN_FORM_NOT_OPENED = "The file list could not open (`{error}`). Click Choose a file again.";
40
+ export const OPEN_NOT_A_FILE = "`{path}` is not a file inside this session's folder.";
41
+ export const OPEN_TOO_LARGE = naming("`{path}` is larger than 1 MB, the most {chat} opens in its file viewer.");
42
+ export const OPEN_EMPTY = "`{path}` is empty: there is nothing to show.";
43
+ /** The anchor of the setup guide's part on the Slack app, which `version.setupGuide` addresses. */
44
+ export const SETUP_SLACK_APP = "part-1-the-slack-app";
45
+ export const OPEN_NO_SCOPE = "Opening a file needs the `files:write` scope: add it to the app and reinstall it from " +
46
+ `\`slack-app-manifest.json\` (see ${setupGuide(SETUP_SLACK_APP)}).`;
47
+ export const OPEN_FAILED = "Could not open `{path}`: {error}";
48
+ export const UPLOAD_FAILED = naming("Nothing was sent to {author}: {name} {reason}. Send the message again without it.");
49
+ // An audio clip waits for the transcript only the owner can ask Slack for (`voice.ts`).
50
+ export const CLIP_WAITING = naming("Waiting for this clip's transcript: choose `Generate transcript` on the clip, and its text " +
51
+ "is sent to {author}.");
52
+ export const CLIP_NOT_SENT = naming("Nothing was sent to {author}: this clip had no transcript after {minutes} minutes. Send it " +
53
+ "again, or type the message.");
54
+ export const CLIP_NOT_YOURS = naming("Nothing was sent to {author}: this audio is a file somebody else uploaded.");
55
+ export const CLIP_EMPTY = naming("Nothing was sent to {author}: {chat}'s transcript of this clip is empty.");
56
+ export const CLIP_UNREADABLE = naming("Nothing was sent to {author}: the transcript of this clip could not be read ({reason}).");
57
+ export const UPLOAD_IMAGE_TYPE = naming("is an image of type `{mimetype}`, and {author} reads only JPEG, PNG, GIF and WebP images");
58
+ export const UPLOAD_IMAGE_SIZE = naming("is {size}, over the {limit} {author} accepts for an image");
59
+ export const UPLOAD_IMAGE_SIDE = naming("is {width}x{height} px, over the 8000x8000 px {author} accepts for an image");
60
+ export const UPLOAD_FILE_TYPE = "is a `{mimetype}` file, and allaboard passes on only text, source code, PDF, JSON, " +
61
+ "XML, YAML and notebook files";
62
+ export const UPLOAD_FILE_SIZE = "is {size}, over the {limit} limit for a file";
63
+ export const UPLOAD_TOO_MANY = naming("Nothing was sent to {author}: the message has {count} images, over the {limit} one message " +
64
+ "takes.");
65
+ export const UPLOAD_TOO_HEAVY = naming("Nothing was sent to {author}: its images total {size}, over the {limit} one message takes.");
66
+ export const UPLOAD_NOT_SHARED = "is not a file shared in this channel that allaboard can download";
67
+ export const UPLOAD_DOWNLOAD = "could not be downloaded ({error})";
68
+ export const BIND_MORE = "Only the first {rows} are shown: `!bind <folder>` binds any other.";
69
+ export const BIND_CURRENT = " · _current_";
70
+ export const BIND_BUTTON = "Bind";
71
+ // The words of the question itself, and close in length: Slack sizes a button by its text, so
72
+ // `Continue` beside `Cancel` made the second look the lesser choice (issue #76).
73
+ export const HOLD_CONTINUE_BUTTON = "Send anyway";
74
+ export const HOLD_CANCEL_BUTTON = "Don't send";
75
+ export const SETUP_HEADER = "*Choose how this session starts*";
76
+ export const SETUP_EFFORT_OPTION = "Effort: {level}";
77
+ export const SETUP_BYPASS_OPTION = "Bypass permissions";
78
+ export const SETUP_BYPASS_DESCRIPTION = "Run every tool without asking, until `!bypass off`.";
79
+ export const SETUP_START_BUTTON = "Start";
80
+ export const APPROVAL_PROMPT = naming("{agent} asks to use *{tool}*");
81
+ export const QUESTIONS_ONE = naming("{agent} has a question");
82
+ export const QUESTIONS_MANY = naming("{agent} has {count} questions");
83
+ export const QUESTION_ANSWER = "Answer";
84
+ export const QUESTION_TITLE = naming("{agent} asks");
85
+ export const QUESTION_CLOSE = "Close";
86
+ export const QUESTION_NOT_OPENED = "The form could not open (`{error}`). Click Answer again.";
87
+ export const QUESTION_MISSING = "Choose an option or type your own answer.";
88
+ export const QUESTION_STALE = "This form was opened again. Use the newest one.";
89
+ export const QUESTION_NEXT = "Next ({number}/{count})";
90
+ export const QUESTION_REVIEW = "Review";
91
+ export const QUESTION_SUBMIT_ALL = "Submit answers";
92
+ export const QUESTION_REVIEW_HEADING = "*Review your answers*";
93
+ // The tab row of a form of several questions, as the terminal draws it (Claude Code 2.1.301).
94
+ export const QUESTION_TAB_SUBMIT = "✔ Submit";
95
+ export const QUESTION_TAB_UNNAMED = "Question {number}";
96
+ export const QUESTION_OTHER = "Other";
97
+ export const QUESTION_YOUR_ANSWER = "Your answer";
98
+ export const QUESTION_EDIT = "Edit";
99
+ export const QUESTION_OTHER_HINT = "Type your own answer";
100
+ export const QUESTION_NOTES = "Notes";
101
+ export const QUESTION_NOTES_HINT = "Add notes to your answer";
102
+ export const APPROVAL_CUT = "_{count} characters of this request are not shown: Deny it unless you know them._";
103
+ export const RESUME_EMPTY = "No sessions in `{directory}` yet.";
104
+ // Every session of the folder is already open in a thread: the list has no row to offer.
105
+ export const RESUME_NONE_LEFT = "No session to resume in `{directory}`.";
106
+ // Under the list: the sessions a thread already holds, counted and not listed (issue #69).
107
+ export const RESUME_OPEN_ONE = "1 more is open in its own thread.";
108
+ export const RESUME_OPEN_MANY = "{count} more are open in their own threads.";
109
+ export const RESUME_BUTTON = "Resume";
110
+ export const RESUME_MORE = "Only the newest {rows} are shown: `!resume <id>`, or `!resume <title>` for a session that " +
111
+ "has one, resumes an older session.";
112
+ // The app's Home tab: the sessions the threads hold, by channel, newest activity first, under
113
+ // four filters. `{time}` is Slack's own date token, shown in the reader's time zone.
114
+ export const HOME_HEADER = "Sessions by channel, newest first · updated {time}";
115
+ export const HOME_EMPTY = "No channel is bound yet: `!bind` in a private channel binds it to a folder.";
116
+ export const HOME_NO_SESSIONS = "No sessions yet.";
117
+ export const HOME_NO_MATCH = "No session matches these filters.";
118
+ export const HOME_MORE = "Only the first {rows} sessions are shown: narrow the filters.";
119
+ export const HOME_OPEN = "Open";
120
+ export const HOME_NEW_THREAD = "New thread";
121
+ export const HOME_EDIT = "Edit";
122
+ export const HOME_DONE = "Done";
123
+ export const HOME_DELETE = "Delete";
124
+ // Bold, behind the hourglass the other two notes carry: mrkdwn has no colour, and the plain
125
+ // word was missed among the row's details.
126
+ export const HOME_DELETING = ":hourglass_flowing_sand: *deleting…*";
127
+ export const HOME_CHANNEL_DELETING_ONE = ":hourglass_flowing_sand: deleting 1 thread…";
128
+ export const HOME_CHANNEL_DELETING_MANY = ":hourglass_flowing_sand: deleting {count} threads…";
129
+ export const HOME_DELETING_ONE = naming(":hourglass_flowing_sand: *Deleting 1 thread.* {chat} limits how fast messages are deleted: " +
130
+ "a thread takes about a minute, and leaves this page when it is gone.");
131
+ export const HOME_DELETING_MANY = naming(":hourglass_flowing_sand: *Deleting {count} threads, one at a time.* {chat} limits how fast " +
132
+ "messages are deleted: each takes about a minute, and leaves this page when it is gone.");
133
+ export const HOME_DELETE_TITLE = "Delete this thread?";
134
+ export const HOME_DELETE_NAMED = "“{title}” in #{channel}, {replies}. ";
135
+ export const HOME_DELETE_NAMED_BARE = "“{title}” in #{channel}. ";
136
+ export const HOME_DELETE_TEXT = naming("Every message of the thread is deleted from {chat}, yours and the bot's. This cannot be " +
137
+ "undone. The session stays in {agent} and can be resumed with !resume. Deleting takes " +
138
+ "about a minute.");
139
+ export const HOME_DELETE_CONFIRM = "Delete thread";
140
+ export const HOME_DELETE_DENY = "Cancel";
141
+ export const HOME_CLEAN = "Clean up";
142
+ export const HOME_CLEAN_TITLE = "Clean up this channel?";
143
+ export const HOME_CLEAN_TEXT = "Deletes from #{channel} what sits outside a thread: your messages there that have no " +
144
+ "reply, commands like !stop included, and the bot's own messages. Every thread that has a " +
145
+ "reply stays. This cannot be undone and can take a few minutes.";
146
+ export const HOME_CLEAN_CONFIRM = "Clean up";
147
+ export const HOME_CHANNEL_CLEANING = ":hourglass_flowing_sand: cleaning up…";
148
+ export const HOME_CLEANING_ONE = naming(":hourglass_flowing_sand: *Cleaning up 1 channel.* {chat} limits how fast messages are " +
149
+ "deleted: this can take a few minutes.");
150
+ export const HOME_CLEANING_MANY = naming(":hourglass_flowing_sand: *Cleaning up {count} channels, one at a time.* {chat} limits how " +
151
+ "fast messages are deleted: this can take a few minutes.");
152
+ export const HOME_CLEAN_FAILED = "Could not clean up that channel (`{error}`). Clean it up again to continue.";
153
+ export const HOME_DELETE_REFUSED = naming("{chat} refused to delete {count} of that thread's messages (`cant_delete_message`): they " +
154
+ "are not yours or the bot's, or your workspace does not let you delete them. Every other " +
155
+ "message is gone and the thread is still listed.");
156
+ export const HOME_CLEAN_REFUSED = naming("{chat} refused to delete {count} of that channel's messages (`cant_delete_message`): your " +
157
+ "workspace does not let you delete them. Every other one is gone.");
158
+ export const HOME_DELETE_BUSY = "Not deleted: that thread is working or waiting for you.";
159
+ export const HOME_DELETE_FAILED = "Could not delete every message of that thread (`{error}`). Delete it again to continue.";
160
+ export const HOME_SHOW_ALL = "Show all {count}";
161
+ export const HOME_UNTITLED = "Session {id}";
162
+ export const HOME_REPLY = "1 reply";
163
+ export const HOME_REPLIES = "{count} replies";
164
+ export const HOME_LAST_REPLY = "last reply {when}";
165
+ export const HOME_STARTED = "started {when}";
166
+ export const HOME_WORKING = "working";
167
+ export const HOME_WAITING = "waiting for you";
168
+ export const HOME_ENDED = "ended";
169
+ export const HOME_ERROR = "error";
170
+ export const HOME_ALL_CHANNELS = "All channels";
171
+ export const HOME_ALL_STATUSES = "All statuses";
172
+ export const HOME_ANY_TIME = "Any time";
173
+ export const HOME_LAST_48 = "Last 48 hours";
174
+ export const HOME_TODAY = "Today";
175
+ export const HOME_YESTERDAY = "Yesterday";
176
+ export const HOME_LAST_7 = "Last 7 days";
177
+ export const HOME_LAST_30 = "Last 30 days";
178
+ export const HOME_SEARCH_LABEL = "Search titles";
179
+ export const HOME_SEARCH_HINT = "Type a word and press Enter";
180
+ // Opens the first line of a record, which stands for a call that ended well (an answered
181
+ // question).
182
+ export const RECORD_MARK = "✓ ";
183
+ // The update of allaboard (issue #361): the words are the core's (`UPDATE_*` of its texts); this
184
+ // is the link to what the version changed, in mrkdwn.
185
+ export const UPDATE_LINKED = "{line} · <{url}|{label}>";