Repository navigation
Modernize and ship Feed entry points #8174
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
8b5baf0
684aef9
d6f5586
063015c
e70b5f8
dfe9628
6a0dd52
047d237
ad4d6db
e28efdd
2de82a6
e9da3ea
bb2d623
b6e9143
4c4bdec
dec2d93
f40923e
6574773
611e3b9
d3fa8c4
832dd8d
75d2c3b
f989061
39348c5
3c85d48
73e0dfa
028f4cf
2ae5360
50a1c2b
f36af54
63f3705
9083f19
3b1869c
8738372
e14b8ab
c30be07
7301411
a698bfe
956a371
bf414a8
e2b97cc
a9864fa
510b4ba
58a947f
54d808b
a3696c4
6137e4a
9058686
62c014b
7e79a5f
911c868
5513a89
61afec9
898c251
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -79,3 +79,131 @@ public struct WorkstreamItem: Identifiable, Codable, Sendable, Equatable { | |
| self.ppid = ppid | ||
| } | ||
| } | ||
|
|
||
| extension WorkstreamItem { | ||
| /// Returns a copy whose variable-sized fields have deterministic limits. | ||
| /// This bounds both the in-memory ring and the pending-item disk snapshot. | ||
| func retainedForFeed() -> WorkstreamItem { | ||
| WorkstreamItem( | ||
| id: id, | ||
| workstreamId: workstreamId.feedPrefix(512), | ||
| source: source, | ||
| kind: kind, | ||
| createdAt: createdAt, | ||
| updatedAt: updatedAt, | ||
| cwd: cwd?.feedPrefix(4_096), | ||
| title: title?.feedPrefix(1_024), | ||
| status: status.retainedForFeed(), | ||
| payload: payload.retainedForFeed(), | ||
| context: context?.retainedForFeed(), | ||
| ppid: ppid | ||
| ) | ||
| } | ||
| } | ||
|
|
||
| private extension WorkstreamStatus { | ||
| func retainedForFeed() -> WorkstreamStatus { | ||
| switch self { | ||
| case .resolved(.question(let selections), let date): | ||
| return .resolved( | ||
| .question(selections: selections.prefix(20).map { $0.feedPrefix(1_024) }), | ||
| at: date | ||
| ) | ||
| case .resolved(.exitPlan(let mode, let feedback), let date): | ||
| return .resolved(.exitPlan(mode, feedback: feedback?.feedPrefix(8_192)), at: date) | ||
| default: | ||
| return self | ||
| } | ||
| } | ||
| } | ||
|
|
||
| private extension WorkstreamPayload { | ||
| func retainedForFeed() -> WorkstreamPayload { | ||
| switch self { | ||
| case .permissionRequest(let requestId, let toolName, let toolInputJSON, let pattern): | ||
| return .permissionRequest( | ||
| requestId: requestId.feedPrefix(512), | ||
| toolName: toolName.feedPrefix(512), | ||
| toolInputJSON: toolInputJSON.feedPrefix(32_768), | ||
| pattern: pattern?.feedPrefix(4_096) | ||
|
Comment on lines
+123
to
+128
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift Redact JSON before any destructive truncation. An over-limit JSON payload becomes invalid before persistence redaction. The redactor then falls back to environment-assignment matching, so JSON secrets such as
📍 Affects 2 files
🤖 Prompt for AI Agents |
||
| ) | ||
| case .exitPlan(let requestId, let plan, let defaultMode): | ||
| return .exitPlan( | ||
| requestId: requestId.feedPrefix(512), | ||
| plan: plan.feedPrefix(65_536), | ||
| defaultMode: defaultMode | ||
| ) | ||
| case .question(let requestId, let questions): | ||
| return .question( | ||
| requestId: requestId.feedPrefix(512), | ||
| questions: questions.prefix(4).map { question in | ||
| WorkstreamQuestionPrompt( | ||
| id: question.id.feedPrefix(512), | ||
| header: question.header?.feedPrefix(1_024), | ||
| prompt: question.prompt.feedPrefix(8_192), | ||
| multiSelect: question.multiSelect, | ||
| options: question.options.prefix(12).map { option in | ||
| WorkstreamQuestionOption( | ||
| id: option.id.feedPrefix(512), | ||
| label: option.label.feedPrefix(1_024), | ||
| description: option.description?.feedPrefix(2_048) | ||
| ) | ||
| } | ||
| ) | ||
| } | ||
| ) | ||
| case .toolUse(let toolName, let toolInputJSON): | ||
| return .toolUse( | ||
| toolName: toolName.feedPrefix(512), | ||
| toolInputJSON: toolInputJSON.feedPrefix(32_768) | ||
| ) | ||
| case .toolResult(let toolName, let resultJSON, let isError): | ||
| return .toolResult( | ||
| toolName: toolName.feedPrefix(512), | ||
| resultJSON: resultJSON.feedPrefix(32_768), | ||
| isError: isError | ||
| ) | ||
| case .userPrompt(let text): | ||
| return .userPrompt(text: text.feedPrefix(16_384)) | ||
| case .assistantMessage(let text): | ||
| return .assistantMessage(text: text.feedPrefix(16_384)) | ||
| case .stop(let reason): | ||
| return .stop(reason: reason?.feedPrefix(4_096)) | ||
| case .todos(let todos): | ||
| return .todos(todos.prefix(100).map { todo in | ||
| WorkstreamTaskTodo( | ||
| id: todo.id.feedPrefix(512), | ||
| content: todo.content.feedPrefix(2_048), | ||
| state: todo.state | ||
| ) | ||
| }) | ||
| case .sessionStart, .sessionEnd: | ||
| return self | ||
| } | ||
| } | ||
| } | ||
|
|
||
| extension WorkstreamContext { | ||
| func retainedForFeed() -> WorkstreamContext { | ||
| WorkstreamContext( | ||
| lastUserMessage: lastUserMessage?.feedPrefix(16_384), | ||
| assistantPreamble: assistantPreamble?.feedPrefix(16_384), | ||
| planSummary: planSummary?.feedPrefix(8_192), | ||
| allowedPrompts: allowedPrompts.prefix(20).map { prompt in | ||
| WorkstreamAllowedPrompt( | ||
| tool: prompt.tool.feedPrefix(512), | ||
| prompt: prompt.prompt.feedPrefix(2_048) | ||
| ) | ||
| }, | ||
| toolSummary: toolSummary?.feedPrefix(8_192), | ||
| permissionMode: permissionMode?.feedPrefix(256) | ||
| ) | ||
| } | ||
| } | ||
|
|
||
| private extension String { | ||
| func feedPrefix(_ maximumCharacters: Int) -> String { | ||
| guard count > maximumCharacters else { return self } | ||
| return String(prefix(maximumCharacters)) | ||
| } | ||
|
Comment on lines
+204
to
+208
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win Bound UTF-8 bytes without scanning the full string.
As per coding guidelines, production hot paths must use bounded construction rather than full-input scans. 🤖 Prompt for AI AgentsSource: Coding guidelines |
||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Do not truncate protocol identifiers.
This rewrites session, request, question, option, and todo IDs, creating collisions and breaking exact reply routing. It also mismatches the context cache: lookup/removal uses raw
event.sessionId, while insertion uses the truncatedworkstreamId.Preserve identifiers exactly, or reject over-limit events at the transport boundary.
As per path instructions, correctness-critical identity must use one authoritative structured source and fail closed rather than be rewritten.
Also applies to: 125-125, 132-132, 138-148, 175-175
🤖 Prompt for AI Agents
Source: Path instructions