@profoundlogic/coderflow-server 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (202) hide show
  1. package/LICENSE.txt +322 -0
  2. package/README.md +158 -0
  3. package/dist/LICENSE.txt +322 -0
  4. package/dist/README.md +158 -0
  5. package/dist/base-image/Dockerfile +184 -0
  6. package/dist/base-image/agent-wrapper.sh +143 -0
  7. package/dist/base-image/apply-local-state.sh +357 -0
  8. package/dist/base-image/coder-git-credential-helper +307 -0
  9. package/dist/base-image/entrypoint.sh +942 -0
  10. package/dist/base-image/ssh_config_template +41 -0
  11. package/dist/base-image/start-code-server.sh +76 -0
  12. package/dist/base-image/sync-repos.sh +170 -0
  13. package/dist/base-image/vscode-extensions.txt +10 -0
  14. package/dist/base-image/vscode-settings.json +41 -0
  15. package/dist/coder-server.js +2 -0
  16. package/dist/config/cli-models.json +45 -0
  17. package/dist/config/imported-skills.schema.json +83 -0
  18. package/dist/config/skill-catalog.json +18 -0
  19. package/dist/config/skill-catalog.schema.json +140 -0
  20. package/dist/config.js +1 -0
  21. package/dist/examples/oidc.json.example +11 -0
  22. package/dist/lib/agent-keepalive.js +1 -0
  23. package/dist/lib/api-keys.js +1 -0
  24. package/dist/lib/apiKeys.js +1 -0
  25. package/dist/lib/auto-judge.js +1 -0
  26. package/dist/lib/basic-auth.js +1 -0
  27. package/dist/lib/build-history.js +1 -0
  28. package/dist/lib/build-output-service.js +1 -0
  29. package/dist/lib/build-scheduler.js +1 -0
  30. package/dist/lib/build-service.js +1 -0
  31. package/dist/lib/claude-oauth-refresh.js +1 -0
  32. package/dist/lib/cli/build.js +1 -0
  33. package/dist/lib/cli/config-command.js +1 -0
  34. package/dist/lib/cli/config.js +1 -0
  35. package/dist/lib/cli/create-user.js +1 -0
  36. package/dist/lib/cli/init.js +1 -0
  37. package/dist/lib/cli/jira.js +1 -0
  38. package/dist/lib/cli/license.js +1 -0
  39. package/dist/lib/cli/server-manager.js +1 -0
  40. package/dist/lib/container-tokens.js +1 -0
  41. package/dist/lib/data-dir.js +1 -0
  42. package/dist/lib/deployment-history.js +1 -0
  43. package/dist/lib/deployment-service.js +1 -0
  44. package/dist/lib/docker-utils.js +1 -0
  45. package/dist/lib/email.js +1 -0
  46. package/dist/lib/emailTemplates.js +1 -0
  47. package/dist/lib/entitlement.js +1 -0
  48. package/dist/lib/fetch-utils.js +1 -0
  49. package/dist/lib/git-provider-service.js +1 -0
  50. package/dist/lib/git-provider-setup/assets/coderflow_github_app.png +0 -0
  51. package/dist/lib/git-provider-setup/github-setup-handler.js +1 -0
  52. package/dist/lib/git-provider-setup/index.js +1 -0
  53. package/dist/lib/git-provider-setup/setup-factory.js +1 -0
  54. package/dist/lib/git-provider-setup/setup-interface.js +1 -0
  55. package/dist/lib/git-providers/azure-devops-provider.js +1 -0
  56. package/dist/lib/git-providers/github-app-provider.js +1 -0
  57. package/dist/lib/git-providers/index.js +1 -0
  58. package/dist/lib/git-providers/provider-factory.js +1 -0
  59. package/dist/lib/git-providers/provider-interface.js +1 -0
  60. package/dist/lib/jira-client.js +1 -0
  61. package/dist/lib/logger.js +1 -0
  62. package/dist/lib/model-fetcher.js +1 -0
  63. package/dist/lib/notifications.js +1 -0
  64. package/dist/lib/oidc-auth.js +1 -0
  65. package/dist/lib/oidc-device-flow.js +1 -0
  66. package/dist/lib/passwordTokens.js +1 -0
  67. package/dist/lib/pin-cascade.js +1 -0
  68. package/dist/lib/provider-accounts.js +1 -0
  69. package/dist/lib/provider-oauth.js +1 -0
  70. package/dist/lib/provider-profile.js +1 -0
  71. package/dist/lib/provider-token-refresh.js +1 -0
  72. package/dist/lib/roles.js +1 -0
  73. package/dist/lib/secrets.js +1 -0
  74. package/dist/lib/state-capture.js +1 -0
  75. package/dist/lib/static-files.js +1 -0
  76. package/dist/lib/task-name-generator.js +1 -0
  77. package/dist/lib/users.js +1 -0
  78. package/dist/middleware/requireAuth.js +1 -0
  79. package/dist/middleware/requireInit.js +1 -0
  80. package/dist/middleware/requirePermission.js +1 -0
  81. package/dist/package-lock.json +4151 -0
  82. package/dist/package.json +50 -0
  83. package/dist/routes/apiKeys.js +1 -0
  84. package/dist/routes/auth-oidc.js +1 -0
  85. package/dist/routes/auth.js +1 -0
  86. package/dist/routes/build.js +1 -0
  87. package/dist/routes/containers.js +1 -0
  88. package/dist/routes/deploy-task.js +1 -0
  89. package/dist/routes/environment-management.js +1 -0
  90. package/dist/routes/environments.js +1 -0
  91. package/dist/routes/external-skills.js +1 -0
  92. package/dist/routes/git-credentials.js +1 -0
  93. package/dist/routes/git-provider-setup.js +1 -0
  94. package/dist/routes/health.js +1 -0
  95. package/dist/routes/jira.js +1 -0
  96. package/dist/routes/objective-management.js +1 -0
  97. package/dist/routes/password.js +1 -0
  98. package/dist/routes/prompt.js +1 -0
  99. package/dist/routes/provider-auth.js +1 -0
  100. package/dist/routes/qa.js +1 -0
  101. package/dist/routes/settings.js +1 -0
  102. package/dist/routes/skill-management.js +1 -0
  103. package/dist/routes/skills.js +1 -0
  104. package/dist/routes/tasks.js +2 -0
  105. package/dist/routes/templates.js +1 -0
  106. package/dist/routes/test-task.js +1 -0
  107. package/dist/routes/test.js +1 -0
  108. package/dist/routes/users.js +1 -0
  109. package/dist/routes/visualizations.js +1 -0
  110. package/dist/schemas/template-metadata.schema.json +178 -0
  111. package/dist/scripts/create-user.js +2 -0
  112. package/dist/shipped-skills/environment-instructions/SKILL.md +154 -0
  113. package/dist/shipped-skills/environment-templates/SKILL.md +282 -0
  114. package/dist/shipped-skills/objective-management/SKILL.md +238 -0
  115. package/dist/shipped-skills/skill-editor/SKILL.md +326 -0
  116. package/dist/start.js +2 -0
  117. package/dist/web-ui/public/activity-detail-modal.js +1 -0
  118. package/dist/web-ui/public/activity-feed.js +1 -0
  119. package/dist/web-ui/public/activity-formatters.js +1 -0
  120. package/dist/web-ui/public/agent-event-parser.js +1 -0
  121. package/dist/web-ui/public/app.js +1 -0
  122. package/dist/web-ui/public/approve-dialog.js +1 -0
  123. package/dist/web-ui/public/coderflow-logo-reversed.svg +46 -0
  124. package/dist/web-ui/public/coderflow-logo.svg +46 -0
  125. package/dist/web-ui/public/comments-widget.js +1 -0
  126. package/dist/web-ui/public/docs/.nojekyll +0 -0
  127. package/dist/web-ui/public/docs/README.md +26 -0
  128. package/dist/web-ui/public/docs/_sidebar.md +47 -0
  129. package/dist/web-ui/public/docs/admin/ai-providers.md +132 -0
  130. package/dist/web-ui/public/docs/admin/email-notifications.md +69 -0
  131. package/dist/web-ui/public/docs/admin/environments.md +215 -0
  132. package/dist/web-ui/public/docs/admin/git-providers.md +147 -0
  133. package/dist/web-ui/public/docs/admin/installation.md +313 -0
  134. package/dist/web-ui/public/docs/admin/skills.md +35 -0
  135. package/dist/web-ui/public/docs/admin/sso.md +241 -0
  136. package/dist/web-ui/public/docs/admin/users-and-roles.md +57 -0
  137. package/dist/web-ui/public/docs/code/cli.md +102 -0
  138. package/dist/web-ui/public/docs/code/files-and-editing.md +86 -0
  139. package/dist/web-ui/public/docs/code/terminal-access.md +110 -0
  140. package/dist/web-ui/public/docs/code/vscode-extension.md +58 -0
  141. package/dist/web-ui/public/docs/getting-started/core-concepts.md +129 -0
  142. package/dist/web-ui/public/docs/getting-started/overview.md +46 -0
  143. package/dist/web-ui/public/docs/index.html +151 -0
  144. package/dist/web-ui/public/docs/integrations/custom.md +58 -0
  145. package/dist/web-ui/public/docs/integrations/ibmi/overview.md +58 -0
  146. package/dist/web-ui/public/docs/integrations/overview.md +48 -0
  147. package/dist/web-ui/public/docs/objectives/qa-mode.md +90 -0
  148. package/dist/web-ui/public/docs/objectives/staged-tasks.md +60 -0
  149. package/dist/web-ui/public/docs/objectives/working-with-objectives.md +102 -0
  150. package/dist/web-ui/public/docs/tasks/approval-and-deployment.md +83 -0
  151. package/dist/web-ui/public/docs/tasks/creating-tasks.md +111 -0
  152. package/dist/web-ui/public/docs/tasks/judging.md +114 -0
  153. package/dist/web-ui/public/docs/tasks/providing-feedback.md +41 -0
  154. package/dist/web-ui/public/docs/tasks/task-groups.md +73 -0
  155. package/dist/web-ui/public/docs/tasks/winner-selection.md +75 -0
  156. package/dist/web-ui/public/docs/templates/batch-processing.md +152 -0
  157. package/dist/web-ui/public/docs/templates/task-templates.md +44 -0
  158. package/dist/web-ui/public/docs/templates/template-examples.md +93 -0
  159. package/dist/web-ui/public/docs/testing/profound-automated-testing.md +77 -0
  160. package/dist/web-ui/public/docs/testing/task-visualizations.md +42 -0
  161. package/dist/web-ui/public/docs/testing/testing-menu.md +118 -0
  162. package/dist/web-ui/public/environments.css +3942 -0
  163. package/dist/web-ui/public/environments.html +1791 -0
  164. package/dist/web-ui/public/environments.js +1 -0
  165. package/dist/web-ui/public/favicon-16.png +0 -0
  166. package/dist/web-ui/public/favicon-32.png +0 -0
  167. package/dist/web-ui/public/favicon.ico +0 -0
  168. package/dist/web-ui/public/feedback-widget.css +3133 -0
  169. package/dist/web-ui/public/feedback-widget.js +1 -0
  170. package/dist/web-ui/public/git-history.css +2663 -0
  171. package/dist/web-ui/public/git-history.html +272 -0
  172. package/dist/web-ui/public/git-history.js +1 -0
  173. package/dist/web-ui/public/git-status.js +1 -0
  174. package/dist/web-ui/public/index.html +1459 -0
  175. package/dist/web-ui/public/index.js +1 -0
  176. package/dist/web-ui/public/login.html +346 -0
  177. package/dist/web-ui/public/login.js +1 -0
  178. package/dist/web-ui/public/markdown-editor.js +1 -0
  179. package/dist/web-ui/public/markdown-file-editor.js +1 -0
  180. package/dist/web-ui/public/modal-maximize.js +1 -0
  181. package/dist/web-ui/public/notifications.js +1 -0
  182. package/dist/web-ui/public/server-health.js +1 -0
  183. package/dist/web-ui/public/settings.css +761 -0
  184. package/dist/web-ui/public/settings.html +1044 -0
  185. package/dist/web-ui/public/settings.js +1 -0
  186. package/dist/web-ui/public/setup-password.html +355 -0
  187. package/dist/web-ui/public/setup-password.js +1 -0
  188. package/dist/web-ui/public/skills.css +1949 -0
  189. package/dist/web-ui/public/skills.html +820 -0
  190. package/dist/web-ui/public/skills.js +1 -0
  191. package/dist/web-ui/public/sse-client.js +1 -0
  192. package/dist/web-ui/public/sse-shared-worker.js +1 -0
  193. package/dist/web-ui/public/styles.css +18614 -0
  194. package/dist/web-ui/public/task.html +1779 -0
  195. package/dist/web-ui/public/task.js +1 -0
  196. package/dist/web-ui/public/terminal.html +45 -0
  197. package/dist/web-ui/public/terminal.js +1 -0
  198. package/dist/web-ui/public/theme.js +1 -0
  199. package/dist/web-ui/public/users.html +298 -0
  200. package/dist/web-ui/public/users.js +1 -0
  201. package/dist/web-ui/public/variant-grouping.js +1 -0
  202. package/package.json +63 -0
@@ -0,0 +1,184 @@
1
+ # CoderFlow Base Image
2
+ # Minimal image with Node.js, Python, and Git for AI agent execution
3
+
4
+ FROM node:24-slim
5
+
6
+ # Set timezone to Eastern Time to match idev
7
+ ENV TZ=America/New_York
8
+
9
+ # Build arguments for user UID/GID (auto-detected from host, defaults to 1000)
10
+ ARG USER_UID=1000
11
+ ARG USER_GID=1000
12
+
13
+ # Install essential tools for development and troubleshooting
14
+ # Includes Playwright/Chromium dependencies for browser automation
15
+ RUN apt-get update && \
16
+ apt-get install -y \
17
+ expect \
18
+ tzdata \
19
+ git \
20
+ jq \
21
+ procps \
22
+ curl \
23
+ wget \
24
+ netcat-openbsd \
25
+ dnsutils \
26
+ iputils-ping \
27
+ telnet \
28
+ vim \
29
+ nano \
30
+ openssh-client \
31
+ make \
32
+ uuid-runtime \
33
+ lsof \
34
+ python3 \
35
+ python3-pip \
36
+ python3-venv \
37
+ ripgrep \
38
+ # Document handling tools
39
+ unzip \
40
+ poppler-utils \
41
+ # Playwright/Chromium dependencies (prebaked to avoid needing sudo at runtime)
42
+ libasound2 \
43
+ libatk-bridge2.0-0 \
44
+ libatk1.0-0 \
45
+ libatspi2.0-0 \
46
+ libcairo2 \
47
+ libcups2 \
48
+ libdbus-1-3 \
49
+ libdrm2 \
50
+ libgbm1 \
51
+ libglib2.0-0 \
52
+ libnspr4 \
53
+ libnss3 \
54
+ libpango-1.0-0 \
55
+ libx11-6 \
56
+ libxcb1 \
57
+ libxcomposite1 \
58
+ libxdamage1 \
59
+ libxext6 \
60
+ libxfixes3 \
61
+ libxkbcommon0 \
62
+ libxrandr2 \
63
+ xvfb && \
64
+ ln -sf /usr/share/zoneinfo/America/New_York /etc/localtime && \
65
+ echo "America/New_York" > /etc/timezone && \
66
+ apt-get clean && \
67
+ rm -rf /var/lib/apt/lists/* && \
68
+ ln -s /usr/bin/python3 /usr/bin/python
69
+
70
+ # Install Python libraries for document handling (docx, pdf)
71
+ RUN pip install --break-system-packages python-docx pypdf pdfplumber
72
+
73
+ # Remove the default 'node' user that comes with node:24-slim (typically UID 1000)
74
+ # This prevents conflicts when we create our own user with specified UID
75
+ RUN userdel -r node 2>/dev/null || true
76
+
77
+ # Create 'coder' user and group with UID/GID from build args
78
+ # This matches the host user's UID/GID, eliminating permission issues
79
+ RUN groupadd -g ${USER_GID} coder && \
80
+ useradd -m -u ${USER_UID} -g ${USER_GID} -s /bin/bash coder && \
81
+ mkdir -p /home/coder/.ssh /home/coder/.claude /home/coder/.codex \
82
+ /home/coder/.config/google-generative-ai /home/coder/.gemini && \
83
+ chown -R coder:coder /home/coder
84
+
85
+ # Configure git for root user (used during docker builds with BuildKit secrets)
86
+ RUN git config --global credential.helper store && \
87
+ git config --global --add safe.directory '*' && \
88
+ git config --global core.autocrlf false
89
+
90
+ # Configure git for coder user (used at runtime in containers)
91
+ # Credential helper chain: our custom helper first, then store as fallback
92
+ # Our helper only responds for managed repos (in CODER_MANAGED_REPOS); others fall through to store
93
+ # useHttpPath ensures Git sends the full repo path to credential helpers (needed for per-repo matching)
94
+ RUN su - coder -c "git config --global credential.helper '/usr/local/bin/coder-git-credential-helper' && \
95
+ git config --global --add credential.helper store && \
96
+ git config --global credential.useHttpPath true && \
97
+ git config --global --add safe.directory '*' && \
98
+ git config --global core.autocrlf false"
99
+
100
+ # Pre-populate SSH known_hosts with GitHub host keys at build time
101
+ # This eliminates the need for ssh-keyscan at runtime, preventing connection storms
102
+ # when multiple containers start simultaneously
103
+ RUN mkdir -p /etc/ssh && \
104
+ ssh-keyscan github.com >> /etc/ssh/ssh_known_hosts 2>/dev/null && \
105
+ chmod 644 /etc/ssh/ssh_known_hosts
106
+
107
+ # Configure SSH connection multiplexing for GitHub
108
+ # This enables connection reuse across multiple git operations, reducing overhead
109
+ COPY ssh_config_template /etc/ssh/ssh_config.d/10-github-multiplexing.conf
110
+ RUN chmod 644 /etc/ssh/ssh_config.d/10-github-multiplexing.conf
111
+
112
+ # Configure shell to source ~/.bash_env for environment variables
113
+ # This allows setup.sh to write env vars that persist in all shells (login and non-login)
114
+ RUN sed -i '/^# If not running interactively/i # Source environment variables from setup.sh\nif [ -f ~/.bash_env ]; then\n . ~/.bash_env\nfi\n' /home/coder/.bashrc && \
115
+ printf '\n# Source environment variables from setup.sh\nif [ -f ~/.bash_env ]; then\n . ~/.bash_env\nfi\n' >> /home/coder/.profile
116
+
117
+ # Install Claude Code, Codex, and Gemini CLI globally
118
+ RUN npm install -g @openai/codex @google/gemini-cli && \
119
+ su - coder -c "curl -fsSL https://claude.ai/install.sh | bash"
120
+
121
+ # Pre-install Playwright browser binaries (not the skill itself)
122
+ # This caches ~165MB of Chromium so skills that need browser automation don't have to download at runtime
123
+ # System dependencies are already installed above (libasound2, libatk*, etc.)
124
+ RUN su - coder -c "npx playwright install chromium"
125
+
126
+ # Install code-server for web-based VS Code
127
+ RUN curl -fsSL https://code-server.dev/install.sh | sh && \
128
+ mkdir -p /home/coder/.local/share/code-server && \
129
+ mkdir -p /home/coder/.config/code-server && \
130
+ chown -R coder:coder /home/coder/.local /home/coder/.config
131
+
132
+ # Pre-install VS Code extensions from configurable list
133
+ COPY vscode-extensions.txt /tmp/vscode-extensions.txt
134
+ RUN while IFS= read -r extension || [ -n "$extension" ]; do \
135
+ # Skip comments and empty lines
136
+ case "$extension" in \
137
+ \#*|'') continue ;; \
138
+ esac; \
139
+ echo "Installing extension: $extension"; \
140
+ su - coder -c "code-server --install-extension $extension" || echo "Failed to install $extension"; \
141
+ done < /tmp/vscode-extensions.txt && \
142
+ rm /tmp/vscode-extensions.txt
143
+
144
+ # Configure default VS Code settings for minimal layout
145
+ COPY vscode-settings.json /tmp/vscode-settings.json
146
+ RUN mkdir -p /home/coder/.local/share/code-server/User && \
147
+ cp /tmp/vscode-settings.json /home/coder/.local/share/code-server/User/settings.json && \
148
+ rm /tmp/vscode-settings.json && \
149
+ chown -R coder:coder /home/coder/.local
150
+
151
+ # Disable built-in chat/AI extensions that may show unwanted panels
152
+ RUN if [ -d "/usr/lib/code-server/lib/vscode/extensions/mermaid-chat-features" ]; then \
153
+ mv /usr/lib/code-server/lib/vscode/extensions/mermaid-chat-features \
154
+ /usr/lib/code-server/lib/vscode/extensions/mermaid-chat-features.disabled || true; \
155
+ fi
156
+
157
+ # Create workspace directory for repositories
158
+ RUN mkdir -p /workspace && \
159
+ chown coder:coder /workspace
160
+
161
+ # Create task-output directory for results
162
+ RUN mkdir -p /task-output && \
163
+ chown coder:coder /task-output
164
+
165
+ # Set working directory
166
+ WORKDIR /workspace
167
+
168
+ # Copy entrypoint script and AI agent wrapper
169
+ COPY entrypoint.sh /usr/local/bin/entrypoint.sh
170
+ COPY sync-repos.sh /usr/local/bin/sync-repos.sh
171
+ COPY agent-wrapper.sh /usr/local/bin/agent-wrapper.sh
172
+ COPY start-code-server.sh /usr/local/bin/start-code-server.sh
173
+ COPY apply-local-state.sh /usr/local/bin/apply-local-state.sh
174
+ COPY coder-git-credential-helper /usr/local/bin/coder-git-credential-helper
175
+ RUN chmod +x /usr/local/bin/entrypoint.sh /usr/local/bin/sync-repos.sh /usr/local/bin/agent-wrapper.sh /usr/local/bin/start-code-server.sh /usr/local/bin/apply-local-state.sh /usr/local/bin/coder-git-credential-helper
176
+
177
+ # Expose code-server port
178
+ EXPOSE 8080
179
+
180
+ # Set entrypoint
181
+ ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
182
+
183
+ # Default command: none (entrypoint will run default task)
184
+ CMD []
@@ -0,0 +1,143 @@
1
+ #!/bin/bash
2
+ # Wrapper script to run AI agents (Claude/Codex/Gemini) and output JSON statistics
3
+ #
4
+ # Model selection via environment variables:
5
+ # CLAUDE_MODEL - Model for Claude (e.g., opus, sonnet, haiku)
6
+ # OPENAI_MODEL - Model for Codex/OpenAI (e.g., gpt-5.2-codex)
7
+ # GEMINI_MODEL - Model for Gemini (e.g., gemini-2.5-pro)
8
+ # CODEX_REASONING_LEVEL - Reasoning level for Codex (low, medium, high, xhigh)
9
+ #
10
+ # If model env vars are not set, agents use their default models.
11
+
12
+ # First argument is the agent command, rest are passed to the agent
13
+ AGENT_CMD="$1"
14
+ shift
15
+
16
+ echo "[$(date +'%Y-%m-%d %H:%M:%S')] Starting agent: $AGENT_CMD" >&2
17
+
18
+ # Log model configuration if set
19
+ if [[ -n "$CLAUDE_MODEL" ]] || [[ -n "$OPENAI_MODEL" ]] || [[ -n "$GEMINI_MODEL" ]]; then
20
+ echo "[$(date +'%Y-%m-%d %H:%M:%S')] Model configuration: CLAUDE_MODEL=${CLAUDE_MODEL:-default} OPENAI_MODEL=${OPENAI_MODEL:-default} GEMINI_MODEL=${GEMINI_MODEL:-default}" >&2
21
+ fi
22
+
23
+ START_TIME=$(date +%s%3N)
24
+
25
+ # Save stdin to temp file
26
+ TEMP_INPUT=$(mktemp)
27
+ cat > "$TEMP_INPUT"
28
+
29
+ mkdir -p /task-output
30
+
31
+ # UNIFIED APPROACH FOR ALL AGENTS:
32
+ # - Run agent once, stream output to debug-stream.jsonl
33
+ # - Capture exit code
34
+ # - Generate a simple success/failure result
35
+ # - Let the UI parse debug-stream.jsonl for detailed activity
36
+
37
+ # Codex requires exec subcommand for non-interactive use (or resume for continuing)
38
+ # Claude accepts stdin directly with --print --output-format stream-json
39
+ # Gemini uses -p flag for prompt with --yolo for auto-approval and --resume for continuing
40
+ if [[ "$AGENT_CMD" == *"codex"* ]]; then
41
+ INSTRUCTION=$(cat "$TEMP_INPUT")
42
+
43
+ # Build model argument if OPENAI_MODEL is set
44
+ MODEL_ARG=""
45
+ if [[ -n "$OPENAI_MODEL" ]]; then
46
+ MODEL_ARG="-m $OPENAI_MODEL"
47
+ fi
48
+
49
+ # Build reasoning level argument if CODEX_REASONING_LEVEL is set
50
+ REASONING_ARG=""
51
+ if [[ -n "$CODEX_REASONING_LEVEL" ]]; then
52
+ echo "[$(date +'%Y-%m-%d %H:%M:%S')] Codex reasoning level: $CODEX_REASONING_LEVEL" >&2
53
+ REASONING_ARG="-c model_reasoning_effort=$CODEX_REASONING_LEVEL"
54
+ fi
55
+
56
+ # Check if this is a resume operation (first arg is 'exec', second is 'resume')
57
+ if [[ "$1" == "exec" ]] && [[ "$2" == "resume" ]]; then
58
+ shift 2 # Remove 'exec' and 'resume' from $@
59
+ echo "$INSTRUCTION" | "$AGENT_CMD" $MODEL_ARG $REASONING_ARG -a never -s danger-full-access -c approval_policy=never -c sandbox_mode=danger-full-access exec --skip-git-repo-check --json resume "$@" >> /task-output/debug-stream.jsonl 2>&1
60
+ EXIT_CODE=$?
61
+ else
62
+ "$AGENT_CMD" $MODEL_ARG $REASONING_ARG -a never -s danger-full-access exec --skip-git-repo-check --json "$@" "$INSTRUCTION" >> /task-output/debug-stream.jsonl 2>&1
63
+ EXIT_CODE=$?
64
+ fi
65
+
66
+ elif [[ "$AGENT_CMD" == *"gemini"* ]]; then
67
+ INSTRUCTION=$(cat "$TEMP_INPUT")
68
+
69
+ # Use GEMINI_MODEL if set, otherwise default to "pro"
70
+ GEMINI_MODEL_TO_USE="${GEMINI_MODEL:-pro}"
71
+
72
+ # Check if this is a resume operation (--resume flag in args)
73
+ if [[ "$*" == *"--resume"* ]]; then
74
+ "$AGENT_CMD" --model "$GEMINI_MODEL_TO_USE" --resume latest -p "$INSTRUCTION" --yolo --include-directories /task-output --output-format stream-json >> /task-output/debug-stream.jsonl 2>&1
75
+ EXIT_CODE=$?
76
+ else
77
+ "$AGENT_CMD" --model "$GEMINI_MODEL_TO_USE" -p "$INSTRUCTION" --yolo --include-directories /task-output --output-format stream-json "$@" >> /task-output/debug-stream.jsonl 2>&1
78
+ EXIT_CODE=$?
79
+ fi
80
+
81
+ else
82
+ # Claude - uses CLAUDE_MODEL env var if set (claude-code reads it automatically)
83
+ # The claude-code CLI respects the CLAUDE_MODEL environment variable
84
+ cat "$TEMP_INPUT" | "$AGENT_CMD" --print --output-format stream-json --verbose "$@" >> /task-output/debug-stream.jsonl 2>&1
85
+ EXIT_CODE=$?
86
+ fi
87
+
88
+ # Clean up temp file
89
+ rm -f "$TEMP_INPUT"
90
+
91
+ END_TIME=$(date +%s%3N)
92
+ DURATION=$((END_TIME - START_TIME))
93
+
94
+ # Extract final response from stream for activity feed
95
+ # This ensures the conversation shows the agent's reply
96
+ TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%S.%3NZ")
97
+
98
+ if [ -f /task-output/debug-stream.jsonl ] && [ $EXIT_CODE -eq 0 ]; then
99
+ # Try to extract final response based on agent type
100
+ if [[ "$AGENT_CMD" == *"claude"* ]]; then
101
+ # Claude: Look for last text block in assistant message
102
+ RESPONSE=$(grep -a '"type":"assistant"' /task-output/debug-stream.jsonl | tail -1 | jq -r '.message.content[] | select(.type=="text") | .text' 2>/dev/null | head -20 || echo "")
103
+ elif [[ "$AGENT_CMD" == *"gemini"* ]]; then
104
+ # Gemini: Extract last assistant message content (the actual response)
105
+ # Gemini format: {"type":"message","role":"assistant","content":"...","delta":true}
106
+ RESPONSE=$(grep -a '"type":"message"' /task-output/debug-stream.jsonl | grep -a '"role":"assistant"' | tail -1 | jq -r '.content' 2>/dev/null || echo "")
107
+ elif [[ "$AGENT_CMD" == *"codex"* ]]; then
108
+ # Codex: Look for last agent_message or reasoning item
109
+ RESPONSE=$(grep -a '"type":"item.completed"' /task-output/debug-stream.jsonl | tail -1 | jq -r 'select(.item.type=="agent_message") | .item.text' 2>/dev/null || \
110
+ grep -a '"type":"item.completed"' /task-output/debug-stream.jsonl | tail -1 | jq -r 'select(.item.type=="reasoning") | .item.text' 2>/dev/null | head -20 || echo "")
111
+ fi
112
+
113
+ # Clean up response (limit length for summary)
114
+ if [ -n "$RESPONSE" ] && [ "$RESPONSE" != "null" ]; then
115
+ RESULT_TEXT="$RESPONSE"
116
+ else
117
+ RESULT_TEXT="Task completed successfully"
118
+ fi
119
+ else
120
+ RESULT_TEXT="Task failed with exit code $EXIT_CODE"
121
+ fi
122
+
123
+ # Estimate tokens from debug stream size
124
+ if [ -f /task-output/debug-stream.jsonl ]; then
125
+ TOKEN_EST=$(wc -c < /task-output/debug-stream.jsonl | awk '{print int($1/4)}')
126
+ else
127
+ TOKEN_EST=0
128
+ fi
129
+
130
+ # Escape result text for JSON
131
+ ESCAPED_RESULT=$(echo "$RESULT_TEXT" | jq -Rs . 2>/dev/null || echo '"Task completed"')
132
+
133
+ # Output result JSON with actual agent response
134
+ # The server and UI will also read debug-stream.jsonl for detailed events
135
+ cat <<EOF
136
+ {"type":"result","result":$ESCAPED_RESULT,"duration_ms":${DURATION},"num_turns":1,"usage":{"output_tokens":${TOKEN_EST},"cache_read_input_tokens":0},"timestamp":"${TIMESTAMP}","exit_code":${EXIT_CODE}}
137
+ EOF
138
+
139
+ # Write exit code and finished time marker files for server monitoring
140
+ echo "$EXIT_CODE" > /task-output/.exit_code
141
+ date -Iseconds > /task-output/.finished_at
142
+
143
+ exit $EXIT_CODE
@@ -0,0 +1,357 @@
1
+ #!/bin/bash
2
+ # Apply Local Repository State
3
+ # Applies captured git state including local branches, staged/unstaged changes, untracked files
4
+
5
+ set -o pipefail
6
+
7
+ STATE_FILE="${LOCAL_STATE_FILE:-/task-output/local-state.json}"
8
+ WORKSPACE_DIR="${WORKSPACE_DIR:-/workspace}"
9
+
10
+ # Track warnings and errors
11
+ HAS_WARNINGS=false
12
+ WARNING_MESSAGES=()
13
+
14
+ # Logging functions
15
+ log() {
16
+ echo "[$(date +'%Y-%m-%d %H:%M:%S')] $*" >&2
17
+ }
18
+
19
+ log_error() {
20
+ echo "[$(date +'%Y-%m-%d %H:%M:%S')] ERROR: $*" >&2
21
+ HAS_WARNINGS=true
22
+ WARNING_MESSAGES+=("ERROR: $*")
23
+ }
24
+
25
+ log_warning() {
26
+ echo "[$(date +'%Y-%m-%d %H:%M:%S')] WARNING: $*" >&2
27
+ HAS_WARNINGS=true
28
+ WARNING_MESSAGES+=("WARNING: $*")
29
+ }
30
+
31
+ log_success() {
32
+ echo "[$(date +'%Y-%m-%d %H:%M:%S')] SUCCESS: $*" >&2
33
+ }
34
+
35
+ # Check if state file exists
36
+ if [ ! -f "$STATE_FILE" ]; then
37
+ log "No local state file found at $STATE_FILE, skipping state application"
38
+ exit 0
39
+ fi
40
+
41
+ log "════════════════════════════════════════════════════════════════"
42
+ log " Applying Local Repository State"
43
+ log "════════════════════════════════════════════════════════════════"
44
+ log ""
45
+
46
+ # Validate JSON
47
+ if ! jq empty "$STATE_FILE" 2>/dev/null; then
48
+ log_error "Invalid JSON in state file: $STATE_FILE"
49
+ exit 1
50
+ fi
51
+
52
+ # Show capture info
53
+ CAPTURED_AT=$(jq -r '.captured_at' "$STATE_FILE")
54
+ REPOS_FOUND=$(jq -r '.repos_found | join(", ")' "$STATE_FILE")
55
+ REPOS_MISSING=$(jq -r '.repos_missing | join(", ")' "$STATE_FILE")
56
+
57
+ log "📋 State Information:"
58
+ log " Captured at: $CAPTURED_AT"
59
+ log " Repositories: $REPOS_FOUND"
60
+ if [ -n "$REPOS_MISSING" ] && [ "$REPOS_MISSING" != "" ]; then
61
+ log " Missing (will use defaults): $REPOS_MISSING"
62
+ fi
63
+ log ""
64
+
65
+ # Process each repository
66
+ jq -r '.repositories | keys[]' "$STATE_FILE" | while read -r repo_name; do
67
+ # Look up the path for this repo from REPOS_CONFIG
68
+ repo_path_from_json=""
69
+ if [ -n "$REPOS_CONFIG" ]; then
70
+ repo_path_from_json=$(echo "$REPOS_CONFIG" | jq -r --arg name "$repo_name" '.[] | select(.name == $name) | .path')
71
+ fi
72
+
73
+ # Use path field from JSON if available, otherwise default to repo_name
74
+ if [ -n "$repo_path_from_json" ] && [ "$repo_path_from_json" != "null" ] && [ "$repo_path_from_json" != "" ]; then
75
+ REPO_PATH="$WORKSPACE_DIR/$repo_path_from_json"
76
+ else
77
+ REPO_PATH="$WORKSPACE_DIR/$repo_name"
78
+ fi
79
+
80
+ log "────────────────────────────────────────────────────────────────"
81
+ log "📁 Repository: $repo_name"
82
+ log " Path: $REPO_PATH"
83
+ log "────────────────────────────────────────────────────────────────"
84
+
85
+ # Check if repo exists in workspace
86
+ if [ ! -d "$REPO_PATH" ]; then
87
+ log_error "Repository not found in workspace: $REPO_PATH"
88
+ log " Skipping..."
89
+ log ""
90
+ continue
91
+ fi
92
+
93
+ if [ ! -d "$REPO_PATH/.git" ]; then
94
+ log_error "Not a git repository: $REPO_PATH"
95
+ log " Skipping..."
96
+ log ""
97
+ continue
98
+ fi
99
+
100
+ cd "$REPO_PATH" || {
101
+ log_error "Failed to cd into $REPO_PATH"
102
+ continue
103
+ }
104
+
105
+ # Extract repo state from JSON
106
+ CURRENT_BRANCH=$(jq -r ".repositories.\"$repo_name\".current_branch" "$STATE_FILE")
107
+ IS_REMOTE_TRACKING=$(jq -r ".repositories.\"$repo_name\".is_remote_tracking" "$STATE_FILE")
108
+ BASE_BRANCH=$(jq -r ".repositories.\"$repo_name\".base_branch" "$STATE_FILE")
109
+ COMMITS_AHEAD=$(jq -r ".repositories.\"$repo_name\".commits_ahead" "$STATE_FILE")
110
+
111
+ log " Current branch in container: $(git rev-parse --abbrev-ref HEAD)"
112
+ log " Target branch: $CURRENT_BRANCH"
113
+ log " Remote tracking: $IS_REMOTE_TRACKING"
114
+
115
+ # Step 1: Handle branch setup
116
+ if [ "$IS_REMOTE_TRACKING" = "true" ]; then
117
+ # Branch exists on remote, checkout first
118
+ log " Checking out remote-tracking branch: $CURRENT_BRANCH"
119
+
120
+ if git rev-parse --verify "origin/$CURRENT_BRANCH" >/dev/null 2>&1; then
121
+ git checkout "$CURRENT_BRANCH" 2>&1 | sed 's/^/ /' || {
122
+ log_error "Failed to checkout branch $CURRENT_BRANCH"
123
+ continue
124
+ }
125
+ else
126
+ log_error "Remote branch origin/$CURRENT_BRANCH not found"
127
+ log " Attempting to checkout local branch..."
128
+ git checkout "$CURRENT_BRANCH" 2>&1 | sed 's/^/ /' || {
129
+ log_error "Failed to checkout branch $CURRENT_BRANCH"
130
+ continue
131
+ }
132
+ fi
133
+
134
+ # Check for unpushed commits on remote-tracking branch
135
+ if [ -n "$COMMITS_AHEAD" ] && [ "$COMMITS_AHEAD" != "null" ] && [ "$COMMITS_AHEAD" -gt 0 ]; then
136
+ log " 📤 Unpushed commits detected: $COMMITS_AHEAD commit(s)"
137
+ COMMIT_PATCH=$(jq -r ".repositories.\"$repo_name\".commit_patch" "$STATE_FILE")
138
+
139
+ if [ -n "$COMMIT_PATCH" ] && [ "$COMMIT_PATCH" != "null" ]; then
140
+ log " Applying unpushed commits..."
141
+
142
+ # Save patch to temp file
143
+ TEMP_PATCH=$(mktemp)
144
+ echo "$COMMIT_PATCH" > "$TEMP_PATCH"
145
+
146
+ # Apply patches using git am
147
+ if git am --3way < "$TEMP_PATCH" 2>&1 | sed 's/^/ /'; then
148
+ log_success "Successfully applied unpushed commits"
149
+ else
150
+ log_error "Failed to apply commit patches, trying format-patch method..."
151
+ git am --abort 2>/dev/null || true
152
+
153
+ # Fallback: try applying as a single diff
154
+ if git apply < "$TEMP_PATCH" 2>&1 | sed 's/^/ /'; then
155
+ log_success "Applied changes as diff (commits not preserved)"
156
+ else
157
+ log_error "Failed to apply commit patches"
158
+ fi
159
+ fi
160
+
161
+ rm -f "$TEMP_PATCH"
162
+ fi
163
+ fi
164
+ else
165
+ # Local-only branch, need to recreate it
166
+ log " ⚠️ Local-only branch detected: $CURRENT_BRANCH"
167
+
168
+ if [ -n "$BASE_BRANCH" ] && [ "$BASE_BRANCH" != "null" ]; then
169
+ log " Base branch: $BASE_BRANCH"
170
+ log " Commits ahead: $COMMITS_AHEAD"
171
+
172
+ # Checkout base branch first
173
+ if git rev-parse --verify "$BASE_BRANCH" >/dev/null 2>&1; then
174
+ log " Checking out base branch: $BASE_BRANCH"
175
+ git checkout "$BASE_BRANCH" 2>&1 | sed 's/^/ /' || {
176
+ log_error "Failed to checkout base branch $BASE_BRANCH"
177
+ continue
178
+ }
179
+ else
180
+ log_error "Base branch $BASE_BRANCH not found"
181
+ continue
182
+ fi
183
+
184
+ # Create new branch
185
+ log " Creating local branch: $CURRENT_BRANCH"
186
+ git checkout -b "$CURRENT_BRANCH" 2>&1 | sed 's/^/ /' || {
187
+ # Branch might already exist
188
+ git checkout "$CURRENT_BRANCH" 2>&1 | sed 's/^/ /' || {
189
+ log_error "Failed to create/checkout branch $CURRENT_BRANCH"
190
+ continue
191
+ }
192
+ }
193
+
194
+ # Apply commit patches if available
195
+ if [ "$COMMITS_AHEAD" != "null" ] && [ "$COMMITS_AHEAD" -gt 0 ]; then
196
+ COMMIT_PATCH=$(jq -r ".repositories.\"$repo_name\".commit_patch" "$STATE_FILE")
197
+
198
+ if [ -n "$COMMIT_PATCH" ] && [ "$COMMIT_PATCH" != "null" ]; then
199
+ log " Applying $COMMITS_AHEAD commit(s)..."
200
+
201
+ # Save patch to temp file
202
+ TEMP_PATCH=$(mktemp)
203
+ echo "$COMMIT_PATCH" > "$TEMP_PATCH"
204
+
205
+ # Apply patches using git am
206
+ if git am --3way < "$TEMP_PATCH" 2>&1 | sed 's/^/ /'; then
207
+ log_success "Successfully applied commit patches"
208
+ else
209
+ log_error "Failed to apply commit patches, trying format-patch method..."
210
+ git am --abort 2>/dev/null || true
211
+
212
+ # Fallback: try applying as a single diff
213
+ if git apply < "$TEMP_PATCH" 2>&1 | sed 's/^/ /'; then
214
+ log_success "Applied changes as diff (commits not preserved)"
215
+ else
216
+ log_error "Failed to apply commit patches"
217
+ fi
218
+ fi
219
+
220
+ rm -f "$TEMP_PATCH"
221
+ fi
222
+ fi
223
+ else
224
+ log " No base branch info, checking out branch as-is..."
225
+ git checkout "$CURRENT_BRANCH" 2>&1 | sed 's/^/ /' || {
226
+ log_error "Failed to checkout branch $CURRENT_BRANCH"
227
+ continue
228
+ }
229
+ fi
230
+ fi
231
+
232
+ log_success "Branch setup complete"
233
+
234
+ # Step 2: Apply unstaged changes (working directory changes)
235
+ UNSTAGED=$(jq -r ".repositories.\"$repo_name\".unstaged" "$STATE_FILE")
236
+ if [ -n "$UNSTAGED" ] && [ "$UNSTAGED" != "null" ] && [ "$UNSTAGED" != "" ]; then
237
+ UNSTAGED_FILE_COUNT=$(echo "$UNSTAGED" | grep -c '^diff --git' || echo "0")
238
+ log " Applying unstaged changes ($UNSTAGED_FILE_COUNT files)..."
239
+
240
+ TEMP_DIFF=$(mktemp)
241
+ echo "$UNSTAGED" > "$TEMP_DIFF"
242
+
243
+ if git apply --verbose < "$TEMP_DIFF" 2>&1 | sed 's/^/ /'; then
244
+ log_success "Unstaged changes applied"
245
+ else
246
+ log_error "Failed to apply unstaged changes for $repo_name"
247
+ log_warning "$repo_name: Unstaged changes had conflicts - check git status in container"
248
+ log " Attempting with --reject to show conflicts..."
249
+ git apply --reject < "$TEMP_DIFF" 2>&1 | sed 's/^/ /' || true
250
+ fi
251
+
252
+ rm -f "$TEMP_DIFF"
253
+ fi
254
+
255
+ # Step 3: Apply staged changes (index)
256
+ STAGED=$(jq -r ".repositories.\"$repo_name\".staged" "$STATE_FILE")
257
+ if [ -n "$STAGED" ] && [ "$STAGED" != "null" ] && [ "$STAGED" != "" ]; then
258
+ STAGED_FILE_COUNT=$(echo "$STAGED" | grep -c '^diff --git' || echo "0")
259
+ log " Applying staged changes ($STAGED_FILE_COUNT files)..."
260
+
261
+ TEMP_STAGED=$(mktemp)
262
+ echo "$STAGED" > "$TEMP_STAGED"
263
+
264
+ if git apply --cached --verbose < "$TEMP_STAGED" 2>&1 | sed 's/^/ /'; then
265
+ log_success "Staged changes applied"
266
+ else
267
+ log_error "Failed to apply staged changes for $repo_name"
268
+ log_warning "$repo_name: Staged changes had conflicts - check git status in container"
269
+ log " Attempting with --reject to show conflicts..."
270
+ git apply --cached --reject < "$TEMP_STAGED" 2>&1 | sed 's/^/ /' || true
271
+ fi
272
+
273
+ rm -f "$TEMP_STAGED"
274
+ fi
275
+
276
+ # Step 4: Create untracked files
277
+ UNTRACKED_FILES=$(jq -r ".repositories.\"$repo_name\".untracked_files | keys[]" "$STATE_FILE" 2>/dev/null)
278
+ if [ -n "$UNTRACKED_FILES" ]; then
279
+ UNTRACKED_COUNT=$(echo "$UNTRACKED_FILES" | wc -l)
280
+ log " Creating $UNTRACKED_COUNT untracked file(s)..."
281
+
282
+ echo "$UNTRACKED_FILES" | while read -r file_path; do
283
+ if [ -z "$file_path" ]; then
284
+ continue
285
+ fi
286
+
287
+ # Get file content from JSON (properly escaped)
288
+ FILE_CONTENT=$(jq -r ".repositories.\"$repo_name\".untracked_files.\"$file_path\"" "$STATE_FILE")
289
+
290
+ if [ -n "$FILE_CONTENT" ] && [ "$FILE_CONTENT" != "null" ]; then
291
+ # Create parent directories if needed
292
+ mkdir -p "$(dirname "$file_path")"
293
+
294
+ # Write content to file
295
+ echo "$FILE_CONTENT" > "$file_path"
296
+ log " + $file_path"
297
+ fi
298
+ done
299
+
300
+ log_success "Untracked files created"
301
+ fi
302
+
303
+ # Step 5: Show final git status
304
+ log ""
305
+ log " 📊 Final repository state:"
306
+ git status --short 2>&1 | sed 's/^/ /' || true
307
+ log " Current branch: $(git rev-parse --abbrev-ref HEAD)"
308
+ log ""
309
+ log_success "State application complete for $repo_name"
310
+ log ""
311
+ done
312
+
313
+ log "════════════════════════════════════════════════════════════════"
314
+ log_success "Local state application complete!"
315
+ log "════════════════════════════════════════════════════════════════"
316
+ log ""
317
+
318
+ # Write summary for CLI to check
319
+ SUMMARY_FILE="/task-output/.local-state-summary"
320
+
321
+ if [ "$HAS_WARNINGS" = "true" ]; then
322
+ # Build warnings JSON array
323
+ WARNINGS_JSON="["
324
+ first=true
325
+ for msg in "${WARNING_MESSAGES[@]}"; do
326
+ if [ "$first" = "true" ]; then
327
+ first=false
328
+ else
329
+ WARNINGS_JSON+=","
330
+ fi
331
+ # Escape quotes in message
332
+ escaped_msg=$(echo "$msg" | sed 's/"/\\"/g')
333
+ WARNINGS_JSON+="\"$escaped_msg\""
334
+ done
335
+ WARNINGS_JSON+="]"
336
+
337
+ cat > "$SUMMARY_FILE" <<EOF
338
+ {
339
+ "status": "completed_with_warnings",
340
+ "applied_at": "$(date -Iseconds)",
341
+ "has_warnings": true,
342
+ "warnings": $WARNINGS_JSON,
343
+ "message": "Local state applied with ${#WARNING_MESSAGES[@]} warning(s) - check git status in container"
344
+ }
345
+ EOF
346
+ else
347
+ cat > "$SUMMARY_FILE" <<EOF
348
+ {
349
+ "status": "success",
350
+ "applied_at": "$(date -Iseconds)",
351
+ "has_warnings": false,
352
+ "message": "Local state applied successfully"
353
+ }
354
+ EOF
355
+ fi
356
+
357
+ exit 0