Skip to content

Commit 1767eb3

Browse files
committed
feat: Add session information environment variable (#42)
Added COPILOT_HERE_SESSION_INFO environment variable containing structured JSON with session details (version, image, mode, mounts, airlock status). Includes session-info helper command for easy viewing with fallback to Python json.tool. Resolves paths to absolute format for clarity. Co-authored-by: Gordon Beeming <me@gordonbeeming.com>
1 parent ae94d20 commit 1767eb3

File tree

8 files changed

+406
-4
lines changed

8 files changed

+406
-4
lines changed

README.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,38 @@ copilot_here --no-cleanup --no-pull "quick question about this code"
411411
copilot_here --model claude-sonnet-4.5 "explain this algorithm"
412412
```
413413

414+
### Accessing Session Information
415+
416+
Inside any copilot_here container, you can view detailed information about your session:
417+
418+
```bash
419+
# View formatted session info (image, mounts, mode, etc.)
420+
session-info
421+
422+
# Or manually format with Python (works in all images without rebuild)
423+
echo $COPILOT_HERE_SESSION_INFO | python3 -m json.tool
424+
425+
# View raw JSON
426+
echo $COPILOT_HERE_SESSION_INFO
427+
428+
# Query specific fields with jq
429+
echo $COPILOT_HERE_SESSION_INFO | jq .image.tag
430+
echo $COPILOT_HERE_SESSION_INFO | jq .mounts
431+
echo $COPILOT_HERE_SESSION_INFO | jq .airlock.network_config # If airlock enabled
432+
```
433+
434+
The `COPILOT_HERE_SESSION_INFO` environment variable contains:
435+
- **Version**: copilot_here build version
436+
- **Image**: Tag and full image name
437+
- **Mode**: "standard" or "yolo"
438+
- **Working Directory**: Container working directory path
439+
- **Mounts**: All mounted paths with their modes (ro/rw) and sources
440+
- **Airlock**: Network proxy status and configuration (if enabled)
441+
442+
This makes it easy for AI assistants to understand the environment without scrolling through startup logs.
443+
444+
> **Note:** The `session-info` command will be available in containers after the next image rebuild. Until then, use `echo $COPILOT_HERE_SESSION_INFO | python3 -m json.tool` for formatted output.
445+
414446
**YOLO Mode** (auto-approves execution):
415447

416448
```bash

app/Commands/Run/RunCommand.cs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -463,7 +463,7 @@ public void Configure(RootCommand root)
463463
// Build Docker args for standard mode
464464
var sessionId = GenerateSessionId();
465465
var containerName = $"copilot_here-{sessionId}";
466-
var dockerArgs = BuildDockerArgs(ctx, imageName, containerName, allMounts, copilotArgs);
466+
var dockerArgs = BuildDockerArgs(ctx, imageName, containerName, allMounts, copilotArgs, _isYolo, imageTag);
467467

468468
// Set terminal title
469469
var titleEmoji = _isYolo ? "🤖⚡️" : "🤖";
@@ -508,8 +508,13 @@ private static List<string> BuildDockerArgs(
508508
string imageName,
509509
string containerName,
510510
List<MountEntry> mounts,
511-
List<string> copilotArgs)
511+
List<string> copilotArgs,
512+
bool isYolo,
513+
string imageTag)
512514
{
515+
// Generate session info JSON
516+
var sessionInfo = SessionInfo.Generate(ctx, imageTag, imageName, mounts, isYolo);
517+
513518
var args = new List<string>
514519
{
515520
"run",
@@ -524,7 +529,8 @@ private static List<string> BuildDockerArgs(
524529
// Environment variables
525530
"-e", $"PUID={ctx.Environment.UserId}",
526531
"-e", $"PGID={ctx.Environment.GroupId}",
527-
"-e", $"GITHUB_TOKEN={ctx.Environment.GitHubToken}"
532+
"-e", $"GITHUB_TOKEN={ctx.Environment.GitHubToken}",
533+
"-e", $"COPILOT_HERE_SESSION_INFO={sessionInfo}"
528534
};
529535

530536
// Add additional mounts

app/Infrastructure/AirlockRunner.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,15 @@ private static void SetupLogsDirectory(AppPaths paths, string rulesPath)
301301
}
302302
copilotCmd.Append(']');
303303

304+
// Generate session info JSON for Airlock mode
305+
var sessionInfo = SessionInfo.GenerateWithNetworkConfig(
306+
ctx,
307+
appImage.Split(':')[1], // Extract tag from full image name
308+
appImage,
309+
mounts,
310+
isYolo,
311+
processedConfigPath);
312+
304313
// Do substitutions
305314
var result = template
306315
.Replace("{{NETWORKS}}", networksYaml)
@@ -314,6 +323,7 @@ private static void SetupLogsDirectory(AppPaths paths, string rulesPath)
314323
.Replace("{{NETWORK_CONFIG}}", processedConfigPath)
315324
.Replace("{{PUID}}", ctx.Environment.UserId.ToString())
316325
.Replace("{{PGID}}", ctx.Environment.GroupId.ToString())
326+
.Replace("{{SESSION_INFO}}", sessionInfo)
317327
.Replace("{{COPILOT_ARGS}}", copilotCmd.ToString());
318328

319329
// Handle multiline placeholders

app/Infrastructure/SessionInfo.cs

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
using System.Text;
2+
using System.Text.Json;
3+
using CopilotHere.Commands.Mounts;
4+
5+
namespace CopilotHere.Infrastructure;
6+
7+
/// <summary>
8+
/// Generates session information as JSON for use within the container.
9+
/// </summary>
10+
public static class SessionInfo
11+
{
12+
/// <summary>
13+
/// Generates session info JSON string for standard Docker run mode.
14+
/// </summary>
15+
public static string Generate(
16+
AppContext ctx,
17+
string imageTag,
18+
string imageName,
19+
List<MountEntry> mounts,
20+
bool isYolo,
21+
bool airlockEnabled = false,
22+
string? airlockRulesPath = null)
23+
{
24+
var json = new StringBuilder();
25+
json.Append('{');
26+
json.Append($"\"copilot_here_version\":\"{BuildInfo.BuildDate}\",");
27+
json.Append($"\"image\":{{\"tag\":\"{imageTag}\",\"full_name\":\"{imageName}\"}},");
28+
json.Append($"\"mode\":\"{(isYolo ? "yolo" : "standard")}\",");
29+
json.Append($"\"working_directory\":\"{JsonEncode(ctx.Paths.ContainerWorkDir)}\",");
30+
json.Append("\"mounts\":[");
31+
32+
for (int i = 0; i < mounts.Count; i++)
33+
{
34+
var m = mounts[i];
35+
if (i > 0) json.Append(',');
36+
json.Append('{');
37+
json.Append($"\"host_path\":\"{JsonEncode(m.ResolvePath(ctx.Paths.UserHome))}\",");
38+
json.Append($"\"container_path\":\"{JsonEncode(m.GetContainerPath(ctx.Paths.UserHome))}\",");
39+
json.Append($"\"mode\":\"{(m.IsReadWrite ? "rw" : "ro")}\",");
40+
json.Append($"\"source\":\"{m.Source.ToString().ToLowerInvariant()}\"");
41+
json.Append('}');
42+
}
43+
44+
json.Append("],");
45+
json.Append($"\"airlock\":{{\"enabled\":{(airlockEnabled ? "true" : "false")},");
46+
json.Append($"\"rules_path\":\"{JsonEncode(airlockRulesPath ?? "")}\",");
47+
json.Append($"\"source\":\"{ctx.AirlockConfig.EnabledSource.ToString().ToLowerInvariant()}\"}}");
48+
json.Append('}');
49+
50+
return json.ToString();
51+
}
52+
53+
/// <summary>
54+
/// Generates extended session info JSON for Airlock mode with network config details.
55+
/// </summary>
56+
public static string GenerateWithNetworkConfig(
57+
AppContext ctx,
58+
string imageTag,
59+
string imageName,
60+
List<MountEntry> mounts,
61+
bool isYolo,
62+
string airlockRulesPath)
63+
{
64+
var baseInfo = Generate(ctx, imageTag, imageName, mounts, isYolo, true, airlockRulesPath);
65+
66+
// Add network config details if rules file exists
67+
if (!File.Exists(airlockRulesPath))
68+
return baseInfo;
69+
70+
try
71+
{
72+
var rulesContent = File.ReadAllText(airlockRulesPath);
73+
using var doc = JsonDocument.Parse(rulesContent);
74+
var root = doc.RootElement;
75+
76+
var networkConfig = new StringBuilder();
77+
networkConfig.Append("\"network_config\":{");
78+
79+
var hasAnyField = false;
80+
81+
// Extract mode
82+
if (root.TryGetProperty("mode", out var modeElement))
83+
{
84+
networkConfig.Append($"\"mode\":\"{JsonEncode(modeElement.GetString() ?? "enforce")}\"");
85+
hasAnyField = true;
86+
}
87+
88+
// Extract logging
89+
if (root.TryGetProperty("enable_logging", out var loggingElement))
90+
{
91+
if (hasAnyField) networkConfig.Append(',');
92+
networkConfig.Append($"\"logging_enabled\":{(loggingElement.GetBoolean() ? "true" : "false")}");
93+
hasAnyField = true;
94+
}
95+
96+
// Count rules and extract sample domains
97+
if (root.TryGetProperty("rules", out var rulesElement) && rulesElement.ValueKind == JsonValueKind.Array)
98+
{
99+
var ruleCount = rulesElement.GetArrayLength();
100+
if (hasAnyField) networkConfig.Append(',');
101+
networkConfig.Append($"\"rules_count\":{ruleCount}");
102+
103+
// Extract first few domains
104+
var domains = new List<string>();
105+
foreach (var rule in rulesElement.EnumerateArray().Take(5))
106+
{
107+
if (rule.TryGetProperty("pattern", out var pattern))
108+
{
109+
var patternStr = pattern.GetString();
110+
if (!string.IsNullOrEmpty(patternStr))
111+
domains.Add(patternStr);
112+
}
113+
}
114+
115+
if (domains.Count > 0)
116+
{
117+
networkConfig.Append(",\"sample_domains\":[");
118+
for (int i = 0; i < domains.Count; i++)
119+
{
120+
if (i > 0) networkConfig.Append(',');
121+
networkConfig.Append($"\"{JsonEncode(domains[i])}\"");
122+
}
123+
networkConfig.Append(']');
124+
}
125+
}
126+
127+
networkConfig.Append('}');
128+
129+
// Insert network_config into airlock object
130+
// Remove the closing brace of airlock and root, add network_config, then close
131+
var insertPoint = baseInfo.LastIndexOf("}}", StringComparison.Ordinal);
132+
if (insertPoint > 0)
133+
{
134+
return baseInfo[..insertPoint] + "," + networkConfig + "}}";
135+
}
136+
}
137+
catch
138+
{
139+
// Non-fatal - return base info without network config
140+
}
141+
142+
return baseInfo;
143+
}
144+
145+
/// <summary>
146+
/// JSON-encodes a string for safe inclusion in JSON.
147+
/// </summary>
148+
private static string JsonEncode(string value)
149+
{
150+
if (string.IsNullOrEmpty(value))
151+
return value;
152+
153+
return value
154+
.Replace("\\", "\\\\")
155+
.Replace("\"", "\\\"")
156+
.Replace("\n", "\\n")
157+
.Replace("\r", "\\r")
158+
.Replace("\t", "\\t");
159+
}
160+
}

app/Resources/docker-compose.airlock.yml.template

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ services:
4545
- PGID={{PGID}}
4646
- GITHUB_TOKEN=${GITHUB_TOKEN}
4747
- TERM=${TERM:-xterm-256color}
48+
- COPILOT_HERE_SESSION_INFO={{SESSION_INFO}}
4849
volumes:
4950
- proxy-ca:/ca:ro
5051
- {{COPILOT_CONFIG}}:/home/appuser/.copilot

docker/Dockerfile.base

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,8 @@ WORKDIR /work
4949
# Copy the entrypoint script into the container and make it executable.
5050
COPY entrypoint.sh /usr/local/bin/
5151
COPY entrypoint-airlock.sh /usr/local/bin/
52-
RUN chmod +x /usr/local/bin/entrypoint.sh /usr/local/bin/entrypoint-airlock.sh
52+
COPY docker/session-info.sh /usr/local/bin/session-info
53+
RUN chmod +x /usr/local/bin/entrypoint.sh /usr/local/bin/entrypoint-airlock.sh /usr/local/bin/session-info
5354

5455
# The entrypoint script will handle user creation and command execution.
5556
ENTRYPOINT [ "entrypoint.sh" ]

docker/session-info.sh

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
#!/bin/bash
2+
# Helper script to display copilot_here session information
3+
# This script is installed in the Docker image and can be run as: session-info
4+
5+
if [ -z "$COPILOT_HERE_SESSION_INFO" ]; then
6+
echo "⚠️ No session information available"
7+
echo "This environment variable is only set when running in copilot_here containers"
8+
exit 1
9+
fi
10+
11+
# Check if jq is available for pretty printing
12+
if command -v jq >/dev/null 2>&1; then
13+
echo "$COPILOT_HERE_SESSION_INFO" | jq .
14+
else
15+
# Fallback: Use Python for pretty-printing (Node.js containers have Python)
16+
if command -v python3 >/dev/null 2>&1; then
17+
echo "$COPILOT_HERE_SESSION_INFO" | python3 -m json.tool
18+
elif command -v python >/dev/null 2>&1; then
19+
echo "$COPILOT_HERE_SESSION_INFO" | python -m json.tool
20+
else
21+
# Last resort: plain JSON with manual formatting attempt
22+
echo "Session Information (install jq for better formatting):"
23+
echo "$COPILOT_HERE_SESSION_INFO" | sed 's/,/,\n/g'
24+
fi
25+
fi

0 commit comments

Comments
 (0)