crewly 1.20.157 → 1.20.159

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 (154) hide show
  1. package/README.md +1 -0
  2. package/config/skills/registry.json +55 -4
  3. package/config/templates/growth-marketing-team.json +5 -5
  4. package/config/templates/personal-assistant-team.json +5 -5
  5. package/dist/backend/backend/src/constants.d.ts +120 -5
  6. package/dist/backend/backend/src/constants.d.ts.map +1 -1
  7. package/dist/backend/backend/src/constants.js +145 -8
  8. package/dist/backend/backend/src/constants.js.map +1 -1
  9. package/dist/backend/backend/src/controllers/worktree-janitor/worktree-janitor.routes.d.ts +2 -1
  10. package/dist/backend/backend/src/controllers/worktree-janitor/worktree-janitor.routes.d.ts.map +1 -1
  11. package/dist/backend/backend/src/controllers/worktree-janitor/worktree-janitor.routes.js +4 -2
  12. package/dist/backend/backend/src/controllers/worktree-janitor/worktree-janitor.routes.js.map +1 -1
  13. package/dist/backend/backend/src/index.d.ts.map +1 -1
  14. package/dist/backend/backend/src/index.js +17 -2
  15. package/dist/backend/backend/src/index.js.map +1 -1
  16. package/dist/backend/backend/src/services/agent/agent-registration.service.d.ts +9 -0
  17. package/dist/backend/backend/src/services/agent/agent-registration.service.d.ts.map +1 -1
  18. package/dist/backend/backend/src/services/agent/agent-registration.service.js +34 -29
  19. package/dist/backend/backend/src/services/agent/agent-registration.service.js.map +1 -1
  20. package/dist/backend/backend/src/services/agent/codex-runtime.service.d.ts +4 -3
  21. package/dist/backend/backend/src/services/agent/codex-runtime.service.d.ts.map +1 -1
  22. package/dist/backend/backend/src/services/agent/codex-runtime.service.js +48 -3
  23. package/dist/backend/backend/src/services/agent/codex-runtime.service.js.map +1 -1
  24. package/dist/backend/backend/src/services/agent/crewly-agent/in-process-log-buffer.d.ts +9 -0
  25. package/dist/backend/backend/src/services/agent/crewly-agent/in-process-log-buffer.d.ts.map +1 -1
  26. package/dist/backend/backend/src/services/agent/crewly-agent/in-process-log-buffer.js +18 -1
  27. package/dist/backend/backend/src/services/agent/crewly-agent/in-process-log-buffer.js.map +1 -1
  28. package/dist/backend/backend/src/services/agent/oauth-relogin-monitor.service.d.ts.map +1 -1
  29. package/dist/backend/backend/src/services/agent/oauth-relogin-monitor.service.js +5 -1
  30. package/dist/backend/backend/src/services/agent/oauth-relogin-monitor.service.js.map +1 -1
  31. package/dist/backend/backend/src/services/agent/runtime-agent.service.abstract.d.ts.map +1 -1
  32. package/dist/backend/backend/src/services/agent/runtime-agent.service.abstract.js +14 -4
  33. package/dist/backend/backend/src/services/agent/runtime-agent.service.abstract.js.map +1 -1
  34. package/dist/backend/backend/src/services/agent/tmux-command.service.d.ts +14 -2
  35. package/dist/backend/backend/src/services/agent/tmux-command.service.d.ts.map +1 -1
  36. package/dist/backend/backend/src/services/agent/tmux-command.service.js +24 -5
  37. package/dist/backend/backend/src/services/agent/tmux-command.service.js.map +1 -1
  38. package/dist/backend/backend/src/services/agent/tmux.service.d.ts.map +1 -1
  39. package/dist/backend/backend/src/services/agent/tmux.service.js +14 -9
  40. package/dist/backend/backend/src/services/agent/tmux.service.js.map +1 -1
  41. package/dist/backend/backend/src/services/backup/backup-archive.service.d.ts +8 -0
  42. package/dist/backend/backend/src/services/backup/backup-archive.service.d.ts.map +1 -1
  43. package/dist/backend/backend/src/services/backup/backup-archive.service.js +16 -8
  44. package/dist/backend/backend/src/services/backup/backup-archive.service.js.map +1 -1
  45. package/dist/backend/backend/src/services/backup/backup.types.d.ts +19 -0
  46. package/dist/backend/backend/src/services/backup/backup.types.d.ts.map +1 -1
  47. package/dist/backend/backend/src/services/backup/backup.types.js +19 -0
  48. package/dist/backend/backend/src/services/backup/backup.types.js.map +1 -1
  49. package/dist/backend/backend/src/services/security/secret-scrub.service.d.ts +108 -0
  50. package/dist/backend/backend/src/services/security/secret-scrub.service.d.ts.map +1 -0
  51. package/dist/backend/backend/src/services/security/secret-scrub.service.js +200 -0
  52. package/dist/backend/backend/src/services/security/secret-scrub.service.js.map +1 -0
  53. package/dist/backend/backend/src/services/session/pty/pty-session-backend.d.ts +13 -9
  54. package/dist/backend/backend/src/services/session/pty/pty-session-backend.d.ts.map +1 -1
  55. package/dist/backend/backend/src/services/session/pty/pty-session-backend.js +30 -14
  56. package/dist/backend/backend/src/services/session/pty/pty-session-backend.js.map +1 -1
  57. package/dist/backend/backend/src/services/session/pty/pty-session.d.ts.map +1 -1
  58. package/dist/backend/backend/src/services/session/pty/pty-session.js +6 -1
  59. package/dist/backend/backend/src/services/session/pty/pty-session.js.map +1 -1
  60. package/dist/backend/backend/src/services/session/session-command-helper.d.ts.map +1 -1
  61. package/dist/backend/backend/src/services/session/session-command-helper.js +3 -2
  62. package/dist/backend/backend/src/services/session/session-command-helper.js.map +1 -1
  63. package/dist/backend/backend/src/services/worktree/low-disk-guard.d.ts +109 -0
  64. package/dist/backend/backend/src/services/worktree/low-disk-guard.d.ts.map +1 -0
  65. package/dist/backend/backend/src/services/worktree/low-disk-guard.js +148 -0
  66. package/dist/backend/backend/src/services/worktree/low-disk-guard.js.map +1 -0
  67. package/dist/backend/backend/src/services/worktree/scratch-janitor.d.ts +140 -0
  68. package/dist/backend/backend/src/services/worktree/scratch-janitor.d.ts.map +1 -0
  69. package/dist/backend/backend/src/services/worktree/scratch-janitor.js +249 -0
  70. package/dist/backend/backend/src/services/worktree/scratch-janitor.js.map +1 -0
  71. package/dist/backend/backend/src/services/worktree/worktree-janitor.git.d.ts +92 -1
  72. package/dist/backend/backend/src/services/worktree/worktree-janitor.git.d.ts.map +1 -1
  73. package/dist/backend/backend/src/services/worktree/worktree-janitor.git.js +207 -1
  74. package/dist/backend/backend/src/services/worktree/worktree-janitor.git.js.map +1 -1
  75. package/dist/backend/backend/src/services/worktree/worktree-janitor.service.d.ts +135 -28
  76. package/dist/backend/backend/src/services/worktree/worktree-janitor.service.d.ts.map +1 -1
  77. package/dist/backend/backend/src/services/worktree/worktree-janitor.service.js +248 -39
  78. package/dist/backend/backend/src/services/worktree/worktree-janitor.service.js.map +1 -1
  79. package/dist/backend/backend/src/types/team-template.types.d.ts +1 -1
  80. package/dist/backend/backend/src/types/team-template.types.d.ts.map +1 -1
  81. package/dist/backend/backend/src/utils/secret-redactor.d.ts +131 -0
  82. package/dist/backend/backend/src/utils/secret-redactor.d.ts.map +1 -0
  83. package/dist/backend/backend/src/utils/secret-redactor.js +233 -0
  84. package/dist/backend/backend/src/utils/secret-redactor.js.map +1 -0
  85. package/dist/backend/backend/src/utils/shell-history.d.ts +48 -0
  86. package/dist/backend/backend/src/utils/shell-history.d.ts.map +1 -0
  87. package/dist/backend/backend/src/utils/shell-history.js +65 -0
  88. package/dist/backend/backend/src/utils/shell-history.js.map +1 -0
  89. package/dist/backend/build-info.json +2 -2
  90. package/dist/cli/backend/src/constants.d.ts +120 -5
  91. package/dist/cli/backend/src/constants.d.ts.map +1 -1
  92. package/dist/cli/backend/src/constants.js +145 -8
  93. package/dist/cli/backend/src/constants.js.map +1 -1
  94. package/dist/cli/backend/src/services/agent/agent-registration.service.d.ts +9 -0
  95. package/dist/cli/backend/src/services/agent/agent-registration.service.d.ts.map +1 -1
  96. package/dist/cli/backend/src/services/agent/agent-registration.service.js +34 -29
  97. package/dist/cli/backend/src/services/agent/agent-registration.service.js.map +1 -1
  98. package/dist/cli/backend/src/services/agent/codex-runtime.service.d.ts +4 -3
  99. package/dist/cli/backend/src/services/agent/codex-runtime.service.d.ts.map +1 -1
  100. package/dist/cli/backend/src/services/agent/codex-runtime.service.js +48 -3
  101. package/dist/cli/backend/src/services/agent/codex-runtime.service.js.map +1 -1
  102. package/dist/cli/backend/src/services/agent/crewly-agent/in-process-log-buffer.d.ts +9 -0
  103. package/dist/cli/backend/src/services/agent/crewly-agent/in-process-log-buffer.d.ts.map +1 -1
  104. package/dist/cli/backend/src/services/agent/crewly-agent/in-process-log-buffer.js +18 -1
  105. package/dist/cli/backend/src/services/agent/crewly-agent/in-process-log-buffer.js.map +1 -1
  106. package/dist/cli/backend/src/services/agent/oauth-relogin-monitor.service.d.ts.map +1 -1
  107. package/dist/cli/backend/src/services/agent/oauth-relogin-monitor.service.js +5 -1
  108. package/dist/cli/backend/src/services/agent/oauth-relogin-monitor.service.js.map +1 -1
  109. package/dist/cli/backend/src/services/agent/runtime-agent.service.abstract.d.ts.map +1 -1
  110. package/dist/cli/backend/src/services/agent/runtime-agent.service.abstract.js +14 -4
  111. package/dist/cli/backend/src/services/agent/runtime-agent.service.abstract.js.map +1 -1
  112. package/dist/cli/backend/src/services/backup/backup-archive.service.d.ts +8 -0
  113. package/dist/cli/backend/src/services/backup/backup-archive.service.d.ts.map +1 -1
  114. package/dist/cli/backend/src/services/backup/backup-archive.service.js +16 -8
  115. package/dist/cli/backend/src/services/backup/backup-archive.service.js.map +1 -1
  116. package/dist/cli/backend/src/services/backup/backup.types.d.ts +19 -0
  117. package/dist/cli/backend/src/services/backup/backup.types.d.ts.map +1 -1
  118. package/dist/cli/backend/src/services/backup/backup.types.js +19 -0
  119. package/dist/cli/backend/src/services/backup/backup.types.js.map +1 -1
  120. package/dist/cli/backend/src/services/security/secret-scrub.service.d.ts +108 -0
  121. package/dist/cli/backend/src/services/security/secret-scrub.service.d.ts.map +1 -0
  122. package/dist/cli/backend/src/services/security/secret-scrub.service.js +200 -0
  123. package/dist/cli/backend/src/services/security/secret-scrub.service.js.map +1 -0
  124. package/dist/cli/backend/src/services/session/pty/pty-session-backend.d.ts +13 -9
  125. package/dist/cli/backend/src/services/session/pty/pty-session-backend.d.ts.map +1 -1
  126. package/dist/cli/backend/src/services/session/pty/pty-session-backend.js +30 -14
  127. package/dist/cli/backend/src/services/session/pty/pty-session-backend.js.map +1 -1
  128. package/dist/cli/backend/src/services/session/pty/pty-session.d.ts.map +1 -1
  129. package/dist/cli/backend/src/services/session/pty/pty-session.js +6 -1
  130. package/dist/cli/backend/src/services/session/pty/pty-session.js.map +1 -1
  131. package/dist/cli/backend/src/services/session/session-command-helper.d.ts.map +1 -1
  132. package/dist/cli/backend/src/services/session/session-command-helper.js +3 -2
  133. package/dist/cli/backend/src/services/session/session-command-helper.js.map +1 -1
  134. package/dist/cli/backend/src/types/team-template.types.d.ts +1 -1
  135. package/dist/cli/backend/src/types/team-template.types.d.ts.map +1 -1
  136. package/dist/cli/backend/src/utils/secret-redactor.d.ts +131 -0
  137. package/dist/cli/backend/src/utils/secret-redactor.d.ts.map +1 -0
  138. package/dist/cli/backend/src/utils/secret-redactor.js +233 -0
  139. package/dist/cli/backend/src/utils/secret-redactor.js.map +1 -0
  140. package/dist/cli/backend/src/utils/shell-history.d.ts +48 -0
  141. package/dist/cli/backend/src/utils/shell-history.d.ts.map +1 -0
  142. package/dist/cli/backend/src/utils/shell-history.js +65 -0
  143. package/dist/cli/backend/src/utils/shell-history.js.map +1 -0
  144. package/dist/cli/cli/src/commands/security.d.ts +41 -0
  145. package/dist/cli/cli/src/commands/security.d.ts.map +1 -0
  146. package/dist/cli/cli/src/commands/security.js +81 -0
  147. package/dist/cli/cli/src/commands/security.js.map +1 -0
  148. package/dist/cli/cli/src/index.js +9 -0
  149. package/dist/cli/cli/src/index.js.map +1 -1
  150. package/frontend/dist/assets/index-edd6f262.css +42 -0
  151. package/frontend/dist/assets/{index-8d4b3ad2.js → index-f79676aa.js} +268 -268
  152. package/frontend/dist/index.html +2 -2
  153. package/package.json +1 -1
  154. package/frontend/dist/assets/index-b6aded4c.css +0 -42
@@ -0,0 +1,131 @@
1
+ /**
2
+ * Secret redactor for persisted terminal output.
3
+ *
4
+ * Every byte of agent PTY output that Crewly writes to disk
5
+ * (~/.crewly/logs/sessions/*.log) passes through here, and so do the
6
+ * one-time scrub of existing logs and shell history files
7
+ * (`crewly security scrub-logs`). Three layers, applied in this order:
8
+ *
9
+ * 1. **Known values** — the exact value of every secret Crewly holds for the
10
+ * session (backend env + the session's spawn env, see
11
+ * collectSecretEnvValues). Catches secrets no pattern recognises (a Slack
12
+ * signing secret is plain hex). Masked as `[REDACTED <NAME>]`.
13
+ * 2. **Secret-named assignments** — `NAME=value` where NAME is a secret name
14
+ * (isSecretEnvKey: `…API_KEY`, `…TOKEN`, `…SECRET`, `…PASSWORD`,
15
+ * `…PRIVATE_KEY`, … plus SECRET_REDACTION_CONSTANTS.KNOWN_SECRET_ENV_NAMES).
16
+ * The value is masked whatever it looks like. Names are matched on the
17
+ * whole identifier, so `TOKEN_COUNT=5` or `MAX_TOKENS=4096` are left alone
18
+ * (the name does not END in a secret suffix); `$VAR` references and
19
+ * already-masked values are left alone too.
20
+ * 3. **Token shapes** — well-known raw credential formats wherever they
21
+ * appear: Slack `xoxb-/xoxp-/xoxa-/xoxe-/xapp-…`, OpenAI/DeepSeek `sk-…`,
22
+ * Anthropic `sk-ant-…`, Google `AIza…`, GitHub `ghp_/gho_/…` and
23
+ * `github_pat_…`, plus the wiki confidentiality patterns (JWTs, bearer
24
+ * headers, private-key blocks, connection strings).
25
+ *
26
+ * Redaction is idempotent: running it over already-redacted text changes
27
+ * nothing and reports a count of 0.
28
+ *
29
+ * Output is streamed in arbitrary chunks, so a secret can be split between
30
+ * two chunks. StreamingSecretRedactor holds back the tail after the last
31
+ * whitespace (secrets never contain whitespace) and prepends it to the next
32
+ * chunk, which keeps the whole thing linear with a small bounded carry.
33
+ *
34
+ * @module utils/secret-redactor
35
+ */
36
+ import { type SecretEnvValue } from './secret-env.js';
37
+ /**
38
+ * Whether an assignment's name marks its value as secret.
39
+ *
40
+ * @param name - Variable name as it appeared (any case)
41
+ * @returns True for secret names (see module docs)
42
+ */
43
+ export declare function isSecretAssignmentName(name: string): boolean;
44
+ /** Result of a counted redaction. */
45
+ export interface RedactionResult {
46
+ /** The masked text */
47
+ text: string;
48
+ /** Number of secrets masked (0 when the text was already clean) */
49
+ count: number;
50
+ }
51
+ /**
52
+ * Masks every secret in a complete piece of text and counts what was masked.
53
+ *
54
+ * @param text - Text to mask
55
+ * @param knownSecrets - Exact secret values to mask (see collectSecretEnvValues)
56
+ * @returns Masked text and the number of secrets masked
57
+ *
58
+ * @example
59
+ * ```ts
60
+ * redactSecretsWithCount('export OPENAI_API_KEY=sk-abc…').count; // 1
61
+ * redactSecretsWithCount('TOKEN_COUNT=5').count; // 0
62
+ * ```
63
+ */
64
+ export declare function redactSecretsWithCount(text: string, knownSecrets?: readonly SecretEnvValue[]): RedactionResult;
65
+ /**
66
+ * Masks every secret in a complete piece of text.
67
+ *
68
+ * @param text - Text to mask
69
+ * @param knownSecrets - Exact secret values to mask
70
+ * @returns The masked text
71
+ */
72
+ export declare function redactSecrets(text: string, knownSecrets?: readonly SecretEnvValue[]): string;
73
+ /**
74
+ * Chunk-boundary-safe redactor for streamed output.
75
+ *
76
+ * `push()` returns the redacted text that is safe to emit now and holds back
77
+ * the tail after the last whitespace; `flush()` returns whatever is held back
78
+ * (call it when the stream closes). Only the new chunk is scanned for
79
+ * whitespace (the carry never contains any), so work is linear in the input.
80
+ * A run of more than MAX_CARRY_CHARS non-whitespace characters is cut at a
81
+ * non-token character near its end (or hard-cut when there is none), keeping
82
+ * the carry bounded.
83
+ *
84
+ * @example
85
+ * ```ts
86
+ * const r = new StreamingSecretRedactor();
87
+ * log.write(r.push('export GEMINI_API_KEY=AIzaSy'));
88
+ * log.write(r.push('…rest of key\n'));
89
+ * log.write(r.flush());
90
+ * ```
91
+ */
92
+ export declare class StreamingSecretRedactor {
93
+ private carry;
94
+ private knownSecrets;
95
+ private readonly maxCarry;
96
+ private readonly minCarry;
97
+ /**
98
+ * @param knownSecrets - Exact secret values to mask
99
+ * @param maxCarry - Longest whitespace-free tail held back before forcing a cut
100
+ */
101
+ constructor(knownSecrets?: readonly SecretEnvValue[], maxCarry?: number);
102
+ /**
103
+ * Replaces the exact-value secret set (e.g. after the session's env changes).
104
+ *
105
+ * @param knownSecrets - Exact secret values to mask
106
+ */
107
+ setKnownSecrets(knownSecrets: readonly SecretEnvValue[]): void;
108
+ /**
109
+ * Feeds one chunk and returns the redacted text that can be emitted now.
110
+ *
111
+ * @param chunk - Next piece of output
112
+ * @returns Redacted text (possibly empty while a word is still arriving)
113
+ */
114
+ push(chunk: string): string;
115
+ /**
116
+ * Returns (redacted) whatever is still held back and resets the carry.
117
+ *
118
+ * @returns Remaining redacted text
119
+ */
120
+ flush(): string;
121
+ /**
122
+ * Picks a cut point for a whitespace-free run that outgrew the carry: the
123
+ * last non-token character before the final minCarry characters, so a
124
+ * credential near the end is not split; hard cut when there is none.
125
+ *
126
+ * @param buf - Buffered text whose tail has no whitespace
127
+ * @returns Index to cut at
128
+ */
129
+ private forcedCut;
130
+ }
131
+ //# sourceMappingURL=secret-redactor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"secret-redactor.d.ts","sourceRoot":"","sources":["../../../../../backend/src/utils/secret-redactor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAIH,OAAO,EAAkB,KAAK,cAAc,EAAE,MAAM,iBAAiB,CAAC;AA6CtE;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAO5D;AAED,qCAAqC;AACrC,MAAM,WAAW,eAAe;IAC/B,sBAAsB;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,mEAAmE;IACnE,KAAK,EAAE,MAAM,CAAC;CACd;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,EAAE,YAAY,GAAE,SAAS,cAAc,EAAO,GAAG,eAAe,CA2BlH;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,YAAY,GAAE,SAAS,cAAc,EAAO,GAAG,MAAM,CAEhG;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,qBAAa,uBAAuB;IACnC,OAAO,CAAC,KAAK,CAAM;IACnB,OAAO,CAAC,YAAY,CAA4B;IAChD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAElC;;;OAGG;gBAEF,YAAY,GAAE,SAAS,cAAc,EAAO,EAC5C,QAAQ,GAAE,MAAmD;IAO9D;;;;OAIG;IACH,eAAe,CAAC,YAAY,EAAE,SAAS,cAAc,EAAE,GAAG,IAAI;IAI9D;;;;;OAKG;IACH,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM;IAyB3B;;;;OAIG;IACH,KAAK,IAAI,MAAM;IAMf;;;;;;;OAOG;IACH,OAAO,CAAC,SAAS;CAQjB"}
@@ -0,0 +1,233 @@
1
+ /**
2
+ * Secret redactor for persisted terminal output.
3
+ *
4
+ * Every byte of agent PTY output that Crewly writes to disk
5
+ * (~/.crewly/logs/sessions/*.log) passes through here, and so do the
6
+ * one-time scrub of existing logs and shell history files
7
+ * (`crewly security scrub-logs`). Three layers, applied in this order:
8
+ *
9
+ * 1. **Known values** — the exact value of every secret Crewly holds for the
10
+ * session (backend env + the session's spawn env, see
11
+ * collectSecretEnvValues). Catches secrets no pattern recognises (a Slack
12
+ * signing secret is plain hex). Masked as `[REDACTED <NAME>]`.
13
+ * 2. **Secret-named assignments** — `NAME=value` where NAME is a secret name
14
+ * (isSecretEnvKey: `…API_KEY`, `…TOKEN`, `…SECRET`, `…PASSWORD`,
15
+ * `…PRIVATE_KEY`, … plus SECRET_REDACTION_CONSTANTS.KNOWN_SECRET_ENV_NAMES).
16
+ * The value is masked whatever it looks like. Names are matched on the
17
+ * whole identifier, so `TOKEN_COUNT=5` or `MAX_TOKENS=4096` are left alone
18
+ * (the name does not END in a secret suffix); `$VAR` references and
19
+ * already-masked values are left alone too.
20
+ * 3. **Token shapes** — well-known raw credential formats wherever they
21
+ * appear: Slack `xoxb-/xoxp-/xoxa-/xoxe-/xapp-…`, OpenAI/DeepSeek `sk-…`,
22
+ * Anthropic `sk-ant-…`, Google `AIza…`, GitHub `ghp_/gho_/…` and
23
+ * `github_pat_…`, plus the wiki confidentiality patterns (JWTs, bearer
24
+ * headers, private-key blocks, connection strings).
25
+ *
26
+ * Redaction is idempotent: running it over already-redacted text changes
27
+ * nothing and reports a count of 0.
28
+ *
29
+ * Output is streamed in arbitrary chunks, so a secret can be split between
30
+ * two chunks. StreamingSecretRedactor holds back the tail after the last
31
+ * whitespace (secrets never contain whitespace) and prepends it to the next
32
+ * chunk, which keeps the whole thing linear with a small bounded carry.
33
+ *
34
+ * @module utils/secret-redactor
35
+ */
36
+ import { SECRET_REDACTION_CONSTANTS } from '../constants.js';
37
+ import { SECRET_PATTERNS } from '../services/wiki/wiki-redaction.js';
38
+ import { isSecretEnvKey } from './secret-env.js';
39
+ /**
40
+ * Raw token shapes beyond the wiki SECRET_PATTERNS (which require a numeric
41
+ * team segment for Slack tokens and do not know `xapp-` or `github_pat_`).
42
+ */
43
+ const EXTRA_TOKEN_PATTERNS = [
44
+ { name: 'slack_token', regex: /\bxox[abposre]-[A-Za-z0-9-]{10,}/g, mask: '[REDACTED slack_token]' },
45
+ { name: 'slack_app_token', regex: /\bxapp-[A-Za-z0-9-]{10,}/g, mask: '[REDACTED slack_app_token]' },
46
+ { name: 'anthropic_key', regex: /\bsk-ant-[A-Za-z0-9_-]{20,}/g, mask: '[REDACTED api_key]' },
47
+ { name: 'github_pat', regex: /\bgithub_pat_[A-Za-z0-9_]{20,}/g, mask: '[REDACTED github_token]' },
48
+ ];
49
+ /** Every token-shape detector, most specific first. */
50
+ const TOKEN_PATTERNS = [...EXTRA_TOKEN_PATTERNS, ...SECRET_PATTERNS];
51
+ /**
52
+ * `NAME=value` (optionally quoted value). The value stops at whitespace,
53
+ * quotes, backticks and shell separators. The identifier must start at a
54
+ * word boundary so it is matched whole.
55
+ */
56
+ const ASSIGNMENT_RE = /\b([A-Za-z_][A-Za-z0-9_]*)=(["']?)([^\s"'`;&|]+)/g;
57
+ /** Prefix every mask this module emits starts with. */
58
+ const REDACTED_PREFIX = '[REDACTED';
59
+ /** Mask written in place of a secret-named assignment's value. */
60
+ const ASSIGNMENT_MASK = '[REDACTED]';
61
+ /** Characters that can appear inside a credential or a `NAME=value` pair. */
62
+ const TOKEN_CHAR_RE = /[A-Za-z0-9_\-=.:/+"'[\]]/;
63
+ /** Known secret names, upper-cased, for O(1) lookup. */
64
+ const KNOWN_SECRET_NAMES = new Set(SECRET_REDACTION_CONSTANTS.KNOWN_SECRET_ENV_NAMES);
65
+ /**
66
+ * Whether an assignment's name marks its value as secret.
67
+ *
68
+ * @param name - Variable name as it appeared (any case)
69
+ * @returns True for secret names (see module docs)
70
+ */
71
+ export function isSecretAssignmentName(name) {
72
+ const upper = name.toUpperCase();
73
+ return (KNOWN_SECRET_NAMES.has(upper) ||
74
+ isSecretEnvKey(name) ||
75
+ SECRET_REDACTION_CONSTANTS.SECRET_NAME_PATTERNS.some((re) => re.test(upper)));
76
+ }
77
+ /**
78
+ * Masks every secret in a complete piece of text and counts what was masked.
79
+ *
80
+ * @param text - Text to mask
81
+ * @param knownSecrets - Exact secret values to mask (see collectSecretEnvValues)
82
+ * @returns Masked text and the number of secrets masked
83
+ *
84
+ * @example
85
+ * ```ts
86
+ * redactSecretsWithCount('export OPENAI_API_KEY=sk-abc…').count; // 1
87
+ * redactSecretsWithCount('TOKEN_COUNT=5').count; // 0
88
+ * ```
89
+ */
90
+ export function redactSecretsWithCount(text, knownSecrets = []) {
91
+ let out = text;
92
+ let count = 0;
93
+ for (const { name, value } of knownSecrets) {
94
+ if (!value || !out.includes(value))
95
+ continue;
96
+ const parts = out.split(value);
97
+ count += parts.length - 1;
98
+ out = parts.join(`${REDACTED_PREFIX} ${name}]`);
99
+ }
100
+ out = out.replace(ASSIGNMENT_RE, (match, name, quote, value) => {
101
+ if (!isSecretAssignmentName(name) || value.startsWith('$') || value.startsWith(REDACTED_PREFIX))
102
+ return match;
103
+ count++;
104
+ return `${name}=${quote}${ASSIGNMENT_MASK}`;
105
+ });
106
+ for (const p of TOKEN_PATTERNS) {
107
+ p.regex.lastIndex = 0;
108
+ out = out.replace(p.regex, (match) => {
109
+ if (match === p.mask)
110
+ return match;
111
+ count++;
112
+ return p.mask;
113
+ });
114
+ }
115
+ return { text: out, count };
116
+ }
117
+ /**
118
+ * Masks every secret in a complete piece of text.
119
+ *
120
+ * @param text - Text to mask
121
+ * @param knownSecrets - Exact secret values to mask
122
+ * @returns The masked text
123
+ */
124
+ export function redactSecrets(text, knownSecrets = []) {
125
+ return redactSecretsWithCount(text, knownSecrets).text;
126
+ }
127
+ /**
128
+ * Chunk-boundary-safe redactor for streamed output.
129
+ *
130
+ * `push()` returns the redacted text that is safe to emit now and holds back
131
+ * the tail after the last whitespace; `flush()` returns whatever is held back
132
+ * (call it when the stream closes). Only the new chunk is scanned for
133
+ * whitespace (the carry never contains any), so work is linear in the input.
134
+ * A run of more than MAX_CARRY_CHARS non-whitespace characters is cut at a
135
+ * non-token character near its end (or hard-cut when there is none), keeping
136
+ * the carry bounded.
137
+ *
138
+ * @example
139
+ * ```ts
140
+ * const r = new StreamingSecretRedactor();
141
+ * log.write(r.push('export GEMINI_API_KEY=AIzaSy'));
142
+ * log.write(r.push('…rest of key\n'));
143
+ * log.write(r.flush());
144
+ * ```
145
+ */
146
+ export class StreamingSecretRedactor {
147
+ carry = '';
148
+ knownSecrets;
149
+ maxCarry;
150
+ minCarry;
151
+ /**
152
+ * @param knownSecrets - Exact secret values to mask
153
+ * @param maxCarry - Longest whitespace-free tail held back before forcing a cut
154
+ */
155
+ constructor(knownSecrets = [], maxCarry = SECRET_REDACTION_CONSTANTS.MAX_CARRY_CHARS) {
156
+ this.knownSecrets = knownSecrets;
157
+ this.maxCarry = Math.max(maxCarry, 2);
158
+ this.minCarry = Math.max(1, Math.min(SECRET_REDACTION_CONSTANTS.MIN_FORCED_CARRY_CHARS, Math.floor(this.maxCarry / 2)));
159
+ }
160
+ /**
161
+ * Replaces the exact-value secret set (e.g. after the session's env changes).
162
+ *
163
+ * @param knownSecrets - Exact secret values to mask
164
+ */
165
+ setKnownSecrets(knownSecrets) {
166
+ this.knownSecrets = knownSecrets;
167
+ }
168
+ /**
169
+ * Feeds one chunk and returns the redacted text that can be emitted now.
170
+ *
171
+ * @param chunk - Next piece of output
172
+ * @returns Redacted text (possibly empty while a word is still arriving)
173
+ */
174
+ push(chunk) {
175
+ if (!chunk)
176
+ return '';
177
+ const carryLength = this.carry.length;
178
+ const buf = this.carry + chunk;
179
+ let cut = -1;
180
+ for (let i = buf.length - 1; i >= carryLength; i--) {
181
+ if (isWhitespace(buf.charCodeAt(i))) {
182
+ cut = i + 1;
183
+ break;
184
+ }
185
+ }
186
+ if (cut === -1 || buf.length - cut > this.maxCarry) {
187
+ if (buf.length <= this.maxCarry) {
188
+ this.carry = buf;
189
+ return '';
190
+ }
191
+ cut = this.forcedCut(buf);
192
+ }
193
+ this.carry = buf.slice(cut);
194
+ return cut > 0 ? redactSecrets(buf.slice(0, cut), this.knownSecrets) : '';
195
+ }
196
+ /**
197
+ * Returns (redacted) whatever is still held back and resets the carry.
198
+ *
199
+ * @returns Remaining redacted text
200
+ */
201
+ flush() {
202
+ const rest = this.carry;
203
+ this.carry = '';
204
+ return rest ? redactSecrets(rest, this.knownSecrets) : '';
205
+ }
206
+ /**
207
+ * Picks a cut point for a whitespace-free run that outgrew the carry: the
208
+ * last non-token character before the final minCarry characters, so a
209
+ * credential near the end is not split; hard cut when there is none.
210
+ *
211
+ * @param buf - Buffered text whose tail has no whitespace
212
+ * @returns Index to cut at
213
+ */
214
+ forcedCut(buf) {
215
+ const target = buf.length - this.minCarry;
216
+ const floor = Math.max(0, target - this.minCarry);
217
+ for (let i = target; i > floor; i--) {
218
+ if (!TOKEN_CHAR_RE.test(buf[i - 1]))
219
+ return i;
220
+ }
221
+ return target;
222
+ }
223
+ }
224
+ /**
225
+ * Whitespace test on a UTF-16 code unit (space, tab, CR, LF, VT, FF).
226
+ *
227
+ * @param code - Character code
228
+ * @returns True for ASCII whitespace
229
+ */
230
+ function isWhitespace(code) {
231
+ return code === 32 || (code >= 9 && code <= 13);
232
+ }
233
+ //# sourceMappingURL=secret-redactor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"secret-redactor.js","sourceRoot":"","sources":["../../../../../backend/src/utils/secret-redactor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Keeps agent shells out of the user's shell history.
3
+ *
4
+ * Crewly used to type `export GEMINI_API_KEY=…` into agent shells, and bash
5
+ * wrote those lines to ~/.bash_history hundreds of times. Secrets now travel
6
+ * only in the spawn env (utils/secret-env), and on top of that agent shells
7
+ * never write a history file at all:
8
+ *
9
+ * - historyOffSpawnEnv() goes into every PTY's spawn environment;
10
+ * - shellHistoryDisableLine() is typed first by the runtime init sequence,
11
+ * after the rc files ran (they may set HISTFILE themselves);
12
+ * - quietShellLine() prefixes every other typed line with a space, which
13
+ * bash (HISTCONTROL=ignorespace) and zsh (HIST_IGNORE_SPACE) skip.
14
+ *
15
+ * See SHELL_HISTORY_CONSTANTS for why HISTSIZE/HISTFILESIZE are not touched.
16
+ *
17
+ * @module utils/shell-history
18
+ */
19
+ /**
20
+ * Env vars that keep a freshly spawned bash/zsh from writing history.
21
+ *
22
+ * @returns A fresh copy (callers may spread or mutate it)
23
+ */
24
+ export declare function historyOffSpawnEnv(): Record<string, string>;
25
+ /**
26
+ * Prefixes a line typed into a shell with a space so it is not recorded.
27
+ * Already-prefixed lines are returned unchanged.
28
+ *
29
+ * @param line - Shell command line
30
+ * @returns The line, space-prefixed
31
+ */
32
+ export declare function quietShellLine(line: string): string;
33
+ /**
34
+ * The (space-prefixed) line that turns history off inside a running shell.
35
+ *
36
+ * @param shell - Shell binary path (defaults to $SHELL, then /bin/bash — what
37
+ * agent PTYs are spawned with)
38
+ * @returns The line to type, or null for a shell we do not know how to quiet
39
+ */
40
+ export declare function shellHistoryDisableLine(shell?: string | undefined): string | null;
41
+ /**
42
+ * Quotes a value for a POSIX shell word (single quotes).
43
+ *
44
+ * @param value - Raw value
45
+ * @returns Single-quoted value safe for `sh -c`
46
+ */
47
+ export declare function shellQuote(value: string): string;
48
+ //# sourceMappingURL=shell-history.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shell-history.d.ts","sourceRoot":"","sources":["../../../../../backend/src/utils/shell-history.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAKH;;;;GAIG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAE3D;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAGnD;AAED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,GAAE,MAAM,GAAG,SAA4C,GAAG,MAAM,GAAG,IAAI,CAOnH;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEhD"}
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Keeps agent shells out of the user's shell history.
3
+ *
4
+ * Crewly used to type `export GEMINI_API_KEY=…` into agent shells, and bash
5
+ * wrote those lines to ~/.bash_history hundreds of times. Secrets now travel
6
+ * only in the spawn env (utils/secret-env), and on top of that agent shells
7
+ * never write a history file at all:
8
+ *
9
+ * - historyOffSpawnEnv() goes into every PTY's spawn environment;
10
+ * - shellHistoryDisableLine() is typed first by the runtime init sequence,
11
+ * after the rc files ran (they may set HISTFILE themselves);
12
+ * - quietShellLine() prefixes every other typed line with a space, which
13
+ * bash (HISTCONTROL=ignorespace) and zsh (HIST_IGNORE_SPACE) skip.
14
+ *
15
+ * See SHELL_HISTORY_CONSTANTS for why HISTSIZE/HISTFILESIZE are not touched.
16
+ *
17
+ * @module utils/shell-history
18
+ */
19
+ import * as path from 'path';
20
+ import { SHELL_HISTORY_CONSTANTS } from '../constants.js';
21
+ /**
22
+ * Env vars that keep a freshly spawned bash/zsh from writing history.
23
+ *
24
+ * @returns A fresh copy (callers may spread or mutate it)
25
+ */
26
+ export function historyOffSpawnEnv() {
27
+ return { ...SHELL_HISTORY_CONSTANTS.SPAWN_ENV };
28
+ }
29
+ /**
30
+ * Prefixes a line typed into a shell with a space so it is not recorded.
31
+ * Already-prefixed lines are returned unchanged.
32
+ *
33
+ * @param line - Shell command line
34
+ * @returns The line, space-prefixed
35
+ */
36
+ export function quietShellLine(line) {
37
+ const prefix = SHELL_HISTORY_CONSTANTS.TYPED_LINE_PREFIX;
38
+ return line.startsWith(prefix) ? line : `${prefix}${line}`;
39
+ }
40
+ /**
41
+ * The (space-prefixed) line that turns history off inside a running shell.
42
+ *
43
+ * @param shell - Shell binary path (defaults to $SHELL, then /bin/bash — what
44
+ * agent PTYs are spawned with)
45
+ * @returns The line to type, or null for a shell we do not know how to quiet
46
+ */
47
+ export function shellHistoryDisableLine(shell = process.env.SHELL || '/bin/bash') {
48
+ const name = path.basename(shell || '').toLowerCase();
49
+ if (SHELL_HISTORY_CONSTANTS.POSIX_SHELLS.includes(name)) {
50
+ return quietShellLine(SHELL_HISTORY_CONSTANTS.DISABLE_COMMAND);
51
+ }
52
+ if (name === 'fish')
53
+ return quietShellLine(SHELL_HISTORY_CONSTANTS.FISH_DISABLE_COMMAND);
54
+ return null;
55
+ }
56
+ /**
57
+ * Quotes a value for a POSIX shell word (single quotes).
58
+ *
59
+ * @param value - Raw value
60
+ * @returns Single-quoted value safe for `sh -c`
61
+ */
62
+ export function shellQuote(value) {
63
+ return `'${value.replace(/'/g, `'\\''`)}'`;
64
+ }
65
+ //# sourceMappingURL=shell-history.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shell-history.js","sourceRoot":"","sources":["../../../../../backend/src/utils/shell-history.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAC7B,OAAO,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAC;AAE1D;;;;GAIG;AACH,MAAM,UAAU,kBAAkB;IACjC,OAAO,EAAE,GAAG,uBAAuB,CAAC,SAAS,EAAE,CAAC;AACjD,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,cAAc,CAAC,IAAY;IAC1C,MAAM,MAAM,GAAG,uBAAuB,CAAC,iBAAiB,CAAC;IACzD,OAAO,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,MAAM,GAAG,IAAI,EAAE,CAAC;AAC5D,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,uBAAuB,CAAC,QAA4B,OAAO,CAAC,GAAG,CAAC,KAAK,IAAI,WAAW;IACnG,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;IACtD,IAAI,uBAAuB,CAAC,YAAY,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QACzD,OAAO,cAAc,CAAC,uBAAuB,CAAC,eAAe,CAAC,CAAC;IAChE,CAAC;IACD,IAAI,IAAI,KAAK,MAAM;QAAE,OAAO,cAAc,CAAC,uBAAuB,CAAC,oBAAoB,CAAC,CAAC;IACzF,OAAO,IAAI,CAAC;AACb,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,UAAU,CAAC,KAAa;IACvC,OAAO,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,CAAC;AAC5C,CAAC"}
@@ -1,4 +1,4 @@
1
1
  {
2
- "commit": "f478a3caf3d69d304efb7f0c925950a86e7655ec",
3
- "builtAt": "2026-09-29T02:10:11.614Z"
2
+ "commit": "c6b0dd7b2bd2a6ad66251af5dd1e6cf085304e75",
3
+ "builtAt": "2026-09-29T18:49:45.817Z"
4
4
  }
@@ -1654,8 +1654,59 @@ export declare const WORKTREE_JANITOR_CONSTANTS: {
1654
1654
  readonly INTERVAL_MS: number;
1655
1655
  /** Delay before the first automatic run after startup (10 minutes). */
1656
1656
  readonly FIRST_RUN_DELAY_MS: number;
1657
- /** A worktree touched more recently than this is kept (2 hours). */
1657
+ /**
1658
+ * A worktree in a known agent location (`<repo>/.claude/worktrees/`,
1659
+ * `<tmp>/crewly-worktrees/`, or a `worktree-agent-*` branch) touched more
1660
+ * recently than this is kept (2 hours).
1661
+ */
1658
1662
  readonly MIN_IDLE_MS: number;
1663
+ /** Any other linked worktree touched more recently than this is kept (24 hours). */
1664
+ readonly MIN_IDLE_OTHER_MS: number;
1665
+ /**
1666
+ * A Claude Code session scratch dir (`<tmp>/claude-<uid>/<slug>/<uuid>/`)
1667
+ * touched more recently than this is kept (3 days).
1668
+ */
1669
+ readonly SCRATCH_MIN_IDLE_MS: number;
1670
+ /** In low-disk mode every idle threshold is divided by this factor… */
1671
+ readonly LOW_DISK_IDLE_DIVISOR: 2;
1672
+ /** …but never drops below this (2 hours). */
1673
+ readonly LOW_DISK_MIN_IDLE_FLOOR_MS: number;
1674
+ /** Prefix of Claude Code's per-user temp root under the temp dir (`claude-<uid>`). */
1675
+ readonly SCRATCH_ROOT_PREFIX: "claude-";
1676
+ /** Extra temp dirs where the per-user Claude root may live besides os.tmpdir(). */
1677
+ readonly SCRATCH_TMP_DIRS: readonly string[];
1678
+ /** Session dir name pattern under `<root>/<project-slug>/` (a UUID). */
1679
+ readonly SCRATCH_SESSION_DIR_PATTERN: RegExp;
1680
+ /** How deep under a session dir git repos are searched for. */
1681
+ readonly SCRATCH_REPO_SEARCH_DEPTH: 3;
1682
+ /** How many levels of a session dir count for its "last touched" time. */
1683
+ readonly SCRATCH_MTIME_DEPTH: 2;
1684
+ /** Directory names never descended into while searching for repos. */
1685
+ readonly SEARCH_SKIP_DIRS: readonly string[];
1686
+ /** How deep under each temp root linked worktrees are searched for (repo discovery). */
1687
+ readonly DISCOVERY_DEPTH: 4;
1688
+ /** Cap on directories visited per temp root during repo discovery. */
1689
+ readonly DISCOVERY_MAX_DIRS: 20000;
1690
+ /** Filesystem walks yield to the event loop (one setImmediate) after this many entries. */
1691
+ readonly YIELD_EVERY_ENTRIES: 50;
1692
+ /** Interval of the low-disk check (10 minutes). */
1693
+ readonly LOW_DISK_CHECK_INTERVAL_MS: number;
1694
+ /** Free space below this on the CREWLY_HOME volume triggers a low-disk pass (15 GB). */
1695
+ readonly LOW_DISK_BYTES: number;
1696
+ /** Free space below this is urgent (5 GB). */
1697
+ readonly CRITICAL_DISK_BYTES: number;
1698
+ /** A low-disk check does not start another pass within this time of the last one (30 minutes). */
1699
+ readonly LOW_DISK_PASS_GAP_MS: number;
1700
+ /** Owner notice about low disk at most this often (24 hours). */
1701
+ readonly LOW_DISK_NOTIFY_INTERVAL_MS: number;
1702
+ /** Urgent owner notice (below CRITICAL_DISK_BYTES) at most this often (6 hours). */
1703
+ readonly CRITICAL_DISK_NOTIFY_INTERVAL_MS: number;
1704
+ /** How many of the biggest kept items the owner notice lists. */
1705
+ readonly LOW_DISK_REPORT_ITEMS: 5;
1706
+ /** Timeout of one `du -sk` size probe (ms). */
1707
+ readonly DU_TIMEOUT_MS: 60000;
1708
+ /** State file under CREWLY_HOME remembering when the owner was last told about low disk. */
1709
+ readonly STATE_FILENAME: "disk-janitor-state.json";
1659
1710
  /**
1660
1711
  * Directory, relative to the repo's main worktree, where agent tools
1661
1712
  * (Claude Code subagents, Crewly agents) create per-task worktrees.
@@ -1673,8 +1724,8 @@ export declare const WORKTREE_JANITOR_CONSTANTS: {
1673
1724
  readonly MANAGED_WORKTREE_DIR: ".crewly/worktrees";
1674
1725
  /**
1675
1726
  * Branch-name prefixes that only agents create. A worktree elsewhere on
1676
- * disk is eligible only when its branch starts with one of these.
1677
- * `worktree-agent-` is Claude Code's automatic subagent branch.
1727
+ * disk with such a branch counts as an agent location (2 h idle threshold
1728
+ * instead of 24 h). `worktree-agent-` is Claude Code's automatic subagent branch.
1678
1729
  */
1679
1730
  readonly AGENT_BRANCH_PREFIXES: readonly string[];
1680
1731
  /** Branches that are never deleted, whatever the verdict. */
@@ -2308,6 +2359,13 @@ export declare const OAUTH_ERROR_PATTERN_SETS: string[][];
2308
2359
  * regex — to stay ReDoS-free.
2309
2360
  */
2310
2361
  export declare const LOGIN_REQUIRED_PATTERN_SETS: string[][];
2362
+ /**
2363
+ * Screen text that means the runtime has just *finished* signing in. The
2364
+ * capture window can still hold the earlier sign-in screen above it, so
2365
+ * these veto a {@link LOGIN_REQUIRED_PATTERN_SETS} match and let the
2366
+ * "Sign-in needed" flag clear. Lower-case substrings.
2367
+ */
2368
+ export declare const LOGIN_COMPLETED_MARKERS: readonly string[];
2311
2369
  /**
2312
2370
  * Constants for first-run / device-code login detection and notification
2313
2371
  * in `OAuthReloginMonitorService`.
@@ -4025,8 +4083,8 @@ export declare const ONBOARDING_CONSTANTS: {
4025
4083
  /** Display copy for the Blank starter (templates carry their own) */
4026
4084
  readonly BLANK_STARTER: {
4027
4085
  readonly NAME: "Blank";
4028
- readonly LABEL: "空白";
4029
- readonly TAGLINE: "只有 Orc(总调度),先不建团队。需要时让 Orc 帮你组队。";
4086
+ readonly LABEL: "Blank";
4087
+ readonly TAGLINE: "Just the Orc (the orchestrator), no team yet. Ask the Orc to put a team together when you need one.";
4030
4088
  readonly SUGGESTIONS: readonly string[];
4031
4089
  };
4032
4090
  /** Longest first task accepted */
@@ -4345,4 +4403,61 @@ export type AgentStatus = (typeof CREWLY_CONSTANTS.AGENT_STATUSES)[keyof typeof
4345
4403
  export type WorkingStatus = (typeof CREWLY_CONSTANTS.WORKING_STATUSES)[keyof typeof CREWLY_CONSTANTS.WORKING_STATUSES];
4346
4404
  export type RuntimeType = (typeof RUNTIME_TYPES)[keyof typeof RUNTIME_TYPES];
4347
4405
  export type AgentId = string;
4406
+ /**
4407
+ * Secret redaction for persisted terminal output (session logs, scrub of old
4408
+ * logs and shell history). See utils/secret-redactor.
4409
+ */
4410
+ export declare const SECRET_REDACTION_CONSTANTS: {
4411
+ /**
4412
+ * Secret variable names masked in `NAME=value` even if the generic suffix
4413
+ * rule (utils/secret-env isSecretEnvKey) ever changed. Upper-case.
4414
+ */
4415
+ readonly KNOWN_SECRET_ENV_NAMES: readonly string[];
4416
+ /** Name families masked in `NAME=value` (tested against the upper-cased name). */
4417
+ readonly SECRET_NAME_PATTERNS: readonly RegExp[];
4418
+ /**
4419
+ * Longest whitespace-free tail the streaming redactor holds back between
4420
+ * chunks before it forces a cut. Far above any credential's length.
4421
+ */
4422
+ readonly MAX_CARRY_CHARS: 4096;
4423
+ /** Tail kept after a forced cut — above any credential's length (≈250 chars). */
4424
+ readonly MIN_FORCED_CARRY_CHARS: 512;
4425
+ };
4426
+ /**
4427
+ * Shell history is kept off in agent PTYs: whatever an agent shell runs
4428
+ * (and anything Crewly types into it) must never land in ~/.bash_history or
4429
+ * ~/.zsh_history. Two layers:
4430
+ *
4431
+ * - SPAWN_ENV is merged into every PTY's spawn environment. It works for a
4432
+ * shell whose rc files leave these alone.
4433
+ * - DISABLE_COMMAND is typed (space-prefixed) as the first line of the
4434
+ * runtime init sequence, after the rc files ran — macOS /etc/zshrc
4435
+ * unconditionally sets HISTFILE, which beats the spawn env. `unset HISTFILE`
4436
+ * stops bash and zsh from ever writing a history file; bash additionally
4437
+ * turns history recording off.
4438
+ *
4439
+ * HISTSIZE / HISTFILESIZE are deliberately NOT set to 0: when a user's
4440
+ * ~/.bashrc re-points HISTFILE at ~/.bash_history but leaves HISTFILESIZE
4441
+ * alone, bash truncates that file to HISTFILESIZE (defaulting to HISTSIZE)
4442
+ * lines — an inherited 0 would wipe the user's own history.
4443
+ */
4444
+ export declare const SHELL_HISTORY_CONSTANTS: {
4445
+ /** Env merged into every PTY spawn (callers' own env still wins). */
4446
+ readonly SPAWN_ENV: Readonly<Record<string, string>>;
4447
+ /** POSIX-shell line typed first into an agent shell (bash, zsh, sh, dash, ksh). */
4448
+ readonly DISABLE_COMMAND: "unset HISTFILE; if [ -n \"$ZSH_VERSION\" ]; then setopt HIST_IGNORE_SPACE; SAVEHIST=0; elif [ -n \"$BASH_VERSION\" ]; then set +o history; fi";
4449
+ /** fish: an empty fish_history keeps history in memory only. */
4450
+ readonly FISH_DISABLE_COMMAND: "set -g fish_history ''";
4451
+ /** Shells that understand DISABLE_COMMAND (basename of $SHELL). */
4452
+ readonly POSIX_SHELLS: readonly string[];
4453
+ /** Prefix for every line Crewly types into a shell (kept out of history by ignorespace / HIST_IGNORE_SPACE). */
4454
+ readonly TYPED_LINE_PREFIX: " ";
4455
+ };
4456
+ /** One-time scrub of secrets already on disk (`crewly security scrub-logs`). */
4457
+ export declare const SECRET_SCRUB_CONSTANTS: {
4458
+ /** Shell history files (relative to the user's home) that are scrubbed */
4459
+ readonly SHELL_HISTORY_FILES: readonly string[];
4460
+ /** settings.json under CREWLY_HOME, whose apiKeys are masked by exact value */
4461
+ readonly SETTINGS_FILE: "settings.json";
4462
+ };
4348
4463
  //# sourceMappingURL=constants.d.ts.map